From b0c313407fd58abc044185e8daf4912cb6f2d022 Mon Sep 17 00:00:00 2001 From: Zsolt Felfoldi Date: Thu, 11 Jan 2018 23:48:54 +0100 Subject: [PATCH] core, trie: new state trie database format with garbage collection --- core/blockchain.go | 35 +++++- core/chain_makers.go | 2 +- core/database_util.go | 17 +-- core/genesis.go | 2 +- core/hashtree/gc.go | 232 +++++++++++++++++++++++++++++++++++++ core/hashtree/hashtree.go | 107 +++++++++++++++++ core/state/database.go | 45 ++++++- core/state/state_object.go | 5 +- core/state/statedb.go | 43 ++++++- eth/api.go | 8 +- eth/api_tracer.go | 36 +++--- ethdb/database.go | 4 + tests/state_test_util.go | 4 +- trie/encoding.go | 32 +++++ trie/hasher.go | 20 ++-- trie/proof.go | 70 ++++++----- trie/secure_trie.go | 18 +-- trie/sync.go | 13 ++- trie/trie.go | 28 ++++- 19 files changed, 616 insertions(+), 105 deletions(-) create mode 100644 core/hashtree/gc.go create mode 100644 core/hashtree/hashtree.go diff --git a/core/blockchain.go b/core/blockchain.go index 737fbe3eee..1759b6587a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/hashtree" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -105,14 +106,18 @@ type BlockChain struct { quit chan struct{} // blockchain quit channel running int32 // running must be called atomically // procInterrupt must be atomically called + processing int32 procInterrupt int32 // interrupt signaler for block processing wg sync.WaitGroup // chain processing wait group for shutting down + writeCounter uint64 engine consensus.Engine processor Processor // block processor interface validator Validator // block and state validator interface vmConfig vm.Config + gc *hashtree.GarbageCollector + badBlocks *lru.Cache // Bad block cache } @@ -126,6 +131,7 @@ func NewBlockChain(chainDb ethdb.Database, config *params.ChainConfig, engine co futureBlocks, _ := lru.New(maxFutureBlocks) badBlocks, _ := lru.New(badBlockLimit) + //hashtree.Print(chainDb, []byte(state.DbPrefix)) bc := &BlockChain{ config: config, chainDb: chainDb, @@ -167,11 +173,29 @@ func NewBlockChain(chainDb ethdb.Database, config *params.ChainConfig, engine co } } } + + bc.gc = hashtree.NewGarbageCollector(chainDb, []byte(state.DbPrefix), bc.hasDataCallback) + + /*headBlock := bc.currentBlock.NumberU64() + if headBlock > 1000 { + bc.gc.FullGC(headBlock - 1000) + }*/ + + bc.gc.BackgroundGC(bc.CurrentBlock, &bc.processing, &bc.procInterrupt, &bc.wg) + // Take ownership of this particular state go bc.update() return bc, nil } +func (bc *BlockChain) hasDataCallback(version uint64) func(position, hash []byte) bool { + header := bc.GetHeaderByNumber(version) + if header == nil { + return nil + } + return state.HasDataCallback(header.Root, bc.chainDb) +} + func (bc *BlockChain) getProcInterrupt() bool { return atomic.LoadInt32(&bc.procInterrupt) == 1 } @@ -292,7 +316,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x…]", hash[:4]) } - if _, err := trie.NewSecure(block.Root(), bc.chainDb, 0); err != nil { + if _, err := trie.NewSecure(block.Root(), hashtree.NewReader(bc.chainDb, state.DbPrefix), 0); err != nil { return err } // If all checks out, manually set the head block @@ -808,7 +832,7 @@ func (bc *BlockChain) WriteBlockAndState(block *types.Block, receipts []*types.R if err := WriteBlock(batch, block); err != nil { return NonStatTy, err } - if _, err := state.CommitTo(batch, bc.config.IsEIP158(block.Number())); err != nil { + if _, err := state.CommitTo(batch, block.NumberU64(), bc.gc, bc.config.IsEIP158(block.Number())); err != nil { return NonStatTy, err } if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil { @@ -842,9 +866,13 @@ func (bc *BlockChain) WriteBlockAndState(block *types.Block, receipts []*types.R } else { status = SideStatTy } + + bc.gc.LockWrite() if err := batch.Write(); err != nil { + bc.gc.UnlockWrite() return NonStatTy, err } + bc.gc.UnlockWrite() // Set new head. if status == CanonStatTy { @@ -888,6 +916,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty bc.chainmu.Lock() defer bc.chainmu.Unlock() + atomic.StoreInt32(&bc.processing, 1) + defer atomic.StoreInt32(&bc.processing, 0) + // A queued approach to delivering events. This is generally // faster than direct delivery and requires much less mutex // acquiring. diff --git a/core/chain_makers.go b/core/chain_makers.go index 5e264a9942..541efadfd7 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -192,7 +192,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if b.engine != nil { block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts) // Write state changes to db - _, err := statedb.CommitTo(db, config.IsEIP158(b.header.Number)) + _, err := statedb.CommitTo(db, b.header.Number.Uint64(), nil, config.IsEIP158(b.header.Number)) if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } diff --git a/core/database_util.go b/core/database_util.go index c6b125dae9..cabb1497c7 100644 --- a/core/database_util.go +++ b/core/database_util.go @@ -25,12 +25,14 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "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/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) // DatabaseReader wraps the Get method of a backing data store. @@ -58,8 +60,7 @@ var ( lookupPrefix = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits - preimagePrefix = "secure-key-" // preimagePrefix + hash -> preimage - configPrefix = []byte("ethereum-config-") // config prefix for the db + configPrefix = []byte("ethereum-config-") // config prefix for the db // Chain index prefixes (use `i` + single byte to avoid mixing data types). BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress @@ -532,20 +533,20 @@ func DeleteTxLookupEntry(db DatabaseDeleter, hash common.Hash) { db.Delete(append(lookupPrefix, hash.Bytes()...)) } -// PreimageTable returns a Database instance with the key prefix for preimage entries. -func PreimageTable(db ethdb.Database) ethdb.Database { - return ethdb.NewTable(db, preimagePrefix) +func GetPreimage(db ethdb.Database, hash common.Hash) ([]byte, error) { + return db.Get(append([]byte(state.DbPrefix), trie.SecHashTreePos(hash.Bytes())...)) } // WritePreimages writes the provided set of preimages to the database. `number` is the // current block number, and is used for debug messages only. func WritePreimages(db ethdb.Database, number uint64, preimages map[common.Hash][]byte) error { - table := PreimageTable(db) + table := ethdb.NewTable(db, state.DbPrefix) batch := table.NewBatch() hitCount := 0 for hash, preimage := range preimages { - if _, err := table.Get(hash.Bytes()); err != nil { - batch.Put(hash.Bytes(), preimage) + key := trie.SecHashTreePos(hash.Bytes()) + if _, err := table.Get(key); err != nil { + batch.Put(key, preimage) hitCount++ } } diff --git a/core/genesis.go b/core/genesis.go index e22985b800..0bd1255993 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -262,7 +262,7 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { if block.Number().Sign() != 0 { return nil, fmt.Errorf("can't commit genesis block with number > 0") } - if _, err := statedb.CommitTo(db, false); err != nil { + if _, err := statedb.CommitTo(db, 0, nil, false); err != nil { return nil, fmt.Errorf("cannot write state: %v", err) } if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil { diff --git a/core/hashtree/gc.go b/core/hashtree/gc.go new file mode 100644 index 0000000000..ad3a069459 --- /dev/null +++ b/core/hashtree/gc.go @@ -0,0 +1,232 @@ +// Copyright 2017 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 hashtree + +import ( + "bytes" + "encoding/binary" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/syndtr/goleveldb/leveldb/util" +) + +func Print(db ethdb.Database, prefix []byte) { + it := db.(*ethdb.LDBDatabase).NewIterator() + defer it.Release() + cnt := 0 + for it.Seek(prefix); it.Valid(); it.Next() { + key := it.Key() + if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) { + return + } + value := it.Value() + cnt++ + fmt.Printf("CNT %d KEY %x HASH %x VALUE %x\n", cnt, key[len(prefix):], crypto.Keccak256(value), value) + } +} + +type hasDataFn func(version uint64) func(position, hash []byte) bool + +type GarbageCollector struct { + db *ethdb.LDBDatabase + prefix []byte + hasData hasDataFn + gcBlock uint64 + gcBlockHasData func(position, hash []byte) bool + delkeys [][]byte + keysChecked, keysRemoved uint64 + refsChecked, refsRemoved uint64 + writeCounter uint64 + writeLock sync.Mutex + valid bool +} + +func NewGarbageCollector(db ethdb.Database, prefix []byte, hasData hasDataFn) *GarbageCollector { + return &GarbageCollector{ + db: db.(*ethdb.LDBDatabase), + prefix: prefix, + hasData: hasData, + } +} + +func (g *GarbageCollector) run(startKey []byte, maxEntries uint64) (nextKey []byte) { + g.writeLock.Lock() + g.valid = true + g.writeLock.Unlock() + + it := g.db.NewIterator() + g.delkeys = nil + + defer func() { + it.Release() + g.writeLock.Lock() + if g.valid { + for _, key := range g.delkeys { + g.db.Delete(key) + } + g.keysRemoved += uint64(len(g.delkeys)) + } + g.writeLock.Unlock() + var r util.Range + if nextKey == nil { + r = *util.BytesPrefix(g.prefix) + } else { + r.Limit = nextKey + } + r.Start = startKey + g.db.LDB().CompactRange(r) + }() + + g.gcBlockHasData = g.hasData(g.gcBlock) + it.Seek(startKey) + for it.Valid() { + key := common.CopyBytes(it.Key()) + //log.Info("key", "key", key) + if len(key) < len(g.prefix) || !bytes.Equal(key[:len(g.prefix)], g.prefix) { + return nil + } + if maxEntries == 0 { + nextKey = key + return nextKey + } + + if len(key) >= len(g.prefix)+33 && key[len(key)-1] == 0 { + it.Next() + var refkeys [][]byte + for it.Valid() { + refkey := common.CopyBytes(it.Key()) + //log.Info("ref", "key", refkey) + if len(refkey) >= len(key) && bytes.Equal(refkey[:len(key)-1], key[:len(key)-1]) { + if len(refkey) == len(key)+8 && refkey[len(key)+7] == 1 { + refkeys = append(refkeys, refkey) + } else { + log.Error("Invalid hashtree ref", "key", refkey) + } + it.Next() + } else { + break + } + } + g.gcEntry(key, refkeys) + maxEntries-- + } else { + log.Error("Invalid hashtree entry", "key", key) + it.Next() + } + } + return nil +} + +func (g *GarbageCollector) gcEntry(key []byte, refkeys [][]byte) { + refcount := len(refkeys) + keylen := len(key) + oldrefs := 0 + for oldrefs < refcount { + version := binary.BigEndian.Uint64(refkeys[oldrefs][keylen-1 : keylen+7]) + if version >= g.gcBlock { + break + } + oldrefs++ + } + + removerefs := 0 + if oldrefs > 0 { + removerefs = oldrefs - 1 + if oldrefs == refcount && !g.gcBlockHasData(key[len(g.prefix):keylen-33], key[keylen-33:keylen-1]) { + removerefs = refcount + } + } + + g.keysChecked++ + if removerefs == refcount { + g.delkeys = append(g.delkeys, key) + } + g.refsChecked += uint64(refcount) + g.refsRemoved += uint64(removerefs) + for i := 0; i < removerefs; i++ { + g.db.Delete(refkeys[i]) + } +} + +func (g *GarbageCollector) FullGC(block uint64) { + log.Info("Starting full GC", "block", block) + g.gcBlock = block + key := g.prefix + for key != nil { + key = g.run(key, 10000) + k := key + if len(k) > 8 { + k = k[:8] + } + log.Info("Running...", "key", k, "keys checked", g.keysChecked, "keys removed", g.keysRemoved, "refs checked", g.refsChecked, "refs removed", g.refsRemoved) + } + log.Info("Finished full GC", "keys checked", g.keysChecked, "keys removed", g.keysRemoved, "refs checked", g.refsChecked, "refs removed", g.refsRemoved) +} + +func (g *GarbageCollector) BackgroundGC(currentBlock func() *types.Block, processing, stop *int32, wg *sync.WaitGroup) { + wg.Add(1) + go func() { + defer wg.Done() + + var gcCounter uint64 + key := g.prefix + + for atomic.LoadInt32(stop) == 0 { + wc := atomic.LoadUint64(&g.writeCounter) + diff := wc - gcCounter + if diff > 10000 { + gcCounter = wc - 10000 + diff = 10000 + } + if diff >= 100 && atomic.LoadInt32(processing) == 0 { + gcCounter += 100 + if key == nil { + key = g.prefix + } + headBlock := currentBlock().NumberU64() + if headBlock > 1000 { + g.gcBlock = headBlock - 1000 + key = g.run(key, 1000) + k := key + if len(k) > 8 { + k = k[:8] + } + log.Info("Running GC...", "key", k, "keys checked", g.keysChecked, "keys removed", g.keysRemoved, "refs checked", g.refsChecked, "refs removed", g.refsRemoved) + } + } else { + time.Sleep(time.Second) + } + } + }() +} + +func (g *GarbageCollector) LockWrite() { + g.writeLock.Lock() + g.valid = false +} + +func (g *GarbageCollector) UnlockWrite() { + g.writeLock.Unlock() +} diff --git a/core/hashtree/hashtree.go b/core/hashtree/hashtree.go new file mode 100644 index 0000000000..8fcf2c49bd --- /dev/null +++ b/core/hashtree/hashtree.go @@ -0,0 +1,107 @@ +// Copyright 2017 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 hashtree + +import ( + "encoding/binary" + "sync/atomic" + // "fmt" +) + +type DatabaseReader interface { + Get([]byte) ([]byte, error) + Has([]byte) (bool, error) +} + +type DatabaseWriter interface { + Put([]byte, []byte) error +} + +type Reader struct { + db DatabaseReader + prefix []byte + lpf int +} + +func NewReader(db DatabaseReader, prefix string) *Reader { + return &Reader{db, []byte(prefix), len(prefix)} +} + +func (h *Reader) Get(position, hash []byte) ([]byte, error) { + lp, lh := len(position), len(hash) + key := make([]byte, h.lpf+lp+lh+1) + copy(key[:h.lpf], h.prefix) + copy(key[h.lpf:h.lpf+lp], position) + copy(key[h.lpf+lp:h.lpf+lp+lh], hash) + data, err := h.db.Get(key) + if err != nil { + //panic(nil) + //fmt.Printf("READ ERR %x %v\n", key, err) + } + return data, err +} + +func (h *Reader) Has(position, hash []byte) (bool, error) { + lp, lh := len(position), len(hash) + key := make([]byte, h.lpf+lp+lh+1) + copy(key[:h.lpf], h.prefix) + copy(key[h.lpf:h.lpf+lp], position) + copy(key[h.lpf+lp:h.lpf+lp+lh], hash) + return h.db.Has(key) +} + +func (h *Reader) Put(position, hash, data []byte) error { + panic(nil) +} + +type Writer struct { + db DatabaseWriter + prefix []byte + lpf int + version uint64 + versionEnc [8]byte + gc *GarbageCollector +} + +func NewWriter(db DatabaseWriter, prefix string, version uint64, gc *GarbageCollector) *Writer { + w := &Writer{ + db: db, + prefix: []byte(prefix), + lpf: len(prefix), + version: version, + gc: gc, + } + binary.BigEndian.PutUint64(w.versionEnc[:], version) + return w +} + +func (w *Writer) Put(position, hash, data []byte) error { + if w.gc != nil { + atomic.AddUint64(&w.gc.writeCounter, 1) + } + lp, lh := len(position), len(hash) + key := make([]byte, w.lpf+lp+lh+9) + copy(key[:w.lpf], w.prefix) + copy(key[w.lpf:w.lpf+lp], position) + copy(key[w.lpf+lp:w.lpf+lp+lh], hash) + if err := w.db.Put(key[:w.lpf+lp+lh+1], data); err != nil { + return err + } + copy(key[w.lpf+lp+lh:w.lpf+lp+lh+8], w.versionEnc[:]) + key[w.lpf+lp+lh+8] = 1 + return w.db.Put(key, nil) +} diff --git a/core/state/database.go b/core/state/database.go index 946625e76e..9d64b4fcd0 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -21,7 +21,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/core/hashtree" "github.com/ethereum/go-ethereum/trie" lru "github.com/hashicorp/golang-lru" ) @@ -29,6 +29,8 @@ import ( // Trie cache generation limit after which to evic trie nodes from memory. var MaxTrieCacheGen = uint16(120) +var DbPrefix = "s" + const ( // Number of past tries to keep. This value is chosen such that // reasonable chain reorg depths will hit an existing trie. @@ -65,13 +67,13 @@ type Trie interface { // NewDatabase creates a backing store for state. The returned database is safe for // concurrent use and retains cached trie nodes in memory. -func NewDatabase(db ethdb.Database) Database { +func NewDatabase(db hashtree.DatabaseReader) Database { csc, _ := lru.New(codeSizeCacheSize) - return &cachingDB{db: db, codeSizeCache: csc} + return &cachingDB{db: hashtree.NewReader(db, DbPrefix), codeSizeCache: csc} //prefix } type cachingDB struct { - db ethdb.Database + db *hashtree.Reader mu sync.Mutex pastTries []*trie.SecureTrie codeSizeCache *lru.Cache @@ -106,7 +108,7 @@ func (db *cachingDB) pushTrie(t *trie.SecureTrie) { } func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { - return trie.NewSecure(root, db.db, 0) + return trie.NewSecure(root, &storageTrieDb{dbr: db.db, addrHash: addrHash.Bytes()}, 0) } func (db *cachingDB) CopyTrie(t Trie) Trie { @@ -121,7 +123,7 @@ func (db *cachingDB) CopyTrie(t Trie) Trie { } func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { - code, err := db.db.Get(codeHash[:]) + code, err := db.db.Get(contractCodePosition(addrHash), codeHash[:]) if err == nil { db.codeSizeCache.Add(codeHash, len(code)) } @@ -152,3 +154,34 @@ func (m cachedTrie) CommitTo(dbw trie.DatabaseWriter) (common.Hash, error) { } return root, err } + +type storageTrieDb struct { + dbr trie.DatabaseReader + dbw trie.DatabaseWriter + addrHash []byte +} + +func (s *storageTrieDb) position(position []byte) []byte { + pos := make([]byte, len(position)+33) + copy(pos[:32], s.addrHash) + pos[32] = 6 + copy(pos[33:], position) + return pos +} + +func (s *storageTrieDb) Put(position, hash, data []byte) error { + return s.dbw.Put(s.position(position), hash, data) +} + +func (s *storageTrieDb) Get(position, hash []byte) ([]byte, error) { + //fmt.Printf("GetStorage %x %x %x\n", s.addrHash, position, hash) + return s.dbr.Get(s.position(position), hash) +} + +func (s *storageTrieDb) Has(position, hash []byte) (bool, error) { + return s.dbr.Has(s.position(position), hash) +} + +func contractCodePosition(addrHash common.Hash) []byte { + return append(addrHash.Bytes(), 5) +} diff --git a/core/state/state_object.go b/core/state/state_object.go index b2378c69c8..29805ce5e8 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -113,10 +113,11 @@ func newObject(db *StateDB, address common.Address, data Account, onDirty func(a if data.CodeHash == nil { data.CodeHash = emptyCodeHash } + addrHash := crypto.Keccak256Hash(address[:]) return &stateObject{ db: db, address: address, - addrHash: crypto.Keccak256Hash(address[:]), + addrHash: addrHash, data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), @@ -243,7 +244,7 @@ func (self *stateObject) CommitTrie(db Database, dbw trie.DatabaseWriter) error if self.dbErr != nil { return self.dbErr } - root, err := self.trie.CommitTo(dbw) + root, err := self.trie.CommitTo(&storageTrieDb{dbw: dbw, addrHash: self.addrHash.Bytes()}) if err == nil { self.data.Root = root } diff --git a/core/state/statedb.go b/core/state/statedb.go index 8e29104d59..676a68ee9e 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,12 +18,14 @@ package state import ( + "bytes" "fmt" "math/big" "sort" "sync" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/hashtree" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -569,9 +571,10 @@ func (s *StateDB) clearJournalAndRefund() { } // CommitTo writes the state to the given database. -func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (root common.Hash, err error) { +func (s *StateDB) CommitTo(db hashtree.DatabaseWriter, blockNumber uint64, gc *hashtree.GarbageCollector, deleteEmptyObjects bool) (root common.Hash, err error) { defer s.clearJournalAndRefund() + dbw := hashtree.NewWriter(db, DbPrefix, blockNumber, gc) // Commit objects to the trie. for addr, stateObject := range s.stateObjects { _, isDirty := s.stateObjectsDirty[addr] @@ -583,7 +586,7 @@ func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (ro case isDirty: // Write any contract code associated with the state object if stateObject.code != nil && stateObject.dirtyCode { - if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil { + if err := dbw.Put(contractCodePosition(stateObject.addrHash), stateObject.CodeHash(), stateObject.code); err != nil { return common.Hash{}, err } stateObject.dirtyCode = false @@ -602,3 +605,39 @@ func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (ro log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads()) return root, err } + +func HasDataCallback(root common.Hash, dbr hashtree.DatabaseReader) func(position, hash []byte) bool { + db := hashtree.NewReader(dbr, DbPrefix) + t, err := trie.New(root, db) + if err != nil { + panic(err) + } + return func(position, hash []byte) bool { + lp := len(position) + if lp < 33 || (lp == 33 && position[32] < 5) { + return t.HasData(position, hash) + } + addrHash := position[:32] + enc, err := t.TryGet(addrHash) + if len(enc) == 0 || err != nil { + return false + } + + var data Account + if err := rlp.DecodeBytes(enc, &data); err != nil { + return false + } + if lp == 33 && position[32] == 5 { + return bytes.Equal(hash, data.CodeHash) + } + if position[32] != 6 { + return false + } + + st, err := trie.New(data.Root, &storageTrieDb{dbr: db, addrHash: addrHash}) + if err != nil { + return false + } + return st.HasData(position[33:], hash) + } +} diff --git a/eth/api.go b/eth/api.go index 0db3eb5548..c04e2028b4 100644 --- a/eth/api.go +++ b/eth/api.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/hashtree" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -343,8 +344,7 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug // Preimage is a debug API function that returns the preimage for a sha3 hash, if known. func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { - db := core.PreimageTable(api.eth.ChainDb()) - return db.Get(hash.Bytes()) + return core.GetPreimage(api.eth.ChainDb(), hash) } // GetBadBLocks returns a list of the last 'bad blocks' that the client has seen on the network @@ -462,11 +462,11 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) } - oldTrie, err := trie.NewSecure(startBlock.Root(), api.eth.chainDb, 0) + oldTrie, err := trie.NewSecure(startBlock.Root(), hashtree.NewReader(api.eth.chainDb, state.DbPrefix), 0) if err != nil { return nil, err } - newTrie, err := trie.NewSecure(endBlock.Root(), api.eth.chainDb, 0) + newTrie, err := trie.NewSecure(endBlock.Root(), hashtree.NewReader(api.eth.chainDb, state.DbPrefix), 0) if err != nil { return nil, err } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index d49f077aed..142c5d329b 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -123,24 +123,24 @@ func (db *ephemeralDatabase) Get(key []byte) ([]byte, error) { // state. func (db *ephemeralDatabase) Prune(root common.Hash) { // Pull the still relevant state data into memory - sync := state.NewStateSync(root, db.diskdb) - for sync.Pending() > 0 { - hash := sync.Missing(1)[0] + /* sync := state.NewStateSync(root, db.diskdb) + for sync.Pending() > 0 { + hash := sync.Missing(1)[0] - // Move the next trie node from the memory layer into a sync struct - node, err := db.memdb.Get(hash[:]) - if err != nil { - panic(err) // memdb must have the data + // Move the next trie node from the memory layer into a sync struct + node, err := db.memdb.Get(hash[:]) + if err != nil { + panic(err) // memdb must have the data + } + if _, _, err := sync.Process([]trie.SyncResult{{Hash: hash, Data: node}}); err != nil { + panic(err) // it's not possible to fail processing a node + } } - if _, _, err := sync.Process([]trie.SyncResult{{Hash: hash, Data: node}}); err != nil { - panic(err) // it's not possible to fail processing a node - } - } - // Discard the old memory layer and write a new one - db.memdb, _ = ethdb.NewMemDatabaseWithCap(db.memdb.Len()) - if _, err := sync.Commit(db); err != nil { - panic(err) // writing into a memdb cannot fail - } + // Discard the old memory layer and write a new one + db.memdb, _ = ethdb.NewMemDatabaseWithCap(db.memdb.Len()) + if _, err := sync.Commit(db); err != nil { + panic(err) // writing into a memdb cannot fail + }*/ } // TraceChain returns the structured logs created during the execution of EVM @@ -340,7 +340,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl break } // Finalize the state so any modifications are written to the trie - root, err := statedb.CommitTo(db, true) + root, err := statedb.CommitTo(db, number, nil, true) if err != nil { failed = err break @@ -587,7 +587,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* return nil, err } // Finalize the state so any modifications are written to the trie - root, err := statedb.CommitTo(db, true) + root, err := statedb.CommitTo(db, block.NumberU64(), nil, true) if err != nil { return nil, err } diff --git a/ethdb/database.go b/ethdb/database.go index 93755dd7e3..4bfcfbcbb2 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -347,6 +347,10 @@ func (dt *table) NewBatch() Batch { return &tableBatch{dt.db.NewBatch(), dt.prefix} } +func NewBatchTable(batch Batch, prefix string) Batch { + return &tableBatch{batch, prefix} +} + func (tb *tableBatch) Put(key, value []byte) error { return tb.batch.Put(append([]byte(tb.prefix), key...), value) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 78c05b0245..1407f73357 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -147,7 +147,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) } - root, _ := statedb.CommitTo(db, config.IsEIP158(block.Number())) + root, _ := statedb.CommitTo(db, block.NumberU64(), nil, config.IsEIP158(block.Number())) if root != common.Hash(post.Root) { return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) } @@ -170,7 +170,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.CommitTo(db, false) + root, _ := statedb.CommitTo(db, 0, nil, false) statedb, _ = state.New(root, sdb) return statedb } diff --git a/trie/encoding.go b/trie/encoding.go index e96a786e40..7be88ff9c1 100644 --- a/trie/encoding.go +++ b/trie/encoding.go @@ -112,3 +112,35 @@ func prefixLen(a, b []byte) int { func hasTerm(s []byte) bool { return len(s) > 0 && s[len(s)-1] == 16 } + +func hexToHashTreePos(hex []byte) []byte { + terminator := byte(0) + if hasTerm(hex) { + terminator = 2 + hex = hex[:len(hex)-1] + } + buf := make([]byte, len(hex)/2+1) + if len(hex)&1 == 1 { + terminator += hex[len(hex)-1]<<4 + 1 + hex = hex[:len(hex)-1] + } + decodeNibbles(hex, buf[:len(buf)-1]) + buf[len(buf)-1] = terminator + return buf +} + +func SecHashTreePos(hash []byte) []byte { + return append(hash, 4) +} + +func hashTreePosToHex(pos []byte) []byte { + base := keybytesToHex(pos) + base = base[:len(base)-1] + term := base[len(base)-1] + base = base[:len(base)-2+int(term&1)] + // apply terminator flag + if term >= 2 { + base = append(base, 16) + } + return base +} diff --git a/trie/hasher.go b/trie/hasher.go index 5186d76698..08770d493b 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -70,7 +70,7 @@ func (h *hasher) returnCalculator(calculator *calculator) { // hash collapses a node down into a hash node, also returning a copy of the // original node initialized with the computed hash to replace the original one. -func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) { +func (h *hasher) hash(n node, db DatabaseWriter, prefix []byte, force bool) (node, node, error) { // If we're not storing the node, just hashing, use available cached data if hash, dirty := n.cache(); hash != nil { if db == nil { @@ -87,11 +87,11 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) } } // Trie not processed yet or needs storage, walk the children - collapsed, cached, err := h.hashChildren(n, db) + collapsed, cached, err := h.hashChildren(n, db, prefix) if err != nil { return hashNode{}, n, err } - hashed, err := h.store(collapsed, db, force) + hashed, err := h.store(collapsed, db, prefix, force) if err != nil { return hashNode{}, n, err } @@ -117,7 +117,7 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) // hashChildren replaces the children of a node with their hashes if the encoded // size of the child is larger than a hash, returning the collapsed node as well // as a replacement for the original node with the child hashes cached in. -func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, error) { +func (h *hasher) hashChildren(original node, db DatabaseWriter, prefix []byte) (node, node, error) { var err error switch n := original.(type) { @@ -128,7 +128,7 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err cached.Key = common.CopyBytes(n.Key) if _, ok := n.Val.(valueNode); !ok { - collapsed.Val, cached.Val, err = h.hash(n.Val, db, false) + collapsed.Val, cached.Val, err = h.hash(n.Val, db, append(prefix, n.Key...), false) if err != nil { return original, original, err } @@ -155,7 +155,11 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err } // Hash all other children properly var herr error - collapsed.Children[index], cached.Children[index], herr = h.hash(n.Children[index], db, false) + // copy prefix in order to be thread safe + childPrefix := make([]byte, len(prefix)+1) + copy(childPrefix[:len(prefix)], prefix) + childPrefix[len(prefix)] = byte(index) + collapsed.Children[index], cached.Children[index], herr = h.hash(n.Children[index], db, childPrefix, false) if herr != nil { h.mu.Lock() // rarely if ever locked, no congenstion err = herr @@ -197,7 +201,7 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err } } -func (h *hasher) store(n node, db DatabaseWriter, force bool) (node, error) { +func (h *hasher) store(n node, db DatabaseWriter, prefix []byte, force bool) (node, error) { // Don't store hashes or empty nodes. if _, isHash := n.(hashNode); n == nil || isHash { return n, nil @@ -221,7 +225,7 @@ func (h *hasher) store(n node, db DatabaseWriter, force bool) (node, error) { if db != nil { // db might be a leveldb batch, which is not safe for concurrent writes h.mu.Lock() - err := db.Put(hash, calculator.buffer.Bytes()) + err := db.Put(hexToHashTreePos(prefix), hash, calculator.buffer.Bytes()) h.mu.Unlock() return hash, err diff --git a/trie/proof.go b/trie/proof.go index 5e886a2598..8f5705be3e 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -26,6 +26,14 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +type ProofReader interface { + Get(hash []byte) (value []byte, err error) +} + +type ProofWriter interface { + Put(hash, data []byte) error +} + // Prove constructs a merkle proof for key. The result contains all // encoded nodes on the path to the value at key. The value itself is // also included in the last node and can be retrieved by verifying @@ -35,65 +43,73 @@ 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, fromLevel uint, proofDb DatabaseWriter) error { - // Collect all nodes on the path to key. +func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ProofWriter) error { key = keybytesToHex(key) + _, err := t.ProveHexKey(key, fromLevel, proofDb) + return err +} + +func (t *Trie) ProveHexKey(key []byte, fromLevel uint, proofDb ProofWriter) (node, error) { + // Collect all nodes on the path to key. nodes := []node{} tn := t.root - for len(key) > 0 && tn != nil { + ptr := 0 + for len(key) > ptr && tn != nil { switch n := tn.(type) { case *shortNode: - if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { + if len(key) < ptr+len(n.Key) || !bytes.Equal(n.Key, key[ptr:ptr+len(n.Key)]) { // The trie doesn't contain the key. tn = nil } else { tn = n.Val - key = key[len(n.Key):] + ptr += len(n.Key) } nodes = append(nodes, n) case *fullNode: - tn = n.Children[key[0]] - key = key[1:] + tn = n.Children[key[ptr]] + ptr++ nodes = append(nodes, n) case hashNode: var err error - tn, err = t.resolveHash(n, nil) + tn, err = t.resolveHash(n, key[:ptr]) if err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - return err + return nil, err } default: panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) } } - hasher := newHasher(0, 0) - for i, n := range nodes { - // Don't bother checking for errors here since hasher panics - // if encoding doesn't work and we're not writing to any database. - n, _, _ = hasher.hashChildren(n, nil) - hn, _ := hasher.store(n, nil, false) - if hash, ok := hn.(hashNode); ok || i == 0 { - // If the node's database encoding is a hash (or is the - // root node), it becomes a proof element. - if fromLevel > 0 { - fromLevel-- - } else { - enc, _ := rlp.EncodeToBytes(n) - if !ok { - hash = crypto.Keccak256(enc) + if proofDb != nil { + hasher := newHasher(0, 0) + for i, n := range nodes { + // Don't bother checking for errors here since hasher panics + // if encoding doesn't work and we're not writing to any database. + n, _, _ = hasher.hashChildren(n, nil, nil) + hn, _ := hasher.store(n, nil, nil, false) + if hash, ok := hn.(hashNode); ok || i == 0 { + // If the node's database encoding is a hash (or is the + // root node), it becomes a proof element. + if fromLevel > 0 { + fromLevel-- + } else { + enc, _ := rlp.EncodeToBytes(n) + if !ok { + hash = crypto.Keccak256(enc) + } + proofDb.Put(hash, enc) } - proofDb.Put(hash, enc) } } } - return nil + return tn, nil } // VerifyProof checks merkle proofs. The given proof must contain the // value for key in a trie with the given root hash. VerifyProof // returns an error if the proof contains invalid trie nodes or the // wrong value. -func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (value []byte, err error, nodes int) { +func VerifyProof(rootHash common.Hash, key []byte, proofDb ProofReader) (value []byte, err error, nodes int) { key = keybytesToHex(key) wantHash := rootHash[:] for i := 0; ; i++ { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 1fde45165e..1fbbd73355 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -23,9 +23,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -var secureKeyPrefix = []byte("secure-key-") - -const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash +const secureKeyLength = 100 //??? // SecureTrie wraps a trie with key hashing. In a secure trie, all // access operations hash the key using keccak256. This prevents @@ -135,7 +133,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.trie.db.Get(SecHashTreePos(shaKey), shaKey) return key } @@ -176,7 +174,8 @@ func (t *SecureTrie) NodeIterator(start []byte) NodeIterator { func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { if len(t.getSecKeyCache()) > 0 { for hk, key := range t.secKeyCache { - if err := db.Put(t.secKey([]byte(hk)), key); err != nil { + shaKey := []byte(hk) + if err := db.Put(SecHashTreePos(shaKey), shaKey, key); err != nil { return common.Hash{}, err } } @@ -185,15 +184,6 @@ func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { return t.trie.CommitTo(db) } -// secKey returns the database key for the preimage of key, as an ephemeral buffer. -// The caller must not hold onto the return value because it will become -// invalid on the next call to hashKey or secKey. -func (t *SecureTrie) secKey(key []byte) []byte { - buf := append(t.secKeyBuf[:0], secureKeyPrefix...) - buf = append(buf, key...) - return buf -} - // hashKey returns the hash of key as an ephemeral buffer. // The caller must not hold onto the return value because it will become // invalid on the next call to hashKey or secKey. diff --git a/trie/sync.go b/trie/sync.go index fea10051f4..fd8ab33e26 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -41,6 +41,7 @@ type request struct { parents []*request // Parent state nodes referencing this entry (notify all upon completion) 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 + prefix []byte // key prefix leading to the trie node callback TrieSyncLeafCallback // Callback to invoke if a leaf node it reached on this branch } @@ -104,7 +105,8 @@ func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, c return } key := root.Bytes() - blob, _ := s.database.Get(key) + panic(nil) // add position + blob, _ := s.database.Get(nil, key) if local, err := decodeNode(key, blob, 0); local != nil && err == nil { return } @@ -138,7 +140,8 @@ func (s *TrieSync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) if _, ok := s.membatch.batch[hash]; ok { return } - if ok, _ := s.database.Has(hash.Bytes()); ok { + panic(nil) // add position + if ok, _ := s.database.Has(nil, hash.Bytes()); ok { return } // Assemble the new sub-trie sync request @@ -220,7 +223,8 @@ func (s *TrieSync) Process(results []SyncResult) (bool, int, error) { func (s *TrieSync) Commit(dbw DatabaseWriter) (int, error) { // Dump the membatch into a database dbw for i, key := range s.membatch.order { - if err := dbw.Put(key[:], s.membatch.batch[key]); err != nil { + panic(nil) // add position + if err := dbw.Put(nil, key[:], s.membatch.batch[key]); err != nil { return i, err } } @@ -296,7 +300,8 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) { if _, ok := s.membatch.batch[hash]; ok { continue } - if ok, _ := s.database.Has(node); ok { + panic(nil) // add position + if ok, _ := s.database.Has(req.prefix, node); ok { continue } // Locally unknown node, schedule for retrieval diff --git a/trie/trie.go b/trie/trie.go index 7c1b5e1b61..eddf519872 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -65,8 +65,8 @@ type Database interface { // DatabaseReader wraps the Get method of a backing store for the trie. type DatabaseReader interface { - Get(key []byte) (value []byte, err error) - Has(key []byte) (bool, error) + Get(position, hash []byte) (value []byte, err error) + Has(position, hash []byte) (bool, error) } // DatabaseWriter wraps the Put method of a backing store for the trie. @@ -74,7 +74,7 @@ type DatabaseWriter interface { // Put stores the mapping key->value in the database. // Implementations must not hold onto the value bytes, the trie // will reuse the slice across calls to Put. - Put(key, value []byte) error + Put(position, hash, data []byte) error } // Trie is a Merkle Patricia Trie. @@ -389,7 +389,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { // shortNode{..., shortNode{...}}. Since the entry // might not be loaded yet, resolve it just for this // check. - cnode, err := t.resolve(n.Children[pos], prefix) + cnode, err := t.resolve(n.Children[pos], append(prefix, byte(pos))) if err != nil { return false, nil, err } @@ -447,7 +447,7 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) { func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { cacheMissCounter.Inc(1) - enc, err := t.db.Get(n) + enc, err := t.db.Get(hexToHashTreePos(prefix), n) if err != nil || enc == nil { return nil, &MissingNodeError{NodeHash: common.BytesToHash(n), Path: prefix} } @@ -501,5 +501,21 @@ func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) { return hashNode(emptyRoot.Bytes()), nil, nil } h := newHasher(t.cachegen, t.cachelimit) - return h.hash(t.root, db, true) + return h.hash(t.root, db, nil, true) +} + +func (t *Trie) HasData(position, hash []byte) bool { + hex := hashTreePosToHex(position) + //fmt.Println("pos", position, "hex", hex, "hash", hash) + n, err := t.ProveHexKey(hex, 0, nil) + if n == nil || err != nil { + return false + } + hasher := newHasher(0, 0) + n, _, _ = hasher.hashChildren(n, nil, nil) + hn, _ := hasher.store(n, nil, nil, false) + nodeHash, ok := hn.(hashNode) + eq := ok && bytes.Equal(nodeHash, hash) + //fmt.Println("eq", eq, ok, nodeHash, hash) + return eq }