all: add stateless verifications

This commit is contained in:
Jared Wasinger 2024-05-19 21:16:11 -07:00 committed by Péter Szilágyi
parent c10ac4f48f
commit d86b650e54
34 changed files with 876 additions and 70 deletions

View file

@ -0,0 +1,86 @@
package stateless
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
)
// StatelessExecute executes the block contained in the Witness returning the post state root or an error
func StatelessExecute(chainCfg *params.ChainConfig, witness *state.Witness) (root common.Hash, err error) {
rawDb := rawdb.NewMemoryDatabase()
if err := witness.PopulateDB(rawDb); err != nil {
return common.Hash{}, err
}
blob := rawdb.ReadAccountTrieNode(rawDb, nil)
prestateRoot := crypto.Keccak256Hash(blob)
db, err := state.New(prestateRoot, state.NewDatabaseWithConfig(rawDb, triedb.PathDefaults), nil)
if err != nil {
return common.Hash{}, err
}
engine := beacon.New(ethash.NewFaker())
validator := core.NewBlockValidator(chainCfg, nil, engine)
processor := core.NewStateProcessor(chainCfg, nil, engine)
receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}, witness)
if err != nil {
return common.Hash{}, err
}
// compute the state root.
if root, err = validator.ValidateState(witness.Block, db, receipts, usedGas, false); err != nil {
return common.Hash{}, err
}
return root, nil
}
// BuildStatelessProof executes a block, collecting the accessed pre-state into
// a Witness. The RLP-encoded witness is returned.
func BuildStatelessProof(blockHash common.Hash, bc *core.BlockChain) ([]byte, error) {
block := bc.GetBlockByHash(blockHash)
if block == nil {
return nil, fmt.Errorf("non-existent block %x", blockHash)
} else if block.NumberU64() == 0 {
return nil, fmt.Errorf("cannot build a stateless proof of the genesis block")
}
parentHash := block.ParentHash()
parent := bc.GetBlockByHash(parentHash)
if parent == nil {
return nil, fmt.Errorf("block %x parent not present", parentHash)
}
db, err := bc.StateAt(parent.Header().Root)
if err != nil {
return nil, err
}
db.EnableWitnessBuilding()
if bc.Snapshots() != nil {
db.StartPrefetcher("BuildStatelessProof", false)
defer db.StopPrefetcher()
}
stateProcessor := core.NewStateProcessor(bc.Config(), bc, bc.Engine())
_, _, _, err = stateProcessor.Process(block, db, vm.Config{}, nil)
if err != nil {
return nil, err
}
if _, err = db.Commit(block.NumberU64(), true); err != nil {
return nil, err
}
proof := db.Witness()
proof.Block = block
enc, err := proof.EncodeRLP()
if err != nil {
return nil, err
}
return enc, nil
}

View file

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@ -121,28 +122,31 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// ValidateState validates the various changes that happen after a state transition,
// such as amount of used gas, the receipt roots and the state root itself.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
// If validateRemoteRoot is false, the provided block header's root is not asserted to be equal to the one computed from
// execution.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRemoteRoot bool) (root common.Hash, err error) {
header := block.Header()
if block.GasUsed() != usedGas {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}
// Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true.
rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
}
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
}
// Validate the state root against the received state root and throw
// an error if they don't match.
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())
// Compute the state root and if enabled, check it against the
// received state root and throw an error if they don't match.
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
if checkRemoteRoot && header.Root != root {
return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
}
return nil
return root, nil
}
// CalcGasLimit computes the gas limit of the next block after parent. It aims

View file

@ -1916,7 +1916,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
// Process block using the parent state as reference point
pstart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil)
if err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
@ -1924,7 +1924,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
ptime := time.Since(pstart)
vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}

View file

@ -163,12 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil {
return err
}
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, nil)
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
}
err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas)
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true)
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err

View file

@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int {
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
b.header.ParentBeaconRoot = &root
var (
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, nil)
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
)
ProcessBeaconBlockRoot(root, vmenv, b.statedb)

View file

