package integrity import ( "bytes" "context" "encoding/binary" "encoding/hex" "fmt" "math/bits" "time" "github.com/ledgerwatch/erigon-lib/common/length" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/turbo/trie" "github.com/ledgerwatch/log/v3" "go.uber.org/atomic" ) // AssertSubset a & b == a - checks whether a is subset of b func AssertSubset(prefix []byte, a, b uint16) { if (a & b) != a { panic(fmt.Errorf("invariant 'is subset' failed: %x, %b, %b", prefix, a, b)) } } func Trie(db kv.RoDB, tx kv.Tx, slowChecks bool, ctx context.Context) { quit := ctx.Done() readAheadCtx, cancel := context.WithCancel(ctx) defer cancel() logEvery := time.NewTicker(10 * time.Second) defer logEvery.Stop() seek := make([]byte, 256) buf := make([]byte, 256) buf2 := make([]byte, 256) { c, err := tx.Cursor(kv.TrieOfAccounts) if err != nil { panic(err) } defer c.Close() kv.ReadAhead(readAheadCtx, db, atomic.NewBool(false), kv.TrieOfAccounts, nil, math.MaxInt32) trieAcc2, err := tx.Cursor(kv.TrieOfAccounts) if err != nil { panic(err) } defer trieAcc2.Close() accC, err := tx.Cursor(kv.HashedAccounts) if err != nil { panic(err) } defer accC.Close() kv.ReadAhead(readAheadCtx, db, atomic.NewBool(false), kv.HashedAccounts, nil, math.MaxInt32) for k, v, errc := c.First(); k != nil; k, v, errc = c.Next() { if errc != nil { panic(errc) } select { default: case <-quit: return case <-logEvery.C: log.Info("trie account integrity", "key", hex.EncodeToString(k)) } hasState, hasTree, hasHash, hashes, _ := trie.UnmarshalTrieNode(v) AssertSubset(k, hasTree, hasState) AssertSubset(k, hasHash, hasState) if bits.OnesCount16(hasHash) != len(hashes)/length.Hash { panic(fmt.Errorf("invariant bits.OnesCount16(hasHash) == len(hashes) failed: %d, %d", bits.OnesCount16(hasHash), len(v[6:])/length.Hash)) } found := false var parentK []byte // must have parent with right hasTree bit for i := len(k) - 1; i > 0 && !found; i-- { parentK = k[:i] kParent, vParent, err := trieAcc2.SeekExact(parentK) if err != nil { panic(err) } if kParent == nil { continue } found = true parenthasTree := binary.BigEndian.Uint16(vParent[2:]) parentHasBit := 1< 1 { panic(fmt.Errorf("trie hash %x has no parent", k)) } // must have all children seek = seek[:len(k)+1] copy(seek, k) for i := uint16(0); i < 16; i++ { if 1<nil", k, hasTree, i, seek)) } if !bytes.HasPrefix(k2, seek) { panic(fmt.Errorf("key %x has branches %016b, but there is no child %d in db; last seen key: %x->%x", k, hasTree, i, seek, k2)) } } if !slowChecks { continue } // each AccTrie must cover some state buf = buf[:len(k)+1] copy(buf, k) for i := uint16(0); i < 16; i++ { if 1<= 40 && !found; i-- { parentK = k[:i] kParent, vParent, err := trieStorage.SeekExact(parentK) if err != nil { panic(err) } if kParent == nil { continue } found = true parentBranches := binary.BigEndian.Uint16(vParent[2:]) parentHasBit := 1< 40 { panic(fmt.Errorf("trie hash %x has no parent. Last checked: %x", k, parentK)) } // must have all children seek = seek[:len(k)+1] copy(seek, k) for i := uint16(0); i < 16; i++ { if 1<