Skip to content

Commit

Permalink
blockchain: add execution witness validation (#515)
Browse files Browse the repository at this point in the history
* blockchain: add execution witness validation

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* more fixes

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* update go-ipa

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* update go-ipa

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* add parentRoot to dispatched output

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* remove todo

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix compilation

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* target tenatative version

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* generate parentRoot for witness check

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* progress

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* more fixes

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* change order of execution witness checks

* fill: remove witness generation in overlay tree when

* cleanup

* fix

* fixes

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* cleanup

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* mod: use latest go-verkle

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* more fixes

* update go-verkle

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* add comment

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* cleanup

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* ci: test fixtures alpha

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* ci change branch

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* restore target branch

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* pr feedback

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix nit

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* disable linter warnings, won't fix them in this branch

* disable more linter checks

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
  • Loading branch information
jsign and gballet authored Oct 29, 2024
1 parent 89330e4 commit 0f986ab
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 156 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/stable-spec-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

env:
FIXTURES_TAG: "verkle@v0.0.6"
FIXTURES_TAG: "verkle@v0.0.7-alpha-4"

jobs:
setup:
Expand Down
15 changes: 15 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ issues:
text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.'
- path: accounts/usbwallet/trezor/
text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.'
- path: accounts/scwallet/securechannel.go
linters:
- staticcheck
- path: p2p/rlpx/rlpx.go
linters:
- staticcheck
- path: crypto/
linters:
- staticcheck
- path: rlp/
linters:
- staticcheck
- path: miner/
linters:
- staticcheck
exclude:
- 'SA1019: event.TypeMux is deprecated: use Feed'
- 'SA1019: strings.Title is deprecated'
Expand Down
45 changes: 28 additions & 17 deletions cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type ExecutionResult struct {
// Verkle witness
VerkleProof *verkle.VerkleProof `json:"verkleProof,omitempty"`
StateDiff verkle.StateDiff `json:"stateDiff,omitempty"`
ParentRoot common.Hash `json:"parentRoot,omitempty"`

// Values to test the verkle conversion
CurrentAccountAddress *common.Address `json:"currentConversionAddress,omitempty" gencodec:"optional"`
Expand Down Expand Up @@ -163,17 +164,18 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return h
}
var (
statedb = MakePreState(rawdb.NewMemoryDatabase(), chainConfig, pre, chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp))
vtrpre *trie.VerkleTrie
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
gaspool = new(core.GasPool)
blockHash = common.Hash{0x13, 0x37}
rejectedTxs []*rejectedTx
includedTxs types.Transactions
gasUsed = uint64(0)
receipts = make(types.Receipts, 0)
txIndex = 0
parentStateRoot, statedb = MakePreState(rawdb.NewMemoryDatabase(), chainConfig, pre, chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp))
vtrpre *trie.VerkleTrie
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
gaspool = new(core.GasPool)
blockHash = common.Hash{0x13, 0x37}
rejectedTxs []*rejectedTx
includedTxs types.Transactions
gasUsed = uint64(0)
receipts = make(types.Receipts, 0)
txIndex = 0
)

gaspool.AddGas(pre.Env.GasLimit)
vmContext := vm.BlockContext{
CanTransfer: core.CanTransfer,
Expand All @@ -191,8 +193,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
switch tr := statedb.GetTrie().(type) {
case *trie.VerkleTrie:
vtrpre = tr.Copy()
case *trie.TransitionTrie:
vtrpre = tr.Overlay().Copy()
}

// If currentBaseFee is defined, add it to the vmContext.
Expand Down Expand Up @@ -391,6 +391,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
VerkleProof: vktProof,
StateDiff: vktStateDiff,
ParentRoot: parentStateRoot,
}
if pre.Env.Withdrawals != nil {
h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil))
Expand Down Expand Up @@ -425,7 +426,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return statedb, execRs, nil
}

func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prestate, verkle bool) *state.StateDB {
func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prestate, verkle bool) (common.Hash, *state.StateDB) {
// Start with generating the MPT DB, which should be empty if it's post-verkle transition
sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true, Verkle: false})

Expand All @@ -451,9 +452,12 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest
codeHash := crypto.Keccak256Hash(acc.Code)
rawdb.WriteCode(codeWriter, codeHash, acc.Code)
}
statedb.Commit(0, false)
root, err := statedb.Commit(0, false)
if err != nil {
panic(err)
}

return statedb
return root, statedb
}

// MPT pre is the same as the pre state for first conversion block
Expand All @@ -473,12 +477,13 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest
panic(err)
}

parentRoot := mptRoot
// If verkle mode started, establish the conversion
if verkle {
// If the current tree is a VerkleTrie, it means the state conversion has ended.
// We don't need to continue with conversion setups and can return early.
if _, ok := statedb.GetTrie().(*trie.VerkleTrie); ok {
return statedb
return parentRoot, statedb
}

rawdb.WritePreimages(sdb.DiskDB(), statedb.Preimages())
Expand Down Expand Up @@ -548,6 +553,12 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest
}

root, _ := statedb.Commit(0, false)
// If the VKT prestate is empty, this means that the "parent" root is the MPT.
// If we don't do this, we'd consider the "parent" to be an empty VKT which isn't
// correct.
if pre.VKT != nil {
parentRoot = root
}

