Skip to content

Commit d86b650

Browse files
jwasingerkaralabe
authored andcommitted
all: add stateless verifications
1 parent c10ac4f commit d86b650

34 files changed

+876
-70
lines changed

cmd/utils/stateless/stateless.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package stateless
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
"github.com/ethereum/go-ethereum/consensus/beacon"
8+
"github.com/ethereum/go-ethereum/consensus/ethash"
9+
"github.com/ethereum/go-ethereum/core"
10+
"github.com/ethereum/go-ethereum/core/rawdb"
11+
"github.com/ethereum/go-ethereum/core/state"
12+
"github.com/ethereum/go-ethereum/core/vm"
13+
"github.com/ethereum/go-ethereum/crypto"
14+
"github.com/ethereum/go-ethereum/params"
15+
"github.com/ethereum/go-ethereum/triedb"
16+
)
17+
18+
// StatelessExecute executes the block contained in the Witness returning the post state root or an error
19+
func StatelessExecute(chainCfg *params.ChainConfig, witness *state.Witness) (root common.Hash, err error) {
20+
rawDb := rawdb.NewMemoryDatabase()
21+
if err := witness.PopulateDB(rawDb); err != nil {
22+
return common.Hash{}, err
23+
}
24+
blob := rawdb.ReadAccountTrieNode(rawDb, nil)
25+
prestateRoot := crypto.Keccak256Hash(blob)
26+
27+
db, err := state.New(prestateRoot, state.NewDatabaseWithConfig(rawDb, triedb.PathDefaults), nil)
28+
if err != nil {
29+
return common.Hash{}, err
30+
}
31+
engine := beacon.New(ethash.NewFaker())
32+
validator := core.NewBlockValidator(chainCfg, nil, engine)
33+
processor := core.NewStateProcessor(chainCfg, nil, engine)
34+
35+
receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}, witness)
36+
if err != nil {
37+
return common.Hash{}, err
38+
}
39+
40+
// compute the state root.
41+
if root, err = validator.ValidateState(witness.Block, db, receipts, usedGas, false); err != nil {
42+
return common.Hash{}, err
43+
}
44+
return root, nil
45+
}
46+
47+
// BuildStatelessProof executes a block, collecting the accessed pre-state into
48+
// a Witness. The RLP-encoded witness is returned.
49+
func BuildStatelessProof(blockHash common.Hash, bc *core.BlockChain) ([]byte, error) {
50+
block := bc.GetBlockByHash(blockHash)
51+
if block == nil {
52+
return nil, fmt.Errorf("non-existent block %x", blockHash)
53+
} else if block.NumberU64() == 0 {
54+
return nil, fmt.Errorf("cannot build a stateless proof of the genesis block")
55+
}
56+
parentHash := block.ParentHash()
57+
parent := bc.GetBlockByHash(parentHash)
58+
if parent == nil {
59+
return nil, fmt.Errorf("block %x parent not present", parentHash)
60+
}
61+
62+
db, err := bc.StateAt(parent.Header().Root)
63+
if err != nil {
64+
return nil, err
65+
}
66+
db.EnableWitnessBuilding()
67+
if bc.Snapshots() != nil {
68+
db.StartPrefetcher("BuildStatelessProof", false)
69+
defer db.StopPrefetcher()
70+
}
71+
stateProcessor := core.NewStateProcessor(bc.Config(), bc, bc.Engine())
72+
_, _, _, err = stateProcessor.Process(block, db, vm.Config{}, nil)
73+
if err != nil {
74+
return nil, err
75+
}
76+
if _, err = db.Commit(block.NumberU64(), true); err != nil {
77+
return nil, err
78+
}
79+
proof := db.Witness()
80+
proof.Block = block
81+
enc, err := proof.EncodeRLP()
82+
if err != nil {
83+
return nil, err
84+
}
85+
return enc, nil
86+
}

core/block_validator.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222

23+
"github.com/ethereum/go-ethereum/common"
2324
"github.com/ethereum/go-ethereum/consensus"
2425
"github.com/ethereum/go-ethereum/core/state"
2526
"github.com/ethereum/go-ethereum/core/types"
@@ -121,28 +122,31 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
121122

122123
// ValidateState validates the various changes that happen after a state transition,
123124
// such as amount of used gas, the receipt roots and the state root itself.
124-
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
125+
// If validateRemoteRoot is false, the provided block header's root is not asserted to be equal to the one computed from
126+
// execution.
127+
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRemoteRoot bool) (root common.Hash, err error) {
125128
header := block.Header()
126129
if block.GasUsed() != usedGas {
127-
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
130+
return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
128131
}
129132
// Validate the received block's bloom with the one derived from the generated receipts.
130133
// For valid blocks this should always validate to true.
131134
rbloom := types.CreateBloom(receipts)
132135
if rbloom != header.Bloom {
133-
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
136+
return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
134137
}
135138
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
136139
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
137140
if receiptSha != header.ReceiptHash {
138-
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
141+
return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
139142
}
140-
// Validate the state root against the received state root and throw
141-
// an error if they don't match.
142-
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
143-
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
143+
// Compute the state root and if enabled, check it against the
144+
// received state root and throw an error if they don't match.
145+
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
146+
if checkRemoteRoot && header.Root != root {
147+
return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
144148
}
145-
return nil
149+
return root, nil
146150
}
147151

