Skip to content

Commit

Permalink
move part of proof building before tree root computation
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
  • Loading branch information
gballet committed Oct 24, 2024
1 parent 954be88 commit f7274e1
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 109 deletions.
69 changes: 24 additions & 45 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,15 +392,8 @@ 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 (
p *verkle.VerkleProof
k verkle.StateDiff
p *verkle.Proof
keys = state.Witness().Keys()
proot common.Hash
)
Expand All @@ -412,65 +405,51 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
}
proot = parent.Root

// Load transition state at beginning of block, because
// OpenTrie needs to know what the conversion status is.
state.Database().LoadTransitionState(parent.Root)

if chain.Config().ProofInBlocks {
preTrie, err := state.Database().OpenTrie(parent.Root)
if err != nil {
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
vtr = trie.NewVerkleTrie(verkle.New(), state.Database().TrieDB(), utils.NewPointCache(), false)
}
if okpre && okpost {
if len(keys) > 0 {
p, k, err = trie.ProveAndSerialize(vtrpre, vtrpost, 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)
}
}
}
}

// 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(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
}
Expand Down
60 changes: 59 additions & 1 deletion core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package core
import (
"bytes"
"crypto/ecdsa"

"encoding/binary"
"encoding/json"
"fmt"
Expand All @@ -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"
Expand Down Expand Up @@ -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.
posttree, 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 = &params.ChainConfig{
Expand Down Expand Up @@ -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)
}
Expand Down
82 changes: 19 additions & 63 deletions trie/verkle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
posttree, 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 nil
}

return verkle.VerifyVerkleProofWithPreState(proof, pretree)
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
Expand Down

0 comments on commit f7274e1

Please sign in to comment.