// recreate the verkle db with the tree root, but this time with the mpt snapshot,
// so that the conversion can proceed.
Expand All @@ -565,7 +576,7 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest
if statedb.Database().InTransition() || statedb.Database().Transitioned() {
log.Info("at end of makestate", "in transition", statedb.Database().InTransition(), "during", statedb.Database().Transitioned(), "account hash", statedb.Database().GetCurrentAccountHash())
}
return statedb
return parentRoot, statedb
}

func rlpHash(x interface{}) (h common.Hash) {
Expand Down
11 changes: 6 additions & 5 deletions cmd/evm/internal/t8ntool/transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func Transition(ctx *cli.Context) error {
vktleaves = make(map[common.Hash]hexutil.Bytes)
s.DumpVKTLeaves(vktleaves)
}
return dispatchOutput(ctx, baseDir, result, collector, vktleaves, body, result.VerkleProof, result.StateDiff)
return dispatchOutput(ctx, baseDir, result, collector, vktleaves, body, result.VerkleProof, result.StateDiff, result.ParentRoot)
}

// txWithKey is a helper-struct, to allow us to use the types.Transaction along with
Expand Down Expand Up @@ -447,7 +447,7 @@ func saveFile(baseDir, filename string, data interface{}) error {

// dispatchOutput writes the output data to either stderr or stdout, or to the specified
// files
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, vkt map[common.Hash]hexutil.Bytes, body hexutil.Bytes, p *verkle.VerkleProof, k verkle.StateDiff) error {
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, vkt map[common.Hash]hexutil.Bytes, body hexutil.Bytes, p *verkle.VerkleProof, k verkle.StateDiff, proot common.Hash) error {
stdOutObject := make(map[string]interface{})
stdErrObject := make(map[string]interface{})
dispatch := func(baseDir, fName, name string, obj interface{}) error {
Expand Down Expand Up @@ -481,9 +481,10 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
}
if p != nil {
if err := dispatch(baseDir, ctx.String(OutputWitnessFlag.Name), "witness", struct {
Proof *verkle.VerkleProof
Diff verkle.StateDiff
}{p, k}); err != nil {
Proof *verkle.VerkleProof
Diff verkle.StateDiff
ParentRoot common.Hash
}{p, k, proot}); err != nil {
return err
}
}
Expand Down
122 changes: 66 additions & 56 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,82 +400,92 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
state.Database().SaveTransitionState(header.Root)

var (
p *verkle.VerkleProof
k verkle.StateDiff
keys = state.Witness().Keys()
proot common.Hash
proof *verkle.VerkleProof
stateDiff verkle.StateDiff
proot common.Hash
)
if chain.Config().IsVerkle(header.Number, header.Time) {
// Open the pre-tree to prove the pre-state against
parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1)
if parent == nil {
return nil, fmt.Errorf("nil parent header for block %d", header.Number)
}
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
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
}
case *trie.TransitionTrie:
vtrpre = pre.Overlay()
okpre = true
post, _ := state.GetTrie().(*trie.TransitionTrie)
vtrpost = post.Overlay()
okpost = true
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 {
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)
}
}
}
var err error
stateDiff, proof, err = BuildVerkleProof(header, state, parent.Root)
if err != nil {
return nil, fmt.Errorf("error building verkle proof: %w", err)
}
proot = parent.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)
block.SetVerkleProof(proof, stateDiff, proot)
}
return block, nil
}

func BuildVerkleProof(header *types.Header, state *state.StateDB, parentRoot common.Hash) (verkle.StateDiff, *verkle.VerkleProof, error) {
var (
proof *verkle.VerkleProof
stateDiff verkle.StateDiff
)

preTrie, err := state.Database().OpenTrie(parentRoot)
if err != nil {
return nil, nil, fmt.Errorf("error opening pre-state tree root: %w", err)
}

var okpre, okpost bool
var vtrpre, vtrpost *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
}
case *trie.TransitionTrie:
vtrpre = pre.Overlay()
okpre = true
post, _ := state.GetTrie().(*trie.TransitionTrie)
vtrpost = post.Overlay()
okpost = true
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)
}
}
}
return stateDiff, proof, nil
}

// Seal generates a new sealing request for the given input block and pushes
// the result into the given channel.
//
Expand Down
19 changes: 19 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"

"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"
Expand Down Expand Up @@ -131,6 +132,24 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
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.GetHeaderByNumber(header.Number.Uint64() - 1)
if parent == nil {
return fmt.Errorf("nil parent header for block %d", header.Number)
}
stateDiff, proof, err := beacon.BuildVerkleProof(header, statedb, parent.Root)
if err != nil {
return fmt.Errorf("error building verkle proof: %w", err)
}
ew := types.ExecutionWitness{
StateDiff: stateDiff,
VerkleProof: proof,
ParentStateRoot: parent.Root,
}
if err := ew.Equal(blockEw); err != nil {
return fmt.Errorf("invalid execution witness: %v", err)
}
}
// Verify that the advertised root is correct before
// it can be used as an identifier for the conversion
// status.
Expand Down
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,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 = verkle.Verify(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 @@ -45,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 @@ -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 = verkle.Verify(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 = verkle.Verify(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1])
if err != nil {
t.Fatal(err)
}
Expand Down
Loading

0 comments on commit 0f986ab

Please sign in to comment.