@ -19,6 +19,8 @@ package core
import (
"math/big"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
@ -38,8 +40,9 @@ type ChainContext interface {
GetHeader(common.Hash, uint64) *types.Header
}
// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
// NewEVMBlockContext creates a new context for use in the EVM. If witness is non-nil, the context sources block hashes
// for the BLOCKHASH opcode from the witness.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext {
var (
beneficiary common.Address
baseFee *big.Int
@ -62,10 +65,18 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.Difficulty.Sign() == 0 {
random = &header.MixDigest
}
var getHash vm.GetHashFunc
if witness != nil {
getHash = func(n uint64) common.Hash {
return witness.BlockHash(n)
}
} else {
getHash = GetHashFn(header, chain)
}
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
GetHash: getHash,
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,

View file

@ -125,6 +125,10 @@ type Trie interface {
// be created with new root and updated trie database for following usage
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
// AccessList returns a map of path->blob containing all trie nodes that have
// been accessed.
AccessList() map[string][]byte
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned
// if fails to create node iterator.

View file

@ -323,10 +323,6 @@ func (s *stateObject) finalise() {
//
// It assumes all the dirty storage slots have been finalized before.
func (s *stateObject) updateTrie() (Trie, error) {
// Short circuit if nothing changed, don't bother with hashing anything
if len(s.uncommittedStorage) == 0 {
return s.trie, nil
}
// Retrieve a pretecher populated trie, or fall back to the database
tr := s.getPrefetchedTrie()
if tr != nil {
@ -341,6 +337,14 @@ func (s *stateObject) updateTrie() (Trie, error) {
return nil, err
}
}
// Short circuit if nothing changed, don't bother with hashing anything.
//
// We only quit after the prefetched trie is potentially resolved above
// because, when building a stateless witness we will need to collect
//storage access witnesses from the object's trie when we commit it.
if len(s.uncommittedStorage) == 0 {
return s.trie, nil
}
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
//
@ -446,7 +450,9 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]byte, error) {
var al map[string][]byte
// commit the account metadata changes
op := &accountUpdate{
address: s.address,
@ -468,12 +474,18 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
if len(op.storages) == 0 {
// nothing changed, don't bother to commit the trie
s.origin = s.data.Copy()
return op, nil, nil
if s.trie != nil && !s.trie.IsVerkle() {
al = s.trie.AccessList()
}
return op, nil, al, nil
}
root, nodes := s.trie.Commit(false)
s.data.Root = root
s.origin = s.data.Copy()
return op, nodes, nil
if !s.trie.IsVerkle() {
al = s.trie.AccessList()
}
return op, nodes, al, nil
}
// AddBalance adds amount to s's balance.

374
core/state/state_witness.go Normal file
View file

@ -0,0 +1,374 @@
package state
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"sort"
"sync"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
)
// A Witness encompasses a block and all state necessary to compute the
// post-state root.
type Witness struct {
Block *types.Block
blockHashes map[uint64]common.Hash
codes map[common.Hash][]byte
root common.Hash
tries map[common.Hash]map[string][]byte
triesLock sync.Mutex
}
// BlockHash returns the block hash corresponding to an ancestor block between 1-256 blocks old.
func (w *Witness) BlockHash(num uint64) common.Hash {
return w.blockHashes[num]
}
// Root returns the post-state root of the witness if it has been computed or 0x00..0 if not.
func (w *Witness) Root() common.Hash {
return w.root
}
// rlpWitness is the encoding structure for a Witness
type rlpWitness struct {
EncBlock []byte
Root common.Hash
Owners []common.Hash
TriesPaths [][]string
TriesNodes [][][]byte
BlockNums []uint64
BlockHashes []common.Hash
Codes [][]byte
}
func (e *rlpWitness) toWitness() (*Witness, error) {
res := NewWitness(e.Root)
if err := rlp.DecodeBytes(e.EncBlock, &res.Block); err != nil {
return nil, err
}
for _, code := range e.Codes {
codeHash := crypto.Keccak256Hash(code)
if _, ok := res.codes[codeHash]; ok {
return nil, errors.New("duplicate code in witness")
}
res.codes[codeHash] = code
}
for i, owner := range e.Owners {
trieNodes := make(map[string][]byte)
for j := 0; j < len(e.TriesPaths[i]); j++ {
trieNodes[e.TriesPaths[i][j]] = e.TriesNodes[i][j]
}
res.tries[owner] = trieNodes
}
for i, blockNum := range e.BlockNums {
res.blockHashes[blockNum] = e.BlockHashes[i]
}
return res, nil
}
// DecodeWitnessRLP decodes a byte slice into a witness object.
func DecodeWitnessRLP(b []byte) (*Witness, error) {
var res rlpWitness
if err := rlp.DecodeBytes(b, &res); err != nil {
return nil, err
}
return res.toWitness()
}
// EncodeRLP encodes a witness object into bytes. The Witness' state root is
// zeroed before the encoding. The encoding is not deterministic (the result
// can differ for the same Witness)
func (w *Witness) EncodeRLP() ([]byte, error) {
var encWit rlpWitness
var encBlock bytes.Buffer
if err := w.Block.EncodeRLPWithZeroRoot(&encBlock); err != nil {
return nil, err
}
encWit.EncBlock = encBlock.Bytes()
for owner, trie := range w.tries {
encWit.Owners = append(encWit.Owners, owner)
var ownerPaths []string
var ownerNodes [][]byte
for path, node := range trie {
ownerPaths = append(ownerPaths, path)
ownerNodes = append(ownerNodes, node)
}
encWit.TriesPaths = append(encWit.TriesPaths, ownerPaths)
encWit.TriesNodes = append(encWit.TriesNodes, ownerNodes)
}
for _, code := range w.codes {
encWit.Codes = append(encWit.Codes, code)
}
for blockNum, blockHash := range w.blockHashes {
encWit.BlockNums = append(encWit.BlockNums, blockNum)
encWit.BlockHashes = append(encWit.BlockHashes, blockHash)
}
res, err := rlp.EncodeToBytes(&encWit)
if err != nil {
return nil, err
}
return res, nil
}
// addAccessList associates a map of RLP-encoded trie nodes keyed by path to
// an owner in the witness. the witness takes ownership of the passed map. It
// is safe to call this method concurrently.
func (w *Witness) addAccessList(owner common.Hash, newTrieNodes map[string][]byte) {
var trie map[string][]byte
if len(newTrieNodes) == 0 {
return
}
w.triesLock.Lock()
defer w.triesLock.Unlock()
trie, ok := w.tries[owner]
if !ok {
trie = make(map[string][]byte)
w.tries[owner] = trie
}
for path, node := range newTrieNodes {
trie[path] = node
}
}
// AddBlockHash adds a block hash/number to the witness
func (w *Witness) AddBlockHash(hash common.Hash, num uint64) {
w.blockHashes[num] = hash
}
// AddCode associates a hash with code in the Witness.
// The Witness takes ownership over the passed code slice.
func (w *Witness) AddCode(hash common.Hash, code []byte) {
if hash == types.EmptyCodeHash || hash == (common.Hash{}) || len(code) == 0 {
return
}
w.codes[hash] = code
}
// Summary prints a human-readable summary containing the total size of the
// witness and the sizes of the underlying components
func (w *Witness) Summary() string {
b := new(bytes.Buffer)
xx, err := rlp.EncodeToBytes(w.Block)
if err != nil {
panic(err)
}
totBlock := len(xx)
yy, _ := w.EncodeRLP()
totWit := len(yy)
totCode := 0
for _, c := range w.codes {
totCode += len(c)
}
totNodes := 0
totPaths := 0
nodePathCount := 0
for _, ownerPaths := range w.tries {
for path, node := range ownerPaths {
nodePathCount++
totNodes += len(node)
totPaths += len(path)
}
}
fmt.Fprintf(b, "%4d hashes: %v\n", len(w.blockHashes), common.StorageSize(len(w.blockHashes)*32))
fmt.Fprintf(b, "%4d owners: %v\n", len(w.tries), common.StorageSize(len(w.tries)*32))
fmt.Fprintf(b, "%4d nodes: %v\n", nodePathCount, common.StorageSize(totNodes))
fmt.Fprintf(b, "%4d paths: %v\n", nodePathCount, common.StorageSize(totPaths))
fmt.Fprintf(b, "%4d codes: %v\n", len(w.codes), common.StorageSize(totCode))
fmt.Fprintf(b, "%4d codeHashes: %v\n", len(w.codes), common.StorageSize(len(w.codes)*32))
fmt.Fprintf(b, "block (%4d txs): %v\n", len(w.Block.Transactions()), common.StorageSize(totBlock))
fmt.Fprintf(b, "Total size: %v\n ", common.StorageSize(totWit))
return b.String()
}
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
// is never mutated by Witness
func (w *Witness) Copy() *Witness {
var res Witness
res.Block = w.Block
for blockNr, blockHash := range w.blockHashes {
res.blockHashes[blockNr] = blockHash
}
for codeHash, code := range w.codes {
cpy := make([]byte, len(code))
copy(cpy, code)
res.codes[codeHash] = cpy
}
res.root = w.root
for owner, owned := range w.tries {
res.tries[owner] = make(map[string][]byte)
for path, node := range owned {
cpy := make([]byte, len(node))
copy(cpy, node)
res.tries[owner][path] = cpy
}
}
return &res
}
// sortedWitness encodes returns an rlpWitness where hash-map items are sorted lexicographically by key
// in the encoder object to ensure that the encoded bytes are always the same for a given witness.
func (w *Witness) sortedWitness() *rlpWitness {
var (
sortedCodeHashes []common.Hash
owners []common.Hash
ownersPaths [][]string
ownersNodes [][][]byte
blockNrs []uint64
blockHashes []common.Hash
codeHashes []common.Hash
codes [][]byte
)
for key := range w.codes {
sortedCodeHashes = append(sortedCodeHashes, key)
}
sort.Slice(sortedCodeHashes, func(i, j int) bool {
return bytes.Compare(sortedCodeHashes[i][:], sortedCodeHashes[j][:]) > 0
})
// sort the list of owners
for owner := range w.tries {
owners = append(owners, owner)
}
sort.Slice(owners, func(i, j int) bool {
return bytes.Compare(owners[i][:], owners[j][:]) > 0
})
// sort the trie nodes of each trie by path
for _, owner := range owners {
nodes := w.tries[owner]
var ownerPaths []string
for path := range nodes {
ownerPaths = append(ownerPaths, path)
}
sort.Strings(ownerPaths)
var ownerNodes [][]byte
for _, path := range ownerPaths {
ownerNodes = append(ownerNodes, nodes[path])
}
ownersPaths = append(ownersPaths, ownerPaths)
ownersNodes = append(ownersNodes, ownerNodes)
}
for blockNr, blockHash := range w.blockHashes {
blockNrs = append(blockNrs, blockNr)
blockHashes = append(blockHashes, blockHash)
}
for codeHash := range w.codes {
codeHashes = append(codeHashes, codeHash)
}
sort.Slice(codeHashes, func(i, j int) bool {
return bytes.Compare(codeHashes[i][:], codeHashes[j][:]) > 0
})
for _, codeHash := range codeHashes {
codes = append(codes, w.codes[codeHash])
}
encBlock, _ := rlp.EncodeToBytes(w.Block)
return &rlpWitness{
EncBlock: encBlock,
Root: common.Hash{},
Owners: owners,
TriesPaths: ownersPaths,
TriesNodes: ownersNodes,
BlockNums: blockNrs,
BlockHashes: blockHashes,
Codes: codes,
}
}
// PrettyPrint displays the contents of a witness object in a human-readable format to standard output.
func (w *Witness) PrettyPrint() string {
sorted := w.sortedWitness()
b := new(bytes.Buffer)
fmt.Fprintf(b, "block: %+v\n", w.Block)
fmt.Fprintf(b, "root: %x\n", sorted.Root)
fmt.Fprint(b, "owners:\n")
for i, owner := range sorted.Owners {
if owner == (common.Hash{}) {
fmt.Fprintf(b, "\troot:\n")
} else {
fmt.Fprintf(b, "\t%x:\n", owner)
}
ownerPaths := sorted.TriesPaths[i]
ownerNodes := sorted.TriesNodes[i]
for j, path := range ownerPaths {
fmt.Fprintf(b, "\t\t%x:%x\n", []byte(path), ownerNodes[j])
}
}
fmt.Fprintf(b, "block hashes:\n")
for i, blockNum := range sorted.BlockNums {
blockHash := sorted.BlockHashes[i]
fmt.Fprintf(b, "\t%d:%x\n", blockNum, blockHash)
}
fmt.Fprintf(b, "codes:\n")
for _, code := range sorted.Codes {
hash := crypto.Keccak256Hash(code)
fmt.Fprintf(b, "\t%x:%x\n", hash, code)
}
return b.String()
}
// Hash returns the sha256 hash of a Witness
func (w *Witness) Hash() common.Hash {
res, err := rlp.EncodeToBytes(w.sortedWitness())
if err != nil {
panic(err)
}
return sha256.Sum256(res[:])
}
// NewWitness returns a new Witness object.
func NewWitness(root common.Hash) *Witness {
return &Witness{
Block: nil,
blockHashes: make(map[uint64]common.Hash),
codes: make(map[common.Hash][]byte),
root: root,
tries: make(map[common.Hash]map[string][]byte),
}
}
// PopulateDB imports tries,codes and block hashes from the witness
// into the specified path-based backing db.
func (w *Witness) PopulateDB(db ethdb.Database) error {
batch := db.NewBatch()
for owner, nodes := range w.tries {
for path, node := range nodes {
if owner == (common.Hash{}) {
rawdb.WriteAccountTrieNode(batch, []byte(path), node)
} else {
rawdb.WriteStorageTrieNode(batch, owner, []byte(path), node)
}
}
}
for codeHash, code := range w.codes {
rawdb.WriteCode(batch, codeHash, code)
}
if err := batch.Write(); err != nil {
return err
}
return nil
}

View file

@ -105,7 +105,7 @@ type StateDB struct {
// resurrection. The account value is tracked as the original value
// before the transition. This map is populated at the transaction
// boundaries.
stateObjectsDestruct map[common.Address]*types.StateAccount
stateObjectsDestruct map[common.Address]*stateObject
// This map tracks the account mutations that occurred during the
// transition. Uncommitted mutations belonging to the same account
@ -163,6 +163,7 @@ type StateDB struct {
StorageUpdated atomic.Int64
AccountDeleted int
StorageDeleted atomic.Int64
witness *Witness
}
// New creates a new state from a given trie.
@ -177,7 +178,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
originalRoot: root,
snaps: snaps,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
stateObjectsDestruct: make(map[common.Address]*stateObject),
mutations: make(map[common.Address]*mutation),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
@ -582,7 +583,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
start := time.Now()
acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes()))
s.SnapshotAccountReads += time.Since(start)
if err == nil {
if acc == nil {
return nil
@ -703,6 +703,9 @@ func (s *StateDB) Copy() *StateDB {
snaps: s.snaps,
snap: s.snap,
}
if s.witness != nil {
state.witness = s.witness.Copy()
}
// Deep copy cached state objects.
for addr, obj := range s.stateObjects {
state.stateObjects[addr] = obj.deepCopy(state)
@ -755,6 +758,12 @@ func (s *StateDB) RevertToSnapshot(revid int) {
s.validRevisions = s.validRevisions[:idx]
}
// EnableWitnessRecording configures the StateDB to build a stateless block
// witness this must becalled before starting prefetchers or applying state changes
func (s *StateDB) EnableWitnessBuilding() {
s.witness = NewWitness(s.originalRoot)
}
// GetRefund returns the current value of the refund counter.
func (s *StateDB) GetRefund() uint64 {
return s.refund
@ -788,7 +797,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// set indefinitely). Note only the first occurred self-destruct
// event is tracked.
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
s.stateObjectsDestruct[obj.address] = obj.origin
s.stateObjectsDestruct[obj.address] = obj
}
} else {
obj.finalise()
@ -808,6 +817,46 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
s.clearJournalAndRefund()
}
// collectNonCommittedTrieAccessLists is called if witness building is enabled.
// It collects witness access lists for accounts that read from storage, and
// whose tries are not going to be hashed/committed (accounts with non-mutated
// storage and self-destructed accounts).
func (s *StateDB) collectNonCommittedTrieAccessLists() {
collectAccount := func(obj *stateObject) {
if obj.Root() == types.EmptyRootHash {
// TODO: unsure if this explicit check is needed
return
}
tr := obj.getPrefetchedTrie()
if tr == nil {
if obj.trie == nil {
// object storage was never read from
return
}
// either the snapshot is not enabled or the object was not present
tr = obj.trie
}
al := tr.AccessList()
if al == nil {
panic("impossible case: storage trie is non-empty and known to have been read from but a nil access list was returned")
}
if len(al) == 0 {
panic("impossible case: storage trie is non-empty and known to have been read from but a zero-length access list was returned")
}
s.witness.addAccessList(obj.addrHash, al)
}
for _, obj := range s.stateObjectsDestruct {
collectAccount(obj)
}
for _, obj := range s.stateObjects {
if _, ok := s.mutations[obj.address]; ok {
continue
}
collectAccount(obj)
}
}
// IntermediateRoot computes the current root hash of the state trie.
// It is called in between transactions to get the root hash that
// goes into transaction receipts.
@ -824,6 +873,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
}()
}
// Process all storage updates concurrently. The state object update root
// method will internally call a blocking trie fetch from the prefetcher,
// so there's no need to explicitly wait for the prefetchers to finish.
@ -849,6 +899,12 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
return nil
})
}
if s.witness != nil {
// if witness building is enabled, collect storage access lists from
// tries that will not be committed: accounts with non-mutated storage
// and self-destructed accounts.
s.collectNonCommittedTrieAccessLists()
}
workers.Wait()
s.StorageUpdates += time.Since(start)
@ -1060,7 +1116,8 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno
buf = crypto.NewKeccakState()
deletes = make(map[common.Hash]*accountDelete)
)
for addr, prev := range s.stateObjectsDestruct {
for addr, obj := range s.stateObjectsDestruct {
prev := obj.origin
// The account was non-existent, and it's marked as destructed in the scope
// of block. It can be either case (a) or (b) and will be interpreted as
// null->null state transition.
@ -1168,6 +1225,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
root common.Hash
workers errgroup.Group
)
// Schedule the account trie first since that will be the biggest, so give
// it the most time to crunch.
//
@ -1178,8 +1236,25 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
// Obviously it's not an end of the world issue, just something the original
// code didn't anticipate for.
workers.Go(func() error {
var (
newroot common.Hash
set *trienode.NodeSet
al map[string][]byte
)
// Write the account trie changes, measuring the amount of wasted time
newroot, set := s.trie.Commit(true)
if s.witness != nil {
newroot, set = s.trie.Commit(true)
al = s.trie.AccessList()
if al == nil {
panic("this should only happen if starting with completely empty state")
}
if len(al) == 0 {
panic("blocks without state changes not possible")
}
s.witness.addAccessList(common.Hash{}, al)
} else {
newroot, set = s.trie.Commit(true)
}
root = newroot
if err := merge(set); err != nil {
@ -1207,7 +1282,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
// Run the storage updates concurrently to one another
workers.Go(func() error {
// Write any storage changes in the state object to its storage trie
update, set, err := obj.commit()
update, set, al, err := obj.commit()
if err != nil {
return err
}
@ -1215,9 +1290,12 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
return err
}
lock.Lock()
defer lock.Unlock()
updates[obj.addrHash] = update
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
lock.Unlock()
if s.witness != nil && len(al) > 0 {
s.witness.addAccessList(obj.addrHash, al)
}
return nil
})
}
@ -1239,7 +1317,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
// Clear all internal flags and update state root at the end.
s.mutations = make(map[common.Address]*mutation)
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
s.stateObjectsDestruct = make(map[common.Address]*stateObject)
origin := s.originalRoot
s.originalRoot = root
@ -1294,6 +1372,12 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
return ret, err
}
// Witness returns a block witness object being constructed or nil if the
// StateDB instance is not configured to record stateless witnesses.
func (s *StateDB) Witness() *Witness {
return s.witness
}
// Commit writes the state mutations into the configured data stores.
//
// Once the state is committed, tries cached in stateDB (including account

View file

@ -51,7 +51,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
var (
header = block.Header()
gaspool = new(GasPool).AddGas(block.GasLimit())
blockContext = NewEVMBlockContext(header, p.bc, nil)
blockContext = NewEVMBlockContext(header, p.bc, nil, nil)
evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
signer = types.MakeSigner(p.config, header.Number, header.Time)
)

View file

@ -36,20 +36,45 @@ import (
//
// StateProcessor implements Processor.
type StateProcessor struct {
config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for block rewards
config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for block rewards
statelessChainCtx ChainContext
}
// NewStateProcessor initialises a new StateProcessor.
// NewStateProcessor initialises a new StateProcessor. If the provided
// Blockchain is nil, stateless execution mode is enabled.
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
return &StateProcessor{
config: config,
bc: bc,
engine: engine,
if bc != nil {
return &StateProcessor{
config: config,
bc: bc,
engine: engine,
}
} else {
return &StateProcessor{
config: config,
engine: engine,
statelessChainCtx: &statelessChainContext{engine},
bc: &BlockChain{
chainConfig: config,
engine: engine,
},
}
}
}
type statelessChainContext struct {
engine consensus.Engine
}
func (s *statelessChainContext) Engine() consensus.Engine {
return s.engine
}
func (s *statelessChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
panic("not implemented")
}
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
@ -57,7 +82,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
@ -73,10 +98,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
misc.ApplyDAOHardFork(statedb)
}
var (
context = NewEVMBlockContext(header, p.bc, nil)
vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
context vm.BlockContext
signer = types.MakeSigner(p.config, header.Number, header.Time)
)
context = NewEVMBlockContext(header, p.bc, nil, witness)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
}
@ -177,7 +203,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
return nil, err
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
blockContext := NewEVMBlockContext(header, bc, author, statedb.Witness())
txContext := NewEVMTxContext(msg)
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)

