From 8acc14febafb19badd7b72e882a86577a61475c7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:16:25 +0200 Subject: [PATCH] move the post-root part to after the poststate root has been rebuilt Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- consensus/beacon/consensus.go | 34 +++++----- core/state_processor_test.go | 114 +++++++++++++++++++++++++--------- trie/verkle.go | 84 ++++++------------------- 3 files changed, 124 insertions(+), 108 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index d4132e26105ce..af00cc7fdb5ac 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -393,8 +393,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea beacon.Finalize(chain, header, state, txs, uncles, withdrawals) var ( - p *verkle.VerkleProof - k verkle.StateDiff + p *verkle.Proof keys = state.Witness().Keys() proot common.Hash ) @@ -412,27 +411,22 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea return nil, fmt.Errorf("error opening pre-state tree root: %w", err) } - var okpre bool - var vtrpre *trie.VerkleTrie + var vtr *trie.VerkleTrie switch pre := preTrie.(type) { case *trie.VerkleTrie: - vtrpre, okpre = preTrie.(*trie.VerkleTrie) + vtr = pre case *trie.TransitionTrie: - vtrpre = pre.Overlay() - okpre = true + vtr = pre.Overlay() default: // This should only happen for the first block of the // conversion, when the previous tree is a merkle tree. // Logically, the "previous" verkle tree is an empty tree. - okpre = true - vtrpre = trie.NewVerkleTrie(verkle.New(), state.Database().TrieDB(), utils.NewPointCache(), false) + vtr = trie.NewVerkleTrie(verkle.New(), state.Database().TrieDB(), utils.NewPointCache(), false) } - if okpre { - if len(keys) > 0 { - p, k, err = trie.ProveAndSerialize(vtrpre, nil, keys, vtrpre.FlatdbNodeResolver) - if err != nil { - return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err) - } + if len(keys) > 0 { + p, err = trie.Prove(vtr, nil, keys, vtr.FlatdbNodeResolver) + if err != nil { + return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err) } } } @@ -447,7 +441,15 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Assemble and return the final block. block := types.NewBlockWithWithdrawals(header, txs, uncles, receipts, withdrawals, trie.NewStackTrie(nil)) if chain.Config().IsVerkle(header.Number, header.Time) && chain.Config().ProofInBlocks { - block.SetVerkleProof(p, k, proot) + err := trie.AddPostValuesToProof(keys, state.GetTrie().(*trie.VerkleTrie), p) + if err != nil { + return nil, fmt.Errorf("error adding post values to proof: %w", err) + } + vp, k, err := verkle.SerializeProof(p) + if err != nil { + return nil, fmt.Errorf("error serializing proof: %w", err) + } + block.SetVerkleProof(vp, k, proot) } return block, nil } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index a0b05e3f50750..2a7676aea1974 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -19,7 +19,8 @@ package core import ( "bytes" "crypto/ecdsa" - // "encoding/binary" + + "encoding/binary" "encoding/json" "fmt" "os" @@ -44,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/utils" + "github.com/ethereum/go-verkle" //"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -441,6 +443,62 @@ var ( intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true) ) +// deserializeAndVerifyVerkleProof is a helper function that rebuilds the pre-tree from the proof, inserts +// the post values inside the tree, checks that the roots match, and then ensure that the proof verifies. +func deserializeAndVerifyVerkleProof(vp *verkle.VerkleProof, preStateRoot []byte, postStateRoot []byte, statediff verkle.StateDiff) error { + // TODO: check that `OtherStems` have expected length and values. + + proof, err := verkle.DeserializeProof(vp, statediff) + if err != nil { + return fmt.Errorf("verkle proof deserialization error: %w", err) + } + + rootC := new(verkle.Point) + rootC.SetBytes(preStateRoot) + pretree, err := verkle.PreStateTreeFromProof(proof, rootC) + if err != nil { + return fmt.Errorf("error rebuilding the pre-tree from proof: %w", err) + } + // TODO this should not be necessary, remove it + // after the new proof generation code has stabilized. + for _, stemdiff := range statediff { + for _, suffixdiff := range stemdiff.SuffixDiffs { + var key [32]byte + copy(key[:31], stemdiff.Stem[:]) + key[31] = suffixdiff.Suffix + + val, err := pretree.Get(key[:], nil) + if err != nil { + return fmt.Errorf("could not find key %x in tree rebuilt from proof: %w", key, err) + } + if len(val) > 0 { + if !bytes.Equal(val, suffixdiff.CurrentValue[:]) { + return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue) + } + } else { + if suffixdiff.CurrentValue != nil && len(suffixdiff.CurrentValue) != 0 { + return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue) + } + } + } + } + + // TODO: this is necessary to verify that the post-values are the correct ones. + // But all this can be avoided with a even faster way. The EVM block execution can + // keep track of the written keys, and compare that list with this post-values list. + // This can avoid regenerating the post-tree which is somewhat expensive. + _, err = verkle.PostStateTreeFromStateDiff(pretree, statediff) + if err != nil { + return fmt.Errorf("error rebuilding the post-tree from proof: %w", err) + } + // regeneratedPostTreeRoot := posttree.Commitment().Bytes() + // if !bytes.Equal(regeneratedPostTreeRoot[:], postStateRoot) { + // return fmt.Errorf("post tree root mismatch: %x != %x", regeneratedPostTreeRoot, postStateRoot) + // } + + return verkle.VerifyVerkleProofWithPreState(proof, pretree) +} + func TestProcessVerkle(t *testing.T) { var ( config = ¶ms.ChainConfig{ @@ -602,7 +660,7 @@ func TestProcessVerkle(t *testing.T) { //f.Write(buf.Bytes()) //fmt.Printf("root= %x\n", chain[0].Root()) // check the proof for the last block - err = trie.DeserializeAndVerifyVerkleProof(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1]) + err = deserializeAndVerifyVerkleProof(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1]) if err != nil { t.Fatal(err) } @@ -748,9 +806,9 @@ func TestProcessVerkleInvalidContractCreation(t *testing.T) { if stemStateDiff.SuffixDiffs[0].CurrentValue != nil { t.Fatalf("non-nil current value in BLOCKHASH contract insert: %x", stemStateDiff.SuffixDiffs[0].CurrentValue) } - // if stemStateDiff.SuffixDiffs[0].NewValue == nil { - // t.Fatalf("nil new value in BLOCKHASH contract insert") - // } + if stemStateDiff.SuffixDiffs[0].NewValue == nil { + t.Fatalf("nil new value in BLOCKHASH contract insert") + } } else { for _, suffixDiff := range stemStateDiff.SuffixDiffs { if suffixDiff.Suffix > 4 { @@ -772,12 +830,12 @@ func TestProcessVerkleInvalidContractCreation(t *testing.T) { if stemStateDiff.SuffixDiffs[0].Suffix != 65 { t.Fatalf("invalid suffix diff value found for BLOCKHASH contract at block #2: %d != 65", stemStateDiff.SuffixDiffs[0].Suffix) } - // if stemStateDiff.SuffixDiffs[0].NewValue == nil { - // t.Fatalf("missing post state value for BLOCKHASH contract at block #2") - // } - // if *stemStateDiff.SuffixDiffs[0].NewValue != common.HexToHash("ac9ab8a7d88cfee11ebcda5f47232c07fcb393c8916e37fa67eb5e315b1f8ef6") { - // t.Fatalf("invalid post state value for BLOCKHASH contract at block #2: ac9ab8a7d88cfee11ebcda5f47232c07fcb393c8916e37fa67eb5e315b1f8ef6 != %x", (*stemStateDiff.SuffixDiffs[0].NewValue)[:]) - // } + if stemStateDiff.SuffixDiffs[0].NewValue == nil { + t.Fatalf("missing post state value for BLOCKHASH contract at block #2") + } + if *stemStateDiff.SuffixDiffs[0].NewValue != common.HexToHash("ac9ab8a7d88cfee11ebcda5f47232c07fcb393c8916e37fa67eb5e315b1f8ef6") { + t.Fatalf("invalid post state value for BLOCKHASH contract at block #2: ac9ab8a7d88cfee11ebcda5f47232c07fcb393c8916e37fa67eb5e315b1f8ef6 != %x", (*stemStateDiff.SuffixDiffs[0].NewValue)[:]) + } } else if suffixDiff.Suffix > 4 { t.Fatalf("invalid suffix diff found for %x in block #2: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) } @@ -862,9 +920,9 @@ func TestProcessVerkleContractWithEmptyCode(t *testing.T) { if stemStateDiff.SuffixDiffs[0].CurrentValue != nil { t.Fatalf("non-nil current value in BLOCKHASH contract insert: %x", stemStateDiff.SuffixDiffs[0].CurrentValue) } - // if stemStateDiff.SuffixDiffs[0].NewValue == nil { - // t.Fatalf("nil new value in BLOCKHASH contract insert") - // } + if stemStateDiff.SuffixDiffs[0].NewValue == nil { + t.Fatalf("nil new value in BLOCKHASH contract insert") + } } else { for _, suffixDiff := range stemStateDiff.SuffixDiffs { if suffixDiff.Suffix > 4 { @@ -1004,9 +1062,9 @@ func TestProcessVerklExtCodeHashOpcode(t *testing.T) { if *codeHashStateDiff.CurrentValue != expCodeHash { t.Fatalf("codeHash.CurrentValue unexpected code hash") } - // if codeHashStateDiff.NewValue != nil { - // t.Fatalf("codeHash.NewValue must be nil") - // } + if codeHashStateDiff.NewValue != nil { + t.Fatalf("codeHash.NewValue must be nil") + } } func TestProcessVerkleBalanceOpcode(t *testing.T) { @@ -1099,9 +1157,9 @@ func TestProcessVerkleBalanceOpcode(t *testing.T) { if *balanceStateDiff.CurrentValue == zero { t.Fatalf("invalid current value") } - // if balanceStateDiff.NewValue != nil { - // t.Fatalf("invalid new value") - // } + if balanceStateDiff.NewValue != nil { + t.Fatalf("invalid new value") + } } func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) { @@ -1239,14 +1297,14 @@ func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) { if balanceStateDiff.CurrentValue == nil { t.Fatalf("codeHash.CurrentValue must not be empty") } - // if balanceStateDiff.NewValue == nil { - // t.Fatalf("codeHash.NewValue must not be empty") - // } - // preStateBalance := binary.BigEndian.Uint64(balanceStateDiff.CurrentValue[utils.BasicDataBalanceOffset+8:]) - // postStateBalance := binary.BigEndian.Uint64(balanceStateDiff.NewValue[utils.BasicDataBalanceOffset+8:]) - // if postStateBalance-preStateBalance != 42 { - // t.Fatalf("the post-state balance after self-destruct must be 42, got %d-%d=%d", postStateBalance, preStateBalance, postStateBalance-preStateBalance) - // } + if balanceStateDiff.NewValue == nil { + t.Fatalf("codeHash.NewValue must not be empty") + } + preStateBalance := binary.BigEndian.Uint64(balanceStateDiff.CurrentValue[utils.BasicDataBalanceOffset+8:]) + postStateBalance := binary.BigEndian.Uint64(balanceStateDiff.NewValue[utils.BasicDataBalanceOffset+8:]) + if postStateBalance-preStateBalance != 42 { + t.Fatalf("the post-state balance after self-destruct must be 42, got %d-%d=%d", postStateBalance, preStateBalance, postStateBalance-preStateBalance) + } } } diff --git a/trie/verkle.go b/trie/verkle.go index e066b0bede07b..1a67c5f9edc0d 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -282,76 +282,32 @@ func (trie *VerkleTrie) IsVerkle() bool { return true } -func ProveAndSerialize(pretrie, posttrie *VerkleTrie, keys [][]byte, resolver verkle.NodeResolverFn) (*verkle.VerkleProof, verkle.StateDiff, error) { - var postroot verkle.VerkleNode - if posttrie != nil { - postroot = posttrie.root - } - proof, _, _, _, err := verkle.MakeVerkleMultiProof(pretrie.root, postroot, keys, resolver) - if err != nil { - return nil, nil, err - } - - p, kvps, err := verkle.SerializeProof(proof) - if err != nil { - return nil, nil, err - } - - return p, kvps, nil -} - -func DeserializeAndVerifyVerkleProof(vp *verkle.VerkleProof, preStateRoot []byte, postStateRoot []byte, statediff verkle.StateDiff) error { - // TODO: check that `OtherStems` have expected length and values. - - proof, err := verkle.DeserializeProof(vp, statediff) - if err != nil { - return fmt.Errorf("verkle proof deserialization error: %w", err) - } - - rootC := new(verkle.Point) - rootC.SetBytes(preStateRoot) - pretree, err := verkle.PreStateTreeFromProof(proof, rootC) - if err != nil { - return fmt.Errorf("error rebuilding the pre-tree from proof: %w", err) - } - // TODO this should not be necessary, remove it - // after the new proof generation code has stabilized. - for _, stemdiff := range statediff { - for _, suffixdiff := range stemdiff.SuffixDiffs { - var key [32]byte - copy(key[:31], stemdiff.Stem[:]) - key[31] = suffixdiff.Suffix - - val, err := pretree.Get(key[:], nil) +func AddPostValuesToProof(keys [][]byte, postroot *VerkleTrie, proof *verkle.Proof) error { + proof.PostValues = make([][]byte, len(keys)) + if postroot != nil { + // keys were sorted already in the above GetcommitmentsForMultiproof. + // Set the post values, if they are untouched, leave them `nil` + for i := range keys { + val, err := postroot.root.Get(keys[i], nil) if err != nil { - return fmt.Errorf("could not find key %x in tree rebuilt from proof: %w", key, err) + return fmt.Errorf("error getting post-state value for key %x: %w", keys[i], err) } - if len(val) > 0 { - if !bytes.Equal(val, suffixdiff.CurrentValue[:]) { - return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue) - } - } else { - if suffixdiff.CurrentValue != nil && len(suffixdiff.CurrentValue) != 0 { - return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue) - } + if !bytes.Equal(proof.PreValues[i], val) { + proof.PostValues[i] = val } } } - // TODO: this is necessary to verify that the post-values are the correct ones. - // But all this can be avoided with a even faster way. The EVM block execution can - // keep track of the written keys, and compare that list with this post-values list. - // This can avoid regenerating the post-tree which is somewhat expensive. - // _, err = verkle.PostStateTreeFromStateDiff(pretree, statediff) - // if err != nil { - // return fmt.Errorf("error rebuilding the post-tree from proof: %w", err) - // } - // regeneratedPostTreeRoot := posttree.Commitment().Bytes() - // if !bytes.Equal(regeneratedPostTreeRoot[:], postStateRoot) { - // return fmt.Errorf("post tree root mismatch: %x != %x", regeneratedPostTreeRoot, postStateRoot) - // } - - return verkle.VerifyVerkleProofWithPreState(proof, pretree) + return nil +} + +func Prove(pretrie, posttrie *VerkleTrie, keys [][]byte, resolver verkle.NodeResolverFn) (*verkle.Proof, error) { + var postroot verkle.VerkleNode + if posttrie != nil { + postroot = posttrie.root + } + proof, _, _, _, err := verkle.MakeVerkleMultiProof(pretrie.root, postroot, keys, resolver) + return proof, err } // ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which