mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
all: simplify witness and integrate it into live geth
This commit is contained in:
parent
d86b650e54
commit
e1dd112c48
40 changed files with 748 additions and 897 deletions
|
|
@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
test := tests[name]
|
test := tests[name]
|
||||||
if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) {
|
if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) {
|
||||||
if ctx.Bool(DumpFlag.Name) {
|
if ctx.Bool(DumpFlag.Name) {
|
||||||
if state, _ := chain.State(); state != nil {
|
if state, _ := chain.State(); state != nil {
|
||||||
fmt.Println(string(state.Dump(nil)))
|
fmt.Println(string(state.Dump(nil)))
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
|
@ -35,14 +36,12 @@ import (
|
||||||
type BlockValidator struct {
|
type BlockValidator struct {
|
||||||
config *params.ChainConfig // Chain configuration options
|
config *params.ChainConfig // Chain configuration options
|
||||||
bc *BlockChain // Canonical block chain
|
bc *BlockChain // Canonical block chain
|
||||||
engine consensus.Engine // Consensus engine used for validating
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockValidator returns a new block validator which is safe for re-use
|
// NewBlockValidator returns a new block validator which is safe for re-use
|
||||||
func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engine consensus.Engine) *BlockValidator {
|
func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *BlockValidator {
|
||||||
validator := &BlockValidator{
|
validator := &BlockValidator{
|
||||||
config: config,
|
config: config,
|
||||||
engine: engine,
|
|
||||||
bc: blockchain,
|
bc: blockchain,
|
||||||
}
|
}
|
||||||
return validator
|
return validator
|
||||||
|
|
@ -60,7 +59,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
// Header validity is known at this point. Here we verify that uncles, transactions
|
// Header validity is known at this point. Here we verify that uncles, transactions
|
||||||
// and withdrawals given in the block body match the header.
|
// and withdrawals given in the block body match the header.
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
|
if err := v.bc.engine.VerifyUncles(v.bc, block); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
|
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
|
||||||
|
|
@ -122,31 +121,55 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
|
|
||||||
// ValidateState validates the various changes that happen after a state transition,
|
// 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.
|
// such as amount of used gas, the receipt roots and the state root itself.
|
||||||
// If validateRemoteRoot is false, the provided block header's root is not asserted to be equal to the one computed from
|
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error {
|
||||||
// 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()
|
header := block.Header()
|
||||||
if block.GasUsed() != usedGas {
|
if block.GasUsed() != usedGas {
|
||||||
return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
|
return 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.
|
// Validate the received block's bloom with the one derived from the generated receipts.
|
||||||
// For valid blocks this should always validate to true.
|
// For valid blocks this should always validate to true.
|
||||||
rbloom := types.CreateBloom(receipts)
|
rbloom := types.CreateBloom(receipts)
|
||||||
if rbloom != header.Bloom {
|
if rbloom != header.Bloom {
|
||||||
return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
|
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
|
||||||
|
}
|
||||||
|
// In stateless mode, return early because the receipt and state root are not
|
||||||
|
// provided through the witness, rather the cross validator needs to return it.
|
||||||
|
if stateless {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
|
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
|
||||||
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
||||||
if receiptSha != header.ReceiptHash {
|
if receiptSha != header.ReceiptHash {
|
||||||
return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
|
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
|
||||||
}
|
}
|
||||||
// Compute the state root and if enabled, check it against the
|
// Validate the state root against the received state root and throw
|
||||||
// received state root and throw an error if they don't match.
|
// an error if they don't match.
|
||||||
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
|
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
|
||||||
if checkRemoteRoot && header.Root != root {
|
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
|
||||||
return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
|
|
||||||
}
|
}
|
||||||
return root, nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWitness cross validates a block execution with stateless remote clients.
|
||||||
|
//
|
||||||
|
// Normally we'd distribute the block witness to remote cross validators, wait
|
||||||
|
// for them to respond and then merge the results. For now, however, it's only
|
||||||
|
// Geth, so do an internal stateless run.
|
||||||
|
func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error {
|
||||||
|
// Run the cross client stateless execution
|
||||||
|
// TODO(karalabe): Self-stateless for now, swap with other clients
|
||||||
|
crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stateless execution failed: %v", err)
|
||||||
|
}
|
||||||
|
// Stateless cross execution suceeeded, validate the withheld computed fields
|
||||||
|
if crossReceiptRoot != receiptRoot {
|
||||||
|
return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot)
|
||||||
|
}
|
||||||
|
if crossStateRoot != stateRoot {
|
||||||
|
return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalcGasLimit computes the gas limit of the next block after parent. It aims
|
// CalcGasLimit computes the gas limit of the next block after parent. It aims
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
@ -302,18 +303,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
||||||
vmConfig: vmConfig,
|
vmConfig: vmConfig,
|
||||||
logger: vmConfig.Tracer,
|
logger: vmConfig.Tracer,
|
||||||
}
|
}
|
||||||
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
|
|
||||||
bc.forker = NewForkChoice(bc, shouldPreserve)
|
|
||||||
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
|
|
||||||
bc.validator = NewBlockValidator(chainConfig, bc, engine)
|
|
||||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
|
|
||||||
bc.processor = NewStateProcessor(chainConfig, bc, engine)
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
|
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
|
||||||
|
bc.forker = NewForkChoice(bc, shouldPreserve)
|
||||||
|
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
|
||||||
|
bc.validator = NewBlockValidator(chainConfig, bc)
|
||||||
|
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
|
||||||
|
bc.processor = NewStateProcessor(chainConfig, bc.hc)
|
||||||
|
|
||||||
bc.genesisBlock = bc.GetBlockByNumber(0)
|
bc.genesisBlock = bc.GetBlockByNumber(0)
|
||||||
if bc.genesisBlock == nil {
|
if bc.genesisBlock == nil {
|
||||||
return nil, ErrNoGenesis
|
return nil, ErrNoGenesis
|
||||||
|
|
@ -1809,7 +1810,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
// while processing transactions. Before Byzantium the prefetcher is mostly
|
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||||
// useless due to the intermediate root hashing after each transaction.
|
// useless due to the intermediate root hashing after each transaction.
|
||||||
if bc.chainConfig.IsByzantium(block.Number()) {
|
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||||
statedb.StartPrefetcher("chain", !bc.vmConfig.EnableWitnessCollection)
|
var witness *stateless.Witness
|
||||||
|
if bc.vmConfig.EnableWitnessCollection {
|
||||||
|
witness, err = stateless.NewWitness(bc, block)
|
||||||
|
if err != nil {
|
||||||
|
return it.index, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statedb.StartPrefetcher("chain", witness)
|
||||||
}
|
}
|
||||||
activeState = statedb
|
activeState = statedb
|
||||||
|
|
||||||
|
|
@ -1916,7 +1924,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
|
||||||
|
|
||||||
// Process block using the parent state as reference point
|
// Process block using the parent state as reference point
|
||||||
pstart := time.Now()
|
pstart := time.Now()
|
||||||
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil)
|
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bc.reportBlock(block, receipts, err)
|
bc.reportBlock(block, receipts, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1924,11 +1932,18 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
|
||||||
ptime := time.Since(pstart)
|
ptime := time.Since(pstart)
|
||||||
|
|
||||||
vstart := time.Now()
|
vstart := time.Now()
|
||||||
if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil {
|
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
|
||||||
bc.reportBlock(block, receipts, err)
|
bc.reportBlock(block, receipts, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
vtime := time.Since(vstart)
|
vtime := time.Since(vstart)
|
||||||
|
|
||||||
|
if witness := statedb.Witness(); witness != nil {
|
||||||
|
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil {
|
||||||
|
bc.reportBlock(block, receipts, err)
|
||||||
|
return nil, fmt.Errorf("cross verification failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
proctime := time.Since(start) // processing + validation
|
proctime := time.Since(start) // processing + validation
|
||||||
|
|
||||||
// Update the metrics touched during block processing and validation
|
// Update the metrics touched during block processing and validation
|
||||||
|
|
|
||||||
|
|
@ -163,13 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, nil)
|
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
blockchain.reportBlock(block, receipts, err)
|
blockchain.reportBlock(block, receipts, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true)
|
if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
|
||||||
if err != nil {
|
|
||||||
blockchain.reportBlock(block, receipts, err)
|
blockchain.reportBlock(block, receipts, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int {
|
||||||
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
|
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
|
||||||
b.header.ParentBeaconRoot = &root
|
b.header.ParentBeaconRoot = &root
|
||||||
var (
|
var (
|
||||||
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, nil)
|
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||||
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
|
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
|
||||||
)
|
)
|
||||||
ProcessBeaconBlockRoot(root, vmenv, b.statedb)
|
ProcessBeaconBlockRoot(root, vmenv, b.statedb)
|
||||||
|
|
|
||||||
17
core/evm.go
17
core/evm.go
|
|
@ -19,8 +19,6 @@ package core
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
|
|
@ -40,9 +38,8 @@ type ChainContext interface {
|
||||||
GetHeader(common.Hash, uint64) *types.Header
|
GetHeader(common.Hash, uint64) *types.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEVMBlockContext creates a new context for use in the EVM. If witness is non-nil, the context sources block hashes
|
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||||
// for the BLOCKHASH opcode from the witness.
|
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
||||||
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext {
|
|
||||||
var (
|
var (
|
||||||
beneficiary common.Address
|
beneficiary common.Address
|
||||||
baseFee *big.Int
|
baseFee *big.Int
|
||||||
|
|
@ -65,18 +62,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||||
if header.Difficulty.Sign() == 0 {
|
if header.Difficulty.Sign() == 0 {
|
||||||
random = &header.MixDigest
|
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{
|
return vm.BlockContext{
|
||||||
CanTransfer: CanTransfer,
|
CanTransfer: CanTransfer,
|
||||||
Transfer: Transfer,
|
Transfer: Transfer,
|
||||||
GetHash: getHash,
|
GetHash: GetHashFn(header, chain),
|
||||||
Coinbase: beneficiary,
|
Coinbase: beneficiary,
|
||||||
BlockNumber: new(big.Int).Set(header.Number),
|
BlockNumber: new(big.Int).Set(header.Number),
|
||||||
Time: header.Time,
|
Time: header.Time,
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,9 @@ type Trie interface {
|
||||||
// be created with new root and updated trie database for following usage
|
// be created with new root and updated trie database for following usage
|
||||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
|
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
|
||||||
|
|
||||||
// AccessList returns a map of path->blob containing all trie nodes that have
|
// Witness returns a set containing all trie nodes that have been accessed.
|
||||||
// been accessed.
|
// The returned map could be nil if the witness is empty.
|
||||||
AccessList() map[string][]byte
|
Witness() map[string]struct{}
|
||||||
|
|
||||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
// 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
|
// starts at the key after the given start key. And error will be returned
|
||||||
|
|
|
||||||
|
|
@ -323,7 +323,17 @@ func (s *stateObject) finalise() {
|
||||||
//
|
//
|
||||||
// It assumes all the dirty storage slots have been finalized before.
|
// It assumes all the dirty storage slots have been finalized before.
|
||||||
func (s *stateObject) updateTrie() (Trie, error) {
|
func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
// Retrieve a pretecher populated trie, or fall back to the database
|
// Short circuit if nothing was accessed, don't trigger a prefetcher warning
|
||||||
|
if len(s.uncommittedStorage) == 0 {
|
||||||
|
// Nothing was written, so we could stop early. Unless we have both reads
|
||||||
|
// and witness collection enabled, in which case we need to fetch the trie.
|
||||||
|
if s.db.witness == nil || len(s.originStorage) == 0 {
|
||||||
|
return s.trie, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Retrieve a pretecher populated trie, or fall back to the database. This will
|
||||||
|
// block until all prefetch tasks are done, which are needed for witnesses even
|
||||||
|
// for unmodified state objects.
|
||||||
tr := s.getPrefetchedTrie()
|
tr := s.getPrefetchedTrie()
|
||||||
if tr != nil {
|
if tr != nil {
|
||||||
// Prefetcher returned a live trie, swap it out for the current one
|
// Prefetcher returned a live trie, swap it out for the current one
|
||||||
|
|
@ -337,11 +347,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Short circuit if nothing changed, don't bother with hashing anything.
|
// 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 {
|
if len(s.uncommittedStorage) == 0 {
|
||||||
return s.trie, nil
|
return s.trie, nil
|
||||||
}
|
}
|
||||||
|
|
@ -450,9 +456,7 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||||
//
|
//
|
||||||
// Note, commit may run concurrently across all the state objects. Do not assume
|
// Note, commit may run concurrently across all the state objects. Do not assume
|
||||||
// thread-safe access to the statedb.
|
// thread-safe access to the statedb.
|
||||||
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]byte, error) {
|
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
||||||
var al map[string][]byte
|
|
||||||
|
|
||||||
// commit the account metadata changes
|
// commit the account metadata changes
|
||||||
op := &accountUpdate{
|
op := &accountUpdate{
|
||||||
address: s.address,
|
address: s.address,
|
||||||
|
|
@ -474,18 +478,12 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]
|
||||||
if len(op.storages) == 0 {
|
if len(op.storages) == 0 {
|
||||||
// nothing changed, don't bother to commit the trie
|
// nothing changed, don't bother to commit the trie
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
if s.trie != nil && !s.trie.IsVerkle() {
|
return op, nil, nil
|
||||||
al = s.trie.AccessList()
|
|
||||||
}
|
|
||||||
return op, nil, al, nil
|
|
||||||
}
|
}
|
||||||
root, nodes := s.trie.Commit(false)
|
root, nodes := s.trie.Commit(false)
|
||||||
s.data.Root = root
|
s.data.Root = root
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
if !s.trie.IsVerkle() {
|
return op, nodes, nil
|
||||||
al = s.trie.AccessList()
|
|
||||||
}
|
|
||||||
return op, nodes, al, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBalance adds amount to s's balance.
|
// AddBalance adds amount to s's balance.
|
||||||
|
|
|
||||||
|
|
@ -1,374 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -146,6 +147,9 @@ type StateDB struct {
|
||||||
validRevisions []revision
|
validRevisions []revision
|
||||||
nextRevisionId int
|
nextRevisionId int
|
||||||
|
|
||||||
|
// State witness if cross validation is needed
|
||||||
|
witness *stateless.Witness
|
||||||
|
|
||||||
// Measurements gathered during execution for debugging purposes
|
// Measurements gathered during execution for debugging purposes
|
||||||
AccountReads time.Duration
|
AccountReads time.Duration
|
||||||
AccountHashes time.Duration
|
AccountHashes time.Duration
|
||||||
|
|
@ -163,7 +167,6 @@ type StateDB struct {
|
||||||
StorageUpdated atomic.Int64
|
StorageUpdated atomic.Int64
|
||||||
AccountDeleted int
|
AccountDeleted int
|
||||||
StorageDeleted atomic.Int64
|
StorageDeleted atomic.Int64
|
||||||
witness *Witness
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// New creates a new state from a given trie.
|
||||||
|
|
@ -201,14 +204,19 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) {
|
||||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||||
// state trie concurrently while the state is mutated so that when we reach the
|
// state trie concurrently while the state is mutated so that when we reach the
|
||||||
// commit phase, most of the needed data is already hot.
|
// commit phase, most of the needed data is already hot.
|
||||||
func (s *StateDB) StartPrefetcher(namespace string, noreads bool) {
|
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
|
||||||
|
// Terminate any previously running prefetcher
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
s.prefetcher.terminate(false)
|
s.prefetcher.terminate(false)
|
||||||
s.prefetcher.report()
|
s.prefetcher.report()
|
||||||
s.prefetcher = nil
|
s.prefetcher = nil
|
||||||
}
|
}
|
||||||
|
// Enable witness collection if requested
|
||||||
|
s.witness = witness
|
||||||
|
|
||||||
|
// If snapshots are enabled, start prefethers explicitly
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, noreads)
|
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, witness == nil)
|
||||||
|
|
||||||
// With the switch to the Proof-of-Stake consensus algorithm, block production
|
// With the switch to the Proof-of-Stake consensus algorithm, block production
|
||||||
// rewards are now handled at the consensus layer. Consequently, a block may
|
// rewards are now handled at the consensus layer. Consequently, a block may
|
||||||
|
|
@ -683,7 +691,7 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
hasher: crypto.NewKeccakState(),
|
hasher: crypto.NewKeccakState(),
|
||||||
originalRoot: s.originalRoot,
|
originalRoot: s.originalRoot,
|
||||||
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
|
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
|
||||||
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
|
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),
|
||||||
mutations: make(map[common.Address]*mutation, len(s.mutations)),
|
mutations: make(map[common.Address]*mutation, len(s.mutations)),
|
||||||
dbErr: s.dbErr,
|
dbErr: s.dbErr,
|
||||||
refund: s.refund,
|
refund: s.refund,
|
||||||
|
|
@ -710,6 +718,10 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
for addr, obj := range s.stateObjects {
|
for addr, obj := range s.stateObjects {
|
||||||
state.stateObjects[addr] = obj.deepCopy(state)
|
state.stateObjects[addr] = obj.deepCopy(state)
|
||||||
}
|
}
|
||||||
|
// Deep copy destructed state objects.
|
||||||
|
for addr, obj := range s.stateObjectsDestruct {
|
||||||
|
state.stateObjectsDestruct[addr] = obj.deepCopy(state)
|
||||||
|
}
|
||||||
// Deep copy the object state markers.
|
// Deep copy the object state markers.
|
||||||
for addr, op := range s.mutations {
|
for addr, op := range s.mutations {
|
||||||
state.mutations[addr] = op.copy()
|
state.mutations[addr] = op.copy()
|
||||||
|
|
@ -758,12 +770,6 @@ func (s *StateDB) RevertToSnapshot(revid int) {
|
||||||
s.validRevisions = s.validRevisions[:idx]
|
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.
|
// GetRefund returns the current value of the refund counter.
|
||||||
func (s *StateDB) GetRefund() uint64 {
|
func (s *StateDB) GetRefund() uint64 {
|
||||||
return s.refund
|
return s.refund
|
||||||
|
|
@ -817,46 +823,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
s.clearJournalAndRefund()
|
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.
|
// IntermediateRoot computes the current root hash of the state trie.
|
||||||
// It is called in between transactions to get the root hash that
|
// It is called in between transactions to get the root hash that
|
||||||
// goes into transaction receipts.
|
// goes into transaction receipts.
|
||||||
|
|
@ -873,7 +839,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
|
s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process all storage updates concurrently. The state object update root
|
// Process all storage updates concurrently. The state object update root
|
||||||
// method will internally call a blocking trie fetch from the prefetcher,
|
// method will internally call a blocking trie fetch from the prefetcher,
|
||||||
// so there's no need to explicitly wait for the prefetchers to finish.
|
// so there's no need to explicitly wait for the prefetchers to finish.
|
||||||
|
|
@ -896,14 +861,45 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
obj := s.stateObjects[addr] // closure for the task runner below
|
obj := s.stateObjects[addr] // closure for the task runner below
|
||||||
workers.Go(func() error {
|
workers.Go(func() error {
|
||||||
obj.updateRoot()
|
obj.updateRoot()
|
||||||
|
|
||||||
|
// If witness building is enabled and the state object has a trie,
|
||||||
|
// gather the witnesses for its specific storage trie
|
||||||
|
if s.witness != nil && obj.trie != nil {
|
||||||
|
s.witness.AddState(obj.trie.Witness())
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// If witness building is enabled, gather all the read-only accesses
|
||||||
if s.witness != nil {
|
if s.witness != nil {
|
||||||
// if witness building is enabled, collect storage access lists from
|
// Pull in anything that has been accessed before destruction
|
||||||
// tries that will not be committed: accounts with non-mutated storage
|
for _, obj := range s.stateObjectsDestruct {
|
||||||
// and self-destructed accounts.
|
// Skip any objects that haven't touched their storage
|
||||||
s.collectNonCommittedTrieAccessLists()
|
if len(obj.originStorage) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||||
|
s.witness.AddState(trie.Witness())
|
||||||
|
} else if obj.trie != nil {
|
||||||
|
s.witness.AddState(obj.trie.Witness())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Pull in only-read and non-destructed trie witnesses
|
||||||
|
for _, obj := range s.stateObjects {
|
||||||
|
// Skip any objects that have been updated
|
||||||
|
if _, ok := s.mutations[obj.address]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Skip any objects that haven't touched their storage
|
||||||
|
if len(obj.originStorage) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||||
|
s.witness.AddState(trie.Witness())
|
||||||
|
} else if obj.trie != nil {
|
||||||
|
s.witness.AddState(obj.trie.Witness())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
workers.Wait()
|
workers.Wait()
|
||||||
s.StorageUpdates += time.Since(start)
|
s.StorageUpdates += time.Since(start)
|
||||||
|
|
@ -960,7 +956,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// Track the amount of time wasted on hashing the account trie
|
// Track the amount of time wasted on hashing the account trie
|
||||||
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
|
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
|
||||||
|
|
||||||
return s.trie.Hash()
|
hash := s.trie.Hash()
|
||||||
|
|
||||||
|
// If witness building is enabled, gather the account trie witness
|
||||||
|
if s.witness != nil {
|
||||||
|
s.witness.AddState(s.trie.Witness())
|
||||||
|
}
|
||||||
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTxContext sets the current transaction hash and index which are
|
// SetTxContext sets the current transaction hash and index which are
|
||||||
|
|
@ -1116,8 +1118,9 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno
|
||||||
buf = crypto.NewKeccakState()
|
buf = crypto.NewKeccakState()
|
||||||
deletes = make(map[common.Hash]*accountDelete)
|
deletes = make(map[common.Hash]*accountDelete)
|
||||||
)
|
)
|
||||||
for addr, obj := range s.stateObjectsDestruct {
|
for addr, prevObj := range s.stateObjectsDestruct {
|
||||||
prev := obj.origin
|
prev := prevObj.origin
|
||||||
|
|
||||||
// The account was non-existent, and it's marked as destructed in the scope
|
// 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
|
// of block. It can be either case (a) or (b) and will be interpreted as
|
||||||
// null->null state transition.
|
// null->null state transition.
|
||||||
|
|
@ -1225,7 +1228,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
||||||
root common.Hash
|
root common.Hash
|
||||||
workers errgroup.Group
|
workers errgroup.Group
|
||||||
)
|
)
|
||||||
|
|
||||||
// Schedule the account trie first since that will be the biggest, so give
|
// Schedule the account trie first since that will be the biggest, so give
|
||||||
// it the most time to crunch.
|
// it the most time to crunch.
|
||||||
//
|
//
|
||||||
|
|
@ -1236,25 +1238,8 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
||||||
// Obviously it's not an end of the world issue, just something the original
|
// Obviously it's not an end of the world issue, just something the original
|
||||||
// code didn't anticipate for.
|
// code didn't anticipate for.
|
||||||
workers.Go(func() error {
|
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
|
// Write the account trie changes, measuring the amount of wasted time
|
||||||
if s.witness != nil {
|
newroot, set := s.trie.Commit(true)
|
||||||
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
|
root = newroot
|
||||||
|
|
||||||
if err := merge(set); err != nil {
|
if err := merge(set); err != nil {
|
||||||
|
|
@ -1282,7 +1267,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
||||||
// Run the storage updates concurrently to one another
|
// Run the storage updates concurrently to one another
|
||||||
workers.Go(func() error {
|
workers.Go(func() error {
|
||||||
// Write any storage changes in the state object to its storage trie
|
// Write any storage changes in the state object to its storage trie
|
||||||
update, set, al, err := obj.commit()
|
update, set, err := obj.commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1290,12 +1275,9 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
|
||||||
updates[obj.addrHash] = update
|
updates[obj.addrHash] = update
|
||||||
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
||||||
if s.witness != nil && len(al) > 0 {
|
lock.Unlock()
|
||||||
s.witness.addAccessList(obj.addrHash, al)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1372,12 +1354,6 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
|
||||||
return ret, err
|
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.
|
// Commit writes the state mutations into the configured data stores.
|
||||||
//
|
//
|
||||||
// Once the state is committed, tries cached in stateDB (including account
|
// Once the state is committed, tries cached in stateDB (including account
|
||||||
|
|
@ -1496,3 +1472,8 @@ func (s *StateDB) markUpdate(addr common.Address) {
|
||||||
func (s *StateDB) PointCache() *utils.PointCache {
|
func (s *StateDB) PointCache() *utils.PointCache {
|
||||||
return s.db.PointCache()
|
return s.db.PointCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Witness retrieves the current state witness being collected.
|
||||||
|
func (s *StateDB) Witness() *stateless.Witness {
|
||||||
|
return s.witness
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package core
|
||||||
import (
|
import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
@ -31,16 +30,14 @@ import (
|
||||||
// data from disk before the main block processor start executing.
|
// data from disk before the main block processor start executing.
|
||||||
type statePrefetcher struct {
|
type statePrefetcher struct {
|
||||||
config *params.ChainConfig // Chain configuration options
|
config *params.ChainConfig // Chain configuration options
|
||||||
bc *BlockChain // Canonical block chain
|
chain *HeaderChain // Canonical block chain
|
||||||
engine consensus.Engine // Consensus engine used for block rewards
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStatePrefetcher initialises a new statePrefetcher.
|
// newStatePrefetcher initialises a new statePrefetcher.
|
||||||
func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *statePrefetcher {
|
func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePrefetcher {
|
||||||
return &statePrefetcher{
|
return &statePrefetcher{
|
||||||
config: config,
|
config: config,
|
||||||
bc: bc,
|
chain: chain,
|
||||||
engine: engine,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +48,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
||||||
var (
|
var (
|
||||||
header = block.Header()
|
header = block.Header()
|
||||||
gaspool = new(GasPool).AddGas(block.GasLimit())
|
gaspool = new(GasPool).AddGas(block.GasLimit())
|
||||||
blockContext = NewEVMBlockContext(header, p.bc, nil, nil)
|
blockContext = NewEVMBlockContext(header, p.chain, nil)
|
||||||
evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
|
evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
|
||||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -36,45 +35,18 @@ import (
|
||||||
//
|
//
|
||||||
// StateProcessor implements Processor.
|
// StateProcessor implements Processor.
|
||||||
type StateProcessor struct {
|
type StateProcessor struct {
|
||||||
config *params.ChainConfig // Chain configuration options
|
config *params.ChainConfig // Chain configuration options
|
||||||
bc *BlockChain // Canonical block chain
|
chain *HeaderChain // Canonical header chain
|
||||||
engine consensus.Engine // Consensus engine used for block rewards
|
|
||||||
statelessChainCtx ChainContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStateProcessor initialises a new StateProcessor. If the provided
|
// NewStateProcessor initialises a new StateProcessor.
|
||||||
// Blockchain is nil, stateless execution mode is enabled.
|
func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor {
|
||||||
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
|
return &StateProcessor{
|
||||||
if bc != nil {
|
config: config,
|
||||||
return &StateProcessor{
|
chain: chain,
|
||||||
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
|
// 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 transaction messages using the statedb and applying any rewards to both
|
||||||
// the processor (coinbase) and any included uncles.
|
// the processor (coinbase) and any included uncles.
|
||||||
|
|
@ -82,7 +54,7 @@ func (s *statelessChainContext) GetHeader(hash common.Hash, number uint64) *type
|
||||||
// Process returns the receipts and logs accumulated during the process and
|
// 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
|
// 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.
|
// 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, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error) {
|
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
|
||||||
var (
|
var (
|
||||||
receipts types.Receipts
|
receipts types.Receipts
|
||||||
usedGas = new(uint64)
|
usedGas = new(uint64)
|
||||||
|
|
@ -101,7 +73,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
context vm.BlockContext
|
context vm.BlockContext
|
||||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||||
)
|
)
|
||||||
context = NewEVMBlockContext(header, p.bc, nil, witness)
|
context = NewEVMBlockContext(header, p.chain, nil)
|
||||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
||||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
|
|
@ -127,7 +99,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
return nil, nil, 0, errors.New("withdrawals before shanghai")
|
return nil, nil, 0, errors.New("withdrawals before shanghai")
|
||||||
}
|
}
|
||||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||||
p.engine.Finalize(p.bc, header, statedb, block.Body())
|
p.chain.engine.Finalize(p.chain, header, statedb, block.Body())
|
||||||
|
|
||||||
return receipts, allLogs, *usedGas, nil
|
return receipts, allLogs, *usedGas, nil
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +175,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create a new context to be used in the EVM environment
|
// Create a new context to be used in the EVM environment
|
||||||
blockContext := NewEVMBlockContext(header, bc, author, statedb.Witness())
|
blockContext := NewEVMBlockContext(header, bc, author)
|
||||||
txContext := NewEVMTxContext(msg)
|
txContext := NewEVMTxContext(msg)
|
||||||
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
|
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
|
||||||
return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
||||||
|
|
|
||||||
73
core/stateless.go
Normal file
73
core/stateless.go
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExecuteStateless runs a stateless execution based on a witness, verifies
|
||||||
|
// everything it can locally and returns the two computed fields that need the
|
||||||
|
// other side to explicitly check.
|
||||||
|
//
|
||||||
|
// This method is a bit of a sore thumb here, but:
|
||||||
|
// - It cannot be placed in core/stateless, because state.New prodces a circular dep
|
||||||
|
// - It cannot be placed outside of core, because it needs to construct a dud headerchain
|
||||||
|
//
|
||||||
|
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
|
||||||
|
func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (common.Hash, common.Hash, error) {
|
||||||
|
// Create and populate the state database to serve as the stateless backend
|
||||||
|
memdb := witness.MakeHashDB()
|
||||||
|
|
||||||
|
db, err := state.New(witness.Root(), state.NewDatabaseWithConfig(memdb, triedb.HashDefaults), nil)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, common.Hash{}, err
|
||||||
|
}
|
||||||
|
// Create a blockchain that is idle, but can be used to access headers through
|
||||||
|
chain := &HeaderChain{
|
||||||
|
config: config,
|
||||||
|
chainDb: memdb,
|
||||||
|
headerCache: lru.NewCache[common.Hash, *types.Header](256),
|
||||||
|
engine: beacon.New(ethash.NewFaker()),
|
||||||
|
}
|
||||||
|
processor := NewStateProcessor(config, chain)
|
||||||
|
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
||||||
|
|
||||||
|
// Run the stateless blocks processing and self-validate certain fields
|
||||||
|
receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, common.Hash{}, err
|
||||||
|
}
|
||||||
|
if err = validator.ValidateState(witness.Block, db, receipts, usedGas, true); err != nil {
|
||||||
|
return common.Hash{}, common.Hash{}, err
|
||||||
|
}
|
||||||
|
// Almost everything validated, but receipt and state root needs to be returned
|
||||||
|
receiptRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
||||||
|
stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number()))
|
||||||
|
|
||||||
|
return receiptRoot, stateRoot, nil
|
||||||
|
}
|
||||||
60
core/stateless/database.go
Normal file
60
core/stateless/database.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package stateless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeHashDB imports tries, codes and block hashes from a witness into a new
|
||||||
|
// hash-based memory db. We could eventually rewrite this into a pathdb, but
|
||||||
|
// simple is better for now.
|
||||||
|
func (w *Witness) MakeHashDB() ethdb.Database {
|
||||||
|
var (
|
||||||
|
memdb = rawdb.NewMemoryDatabase()
|
||||||
|
hasher = crypto.NewKeccakState()
|
||||||
|
hash = make([]byte, 32)
|
||||||
|
)
|
||||||
|
// Inject all the "block hashes" (i.e. headers) into the ephemeral database
|
||||||
|
for _, header := range w.Headers {
|
||||||
|
rawdb.WriteHeader(memdb, header)
|
||||||
|
}
|
||||||
|
// Inject all the bytecodes into the ephemeral database
|
||||||
|
for code := range w.Codes {
|
||||||
|
blob := []byte(code)
|
||||||
|
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(blob)
|
||||||
|
hasher.Read(hash)
|
||||||
|
|
||||||
|
rawdb.WriteCode(memdb, common.BytesToHash(hash), blob)
|
||||||
|
}
|
||||||
|
// Inject all the MPT trie nodes into the ephemeral database
|
||||||
|
for node := range w.State {
|
||||||
|
blob := []byte(node)
|
||||||
|
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(blob)
|
||||||
|
hasher.Read(hash)
|
||||||
|
|
||||||
|
rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob)
|
||||||
|
}
|
||||||
|
return memdb
|
||||||
|
}
|
||||||
129
core/stateless/encoding.go
Normal file
129
core/stateless/encoding.go
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package stateless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go
|
||||||
|
|
||||||
|
// toExtWitness converts our internal witness representation to the consensus one.
|
||||||
|
func (w *Witness) toExtWitness() *extWitness {
|
||||||
|
ext := &extWitness{
|
||||||
|
Block: w.Block,
|
||||||
|
Headers: w.Headers,
|
||||||
|
}
|
||||||
|
ext.Codes = make([][]byte, 0, len(w.Codes))
|
||||||
|
for code := range w.Codes {
|
||||||
|
ext.Codes = append(ext.Codes, []byte(code))
|
||||||
|
}
|
||||||
|
slices.SortFunc(ext.Codes, bytes.Compare)
|
||||||
|
|
||||||
|
ext.State = make([][]byte, 0, len(w.State))
|
||||||
|
for node := range w.State {
|
||||||
|
ext.State = append(ext.State, []byte(node))
|
||||||
|
}
|
||||||
|
slices.SortFunc(ext.State, bytes.Compare)
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromExtWitness converts the consensus witness format into our internal one.
|
||||||
|
func (w *Witness) fromExtWitness(ext *extWitness) error {
|
||||||
|
w.Block, w.Headers = ext.Block, ext.Headers
|
||||||
|
|
||||||
|
w.Codes = make(map[string]struct{}, len(ext.Codes))
|
||||||
|
for _, code := range ext.Codes {
|
||||||
|
w.Codes[string(code)] = struct{}{}
|
||||||
|
}
|
||||||
|
w.State = make(map[string]struct{}, len(ext.State))
|
||||||
|
for _, node := range ext.State {
|
||||||
|
w.State[string(node)] = struct{}{}
|
||||||
|
}
|
||||||
|
return w.sanitize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals a witness as JSON.
|
||||||
|
func (w *Witness) MarshalJSON() ([]byte, error) {
|
||||||
|
return w.toExtWitness().MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRLP serializes a witness as RLP.
|
||||||
|
func (w *Witness) EncodeRLP(wr io.Writer) error {
|
||||||
|
return rlp.Encode(wr, w.toExtWitness())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (w *Witness) UnmarshalJSON(input []byte) error {
|
||||||
|
var ext extWitness
|
||||||
|
if err := ext.UnmarshalJSON(input); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.fromExtWitness(&ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRLP decodes a witness from RLP.
|
||||||
|
func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
||||||
|
var ext extWitness
|
||||||
|
if err := s.Decode(&ext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.fromExtWitness(&ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize checks for some mandatory fields in the witness after decoding so
|
||||||
|
// the rest of the code can assume invariants and doesn't have to deal with
|
||||||
|
// corrupted data.
|
||||||
|
func (w *Witness) sanitize() error {
|
||||||
|
// Verify that the "parent" header (i.e. index 0) is available, and is the
|
||||||
|
// true parent of the block-to-be executed, since we use that to link the
|
||||||
|
// current block to the pre-state.
|
||||||
|
if len(w.Headers) == 0 {
|
||||||
|
return errors.New("parent header (for pre-root hash) missing")
|
||||||
|
}
|
||||||
|
for i, header := range w.Headers {
|
||||||
|
if header == nil {
|
||||||
|
return fmt.Errorf("witness header nil at position %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if w.Headers[0].Hash() != w.Block.ParentHash() {
|
||||||
|
return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extWitness is a witness RLP encoding for transferring across clients.
|
||||||
|
type extWitness struct {
|
||||||
|
Block *types.Block `json:"block" gencodec:"required"`
|
||||||
|
Headers []*types.Header `json:"headers" gencodec:"required"`
|
||||||
|
Codes [][]byte `json:"codes"`
|
||||||
|
State [][]byte `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// extWitnessMarshalling defines the hex marshalling types for a witness.
|
||||||
|
type extWitnessMarshalling struct {
|
||||||
|
Codes []hexutil.Bytes
|
||||||
|
State []hexutil.Bytes
|
||||||
|
}
|
||||||
74
core/stateless/gen_encoding_json.go
Normal file
74
core/stateless/gen_encoding_json.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package stateless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*extWitnessMarshalling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (e extWitness) MarshalJSON() ([]byte, error) {
|
||||||
|
type extWitness struct {
|
||||||
|
Block *types.Block `json:"block" gencodec:"required"`
|
||||||
|
Headers []*types.Header `json:"headers" gencodec:"required"`
|
||||||
|
Codes []hexutil.Bytes `json:"codes"`
|
||||||
|
State []hexutil.Bytes `json:"state"`
|
||||||
|
}
|
||||||
|
var enc extWitness
|
||||||
|
enc.Block = e.Block
|
||||||
|
enc.Headers = e.Headers
|
||||||
|
if e.Codes != nil {
|
||||||
|
enc.Codes = make([]hexutil.Bytes, len(e.Codes))
|
||||||
|
for k, v := range e.Codes {
|
||||||
|
enc.Codes[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.State != nil {
|
||||||
|
enc.State = make([]hexutil.Bytes, len(e.State))
|
||||||
|
for k, v := range e.State {
|
||||||
|
enc.State[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (e *extWitness) UnmarshalJSON(input []byte) error {
|
||||||
|
type extWitness struct {
|
||||||
|
Block *types.Block `json:"block" gencodec:"required"`
|
||||||
|
Headers []*types.Header `json:"headers" gencodec:"required"`
|
||||||
|
Codes []hexutil.Bytes `json:"codes"`
|
||||||
|
State []hexutil.Bytes `json:"state"`
|
||||||
|
}
|
||||||
|
var dec extWitness
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.Block == nil {
|
||||||
|
return errors.New("missing required field 'block' for extWitness")
|
||||||
|
}
|
||||||
|
e.Block = dec.Block
|
||||||
|
if dec.Headers == nil {
|
||||||
|
return errors.New("missing required field 'headers' for extWitness")
|
||||||
|
}
|
||||||
|
e.Headers = dec.Headers
|
||||||
|
if dec.Codes != nil {
|
||||||
|
e.Codes = make([][]byte, len(dec.Codes))
|
||||||
|
for k, v := range dec.Codes {
|
||||||
|
e.Codes[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dec.State != nil {
|
||||||
|
e.State = make([][]byte, len(dec.State))
|
||||||
|
for k, v := range dec.State {
|
||||||
|
e.State[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
159
core/stateless/witness.go
Normal file
159
core/stateless/witness.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package stateless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderReader is an interface to pull in headers in place of block hashes for
|
||||||
|
// the witness.
|
||||||
|
type HeaderReader interface {
|
||||||
|
// GetHeader retrieves a block header from the database by hash and number,
|
||||||
|
GetHeader(hash common.Hash, number uint64) *types.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Witness encompasses a block, state and any other chain data required to apply
|
||||||
|
// a set of transactions and derive a post state/receipt root.
|
||||||
|
type Witness struct {
|
||||||
|
Block *types.Block // Current block with rootHash and receiptHash zeroed out
|
||||||
|
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
|
||||||
|
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
||||||
|
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
||||||
|
|
||||||
|
chain HeaderReader // Chain reader to convert block hash ops to header proofs
|
||||||
|
lock sync.Mutex // Lock to allow concurrent state insertions
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWitness creates an empty witness ready for population.
|
||||||
|
func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
|
||||||
|
// Zero out the result fields to avoid accidentally sending them to the verifier
|
||||||
|
header := block.Header()
|
||||||
|
header.Root = common.Hash{}
|
||||||
|
header.ReceiptHash = common.Hash{}
|
||||||
|
|
||||||
|
// Retrieve the parent header, which will *always* be included to act as a
|
||||||
|
// trustless pre-root hash container
|
||||||
|
parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||||
|
if parent == nil {
|
||||||
|
return nil, errors.New("failed to retrieve parent header")
|
||||||
|
}
|
||||||
|
// Create the wtness with a reconstructed gutted out block
|
||||||
|
return &Witness{
|
||||||
|
Block: types.NewBlockWithHeader(header).WithBody(*block.Body()),
|
||||||
|
Codes: make(map[string]struct{}),
|
||||||
|
State: make(map[string]struct{}),
|
||||||
|
Headers: []*types.Header{parent},
|
||||||
|
chain: chain,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockHash adds a "blockhash" to the witness with the designated offset from
|
||||||
|
// chain head. Under the hood, this method actually pulls in enough headers from
|
||||||
|
// the chain to cover the block being added.
|
||||||
|
func (w *Witness) AddBlockHash(number uint64) {
|
||||||
|
// Keep pulling in headers until this hash is populated
|
||||||
|
for int(w.Block.NumberU64()-number) > len(w.Headers) {
|
||||||
|
tail := w.Block.Header()
|
||||||
|
if len(w.Headers) > 0 {
|
||||||
|
tail = w.Headers[len(w.Headers)-1]
|
||||||
|
}
|
||||||
|
w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCode adds a bytecode blob to the witness.
|
||||||
|
func (w *Witness) AddCode(code []byte) {
|
||||||
|
if len(code) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Codes[string(code)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddState inserts a batch of MPT trie nodes into the witness.
|
||||||
|
func (w *Witness) AddState(nodes map[string]struct{}) {
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.lock.Lock()
|
||||||
|
defer w.lock.Unlock()
|
||||||
|
|
||||||
|
for node := range nodes {
|
||||||
|
w.State[node] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
|
||||||
|
// is never mutated by Witness
|
||||||
|
func (w *Witness) Copy() *Witness {
|
||||||
|
return &Witness{
|
||||||
|
Block: w.Block,
|
||||||
|
Headers: slices.Clone(w.Headers),
|
||||||
|
Codes: maps.Clone(w.Codes),
|
||||||
|
State: maps.Clone(w.State),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints a human-readable summary containing the total size of the
|
||||||
|
// witness and the sizes of the underlying components
|
||||||
|
func (w *Witness) String() string {
|
||||||
|
blob, _ := rlp.EncodeToBytes(w)
|
||||||
|
bytesTotal := len(blob)
|
||||||
|
|
||||||
|
blob, _ = rlp.EncodeToBytes(w.Block)
|
||||||
|
bytesBlock := len(blob)
|
||||||
|
|
||||||
|
bytesHeaders := 0
|
||||||
|
for _, header := range w.Headers {
|
||||||
|
blob, _ = rlp.EncodeToBytes(header)
|
||||||
|
bytesHeaders += len(blob)
|
||||||
|
}
|
||||||
|
bytesCodes := 0
|
||||||
|
for code := range w.Codes {
|
||||||
|
bytesCodes += len(code)
|
||||||
|
}
|
||||||
|
bytesState := 0
|
||||||
|
for node := range w.State {
|
||||||
|
bytesState += len(node)
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal))
|
||||||
|
fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock))
|
||||||
|
fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders))
|
||||||
|
fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState))
|
||||||
|
fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes))
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the pre-state root from the first header.
|
||||||
|
//
|
||||||
|
// Note, this method will panic in case of a bad witness (but RLP decoding will
|
||||||
|
// sanitize it and fail before that).
|
||||||
|
func (w *Witness) Root() common.Hash {
|
||||||
|
return w.Headers[0].Root
|
||||||
|
}
|
||||||
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
)
|
)
|
||||||
|
|
@ -35,7 +35,10 @@ type Validator interface {
|
||||||
|
|
||||||
// ValidateState validates the given statedb and optionally the receipts and
|
// ValidateState validates the given statedb and optionally the receipts and
|
||||||
// gas used.
|
// gas used.
|
||||||
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, rootCheck bool) (common.Hash, error)
|
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error
|
||||||
|
|
||||||
|
// ValidateWitness cross validates a block execution with stateless remote clients.
|
||||||
|
ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefetcher is an interface for pre-caching transaction signatures and state.
|
// Prefetcher is an interface for pre-caching transaction signatures and state.
|
||||||
|
|
@ -51,5 +54,5 @@ type Processor interface {
|
||||||
// Process processes the state changes according to the Ethereum rules by running
|
// 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 transaction messages using the statedb and applying any rewards to both
|
||||||
// the processor (coinbase) and any included uncles.
|
// the processor (coinbase) and any included uncles.
|
||||||
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error)
|
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,18 +174,6 @@ type Body struct {
|
||||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
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.
|
// Block represents an Ethereum block.
|
||||||
//
|
//
|
||||||
// Note the Block type tries to be 'immutable', and contains certain caches that rely
|
// Note the Block type tries to be 'immutable', and contains certain caches that rely
|
||||||
|
|
|
||||||
|
|
@ -231,10 +231,8 @@ 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.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
code := evm.StateDB.GetCode(addr)
|
code := evm.StateDB.GetCode(addr)
|
||||||
codeCopy := make([]byte, len(code))
|
|
||||||
copy(codeCopy[:], code[:])
|
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddCode(evm.StateDB.GetCodeHash(addr), codeCopy)
|
witness.AddCode(code)
|
||||||
}
|
}
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
ret, err = nil, nil // gas is unchanged
|
ret, err = nil, nil // gas is unchanged
|
||||||
|
|
@ -304,7 +302,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, 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)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
|
|
@ -354,7 +352,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, 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)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
|
|
@ -412,7 +410,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
||||||
if witness := evm.StateDB.Witness(); witness != nil {
|
if witness := evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
witness.AddCode(evm.StateDB.GetCode(addrCopy))
|
||||||
}
|
}
|
||||||
contract.SetCallCode(&addrCopy, 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
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
|
|
|
||||||
|
|
@ -342,9 +342,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||||
slot := scope.Stack.peek()
|
slot := scope.Stack.peek()
|
||||||
address := slot.Bytes20()
|
address := slot.Bytes20()
|
||||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||||
code := interpreter.evm.StateDB.GetCode(address)
|
witness.AddCode(interpreter.evm.StateDB.GetCode(address))
|
||||||
codeHash := interpreter.evm.StateDB.GetCodeHash(address)
|
|
||||||
witness.AddCode(codeHash, code)
|
|
||||||
}
|
}
|
||||||
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
|
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -384,12 +382,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||||
uint64CodeOffset = math.MaxUint64
|
uint64CodeOffset = math.MaxUint64
|
||||||
}
|
}
|
||||||
addr := common.Address(a.Bytes20())
|
addr := common.Address(a.Bytes20())
|
||||||
|
code := interpreter.evm.StateDB.GetCode(addr)
|
||||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||||
hash := interpreter.evm.StateDB.GetCodeHash(addr)
|
witness.AddCode(code)
|
||||||
code := interpreter.evm.StateDB.GetCode(addr)
|
|
||||||
witness.AddCode(hash, code)
|
|
||||||
}
|
}
|
||||||
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
codeCopy := getData(code, uint64CodeOffset, length.Uint64())
|
||||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -427,11 +424,6 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||||
if interpreter.evm.StateDB.Empty(address) {
|
if interpreter.evm.StateDB.Empty(address) {
|
||||||
slot.Clear()
|
slot.Clear()
|
||||||
} else {
|
} 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())
|
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -461,7 +453,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
||||||
if num64 >= lower && num64 < upper {
|
if num64 >= lower && num64 < upper {
|
||||||
res := interpreter.evm.Context.GetHash(num64)
|
res := interpreter.evm.Context.GetHash(num64)
|
||||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||||
witness.AddBlockHash(res, num64)
|
witness.AddBlockHash(num64)
|
||||||
}
|
}
|
||||||
num.SetBytes(res[:])
|
num.SetBytes(res[:])
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,8 @@ package vm
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -90,7 +89,7 @@ type StateDB interface {
|
||||||
AddLog(*types.Log)
|
AddLog(*types.Log)
|
||||||
AddPreimage(common.Hash, []byte)
|
AddPreimage(common.Hash, []byte)
|
||||||
|
|
||||||
Witness() *state.Witness
|
Witness() *stateless.Witness
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
|
||||||
if blockCtx != nil {
|
if blockCtx != nil {
|
||||||
context = *blockCtx
|
context = *blockCtx
|
||||||
} else {
|
} else {
|
||||||
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, nil)
|
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
|
||||||
}
|
}
|
||||||
return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig)
|
return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils/stateless"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -445,17 +443,3 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
|
||||||
}
|
}
|
||||||
return api.eth.blockchain.GetTrieFlushInterval().String(), nil
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
||||||
// Assemble the call and the call context
|
// Assemble the call and the call context
|
||||||
var (
|
var (
|
||||||
msgContext = core.NewEVMTxContext(call)
|
msgContext = core.NewEVMTxContext(call)
|
||||||
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, nil)
|
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
|
||||||
|
|
||||||
dirtyState = opts.State.Copy()
|
dirtyState = opts.State.Copy()
|
||||||
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
|
||||||
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
||||||
return nil, nil, fmt.Errorf("block #%d not found", next)
|
return nil, nil, fmt.Errorf("block #%d not found", next)
|
||||||
}
|
}
|
||||||
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil)
|
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
|
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.
|
// Insert parent beacon block root in the state as per EIP-4788.
|
||||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil)
|
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
|
||||||
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
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
|
// Assemble the transaction call message and return if the requested offset
|
||||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil)
|
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||||
if idx == txIndex {
|
if idx == txIndex {
|
||||||
return tx, context, statedb, release, nil
|
return tx, context, statedb, release, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
||||||
for task := range taskCh {
|
for task := range taskCh {
|
||||||
var (
|
var (
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
|
signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
|
||||||
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, nil)
|
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil)
|
||||||
)
|
)
|
||||||
// Trace all the transactions contained within
|
// Trace all the transactions contained within
|
||||||
for i, tx := range task.block.Transactions() {
|
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
|
// Insert block's parent beacon block root in the state
|
||||||
// as per EIP-4788.
|
// as per EIP-4788.
|
||||||
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
||||||
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, nil)
|
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
|
||||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
|
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||||
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
}
|
}
|
||||||
|
|
@ -527,7 +527,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
||||||
roots []common.Hash
|
roots []common.Hash
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||||
chainConfig = api.backend.ChainConfig()
|
chainConfig = api.backend.ChainConfig()
|
||||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
||||||
)
|
)
|
||||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
|
|
@ -605,7 +605,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
var (
|
var (
|
||||||
txs = block.Transactions()
|
txs = block.Transactions()
|
||||||
blockHash = block.Hash()
|
blockHash = block.Hash()
|
||||||
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||||
results = make([]*txTraceResult, len(txs))
|
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
|
// as the GetHash function of BlockContext is not safe for
|
||||||
// concurrent use.
|
// concurrent use.
|
||||||
// See: https://github.com/ethereum/go-ethereum/issues/29114
|
// See: https://github.com/ethereum/go-ethereum/issues/29114
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
|
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
|
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
|
// Feed the transactions into the tracers and return
|
||||||
var failed error
|
var failed error
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
txloop:
|
txloop:
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
// Send the trace task over for execution
|
// Send the trace task over for execution
|
||||||
|
|
@ -755,7 +755,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||||
dumps []string
|
dumps []string
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||||
chainConfig = api.backend.ChainConfig()
|
chainConfig = api.backend.ChainConfig()
|
||||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
canon = true
|
canon = true
|
||||||
)
|
)
|
||||||
// Check if there are any overrides: the caller may wish to enable a future
|
// 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()
|
defer release()
|
||||||
|
|
||||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
// Apply the customization rules if required.
|
// Apply the customization rules if required.
|
||||||
if config != nil {
|
if config != nil {
|
||||||
if err := config.StateOverrides.Apply(statedb); err != nil {
|
if err := config.StateOverrides.Apply(statedb); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
|
||||||
for idx, tx := range block.Transactions() {
|
for idx, tx := range block.Transactions() {
|
||||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(block.Header(), b.chain, nil, nil)
|
context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
|
||||||
if idx == txIndex {
|
if idx == txIndex {
|
||||||
return tx, context, statedb, release, nil
|
return tx, context, statedb, release, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1110,7 +1110,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Get a new instance of the EVM.
|
// Get a new instance of the EVM.
|
||||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, nil)
|
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||||||
if blockOverrides != nil {
|
if blockOverrides != nil {
|
||||||
blockOverrides.Apply(&blockCtx)
|
blockOverrides.Apply(&blockCtx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -569,7 +569,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state
|
||||||
vmConfig = b.chain.GetVMConfig()
|
vmConfig = b.chain.GetVMConfig()
|
||||||
}
|
}
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(header, b.chain, nil, nil)
|
context := core.NewEVMBlockContext(header, b.chain, nil)
|
||||||
if blockContext != nil {
|
if blockContext != nil {
|
||||||
context = *blockContext
|
context = *blockContext
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if header.ParentBeaconRoot != nil {
|
if header.ParentBeaconRoot != nil {
|
||||||
context := core.NewEVMBlockContext(header, miner.chain, nil, nil)
|
context := core.NewEVMBlockContext(header, miner.chain, nil)
|
||||||
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
|
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
|
||||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
|
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,101 +18,19 @@ package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"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) {
|
func TestBlockchain(t *testing.T) {
|
||||||
bt := new(testMatcher)
|
bt := new(testMatcher)
|
||||||
// General state tests are 'exported' as blockchain tests, but we can run them natively.
|
|
||||||
// For speedier CI-runs, the line below can be uncommented, so those are skipped.
|
// We are running most of GeneralStatetests to tests witness support, even
|
||||||
// For now, in hardfork-times (Berlin), we run the tests both as StateTests and
|
// though they are ran as state tests too. Still, the performance tests are
|
||||||
// as blockchain tests, since the latter also covers things like receipt root
|
// less about state andmore about EVM number crunching, so skip those.
|
||||||
bt.skipLoad(`^GeneralStateTests/`)
|
bt.skipLoad(`^GeneralStateTests/VMTests/vmPerformance`)
|
||||||
|
|
||||||
// Skip random failures due to selfish mining test
|
// Skip random failures due to selfish mining test
|
||||||
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
|
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
|
||||||
|
|
@ -152,33 +70,25 @@ func TestExecutionSpecBlocktests(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||||
// If -short flag is used, we don't execute all four permutations, only one.
|
// Define all the different flag combinations we should run the tests with,
|
||||||
executionMask := 0xf
|
// picking only one for short tests.
|
||||||
|
//
|
||||||
|
// Note, witness building and self-testing is always enabled as it's a very
|
||||||
|
// good test to ensure that we don't break it.
|
||||||
|
var (
|
||||||
|
snapshotConf = []bool{false, true}
|
||||||
|
dbschemeConf = []string{rawdb.HashScheme, rawdb.PathScheme}
|
||||||
|
)
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
executionMask = (1 << (rand.Int63() & 4))
|
snapshotConf = []bool{snapshotConf[rand.Int()%2]}
|
||||||
|
dbschemeConf = []string{dbschemeConf[rand.Int()%2]}
|
||||||
}
|
}
|
||||||
if executionMask&0x1 != 0 {
|
for _, snapshot := range snapshotConf {
|
||||||
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil {
|
for _, dbscheme := range dbschemeConf {
|
||||||
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil {
|
||||||
return
|
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
if executionMask&0x2 != 0 {
|
|
||||||
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil {
|
|
||||||
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if executionMask&0x4 != 0 {
|
|
||||||
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil {
|
|
||||||
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if executionMask&0x8 != 0 {
|
|
||||||
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil {
|
|
||||||
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"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"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -37,6 +34,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"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/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -112,15 +110,7 @@ type btHeaderMarshaling struct {
|
||||||
ExcessBlobGas *math.HexOrDecimal64
|
ExcessBlobGas *math.HexOrDecimal64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, 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]
|
config, ok := Forks[t.json.Network]
|
||||||
if !ok {
|
if !ok {
|
||||||
return UnsupportedForkError{t.json.Network}
|
return UnsupportedForkError{t.json.Network}
|
||||||
|
|
@ -161,7 +151,8 @@ func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, trace
|
||||||
cache.SnapshotWait = true
|
cache.SnapshotWait = true
|
||||||
}
|
}
|
||||||
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
|
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
|
||||||
Tracer: tracer,
|
Tracer: tracer,
|
||||||
|
EnableWitnessCollection: witness,
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -194,29 +185,7 @@ func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, trace
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := t.validateImportedHeaders(chain, validBlocks); err != nil {
|
return t.validateImportedHeaders(chain, validBlocks)
|
||||||
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 {
|
func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
|
||||||
|
|
|
||||||
|
|
@ -302,7 +302,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
||||||
|
|
||||||
// Prepare the EVM.
|
// Prepare the EVM.
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil)
|
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
|
||||||
context.GetHash = vmTestBlockHash
|
context.GetHash = vmTestBlockHash
|
||||||
context.BaseFee = baseFee
|
context.BaseFee = baseFee
|
||||||
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig)
|
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig)
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
||||||
|
|
||||||
// Prepare the EVM.
|
// Prepare the EVM.
|
||||||
txContext := core.NewEVMTxContext(msg)
|
txContext := core.NewEVMTxContext(msg)
|
||||||
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil)
|
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
|
||||||
context.GetHash = vmTestBlockHash
|
context.GetHash = vmTestBlockHash
|
||||||
context.BaseFee = baseFee
|
context.BaseFee = baseFee
|
||||||
context.Random = nil
|
context.Random = nil
|
||||||
|
|
|
||||||
|
|
@ -214,8 +214,9 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
|
||||||
return t.db.Preimage(common.BytesToHash(shaKey))
|
return t.db.Preimage(common.BytesToHash(shaKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *StateTrie) AccessList() map[string][]byte {
|
// Witness returns a set containing all trie nodes that have been accessed.
|
||||||
return t.trie.AccessList()
|
func (t *StateTrie) Witness() map[string]struct{} {
|
||||||
|
return t.trie.Witness()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit collects all dirty nodes in the trie and replaces them with the
|
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||||
|
|
|
||||||
18
trie/trie.go
18
trie/trie.go
|
|
@ -601,12 +601,6 @@ func (t *Trie) Hash() common.Hash {
|
||||||
return common.BytesToHash(hash.(hashNode))
|
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
|
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
// corresponding node hash. All collected nodes (including dirty leaves if
|
||||||
// collectLeaf is true) will be encapsulated into a nodeset for return.
|
// collectLeaf is true) will be encapsulated into a nodeset for return.
|
||||||
|
|
@ -667,6 +661,18 @@ func (t *Trie) hashRoot() (node, node) {
|
||||||
return hashed, cached
|
return hashed, cached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Witness returns a set containing all trie nodes that have been accessed.
|
||||||
|
func (t *Trie) Witness() map[string]struct{} {
|
||||||
|
if len(t.tracer.accessList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
witness := make(map[string]struct{})
|
||||||
|
for _, node := range t.tracer.accessList {
|
||||||
|
witness[string(node)] = struct{}{}
|
||||||
|
}
|
||||||
|
return witness
|
||||||
|
}
|
||||||
|
|
||||||
// Reset drops the referenced root node and cleans all internal state.
|
// Reset drops the referenced root node and cleans all internal state.
|
||||||
func (t *Trie) Reset() {
|
func (t *Trie) Reset() {
|
||||||
t.root = nil
|
t.root = nil
|
||||||
|
|
|
||||||
|
|
@ -370,8 +370,7 @@ func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
|
||||||
return t.reader.node(path, common.Hash{})
|
return t.reader.node(path, common.Hash{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessList returns a map of path->blob containing all trie nodes that have
|
// Witness returns a set containing all trie nodes that have been accessed.
|
||||||
// been accessed.
|
func (t *VerkleTrie) Witness() map[string]struct{} {
|
||||||
func (t *VerkleTrie) AccessList() map[string][]byte {
|
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,6 @@ var HashDefaults = &Config{
|
||||||
HashDB: hashdb.Defaults,
|
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
|
// backend defines the methods needed to access/update trie nodes in different
|
||||||
// state scheme.
|
// state scheme.
|
||||||
type backend interface {
|
type backend interface {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue