diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 675333bcc4..87a9833774 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -44,7 +44,7 @@ import ( const ( checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory - inmemorySignatures = 1024 // Number of recent blocks to keep in memory + inmemorySignatures = 4096 // Number of recent block signatures to keep in memory wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers ) @@ -162,7 +162,12 @@ func sigHash(header *types.Header) (hash common.Hash) { } // ecrecover extracts the Ethereum account address from a signed header. -func ecrecover(header *types.Header) (common.Address, error) { +func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { + // If the signature's already cached, return that + hash := header.Hash() + if address, known := sigcache.Get(hash); known { + return address.(common.Address), nil + } // Retrieve the signature from the header extra-data if len(header.Extra) < extraSeal { return common.Address{}, errMissingSignature @@ -177,6 +182,7 @@ func ecrecover(header *types.Header) (common.Address, error) { var signer common.Address copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + sigcache.Add(hash, signer) return signer, nil } @@ -223,7 +229,7 @@ func New(config *params.CliqueConfig, db ethdb.Database) *Clique { // Author implements consensus.Engine, returning the Ethereum address recovered // from the signature in the header's extra-data section. func (c *Clique) Author(header *types.Header) (common.Address, error) { - return ecrecover(header) + return ecrecover(header, c.signatures) } // VerifyHeader checks whether a header conforms to the consensus rules. @@ -369,7 +375,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo } // If an on-disk checkpoint snapshot can be found, use that if number%checkpointInterval == 0 { - if s, err := loadSnapshot(c.config, c.db, hash); err == nil { + if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash) snap = s break @@ -385,7 +391,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo for i := 0; i < len(signers); i++ { copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:]) } - snap = newSnapshot(c.config, 0, genesis.Hash(), signers) + snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers) if err := snap.store(c.db); err != nil { return nil, err } @@ -464,7 +470,7 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p c.recents.Add(snap.Hash, snap) // Resolve the authorization key and check against signers - signer, err := ecrecover(header) + signer, err := ecrecover(header, c.signatures) if err != nil { return err } diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go index 46b32ca5f2..fb86bc5e69 100644 --- a/consensus/clique/snapshot.go +++ b/consensus/clique/snapshot.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" + lru "github.com/hashicorp/golang-lru" ) // Vote represents a single vote that an authorized signer made to modify the @@ -44,7 +45,8 @@ type Tally struct { // Snapshot is the state of the authorization voting at a given point in time. type Snapshot struct { - config *params.CliqueConfig // Consensus engine parameters to fine tune behavior + config *params.CliqueConfig // Consensus engine parameters to fine tune behavior + sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover Number uint64 `json:"number"` // Block number where the snapshot was created Hash common.Hash `json:"hash"` // Block hash where the snapshot was created @@ -57,14 +59,15 @@ type Snapshot struct { // newSnapshot create a new snapshot with the specified startup parameters. This // method does not initialize the set of recent signers, so only ever use if for // the genesis block. -func newSnapshot(config *params.CliqueConfig, number uint64, hash common.Hash, signers []common.Address) *Snapshot { +func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot { snap := &Snapshot{ - config: config, - Number: number, - Hash: hash, - Signers: make(map[common.Address]struct{}), - Recents: make(map[uint64]common.Address), - Tally: make(map[common.Address]Tally), + config: config, + sigcache: sigcache, + Number: number, + Hash: hash, + Signers: make(map[common.Address]struct{}), + Recents: make(map[uint64]common.Address), + Tally: make(map[common.Address]Tally), } for _, signer := range signers { snap.Signers[signer] = struct{}{} @@ -73,7 +76,7 @@ func newSnapshot(config *params.CliqueConfig, number uint64, hash common.Hash, s } // loadSnapshot loads an existing snapshot from the database. -func loadSnapshot(config *params.CliqueConfig, db ethdb.Database, hash common.Hash) (*Snapshot, error) { +func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) { blob, err := db.Get(append([]byte("clique-"), hash[:]...)) if err != nil { return nil, err @@ -83,6 +86,7 @@ func loadSnapshot(config *params.CliqueConfig, db ethdb.Database, hash common.Ha return nil, err } snap.config = config + snap.sigcache = sigcache return snap, nil } @@ -99,13 +103,14 @@ func (s *Snapshot) store(db ethdb.Database) error { // copy creates a deep copy of the snapshot, though not the individual votes. func (s *Snapshot) copy() *Snapshot { cpy := &Snapshot{ - config: s.config, - Number: s.Number, - Hash: s.Hash, - Signers: make(map[common.Address]struct{}), - Recents: make(map[uint64]common.Address), - Votes: make([]*Vote, len(s.Votes)), - Tally: make(map[common.Address]Tally), + config: s.config, + sigcache: s.sigcache, + Number: s.Number, + Hash: s.Hash, + Signers: make(map[common.Address]struct{}), + Recents: make(map[uint64]common.Address), + Votes: make([]*Vote, len(s.Votes)), + Tally: make(map[common.Address]Tally), } for signer := range s.Signers { cpy.Signers[signer] = struct{}{} @@ -190,7 +195,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { delete(snap.Recents, number-limit) } // Resolve the authorization key and check against signers - signer, err := ecrecover(header) + signer, err := ecrecover(header, s.sigcache) if err != nil { return nil, err }