Skip to content

Commit

Permalink
move the post-root part to after the poststate root has been rebuilt
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Ballet <[email protected]>
  • Loading branch information
gballet committed Oct 24, 2024
1 parent 9309d64 commit 8acc14f
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 108 deletions.
34 changes: 18 additions & 16 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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)
}
}
}
Expand All @@ -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
}
Expand Down
114 changes: 86 additions & 28 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ package core
import (
"bytes"
"crypto/ecdsa"
// "encoding/binary"

"encoding/binary"
"encoding/json"
"fmt"
"os"
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.
_, 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 Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
84 changes: 20 additions & 64 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.
// _, 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
Expand Down

0 comments on commit 8acc14f

Please sign in to comment.