View file

@ -19,6 +19,8 @@ package core
import (
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@ -33,7 +35,7 @@ type Validator interface {
// ValidateState validates the given statedb and optionally the receipts and
// gas used.
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, rootCheck bool) (common.Hash, error)
}
// Prefetcher is an interface for pre-caching transaction signatures and state.
@ -49,5 +51,5 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error)
}

View file

@ -174,6 +174,18 @@ type Body struct {
Withdrawals []*Withdrawal `rlp:"optional"`
}
// EncodeRLPWithZeroRoot encodes a block (with header state root set to 0x00...0) to RLP
func (b *Block) EncodeRLPWithZeroRoot(w io.Writer) error {
old := b.header.Root
b.header.Root = common.Hash{}
err := b.EncodeRLP(w)
b.header.Root = old
if err != nil {
return err
}
return nil
}
// Block represents an Ethereum block.
//
// Note the Block type tries to be 'immutable', and contains certain caches that rely

View file

@ -231,6 +231,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
code := evm.StateDB.GetCode(addr)
codeCopy := make([]byte, len(code))
copy(codeCopy[:], code[:])
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCodeHash(addr), codeCopy)
}
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
@ -298,6 +303,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
}
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
@ -345,6 +353,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
}
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
@ -400,6 +411,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
}
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally

View file

@ -340,6 +340,12 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek()
address := slot.Bytes20()
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
code := interpreter.evm.StateDB.GetCode(address)
codeHash := interpreter.evm.StateDB.GetCodeHash(address)
witness.AddCode(codeHash, code)
}
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
return nil, nil
}
@ -378,6 +384,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
uint64CodeOffset = math.MaxUint64
}
addr := common.Address(a.Bytes20())
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
hash := interpreter.evm.StateDB.GetCodeHash(addr)
code := interpreter.evm.StateDB.GetCode(addr)
witness.AddCode(hash, code)
}
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
@ -416,6 +427,11 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if interpreter.evm.StateDB.Empty(address) {
slot.Clear()
} else {
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
hash := interpreter.evm.StateDB.GetCodeHash(address)
code := interpreter.evm.StateDB.GetCode(address)
witness.AddCode(hash, code)
}
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
}
return nil, nil
@ -443,7 +459,11 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
lower = upper - 256
}
if num64 >= lower && num64 < upper {
num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes())
res := interpreter.evm.Context.GetHash(num64)
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
witness.AddBlockHash(res, num64)
}
num.SetBytes(res[:])
} else {
num.Clear()
}

View file

@ -19,6 +19,8 @@ package vm
import (
"math/big"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
@ -87,6 +89,8 @@ type StateDB interface {
AddLog(*types.Log)
AddPreimage(common.Hash, []byte)
Witness() *state.Witness
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM

View file

@ -258,7 +258,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
if blockCtx != nil {
context = *blockCtx
} else {
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, nil)
}
return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig)
}

