Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move proof building before tree root computation #517

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,20 @@ 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.Prove(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)
}
}
}

Expand Down
69 changes: 24 additions & 45 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,15 +393,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 @@ -413,65 +406,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
59 changes: 58 additions & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"bytes"
"fmt"
"math/big"

Expand Down Expand Up @@ -365,6 +366,62 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
return db, blocks, receipts
}

// 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 GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, diskdb ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff, []common.Hash) {
if config == nil {
config = params.TestChainConfig
Expand Down Expand Up @@ -432,7 +489,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
proots = append(proots, parent.Root())

// quick check that we are self-consistent
err = trie.DeserializeAndVerifyVerkleProof(block.ExecutionWitness().VerkleProof, block.ExecutionWitness().ParentStateRoot[:], block.Root().Bytes(), block.ExecutionWitness().StateDiff)
err = deserializeAndVerifyVerkleProof(block.ExecutionWitness().VerkleProof, block.ExecutionWitness().ParentStateRoot[:], block.Root().Bytes(), block.ExecutionWitness().StateDiff)
if err != nil {
panic(err)
}
Expand Down
5 changes: 3 additions & 2 deletions 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 Down Expand Up @@ -608,13 +609,13 @@ func TestProcessVerkle(t *testing.T) {
//fmt.Printf("root= %x\n", chain[0].Root())

// check the proof for the 1st block
err = trie.DeserializeAndVerifyVerkleProof(proofs[0], genesis.Root().Bytes(), chain[0].Root().Bytes(), keyvals[0])
err = deserializeAndVerifyVerkleProof(proofs[0], genesis.Root().Bytes(), chain[0].Root().Bytes(), keyvals[0])
if err != nil {
spew.Dump(genesis.Root().Bytes(), proofs[0])
t.Fatal(err)
}
// 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
Loading