diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 9c9637ee02..dfd5ab280a 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -64,6 +64,8 @@ const ( // triggering range compaction. It's a quite arbitrary number but just // to avoid triggering range compaction because of small deletion. rangeCompactionThreshold = 100000 + + FixedPrefixAndAddrSize = 33 ) // Config includes all the configurations for pruning. @@ -136,6 +138,9 @@ func NewPruner(db ethdb.Database, config Config, triesInMemory uint64) (*Pruner, flattenBlockHash := rawdb.ReadCanonicalHash(db, headBlock.NumberU64()-triesInMemory) flattenBlock := rawdb.ReadHeader(db, flattenBlockHash, headBlock.NumberU64()-triesInMemory) + if flattenBlock == nil { + return nil, fmt.Errorf("cannot find %v depth block, it cannot prune", triesInMemory) + } return &Pruner{ config: config, chainHeader: headBlock.Header(), @@ -831,7 +836,7 @@ func asyncScanUnExpiredInTrie(db *trie.Database, stateRoot common.Hash, epoch ty return err } if time.Since(logged) > 8*time.Second { - log.Info("Pruning expired states", "trieNodes", trieCount.Load()) + log.Info("Scan unexpired states", "trieNodes", trieCount.Load()) logged = time.Now() } } @@ -850,7 +855,7 @@ func asyncScanExpiredInTrie(db *trie.Database, stateRoot common.Hash, epoch type logged = time.Now() ) for item := range expireContractCh { - log.Info("start scan trie expired state", "addrHash", item.Addr, "root", item.Root) + log.Debug("start scan trie expired state", "addrHash", item.Addr, "root", item.Root) tr, err := trie.New(&trie.ID{ StateRoot: stateRoot, Owner: item.Addr, @@ -866,7 +871,7 @@ func asyncScanExpiredInTrie(db *trie.Database, stateRoot common.Hash, epoch type return err } if time.Since(logged) > 8*time.Second { - log.Info("Pruning expired states", "trieNodes", trieCount.Load()) + log.Info("Scan unexpired states", "trieNodes", trieCount.Load()) logged = time.Now() } } @@ -877,6 +882,7 @@ func asyncScanExpiredInTrie(db *trie.Database, stateRoot common.Hash, epoch type func asyncPruneExpiredStorageInDisk(diskdb ethdb.Database, pruneExpiredInDisk chan *trie.NodeInfo, bloom *bloomfilter.Filter, scheme string) error { var ( + itemCount = 0 trieCount = 0 epochMetaCount = 0 snapCount = 0 @@ -891,37 +897,53 @@ func asyncPruneExpiredStorageInDisk(diskdb ethdb.Database, pruneExpiredInDisk ch log.Debug("found expired state", "addr", info.Addr, "path", hex.EncodeToString(info.Path), "epoch", info.Epoch, "isBranch", info.IsBranch, "isLeaf", info.IsLeaf) + itemCount++ addr := info.Addr - // delete trie kv - trieCount++ switch scheme { case rawdb.PathScheme: val := rawdb.ReadTrieNode(diskdb, addr, info.Path, info.Hash, rawdb.PathScheme) - trieSize += common.StorageSize(len(val) + 33 + len(info.Path)) - rawdb.DeleteTrieNode(batch, addr, info.Path, info.Hash, rawdb.PathScheme) + if len(val) == 0 { + log.Debug("cannot find source trie?", "addr", addr, "path", info.Path, "hash", info.Hash, "epoch", info.Epoch) + } else { + trieCount++ + trieSize += common.StorageSize(len(val) + FixedPrefixAndAddrSize + len(info.Path)) + rawdb.DeleteTrieNode(batch, addr, info.Path, info.Hash, rawdb.PathScheme) + } case rawdb.HashScheme: // hbss has shared kv, so using bloom to filter them out. if bloom == nil || !bloom.Contains(stateBloomHasher(info.Hash.Bytes())) { val := rawdb.ReadTrieNode(diskdb, addr, info.Path, info.Hash, rawdb.HashScheme) - trieSize += common.StorageSize(len(val) + 33) - rawdb.DeleteTrieNode(batch, addr, info.Path, info.Hash, rawdb.HashScheme) + if len(val) == 0 { + log.Debug("cannot find source trie?", "addr", addr, "path", info.Path, "hash", info.Hash, "epoch", info.Epoch) + } else { + trieCount++ + trieSize += common.StorageSize(len(val) + FixedPrefixAndAddrSize) + rawdb.DeleteTrieNode(batch, addr, info.Path, info.Hash, rawdb.HashScheme) + } } } // delete epoch meta if info.IsBranch { - epochMetaCount++ val := rawdb.ReadEpochMetaPlainState(diskdb, addr, string(info.Path)) - epochMetaSize += common.StorageSize(33 + len(info.Path) + len(val)) - rawdb.DeleteEpochMetaPlainState(batch, addr, string(info.Path)) + if len(val) == 0 && info.Epoch > types.StateEpoch0 { + log.Debug("cannot find source epochmeta?", "addr", addr, "path", info.Path, "hash", info.Hash, "epoch", info.Epoch) + } + if len(val) > 0 { + epochMetaCount++ + epochMetaSize += common.StorageSize(FixedPrefixAndAddrSize + len(info.Path) + len(val)) + rawdb.DeleteEpochMetaPlainState(batch, addr, string(info.Path)) + } } // replace snapshot kv only epoch if info.IsLeaf { - snapCount++ size, err := snapshot.ShrinkExpiredLeaf(batch, diskdb, addr, info.Key, info.Epoch, scheme) if err != nil { log.Error("ShrinkExpiredLeaf err", "addr", addr, "key", info.Key, "err", err) } - snapSize += common.StorageSize(size) + if size > 0 { + snapCount++ + snapSize += common.StorageSize(size) + } } if batch.ValueSize() >= ethdb.IdealBatchSize { if err := batch.Write(); err != nil { @@ -930,7 +952,7 @@ func asyncPruneExpiredStorageInDisk(diskdb ethdb.Database, pruneExpiredInDisk ch batch.Reset() } if time.Since(logged) > 8*time.Second { - log.Info("Pruning expired states", "trieNodes", trieCount, "trieSize", trieSize, + log.Info("Pruning expired states", "items", itemCount, "trieNodes", trieCount, "trieSize", trieSize, "SnapKV", snapCount, "SnapKVSize", snapSize, "EpochMeta", epochMetaCount, "EpochMetaSize", epochMetaSize) logged = time.Now() @@ -942,7 +964,7 @@ func asyncPruneExpiredStorageInDisk(diskdb ethdb.Database, pruneExpiredInDisk ch } batch.Reset() } - log.Info("Pruned expired states", "trieNodes", trieCount, "trieSize", trieSize, + log.Info("Pruned expired states", "items", itemCount, "trieNodes", trieCount, "trieSize", trieSize, "SnapKV", snapCount, "SnapKVSize", snapSize, "EpochMeta", epochMetaCount, "EpochMetaSize", epochMetaSize, "elapsed", common.PrettyDuration(time.Since(start))) // Start compactions, will remove the deleted data from the disk immediately. diff --git a/core/state/snapshot/snapshot_expire.go b/core/state/snapshot/snapshot_expire.go index 29520160b0..6659494914 100644 --- a/core/state/snapshot/snapshot_expire.go +++ b/core/state/snapshot/snapshot_expire.go @@ -5,6 +5,7 @@ import ( "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" ) // ShrinkExpiredLeaf tool function for snapshot kv prune @@ -14,13 +15,21 @@ func ShrinkExpiredLeaf(writer ethdb.KeyValueWriter, reader ethdb.KeyValueReader, //cannot prune snapshot in hbss, because it will used for trie prune, but it's ok in pbss. case rawdb.PathScheme: val := rawdb.ReadStorageSnapshot(reader, accountHash, storageHash) + if len(val) == 0 { + log.Warn("cannot find source snapshot?", "addr", accountHash, "key", storageHash, "epoch", epoch) + return 0, nil + } valWithEpoch := NewValueWithEpoch(epoch, nil) enc, err := EncodeValueToRLPBytes(valWithEpoch) if err != nil { return 0, err } rawdb.WriteStorageSnapshot(writer, accountHash, storageHash, enc) - return int64(65 + len(val)), nil + shrinkSize := len(val) - len(enc) + if shrinkSize < 0 { + shrinkSize = 0 + } + return int64(shrinkSize), nil } return 0, nil } diff --git a/core/state/state_expiry.go b/core/state/state_expiry.go index 1f90760a43..8d1e3b9bbf 100644 --- a/core/state/state_expiry.go +++ b/core/state/state_expiry.go @@ -13,7 +13,10 @@ import ( ) var ( - reviveStorageTrieTimer = metrics.NewRegisteredTimer("state/revivetrie/rt", nil) + reviveTrieTimer = metrics.NewRegisteredTimer("state/revivetrie/rt", nil) + reviveTrieMeter = metrics.NewRegisteredMeter("state/revivetrie", nil) + reviveFromLocalMeter = metrics.NewRegisteredMeter("state/revivetrie/local", nil) + reviveFromRemoteMeter = metrics.NewRegisteredMeter("state/revivetrie/remote", nil) ) // stateExpiryMeta it contains all state expiry meta for target block @@ -33,6 +36,7 @@ func defaultStateExpiryMeta() *stateExpiryMeta { // fetchExpiredStorageFromRemote request expired state from remote full state node; func fetchExpiredStorageFromRemote(meta *stateExpiryMeta, addr common.Address, root common.Hash, tr Trie, prefixKey []byte, key common.Hash) (map[string][]byte, error) { log.Debug("fetching expired storage from remoteDB", "addr", addr, "prefix", prefixKey, "key", key) + reviveTrieMeter.Mark(1) if meta.enableLocalRevive { // if there need revive expired state, try to revive locally, when the node is not being pruned, just renew the epoch val, err := tr.TryLocalRevive(addr, key.Bytes()) @@ -44,6 +48,7 @@ func fetchExpiredStorageFromRemote(meta *stateExpiryMeta, addr common.Address, r case *trie.MissingNodeError: // cannot revive locally, request from remote case nil: + reviveFromLocalMeter.Mark(1) ret := make(map[string][]byte, 1) ret[key.String()] = val return ret, nil @@ -52,6 +57,7 @@ func fetchExpiredStorageFromRemote(meta *stateExpiryMeta, addr common.Address, r } } + reviveFromRemoteMeter.Mark(1) // cannot revive locally, fetch remote proof proofs, err := meta.fullStateDB.GetStorageReviveProof(meta.originalRoot, addr, root, []string{common.Bytes2Hex(prefixKey)}, []string{common.Bytes2Hex(key[:])}) log.Debug("fetchExpiredStorageFromRemote GetStorageReviveProof", "addr", addr, "key", key, "proofs", len(proofs), "err", err) @@ -69,7 +75,7 @@ func fetchExpiredStorageFromRemote(meta *stateExpiryMeta, addr common.Address, r // batchFetchExpiredStorageFromRemote request expired state from remote full state node with a list of keys and prefixes. func batchFetchExpiredFromRemote(expiryMeta *stateExpiryMeta, addr common.Address, root common.Hash, tr Trie, prefixKeys [][]byte, keys []common.Hash) ([]map[string][]byte, error) { - + reviveTrieMeter.Mark(int64(len(keys))) ret := make([]map[string][]byte, len(keys)) prefixKeysStr := make([]string, len(prefixKeys)) keysStr := make([]string, len(keys)) @@ -95,7 +101,7 @@ func batchFetchExpiredFromRemote(expiryMeta *stateExpiryMeta, addr common.Addres return nil, err } } - + reviveFromLocalMeter.Mark(int64(len(keys) - len(expiredKeys))) for i, prefix := range expiredPrefixKeys { prefixKeysStr[i] = common.Bytes2Hex(prefix) } @@ -117,6 +123,7 @@ func batchFetchExpiredFromRemote(expiryMeta *stateExpiryMeta, addr common.Addres } // cannot revive locally, fetch remote proof + reviveFromRemoteMeter.Mark(int64(len(keysStr))) proofs, err := expiryMeta.fullStateDB.GetStorageReviveProof(expiryMeta.originalRoot, addr, root, prefixKeysStr, keysStr) log.Debug("fetchExpiredStorageFromRemote GetStorageReviveProof", "addr", addr, "keys", keysStr, "prefixKeys", prefixKeysStr, "proofs", len(proofs), "err", err) if err != nil { @@ -144,7 +151,7 @@ func batchFetchExpiredFromRemote(expiryMeta *stateExpiryMeta, addr common.Addres // ReviveStorageTrie revive trie's expired state from proof func ReviveStorageTrie(addr common.Address, tr Trie, proof types.ReviveStorageProof, targetKey common.Hash) (map[string][]byte, error) { defer func(start time.Time) { - reviveStorageTrieTimer.Update(time.Since(start)) + reviveTrieTimer.Update(time.Since(start)) }(time.Now()) // Decode keys and proofs diff --git a/trie/committer.go b/trie/committer.go index c028cfc151..3669bcda21 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -142,18 +142,25 @@ func (c *committer) store(path []byte, n node) node { } // Collect the dirty node to nodeset for return. nhash := common.BytesToHash(hash) - c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n))) + blob := nodeToBytes(n) + changed := c.tracer.checkNodeChanged(path, blob) + if changed { + c.nodes.AddNode(path, trienode.New(nhash, blob)) + } if c.enableStateExpiry { switch n := n.(type) { case *fullNode: - c.nodes.AddBranchNodeEpochMeta(path, epochmeta.NewBranchNodeEpochMeta(n.EpochMap)) + metaBlob := epochmeta.BranchMeta2Bytes(epochmeta.NewBranchNodeEpochMeta(n.EpochMap)) + if c.tracer.checkEpochMetaChanged(path, metaBlob) { + c.nodes.AddBranchNodeEpochMeta(path, metaBlob) + } } } // 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 // length of leaves should be exactly same. - if c.collectLeaf { + if changed && c.collectLeaf { if sn, ok := n.(*shortNode); ok { if val, ok := sn.Val.(valueNode); ok { c.nodes.AddLeaf(nhash, val) diff --git a/trie/epochmeta/database.go b/trie/epochmeta/database.go index 4921451dea..bfd2072303 100644 --- a/trie/epochmeta/database.go +++ b/trie/epochmeta/database.go @@ -74,3 +74,19 @@ func (s *Reader) Get(addr common.Hash, path string) ([]byte, error) { metaAccessMeter.Mark(1) return s.snap.EpochMeta(addr, path) } + +func BranchMeta2Bytes(meta *BranchNodeEpochMeta) []byte { + if meta == nil || *meta == (BranchNodeEpochMeta{}) { + return []byte{} + } + buf := rlp.NewEncoderBuffer(nil) + meta.Encode(buf) + return buf.ToBytes() +} + +func AccountMeta2Bytes(meta types.StateMeta) ([]byte, error) { + if meta == nil { + return []byte{}, nil + } + return meta.EncodeToRLPBytes() +} diff --git a/trie/epochmeta/disklayer.go b/trie/epochmeta/disklayer.go index ac82dc441f..cc7894736c 100644 --- a/trie/epochmeta/disklayer.go +++ b/trie/epochmeta/disklayer.go @@ -14,7 +14,7 @@ import ( ) const ( - defaultDiskLayerCacheSize = 100000 + defaultDiskLayerCacheSize = 1024000 ) type diskLayer struct { diff --git a/trie/inspect_trie.go b/trie/inspect_trie.go index d00c6fde75..64c9bedd6e 100644 --- a/trie/inspect_trie.go +++ b/trie/inspect_trie.go @@ -236,7 +236,7 @@ func (inspect *Inspector) ConcurrentTraversal(theTrie *Trie, theTrieTreeStat *Tr } if len(inspect.concurrentQueue)*2 < cap(inspect.concurrentQueue) { inspect.wg.Add(1) - go inspect.SubConcurrentTraversal(theTrie, theTrieTreeStat, child, height+1, copyNewSlice(path, []byte{byte(idx)})) + go inspect.SubConcurrentTraversal(theTrie, theTrieTreeStat, child, height+1, copy2NewBytes(path, []byte{byte(idx)})) } else { inspect.ConcurrentTraversal(theTrie, theTrieTreeStat, child, height+1, append(path, byte(idx))) } diff --git a/trie/proof.go b/trie/proof.go index f4c784c143..6b8a080a1e 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -919,7 +919,7 @@ func (m *MPTProofCache) VerifyProof() error { prefix := m.RootKeyHex for i := 0; i < len(m.cacheNodes); i++ { if i-1 >= 0 { - prefix = copyNewSlice(prefix, m.cacheHexPath[i-1]) + prefix = copy2NewBytes(prefix, m.cacheHexPath[i-1]) } // prefix = append(prefix, m.cacheHexPath[i]...) n1 := m.cacheNodes[i] @@ -938,7 +938,7 @@ func (m *MPTProofCache) VerifyProof() error { } if merge { i++ - prefix = copyNewSlice(prefix, m.cacheHexPath[i-1]) + prefix = copy2NewBytes(prefix, m.cacheHexPath[i-1]) nub.n2 = m.cacheNodes[i] nub.n2PrefixKey = prefix } @@ -948,13 +948,19 @@ func (m *MPTProofCache) VerifyProof() error { return nil } -func copyNewSlice(s1, s2 []byte) []byte { +func copy2NewBytes(s1, s2 []byte) []byte { ret := make([]byte, len(s1)+len(s2)) copy(ret, s1) copy(ret[len(s1):], s2) return ret } +func renewBytes(s []byte) []byte { + ret := make([]byte, len(s)) + copy(ret, s) + return ret +} + func (m *MPTProofCache) CacheNubs() []*MPTProofNub { return m.cacheNubs } diff --git a/trie/tracer.go b/trie/tracer.go index 993869db52..e8d5afb3ba 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -17,6 +17,7 @@ package trie import ( + "bytes" "github.com/ethereum/go-ethereum/common" ) @@ -40,19 +41,21 @@ import ( // Note tracer is not thread-safe, callers should be responsible for handling // the concurrency issues by themselves. type tracer struct { - inserts map[string]struct{} - deletes map[string]struct{} - deleteBranchNodes map[string]struct{} // record for epoch meta - accessList map[string][]byte + inserts map[string]struct{} + deletes map[string]struct{} + deleteEpochMetas map[string]struct{} // record for epoch meta + accessList map[string][]byte + accessEpochMetaList map[string][]byte } // newTracer initializes the tracer for capturing trie changes. func newTracer() *tracer { return &tracer{ - inserts: make(map[string]struct{}), - deletes: make(map[string]struct{}), - deleteBranchNodes: make(map[string]struct{}), - accessList: make(map[string][]byte), + inserts: make(map[string]struct{}), + deletes: make(map[string]struct{}), + deleteEpochMetas: make(map[string]struct{}), + accessList: make(map[string][]byte), + accessEpochMetaList: make(map[string][]byte), } } @@ -63,6 +66,11 @@ func (t *tracer) onRead(path []byte, val []byte) { t.accessList[string(path)] = val } +// onReadEpochMeta tracks the newly loaded trie epoch meta +func (t *tracer) onReadEpochMeta(path string, val []byte) { + t.accessEpochMetaList[path] = val +} + // onInsert tracks the newly inserted trie node. If it's already // in the deletion set (resurrected node), then just wipe it from // the deletion set as it's "untouched". @@ -76,8 +84,8 @@ func (t *tracer) onInsert(path []byte) { // onExpandToBranchNode tracks the newly inserted trie branch node. func (t *tracer) onExpandToBranchNode(path []byte) { - if _, present := t.deleteBranchNodes[string(path)]; present { - delete(t.deleteBranchNodes, string(path)) + if _, present := t.deleteEpochMetas[string(path)]; present { + delete(t.deleteEpochMetas, string(path)) } } @@ -94,24 +102,26 @@ func (t *tracer) onDelete(path []byte) { // onDeleteBranchNode tracks the newly deleted trie branch node. func (t *tracer) onDeleteBranchNode(path []byte) { - t.deleteBranchNodes[string(path)] = struct{}{} + t.deleteEpochMetas[string(path)] = struct{}{} } // reset clears the content tracked by tracer. func (t *tracer) reset() { t.inserts = make(map[string]struct{}) t.deletes = make(map[string]struct{}) - t.deleteBranchNodes = make(map[string]struct{}) + t.deleteEpochMetas = make(map[string]struct{}) t.accessList = make(map[string][]byte) + t.accessEpochMetaList = make(map[string][]byte) } // copy returns a deep copied tracer instance. func (t *tracer) copy() *tracer { var ( - inserts = make(map[string]struct{}) - deletes = make(map[string]struct{}) - deleteBranchNodes = make(map[string]struct{}) - accessList = make(map[string][]byte) + inserts = make(map[string]struct{}) + deletes = make(map[string]struct{}) + deleteBranchNodes = make(map[string]struct{}) + accessList = make(map[string][]byte) + accessEpochMetaList = make(map[string][]byte) ) for path := range t.inserts { inserts[path] = struct{}{} @@ -119,17 +129,21 @@ func (t *tracer) copy() *tracer { for path := range t.deletes { deletes[path] = struct{}{} } - for path := range t.deleteBranchNodes { + for path := range t.deleteEpochMetas { deleteBranchNodes[path] = struct{}{} } for path, blob := range t.accessList { accessList[path] = common.CopyBytes(blob) } + for path, blob := range t.accessEpochMetaList { + accessEpochMetaList[path] = common.CopyBytes(blob) + } return &tracer{ - inserts: inserts, - deletes: deletes, - deleteBranchNodes: deleteBranchNodes, - accessList: accessList, + inserts: inserts, + deletes: deletes, + deleteEpochMetas: deleteBranchNodes, + accessList: accessList, + accessEpochMetaList: accessEpochMetaList, } } @@ -152,8 +166,32 @@ func (t *tracer) deletedNodes() []string { // deletedBranchNodes returns a list of branch node paths which are deleted from the trie. func (t *tracer) deletedBranchNodes() []string { var paths []string - for path := range t.deleteBranchNodes { + for path := range t.deleteEpochMetas { + _, ok := t.accessEpochMetaList[path] + if !ok { + continue + } paths = append(paths, path) } return paths } + +// checkNodeChanged check if change for node. +func (t *tracer) checkNodeChanged(path []byte, blob []byte) bool { + val, ok := t.accessList[string(path)] + if !ok { + return len(blob) > 0 + } + + return !bytes.Equal(val, blob) +} + +// checkEpochMetaChanged check if change for epochMeta. +func (t *tracer) checkEpochMetaChanged(path []byte, blob []byte) bool { + val, ok := t.accessEpochMetaList[string(path)] + if !ok { + return len(blob) > 0 + } + + return !bytes.Equal(val, blob) +} diff --git a/trie/trie.go b/trie/trie.go index fe364aac0b..0a401ee95e 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/trie/epochmeta" "github.com/ethereum/go-ethereum/trie/trienode" "sync/atomic" ) @@ -107,13 +108,13 @@ func New(id *ID, db *Database) (*Trie, error) { } // resolve root epoch if trie.enableExpiry { - meta, err := reader.accountMeta() - if err != nil { - return nil, err - } - trie.rootEpoch = meta.Epoch() if id.Root != (common.Hash{}) && id.Root != types.EmptyRootHash { trie.root = hashNode(id.Root[:]) + meta, err := trie.resolveAccountMetaAndTrack() + if err != nil { + return nil, err + } + trie.rootEpoch = meta.Epoch() } return trie, nil } @@ -299,7 +300,7 @@ func (t *Trie) getWithEpoch(origNode node, key []byte, pos int, epoch types.Stat return nil, n, true, err } - if err = t.resolveEpochMeta(child, epoch, key[:pos]); err != nil { + if err = t.resolveEpochMetaAndTrack(child, epoch, key[:pos]); err != nil { return nil, n, true, err } value, newnode, _, err := t.getWithEpoch(child, key, pos, epoch, updateEpoch) @@ -671,7 +672,7 @@ func (t *Trie) insertWithEpoch(n node, prefix, key []byte, value node, epoch typ return false, nil, err } - if err = t.resolveEpochMeta(rn, epoch, prefix); err != nil { + if err = t.resolveEpochMetaAndTrack(rn, epoch, prefix); err != nil { return false, nil, err } @@ -987,7 +988,7 @@ func (t *Trie) deleteWithEpoch(n node, prefix, key []byte, epoch types.StateEpoc return false, nil, err } - if err = t.resolveEpochMeta(rn, epoch, prefix); err != nil { + if err = t.resolveEpochMetaAndTrack(rn, epoch, prefix); err != nil { return false, nil, err } @@ -1016,7 +1017,7 @@ func (t *Trie) resolve(n node, prefix []byte, epoch types.StateEpoch) (node, err if err != nil { return nil, err } - if err = t.resolveEpochMeta(n, epoch, prefix); err != nil { + if err = t.resolveEpochMetaAndTrack(n, epoch, prefix); err != nil { return nil, err } return n, nil @@ -1057,11 +1058,15 @@ func (t *Trie) resolveEpochMeta(n node, epoch types.StateEpoch, prefix []byte) e return nil case *fullNode: n.setEpoch(epoch) - meta, err := t.reader.epochMeta(prefix) + enc, err := t.reader.epochMeta(prefix) if err != nil { return err } - if meta != nil { + if len(enc) > 0 { + meta, err := epochmeta.DecodeFullNodeEpochMeta(enc) + if err != nil { + return err + } n.EpochMap = meta.EpochMap } return nil @@ -1073,6 +1078,55 @@ func (t *Trie) resolveEpochMeta(n node, epoch types.StateEpoch, prefix []byte) e } } +// resolveEpochMetaAndTrack resolve full node's epoch map. +func (t *Trie) resolveEpochMetaAndTrack(n node, epoch types.StateEpoch, prefix []byte) error { + if !t.enableExpiry { + return nil + } + // 1. Check if the node is a full node + switch n := n.(type) { + case *shortNode: + n.setEpoch(epoch) + return nil + case *fullNode: + n.setEpoch(epoch) + enc, err := t.reader.epochMeta(prefix) + if err != nil { + return err + } + t.tracer.onReadEpochMeta(string(prefix), enc) + if len(enc) > 0 { + meta, err := epochmeta.DecodeFullNodeEpochMeta(enc) + if err != nil { + return err + } + n.EpochMap = meta.EpochMap + } + return nil + case valueNode, hashNode, nil: + // just skip + return nil + default: + return errors.New("resolveShadowNode unsupported node type") + } +} + +// resolveAccountMetaAndTrack resolve account's epoch map. +func (t *Trie) resolveAccountMetaAndTrack() (types.MetaNoConsensus, error) { + if !t.enableExpiry { + return types.EmptyMetaNoConsensus, nil + } + enc, err := t.reader.accountMeta() + if err != nil { + return types.EmptyMetaNoConsensus, err + } + t.tracer.onReadEpochMeta(epochmeta.AccountMetadataPath, enc) + if len(enc) > 0 { + return types.DecodeMetaNoConsensusFromRLPBytes(enc) + } + return types.EmptyMetaNoConsensus, nil +} + // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. func (t *Trie) Hash() common.Hash { @@ -1135,9 +1189,13 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) } // store state expiry account meta if t.enableExpiry { - if err := nodes.AddAccountMeta(types.NewMetaNoConsensus(t.rootEpoch)); err != nil { + blob, err := epochmeta.AccountMeta2Bytes(types.NewMetaNoConsensus(t.rootEpoch)) + if err != nil { return common.Hash{}, nil, err } + if t.tracer.checkEpochMetaChanged([]byte(epochmeta.AccountMetadataPath), blob) { + nodes.AddAccountMeta(blob) + } } t.root = newCommitter(nodes, t.tracer, collectLeaf, t.enableExpiry).Commit(t.root) return rootHash, nodes, nil @@ -1296,7 +1354,7 @@ func (t *Trie) tryRevive(n node, key []byte, targetPrefixKey []byte, nub MPTProo if err != nil { return nil, false, err } - if err = t.resolveEpochMeta(child, epoch, key[:pos]); err != nil { + if err = t.resolveEpochMetaAndTrack(child, epoch, key[:pos]); err != nil { return nil, false, err } @@ -1507,7 +1565,7 @@ func (t *Trie) findExpiredSubTree(n node, path []byte, epoch types.StateEpoch, p if err != nil { return err } - if err = t.resolveEpochMeta(resolve, epoch, path); err != nil { + if err = t.resolveEpochMetaAndTrack(resolve, epoch, path); err != nil { return err } @@ -1524,26 +1582,34 @@ func (t *Trie) findExpiredSubTree(n node, path []byte, epoch types.StateEpoch, p func (t *Trie) recursePruneExpiredNode(n node, path []byte, epoch types.StateEpoch, pruneItemCh chan *NodeInfo) error { switch n := n.(type) { case *shortNode: + subPath := append(path, n.Key...) key := common.Hash{} - _, ok := n.Val.(valueNode) - if ok { - key = common.BytesToHash(hexToKeybytes(append(path, n.Key...))) - } - err := t.recursePruneExpiredNode(n.Val, append(path, n.Key...), epoch, pruneItemCh) - if err != nil { - return err + _, isLeaf := n.Val.(valueNode) + if isLeaf { + key = common.BytesToHash(hexToKeybytes(subPath)) } - // prune child first pruneItemCh <- &NodeInfo{ Addr: t.owner, Hash: common.BytesToHash(n.flags.hash), - Path: path, + Path: renewBytes(path), Key: key, Epoch: epoch, - IsLeaf: ok, + IsLeaf: isLeaf, + } + + err := t.recursePruneExpiredNode(n.Val, subPath, epoch, pruneItemCh) + if err != nil { + return err } return nil case *fullNode: + pruneItemCh <- &NodeInfo{ + Addr: t.owner, + Hash: common.BytesToHash(n.flags.hash), + Path: renewBytes(path), + Epoch: epoch, + IsBranch: true, + } // recurse child, and except valueNode for i := 0; i < BranchNodeLength-1; i++ { err := t.recursePruneExpiredNode(n.Children[i], append(path, byte(i)), n.EpochMap[i], pruneItemCh) @@ -1551,25 +1617,21 @@ func (t *Trie) recursePruneExpiredNode(n node, path []byte, epoch types.StateEpo return err } } - // prune child first - pruneItemCh <- &NodeInfo{ - Addr: t.owner, - Hash: common.BytesToHash(n.flags.hash), - Path: path, - Epoch: epoch, - IsBranch: true, - } return nil case hashNode: // hashNode is a index of trie node storage, need not prune. - resolve, err := t.resolveAndTrack(n, path) + rn, err := t.resolveAndTrack(n, path) + // if touch miss node, just skip + if _, ok := err.(*MissingNodeError); ok { + return nil + } if err != nil { return err } - if err = t.resolveEpochMeta(resolve, epoch, path); err != nil { + if err = t.resolveEpochMetaAndTrack(rn, epoch, path); err != nil { return err } - return t.recursePruneExpiredNode(resolve, path, epoch, pruneItemCh) + return t.recursePruneExpiredNode(rn, path, epoch, pruneItemCh) case valueNode: // value node is not a single storage uint, so pass to prune. return nil diff --git a/trie/trie_expiry.go b/trie/trie_expiry.go index c284e4b6ae..94ee7927ee 100644 --- a/trie/trie_expiry.go +++ b/trie/trie_expiry.go @@ -61,7 +61,7 @@ func (t *Trie) tryLocalRevive(origNode node, key []byte, pos int, epoch types.St return nil, n, true, err } - if err = t.resolveEpochMeta(child, epoch, key[:pos]); err != nil { + if err = t.resolveEpochMetaAndTrack(child, epoch, key[:pos]); err != nil { return nil, n, true, err } value, newnode, _, err := t.tryLocalRevive(child, key, pos, epoch) diff --git a/trie/trie_reader.go b/trie/trie_reader.go index 24b63c3593..c4c61fc4d2 100644 --- a/trie/trie_reader.go +++ b/trie/trie_reader.go @@ -133,7 +133,7 @@ func (l *trieLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root commo } // epochMeta resolve from epoch meta storage -func (r *trieReader) epochMeta(path []byte) (*epochmeta.BranchNodeEpochMeta, error) { +func (r *trieReader) epochMeta(path []byte) ([]byte, error) { defer func(start time.Time) { epochMetaTimer.Update(time.Since(start)) }(time.Now()) @@ -146,28 +146,21 @@ func (r *trieReader) epochMeta(path []byte) (*epochmeta.BranchNodeEpochMeta, err if err != nil { return nil, fmt.Errorf("resolve epoch meta err, path: %#x, err: %v", path, err) } - if len(blob) == 0 { - return nil, nil - } - meta, err := epochmeta.DecodeFullNodeEpochMeta(blob) - if err != nil { - return nil, err - } - return meta, nil + return blob, nil } // accountMeta resolve account metadata -func (r *trieReader) accountMeta() (types.MetaNoConsensus, error) { +func (r *trieReader) accountMeta() ([]byte, error) { defer func(start time.Time) { accountMetaTimer.Update(time.Since(start)) }(time.Now()) if r.emReader == nil { - return types.EmptyMetaNoConsensus, errors.New("cannot resolve epoch meta without db for account") + return nil, errors.New("cannot resolve epoch meta without db for account") } blob, err := r.emReader.Get(r.owner, epochmeta.AccountMetadataPath) if err != nil { - return types.EmptyMetaNoConsensus, fmt.Errorf("resolve epoch meta err for account, err: %v", err) + return nil, fmt.Errorf("resolve epoch meta err for account, err: %v", err) } - return types.DecodeMetaNoConsensusFromRLPBytes(blob) + return blob, nil } diff --git a/trie/triedb/pathdb/difflayer.go b/trie/triedb/pathdb/difflayer.go index bea0208461..4ffe0bee2f 100644 --- a/trie/triedb/pathdb/difflayer.go +++ b/trie/triedb/pathdb/difflayer.go @@ -113,7 +113,7 @@ func (dl *diffLayer) node(owner common.Hash, path []byte, hash common.Hash, dept // 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", "root", dl.root, "owner", owner, "path", path, "expect", hash, "got", n.Hash) + log.Debug("Unexpected trie node in diff layer", "root", dl.root, "owner", owner, "path", path, "expect", hash, "got", n.Hash) return nil, newUnexpectedNodeError("diff", hash, n.Hash, owner, path) } dirtyHitMeter.Mark(1) diff --git a/trie/triedb/pathdb/disklayer.go b/trie/triedb/pathdb/disklayer.go index 87718290f9..80da6db7da 100644 --- a/trie/triedb/pathdb/disklayer.go +++ b/trie/triedb/pathdb/disklayer.go @@ -133,7 +133,7 @@ func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]b return blob, nil } cleanFalseMeter.Mark(1) - log.Error("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got) + log.Debug("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got) } cleanMissMeter.Mark(1) } @@ -149,7 +149,7 @@ func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]b } if nHash != hash { diskFalseMeter.Mark(1) - log.Error("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash) + log.Debug("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 { diff --git a/trie/triedb/pathdb/nodebuffer.go b/trie/triedb/pathdb/nodebuffer.go index 67de225b04..42d06bd6dd 100644 --- a/trie/triedb/pathdb/nodebuffer.go +++ b/trie/triedb/pathdb/nodebuffer.go @@ -70,7 +70,7 @@ func (b *nodebuffer) node(owner common.Hash, path []byte, hash common.Hash) (*tr } if n.Hash != hash { dirtyFalseMeter.Mark(1) - log.Error("Unexpected trie node in node buffer", "owner", owner, "path", path, "expect", hash, "got", n.Hash) + log.Debug("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 diff --git a/trie/trienode/node.go b/trie/trienode/node.go index 3b0b71e9e5..ebd13aeae1 100644 --- a/trie/trienode/node.go +++ b/trie/trienode/node.go @@ -18,8 +18,6 @@ package trienode import ( "fmt" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/epochmeta" "sort" "strings" @@ -105,28 +103,13 @@ func (set *NodeSet) AddNode(path []byte, n *Node) { } // AddBranchNodeEpochMeta adds the provided epoch meta into set. -func (set *NodeSet) AddBranchNodeEpochMeta(path []byte, meta *epochmeta.BranchNodeEpochMeta) { - if meta == nil || *meta == (epochmeta.BranchNodeEpochMeta{}) { - set.EpochMetas[string(path)] = []byte{} - return - } - buf := rlp.NewEncoderBuffer(nil) - meta.Encode(buf) - set.EpochMetas[string(path)] = buf.ToBytes() +func (set *NodeSet) AddBranchNodeEpochMeta(path []byte, blob []byte) { + set.EpochMetas[string(path)] = blob } // AddAccountMeta adds the provided account into set. -func (set *NodeSet) AddAccountMeta(meta types.StateMeta) error { - if meta == nil { - set.EpochMetas[epochmeta.AccountMetadataPath] = []byte{} - return nil - } - enc, err := meta.EncodeToRLPBytes() - if err != nil { - return err - } - set.EpochMetas[epochmeta.AccountMetadataPath] = enc - return nil +func (set *NodeSet) AddAccountMeta(blob []byte) { + set.EpochMetas[epochmeta.AccountMetadataPath] = blob } // Merge adds a set of nodes into the set.