View file

@ -22,6 +22,8 @@ import (
"fmt"
"time"
"github.com/ethereum/go-ethereum/cmd/utils/stateless"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
@ -443,3 +445,17 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
}
return api.eth.blockchain.GetTrieFlushInterval().String(), nil
}
// BuildStatelessProof executes a block, collecting the accessed pre-state into
// a Witness. The RLP-encoded witness is returned.
func (api *DebugAPI) BuildStatelessProof(numOrHash rpc.BlockNumberOrHash) ([]byte, error) {
var blockHash common.Hash
if numOrHash.BlockNumber != nil {
number := numOrHash.BlockNumber.Int64()
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
blockHash = block.Hash()
} else {
blockHash = *numOrHash.BlockHash
}
return stateless.BuildStatelessProof(blockHash, api.eth.blockchain)
}

View file

@ -218,7 +218,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
// Assemble the call and the call context
var (
msgContext = core.NewEVMTxContext(call)
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, nil)
dirtyState = opts.State.Copy()
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})

View file

@ -146,7 +146,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next)
}
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil)
if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}
@ -235,7 +235,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
}
// Insert parent beacon block root in the state as per EIP-4788.
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
}
@ -248,7 +248,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
// Assemble the transaction call message and return if the requested offset
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil)
if idx == txIndex {
return tx, context, statedb, release, nil
}