148152
// CalcGasLimit computes the gas limit of the next block after parent. It aims

core/blockchain.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,15 +1916,15 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
19161916

19171917
// Process block using the parent state as reference point
19181918
pstart := time.Now()
1919-
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
1919+
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil)
19201920
if err != nil {
19211921
bc.reportBlock(block, receipts, err)
19221922
return nil, err
19231923
}
19241924
ptime := time.Since(pstart)
19251925

19261926
vstart := time.Now()
1927-
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
1927+
if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil {
19281928
bc.reportBlock(block, receipts, err)
19291929
return nil, err
19301930
}

core/blockchain_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
163163
if err != nil {
164164
return err
165165
}
166-
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
166+
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, nil)
167167
if err != nil {
168168
blockchain.reportBlock(block, receipts, err)
169169
return err
170170
}
171-
err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas)
171+
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true)
172172
if err != nil {
173173
blockchain.reportBlock(block, receipts, err)
174174
return err

core/chain_makers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int {
9999
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
100100
b.header.ParentBeaconRoot = &root
101101
var (
102-
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
102+
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, nil)
103103
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
104104
)
105105
ProcessBeaconBlockRoot(root, vmenv, b.statedb)

core/evm.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package core
1919
import (
2020
"math/big"
2121

22+
"github.com/ethereum/go-ethereum/core/state"
23+
2224
"github.com/ethereum/go-ethereum/common"
2325
"github.com/ethereum/go-ethereum/consensus"
2426
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
@@ -38,8 +40,9 @@ type ChainContext interface {
3840
GetHeader(common.Hash, uint64) *types.Header
3941
}
4042

41-
// NewEVMBlockContext creates a new context for use in the EVM.
42-
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
43+
// NewEVMBlockContext creates a new context for use in the EVM. If witness is non-nil, the context sources block hashes
44+
// for the BLOCKHASH opcode from the witness.
45+
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext {
4346
var (
4447
beneficiary common.Address
4548
baseFee *big.Int
@@ -62,10 +65,18 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
6265
if header.Difficulty.Sign() == 0 {
6366
random = &header.MixDigest
6467
}
68+
var getHash vm.GetHashFunc
69+
if witness != nil {
70+
getHash = func(n uint64) common.Hash {
71+
return witness.BlockHash(n)
72+
}
73+
} else {
74+
getHash = GetHashFn(header, chain)
75+
}
6576
return vm.BlockContext{
6677
CanTransfer: CanTransfer,
6778
Transfer: Transfer,
68-
GetHash: GetHashFn(header, chain),
79+
GetHash: getHash,
6980
Coinbase: beneficiary,
7081
BlockNumber: new(big.Int).Set(header.Number),
7182
Time: header.Time,

core/state/database.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ type Trie interface {
125125
// be created with new root and updated trie database for following usage
126126
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
127127

128+
// AccessList returns a map of path->blob containing all trie nodes that have
129+
// been accessed.
130+
AccessList() map[string][]byte
131+
128132
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
129133
// starts at the key after the given start key. And error will be returned
130134
// if fails to create node iterator.

core/state/state_object.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,6 @@ func (s *stateObject) finalise() {
323323
//
324324
// It assumes all the dirty storage slots have been finalized before.
325325
func (s *stateObject) updateTrie() (Trie, error) {
326-
// Short circuit if nothing changed, don't bother with hashing anything
327-
if len(s.uncommittedStorage) == 0 {
328-
return s.trie, nil
329-
}
330326
// Retrieve a pretecher populated trie, or fall back to the database
331327
tr := s.getPrefetchedTrie()
332328
if tr != nil {
@@ -341,6 +337,14 @@ func (s *stateObject) updateTrie() (Trie, error) {
341337
return nil, err
342338
}
343339
}
340+
// Short circuit if nothing changed, don't bother with hashing anything.
341+
//
342+
// We only quit after the prefetched trie is potentially resolved above
343+
// because, when building a stateless witness we will need to collect
344+
//storage access witnesses from the object's trie when we commit it.
345+
if len(s.uncommittedStorage) == 0 {
346+
return s.trie, nil
347+
}
344348
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
345349
// in circumstances similar to the following:
346350
//
@@ -446,7 +450,9 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
446450
//
447451
// Note, commit may run concurrently across all the state objects. Do not assume
448452
// thread-safe access to the statedb.
449-
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
453+
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]byte, error) {
454+
var al map[string][]byte
455+
450456
// commit the account metadata changes
451457
op := &accountUpdate{
452458
address: s.address,
@@ -468,12 +474,18 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
468474
if len(op.storages) == 0 {
469475
// nothing changed, don't bother to commit the trie
470476
s.origin = s.data.Copy()
471-
return op, nil, nil
477+
if s.trie != nil && !s.trie.IsVerkle() {
478+
al = s.trie.AccessList()
479+
}
480+
return op, nil, al, nil
472481
}
473482
root, nodes := s.trie.Commit(false)
474483
s.data.Root = root
475484
s.origin = s.data.Copy()
476-
return op, nodes, nil
485+
if !s.trie.IsVerkle() {
486+
al = s.trie.AccessList()
487+
}
488+
return op, nodes, al, nil
477489
}
478490

479491
// AddBalance adds amount to s's balance.

0 commit comments

Comments
 (0)