From c284ea4cf802e9b5fcd4fe1ca44dca8b45d03922 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:44:10 +0100 Subject: [PATCH] move proof building before root computation Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- cmd/evm/internal/t8ntool/execution.go | 10 +++- consensus/beacon/consensus.go | 85 ++++++++++----------------- core/block_validator.go | 34 ++++++++--- trie/verkle.go | 32 ++++++---- 4 files changed, 89 insertions(+), 72 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 2fb49139d71f..b011a5eb2adc 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -371,10 +371,18 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, default: return nil, nil, fmt.Errorf("invalid tree type in proof generation: %v", tr) } - vktProof, vktStateDiff, err = trie.ProveAndSerialize(vtrpre, proofTrie, keys, vtrpre.FlatdbNodeResolver) + proof, err := trie.Proof(vtrpre, proofTrie, keys, vtrpre.FlatdbNodeResolver) if err != nil { return nil, nil, fmt.Errorf("error generating verkle proof for block %d: %w", pre.Env.Number, err) } + err = trie.AddPostValuesToProof(keys, proofTrie, proof) + if err != nil { + return nil, nil, fmt.Errorf("error adding post values to proof: %w", err) + } + vktProof, vktStateDiff, err = verkle.SerializeProof(proof) + if err != nil { + return nil, nil, fmt.Errorf("error serializing proof: %w", err) + } } } diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index c7bfd1e55b09..e30c39f6a037 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -393,97 +393,76 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Finalize and assemble the block. beacon.Finalize(chain, header, state, txs, uncles, withdrawals) - // Assign the final state root to header. - header.Root = state.IntermediateRoot(true) - // Associate current conversion state to computed state - // root and store it in the database for later recovery. - state.Database().SaveTransitionState(header.Root) - var ( - proof *verkle.VerkleProof - stateDiff verkle.StateDiff - proot common.Hash + proof *verkle.Proof + proot common.Hash + keys = state.Witness().Keys() ) if chain.Config().IsVerkle(header.Number, header.Time) { parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1) if parent == nil { return nil, fmt.Errorf("nil parent header for block %d", header.Number) } - // Load transition state at beginning of block, because - // OpenTrie needs to know what the conversion status is. - state.Database().LoadTransitionState(parent.Root) var err error - stateDiff, proof, err = BuildVerkleProof(header, state, parent.Root) + proof, err = BuildVerkleProof(header, state, parent.Root, keys) if err != nil { return nil, fmt.Errorf("error building verkle proof: %w", err) } proot = parent.Root } + // Assign the final state root to header. + header.Root = state.IntermediateRoot(true) + // Associate current conversion state to computed state + // root and store it in the database for later recovery. + state.Database().SaveTransitionState(header.Root) + // 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(proof, stateDiff, proot) + err := trie.AddPostValuesToProof(keys, state.GetTrie().(*trie.VerkleTrie), proof) + if err != nil { + return nil, fmt.Errorf("error adding post values to proof: %w", err) + } + vp, stateDiff, err := verkle.SerializeProof(proof) + if err != nil { + return nil, fmt.Errorf("error serializing proof: %w", err) + } + block.SetVerkleProof(vp, stateDiff, proot) } return block, nil } -func BuildVerkleProof(header *types.Header, state *state.StateDB, parentRoot common.Hash) (verkle.StateDiff, *verkle.VerkleProof, error) { +func BuildVerkleProof(header *types.Header, state *state.StateDB, parentRoot common.Hash, keys [][]byte) (*verkle.Proof, error) { var ( - proof *verkle.VerkleProof - stateDiff verkle.StateDiff + proof *verkle.Proof ) preTrie, err := state.Database().OpenTrie(parentRoot) if err != nil { - return nil, nil, fmt.Errorf("error opening pre-state tree root: %w", err) + return nil, fmt.Errorf("error opening pre-state tree root: %w", err) } - var okpre, okpost bool - var vtrpre, vtrpost *trie.VerkleTrie + var vtr *trie.VerkleTrie switch pre := preTrie.(type) { case *trie.VerkleTrie: - vtrpre, okpre = preTrie.(*trie.VerkleTrie) - switch tr := state.GetTrie().(type) { - case *trie.VerkleTrie: - vtrpost = tr - okpost = true - // This is to handle a situation right at the start of the conversion: - // the post trie is a transition tree when the pre tree is an empty - // verkle tree. - case *trie.TransitionTrie: - vtrpost = tr.Overlay() - okpost = true - default: - okpost = false - } + vtr = pre case *trie.TransitionTrie: - vtrpre = pre.Overlay() - okpre = true - post, _ := state.GetTrie().(*trie.TransitionTrie) - vtrpost = post.Overlay() - okpost = 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) - post := state.GetTrie().(*trie.TransitionTrie) - vtrpost = post.Overlay() - okpost = true - } - if okpre && okpost { - keys := state.Witness().Keys() - if len(keys) > 0 { - proof, stateDiff, err = trie.ProveAndSerialize(vtrpre, vtrpost, keys, vtrpre.FlatdbNodeResolver) - if err != nil { - return nil, nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err) - } + vtr = trie.NewVerkleTrie(verkle.New(), state.Database().TrieDB(), utils.NewPointCache(), false) + } + if len(keys) > 0 { + proof, err = trie.Proof(vtr, nil, keys, vtr.FlatdbNodeResolver) + if err != nil { + return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err) } } - return stateDiff, proof, nil + return proof, nil } // Seal generates a new sealing request for the given input block and pushes diff --git a/core/block_validator.go b/core/block_validator.go index 4f15087e62b6..3f0c71c872fe 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -20,12 +20,14 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-verkle" ) // BlockValidator is responsible for validating block headers, uncles and @@ -127,24 +129,42 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD if receiptSha != header.ReceiptHash { return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) } + var ( + proot common.Hash + proof *verkle.Proof + err error + keys [][]byte + ) + if blockEw := block.ExecutionWitness(); blockEw != nil { + parent := v.bc.GetBlockByHash(header.ParentHash) + if parent == nil { + return fmt.Errorf("nil parent header for block %d", header.Number) + } + proot = parent.Root() + keys = statedb.Witness().Keys() + proof, err = beacon.BuildVerkleProof(header, statedb, proot, keys) + if err != nil { + return fmt.Errorf("error building verkle proof: %w", err) + } + } // Validate the state root against the received state root and throw // an error if they don't match. if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) } if blockEw := block.ExecutionWitness(); blockEw != nil { - parent := v.bc.GetBlockByHash(header.ParentHash) - if parent == nil { - return fmt.Errorf("nil parent header for block %d", header.Number) + err := trie.AddPostValuesToProof(keys, statedb.GetTrie().(*trie.VerkleTrie), proof) + if err != nil { + return fmt.Errorf("error adding post values to proof: %w", err) } - stateDiff, proof, err := beacon.BuildVerkleProof(header, statedb, parent.Root()) + vp, stateDiff, err := verkle.SerializeProof(proof) if err != nil { - return fmt.Errorf("error building verkle proof: %w", err) + return fmt.Errorf("error serializing proof: %w", err) } ew := types.ExecutionWitness{ StateDiff: stateDiff, - VerkleProof: proof, - ParentStateRoot: parent.Root(), + VerkleProof: vp, + ParentStateRoot: proot, } if err := ew.Equal(blockEw); err != nil { return fmt.Errorf("invalid execution witness: %v", err) diff --git a/trie/verkle.go b/trie/verkle.go index 2bd40659da95..c626d6cc9233 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -290,22 +290,32 @@ func (trie *VerkleTrie) IsVerkle() bool { return true } -func ProveAndSerialize(pretrie, posttrie *VerkleTrie, keys [][]byte, resolver verkle.NodeResolverFn) (*verkle.VerkleProof, verkle.StateDiff, error) { +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("error getting post-state value for key %x: %w", keys[i], err) + } + if !bytes.Equal(proof.PreValues[i], val) { + proof.PostValues[i] = val + } + } + } + + return nil +} + +func Proof(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) - if err != nil { - return nil, nil, err - } - - p, kvps, err := verkle.SerializeProof(proof) - if err != nil { - return nil, nil, err - } - - return p, kvps, nil + return proof, err } // ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which