View file

@ -266,7 +266,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
for task := range taskCh {
var (
signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil)
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, nil)
)
// Trace all the transactions contained within
for i, tx := range task.block.Transactions() {
@ -378,7 +378,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
// Insert block's parent beacon block root in the state
// as per EIP-4788.
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
}
@ -527,7 +527,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
roots []common.Hash
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
@ -605,7 +605,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
var (
txs = block.Transactions()
blockHash = block.Hash()
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
results = make([]*txTraceResult, len(txs))
)
@ -665,7 +665,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
// as the GetHash function of BlockContext is not safe for
// concurrent use.
// See: https://github.com/ethereum/go-ethereum/issues/29114
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
@ -678,7 +678,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
// Feed the transactions into the tracers and return
var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
txloop:
for i, tx := range txs {
// Send the trace task over for execution
@ -755,7 +755,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
dumps []string
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
canon = true
)
// Check if there are any overrides: the caller may wish to enable a future
@ -935,7 +935,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
}
defer release()
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
// Apply the customization rules if required.
if config != nil {
if err := config.StateOverrides.Apply(statedb); err != nil {

View file

@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
for idx, tx := range block.Transactions() {
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
context := core.NewEVMBlockContext(block.Header(), b.chain, nil, nil)
if idx == txIndex {
return tx, context, statedb, release, nil
}

View file

@ -1110,7 +1110,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
defer cancel()
// Get a new instance of the EVM.
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, nil)
if blockOverrides != nil {
blockOverrides.Apply(&blockCtx)
}

View file

@ -569,7 +569,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state
vmConfig = b.chain.GetVMConfig()
}
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(header, b.chain, nil)
context := core.NewEVMBlockContext(header, b.chain, nil, nil)
if blockContext != nil {
context = *blockContext
}

View file

@ -196,7 +196,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
return nil, err
}
if header.ParentBeaconRoot != nil {
context := core.NewEVMBlockContext(header, miner.chain, nil)
context := core.NewEVMBlockContext(header, miner.chain, nil, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
}

View file

@ -18,12 +18,94 @@ package tests
import (
"math/rand"
"runtime"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
)
func TestStatelessBlockchain(t *testing.T) {
bt := new(testMatcher)
// These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
// no longer delete "leftover storage" when deploying a contract.
bt.skipLoad(`^GeneralStateTests/stSStoreTest/InitCollision\.json`)
bt.skipLoad(`^GeneralStateTests/stRevertTest/RevertInCreateInInit\.json`)
bt.skipLoad(`^GeneralStateTests/stExtCodeHash/dynamicAccountOverwriteEmpty\.json`)
bt.skipLoad(`^GeneralStateTests/stCreate2/create2collisionStorage\.json`)
bt.skipLoad(`^GeneralStateTests/stCreate2/RevertInCreateInInitCreate2\.json`)
// this test imports a forked chain. The witness builder API receives a block by number
// loading it from the chain. So it fails to properly source the forked chain block,
// erroneously using the one from the main chain (hence the state root mismatch).
bt.skipLoad(`^InvalidBlocks/bcMultiChainTest/UncleFromSideChain\.json`)
// Skip random failures due to selfish mining test
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
// Skip random failures due to selfish mining test
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
// Slow tests
bt.slow(`.*bcExploitTest/DelegateCallSpam.json`)
bt.slow(`.*bcExploitTest/ShanghaiLove.json`)
bt.slow(`.*bcExploitTest/SuicideIssue.json`)
bt.slow(`.*/bcForkStressTest/`)
bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`)
bt.slow(`.*/bcWalletTest/`)
// Very slow test
bt.skipLoad(`.*/stTimeConsuming/.*`)
// test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range,
// using 4.6 TGas
bt.skipLoad(`.*randomStatetest94.json.*`)
// skip uncle tests for stateless
bt.skipLoad(`.*/UnclePopulation.json`)
// skip this test in stateless because it uses 5000 blocks and the
// historical state of older blocks is unavailable for stateless
// test verification after importing the test set.
bt.skipLoad(`.*/bcWalletTest/walletReorganizeOwners.json`)
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 {
t.Skip("test (randomly) skipped on 32-bit windows")
}
config, ok := Forks[test.json.Network]
if !ok {
t.Fatalf("test malformed: doesn't have chain config embedded")
}
isMerged := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.BitLen() == 0
if isMerged {
execBlockTestStateless(t, bt, test)
} else {
t.Skip("skipping pre-merge test")
}
})
// There is also a LegacyTests folder, containing blockchain tests generated
// prior to Istanbul. However, they are all derived from GeneralStateTests,
// which run natively, so there's no reason to run them here.
}
func execBlockTestStateless(t *testing.T, bt *testMatcher, test *BlockTest) {
if err := bt.checkFailure(t, test.RunStateless(false, rawdb.HashScheme, nil, nil)); err != nil {
t.Errorf("test in hash mode without snapshotter failed: %v", err)
return
}
if err := bt.checkFailure(t, test.RunStateless(true, rawdb.HashScheme, nil, nil)); err != nil {
t.Errorf("test in hash mode with snapshotter failed: %v", err)
return
}
if err := bt.checkFailure(t, test.RunStateless(false, rawdb.PathScheme, nil, nil)); err != nil {
t.Errorf("test in path mode without snapshotter failed: %v", err)
return
}
if err := bt.checkFailure(t, test.RunStateless(true, rawdb.PathScheme, nil, nil)); err != nil {
t.Errorf("test in path mode with snapshotter failed: %v", err)
return
}
}
func TestBlockchain(t *testing.T) {
bt := new(testMatcher)
// General state tests are 'exported' as blockchain tests, but we can run them natively.

View file

@ -26,6 +26,9 @@ import (
"os"
"reflect"
"github.com/ethereum/go-ethereum/cmd/utils/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
@ -34,7 +37,6 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
@ -111,6 +113,14 @@ type btHeaderMarshaling struct {
}
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
return t.run(false, snapshotter, scheme, tracer, postCheck)
}
func (t *BlockTest) RunStateless(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
return t.run(true, snapshotter, scheme, tracer, postCheck)
}
func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
config, ok := Forks[t.json.Network]
if !ok {
return UnsupportedForkError{t.json.Network}
@ -184,7 +194,29 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks,
return err
}
}
return t.validateImportedHeaders(chain, validBlocks)
if err := t.validateImportedHeaders(chain, validBlocks); err != nil {
return err
}
if isStateless {
for _, blk := range validBlocks {
proof, err := stateless.BuildStatelessProof(blk.BlockHeader.Hash, chain)
if err != nil {
return fmt.Errorf("failed to build proof: %v", err)
}
witness, err := state.DecodeWitnessRLP(proof)
if err != nil {
return fmt.Errorf("failed to decode witness RLP: %v", err)
}
root, err := stateless.StatelessExecute(config, witness)
if err != nil {
return fmt.Errorf("verification execution error: %v", err)
}
if root != blk.BlockHeader.StateRoot {
return fmt.Errorf("state root mismatch (wanted: %x, got: %x)", blk.BlockHeader.StateRoot, root)
}
}
}
return nil
}
func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {

View file

@ -302,7 +302,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
// Prepare the EVM.
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig)

View file

@ -278,7 +278,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
// Prepare the EVM.
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
context.Random = nil

View file

@ -214,6 +214,10 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
return t.db.Preimage(common.BytesToHash(shaKey))
}
func (t *StateTrie) AccessList() map[string][]byte {
return t.trie.AccessList()
}
// Commit collects all dirty nodes in the trie and replaces them with the
// corresponding node hash. All collected nodes (including dirty leaves if
// collectLeaf is true) will be encapsulated into a nodeset for return.

View file

@ -601,6 +601,12 @@ func (t *Trie) Hash() common.Hash {
return common.BytesToHash(hash.(hashNode))
}
// AccessList returns a map of path->blob containing all trie nodes that have
// been accessed.
func (t *Trie) AccessList() map[string][]byte {
return t.tracer.accessList
}
// Commit collects all dirty nodes in the trie and replaces them with the
// corresponding node hash. All collected nodes (including dirty leaves if
// collectLeaf is true) will be encapsulated into a nodeset for return.

View file

@ -369,3 +369,9 @@ func (t *VerkleTrie) ToDot() string {
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
return t.reader.node(path, common.Hash{})
}
// AccessList returns a map of path->blob containing all trie nodes that have
// been accessed.
func (t *VerkleTrie) AccessList() map[string][]byte {
panic("not implemented")
}

View file

@ -46,6 +46,13 @@ var HashDefaults = &Config{
HashDB: hashdb.Defaults,
}
// PathDefaults represents a config for using path-based scheme with
// default settings.
var PathDefaults = &Config{
Preimages: false,
PathDB: pathdb.Defaults,
}
// backend defines the methods needed to access/update trie nodes in different
// state scheme.
type backend interface {