From 563c37711587a230efee9923005fb08780978a42 Mon Sep 17 00:00:00 2001 From: devopsbo3 <69951731+devopsbo3@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:27:53 -0600 Subject: [PATCH] Revert "all: implement path-based state scheme (#25963)" This reverts commit b7ea2f24450be3a10c8f3df302db6070e2788d83. --- core/blockchain.go | 6 +- core/rawdb/accessors_chain_test.go | 4 +- core/rawdb/accessors_indexes_test.go | 4 +- core/rawdb/accessors_state.go | 172 ------- core/rawdb/accessors_trie.go | 46 +- core/rawdb/ancient_scheme.go | 30 +- core/rawdb/chain_iterator_test.go | 8 +- core/rawdb/database.go | 8 +- core/rawdb/freezer.go | 27 +- core/rawdb/freezer_resettable.go | 6 +- core/rawdb/freezer_test.go | 4 +- core/rawdb/schema.go | 12 - core/rawdb/table.go | 4 +- core/state/statedb.go | 11 +- core/types/hashes.go | 11 - ethdb/database.go | 4 +- ethdb/remotedb/remotedb.go | 4 +- trie/committer.go | 15 +- trie/database.go | 45 +- trie/database_test.go | 8 +- trie/iterator_test.go | 12 +- trie/sync_test.go | 38 +- trie/testutil/utils.go | 61 --- trie/tracer.go | 11 +- trie/trie.go | 22 +- trie/trie_reader.go | 11 +- trie/trie_test.go | 44 +- trie/triedb/hashdb/database.go | 51 +-- trie/triedb/pathdb/database.go | 392 ---------------- trie/triedb/pathdb/database_test.go | 573 ------------------------ trie/triedb/pathdb/difflayer.go | 174 -------- trie/triedb/pathdb/difflayer_test.go | 170 ------- trie/triedb/pathdb/disklayer.go | 296 ------------- trie/triedb/pathdb/errors.go | 51 --- trie/triedb/pathdb/history.go | 641 --------------------------- trie/triedb/pathdb/history_test.go | 290 ------------ trie/triedb/pathdb/journal.go | 378 ---------------- trie/triedb/pathdb/layertree.go | 214 --------- trie/triedb/pathdb/metrics.go | 50 --- trie/triedb/pathdb/nodebuffer.go | 275 ------------ trie/triedb/pathdb/testutils.go | 156 ------- trie/trienode/node.go | 62 ++- trie/triestate/state.go | 243 +--------- 43 files changed, 226 insertions(+), 4418 deletions(-) delete mode 100644 trie/testutil/utils.go delete mode 100644 trie/triedb/pathdb/database.go delete mode 100644 trie/triedb/pathdb/database_test.go delete mode 100644 trie/triedb/pathdb/difflayer.go delete mode 100644 trie/triedb/pathdb/difflayer_test.go delete mode 100644 trie/triedb/pathdb/disklayer.go delete mode 100644 trie/triedb/pathdb/errors.go delete mode 100644 trie/triedb/pathdb/history.go delete mode 100644 trie/triedb/pathdb/history_test.go delete mode 100644 trie/triedb/pathdb/journal.go delete mode 100644 trie/triedb/pathdb/layertree.go delete mode 100644 trie/triedb/pathdb/metrics.go delete mode 100644 trie/triedb/pathdb/nodebuffer.go delete mode 100644 trie/triedb/pathdb/testutils.go diff --git a/core/blockchain.go b/core/blockchain.go index 3952c31b68..2f549806c6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -713,7 +713,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha if num+1 <= frozen { // Truncate all relative data(header, total difficulty, body, receipt // and canonical hash) from ancient store. - if _, err := bc.db.TruncateHead(num); err != nil { + if err := bc.db.TruncateHead(num); err != nil { log.Crit("Failed to truncate ancient data", "number", num, "err", err) } // Remove the hash <-> number mapping from the active store. @@ -1136,7 +1136,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ size += int64(batch.ValueSize()) if err = batch.Write(); err != nil { snapBlock := bc.CurrentSnapBlock().Number.Uint64() - if _, err := bc.db.TruncateHead(snapBlock + 1); err != nil { + if err := bc.db.TruncateHead(snapBlock + 1); err != nil { log.Error("Can't truncate ancient store after failed insert", "err", err) } return 0, err @@ -1154,7 +1154,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if !updateHead(blockChain[len(blockChain)-1]) { // We end up here if the header chain has reorg'ed, and the blocks/receipts // don't match the canonical chain. - if _, err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil { + if err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil { log.Error("Can't truncate ancient store after failed insert", "err", err) } return 0, errSideChainReceipts diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index beeec9f5a6..32e38a81ce 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -85,7 +85,7 @@ func TestBodyStorage(t *testing.T) { WriteBody(db, hash, 0, body) if entry := ReadBody(db, hash, 0); entry == nil { t.Fatalf("Stored body not found") - } else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { + } else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body) } if entry := ReadBodyRLP(db, hash, 0); entry == nil { @@ -139,7 +139,7 @@ func TestBlockStorage(t *testing.T) { } if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored body not found") - } else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(block.Transactions(), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { + } else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(block.Transactions(), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body()) } // Delete the block and verify the execution diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index 124389ba7a..0200755251 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -29,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -var newTestHasher = blocktest.NewHasher +var newHasher = blocktest.NewHasher // Tests that positional lookup metadata can be stored and retrieved. func TestLookupStorage(t *testing.T) { @@ -76,7 +76,7 @@ func TestLookupStorage(t *testing.T) { tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) txs := []*types.Transaction{tx1, tx2, tx3} - block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newTestHasher()) + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newHasher()) // Check that no transactions entries are in a pristine database for i, tx := range txs { diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 9ce58e7d27..39900df23e 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -17,8 +17,6 @@ package rawdb import ( - "encoding/binary" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -94,173 +92,3 @@ func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { log.Crit("Failed to delete contract code", "err", err) } } - -// ReadStateID retrieves the state id with the provided state root. -func ReadStateID(db ethdb.KeyValueReader, root common.Hash) *uint64 { - data, err := db.Get(stateIDKey(root)) - if err != nil || len(data) == 0 { - return nil - } - number := binary.BigEndian.Uint64(data) - return &number -} - -// WriteStateID writes the provided state lookup to database. -func WriteStateID(db ethdb.KeyValueWriter, root common.Hash, id uint64) { - var buff [8]byte - binary.BigEndian.PutUint64(buff[:], id) - if err := db.Put(stateIDKey(root), buff[:]); err != nil { - log.Crit("Failed to store state ID", "err", err) - } -} - -// DeleteStateID deletes the specified state lookup from the database. -func DeleteStateID(db ethdb.KeyValueWriter, root common.Hash) { - if err := db.Delete(stateIDKey(root)); err != nil { - log.Crit("Failed to delete state ID", "err", err) - } -} - -// ReadPersistentStateID retrieves the id of the persistent state from the database. -func ReadPersistentStateID(db ethdb.KeyValueReader) uint64 { - data, _ := db.Get(persistentStateIDKey) - if len(data) != 8 { - return 0 - } - return binary.BigEndian.Uint64(data) -} - -// WritePersistentStateID stores the id of the persistent state into database. -func WritePersistentStateID(db ethdb.KeyValueWriter, number uint64) { - if err := db.Put(persistentStateIDKey, encodeBlockNumber(number)); err != nil { - log.Crit("Failed to store the persistent state ID", "err", err) - } -} - -// ReadTrieJournal retrieves the serialized in-memory trie nodes of layers saved at -// the last shutdown. -func ReadTrieJournal(db ethdb.KeyValueReader) []byte { - data, _ := db.Get(trieJournalKey) - return data -} - -// WriteTrieJournal stores the serialized in-memory trie nodes of layers to save at -// shutdown. -func WriteTrieJournal(db ethdb.KeyValueWriter, journal []byte) { - if err := db.Put(trieJournalKey, journal); err != nil { - log.Crit("Failed to store tries journal", "err", err) - } -} - -// DeleteTrieJournal deletes the serialized in-memory trie nodes of layers saved at -// the last shutdown. -func DeleteTrieJournal(db ethdb.KeyValueWriter) { - if err := db.Delete(trieJournalKey); err != nil { - log.Crit("Failed to remove tries journal", "err", err) - } -} - -// ReadStateHistoryMeta retrieves the metadata corresponding to the specified -// state history. Compute the position of state history in freezer by minus -// one since the id of first state history starts from one(zero for initial -// state). -func ReadStateHistoryMeta(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryMeta, id-1) - if err != nil { - return nil - } - return blob -} - -// ReadStateHistoryMetaList retrieves a batch of meta objects with the specified -// start position and count. Compute the position of state history in freezer by -// minus one since the id of first state history starts from one(zero for initial -// state). -func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, error) { - return db.AncientRange(stateHistoryMeta, start-1, count, 0) -} - -// ReadStateAccountIndex retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). -func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryAccountIndex, id-1) - if err != nil { - return nil - } - return blob -} - -// ReadStateStorageIndex retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). -func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryStorageIndex, id-1) - if err != nil { - return nil - } - return blob -} - -// ReadStateAccountHistory retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). -func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryAccountData, id-1) - if err != nil { - return nil - } - return blob -} - -// ReadStateStorageHistory retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). -func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryStorageData, id-1) - if err != nil { - return nil - } - return blob -} - -// ReadStateHistory retrieves the state history from database with provided id. -// Compute the position of state history in freezer by minus one since the id -// of first state history starts from one(zero for initial state). -func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, []byte, []byte, error) { - meta, err := db.Ancient(stateHistoryMeta, id-1) - if err != nil { - return nil, nil, nil, nil, nil, err - } - accountIndex, err := db.Ancient(stateHistoryAccountIndex, id-1) - if err != nil { - return nil, nil, nil, nil, nil, err - } - storageIndex, err := db.Ancient(stateHistoryStorageIndex, id-1) - if err != nil { - return nil, nil, nil, nil, nil, err - } - accountData, err := db.Ancient(stateHistoryAccountData, id-1) - if err != nil { - return nil, nil, nil, nil, nil, err - } - storageData, err := db.Ancient(stateHistoryStorageData, id-1) - if err != nil { - return nil, nil, nil, nil, nil, err - } - return meta, accountIndex, storageIndex, accountData, storageData, nil -} - -// WriteStateHistory writes the provided state history to database. Compute the -// position of state history in freezer by minus one since the id of first state -// history starts from one(zero for initial state). -func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) { - db.ModifyAncients(func(op ethdb.AncientWriteOp) error { - op.AppendRaw(stateHistoryMeta, id-1, meta) - op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex) - op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex) - op.AppendRaw(stateHistoryAccountData, id-1, accounts) - op.AppendRaw(stateHistoryStorageData, id-1, storages) - return nil - }) -} diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go index 12f1ecdf83..e240213025 100644 --- a/core/rawdb/accessors_trie.go +++ b/core/rawdb/accessors_trie.go @@ -46,23 +46,21 @@ const HashScheme = "hashScheme" // on extra state diffs to survive deep reorg. const PathScheme = "pathScheme" -// hasher is used to compute the sha256 hash of the provided data. -type hasher struct{ sha crypto.KeccakState } +// nodeHasher used to derive the hash of trie node. +type nodeHasher struct{ sha crypto.KeccakState } var hasherPool = sync.Pool{ - New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, + New: func() interface{} { return &nodeHasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, } -func newHasher() *hasher { - return hasherPool.Get().(*hasher) -} +func newNodeHasher() *nodeHasher { return hasherPool.Get().(*nodeHasher) } +func returnHasherToPool(h *nodeHasher) { hasherPool.Put(h) } -func (h *hasher) hash(data []byte) common.Hash { - return crypto.HashData(h.sha, data) -} - -func (h *hasher) release() { - hasherPool.Put(h) +func (h *nodeHasher) hashData(data []byte) (n common.Hash) { + h.sha.Reset() + h.sha.Write(data) + h.sha.Read(n[:]) + return n } // ReadAccountTrieNode retrieves the account trie node and the associated node @@ -72,9 +70,9 @@ func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.H if err != nil { return nil, common.Hash{} } - h := newHasher() - defer h.release() - return data, h.hash(data) + hasher := newNodeHasher() + defer returnHasherToPool(hasher) + return data, hasher.hashData(data) } // HasAccountTrieNode checks the account trie node presence with the specified @@ -84,9 +82,9 @@ func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) if err != nil { return false } - h := newHasher() - defer h.release() - return h.hash(data) == hash + hasher := newNodeHasher() + defer returnHasherToPool(hasher) + return hasher.hashData(data) == hash } // WriteAccountTrieNode writes the provided account trie node into database. @@ -110,9 +108,9 @@ func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path if err != nil { return nil, common.Hash{} } - h := newHasher() - defer h.release() - return data, h.hash(data) + hasher := newNodeHasher() + defer returnHasherToPool(hasher) + return data, hasher.hashData(data) } // HasStorageTrieNode checks the storage trie node presence with the provided @@ -122,9 +120,9 @@ func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path [ if err != nil { return false } - h := newHasher() - defer h.release() - return h.hash(data) == hash + hasher := newNodeHasher() + defer returnHasherToPool(hasher) + return hasher.hashData(data) == hash } // WriteStorageTrieNode writes the provided storage trie node into database. diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go index c1cd7fda17..b0428c5f5b 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -16,8 +16,6 @@ package rawdb -import "path/filepath" - // The list of table names of chain freezer. const ( // ChainFreezerHeaderTable indicates the name of the freezer header table. @@ -46,36 +44,10 @@ var chainFreezerNoSnappy = map[string]bool{ ChainFreezerDifficultyTable: true, } -const ( - // stateHistoryTableSize defines the maximum size of freezer data files. - stateHistoryTableSize = 2 * 1000 * 1000 * 1000 - - // stateHistoryAccountIndex indicates the name of the freezer state history table. - stateHistoryMeta = "history.meta" - stateHistoryAccountIndex = "account.index" - stateHistoryStorageIndex = "storage.index" - stateHistoryAccountData = "account.data" - stateHistoryStorageData = "storage.data" -) - -var stateHistoryFreezerNoSnappy = map[string]bool{ - stateHistoryMeta: true, - stateHistoryAccountIndex: false, - stateHistoryStorageIndex: false, - stateHistoryAccountData: false, - stateHistoryStorageData: false, -} - // The list of identifiers of ancient stores. var ( chainFreezerName = "chain" // the folder name of chain segment ancient store. - stateFreezerName = "state" // the folder name of reverse diff ancient store. ) // freezers the collections of all builtin freezers. -var freezers = []string{chainFreezerName, stateFreezerName} - -// NewStateHistoryFreezer initializes the freezer for state history. -func NewStateHistoryFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) { - return NewResettableFreezer(filepath.Join(ancientDir, stateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateHistoryFreezerNoSnappy) -} +var freezers = []string{chainFreezerName} diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index 9e18c8605c..fd405e9d69 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -34,7 +34,7 @@ func TestChainIterator(t *testing.T) { var block *types.Block var txs []*types.Transaction to := common.BytesToAddress([]byte{0x11}) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) // Empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) for i := uint64(1); i <= 10; i++ { @@ -60,7 +60,7 @@ func TestChainIterator(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } @@ -113,7 +113,7 @@ func TestIndexTransactions(t *testing.T) { to := common.BytesToAddress([]byte{0x11}) // Write empty genesis block - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) @@ -140,7 +140,7 @@ func TestIndexTransactions(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 7bc9109061..e864bcb2e8 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -123,13 +123,13 @@ func (db *nofreezedb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, e } // TruncateHead returns an error as we don't have a backing chain freezer. -func (db *nofreezedb) TruncateHead(items uint64) (uint64, error) { - return 0, errNotSupported +func (db *nofreezedb) TruncateHead(items uint64) error { + return errNotSupported } // TruncateTail returns an error as we don't have a backing chain freezer. -func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) { - return 0, errNotSupported +func (db *nofreezedb) TruncateTail(items uint64) error { + return errNotSupported } // Sync returns an error as we don't have a backing chain freezer. diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a9fe234321..2846858e0b 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -275,46 +275,43 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize } // TruncateHead discards any recent data above the provided threshold number. -// It returns the previous head number. -func (f *Freezer) TruncateHead(items uint64) (uint64, error) { +func (f *Freezer) TruncateHead(items uint64) error { if f.readonly { - return 0, errReadOnly + return errReadOnly } f.writeLock.Lock() defer f.writeLock.Unlock() - oitems := f.frozen.Load() - if oitems <= items { - return oitems, nil + if f.frozen.Load() <= items { + return nil } for _, table := range f.tables { if err := table.truncateHead(items); err != nil { - return 0, err + return err } } f.frozen.Store(items) - return oitems, nil + return nil } // TruncateTail discards any recent data below the provided threshold number. -func (f *Freezer) TruncateTail(tail uint64) (uint64, error) { +func (f *Freezer) TruncateTail(tail uint64) error { if f.readonly { - return 0, errReadOnly + return errReadOnly } f.writeLock.Lock() defer f.writeLock.Unlock() - old := f.tail.Load() - if old >= tail { - return old, nil + if f.tail.Load() >= tail { + return nil } for _, table := range f.tables { if err := table.truncateTail(tail); err != nil { - return 0, err + return err } } f.tail.Store(tail) - return old, nil + return nil } // Sync flushes all data tables to disk. diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index 0a3892bcdf..f9a56c6de5 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -170,8 +170,7 @@ func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) } // TruncateHead discards any recent data above the provided threshold number. -// It returns the previous head number. -func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) { +func (f *ResettableFreezer) TruncateHead(items uint64) error { f.lock.RLock() defer f.lock.RUnlock() @@ -179,8 +178,7 @@ func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) { } // TruncateTail discards any recent data below the provided threshold number. -// It returns the previous value -func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) { +func (f *ResettableFreezer) TruncateTail(tail uint64) error { f.lock.RLock() defer f.lock.RUnlock() diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index 96d24cc947..630c9029b0 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -192,7 +192,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { for i := 0; i < 10; i++ { // First reset and write 100 items. - if _, err := f.TruncateHead(0); err != nil { + if err := f.TruncateHead(0); err != nil { t.Fatal("truncate failed:", err) } _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { @@ -227,7 +227,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { wg.Done() }() go func() { - _, truncateErr = f.TruncateHead(10) + truncateErr = f.TruncateHead(10) wg.Done() }() go func() { diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 940ce01549..18722ed5d4 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -43,9 +43,6 @@ var ( // headFinalizedBlockKey tracks the latest known finalized block hash. headFinalizedBlockKey = []byte("LastFinalized") - // persistentStateIDKey tracks the id of latest stored state(for path-based only). - persistentStateIDKey = []byte("LastStateID") - // lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead). lastPivotKey = []byte("LastPivot") @@ -73,9 +70,6 @@ var ( // skeletonSyncStatusKey tracks the skeleton sync status across restarts. skeletonSyncStatusKey = []byte("SkeletonSyncStatus") - // trieJournalKey tracks the in-memory trie node layers across restarts. - trieJournalKey = []byte("TrieJournal") - // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") @@ -110,7 +104,6 @@ var ( // Path-based storage scheme of merkle patricia trie. trieNodeAccountPrefix = []byte("A") // trieNodeAccountPrefix + hexPath -> trie node trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node - stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -247,11 +240,6 @@ func genesisStateSpecKey(hash common.Hash) []byte { return append(genesisPrefix, hash.Bytes()...) } -// stateIDKey = stateIDPrefix + root (32 bytes) -func stateIDKey(root common.Hash) []byte { - return append(stateIDPrefix, root.Bytes()...) -} - // accountTrieNodeKey = trieNodeAccountPrefix + nodePath. func accountTrieNodeKey(path []byte) []byte { return append(trieNodeAccountPrefix, path...) diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 1895f61da2..6d6fa0555d 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -97,13 +97,13 @@ func (t *table) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err e // TruncateHead is a noop passthrough that just forwards the request to the underlying // database. -func (t *table) TruncateHead(items uint64) (uint64, error) { +func (t *table) TruncateHead(items uint64) error { return t.db.TruncateHead(items) } // TruncateTail is a noop passthrough that just forwards the request to the underlying // database. -func (t *table) TruncateTail(items uint64) (uint64, error) { +func (t *table) TruncateTail(items uint64) error { return t.db.TruncateTail(items) } diff --git a/core/state/statedb.go b/core/state/statedb.go index fdaeacc6b3..8321128dcd 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1054,8 +1054,8 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root if it.Hash() == (common.Hash{}) { continue } - nodeSize += common.StorageSize(len(it.Path())) - set.AddNode(it.Path(), trienode.NewDeleted()) + nodeSize += common.StorageSize(len(it.Path()) + len(it.NodeBlob())) + set.AddNode(it.Path(), trienode.NewWithPrev(common.Hash{}, nil, it.NodeBlob())) } if err := it.Error(); err != nil { return false, nil, nil, err @@ -1274,7 +1274,12 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } if root != origin { start := time.Now() - if err := s.db.TrieDB().Update(root, origin, block, nodes, triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)); err != nil { + set := &triestate.Set{ + Accounts: s.accountsOrigin, + Storages: s.storagesOrigin, + Incomplete: incomplete, + } + if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil { return common.Hash{}, err } s.originalRoot = root diff --git a/core/types/hashes.go b/core/types/hashes.go index 3a787aa136..3bad430be5 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -19,7 +19,6 @@ package types import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" ) var ( @@ -41,13 +40,3 @@ var ( // EmptyWithdrawalsHash is the known hash of the empty withdrawal set. EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") ) - -// TrieRootHash returns the hash itself if it's non-empty or the predefined -// emptyHash one instead. -func TrieRootHash(hash common.Hash) common.Hash { - if hash == (common.Hash{}) { - log.Error("Zero trie root hash!") - return EmptyRootHash - } - return hash -} diff --git a/ethdb/database.go b/ethdb/database.go index 4d4817daf2..3fe6dfa73c 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -114,14 +114,14 @@ type AncientWriter interface { // TruncateHead discards all but the first n ancient data from the ancient store. // After the truncation, the latest item can be accessed it item_n-1(start from 0). - TruncateHead(n uint64) (uint64, error) + TruncateHead(n uint64) error // TruncateTail discards the first n ancient data from the ancient store. The already // deleted items are ignored. After the truncation, the earliest item can be accessed // is item_n(start from 0). The deleted items may not be removed from the ancient store // immediately, but only when the accumulated deleted data reach the threshold then // will be removed all together. - TruncateTail(n uint64) (uint64, error) + TruncateTail(n uint64) error // Sync flushes all in-memory ancient store data to disk. Sync() error diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index c1c803caf2..9ce657d780 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -98,11 +98,11 @@ func (db *Database) ModifyAncients(f func(ethdb.AncientWriteOp) error) (int64, e panic("not supported") } -func (db *Database) TruncateHead(n uint64) (uint64, error) { +func (db *Database) TruncateHead(n uint64) error { panic("not supported") } -func (db *Database) TruncateTail(n uint64) (uint64, error) { +func (db *Database) TruncateTail(n uint64) error { panic("not supported") } diff --git a/trie/committer.go b/trie/committer.go index 92163cdb3b..e825287fd2 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -131,15 +131,22 @@ func (c *committer) store(path []byte, n node) node { // The node is embedded in its parent, in other words, this node // will not be stored in the database independently, mark it as // deleted only if the node was existent in database before. - _, ok := c.tracer.accessList[string(path)] + prev, ok := c.tracer.accessList[string(path)] if ok { - c.nodes.AddNode(path, trienode.NewDeleted()) + c.nodes.AddNode(path, trienode.NewWithPrev(common.Hash{}, nil, prev)) } return n } // Collect the dirty node to nodeset for return. - nhash := common.BytesToHash(hash) - c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n))) + var ( + nhash = common.BytesToHash(hash) + node = trienode.NewWithPrev( + nhash, + nodeToBytes(n), + c.tracer.accessList[string(path)], + ) + ) + c.nodes.AddNode(path, node) // Collect the corresponding leaf node if it's required. We don't check // full node since it's impossible to store value in fullNode. The key diff --git a/trie/database.go b/trie/database.go index 49a884fd7f..08ef5d07e1 100644 --- a/trie/database.go +++ b/trie/database.go @@ -19,19 +19,18 @@ package trie import ( "errors" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie/triedb/hashdb" - "github.com/ethereum/go-ethereum/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/triestate" ) // Config defines all necessary options for database. type Config struct { - Cache int // Memory allowance (MB) to use for caching trie nodes in memory - Preimages bool // Flag whether the preimage of trie key is recorded - PathDB *pathdb.Config // Configs for experimental path-based scheme, not used yet. + Cache int // Memory allowance (MB) to use for caching trie nodes in memory + Preimages bool // Flag whether the preimage of trie key is recorded // Testing hooks OnCommit func(states *triestate.Set) // Hook invoked when commit is performed @@ -54,10 +53,7 @@ type backend interface { // Update performs a state transition by committing dirty nodes contained // in the given set in order to update state from the specified parent to // the specified root. - // - // The passed in maps(nodes, states) will be retained to avoid copying - // everything. Therefore, these maps must not be changed afterwards. - Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error + Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error // Commit writes all relevant trie nodes belonging to the specified state // to disk. Report specifies whether logs will be displayed in info level. @@ -71,15 +67,20 @@ type backend interface { // types of node backend as an entrypoint. It's responsible for all interactions // relevant with trie nodes and node preimages. type Database struct { - config *Config // Configuration for trie database - diskdb ethdb.Database // Persistent database to store the snapshot - preimages *preimageStore // The store for caching preimages - backend backend // The backend for managing trie nodes + config *Config // Configuration for trie database + diskdb ethdb.Database // Persistent database to store the snapshot + cleans *fastcache.Cache // Megabytes permitted using for read caches + preimages *preimageStore // The store for caching preimages + backend backend // The backend for managing trie nodes } // prepare initializes the database with provided configs, but the // database backend is still left as nil. func prepare(diskdb ethdb.Database, config *Config) *Database { + var cleans *fastcache.Cache + if config != nil && config.Cache > 0 { + cleans = fastcache.New(config.Cache * 1024 * 1024) + } var preimages *preimageStore if config != nil && config.Preimages { preimages = newPreimageStore(diskdb) @@ -87,6 +88,7 @@ func prepare(diskdb ethdb.Database, config *Config) *Database { return &Database{ config: config, diskdb: diskdb, + cleans: cleans, preimages: preimages, } } @@ -101,34 +103,21 @@ func NewDatabase(diskdb ethdb.Database) *Database { // The path-based scheme is not activated yet, always initialized with legacy // hash-based scheme by default. func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database { - var cleans int - if config != nil && config.Cache != 0 { - cleans = config.Cache * 1024 * 1024 - } db := prepare(diskdb, config) - db.backend = hashdb.New(diskdb, cleans, mptResolver{}) + db.backend = hashdb.New(diskdb, db.cleans, mptResolver{}) return db } // Reader returns a reader for accessing all trie nodes with provided state root. // An error will be returned if the requested state is not available. func (db *Database) Reader(blockRoot common.Hash) (Reader, error) { - switch b := db.backend.(type) { - case *hashdb.Database: - return b.Reader(blockRoot) - case *pathdb.Database: - return b.Reader(blockRoot) - } - return nil, errors.New("unknown backend") + return db.backend.(*hashdb.Database).Reader(blockRoot) } // Update performs a state transition by committing dirty nodes contained in the // given set in order to update state from the specified parent to the specified // root. The held pre-images accumulated up to this point will be flushed in case // the size exceeds the threshold. -// -// The passed in maps(nodes, states) will be retained to avoid copying everything. -// Therefore, these maps must not be changed afterwards. func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { if db.config != nil && db.config.OnCommit != nil { db.config.OnCommit(states) @@ -136,7 +125,7 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n if db.preimages != nil { db.preimages.commit(false) } - return db.backend.Update(root, parent, block, nodes, states) + return db.backend.Update(root, parent, nodes) } // Commit iterates over all the children of a particular node, writes them out diff --git a/trie/database_test.go b/trie/database_test.go index ed43a81e59..cad462f737 100644 --- a/trie/database_test.go +++ b/trie/database_test.go @@ -20,16 +20,16 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie/triedb/hashdb" - "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) // newTestDatabase initializes the trie database with specified scheme. func newTestDatabase(diskdb ethdb.Database, scheme string) *Database { db := prepare(diskdb, nil) if scheme == rawdb.HashScheme { - db.backend = hashdb.New(diskdb, 0, mptResolver{}) - } else { - db.backend = pathdb.New(diskdb, &pathdb.Config{}) // disable clean/dirty cache + db.backend = hashdb.New(diskdb, db.cleans, mptResolver{}) } + //} else { + // db.backend = snap.New(diskdb, db.cleans, nil) + //} return db } diff --git a/trie/iterator_test.go b/trie/iterator_test.go index bf20c00c64..abe9cf1b11 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -130,7 +130,7 @@ type iterationElement struct { // Tests that the node iterator indeed walks over the entire database contents. func TestNodeIteratorCoverage(t *testing.T) { testNodeIteratorCoverage(t, rawdb.HashScheme) - testNodeIteratorCoverage(t, rawdb.PathScheme) + //testNodeIteratorCoverage(t, rawdb.PathScheme) } func testNodeIteratorCoverage(t *testing.T, scheme string) { @@ -355,8 +355,8 @@ func TestIteratorNoDups(t *testing.T) { func TestIteratorContinueAfterError(t *testing.T) { testIteratorContinueAfterError(t, false, rawdb.HashScheme) testIteratorContinueAfterError(t, true, rawdb.HashScheme) - testIteratorContinueAfterError(t, false, rawdb.PathScheme) - testIteratorContinueAfterError(t, true, rawdb.PathScheme) + // testIteratorContinueAfterError(t, false, rawdb.PathScheme) + // testIteratorContinueAfterError(t, true, rawdb.PathScheme) } func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { @@ -461,8 +461,8 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { func TestIteratorContinueAfterSeekError(t *testing.T) { testIteratorContinueAfterSeekError(t, false, rawdb.HashScheme) testIteratorContinueAfterSeekError(t, true, rawdb.HashScheme) - testIteratorContinueAfterSeekError(t, false, rawdb.PathScheme) - testIteratorContinueAfterSeekError(t, true, rawdb.PathScheme) + // testIteratorContinueAfterSeekError(t, false, rawdb.PathScheme) + // testIteratorContinueAfterSeekError(t, true, rawdb.PathScheme) } func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme string) { @@ -534,7 +534,7 @@ func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) in func TestIteratorNodeBlob(t *testing.T) { testIteratorNodeBlob(t, rawdb.HashScheme) - testIteratorNodeBlob(t, rawdb.PathScheme) + //testIteratorNodeBlob(t, rawdb.PathScheme) } type loggingDb struct { diff --git a/trie/sync_test.go b/trie/sync_test.go index b6fe8d84a6..589e2858fb 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -111,16 +111,16 @@ type trieElement struct { func TestEmptySync(t *testing.T) { dbA := NewDatabase(rawdb.NewMemoryDatabase()) dbB := NewDatabase(rawdb.NewMemoryDatabase()) - dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) - dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + //dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + //dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) emptyA := NewEmpty(dbA) emptyB, _ := New(TrieID(types.EmptyRootHash), dbB) - emptyC := NewEmpty(dbC) - emptyD, _ := New(TrieID(types.EmptyRootHash), dbD) + //emptyC := NewEmpty(dbC) + //emptyD, _ := New(TrieID(types.EmptyRootHash), dbD) - for i, trie := range []*Trie{emptyA, emptyB, emptyC, emptyD} { - sync := NewSync(trie.Hash(), memorydb.New(), nil, []*Database{dbA, dbB, dbC, dbD}[i].Scheme()) + for i, trie := range []*Trie{emptyA, emptyB /*emptyC, emptyD*/} { + sync := NewSync(trie.Hash(), memorydb.New(), nil, []*Database{dbA, dbB /*dbC, dbD*/}[i].Scheme()) if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, paths, nodes, codes) } @@ -134,10 +134,10 @@ func TestIterativeSync(t *testing.T) { testIterativeSync(t, 100, false, rawdb.HashScheme) testIterativeSync(t, 1, true, rawdb.HashScheme) testIterativeSync(t, 100, true, rawdb.HashScheme) - testIterativeSync(t, 1, false, rawdb.PathScheme) - testIterativeSync(t, 100, false, rawdb.PathScheme) - testIterativeSync(t, 1, true, rawdb.PathScheme) - testIterativeSync(t, 100, true, rawdb.PathScheme) + // testIterativeSync(t, 1, false, rawdb.PathScheme) + // testIterativeSync(t, 100, false, rawdb.PathScheme) + // testIterativeSync(t, 1, true, rawdb.PathScheme) + // testIterativeSync(t, 100, true, rawdb.PathScheme) } func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { @@ -212,7 +212,7 @@ func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { // partial results are returned, and the others sent only later. func TestIterativeDelayedSync(t *testing.T) { testIterativeDelayedSync(t, rawdb.HashScheme) - testIterativeDelayedSync(t, rawdb.PathScheme) + //testIterativeDelayedSync(t, rawdb.PathScheme) } func testIterativeDelayedSync(t *testing.T, scheme string) { @@ -280,8 +280,8 @@ func testIterativeDelayedSync(t *testing.T, scheme string) { func TestIterativeRandomSyncIndividual(t *testing.T) { testIterativeRandomSync(t, 1, rawdb.HashScheme) testIterativeRandomSync(t, 100, rawdb.HashScheme) - testIterativeRandomSync(t, 1, rawdb.PathScheme) - testIterativeRandomSync(t, 100, rawdb.PathScheme) + // testIterativeRandomSync(t, 1, rawdb.PathScheme) + // testIterativeRandomSync(t, 100, rawdb.PathScheme) } func testIterativeRandomSync(t *testing.T, count int, scheme string) { @@ -348,7 +348,7 @@ func testIterativeRandomSync(t *testing.T, count int, scheme string) { // partial results are returned (Even those randomly), others sent only later. func TestIterativeRandomDelayedSync(t *testing.T) { testIterativeRandomDelayedSync(t, rawdb.HashScheme) - testIterativeRandomDelayedSync(t, rawdb.PathScheme) + // testIterativeRandomDelayedSync(t, rawdb.PathScheme) } func testIterativeRandomDelayedSync(t *testing.T, scheme string) { @@ -420,7 +420,7 @@ func testIterativeRandomDelayedSync(t *testing.T, scheme string) { // have such references. func TestDuplicateAvoidanceSync(t *testing.T) { testDuplicateAvoidanceSync(t, rawdb.HashScheme) - testDuplicateAvoidanceSync(t, rawdb.PathScheme) + // testDuplicateAvoidanceSync(t, rawdb.PathScheme) } func testDuplicateAvoidanceSync(t *testing.T, scheme string) { @@ -491,10 +491,12 @@ func testDuplicateAvoidanceSync(t *testing.T, scheme string) { // the database. func TestIncompleteSyncHash(t *testing.T) { testIncompleteSync(t, rawdb.HashScheme) - testIncompleteSync(t, rawdb.PathScheme) + // testIncompleteSync(t, rawdb.PathScheme) } func testIncompleteSync(t *testing.T, scheme string) { + t.Parallel() + // Create a random trie to copy _, srcDb, srcTrie, _ := makeTestTrie(scheme) @@ -580,7 +582,7 @@ func testIncompleteSync(t *testing.T, scheme string) { // depth. func TestSyncOrdering(t *testing.T) { testSyncOrdering(t, rawdb.HashScheme) - testSyncOrdering(t, rawdb.PathScheme) + // testSyncOrdering(t, rawdb.PathScheme) } func testSyncOrdering(t *testing.T, scheme string) { @@ -714,7 +716,7 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database // states synced in the last cycle. func TestSyncMovingTarget(t *testing.T) { testSyncMovingTarget(t, rawdb.HashScheme) - testSyncMovingTarget(t, rawdb.PathScheme) + // testSyncMovingTarget(t, rawdb.PathScheme) } func testSyncMovingTarget(t *testing.T, scheme string) { diff --git a/trie/testutil/utils.go b/trie/testutil/utils.go deleted file mode 100644 index a75d0431b0..0000000000 --- a/trie/testutil/utils.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023 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 testutil - -import ( - crand "crypto/rand" - "encoding/binary" - mrand "math/rand" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/trie/trienode" -) - -// Prng is a pseudo random number generator seeded by strong randomness. -// The randomness is printed on startup in order to make failures reproducible. -var prng = initRand() - -func initRand() *mrand.Rand { - var seed [8]byte - crand.Read(seed[:]) - rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:])))) - return rnd -} - -// RandBytes generates a random byte slice with specified length. -func RandBytes(n int) []byte { - r := make([]byte, n) - prng.Read(r) - return r -} - -// RandomHash generates a random blob of data and returns it as a hash. -func RandomHash() common.Hash { - return common.BytesToHash(RandBytes(common.HashLength)) -} - -// RandomAddress generates a random blob of data and returns it as an address. -func RandomAddress() common.Address { - return common.BytesToAddress(RandBytes(common.AddressLength)) -} - -// RandomNode generates a random node. -func RandomNode() *trienode.Node { - val := RandBytes(100) - return trienode.New(crypto.Keccak256Hash(val), val) -} diff --git a/trie/tracer.go b/trie/tracer.go index 5786af4d3e..2b5de8ec47 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -18,6 +18,7 @@ package trie import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/trie/trienode" ) // tracer tracks the changes of trie nodes. During the trie operations, @@ -113,18 +114,16 @@ func (t *tracer) copy() *tracer { } } -// deletedNodes returns a list of node paths which are deleted from the trie. -func (t *tracer) deletedNodes() []string { - var paths []string +// markDeletions puts all tracked deletions into the provided nodeset. +func (t *tracer) markDeletions(set *trienode.NodeSet) { for path := range t.deletes { // It's possible a few deleted nodes were embedded // in their parent before, the deletions can be no // effect by deleting nothing, filter them out. - _, ok := t.accessList[path] + prev, ok := t.accessList[path] if !ok { continue } - paths = append(paths, path) + set.AddNode([]byte(path), trienode.NewWithPrev(common.Hash{}, nil, prev)) } - return paths } diff --git a/trie/trie.go b/trie/trie.go index 07467ac69c..8fbd4e64a3 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -612,20 +612,14 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) defer func() { t.committed = true }() + nodes := trienode.NewNodeSet(t.owner) + t.tracer.markDeletions(nodes) + // Trie is empty and can be classified into two types of situations: - // (a) The trie was empty and no update happens => return nil - // (b) The trie was non-empty and all nodes are dropped => return - // the node set includes all deleted nodes + // - The trie was empty and no update happens + // - The trie was non-empty and all nodes are dropped if t.root == nil { - paths := t.tracer.deletedNodes() - if len(paths) == 0 { - return types.EmptyRootHash, nil, nil // case (a) - } - nodes := trienode.NewNodeSet(t.owner) - for _, path := range paths { - nodes.AddNode([]byte(path), trienode.NewDeleted()) - } - return types.EmptyRootHash, nodes, nil // case (b) + return types.EmptyRootHash, nodes, nil } // Derive the hash for all dirty nodes first. We hold the assumption // in the following procedure that all nodes are hashed. @@ -639,10 +633,6 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) t.root = hashedNode return rootHash, nil, nil } - nodes := trienode.NewNodeSet(t.owner) - for _, path := range t.tracer.deletedNodes() { - nodes.AddNode([]byte(path), trienode.NewDeleted()) - } t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root) return rootHash, nodes, nil } diff --git a/trie/trie_reader.go b/trie/trie_reader.go index 1c63ff4544..d42adad2c2 100644 --- a/trie/trie_reader.go +++ b/trie/trie_reader.go @@ -24,14 +24,9 @@ import ( // Reader wraps the Node method of a backing trie store. type Reader interface { - // Node retrieves the trie node blob with the provided trie identifier, node path and - // the corresponding node hash. No error will be returned if the node is not found. - // - // When looking up nodes in the account trie, 'owner' is the zero hash. For contract - // storage trie nodes, 'owner' is the hash of the account address that containing the - // storage. - // - // TODO(rjl493456442): remove the 'hash' parameter, it's redundant in PBSS. + // Node retrieves the RLP-encoded trie node blob with the provided trie + // identifier, node path and the corresponding node hash. No error will + // be returned if the node is not found. Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) } diff --git a/trie/trie_test.go b/trie/trie_test.go index 3cb21c1956..cc86e7aee2 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -76,9 +76,9 @@ func TestMissingRoot(t *testing.T) { func TestMissingNode(t *testing.T) { testMissingNode(t, false, rawdb.HashScheme) - testMissingNode(t, false, rawdb.PathScheme) + //testMissingNode(t, false, rawdb.PathScheme) testMissingNode(t, true, rawdb.HashScheme) - testMissingNode(t, true, rawdb.PathScheme) + //testMissingNode(t, true, rawdb.PathScheme) } func testMissingNode(t *testing.T, memonly bool, scheme string) { @@ -422,44 +422,44 @@ func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error { if !ok || n.IsDeleted() { return errors.New("expect new node") } - //if len(n.Prev) > 0 { - // return errors.New("unexpected origin value") - //} + if len(n.Prev) > 0 { + return errors.New("unexpected origin value") + } } // Check deletion set - for path := range deletes { + for path, blob := range deletes { n, ok := set.Nodes[path] if !ok || !n.IsDeleted() { return errors.New("expect deleted node") } - //if len(n.Prev) == 0 { - // return errors.New("expect origin value") - //} - //if !bytes.Equal(n.Prev, blob) { - // return errors.New("invalid origin value") - //} + if len(n.Prev) == 0 { + return errors.New("expect origin value") + } + if !bytes.Equal(n.Prev, blob) { + return errors.New("invalid origin value") + } } // Check update set - for path := range updates { + for path, blob := range updates { n, ok := set.Nodes[path] if !ok || n.IsDeleted() { return errors.New("expect updated node") } - //if len(n.Prev) == 0 { - // return errors.New("expect origin value") - //} - //if !bytes.Equal(n.Prev, blob) { - // return errors.New("invalid origin value") - //} + if len(n.Prev) == 0 { + return errors.New("expect origin value") + } + if !bytes.Equal(n.Prev, blob) { + return errors.New("invalid origin value") + } } return nil } func runRandTest(rt randTest) bool { var scheme = rawdb.HashScheme - if rand.Intn(2) == 0 { - scheme = rawdb.PathScheme - } + //if rand.Intn(2) == 0 { + // scheme = rawdb.PathScheme + //} var ( origin = types.EmptyRootHash triedb = newTestDatabase(rawdb.NewMemoryDatabase(), scheme) diff --git a/trie/triedb/hashdb/database.go b/trie/triedb/hashdb/database.go index 4441f2a382..ed20ff9cde 100644 --- a/trie/triedb/hashdb/database.go +++ b/trie/triedb/hashdb/database.go @@ -32,31 +32,30 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" ) var ( - memcacheCleanHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/hit", nil) - memcacheCleanMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/miss", nil) - memcacheCleanReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/read", nil) - memcacheCleanWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/write", nil) + memcacheCleanHitMeter = metrics.NewRegisteredMeter("trie/memcache/clean/hit", nil) + memcacheCleanMissMeter = metrics.NewRegisteredMeter("trie/memcache/clean/miss", nil) + memcacheCleanReadMeter = metrics.NewRegisteredMeter("trie/memcache/clean/read", nil) + memcacheCleanWriteMeter = metrics.NewRegisteredMeter("trie/memcache/clean/write", nil) - memcacheDirtyHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/hit", nil) - memcacheDirtyMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/miss", nil) - memcacheDirtyReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/read", nil) - memcacheDirtyWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/write", nil) + memcacheDirtyHitMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/hit", nil) + memcacheDirtyMissMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/miss", nil) + memcacheDirtyReadMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/read", nil) + memcacheDirtyWriteMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/write", nil) - memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/flush/time", nil) - memcacheFlushNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/nodes", nil) - memcacheFlushBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/bytes", nil) + memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/flush/time", nil) + memcacheFlushNodesMeter = metrics.NewRegisteredMeter("trie/memcache/flush/nodes", nil) + memcacheFlushSizeMeter = metrics.NewRegisteredMeter("trie/memcache/flush/size", nil) - memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/gc/time", nil) - memcacheGCNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/nodes", nil) - memcacheGCBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/bytes", nil) + memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/gc/time", nil) + memcacheGCNodesMeter = metrics.NewRegisteredMeter("trie/memcache/gc/nodes", nil) + memcacheGCSizeMeter = metrics.NewRegisteredMeter("trie/memcache/gc/size", nil) - memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/commit/time", nil) - memcacheCommitNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/nodes", nil) - memcacheCommitBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/bytes", nil) + memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/commit/time", nil) + memcacheCommitNodesMeter = metrics.NewRegisteredMeter("trie/memcache/commit/nodes", nil) + memcacheCommitSizeMeter = metrics.NewRegisteredMeter("trie/memcache/commit/size", nil) ) // ChildResolver defines the required method to decode the provided @@ -122,13 +121,7 @@ func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash commo } // New initializes the hash-based node database. -func New(diskdb ethdb.Database, size int, resolver ChildResolver) *Database { - // Initialize the clean cache if the specified cache allowance - // is non-zero. Note, the size is in bytes. - var cleans *fastcache.Cache - if size > 0 { - cleans = fastcache.New(size) - } +func New(diskdb ethdb.Database, cleans *fastcache.Cache, resolver ChildResolver) *Database { return &Database{ diskdb: diskdb, resolver: resolver, @@ -276,7 +269,7 @@ func (db *Database) Dereference(root common.Hash) { db.gctime += time.Since(start) memcacheGCTimeTimer.Update(time.Since(start)) - memcacheGCBytesMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheGCSizeMeter.Mark(int64(storage - db.dirtiesSize)) memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties))) log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start), @@ -397,7 +390,7 @@ func (db *Database) Cap(limit common.StorageSize) error { db.flushtime += time.Since(start) memcacheFlushTimeTimer.Update(time.Since(start)) - memcacheFlushBytesMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheFlushSizeMeter.Mark(int64(storage - db.dirtiesSize)) memcacheFlushNodesMeter.Mark(int64(nodes - len(db.dirties))) log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start), @@ -443,7 +436,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { // Reset the storage counters and bumped metrics memcacheCommitTimeTimer.Update(time.Since(start)) - memcacheCommitBytesMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize)) memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties))) logger := log.Info @@ -556,7 +549,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { // Update inserts the dirty nodes in provided nodeset into database and link the // account trie with multiple storage tries if necessary. -func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { +func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error { // Ensure the parent state is present and signal a warning if not. if parent != types.EmptyRootHash { if blob, _ := db.Node(parent); len(blob) == 0 { diff --git a/trie/triedb/pathdb/database.go b/trie/triedb/pathdb/database.go deleted file mode 100644 index 29f6b5e103..0000000000 --- a/trie/triedb/pathdb/database.go +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "errors" - "fmt" - "io" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" -) - -// maxDiffLayers is the maximum diff layers allowed in the layer tree. -const maxDiffLayers = 128 - -// layer is the interface implemented by all state layers which includes some -// public methods and some additional methods for internal usage. -type layer interface { - // Node retrieves the trie node with the node info. An error will be returned - // if the read operation exits abnormally. For example, if the layer is already - // stale, or the associated state is regarded as corrupted. Notably, no error - // will be returned if the requested node is not found in database. - Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) - - // rootHash returns the root hash for which this layer was made. - rootHash() common.Hash - - // stateID returns the associated state id of layer. - stateID() uint64 - - // parentLayer returns the subsequent layer of it, or nil if the disk was reached. - parentLayer() layer - - // update creates a new layer on top of the existing layer diff tree with - // the provided dirty trie nodes along with the state change set. - // - // Note, the maps are retained by the method to avoid copying everything. - update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer - - // journal commits an entire diff hierarchy to disk into a single journal entry. - // This is meant to be used during shutdown to persist the layer without - // flattening everything down (bad for reorgs). - journal(w io.Writer) error -} - -// Config contains the settings for database. -type Config struct { - StateLimit uint64 // Number of recent blocks to maintain state history for - CleanSize int // Maximum memory allowance (in bytes) for caching clean nodes - DirtySize int // Maximum memory allowance (in bytes) for caching dirty nodes - ReadOnly bool // Flag whether the database is opened in read only mode. -} - -var ( - // defaultCleanSize is the default memory allowance of clean cache. - defaultCleanSize = 16 * 1024 * 1024 - - // defaultBufferSize is the default memory allowance of node buffer - // that aggregates the writes from above until it's flushed into the - // disk. Do not increase the buffer size arbitrarily, otherwise the - // system pause time will increase when the database writes happen. - defaultBufferSize = 128 * 1024 * 1024 -) - -// Defaults contains default settings for Ethereum mainnet. -var Defaults = &Config{ - StateLimit: params.FullImmutabilityThreshold, - CleanSize: defaultCleanSize, - DirtySize: defaultBufferSize, -} - -// Database is a multiple-layered structure for maintaining in-memory trie nodes. -// It consists of one persistent base layer backed by a key-value store, on top -// of which arbitrarily many in-memory diff layers are stacked. The memory diffs -// can form a tree with branching, but the disk layer is singleton and common to -// all. If a reorg goes deeper than the disk layer, a batch of reverse diffs can -// be applied to rollback. The deepest reorg that can be handled depends on the -// amount of state histories tracked in the disk. -// -// At most one readable and writable database can be opened at the same time in -// the whole system which ensures that only one database writer can operate disk -// state. Unexpected open operations can cause the system to panic. -type Database struct { - // readOnly is the flag whether the mutation is allowed to be applied. - // It will be set automatically when the database is journaled during - // the shutdown to reject all following unexpected mutations. - readOnly bool // Indicator if database is opened in read only mode - bufferSize int // Memory allowance (in bytes) for caching dirty nodes - config *Config // Configuration for database - diskdb ethdb.Database // Persistent storage for matured trie nodes - tree *layerTree // The group for all known layers - freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests - lock sync.RWMutex // Lock to prevent mutations from happening at the same time -} - -// New attempts to load an already existing layer from a persistent key-value -// store (with a number of memory layers from a journal). If the journal is not -// matched with the base persistent layer, all the recorded diff layers are discarded. -func New(diskdb ethdb.Database, config *Config) *Database { - if config == nil { - config = Defaults - } - db := &Database{ - readOnly: config.ReadOnly, - bufferSize: config.DirtySize, - config: config, - diskdb: diskdb, - } - // Construct the layer tree by resolving the in-disk singleton state - // and in-memory layer journal. - db.tree = newLayerTree(db.loadLayers()) - - // Open the freezer for state history if the passed database contains an - // ancient store. Otherwise, all the relevant functionalities are disabled. - // - // Because the freezer can only be opened once at the same time, this - // mechanism also ensures that at most one **non-readOnly** database - // is opened at the same time to prevent accidental mutation. - if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly { - freezer, err := rawdb.NewStateHistoryFreezer(ancient, false) - if err != nil { - log.Crit("Failed to open state history freezer", "err", err) - } - db.freezer = freezer - - // Truncate the extra state histories above in freezer in case - // it's not aligned with the disk layer. - pruned, err := truncateFromHead(db.diskdb, freezer, db.tree.bottom().stateID()) - if err != nil { - log.Crit("Failed to truncate extra state histories", "err", err) - } - if pruned != 0 { - log.Warn("Truncated extra state histories", "number", pruned) - } - } - log.Warn("Path-based state scheme is an experimental feature") - return db -} - -// Reader retrieves a layer belonging to the given state root. -func (db *Database) Reader(root common.Hash) (layer, error) { - l := db.tree.get(root) - if l == nil { - return nil, fmt.Errorf("state %#x is not available", root) - } - return l, nil -} - -// Update adds a new layer into the tree, if that can be linked to an existing -// old parent. It is disallowed to insert a disk layer (the origin of all). Apart -// from that this function will flatten the extra diff layers at bottom into disk -// to only keep 128 diff layers in memory by default. -// -// The passed in maps(nodes, states) will be retained to avoid copying everything. -// Therefore, these maps must not be changed afterwards. -func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { - // Hold the lock to prevent concurrent mutations. - db.lock.Lock() - defer db.lock.Unlock() - - // Short circuit if the database is in read only mode. - if db.readOnly { - return errSnapshotReadOnly - } - if err := db.tree.add(root, parentRoot, block, nodes, states); err != nil { - return err - } - // Keep 128 diff layers in the memory, persistent layer is 129th. - // - head layer is paired with HEAD state - // - head-1 layer is paired with HEAD-1 state - // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state - // - head-128 layer(disk layer) is paired with HEAD-128 state - return db.tree.cap(root, maxDiffLayers) -} - -// Commit traverses downwards the layer tree from a specified layer with the -// provided state root and all the layers below are flattened downwards. It -// can be used alone and mostly for test purposes. -func (db *Database) Commit(root common.Hash, report bool) error { - // Hold the lock to prevent concurrent mutations. - db.lock.Lock() - defer db.lock.Unlock() - - // Short circuit if the database is in read only mode. - if db.readOnly { - return errSnapshotReadOnly - } - return db.tree.cap(root, 0) -} - -// Reset rebuilds the database with the specified state as the base. -// -// - if target state is empty, clear the stored state and all layers on top -// - if target state is non-empty, ensure the stored state matches with it -// and clear all other layers on top. -func (db *Database) Reset(root common.Hash) error { - db.lock.Lock() - defer db.lock.Unlock() - - // Short circuit if the database is in read only mode. - if db.readOnly { - return errSnapshotReadOnly - } - batch := db.diskdb.NewBatch() - root = types.TrieRootHash(root) - if root == types.EmptyRootHash { - // Empty state is requested as the target, nuke out - // the root node and leave all others as dangling. - rawdb.DeleteAccountTrieNode(batch, nil) - } else { - // Ensure the requested state is existent before any - // action is applied. - _, hash := rawdb.ReadAccountTrieNode(db.diskdb, nil) - if hash != root { - return fmt.Errorf("state is mismatched, local: %x, target: %x", hash, root) - } - } - // Mark the disk layer as stale before applying any mutation. - db.tree.bottom().markStale() - - // Drop the stale state journal in persistent database and - // reset the persistent state id back to zero. - rawdb.DeleteTrieJournal(batch) - rawdb.WritePersistentStateID(batch, 0) - if err := batch.Write(); err != nil { - return err - } - // Clean up all state histories in freezer. Theoretically - // all root->id mappings should be removed as well. Since - // mappings can be huge and might take a while to clear - // them, just leave them in disk and wait for overwriting. - if db.freezer != nil { - if err := db.freezer.Reset(); err != nil { - return err - } - } - // Re-construct a new disk layer backed by persistent state - // with **empty clean cache and node buffer**. - dl := newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0)) - db.tree.reset(dl) - log.Info("Rebuilt trie database", "root", root) - return nil -} - -// Recover rollbacks the database to a specified historical point. -// The state is supported as the rollback destination only if it's -// canonical state and the corresponding trie histories are existent. -func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error { - db.lock.Lock() - defer db.lock.Unlock() - - // Short circuit if rollback operation is not supported. - if db.readOnly || db.freezer == nil { - return errors.New("state rollback is non-supported") - } - // Short circuit if the target state is not recoverable. - root = types.TrieRootHash(root) - if !db.Recoverable(root) { - return errStateUnrecoverable - } - // Apply the state histories upon the disk layer in order. - var ( - start = time.Now() - dl = db.tree.bottom() - ) - for dl.rootHash() != root { - h, err := readHistory(db.freezer, dl.stateID()) - if err != nil { - return err - } - dl, err = dl.revert(h, loader) - if err != nil { - return err - } - // reset layer with newly created disk layer. It must be - // done after each revert operation, otherwise the new - // disk layer won't be accessible from outside. - db.tree.reset(dl) - } - rawdb.DeleteTrieJournal(db.diskdb) - _, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID()) - if err != nil { - return err - } - log.Debug("Recovered state", "root", root, "elapsed", common.PrettyDuration(time.Since(start))) - return nil -} - -// Recoverable returns the indicator if the specified state is recoverable. -func (db *Database) Recoverable(root common.Hash) bool { - // Ensure the requested state is a known state. - root = types.TrieRootHash(root) - id := rawdb.ReadStateID(db.diskdb, root) - if id == nil { - return false - } - // Recoverable state must below the disk layer. The recoverable - // state only refers the state that is currently not available, - // but can be restored by applying state history. - dl := db.tree.bottom() - if *id >= dl.stateID() { - return false - } - // Ensure the requested state is a canonical state and all state - // histories in range [id+1, disklayer.ID] are present and complete. - parent := root - return checkHistories(db.freezer, *id+1, dl.stateID()-*id, func(m *meta) error { - if m.parent != parent { - return errors.New("unexpected state history") - } - if len(m.incomplete) > 0 { - return errors.New("incomplete state history") - } - parent = m.root - return nil - }) == nil -} - -// Close closes the trie database and the held freezer. -func (db *Database) Close() error { - db.lock.Lock() - defer db.lock.Unlock() - - db.readOnly = true - if db.freezer == nil { - return nil - } - return db.freezer.Close() -} - -// Size returns the current storage size of the memory cache in front of the -// persistent database layer. -func (db *Database) Size() (size common.StorageSize) { - db.tree.forEach(func(layer layer) { - if diff, ok := layer.(*diffLayer); ok { - size += common.StorageSize(diff.memory) - } - if disk, ok := layer.(*diskLayer); ok { - size += disk.size() - } - }) - return size -} - -// Initialized returns an indicator if the state data is already -// initialized in path-based scheme. -func (db *Database) Initialized(genesisRoot common.Hash) bool { - var inited bool - db.tree.forEach(func(layer layer) { - if layer.rootHash() != types.EmptyRootHash { - inited = true - } - }) - return inited -} - -// SetBufferSize sets the node buffer size to the provided value(in bytes). -func (db *Database) SetBufferSize(size int) error { - db.lock.Lock() - defer db.lock.Unlock() - - db.bufferSize = size - return db.tree.bottom().setBufferSize(db.bufferSize) -} - -// Scheme returns the node scheme used in the database. -func (db *Database) Scheme() string { - return rawdb.PathScheme -} diff --git a/trie/triedb/pathdb/database_test.go b/trie/triedb/pathdb/database_test.go deleted file mode 100644 index bcc37e59c8..0000000000 --- a/trie/triedb/pathdb/database_test.go +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "bytes" - "errors" - "fmt" - "math/big" - "math/rand" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie/testutil" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" -) - -func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) { - h, err := newTestHasher(addrHash, root, cleans) - if err != nil { - panic(fmt.Errorf("failed to create hasher, err: %w", err)) - } - for key, val := range dirties { - if len(val) == 0 { - h.Delete(key.Bytes()) - } else { - h.Update(key.Bytes(), val) - } - } - return h.Commit(false) -} - -func generateAccount(storageRoot common.Hash) types.StateAccount { - return types.StateAccount{ - Nonce: uint64(rand.Intn(100)), - Balance: big.NewInt(rand.Int63()), - CodeHash: testutil.RandBytes(32), - Root: storageRoot, - } -} - -const ( - createAccountOp int = iota - modifyAccountOp - deleteAccountOp - opLen -) - -type genctx struct { - accounts map[common.Hash][]byte - storages map[common.Hash]map[common.Hash][]byte - accountOrigin map[common.Address][]byte - storageOrigin map[common.Address]map[common.Hash][]byte - nodes *trienode.MergedNodeSet -} - -func newCtx() *genctx { - return &genctx{ - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountOrigin: make(map[common.Address][]byte), - storageOrigin: make(map[common.Address]map[common.Hash][]byte), - nodes: trienode.NewMergedNodeSet(), - } -} - -type tester struct { - db *Database - roots []common.Hash - preimages map[common.Hash]common.Address - accounts map[common.Hash][]byte - storages map[common.Hash]map[common.Hash][]byte - - // state snapshots - snapAccounts map[common.Hash]map[common.Hash][]byte - snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte -} - -func newTester(t *testing.T) *tester { - var ( - disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) - db = New(disk, &Config{CleanSize: 256 * 1024, DirtySize: 256 * 1024}) - obj = &tester{ - db: db, - preimages: make(map[common.Hash]common.Address), - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - snapAccounts: make(map[common.Hash]map[common.Hash][]byte), - snapStorages: make(map[common.Hash]map[common.Hash]map[common.Hash][]byte), - } - ) - for i := 0; i < 2*128; i++ { - var parent = types.EmptyRootHash - if len(obj.roots) != 0 { - parent = obj.roots[len(obj.roots)-1] - } - root, nodes, states := obj.generate(parent) - if err := db.Update(root, parent, uint64(i), nodes, states); err != nil { - panic(fmt.Errorf("failed to update state changes, err: %w", err)) - } - obj.roots = append(obj.roots, root) - } - return obj -} - -func (t *tester) release() { - t.db.Close() - t.db.diskdb.Close() -} - -func (t *tester) randAccount() (common.Address, []byte) { - for addrHash, account := range t.accounts { - return t.preimages[addrHash], account - } - return common.Address{}, nil -} - -func (t *tester) generateStorage(ctx *genctx, addr common.Address) common.Hash { - var ( - addrHash = crypto.Keccak256Hash(addr.Bytes()) - storage = make(map[common.Hash][]byte) - origin = make(map[common.Hash][]byte) - ) - for i := 0; i < 10; i++ { - v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32))) - hash := testutil.RandomHash() - - storage[hash] = v - origin[hash] = nil - } - root, set := updateTrie(addrHash, types.EmptyRootHash, storage, nil) - - ctx.storages[addrHash] = storage - ctx.storageOrigin[addr] = origin - ctx.nodes.Merge(set) - return root -} - -func (t *tester) mutateStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash { - var ( - addrHash = crypto.Keccak256Hash(addr.Bytes()) - storage = make(map[common.Hash][]byte) - origin = make(map[common.Hash][]byte) - ) - for hash, val := range t.storages[addrHash] { - origin[hash] = val - storage[hash] = nil - - if len(origin) == 3 { - break - } - } - for i := 0; i < 3; i++ { - v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32))) - hash := testutil.RandomHash() - - storage[hash] = v - origin[hash] = nil - } - root, set := updateTrie(crypto.Keccak256Hash(addr.Bytes()), root, storage, t.storages[addrHash]) - - ctx.storages[addrHash] = storage - ctx.storageOrigin[addr] = origin - ctx.nodes.Merge(set) - return root -} - -func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash { - var ( - addrHash = crypto.Keccak256Hash(addr.Bytes()) - storage = make(map[common.Hash][]byte) - origin = make(map[common.Hash][]byte) - ) - for hash, val := range t.storages[addrHash] { - origin[hash] = val - storage[hash] = nil - } - root, set := updateTrie(addrHash, root, storage, t.storages[addrHash]) - if root != types.EmptyRootHash { - panic("failed to clear storage trie") - } - ctx.storages[addrHash] = storage - ctx.storageOrigin[addr] = origin - ctx.nodes.Merge(set) - return root -} - -func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNodeSet, *triestate.Set) { - var ( - ctx = newCtx() - dirties = make(map[common.Hash]struct{}) - ) - for i := 0; i < 20; i++ { - switch rand.Intn(opLen) { - case createAccountOp: - // account creation - addr := testutil.RandomAddress() - addrHash := crypto.Keccak256Hash(addr.Bytes()) - if _, ok := t.accounts[addrHash]; ok { - continue - } - if _, ok := dirties[addrHash]; ok { - continue - } - dirties[addrHash] = struct{}{} - - root := t.generateStorage(ctx, addr) - ctx.accounts[addrHash] = types.SlimAccountRLP(generateAccount(root)) - ctx.accountOrigin[addr] = nil - t.preimages[addrHash] = addr - - case modifyAccountOp: - // account mutation - addr, account := t.randAccount() - if addr == (common.Address{}) { - continue - } - addrHash := crypto.Keccak256Hash(addr.Bytes()) - if _, ok := dirties[addrHash]; ok { - continue - } - dirties[addrHash] = struct{}{} - - acct, _ := types.FullAccount(account) - stRoot := t.mutateStorage(ctx, addr, acct.Root) - newAccount := types.SlimAccountRLP(generateAccount(stRoot)) - - ctx.accounts[addrHash] = newAccount - ctx.accountOrigin[addr] = account - - case deleteAccountOp: - // account deletion - addr, account := t.randAccount() - if addr == (common.Address{}) { - continue - } - addrHash := crypto.Keccak256Hash(addr.Bytes()) - if _, ok := dirties[addrHash]; ok { - continue - } - dirties[addrHash] = struct{}{} - - acct, _ := types.FullAccount(account) - if acct.Root != types.EmptyRootHash { - t.clearStorage(ctx, addr, acct.Root) - } - ctx.accounts[addrHash] = nil - ctx.accountOrigin[addr] = account - } - } - root, set := updateTrie(common.Hash{}, parent, ctx.accounts, t.accounts) - ctx.nodes.Merge(set) - - // Save state snapshot before commit - t.snapAccounts[parent] = copyAccounts(t.accounts) - t.snapStorages[parent] = copyStorages(t.storages) - - // Commit all changes to live state set - for addrHash, account := range ctx.accounts { - if len(account) == 0 { - delete(t.accounts, addrHash) - } else { - t.accounts[addrHash] = account - } - } - for addrHash, slots := range ctx.storages { - if _, ok := t.storages[addrHash]; !ok { - t.storages[addrHash] = make(map[common.Hash][]byte) - } - for sHash, slot := range slots { - if len(slot) == 0 { - delete(t.storages[addrHash], sHash) - } else { - t.storages[addrHash][sHash] = slot - } - } - } - return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin, nil) -} - -// lastRoot returns the latest root hash, or empty if nothing is cached. -func (t *tester) lastHash() common.Hash { - if len(t.roots) == 0 { - return common.Hash{} - } - return t.roots[len(t.roots)-1] -} - -func (t *tester) verifyState(root common.Hash) error { - reader, err := t.db.Reader(root) - if err != nil { - return err - } - _, err = reader.Node(common.Hash{}, nil, root) - if err != nil { - return errors.New("root node is not available") - } - for addrHash, account := range t.snapAccounts[root] { - blob, err := reader.Node(common.Hash{}, addrHash.Bytes(), crypto.Keccak256Hash(account)) - if err != nil || !bytes.Equal(blob, account) { - return fmt.Errorf("account is mismatched: %w", err) - } - } - for addrHash, slots := range t.snapStorages[root] { - for hash, slot := range slots { - blob, err := reader.Node(addrHash, hash.Bytes(), crypto.Keccak256Hash(slot)) - if err != nil || !bytes.Equal(blob, slot) { - return fmt.Errorf("slot is mismatched: %w", err) - } - } - } - return nil -} - -func (t *tester) verifyHistory() error { - bottom := t.bottomIndex() - for i, root := range t.roots { - // The state history related to the state above disk layer should not exist. - if i > bottom { - _, err := readHistory(t.db.freezer, uint64(i+1)) - if err == nil { - return errors.New("unexpected state history") - } - continue - } - // The state history related to the state below or equal to the disk layer - // should exist. - obj, err := readHistory(t.db.freezer, uint64(i+1)) - if err != nil { - return err - } - parent := types.EmptyRootHash - if i != 0 { - parent = t.roots[i-1] - } - if obj.meta.parent != parent { - return fmt.Errorf("unexpected parent, want: %x, got: %x", parent, obj.meta.parent) - } - if obj.meta.root != root { - return fmt.Errorf("unexpected root, want: %x, got: %x", root, obj.meta.root) - } - } - return nil -} - -// bottomIndex returns the index of current disk layer. -func (t *tester) bottomIndex() int { - bottom := t.db.tree.bottom() - for i := 0; i < len(t.roots); i++ { - if t.roots[i] == bottom.rootHash() { - return i - } - } - return -1 -} - -func TestDatabaseRollback(t *testing.T) { - // Verify state histories - tester := newTester(t) - defer tester.release() - - if err := tester.verifyHistory(); err != nil { - t.Fatalf("Invalid state history, err: %v", err) - } - // Revert database from top to bottom - for i := tester.bottomIndex(); i >= 0; i-- { - root := tester.roots[i] - parent := types.EmptyRootHash - if i > 0 { - parent = tester.roots[i-1] - } - loader := newHashLoader(tester.snapAccounts[root], tester.snapStorages[root]) - if err := tester.db.Recover(parent, loader); err != nil { - t.Fatalf("Failed to revert db, err: %v", err) - } - tester.verifyState(parent) - } - if tester.db.tree.len() != 1 { - t.Fatal("Only disk layer is expected") - } -} - -func TestDatabaseRecoverable(t *testing.T) { - var ( - tester = newTester(t) - index = tester.bottomIndex() - ) - defer tester.release() - - var cases = []struct { - root common.Hash - expect bool - }{ - // Unknown state should be unrecoverable - {common.Hash{0x1}, false}, - - // Initial state should be recoverable - {types.EmptyRootHash, true}, - - // Initial state should be recoverable - {common.Hash{}, true}, - - // Layers below current disk layer are recoverable - {tester.roots[index-1], true}, - - // Disklayer itself is not recoverable, since it's - // available for accessing. - {tester.roots[index], false}, - - // Layers above current disk layer are not recoverable - // since they are available for accessing. - {tester.roots[index+1], false}, - } - for i, c := range cases { - result := tester.db.Recoverable(c.root) - if result != c.expect { - t.Fatalf("case: %d, unexpected result, want %t, got %t", i, c.expect, result) - } - } -} - -func TestReset(t *testing.T) { - var ( - tester = newTester(t) - index = tester.bottomIndex() - ) - defer tester.release() - - // Reset database to unknown target, should reject it - if err := tester.db.Reset(testutil.RandomHash()); err == nil { - t.Fatal("Failed to reject invalid reset") - } - // Reset database to state persisted in the disk - if err := tester.db.Reset(types.EmptyRootHash); err != nil { - t.Fatalf("Failed to reset database %v", err) - } - // Ensure journal is deleted from disk - if blob := rawdb.ReadTrieJournal(tester.db.diskdb); len(blob) != 0 { - t.Fatal("Failed to clean journal") - } - // Ensure all trie histories are removed - for i := 0; i <= index; i++ { - _, err := readHistory(tester.db.freezer, uint64(i+1)) - if err == nil { - t.Fatalf("Failed to clean state history, index %d", i+1) - } - } - // Verify layer tree structure, single disk layer is expected - if tester.db.tree.len() != 1 { - t.Fatalf("Extra layer kept %d", tester.db.tree.len()) - } - if tester.db.tree.bottom().rootHash() != types.EmptyRootHash { - t.Fatalf("Root hash is not matched exp %x got %x", types.EmptyRootHash, tester.db.tree.bottom().rootHash()) - } -} - -func TestCommit(t *testing.T) { - tester := newTester(t) - defer tester.release() - - if err := tester.db.Commit(tester.lastHash(), false); err != nil { - t.Fatalf("Failed to cap database, err: %v", err) - } - // Verify layer tree structure, single disk layer is expected - if tester.db.tree.len() != 1 { - t.Fatal("Layer tree structure is invalid") - } - if tester.db.tree.bottom().rootHash() != tester.lastHash() { - t.Fatal("Layer tree structure is invalid") - } - // Verify states - if err := tester.verifyState(tester.lastHash()); err != nil { - t.Fatalf("State is invalid, err: %v", err) - } - // Verify state histories - if err := tester.verifyHistory(); err != nil { - t.Fatalf("State history is invalid, err: %v", err) - } -} - -func TestJournal(t *testing.T) { - tester := newTester(t) - defer tester.release() - - if err := tester.db.Journal(tester.lastHash()); err != nil { - t.Errorf("Failed to journal, err: %v", err) - } - tester.db.Close() - tester.db = New(tester.db.diskdb, nil) - - // Verify states including disk layer and all diff on top. - for i := 0; i < len(tester.roots); i++ { - if i >= tester.bottomIndex() { - if err := tester.verifyState(tester.roots[i]); err != nil { - t.Fatalf("Invalid state, err: %v", err) - } - continue - } - if err := tester.verifyState(tester.roots[i]); err == nil { - t.Fatal("Unexpected state") - } - } -} - -func TestCorruptedJournal(t *testing.T) { - tester := newTester(t) - defer tester.release() - - if err := tester.db.Journal(tester.lastHash()); err != nil { - t.Errorf("Failed to journal, err: %v", err) - } - tester.db.Close() - _, root := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil) - - // Mutate the journal in disk, it should be regarded as invalid - blob := rawdb.ReadTrieJournal(tester.db.diskdb) - blob[0] = 1 - rawdb.WriteTrieJournal(tester.db.diskdb, blob) - - // Verify states, all not-yet-written states should be discarded - tester.db = New(tester.db.diskdb, nil) - for i := 0; i < len(tester.roots); i++ { - if tester.roots[i] == root { - if err := tester.verifyState(root); err != nil { - t.Fatalf("Disk state is corrupted, err: %v", err) - } - continue - } - if err := tester.verifyState(tester.roots[i]); err == nil { - t.Fatal("Unexpected state") - } - } -} - -// copyAccounts returns a deep-copied account set of the provided one. -func copyAccounts(set map[common.Hash][]byte) map[common.Hash][]byte { - copied := make(map[common.Hash][]byte, len(set)) - for key, val := range set { - copied[key] = common.CopyBytes(val) - } - return copied -} - -// copyStorages returns a deep-copied storage set of the provided one. -func copyStorages(set map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { - copied := make(map[common.Hash]map[common.Hash][]byte, len(set)) - for addrHash, subset := range set { - copied[addrHash] = make(map[common.Hash][]byte, len(subset)) - for key, val := range subset { - copied[addrHash][key] = common.CopyBytes(val) - } - } - return copied -} diff --git a/trie/triedb/pathdb/difflayer.go b/trie/triedb/pathdb/difflayer.go deleted file mode 100644 index d25ac1c601..0000000000 --- a/trie/triedb/pathdb/difflayer.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" -) - -// diffLayer represents a collection of modifications made to the in-memory tries -// along with associated state changes after running a block on top. -// -// The goal of a diff layer is to act as a journal, tracking recent modifications -// made to the state, that have not yet graduated into a semi-immutable state. -type diffLayer struct { - // Immutables - root common.Hash // Root hash to which this layer diff belongs to - id uint64 // Corresponding state id - block uint64 // Associated block number - nodes map[common.Hash]map[string]*trienode.Node // Cached trie nodes indexed by owner and path - states *triestate.Set // Associated state change set for building history - memory uint64 // Approximate guess as to how much memory we use - - parent layer // Parent layer modified by this one, never nil, **can be changed** - lock sync.RWMutex // Lock used to protect parent -} - -// newDiffLayer creates a new diff layer on top of an existing layer. -func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { - var ( - size int64 - count int - ) - dl := &diffLayer{ - root: root, - id: id, - block: block, - nodes: nodes, - states: states, - parent: parent, - } - for _, subset := range nodes { - for path, n := range subset { - dl.memory += uint64(n.Size() + len(path)) - size += int64(len(n.Blob) + len(path)) - } - count += len(subset) - } - if states != nil { - dl.memory += uint64(states.Size()) - } - dirtyWriteMeter.Mark(size) - diffLayerNodesMeter.Mark(int64(count)) - diffLayerBytesMeter.Mark(int64(dl.memory)) - log.Debug("Created new diff layer", "id", id, "block", block, "nodes", count, "size", common.StorageSize(dl.memory)) - return dl -} - -// rootHash implements the layer interface, returning the root hash of -// corresponding state. -func (dl *diffLayer) rootHash() common.Hash { - return dl.root -} - -// stateID implements the layer interface, returning the state id of the layer. -func (dl *diffLayer) stateID() uint64 { - return dl.id -} - -// parentLayer implements the layer interface, returning the subsequent -// layer of the diff layer. -func (dl *diffLayer) parentLayer() layer { - dl.lock.RLock() - defer dl.lock.RUnlock() - - return dl.parent -} - -// node retrieves the node with provided node information. It's the internal -// version of Node function with additional accessed layer tracked. No error -// will be returned if node is not found. -func (dl *diffLayer) node(owner common.Hash, path []byte, hash common.Hash, depth int) ([]byte, error) { - // Hold the lock, ensure the parent won't be changed during the - // state accessing. - dl.lock.RLock() - defer dl.lock.RUnlock() - - // If the trie node is known locally, return it - subset, ok := dl.nodes[owner] - if ok { - n, ok := subset[string(path)] - if ok { - // If the trie node is not hash matched, or marked as removed, - // bubble up an error here. It shouldn't happen at all. - if n.Hash != hash { - dirtyFalseMeter.Mark(1) - log.Error("Unexpected trie node in diff layer", "owner", owner, "path", path, "expect", hash, "got", n.Hash) - return nil, newUnexpectedNodeError("diff", hash, n.Hash, owner, path) - } - dirtyHitMeter.Mark(1) - dirtyNodeHitDepthHist.Update(int64(depth)) - dirtyReadMeter.Mark(int64(len(n.Blob))) - return n.Blob, nil - } - } - // Trie node unknown to this layer, resolve from parent - if diff, ok := dl.parent.(*diffLayer); ok { - return diff.node(owner, path, hash, depth+1) - } - // Failed to resolve through diff layers, fallback to disk layer - return dl.parent.Node(owner, path, hash) -} - -// Node implements the layer interface, retrieving the trie node blob with the -// provided node information. No error will be returned if the node is not found. -func (dl *diffLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { - return dl.node(owner, path, hash, 0) -} - -// update implements the layer interface, creating a new layer on top of the -// existing layer tree with the specified data items. -func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { - return newDiffLayer(dl, root, id, block, nodes, states) -} - -// persist flushes the diff layer and all its parent layers to disk layer. -func (dl *diffLayer) persist(force bool) (layer, error) { - if parent, ok := dl.parentLayer().(*diffLayer); ok { - // Hold the lock to prevent any read operation until the new - // parent is linked correctly. - dl.lock.Lock() - - // The merging of diff layers starts at the bottom-most layer, - // therefore we recurse down here, flattening on the way up - // (diffToDisk). - result, err := parent.persist(force) - if err != nil { - dl.lock.Unlock() - return nil, err - } - dl.parent = result - dl.lock.Unlock() - } - return diffToDisk(dl, force) -} - -// diffToDisk merges a bottom-most diff into the persistent disk layer underneath -// it. The method will panic if called onto a non-bottom-most diff layer. -func diffToDisk(layer *diffLayer, force bool) (layer, error) { - disk, ok := layer.parentLayer().(*diskLayer) - if !ok { - panic(fmt.Sprintf("unknown layer type: %T", layer.parentLayer())) - } - return disk.commit(layer, force) -} diff --git a/trie/triedb/pathdb/difflayer_test.go b/trie/triedb/pathdb/difflayer_test.go deleted file mode 100644 index 77c4cd5722..0000000000 --- a/trie/triedb/pathdb/difflayer_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2019 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 pathdb - -import ( - "bytes" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/trie/testutil" - "github.com/ethereum/go-ethereum/trie/trienode" -) - -func emptyLayer() *diskLayer { - return &diskLayer{ - db: New(rawdb.NewMemoryDatabase(), nil), - buffer: newNodeBuffer(defaultBufferSize, nil, 0), - } -} - -// goos: darwin -// goarch: arm64 -// pkg: github.com/ethereum/go-ethereum/trie -// BenchmarkSearch128Layers -// BenchmarkSearch128Layers-8 243826 4755 ns/op -func BenchmarkSearch128Layers(b *testing.B) { benchmarkSearch(b, 0, 128) } - -// goos: darwin -// goarch: arm64 -// pkg: github.com/ethereum/go-ethereum/trie -// BenchmarkSearch512Layers -// BenchmarkSearch512Layers-8 49686 24256 ns/op -func BenchmarkSearch512Layers(b *testing.B) { benchmarkSearch(b, 0, 512) } - -// goos: darwin -// goarch: arm64 -// pkg: github.com/ethereum/go-ethereum/trie -// BenchmarkSearch1Layer -// BenchmarkSearch1Layer-8 14062725 88.40 ns/op -func BenchmarkSearch1Layer(b *testing.B) { benchmarkSearch(b, 127, 128) } - -func benchmarkSearch(b *testing.B, depth int, total int) { - var ( - npath []byte - nhash common.Hash - nblob []byte - ) - // First, we set up 128 diff layers, with 3K items each - fill := func(parent layer, index int) *diffLayer { - nodes := make(map[common.Hash]map[string]*trienode.Node) - nodes[common.Hash{}] = make(map[string]*trienode.Node) - for i := 0; i < 3000; i++ { - var ( - path = testutil.RandBytes(32) - node = testutil.RandomNode() - ) - nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) - if npath == nil && depth == index { - npath = common.CopyBytes(path) - nblob = common.CopyBytes(node.Blob) - nhash = node.Hash - } - } - return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) - } - var layer layer - layer = emptyLayer() - for i := 0; i < total; i++ { - layer = fill(layer, i) - } - b.ResetTimer() - - var ( - have []byte - err error - ) - for i := 0; i < b.N; i++ { - have, err = layer.Node(common.Hash{}, npath, nhash) - if err != nil { - b.Fatal(err) - } - } - if !bytes.Equal(have, nblob) { - b.Fatalf("have %x want %x", have, nblob) - } -} - -// goos: darwin -// goarch: arm64 -// pkg: github.com/ethereum/go-ethereum/trie -// BenchmarkPersist -// BenchmarkPersist-8 10 111252975 ns/op -func BenchmarkPersist(b *testing.B) { - // First, we set up 128 diff layers, with 3K items each - fill := func(parent layer) *diffLayer { - nodes := make(map[common.Hash]map[string]*trienode.Node) - nodes[common.Hash{}] = make(map[string]*trienode.Node) - for i := 0; i < 3000; i++ { - var ( - path = testutil.RandBytes(32) - node = testutil.RandomNode() - ) - nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) - } - return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) - } - for i := 0; i < b.N; i++ { - b.StopTimer() - var layer layer - layer = emptyLayer() - for i := 1; i < 128; i++ { - layer = fill(layer) - } - b.StartTimer() - - dl, ok := layer.(*diffLayer) - if !ok { - break - } - dl.persist(false) - } -} - -// BenchmarkJournal benchmarks the performance for journaling the layers. -// -// BenchmarkJournal -// BenchmarkJournal-8 10 110969279 ns/op -func BenchmarkJournal(b *testing.B) { - b.SkipNow() - - // First, we set up 128 diff layers, with 3K items each - fill := func(parent layer) *diffLayer { - nodes := make(map[common.Hash]map[string]*trienode.Node) - nodes[common.Hash{}] = make(map[string]*trienode.Node) - for i := 0; i < 3000; i++ { - var ( - path = testutil.RandBytes(32) - node = testutil.RandomNode() - ) - nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) - } - // TODO(rjl493456442) a non-nil state set is expected. - return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) - } - var layer layer - layer = emptyLayer() - for i := 0; i < 128; i++ { - layer = fill(layer) - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - layer.journal(new(bytes.Buffer)) - } -} diff --git a/trie/triedb/pathdb/disklayer.go b/trie/triedb/pathdb/disklayer.go deleted file mode 100644 index b526b3b7dd..0000000000 --- a/trie/triedb/pathdb/disklayer.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "errors" - "fmt" - "sync" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" - "golang.org/x/crypto/sha3" -) - -// diskLayer is a low level persistent layer built on top of a key-value store. -type diskLayer struct { - root common.Hash // Immutable, root hash to which this layer was made for - id uint64 // Immutable, corresponding state id - db *Database // Path-based trie database - cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs - buffer *nodebuffer // Node buffer to aggregate writes - stale bool // Signals that the layer became stale (state progressed) - lock sync.RWMutex // Lock used to protect stale flag -} - -// newDiskLayer creates a new disk layer based on the passing arguments. -func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.Cache, buffer *nodebuffer) *diskLayer { - // Initialize a clean cache if the memory allowance is not zero - // or reuse the provided cache if it is not nil (inherited from - // the original disk layer). - if cleans == nil && db.config.CleanSize != 0 { - cleans = fastcache.New(db.config.CleanSize) - } - return &diskLayer{ - root: root, - id: id, - db: db, - cleans: cleans, - buffer: buffer, - } -} - -// root implements the layer interface, returning root hash of corresponding state. -func (dl *diskLayer) rootHash() common.Hash { - return dl.root -} - -// stateID implements the layer interface, returning the state id of disk layer. -func (dl *diskLayer) stateID() uint64 { - return dl.id -} - -// parent implements the layer interface, returning nil as there's no layer -// below the disk. -func (dl *diskLayer) parentLayer() layer { - return nil -} - -// isStale return whether this layer has become stale (was flattened across) or if -// it's still live. -func (dl *diskLayer) isStale() bool { - dl.lock.RLock() - defer dl.lock.RUnlock() - - return dl.stale -} - -// markStale sets the stale flag as true. -func (dl *diskLayer) markStale() { - dl.lock.Lock() - defer dl.lock.Unlock() - - if dl.stale { - panic("triedb disk layer is stale") // we've committed into the same base from two children, boom - } - dl.stale = true -} - -// Node implements the layer interface, retrieving the trie node with the -// provided node info. No error will be returned if the node is not found. -func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { - dl.lock.RLock() - defer dl.lock.RUnlock() - - if dl.stale { - return nil, errSnapshotStale - } - // Try to retrieve the trie node from the not-yet-written - // node buffer first. Note the buffer is lock free since - // it's impossible to mutate the buffer before tagging the - // layer as stale. - n, err := dl.buffer.node(owner, path, hash) - if err != nil { - return nil, err - } - if n != nil { - dirtyHitMeter.Mark(1) - dirtyReadMeter.Mark(int64(len(n.Blob))) - return n.Blob, nil - } - dirtyMissMeter.Mark(1) - - // Try to retrieve the trie node from the clean memory cache - key := cacheKey(owner, path) - if dl.cleans != nil { - if blob := dl.cleans.Get(nil, key); len(blob) > 0 { - h := newHasher() - defer h.release() - - got := h.hash(blob) - if got == hash { - cleanHitMeter.Mark(1) - cleanReadMeter.Mark(int64(len(blob))) - return blob, nil - } - cleanFalseMeter.Mark(1) - log.Error("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got) - } - cleanMissMeter.Mark(1) - } - // Try to retrieve the trie node from the disk. - var ( - nBlob []byte - nHash common.Hash - ) - if owner == (common.Hash{}) { - nBlob, nHash = rawdb.ReadAccountTrieNode(dl.db.diskdb, path) - } else { - nBlob, nHash = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path) - } - if nHash != hash { - diskFalseMeter.Mark(1) - log.Error("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash) - return nil, newUnexpectedNodeError("disk", hash, nHash, owner, path) - } - if dl.cleans != nil && len(nBlob) > 0 { - dl.cleans.Set(key, nBlob) - cleanWriteMeter.Mark(int64(len(nBlob))) - } - return nBlob, nil -} - -// update implements the layer interface, returning a new diff layer on top -// with the given state set. -func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { - return newDiffLayer(dl, root, id, block, nodes, states) -} - -// commit merges the given bottom-most diff layer into the node buffer -// and returns a newly constructed disk layer. Note the current disk -// layer must be tagged as stale first to prevent re-access. -func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { - dl.lock.Lock() - defer dl.lock.Unlock() - - // Construct and store the state history first. If crash happens - // after storing the state history but without flushing the - // corresponding states(journal), the stored state history will - // be truncated in the next restart. - if dl.db.freezer != nil { - err := writeHistory(dl.db.diskdb, dl.db.freezer, bottom, dl.db.config.StateLimit) - if err != nil { - return nil, err - } - } - // Mark the diskLayer as stale before applying any mutations on top. - dl.stale = true - - // Store the root->id lookup afterwards. All stored lookups are - // identified by the **unique** state root. It's impossible that - // in the same chain blocks are not adjacent but have the same - // root. - if dl.id == 0 { - rawdb.WriteStateID(dl.db.diskdb, dl.root, 0) - } - rawdb.WriteStateID(dl.db.diskdb, bottom.rootHash(), bottom.stateID()) - - // Construct a new disk layer by merging the nodes from the provided - // diff layer, and flush the content in disk layer if there are too - // many nodes cached. The clean cache is inherited from the original - // disk layer for reusing. - ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.cleans, dl.buffer.commit(bottom.nodes)) - err := ndl.buffer.flush(ndl.db.diskdb, ndl.cleans, ndl.id, force) - if err != nil { - return nil, err - } - return ndl, nil -} - -// revert applies the given state history and return a reverted disk layer. -func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer, error) { - if h.meta.root != dl.rootHash() { - return nil, errUnexpectedHistory - } - // Reject if the provided state history is incomplete. It's due to - // a large construct SELF-DESTRUCT which can't be handled because - // of memory limitation. - if len(h.meta.incomplete) > 0 { - return nil, errors.New("incomplete state history") - } - if dl.id == 0 { - return nil, fmt.Errorf("%w: zero state id", errStateUnrecoverable) - } - // Apply the reverse state changes upon the current state. This must - // be done before holding the lock in order to access state in "this" - // layer. - nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader) - if err != nil { - return nil, err - } - // Mark the diskLayer as stale before applying any mutations on top. - dl.lock.Lock() - defer dl.lock.Unlock() - - dl.stale = true - - // State change may be applied to node buffer, or the persistent - // state, depends on if node buffer is empty or not. If the node - // buffer is not empty, it means that the state transition that - // needs to be reverted is not yet flushed and cached in node - // buffer, otherwise, manipulate persistent state directly. - if !dl.buffer.empty() { - err := dl.buffer.revert(dl.db.diskdb, nodes) - if err != nil { - return nil, err - } - } else { - batch := dl.db.diskdb.NewBatch() - writeNodes(batch, nodes, dl.cleans) - rawdb.WritePersistentStateID(batch, dl.id-1) - if err := batch.Write(); err != nil { - log.Crit("Failed to write states", "err", err) - } - } - return newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.cleans, dl.buffer), nil -} - -// setBufferSize sets the node buffer size to the provided value. -func (dl *diskLayer) setBufferSize(size int) error { - dl.lock.RLock() - defer dl.lock.RUnlock() - - if dl.stale { - return errSnapshotStale - } - return dl.buffer.setSize(size, dl.db.diskdb, dl.cleans, dl.id) -} - -// size returns the approximate size of cached nodes in the disk layer. -func (dl *diskLayer) size() common.StorageSize { - dl.lock.RLock() - defer dl.lock.RUnlock() - - if dl.stale { - return 0 - } - return common.StorageSize(dl.buffer.size) -} - -// hasher is used to compute the sha256 hash of the provided data. -type hasher struct{ sha crypto.KeccakState } - -var hasherPool = sync.Pool{ - New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, -} - -func newHasher() *hasher { - return hasherPool.Get().(*hasher) -} - -func (h *hasher) hash(data []byte) common.Hash { - return crypto.HashData(h.sha, data) -} - -func (h *hasher) release() { - hasherPool.Put(h) -} diff --git a/trie/triedb/pathdb/errors.go b/trie/triedb/pathdb/errors.go deleted file mode 100644 index f503a9c49d..0000000000 --- a/trie/triedb/pathdb/errors.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023 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 pathdb - -import ( - "errors" - "fmt" - - "github.com/ethereum/go-ethereum/common" -) - -var ( - // errSnapshotReadOnly is returned if the database is opened in read only mode - // and mutation is requested. - errSnapshotReadOnly = errors.New("read only") - - // errSnapshotStale is returned from data accessors if the underlying layer - // layer had been invalidated due to the chain progressing forward far enough - // to not maintain the layer's original state. - errSnapshotStale = errors.New("layer stale") - - // errUnexpectedHistory is returned if an unmatched state history is applied - // to the database for state rollback. - errUnexpectedHistory = errors.New("unexpected state history") - - // errStateUnrecoverable is returned if state is required to be reverted to - // a destination without associated state history available. - errStateUnrecoverable = errors.New("state is unrecoverable") - - // errUnexpectedNode is returned if the requested node with specified path is - // not hash matched with expectation. - errUnexpectedNode = errors.New("unexpected node") -) - -func newUnexpectedNodeError(loc string, expHash common.Hash, gotHash common.Hash, owner common.Hash, path []byte) error { - return fmt.Errorf("%w, loc: %s, node: (%x %v), %x!=%x", errUnexpectedNode, loc, owner, path, expHash, gotHash) -} diff --git a/trie/triedb/pathdb/history.go b/trie/triedb/pathdb/history.go deleted file mode 100644 index c12fc55184..0000000000 --- a/trie/triedb/pathdb/history.go +++ /dev/null @@ -1,641 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/trie/triestate" - "golang.org/x/exp/slices" -) - -// State history records the state changes involved in executing a block. The -// state can be reverted to the previous version by applying the associated -// history object (state reverse diff). State history objects are kept to -// guarantee that the system can perform state rollbacks in case of deep reorg. -// -// Each state transition will generate a state history object. Note that not -// every block has a corresponding state history object. If a block performs -// no state changes whatsoever, no state is created for it. Each state history -// will have a sequentially increasing number acting as its unique identifier. -// -// The state history is written to disk (ancient store) when the corresponding -// diff layer is merged into the disk layer. At the same time, system can prune -// the oldest histories according to config. -// -// Disk State -// ^ -// | -// +------------+ +---------+ +---------+ +---------+ -// | Init State |---->| State 1 |---->| ... |---->| State n | -// +------------+ +---------+ +---------+ +---------+ -// -// +-----------+ +------+ +-----------+ -// | History 1 |----> | ... |---->| History n | -// +-----------+ +------+ +-----------+ -// -// # Rollback -// -// If the system wants to roll back to a previous state n, it needs to ensure -// all history objects from n+1 up to the current disk layer are existent. The -// history objects are applied to the state in reverse order, starting from the -// current disk layer. - -const ( - accountIndexSize = common.AddressLength + 13 // The length of encoded account index - slotIndexSize = common.HashLength + 5 // The length of encoded slot index - historyMetaSize = 9 + 2*common.HashLength // The length of fixed size part of meta object - - stateHistoryVersion = uint8(0) // initial version of state history structure. -) - -// Each state history entry is consisted of five elements: -// -// # metadata -// This object contains a few meta fields, such as the associated state root, -// block number, version tag and so on. This object may contain an extra -// accountHash list which means the storage changes belong to these accounts -// are not complete due to large contract destruction. The incomplete history -// can not be used for rollback and serving archive state request. -// -// # account index -// This object contains some index information of account. For example, offset -// and length indicate the location of the data belonging to the account. Besides, -// storageOffset and storageSlots indicate the storage modification location -// belonging to the account. -// -// The size of each account index is *fixed*, and all indexes are sorted -// lexicographically. Thus binary search can be performed to quickly locate a -// specific account. -// -// # account data -// Account data is a concatenated byte stream composed of all account data. -// The account data can be solved by the offset and length info indicated -// by corresponding account index. -// -// fixed size -// ^ ^ -// / \ -// +-----------------+-----------------+----------------+-----------------+ -// | Account index 1 | Account index 2 | ... | Account index N | -// +-----------------+-----------------+----------------+-----------------+ -// | -// | length -// offset |----------------+ -// v v -// +----------------+----------------+----------------+----------------+ -// | Account data 1 | Account data 2 | ... | Account data N | -// +----------------+----------------+----------------+----------------+ -// -// # storage index -// This object is similar with account index. It's also fixed size and contains -// the location info of storage slot data. -// -// # storage data -// Storage data is a concatenated byte stream composed of all storage slot data. -// The storage slot data can be solved by the location info indicated by -// corresponding account index and storage slot index. -// -// fixed size -// ^ ^ -// / \ -// +-----------------+-----------------+----------------+-----------------+ -// | Account index 1 | Account index 2 | ... | Account index N | -// +-----------------+-----------------+----------------+-----------------+ -// | -// | storage slots -// storage offset |-----------------------------------------------------+ -// v v -// +-----------------+-----------------+-----------------+ -// | storage index 1 | storage index 2 | storage index 3 | -// +-----------------+-----------------+-----------------+ -// | length -// offset |-------------+ -// v v -// +-------------+ -// | slot data 1 | -// +-------------+ - -// accountIndex describes the metadata belonging to an account. -type accountIndex struct { - address common.Address // The address of account - length uint8 // The length of account data, size limited by 255 - offset uint32 // The offset of item in account data table - storageOffset uint32 // The offset of storage index in storage index table - storageSlots uint32 // The number of mutated storage slots belonging to the account -} - -// encode packs account index into byte stream. -func (i *accountIndex) encode() []byte { - var buf [accountIndexSize]byte - copy(buf[:], i.address.Bytes()) - buf[common.AddressLength] = i.length - binary.BigEndian.PutUint32(buf[common.AddressLength+1:], i.offset) - binary.BigEndian.PutUint32(buf[common.AddressLength+5:], i.storageOffset) - binary.BigEndian.PutUint32(buf[common.AddressLength+9:], i.storageSlots) - return buf[:] -} - -// decode unpacks account index from byte stream. -func (i *accountIndex) decode(blob []byte) { - i.address = common.BytesToAddress(blob[:common.AddressLength]) - i.length = blob[common.AddressLength] - i.offset = binary.BigEndian.Uint32(blob[common.AddressLength+1:]) - i.storageOffset = binary.BigEndian.Uint32(blob[common.AddressLength+5:]) - i.storageSlots = binary.BigEndian.Uint32(blob[common.AddressLength+9:]) -} - -// slotIndex describes the metadata belonging to a storage slot. -type slotIndex struct { - hash common.Hash // The hash of slot key - length uint8 // The length of storage slot, up to 32 bytes defined in protocol - offset uint32 // The offset of item in storage slot data table -} - -// encode packs slot index into byte stream. -func (i *slotIndex) encode() []byte { - var buf [slotIndexSize]byte - copy(buf[:common.HashLength], i.hash.Bytes()) - buf[common.HashLength] = i.length - binary.BigEndian.PutUint32(buf[common.HashLength+1:], i.offset) - return buf[:] -} - -// decode unpack slot index from the byte stream. -func (i *slotIndex) decode(blob []byte) { - i.hash = common.BytesToHash(blob[:common.HashLength]) - i.length = blob[common.HashLength] - i.offset = binary.BigEndian.Uint32(blob[common.HashLength+1:]) -} - -// meta describes the meta data of state history object. -type meta struct { - version uint8 // version tag of history object - parent common.Hash // prev-state root before the state transition - root common.Hash // post-state root after the state transition - block uint64 // associated block number - incomplete []common.Address // list of address whose storage set is incomplete -} - -// encode packs the meta object into byte stream. -func (m *meta) encode() []byte { - buf := make([]byte, historyMetaSize+len(m.incomplete)*common.AddressLength) - buf[0] = m.version - copy(buf[1:1+common.HashLength], m.parent.Bytes()) - copy(buf[1+common.HashLength:1+2*common.HashLength], m.root.Bytes()) - binary.BigEndian.PutUint64(buf[1+2*common.HashLength:historyMetaSize], m.block) - for i, h := range m.incomplete { - copy(buf[i*common.AddressLength+historyMetaSize:], h.Bytes()) - } - return buf[:] -} - -// decode unpacks the meta object from byte stream. -func (m *meta) decode(blob []byte) error { - if len(blob) < 1 { - return fmt.Errorf("no version tag") - } - switch blob[0] { - case stateHistoryVersion: - if len(blob) < historyMetaSize { - return fmt.Errorf("invalid state history meta, len: %d", len(blob)) - } - if (len(blob)-historyMetaSize)%common.AddressLength != 0 { - return fmt.Errorf("corrupted state history meta, len: %d", len(blob)) - } - m.version = blob[0] - m.parent = common.BytesToHash(blob[1 : 1+common.HashLength]) - m.root = common.BytesToHash(blob[1+common.HashLength : 1+2*common.HashLength]) - m.block = binary.BigEndian.Uint64(blob[1+2*common.HashLength : historyMetaSize]) - for pos := historyMetaSize; pos < len(blob); { - m.incomplete = append(m.incomplete, common.BytesToAddress(blob[pos:pos+common.AddressLength])) - pos += common.AddressLength - } - return nil - default: - return fmt.Errorf("unknown version %d", blob[0]) - } -} - -// history represents a set of state changes belong to a block along with -// the metadata including the state roots involved in the state transition. -// State history objects in disk are linked with each other by a unique id -// (8-bytes integer), the oldest state history object can be pruned on demand -// in order to control the storage size. -type history struct { - meta *meta // Meta data of history - accounts map[common.Address][]byte // Account data keyed by its address hash - accountList []common.Address // Sorted account hash list - storages map[common.Address]map[common.Hash][]byte // Storage data keyed by its address hash and slot hash - storageList map[common.Address][]common.Hash // Sorted slot hash list -} - -// newHistory constructs the state history object with provided state change set. -func newHistory(root common.Hash, parent common.Hash, block uint64, states *triestate.Set) *history { - var ( - accountList []common.Address - storageList = make(map[common.Address][]common.Hash) - incomplete []common.Address - ) - for addr := range states.Accounts { - accountList = append(accountList, addr) - } - slices.SortFunc(accountList, func(a, b common.Address) bool { return a.Less(b) }) - - for addr, slots := range states.Storages { - slist := make([]common.Hash, 0, len(slots)) - for slotHash := range slots { - slist = append(slist, slotHash) - } - slices.SortFunc(slist, func(a, b common.Hash) bool { return a.Less(b) }) - storageList[addr] = slist - } - for addr := range states.Incomplete { - incomplete = append(incomplete, addr) - } - slices.SortFunc(incomplete, func(a, b common.Address) bool { return a.Less(b) }) - - return &history{ - meta: &meta{ - version: stateHistoryVersion, - parent: parent, - root: root, - block: block, - incomplete: incomplete, - }, - accounts: states.Accounts, - accountList: accountList, - storages: states.Storages, - storageList: storageList, - } -} - -// encode serializes the state history and returns four byte streams represent -// concatenated account/storage data, account/storage indexes respectively. -func (h *history) encode() ([]byte, []byte, []byte, []byte) { - var ( - slotNumber uint32 // the number of processed slots - accountData []byte // the buffer for concatenated account data - storageData []byte // the buffer for concatenated storage data - accountIndexes []byte // the buffer for concatenated account index - storageIndexes []byte // the buffer for concatenated storage index - ) - for _, addr := range h.accountList { - accIndex := accountIndex{ - address: addr, - length: uint8(len(h.accounts[addr])), - offset: uint32(len(accountData)), - } - slots, exist := h.storages[addr] - if exist { - // Encode storage slots in order - for _, slotHash := range h.storageList[addr] { - sIndex := slotIndex{ - hash: slotHash, - length: uint8(len(slots[slotHash])), - offset: uint32(len(storageData)), - } - storageData = append(storageData, slots[slotHash]...) - storageIndexes = append(storageIndexes, sIndex.encode()...) - } - // Fill up the storage meta in account index - accIndex.storageOffset = slotNumber - accIndex.storageSlots = uint32(len(slots)) - slotNumber += uint32(len(slots)) - } - accountData = append(accountData, h.accounts[addr]...) - accountIndexes = append(accountIndexes, accIndex.encode()...) - } - return accountData, storageData, accountIndexes, storageIndexes -} - -// decoder wraps the byte streams for decoding with extra meta fields. -type decoder struct { - accountData []byte // the buffer for concatenated account data - storageData []byte // the buffer for concatenated storage data - accountIndexes []byte // the buffer for concatenated account index - storageIndexes []byte // the buffer for concatenated storage index - - lastAccount *common.Address // the address of last resolved account - lastAccountRead uint32 // the read-cursor position of account data - lastSlotIndexRead uint32 // the read-cursor position of storage slot index - lastSlotDataRead uint32 // the read-cursor position of storage slot data -} - -// verify validates the provided byte streams for decoding state history. A few -// checks will be performed to quickly detect data corruption. The byte stream -// is regarded as corrupted if: -// -// - account indexes buffer is empty(empty state set is invalid) -// - account indexes/storage indexer buffer is not aligned -// -// note, these situations are allowed: -// -// - empty account data: all accounts were not present -// - empty storage set: no slots are modified -func (r *decoder) verify() error { - if len(r.accountIndexes)%accountIndexSize != 0 || len(r.accountIndexes) == 0 { - return fmt.Errorf("invalid account index, len: %d", len(r.accountIndexes)) - } - if len(r.storageIndexes)%slotIndexSize != 0 { - return fmt.Errorf("invalid storage index, len: %d", len(r.storageIndexes)) - } - return nil -} - -// readAccount parses the account from the byte stream with specified position. -func (r *decoder) readAccount(pos int) (accountIndex, []byte, error) { - // Decode account index from the index byte stream. - var index accountIndex - if (pos+1)*accountIndexSize > len(r.accountIndexes) { - return accountIndex{}, nil, errors.New("account data buffer is corrupted") - } - index.decode(r.accountIndexes[pos*accountIndexSize : (pos+1)*accountIndexSize]) - - // Perform validation before parsing account data, ensure - // - account is sorted in order in byte stream - // - account data is strictly encoded with no gap inside - // - account data is not out-of-slice - if r.lastAccount != nil { // zero address is possible - if bytes.Compare(r.lastAccount.Bytes(), index.address.Bytes()) >= 0 { - return accountIndex{}, nil, errors.New("account is not in order") - } - } - if index.offset != r.lastAccountRead { - return accountIndex{}, nil, errors.New("account data buffer is gaped") - } - last := index.offset + uint32(index.length) - if uint32(len(r.accountData)) < last { - return accountIndex{}, nil, errors.New("account data buffer is corrupted") - } - data := r.accountData[index.offset:last] - - r.lastAccount = &index.address - r.lastAccountRead = last - - return index, data, nil -} - -// readStorage parses the storage slots from the byte stream with specified account. -func (r *decoder) readStorage(accIndex accountIndex) ([]common.Hash, map[common.Hash][]byte, error) { - var ( - last common.Hash - list []common.Hash - storage = make(map[common.Hash][]byte) - ) - for j := 0; j < int(accIndex.storageSlots); j++ { - var ( - index slotIndex - start = (accIndex.storageOffset + uint32(j)) * uint32(slotIndexSize) - end = (accIndex.storageOffset + uint32(j+1)) * uint32(slotIndexSize) - ) - // Perform validation before parsing storage slot data, ensure - // - slot index is not out-of-slice - // - slot data is not out-of-slice - // - slot is sorted in order in byte stream - // - slot indexes is strictly encoded with no gap inside - // - slot data is strictly encoded with no gap inside - if start != r.lastSlotIndexRead { - return nil, nil, errors.New("storage index buffer is gapped") - } - if uint32(len(r.storageIndexes)) < end { - return nil, nil, errors.New("storage index buffer is corrupted") - } - index.decode(r.storageIndexes[start:end]) - - if bytes.Compare(last.Bytes(), index.hash.Bytes()) >= 0 { - return nil, nil, errors.New("storage slot is not in order") - } - if index.offset != r.lastSlotDataRead { - return nil, nil, errors.New("storage data buffer is gapped") - } - sEnd := index.offset + uint32(index.length) - if uint32(len(r.storageData)) < sEnd { - return nil, nil, errors.New("storage data buffer is corrupted") - } - storage[index.hash] = r.storageData[r.lastSlotDataRead:sEnd] - list = append(list, index.hash) - - last = index.hash - r.lastSlotIndexRead = end - r.lastSlotDataRead = sEnd - } - return list, storage, nil -} - -// decode deserializes the account and storage data from the provided byte stream. -func (h *history) decode(accountData, storageData, accountIndexes, storageIndexes []byte) error { - var ( - accounts = make(map[common.Address][]byte) - storages = make(map[common.Address]map[common.Hash][]byte) - accountList []common.Address - storageList = make(map[common.Address][]common.Hash) - - r = &decoder{ - accountData: accountData, - storageData: storageData, - accountIndexes: accountIndexes, - storageIndexes: storageIndexes, - } - ) - if err := r.verify(); err != nil { - return err - } - for i := 0; i < len(accountIndexes)/accountIndexSize; i++ { - // Resolve account first - accIndex, accData, err := r.readAccount(i) - if err != nil { - return err - } - accounts[accIndex.address] = accData - accountList = append(accountList, accIndex.address) - - // Resolve storage slots - slotList, slotData, err := r.readStorage(accIndex) - if err != nil { - return err - } - if len(slotList) > 0 { - storageList[accIndex.address] = slotList - storages[accIndex.address] = slotData - } - } - h.accounts = accounts - h.accountList = accountList - h.storages = storages - h.storageList = storageList - return nil -} - -// readHistory reads and decodes the state history object by the given id. -func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error) { - blob := rawdb.ReadStateHistoryMeta(freezer, id) - if len(blob) == 0 { - return nil, fmt.Errorf("state history not found %d", id) - } - var m meta - if err := m.decode(blob); err != nil { - return nil, err - } - var ( - dec = history{meta: &m} - accountData = rawdb.ReadStateAccountHistory(freezer, id) - storageData = rawdb.ReadStateStorageHistory(freezer, id) - accountIndexes = rawdb.ReadStateAccountIndex(freezer, id) - storageIndexes = rawdb.ReadStateStorageIndex(freezer, id) - ) - if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { - return nil, err - } - return &dec, nil -} - -// writeHistory writes the state history with provided state set. After -// storing the corresponding state history, it will also prune the stale -// histories from the disk with the given threshold. -func writeHistory(db ethdb.KeyValueStore, freezer *rawdb.ResettableFreezer, dl *diffLayer, limit uint64) error { - // Short circuit if state set is not available. - if dl.states == nil { - return errors.New("state change set is not available") - } - var ( - err error - n int - start = time.Now() - h = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states) - ) - accountData, storageData, accountIndex, storageIndex := h.encode() - dataSize := common.StorageSize(len(accountData) + len(storageData)) - indexSize := common.StorageSize(len(accountIndex) + len(storageIndex)) - - // Write history data into five freezer table respectively. - rawdb.WriteStateHistory(freezer, dl.stateID(), h.meta.encode(), accountIndex, storageIndex, accountData, storageData) - - // Prune stale state histories based on the config. - if limit != 0 && dl.stateID() > limit { - n, err = truncateFromTail(db, freezer, dl.stateID()-limit) - if err != nil { - return err - } - } - historyDataBytesMeter.Mark(int64(dataSize)) - historyIndexBytesMeter.Mark(int64(indexSize)) - historyBuildTimeMeter.UpdateSince(start) - log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "pruned", n, "elapsed", common.PrettyDuration(time.Since(start))) - return nil -} - -// checkHistories retrieves a batch of meta objects with the specified range -// and performs the callback on each item. -func checkHistories(freezer *rawdb.ResettableFreezer, start, count uint64, check func(*meta) error) error { - for count > 0 { - number := count - if number > 10000 { - number = 10000 // split the big read into small chunks - } - blobs, err := rawdb.ReadStateHistoryMetaList(freezer, start, number) - if err != nil { - return err - } - for _, blob := range blobs { - var dec meta - if err := dec.decode(blob); err != nil { - return err - } - if err := check(&dec); err != nil { - return err - } - } - count -= uint64(len(blobs)) - start += uint64(len(blobs)) - } - return nil -} - -// truncateFromHead removes the extra state histories from the head with the given -// parameters. It returns the number of items removed from the head. -func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead uint64) (int, error) { - ohead, err := freezer.Ancients() - if err != nil { - return 0, err - } - if ohead <= nhead { - return 0, nil - } - // Load the meta objects in range [nhead+1, ohead] - blobs, err := rawdb.ReadStateHistoryMetaList(freezer, nhead+1, ohead-nhead) - if err != nil { - return 0, err - } - batch := db.NewBatch() - for _, blob := range blobs { - var m meta - if err := m.decode(blob); err != nil { - return 0, err - } - rawdb.DeleteStateID(batch, m.root) - } - if err := batch.Write(); err != nil { - return 0, err - } - ohead, err = freezer.TruncateHead(nhead) - if err != nil { - return 0, err - } - return int(ohead - nhead), nil -} - -// truncateFromTail removes the extra state histories from the tail with the given -// parameters. It returns the number of items removed from the tail. -func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail uint64) (int, error) { - otail, err := freezer.Tail() - if err != nil { - return 0, err - } - if otail >= ntail { - return 0, nil - } - // Load the meta objects in range [otail+1, ntail] - blobs, err := rawdb.ReadStateHistoryMetaList(freezer, otail+1, ntail-otail) - if err != nil { - return 0, err - } - batch := db.NewBatch() - for _, blob := range blobs { - var m meta - if err := m.decode(blob); err != nil { - return 0, err - } - rawdb.DeleteStateID(batch, m.root) - } - if err := batch.Write(); err != nil { - return 0, err - } - otail, err = freezer.TruncateTail(ntail) - if err != nil { - return 0, err - } - return int(ntail - otail), nil -} diff --git a/trie/triedb/pathdb/history_test.go b/trie/triedb/pathdb/history_test.go deleted file mode 100644 index 6c250c2591..0000000000 --- a/trie/triedb/pathdb/history_test.go +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "bytes" - "fmt" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie/testutil" - "github.com/ethereum/go-ethereum/trie/triestate" -) - -// randomStateSet generates a random state change set. -func randomStateSet(n int) *triestate.Set { - var ( - accounts = make(map[common.Address][]byte) - storages = make(map[common.Address]map[common.Hash][]byte) - ) - for i := 0; i < n; i++ { - addr := testutil.RandomAddress() - storages[addr] = make(map[common.Hash][]byte) - for j := 0; j < 3; j++ { - v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32))) - storages[addr][testutil.RandomHash()] = v - } - account := generateAccount(types.EmptyRootHash) - accounts[addr] = types.SlimAccountRLP(account) - } - return triestate.New(accounts, storages, nil) -} - -func makeHistory() *history { - return newHistory(testutil.RandomHash(), types.EmptyRootHash, 0, randomStateSet(3)) -} - -func makeHistories(n int) []*history { - var ( - parent = types.EmptyRootHash - result []*history - ) - for i := 0; i < n; i++ { - root := testutil.RandomHash() - h := newHistory(root, parent, uint64(i), randomStateSet(3)) - parent = root - result = append(result, h) - } - return result -} - -func TestEncodeDecodeHistory(t *testing.T) { - var ( - m meta - dec history - obj = makeHistory() - ) - // check if meta data can be correctly encode/decode - blob := obj.meta.encode() - if err := m.decode(blob); err != nil { - t.Fatalf("Failed to decode %v", err) - } - if !reflect.DeepEqual(&m, obj.meta) { - t.Fatal("meta is mismatched") - } - - // check if account/storage data can be correctly encode/decode - accountData, storageData, accountIndexes, storageIndexes := obj.encode() - if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { - t.Fatalf("Failed to decode, err: %v", err) - } - if !compareSet(dec.accounts, obj.accounts) { - t.Fatal("account data is mismatched") - } - if !compareStorages(dec.storages, obj.storages) { - t.Fatal("storage data is mismatched") - } - if !compareList(dec.accountList, obj.accountList) { - t.Fatal("account list is mismatched") - } - if !compareStorageList(dec.storageList, obj.storageList) { - t.Fatal("storage list is mismatched") - } -} - -func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, id uint64, root common.Hash, exist bool) { - blob := rawdb.ReadStateHistoryMeta(freezer, id) - if exist && len(blob) == 0 { - t.Fatalf("Failed to load trie history, %d", id) - } - if !exist && len(blob) != 0 { - t.Fatalf("Unexpected trie history, %d", id) - } - if exist && rawdb.ReadStateID(db, root) == nil { - t.Fatalf("Root->ID mapping is not found, %d", id) - } - if !exist && rawdb.ReadStateID(db, root) != nil { - t.Fatalf("Unexpected root->ID mapping, %d", id) - } -} - -func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, from, to uint64, roots []common.Hash, exist bool) { - for i, j := from, 0; i <= to; i, j = i+1, j+1 { - checkHistory(t, db, freezer, i, roots[j], exist) - } -} - -func TestTruncateHeadHistory(t *testing.T) { - var ( - roots []common.Hash - hs = makeHistories(10) - db = rawdb.NewMemoryDatabase() - freezer, _ = openFreezer(t.TempDir(), false) - ) - defer freezer.Close() - - for i := 0; i < len(hs); i++ { - accountData, storageData, accountIndex, storageIndex := hs[i].encode() - rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) - rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) - roots = append(roots, hs[i].meta.root) - } - for size := len(hs); size > 0; size-- { - pruned, err := truncateFromHead(db, freezer, uint64(size-1)) - if err != nil { - t.Fatalf("Failed to truncate from head %v", err) - } - if pruned != 1 { - t.Error("Unexpected pruned items", "want", 1, "got", pruned) - } - checkHistoriesInRange(t, db, freezer, uint64(size), uint64(10), roots[size-1:], false) - checkHistoriesInRange(t, db, freezer, uint64(1), uint64(size-1), roots[:size-1], true) - } -} - -func TestTruncateTailHistory(t *testing.T) { - var ( - roots []common.Hash - hs = makeHistories(10) - db = rawdb.NewMemoryDatabase() - freezer, _ = openFreezer(t.TempDir(), false) - ) - defer freezer.Close() - - for i := 0; i < len(hs); i++ { - accountData, storageData, accountIndex, storageIndex := hs[i].encode() - rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) - rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) - roots = append(roots, hs[i].meta.root) - } - for newTail := 1; newTail < len(hs); newTail++ { - pruned, _ := truncateFromTail(db, freezer, uint64(newTail)) - if pruned != 1 { - t.Error("Unexpected pruned items", "want", 1, "got", pruned) - } - checkHistoriesInRange(t, db, freezer, uint64(1), uint64(newTail), roots[:newTail], false) - checkHistoriesInRange(t, db, freezer, uint64(newTail+1), uint64(10), roots[newTail:], true) - } -} - -func TestTruncateTailHistories(t *testing.T) { - var cases = []struct { - limit uint64 - expPruned int - maxPruned uint64 - minUnpruned uint64 - empty bool - }{ - { - 1, 9, 9, 10, false, - }, - { - 0, 10, 10, 0 /* no meaning */, true, - }, - { - 10, 0, 0, 1, false, - }, - } - for i, c := range cases { - var ( - roots []common.Hash - hs = makeHistories(10) - db = rawdb.NewMemoryDatabase() - freezer, _ = openFreezer(t.TempDir()+fmt.Sprintf("%d", i), false) - ) - defer freezer.Close() - - for i := 0; i < len(hs); i++ { - accountData, storageData, accountIndex, storageIndex := hs[i].encode() - rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) - rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) - roots = append(roots, hs[i].meta.root) - } - pruned, _ := truncateFromTail(db, freezer, uint64(10)-c.limit) - if pruned != c.expPruned { - t.Error("Unexpected pruned items", "want", c.expPruned, "got", pruned) - } - if c.empty { - checkHistoriesInRange(t, db, freezer, uint64(1), uint64(10), roots, false) - } else { - tail := 10 - int(c.limit) - checkHistoriesInRange(t, db, freezer, uint64(1), c.maxPruned, roots[:tail], false) - checkHistoriesInRange(t, db, freezer, c.minUnpruned, uint64(10), roots[tail:], true) - } - } -} - -// openFreezer initializes the freezer instance for storing state histories. -func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) { - return rawdb.NewStateHistoryFreezer(datadir, readOnly) -} - -func compareSet[k comparable](a, b map[k][]byte) bool { - if len(a) != len(b) { - return false - } - for key, valA := range a { - valB, ok := b[key] - if !ok { - return false - } - if !bytes.Equal(valA, valB) { - return false - } - } - return true -} - -func compareList[k comparable](a, b []k) bool { - if len(a) != len(b) { - return false - } - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true -} - -func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool { - if len(a) != len(b) { - return false - } - for h, subA := range a { - subB, ok := b[h] - if !ok { - return false - } - if !compareSet(subA, subB) { - return false - } - } - return true -} - -func compareStorageList(a, b map[common.Address][]common.Hash) bool { - if len(a) != len(b) { - return false - } - for h, la := range a { - lb, ok := b[h] - if !ok { - return false - } - if !compareList(la, lb) { - return false - } - } - return true -} diff --git a/trie/triedb/pathdb/journal.go b/trie/triedb/pathdb/journal.go deleted file mode 100644 index d8c7d39fb9..0000000000 --- a/trie/triedb/pathdb/journal.go +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "bytes" - "errors" - "fmt" - "io" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" -) - -var ( - errMissJournal = errors.New("journal not found") - errMissVersion = errors.New("version not found") - errUnexpectedVersion = errors.New("unexpected journal version") - errMissDiskRoot = errors.New("disk layer root not found") - errUnmatchedJournal = errors.New("unmatched journal") -) - -const journalVersion uint64 = 0 - -// journalNode represents a trie node persisted in the journal. -type journalNode struct { - Path []byte // Path of the node in the trie - Blob []byte // RLP-encoded trie node blob, nil means the node is deleted -} - -// journalNodes represents a list trie nodes belong to a single account -// or the main account trie. -type journalNodes struct { - Owner common.Hash - Nodes []journalNode -} - -// journalAccounts represents a list accounts belong to the layer. -type journalAccounts struct { - Addresses []common.Address - Accounts [][]byte -} - -// journalStorage represents a list of storage slots belong to an account. -type journalStorage struct { - Incomplete bool - Account common.Address - Hashes []common.Hash - Slots [][]byte -} - -// loadJournal tries to parse the layer journal from the disk. -func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) { - journal := rawdb.ReadTrieJournal(db.diskdb) - if len(journal) == 0 { - return nil, errMissJournal - } - r := rlp.NewStream(bytes.NewReader(journal), 0) - - // Firstly, resolve the first element as the journal version - version, err := r.Uint64() - if err != nil { - return nil, errMissVersion - } - if version != journalVersion { - return nil, fmt.Errorf("%w want %d got %d", errUnexpectedVersion, journalVersion, version) - } - // Secondly, resolve the disk layer root, ensure it's continuous - // with disk layer. Note now we can ensure it's the layer journal - // correct version, so we expect everything can be resolved properly. - var root common.Hash - if err := r.Decode(&root); err != nil { - return nil, errMissDiskRoot - } - // The journal is not matched with persistent state, discard them. - // It can happen that geth crashes without persisting the journal. - if !bytes.Equal(root.Bytes(), diskRoot.Bytes()) { - return nil, fmt.Errorf("%w want %x got %x", errUnmatchedJournal, root, diskRoot) - } - // Load the disk layer from the journal - base, err := db.loadDiskLayer(r) - if err != nil { - return nil, err - } - // Load all the diff layers from the journal - head, err := db.loadDiffLayer(base, r) - if err != nil { - return nil, err - } - log.Debug("Loaded layer journal", "diskroot", diskRoot, "diffhead", head.rootHash()) - return head, nil -} - -// loadLayers loads a pre-existing state layer backed by a key-value store. -func (db *Database) loadLayers() layer { - // Retrieve the root node of persistent state. - _, root := rawdb.ReadAccountTrieNode(db.diskdb, nil) - root = types.TrieRootHash(root) - - // Load the layers by resolving the journal - head, err := db.loadJournal(root) - if err == nil { - return head - } - // journal is not matched(or missing) with the persistent state, discard - // it. Display log for discarding journal, but try to avoid showing - // useless information when the db is created from scratch. - if !(root == types.EmptyRootHash && errors.Is(err, errMissJournal)) { - log.Info("Failed to load journal, discard it", "err", err) - } - // Return single layer with persistent state. - return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, newNodeBuffer(db.bufferSize, nil, 0)) -} - -// loadDiskLayer reads the binary blob from the layer journal, reconstructing -// a new disk layer on it. -func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) { - // Resolve disk layer root - var root common.Hash - if err := r.Decode(&root); err != nil { - return nil, fmt.Errorf("load disk root: %v", err) - } - // Resolve the state id of disk layer, it can be different - // with the persistent id tracked in disk, the id distance - // is the number of transitions aggregated in disk layer. - var id uint64 - if err := r.Decode(&id); err != nil { - return nil, fmt.Errorf("load state id: %v", err) - } - stored := rawdb.ReadPersistentStateID(db.diskdb) - if stored > id { - return nil, fmt.Errorf("invalid state id: stored %d resolved %d", stored, id) - } - // Resolve nodes cached in node buffer - var encoded []journalNodes - if err := r.Decode(&encoded); err != nil { - return nil, fmt.Errorf("load disk nodes: %v", err) - } - nodes := make(map[common.Hash]map[string]*trienode.Node) - for _, entry := range encoded { - subset := make(map[string]*trienode.Node) - for _, n := range entry.Nodes { - if len(n.Blob) > 0 { - subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) - } else { - subset[string(n.Path)] = trienode.NewDeleted() - } - } - nodes[entry.Owner] = subset - } - // Calculate the internal state transitions by id difference. - base := newDiskLayer(root, id, db, nil, newNodeBuffer(db.bufferSize, nodes, id-stored)) - return base, nil -} - -// loadDiffLayer reads the next sections of a layer journal, reconstructing a new -// diff and verifying that it can be linked to the requested parent. -func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) { - // Read the next diff journal entry - var root common.Hash - if err := r.Decode(&root); err != nil { - // The first read may fail with EOF, marking the end of the journal - if err == io.EOF { - return parent, nil - } - return nil, fmt.Errorf("load diff root: %v", err) - } - var block uint64 - if err := r.Decode(&block); err != nil { - return nil, fmt.Errorf("load block number: %v", err) - } - // Read in-memory trie nodes from journal - var encoded []journalNodes - if err := r.Decode(&encoded); err != nil { - return nil, fmt.Errorf("load diff nodes: %v", err) - } - nodes := make(map[common.Hash]map[string]*trienode.Node) - for _, entry := range encoded { - subset := make(map[string]*trienode.Node) - for _, n := range entry.Nodes { - if len(n.Blob) > 0 { - subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) - } else { - subset[string(n.Path)] = trienode.NewDeleted() - } - } - nodes[entry.Owner] = subset - } - // Read state changes from journal - var ( - jaccounts journalAccounts - jstorages []journalStorage - accounts = make(map[common.Address][]byte) - storages = make(map[common.Address]map[common.Hash][]byte) - incomplete = make(map[common.Address]struct{}) - ) - if err := r.Decode(&jaccounts); err != nil { - return nil, fmt.Errorf("load diff accounts: %v", err) - } - for i, addr := range jaccounts.Addresses { - accounts[addr] = jaccounts.Accounts[i] - } - if err := r.Decode(&jstorages); err != nil { - return nil, fmt.Errorf("load diff storages: %v", err) - } - for _, entry := range jstorages { - set := make(map[common.Hash][]byte) - for i, h := range entry.Hashes { - if len(entry.Slots[i]) > 0 { - set[h] = entry.Slots[i] - } else { - set[h] = nil - } - } - if entry.Incomplete { - incomplete[entry.Account] = struct{}{} - } - storages[entry.Account] = set - } - return db.loadDiffLayer(newDiffLayer(parent, root, parent.stateID()+1, block, nodes, triestate.New(accounts, storages, incomplete)), r) -} - -// journal implements the layer interface, marshaling the un-flushed trie nodes -// along with layer meta data into provided byte buffer. -func (dl *diskLayer) journal(w io.Writer) error { - dl.lock.RLock() - defer dl.lock.RUnlock() - - // Ensure the layer didn't get stale - if dl.stale { - return errSnapshotStale - } - // Step one, write the disk root into the journal. - if err := rlp.Encode(w, dl.root); err != nil { - return err - } - // Step two, write the corresponding state id into the journal - if err := rlp.Encode(w, dl.id); err != nil { - return err - } - // Step three, write all unwritten nodes into the journal - nodes := make([]journalNodes, 0, len(dl.buffer.nodes)) - for owner, subset := range dl.buffer.nodes { - entry := journalNodes{Owner: owner} - for path, node := range subset { - entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob}) - } - nodes = append(nodes, entry) - } - if err := rlp.Encode(w, nodes); err != nil { - return err - } - log.Debug("Journaled pathdb disk layer", "root", dl.root, "nodes", len(dl.buffer.nodes)) - return nil -} - -// journal implements the layer interface, writing the memory layer contents -// into a buffer to be stored in the database as the layer journal. -func (dl *diffLayer) journal(w io.Writer) error { - dl.lock.RLock() - defer dl.lock.RUnlock() - - // journal the parent first - if err := dl.parent.journal(w); err != nil { - return err - } - // Everything below was journaled, persist this layer too - if err := rlp.Encode(w, dl.root); err != nil { - return err - } - if err := rlp.Encode(w, dl.block); err != nil { - return err - } - // Write the accumulated trie nodes into buffer - nodes := make([]journalNodes, 0, len(dl.nodes)) - for owner, subset := range dl.nodes { - entry := journalNodes{Owner: owner} - for path, node := range subset { - entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob}) - } - nodes = append(nodes, entry) - } - if err := rlp.Encode(w, nodes); err != nil { - return err - } - // Write the accumulated state changes into buffer - var jacct journalAccounts - for addr, account := range dl.states.Accounts { - jacct.Addresses = append(jacct.Addresses, addr) - jacct.Accounts = append(jacct.Accounts, account) - } - if err := rlp.Encode(w, jacct); err != nil { - return err - } - storage := make([]journalStorage, 0, len(dl.states.Storages)) - for addr, slots := range dl.states.Storages { - entry := journalStorage{Account: addr} - if _, ok := dl.states.Incomplete[addr]; ok { - entry.Incomplete = true - } - for slotHash, slot := range slots { - entry.Hashes = append(entry.Hashes, slotHash) - entry.Slots = append(entry.Slots, slot) - } - storage = append(storage, entry) - } - if err := rlp.Encode(w, storage); err != nil { - return err - } - log.Debug("Journaled pathdb diff layer", "root", dl.root, "parent", dl.parent.rootHash(), "id", dl.stateID(), "block", dl.block, "nodes", len(dl.nodes)) - return nil -} - -// Journal commits an entire diff hierarchy to disk into a single journal entry. -// This is meant to be used during shutdown to persist the layer without -// flattening everything down (bad for reorgs). And this function will mark the -// database as read-only to prevent all following mutation to disk. -func (db *Database) Journal(root common.Hash) error { - // Retrieve the head layer to journal from. - l := db.tree.get(root) - if l == nil { - return fmt.Errorf("triedb layer [%#x] missing", root) - } - // Run the journaling - db.lock.Lock() - defer db.lock.Unlock() - - // Short circuit if the database is in read only mode. - if db.readOnly { - return errSnapshotReadOnly - } - // Firstly write out the metadata of journal - journal := new(bytes.Buffer) - if err := rlp.Encode(journal, journalVersion); err != nil { - return err - } - // The stored state in disk might be empty, convert the - // root to emptyRoot in this case. - _, diskroot := rawdb.ReadAccountTrieNode(db.diskdb, nil) - diskroot = types.TrieRootHash(diskroot) - - // Secondly write out the state root in disk, ensure all layers - // on top are continuous with disk. - if err := rlp.Encode(journal, diskroot); err != nil { - return err - } - // Finally write out the journal of each layer in reverse order. - if err := l.journal(journal); err != nil { - return err - } - // Store the journal into the database and return - rawdb.WriteTrieJournal(db.diskdb, journal.Bytes()) - - // Set the db in read only mode to reject all following mutations - db.readOnly = true - log.Info("Stored journal in triedb", "disk", diskroot, "size", common.StorageSize(journal.Len())) - return nil -} diff --git a/trie/triedb/pathdb/layertree.go b/trie/triedb/pathdb/layertree.go deleted file mode 100644 index d314779910..0000000000 --- a/trie/triedb/pathdb/layertree.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "errors" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" -) - -// layerTree is a group of state layers identified by the state root. -// This structure defines a few basic operations for manipulating -// state layers linked with each other in a tree structure. It's -// thread-safe to use. However, callers need to ensure the thread-safety -// of the referenced layer by themselves. -type layerTree struct { - lock sync.RWMutex - layers map[common.Hash]layer -} - -// newLayerTree constructs the layerTree with the given head layer. -func newLayerTree(head layer) *layerTree { - tree := new(layerTree) - tree.reset(head) - return tree -} - -// reset initializes the layerTree by the given head layer. -// All the ancestors will be iterated out and linked in the tree. -func (tree *layerTree) reset(head layer) { - tree.lock.Lock() - defer tree.lock.Unlock() - - var layers = make(map[common.Hash]layer) - for head != nil { - layers[head.rootHash()] = head - head = head.parentLayer() - } - tree.layers = layers -} - -// get retrieves a layer belonging to the given state root. -func (tree *layerTree) get(root common.Hash) layer { - tree.lock.RLock() - defer tree.lock.RUnlock() - - return tree.layers[types.TrieRootHash(root)] -} - -// forEach iterates the stored layers inside and applies the -// given callback on them. -func (tree *layerTree) forEach(onLayer func(layer)) { - tree.lock.RLock() - defer tree.lock.RUnlock() - - for _, layer := range tree.layers { - onLayer(layer) - } -} - -// len returns the number of layers cached. -func (tree *layerTree) len() int { - tree.lock.RLock() - defer tree.lock.RUnlock() - - return len(tree.layers) -} - -// add inserts a new layer into the tree if it can be linked to an existing old parent. -func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { - // Reject noop updates to avoid self-loops. This is a special case that can - // happen for clique networks and proof-of-stake networks where empty blocks - // don't modify the state (0 block subsidy). - // - // Although we could silently ignore this internally, it should be the caller's - // responsibility to avoid even attempting to insert such a layer. - root, parentRoot = types.TrieRootHash(root), types.TrieRootHash(parentRoot) - if root == parentRoot { - return errors.New("layer cycle") - } - parent := tree.get(parentRoot) - if parent == nil { - return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot) - } - l := parent.update(root, parent.stateID()+1, block, nodes.Flatten(), states) - - tree.lock.Lock() - tree.layers[l.rootHash()] = l - tree.lock.Unlock() - return nil -} - -// cap traverses downwards the diff tree until the number of allowed diff layers -// are crossed. All diffs beyond the permitted number are flattened downwards. -func (tree *layerTree) cap(root common.Hash, layers int) error { - // Retrieve the head layer to cap from - root = types.TrieRootHash(root) - l := tree.get(root) - if l == nil { - return fmt.Errorf("triedb layer [%#x] missing", root) - } - diff, ok := l.(*diffLayer) - if !ok { - return fmt.Errorf("triedb layer [%#x] is disk layer", root) - } - tree.lock.Lock() - defer tree.lock.Unlock() - - // If full commit was requested, flatten the diffs and merge onto disk - if layers == 0 { - base, err := diff.persist(true) - if err != nil { - return err - } - // Replace the entire layer tree with the flat base - tree.layers = map[common.Hash]layer{base.rootHash(): base} - return nil - } - // Dive until we run out of layers or reach the persistent database - for i := 0; i < layers-1; i++ { - // If we still have diff layers below, continue down - if parent, ok := diff.parentLayer().(*diffLayer); ok { - diff = parent - } else { - // Diff stack too shallow, return without modifications - return nil - } - } - // We're out of layers, flatten anything below, stopping if it's the disk or if - // the memory limit is not yet exceeded. - switch parent := diff.parentLayer().(type) { - case *diskLayer: - return nil - - case *diffLayer: - // Hold the lock to prevent any read operations until the new - // parent is linked correctly. - diff.lock.Lock() - - base, err := parent.persist(false) - if err != nil { - diff.lock.Unlock() - return err - } - tree.layers[base.rootHash()] = base - diff.parent = base - - diff.lock.Unlock() - - default: - panic(fmt.Sprintf("unknown data layer in triedb: %T", parent)) - } - // Remove any layer that is stale or links into a stale layer - children := make(map[common.Hash][]common.Hash) - for root, layer := range tree.layers { - if dl, ok := layer.(*diffLayer); ok { - parent := dl.parentLayer().rootHash() - children[parent] = append(children[parent], root) - } - } - var remove func(root common.Hash) - remove = func(root common.Hash) { - delete(tree.layers, root) - for _, child := range children[root] { - remove(child) - } - delete(children, root) - } - for root, layer := range tree.layers { - if dl, ok := layer.(*diskLayer); ok && dl.isStale() { - remove(root) - } - } - return nil -} - -// bottom returns the bottom-most disk layer in this tree. -func (tree *layerTree) bottom() *diskLayer { - tree.lock.RLock() - defer tree.lock.RUnlock() - - if len(tree.layers) == 0 { - return nil // Shouldn't happen, empty tree - } - // pick a random one as the entry point - var current layer - for _, layer := range tree.layers { - current = layer - break - } - for current.parentLayer() != nil { - current = current.parentLayer() - } - return current.(*diskLayer) -} diff --git a/trie/triedb/pathdb/metrics.go b/trie/triedb/pathdb/metrics.go deleted file mode 100644 index 9e2b1dcbf5..0000000000 --- a/trie/triedb/pathdb/metrics.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 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 pathdb - -import "github.com/ethereum/go-ethereum/metrics" - -var ( - cleanHitMeter = metrics.NewRegisteredMeter("pathdb/clean/hit", nil) - cleanMissMeter = metrics.NewRegisteredMeter("pathdb/clean/miss", nil) - cleanReadMeter = metrics.NewRegisteredMeter("pathdb/clean/read", nil) - cleanWriteMeter = metrics.NewRegisteredMeter("pathdb/clean/write", nil) - - dirtyHitMeter = metrics.NewRegisteredMeter("pathdb/dirty/hit", nil) - dirtyMissMeter = metrics.NewRegisteredMeter("pathdb/dirty/miss", nil) - dirtyReadMeter = metrics.NewRegisteredMeter("pathdb/dirty/read", nil) - dirtyWriteMeter = metrics.NewRegisteredMeter("pathdb/dirty/write", nil) - dirtyNodeHitDepthHist = metrics.NewRegisteredHistogram("pathdb/dirty/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) - - cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil) - dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil) - diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil) - - commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil) - commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil) - commitBytesMeter = metrics.NewRegisteredMeter("pathdb/commit/bytes", nil) - - gcNodesMeter = metrics.NewRegisteredMeter("pathdb/gc/nodes", nil) - gcBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/bytes", nil) - - diffLayerBytesMeter = metrics.NewRegisteredMeter("pathdb/diff/bytes", nil) - diffLayerNodesMeter = metrics.NewRegisteredMeter("pathdb/diff/nodes", nil) - - historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil) - historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) - historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) -) diff --git a/trie/triedb/pathdb/nodebuffer.go b/trie/triedb/pathdb/nodebuffer.go deleted file mode 100644 index 67de225b04..0000000000 --- a/trie/triedb/pathdb/nodebuffer.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2022 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 pathdb - -import ( - "fmt" - "time" - - "github.com/VictoriaMetrics/fastcache" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/trie/trienode" -) - -// nodebuffer is a collection of modified trie nodes to aggregate the disk -// write. The content of the nodebuffer must be checked before diving into -// disk (since it basically is not-yet-written data). -type nodebuffer struct { - layers uint64 // The number of diff layers aggregated inside - size uint64 // The size of aggregated writes - limit uint64 // The maximum memory allowance in bytes - nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path -} - -// newNodeBuffer initializes the node buffer with the provided nodes. -func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer { - if nodes == nil { - nodes = make(map[common.Hash]map[string]*trienode.Node) - } - var size uint64 - for _, subset := range nodes { - for path, n := range subset { - size += uint64(len(n.Blob) + len(path)) - } - } - return &nodebuffer{ - layers: layers, - nodes: nodes, - size: size, - limit: uint64(limit), - } -} - -// node retrieves the trie node with given node info. -func (b *nodebuffer) node(owner common.Hash, path []byte, hash common.Hash) (*trienode.Node, error) { - subset, ok := b.nodes[owner] - if !ok { - return nil, nil - } - n, ok := subset[string(path)] - if !ok { - return nil, nil - } - if n.Hash != hash { - dirtyFalseMeter.Mark(1) - log.Error("Unexpected trie node in node buffer", "owner", owner, "path", path, "expect", hash, "got", n.Hash) - return nil, newUnexpectedNodeError("dirty", hash, n.Hash, owner, path) - } - return n, nil -} - -// commit merges the dirty nodes into the nodebuffer. This operation won't take -// the ownership of the nodes map which belongs to the bottom-most diff layer. -// It will just hold the node references from the given map which are safe to -// copy. -func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer { - var ( - delta int64 - overwrite int64 - overwriteSize int64 - ) - for owner, subset := range nodes { - current, exist := b.nodes[owner] - if !exist { - // Allocate a new map for the subset instead of claiming it directly - // from the passed map to avoid potential concurrent map read/write. - // The nodes belong to original diff layer are still accessible even - // after merging, thus the ownership of nodes map should still belong - // to original layer and any mutation on it should be prevented. - current = make(map[string]*trienode.Node) - for path, n := range subset { - current[path] = n - delta += int64(len(n.Blob) + len(path)) - } - b.nodes[owner] = current - continue - } - for path, n := range subset { - if orig, exist := current[path]; !exist { - delta += int64(len(n.Blob) + len(path)) - } else { - delta += int64(len(n.Blob) - len(orig.Blob)) - overwrite++ - overwriteSize += int64(len(orig.Blob) + len(path)) - } - current[path] = n - } - b.nodes[owner] = current - } - b.updateSize(delta) - b.layers++ - gcNodesMeter.Mark(overwrite) - gcBytesMeter.Mark(overwriteSize) - return b -} - -// revert is the reverse operation of commit. It also merges the provided nodes -// into the nodebuffer, the difference is that the provided node set should -// revert the changes made by the last state transition. -func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error { - // Short circuit if no embedded state transition to revert. - if b.layers == 0 { - return errStateUnrecoverable - } - b.layers-- - - // Reset the entire buffer if only a single transition left. - if b.layers == 0 { - b.reset() - return nil - } - var delta int64 - for owner, subset := range nodes { - current, ok := b.nodes[owner] - if !ok { - panic(fmt.Sprintf("non-existent subset (%x)", owner)) - } - for path, n := range subset { - orig, ok := current[path] - if !ok { - // There is a special case in MPT that one child is removed from - // a fullNode which only has two children, and then a new child - // with different position is immediately inserted into the fullNode. - // In this case, the clean child of the fullNode will also be - // marked as dirty because of node collapse and expansion. - // - // In case of database rollback, don't panic if this "clean" - // node occurs which is not present in buffer. - var nhash common.Hash - if owner == (common.Hash{}) { - _, nhash = rawdb.ReadAccountTrieNode(db, []byte(path)) - } else { - _, nhash = rawdb.ReadStorageTrieNode(db, owner, []byte(path)) - } - // Ignore the clean node in the case described above. - if nhash == n.Hash { - continue - } - panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex())) - } - current[path] = n - delta += int64(len(n.Blob)) - int64(len(orig.Blob)) - } - } - b.updateSize(delta) - return nil -} - -// updateSize updates the total cache size by the given delta. -func (b *nodebuffer) updateSize(delta int64) { - size := int64(b.size) + delta - if size >= 0 { - b.size = uint64(size) - return - } - s := b.size - b.size = 0 - log.Error("Invalid pathdb buffer size", "prev", common.StorageSize(s), "delta", common.StorageSize(delta)) -} - -// reset cleans up the disk cache. -func (b *nodebuffer) reset() { - b.layers = 0 - b.size = 0 - b.nodes = make(map[common.Hash]map[string]*trienode.Node) -} - -// empty returns an indicator if nodebuffer contains any state transition inside. -func (b *nodebuffer) empty() bool { - return b.layers == 0 -} - -// setSize sets the buffer size to the provided number, and invokes a flush -// operation if the current memory usage exceeds the new limit. -func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error { - b.limit = uint64(size) - return b.flush(db, clean, id, false) -} - -// flush persists the in-memory dirty trie node into the disk if the configured -// memory threshold is reached. Note, all data must be written atomically. -func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error { - if b.size <= b.limit && !force { - return nil - } - // Ensure the target state id is aligned with the internal counter. - head := rawdb.ReadPersistentStateID(db) - if head+b.layers != id { - return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id) - } - var ( - start = time.Now() - batch = db.NewBatchWithSize(int(b.size)) - ) - nodes := writeNodes(batch, b.nodes, clean) - rawdb.WritePersistentStateID(batch, id) - - // Flush all mutations in a single batch - size := batch.ValueSize() - if err := batch.Write(); err != nil { - return err - } - commitBytesMeter.Mark(int64(size)) - commitNodesMeter.Mark(int64(nodes)) - commitTimeTimer.UpdateSince(start) - log.Debug("Persisted pathdb nodes", "nodes", len(b.nodes), "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) - b.reset() - return nil -} - -// writeNodes writes the trie nodes into the provided database batch. -// Note this function will also inject all the newly written nodes -// into clean cache. -func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) { - for owner, subset := range nodes { - for path, n := range subset { - if n.IsDeleted() { - if owner == (common.Hash{}) { - rawdb.DeleteAccountTrieNode(batch, []byte(path)) - } else { - rawdb.DeleteStorageTrieNode(batch, owner, []byte(path)) - } - if clean != nil { - clean.Del(cacheKey(owner, []byte(path))) - } - } else { - if owner == (common.Hash{}) { - rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob) - } else { - rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob) - } - if clean != nil { - clean.Set(cacheKey(owner, []byte(path)), n.Blob) - } - } - } - total += len(subset) - } - return total -} - -// cacheKey constructs the unique key of clean cache. -func cacheKey(owner common.Hash, path []byte) []byte { - if owner == (common.Hash{}) { - return path - } - return append(owner.Bytes(), path...) -} diff --git a/trie/triedb/pathdb/testutils.go b/trie/triedb/pathdb/testutils.go deleted file mode 100644 index cb3a240cc4..0000000000 --- a/trie/triedb/pathdb/testutils.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2023 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 pathdb - -import ( - "bytes" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/triestate" - "golang.org/x/exp/slices" -) - -// testHasher is a test utility for computing root hash of a batch of state -// elements. The hash algorithm is to sort all the elements in lexicographical -// order, concat the key and value in turn, and perform hash calculation on -// the concatenated bytes. Except the root hash, a nodeset will be returned -// once Commit is called, which contains all the changes made to hasher. -type testHasher struct { - owner common.Hash // owner identifier - root common.Hash // original root - dirties map[common.Hash][]byte // dirty states - cleans map[common.Hash][]byte // clean states -} - -// newTestHasher constructs a hasher object with provided states. -func newTestHasher(owner common.Hash, root common.Hash, cleans map[common.Hash][]byte) (*testHasher, error) { - if cleans == nil { - cleans = make(map[common.Hash][]byte) - } - if got, _ := hash(cleans); got != root { - return nil, fmt.Errorf("state root mismatched, want: %x, got: %x", root, got) - } - return &testHasher{ - owner: owner, - root: root, - dirties: make(map[common.Hash][]byte), - cleans: cleans, - }, nil -} - -// Get returns the value for key stored in the trie. -func (h *testHasher) Get(key []byte) ([]byte, error) { - hash := common.BytesToHash(key) - val, ok := h.dirties[hash] - if ok { - return val, nil - } - return h.cleans[hash], nil -} - -// Update associates key with value in the trie. -func (h *testHasher) Update(key, value []byte) error { - h.dirties[common.BytesToHash(key)] = common.CopyBytes(value) - return nil -} - -// Delete removes any existing value for key from the trie. -func (h *testHasher) Delete(key []byte) error { - h.dirties[common.BytesToHash(key)] = nil - return nil -} - -// Commit computes the new hash of the states and returns the set with all -// state changes. -func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { - var ( - nodes = make(map[common.Hash][]byte) - set = trienode.NewNodeSet(h.owner) - ) - for hash, val := range h.cleans { - nodes[hash] = val - } - for hash, val := range h.dirties { - nodes[hash] = val - if bytes.Equal(val, h.cleans[hash]) { - continue - } - if len(val) == 0 { - set.AddNode(hash.Bytes(), trienode.NewDeleted()) - } else { - set.AddNode(hash.Bytes(), trienode.New(crypto.Keccak256Hash(val), val)) - } - } - root, blob := hash(nodes) - - // Include the dirty root node as well. - if root != types.EmptyRootHash && root != h.root { - set.AddNode(nil, trienode.New(root, blob)) - } - if root == types.EmptyRootHash && h.root != types.EmptyRootHash { - set.AddNode(nil, trienode.NewDeleted()) - } - return root, set -} - -// hash performs the hash computation upon the provided states. -func hash(states map[common.Hash][]byte) (common.Hash, []byte) { - var hs []common.Hash - for hash := range states { - hs = append(hs, hash) - } - slices.SortFunc(hs, func(a, b common.Hash) bool { return a.Less(b) }) - - var input []byte - for _, hash := range hs { - if len(states[hash]) == 0 { - continue - } - input = append(input, hash.Bytes()...) - input = append(input, states[hash]...) - } - if len(input) == 0 { - return types.EmptyRootHash, nil - } - return crypto.Keccak256Hash(input), input -} - -type hashLoader struct { - accounts map[common.Hash][]byte - storages map[common.Hash]map[common.Hash][]byte -} - -func newHashLoader(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *hashLoader { - return &hashLoader{ - accounts: accounts, - storages: storages, - } -} - -// OpenTrie opens the main account trie. -func (l *hashLoader) OpenTrie(root common.Hash) (triestate.Trie, error) { - return newTestHasher(common.Hash{}, root, l.accounts) -} - -// OpenStorageTrie opens the storage trie of an account. -func (l *hashLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) { - return newTestHasher(addrHash, root, l.storages[addrHash]) -} diff --git a/trie/trienode/node.go b/trie/trienode/node.go index 8998bcba06..d99f04bd5e 100644 --- a/trie/trienode/node.go +++ b/trie/trienode/node.go @@ -25,8 +25,8 @@ import ( ) // Node is a wrapper which contains the encoded blob of the trie node and its -// node hash. It is general enough that can be used to represent trie node -// corresponding to different trie implementations. +// unique hash identifier. It is general enough that can be used to represent +// trie nodes corresponding to different trie implementations. type Node struct { Hash common.Hash // Node hash, empty for deleted node Blob []byte // Encoded node blob, nil for the deleted node @@ -42,13 +42,35 @@ func (n *Node) IsDeleted() bool { return n.Hash == (common.Hash{}) } +// WithPrev wraps the Node with the previous node value attached. +type WithPrev struct { + *Node + Prev []byte // Encoded original value, nil means it's non-existent +} + +// Unwrap returns the internal Node object. +func (n *WithPrev) Unwrap() *Node { + return n.Node +} + +// Size returns the total memory size used by this node. It overloads +// the function in Node by counting the size of previous value as well. +func (n *WithPrev) Size() int { + return n.Node.Size() + len(n.Prev) +} + // New constructs a node with provided node information. func New(hash common.Hash, blob []byte) *Node { return &Node{Hash: hash, Blob: blob} } -// NewDeleted constructs a node which is deleted. -func NewDeleted() *Node { return New(common.Hash{}, nil) } +// NewWithPrev constructs a node with provided node information. +func NewWithPrev(hash common.Hash, blob []byte, prev []byte) *WithPrev { + return &WithPrev{ + Node: New(hash, blob), + Prev: prev, + } +} // leaf represents a trie leaf node type leaf struct { @@ -61,7 +83,7 @@ type leaf struct { type NodeSet struct { Owner common.Hash Leaves []*leaf - Nodes map[string]*Node + Nodes map[string]*WithPrev updates int // the count of updated and inserted nodes deletes int // the count of deleted nodes } @@ -71,7 +93,7 @@ type NodeSet struct { func NewNodeSet(owner common.Hash) *NodeSet { return &NodeSet{ Owner: owner, - Nodes: make(map[string]*Node), + Nodes: make(map[string]*WithPrev), } } @@ -82,17 +104,17 @@ func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) { for path := range set.Nodes { paths = append(paths, path) } - // Bottom-up, the longest path first + // Bottom-up, longest path first slices.SortFunc(paths, func(a, b string) bool { return a > b // Sort in reverse order }) for _, path := range paths { - callback(path, set.Nodes[path]) + callback(path, set.Nodes[path].Unwrap()) } } // AddNode adds the provided node into set. -func (set *NodeSet) AddNode(path []byte, n *Node) { +func (set *NodeSet) AddNode(path []byte, n *WithPrev) { if n.IsDeleted() { set.deletes += 1 } else { @@ -102,7 +124,7 @@ func (set *NodeSet) AddNode(path []byte, n *Node) { } // Merge adds a set of nodes into the set. -func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error { +func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*WithPrev) error { if set.Owner != owner { return fmt.Errorf("nodesets belong to different owner are not mergeable %x-%x", set.Owner, owner) } @@ -150,11 +172,16 @@ func (set *NodeSet) Summary() string { for path, n := range set.Nodes { // Deletion if n.IsDeleted() { - fmt.Fprintf(out, " [-]: %x\n", path) + fmt.Fprintf(out, " [-]: %x prev: %x\n", path, n.Prev) continue } - // Insertion or update - fmt.Fprintf(out, " [+/*]: %x -> %v \n", path, n.Hash) + // Insertion + if len(n.Prev) == 0 { + fmt.Fprintf(out, " [+]: %x -> %v\n", path, n.Hash) + continue + } + // Update + fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.Hash, n.Prev) } } for _, n := range set.Leaves { @@ -190,12 +217,3 @@ func (set *MergedNodeSet) Merge(other *NodeSet) error { set.Sets[other.Owner] = other return nil } - -// Flatten returns a two-dimensional map for internal nodes. -func (set *MergedNodeSet) Flatten() map[common.Hash]map[string]*Node { - nodes := make(map[common.Hash]map[string]*Node) - for owner, set := range set.Sets { - nodes[owner] = set.Nodes - } - return nodes -} diff --git a/trie/triestate/state.go b/trie/triestate/state.go index cb3611baf9..68fee26d57 100644 --- a/trie/triestate/state.go +++ b/trie/triestate/state.go @@ -16,44 +16,7 @@ package triestate -import ( - "errors" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie/trienode" - "golang.org/x/crypto/sha3" -) - -// Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia -// tree or Verkle tree. -type Trie interface { - // Get returns the value for key stored in the trie. - Get(key []byte) ([]byte, error) - - // Update associates key with value in the trie. - Update(key, value []byte) error - - // Delete removes any existing value for key from the trie. - Delete(key []byte) error - - // Commit the trie and returns a set of dirty nodes generated along with - // the new root hash. - Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) -} - -// TrieLoader wraps functions to load tries. -type TrieLoader interface { - // OpenTrie opens the main account trie. - OpenTrie(root common.Hash) (Trie, error) - - // OpenStorageTrie opens the storage trie of an account. - OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) -} +import "github.com/ethereum/go-ethereum/common" // Set represents a collection of mutated states during a state transition. // The value refers to the original content of state before the transition @@ -61,207 +24,5 @@ type TrieLoader interface { type Set struct { Accounts map[common.Address][]byte // Mutated account set, nil means the account was not present Storages map[common.Address]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present - Incomplete map[common.Address]struct{} // Indicator whether the storage is incomplete due to large deletion - size common.StorageSize // Approximate size of set -} - -// New constructs the state set with provided data. -func New(accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, incomplete map[common.Address]struct{}) *Set { - return &Set{ - Accounts: accounts, - Storages: storages, - Incomplete: incomplete, - } -} - -// Size returns the approximate memory size occupied by the set. -func (s *Set) Size() common.StorageSize { - if s.size != 0 { - return s.size - } - for _, account := range s.Accounts { - s.size += common.StorageSize(common.AddressLength + len(account)) - } - for _, slots := range s.Storages { - for _, val := range slots { - s.size += common.StorageSize(common.HashLength + len(val)) - } - s.size += common.StorageSize(common.AddressLength) - } - s.size += common.StorageSize(common.AddressLength * len(s.Incomplete)) - return s.size -} - -// context wraps all fields for executing state diffs. -type context struct { - prevRoot common.Hash - postRoot common.Hash - accounts map[common.Address][]byte - storages map[common.Address]map[common.Hash][]byte - accountTrie Trie - nodes *trienode.MergedNodeSet -} - -// Apply traverses the provided state diffs, apply them in the associated -// post-state and return the generated dirty trie nodes. The state can be -// loaded via the provided trie loader. -func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, loader TrieLoader) (map[common.Hash]map[string]*trienode.Node, error) { - tr, err := loader.OpenTrie(postRoot) - if err != nil { - return nil, err - } - ctx := &context{ - prevRoot: prevRoot, - postRoot: postRoot, - accounts: accounts, - storages: storages, - accountTrie: tr, - nodes: trienode.NewMergedNodeSet(), - } - for addr, account := range accounts { - var err error - if len(account) == 0 { - err = deleteAccount(ctx, loader, addr) - } else { - err = updateAccount(ctx, loader, addr) - } - if err != nil { - return nil, fmt.Errorf("failed to revert state, err: %w", err) - } - } - root, result := tr.Commit(false) - if root != prevRoot { - return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root) - } - if err := ctx.nodes.Merge(result); err != nil { - return nil, err - } - return ctx.nodes.Flatten(), nil -} - -// updateAccount the account was present in prev-state, and may or may not -// existent in post-state. Apply the reverse diff and verify if the storage -// root matches the one in prev-state account. -func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error { - // The account was present in prev-state, decode it from the - // 'slim-rlp' format bytes. - h := newHasher() - defer h.release() - - addrHash := h.hash(addr.Bytes()) - prev, err := types.FullAccount(ctx.accounts[addr]) - if err != nil { - return err - } - // The account may or may not existent in post-state, try to - // load it and decode if it's found. - blob, err := ctx.accountTrie.Get(addrHash.Bytes()) - if err != nil { - return err - } - post := types.NewEmptyStateAccount() - if len(blob) != 0 { - if err := rlp.DecodeBytes(blob, &post); err != nil { - return err - } - } - // Apply all storage changes into the post-state storage trie. - st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root) - if err != nil { - return err - } - for key, val := range ctx.storages[addr] { - var err error - if len(val) == 0 { - err = st.Delete(key.Bytes()) - } else { - err = st.Update(key.Bytes(), val) - } - if err != nil { - return err - } - } - root, result := st.Commit(false) - if root != prev.Root { - return errors.New("failed to reset storage trie") - } - // The returned set can be nil if storage trie is not changed - // at all. - if result != nil { - if err := ctx.nodes.Merge(result); err != nil { - return err - } - } - // Write the prev-state account into the main trie - full, err := rlp.EncodeToBytes(prev) - if err != nil { - return err - } - return ctx.accountTrie.Update(addrHash.Bytes(), full) -} - -// deleteAccount the account was not present in prev-state, and is expected -// to be existent in post-state. Apply the reverse diff and verify if the -// account and storage is wiped out correctly. -func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error { - // The account must be existent in post-state, load the account. - h := newHasher() - defer h.release() - - addrHash := h.hash(addr.Bytes()) - blob, err := ctx.accountTrie.Get(addrHash.Bytes()) - if err != nil { - return err - } - if len(blob) == 0 { - return fmt.Errorf("account is non-existent %#x", addrHash) - } - var post types.StateAccount - if err := rlp.DecodeBytes(blob, &post); err != nil { - return err - } - st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root) - if err != nil { - return err - } - for key, val := range ctx.storages[addr] { - if len(val) != 0 { - return errors.New("expect storage deletion") - } - if err := st.Delete(key.Bytes()); err != nil { - return err - } - } - root, result := st.Commit(false) - if root != types.EmptyRootHash { - return errors.New("failed to clear storage trie") - } - // The returned set can be nil if storage trie is not changed - // at all. - if result != nil { - if err := ctx.nodes.Merge(result); err != nil { - return err - } - } - // Delete the post-state account from the main trie. - return ctx.accountTrie.Delete(addrHash.Bytes()) -} - -// hasher is used to compute the sha256 hash of the provided data. -type hasher struct{ sha crypto.KeccakState } - -var hasherPool = sync.Pool{ - New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, -} - -func newHasher() *hasher { - return hasherPool.Get().(*hasher) -} - -func (h *hasher) hash(data []byte) common.Hash { - return crypto.HashData(h.sha, data) -} - -func (h *hasher) release() { - hasherPool.Put(h) + Incomplete map[common.Address]struct{} // Indicator whether the storage slot is incomplete due to large deletion }