From 342312acab553e6a62d21c57b7a73514f08e9542 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Fri, 21 Oct 2016 15:00:20 +0100 Subject: [PATCH 01/16] trie: Refactor trie implementation to use an interface --- cmd/geth/chaincmd.go | 44 +++++++++++++++++ core/blockchain.go | 2 +- core/state/iterator.go | 4 +- core/state/state_object.go | 5 +- core/state/statedb.go | 11 +++-- core/types/derive_sha.go | 2 +- eth/downloader/downloader_test.go | 2 +- light/state_test.go | 2 +- light/trie.go | 33 ++++++++----- trie/iterator.go | 8 ++-- trie/proof.go | 2 +- trie/proof_test.go | 6 +-- trie/secure_trie.go | 46 +++++++++--------- trie/secure_trie_test.go | 8 ++-- trie/sync_test.go | 14 +++--- trie/trie.go | 78 +++++++++++++++++++------------ trie/trie_test.go | 55 +++++++++++----------- 17 files changed, 199 insertions(+), 123 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index c1bbbd8dcf..20e718a739 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/syndtr/goleveldb/leveldb/util" "gopkg.in/urfave/cli.v1" @@ -271,6 +272,49 @@ func dump(ctx *cli.Context) error { return nil } +var accountCacheKeyPrefix = []byte("accounthashcache:") + +type cachedAccount struct { + state.Account + BlockNum uint64 + BlockHash common.Hash +} + +func buildCache(ctx *cli.Context) error { + stack := makeFullNode(ctx) + chain, chainDb := utils.MakeChain(ctx, stack) + defer chainDb.Close() + + num, _ := strconv.Atoi(ctx.Args()[0]) + blockNum := uint64(num) + block := chain.GetBlockByNumber(blockNum) + blockHash := block.Hash() + t, err := trie.New(block.Root(), chainDb, 0) + if err != nil { + utils.Fatalf("Could not open trie: %v", err) + } + st := trie.NewSecure(t, chainDb) + iter := st.Iterator() + i := 0 + for iter.Next() { + var data state.Account + if err := rlp.DecodeBytes(iter.Value, &data); err != nil { + utils.Fatalf("can't decode object at %x: %v", iter.Key[:], err) + } + + enc, _ := rlp.EncodeToBytes(cachedAccount{data, blockNum, blockHash}) + if err := chainDb.Put(append(accountCacheKeyPrefix, iter.Key[:]...), enc); err != nil { + utils.Fatalf("Could not write to DB: %v", err) + } + + i += 1 + if i % 10000 == 0 { + fmt.Printf("Processed %d accounts, at %v\n", i, common.ToHex(iter.Key)) + } + } + return nil +} + // hashish returns true for strings that look like hashes. func hashish(x string) bool { _, err := strconv.Atoi(x) diff --git a/core/blockchain.go b/core/blockchain.go index d806c143d5..c5b304ff35 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -269,7 +269,7 @@ func (self *BlockChain) FastSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x…]", hash[:4]) } - if _, err := trie.NewSecure(block.Root(), self.chainDb, 0); err != nil { + if _, err := trie.New(block.Root(), self.chainDb, 0); err != nil { return err } // If all checks out, manually set the head block diff --git a/core/state/iterator.go b/core/state/iterator.go index 14265b277a..f48aab5583 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -115,11 +115,11 @@ func (it *NodeIterator) step() error { if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob), &account); err != nil { return err } - dataTrie, err := trie.New(account.Root, it.state.db) + dataTrie, err := trie.New(account.Root, it.state.db, 0) if err != nil { return err } - it.dataIt = trie.NewNodeIterator(dataTrie) + it.dataIt = dataTrie.NodeIterator() if !it.dataIt.Next() { it.dataIt = nil } diff --git a/core/state/state_object.go b/core/state/state_object.go index edb073173d..e770aac254 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -137,11 +137,12 @@ func (self *StateObject) markSuicided() { func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie { if c.trie == nil { var err error - c.trie, err = trie.NewSecure(c.data.Root, db, 0) + t, err := trie.New(c.data.Root, db, 0) if err != nil { - c.trie, _ = trie.NewSecure(common.Hash{}, db, 0) + t, _ = trie.New(common.Hash{}, nil, 0) c.setError(fmt.Errorf("can't create storage trie: %v", err)) } + c.trie = trie.NewSecure(t, db) } return c.trie } diff --git a/core/state/statedb.go b/core/state/statedb.go index ae106e03b5..394df93904 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -89,14 +89,15 @@ type StateDB struct { // Create a new state from a given trie func New(root common.Hash, db ethdb.Database) (*StateDB, error) { - tr, err := trie.NewSecure(root, db, MaxTrieCacheGen) + tr, err := trie.New(root, db, MaxTrieCacheGen) if err != nil { return nil, err } + st := trie.NewSecure(tr, db) csc, _ := lru.New(codeSizeCacheSize) return &StateDB{ db: db, - trie: tr, + trie: st, codeSizeCache: csc, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), @@ -158,7 +159,11 @@ func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) { return &tr, nil } } - return trie.NewSecure(root, self.db, MaxTrieCacheGen) + t, err := trie.New(root, self.db, MaxTrieCacheGen) + if err != nil { + return nil, err + } + return trie.NewSecure(t, self.db), nil } func (self *StateDB) pushTrie(t *trie.SecureTrie) { diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go index 00c42c5bc6..dd573425fe 100644 --- a/core/types/derive_sha.go +++ b/core/types/derive_sha.go @@ -31,7 +31,7 @@ type DerivableList interface { func DeriveSha(list DerivableList) common.Hash { keybuf := new(bytes.Buffer) - trie := new(trie.Trie) + trie, _ := trie.New(common.Hash{}, nil, 0) for i := 0; i < list.Len(); i++ { keybuf.Reset() rlp.Encode(keybuf, uint(i)) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 366c248bbe..11c4fd7310 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -286,7 +286,7 @@ func (dl *downloadTester) headFastBlock() *types.Block { func (dl *downloadTester) commitHeadBlock(hash common.Hash) error { // For now only check that the state trie is correct if block := dl.getBlock(hash); block != nil { - _, err := trie.NewSecure(block.Root(), dl.stateDb, 0) + _, err := trie.New(block.Root(), dl.stateDb, 0) return err } return fmt.Errorf("non existent block: %x", hash[:4]) diff --git a/light/state_test.go b/light/state_test.go index a6b1157868..37e5191a67 100644 --- a/light/state_test.go +++ b/light/state_test.go @@ -40,7 +40,7 @@ func (odr *testOdr) Database() ethdb.Database { func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { switch req := req.(type) { case *TrieRequest: - t, _ := trie.New(req.root, odr.sdb) + t, _ := trie.New(req.root, odr.sdb, 0) req.proof = t.Prove(req.key) case *NodeDataRequest: req.data, _ = odr.sdb.Get(req.hash[:]) diff --git a/light/trie.go b/light/trie.go index 42a943d50c..3436a37bdd 100644 --- a/light/trie.go +++ b/light/trie.go @@ -74,15 +74,26 @@ func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error) return err } +func (t *LightTrie) getTrie() (ret *trie.SecureTrie, err error) { + if t.trie == nil { + var tr trie.Trie + tr, err = trie.New(t.originalRoot, t.db, 0) + if err != nil { + return nil, err + } + t.trie = trie.NewSecure(tr, t.db) + } + return t.trie, nil +} + // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) { err = t.do(ctx, key, func() (err error) { - if t.trie == nil { - t.trie, err = trie.NewSecure(t.originalRoot, t.db, 0) - } + var tr *trie.SecureTrie + tr, err = t.getTrie() if err == nil { - res, err = t.trie.TryGet(key) + res, err = tr.TryGet(key) } return }) @@ -97,11 +108,10 @@ func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) // stored in the trie. func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { err = t.do(ctx, key, func() (err error) { - if t.trie == nil { - t.trie, err = trie.NewSecure(t.originalRoot, t.db, 0) - } + var tr *trie.SecureTrie + tr, err = t.getTrie() if err == nil { - err = t.trie.TryUpdate(key, value) + err = tr.TryUpdate(key, value) } return }) @@ -111,11 +121,10 @@ func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { // Delete removes any existing value for key from the trie. func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) { err = t.do(ctx, key, func() (err error) { - if t.trie == nil { - t.trie, err = trie.NewSecure(t.originalRoot, t.db, 0) - } + var tr *trie.SecureTrie + tr, err = t.getTrie() if err == nil { - err = t.trie.TryDelete(key) + err = tr.TryDelete(key) } return }) diff --git a/trie/iterator.go b/trie/iterator.go index afde6e19e6..750bbab12c 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -20,7 +20,7 @@ import "github.com/ethereum/go-ethereum/common" // Iterator is a key-value trie iterator that traverses a Trie. type Iterator struct { - trie *Trie + trie *SimpleTrie nodeIt *NodeIterator keyBuf []byte @@ -29,7 +29,7 @@ type Iterator struct { } // NewIterator creates a new key-value iterator. -func NewIterator(trie *Trie) *Iterator { +func NewIterator(trie *SimpleTrie) *Iterator { return &Iterator{ trie: trie, nodeIt: NewNodeIterator(trie), @@ -82,7 +82,7 @@ type nodeIteratorState struct { // NodeIterator is an iterator to traverse the trie post-order. type NodeIterator struct { - trie *Trie // Trie being iterated + trie *SimpleTrie // Trie being iterated stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state Hash common.Hash // Hash of the current node being iterated (nil if not standalone) @@ -95,7 +95,7 @@ type NodeIterator struct { } // NewNodeIterator creates an post-order trie iterator. -func NewNodeIterator(trie *Trie) *NodeIterator { +func NewNodeIterator(trie *SimpleTrie) *NodeIterator { if trie.Hash() == emptyState { return new(NodeIterator) } diff --git a/trie/proof.go b/trie/proof.go index bea5e5c098..bf796a95cd 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -37,7 +37,7 @@ import ( // contains all nodes of the longest existing prefix of the key // (at least the root node), ending with the node that proves the // absence of the key. -func (t *Trie) Prove(key []byte) []rlp.RawValue { +func (t *SimpleTrie) Prove(key []byte) []rlp.RawValue { // Collect all nodes on the path to key. key = compactHexDecode(key) nodes := []node{} diff --git a/trie/proof_test.go b/trie/proof_test.go index 91ebcd4a57..eddbafe00a 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -50,7 +50,7 @@ func TestProof(t *testing.T) { } func TestOneElementProof(t *testing.T) { - trie := new(Trie) + trie, _ := New(common.Hash{}, nil, 0) updateString(trie, "k", "v") proof := trie.Prove([]byte("k")) if proof == nil { @@ -129,8 +129,8 @@ func BenchmarkVerifyProof(b *testing.B) { } } -func randomTrie(n int) (*Trie, map[string]*kv) { - trie := new(Trie) +func randomTrie(n int) (*SimpleTrie, map[string]*kv) { + trie, _ := New(common.Hash{}, nil, 0) vals := make(map[string]*kv) for i := byte(0); i < 100; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 4d9ebe4d37..00160fd62c 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -38,32 +38,22 @@ const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash // SecureTrie is not safe for concurrent use. type SecureTrie struct { trie Trie + db Database hashKeyBuf [secureKeyLength]byte secKeyBuf [200]byte secKeyCache map[string][]byte secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch } -// NewSecure creates a trie with an existing root node from db. -// -// If root is the zero hash or the sha3 hash of an empty string, the -// trie is initially empty. Otherwise, New will panic if db is nil -// and returns MissingNodeError if the root node cannot be found. -// -// Accessing the trie loads nodes from db on demand. -// Loaded nodes are kept around until their 'cache generation' expires. -// A new cache generation is created by each call to Commit. -// cachelimit sets the number of past cache generations to keep. -func NewSecure(root common.Hash, db Database, cachelimit uint16) (*SecureTrie, error) { +// NewSecure creates a secure trie from an existing trie. +func NewSecure(t Trie, db Database) *SecureTrie { + if t == nil { + panic("NewSecure called with nil trie") + } if db == nil { panic("NewSecure called with nil database") } - trie, err := New(root, db) - if err != nil { - return nil, err - } - trie.SetCacheLimit(cachelimit) - return &SecureTrie{trie: *trie}, nil + return &SecureTrie{trie: t, db: db} } // Get returns the value for key stored in the trie. @@ -134,7 +124,7 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { return key } - key, _ := t.trie.db.Get(t.secKey(shaKey)) + key, _ := t.db.Get(t.secKey(shaKey)) return key } @@ -144,7 +134,10 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { // Committing flushes nodes from memory. Subsequent Get calls will load nodes // from the database. func (t *SecureTrie) Commit() (root common.Hash, err error) { - return t.CommitTo(t.trie.db) + if err := t.CommitPreimages(); err != nil { + return common.Hash{}, err + } + return t.trie.Commit() } func (t *SecureTrie) Hash() common.Hash { @@ -160,7 +153,7 @@ func (t *SecureTrie) Iterator() *Iterator { } func (t *SecureTrie) NodeIterator() *NodeIterator { - return NewNodeIterator(&t.trie) + return t.trie.NodeIterator() } // CommitTo writes all nodes and the secure hash pre-images to the given database. @@ -170,15 +163,22 @@ func (t *SecureTrie) NodeIterator() *NodeIterator { // the trie's database. Calling code must ensure that the changes made to db are // written back to the trie's attached database before using the trie. func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { + if err := t.CommitPreimages(); err != nil { + return common.Hash{}, err + } + return t.trie.CommitTo(db) +} + +func (t *SecureTrie) CommitPreimages() error { if len(t.getSecKeyCache()) > 0 { for hk, key := range t.secKeyCache { - if err := db.Put(t.secKey([]byte(hk)), key); err != nil { - return common.Hash{}, err + if err := t.db.Put(t.secKey([]byte(hk)), key); err != nil { + return err } } t.secKeyCache = make(map[string][]byte) } - return t.trie.CommitTo(db) + return nil } // secKey returns the database key for the preimage of key, as an ephemeral buffer. diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index 159640fdaf..a0d04ac7f3 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -29,15 +29,17 @@ import ( func newEmptySecure() *SecureTrie { db, _ := ethdb.NewMemDatabase() - trie, _ := NewSecure(common.Hash{}, db, 0) - return trie + tr, _ := New(common.Hash{}, db, 0) + st := NewSecure(tr, db) + return st } // makeTestSecureTrie creates a large enough secure trie for testing. func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) { // Create an empty trie db, _ := ethdb.NewMemDatabase() - trie, _ := NewSecure(common.Hash{}, db, 0) + tr, _ := New(common.Hash{}, db, 0) + trie := NewSecure(tr, db) // Fill it with some arbitrary data content := make(map[string][]byte) diff --git a/trie/sync_test.go b/trie/sync_test.go index a763dc5640..62db45a01d 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -25,10 +25,10 @@ import ( ) // makeTestTrie create a sample test trie to test node-wise reconstruction. -func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) { +func makeTestTrie() (ethdb.Database, *SimpleTrie, map[string][]byte) { // Create an empty trie db, _ := ethdb.NewMemDatabase() - trie, _ := New(common.Hash{}, db) + trie, _ := New(common.Hash{}, db, 0) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -59,7 +59,7 @@ func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) { // content map. func checkTrieContents(t *testing.T, db Database, root []byte, content map[string][]byte) { // Check root availability and trie contents - trie, err := New(common.BytesToHash(root), db) + trie, err := New(common.BytesToHash(root), db, 0) if err != nil { t.Fatalf("failed to create trie at %x: %v", root, err) } @@ -76,7 +76,7 @@ func checkTrieContents(t *testing.T, db Database, root []byte, content map[strin // checkTrieConsistency checks that all nodes in a trie are indeed present. func checkTrieConsistency(db Database, root common.Hash) error { // Create and iterate a trie rooted in a subnode - trie, err := New(root, db) + trie, err := New(root, db, 0) if err != nil { return nil // // Consider a non existent state consistent } @@ -88,10 +88,10 @@ func checkTrieConsistency(db Database, root common.Hash) error { // Tests that an empty trie is not scheduled for syncing. func TestEmptyTrieSync(t *testing.T) { - emptyA, _ := New(common.Hash{}, nil) - emptyB, _ := New(emptyRoot, nil) + emptyA, _ := New(common.Hash{}, nil, 0) + emptyB, _ := New(emptyRoot, nil, 0) - for i, trie := range []*Trie{emptyA, emptyB} { + for i, trie := range []*SimpleTrie{emptyA, emptyB} { db, _ := ethdb.NewMemDatabase() if req := NewTrieSync(common.BytesToHash(trie.Root()), db, nil).Missing(1); len(req) != 0 { t.Errorf("test %d: content requested for empty trie: %v", i, req) diff --git a/trie/trie.go b/trie/trie.go index 035a80e74c..4427e523ae 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Package trie implements Merkle Patricia Tries. +// Package trie implements Merkle Patricia tries. package trie import ( @@ -73,12 +73,27 @@ type DatabaseWriter interface { Put(key, value []byte) error } -// Trie is a Merkle Patricia Trie. +// trie is a Merkle Patricia trie. // The zero value is an empty trie with no database. // Use New to create a trie that sits on top of a database. // -// Trie is not safe for concurrent use. -type Trie struct { +// trie is not safe for concurrent use. +type Trie interface { + Iterator() *Iterator + NodeIterator() *NodeIterator + Get(key []byte) []byte + TryGet(key []byte) ([]byte, error) + Update(key, value []byte) + TryUpdate(key, value []byte) error + Delete(key []byte) + TryDelete(key []byte) error + Root() []byte + Hash() common.Hash + Commit() (root common.Hash, err error) + CommitTo(db DatabaseWriter) (root common.Hash, err error) +} + +type SimpleTrie struct { root node db Database originalRoot common.Hash @@ -90,14 +105,8 @@ type Trie struct { cachegen, cachelimit uint16 } -// SetCacheLimit sets the number of 'cache generations' to keep. -// A cache generations is created by a call to Commit. -func (t *Trie) SetCacheLimit(l uint16) { - t.cachelimit = l -} - // newFlag returns the cache flag value for a newly created node. -func (t *Trie) newFlag() nodeFlag { +func (t *SimpleTrie) newFlag() nodeFlag { return nodeFlag{dirty: true, gen: t.cachegen} } @@ -107,11 +116,14 @@ func (t *Trie) newFlag() nodeFlag { // trie is initially empty and does not require a database. Otherwise, // New will panic if db is nil and returns a MissingNodeError if root does // not exist in the database. Accessing the trie loads nodes from db on demand. -func New(root common.Hash, db Database) (*Trie, error) { - trie := &Trie{db: db, originalRoot: root} +// +// cacheLimit is the number of 'cache generations' to keep. +// A cache generations is created by a call to Commit. +func New(root common.Hash, db Database, cacheLimit uint16) (*SimpleTrie, error) { + trie := &SimpleTrie{db: db, originalRoot: root, cachelimit: cacheLimit} if (root != common.Hash{}) && root != emptyRoot { if db == nil { - panic("trie.New: cannot use existing root without a database") + panic("SimpleTrie.New: cannot use existing root without a database") } rootnode, err := trie.resolveHash(root[:], nil, nil) if err != nil { @@ -123,13 +135,17 @@ func New(root common.Hash, db Database) (*Trie, error) { } // Iterator returns an iterator over all mappings in the trie. -func (t *Trie) Iterator() *Iterator { +func (t *SimpleTrie) Iterator() *Iterator { return NewIterator(t) } +func (t *SimpleTrie) NodeIterator() *NodeIterator { + return NewNodeIterator(t) +} + // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. -func (t *Trie) Get(key []byte) []byte { +func (t *SimpleTrie) Get(key []byte) []byte { res, err := t.TryGet(key) if err != nil && glog.V(logger.Error) { glog.Errorf("Unhandled trie error: %v", err) @@ -140,7 +156,7 @@ func (t *Trie) Get(key []byte) []byte { // TryGet returns the value for key stored in the trie. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryGet(key []byte) ([]byte, error) { +func (t *SimpleTrie) TryGet(key []byte) ([]byte, error) { key = compactHexDecode(key) value, newroot, didResolve, err := t.tryGet(t.root, key, 0) if err == nil && didResolve { @@ -149,7 +165,7 @@ func (t *Trie) TryGet(key []byte) ([]byte, error) { return value, err } -func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { +func (t *SimpleTrie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { switch n := (origNode).(type) { case nil: return nil, nil, false, nil @@ -193,7 +209,7 @@ func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode // // The value bytes must not be modified by the caller while they are // stored in the trie. -func (t *Trie) Update(key, value []byte) { +func (t *SimpleTrie) Update(key, value []byte) { if err := t.TryUpdate(key, value); err != nil && glog.V(logger.Error) { glog.Errorf("Unhandled trie error: %v", err) } @@ -207,7 +223,7 @@ func (t *Trie) Update(key, value []byte) { // stored in the trie. // // If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryUpdate(key, value []byte) error { +func (t *SimpleTrie) TryUpdate(key, value []byte) error { k := compactHexDecode(key) if len(value) != 0 { _, n, err := t.insert(t.root, nil, k, valueNode(value)) @@ -225,7 +241,7 @@ func (t *Trie) TryUpdate(key, value []byte) error { return nil } -func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) { +func (t *SimpleTrie) insert(n node, prefix, key []byte, value node) (bool, node, error) { if len(key) == 0 { if v, ok := n.(valueNode); ok { return !bytes.Equal(v, value.(valueNode)), value, nil @@ -295,7 +311,7 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error } // Delete removes any existing value for key from the trie. -func (t *Trie) Delete(key []byte) { +func (t *SimpleTrie) Delete(key []byte) { if err := t.TryDelete(key); err != nil && glog.V(logger.Error) { glog.Errorf("Unhandled trie error: %v", err) } @@ -303,7 +319,7 @@ func (t *Trie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryDelete(key []byte) error { +func (t *SimpleTrie) TryDelete(key []byte) error { k := compactHexDecode(key) _, n, err := t.delete(t.root, nil, k) if err != nil { @@ -316,7 +332,7 @@ func (t *Trie) TryDelete(key []byte) error { // delete returns the new root of the trie with key deleted. // It reduces the trie to minimal form by simplifying // nodes on the way up after deleting recursively. -func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { +func (t *SimpleTrie) delete(n node, prefix, key []byte) (bool, node, error) { switch n := n.(type) { case *shortNode: matchlen := prefixLen(key, n.Key) @@ -432,14 +448,14 @@ func concat(s1 []byte, s2 ...byte) []byte { return r } -func (t *Trie) resolve(n node, prefix, suffix []byte) (node, error) { +func (t *SimpleTrie) resolve(n node, prefix, suffix []byte) (node, error) { if n, ok := n.(hashNode); ok { return t.resolveHash(n, prefix, suffix) } return n, nil } -func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { +func (t *SimpleTrie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { cacheMissCounter.Inc(1) enc, err := t.db.Get(n) @@ -458,11 +474,11 @@ func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { // Root returns the root hash of the trie. // Deprecated: use Hash instead. -func (t *Trie) Root() []byte { return t.Hash().Bytes() } +func (t *SimpleTrie) Root() []byte { return t.Hash().Bytes() } // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. -func (t *Trie) Hash() common.Hash { +func (t *SimpleTrie) Hash() common.Hash { hash, cached, _ := t.hashRoot(nil) t.root = cached return common.BytesToHash(hash.(hashNode)) @@ -473,7 +489,7 @@ func (t *Trie) Hash() common.Hash { // // Committing flushes nodes from memory. // Subsequent Get calls will load nodes from the database. -func (t *Trie) Commit() (root common.Hash, err error) { +func (t *SimpleTrie) Commit() (root common.Hash, err error) { if t.db == nil { panic("Commit called on trie with nil database") } @@ -487,7 +503,7 @@ func (t *Trie) Commit() (root common.Hash, err error) { // load nodes from the trie's database. Calling code must ensure that // the changes made to db are written back to the trie's attached // database before using the trie. -func (t *Trie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { +func (t *SimpleTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { hash, cached, err := t.hashRoot(db) if err != nil { return (common.Hash{}), err @@ -497,7 +513,7 @@ func (t *Trie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { return common.BytesToHash(hash.(hashNode)), nil } -func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) { +func (t *SimpleTrie) hashRoot(db DatabaseWriter) (node, node, error) { if t.root == nil { return hashNode(emptyRoot.Bytes()), nil, nil } diff --git a/trie/trie_test.go b/trie/trie_test.go index 14ac5a6669..fd641d8e10 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -38,14 +38,14 @@ func init() { } // Used for testing -func newEmpty() *Trie { +func newEmpty() *SimpleTrie { db, _ := ethdb.NewMemDatabase() - trie, _ := New(common.Hash{}, db) + trie, _ := New(common.Hash{}, db, 0) return trie } func TestEmptyTrie(t *testing.T) { - var trie Trie + var trie SimpleTrie res := trie.Hash() exp := emptyRoot if res != common.Hash(exp) { @@ -54,7 +54,7 @@ func TestEmptyTrie(t *testing.T) { } func TestNull(t *testing.T) { - var trie Trie + var trie SimpleTrie key := make([]byte, 32) value := common.FromHex("0x823140710bf13990e4500136726d8b55") trie.Update(key, value) @@ -63,7 +63,7 @@ func TestNull(t *testing.T) { func TestMissingRoot(t *testing.T) { db, _ := ethdb.NewMemDatabase() - trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), db) + trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), db, 0) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -74,36 +74,36 @@ func TestMissingRoot(t *testing.T) { func TestMissingNode(t *testing.T) { db, _ := ethdb.NewMemDatabase() - trie, _ := New(common.Hash{}, db) + trie, _ := New(common.Hash{}, db, 0) updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") root, _ := trie.Commit() - trie, _ = New(root, db) + trie, _ = New(root, db, 0) _, err := trie.TryGet([]byte("120000")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) _, err = trie.TryGet([]byte("120099")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) _, err = trie.TryGet([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) err = trie.TryDelete([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -111,31 +111,31 @@ func TestMissingNode(t *testing.T) { db.Delete(common.FromHex("e1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9")) - trie, _ = New(root, db) + trie, _ = New(root, db, 0) _, err = trie.TryGet([]byte("120000")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) _, err = trie.TryGet([]byte("120099")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) _, err = trie.TryGet([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) err = trie.TryUpdate([]byte("120099"), []byte("zxcv")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(root, db) + trie, _ = New(root, db, 0) err = trie.TryDelete([]byte("123456")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) @@ -263,7 +263,7 @@ func TestReplication(t *testing.T) { } // create a new trie on top of the database and check that lookups work. - trie2, err := New(exp, trie.db) + trie2, err := New(exp, trie.db, 0) if err != nil { t.Fatalf("can't recreate trie at %x: %v", exp, err) } @@ -332,8 +332,7 @@ func TestCacheUnload(t *testing.T) { // The branch containing it is loaded from DB exactly two times: // in the 0th and 6th iteration. db := &countingDB{Database: trie.db, gets: make(map[string]int)} - trie, _ = New(root, db) - trie.SetCacheLimit(5) + trie, _ = New(root, db, 5) for i := 0; i < 12; i++ { getString(trie, key1) trie.Commit() @@ -417,7 +416,7 @@ func randRead(r *rand.Rand, b []byte) { func runRandTest(rt randTest) bool { db, _ := ethdb.NewMemDatabase() - tr, _ := New(common.Hash{}, db) + tr, _ := New(common.Hash{}, db, 0) values := make(map[string]string) // tracks content of the trie for _, step := range rt { @@ -446,13 +445,13 @@ func runRandTest(rt randTest) bool { if err != nil { panic(err) } - newtr, err := New(hash, db) + newtr, err := New(hash, db, 0) if err != nil { panic(err) } tr = newtr case opItercheckhash: - checktr, _ := New(common.Hash{}, nil) + checktr, _ := New(common.Hash{}, nil, 0) it := tr.Iterator() for it.Next() { checktr.Update(it.Key, it.Value) @@ -520,10 +519,10 @@ func BenchmarkHashLE(b *testing.B) { benchHash(b, binary.LittleEndian) } const benchElemCount = 20000 func benchGet(b *testing.B, commit bool) { - trie := new(Trie) + trie, _ := New(common.Hash{}, nil, 0) if commit { _, tmpdb := tempDB() - trie, _ = New(common.Hash{}, tmpdb) + trie, _ = New(common.Hash{}, tmpdb, 0) } k := make([]byte, 32) for i := 0; i < benchElemCount; i++ { @@ -548,7 +547,7 @@ func benchGet(b *testing.B, commit bool) { } } -func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { +func benchUpdate(b *testing.B, e binary.ByteOrder) *SimpleTrie { trie := newEmpty() k := make([]byte, 32) for i := 0; i < b.N; i++ { @@ -584,14 +583,14 @@ func tempDB() (string, Database) { return dir, db } -func getString(trie *Trie, k string) []byte { +func getString(trie Trie, k string) []byte { return trie.Get([]byte(k)) } -func updateString(trie *Trie, k, v string) { +func updateString(trie Trie, k, v string) { trie.Update([]byte(k), []byte(v)) } -func deleteString(trie *Trie, k string) { +func deleteString(trie Trie, k string) { trie.Delete([]byte(k)) } From 9683a38895254dabd5d53d402769dbba310984fb Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Mon, 24 Oct 2016 11:42:16 +0100 Subject: [PATCH 02/16] core/state, light, trie: Rename Trie to Storage; remove Hash() and Root() methods --- core/state/dump.go | 8 ++--- core/state/state_object.go | 24 +++++++------ core/state/statedb.go | 30 ++++++++-------- light/trie.go | 30 ++++++++-------- trie/secure_trie.go | 70 ++++++++++++++++---------------------- trie/secure_trie_test.go | 18 +++++----- trie/trie.go | 4 +-- 7 files changed, 86 insertions(+), 98 deletions(-) diff --git a/core/state/dump.go b/core/state/dump.go index 8294d61b9f..49569ac9fd 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -44,9 +44,9 @@ func (self *StateDB) RawDump() Dump { Accounts: make(map[string]DumpAccount), } - it := self.trie.Iterator() + it := self.storage.Iterator() for it.Next() { - addr := self.trie.GetKey(it.Key) + addr := self.storage.GetKey(it.Key) var data Account if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) @@ -61,9 +61,9 @@ func (self *StateDB) RawDump() Dump { Code: common.Bytes2Hex(obj.Code(self.db)), Storage: make(map[string]string), } - storageIt := obj.getTrie(self.db).Iterator() + storageIt := obj.getStorage(self.db).Iterator() for storageIt.Next() { - account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) + account.Storage[common.Bytes2Hex(self.storage.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) } dump.Accounts[common.Bytes2Hex(addr)] = account } diff --git a/core/state/state_object.go b/core/state/state_object.go index e770aac254..1817299493 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -76,7 +76,8 @@ type StateObject struct { dbErr error // Write caches. - trie *trie.SecureTrie // storage trie, which becomes non-nil on first access + trie *trie.SimpleTrie // storage trie, which becomes non-nil on first access + storage *trie.SecureTrie // Interface to storage code Code // contract bytecode, which gets set when code is loaded cachedStorage Storage // Storage entry cache to avoid duplicate reads @@ -134,17 +135,17 @@ func (self *StateObject) markSuicided() { } } -func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie { +func (c *StateObject) getStorage(db trie.Database) *trie.SecureTrie { if c.trie == nil { var err error - t, err := trie.New(c.data.Root, db, 0) + c.trie, err = trie.New(c.data.Root, db, 0) if err != nil { - t, _ = trie.New(common.Hash{}, nil, 0) + c.trie, _ = trie.New(common.Hash{}, nil, 0) c.setError(fmt.Errorf("can't create storage trie: %v", err)) } - c.trie = trie.NewSecure(t, db) + c.storage = trie.NewSecure(c.trie, db) } - return c.trie + return c.storage } // GetState returns a value in account storage. @@ -154,7 +155,7 @@ func (self *StateObject) GetState(db trie.Database, key common.Hash) common.Hash return value } // Load from DB in case it is missing. - if enc := self.getTrie(db).Get(key[:]); len(enc) > 0 { + if enc := self.getStorage(db).Get(key[:]); len(enc) > 0 { _, content, _, err := rlp.Split(enc) if err != nil { self.setError(err) @@ -189,7 +190,7 @@ func (self *StateObject) setState(key, value common.Hash) { // updateTrie writes cached storage modifications into the object's storage trie. func (self *StateObject) updateTrie(db trie.Database) { - tr := self.getTrie(db) + tr := self.getStorage(db) for key, value := range self.dirtyStorage { delete(self.dirtyStorage, key) if (value == common.Hash{}) { @@ -215,7 +216,7 @@ func (self *StateObject) CommitTrie(db trie.Database, dbw trie.DatabaseWriter) e if self.dbErr != nil { return self.dbErr } - root, err := self.trie.CommitTo(dbw) + root, err := self.storage.CommitTo(dbw) if err == nil { self.data.Root = root } @@ -266,6 +267,7 @@ func (c *StateObject) ReturnGas(gas, price *big.Int) {} func (self *StateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *StateObject { stateObject := newObject(db, self.address, self.data, onDirty) stateObject.trie = self.trie + stateObject.storage = self.storage stateObject.code = self.code stateObject.dirtyStorage = self.dirtyStorage.Copy() stateObject.cachedStorage = self.dirtyStorage.Copy() @@ -361,10 +363,10 @@ func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { cb(h, value) } - it := self.getTrie(self.db.db).Iterator() + it := self.getStorage(self.db.db).Iterator() for it.Next() { // ignore cached values - key := common.BytesToHash(self.trie.GetKey(it.Key)) + key := common.BytesToHash(self.storage.GetKey(it.Key)) if _, ok := self.cachedStorage[key]; !ok { cb(key, common.BytesToHash(it.Value)) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 394df93904..2fd41e6180 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -62,8 +62,9 @@ type revision struct { // * Accounts type StateDB struct { db ethdb.Database - trie *trie.SecureTrie - pastTries []*trie.SecureTrie + trie *trie.SimpleTrie + storage *trie.SecureTrie + pastTries []*trie.SimpleTrie codeSizeCache *lru.Cache // This map holds 'live' objects, which will get modified while processing a state transition. @@ -93,11 +94,11 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) { if err != nil { return nil, err } - st := trie.NewSecure(tr, db) csc, _ := lru.New(codeSizeCacheSize) return &StateDB{ db: db, - trie: st, + trie: tr, + storage: trie.NewSecure(tr, db), codeSizeCache: csc, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), @@ -119,6 +120,7 @@ func (self *StateDB) New(root common.Hash) (*StateDB, error) { return &StateDB{ db: self.db, trie: tr, + storage: trie.NewSecure(tr, self.db), codeSizeCache: self.codeSizeCache, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), @@ -138,6 +140,7 @@ func (self *StateDB) Reset(root common.Hash) error { return err } self.trie = tr + self.storage = trie.NewSecure(tr, self.db) self.stateObjects = make(map[common.Address]*StateObject) self.stateObjectsDirty = make(map[common.Address]struct{}) self.thash = common.Hash{} @@ -152,21 +155,17 @@ func (self *StateDB) Reset(root common.Hash) error { // openTrie creates a trie. It uses an existing trie if one is available // from the journal if available. -func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) { +func (self *StateDB) openTrie(root common.Hash) (*trie.SimpleTrie, error) { for i := len(self.pastTries) - 1; i >= 0; i-- { if self.pastTries[i].Hash() == root { tr := *self.pastTries[i] return &tr, nil } } - t, err := trie.New(root, self.db, MaxTrieCacheGen) - if err != nil { - return nil, err - } - return trie.NewSecure(t, self.db), nil + return trie.New(root, self.db, MaxTrieCacheGen) } -func (self *StateDB) pushTrie(t *trie.SecureTrie) { +func (self *StateDB) pushTrie(t *trie.SimpleTrie) { self.lock.Lock() defer self.lock.Unlock() @@ -361,14 +360,14 @@ func (self *StateDB) updateStateObject(stateObject *StateObject) { if err != nil { panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) } - self.trie.Update(addr[:], data) + self.storage.Update(addr[:], data) } // deleteStateObject removes the given object from the state trie. func (self *StateDB) deleteStateObject(stateObject *StateObject) { stateObject.deleted = true addr := stateObject.Address() - self.trie.Delete(addr[:]) + self.storage.Delete(addr[:]) } // Retrieve a state object given my the address. Returns nil if not found. @@ -382,7 +381,7 @@ func (self *StateDB) GetStateObject(addr common.Address) (stateObject *StateObje } // Load the object from the database. - enc := self.trie.Get(addr[:]) + enc := self.storage.Get(addr[:]) if len(enc) == 0 { return nil } @@ -462,6 +461,7 @@ func (self *StateDB) Copy() *StateDB { state := &StateDB{ db: self.db, trie: self.trie, + storage: self.storage, pastTries: self.pastTries, codeSizeCache: self.codeSizeCache, stateObjects: make(map[common.Address]*StateObject, len(self.stateObjectsDirty)), @@ -607,7 +607,7 @@ func (s *StateDB) commit(dbw trie.DatabaseWriter) (root common.Hash, err error) delete(s.stateObjectsDirty, addr) } // Write trie changes. - root, err = s.trie.CommitTo(dbw) + root, err = s.storage.CommitTo(dbw) if err == nil { s.pushTrie(s.trie) } diff --git a/light/trie.go b/light/trie.go index 3436a37bdd..9e2d067ce6 100644 --- a/light/trie.go +++ b/light/trie.go @@ -25,7 +25,7 @@ import ( // LightTrie is an ODR-capable wrapper around trie.SecureTrie type LightTrie struct { - trie *trie.SecureTrie + storage *trie.SecureTrie originalRoot common.Hash odr OdrBackend db ethdb.Database @@ -74,26 +74,26 @@ func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error) return err } -func (t *LightTrie) getTrie() (ret *trie.SecureTrie, err error) { - if t.trie == nil { - var tr trie.Trie +func (t *LightTrie) getStorage() (ret *trie.SecureTrie, err error) { + if t.storage == nil { + var tr trie.Storage tr, err = trie.New(t.originalRoot, t.db, 0) if err != nil { return nil, err } - t.trie = trie.NewSecure(tr, t.db) + t.storage = trie.NewSecure(tr, t.db) } - return t.trie, nil + return t.storage, nil } // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) { err = t.do(ctx, key, func() (err error) { - var tr *trie.SecureTrie - tr, err = t.getTrie() + var st *trie.SecureTrie + st, err = t.getStorage() if err == nil { - res, err = tr.TryGet(key) + res, err = st.TryGet(key) } return }) @@ -108,10 +108,10 @@ func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) // stored in the trie. func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { err = t.do(ctx, key, func() (err error) { - var tr *trie.SecureTrie - tr, err = t.getTrie() + var st *trie.SecureTrie + st, err = t.getStorage() if err == nil { - err = tr.TryUpdate(key, value) + err = st.TryUpdate(key, value) } return }) @@ -121,10 +121,10 @@ func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { // Delete removes any existing value for key from the trie. func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) { err = t.do(ctx, key, func() (err error) { - var tr *trie.SecureTrie - tr, err = t.getTrie() + var st *trie.SecureTrie + st, err = t.getStorage() if err == nil { - err = tr.TryDelete(key) + err = st.TryDelete(key) } return }) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 00160fd62c..f17704effc 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -37,7 +37,7 @@ const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash // // SecureTrie is not safe for concurrent use. type SecureTrie struct { - trie Trie + storage Storage db Database hashKeyBuf [secureKeyLength]byte secKeyBuf [200]byte @@ -45,57 +45,57 @@ type SecureTrie struct { secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch } -// NewSecure creates a secure trie from an existing trie. -func NewSecure(t Trie, db Database) *SecureTrie { - if t == nil { - panic("NewSecure called with nil trie") +// NewSecure creates a secure Storage from an existing Storage. +func NewSecure(s Storage, db Database) *SecureTrie { + if s == nil { + panic("NewSecure called with nil storage") } if db == nil { panic("NewSecure called with nil database") } - return &SecureTrie{trie: t, db: db} + return &SecureTrie{storage: s, db: db} } -// Get returns the value for key stored in the trie. +// Get returns the value for key stored in the storage. // The value bytes must not be modified by the caller. func (t *SecureTrie) Get(key []byte) []byte { res, err := t.TryGet(key) if err != nil && glog.V(logger.Error) { - glog.Errorf("Unhandled trie error: %v", err) + glog.Errorf("Unhandled storage error: %v", err) } return res } -// TryGet returns the value for key stored in the trie. +// TryGet returns the value for key stored in the storage. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { - return t.trie.TryGet(t.hashKey(key)) + return t.storage.TryGet(t.hashKey(key)) } -// Update associates key with value in the trie. Subsequent calls to +// Update associates key with value in the storage. Subsequent calls to // Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. +// is deleted from the storage and calls to Get will return nil. // // The value bytes must not be modified by the caller while they are -// stored in the trie. +// stored in the storage. func (t *SecureTrie) Update(key, value []byte) { if err := t.TryUpdate(key, value); err != nil && glog.V(logger.Error) { - glog.Errorf("Unhandled trie error: %v", err) + glog.Errorf("Unhandled storage error: %v", err) } } -// TryUpdate associates key with value in the trie. Subsequent calls to +// TryUpdate associates key with value in the storage. Subsequent calls to // Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. +// is deleted from the storage and calls to Get will return nil. // // The value bytes must not be modified by the caller while they are -// stored in the trie. +// stored in the storage. // // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryUpdate(key, value []byte) error { hk := t.hashKey(key) - err := t.trie.TryUpdate(hk, value) + err := t.storage.TryUpdate(hk, value) if err != nil { return err } @@ -103,19 +103,19 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { return nil } -// Delete removes any existing value for key from the trie. +// Delete removes any existing value for key from the storage. func (t *SecureTrie) Delete(key []byte) { if err := t.TryDelete(key); err != nil && glog.V(logger.Error) { - glog.Errorf("Unhandled trie error: %v", err) + glog.Errorf("Unhandled storage error: %v", err) } } -// TryDelete removes any existing value for key from the trie. +// TryDelete removes any existing value for key from the storage. // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryDelete(key []byte) error { hk := t.hashKey(key) delete(t.getSecKeyCache(), string(hk)) - return t.trie.TryDelete(hk) + return t.storage.TryDelete(hk) } // GetKey returns the sha3 preimage of a hashed key that was @@ -128,7 +128,7 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { return key } -// Commit writes all nodes and the secure hash pre-images to the trie's database. +// Commit writes all nodes and the secure hash pre-images to the database. // Nodes are stored with their sha3 hash as the key. // // Committing flushes nodes from memory. Subsequent Get calls will load nodes @@ -137,36 +137,24 @@ func (t *SecureTrie) Commit() (root common.Hash, err error) { if err := t.CommitPreimages(); err != nil { return common.Hash{}, err } - return t.trie.Commit() -} - -func (t *SecureTrie) Hash() common.Hash { - return t.trie.Hash() -} - -func (t *SecureTrie) Root() []byte { - return t.trie.Root() + return t.storage.Commit() } func (t *SecureTrie) Iterator() *Iterator { - return t.trie.Iterator() -} - -func (t *SecureTrie) NodeIterator() *NodeIterator { - return t.trie.NodeIterator() + return t.storage.Iterator() } // CommitTo writes all nodes and the secure hash pre-images to the given database. // Nodes are stored with their sha3 hash as the key. // // Committing flushes nodes from memory. Subsequent Get calls will load nodes from -// the trie's database. Calling code must ensure that the changes made to db are -// written back to the trie's attached database before using the trie. +// the database. Calling code must ensure that the changes made to db are +// written back to the attached database before using the storage. func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { if err := t.CommitPreimages(); err != nil { return common.Hash{}, err } - return t.trie.CommitTo(db) + return t.storage.CommitTo(db) } func (t *SecureTrie) CommitPreimages() error { @@ -203,7 +191,7 @@ func (t *SecureTrie) hashKey(key []byte) []byte { } // getSecKeyCache returns the current secure key cache, creating a new one if -// ownership changed (i.e. the current secure trie is a copy of another owning +// ownership changed (i.e. the current secure storage is a copy of another owning // the actual cache). func (t *SecureTrie) getSecKeyCache() map[string][]byte { if t != t.secKeyCacheOwner { diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index a0d04ac7f3..a595c2f1ea 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -27,11 +27,11 @@ import ( "github.com/ethereum/go-ethereum/ethdb" ) -func newEmptySecure() *SecureTrie { +func newEmptySecure() (*SimpleTrie, *SecureTrie) { db, _ := ethdb.NewMemDatabase() tr, _ := New(common.Hash{}, db, 0) st := NewSecure(tr, db) - return st + return tr, st } // makeTestSecureTrie creates a large enough secure trie for testing. @@ -67,7 +67,7 @@ func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) { } func TestSecureDelete(t *testing.T) { - trie := newEmptySecure() + trie, st := newEmptySecure() vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -80,9 +80,9 @@ func TestSecureDelete(t *testing.T) { } for _, val := range vals { if val.v != "" { - trie.Update([]byte(val.k), []byte(val.v)) + st.Update([]byte(val.k), []byte(val.v)) } else { - trie.Delete([]byte(val.k)) + st.Delete([]byte(val.k)) } } hash := trie.Hash() @@ -93,17 +93,17 @@ func TestSecureDelete(t *testing.T) { } func TestSecureGetKey(t *testing.T) { - trie := newEmptySecure() - trie.Update([]byte("foo"), []byte("bar")) + _, st := newEmptySecure() + st.Update([]byte("foo"), []byte("bar")) key := []byte("foo") value := []byte("bar") seckey := crypto.Keccak256(key) - if !bytes.Equal(trie.Get(key), value) { + if !bytes.Equal(st.Get(key), value) { t.Errorf("Get did not return bar") } - if k := trie.GetKey(seckey); !bytes.Equal(k, key) { + if k := st.GetKey(seckey); !bytes.Equal(k, key) { t.Errorf("GetKey returned %q, want %q", k, key) } } diff --git a/trie/trie.go b/trie/trie.go index 4427e523ae..f9eae6053e 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -78,7 +78,7 @@ type DatabaseWriter interface { // Use New to create a trie that sits on top of a database. // // trie is not safe for concurrent use. -type Trie interface { +type Storage interface { Iterator() *Iterator NodeIterator() *NodeIterator Get(key []byte) []byte @@ -87,8 +87,6 @@ type Trie interface { TryUpdate(key, value []byte) error Delete(key []byte) TryDelete(key []byte) error - Root() []byte - Hash() common.Hash Commit() (root common.Hash, err error) CommitTo(db DatabaseWriter) (root common.Hash, err error) } From cee7eae5dce74e15691bdf03db7fdbfbaeb16684 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Mon, 24 Oct 2016 11:42:39 +0100 Subject: [PATCH 03/16] trie, metrics: Implement DirectCache for storage caching --- metrics/metrics.go | 9 +++ trie/directcache.go | 179 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 trie/directcache.go diff --git a/metrics/metrics.go b/metrics/metrics.go index 7f647cd00b..bb6bfb5e10 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -66,6 +66,15 @@ func NewTimer(name string) metrics.Timer { return metrics.GetOrRegisterTimer(name, metrics.DefaultRegistry) } +// NewCounter create a new metrics Counter, either a real one of a NOP stub depending +// on the metrics flag. +func NewCounter(name string) metrics.Counter { + if !Enabled { + return new(metrics.NilCounter) + } + return metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry) +} + // CollectProcessMetrics periodically collects various metrics about the running // process. func CollectProcessMetrics(refresh time.Duration) { diff --git a/trie/directcache.go b/trie/directcache.go new file mode 100644 index 0000000000..0d2a0f2410 --- /dev/null +++ b/trie/directcache.go @@ -0,0 +1,179 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" +) + +var directCacheWrites = metrics.NewCounter("directcache/writes") +var directCacheHitTimer = metrics.NewTimer("directcache/timer/hits") +var directCacheMissTimer = metrics.NewTimer("directcache/timer/misses") + +type cachedValue struct { + Value []byte + BlockNum uint64 + BlockHash common.Hash +} + +// CacheValidator can check whether a certain block is in the current canonical chain. +type CacheValidator interface { + IsCanonChainBlock(uint64, common.Hash) bool +} + +type DirectCache struct { + storage Storage + db Database + keyPrefix []byte + blockNum uint64 + blockHash common.Hash + validator CacheValidator + complete bool + dirty map[string]bool +} + +func NewDirectCache(s Storage, db Database, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache { + return &DirectCache{ + storage: s, + db: db, + keyPrefix: keyPrefix, + blockNum: blockNum, + blockHash: blockHash, + validator: validator, + complete: complete, + dirty: make(map[string]bool), + } +} + +func (dc *DirectCache) Iterator() *Iterator { + // Todo: If complete is true, implement an iterator over the DB instead. + return dc.storage.Iterator() +} + +func (dc *DirectCache) NodeIterator() *NodeIterator { + return dc.storage.NodeIterator() +} + +func (dc *DirectCache) makeKey(key []byte) []byte { + return append(dc.keyPrefix, key...) +} + +func (dc *DirectCache) Get(key []byte) []byte { + res, err := dc.TryGet(key) + if err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled error: %v", err) + } + return res +} + +func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { + start := time.Now() + + // Use the underlying object for dirty keys + if !dc.dirty[string(key)] { + cacheKey := dc.makeKey(key) + if cached, ok := dc.getCached(cacheKey); ok { + directCacheHitTimer.UpdateSince(start) + return cached, nil + } + } + + value, err := dc.storage.TryGet(key) + if err != nil { + return value, err + } + + // If the cache isn't comprehensive, update it + if !dc.complete && !dc.dirty[string(key)] { + if err := dc.putCache(dc.db, key, value); err != nil && glog.V(logger.Error) { + glog.Errorf("Error updating cache: %v", err) + } + } + + directCacheMissTimer.UpdateSince(start) + return value, nil +} + +func (dc *DirectCache) getCached(key []byte) ([]byte, bool) { + enc, _ := dc.db.Get(key) + if len(enc) == 0 { + return nil, dc.complete + } + + var data cachedValue + if err := rlp.DecodeBytes(enc, &data); err != nil && glog.V(logger.Error) { + glog.Errorf("Can't decode cached object at %x: %v", key, err) + return nil, false + } + + return data.Value, dc.validator.IsCanonChainBlock(data.BlockNum, data.BlockHash) +} + +func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { + enc, _ := rlp.EncodeToBytes(cachedValue{value, dc.blockNum, dc.blockHash}) + if err := dbw.Put(append(dc.keyPrefix, key...), enc); err != nil { + return err + } + return nil +} + +func (dc *DirectCache) Update(key, value []byte) { + if err := dc.TryUpdate(key, value); err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled error: %v", err) + } +} + +func (dc *DirectCache) TryUpdate(key, value []byte) error { + dc.dirty[string(key)] = true + return dc.storage.TryUpdate(key, value) +} + +func (dc *DirectCache) Delete(key []byte) { + if err := dc.TryDelete(key); err != nil && glog.V(logger.Error) { + glog.Errorf("Unhandled error: %v", err) + } +} + +func (dc *DirectCache) TryDelete(key []byte) error { + dc.dirty[string(key)] = true + return dc.storage.TryDelete(key) +} + +func (dc *DirectCache) Commit() (root common.Hash, err error) { + return dc.CommitTo(dc.db) +} + +func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error) { + directCacheWrites.Inc(int64(len(dc.dirty))) + for k, _ := range dc.dirty { + v, err := dc.storage.TryGet([]byte(k)) + if err, ok := err.(*MissingNodeError); err != nil && !ok { + return common.Hash{}, err + } + if err := dc.putCache(dbw, []byte(k), v); err != nil { + return common.Hash{}, err + } + } + dc.dirty = make(map[string]bool) + return dc.storage.CommitTo(dbw) +} From 2fb08643cbd862a07801de09aa0aa55edc583bfa Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Mon, 24 Oct 2016 11:58:50 +0100 Subject: [PATCH 04/16] Remove NodeIterator from the storage interface --- core/state/iterator.go | 4 ++-- core/state/state_object.go | 2 +- core/state/statedb.go | 8 +++---- trie/directcache.go | 4 ---- trie/iterator.go | 8 +++---- trie/proof.go | 2 +- trie/proof_test.go | 2 +- trie/secure_trie_test.go | 2 +- trie/sync_test.go | 4 ++-- trie/trie.go | 49 +++++++++++++++++--------------------- trie/trie_test.go | 8 +++---- 11 files changed, 42 insertions(+), 51 deletions(-) diff --git a/core/state/iterator.go b/core/state/iterator.go index f48aab5583..9600c92ed5 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -76,7 +76,7 @@ func (it *NodeIterator) step() error { } // Initialize the iterator if we've just started if it.stateIt == nil { - it.stateIt = it.state.trie.NodeIterator() + it.stateIt = trie.NewNodeIterator(it.state.trie) } // If we had data nodes previously, we surely have at least state nodes if it.dataIt != nil { @@ -119,7 +119,7 @@ func (it *NodeIterator) step() error { if err != nil { return err } - it.dataIt = dataTrie.NodeIterator() + it.dataIt = trie.NewNodeIterator(dataTrie) if !it.dataIt.Next() { it.dataIt = nil } diff --git a/core/state/state_object.go b/core/state/state_object.go index 1817299493..cd60c33776 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -76,7 +76,7 @@ type StateObject struct { dbErr error // Write caches. - trie *trie.SimpleTrie // storage trie, which becomes non-nil on first access + trie *trie.Trie // storage trie, which becomes non-nil on first access storage *trie.SecureTrie // Interface to storage code Code // contract bytecode, which gets set when code is loaded diff --git a/core/state/statedb.go b/core/state/statedb.go index 2fd41e6180..eef2f38d50 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -62,9 +62,9 @@ type revision struct { // * Accounts type StateDB struct { db ethdb.Database - trie *trie.SimpleTrie + trie *trie.Trie storage *trie.SecureTrie - pastTries []*trie.SimpleTrie + pastTries []*trie.Trie codeSizeCache *lru.Cache // This map holds 'live' objects, which will get modified while processing a state transition. @@ -155,7 +155,7 @@ func (self *StateDB) Reset(root common.Hash) error { // openTrie creates a trie. It uses an existing trie if one is available // from the journal if available. -func (self *StateDB) openTrie(root common.Hash) (*trie.SimpleTrie, error) { +func (self *StateDB) openTrie(root common.Hash) (*trie.Trie, error) { for i := len(self.pastTries) - 1; i >= 0; i-- { if self.pastTries[i].Hash() == root { tr := *self.pastTries[i] @@ -165,7 +165,7 @@ func (self *StateDB) openTrie(root common.Hash) (*trie.SimpleTrie, error) { return trie.New(root, self.db, MaxTrieCacheGen) } -func (self *StateDB) pushTrie(t *trie.SimpleTrie) { +func (self *StateDB) pushTrie(t *trie.Trie) { self.lock.Lock() defer self.lock.Unlock() diff --git a/trie/directcache.go b/trie/directcache.go index 0d2a0f2410..6f87ba5ef8 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -70,10 +70,6 @@ func (dc *DirectCache) Iterator() *Iterator { return dc.storage.Iterator() } -func (dc *DirectCache) NodeIterator() *NodeIterator { - return dc.storage.NodeIterator() -} - func (dc *DirectCache) makeKey(key []byte) []byte { return append(dc.keyPrefix, key...) } diff --git a/trie/iterator.go b/trie/iterator.go index 750bbab12c..4b5f42890a 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -20,7 +20,7 @@ import "github.com/ethereum/go-ethereum/common" // Iterator is a key-value trie iterator that traverses a Trie. type Iterator struct { - trie *SimpleTrie + trie *Trie nodeIt *NodeIterator keyBuf []byte @@ -29,7 +29,7 @@ type Iterator struct { } // NewIterator creates a new key-value iterator. -func NewIterator(trie *SimpleTrie) *Iterator { +func NewIterator(trie *Trie) *Iterator { return &Iterator{ trie: trie, nodeIt: NewNodeIterator(trie), @@ -82,7 +82,7 @@ type nodeIteratorState struct { // NodeIterator is an iterator to traverse the trie post-order. type NodeIterator struct { - trie *SimpleTrie // Trie being iterated + trie *Trie // Trie being iterated stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state Hash common.Hash // Hash of the current node being iterated (nil if not standalone) @@ -95,7 +95,7 @@ type NodeIterator struct { } // NewNodeIterator creates an post-order trie iterator. -func NewNodeIterator(trie *SimpleTrie) *NodeIterator { +func NewNodeIterator(trie *Trie) *NodeIterator { if trie.Hash() == emptyState { return new(NodeIterator) } diff --git a/trie/proof.go b/trie/proof.go index bf796a95cd..bea5e5c098 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -37,7 +37,7 @@ import ( // contains all nodes of the longest existing prefix of the key // (at least the root node), ending with the node that proves the // absence of the key. -func (t *SimpleTrie) Prove(key []byte) []rlp.RawValue { +func (t *Trie) Prove(key []byte) []rlp.RawValue { // Collect all nodes on the path to key. key = compactHexDecode(key) nodes := []node{} diff --git a/trie/proof_test.go b/trie/proof_test.go index eddbafe00a..ce7eac5045 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -129,7 +129,7 @@ func BenchmarkVerifyProof(b *testing.B) { } } -func randomTrie(n int) (*SimpleTrie, map[string]*kv) { +func randomTrie(n int) (*Trie, map[string]*kv) { trie, _ := New(common.Hash{}, nil, 0) vals := make(map[string]*kv) for i := byte(0); i < 100; i++ { diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index a595c2f1ea..7b8907deeb 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" ) -func newEmptySecure() (*SimpleTrie, *SecureTrie) { +func newEmptySecure() (*Trie, *SecureTrie) { db, _ := ethdb.NewMemDatabase() tr, _ := New(common.Hash{}, db, 0) st := NewSecure(tr, db) diff --git a/trie/sync_test.go b/trie/sync_test.go index 62db45a01d..a28e81adb7 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -25,7 +25,7 @@ import ( ) // makeTestTrie create a sample test trie to test node-wise reconstruction. -func makeTestTrie() (ethdb.Database, *SimpleTrie, map[string][]byte) { +func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) { // Create an empty trie db, _ := ethdb.NewMemDatabase() trie, _ := New(common.Hash{}, db, 0) @@ -91,7 +91,7 @@ func TestEmptyTrieSync(t *testing.T) { emptyA, _ := New(common.Hash{}, nil, 0) emptyB, _ := New(emptyRoot, nil, 0) - for i, trie := range []*SimpleTrie{emptyA, emptyB} { + for i, trie := range []*Trie{emptyA, emptyB} { db, _ := ethdb.NewMemDatabase() if req := NewTrieSync(common.BytesToHash(trie.Root()), db, nil).Missing(1); len(req) != 0 { t.Errorf("test %d: content requested for empty trie: %v", i, req) diff --git a/trie/trie.go b/trie/trie.go index f9eae6053e..e186fc1d7c 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -80,7 +80,6 @@ type DatabaseWriter interface { // trie is not safe for concurrent use. type Storage interface { Iterator() *Iterator - NodeIterator() *NodeIterator Get(key []byte) []byte TryGet(key []byte) ([]byte, error) Update(key, value []byte) @@ -91,7 +90,7 @@ type Storage interface { CommitTo(db DatabaseWriter) (root common.Hash, err error) } -type SimpleTrie struct { +type Trie struct { root node db Database originalRoot common.Hash @@ -104,7 +103,7 @@ type SimpleTrie struct { } // newFlag returns the cache flag value for a newly created node. -func (t *SimpleTrie) newFlag() nodeFlag { +func (t *Trie) newFlag() nodeFlag { return nodeFlag{dirty: true, gen: t.cachegen} } @@ -117,11 +116,11 @@ func (t *SimpleTrie) newFlag() nodeFlag { // // cacheLimit is the number of 'cache generations' to keep. // A cache generations is created by a call to Commit. -func New(root common.Hash, db Database, cacheLimit uint16) (*SimpleTrie, error) { - trie := &SimpleTrie{db: db, originalRoot: root, cachelimit: cacheLimit} +func New(root common.Hash, db Database, cacheLimit uint16) (*Trie, error) { + trie := &Trie{db: db, originalRoot: root, cachelimit: cacheLimit} if (root != common.Hash{}) && root != emptyRoot { if db == nil { - panic("SimpleTrie.New: cannot use existing root without a database") + panic("Trie.New: cannot use existing root without a database") } rootnode, err := trie.resolveHash(root[:], nil, nil) if err != nil { @@ -133,17 +132,13 @@ func New(root common.Hash, db Database, cacheLimit uint16) (*SimpleTrie, error) } // Iterator returns an iterator over all mappings in the trie. -func (t *SimpleTrie) Iterator() *Iterator { +func (t *Trie) Iterator() *Iterator { return NewIterator(t) } -func (t *SimpleTrie) NodeIterator() *NodeIterator { - return NewNodeIterator(t) -} - // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. -func (t *SimpleTrie) Get(key []byte) []byte { +func (t *Trie) Get(key []byte) []byte { res, err := t.TryGet(key) if err != nil && glog.V(logger.Error) { glog.Errorf("Unhandled trie error: %v", err) @@ -154,7 +149,7 @@ func (t *SimpleTrie) Get(key []byte) []byte { // TryGet returns the value for key stored in the trie. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. -func (t *SimpleTrie) TryGet(key []byte) ([]byte, error) { +func (t *Trie) TryGet(key []byte) ([]byte, error) { key = compactHexDecode(key) value, newroot, didResolve, err := t.tryGet(t.root, key, 0) if err == nil && didResolve { @@ -163,7 +158,7 @@ func (t *SimpleTrie) TryGet(key []byte) ([]byte, error) { return value, err } -func (t *SimpleTrie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { +func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { switch n := (origNode).(type) { case nil: return nil, nil, false, nil @@ -207,7 +202,7 @@ func (t *SimpleTrie) tryGet(origNode node, key []byte, pos int) (value []byte, n // // The value bytes must not be modified by the caller while they are // stored in the trie. -func (t *SimpleTrie) Update(key, value []byte) { +func (t *Trie) Update(key, value []byte) { if err := t.TryUpdate(key, value); err != nil && glog.V(logger.Error) { glog.Errorf("Unhandled trie error: %v", err) } @@ -221,7 +216,7 @@ func (t *SimpleTrie) Update(key, value []byte) { // stored in the trie. // // If a node was not found in the database, a MissingNodeError is returned. -func (t *SimpleTrie) TryUpdate(key, value []byte) error { +func (t *Trie) TryUpdate(key, value []byte) error { k := compactHexDecode(key) if len(value) != 0 { _, n, err := t.insert(t.root, nil, k, valueNode(value)) @@ -239,7 +234,7 @@ func (t *SimpleTrie) TryUpdate(key, value []byte) error { return nil } -func (t *SimpleTrie) insert(n node, prefix, key []byte, value node) (bool, node, error) { +func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) { if len(key) == 0 { if v, ok := n.(valueNode); ok { return !bytes.Equal(v, value.(valueNode)), value, nil @@ -309,7 +304,7 @@ func (t *SimpleTrie) insert(n node, prefix, key []byte, value node) (bool, node, } // Delete removes any existing value for key from the trie. -func (t *SimpleTrie) Delete(key []byte) { +func (t *Trie) Delete(key []byte) { if err := t.TryDelete(key); err != nil && glog.V(logger.Error) { glog.Errorf("Unhandled trie error: %v", err) } @@ -317,7 +312,7 @@ func (t *SimpleTrie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. -func (t *SimpleTrie) TryDelete(key []byte) error { +func (t *Trie) TryDelete(key []byte) error { k := compactHexDecode(key) _, n, err := t.delete(t.root, nil, k) if err != nil { @@ -330,7 +325,7 @@ func (t *SimpleTrie) TryDelete(key []byte) error { // delete returns the new root of the trie with key deleted. // It reduces the trie to minimal form by simplifying // nodes on the way up after deleting recursively. -func (t *SimpleTrie) delete(n node, prefix, key []byte) (bool, node, error) { +func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { switch n := n.(type) { case *shortNode: matchlen := prefixLen(key, n.Key) @@ -446,14 +441,14 @@ func concat(s1 []byte, s2 ...byte) []byte { return r } -func (t *SimpleTrie) resolve(n node, prefix, suffix []byte) (node, error) { +func (t *Trie) resolve(n node, prefix, suffix []byte) (node, error) { if n, ok := n.(hashNode); ok { return t.resolveHash(n, prefix, suffix) } return n, nil } -func (t *SimpleTrie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { +func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) { cacheMissCounter.Inc(1) enc, err := t.db.Get(n) @@ -472,11 +467,11 @@ func (t *SimpleTrie) resolveHash(n hashNode, prefix, suffix []byte) (node, error // Root returns the root hash of the trie. // Deprecated: use Hash instead. -func (t *SimpleTrie) Root() []byte { return t.Hash().Bytes() } +func (t *Trie) Root() []byte { return t.Hash().Bytes() } // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. -func (t *SimpleTrie) Hash() common.Hash { +func (t *Trie) Hash() common.Hash { hash, cached, _ := t.hashRoot(nil) t.root = cached return common.BytesToHash(hash.(hashNode)) @@ -487,7 +482,7 @@ func (t *SimpleTrie) Hash() common.Hash { // // Committing flushes nodes from memory. // Subsequent Get calls will load nodes from the database. -func (t *SimpleTrie) Commit() (root common.Hash, err error) { +func (t *Trie) Commit() (root common.Hash, err error) { if t.db == nil { panic("Commit called on trie with nil database") } @@ -501,7 +496,7 @@ func (t *SimpleTrie) Commit() (root common.Hash, err error) { // load nodes from the trie's database. Calling code must ensure that // the changes made to db are written back to the trie's attached // database before using the trie. -func (t *SimpleTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { +func (t *Trie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { hash, cached, err := t.hashRoot(db) if err != nil { return (common.Hash{}), err @@ -511,7 +506,7 @@ func (t *SimpleTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { return common.BytesToHash(hash.(hashNode)), nil } -func (t *SimpleTrie) hashRoot(db DatabaseWriter) (node, node, error) { +func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) { if t.root == nil { return hashNode(emptyRoot.Bytes()), nil, nil } diff --git a/trie/trie_test.go b/trie/trie_test.go index fd641d8e10..bb2861cf19 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -38,14 +38,14 @@ func init() { } // Used for testing -func newEmpty() *SimpleTrie { +func newEmpty() *Trie { db, _ := ethdb.NewMemDatabase() trie, _ := New(common.Hash{}, db, 0) return trie } func TestEmptyTrie(t *testing.T) { - var trie SimpleTrie + var trie Trie res := trie.Hash() exp := emptyRoot if res != common.Hash(exp) { @@ -54,7 +54,7 @@ func TestEmptyTrie(t *testing.T) { } func TestNull(t *testing.T) { - var trie SimpleTrie + var trie Trie key := make([]byte, 32) value := common.FromHex("0x823140710bf13990e4500136726d8b55") trie.Update(key, value) @@ -547,7 +547,7 @@ func benchGet(b *testing.B, commit bool) { } } -func benchUpdate(b *testing.B, e binary.ByteOrder) *SimpleTrie { +func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { trie := newEmpty() k := make([]byte, 32) for i := 0; i < b.N; i++ { From de5a818baa29ded0826d3dfced09ae633284f033 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Tue, 25 Oct 2016 10:58:44 +0100 Subject: [PATCH 05/16] core, miner, trie: Integrate directcache with statedb --- core/blockchain.go | 5 +++++ core/chain_makers.go | 2 +- core/state/iterator_test.go | 2 +- core/state/statedb.go | 34 +++++++++++++++++++++++----------- core/state_processor.go | 2 +- miner/worker.go | 2 +- trie/directcache.go | 11 ++++++++--- trie/trie_test.go | 6 +++--- 8 files changed, 43 insertions(+), 21 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index c5b304ff35..0167faf41a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -545,6 +545,11 @@ func (self *BlockChain) GetBlockByNumber(number uint64) *types.Block { return self.GetBlock(hash, number) } +// IsCanonChainBlock checks whether the given block is in the current canonical chain. +func (self *BlockChain) IsCanonChainBlock(number uint64, hash common.Hash) bool { + return number < uint64(self.currentBlock.Number().Int64()) && GetCanonicalHash(self.chainDb, number) == hash +} + // [deprecated by eth/62] // GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors. func (self *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) { diff --git a/core/chain_makers.go b/core/chain_makers.go index e3ad9cda08..b361a69a31 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -105,7 +105,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { if b.gasPool == nil { b.SetCoinbase(common.Address{}) } - b.statedb.StartRecord(tx.Hash(), common.Hash{}, len(b.txs)) + b.statedb.SetTxContext(common.Hash{}, b.header.Number.Uint64(), tx.Hash(), len(b.txs), nil) receipt, _, _, err := ApplyTransaction(MakeChainConfig(), nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed, vm.Config{}) if err != nil { panic(err) diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index aa05c5dfe7..dc16523eff 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -47,7 +47,7 @@ func TestNodeIteratorCoverage(t *testing.T) { } } for _, key := range db.(*ethdb.MemDatabase).Keys() { - if bytes.HasPrefix(key, []byte("secure-key-")) { + if bytes.HasPrefix(key, []byte("secure-key-")) || bytes.HasPrefix(key, CachePrefix) { continue } if _, ok := hashes[common.BytesToHash(key)]; !ok { diff --git a/core/state/statedb.go b/core/state/statedb.go index eef2f38d50..dc5323f2b1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -41,6 +41,8 @@ var StartingNonce uint64 // Trie cache generation limit after which to evic trie nodes from memory. var MaxTrieCacheGen = uint16(120) +var CachePrefix = []byte("accounthashcache:") + const ( // Number of past tries to keep. This value is chosen such that // reasonable chain reorg depths will hit an existing trie. @@ -94,17 +96,19 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) { if err != nil { return nil, err } + csc, _ := lru.New(codeSizeCacheSize) - return &StateDB{ + ret := &StateDB{ db: db, trie: tr, - storage: trie.NewSecure(tr, db), codeSizeCache: csc, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), - }, nil + } + ret.SetTxContext(common.Hash{}, 0, common.Hash{}, 0, nil) + return ret, nil } // New creates a new statedb by reusing any journalled tries to avoid costly @@ -117,16 +121,17 @@ func (self *StateDB) New(root common.Hash) (*StateDB, error) { if err != nil { return nil, err } - return &StateDB{ + ret := &StateDB{ db: self.db, trie: tr, - storage: trie.NewSecure(tr, self.db), codeSizeCache: self.codeSizeCache, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), - }, nil + } + ret.SetTxContext(common.Hash{}, 0, common.Hash{}, 0, nil) + return ret, nil } // Reset clears out all emphemeral state objects from the state db, but keeps @@ -140,7 +145,6 @@ func (self *StateDB) Reset(root common.Hash) error { return err } self.trie = tr - self.storage = trie.NewSecure(tr, self.db) self.stateObjects = make(map[common.Address]*StateObject) self.stateObjectsDirty = make(map[common.Address]struct{}) self.thash = common.Hash{} @@ -149,6 +153,8 @@ func (self *StateDB) Reset(root common.Hash) error { self.logs = make(map[common.Hash]vm.Logs) self.logSize = 0 self.clearJournalAndRefund() + self.SetTxContext(common.Hash{}, 0, common.Hash{}, 0, nil) + return nil } @@ -177,10 +183,16 @@ func (self *StateDB) pushTrie(t *trie.Trie) { } } -func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) { - self.thash = thash - self.bhash = bhash - self.txIndex = ti +func (self *StateDB) SetTxContext(blockHash common.Hash, blockNum uint64, txHash common.Hash, txIndex int, validator trie.CacheValidator) { + self.bhash = blockHash + self.thash = txHash + self.txIndex = txIndex + + if validator == nil { + validator = &trie.NullCacheValidator{} + } + storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, validator, true) + self.storage = trie.NewSecure(storage, self.db) } func (self *StateDB) AddLog(log *vm.Log) { diff --git a/core/state_processor.go b/core/state_processor.go index fd8e9762e9..8722e3c342 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -71,7 +71,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - statedb.StartRecord(tx.Hash(), block.Hash(), i) + statedb.SetTxContext(block.Hash(), block.NumberU64(), tx.Hash(), i, p.bc) receipt, logs, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg) if err != nil { return nil, nil, totalUsedGas, err diff --git a/miner/worker.go b/miner/worker.go index 89064c3b95..7826605238 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -583,7 +583,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB continue } // Start executing the transaction - env.state.StartRecord(tx.Hash(), common.Hash{}, env.tcount) + env.state.SetTxContext(common.Hash{}, env.header.Number.Uint64(), tx.Hash(), env.tcount, bc) err, logs := env.commitTransaction(tx, bc, gp) switch { diff --git a/trie/directcache.go b/trie/directcache.go index 6f87ba5ef8..25d3d6e305 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -52,13 +52,17 @@ type DirectCache struct { dirty map[string]bool } -func NewDirectCache(s Storage, db Database, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache { +type NullCacheValidator struct {} + +func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bool { + return false +} + +func NewDirectCache(s Storage, db Database, keyPrefix []byte, validator CacheValidator, complete bool) *DirectCache { return &DirectCache{ storage: s, db: db, keyPrefix: keyPrefix, - blockNum: blockNum, - blockHash: blockHash, validator: validator, complete: complete, dirty: make(map[string]bool), @@ -83,6 +87,7 @@ func (dc *DirectCache) Get(key []byte) []byte { } func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { + return dc.storage.TryGet(key) start := time.Now() // Use the underlying object for dirty keys diff --git a/trie/trie_test.go b/trie/trie_test.go index bb2861cf19..0e11bf931d 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -583,14 +583,14 @@ func tempDB() (string, Database) { return dir, db } -func getString(trie Trie, k string) []byte { +func getString(trie *Trie, k string) []byte { return trie.Get([]byte(k)) } -func updateString(trie Trie, k, v string) { +func updateString(trie *Trie, k, v string) { trie.Update([]byte(k), []byte(v)) } -func deleteString(trie Trie, k string) { +func deleteString(trie *Trie, k string) { trie.Delete([]byte(k)) } From 5b73f9aacdcd6bfe6f9db6c9462544db09bcc113 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Tue, 25 Oct 2016 11:05:59 +0100 Subject: [PATCH 06/16] trie, light: Rename Storage to PersistentMap --- core/state/statedb.go | 2 +- light/trie.go | 18 ++++++------ trie/directcache.go | 19 ++++++------- trie/secure_trie.go | 66 +++++++++++++++++++++++++------------------ trie/trie.go | 12 -------- 5 files changed, 58 insertions(+), 59 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index dc5323f2b1..013e330a5c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -191,7 +191,7 @@ func (self *StateDB) SetTxContext(blockHash common.Hash, blockNum uint64, txHash if validator == nil { validator = &trie.NullCacheValidator{} } - storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, validator, true) + storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, &trie.NullCacheValidator{}, true) self.storage = trie.NewSecure(storage, self.db) } diff --git a/light/trie.go b/light/trie.go index 9e2d067ce6..e526d6b2c6 100644 --- a/light/trie.go +++ b/light/trie.go @@ -25,7 +25,7 @@ import ( // LightTrie is an ODR-capable wrapper around trie.SecureTrie type LightTrie struct { - storage *trie.SecureTrie + data *trie.SecureTrie originalRoot common.Hash odr OdrBackend db ethdb.Database @@ -74,16 +74,16 @@ func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error) return err } -func (t *LightTrie) getStorage() (ret *trie.SecureTrie, err error) { - if t.storage == nil { - var tr trie.Storage +func (t *LightTrie) getMap() (ret *trie.SecureTrie, err error) { + if t.data == nil { + var tr trie.PersistentMap tr, err = trie.New(t.originalRoot, t.db, 0) if err != nil { return nil, err } - t.storage = trie.NewSecure(tr, t.db) + t.data = trie.NewSecure(tr, t.db) } - return t.storage, nil + return t.data, nil } // Get returns the value for key stored in the trie. @@ -91,7 +91,7 @@ func (t *LightTrie) getStorage() (ret *trie.SecureTrie, err error) { func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) { err = t.do(ctx, key, func() (err error) { var st *trie.SecureTrie - st, err = t.getStorage() + st, err = t.getMap() if err == nil { res, err = st.TryGet(key) } @@ -109,7 +109,7 @@ func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { err = t.do(ctx, key, func() (err error) { var st *trie.SecureTrie - st, err = t.getStorage() + st, err = t.getMap() if err == nil { err = st.TryUpdate(key, value) } @@ -122,7 +122,7 @@ func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) { err = t.do(ctx, key, func() (err error) { var st *trie.SecureTrie - st, err = t.getStorage() + st, err = t.getMap() if err == nil { err = st.TryDelete(key) } diff --git a/trie/directcache.go b/trie/directcache.go index 25d3d6e305..8beaf666e0 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -42,7 +42,7 @@ type CacheValidator interface { } type DirectCache struct { - storage Storage + data PersistentMap db Database keyPrefix []byte blockNum uint64 @@ -58,9 +58,9 @@ func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bo return false } -func NewDirectCache(s Storage, db Database, keyPrefix []byte, validator CacheValidator, complete bool) *DirectCache { +func NewDirectCache(pm PersistentMap, db Database, keyPrefix []byte, validator CacheValidator, complete bool) *DirectCache { return &DirectCache{ - storage: s, + data: pm, db: db, keyPrefix: keyPrefix, validator: validator, @@ -71,7 +71,7 @@ func NewDirectCache(s Storage, db Database, keyPrefix []byte, validator CacheVal func (dc *DirectCache) Iterator() *Iterator { // Todo: If complete is true, implement an iterator over the DB instead. - return dc.storage.Iterator() + return dc.data.Iterator() } func (dc *DirectCache) makeKey(key []byte) []byte { @@ -87,7 +87,6 @@ func (dc *DirectCache) Get(key []byte) []byte { } func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { - return dc.storage.TryGet(key) start := time.Now() // Use the underlying object for dirty keys @@ -99,7 +98,7 @@ func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { } } - value, err := dc.storage.TryGet(key) + value, err := dc.data.TryGet(key) if err != nil { return value, err } @@ -146,7 +145,7 @@ func (dc *DirectCache) Update(key, value []byte) { func (dc *DirectCache) TryUpdate(key, value []byte) error { dc.dirty[string(key)] = true - return dc.storage.TryUpdate(key, value) + return dc.data.TryUpdate(key, value) } func (dc *DirectCache) Delete(key []byte) { @@ -157,7 +156,7 @@ func (dc *DirectCache) Delete(key []byte) { func (dc *DirectCache) TryDelete(key []byte) error { dc.dirty[string(key)] = true - return dc.storage.TryDelete(key) + return dc.data.TryDelete(key) } func (dc *DirectCache) Commit() (root common.Hash, err error) { @@ -167,7 +166,7 @@ func (dc *DirectCache) Commit() (root common.Hash, err error) { func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error) { directCacheWrites.Inc(int64(len(dc.dirty))) for k, _ := range dc.dirty { - v, err := dc.storage.TryGet([]byte(k)) + v, err := dc.data.TryGet([]byte(k)) if err, ok := err.(*MissingNodeError); err != nil && !ok { return common.Hash{}, err } @@ -176,5 +175,5 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error } } dc.dirty = make(map[string]bool) - return dc.storage.CommitTo(dbw) + return dc.data.CommitTo(dbw) } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index f17704effc..4f8af02cef 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -26,6 +26,18 @@ var secureKeyPrefix = []byte("secure-key-") const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash +type PersistentMap interface { + Iterator() *Iterator + Get(key []byte) []byte + TryGet(key []byte) ([]byte, error) + Update(key, value []byte) + TryUpdate(key, value []byte) error + Delete(key []byte) + TryDelete(key []byte) error + Commit() (root common.Hash, err error) + CommitTo(db DatabaseWriter) (root common.Hash, err error) +} + // SecureTrie wraps a trie with key hashing. In a secure trie, all // access operations hash the key using keccak256. This prevents // calling code from creating long chains of nodes that @@ -37,7 +49,7 @@ const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash // // SecureTrie is not safe for concurrent use. type SecureTrie struct { - storage Storage + data PersistentMap db Database hashKeyBuf [secureKeyLength]byte secKeyBuf [200]byte @@ -45,57 +57,57 @@ type SecureTrie struct { secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch } -// NewSecure creates a secure Storage from an existing Storage. -func NewSecure(s Storage, db Database) *SecureTrie { - if s == nil { - panic("NewSecure called with nil storage") +// NewSecure creates a secure persistent map from an existing map. +func NewSecure(pm PersistentMap, db Database) *SecureTrie { + if pm == nil { + panic("NewSecure called with nil persistent map") } if db == nil { panic("NewSecure called with nil database") } - return &SecureTrie{storage: s, db: db} + return &SecureTrie{data: pm, db: db} } -// Get returns the value for key stored in the storage. +// Get returns the value for key stored in the map. // The value bytes must not be modified by the caller. func (t *SecureTrie) Get(key []byte) []byte { res, err := t.TryGet(key) if err != nil && glog.V(logger.Error) { - glog.Errorf("Unhandled storage error: %v", err) + glog.Errorf("Unhandled persistent map error: %v", err) } return res } -// TryGet returns the value for key stored in the storage. +// TryGet returns the value for key stored in the map. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { - return t.storage.TryGet(t.hashKey(key)) + return t.data.TryGet(t.hashKey(key)) } -// Update associates key with value in the storage. Subsequent calls to +// Update associates key with value in the map. Subsequent calls to // Get will return value. If value has length zero, any existing value -// is deleted from the storage and calls to Get will return nil. +// is deleted from the map and calls to Get will return nil. // // The value bytes must not be modified by the caller while they are -// stored in the storage. +// stored in the map. func (t *SecureTrie) Update(key, value []byte) { if err := t.TryUpdate(key, value); err != nil && glog.V(logger.Error) { - glog.Errorf("Unhandled storage error: %v", err) + glog.Errorf("Unhandled persistent map error: %v", err) } } -// TryUpdate associates key with value in the storage. Subsequent calls to +// TryUpdate associates key with value in the map. Subsequent calls to // Get will return value. If value has length zero, any existing value -// is deleted from the storage and calls to Get will return nil. +// is deleted from the map and calls to Get will return nil. // // The value bytes must not be modified by the caller while they are -// stored in the storage. +// stored in the map. // // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryUpdate(key, value []byte) error { hk := t.hashKey(key) - err := t.storage.TryUpdate(hk, value) + err := t.data.TryUpdate(hk, value) if err != nil { return err } @@ -103,19 +115,19 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { return nil } -// Delete removes any existing value for key from the storage. +// Delete removes any existing value for key from the map. func (t *SecureTrie) Delete(key []byte) { if err := t.TryDelete(key); err != nil && glog.V(logger.Error) { - glog.Errorf("Unhandled storage error: %v", err) + glog.Errorf("Unhandled persistent map error: %v", err) } } -// TryDelete removes any existing value for key from the storage. +// TryDelete removes any existing value for key from the map. // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryDelete(key []byte) error { hk := t.hashKey(key) delete(t.getSecKeyCache(), string(hk)) - return t.storage.TryDelete(hk) + return t.data.TryDelete(hk) } // GetKey returns the sha3 preimage of a hashed key that was @@ -137,11 +149,11 @@ func (t *SecureTrie) Commit() (root common.Hash, err error) { if err := t.CommitPreimages(); err != nil { return common.Hash{}, err } - return t.storage.Commit() + return t.data.Commit() } func (t *SecureTrie) Iterator() *Iterator { - return t.storage.Iterator() + return t.data.Iterator() } // CommitTo writes all nodes and the secure hash pre-images to the given database. @@ -149,12 +161,12 @@ func (t *SecureTrie) Iterator() *Iterator { // // Committing flushes nodes from memory. Subsequent Get calls will load nodes from // the database. Calling code must ensure that the changes made to db are -// written back to the attached database before using the storage. +// written back to the attached database before using the map. func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { if err := t.CommitPreimages(); err != nil { return common.Hash{}, err } - return t.storage.CommitTo(db) + return t.data.CommitTo(db) } func (t *SecureTrie) CommitPreimages() error { @@ -191,7 +203,7 @@ func (t *SecureTrie) hashKey(key []byte) []byte { } // getSecKeyCache returns the current secure key cache, creating a new one if -// ownership changed (i.e. the current secure storage is a copy of another owning +// ownership changed (i.e. the current secure map is a copy of another owning // the actual cache). func (t *SecureTrie) getSecKeyCache() map[string][]byte { if t != t.secKeyCacheOwner { diff --git a/trie/trie.go b/trie/trie.go index e186fc1d7c..2e0ecf9692 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -78,18 +78,6 @@ type DatabaseWriter interface { // Use New to create a trie that sits on top of a database. // // trie is not safe for concurrent use. -type Storage interface { - Iterator() *Iterator - Get(key []byte) []byte - TryGet(key []byte) ([]byte, error) - Update(key, value []byte) - TryUpdate(key, value []byte) error - Delete(key []byte) - TryDelete(key []byte) error - Commit() (root common.Hash, err error) - CommitTo(db DatabaseWriter) (root common.Hash, err error) -} - type Trie struct { root node db Database From c4e5dc3bea1436f884c1728d3628221e5776aa70 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Tue, 25 Oct 2016 15:49:36 +0100 Subject: [PATCH 07/16] core/state, trie: Remove Commit() from PersistentMap interface --- core/state/statedb.go | 2 +- trie/directcache.go | 4 ---- trie/secure_trie.go | 13 ------------- trie/secure_trie_test.go | 6 +++--- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 013e330a5c..dc5323f2b1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -191,7 +191,7 @@ func (self *StateDB) SetTxContext(blockHash common.Hash, blockNum uint64, txHash if validator == nil { validator = &trie.NullCacheValidator{} } - storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, &trie.NullCacheValidator{}, true) + storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, validator, true) self.storage = trie.NewSecure(storage, self.db) } diff --git a/trie/directcache.go b/trie/directcache.go index 8beaf666e0..41a416526f 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -159,10 +159,6 @@ func (dc *DirectCache) TryDelete(key []byte) error { return dc.data.TryDelete(key) } -func (dc *DirectCache) Commit() (root common.Hash, err error) { - return dc.CommitTo(dc.db) -} - func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error) { directCacheWrites.Inc(int64(len(dc.dirty))) for k, _ := range dc.dirty { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 4f8af02cef..359c878d7e 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -34,7 +34,6 @@ type PersistentMap interface { TryUpdate(key, value []byte) error Delete(key []byte) TryDelete(key []byte) error - Commit() (root common.Hash, err error) CommitTo(db DatabaseWriter) (root common.Hash, err error) } @@ -140,18 +139,6 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { return key } -// Commit writes all nodes and the secure hash pre-images to the database. -// Nodes are stored with their sha3 hash as the key. -// -// Committing flushes nodes from memory. Subsequent Get calls will load nodes -// from the database. -func (t *SecureTrie) Commit() (root common.Hash, err error) { - if err := t.CommitPreimages(); err != nil { - return common.Hash{}, err - } - return t.data.Commit() -} - func (t *SecureTrie) Iterator() *Iterator { return t.data.Iterator() } diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index 7b8907deeb..c0154b31b6 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -60,7 +60,7 @@ func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) { trie.Update(key, val) } } - trie.Commit() + trie.CommitTo(db) // Return the generated trie return db, trie, content @@ -110,7 +110,7 @@ func TestSecureGetKey(t *testing.T) { func TestSecureTrieConcurrency(t *testing.T) { // Create an initial trie and copy if for concurrent access - _, trie, _ := makeTestSecureTrie() + db, trie, _ := makeTestSecureTrie() threads := runtime.NumCPU() tries := make([]*SecureTrie, threads) @@ -139,7 +139,7 @@ func TestSecureTrieConcurrency(t *testing.T) { tries[index].Update(key, val) } } - tries[index].Commit() + tries[index].CommitTo(db) }(i) } // Wait for all threads to finish From 199b4de6937d23207767bcc956b1fc6c86d977f7 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Tue, 25 Oct 2016 17:10:52 +0100 Subject: [PATCH 08/16] trie: Write cache misses at commit time instead of immediately. --- trie/directcache.go | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/trie/directcache.go b/trie/directcache.go index 41a416526f..76338ee804 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -89,8 +89,10 @@ func (dc *DirectCache) Get(key []byte) []byte { func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { start := time.Now() + dirty := dc.dirty[string(key)] + // Use the underlying object for dirty keys - if !dc.dirty[string(key)] { + if !dirty { cacheKey := dc.makeKey(key) if cached, ok := dc.getCached(cacheKey); ok { directCacheHitTimer.UpdateSince(start) @@ -103,14 +105,16 @@ func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { return value, err } - // If the cache isn't comprehensive, update it - if !dc.complete && !dc.dirty[string(key)] { - if err := dc.putCache(dc.db, key, value); err != nil && glog.V(logger.Error) { - glog.Errorf("Error updating cache: %v", err) - } + if !dc.dirty[string(key)] { + // Flag the key as dirty so it gets written at commit time + dc.dirty[string(key)] = true + } + + // Don't count fetches of dirty data as cache misses + if !dirty { + directCacheMissTimer.UpdateSince(start) } - directCacheMissTimer.UpdateSince(start) return value, nil } @@ -126,15 +130,8 @@ func (dc *DirectCache) getCached(key []byte) ([]byte, bool) { return nil, false } - return data.Value, dc.validator.IsCanonChainBlock(data.BlockNum, data.BlockHash) -} - -func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { - enc, _ := rlp.EncodeToBytes(cachedValue{value, dc.blockNum, dc.blockHash}) - if err := dbw.Put(append(dc.keyPrefix, key...), enc); err != nil { - return err - } - return nil + canonical := dc.validator.IsCanonChainBlock(data.BlockNum, data.BlockHash) + return data.Value, canonical } func (dc *DirectCache) Update(key, value []byte) { @@ -173,3 +170,11 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error dc.dirty = make(map[string]bool) return dc.data.CommitTo(dbw) } + +func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { + enc, _ := rlp.EncodeToBytes(cachedValue{value, dc.blockNum, dc.blockHash}) + if err := dbw.Put(append(dc.keyPrefix, key...), enc); err != nil { + return err + } + return nil +} From 2f913fbc01d24667bb199493490f6713001cb90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 25 Oct 2016 19:37:15 +0300 Subject: [PATCH 09/16] core/state, eth/downloader, trie: construct direct cache during fast --- core/state/sync.go | 12 ++++++-- core/state/sync_test.go | 16 +++++----- eth/downloader/queue.go | 4 +-- trie/directcache.go | 21 +++++++------ trie/iterator.go | 47 ++++++++++++++++------------ trie/sync.go | 52 +++++++++++++++++++++++++++++-- trie/sync_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 177 insertions(+), 43 deletions(-) diff --git a/core/state/sync.go b/core/state/sync.go index ef2b4b84c5..cc42d4961d 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -32,10 +32,11 @@ import ( type StateSync trie.TrieSync // NewStateSync create a new state trie download scheduler. -func NewStateSync(root common.Hash, database ethdb.Database) *StateSync { +func NewStateSync(number uint64, hash common.Hash, root common.Hash, database ethdb.Database) *StateSync { var syncer *trie.TrieSync - callback := func(leaf []byte, parent common.Hash) error { + callback := func(keys [][]byte, leaf []byte, parent common.Hash) error { + // Try to decode any leaf nodes as accounts var obj struct { Nonce uint64 Balance *big.Int @@ -45,6 +46,13 @@ func NewStateSync(root common.Hash, database ethdb.Database) *StateSync { if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil { return err } + // Populate the direct account caches in the database + for _, key := range keys { + if err := trie.WriteDirectCache(CachePrefix, key, leaf, number, hash, database); err != nil { + return err + } + } + // Schedule downloading all the dependencies of the account syncer.AddSubTrie(obj.Root, 64, parent, nil) syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent) diff --git a/core/state/sync_test.go b/core/state/sync_test.go index 949df7301f..895bf5349b 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -47,8 +47,8 @@ func makeTestState() (ethdb.Database, common.Hash, []*testAccount) { obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) acc := &testAccount{address: common.BytesToAddress([]byte{i})} - obj.AddBalance(big.NewInt(int64(11 * i))) - acc.balance = big.NewInt(int64(11 * i)) + obj.AddBalance(big.NewInt(11 * int64(i))) + acc.balance = big.NewInt(11 * int64(i)) obj.SetNonce(uint64(42 * i)) acc.nonce = uint64(42 * i) @@ -110,7 +110,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { func TestEmptyStateSync(t *testing.T) { empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") db, _ := ethdb.NewMemDatabase() - if req := NewStateSync(empty, db).Missing(1); len(req) != 0 { + if req := NewStateSync(0, common.Hash{}, empty, db).Missing(1); len(req) != 0 { t.Errorf("content requested for empty state: %v", req) } } @@ -126,7 +126,7 @@ func testIterativeStateSync(t *testing.T, batch int) { // Create a destination state and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewStateSync(srcRoot, dstDb) + sched := NewStateSync(0, common.Hash{}, srcRoot, dstDb) queue := append([]common.Hash{}, sched.Missing(batch)...) for len(queue) > 0 { @@ -155,7 +155,7 @@ func TestIterativeDelayedStateSync(t *testing.T) { // Create a destination state and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewStateSync(srcRoot, dstDb) + sched := NewStateSync(0, common.Hash{}, srcRoot, dstDb) queue := append([]common.Hash{}, sched.Missing(0)...) for len(queue) > 0 { @@ -189,7 +189,7 @@ func testIterativeRandomStateSync(t *testing.T, batch int) { // Create a destination state and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewStateSync(srcRoot, dstDb) + sched := NewStateSync(0, common.Hash{}, srcRoot, dstDb) queue := make(map[common.Hash]struct{}) for _, hash := range sched.Missing(batch) { @@ -226,7 +226,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { // Create a destination state and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewStateSync(srcRoot, dstDb) + sched := NewStateSync(0, common.Hash{}, srcRoot, dstDb) queue := make(map[common.Hash]struct{}) for _, hash := range sched.Missing(0) { @@ -268,7 +268,7 @@ func TestIncompleteStateSync(t *testing.T) { // Create a destination state and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewStateSync(srcRoot, dstDb) + sched := NewStateSync(0, common.Hash{}, srcRoot, dstDb) added := []common.Hash{} queue := append([]common.Hash{}, sched.Missing(1)...) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index fd239f7e41..5bff27c0b6 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -390,7 +390,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header { if q.mode == FastSync && header.Number.Uint64() == q.fastSyncPivot { // Pivoting point of the fast sync, retrieve the state tries q.stateSchedLock.Lock() - q.stateScheduler = state.NewStateSync(header.Root, q.stateDatabase) + q.stateScheduler = state.NewStateSync(header.Number.Uint64(), header.Hash(), header.Root, q.stateDatabase) q.stateSchedLock.Unlock() } inserts = append(inserts, header) @@ -1141,6 +1141,6 @@ func (q *queue) Prepare(offset uint64, mode SyncMode, pivot uint64, head *types. // If long running fast sync, also start up a head stateretrieval immediately if mode == FastSync && pivot > 0 { - q.stateScheduler = state.NewStateSync(head.Root, q.stateDatabase) + q.stateScheduler = state.NewStateSync(head.Number.Uint64(), head.Hash(), head.Root, q.stateDatabase) } } diff --git a/trie/directcache.go b/trie/directcache.go index 76338ee804..c6f0812722 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -52,7 +52,7 @@ type DirectCache struct { dirty map[string]bool } -type NullCacheValidator struct {} +type NullCacheValidator struct{} func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bool { return false @@ -60,12 +60,12 @@ func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bo func NewDirectCache(pm PersistentMap, db Database, keyPrefix []byte, validator CacheValidator, complete bool) *DirectCache { return &DirectCache{ - data: pm, - db: db, + data: pm, + db: db, keyPrefix: keyPrefix, validator: validator, - complete: complete, - dirty: make(map[string]bool), + complete: complete, + dirty: make(map[string]bool), } } @@ -172,9 +172,10 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error } func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { - enc, _ := rlp.EncodeToBytes(cachedValue{value, dc.blockNum, dc.blockHash}) - if err := dbw.Put(append(dc.keyPrefix, key...), enc); err != nil { - return err - } - return nil + return WriteDirectCache(dc.keyPrefix, key, value, dc.blockNum, dc.blockHash, dbw) +} + +func WriteDirectCache(prefix, key, value []byte, number uint64, hash common.Hash, dbw DatabaseWriter) error { + enc, _ := rlp.EncodeToBytes(cachedValue{value, number, hash}) + return dbw.Put(append(prefix, key...), enc) } diff --git a/trie/iterator.go b/trie/iterator.go index 4b5f42890a..56759ed681 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -74,20 +74,22 @@ func (it *Iterator) makeKey() []byte { // nodeIteratorState represents the iteration state at one particular node of the // trie, which can be resumed at a later invocation. type nodeIteratorState struct { - hash common.Hash // Hash of the node being iterated (nil if not standalone) - node node // Trie node being iterated - parent common.Hash // Hash of the first full ancestor node (nil if current is the root) - child int // Child to be processed next + hash common.Hash // Hash of the node being iterated (nil if not standalone) + node node // Trie node being iterated + parent common.Hash // Hash of the first full ancestor node (nil if current is the root) + nibbles []byte // Key nibbles leading up to this node for key reconstruction + child int // Child to be processed next } // NodeIterator is an iterator to traverse the trie post-order. type NodeIterator struct { - trie *Trie // Trie being iterated + trie *Trie // Trie being iterated stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state Hash common.Hash // Hash of the current node being iterated (nil if not standalone) Node node // Current node being iterated (internal representation) Parent common.Hash // Hash of the first full ancestor node (nil if current is the root) + Nibbles []byte // Key nibbles leading up to this node for key reconstruction Leaf bool // Flag whether the current node is a value (data) node LeafBlob []byte // Data blob contained within a leaf (otherwise nil) @@ -155,11 +157,16 @@ func (it *NodeIterator) step() error { } for parent.child++; parent.child < len(node.Children); parent.child++ { if current := node.Children[parent.child]; current != nil { + nibbles := parent.nibbles + if parent.child < 16 { + nibbles = append(nibbles, byte(parent.child)) + } it.stack = append(it.stack, &nodeIteratorState{ - hash: common.BytesToHash(node.flags.hash), - node: current, - parent: ancestor, - child: -1, + hash: common.BytesToHash(node.flags.hash), + node: current, + parent: ancestor, + nibbles: nibbles, + child: -1, }) break } @@ -171,10 +178,11 @@ func (it *NodeIterator) step() error { } parent.child++ it.stack = append(it.stack, &nodeIteratorState{ - hash: common.BytesToHash(node.flags.hash), - node: node.Val, - parent: ancestor, - child: -1, + hash: common.BytesToHash(node.flags.hash), + node: node.Val, + parent: ancestor, + nibbles: append(parent.nibbles, node.Key...), + child: -1, }) } else if hash, ok := parent.node.(hashNode); ok { // Hash node, resolve the hash child from the database, then the node itself @@ -188,10 +196,11 @@ func (it *NodeIterator) step() error { return err } it.stack = append(it.stack, &nodeIteratorState{ - hash: common.BytesToHash(hash), - node: node, - parent: ancestor, - child: -1, + hash: common.BytesToHash(hash), + node: node, + parent: ancestor, + nibbles: parent.nibbles, + child: -1, }) } else { break @@ -207,7 +216,7 @@ func (it *NodeIterator) step() error { // The method returns whether there are any more data left for inspection. func (it *NodeIterator) retrieve() bool { // Clear out any previously set values - it.Hash, it.Node, it.Parent, it.Leaf, it.LeafBlob = common.Hash{}, nil, common.Hash{}, false, nil + it.Hash, it.Node, it.Parent, it.Nibbles, it.Leaf, it.LeafBlob = common.Hash{}, nil, common.Hash{}, nil, false, nil // If the iteration's done, return no available data if it.trie == nil { @@ -216,7 +225,7 @@ func (it *NodeIterator) retrieve() bool { // Otherwise retrieve the current node and resolve leaf accessors state := it.stack[len(it.stack)-1] - it.Hash, it.Node, it.Parent = state.hash, state.node, state.parent + it.Hash, it.Node, it.Parent, it.Nibbles = state.hash, state.node, state.parent, state.nibbles if value, ok := it.Node.(valueNode); ok { it.Leaf, it.LeafBlob = true, []byte(value) } diff --git a/trie/sync.go b/trie/sync.go index 58b8a1fb6c..4272d20358 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -36,12 +36,40 @@ type request struct { raw bool // Whether this is a raw entry (code) or a trie node parents []*request // Parent state nodes referencing this entry (notify all upon completion) + nibbles [][]byte // Trie path nibbles that accumulate up to the key at the leaf (paired with parents) depth int // Depth level within the trie the node is located to prioritise DFS deps int // Number of dependencies before allowed to commit this node callback TrieSyncLeafCallback // Callback to invoke if a leaf node it reached on this branch } +// keys traverses the request ancestry tree, reconstructing the key paths leading +// to the current request through the nibbles represented by each node. +func (r *request) keys(suffix []byte) [][]byte { + keys := r.paths(suffix) + for i := 0; i < len(keys); i++ { + keys[i] = compactHexEncode(keys[i]) + } + return keys +} + +// paths traverses the request ancestry tree, reconstructing the key paths leading +// to the current request through the nibbles represented by each node. +func (r *request) paths(suffix []byte) [][]byte { + // A root level request just returns the trailing suffix + if len(r.parents) == 0 { + return [][]byte{suffix} + } + // Otherwise collect all the ancestor paths, and append the current one + var keys [][]byte + for i, parent := range r.parents { + for _, key := range parent.paths(r.nibbles[i]) { + keys = append(keys, append(key, suffix...)) + } + } + return keys +} + // SyncResult is a simple list to return missing nodes along with their request // hashes. type SyncResult struct { @@ -52,7 +80,7 @@ type SyncResult struct { // TrieSyncLeafCallback is a callback type invoked when a trie sync reaches a // leaf node. It's used by state syncing to check if the leaf node requires some // further data syncing. -type TrieSyncLeafCallback func(leaf []byte, parent common.Hash) error +type TrieSyncLeafCallback func(keys [][]byte, leaf []byte, parent common.Hash) error // TrieSync is the main state trie synchronisation scheduler, which provides yet // unknown trie hashes to retrieve, accepts node data associated with said hashes @@ -192,6 +220,7 @@ func (s *TrieSync) schedule(req *request) { // If we're already requesting this node, add a new reference and stop if old, ok := s.requests[req.hash]; ok { old.parents = append(old.parents, req.parents...) + old.nibbles = append(old.nibbles, req.nibbles...) return } // Schedule the request for future retrieval @@ -204,6 +233,7 @@ func (s *TrieSync) schedule(req *request) { func (s *TrieSync) children(req *request, object node) ([]*request, error) { // Gather all the children of the node, irrelevant whether known or not type child struct { + path []byte node node depth int } @@ -212,13 +242,19 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { switch node := (object).(type) { case *shortNode: children = []child{{ + path: node.Key, node: node.Val, depth: req.depth + len(node.Key), }} case *fullNode: for i := 0; i < 17; i++ { if node.Children[i] != nil { + var path []byte + if i < 16 { + path = []byte{byte(i)} + } children = append(children, child{ + path: path, node: node.Children[i], depth: req.depth + 1, }) @@ -233,7 +269,7 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { // Notify any external watcher of a new key/value node if req.callback != nil { if node, ok := (child.node).(valueNode); ok { - if err := req.callback(node, req.hash); err != nil { + if err := req.callback(req.keys(child.path), node, req.hash); err != nil { return nil, err } } @@ -243,12 +279,24 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { // Try to resolve the node from the local database blob, _ := s.database.Get(node) if local, err := decodeNode(node[:], blob, 0); local != nil && err == nil { + // Local node found, iterate and invoke the leaf callback for all subleaves + if req.callback != nil { + trie, _ := New(common.BytesToHash(node[:]), s.database, 0) + + it := NewNodeIterator(trie) + for it.Next() { + if it.Leaf { + req.callback(req.keys(append(child.path, it.Nibbles...)), nil, req.hash) + } + } + } continue } // Locally unknown node, schedule for retrieval requests = append(requests, &request{ hash: common.BytesToHash(node), parents: []*request{req}, + nibbles: [][]byte{child.path}, depth: child.depth, callback: req.callback, }) diff --git a/trie/sync_test.go b/trie/sync_test.go index a28e81adb7..f0a5acebb2 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -55,6 +55,28 @@ func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) { return db, trie, content } +// makeRepetitiveTestTrie creates a test trie to test node-wise reconstruction, +// where all values are the same, forcing large parts of the trie to share nodes. +func makeRepetitiveTestTrie() (ethdb.Database, *Trie, map[string][]byte) { + // Create an empty trie + db, _ := ethdb.NewMemDatabase() + trie, _ := New(common.Hash{}, db, 0) + + // Fill it with some arbitrary data + content := make(map[string][]byte) + for i := byte(0); i < 255; i++ { + for j := byte(0); j < 255; j++ { + key, val := common.LeftPadBytes([]byte{j, i}, 32), common.LeftPadBytes(nil, 32) + content[string(key)] = val + trie.Update(key, val) + } + } + trie.Commit() + + // Return the generated trie + return db, trie, content +} + // checkTrieContents cross references a reconstructed trie with an expected data // content map. func checkTrieContents(t *testing.T, db Database, root []byte, content map[string][]byte) { @@ -331,3 +353,49 @@ func TestIncompleteTrieSync(t *testing.T) { dstDb.Put(key, value) } } + +// Tests that if a leaf callback is specified, all leaf nodes are invoked with +// it, even if sync short circuits with existing local nodes. +func TestAllLeafCallbackTrieSync(t *testing.T) { + // Create a random trie to copy + srcDb, srcTrie, srcData := makeRepetitiveTestTrie() + + // Create a callback to accumulate all keys for later verification + leaves, count := make(map[string]struct{}), 0 + callback := func(keys [][]byte, leaf []byte, parent common.Hash) error { + for _, key := range keys { + leaves[string(key)] = struct{}{} + count++ + } + return nil + } + // Create a destination trie and sync with the scheduler + dstDb, _ := ethdb.NewMemDatabase() + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, callback) + + queue := append([]common.Hash{}, sched.Missing(1)...) + for len(queue) > 0 { + results := make([]SyncResult, len(queue)) + for i, hash := range queue { + data, err := srcDb.Get(hash.Bytes()) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", hash, err) + } + results[i] = SyncResult{hash, data} + } + if index, err := sched.Process(results); err != nil { + t.Fatalf("failed to process result #%d: %v", index, err) + } + queue = append(queue[:0], sched.Missing(1)...) + } + // Cross check that the two tries are in sync + checkTrieContents(t, dstDb, srcTrie.Root(), srcData) + + // Ensure the leaf callback was invoked for all leaves, including cached ones + if len(leaves) != len(srcData) { + t.Errorf("Leaf callback count mismatch: have %v, want %v", len(leaves), len(srcData)) + } + if len(leaves) != count { + t.Errorf("Duplicate leaf callbacks: have %v, want %v", count, len(leaves)) + } +} From cb3e60601226f2c76d714bcb81bf8c3b235907ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 25 Oct 2016 19:37:15 +0300 Subject: [PATCH 10/16] cmd/geth, core/state, trie: don't iterate known subtries --- cmd/geth/chaincmd.go | 9 ++++++--- core/state/sync.go | 4 ++-- trie/directcache.go | 38 +++++++++++++++++++++++++++++++++++++- trie/sync.go | 26 +++++++++++++++++++++----- trie/sync_test.go | 16 ++++++++-------- 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 20e718a739..99cf35a7c7 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -118,8 +118,11 @@ func importChain(ctx *cli.Context) error { utils.Fatalf("Failed to read database stats: %v", err) } fmt.Println(stats) - fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses()) - fmt.Printf("Trie cache unloads: %d\n\n", trie.CacheUnloads()) + fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses()) + fmt.Printf("Trie cache unloads: %d\n", trie.CacheUnloads()) + fmt.Printf("Direct cache reads: %d\n", trie.DirectCacheReads()) + fmt.Printf("Direct cache writes: %d\n", trie.DirectCacheWrites()) + fmt.Printf("Direct cache misses: %d\n\n", trie.DirectCacheMisses()) // Print the memory statistics used by the importing mem := new(runtime.MemStats) @@ -308,7 +311,7 @@ func buildCache(ctx *cli.Context) error { } i += 1 - if i % 10000 == 0 { + if i%10000 == 0 { fmt.Printf("Processed %d accounts, at %v\n", i, common.ToHex(iter.Key)) } } diff --git a/core/state/sync.go b/core/state/sync.go index cc42d4961d..28c0fb7731 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -53,12 +53,12 @@ func NewStateSync(number uint64, hash common.Hash, root common.Hash, database et } } // Schedule downloading all the dependencies of the account - syncer.AddSubTrie(obj.Root, 64, parent, nil) + syncer.AddSubTrie(obj.Root, 64, parent, nil, nil) syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent) return nil } - syncer = trie.NewTrieSync(root, database, callback) + syncer = trie.NewTrieSync(root, database, callback, CachePrefix) return (*StateSync)(syncer) } diff --git a/trie/directcache.go b/trie/directcache.go index c6f0812722..1ca04f2284 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -30,6 +30,27 @@ var directCacheWrites = metrics.NewCounter("directcache/writes") var directCacheHitTimer = metrics.NewTimer("directcache/timer/hits") var directCacheMissTimer = metrics.NewTimer("directcache/timer/misses") +// DirectCacheReads retrieves a global counter measuring the number of direct +// cache reads from the disk since process startup. This isn't useful for anything +// apart from trie debugging purposes. +func DirectCacheReads() int64 { + return directCacheHitTimer.Count() + directCacheMissTimer.Count() +} + +// DirectCacheWrites retrieves a global counter measuring the number of direct +// cache writes from the disk since process startup. This isn't useful for anything +// apart from trie debugging purposes. +func DirectCacheWrites() int64 { + return directCacheWrites.Count() +} + +// DirectCacheMisses retrieves a global counter measuring the number of direct +// cache writes from the disk since process startup. This isn't useful for anything +// apart from trie debugging purposes. +func DirectCacheMisses() int64 { + return directCacheMissTimer.Count() +} + type cachedValue struct { Value []byte BlockNum uint64 @@ -157,7 +178,6 @@ func (dc *DirectCache) TryDelete(key []byte) error { } func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error) { - directCacheWrites.Inc(int64(len(dc.dirty))) for k, _ := range dc.dirty { v, err := dc.data.TryGet([]byte(k)) if err, ok := err.(*MissingNodeError); err != nil && !ok { @@ -175,7 +195,23 @@ func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { return WriteDirectCache(dc.keyPrefix, key, value, dc.blockNum, dc.blockHash, dbw) } +// WriteDirectCache places a value node directly into the database along with +// block metadata to validate its relevancy. +// +// The method is meant to be used by code that circumvents the state database +// and its integrated cache, namely during fast sync and database upgrades. func WriteDirectCache(prefix, key, value []byte, number uint64, hash common.Hash, dbw DatabaseWriter) error { + directCacheWrites.Inc(1) enc, _ := rlp.EncodeToBytes(cachedValue{value, number, hash}) return dbw.Put(append(prefix, key...), enc) } + +// GetDirectCache retrieves a value node directly from the database along with +// block metadata to validate its relevancy. +// +// The method is meant to be used by code that circumvents the state database +// and its integrated cache, namely during fast sync and database upgrades. +func GetDirectCache(prefix, key []byte, db Database) ([]byte, error) { + defer func(start time.Time) { directCacheHitTimer.UpdateSince(start) }(time.Now()) + return db.Get(append(prefix, key...)) +} diff --git a/trie/sync.go b/trie/sync.go index 4272d20358..8ab789f4ab 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -41,6 +41,7 @@ type request struct { deps int // Number of dependencies before allowed to commit this node callback TrieSyncLeafCallback // Callback to invoke if a leaf node it reached on this branch + dcPrefix []byte // Database prefix to use for leaf direct caching } // keys traverses the request ancestry tree, reconstructing the key paths leading @@ -92,18 +93,18 @@ type TrieSync struct { } // NewTrieSync creates a new trie data download scheduler. -func NewTrieSync(root common.Hash, database ethdb.Database, callback TrieSyncLeafCallback) *TrieSync { +func NewTrieSync(root common.Hash, database ethdb.Database, callback TrieSyncLeafCallback, dcPrefix []byte) *TrieSync { ts := &TrieSync{ database: database, requests: make(map[common.Hash]*request), queue: prque.New(), } - ts.AddSubTrie(root, 0, common.Hash{}, callback) + ts.AddSubTrie(root, 0, common.Hash{}, callback, dcPrefix) return ts } // AddSubTrie registers a new trie to the sync code, rooted at the designated parent. -func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback TrieSyncLeafCallback) { +func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback TrieSyncLeafCallback, dcPrefix []byte) { // Short circuit if the trie is empty or already known if root == emptyRoot { return @@ -118,6 +119,7 @@ func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, c hash: root, depth: depth, callback: callback, + dcPrefix: dcPrefix, } // If this sub-trie has a designated parent, link them together if parent != (common.Hash{}) { @@ -280,13 +282,26 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { blob, _ := s.database.Get(node) if local, err := decodeNode(node[:], blob, 0); local != nil && err == nil { // Local node found, iterate and invoke the leaf callback for all subleaves - if req.callback != nil { + if req.callback != nil && req.dcPrefix != nil { trie, _ := New(common.BytesToHash(node[:]), s.database, 0) + unknown := false it := NewNodeIterator(trie) for it.Next() { if it.Leaf { - req.callback(req.keys(append(child.path, it.Nibbles...)), nil, req.hash) + // If this branch was already visited, abort iteration + keys := req.keys(append(child.path, it.Nibbles...)) + if !unknown { + if val, err := GetDirectCache(req.dcPrefix, keys[0], s.database); val != nil && err == nil { + break + } + // Branch is unknown, mark as so to avoid repeated db lookups + unknown = true + } + // Otherwise write out all accounts ending in this leaf + if err := req.callback(keys, nil, req.hash); err != nil { + return nil, err + } } } } @@ -299,6 +314,7 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { nibbles: [][]byte{child.path}, depth: child.depth, callback: req.callback, + dcPrefix: req.dcPrefix, }) } } diff --git a/trie/sync_test.go b/trie/sync_test.go index f0a5acebb2..b022f80cbf 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -115,7 +115,7 @@ func TestEmptyTrieSync(t *testing.T) { for i, trie := range []*Trie{emptyA, emptyB} { db, _ := ethdb.NewMemDatabase() - if req := NewTrieSync(common.BytesToHash(trie.Root()), db, nil).Missing(1); len(req) != 0 { + if req := NewTrieSync(common.BytesToHash(trie.Root()), db, nil, nil).Missing(1); len(req) != 0 { t.Errorf("test %d: content requested for empty trie: %v", i, req) } } @@ -132,7 +132,7 @@ func testIterativeTrieSync(t *testing.T, batch int) { // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil, nil) queue := append([]common.Hash{}, sched.Missing(batch)...) for len(queue) > 0 { @@ -161,7 +161,7 @@ func TestIterativeDelayedTrieSync(t *testing.T) { // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil, nil) queue := append([]common.Hash{}, sched.Missing(10000)...) for len(queue) > 0 { @@ -195,7 +195,7 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) { // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil, nil) queue := make(map[common.Hash]struct{}) for _, hash := range sched.Missing(batch) { @@ -232,7 +232,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) { // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil, nil) queue := make(map[common.Hash]struct{}) for _, hash := range sched.Missing(10000) { @@ -275,7 +275,7 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) { // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil, nil) queue := append([]common.Hash{}, sched.Missing(0)...) requested := make(map[common.Hash]struct{}) @@ -311,7 +311,7 @@ func TestIncompleteTrieSync(t *testing.T) { // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil, nil) added := []common.Hash{} queue := append([]common.Hash{}, sched.Missing(1)...) @@ -371,7 +371,7 @@ func TestAllLeafCallbackTrieSync(t *testing.T) { } // Create a destination trie and sync with the scheduler dstDb, _ := ethdb.NewMemDatabase() - sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, callback) + sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, callback, []byte("leaf:")) queue := append([]common.Hash{}, sched.Missing(1)...) for len(queue) > 0 { From 6d4b9987d38f241769c6fd50d9d26c2c363ea61e Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Wed, 26 Oct 2016 11:07:35 +0100 Subject: [PATCH 11/16] core, miner: Add separate SetBlockContext function so mining rewards are cached --- core/blockchain.go | 1 + core/chain_makers.go | 2 +- core/state/statedb.go | 18 ++++++++++-------- core/state_processor.go | 2 +- miner/worker.go | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 0167faf41a..ce1b79a6da 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -922,6 +922,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { reportBlock(block, err) return i, err } + self.stateCache.SetBlockContext(block.Hash(), block.NumberU64(), self) // Process block using the parent state as reference point. receipts, logs, usedGas, err := self.processor.Process(block, self.stateCache, self.config.VmConfig) if err != nil { diff --git a/core/chain_makers.go b/core/chain_makers.go index b361a69a31..a2c31c8dda 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -105,7 +105,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { if b.gasPool == nil { b.SetCoinbase(common.Address{}) } - b.statedb.SetTxContext(common.Hash{}, b.header.Number.Uint64(), tx.Hash(), len(b.txs), nil) + b.statedb.SetTxContext(tx.Hash(), len(b.txs)) receipt, _, _, err := ApplyTransaction(MakeChainConfig(), nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed, vm.Config{}) if err != nil { panic(err) diff --git a/core/state/statedb.go b/core/state/statedb.go index dc5323f2b1..a135dbabd4 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -107,7 +107,7 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) { refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), } - ret.SetTxContext(common.Hash{}, 0, common.Hash{}, 0, nil) + ret.SetBlockContext(common.Hash{}, 0, nil) return ret, nil } @@ -130,7 +130,7 @@ func (self *StateDB) New(root common.Hash) (*StateDB, error) { refund: new(big.Int), logs: make(map[common.Hash]vm.Logs), } - ret.SetTxContext(common.Hash{}, 0, common.Hash{}, 0, nil) + ret.SetBlockContext(common.Hash{}, 0, nil) return ret, nil } @@ -153,8 +153,8 @@ func (self *StateDB) Reset(root common.Hash) error { self.logs = make(map[common.Hash]vm.Logs) self.logSize = 0 self.clearJournalAndRefund() - self.SetTxContext(common.Hash{}, 0, common.Hash{}, 0, nil) - + self.SetBlockContext(common.Hash{}, 0, nil) + self.SetTxContext(common.Hash{}, 0) return nil } @@ -183,11 +183,8 @@ func (self *StateDB) pushTrie(t *trie.Trie) { } } -func (self *StateDB) SetTxContext(blockHash common.Hash, blockNum uint64, txHash common.Hash, txIndex int, validator trie.CacheValidator) { +func (self *StateDB) SetBlockContext(blockHash common.Hash, blockNum uint64, validator trie.CacheValidator) { self.bhash = blockHash - self.thash = txHash - self.txIndex = txIndex - if validator == nil { validator = &trie.NullCacheValidator{} } @@ -195,6 +192,11 @@ func (self *StateDB) SetTxContext(blockHash common.Hash, blockNum uint64, txHash self.storage = trie.NewSecure(storage, self.db) } +func (self *StateDB) SetTxContext(txHash common.Hash, txIndex int) { + self.thash = txHash + self.txIndex = txIndex +} + func (self *StateDB) AddLog(log *vm.Log) { self.journal = append(self.journal, addLogChange{txhash: self.thash}) diff --git a/core/state_processor.go b/core/state_processor.go index 8722e3c342..63cdf19d0a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -71,7 +71,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - statedb.SetTxContext(block.Hash(), block.NumberU64(), tx.Hash(), i, p.bc) + statedb.SetTxContext(tx.Hash(), i) receipt, logs, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg) if err != nil { return nil, nil, totalUsedGas, err diff --git a/miner/worker.go b/miner/worker.go index 7826605238..b9a3eaac1d 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -583,7 +583,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB continue } // Start executing the transaction - env.state.SetTxContext(common.Hash{}, env.header.Number.Uint64(), tx.Hash(), env.tcount, bc) + env.state.SetTxContext(tx.Hash(), env.tcount) err, logs := env.commitTransaction(tx, bc, gp) switch { From 5c5de90f0c283a85a55c71eaa45b3f45d78134c8 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Wed, 26 Oct 2016 11:52:32 +0100 Subject: [PATCH 12/16] core, trie: Correctly propagate blocknum/hash, and fix a consensus issue --- core/blockchain.go | 2 +- core/state/statedb.go | 2 +- trie/directcache.go | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index ce1b79a6da..56de73c445 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -547,7 +547,7 @@ func (self *BlockChain) GetBlockByNumber(number uint64) *types.Block { // IsCanonChainBlock checks whether the given block is in the current canonical chain. func (self *BlockChain) IsCanonChainBlock(number uint64, hash common.Hash) bool { - return number < uint64(self.currentBlock.Number().Int64()) && GetCanonicalHash(self.chainDb, number) == hash + return GetCanonicalHash(self.chainDb, number) == hash } // [deprecated by eth/62] diff --git a/core/state/statedb.go b/core/state/statedb.go index a135dbabd4..c0c731eda2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -188,7 +188,7 @@ func (self *StateDB) SetBlockContext(blockHash common.Hash, blockNum uint64, val if validator == nil { validator = &trie.NullCacheValidator{} } - storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, validator, true) + storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, blockNum, blockHash, validator, true) self.storage = trie.NewSecure(storage, self.db) } diff --git a/trie/directcache.go b/trie/directcache.go index 1ca04f2284..9fe736111f 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -79,11 +79,13 @@ func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bo return false } -func NewDirectCache(pm PersistentMap, db Database, keyPrefix []byte, validator CacheValidator, complete bool) *DirectCache { +func NewDirectCache(pm PersistentMap, db Database, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache { return &DirectCache{ data: pm, db: db, keyPrefix: keyPrefix, + blockNum: blockNum, + blockHash: blockHash, validator: validator, complete: complete, dirty: make(map[string]bool), @@ -151,7 +153,7 @@ func (dc *DirectCache) getCached(key []byte) ([]byte, bool) { return nil, false } - canonical := dc.validator.IsCanonChainBlock(data.BlockNum, data.BlockHash) + canonical := dc.blockNum > 0 && data.BlockNum < dc.blockNum && dc.validator.IsCanonChainBlock(data.BlockNum, data.BlockHash) return data.Value, canonical } From 21ce20c35999d25787e8ea2ed03ae5361cfd4471 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Wed, 26 Oct 2016 14:38:18 +0100 Subject: [PATCH 13/16] trie: Add Populate method to DirectCache --- trie/directcache.go | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/trie/directcache.go b/trie/directcache.go index 9fe736111f..bbb2f6e23c 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -17,6 +17,7 @@ package trie import ( + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -26,6 +27,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +var directCacheLock = &sync.Mutex{} + var directCacheWrites = metrics.NewCounter("directcache/writes") var directCacheHitTimer = metrics.NewTimer("directcache/timer/hits") var directCacheMissTimer = metrics.NewTimer("directcache/timer/misses") @@ -180,12 +183,16 @@ func (dc *DirectCache) TryDelete(key []byte) error { } func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error) { + directCacheWrites.Inc(len(dc.dirty)) + directCacheLock.Lock() + defer directCacheLock.Unlock() + for k, _ := range dc.dirty { v, err := dc.data.TryGet([]byte(k)) if err, ok := err.(*MissingNodeError); err != nil && !ok { return common.Hash{}, err } - if err := dc.putCache(dbw, []byte(k), v); err != nil { + if err := writeCacheEntry(dc.keyPrefix, []byte(k), v, dc.blockNum, dc.blockHash, dbw); err != nil { return common.Hash{}, err } } @@ -193,10 +200,33 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error return dc.data.CommitTo(dbw) } -func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { - return WriteDirectCache(dc.keyPrefix, key, value, dc.blockNum, dc.blockHash, dbw) +// Populate iterates over the underlying trie, filling in any unset cache entries. +// After Populate has completed, future DirectCache instances can have `complete` +// set to true, for better efficiency on cache misses. +func (dc *DirectCache) Populate() error { + i := 0 + writes := 0 + it := dc.Iterator() + for it.Next() { + directCacheLock.Lock() + if val, err := dc.db.Get(append(dc.keyPrefix, it.Key...)); err != nil || val == nil { + writes += 1 + if err := writeCacheEntry(dc.keyPrefix, it.Key, it.Value, dc.blockNum, dc.blockHash, dc.db); err != nil { + directCacheLock.Unlock() + return err + } + } + directCacheLock.Unlock() + + i += 1 + if i % 10000 == 0 && glog.V(logger.Info) { + glog.V(logger.Info).Infof("Constructing direct cache: processed %v entries, writing %v", i, writes) + } + } + return nil } + // WriteDirectCache places a value node directly into the database along with // block metadata to validate its relevancy. // @@ -204,6 +234,12 @@ func (dc *DirectCache) putCache(dbw DatabaseWriter, key, value []byte) error { // and its integrated cache, namely during fast sync and database upgrades. func WriteDirectCache(prefix, key, value []byte, number uint64, hash common.Hash, dbw DatabaseWriter) error { directCacheWrites.Inc(1) + directCacheLock.Lock() + defer directCacheLock.Unlock() + return writeCacheEntry(prefix, key, value, number, hash, dbw) +} + +func writeCacheEntry(prefix, key, value []byte, number uint64, hash common.Hash, dbw DatabaseWriter) error { enc, _ := rlp.EncodeToBytes(cachedValue{value, number, hash}) return dbw.Put(append(prefix, key...), enc) } From 32622544967bf93d146ca1135cbb84ee89a3d2a9 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Thu, 27 Oct 2016 14:10:11 +0100 Subject: [PATCH 14/16] core/state, trie: Refactored direct cache backend to be more coherent. --- core/state/iterator_test.go | 2 +- core/state/statedb.go | 14 ++- core/state/sync.go | 13 ++- eth/backend.go | 4 + eth/db_upgrade.go | 7 ++ trie/directcache.go | 191 +++++++++++++++++++++--------------- trie/sync.go | 2 +- 7 files changed, 145 insertions(+), 88 deletions(-) diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index dc16523eff..369f2a9998 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -47,7 +47,7 @@ func TestNodeIteratorCoverage(t *testing.T) { } } for _, key := range db.(*ethdb.MemDatabase).Keys() { - if bytes.HasPrefix(key, []byte("secure-key-")) || bytes.HasPrefix(key, CachePrefix) { + if bytes.HasPrefix(key, []byte("secure-key-")) || bytes.HasPrefix(key, DirectCachePrefix) { continue } if _, ok := hashes[common.BytesToHash(key)]; !ok { diff --git a/core/state/statedb.go b/core/state/statedb.go index c0c731eda2..fe29e318a5 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -41,7 +41,8 @@ var StartingNonce uint64 // Trie cache generation limit after which to evic trie nodes from memory. var MaxTrieCacheGen = uint16(120) -var CachePrefix = []byte("accounthashcache:") +var DirectCachePrefix = []byte("accounthashcache:") +var DirectStateCacheCompleteKey = []byte("directstatecache:complete") const ( // Number of past tries to keep. This value is chosen such that @@ -66,6 +67,7 @@ type StateDB struct { db ethdb.Database trie *trie.Trie storage *trie.SecureTrie + cacheComplete bool // True if we know that the directcache is complete pastTries []*trie.Trie codeSizeCache *lru.Cache @@ -188,7 +190,15 @@ func (self *StateDB) SetBlockContext(blockHash common.Hash, blockNum uint64, val if validator == nil { validator = &trie.NullCacheValidator{} } - storage := trie.NewDirectCache(self.trie, self.db, CachePrefix, blockNum, blockHash, validator, true) + + // Check if cache population has completed + if !self.cacheComplete { + if value, err := self.db.Get(DirectStateCacheCompleteKey); err == nil && value != nil { + self.cacheComplete = true + } + } + + storage := trie.NewDirectCache(self.trie, self.db, DirectCachePrefix, blockNum, blockHash, validator, true) self.storage = trie.NewSecure(storage, self.db) } diff --git a/core/state/sync.go b/core/state/sync.go index 28c0fb7731..83cbc90320 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -47,10 +47,15 @@ func NewStateSync(number uint64, hash common.Hash, root common.Hash, database et return err } // Populate the direct account caches in the database - for _, key := range keys { - if err := trie.WriteDirectCache(CachePrefix, key, leaf, number, hash, database); err != nil { - return err + if err := trie.DirectCacheTransaction(func() error { + for _, key := range keys { + if err := trie.WriteDirectCache(DirectCachePrefix, key, leaf, number, hash, database); err != nil { + return err + } } + return nil + }); err != nil { + return err } // Schedule downloading all the dependencies of the account syncer.AddSubTrie(obj.Root, 64, parent, nil, nil) @@ -58,7 +63,7 @@ func NewStateSync(number uint64, hash common.Hash, root common.Hash, database et return nil } - syncer = trie.NewTrieSync(root, database, callback, CachePrefix) + syncer = trie.NewTrieSync(root, database, callback, DirectCachePrefix) return (*StateSync)(syncer) } diff --git a/eth/backend.go b/eth/backend.go index c4a883c9e9..7eba71718e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -235,6 +235,10 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { gpo := gasprice.NewGasPriceOracle(eth.blockchain, chainDb, eth.eventMux, gpoParams) eth.apiBackend = &EthApiBackend{eth, gpo} + if err := populateDirectCache(chainDb, eth.blockchain); err != nil { + return nil, err + } + return eth, nil } diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index 172bb0954a..f7ba391576 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -354,3 +354,10 @@ func addMipmapBloomBins(db ethdb.Database) (err error) { glog.V(logger.Info).Infoln("upgrade completed in", time.Since(tstart)) return nil } + +//struct upgradeState + +func populateDirectCache(db ethdb.Database, blockchain *core.BlockChain) error { + + return nil +} diff --git a/trie/directcache.go b/trie/directcache.go index bbb2f6e23c..84b555be13 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -17,8 +17,10 @@ package trie import ( - "sync" + "fmt" + "errors" "time" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" @@ -27,38 +29,15 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -var directCacheLock = &sync.Mutex{} - -var directCacheWrites = metrics.NewCounter("directcache/writes") -var directCacheHitTimer = metrics.NewTimer("directcache/timer/hits") -var directCacheMissTimer = metrics.NewTimer("directcache/timer/misses") - -// DirectCacheReads retrieves a global counter measuring the number of direct -// cache reads from the disk since process startup. This isn't useful for anything -// apart from trie debugging purposes. -func DirectCacheReads() int64 { - return directCacheHitTimer.Count() + directCacheMissTimer.Count() -} - -// DirectCacheWrites retrieves a global counter measuring the number of direct -// cache writes from the disk since process startup. This isn't useful for anything -// apart from trie debugging purposes. -func DirectCacheWrites() int64 { - return directCacheWrites.Count() -} - -// DirectCacheMisses retrieves a global counter measuring the number of direct -// cache writes from the disk since process startup. This isn't useful for anything -// apart from trie debugging purposes. -func DirectCacheMisses() int64 { - return directCacheMissTimer.Count() -} - -type cachedValue struct { - Value []byte - BlockNum uint64 - BlockHash common.Hash -} +var ( + directCacheLock = &sync.Mutex{} + directCacheLocked = false + directCacheWrites = metrics.NewCounter("directcache/writes") + directCacheHits = metrics.NewCounter("directcache/hits") + directCacheMisses = metrics.NewCounter("directcache/misses") + directCacheTimer = metrics.NewTimer("directcache/timer") + NotFound = errors.New("Cache entry not found") +) // CacheValidator can check whether a certain block is in the current canonical chain. type CacheValidator interface { @@ -100,10 +79,6 @@ func (dc *DirectCache) Iterator() *Iterator { return dc.data.Iterator() } -func (dc *DirectCache) makeKey(key []byte) []byte { - return append(dc.keyPrefix, key...) -} - func (dc *DirectCache) Get(key []byte) []byte { res, err := dc.TryGet(key) if err != nil && glog.V(logger.Error) { @@ -113,15 +88,11 @@ func (dc *DirectCache) Get(key []byte) []byte { } func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { - start := time.Now() - dirty := dc.dirty[string(key)] // Use the underlying object for dirty keys if !dirty { - cacheKey := dc.makeKey(key) - if cached, ok := dc.getCached(cacheKey); ok { - directCacheHitTimer.UpdateSince(start) + if cached, ok := dc.getCached(key); ok { return cached, nil } } @@ -138,26 +109,24 @@ func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { // Don't count fetches of dirty data as cache misses if !dirty { - directCacheMissTimer.UpdateSince(start) + directCacheMisses.Inc(1) } return value, nil } func (dc *DirectCache) getCached(key []byte) ([]byte, bool) { - enc, _ := dc.db.Get(key) - if len(enc) == 0 { - return nil, dc.complete - } - - var data cachedValue - if err := rlp.DecodeBytes(enc, &data); err != nil && glog.V(logger.Error) { - glog.Errorf("Can't decode cached object at %x: %v", key, err) + data, blockNum, blockHash, err := GetDirectCache(dc.keyPrefix, key, dc.db) + if err != nil { + if err == NotFound { + return nil, dc.complete + } + glog.Errorf("Error retrieving direct cache data: %v", err) return nil, false } - canonical := dc.blockNum > 0 && data.BlockNum < dc.blockNum && dc.validator.IsCanonChainBlock(data.BlockNum, data.BlockHash) - return data.Value, canonical + canonical := dc.blockNum > 0 && blockNum < dc.blockNum && dc.validator.IsCanonChainBlock(blockNum, blockHash) + return data, canonical } func (dc *DirectCache) Update(key, value []byte) { @@ -183,19 +152,21 @@ func (dc *DirectCache) TryDelete(key []byte) error { } func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error) { - directCacheWrites.Inc(len(dc.dirty)) - directCacheLock.Lock() - defer directCacheLock.Unlock() - - for k, _ := range dc.dirty { - v, err := dc.data.TryGet([]byte(k)) - if err, ok := err.(*MissingNodeError); err != nil && !ok { - return common.Hash{}, err - } - if err := writeCacheEntry(dc.keyPrefix, []byte(k), v, dc.blockNum, dc.blockHash, dbw); err != nil { - return common.Hash{}, err + if err := DirectCacheTransaction(func() error { + for k, _ := range dc.dirty { + v, err := dc.data.TryGet([]byte(k)) + if _, ok := err.(*MissingNodeError); err != nil && !ok { + return err + } + if err := WriteDirectCache(dc.keyPrefix, []byte(k), v, dc.blockNum, dc.blockHash, dbw); err != nil { + return err + } } + return nil + }); err != nil { + return common.Hash{}, err } + dc.dirty = make(map[string]bool) return dc.data.CommitTo(dbw) } @@ -203,20 +174,22 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error // Populate iterates over the underlying trie, filling in any unset cache entries. // After Populate has completed, future DirectCache instances can have `complete` // set to true, for better efficiency on cache misses. -func (dc *DirectCache) Populate() error { +func (dc *DirectCache) Populate() (err error) { i := 0 writes := 0 it := dc.Iterator() for it.Next() { - directCacheLock.Lock() - if val, err := dc.db.Get(append(dc.keyPrefix, it.Key...)); err != nil || val == nil { - writes += 1 - if err := writeCacheEntry(dc.keyPrefix, it.Key, it.Value, dc.blockNum, dc.blockHash, dc.db); err != nil { - directCacheLock.Unlock() - return err + if err := DirectCacheTransaction(func() error { + if HasDirectCache(dc.keyPrefix, it.Key, dc.db) { + writes += 1 + if err = WriteDirectCache(dc.keyPrefix, it.Key, it.Value, dc.blockNum, dc.blockHash, dc.db); err != nil { + return err + } } + return nil + }); err != nil { + return err } - directCacheLock.Unlock() i += 1 if i % 10000 == 0 && glog.V(logger.Info) { @@ -226,6 +199,26 @@ func (dc *DirectCache) Populate() error { return nil } +type cachedValue struct { + Value []byte + BlockNum uint64 + BlockHash common.Hash +} + +func DirectCacheTransaction(tx func() (error)) error { + if directCacheLocked { + return fmt.Errorf("Reentrant calls to DirectCacheTransaction are not permitted") + } + + directCacheLock.Lock() + directCacheLocked = true + defer func() { + directCacheLocked = false + directCacheLock.Unlock() + }() + + return tx() +} // WriteDirectCache places a value node directly into the database along with // block metadata to validate its relevancy. @@ -234,12 +227,11 @@ func (dc *DirectCache) Populate() error { // and its integrated cache, namely during fast sync and database upgrades. func WriteDirectCache(prefix, key, value []byte, number uint64, hash common.Hash, dbw DatabaseWriter) error { directCacheWrites.Inc(1) - directCacheLock.Lock() - defer directCacheLock.Unlock() - return writeCacheEntry(prefix, key, value, number, hash, dbw) -} -func writeCacheEntry(prefix, key, value []byte, number uint64, hash common.Hash, dbw DatabaseWriter) error { + if !directCacheLocked { + return fmt.Errorf("WriteDirectCache may only be called in a DirectCacheTransaction") + } + enc, _ := rlp.EncodeToBytes(cachedValue{value, number, hash}) return dbw.Put(append(prefix, key...), enc) } @@ -249,7 +241,46 @@ func writeCacheEntry(prefix, key, value []byte, number uint64, hash common.Hash, // // The method is meant to be used by code that circumvents the state database // and its integrated cache, namely during fast sync and database upgrades. -func GetDirectCache(prefix, key []byte, db Database) ([]byte, error) { - defer func(start time.Time) { directCacheHitTimer.UpdateSince(start) }(time.Now()) - return db.Get(append(prefix, key...)) +func GetDirectCache(prefix, key []byte, db Database) ([]byte, uint64, common.Hash, error) { + defer func(start time.Time) { directCacheTimer.UpdateSince(start) }(time.Now()) + + enc, _ := db.Get(append(prefix, key...)) + if len(enc) == 0 { + return nil, 0, common.Hash{}, NotFound + } + + var data cachedValue + if err := rlp.DecodeBytes(enc, &data); err != nil && glog.V(logger.Error) { + return nil, 0, common.Hash{}, fmt.Errorf("Can't decode cached object at %x: %v", key, err) + } + return data.Value, data.BlockNum, data.BlockHash, nil +} + +// HasDirectCache returns true iff a direct cache node exists for the specified key +func HasDirectCache(prefix, key []byte, db Database) bool { + if enc, err := db.Get(append(prefix, key...)); err == nil && len(enc) > 0 { + return true + } + return false +} + +// DirectCacheReads retrieves a global counter measuring the number of direct +// cache reads from the disk since process startup. This isn't useful for anything +// apart from trie debugging purposes. +func DirectCacheReads() int64 { + return directCacheTimer.Count() +} + +// DirectCacheWrites retrieves a global counter measuring the number of direct +// cache writes from the disk since process startup. This isn't useful for anything +// apart from trie debugging purposes. +func DirectCacheWrites() int64 { + return directCacheWrites.Count() +} + +// DirectCacheMisses retrieves a global counter measuring the number of direct +// cache misses from the disk since process startup. This isn't useful for anything +// apart from trie debugging purposes. +func DirectCacheMisses() int64 { + return directCacheMisses.Count() } diff --git a/trie/sync.go b/trie/sync.go index 8ab789f4ab..335c7a2a0f 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -292,7 +292,7 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { // If this branch was already visited, abort iteration keys := req.keys(append(child.path, it.Nibbles...)) if !unknown { - if val, err := GetDirectCache(req.dcPrefix, keys[0], s.database); val != nil && err == nil { + if HasDirectCache(req.dcPrefix, keys[0], s.database) { break } // Branch is unknown, mark as so to avoid repeated db lookups From 548487b968f25d77de20e91e7c9df17a513586d8 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Thu, 27 Oct 2016 17:04:01 +0100 Subject: [PATCH 15/16] core, eth, trie: Beginnings of a migration process for direct cache --- core/state/statedb.go | 5 ++-- eth/db_upgrade.go | 3 --- trie/directcache.go | 57 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index fe29e318a5..6a35e2d425 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -42,7 +42,6 @@ var StartingNonce uint64 var MaxTrieCacheGen = uint16(120) var DirectCachePrefix = []byte("accounthashcache:") -var DirectStateCacheCompleteKey = []byte("directstatecache:complete") const ( // Number of past tries to keep. This value is chosen such that @@ -193,12 +192,12 @@ func (self *StateDB) SetBlockContext(blockHash common.Hash, blockNum uint64, val // Check if cache population has completed if !self.cacheComplete { - if value, err := self.db.Get(DirectStateCacheCompleteKey); err == nil && value != nil { + if _, status := trie.GetMigrationState(DirectCachePrefix, self.db); status == trie.Complete { self.cacheComplete = true } } - storage := trie.NewDirectCache(self.trie, self.db, DirectCachePrefix, blockNum, blockHash, validator, true) + storage := trie.NewDirectCache(self.trie, self.db, DirectCachePrefix, blockNum, blockHash, validator, self.cacheComplete) self.storage = trie.NewSecure(storage, self.db) } diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index f7ba391576..d53ca2930f 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -355,9 +355,6 @@ func addMipmapBloomBins(db ethdb.Database) (err error) { return nil } -//struct upgradeState - func populateDirectCache(db ethdb.Database, blockchain *core.BlockChain) error { - return nil } diff --git a/trie/directcache.go b/trie/directcache.go index 84b555be13..89825615b3 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -30,13 +30,22 @@ import ( ) var ( - directCacheLock = &sync.Mutex{} - directCacheLocked = false - directCacheWrites = metrics.NewCounter("directcache/writes") - directCacheHits = metrics.NewCounter("directcache/hits") - directCacheMisses = metrics.NewCounter("directcache/misses") - directCacheTimer = metrics.NewTimer("directcache/timer") - NotFound = errors.New("Cache entry not found") + directCacheLock = &sync.Mutex{} + directCacheLocked = false + directCacheWrites = metrics.NewCounter("directcache/writes") + directCacheHits = metrics.NewCounter("directcache/hits") + directCacheMisses = metrics.NewCounter("directcache/misses") + directCacheTimer = metrics.NewTimer("directcache/timer") + NotFound = errors.New("Cache entry not found") + MigrationPrefix = []byte("directstatecachemigration:") +) + +type MigrationStatus int + +const ( + NotStarted = iota + Running = iota + Complete = iota ) // CacheValidator can check whether a certain block is in the current canonical chain. @@ -206,10 +215,6 @@ type cachedValue struct { } func DirectCacheTransaction(tx func() (error)) error { - if directCacheLocked { - return fmt.Errorf("Reentrant calls to DirectCacheTransaction are not permitted") - } - directCacheLock.Lock() directCacheLocked = true defer func() { @@ -250,7 +255,7 @@ func GetDirectCache(prefix, key []byte, db Database) ([]byte, uint64, common.Has } var data cachedValue - if err := rlp.DecodeBytes(enc, &data); err != nil && glog.V(logger.Error) { + if err := rlp.DecodeBytes(enc, &data); err != nil { return nil, 0, common.Hash{}, fmt.Errorf("Can't decode cached object at %x: %v", key, err) } return data.Value, data.BlockNum, data.BlockHash, nil @@ -264,6 +269,34 @@ func HasDirectCache(prefix, key []byte, db Database) bool { return false } +type migrationState struct { + Status MigrationStatus + Number uint64 +} + +// GetMigrationState returns the block number of the migration to the direct cache, and +// whether or not it's complete. +func GetMigrationState(prefix []byte, db Database) (uint64, MigrationStatus) { + enc, _ := db.Get(append(MigrationPrefix, prefix...)) + if len(enc) == 0 { + return 0, NotStarted + } + + var data migrationState + if err := rlp.DecodeBytes(enc, &data); err != nil { + glog.Errorf("Could not decode migration status: %v", err) + return 0, NotStarted + } + + return data.Number, data.Status +} + +// SetMigrationState updates the migration state in the database +func SetMigrationState(prefix []byte, blockNum uint64, status MigrationStatus, db Database) error { + enc, _ := rlp.EncodeToBytes(migrationState{status, blockNum}) + return db.Put(append(MigrationPrefix, prefix...), enc) +} + // DirectCacheReads retrieves a global counter measuring the number of direct // cache reads from the disk since process startup. This isn't useful for anything // apart from trie debugging purposes. From 0b12010d843dc322128807a55f9ae4e2e5747775 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Mon, 31 Oct 2016 12:09:26 +0000 Subject: [PATCH 16/16] eth/trie: Implement upgrade process for direct cache --- eth/db_upgrade.go | 30 +++++++++++++++++++++++++++++- trie/directcache.go | 18 +++++++++--------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index d53ca2930f..6e2657e010 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -26,11 +26,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) var useSequentialKeys = []byte("dbUpgrade_20160530sequentialKeys") @@ -355,6 +357,32 @@ func addMipmapBloomBins(db ethdb.Database) (err error) { return nil } -func populateDirectCache(db ethdb.Database, blockchain *core.BlockChain) error { +func populateDirectCache(db ethdb.Database, bc *core.BlockChain) error { + blockHash, status := trie.GetMigrationState(state.DirectCachePrefix, db) + if status == trie.NotStarted { + blockHash = core.GetHeadBlockHash(db) + status = trie.Running + trie.SetMigrationState(state.DirectCachePrefix, blockHash, status, db) + } + blockNum := core.GetBlockNumber(db, blockHash) + + if status == trie.Running { + tr, err := trie.New(core.GetBlock(db, blockHash, blockNum).Root(), db, 0) + if err != nil { + return err + } + dc := trie.NewDirectCache(tr, db, state.DirectCachePrefix, blockNum, blockHash, bc, false) + go func() { + for core.GetBlockNumber(db, core.GetHeadBlockHash(db)) < blockNum + 8 { + time.Sleep(10 * time.Second) + } + glog.Infof("Beginning direct cache upgrade at block %v", blockNum) + + if err := dc.Populate(); err != nil { + glog.Errorf("Direct cache upgrade failed: %v", err) + } + trie.SetMigrationState(state.DirectCachePrefix, blockHash, trie.Complete, db) + }() + } return nil } diff --git a/trie/directcache.go b/trie/directcache.go index 89825615b3..c7e114c1bc 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -189,7 +189,7 @@ func (dc *DirectCache) Populate() (err error) { it := dc.Iterator() for it.Next() { if err := DirectCacheTransaction(func() error { - if HasDirectCache(dc.keyPrefix, it.Key, dc.db) { + if !HasDirectCache(dc.keyPrefix, it.Key, dc.db) { writes += 1 if err = WriteDirectCache(dc.keyPrefix, it.Key, it.Value, dc.blockNum, dc.blockHash, dc.db); err != nil { return err @@ -202,7 +202,7 @@ func (dc *DirectCache) Populate() (err error) { i += 1 if i % 10000 == 0 && glog.V(logger.Info) { - glog.V(logger.Info).Infof("Constructing direct cache: processed %v entries, writing %v", i, writes) + glog.V(logger.Info).Infof("Constructing direct cache: processed %v entries, written %v", i, writes) } } return nil @@ -271,29 +271,29 @@ func HasDirectCache(prefix, key []byte, db Database) bool { type migrationState struct { Status MigrationStatus - Number uint64 + Hash common.Hash } // GetMigrationState returns the block number of the migration to the direct cache, and // whether or not it's complete. -func GetMigrationState(prefix []byte, db Database) (uint64, MigrationStatus) { +func GetMigrationState(prefix []byte, db Database) (common.Hash, MigrationStatus) { enc, _ := db.Get(append(MigrationPrefix, prefix...)) if len(enc) == 0 { - return 0, NotStarted + return common.Hash{}, NotStarted } var data migrationState if err := rlp.DecodeBytes(enc, &data); err != nil { glog.Errorf("Could not decode migration status: %v", err) - return 0, NotStarted + return common.Hash{}, NotStarted } - return data.Number, data.Status + return data.Hash, data.Status } // SetMigrationState updates the migration state in the database -func SetMigrationState(prefix []byte, blockNum uint64, status MigrationStatus, db Database) error { - enc, _ := rlp.EncodeToBytes(migrationState{status, blockNum}) +func SetMigrationState(prefix []byte, blockHash common.Hash, status MigrationStatus, db Database) error { + enc, _ := rlp.EncodeToBytes(migrationState{status, blockHash}) return db.Put(append(MigrationPrefix, prefix...), enc) }