diff --git a/erigon-lib/commitment/commitment.go b/erigon-lib/commitment/commitment.go index 9113a8c79ba..a6a6d61d2af 100644 --- a/erigon-lib/commitment/commitment.go +++ b/erigon-lib/commitment/commitment.go @@ -22,21 +22,23 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/erigontech/erigon-lib/types/accounts" "math/bits" "sort" "strings" "unsafe" - "github.com/google/btree" "github.com/holiman/uint256" + + "github.com/google/btree" "golang.org/x/crypto/sha3" + "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/common/cryptozerocopy" "github.com/erigontech/erigon-lib/common/length" "github.com/erigontech/erigon-lib/etl" "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon-lib/metrics" - "github.com/erigontech/erigon-lib/types/accounts" ) var ( @@ -134,17 +136,14 @@ func InitializeTrieAndUpdates(tv TrieVariant, mode Mode, tmpdir string) (Trie, * fallthrough default: - trie := NewHexPatriciaHashed(length.Addr, nil) + trie := NewHexPatriciaHashed(length.Addr, nil, tmpdir) tree := NewUpdates(mode, tmpdir, KeyToHexNibbleHash) return trie, tree } } -// cellFields is a bitmask of fields presented in the cell for encoding type cellFields uint8 -func (c cellFields) Has(field cellFields) bool { return c&field != 0 } - const ( fieldExtension cellFields = 1 fieldAccountAddr cellFields = 2 @@ -155,19 +154,19 @@ const ( func (p cellFields) String() string { var sb strings.Builder - if p.Has(fieldExtension) { + if p&fieldExtension != 0 { sb.WriteString("DownHash") } - if p.Has(fieldAccountAddr) { + if p&fieldAccountAddr != 0 { sb.WriteString("+AccountPlain") } - if p.Has(fieldStorageAddr) { + if p&fieldStorageAddr != 0 { sb.WriteString("+StoragePlain") } - if p.Has(fieldHash) { + if p&fieldHash != 0 { sb.WriteString("+Hash") } - if p.Has(fieldStateHash) { + if p&fieldStateHash != 0 { sb.WriteString("+LeafHash") } return sb.String() @@ -177,110 +176,110 @@ type BranchEncoder struct { buf *bytes.Buffer bitmapBuf [binary.MaxVarintLen64]byte merger *BranchMerger + updates *etl.Collector + tmpdir string } -func NewBranchEncoder(sz uint64) *BranchEncoder { - return &BranchEncoder{ +func NewBranchEncoder(sz uint64, tmpdir string) *BranchEncoder { + be := &BranchEncoder{ buf: bytes.NewBuffer(make([]byte, sz)), + tmpdir: tmpdir, merger: NewHexBranchMerger(sz / 2), } + //be.initCollector() + return be } -func (be *BranchEncoder) putUvarAndVal(size uint64, val []byte) error { - n := binary.PutUvarint(be.bitmapBuf[:], size) - if _, err := be.buf.Write(be.bitmapBuf[:n]); err != nil { - return err +func (be *BranchEncoder) initCollector() { + if be.updates != nil { + be.updates.Close() } - if _, err := be.buf.Write(val); err != nil { - return err - } - return nil + be.updates = etl.NewCollector("commitment.BranchEncoder", be.tmpdir, etl.NewOldestEntryBuffer(etl.BufferOptimalSize/4), log.Root().New("branch-encoder")) + be.updates.LogLvl(log.LvlDebug) + be.updates.SortAndFlushInBackground(true) } -func (cell *cell) EncodeInto(be *BranchEncoder) error { - var fields cellFields - if cell.extLen > 0 && cell.storageAddrLen == 0 { - fields |= fieldExtension - } - if cell.accountAddrLen > 0 { - fields |= fieldAccountAddr - } - if cell.storageAddrLen > 0 { - fields |= fieldStorageAddr - } - if cell.hashLen > 0 { - fields |= fieldHash - } - if cell.stateHashLen == 32 && (cell.accountAddrLen > 0 || cell.storageAddrLen > 0) { - fields |= fieldStateHash - } - if err := be.buf.WriteByte(byte(fields)); err != nil { - return err +func (be *BranchEncoder) Load(pc PatriciaContext, args etl.TransformArgs) error { + // do not collect them at least now. Write them at CollectUpdate into pc + if be.updates == nil { + return nil } - if fields.Has(fieldExtension) { - if err := be.putUvarAndVal(uint64(cell.extLen), cell.extension[:cell.extLen]); err != nil { + + if err := be.updates.Load(nil, "", func(prefix, update []byte, table etl.CurrentTableReader, next etl.LoadNextFunc) error { + stateValue, stateStep, err := pc.Branch(prefix) + if err != nil { return err } - } - if fields.Has(fieldAccountAddr) { - if err := be.putUvarAndVal(uint64(cell.accountAddrLen), cell.accountAddr[:cell.accountAddrLen]); err != nil { + + cp, cu := common.Copy(prefix), common.Copy(update) // has to copy :( + if err = pc.PutBranch(cp, cu, stateValue, stateStep); err != nil { return err } + mxTrieBranchesUpdated.Inc() + return nil + }, args); err != nil { + return err } - if fields.Has(fieldStorageAddr) { - if err := be.putUvarAndVal(uint64(cell.storageAddrLen), cell.storageAddr[:cell.storageAddrLen]); err != nil { - return err - } + be.initCollector() + return nil +} + +func (be *BranchEncoder) CollectUpdate( + ctx PatriciaContext, + prefix []byte, + bitmap, touchMap, afterMap uint16, + readCell func(nibble int, skip bool) (*cell, error), +) (lastNibble int, err error) { + + prev, prevStep, err := ctx.Branch(prefix) + if err != nil { + return 0, err } - if fields.Has(fieldHash) { - if err := be.putUvarAndVal(uint64(cell.hashLen), cell.hash[:cell.hashLen]); err != nil { - return err - } + update, lastNibble, err := be.EncodeBranch(bitmap, touchMap, afterMap, readCell) + if err != nil { + return 0, err } - if fields.Has(fieldStateHash) { - if err := be.putUvarAndVal(uint64(cell.stateHashLen), cell.stateHash[:cell.stateHashLen]); err != nil { - return err + + if len(prev) > 0 { + if bytes.Equal(prev, update) { + //fmt.Printf("skip collectBranchUpdate [%x]\n", prefix) + return lastNibble, nil // do not write the same data for prefix + } + update, err = be.merger.Merge(prev, update) + if err != nil { + return 0, err } } - return nil -} - -// EncodeDelete encodes deleted branch with given touchMap. -// Returned slice is valid until next call to encodeMaps/Reset() -func (be *BranchEncoder) EncodeDelete(tm uint16) ([]byte, error) { - if err := be.encodeMaps(tm, 0); err != nil { - return nil, err + //fmt.Printf("\ncollectBranchUpdate [%x] -> %s\n", prefix, BranchData(update).String()) + // has to copy :( + if err = ctx.PutBranch(common.Copy(prefix), common.Copy(update), prev, prevStep); err != nil { + return 0, err } - return be.EncodedBranch(), nil + return lastNibble, nil } -// Each branch begins with 4 bytes bitmap (touchMap, afterMap). -// encodeMaps resets be.buf and encodes them into be.buf -func (be *BranchEncoder) encodeMaps(touchMap, afterMap uint16) error { - binary.BigEndian.PutUint16(be.bitmapBuf[:], touchMap) - binary.BigEndian.PutUint16(be.bitmapBuf[2:], afterMap) - - be.buf.Reset() - - if _, err := be.buf.Write(be.bitmapBuf[:4]); err != nil { - be.buf.Reset() +func (be *BranchEncoder) putUvarAndVal(size uint64, val []byte) error { + n := binary.PutUvarint(be.bitmapBuf[:], size) + if _, err := be.buf.Write(be.bitmapBuf[:n]); err != nil { + return err + } + if _, err := be.buf.Write(val); err != nil { return err } return nil } -// Cells in branch comes one by one without mentionting the nibble -func (be *BranchEncoder) encodeCell(c *cell) error { return c.EncodeInto(be) } - -// Returned slice is valid until next call to encodeMaps/be.Reset() -func (be *BranchEncoder) EncodedBranch() []byte { return be.buf.Bytes() } - -func (be *BranchEncoder) Reset() { be.buf.Reset() } - // Encoded result should be copied before next call to EncodeBranch, underlying slice is reused -// DEPRECATED func (be *BranchEncoder) EncodeBranch(bitmap, touchMap, afterMap uint16, readCell func(nibble int, skip bool) (*cell, error)) (BranchData, int, error) { - if err := be.encodeMaps(touchMap, afterMap); err != nil { + be.buf.Reset() + + var encoded [2]byte + binary.BigEndian.PutUint16(encoded[:], touchMap) + if _, err := be.buf.Write(encoded[:]); err != nil { + return nil, 0, err + } + binary.BigEndian.PutUint16(encoded[:], afterMap) + if _, err := be.buf.Write(encoded[:]); err != nil { return nil, 0, err } @@ -301,16 +300,59 @@ func (be *BranchEncoder) EncodeBranch(bitmap, touchMap, afterMap uint16, readCel } if bitmap&bit != 0 { - if err := cell.EncodeInto(be); err != nil { + var fields cellFields + if cell.extLen > 0 && cell.storageAddrLen == 0 { + fields |= fieldExtension + } + if cell.accountAddrLen > 0 { + fields |= fieldAccountAddr + } + if cell.storageAddrLen > 0 { + fields |= fieldStorageAddr + } + if cell.hashLen > 0 { + fields |= fieldHash + } + if cell.stateHashLen == 32 && (cell.accountAddrLen > 0 || cell.storageAddrLen > 0) { + fields |= fieldStateHash + } + if err := be.buf.WriteByte(byte(fields)); err != nil { return nil, 0, err } + if fields&fieldExtension != 0 { + if err := be.putUvarAndVal(uint64(cell.extLen), cell.extension[:cell.extLen]); err != nil { + return nil, 0, err + } + } + if fields&fieldAccountAddr != 0 { + if err := be.putUvarAndVal(uint64(cell.accountAddrLen), cell.accountAddr[:cell.accountAddrLen]); err != nil { + return nil, 0, err + } + } + if fields&fieldStorageAddr != 0 { + if err := be.putUvarAndVal(uint64(cell.storageAddrLen), cell.storageAddr[:cell.storageAddrLen]); err != nil { + return nil, 0, err + } + } + if fields&fieldHash != 0 { + if err := be.putUvarAndVal(uint64(cell.hashLen), cell.hash[:cell.hashLen]); err != nil { + return nil, 0, err + } + } + if fields&fieldStateHash != 0 { + if err := be.putUvarAndVal(uint64(cell.stateHashLen), cell.stateHash[:cell.stateHashLen]); err != nil { + return nil, 0, err + } + } } bitset ^= bit } //fmt.Printf("EncodeBranch [%x] size: %d\n", be.buf.Bytes(), be.buf.Len()) - return be.EncodedBranch(), lastNibble, nil + return be.buf.Bytes(), lastNibble, nil } +func RetrieveCellNoop(nibble int, skip bool) (*cell, error) { return nil, nil } + type BranchData []byte func (branchData BranchData) String() string { @@ -337,7 +379,27 @@ func (branchData BranchData) String() string { // This is used for test output, so ok to panic panic(err) } - sb.WriteString(cell.String()) + sb.WriteString("{") + var comma string + if cell.hashedExtLen > 0 { + fmt.Fprintf(&sb, "hashedExtension=[%x]", cell.hashedExtension[:cell.hashedExtLen]) + comma = "," + } + if cell.accountAddrLen > 0 { + fmt.Fprintf(&sb, "%saccountAddr=[%x]", comma, cell.accountAddr[:cell.accountAddrLen]) + comma = "," + } + if cell.storageAddrLen > 0 { + fmt.Fprintf(&sb, "%sstorageAddr=[%x]", comma, cell.storageAddr[:cell.storageAddrLen]) + comma = "," + } + if cell.hashLen > 0 { + fmt.Fprintf(&sb, "%shash=[%x]", comma, cell.hash[:cell.hashLen]) + } + if cell.stateHashLen > 0 { + fmt.Fprintf(&sb, "%sleafHash=[%x]", comma, cell.stateHash[:cell.stateHashLen]) + } + sb.WriteString("}\n") } bitset ^= bit } @@ -363,7 +425,7 @@ func (branchData BranchData) ReplacePlainKeys(newData []byte, fn func(key []byte fields := cellFields(branchData[pos]) newData = append(newData, byte(fields)) pos++ - if fields.Has(fieldExtension) { + if fields&fieldExtension != 0 { l, n := binary.Uvarint(branchData[pos:]) if n == 0 { return nil, errors.New("replacePlainKeys buffer too small for hashedKey len") @@ -380,7 +442,7 @@ func (branchData BranchData) ReplacePlainKeys(newData []byte, fn func(key []byte pos += int(l) } } - if fields.Has(fieldAccountAddr) { + if fields&fieldAccountAddr != 0 { l, n := binary.Uvarint(branchData[pos:]) if n == 0 { return nil, errors.New("replacePlainKeys buffer too small for accountAddr len") @@ -413,7 +475,7 @@ func (branchData BranchData) ReplacePlainKeys(newData []byte, fn func(key []byte newData = append(newData, newKey...) } } - if fields.Has(fieldStorageAddr) { + if fields&fieldStorageAddr != 0 { l, n := binary.Uvarint(branchData[pos:]) if n == 0 { return nil, errors.New("replacePlainKeys buffer too small for storageAddr len") @@ -446,7 +508,7 @@ func (branchData BranchData) ReplacePlainKeys(newData []byte, fn func(key []byte newData = append(newData, newKey...) } } - if fields.Has(fieldHash) { + if fields&fieldHash != 0 { l, n := binary.Uvarint(branchData[pos:]) if n == 0 { return nil, errors.New("replacePlainKeys buffer too small for hash len") @@ -463,7 +525,7 @@ func (branchData BranchData) ReplacePlainKeys(newData []byte, fn func(key []byte pos += int(l) } } - if fields.Has(fieldStateHash) { + if fields&fieldStateHash != 0 { l, n := binary.Uvarint(branchData[pos:]) if n == 0 { return nil, errors.New("replacePlainKeys buffer too small for acLeaf hash len") @@ -803,7 +865,7 @@ func DecodeBranchAndCollectStat(key, branch []byte, tv TrieVariant) *BranchStat if c == nil { continue } - enc := uint64(len(c.EncodeRoot())) + enc := uint64(len(c.Encode())) stat.MinCellSize = min(stat.MinCellSize, enc) stat.MaxCellSize = max(stat.MaxCellSize, enc) switch { diff --git a/erigon-lib/commitment/commitment_bench_test.go b/erigon-lib/commitment/commitment_bench_test.go index be9c330f990..6856e58ccdc 100644 --- a/erigon-lib/commitment/commitment_bench_test.go +++ b/erigon-lib/commitment/commitment_bench_test.go @@ -28,7 +28,7 @@ func BenchmarkBranchMerger_Merge(b *testing.B) { b.StopTimer() row, bm := generateCellRow(b, 16) - be := NewBranchEncoder(1024) + be := NewBranchEncoder(1024, b.TempDir()) enc, _, err := be.EncodeBranch(bm, bm, bm, func(i int, skip bool) (*cell, error) { return row[i], nil }) @@ -89,7 +89,7 @@ func BenchmarkBranchData_ReplacePlainKeys(b *testing.B) { return row[nibble], nil } - be := NewBranchEncoder(1024) + be := NewBranchEncoder(1024, b.TempDir()) enc, _, err := be.EncodeBranch(bm, bm, bm, cg) require.NoError(b, err) diff --git a/erigon-lib/commitment/commitment_test.go b/erigon-lib/commitment/commitment_test.go index 3a4b6235b05..abe9d0afee7 100644 --- a/erigon-lib/commitment/commitment_test.go +++ b/erigon-lib/commitment/commitment_test.go @@ -68,7 +68,7 @@ func TestBranchData_MergeHexBranches2(t *testing.T) { t.Parallel() row, bm := generateCellRow(t, 16) - be := NewBranchEncoder(1024) + be := NewBranchEncoder(1024, t.TempDir()) enc, _, err := be.EncodeBranch(bm, bm, bm, func(i int, skip bool) (*cell, error) { return row[i], nil }) @@ -139,14 +139,27 @@ func TestBranchData_MergeHexBranches3(t *testing.T) { //_, _ = tm, am } -// used as a tool for occasional decode of arbitrary BranchData func TestDecodeBranchWithLeafHashes(t *testing.T) { - t.Helper() + // enc := "00061614a8f8d73af90eee32dc9729ce8d5bb762f30d21a434a8f8d73af90eee32dc9729ce8d5bb762f30d21a49f49fdd48601f00df18ebc29b1264e27d09cf7cbd514fe8af173e534db038033203c7e2acaef5400189202e1a6a3b0b3d9add71fb52ad24ae35be6b6c85ca78bb51214ba7a3b7b095d3370c022ca655c790f0c0ead66f52025c143802ceb44bbe35e883927edb5933fc33416d4cc354dd88c7bcf1aad66a1" + // unfoldBranchDataFromString(t, enc) + + row, bm := generateCellRow(t, 16) + + for i := 0; i < len(row); i++ { + if row[i].accountAddrLen > 0 { + rand.Read(row[i].stateHash[:]) + row[i].stateHashLen = 32 + } + } + + be := NewBranchEncoder(1024, t.TempDir()) + enc, _, err := be.EncodeBranch(bm, bm, bm, func(i int, skip bool) (*cell, error) { + return row[i], nil + }) + require.NoError(t, err) + + fmt.Printf("%s\n", enc.String()) - enc := "ffffffff0820c897b4bd17e28055ef895b93b3c9d860f500d5f71cc4d88da43174f21855e7360820de5fad02cc43b2adc2d69d3bcf075c46f94f2e93c99e705e5e38e78b0deecfce08201913bafd6fa83683fc070e3bce0aca7276801fac48b671e85eba2c518afb5b090820e49b6b70a891c92d92018ebea62b10de7e64c91d19b3dd53ab566c2960d02b35082001043b43a93d87d986ff699fc6dcbfc24bb1e4bb5de01e1a873a31376ec2f362082094854c60e0b479a4fe32338da59c19f9fa678c98ac279310de541c406f78f7d208200a47e9bedcd30cb8445b11a701dc895ff6935a3b4d03818cd34bccd91da77cab0820da84ca186cd0ef061b53e2f8dde6f63ce926ef53dd238f6421f7e9a518d3b1ee0820b33cc57f00e8dbf97acf4f8d53f450cd305f126caa3aaec39f1a2c26ac53e87d0820240ec57bdbed7448939cf084698c33037c0f7333bdd6ef8a5b9cb16e2ad4e26f08205c3b6e60f80a65ca3feeec5e5c79aa7861cd37de95af2a9909c39ee5c276d5660820e76fd92681b17a61d625b429c54e4d94b90a0e377795e0f08fe006b66e918bb808204049fddbeb5275fae0e3e0a4d91fbe85f903326727bf99a023c67a8a136ec01d0820be379602300baaa9ff56feb0ad049ed5236dfbe8d1bb0d16209209b9910d58520820c4d66181775ac0addfaaf11e24accd8a12e9cd59d994d64476b4f8057e0081e208202b09e493d0eb47fbc7d2c9acf8c5c7b47a771ec20ea263e44513aa699a254350" - enc1 := "0520ffff0820c897b4bd17e28055ef895b93b3c9d860f500d5f71cc4d88da43174f21855e7360820de5fad02cc43b2adc2d69d3bcf075c46f94f2e93c99e705e5e38e78b0deecfce08201913bafd6fa83683fc070e3bce0aca7276801fac48b671e85eba2c518afb5b090820e49b6b70a891c92d92018ebea62b10de7e64c91d19b3dd53ab566c2960d02b35082001043b43a93d87d986ff699fc6dcbfc24bb1e4bb5de01e1a873a31376ec2f362082094854c60e0b479a4fe32338da59c19f9fa678c98ac279310de541c406f78f7d208200a47e9bedcd30cb8445b11a701dc895ff6935a3b4d03818cd34bccd91da77cab0820da84ca186cd0ef061b53e2f8dde6f63ce926ef53dd238f6421f7e9a518d3b1ee0820b33cc57f00e8dbf97acf4f8d53f450cd305f126caa3aaec39f1a2c26ac53e87d0820240ec57bdbed7448939cf084698c33037c0f7333bdd6ef8a5b9cb16e2ad4e26f08205c3b6e60f80a65ca3feeec5e5c79aa7861cd37de95af2a9909c39ee5c276d5660820e76fd92681b17a61d625b429c54e4d94b90a0e377795e0f08fe006b66e918bb808204049fddbeb5275fae0e3e0a4d91fbe85f903326727bf99a023c67a8a136ec01d0820be379602300baaa9ff56feb0ad049ed5236dfbe8d1bb0d16209209b9910d58520820c4d66181775ac0addfaaf11e24accd8a12e9cd59d994d64476b4f8057e0081e208202b09e493d0eb47fbc7d2c9acf8c5c7b47a771ec20ea263e44513aa699a254350" - unfoldBranchDataFromString(t, enc) - unfoldBranchDataFromString(t, enc1) } // helper to decode row of cells from string @@ -200,7 +213,7 @@ func TestBranchData_ReplacePlainKeys(t *testing.T) { return row[nibble], nil } - be := NewBranchEncoder(1024) + be := NewBranchEncoder(1024, t.TempDir()) enc, _, err := be.EncodeBranch(bm, bm, bm, cg) require.NoError(t, err) @@ -249,7 +262,7 @@ func TestBranchData_ReplacePlainKeys_WithEmpty(t *testing.T) { return row[nibble], nil } - be := NewBranchEncoder(1024) + be := NewBranchEncoder(1024, t.TempDir()) enc, _, err := be.EncodeBranch(bm, bm, bm, cg) require.NoError(t, err) diff --git a/erigon-lib/commitment/hex_patricia_hashed.go b/erigon-lib/commitment/hex_patricia_hashed.go index 2f032dead10..561d276df68 100644 --- a/erigon-lib/commitment/hex_patricia_hashed.go +++ b/erigon-lib/commitment/hex_patricia_hashed.go @@ -26,23 +26,27 @@ import ( "hash" "io" "math/bits" + "path/filepath" "runtime" "sort" "strings" "sync/atomic" "time" - "github.com/erigontech/erigon-lib/common" - "github.com/erigontech/erigon-lib/common/dbg" - "github.com/erigontech/erigon-lib/common/hexutility" - "github.com/erigontech/erigon-lib/common/length" - ecrypto "github.com/erigontech/erigon-lib/crypto" + "github.com/erigontech/erigon-lib/etl" "github.com/erigontech/erigon-lib/log/v3" - "github.com/erigontech/erigon-lib/rlp" "github.com/erigontech/erigon-lib/trie" "github.com/erigontech/erigon-lib/types/accounts" witnesstypes "github.com/erigontech/erigon-lib/types/witness" + "github.com/erigontech/erigon-lib/common/dbg" + + "github.com/erigontech/erigon-lib/common" + libcommon "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon-lib/common/hexutility" + "github.com/erigontech/erigon-lib/common/length" + ecrypto "github.com/erigontech/erigon-lib/crypto" + "github.com/erigontech/erigon-lib/rlp" "golang.org/x/crypto/sha3" ) @@ -76,7 +80,7 @@ type HexPatriciaHashed struct { keccak keccakState keccak2 keccakState rootChecked bool // Set to false if it is not known whether the root is empty, set to true if it is checked - rootTouched bool // todo replace with bitmap + rootTouched bool rootPresent bool trace bool ctx PatriciaContext @@ -91,7 +95,7 @@ type HexPatriciaHashed struct { accValBuf rlp.RlpEncodedBytes } -func NewHexPatriciaHashed(accountKeyLen int, ctx PatriciaContext) *HexPatriciaHashed { +func NewHexPatriciaHashed(accountKeyLen int, ctx PatriciaContext, tmpdir string) *HexPatriciaHashed { hph := &HexPatriciaHashed{ ctx: ctx, keccak: sha3.NewLegacyKeccak256().(keccakState), @@ -101,7 +105,7 @@ func NewHexPatriciaHashed(accountKeyLen int, ctx PatriciaContext) *HexPatriciaHa hadToLoadL: make(map[uint64]skipStat), accValBuf: make(rlp.RlpEncodedBytes, 128), } - hph.branchEncoder = NewBranchEncoder(1024) + hph.branchEncoder = NewBranchEncoder(1024, filepath.Join(tmpdir, "branch-encoder")) return hph } @@ -382,7 +386,7 @@ func (cell *cell) deriveHashedKeys(depth int, keccak keccakState, accountKeyLen func (cell *cell) fillFromFields(data []byte, pos int, fieldBits cellFields) (int, error) { fields := []struct { - field cellFields + flag cellFields lenField *int dataField []byte extraFunc func(int) @@ -399,9 +403,8 @@ func (cell *cell) fillFromFields(data []byte, pos int, fieldBits cellFields) (in {fieldStateHash, &cell.stateHashLen, cell.stateHash[:], nil}, } - cell.reset() for _, f := range fields { - if fieldBits.Has(f.field) { + if fieldBits&f.flag != 0 { l, n, err := readUvarint(data[pos:]) if err != nil { return 0, err @@ -409,7 +412,7 @@ func (cell *cell) fillFromFields(data []byte, pos int, fieldBits cellFields) (in pos += n if len(data) < pos+int(l) { - return 0, fmt.Errorf("buffer too small for %v", f.field) + return 0, fmt.Errorf("buffer too small for %v", f.flag) } *f.lenField = int(l) @@ -420,8 +423,17 @@ func (cell *cell) fillFromFields(data []byte, pos int, fieldBits cellFields) (in if f.extraFunc != nil { f.extraFunc(int(l)) } + } else { + *f.lenField = 0 + if f.flag == fieldExtension { + cell.extLen = 0 + } } } + + if fieldBits&fieldAccountAddr != 0 { + copy(cell.CodeHash[:], EmptyCodeHash) + } return pos, nil } @@ -668,7 +680,6 @@ func (hph *HexPatriciaHashed) extensionHash(key []byte, hash []byte) ([length.Ha return hashBuf, nil } -// nolint - yet we do not compute cell hash length ahead of time func (hph *HexPatriciaHashed) computeCellHashLen(cell *cell, depth int) int { if cell.storageAddrLen > 0 && depth >= 64 { if cell.stateHashLen > 0 { @@ -869,12 +880,6 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) var err error var storageRootHash [length.Hash]byte var storageRootHashIsSet bool - - c := hph.hadToLoadL[hph.depthsToTxNum[depth]] - defer func() { - hph.hadToLoadL[hph.depthsToTxNum[depth]] = c - }() - if cell.storageAddrLen > 0 { var hashedKeyOffset int if depth >= 64 { @@ -898,22 +903,19 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) } mxTrieStateSkipRate.Inc() skippedLoad.Add(1) - c.storSkipped++ if !singleton { return append(append(buf[:0], byte(160)), cell.stateHash[:cell.stateHashLen]...), nil } - storageRootHashIsSet = true storageRootHash = *(*[length.Hash]byte)(cell.stateHash[:cell.stateHashLen]) - } else { if !cell.loaded.storage() { - // return nil, fmt.Errorf("storage %x was not loaded as expected: cell %v", cell.storageAddr[:cell.storageAddrLen], cell.String()) - update, err := hph.ctx.Storage(cell.storageAddr[:cell.storageAddrLen]) - if err != nil { - return nil, err - } - cell.setFromUpdate(update) + return nil, fmt.Errorf("storage %x was not loaded as expected: cell %v", cell.storageAddr[:cell.storageAddrLen], cell.String()) + // update, err := hph.ctx.Storage(cell.storageAddr[:cell.storageAddrLen]) + // if err != nil { + // return nil, err + // } + // cell.setFromUpdate(update) } leafHash, err := hph.leafHashWithKeyVal(buf, cell.hashedExtension[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], singleton) @@ -925,16 +927,14 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) singleton, leafHash, cell.hashedExtension[:64-hashedKeyOffset+1], cell.Storage[:cell.StorageLen], cell.String()) } if !singleton { - cell.stateHashLen = len(leafHash[1:]) copy(cell.stateHash[:], leafHash[1:]) - + cell.stateHashLen = len(leafHash) - 1 return leafHash, nil } storageRootHash = *(*[length.Hash]byte)(leafHash[1:]) storageRootHashIsSet = true cell.stateHashLen = 0 hadToReset.Add(1) - c.storReset++ } } if cell.accountAddrLen > 0 { @@ -958,7 +958,6 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) } cell.stateHashLen = 0 hadToReset.Add(1) - c.accReset++ } else if cell.hashLen > 0 { storageRootHash = cell.hash } else { @@ -974,10 +973,6 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) if hph.trace { fmt.Printf("REUSED stateHash %x apk %x\n", cell.stateHash[:cell.stateHashLen], cell.accountAddr[:cell.accountAddrLen]) } - - c.accSkipped++ - hph.hadToLoadL[hph.depthsToTxNum[depth]] = c - return append(append(buf[:0], byte(160)), cell.stateHash[:cell.stateHashLen]...), nil } // storage root update or extension update could invalidate older stateHash, so we need to reload state @@ -986,8 +981,6 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) return nil, err } cell.setFromUpdate(update) - c.accLoaded++ - //fmt.Printf("loaded account during cellHash %x %+v\n", cell.accountAddr[:cell.accountAddrLen], cell.FullString()) } valLen := cell.accountForHashing(hph.accValBuf, storageRootHash) @@ -1003,25 +996,25 @@ func (hph *HexPatriciaHashed) computeCellHash(cell *cell, depth int, buf []byte) return buf, nil } - buf = append(buf, 0x80+32) // todo could move to beginning - if cell.extLen > 0 { // Extension - if cell.hashLen == 0 { + buf = append(buf, 0x80+32) + if cell.extLen > 0 { // Extension + if cell.hashLen > 0 { + if hph.trace { + fmt.Printf("extensionHash for [%x]=>[%x]\n", cell.extension[:cell.extLen], cell.hash[:cell.hashLen]) + } + if storageRootHash, err = hph.extensionHash(cell.extension[:cell.extLen], cell.hash[:cell.hashLen]); err != nil { + return nil, err + } + buf = append(buf, storageRootHash[:]...) + } else { return nil, errors.New("computeCellHash extension without hash") } - if hph.trace { - fmt.Printf("extensionHash for [%x]=>[%x]\n", cell.extension[:cell.extLen], cell.hash[:cell.hashLen]) - } - if storageRootHash, err = hph.extensionHash(cell.extension[:cell.extLen], cell.hash[:cell.hashLen]); err != nil { - return nil, err - } - buf = append(buf, storageRootHash[:]...) } else if cell.hashLen > 0 { buf = append(buf, cell.hash[:cell.hashLen]...) } else if storageRootHashIsSet { + buf = append(buf, storageRootHash[:]...) copy(cell.hash[:], storageRootHash[:]) cell.hashLen = len(storageRootHash) - buf = append(buf, cell.hash[:cell.hashLen]...) - // buf = append(buf, storageRootHash[:]...) } else { buf = append(buf, EmptyRootHash...) } @@ -1136,7 +1129,7 @@ func (hph *HexPatriciaHashed) PrintGrid() { } // this function is only related to the witness -func (hph *HexPatriciaHashed) createAccountNode(c *cell, row int, hashedKey []byte, codeReads map[common.Hash]witnesstypes.CodeWithHash) (*trie.AccountNode, error) { +func (hph *HexPatriciaHashed) createAccountNode(c *cell, row int, hashedKey []byte, codeReads map[libcommon.Hash]witnesstypes.CodeWithHash) (*trie.AccountNode, error) { _, storageIsSet, storageRootHash, err := hph.computeCellHashWithStorage(c, hph.depths[row], nil) if err != nil { return nil, err @@ -1192,7 +1185,7 @@ func (hph *HexPatriciaHashed) nCellsInRow(row int) int { //nolint:unused } // Traverse the grid following `hashedKey` and produce the witness `trie.Trie` for that key -func (hph *HexPatriciaHashed) ToTrie(hashedKey []byte, codeReads map[common.Hash]witnesstypes.CodeWithHash) (*trie.Trie, error) { +func (hph *HexPatriciaHashed) ToTrie(hashedKey []byte, codeReads map[libcommon.Hash]witnesstypes.CodeWithHash) (*trie.Trie, error) { rootNode := &trie.FullNode{} var currentNode trie.Node = rootNode keyPos := 0 // current position in hashedKey (usually same as row, but could be different due to extension nodes) @@ -1470,6 +1463,75 @@ type skipStat struct { accLoaded, accSkipped, accReset, storReset, storLoaded, storSkipped uint64 } +const DepthWithoutNodeHashes = 35 //nolint + +func (hph *HexPatriciaHashed) createCellGetter(b []byte, updateKey []byte, row, depth int) func(nibble int, skip bool) (*cell, error) { + hashBefore := make([]byte, 32) // buffer re-used between calls + return func(nibble int, skip bool) (*cell, error) { + if skip { + if _, err := hph.keccak2.Write(b); err != nil { + return nil, fmt.Errorf("failed to write empty nibble to hash: %w", err) + } + if hph.trace { + fmt.Printf(" %x: empty(%d, %x, depth=%d)\n", nibble, row, nibble, depth) + } + return nil, nil + } + cell := &hph.grid[row][nibble] + if cell.accountAddrLen > 0 && cell.stateHashLen == 0 && !cell.loaded.account() && !cell.Deleted() { + //panic("account not loaded" + fmt.Sprintf("%x", cell.accountAddr[:cell.accountAddrLen])) + log.Warn("account not loaded", "pref", updateKey, "c", fmt.Sprintf("(%d, %x, depth=%d", row, nibble, depth), "cell", cell.String()) + } + if cell.storageAddrLen > 0 && cell.stateHashLen == 0 && !cell.loaded.storage() && !cell.Deleted() { + //panic("storage not loaded" + fmt.Sprintf("%x", cell.storageAddr[:cell.storageAddrLen])) + log.Warn("storage not loaded", "pref", updateKey, "c", fmt.Sprintf("(%d, %x, depth=%d", row, nibble, depth), "cell", cell.String()) + } + + loadedBefore := cell.loaded + copy(hashBefore, cell.stateHash[:cell.stateHashLen]) + hashBefore = hashBefore[:cell.stateHashLen] + + cellHash, err := hph.computeCellHash(cell, depth, hph.hashAuxBuffer[:0]) + if err != nil { + return nil, err + } + if hph.trace { + fmt.Printf(" %x: computeCellHash(%d, %x, depth=%d)=[%x]\n", nibble, row, nibble, depth, cellHash) + } + + if hashBefore != nil && (cell.accountAddrLen > 0 || cell.storageAddrLen > 0) { + counters := hph.hadToLoadL[hph.depthsToTxNum[depth]] + if !bytes.Equal(hashBefore, cell.stateHash[:cell.stateHashLen]) { + if cell.accountAddrLen > 0 { + counters.accReset++ + counters.accLoaded++ + } + if cell.storageAddrLen > 0 { + counters.storReset++ + counters.storLoaded++ + } + } else { + if cell.accountAddrLen > 0 && (!loadedBefore.account() && !cell.loaded.account()) { + counters.accSkipped++ + } + if cell.storageAddrLen > 0 && (!loadedBefore.storage() && !cell.loaded.storage()) { + counters.storSkipped++ + } + } + hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters + } + //if len(updateKey) > DepthWithoutNodeHashes { + // cell.hashLen = 0 // do not write hashes for storages in the branch node, should reset ext as well which can break unfolding.. - + // cell.extLen = 0 + //} + if _, err := hph.keccak2.Write(cellHash); err != nil { + return nil, err + } + + return cell, nil + } +} + const terminatorHexByte = 16 // max nibble value +1. Defines end of nibble line in the trie or splits address and storage space in trie. // updateKind is a type of update that is being applied to the trie structure. @@ -1516,9 +1578,9 @@ func (hph *HexPatriciaHashed) fold() (err error) { if hph.activeRows == 0 { return errors.New("cannot fold - no active rows") } - // if hph.trace { - // fmt.Printf("fold [%x] activeRows: %d touchMap: %016b afterMap: %016b\n", hph.currentKey[:hph.currentKeyLen], hph.activeRows, hph.touchMap[hph.activeRows-1], hph.afterMap[hph.activeRows-1]) - // } + if hph.trace { + fmt.Printf("fold [%x] activeRows: %d touchMap: %016b afterMap: %016b\n", hph.currentKey[:hph.currentKeyLen], hph.activeRows, hph.touchMap[hph.activeRows-1], hph.afterMap[hph.activeRows-1]) + } // Move information to the row above var upCell *cell var nibble, upDepth int @@ -1543,12 +1605,10 @@ func (hph *HexPatriciaHashed) fold() (err error) { if hph.trace { fmt.Printf("fold: (row=%d, {%s}, depth=%d) prefix [%x] touchMap: %016b afterMap: %016b \n", - row, updatedNibs(hph.touchMap[row]), depth, hph.currentKey[:hph.currentKeyLen], hph.touchMap[row], hph.afterMap[row]) + row, updatedNibs(hph.touchMap[row]&hph.afterMap[row]), depth, hph.currentKey[:hph.currentKeyLen], hph.touchMap[row], hph.afterMap[row]) } - var branchUpdate []byte updateKind, nibblesLeftAfterUpdate := afterMapUpdateKind(hph.afterMap[row]) - switch updateKind { case updateKindDelete: // Everything deleted if hph.touchMap[row] != 0 { @@ -1569,15 +1629,20 @@ func (hph *HexPatriciaHashed) fold() (err error) { upCell.reset() if hph.branchBefore[row] { - branchUpdate, err = hph.branchEncoder.EncodeDelete(hph.touchMap[row]) + _, err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, 0, hph.touchMap[row], 0, RetrieveCellNoop) if err != nil { - return fmt.Errorf("failed to encode deletion of pre-existed branch: %w", err) + return fmt.Errorf("failed to encode leaf node update: %w", err) } } - hph.currentKeyLen = max(upDepth-1, 0) hph.activeRows-- + if upDepth > 0 { + hph.currentKeyLen = upDepth - 1 + } else { + hph.currentKeyLen = 0 + } case updateKindPropagate: // Leaf or extension node - if hph.touchMap[row] != 0 { // any modifications + if hph.touchMap[row] != 0 { + // any modifications if row == 0 { hph.rootTouched = true } else { @@ -1594,20 +1659,16 @@ func (hph *HexPatriciaHashed) fold() (err error) { if hph.branchBefore[row] { // encode Delete if prefix existed before //fmt.Printf("delete existed row %d prefix %x\n", row, updateKey) - del, err := hph.branchEncoder.EncodeDelete(hph.touchMap[row]) + _, err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, 0, hph.touchMap[row], 0, RetrieveCellNoop) if err != nil { return fmt.Errorf("failed to encode leaf node update: %w", err) } - // if err = hph.ctx.PutBranch(common.Copy(updateKey), common.Copy(del), nil, 0); err != nil { - if err = hph.ctx.PutBranch(updateKey, del, nil, 0); err != nil { - return fmt.Errorf("failed to collect leaf node update: %w", err) - } } + hph.activeRows-- + hph.currentKeyLen = max(upDepth-1, 0) if hph.trace { - fmt.Printf("formed leaf (%d, %x, depth=%d) [%x] %s\n", row, nibble, depth, updateKey, cell.FullString()) + fmt.Printf("formed leaf (%d %x, depth=%d) [%x] %s\n", row, nibble, depth, updateKey, cell.FullString()) } - hph.currentKeyLen = max(upDepth-1, 0) - hph.activeRows-- case updateKindBranch: if hph.touchMap[row] != 0 { // any modifications if row == 0 { @@ -1618,22 +1679,87 @@ func (hph *HexPatriciaHashed) fold() (err error) { hph.touchMap[row-1] |= uint16(1) << nibble } } - if !hph.branchBefore[row] { // There was no branch node before, so we need to touch even the singular child that existed + bitmap := hph.touchMap[row] & hph.afterMap[row] + if !hph.branchBefore[row] { + // There was no branch node before, so we need to touch even the singular child that existed hph.touchMap[row] |= hph.afterMap[row] + bitmap |= hph.afterMap[row] } - bitmap := hph.touchMap[row] & hph.afterMap[row] - // if !hph.branchBefore[row] { - // // There was no branch node before, so we need to touch even the singular child that existed - // hph.touchMap[row] |= hph.afterMap[row] - // bitmap |= hph.afterMap[row] - // } - - var branchRoot []byte - branchRoot, branchUpdate, err = hph.foldRow(bitmap, row, depth, nibblesLeftAfterUpdate) + + // Calculate total length of all hashes + totalBranchLen := 17 - nibblesLeftAfterUpdate // For every empty cell, one byte + for bitset, j := hph.afterMap[row], 0; bitset != 0; j++ { + bit := bitset & -bitset + nibble := bits.TrailingZeros16(bit) + cell := &hph.grid[row][nibble] + + /* memoization of state hashes*/ + counters := hph.hadToLoadL[hph.depthsToTxNum[depth]] + if cell.stateHashLen > 0 && (hph.touchMap[row]&hph.afterMap[row]&uint16(1< 0 || cell.stateHashLen != length.Hash) { + // drop state hash if updated or hashLen < 32 (corner case, may even not encode such leaf hashes) + if hph.trace { + fmt.Printf("DROP hash for (%d, %x, depth=%d) %s\n", row, nibble, depth, cell.FullString()) + } + cell.stateHashLen = 0 + hadToReset.Add(1) + if cell.accountAddrLen > 0 { + counters.accReset++ + } + if cell.storageAddrLen > 0 { + counters.storReset++ + } + } + + if cell.stateHashLen == 0 { // load state if needed + if !cell.loaded.account() && cell.accountAddrLen > 0 { + upd, err := hph.ctx.Account(cell.accountAddr[:cell.accountAddrLen]) + if err != nil { + return fmt.Errorf("failed to get account: %w", err) + } + cell.setFromUpdate(upd) + // if update is empty, loaded flag was not updated so do it manually + cell.loaded = cell.loaded.addFlag(cellLoadAccount) + counters.accLoaded++ + } + if !cell.loaded.storage() && cell.storageAddrLen > 0 { + upd, err := hph.ctx.Storage(cell.storageAddr[:cell.storageAddrLen]) + if err != nil { + return fmt.Errorf("failed to get storage: %w", err) + } + cell.setFromUpdate(upd) + // if update is empty, loaded flag was not updated so do it manually + cell.loaded = cell.loaded.addFlag(cellLoadStorage) + counters.storLoaded++ + } + // computeCellHash can reset hash as well so have to check if node has been skipped right after computeCellHash. + } + hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters + /* end of memoization */ + + totalBranchLen += hph.computeCellHashLen(cell, depth) + bitset ^= bit + } + + hph.keccak2.Reset() + pt := rlp.GenerateStructLen(hph.hashAuxBuffer[:], totalBranchLen) + if _, err := hph.keccak2.Write(hph.hashAuxBuffer[:pt]); err != nil { + return err + } + + b := [...]byte{0x80} + cellGetter := hph.createCellGetter(b[:], updateKey, row, depth) + lastNibble, err := hph.branchEncoder.CollectUpdate(hph.ctx, updateKey, bitmap, hph.touchMap[row], hph.afterMap[row], cellGetter) if err != nil { return fmt.Errorf("failed to encode branch update: %w", err) } - + for i := lastNibble; i < 17; i++ { + if _, err := hph.keccak2.Write(b[:]); err != nil { + return err + } + if hph.trace { + fmt.Printf(" %x: empty(%d, %x, depth=%d)\n", i, row, i, depth) + } + } upCell.extLen = depth - upDepth - 1 upCell.hashedExtLen = upCell.extLen if upCell.extLen > 0 { @@ -1644,149 +1770,21 @@ func (hph *HexPatriciaHashed) fold() (err error) { upCell.accountAddrLen = 0 } upCell.storageAddrLen = 0 - upCell.hashLen = length.Hash - copy(upCell.hash[:], branchRoot) - - hph.currentKeyLen = max(upDepth-1, 0) - hph.activeRows-- - } - - // fetch previous branch data - prev, prevStep, err := hph.ctx.Branch(updateKey) - if err != nil { - return err - } - // skip if no changes - if len(prev) > 0 && bytes.Equal(prev, branchUpdate) { - return nil - } - - // bot kv has to be copied :( - if err = hph.ctx.PutBranch(common.Copy(updateKey), common.Copy(branchUpdate), prev, prevStep); err != nil { - return fmt.Errorf("failed to collect trie update: %w", err) - } - mxTrieBranchesUpdated.Inc() - return nil -} - -// folds branch row and providing branch hash along with latest encoded version of this branch at given row. -func (hph *HexPatriciaHashed) foldRow(bitmap uint16, row, depth, nibblesLeftAfterUpdate int) (branchRoot, branchUpdate []byte, err error) { - touchMap := hph.touchMap[row] - afterMap := hph.afterMap[row] // can get nibblesLeftAfterUpdate from aftermap - - toHash := make([]byte, 0, length.Hash*bits.OnesCount16(afterMap)) - fill := func(lastNibble, until int) { - for ; lastNibble < until; lastNibble++ { - toHash = append(toHash, 0x80) - if hph.trace { - fmt.Printf(" %x: empty(%d, %x, depth=%d)\n", lastNibble, row, lastNibble, depth) - } - } - } - - if hph.trace { - fmt.Printf("fold: (row=%d, {%s}, depth=%d) prefix [%x] touchMap: %016b afterMap: %016b \n", - row, updatedNibs(bitmap), depth, hph.currentKey[:hph.currentKeyLen], hph.touchMap[row], hph.afterMap[row]) - } - - if err := hph.branchEncoder.encodeMaps(touchMap, afterMap); err != nil { - return nil, nil, err - } - - var lastNibble int - totalBranchLen := 17 - nibblesLeftAfterUpdate // For every empty cell, one byte - - for bitset, j := afterMap, 0; bitset != 0; j++ { - bit := bitset & -bitset - nibble := bits.TrailingZeros16(bit) - cell := &hph.grid[row][nibble] - - fill(lastNibble, nibble) - - /* memoization of state hashes*/ - counters := hph.hadToLoadL[hph.depthsToTxNum[depth]] - if cell.stateHashLen > 0 && (touchMap&afterMap&uint16(1< 0 || cell.stateHashLen != length.Hash) { - // // drop state hash if updated or hashLen < 32 (corner case, may even not encode such leaf hashes) - // if hph.trace { - // fmt.Printf("DROP hash for (%d, %x, depth=%d) %s\n", row, nibble, depth, cell.FullString()) - // } - cell.stateHashLen = 0 - hadToReset.Add(1) - if cell.accountAddrLen > 0 { - counters.accReset++ - } - if cell.storageAddrLen > 0 { - counters.storReset++ - } - } - - if cell.stateHashLen == 0 { // load state if needed - if !cell.loaded.account() && cell.accountAddrLen > 0 { - upd, err := hph.ctx.Account(cell.accountAddr[:cell.accountAddrLen]) - if err != nil { - return nil, nil, fmt.Errorf("failed to get account: %w", err) - } - cell.setFromUpdate(upd) - // if update is empty, loaded flag was not updated so do it manually - cell.loaded = cell.loaded.addFlag(cellLoadAccount) - counters.accLoaded++ - } - if !cell.loaded.storage() && cell.storageAddrLen > 0 { - upd, err := hph.ctx.Storage(cell.storageAddr[:cell.storageAddrLen]) - if err != nil { - return nil, nil, fmt.Errorf("failed to get storage: %w", err) - } - cell.setFromUpdate(upd) - // if update is empty, loaded flag was not updated so do it manually - cell.loaded = cell.loaded.addFlag(cellLoadStorage) - counters.storLoaded++ - } - // computeCellHash can reset hash as well so have to check if node has been skipped right after computeCellHash. - } - hph.hadToLoadL[hph.depthsToTxNum[depth]] = counters - /* end of memoization stats */ - - cellHash, err := hph.computeCellHash(cell, depth, hph.hashAuxBuffer[:0]) - if err != nil { - return nil, nil, err - } - // totalBranchLen += hph.computeCellHashLen(cell, depth) - totalBranchLen += len(cellHash) - - toHash = append(toHash, cellHash...) - if afterMap&bit != 0 { // serialize cell data into branch - if err = hph.branchEncoder.encodeCell(&hph.grid[row][nibble]); err != nil { - // if err = hph.grid[row][nibble].EncodeInto(hph.branchEncoder); err != nil { - return nil, nil, err - } + upCell.hashLen = 32 + if _, err := hph.keccak2.Read(upCell.hash[:]); err != nil { + return err } if hph.trace { - fmt.Printf(" %x: computeCellHash(%d, %x, depth=%d)=[%x]\n", nibble, row, nibble, depth, cellHash) + fmt.Printf("} [%x]\n", upCell.hash[:]) + } + hph.activeRows-- + if upDepth > 0 { + hph.currentKeyLen = upDepth - 1 + } else { + hph.currentKeyLen = 0 } - lastNibble = nibble + 1 - bitset ^= bit - } - fill(lastNibble, 17) // finish rest non-existing nibbles with 0x80 - - hph.keccak2.Reset() // begin branch hash computation - pt := rlp.GenerateStructLen(hph.branchEncoder.bitmapBuf[:], totalBranchLen) - if _, err := hph.keccak2.Write(hph.branchEncoder.bitmapBuf[:pt]); err != nil { - return nil, nil, err - } - - // fmt.Printf("toHash [%x]%x\n", hph.branchEncoder.bitmapBuf[:pt], toHash) - if _, err := hph.keccak2.Write(toHash); err != nil { - return nil, nil, err - } - branchRoot = hph.keccak2.Sum(nil) - if hph.trace { - fmt.Printf("} [%x]\n", branchRoot) } - // branchRoot = make([]byte, length.Hash) - // if _, err := hph.keccak2.Read(branchRoot[:0]); err != nil { - // return nil, err - // } - return branchRoot, hph.branchEncoder.EncodedBranch(), nil + return nil } func (hph *HexPatriciaHashed) deleteCell(hashedKey []byte) { @@ -1890,7 +1888,7 @@ func (hph *HexPatriciaHashed) RootHash() ([]byte, error) { // but currently need to be defined like that for the fold/unfold algorithm) into the grid and traversing the grid to convert it into `trie.Trie`. // All the individual tries are combined to create the final witness trie. // Because the grid is lacking information about the code in smart contract accounts which is also part of the witness, we need to provide that as an input parameter to this function (`codeReads`) -func (hph *HexPatriciaHashed) GenerateWitness(ctx context.Context, updates *Updates, codeReads map[common.Hash]witnesstypes.CodeWithHash, expectedRootHash []byte, logPrefix string) (witnessTrie *trie.Trie, rootHash []byte, err error) { +func (hph *HexPatriciaHashed) GenerateWitness(ctx context.Context, updates *Updates, codeReads map[libcommon.Hash]witnesstypes.CodeWithHash, expectedRootHash []byte, logPrefix string) (witnessTrie *trie.Trie, rootHash []byte, err error) { var ( m runtime.MemStats ki uint64 @@ -2085,10 +2083,10 @@ func (hph *HexPatriciaHashed) Process(ctx context.Context, updates *Updates, log if hph.trace { fmt.Printf("root hash %x updates %d\n", rootHash, updatesCount) } - // err = hph.branchEncoder.Load(hph.ctx, etl.TransformArgs{Quit: ctx.Done()}) - // if err != nil { - // return nil, fmt.Errorf("branch update failed: %w", err) - // } + err = hph.branchEncoder.Load(hph.ctx, etl.TransformArgs{Quit: ctx.Done()}) + if err != nil { + return nil, fmt.Errorf("branch update failed: %w", err) + } if dbg.KVReadLevelledMetrics { log.Debug("commitment finished, counters updated (no reset)", //"hadToLoad", common.PrettyCounter(hadToLoad.Load()), "skippedLoad", common.PrettyCounter(skippedLoad.Load()), @@ -2275,13 +2273,12 @@ func (s *state) Decode(buf []byte) error { return nil } -// Encodes root cell only. Does not match with encoding inside branches -func (cell *cell) EncodeRoot() []byte { +func (cell *cell) Encode() []byte { var pos = 1 size := pos + 5 + cell.hashLen + cell.accountAddrLen + cell.storageAddrLen + cell.hashedExtLen + cell.extLen // max size buf := make([]byte, size) - var flags cellFlag + var flags uint8 if cell.hashLen != 0 { flags |= cellFlagHash buf[pos] = byte(cell.hashLen) @@ -2320,16 +2317,12 @@ func (cell *cell) EncodeRoot() []byte { if cell.Deleted() { flags |= cellFlagDelete } - buf[0] = byte(flags) + buf[0] = flags return buf } -type cellFlag uint8 - -func (f cellFlag) Has(flag cellFlag) bool { return f&flag != 0 } - const ( - cellFlagHash = cellFlag(1 << iota) + cellFlagHash = uint8(1 << iota) cellFlagAccount cellFlagStorage cellFlagDownHash @@ -2337,48 +2330,47 @@ const ( cellFlagDelete ) -// Use for root encoding only, has twisted encoding for hash and extension with branchEncoding/encodeInto -func (cell *cell) DecodeRoot(buf []byte) error { +func (cell *cell) Decode(buf []byte) error { if len(buf) < 1 { return errors.New("invalid buffer size to contain cell (at least 1 byte expected)") } cell.reset() var pos int - flags := cellFlag(buf[pos]) + flags := buf[pos] pos++ - if flags.Has(cellFlagHash) { + if flags&cellFlagHash != 0 { cell.hashLen = int(buf[pos]) pos++ copy(cell.hash[:], buf[pos:pos+cell.hashLen]) pos += cell.hashLen } - if flags.Has(cellFlagAccount) { + if flags&cellFlagAccount != 0 { cell.accountAddrLen = int(buf[pos]) pos++ copy(cell.accountAddr[:], buf[pos:pos+cell.accountAddrLen]) pos += cell.accountAddrLen } - if flags.Has(cellFlagStorage) { + if flags&cellFlagStorage != 0 { cell.storageAddrLen = int(buf[pos]) pos++ copy(cell.storageAddr[:], buf[pos:pos+cell.storageAddrLen]) pos += cell.storageAddrLen } - if flags.Has(cellFlagDownHash) { + if flags&cellFlagDownHash != 0 { cell.hashedExtLen = int(buf[pos]) pos++ copy(cell.hashedExtension[:], buf[pos:pos+cell.hashedExtLen]) pos += cell.hashedExtLen } - if flags.Has(cellFlagExtension) { + if flags&cellFlagExtension != 0 { cell.extLen = int(buf[pos]) pos++ copy(cell.extension[:], buf[pos:pos+cell.extLen]) pos += cell.extLen //nolint } - if flags.Has(cellFlagDelete) { + if flags&cellFlagDelete != 0 { log.Warn("deleted cell should not be encoded", "cell", cell.String()) cell.Update.Flags = DeleteUpdate } @@ -2396,7 +2388,7 @@ func (hph *HexPatriciaHashed) EncodeCurrentState(buf []byte) ([]byte, error) { panic("currentKeyLen > 0") } - s.Root = hph.root.EncodeRoot() + s.Root = hph.root.Encode() copy(s.Depths[:], hph.depths[:]) copy(s.BranchBefore[:], hph.branchBefore[:]) copy(s.TouchMap[:], hph.touchMap[:]) @@ -2405,7 +2397,7 @@ func (hph *HexPatriciaHashed) EncodeCurrentState(buf []byte) ([]byte, error) { return s.Encode(buf) } -// buf expected to be encoded hph state. DecodeRoot state and set up hph to that state. +// buf expected to be encoded hph state. Decode state and set up hph to that state. func (hph *HexPatriciaHashed) SetState(buf []byte) error { hph.Reset() @@ -2434,7 +2426,7 @@ func (hph *HexPatriciaHashed) SetState(buf []byte) error { return err } - if err := hph.root.DecodeRoot(s.Root); err != nil { + if err := hph.root.Decode(s.Root); err != nil { return err } hph.rootChecked = s.RootChecked @@ -2485,7 +2477,7 @@ func HexTrieExtractStateRoot(enc []byte) ([]byte, error) { return nil, err } root := new(cell) - if err := root.DecodeRoot(s.Root); err != nil { + if err := root.Decode(s.Root); err != nil { return nil, err } return root.hash[:], nil @@ -2533,7 +2525,7 @@ func HexTrieStateToString(enc []byte) (string, error) { fmt.Fprintf(sb, " rootNode: %x [touched=%t, present=%t, checked=%t]\n", s.Root, s.RootTouched, s.RootPresent, s.RootChecked) root := new(cell) - if err := root.DecodeRoot(s.Root); err != nil { + if err := root.Decode(s.Root); err != nil { return "", err } diff --git a/erigon-lib/commitment/hex_patricia_hashed_bench_test.go b/erigon-lib/commitment/hex_patricia_hashed_bench_test.go index 99af1584d3a..2ea52303874 100644 --- a/erigon-lib/commitment/hex_patricia_hashed_bench_test.go +++ b/erigon-lib/commitment/hex_patricia_hashed_bench_test.go @@ -48,7 +48,7 @@ func Benchmark_HexPatriciaHashed_Process(b *testing.B) { err := ms.applyPlainUpdates(pk, updates) require.NoError(b, err) - hph := NewHexPatriciaHashed(length.Addr, ms) + hph := NewHexPatriciaHashed(length.Addr, ms, ms.TempDir()) upds := WrapKeyUpdates(b, ModeDirect, KeyToHexNibbleHash, nil, nil) defer upds.Close() diff --git a/erigon-lib/commitment/hex_patricia_hashed_fuzz_test.go b/erigon-lib/commitment/hex_patricia_hashed_fuzz_test.go index cab6611eea5..aed644d7aeb 100644 --- a/erigon-lib/commitment/hex_patricia_hashed_fuzz_test.go +++ b/erigon-lib/commitment/hex_patricia_hashed_fuzz_test.go @@ -53,8 +53,8 @@ func Fuzz_ProcessUpdate(f *testing.F) { ms := NewMockState(t) ms2 := NewMockState(t) - hph := NewHexPatriciaHashed(length.Addr, ms) - hphAnother := NewHexPatriciaHashed(length.Addr, ms2) + hph := NewHexPatriciaHashed(length.Addr, ms, ms.TempDir()) + hphAnother := NewHexPatriciaHashed(length.Addr, ms2, ms2.TempDir()) hph.SetTrace(false) hphAnother.SetTrace(false) @@ -138,8 +138,8 @@ func Fuzz_ProcessUpdates_ArbitraryUpdateCount2(f *testing.F) { ms := NewMockState(t) ms2 := NewMockState(t) - hph := NewHexPatriciaHashed(length.Addr, ms) - hphAnother := NewHexPatriciaHashed(length.Addr, ms2) + hph := NewHexPatriciaHashed(length.Addr, ms, ms.TempDir()) + hphAnother := NewHexPatriciaHashed(length.Addr, ms2, ms2.TempDir()) trace := false hph.SetTrace(trace) @@ -206,7 +206,7 @@ func Fuzz_HexPatriciaHashed_ReviewKeys(f *testing.F) { t.Logf("keys count: %d", kc) ms := NewMockState(t) - hph := NewHexPatriciaHashed(length.Addr, ms) + hph := NewHexPatriciaHashed(length.Addr, ms, ms.TempDir()) plainKeys, updates := builder.Build() if err := ms.applyPlainUpdates(plainKeys, updates); err != nil { diff --git a/erigon-lib/commitment/hex_patricia_hashed_test.go b/erigon-lib/commitment/hex_patricia_hashed_test.go index 3068d771a92..8f3def470b4 100644 --- a/erigon-lib/commitment/hex_patricia_hashed_test.go +++ b/erigon-lib/commitment/hex_patricia_hashed_test.go @@ -37,7 +37,7 @@ func Test_HexPatriciaHashed_ResetThenSingularUpdates(t *testing.T) { ctx := context.Background() ms := NewMockState(t) - hph := NewHexPatriciaHashed(1, ms) + hph := NewHexPatriciaHashed(1, ms, ms.TempDir()) hph.SetTrace(false) plainKeys, updates := NewUpdateBuilder(). Balance("00", 4). @@ -106,7 +106,7 @@ func Test_HexPatriciaHashed_EmptyUpdate(t *testing.T) { ms := NewMockState(t) ctx := context.Background() - hph := NewHexPatriciaHashed(1, ms) + hph := NewHexPatriciaHashed(1, ms, ms.TempDir()) hph.SetTrace(false) plainKeys, updates := NewUpdateBuilder(). Balance("00", 4). @@ -161,8 +161,8 @@ func Test_HexPatriciaHashed_UniqueRepresentation2(t *testing.T) { Balance("1337beef00000000000000000000000000000000", 4000000000000138901). Build() - trieOne := NewHexPatriciaHashed(length.Addr, msOne) - trieTwo := NewHexPatriciaHashed(length.Addr, msTwo) + trieOne := NewHexPatriciaHashed(length.Addr, msOne, msOne.TempDir()) + trieTwo := NewHexPatriciaHashed(length.Addr, msTwo, msTwo.TempDir()) //trieOne.SetTrace(true) //trieTwo.SetTrace(true) @@ -299,8 +299,8 @@ func Test_HexPatriciaHashed_BrokenUniqueRepr(t *testing.T) { Build() keyLen := 20 - trieSequential := NewHexPatriciaHashed(keyLen, stateSeq) - trieBatch := NewHexPatriciaHashed(keyLen, stateBatch) + trieSequential := NewHexPatriciaHashed(keyLen, stateSeq, stateSeq.TempDir()) + trieBatch := NewHexPatriciaHashed(keyLen, stateBatch, stateBatch.TempDir()) if sortHashedKeys { plainKeys, updates = sortUpdatesByHashIncrease(t, trieSequential, plainKeys, updates) @@ -382,14 +382,15 @@ func Test_HexPatriciaHashed_UniqueRepresentation(t *testing.T) { Storage("68ee6c0e9cdc73b2b2d52dbd79f19d24fe25e2f9", "d1664244ae1a8a05f8f1d41e45548fbb7aa54609b985d6439ee5fd9bb0da619f", "9898"). Build() - trieSequential := NewHexPatriciaHashed(length.Addr, stateSeq) - trieBatch := NewHexPatriciaHashed(length.Addr, stateBatch) + trieSequential := NewHexPatriciaHashed(length.Addr, stateSeq, stateSeq.TempDir()) + trieSequential.trace = true + trieBatch := NewHexPatriciaHashed(length.Addr, stateBatch, stateBatch.TempDir()) + trieBatch.trace = true plainKeys, updates = sortUpdatesByHashIncrease(t, trieSequential, plainKeys, updates) - trace := false - trieSequential.SetTrace(trace) - trieBatch.SetTrace(trace) + // trieSequential.SetTrace(true) + // trieBatch.SetTrace(true) var rSeq, rBatch []byte { @@ -470,7 +471,7 @@ func Test_HexPatriciaHashed_Sepolia(t *testing.T) { }, } - hph := NewHexPatriciaHashed(length.Addr, state) + hph := NewHexPatriciaHashed(length.Addr, state, state.TempDir()) //hph.SetTrace(true) for _, testData := range tests { @@ -518,7 +519,7 @@ func Test_Cell_EncodeDecode(t *testing.T) { rnd.Read(first.hash[:]) second := new(cell) - err := second.DecodeRoot(first.EncodeRoot()) + err := second.Decode(first.Encode()) require.NoError(t, err) cellMustEqual(t, first, second) @@ -591,8 +592,8 @@ func Test_HexPatriciaHashed_StateEncodeDecodeSetup(t *testing.T) { Storage("f5", "04", "9898"). Build() - before := NewHexPatriciaHashed(1, ms) - after := NewHexPatriciaHashed(1, ms) + before := NewHexPatriciaHashed(1, ms, ms.TempDir()) + after := NewHexPatriciaHashed(1, ms, ms.TempDir()) err := ms.applyPlainUpdates(plainKeys, updates) require.NoError(t, err) @@ -627,10 +628,8 @@ func Test_HexPatriciaHashed_StateEncodeDecodeSetup(t *testing.T) { WrapKeyUpdatesInto(t, upds, nextPK, nextUpdates) - // before.SetTrace(true) rh2Before, err := before.Process(ctx, upds, "") require.NoError(t, err) - // after.SetTrace(true) WrapKeyUpdatesInto(t, upds, nextPK, nextUpdates) // they're resetted after Process @@ -653,7 +652,7 @@ func Test_HexPatriciaHashed_StateRestoreAndContinue(t *testing.T) { Balance("ff", 900234). Build() - trieOne := NewHexPatriciaHashed(1, msOne) + trieOne := NewHexPatriciaHashed(1, msOne, msOne.TempDir()) err := msOne.applyPlainUpdates(plainKeys, updates) require.NoError(t, err) @@ -679,7 +678,7 @@ func Test_HexPatriciaHashed_StateRestoreAndContinue(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, buf) - trieTwo := NewHexPatriciaHashed(1, msTwo) + trieTwo := NewHexPatriciaHashed(1, msTwo, msTwo.TempDir()) err = trieTwo.SetState(buf) require.NoError(t, err) @@ -753,8 +752,8 @@ func Test_HexPatriciaHashed_RestoreAndContinue(t *testing.T) { Storage("f5", "04", "9898"). Build() - trieOne := NewHexPatriciaHashed(1, ms) - trieTwo := NewHexPatriciaHashed(1, ms) + trieOne := NewHexPatriciaHashed(1, ms, ms.TempDir()) + trieTwo := NewHexPatriciaHashed(1, ms, ms.TempDir()) err := ms.applyPlainUpdates(plainKeys, updates) require.NoError(t, err) @@ -833,8 +832,8 @@ func Test_HexPatriciaHashed_ProcessUpdates_UniqueRepresentation_AfterStateRestor Storage("68ee6c0e9cdc73b2b2d52dbd79f19d24fe25e2f9", "d1664244ae1a8a05f8f1d41e45548fbb7aa54609b985d6439ee5fd9bb0da619f", "9898"). Build() - trieSequential := NewHexPatriciaHashed(length.Addr, stateSeq) - trieBatch := NewHexPatriciaHashed(length.Addr, stateBatch) + trieSequential := NewHexPatriciaHashed(length.Addr, stateSeq, stateSeq.TempDir()) + trieBatch := NewHexPatriciaHashed(length.Addr, stateBatch, stateBatch.TempDir()) plainKeys, updates = sortUpdatesByHashIncrease(t, trieSequential, plainKeys, updates) @@ -860,7 +859,7 @@ func Test_HexPatriciaHashed_ProcessUpdates_UniqueRepresentation_AfterStateRestor require.NoError(t, err) trieSequential.Reset() - trieSequential = NewHexPatriciaHashed(length.Addr, stateSeq) + trieSequential = NewHexPatriciaHashed(length.Addr, stateSeq, stateSeq.TempDir()) err = trieSequential.SetState(prevState) require.NoError(t, err) @@ -928,8 +927,8 @@ func Test_HexPatriciaHashed_ProcessUpdates_UniqueRepresentationInTheMiddle(t *te Balance("ba7a3b7b095d3370c022ca655c790f0c0ead66f5", 5*1e17). Build() - sequential := NewHexPatriciaHashed(length.Addr, stateSeq) - batch := NewHexPatriciaHashed(length.Addr, stateBatch) + sequential := NewHexPatriciaHashed(20, stateSeq, stateSeq.TempDir()) + batch := NewHexPatriciaHashed(20, stateBatch, stateBatch.TempDir()) plainKeys, updates = sortUpdatesByHashIncrease(t, sequential, plainKeys, updates) @@ -960,7 +959,7 @@ func Test_HexPatriciaHashed_ProcessUpdates_UniqueRepresentationInTheMiddle(t *te require.NoError(t, err) sequential.Reset() - sequential = NewHexPatriciaHashed(length.Addr, stateSeq) + sequential = NewHexPatriciaHashed(length.Addr, stateSeq, stateSeq.TempDir()) err = sequential.SetState(prevState) require.NoError(t, err) @@ -1170,7 +1169,7 @@ func TestCell_fillFromFields(t *testing.T) { return c, nil } - be := NewBranchEncoder(1024) + be := NewBranchEncoder(1024, t.TempDir()) enc, _, err := be.EncodeBranch(bm, bm, bm, cg) require.NoError(t, err) @@ -1268,7 +1267,7 @@ func Test_HexPatriciaHashed_ProcessWithDozensOfStorageKeys(t *testing.T) { Storage("00000000000000000000000000000000000000f5", "1f00000000000000000000000000000000000000f5", "00000000000000000000000000000000000000f5"). Build() - trieOne := NewHexPatriciaHashed(length.Addr, msOne) + trieOne := NewHexPatriciaHashed(length.Addr, msOne, msOne.TempDir()) plainKeys, updates = sortUpdatesByHashIncrease(t, trieOne, plainKeys, updates) //rnd := rand.New(rand.NewSource(345)) @@ -1299,10 +1298,10 @@ func Test_HexPatriciaHashed_ProcessWithDozensOfStorageKeys(t *testing.T) { //} //fmt.Printf("total %d\n", count) - trieTwo := NewHexPatriciaHashed(length.Addr, msTwo) + trieTwo := NewHexPatriciaHashed(length.Addr, msTwo, msTwo.TempDir()) - // trieOne.SetTrace(true) - // trieTwo.SetTrace(true) + trieOne.SetTrace(true) + trieTwo.SetTrace(true) var rSeq, rBatch []byte { diff --git a/erigon-lib/commitment/patricia_state_mock_test.go b/erigon-lib/commitment/patricia_state_mock_test.go index 714b94481ac..c1bb9712f88 100644 --- a/erigon-lib/commitment/patricia_state_mock_test.go +++ b/erigon-lib/commitment/patricia_state_mock_test.go @@ -54,7 +54,7 @@ func (ms *MockState) TempDir() string { func (ms *MockState) PutBranch(prefix []byte, data []byte, prevData []byte, prevStep uint64) error { // updates already merged by trie - ms.cm[toStringZeroCopy(prefix)] = common.Copy(data) + ms.cm[string(prefix)] = data return nil } diff --git a/erigon-lib/state/domain_shared.go b/erigon-lib/state/domain_shared.go index c1f8ef53454..b0578ab0290 100644 --- a/erigon-lib/state/domain_shared.go +++ b/erigon-lib/state/domain_shared.go @@ -1168,7 +1168,8 @@ func (sdc *SharedDomainsCommitmentContext) PutBranch(prefix []byte, data []byte, if sdc.sharedDomains.trace { fmt.Printf("[SDC] PutBranch: %x: %x\n", prefix, data) } - sdc.branches[prefixS] = cachedBranch{data: common.Copy(data), step: prevStep} + sdc.branches[prefixS] = cachedBranch{data: data, step: prevStep} + return sdc.sharedDomains.updateCommitmentData(prefixS, data, prevData, prevStep) } diff --git a/erigon-lib/state/domain_shared_test.go b/erigon-lib/state/domain_shared_test.go index 2f884672cdd..7fa9ea86e3a 100644 --- a/erigon-lib/state/domain_shared_test.go +++ b/erigon-lib/state/domain_shared_test.go @@ -75,9 +75,6 @@ func TestSharedDomain_CommitmentKeyReplacement(t *testing.T) { // 3. calculate commitment with all data +removed key expectedHash, err := domains.ComputeCommitment(context.Background(), false, domains.txNum/stepSize, "") require.NoError(t, err) - err = domains.Flush(ctx, rwTx) - require.NoError(t, err) - domains.Close() err = rwTx.Commit()