mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
all: add block access list construction via flag --experimentalbal. When enabled, post-Cancun blocks which lack access lists will have them constructed on execution during import. When importing blocks which contain access lists, transaction execution and state root calculation is performed in parallel.
This commit is contained in:
parent
961f517a26
commit
cb541b833d
91 changed files with 12472 additions and 380 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
)
|
||||
|
||||
var _ = (*executableDataMarshaling)(nil)
|
||||
|
|
@ -34,6 +35,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||
}
|
||||
var enc ExecutableData
|
||||
|
|
@ -59,6 +61,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
enc.Withdrawals = e.Withdrawals
|
||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||
enc.BlockAccessList = e.BlockAccessList
|
||||
enc.ExecutionWitness = e.ExecutionWitness
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
|
@ -83,6 +86,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||
}
|
||||
var dec ExecutableData
|
||||
|
|
@ -157,6 +161,9 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
if dec.ExcessBlobGas != nil {
|
||||
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.BlockAccessList != nil {
|
||||
e.BlockAccessList = dec.BlockAccessList
|
||||
}
|
||||
if dec.ExecutionWitness != nil {
|
||||
e.ExecutionWitness = dec.ExecutionWitness
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ package engine
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"math/big"
|
||||
"slices"
|
||||
|
||||
|
|
@ -50,6 +53,7 @@ var (
|
|||
// ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new
|
||||
// fields: blobGasUsed and excessBlobGas.
|
||||
PayloadV3 PayloadVersion = 0x3
|
||||
PayloadV4 PayloadVersion = 0x4
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||
|
|
@ -90,6 +94,7 @@ type ExecutableData struct {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -293,6 +298,16 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
requestsHash = &h
|
||||
}
|
||||
|
||||
var blockAccessListHash *common.Hash
|
||||
body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}
|
||||
if data.BlockAccessList != nil {
|
||||
body.AccessList = data.BlockAccessList
|
||||
// Calculate the hash of the RLP-encoded BAL
|
||||
rlpBytes, _ := rlp.EncodeToBytes(data.BlockAccessList)
|
||||
h := crypto.Keccak256Hash(rlpBytes)
|
||||
blockAccessListHash = &h
|
||||
}
|
||||
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
|
|
@ -314,9 +329,10 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
BlockAccessListHash: blockAccessListHash,
|
||||
}
|
||||
return types.NewBlockWithHeader(header).
|
||||
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).
|
||||
WithBody(body).
|
||||
WithWitness(data.ExecutionWitness),
|
||||
nil
|
||||
}
|
||||
|
|
@ -343,6 +359,7 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
|
|||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
ExecutionWitness: block.ExecutionWitness(),
|
||||
BlockAccessList: block.Body().AccessList,
|
||||
}
|
||||
|
||||
// Add blobs.
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
|
|||
continue
|
||||
}
|
||||
result := &testResult{Name: name, Pass: true}
|
||||
if err := tests[name].Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
|
||||
if err := tests[name].Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), false, tracer, func(res error, chain *core.BlockChain) {
|
||||
if ctx.Bool(DumpFlag.Name) {
|
||||
if s, _ := chain.State(); s != nil {
|
||||
result.State = dump(s)
|
||||
|
|
|
|||
|
|
@ -316,11 +316,11 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
if _, _, err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
if _, _, err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ func runCmd(ctx *cli.Context) error {
|
|||
input = append(code, input...)
|
||||
execFunc = func() ([]byte, uint64, error) {
|
||||
// don't mutate the state!
|
||||
runtimeConfig.State = prestate.Copy()
|
||||
runtimeConfig.State = prestate.Copy().(*state.StateDB)
|
||||
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
|
||||
return output, gasLeft, err
|
||||
}
|
||||
|
|
@ -326,7 +326,7 @@ func runCmd(ctx *cli.Context) error {
|
|||
}
|
||||
execFunc = func() ([]byte, uint64, error) {
|
||||
// don't mutate the state!
|
||||
runtimeConfig.State = prestate.Copy()
|
||||
runtimeConfig.State = prestate.Copy().(*state.StateDB)
|
||||
output, gasLeft, err := runtime.Call(receiver, input, &runtimeConfig)
|
||||
return output, initialGas - gasLeft, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ package beacon
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
state2 "github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -343,7 +343,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
||||
// assembling the block.
|
||||
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state state2.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if !beacon.IsPoSHeader(header) {
|
||||
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts)
|
||||
}
|
||||
|
|
@ -364,6 +364,11 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
|||
// Assign the final state root to header.
|
||||
header.Root = state.IntermediateRoot(true)
|
||||
|
||||
// embed the block access list in the body
|
||||
if chain.Config().IsAmsterdam(header.Number, header.Time) {
|
||||
body.AccessList = state.(*state2.AccessListCreationDB).ConstructedBlockAccessList().ToEncodingObj()
|
||||
}
|
||||
|
||||
// Assemble the final block.
|
||||
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||
// nor block rewards given, and returns the final block.
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state state.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("clique does not support withdrawals")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
state2 "github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ type Engine interface {
|
|||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state state2.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel.
|
||||
|
|
|
|||
|
|
@ -511,7 +511,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||
// uncle rewards, setting the final state and assembling the block.
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state state.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("ethash does not support withdrawals")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package core
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -148,7 +147,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
|||
|
||||
// ValidateState validates the various changes that happen after a state transition,
|
||||
// such as amount of used gas, the receipt roots and the state root itself.
|
||||
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error {
|
||||
func (v *BlockValidator) ValidateState(block *types.Block, statedb state.BlockProcessingDB, res *ProcessResult, validateStateRoot, stateless bool) error {
|
||||
if res == nil {
|
||||
return errors.New("nil ProcessResult value")
|
||||
}
|
||||
|
|
@ -185,11 +184,14 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
|
|||
} else if res.Requests != nil {
|
||||
return errors.New("block has requests before prague fork")
|
||||
}
|
||||
|
||||
if validateStateRoot {
|
||||
// Validate the state root against the received state root and throw
|
||||
// an error if they don't match.
|
||||
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
|
||||
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,13 @@ var (
|
|||
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
|
||||
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
|
||||
|
||||
// BAL-specific timers
|
||||
blockPreprocessingTimer = metrics.NewRegisteredResettingTimer("chain/preprocess", nil)
|
||||
blockPrestateLoadTimer = metrics.NewRegisteredResettingTimer("chain/prestateload", nil)
|
||||
txExecutionTimer = metrics.NewRegisteredResettingTimer("chain/txexecution", nil)
|
||||
stateRootCalctimer = metrics.NewRegisteredResettingTimer("chain/rootcalculation", nil)
|
||||
blockPostprocessingTimer = metrics.NewRegisteredResettingTimer("chain/postprocess", nil)
|
||||
|
||||
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
|
||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
|
||||
|
|
@ -203,6 +210,8 @@ type BlockChainConfig struct {
|
|||
|
||||
// StateSizeTracking indicates whether the state size tracking is enabled.
|
||||
StateSizeTracking bool
|
||||
// EnableBAL enables block access list creation and verification for post-Cancun blocks which contain access lists.
|
||||
EnableBAL bool
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default config.
|
||||
|
|
@ -339,9 +348,9 @@ type BlockChain struct {
|
|||
validator Validator // Block and state validator interface
|
||||
prefetcher Prefetcher
|
||||
processor Processor // Block transaction processor interface
|
||||
parallelProcessor ParallelStateProcessor
|
||||
logger *tracing.Hooks
|
||||
stateSizer *state.SizeTracker // State size tracking
|
||||
|
||||
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
|
||||
}
|
||||
|
||||
|
|
@ -400,6 +409,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
bc.validator = NewBlockValidator(chainConfig, bc)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(chainConfig, bc.hc)
|
||||
bc.parallelProcessor = NewParallelStateProcessor(chainConfig, bc.hc, bc.GetVMConfig())
|
||||
|
||||
genesisHeader := bc.GetHeaderByNumber(0)
|
||||
if genesisHeader == nil {
|
||||
|
|
@ -1910,15 +1920,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
|||
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
}
|
||||
|
||||
// The traced section of block import.
|
||||
start := time.Now()
|
||||
// construct or verify block access lists if BALs are enabled and
|
||||
// we are post-selfdestruct removal fork.
|
||||
enableBAL := (bc.cfg.EnableBALForTesting && bc.chainConfig.IsCancun(block.Number(), block.Time())) || bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
|
||||
enableBAL := (bc.cfg.EnableBAL && bc.chainConfig.IsCancun(block.Number(), block.Time())) || bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
|
||||
blockHasAccessList := block.Body().AccessList != nil
|
||||
makeBAL := enableBAL && !blockHasAccessList
|
||||
validateBAL := enableBAL && blockHasAccessList
|
||||
|
||||
// The traced section of block import.
|
||||
start := time.Now()
|
||||
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, makeBAL, validateBAL)
|
||||
if err != nil {
|
||||
return nil, it.index, err
|
||||
|
|
@ -2002,6 +2012,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
|
||||
if bc.cfg.NoPrefetch {
|
||||
statedb, err = state.New(parentRoot, bc.statedb)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -2041,7 +2052,10 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
// Disable tracing for prefetcher executions.
|
||||
vmCfg := bc.cfg.VmConfig
|
||||
vmCfg.Tracer = nil
|
||||
if block.Body().AccessList == nil {
|
||||
// only use the state prefetcher for non-BAL blocks.
|
||||
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
|
||||
}
|
||||
|
||||
blockPrefetchExecuteTimer.Update(time.Since(start))
|
||||
if interrupt.Load() {
|
||||
|
|
@ -2050,6 +2064,11 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}(time.Now(), throwaway, block)
|
||||
}
|
||||
|
||||
// TODO: can remove validateBAL parameter and just look at whether the block has an access list?
|
||||
if constructBALForTesting || validateBAL {
|
||||
statedb.EnableStateDiffRecording()
|
||||
}
|
||||
|
||||
// If we are past Byzantium, enable prefetching to pull in trie node paths
|
||||
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||
// useless due to the intermediate root hashing after each transaction.
|
||||
|
|
@ -2070,9 +2089,16 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
witnessStats = stateless.NewWitnessStats()
|
||||
}
|
||||
}
|
||||
|
||||
// access-list containing blocks don't use the prefetcher because
|
||||
// state root computation proceeds concurrently with transaction
|
||||
// execution, meaning the prefetcher doesn't have any time to run
|
||||
// before the trie nodes are needed for state root computation.
|
||||
if block.Body().AccessList == nil {
|
||||
statedb.StartPrefetcher("chain", witness, witnessStats)
|
||||
defer statedb.StopPrefetcher()
|
||||
}
|
||||
}
|
||||
|
||||
if bc.logger != nil && bc.logger.OnBlockStart != nil {
|
||||
bc.logger.OnBlockStart(tracing.BlockEvent{
|
||||
|
|
@ -2087,27 +2113,62 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}()
|
||||
}
|
||||
|
||||
blockHadBAL := block.Body().AccessList != nil
|
||||
var res *ProcessResult
|
||||
var resWithMetrics *ProcessResultWithMetrics
|
||||
var ptime, vtime time.Duration
|
||||
if block.Body().AccessList != nil {
|
||||
if block.NumberU64() == 0 {
|
||||
return nil, fmt.Errorf("genesis block cannot have a block access list")
|
||||
}
|
||||
// TODO: rename 'validateBAL' to indicate that it's for validating that the BAL
|
||||
// is present and we are after amsterdam fork. validateBAL=false is only used for
|
||||
// testing BALs in pre-Amsterdam blocks.
|
||||
if !validateBAL && !bc.chainConfig.IsAmsterdam(block.Number(), block.Time()) {
|
||||
bc.reportBlock(block, res, fmt.Errorf("received block containing access list before glamsterdam activated"))
|
||||
return nil, err
|
||||
}
|
||||
// Process block using the parent state as reference point
|
||||
pstart := time.Now()
|
||||
resWithMetrics, err = bc.parallelProcessor.Process(block, statedb, bc.cfg.VmConfig)
|
||||
if err != nil {
|
||||
// TODO: okay to pass nil here as execution result?
|
||||
bc.reportBlock(block, nil, err)
|
||||
return nil, err
|
||||
}
|
||||
ptime = time.Since(pstart)
|
||||
|
||||
vstart := time.Now()
|
||||
var err error
|
||||
err = bc.validator.ValidateState(block, statedb, resWithMetrics.ProcessResult, false, false)
|
||||
if err != nil {
|
||||
// TODO: okay to pass nil here as execution result?
|
||||
bc.reportBlock(block, nil, err)
|
||||
return nil, err
|
||||
}
|
||||
res = resWithMetrics.ProcessResult
|
||||
vtime = time.Since(vstart)
|
||||
} else {
|
||||
var balTracer *BlockAccessListTracer
|
||||
// Process block using the parent state as reference point
|
||||
if constructBALForTesting {
|
||||
balTracer, bc.cfg.VmConfig.Tracer = NewBlockAccessListTracer()
|
||||
}
|
||||
|
||||
// Process block using the parent state as reference point
|
||||
pstart := time.Now()
|
||||
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
|
||||
res, err = bc.processor.Process(block, statedb, bc.cfg.VmConfig)
|
||||
if err != nil {
|
||||
bc.reportBlock(block, res, err)
|
||||
return nil, err
|
||||
}
|
||||
ptime := time.Since(pstart)
|
||||
ptime = time.Since(pstart)
|
||||
|
||||
vstart := time.Now()
|
||||
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
|
||||
if err := bc.validator.ValidateState(block, statedb, res, true, false); err != nil {
|
||||
bc.reportBlock(block, res, err)
|
||||
return nil, err
|
||||
}
|
||||
vtime := time.Since(vstart)
|
||||
vtime = time.Since(vstart)
|
||||
|
||||
if constructBALForTesting {
|
||||
// very ugly... deep-copy the block body before setting the block access
|
||||
|
|
@ -2118,6 +2179,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
existingBody.AccessList = balTracer.AccessList().ToEncodingObj()
|
||||
block = block.WithBody(*existingBody)
|
||||
}
|
||||
}
|
||||
|
||||
// If witnesses was generated and stateless self-validation requested, do
|
||||
// that now. Self validation should *never* run in production, it's more of
|
||||
|
|
@ -2148,8 +2210,18 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}
|
||||
}
|
||||
|
||||
var proctime time.Duration
|
||||
if blockHadBAL {
|
||||
blockPreprocessingTimer.Update(resWithMetrics.PreProcessTime)
|
||||
blockPrestateLoadTimer.Update(resWithMetrics.PrestateLoadTime)
|
||||
txExecutionTimer.Update(resWithMetrics.ExecTime)
|
||||
stateRootCalctimer.Update(resWithMetrics.RootCalcTime)
|
||||
blockPostprocessingTimer.Update(resWithMetrics.PostProcessTime)
|
||||
|
||||
accountHashTimer.Update(statedb.AccountHashes)
|
||||
} else {
|
||||
xvtime := time.Since(xvstart)
|
||||
proctime := time.Since(startTime) // processing + validation + cross validation
|
||||
proctime = time.Since(startTime) // processing + validation + cross validation
|
||||
|
||||
// Update the metrics touched during block processing and validation
|
||||
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
|
||||
|
|
@ -2168,6 +2240,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
|
||||
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
|
||||
blockCrossValidationTimer.Update(xvtime) // The time spent on stateless cross validation
|
||||
}
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
|||
// The system contracts clear themselves on a system-initiated read.
|
||||
// When reading the requests mid-block, we don't want this behavior, so fork
|
||||
// off the statedb before executing the system calls.
|
||||
statedb = statedb.Copy()
|
||||
statedb = statedb.Copy().(*state.StateDB)
|
||||
}
|
||||
|
||||
if b.cm.config.IsPrague(b.header.Number, b.header.Time) {
|
||||
|
|
@ -328,11 +328,11 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
|||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
// EIP-7002
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
if _, _, err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
if _, _, err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package core
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -34,6 +35,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
|||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"`
|
||||
}
|
||||
var enc Genesis
|
||||
enc.Config = g.Config
|
||||
|
|
@ -56,6 +58,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
|||
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas)
|
||||
enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed)
|
||||
enc.BlockAccessListHash = g.BlockAccessListHash
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +80,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
|||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"`
|
||||
}
|
||||
var dec Genesis
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -133,5 +137,9 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
|||
if dec.BlobGasUsed != nil {
|
||||
g.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
|
||||
}
|
||||
fmt.Printf("dec al hash is %v\n", dec.BlockAccessListHash)
|
||||
if dec.BlockAccessListHash != nil {
|
||||
g.BlockAccessListHash = dec.BlockAccessListHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
374
core/parallel_state_processor.go
Normal file
374
core/parallel_state_processor.go
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProcessResultWithMetrics wraps ProcessResult with some metrics that are
|
||||
// emitted when executing blocks containing access lists.
|
||||
type ProcessResultWithMetrics struct {
|
||||
ProcessResult *ProcessResult
|
||||
// the time it took to load modified prestate accounts from disk and instantiate statedbs for execution
|
||||
PreProcessTime time.Duration
|
||||
// the time it took to validate the block post transaction execution and state root calculation
|
||||
PostProcessTime time.Duration
|
||||
// the time it took to hash the state root, including intermediate node reads
|
||||
RootCalcTime time.Duration
|
||||
// the time that it took to load the prestate for accounts that were updated as part of
|
||||
// the state root update
|
||||
PrestateLoadTime time.Duration
|
||||
// the time it took to execute all txs in the block
|
||||
ExecTime time.Duration
|
||||
}
|
||||
|
||||
// ParallelStateProcessor is used to execute and verify blocks containing
|
||||
// access lists.
|
||||
type ParallelStateProcessor struct {
|
||||
*StateProcessor
|
||||
vmCfg *vm.Config
|
||||
}
|
||||
|
||||
// NewParallelStateProcessor returns a new ParallelStateProcessor instance.
|
||||
func NewParallelStateProcessor(config *params.ChainConfig, chain *HeaderChain, cfg *vm.Config) ParallelStateProcessor {
|
||||
res := NewStateProcessor(config, chain)
|
||||
return ParallelStateProcessor{
|
||||
res,
|
||||
cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// called by resultHandler when all transactions have successfully executed.
|
||||
// performs post-tx state transition (system contracts and withdrawals)
|
||||
// and calculates the ProcessResult, returning it to be sent on resCh
|
||||
// by resultHandler
|
||||
func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateReads *bal.StateAccesses, tExecStart time.Time, postTxState *state.StateDB, receipts types.Receipts) *ProcessResultWithMetrics {
|
||||
tExec := time.Since(tExecStart)
|
||||
var requests [][]byte
|
||||
tPostprocessStart := time.Now()
|
||||
header := block.Header()
|
||||
|
||||
postTxState.SetAccessListIndex(len(block.Transactions()) + 1)
|
||||
var tracingStateDB = vm.StateDB(postTxState)
|
||||
if hooks := p.vmCfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(postTxState, hooks)
|
||||
}
|
||||
context := NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, p.config, *p.vmCfg)
|
||||
|
||||
// 1. order the receipts by tx index
|
||||
// 2. correctly calculate the cumulative gas used per receipt, returning bad block error if it goes over the allowed
|
||||
slices.SortFunc(receipts, func(a, b *types.Receipt) int {
|
||||
return cmp.Compare(a.TransactionIndex, b.TransactionIndex)
|
||||
})
|
||||
|
||||
var cumulativeGasUsed uint64
|
||||
var allLogs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
receipt.CumulativeGasUsed = cumulativeGasUsed + receipt.GasUsed
|
||||
cumulativeGasUsed += receipt.GasUsed
|
||||
if receipt.CumulativeGasUsed > header.GasLimit {
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")},
|
||||
}
|
||||
}
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
|
||||
computedDiff := &bal.StateDiff{make(map[common.Address]*bal.AccountState)}
|
||||
computedAccesses := make(bal.StateAccesses)
|
||||
|
||||
// Read requests if Prague is enabled.
|
||||
if p.config.IsPrague(block.Number(), block.Time()) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil {
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{Error: err},
|
||||
}
|
||||
}
|
||||
|
||||
// EIP-7002
|
||||
diff, accesses, err := ProcessWithdrawalQueue(&requests, evm)
|
||||
if err != nil {
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{Error: err},
|
||||
}
|
||||
}
|
||||
computedDiff = diff
|
||||
computedAccesses = *accesses
|
||||
|
||||
// EIP-7251
|
||||
diff, accesses, err = ProcessConsolidationQueue(&requests, evm)
|
||||
if err != nil {
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{Error: err},
|
||||
}
|
||||
}
|
||||
computedDiff.Merge(diff)
|
||||
computedAccesses.Merge(*accesses)
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
// invoke Finalise so that withdrawals are accounted for in the state diff
|
||||
finalDiff, finalAccesses := postTxState.Finalise(true)
|
||||
computedDiff.Merge(finalDiff)
|
||||
computedAccesses.Merge(*finalAccesses)
|
||||
|
||||
if err := postTxState.BlockAccessList().ValidateStateDiff(len(block.Transactions())+1, computedDiff); err != nil {
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{Error: err},
|
||||
}
|
||||
}
|
||||
|
||||
allStateReads.Merge(computedAccesses)
|
||||
if err := postTxState.BlockAccessList().ValidateStateReads(*allStateReads); err != nil {
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{Error: err},
|
||||
}
|
||||
}
|
||||
|
||||
tPostprocess := time.Since(tPostprocessStart)
|
||||
|
||||
return &ProcessResultWithMetrics{
|
||||
ProcessResult: &ProcessResult{
|
||||
Receipts: receipts,
|
||||
Requests: requests,
|
||||
Logs: allLogs,
|
||||
GasUsed: cumulativeGasUsed,
|
||||
},
|
||||
PostProcessTime: tPostprocess,
|
||||
ExecTime: tExec,
|
||||
}
|
||||
}
|
||||
|
||||
type txExecResult struct {
|
||||
idx int // transaction index
|
||||
receipt *types.Receipt
|
||||
err error // non-EVM error which would render the block invalid
|
||||
|
||||
mutations *bal.StateDiff
|
||||
stateReads *bal.StateAccesses
|
||||
}
|
||||
|
||||
// resultHandler polls until all transactions have finished executing and the
|
||||
// state root calculation is complete. The result is emitted on resCh.
|
||||
func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxStateReads *bal.StateAccesses, postTxState *state.StateDB, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) {
|
||||
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
|
||||
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
|
||||
var receipts []*types.Receipt
|
||||
gp := new(GasPool)
|
||||
gp.SetGas(block.GasLimit())
|
||||
var execErr error
|
||||
var numTxComplete int
|
||||
|
||||
allReads := make(bal.StateAccesses)
|
||||
allReads.Merge(*preTxStateReads)
|
||||
if len(block.Transactions()) > 0 {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case res := <-txResCh:
|
||||
if execErr == nil {
|
||||
if res.err != nil {
|
||||
execErr = res.err
|
||||
} else {
|
||||
if err := gp.SubGas(res.receipt.GasUsed); err != nil {
|
||||
execErr = err
|
||||
} else {
|
||||
receipts = append(receipts, res.receipt)
|
||||
allReads.Merge(*res.stateReads)
|
||||
}
|
||||
}
|
||||
}
|
||||
numTxComplete++
|
||||
if numTxComplete == len(block.Transactions()) {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if execErr != nil {
|
||||
resCh <- &ProcessResultWithMetrics{ProcessResult: &ProcessResult{Error: execErr}}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
execResults := p.prepareExecResult(block, &allReads, tExecStart, postTxState, receipts)
|
||||
rootCalcRes := <-stateRootCalcResCh
|
||||
|
||||
if execResults.ProcessResult.Error != nil {
|
||||
resCh <- execResults
|
||||
} else if rootCalcRes.err != nil {
|
||||
resCh <- &ProcessResultWithMetrics{ProcessResult: &ProcessResult{Error: rootCalcRes.err}}
|
||||
} else {
|
||||
execResults.RootCalcTime = rootCalcRes.rootCalcTime
|
||||
execResults.PrestateLoadTime = rootCalcRes.prestateLoadTime
|
||||
resCh <- execResults
|
||||
}
|
||||
}
|
||||
|
||||
type stateRootCalculationResult struct {
|
||||
err error
|
||||
prestateLoadTime time.Duration
|
||||
rootCalcTime time.Duration
|
||||
root common.Hash
|
||||
}
|
||||
|
||||
// calcAndVerifyRoot performs the post-state root hash calculation, verifying
|
||||
// it against what is reported by the block and returning a result on resCh.
|
||||
func (p *ParallelStateProcessor) calcAndVerifyRoot(preState *state.StateDB, block *types.Block, resCh chan stateRootCalculationResult) {
|
||||
// calculate and apply the block state modifications
|
||||
root, prestateLoadTime, rootCalcTime := preState.BlockAccessList().StateRoot(preState)
|
||||
|
||||
res := stateRootCalculationResult{
|
||||
root: root,
|
||||
prestateLoadTime: prestateLoadTime,
|
||||
rootCalcTime: rootCalcTime,
|
||||
}
|
||||
|
||||
if root != block.Root() {
|
||||
res.err = fmt.Errorf("state root mismatch. local: %x. remote: %x", root, block.Root())
|
||||
}
|
||||
resCh <- res
|
||||
}
|
||||
|
||||
// execTx executes single transaction returning a result which includes state accessed/modified
|
||||
func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transaction, idx int, db *state.StateDB, signer types.Signer) *txExecResult {
|
||||
header := block.Header()
|
||||
var tracingStateDB = vm.StateDB(db)
|
||||
if hooks := p.vmCfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(db, hooks)
|
||||
}
|
||||
context := NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, p.config, *p.vmCfg)
|
||||
|
||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not apply tx %d [%v]: %w", idx, tx.Hash().Hex(), err)
|
||||
return &txExecResult{err: err}
|
||||
}
|
||||
sender, _ := types.Sender(signer, tx)
|
||||
db.SetTxSender(sender)
|
||||
db.SetTxContext(tx.Hash(), idx)
|
||||
db.SetAccessListIndex(idx + 1)
|
||||
|
||||
evm.StateDB = db
|
||||
gp := new(GasPool)
|
||||
gp.SetGas(block.GasLimit())
|
||||
var gasUsed uint64
|
||||
mutatedState, accessedState, receipt, err := ApplyTransactionWithEVM(msg, gp, db, block.Number(), block.Hash(), context.Time, tx, &gasUsed, evm)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("could not apply tx %d [%v]: %w", idx, tx.Hash().Hex(), err)
|
||||
return &txExecResult{err: err}
|
||||
}
|
||||
|
||||
if err := db.BlockAccessList().ValidateStateDiff(idx+1, mutatedState); err != nil {
|
||||
return &txExecResult{err: err}
|
||||
}
|
||||
|
||||
return &txExecResult{
|
||||
idx: idx,
|
||||
receipt: receipt,
|
||||
mutations: mutatedState,
|
||||
stateReads: accessedState,
|
||||
}
|
||||
}
|
||||
|
||||
// Process performs EVM execution and state root computation for a block which is known
|
||||
// to contain an access list.
|
||||
func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResultWithMetrics, error) {
|
||||
var (
|
||||
header = block.Header()
|
||||
resCh = make(chan *ProcessResultWithMetrics)
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
)
|
||||
|
||||
txResCh := make(chan txExecResult)
|
||||
pStart := time.Now()
|
||||
var (
|
||||
tPreprocess time.Duration // time to create a set of prestates for parallel transaction execution
|
||||
tExecStart time.Time
|
||||
rootCalcResultCh = make(chan stateRootCalculationResult)
|
||||
)
|
||||
|
||||
// Mutate the block and state according to any hard-fork specs
|
||||
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
var (
|
||||
context vm.BlockContext
|
||||
)
|
||||
alReader := state.NewBALReader(block, statedb)
|
||||
statedb.SetBlockAccessList(alReader)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
var tracingStateDB = vm.StateDB(statedb)
|
||||
if hooks := cfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
}
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, p.config, cfg)
|
||||
|
||||
// validate the correctness of pre-transaction execution state changes
|
||||
computedPreTxDiff := &bal.StateDiff{make(map[common.Address]*bal.AccountState)}
|
||||
preTxStateReads := make(bal.StateAccesses)
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
diff, reads := ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
computedPreTxDiff.Merge(diff)
|
||||
preTxStateReads.Merge(*reads)
|
||||
}
|
||||
if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) {
|
||||
diff, reads := ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
computedPreTxDiff.Merge(diff)
|
||||
preTxStateReads.Merge(*reads)
|
||||
}
|
||||
|
||||
if err := statedb.BlockAccessList().ValidateStateDiff(0, computedPreTxDiff); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// compute the post-tx state prestate (before applying final block system calls and eip-4895 withdrawals)
|
||||
// the post-tx state transition is verified by resultHandler
|
||||
postTxState := statedb.Copy().(*state.StateDB)
|
||||
|
||||
tPreprocess = time.Since(pStart)
|
||||
|
||||
// execute transactions and state root calculation in parallel
|
||||
|
||||
tExecStart = time.Now()
|
||||
go p.resultHandler(block, &preTxStateReads, postTxState, tExecStart, txResCh, rootCalcResultCh, resCh)
|
||||
var workers errgroup.Group
|
||||
startingState := statedb.Copy()
|
||||
for i, tx := range block.Transactions() {
|
||||
tx := tx
|
||||
i := i
|
||||
workers.Go(func() error {
|
||||
res := p.execTx(block, tx, i, startingState.Copy().(*state.StateDB), signer)
|
||||
txResCh <- *res
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
go p.calcAndVerifyRoot(statedb, block, rootCalcResultCh)
|
||||
|
||||
res := <-resCh
|
||||
if res.ProcessResult.Error != nil {
|
||||
return nil, res.ProcessResult.Error
|
||||
}
|
||||
res.PreProcessTime = tPreprocess
|
||||
// res.PreProcessLoadTime = tPreprocessLoad
|
||||
return res, nil
|
||||
}
|
||||
376
core/state/bal_reader.go
Normal file
376
core/state/bal_reader.go
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: probably unnecessary to cache the resolved state object here as it will already be in the db cache?
|
||||
// ^ experiment with the performance of keeping this as-is vs just using the db cache.
|
||||
type prestateResolver struct {
|
||||
inProgress map[common.Address]chan struct{}
|
||||
resolved sync.Map
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func (p *prestateResolver) resolve(r Reader, addrs []common.Address) {
|
||||
p.inProgress = make(map[common.Address]chan struct{})
|
||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||
|
||||
for _, addr := range addrs {
|
||||
p.inProgress[addr] = make(chan struct{})
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
resolveAddr := addr
|
||||
go func() {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
acct, err := r.Account(resolveAddr)
|
||||
if err != nil {
|
||||
// TODO: what do here?
|
||||
}
|
||||
p.resolved.Store(resolveAddr, acct)
|
||||
close(p.inProgress[resolveAddr])
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prestateResolver) stop() {
|
||||
p.cancel()
|
||||
}
|
||||
|
||||
func (p *prestateResolver) account(addr common.Address) *types.StateAccount {
|
||||
if _, ok := p.inProgress[addr]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-p.inProgress[addr]:
|
||||
}
|
||||
res, exist := p.resolved.Load(addr)
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
return res.(*types.StateAccount)
|
||||
}
|
||||
|
||||
func (r *BALReader) initObjFromDiff(db *StateDB, addr common.Address, a *types.StateAccount, diff *bal.AccountState) *stateObject {
|
||||
var acct *types.StateAccount
|
||||
if a == nil {
|
||||
acct = &types.StateAccount{
|
||||
Nonce: 0,
|
||||
Balance: uint256.NewInt(0),
|
||||
Root: types.EmptyRootHash,
|
||||
CodeHash: types.EmptyCodeHash[:],
|
||||
}
|
||||
} else {
|
||||
acct = a.Copy()
|
||||
}
|
||||
if diff == nil {
|
||||
return newObject(db, addr, acct)
|
||||
}
|
||||
|
||||
if diff.Nonce != nil {
|
||||
acct.Nonce = *diff.Nonce
|
||||
}
|
||||
if diff.Balance != nil {
|
||||
acct.Balance = new(uint256.Int).Set(diff.Balance)
|
||||
}
|
||||
obj := newObject(db, addr, acct)
|
||||
if diff.Code != nil {
|
||||
obj.setCode(crypto.Keccak256Hash(diff.Code), diff.Code)
|
||||
}
|
||||
if diff.StorageWrites != nil {
|
||||
for key, val := range diff.StorageWrites {
|
||||
obj.pendingStorage[key] = val
|
||||
}
|
||||
}
|
||||
if obj.empty() {
|
||||
return nil
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
func (s *BALReader) initMutatedObjFromDiff(db *StateDB, addr common.Address, a *types.StateAccount, diff *bal.AccountState) *stateObject {
|
||||
var acct *types.StateAccount
|
||||
if a == nil {
|
||||
acct = &types.StateAccount{
|
||||
Nonce: 0,
|
||||
Balance: uint256.NewInt(0),
|
||||
Root: types.EmptyRootHash,
|
||||
CodeHash: types.EmptyCodeHash[:],
|
||||
}
|
||||
} else {
|
||||
acct = a.Copy()
|
||||
}
|
||||
obj := newObject(db, addr, acct)
|
||||
if diff.Nonce != nil {
|
||||
obj.SetNonce(*diff.Nonce)
|
||||
}
|
||||
if diff.Balance != nil {
|
||||
obj.SetBalance(new(uint256.Int).Set(diff.Balance))
|
||||
}
|
||||
if diff.Code != nil {
|
||||
obj.SetCode(crypto.Keccak256Hash(diff.Code), diff.Code)
|
||||
}
|
||||
if diff.StorageWrites != nil {
|
||||
for key, val := range diff.StorageWrites {
|
||||
obj.SetState(key, val)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
var IgnoredBALAddresses map[common.Address]struct{} = map[common.Address]struct{}{
|
||||
params.SystemAddress: {},
|
||||
}
|
||||
|
||||
// BALReader provides methods for reading account state from a block access
|
||||
// list. State values returned from the Reader methods must not be modified.
|
||||
type BALReader struct {
|
||||
block *types.Block
|
||||
accesses map[common.Address]*bal.AccountAccess
|
||||
prestateReader prestateResolver
|
||||
}
|
||||
|
||||
// NewBALReader constructs a new reader from an access list. db is expected to have been instantiated with a reader.
|
||||
func NewBALReader(block *types.Block, db *StateDB) *BALReader {
|
||||
r := &BALReader{accesses: make(map[common.Address]*bal.AccountAccess), block: block}
|
||||
for _, acctDiff := range *block.Body().AccessList {
|
||||
r.accesses[acctDiff.Address] = &acctDiff
|
||||
}
|
||||
r.prestateReader.resolve(db.Reader(), r.ModifiedAccounts())
|
||||
return r
|
||||
}
|
||||
|
||||
// ModifiedAccounts returns a list of all accounts with mutations in the access list
|
||||
func (r *BALReader) ModifiedAccounts() (res []common.Address) {
|
||||
for addr, access := range r.accesses {
|
||||
if len(access.NonceChanges) != 0 || len(access.CodeChanges) != 0 || len(access.StorageChanges) != 0 || len(access.BalanceChanges) != 0 {
|
||||
res = append(res, addr)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *BALReader) ValidateStateReads(allReads bal.StateAccesses) error {
|
||||
// 1. remove any slots from 'allReads' which were written
|
||||
// 2. validate that the read set in the BAL matches 'allReads' exactly
|
||||
for addr, reads := range allReads {
|
||||
balAcctDiff := r.readAccountDiff(addr, len(r.block.Transactions())+2)
|
||||
if balAcctDiff != nil {
|
||||
for writeSlot := range balAcctDiff.StorageWrites {
|
||||
delete(reads, writeSlot)
|
||||
}
|
||||
}
|
||||
|
||||
expectedReads := r.accesses[addr].StorageReads
|
||||
if len(reads) != len(expectedReads) {
|
||||
return fmt.Errorf("mismatch between the number of computed reads and number of expected reads")
|
||||
}
|
||||
|
||||
for _, slot := range expectedReads {
|
||||
if _, ok := reads[slot]; !ok {
|
||||
return fmt.Errorf("expected read is missing from BAL")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *BALReader) AccessedState() (res map[common.Address]map[common.Hash]struct{}) {
|
||||
res = make(map[common.Address]map[common.Hash]struct{})
|
||||
for addr, accesses := range r.accesses {
|
||||
if len(accesses.StorageReads) > 0 {
|
||||
res[addr] = make(map[common.Hash]struct{})
|
||||
for _, slot := range accesses.StorageReads {
|
||||
res[addr][slot] = struct{}{}
|
||||
}
|
||||
} else if len(accesses.BalanceChanges) == 0 && len(accesses.NonceChanges) == 0 && len(accesses.StorageChanges) == 0 && len(accesses.CodeChanges) == 0 {
|
||||
res[addr] = make(map[common.Hash]struct{})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: it feels weird that this modifies the prestate instance. However, it's needed because it will
|
||||
// subsequently be used in Commit.
|
||||
func (r *BALReader) StateRoot(prestate *StateDB) (root common.Hash, prestateLoadTime time.Duration, rootUpdateTime time.Duration) {
|
||||
lastIdx := len(r.block.Transactions()) + 1
|
||||
modifiedAccts := r.ModifiedAccounts()
|
||||
startPrestateLoad := time.Now()
|
||||
for _, addr := range modifiedAccts {
|
||||
diff := r.readAccountDiff(addr, lastIdx)
|
||||
acct := r.prestateReader.account(addr)
|
||||
obj := r.initMutatedObjFromDiff(prestate, addr, acct, diff)
|
||||
if obj != nil {
|
||||
prestate.setStateObject(obj)
|
||||
}
|
||||
}
|
||||
prestateLoadTime = time.Since(startPrestateLoad)
|
||||
rootUpdateStart := time.Now()
|
||||
root = prestate.IntermediateRoot(true)
|
||||
rootUpdateTime = time.Since(rootUpdateStart)
|
||||
return root, prestateLoadTime, rootUpdateTime
|
||||
}
|
||||
|
||||
// changesAt returns all state changes at the given index.
|
||||
func (r *BALReader) changesAt(idx int) *bal.StateDiff {
|
||||
res := &bal.StateDiff{make(map[common.Address]*bal.AccountState)}
|
||||
for addr, _ := range r.accesses {
|
||||
accountChanges := r.accountChangesAt(addr, idx)
|
||||
if accountChanges != nil {
|
||||
res.Mutations[addr] = accountChanges
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// accountChangesAt returns the state changes of an account at a given index,
|
||||
// or nil if there are no changes.
|
||||
func (r *BALReader) accountChangesAt(addr common.Address, idx int) *bal.AccountState {
|
||||
acct, exist := r.accesses[addr]
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res bal.AccountState
|
||||
|
||||
for i := len(acct.BalanceChanges) - 1; i >= 0; i-- {
|
||||
if acct.BalanceChanges[i].TxIdx == uint16(idx) {
|
||||
res.Balance = acct.BalanceChanges[i].Balance
|
||||
}
|
||||
if acct.BalanceChanges[i].TxIdx < uint16(idx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(acct.CodeChanges) - 1; i >= 0; i-- {
|
||||
if acct.CodeChanges[i].TxIdx == uint16(idx) {
|
||||
res.Code = acct.CodeChanges[i].Code
|
||||
break
|
||||
}
|
||||
if acct.CodeChanges[i].TxIdx < uint16(idx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(acct.NonceChanges) - 1; i >= 0; i-- {
|
||||
if acct.NonceChanges[i].TxIdx == uint16(idx) {
|
||||
res.Nonce = &acct.NonceChanges[i].Nonce
|
||||
break
|
||||
}
|
||||
if acct.NonceChanges[i].TxIdx < uint16(idx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(acct.StorageChanges) - 1; i >= 0; i-- {
|
||||
if res.StorageWrites == nil {
|
||||
res.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
slotWrites := acct.StorageChanges[i]
|
||||
|
||||
for j := len(slotWrites.Accesses) - 1; j >= 0; j-- {
|
||||
if slotWrites.Accesses[j].TxIdx == uint16(idx) {
|
||||
res.StorageWrites[slotWrites.Slot] = slotWrites.Accesses[j].ValueAfter
|
||||
break
|
||||
}
|
||||
if slotWrites.Accesses[j].TxIdx < uint16(idx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(res.StorageWrites) == 0 {
|
||||
res.StorageWrites = nil
|
||||
}
|
||||
}
|
||||
|
||||
if res.Code == nil && res.Nonce == nil && len(res.StorageWrites) == 0 && res.Balance == nil {
|
||||
return nil
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
func (r *BALReader) isModified(addr common.Address) bool {
|
||||
access, ok := r.accesses[addr]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return len(access.StorageChanges) > 0 || len(access.BalanceChanges) > 0 || len(access.CodeChanges) > 0 || len(access.NonceChanges) > 0
|
||||
}
|
||||
|
||||
func (r *BALReader) readAccount(db *StateDB, addr common.Address, idx int) *stateObject {
|
||||
diff := r.readAccountDiff(addr, idx)
|
||||
prestate := r.prestateReader.account(addr)
|
||||
return r.initObjFromDiff(db, addr, prestate, diff)
|
||||
}
|
||||
|
||||
// readAccountDiff returns the accumulated state changes of an account up through idx.
|
||||
func (r *BALReader) readAccountDiff(addr common.Address, idx int) *bal.AccountState {
|
||||
diff, exist := r.accesses[addr]
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res bal.AccountState
|
||||
|
||||
for i := 0; i < len(diff.BalanceChanges) && diff.BalanceChanges[i].TxIdx <= uint16(idx); i++ {
|
||||
res.Balance = diff.BalanceChanges[i].Balance
|
||||
}
|
||||
|
||||
for i := 0; i < len(diff.CodeChanges) && diff.CodeChanges[i].TxIdx <= uint16(idx); i++ {
|
||||
res.Code = diff.CodeChanges[i].Code
|
||||
}
|
||||
|
||||
for i := 0; i < len(diff.NonceChanges) && diff.NonceChanges[i].TxIdx <= uint16(idx); i++ {
|
||||
res.Nonce = &diff.NonceChanges[i].Nonce
|
||||
}
|
||||
|
||||
if len(diff.StorageChanges) > 0 {
|
||||
res.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
for _, slotWrites := range diff.StorageChanges {
|
||||
for i := 0; i < len(slotWrites.Accesses) && slotWrites.Accesses[i].TxIdx <= uint16(idx); i++ {
|
||||
res.StorageWrites[slotWrites.Slot] = slotWrites.Accesses[i].ValueAfter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &res
|
||||
}
|
||||
|
||||
// ValidateStateDiff returns an error if the computed state diff is not equal to
|
||||
// diff reported from the access list at the given index.
|
||||
func (r *BALReader) ValidateStateDiff(idx int, computedDiff *bal.StateDiff) error {
|
||||
balChanges := r.changesAt(idx)
|
||||
for addr, state := range balChanges.Mutations {
|
||||
computedAccountDiff, ok := computedDiff.Mutations[addr]
|
||||
if !ok {
|
||||
return fmt.Errorf("BAL contained account %x which wasn't present in computed state diff", addr)
|
||||
}
|
||||
|
||||
if !state.Eq(computedAccountDiff) {
|
||||
return fmt.Errorf("difference between computed state diff and BAL entry for account %x", addr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(balChanges.Mutations) != len(computedDiff.Mutations) {
|
||||
return fmt.Errorf("computed state diff contained mutated accounts which weren't reported in BAL")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
218
core/state/block_access_list_creation.go
Normal file
218
core/state/block_access_list_creation.go
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
type AccessListCreationDB struct {
|
||||
idx uint16
|
||||
inner BlockProcessingDB
|
||||
accessList *bal.ConstructionBlockAccessList
|
||||
}
|
||||
|
||||
func NewBlockAccessListBuilder(db BlockProcessingDB) *AccessListCreationDB {
|
||||
return &AccessListCreationDB{0, db, bal.NewConstructionBlockAccessList()}
|
||||
}
|
||||
func (a *AccessListCreationDB) SetAccessListIndex(idx int) {
|
||||
a.idx = uint16(idx)
|
||||
}
|
||||
|
||||
// ConstructedBlockAccessList retrieves the access list that has been constructed
|
||||
// by the StateDB instance, or nil if BAL construction was not enabled.
|
||||
func (a *AccessListCreationDB) ConstructedBlockAccessList() *bal.ConstructionBlockAccessList {
|
||||
return a.accessList
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) CreateAccount(address common.Address) {
|
||||
a.inner.CreateAccount(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) CreateContract(address common.Address) {
|
||||
a.inner.CreateContract(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SubBalance(address common.Address, u *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
|
||||
return a.inner.SubBalance(address, u, reason)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddBalance(address common.Address, u *uint256.Int, reason tracing.BalanceChangeReason) uint256.Int {
|
||||
return a.inner.AddBalance(address, u, reason)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetBalance(address common.Address) *uint256.Int {
|
||||
return a.inner.GetBalance(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetNonce(address common.Address) uint64 {
|
||||
return a.inner.GetNonce(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SetNonce(address common.Address, u uint64, reason tracing.NonceChangeReason) {
|
||||
a.inner.SetNonce(address, u, reason)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetCodeHash(address common.Address) common.Hash {
|
||||
return a.inner.GetCodeHash(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetCode(address common.Address) []byte {
|
||||
return a.inner.GetCode(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SetCode(addr common.Address, code []byte, reason tracing.CodeChangeReason) (prev []byte) {
|
||||
return a.inner.SetCode(addr, code, reason)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetCodeSize(address common.Address) int {
|
||||
return a.inner.GetCodeSize(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddRefund(u uint64) {
|
||||
a.inner.AddRefund(u)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SubRefund(u uint64) {
|
||||
a.inner.SubRefund(u)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetRefund() uint64 {
|
||||
return a.inner.GetRefund()
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetStateAndCommittedState(address common.Address, hash common.Hash) (common.Hash, common.Hash) {
|
||||
return a.inner.GetStateAndCommittedState(address, hash)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetState(address common.Address, hash common.Hash) common.Hash {
|
||||
return a.inner.GetState(address, hash)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SetState(address common.Address, hash common.Hash, hash2 common.Hash) common.Hash {
|
||||
return a.inner.SetState(address, hash, hash2)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||
return a.inner.GetStorageRoot(addr)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
|
||||
return a.inner.GetTransientState(addr, key)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SetTransientState(addr common.Address, key, value common.Hash) {
|
||||
a.inner.SetTransientState(addr, key, value)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SelfDestruct(address common.Address) uint256.Int {
|
||||
return a.inner.SelfDestruct(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) HasSelfDestructed(address common.Address) bool {
|
||||
return a.inner.HasSelfDestructed(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SelfDestruct6780(address common.Address) (uint256.Int, bool) {
|
||||
return a.inner.SelfDestruct6780(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Exist(address common.Address) bool {
|
||||
return a.inner.Exist(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Empty(address common.Address) bool {
|
||||
return a.inner.Empty(address)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddressInAccessList(addr common.Address) bool {
|
||||
return a.inner.AddressInAccessList(addr)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
|
||||
return a.inner.SlotInAccessList(addr, slot)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddAddressToAccessList(addr common.Address) {
|
||||
a.inner.AddAddressToAccessList(addr)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
|
||||
a.inner.AddSlotToAccessList(addr, slot)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) PointCache() *utils.PointCache {
|
||||
return a.inner.PointCache()
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
|
||||
a.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) RevertToSnapshot(i int) {
|
||||
a.inner.RevertToSnapshot(i)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Snapshot() int {
|
||||
return a.inner.Snapshot()
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddLog(log *types.Log) {
|
||||
a.inner.AddLog(log)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AddPreimage(hash common.Hash, bytes []byte) {
|
||||
a.inner.AddPreimage(hash, bytes)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Witness() *stateless.Witness {
|
||||
return a.inner.Witness()
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) AccessEvents() *AccessEvents {
|
||||
return a.inner.AccessEvents()
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) TxIndex() int {
|
||||
return a.inner.TxIndex()
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Finalise(b bool) (*bal.StateDiff, *bal.StateAccesses) {
|
||||
diff, accesses := a.inner.Finalise(b)
|
||||
a.accessList.ApplyDiff(uint(a.idx), diff)
|
||||
a.accessList.ApplyAccesses(*accesses) // TODO: can remove the pointer on accesses (map is already a reference type)
|
||||
return nil, nil // TODO: not sure what to do here. The diff has been applied to the access list so it is "owned" by the access list, not sure why a caller would need it...
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log {
|
||||
return a.inner.GetLogs(hash, blockNumber, blockHash, blockTime)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) IntermediateRoot(deleteEmpty bool) common.Hash {
|
||||
return a.inner.IntermediateRoot(deleteEmpty)
|
||||
}
|
||||
|
||||
func (a *AccessListCreationDB) Database() Database {
|
||||
return a.inner.Database()
|
||||
}
|
||||
func (a *AccessListCreationDB) GetTrie() Trie {
|
||||
return a.inner.GetTrie()
|
||||
}
|
||||
func (s *AccessListCreationDB) SetTxContext(thash common.Hash, ti int) {
|
||||
s.inner.SetTxContext(thash, ti)
|
||||
}
|
||||
|
||||
func (s *AccessListCreationDB) Error() error {
|
||||
return s.inner.Error()
|
||||
}
|
||||
|
||||
func (s *AccessListCreationDB) Copy() BlockProcessingDB {
|
||||
return &AccessListCreationDB{s.idx, s.inner.Copy(), s.accessList.Copy()}
|
||||
}
|
||||
|
||||
var _ BlockProcessingDB = &AccessListCreationDB{}
|
||||
|
|
@ -99,12 +99,18 @@ type Trie interface {
|
|||
// in the trie with provided address.
|
||||
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error
|
||||
|
||||
// UpdateAccountBatch attempts to update a list accounts in the batch manner.
|
||||
UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error
|
||||
|
||||
// UpdateStorage associates key with value in the trie. If value has length zero,
|
||||
// any existing value is deleted from the trie. The value bytes must not be modified
|
||||
// by the caller while they are stored in the trie. If a node was not found in the
|
||||
// database, a trie.MissingNodeError is returned.
|
||||
UpdateStorage(addr common.Address, key, value []byte) error
|
||||
|
||||
// UpdateStorageBatch attempts to update a list storages in the batch manner.
|
||||
UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error
|
||||
|
||||
// DeleteAccount abstracts an account deletion from the trie.
|
||||
DeleteAccount(address common.Address) error
|
||||
|
||||
|
|
|
|||
103
core/state/interface.go
Normal file
103
core/state/interface.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
type BlockProcessingDB interface {
|
||||
CreateAccount(common.Address)
|
||||
CreateContract(common.Address)
|
||||
|
||||
SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int
|
||||
AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int
|
||||
GetBalance(common.Address) *uint256.Int
|
||||
|
||||
GetNonce(common.Address) uint64
|
||||
SetNonce(common.Address, uint64, tracing.NonceChangeReason)
|
||||
|
||||
GetCodeHash(common.Address) common.Hash
|
||||
GetCode(common.Address) []byte
|
||||
|
||||
// SetCode sets the new code for the address, and returns the previous code, if any.
|
||||
SetCode(addr common.Address, code []byte, reason tracing.CodeChangeReason) (prev []byte)
|
||||
GetCodeSize(common.Address) int
|
||||
|
||||
AddRefund(uint64)
|
||||
SubRefund(uint64)
|
||||
GetRefund() uint64
|
||||
|
||||
GetStateAndCommittedState(common.Address, common.Hash) (common.Hash, common.Hash)
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
SetState(common.Address, common.Hash, common.Hash) common.Hash
|
||||
GetStorageRoot(addr common.Address) common.Hash
|
||||
|
||||
GetTransientState(addr common.Address, key common.Hash) common.Hash
|
||||
SetTransientState(addr common.Address, key, value common.Hash)
|
||||
|
||||
SelfDestruct(common.Address) uint256.Int
|
||||
HasSelfDestructed(common.Address) bool
|
||||
|
||||
// SelfDestruct6780 is post-EIP6780 selfdestruct, which means that it's a
|
||||
// send-all-to-beneficiary, unless the contract was created in this same
|
||||
// transaction, in which case it will be destructed.
|
||||
// This method returns the prior balance, along with a boolean which is
|
||||
// true iff the object was indeed destructed.
|
||||
SelfDestruct6780(common.Address) (uint256.Int, bool)
|
||||
|
||||
// Exist reports whether the given account exists in
|
||||
// Notably this also returns true for self-destructed accounts within the current transaction.
|
||||
Exist(common.Address) bool
|
||||
// Empty returns whether the given account is empty. Empty
|
||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||
Empty(common.Address) bool
|
||||
|
||||
AddressInAccessList(addr common.Address) bool
|
||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddAddressToAccessList(addr common.Address)
|
||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||
|
||||
// PointCache returns the point cache used in computations
|
||||
PointCache() *utils.PointCache
|
||||
|
||||
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||
|
||||
RevertToSnapshot(int)
|
||||
Snapshot() int
|
||||
|
||||
AddLog(*types.Log)
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
Witness() *stateless.Witness
|
||||
|
||||
AccessEvents() *AccessEvents
|
||||
|
||||
// Finalise must be invoked at the end of a transaction
|
||||
Finalise(bool) (*bal.StateDiff, *bal.StateAccesses)
|
||||
|
||||
// These two methods are not used in the EVM. however, I need them to be part of the interface
|
||||
// so that block processing/production can use instances of this interface so that the StateDB
|
||||
// wrapped with BAL creation functionality can be passed in case of BALs
|
||||
GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log
|
||||
|
||||
IntermediateRoot(deleteEmpty bool) common.Hash
|
||||
|
||||
Database() Database
|
||||
GetTrie() Trie
|
||||
SetTxContext(thash common.Hash, ti int)
|
||||
Error() error
|
||||
|
||||
TxIndex() int
|
||||
|
||||
Copy() BlockProcessingDB
|
||||
}
|
||||
|
|
@ -381,7 +381,7 @@ func (ch nonceChange) copy() journalEntry {
|
|||
}
|
||||
|
||||
func (ch codeChange) revert(s *StateDB) {
|
||||
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
||||
s.getStateObject(ch.account).setCodeModified(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
|
||||
}
|
||||
|
||||
func (ch codeChange) dirtied() *common.Address {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import (
|
|||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
|
@ -51,6 +53,9 @@ type stateObject struct {
|
|||
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
|
||||
data types.StateAccount // Account data with all mutations applied in the scope of block
|
||||
|
||||
txPreBalance *uint256.Int // the account balance after the last call to finalise
|
||||
txPreNonce uint64 // the account nonce after the last call to finalise
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
code []byte // contract bytecode, which gets set when code is loaded
|
||||
|
|
@ -73,6 +78,8 @@ type stateObject struct {
|
|||
// Cache flags.
|
||||
dirtyCode bool // true if the code was updated
|
||||
|
||||
nonFinalizedCode bool // true if the code was updated since the last call to finalise
|
||||
|
||||
// Flag whether the account was marked as self-destructed. The self-destructed
|
||||
// account is still accessible in the scope of same transaction.
|
||||
selfDestructed bool
|
||||
|
|
@ -82,6 +89,8 @@ type stateObject struct {
|
|||
// the contract is just created within the current transaction, or when the
|
||||
// object was previously existent and is being deployed as a contract within
|
||||
// the current transaction.
|
||||
//
|
||||
// the flag is set upon beginning of contract initcode execution, not when the code is actually deployed to the address.
|
||||
newContract bool
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +111,8 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
|||
addrHash: crypto.Keccak256Hash(address[:]),
|
||||
origin: origin,
|
||||
data: *acct,
|
||||
txPreBalance: acct.Balance.Clone(),
|
||||
txPreNonce: acct.Nonce,
|
||||
originStorage: make(Storage),
|
||||
dirtyStorage: make(Storage),
|
||||
pendingStorage: make(Storage),
|
||||
|
|
@ -175,6 +186,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
if value, pending := s.pendingStorage[key]; pending {
|
||||
return value
|
||||
}
|
||||
|
||||
if value, cached := s.originStorage[key]; cached {
|
||||
return value
|
||||
}
|
||||
|
|
@ -236,21 +248,62 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common
|
|||
|
||||
// finalise moves all dirty storage slots into the pending area to be hashed or
|
||||
// committed later. It is invoked at the end of every transaction.
|
||||
func (s *stateObject) finalise() {
|
||||
func (s *stateObject) finalise() *bal.AccountState {
|
||||
var accountPost bal.AccountState
|
||||
if s.db.enableStateDiffRecording {
|
||||
if s.Balance().Cmp(s.txPreBalance) != 0 {
|
||||
accountPost.Balance = s.Balance()
|
||||
}
|
||||
|
||||
if s.Nonce() != s.txPreNonce {
|
||||
accountPost.Nonce = new(uint64)
|
||||
*accountPost.Nonce = s.Nonce()
|
||||
}
|
||||
|
||||
// include account code changes: created contracts and 7702 delegation authority code changes
|
||||
if s.nonFinalizedCode {
|
||||
if s.code == nil {
|
||||
// code cleared (7702). code must be non-nil in the post to signal that it's part of the diff vs being unchanged.
|
||||
accountPost.Code = []byte{}
|
||||
} else {
|
||||
accountPost.Code = s.code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage))
|
||||
for key, value := range s.dirtyStorage {
|
||||
if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
|
||||
// The slot is reverted to its original value, delete the entry
|
||||
// to avoid thrashing the data structures.
|
||||
delete(s.uncommittedStorage, key)
|
||||
|
||||
if s.db.enableStateDiffRecording {
|
||||
if accountPost.StorageWrites == nil {
|
||||
accountPost.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
accountPost.StorageWrites[key] = value
|
||||
}
|
||||
} else if exist {
|
||||
// The slot is modified to another value and the slot has been
|
||||
// tracked for commit, do nothing here.
|
||||
// tracked for commit in uncommittedStorage.
|
||||
if s.db.enableStateDiffRecording {
|
||||
if accountPost.StorageWrites == nil {
|
||||
accountPost.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
accountPost.StorageWrites[key] = value
|
||||
}
|
||||
} else {
|
||||
// The slot is different from its original value and hasn't been
|
||||
// tracked for commit yet.
|
||||
s.uncommittedStorage[key] = s.GetCommittedState(key)
|
||||
slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure
|
||||
if s.db.enableStateDiffRecording {
|
||||
if accountPost.StorageWrites == nil {
|
||||
accountPost.StorageWrites = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
accountPost.StorageWrites[key] = value
|
||||
}
|
||||
}
|
||||
// Aggregate the dirty storage slots into the pending area. It might
|
||||
// be possible that the value of tracked slot here is same with the
|
||||
|
|
@ -272,6 +325,12 @@ func (s *stateObject) finalise() {
|
|||
// of the newly-created object as it's no longer eligible for self-destruct
|
||||
// by EIP-6780. For non-newly-created objects, it's a no-op.
|
||||
s.newContract = false
|
||||
|
||||
s.nonFinalizedCode = false
|
||||
|
||||
s.txPreBalance = s.data.Balance.Clone()
|
||||
s.txPreNonce = s.data.Nonce
|
||||
return &accountPost
|
||||
}
|
||||
|
||||
// updateTrie is responsible for persisting cached storage changes into the
|
||||
|
|
@ -324,6 +383,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
var (
|
||||
deletions []common.Hash
|
||||
used = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||
updateKeys [][]byte
|
||||
updateValues [][]byte
|
||||
)
|
||||
for key, origin := range s.uncommittedStorage {
|
||||
// Skip noop changes, persist actual changes
|
||||
|
|
@ -337,10 +398,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
continue
|
||||
}
|
||||
if (value != common.Hash{}) {
|
||||
if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil {
|
||||
s.db.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
updateKeys = append(updateKeys, key[:])
|
||||
updateValues = append(updateValues, common.TrimLeftZeroes(value[:]))
|
||||
s.db.StorageUpdated.Add(1)
|
||||
} else {
|
||||
deletions = append(deletions, key)
|
||||
|
|
@ -348,6 +407,12 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
// Cache the items for preloading
|
||||
used = append(used, key) // Copy needed for closure
|
||||
}
|
||||
if len(updateKeys) > 0 {
|
||||
if err := tr.UpdateStorageBatch(common.Address{}, updateKeys, updateValues); err != nil {
|
||||
s.db.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, key := range deletions {
|
||||
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
|
||||
s.db.setError(err)
|
||||
|
|
@ -493,6 +558,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
|||
dirtyCode: s.dirtyCode,
|
||||
selfDestructed: s.selfDestructed,
|
||||
newContract: s.newContract,
|
||||
txPreNonce: s.txPreNonce,
|
||||
txPreBalance: s.txPreBalance.Clone(),
|
||||
}
|
||||
if s.trie != nil {
|
||||
obj.trie = mustCopyTrie(s.trie)
|
||||
|
|
@ -551,14 +618,20 @@ func (s *stateObject) CodeSize() int {
|
|||
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) (prev []byte) {
|
||||
prev = slices.Clone(s.code)
|
||||
s.db.journal.setCode(s.address, prev)
|
||||
s.setCode(codeHash, code)
|
||||
s.setCodeModified(codeHash, code)
|
||||
return prev
|
||||
}
|
||||
|
||||
func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
|
||||
s.code = code
|
||||
s.data.CodeHash = codeHash[:]
|
||||
}
|
||||
|
||||
// setCodeModified sets the code and hash and dirty markers.
|
||||
func (s *stateObject) setCodeModified(codeHash common.Hash, code []byte) {
|
||||
s.setCode(codeHash, code)
|
||||
s.dirtyCode = true
|
||||
s.nonFinalizedCode = true
|
||||
}
|
||||
|
||||
func (s *stateObject) SetNonce(nonce uint64) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
|
|
@ -118,6 +120,14 @@ type StateDB struct {
|
|||
// The tx context and all occurred logs in the scope of transaction.
|
||||
thash common.Hash
|
||||
txIndex int
|
||||
sender common.Address
|
||||
|
||||
// block access list modifications will be recorded with this index.
|
||||
// 0 - state access before transaction execution
|
||||
// 1 -> len(block txs) - state access of each transaction
|
||||
// len(block txs) + 1 - state access after transaction execution.
|
||||
balIndex int
|
||||
|
||||
logs map[common.Hash][]*types.Log
|
||||
logSize uint
|
||||
|
||||
|
|
@ -139,6 +149,11 @@ type StateDB struct {
|
|||
witness *stateless.Witness
|
||||
witnessStats *stateless.WitnessStats
|
||||
|
||||
enableStateDiffRecording bool // if true, calls to Finalise will return the mutated state
|
||||
stateAccesses bal.StateAccesses // accounts/storage accessed during transaction execution
|
||||
|
||||
blockAccessList *BALReader
|
||||
|
||||
// Measurements gathered during execution for debugging purposes
|
||||
AccountReads time.Duration
|
||||
AccountHashes time.Duration
|
||||
|
|
@ -158,6 +173,10 @@ type StateDB struct {
|
|||
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
||||
}
|
||||
|
||||
func (s *StateDB) BlockAccessList() *BALReader {
|
||||
return s.blockAccessList
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
func New(root common.Hash, db Database) (*StateDB, error) {
|
||||
reader, err := db.Reader(root)
|
||||
|
|
@ -182,6 +201,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
|||
journal: newJournal(),
|
||||
accessList: newAccessList(),
|
||||
transientStorage: newTransientStorage(),
|
||||
stateAccesses: make(bal.StateAccesses),
|
||||
}
|
||||
if db.TrieDB().IsVerkle() {
|
||||
sdb.accessEvents = NewAccessEvents(db.PointCache())
|
||||
|
|
@ -189,6 +209,13 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
|||
return sdb, nil
|
||||
}
|
||||
|
||||
// EnableStateDiffRecording enables the recording of state modifications
|
||||
// which are accumulated at each call to Finalise and can be retrieved
|
||||
// using GetStateDiff.
|
||||
func (s *StateDB) EnableStateDiffRecording() {
|
||||
s.enableStateDiffRecording = true
|
||||
}
|
||||
|
||||
// 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
|
||||
// commit phase, most of the needed data is already hot.
|
||||
|
|
@ -285,6 +312,38 @@ func (s *StateDB) AddRefund(gas uint64) {
|
|||
s.refund += gas
|
||||
}
|
||||
|
||||
func (s *StateDB) SetBlockAccessList(al *BALReader) {
|
||||
s.blockAccessList = al
|
||||
}
|
||||
|
||||
// LoadModifiedPrestate instantiates the live object based on accounts
|
||||
// which appeared in the total state diff of a block, and were also preexisting.
|
||||
func (s *StateDB) LoadModifiedPrestate(addrs []common.Address) (res map[common.Address]*types.StateAccount) {
|
||||
stateAccounts := new(sync.Map)
|
||||
wg := new(sync.WaitGroup)
|
||||
res = make(map[common.Address]*types.StateAccount)
|
||||
|
||||
for _, addr := range addrs {
|
||||
wg.Add(1)
|
||||
go func(addr common.Address) {
|
||||
acct, err := s.reader.Account(addr)
|
||||
if err == nil && acct != nil { // TODO: what should we do if the error is not nil?
|
||||
stateAccounts.Store(addr, acct)
|
||||
}
|
||||
wg.Done()
|
||||
}(addr)
|
||||
}
|
||||
wg.Wait()
|
||||
stateAccounts.Range(func(addr any, val any) bool {
|
||||
address := addr.(common.Address)
|
||||
stateAccount := val.(*types.StateAccount)
|
||||
res[address] = stateAccount
|
||||
return true
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// SubRefund removes gas from the refund counter.
|
||||
// This method will panic if the refund counter goes below zero
|
||||
func (s *StateDB) SubRefund(gas uint64) {
|
||||
|
|
@ -375,6 +434,14 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
|||
// GetState retrieves the value associated with the specific key.
|
||||
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if s.enableStateDiffRecording {
|
||||
if _, shouldIgnore := IgnoredBALAddresses[addr]; !shouldIgnore {
|
||||
if _, ok := s.stateAccesses[addr]; !ok {
|
||||
s.stateAccesses[addr] = make(map[common.Hash]struct{})
|
||||
}
|
||||
s.stateAccesses[addr][hash] = struct{}{}
|
||||
}
|
||||
}
|
||||
if stateObject != nil {
|
||||
return stateObject.GetState(hash)
|
||||
}
|
||||
|
|
@ -576,6 +643,25 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
|||
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
}
|
||||
}
|
||||
func (s *StateDB) updateStateObjects(objs []*stateObject) {
|
||||
var addrs []common.Address
|
||||
var accts []*types.StateAccount
|
||||
|
||||
for _, obj := range objs {
|
||||
addrs = append(addrs, obj.Address())
|
||||
accts = append(accts, &obj.data)
|
||||
}
|
||||
|
||||
if err := s.trie.UpdateAccountBatch(addrs, accts, nil); err != nil {
|
||||
s.setError(fmt.Errorf("updateStateObjects error: %v", err))
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
if obj.dirtyCode {
|
||||
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteStateObject removes the given object from the state trie.
|
||||
func (s *StateDB) deleteStateObject(addr common.Address) {
|
||||
|
|
@ -587,6 +673,13 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
|
|||
// getStateObject retrieves a state object given by the address, returning nil if
|
||||
// the object is not found or was deleted in this execution context.
|
||||
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
||||
if s.enableStateDiffRecording {
|
||||
if _, shouldIgnore := IgnoredBALAddresses[addr]; !shouldIgnore {
|
||||
if _, ok := s.stateAccesses[addr]; !ok {
|
||||
s.stateAccesses[addr] = make(map[common.Hash]struct{})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Prefer live objects if any is available
|
||||
if obj := s.stateObjects[addr]; obj != nil {
|
||||
return obj
|
||||
|
|
@ -595,6 +688,24 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
if _, ok := s.stateObjectsDestruct[addr]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we are executing against a block access list, construct the account
|
||||
// state at the current tx index by applying the access-list diff on top
|
||||
// of the prestate value for the account.
|
||||
if s.blockAccessList != nil && s.balIndex != 0 && s.blockAccessList.isModified(addr) {
|
||||
acct := s.blockAccessList.readAccount(s, addr, s.balIndex-1)
|
||||
if acct != nil {
|
||||
s.setStateObject(acct)
|
||||
return acct
|
||||
}
|
||||
return nil
|
||||
|
||||
// if the acct was nil, it might be non-existent or was not explicitly requested for loading from the blockAcccessList object.
|
||||
// try to load it from the snapshot.
|
||||
|
||||
// TODO: if the acct was non-existent because it was deleted, we should just return nil herre.
|
||||
}
|
||||
|
||||
s.AccountLoaded++
|
||||
|
||||
start := time.Now()
|
||||
|
|
@ -631,6 +742,7 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
|
|||
if obj == nil {
|
||||
obj = s.createObject(addr)
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
@ -666,7 +778,7 @@ func (s *StateDB) CreateContract(addr common.Address) {
|
|||
|
||||
// Copy creates a deep, independent copy of the state.
|
||||
// Snapshots of the copied state cannot be applied to the copy.
|
||||
func (s *StateDB) Copy() *StateDB {
|
||||
func (s *StateDB) Copy() BlockProcessingDB {
|
||||
// Copy all the basic fields, initialize the memory ones
|
||||
state := &StateDB{
|
||||
db: s.db,
|
||||
|
|
@ -679,10 +791,16 @@ func (s *StateDB) Copy() *StateDB {
|
|||
refund: s.refund,
|
||||
thash: s.thash,
|
||||
txIndex: s.txIndex,
|
||||
balIndex: s.txIndex,
|
||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||
logSize: s.logSize,
|
||||
preimages: maps.Clone(s.preimages),
|
||||
|
||||
// don't deep-copy these
|
||||
enableStateDiffRecording: s.enableStateDiffRecording,
|
||||
stateAccesses: make(bal.StateAccesses), // Don't deep copy state accesses
|
||||
blockAccessList: s.blockAccessList,
|
||||
|
||||
// Do we need to copy the access list and transient storage?
|
||||
// In practice: No. At the start of a transaction, these two lists are empty.
|
||||
// In practice, we only ever copy state _between_ transactions/blocks, never
|
||||
|
|
@ -744,7 +862,11 @@ func (s *StateDB) GetRefund() uint64 {
|
|||
// Finalise finalises the state by removing the destructed objects and clears
|
||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
//
|
||||
// If EnableStateDiffRecording has been called, it returns a state diff containing
|
||||
// the state which was mutated since the previous invocation of Finalise. Otherwise, nil.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations *bal.StateDiff, accesses *bal.StateAccesses) {
|
||||
mutations = &bal.StateDiff{Mutations: make(map[common.Address]*bal.AccountState)}
|
||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
|
||||
for addr := range s.journal.dirties {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
|
|
@ -758,6 +880,24 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
continue
|
||||
}
|
||||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||
// record state diffs for any preexisting accounts which became empty
|
||||
if s.enableStateDiffRecording && obj.txPreBalance != nil && !obj.txPreBalance.IsZero() {
|
||||
if _, shouldIgnore := IgnoredBALAddresses[obj.address]; !shouldIgnore {
|
||||
// TODO: IsZero check is somehow needed for coinbase. figure out why.
|
||||
// TODO: the above check should be as easy as checking that the account was not selfdestructed in the
|
||||
// current transaction, but that causes spec tests to fail. need to figure out why.
|
||||
postState := bal.NewEmptyAccountState()
|
||||
postState.Balance = new(uint256.Int)
|
||||
mutations.Mutations[obj.address] = postState
|
||||
|
||||
// note that this account cannot have any storage accesses in the same tx:
|
||||
// * it cannot have code if it was previously-existing and became empty in this tx
|
||||
// * perhaps it could have had code deployed before selfdestruct removal, that set some storage
|
||||
// and the code was removed (pre-selfdestruct removal) leaving the storage. But the storage
|
||||
// won't be cleared from the state here right
|
||||
}
|
||||
}
|
||||
|
||||
delete(s.stateObjects, obj.address)
|
||||
s.markDelete(addr)
|
||||
// We need to maintain account deletions explicitly (will remain
|
||||
|
|
@ -767,10 +907,28 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
s.stateObjectsDestruct[obj.address] = obj
|
||||
}
|
||||
} else {
|
||||
obj.finalise()
|
||||
s.markUpdate(addr)
|
||||
accountPost := obj.finalise()
|
||||
|
||||
if s.enableStateDiffRecording {
|
||||
if accountPost.Nonce != nil || accountPost.Code != nil || accountPost.StorageWrites != nil || accountPost.Balance != nil {
|
||||
// the account executed SENDALL but did not send a balance, don't include it in the diff
|
||||
// TODO: probably shouldn't include the account in the dirty set in this case (unrelated to the BAL changes)
|
||||
mutations.Mutations[obj.address] = accountPost
|
||||
}
|
||||
// At this point, also ship the address off to the precacher. The precacher
|
||||
if len(accountPost.StorageWrites) > 0 {
|
||||
// remove all the written slots from the accessedState
|
||||
if _, ok := s.stateAccesses[obj.address]; ok {
|
||||
for slot, _ := range accountPost.StorageWrites {
|
||||
delete(s.stateAccesses[obj.address], slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := s.stateAccesses[obj.address]; ok && len(s.stateAccesses[obj.address]) == 0 {
|
||||
delete(s.stateAccesses, obj.address)
|
||||
}
|
||||
}
|
||||
s.markUpdate(addr)
|
||||
} // At this point, also ship the address off to the precacher. The precacher
|
||||
// will start loading tries, and when the change is eventually committed,
|
||||
// the commit-phase will be a lot faster
|
||||
addressesToPrefetch = append(addressesToPrefetch, addr) // Copy needed for closure
|
||||
|
|
@ -780,8 +938,14 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
|
||||
accesses = new(bal.StateAccesses)
|
||||
*accesses = s.stateAccesses
|
||||
s.stateAccesses = make(bal.StateAccesses)
|
||||
return mutations, accesses
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root hash of the state trie.
|
||||
|
|
@ -929,6 +1093,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
var (
|
||||
usedAddrs []common.Address
|
||||
deletedAddrs []common.Address
|
||||
updatedObjs []*stateObject
|
||||
)
|
||||
for addr, op := range s.mutations {
|
||||
if op.applied {
|
||||
|
|
@ -939,11 +1104,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
if op.isDelete() {
|
||||
deletedAddrs = append(deletedAddrs, addr)
|
||||
} else {
|
||||
s.updateStateObject(s.stateObjects[addr])
|
||||
updatedObjs = append(updatedObjs, s.stateObjects[addr])
|
||||
s.AccountUpdated += 1
|
||||
}
|
||||
usedAddrs = append(usedAddrs, addr) // Copy needed for closure
|
||||
}
|
||||
if len(updatedObjs) > 0 {
|
||||
s.updateStateObjects(updatedObjs)
|
||||
}
|
||||
for _, deletedAddr := range deletedAddrs {
|
||||
s.deleteStateObject(deletedAddr)
|
||||
s.AccountDeleted += 1
|
||||
|
|
@ -955,9 +1123,21 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
}
|
||||
// Track the amount of time wasted on hashing the account trie
|
||||
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
|
||||
|
||||
hash := s.trie.Hash()
|
||||
|
||||
/*
|
||||
it, err := s.trie.NodeIterator([]byte{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("state trie")
|
||||
for it.Next(true) {
|
||||
if it.Leaf() {
|
||||
fmt.Printf("%x: %x\n", it.Path(), it.LeafBlob())
|
||||
} else {
|
||||
fmt.Printf("%x: %x\n", it.Path(), it.Hash())
|
||||
}
|
||||
}
|
||||
*/
|
||||
// If witness building is enabled, gather the account trie witness
|
||||
if s.witness != nil {
|
||||
witness := s.trie.Witness()
|
||||
|
|
@ -975,6 +1155,19 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
|
||||
s.thash = thash
|
||||
s.txIndex = ti
|
||||
s.balIndex = ti + 1
|
||||
}
|
||||
|
||||
// SetAccessListIndex sets the current index that state mutations will
|
||||
// be reported as in the BAL. It is only relevant if this StateDB instance
|
||||
// is being used in the BAL construction path.
|
||||
func (s *StateDB) SetAccessListIndex(idx int) {
|
||||
s.balIndex = idx
|
||||
}
|
||||
|
||||
// SetTxSender sets the sender of the currently-executing transaction.
|
||||
func (s *StateDB) SetTxSender(sender common.Address) {
|
||||
s.sender = sender
|
||||
}
|
||||
|
||||
func (s *StateDB) clearJournalAndRefund() {
|
||||
|
|
@ -1160,6 +1353,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
|||
if s.dbErr != nil {
|
||||
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
|
||||
}
|
||||
|
||||
// Finalize any pending changes and merge everything into the tries
|
||||
s.IntermediateRoot(deleteEmptyObjects)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package state
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
|
|
@ -32,12 +34,12 @@ import (
|
|||
// hookedStateDB represents a statedb which emits calls to tracing-hooks
|
||||
// on state operations.
|
||||
type hookedStateDB struct {
|
||||
inner *StateDB
|
||||
inner BlockProcessingDB
|
||||
hooks *tracing.Hooks
|
||||
}
|
||||
|
||||
// NewHookedState wraps the given stateDb with the given hooks
|
||||
func NewHookedState(stateDb *StateDB, hooks *tracing.Hooks) *hookedStateDB {
|
||||
func NewHookedState(stateDb BlockProcessingDB, hooks *tracing.Hooks) *hookedStateDB {
|
||||
s := &hookedStateDB{stateDb, hooks}
|
||||
if s.hooks == nil {
|
||||
s.hooks = new(tracing.Hooks)
|
||||
|
|
@ -275,11 +277,14 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||
defer s.inner.Finalise(deleteEmptyObjects)
|
||||
if s.hooks.OnBalanceChange == nil {
|
||||
return
|
||||
func (s *hookedStateDB) TxIndex() int {
|
||||
return s.inner.TxIndex()
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) (*bal.StateDiff, *bal.StateAccesses) {
|
||||
/*
|
||||
// TODO: implement this code without peering into statedb internals!!!
|
||||
if s.hooks.OnBalanceChange != nil {
|
||||
for addr := range s.inner.journal.dirties {
|
||||
obj := s.inner.stateObjects[addr]
|
||||
if obj != nil && obj.selfDestructed {
|
||||
|
|
@ -290,3 +295,24 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
return s.inner.Finalise(deleteEmptyObjects)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log {
|
||||
return s.inner.GetLogs(hash, blockNumber, blockHash, blockTime)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) IntermediateRoot(deleteEmpty bool) common.Hash {
|
||||
return s.inner.IntermediateRoot(deleteEmpty)
|
||||
}
|
||||
func (a *hookedStateDB) Database() Database {
|
||||
return a.inner.Database()
|
||||
}
|
||||
func (a *hookedStateDB) GetTrie() Trie {
|
||||
return a.inner.GetTrie()
|
||||
}
|
||||
|
||||
func (a *hookedStateDB) SetTxContext(thash common.Hash, ti int) {
|
||||
a.inner.SetTxContext(thash, ti)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,10 +175,10 @@ func TestCopy(t *testing.T) {
|
|||
orig.Finalise(false)
|
||||
|
||||
// Copy the state
|
||||
copy := orig.Copy()
|
||||
copy := orig.Copy().(*StateDB)
|
||||
|
||||
// Copy the copy state
|
||||
ccopy := copy.Copy()
|
||||
ccopy := copy.Copy().(*StateDB)
|
||||
|
||||
// modify all in memory
|
||||
for i := byte(0); i < 255; i++ {
|
||||
|
|
@ -243,7 +243,7 @@ func TestCopyWithDirtyJournal(t *testing.T) {
|
|||
amount := uint256.NewInt(uint64(i))
|
||||
obj.SetBalance(new(uint256.Int).Sub(obj.Balance(), amount))
|
||||
}
|
||||
cpy := orig.Copy()
|
||||
cpy := orig.Copy().(*StateDB)
|
||||
|
||||
orig.Finalise(true)
|
||||
for i := byte(0); i < 255; i++ {
|
||||
|
|
@ -278,7 +278,7 @@ func TestCopyObjectState(t *testing.T) {
|
|||
obj.data.Root = common.HexToHash("0xdeadbeef")
|
||||
}
|
||||
orig.Finalise(true)
|
||||
cpy := orig.Copy()
|
||||
cpy := orig.Copy().(*StateDB)
|
||||
for _, op := range cpy.mutations {
|
||||
if have, want := op.applied, false; have != want {
|
||||
t.Fatalf("Error in test itself, the 'done' flag should not be set before Commit, have %v want %v", have, want)
|
||||
|
|
@ -528,7 +528,7 @@ func (test *snapshotTest) run() bool {
|
|||
for i, action := range test.actions {
|
||||
if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
|
||||
snapshotRevs[sindex] = state.Snapshot()
|
||||
checkstates[sindex] = state.Copy()
|
||||
checkstates[sindex] = state.Copy().(*StateDB)
|
||||
sindex++
|
||||
}
|
||||
action.fn(action, state)
|
||||
|
|
@ -747,7 +747,7 @@ func TestCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the non-committed state database and check pre/post commit balance
|
||||
copyOne := state.Copy()
|
||||
copyOne := state.Copy().(*StateDB)
|
||||
if balance := copyOne.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||
t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -761,7 +761,7 @@ func TestCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the copy and check the balance once more
|
||||
copyTwo := copyOne.Copy()
|
||||
copyTwo := copyOne.Copy().(*StateDB)
|
||||
if balance := copyTwo.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||
t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -820,7 +820,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the non-committed state database and check pre/post commit balance
|
||||
copyOne := state.Copy()
|
||||
copyOne := state.Copy().(*StateDB)
|
||||
if balance := copyOne.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||
t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -834,7 +834,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the copy and check the balance once more
|
||||
copyTwo := copyOne.Copy()
|
||||
copyTwo := copyOne.Copy().(*StateDB)
|
||||
if balance := copyTwo.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||
t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -848,7 +848,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the copy-copy and check the balance once more
|
||||
copyThree := copyTwo.Copy()
|
||||
copyThree := copyTwo.Copy().(*StateDB)
|
||||
if balance := copyThree.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||
t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -896,7 +896,7 @@ func TestCommitCopy(t *testing.T) {
|
|||
state.Commit(1, true, false)
|
||||
|
||||
// Copy the committed state database, the copied one is not fully functional.
|
||||
copied := state.Copy()
|
||||
copied := state.Copy().(*StateDB)
|
||||
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||
t.Fatalf("unexpected balance: have %v", balance)
|
||||
}
|
||||
|
|
@ -1098,7 +1098,7 @@ func TestStateDBAccessList(t *testing.T) {
|
|||
verifySlots("bb", "01", "02")
|
||||
|
||||
// Make a copy
|
||||
stateCopy1 := state.Copy()
|
||||
stateCopy1 := state.Copy().(*StateDB)
|
||||
if exp, got := 4, state.journal.length(); exp != got {
|
||||
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -54,7 +55,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro
|
|||
// Process returns the receipts and logs accumulated during the process and
|
||||
// returns the amount of gas that was used in the process. If any of the
|
||||
// transactions failed to execute due to insufficient gas it will return an error.
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessingDB, cfg vm.Config) (*ProcessResult, error) {
|
||||
var (
|
||||
receipts types.Receipts
|
||||
usedGas = new(uint64)
|
||||
|
|
@ -75,7 +76,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
var tracingStateDB = vm.StateDB(statedb)
|
||||
var tracingStateDB vm.StateDB = statedb
|
||||
if hooks := cfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
}
|
||||
|
|
@ -97,13 +98,21 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
|
||||
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
|
||||
if alDB, ok := statedb.(*state.AccessListCreationDB); ok {
|
||||
alDB.SetAccessListIndex(i + 1)
|
||||
}
|
||||
|
||||
_, _, receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
receipts = append(receipts, receipt)
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
|
||||
if alDB, ok := statedb.(*state.AccessListCreationDB); ok {
|
||||
alDB.SetAccessListIndex(len(block.Transactions()) + 1)
|
||||
}
|
||||
// Read requests if Prague is enabled.
|
||||
var requests [][]byte
|
||||
if p.config.IsPrague(block.Number(), block.Time()) {
|
||||
|
|
@ -113,18 +122,25 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
}
|
||||
// EIP-7002
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
||||
if _, _, err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// EIP-7251
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
if _, _, err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
|
||||
// if we are building access list, manually call finalise here to ensure
|
||||
// that state diffs due to eip-4895 withdrawals are picked up on the access
|
||||
// list.
|
||||
if alDB, ok := statedb.(*state.AccessListCreationDB); ok {
|
||||
alDB.Finalise(true)
|
||||
}
|
||||
|
||||
return &ProcessResult{
|
||||
Receipts: receipts,
|
||||
Requests: requests,
|
||||
|
|
@ -136,7 +152,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
|
||||
// and uses the input parameters for its environment similar to ApplyTransaction. However,
|
||||
// this method takes an already created EVM instance as input.
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb state.BlockProcessingDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (mutatedState *bal.StateDiff, accessedState *bal.StateAccesses, receipt *types.Receipt, err error) {
|
||||
if hooks := evm.Config.Tracer; hooks != nil {
|
||||
if hooks.OnTxStart != nil {
|
||||
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
|
|
@ -148,12 +164,13 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
// Apply the transaction to the current state (included in the env).
|
||||
result, err := ApplyMessage(evm, msg, gp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Update the state with pending changes.
|
||||
var root []byte
|
||||
if evm.ChainConfig().IsByzantium(blockNumber) {
|
||||
evm.StateDB.Finalise(true)
|
||||
mutatedState, accessedState = evm.StateDB.Finalise(true)
|
||||
} else {
|
||||
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes()
|
||||
}
|
||||
|
|
@ -164,11 +181,11 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
if statedb.Database().TrieDB().IsVerkle() {
|
||||
statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
|
||||
return mutatedState, accessedState, MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
|
||||
}
|
||||
|
||||
// MakeReceipt generates the receipt object for a transaction given its execution result.
|
||||
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
|
||||
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb state.BlockProcessingDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
|
||||
// Create a new receipt for the transaction, storing the intermediate root and gas used
|
||||
// by the tx.
|
||||
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
|
||||
|
|
@ -203,18 +220,19 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
|
|||
// and uses the input parameters for its environment. It returns the receipt
|
||||
// for the transaction, gas used and an error if the transaction failed,
|
||||
// indicating the block was invalid.
|
||||
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) {
|
||||
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb state.BlockProcessingDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) {
|
||||
msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new context to be used in the EVM environment
|
||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm)
|
||||
_, _, receipts, err := ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm)
|
||||
return receipts, err
|
||||
}
|
||||
|
||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||
// contract. This method is exported to be used in tests.
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) (*bal.StateDiff, *bal.StateAccesses) {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -233,12 +251,12 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
|||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
evm.StateDB.Finalise(true)
|
||||
return evm.StateDB.Finalise(true)
|
||||
}
|
||||
|
||||
// ProcessParentBlockHash stores the parent block hash in the history storage contract
|
||||
// as per EIP-2935/7709.
|
||||
func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
||||
func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) (*bal.StateDiff, *bal.StateAccesses) {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -263,22 +281,22 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
evm.StateDB.Finalise(true)
|
||||
return evm.StateDB.Finalise(true)
|
||||
}
|
||||
|
||||
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
||||
// It returns the opaque request data returned by the contract.
|
||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) (*bal.StateDiff, *bal.StateAccesses, error) {
|
||||
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
||||
}
|
||||
|
||||
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
||||
// It returns the opaque request data returned by the contract.
|
||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) (*bal.StateDiff, *bal.StateAccesses, error) {
|
||||
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
||||
}
|
||||
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) (*bal.StateDiff, *bal.StateAccesses, error) {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -296,19 +314,19 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
|||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
evm.StateDB.Finalise(true)
|
||||
diff, accesses := evm.StateDB.Finalise(true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("system call failed to execute: %v", err)
|
||||
return nil, nil, fmt.Errorf("system call failed to execute: %v", err)
|
||||
}
|
||||
if len(ret) == 0 {
|
||||
return nil // skip empty output
|
||||
return diff, accesses, nil // skip empty output
|
||||
}
|
||||
// Append prefixed requestsData to the requests list.
|
||||
requestsData := make([]byte, len(ret)+1)
|
||||
requestsData[0] = requestType
|
||||
copy(requestsData[1:], ret)
|
||||
*requests = append(*requests, requestsData)
|
||||
return nil
|
||||
return diff, accesses, nil
|
||||
}
|
||||
|
||||
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@ package core
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -29,6 +26,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// ExecutionResult includes all output after executing given evm
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ
|
|||
if err != nil {
|
||||
return common.Hash{}, common.Hash{}, err
|
||||
}
|
||||
if err = validator.ValidateState(block, db, res, true); err != nil {
|
||||
if err = validator.ValidateState(block, db, res, true, true); err != nil {
|
||||
return common.Hash{}, common.Hash{}, err
|
||||
}
|
||||
// Almost everything validated, but receipt and state root needs to be returned
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type noncer struct {
|
|||
// newNoncer creates a new virtual state database to track the pool nonces.
|
||||
func newNoncer(statedb *state.StateDB) *noncer {
|
||||
return &noncer{
|
||||
fallback: statedb.Copy(),
|
||||
fallback: statedb.Copy().(*state.StateDB),
|
||||
nonces: make(map[common.Address]uint64),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ type Validator interface {
|
|||
ValidateBody(block *types.Block) error
|
||||
|
||||
// ValidateState validates the given statedb and optionally the process result.
|
||||
ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error
|
||||
ValidateState(block *types.Block, state state.BlockProcessingDB, res *ProcessResult, validateState, stateless bool) error
|
||||
}
|
||||
|
||||
// Prefetcher is an interface for pre-caching transaction signatures and state.
|
||||
|
|
@ -48,7 +48,7 @@ type Processor interface {
|
|||
// Process processes the state changes according to the Ethereum rules by running
|
||||
// the transaction messages using the statedb and applying any rewards to both
|
||||
// the processor (coinbase) and any included uncles.
|
||||
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
|
||||
Process(block *types.Block, statedb state.BlockProcessingDB, cfg vm.Config) (*ProcessResult, error)
|
||||
}
|
||||
|
||||
// ProcessResult contains the values computed by Process.
|
||||
|
|
@ -57,4 +57,5 @@ type ProcessResult struct {
|
|||
Requests [][]byte
|
||||
Logs []*types.Log
|
||||
GasUsed uint64
|
||||
Error error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,12 +266,48 @@ func (c *ConstructionBlockAccessList) ApplyDiff(i uint, diff *StateDiff) {
|
|||
}
|
||||
}
|
||||
|
||||
// ApplyAccesses records the given account/storage accesses in the BAL.
|
||||
func (c *ConstructionBlockAccessList) ApplyAccesses(accesses StateAccesses) {
|
||||
for addr, acctAccesses := range accesses {
|
||||
if c.Accounts[addr] == nil {
|
||||
c.Accounts[addr] = &ConstructionAccountAccess{}
|
||||
}
|
||||
if len(acctAccesses) > 0 {
|
||||
|
||||
if c.Accounts[addr].StorageReads == nil {
|
||||
c.Accounts[addr].StorageReads = make(map[common.Hash]struct{})
|
||||
}
|
||||
for key, _ := range acctAccesses {
|
||||
// if any of the accessed keys were previously written, they
|
||||
// appear in the written set only and not also in accesses.
|
||||
if len(c.Accounts[addr].StorageWrites) > 0 {
|
||||
if _, ok := c.Accounts[addr].StorageWrites[key]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
c.Accounts[addr].StorageReads[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StateDiff struct {
|
||||
Mutations map[common.Address]*AccountState `json:"Mutations,omitempty"`
|
||||
}
|
||||
|
||||
type StateAccesses map[common.Address]map[common.Hash]struct{}
|
||||
|
||||
func (s *StateAccesses) Merge(other StateAccesses) {
|
||||
for addr, accesses := range other {
|
||||
if _, ok := (*s)[addr]; !ok {
|
||||
(*s)[addr] = make(map[common.Hash]struct{})
|
||||
}
|
||||
for slot := range accesses {
|
||||
(*s)[addr][slot] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type AccountState struct {
|
||||
Balance *uint256.Int `json:"Balance,omitempty"`
|
||||
Nonce *uint64 `json:"Nonce,omitempty"`
|
||||
|
|
|
|||
1
core/types/bal/bal.rlp.hex
Normal file
1
core/types/bal/bal.rlp.hex
Normal file
File diff suppressed because one or more lines are too long
32
core/types/bal_blocks_test.go
Normal file
32
core/types/bal_blocks_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBALDecoding(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
)
|
||||
data, err = os.ReadFile("blocks_bal_one.rlp")
|
||||
if err != nil {
|
||||
t.Fatalf("error opening file: %v", err)
|
||||
}
|
||||
reader := bytes.NewReader(data)
|
||||
stream := rlp.NewStream(reader, 0)
|
||||
var blocks Block
|
||||
for i := 0; err == nil; i++ {
|
||||
fmt.Printf("decode %d\n", i)
|
||||
err = stream.Decode(&blocks)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("error decoding blocks: %v", err)
|
||||
}
|
||||
fmt.Printf("block number is %d\n", blocks.NumberU64())
|
||||
}
|
||||
}
|
||||
|
|
@ -349,9 +349,12 @@ func CopyHeader(h *Header) *Header {
|
|||
|
||||
// DecodeRLP decodes a block from RLP.
|
||||
func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
||||
var eb extblock
|
||||
var (
|
||||
eb extblock
|
||||
)
|
||||
_, size, _ := s.Kind()
|
||||
if err := s.Decode(&eb); err != nil {
|
||||
fmt.Println("error here")
|
||||
return err
|
||||
}
|
||||
b.header, b.uncles, b.transactions, b.withdrawals, b.accessList = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals, eb.AccessList
|
||||
|
|
@ -366,6 +369,7 @@ func (b *Block) EncodeRLP(w io.Writer) error {
|
|||
Txs: b.transactions,
|
||||
Uncles: b.uncles,
|
||||
Withdrawals: b.withdrawals,
|
||||
AccessList: b.accessList,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -539,6 +543,7 @@ func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
|
|||
transactions: b.transactions,
|
||||
uncles: b.uncles,
|
||||
withdrawals: b.withdrawals,
|
||||
accessList: b.accessList,
|
||||
witness: witness,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
|||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
var enc Header
|
||||
|
|
@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
|||
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
|
||||
enc.ParentBeaconRoot = h.ParentBeaconRoot
|
||||
enc.RequestsHash = h.RequestsHash
|
||||
enc.BlockAccessListHash = h.BlockAccessListHash
|
||||
enc.Hash = h.Hash()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
|
@ -89,6 +91,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
|||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||
}
|
||||
var dec Header
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
|||
if dec.RequestsHash != nil {
|
||||
h.RequestsHash = dec.RequestsHash
|
||||
}
|
||||
if dec.BlockAccessListHash != nil {
|
||||
h.BlockAccessListHash = dec.BlockAccessListHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
|||
_tmp4 := obj.ExcessBlobGas != nil
|
||||
_tmp5 := obj.ParentBeaconRoot != nil
|
||||
_tmp6 := obj.RequestsHash != nil
|
||||
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
|
||||
_tmp7 := obj.BlockAccessListHash != nil
|
||||
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.BaseFee == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
|
|
@ -53,41 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
|||
w.WriteBigInt(obj.BaseFee)
|
||||
}
|
||||
}
|
||||
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
|
||||
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.WithdrawalsHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.WithdrawalsHash[:])
|
||||
}
|
||||
}
|
||||
if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
|
||||
if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.BlobGasUsed == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteUint64((*obj.BlobGasUsed))
|
||||
}
|
||||
}
|
||||
if _tmp4 || _tmp5 || _tmp6 {
|
||||
if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.ExcessBlobGas == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteUint64((*obj.ExcessBlobGas))
|
||||
}
|
||||
}
|
||||
if _tmp5 || _tmp6 {
|
||||
if _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.ParentBeaconRoot == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.ParentBeaconRoot[:])
|
||||
}
|
||||
}
|
||||
if _tmp6 {
|
||||
if _tmp6 || _tmp7 {
|
||||
if obj.RequestsHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.RequestsHash[:])
|
||||
}
|
||||
}
|
||||
if _tmp7 {
|
||||
if obj.BlockAccessListHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.BlockAccessListHash[:])
|
||||
}
|
||||
}
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ type TxContext struct {
|
|||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
||||
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
||||
Index uint64 // the index of the transaction within the block being executed (0 if executing a standalone call)
|
||||
}
|
||||
|
||||
// EVM is the Ethereum Virtual Machine base object and provides
|
||||
|
|
@ -514,6 +515,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
|
|||
// - the storage is non-empty
|
||||
contractHash := evm.StateDB.GetCodeHash(address)
|
||||
storageRoot := evm.StateDB.GetStorageRoot(address)
|
||||
|
||||
if evm.StateDB.GetNonce(address) != 0 ||
|
||||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
||||
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -99,6 +100,8 @@ type StateDB interface {
|
|||
|
||||
AccessEvents() *state.AccessEvents
|
||||
|
||||
TxIndex() int
|
||||
|
||||
// Finalise must be invoked at the end of a transaction
|
||||
Finalise(bool)
|
||||
Finalise(bool) (*bal.StateDiff, *bal.StateAccesses)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
|||
switch {
|
||||
case rules.IsVerkle:
|
||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||
case rules.IsAmsterdam:
|
||||
return newPragueInstructionSet(), errors.New("glamsterdam-fork not defined yet")
|
||||
case rules.IsOsaka:
|
||||
return newOsakaInstructionSet(), nil
|
||||
case rules.IsPrague:
|
||||
|
|
|
|||
|
|
@ -664,7 +664,7 @@ func TestColdAccountAccessCost(t *testing.T) {
|
|||
Tracer: &tracing.Hooks{
|
||||
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
|
||||
// Uncomment to investigate failures:
|
||||
//t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)
|
||||
//t.Logf("%d: %v %d", step, vm.OpCode(op).PrettyPrint(), cost)
|
||||
if step == tc.step {
|
||||
have = cost
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,43 @@ const (
|
|||
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||
)
|
||||
|
||||
// All methods provided over the engine endpoint.
|
||||
var caps = []string{
|
||||
"engine_forkchoiceUpdatedV1",
|
||||
"engine_forkchoiceUpdatedV2",
|
||||
"engine_forkchoiceUpdatedV3",
|
||||
"engine_forkchoiceUpdatedWithWitnessV1",
|
||||
"engine_forkchoiceUpdatedWithWitnessV2",
|
||||
"engine_forkchoiceUpdatedWithWitnessV3",
|
||||
"engine_exchangeTransitionConfigurationV1",
|
||||
"engine_getPayloadV1",
|
||||
"engine_getPayloadV2",
|
||||
"engine_getPayloadV3",
|
||||
"engine_getPayloadV4",
|
||||
"engine_getPayloadV5",
|
||||
"engine_getPayloadV6",
|
||||
"engine_getBlobsV1",
|
||||
"engine_getBlobsV2",
|
||||
"engine_newPayloadV1",
|
||||
"engine_newPayloadV2",
|
||||
"engine_newPayloadV3",
|
||||
"engine_newPayloadV4",
|
||||
"engine_newPayloadV5",
|
||||
"engine_newPayloadWithWitnessV1",
|
||||
"engine_newPayloadWithWitnessV2",
|
||||
"engine_newPayloadWithWitnessV3",
|
||||
"engine_newPayloadWithWitnessV4",
|
||||
"engine_executeStatelessPayloadV1",
|
||||
"engine_executeStatelessPayloadV2",
|
||||
"engine_executeStatelessPayloadV3",
|
||||
"engine_executeStatelessPayloadV4",
|
||||
"engine_getPayloadBodiesByHashV1",
|
||||
"engine_getPayloadBodiesByHashV2",
|
||||
"engine_getPayloadBodiesByRangeV1",
|
||||
"engine_getPayloadBodiesByRangeV2",
|
||||
"engine_getClientVersionV1",
|
||||
}
|
||||
|
||||
var (
|
||||
// Number of blobs requested via getBlobsV2
|
||||
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
|
||||
|
|
@ -213,9 +250,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
|
|||
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
|
||||
case params.BeaconRoot == nil:
|
||||
return engine.STATUS_INVALID, attributesErr("missing beacon root")
|
||||
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
|
||||
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5, forks.Amsterdam):
|
||||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads")
|
||||
}
|
||||
|
||||
if api.checkFork(params.Timestamp, forks.Amsterdam) {
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV4, false)
|
||||
}
|
||||
}
|
||||
// TODO(matt): the spec requires that fcu is applied when called on a valid
|
||||
// hash, even if params are wrong. To do this we need to split up
|
||||
|
|
@ -461,6 +502,14 @@ func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.Execu
|
|||
return api.getPayload(payloadID, false)
|
||||
}
|
||||
|
||||
// GetPayloadV6 returns a cached payload by id.
|
||||
func (api *ConsensusAPI) GetPayloadV6(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
|
||||
if !payloadID.Is(engine.PayloadV4) {
|
||||
return nil, engine.UnsupportedFork
|
||||
}
|
||||
return api.getPayload(payloadID, false)
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
|
||||
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
|
||||
data := api.localBlocks.get(payloadID, full)
|
||||
|
|
@ -666,6 +715,33 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
|
|||
return api.newPayload(params, versionedHashes, beaconRoot, requests, false)
|
||||
}
|
||||
|
||||
// NewPayloadV5 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||
func (api *ConsensusAPI) NewPayloadV5(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
|
||||
switch {
|
||||
case params.Withdrawals == nil:
|
||||
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
|
||||
case params.ExcessBlobGas == nil:
|
||||
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
|
||||
case params.BlobGasUsed == nil:
|
||||
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
|
||||
case versionedHashes == nil:
|
||||
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
|
||||
case beaconRoot == nil:
|
||||
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
|
||||
case executionRequests == nil:
|
||||
return invalidStatus, paramsErr("nil executionRequests post-prague")
|
||||
case params.BlockAccessList == nil:
|
||||
return invalidStatus, paramsErr("nil block access list post-amsterdam")
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.Amsterdam):
|
||||
return invalidStatus, unsupportedForkErr("newPayloadV5 must only be called for amsterdam payloads")
|
||||
}
|
||||
requests := convertRequests(executionRequests)
|
||||
if err := validateRequests(requests); err != nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
|
||||
}
|
||||
return api.newPayload(params, versionedHashes, beaconRoot, requests, false)
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) {
|
||||
// The locking here is, strictly, not required. Without these locks, this can happen:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -88,10 +88,10 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan
|
|||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the body data and delivering it to the downloader's queue.
|
||||
func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesResponse).Unpack()
|
||||
hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes}
|
||||
txs, uncles, withdrawals, accessLists := packet.Res.(*eth.BlockBodiesResponse).Unpack()
|
||||
hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes, access list hashes}
|
||||
|
||||
accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2])
|
||||
accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2], accessLists, hashsets[3])
|
||||
switch {
|
||||
case err == nil && len(txs) == 0:
|
||||
peer.log.Trace("Requested bodies delivered")
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ package downloader
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -564,6 +565,7 @@ func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue
|
|||
func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash,
|
||||
uncleLists [][]*types.Header, uncleListHashes []common.Hash,
|
||||
withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash,
|
||||
blockAccessLists []*bal.BlockAccessList, accessListHashes []common.Hash,
|
||||
) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
|
@ -588,6 +590,19 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH
|
|||
return errInvalidBody
|
||||
}
|
||||
}
|
||||
if header.BlockAccessListHash == nil {
|
||||
// nil hash means that access list should not be present in body
|
||||
if blockAccessLists[index] != nil {
|
||||
return errInvalidBody
|
||||
}
|
||||
} else { // non-nil hash: body must have access list
|
||||
if blockAccessLists[index] == nil {
|
||||
return errInvalidBody
|
||||
}
|
||||
if accessListHashes[index] != header.Hash() {
|
||||
return errInvalidBody
|
||||
}
|
||||
}
|
||||
// Blocks must have a number of blobs corresponding to the header gas usage,
|
||||
// and zero before the Cancun hardfork.
|
||||
var blobs int
|
||||
|
|
|
|||
|
|
@ -186,6 +186,9 @@ type Config struct {
|
|||
|
||||
// ExperimentalBAL enables EIP-7928 block access list creation during execution
|
||||
// of post Cancun blocks, and persistence via embedding the BAL in the block body.
|
||||
//
|
||||
// TODO: also note that it will cause execution of blocks with access lists to base
|
||||
// their execution on the BAL.
|
||||
ExperimentalBAL bool `toml:",omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -381,6 +381,7 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
|
|||
txsHashes = make([]common.Hash, len(res.BlockBodiesResponse))
|
||||
uncleHashes = make([]common.Hash, len(res.BlockBodiesResponse))
|
||||
withdrawalHashes = make([]common.Hash, len(res.BlockBodiesResponse))
|
||||
accessListHashes = make([]common.Hash, len(res.BlockBodiesResponse))
|
||||
)
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
for i, body := range res.BlockBodiesResponse {
|
||||
|
|
@ -389,8 +390,11 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
|
|||
if body.Withdrawals != nil {
|
||||
withdrawalHashes[i] = types.DeriveSha(types.Withdrawals(body.Withdrawals), hasher)
|
||||
}
|
||||
if body.AccessList != nil {
|
||||
accessListHashes[i] = body.AccessList.Hash()
|
||||
}
|
||||
return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes}
|
||||
}
|
||||
return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes, accessListHashes}
|
||||
}
|
||||
return peer.dispatchResponse(&Response{
|
||||
id: res.RequestId,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package eth
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
|
|
@ -239,20 +240,22 @@ type BlockBody struct {
|
|||
Transactions []*types.Transaction // Transactions contained within a block
|
||||
Uncles []*types.Header // Uncles contained within a block
|
||||
Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block
|
||||
AccessList *bal.BlockAccessList `rlp:"optional"`
|
||||
}
|
||||
|
||||
// Unpack retrieves the transactions and uncles from the range packet and returns
|
||||
// them in a split flat format that's more consistent with the internal data structures.
|
||||
func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) {
|
||||
func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal, []*bal.BlockAccessList) {
|
||||
var (
|
||||
txset = make([][]*types.Transaction, len(*p))
|
||||
uncleset = make([][]*types.Header, len(*p))
|
||||
withdrawalset = make([][]*types.Withdrawal, len(*p))
|
||||
accessListSet = make([]*bal.BlockAccessList, len(*p))
|
||||
)
|
||||
for i, body := range *p {
|
||||
txset[i], uncleset[i], withdrawalset[i] = body.Transactions, body.Uncles, body.Withdrawals
|
||||
txset[i], uncleset[i], withdrawalset[i], accessListSet[i] = body.Transactions, body.Uncles, body.Withdrawals, body.AccessList
|
||||
}
|
||||
return txset, uncleset, withdrawalset
|
||||
return txset, uncleset, withdrawalset, accessListSet
|
||||
}
|
||||
|
||||
// GetReceiptsRequest represents a block receipts query.
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
// Send the block over to the concurrent tracers (if not in the fast-forward phase)
|
||||
txs := next.Transactions()
|
||||
select {
|
||||
case taskCh <- &blockTraceTask{statedb: statedb.Copy(), block: next, release: release, results: make([]*txTraceResult, len(txs))}:
|
||||
case taskCh <- &blockTraceTask{statedb: statedb.Copy().(*state.StateDB), block: next, release: release, results: make([]*txTraceResult, len(txs))}:
|
||||
case <-closed:
|
||||
tracker.releaseState(number, release)
|
||||
return
|
||||
|
|
@ -695,7 +695,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
|||
txloop:
|
||||
for i, tx := range txs {
|
||||
// Send the trace task over for execution
|
||||
task := &txTraceTask{statedb: statedb.Copy(), index: i}
|
||||
task := &txTraceTask{statedb: statedb.Copy().(*state.StateDB), index: i}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
failed = ctx.Err()
|
||||
|
|
@ -1054,7 +1054,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
|
|||
|
||||
// Call Prepare to clear out the statedb access list
|
||||
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
|
||||
_, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, &usedGas, evm)
|
||||
_, _, _, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, &usedGas, evm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -958,6 +958,9 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
|
|||
if block.Withdrawals() != nil {
|
||||
fields["withdrawals"] = block.Withdrawals()
|
||||
}
|
||||
if block.Body().AccessList != nil {
|
||||
fields["accessList"] = block.Body().AccessList
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
|
|
@ -1298,7 +1301,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
|||
log.Trace("Creating access list", "input", accessList)
|
||||
|
||||
// Copy the original db so we don't modify it
|
||||
statedb := db.Copy()
|
||||
statedb := db.Copy().(*state.StateDB)
|
||||
// Set the accesslist to the last al
|
||||
args.AccessList = &accessList
|
||||
msg := args.ToMessage(header.BaseFee, true)
|
||||
|
|
|
|||
|
|
@ -333,11 +333,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
return nil, nil, nil, err
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
if _, _, err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// EIP-7251
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
if _, _, err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func (miner *Miner) Pending() (*types.Block, types.Receipts, *state.StateDB) {
|
|||
if pending == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return pending.block, pending.receipts, pending.stateDB.Copy()
|
||||
return pending.block, pending.receipts, pending.stateDB.Copy().(*state.StateDB)
|
||||
}
|
||||
|
||||
// SetExtra sets the content used to initialize the block extra field.
|
||||
|
|
|
|||
|
|
@ -153,11 +153,11 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay
|
|||
return &newPayloadResult{err: err}
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
|
||||
if _, _, err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
// EIP-7251 consolidations
|
||||
if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
|
||||
if _, _, err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package tests
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -67,13 +68,119 @@ func TestBlockchain(t *testing.T) {
|
|||
bt.skipLoad(`.*\.meta/.*`)
|
||||
|
||||
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
execBlockTest(t, bt, test)
|
||||
execBlockTest(t, bt, test, false)
|
||||
})
|
||||
// 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 TestBlockchainBAL(t *testing.T) {
|
||||
bt := new(testMatcher)
|
||||
|
||||
// We are running most of GeneralStatetests to tests witness support, even
|
||||
// though they are ran as state tests too. Still, the performance tests are
|
||||
// less about state andmore about EVM number crunching, so skip those.
|
||||
bt.skipLoad(`^GeneralStateTests/VMTests/vmPerformance`)
|
||||
|
||||
// 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.*`)
|
||||
|
||||
// After the merge we would accept side chains as canonical even if they have lower td
|
||||
bt.skipLoad(`.*bcMultiChainTest/ChainAtoChainB_difficultyB.json`)
|
||||
bt.skipLoad(`.*bcMultiChainTest/CallContractFromNotBestBlock.json`)
|
||||
bt.skipLoad(`.*bcTotalDifficultyTest/uncleBlockAtBlock3afterBlock4.json`)
|
||||
bt.skipLoad(`.*bcTotalDifficultyTest/lotsOfBranchesOverrideAtTheMiddle.json`)
|
||||
bt.skipLoad(`.*bcTotalDifficultyTest/sideChainWithMoreTransactions.json`)
|
||||
bt.skipLoad(`.*bcForkStressTest/ForkStressTest.json`)
|
||||
bt.skipLoad(`.*bcMultiChainTest/lotsOfLeafs.json`)
|
||||
bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain.json`)
|
||||
bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain2.json`)
|
||||
|
||||
// With chain history removal, TDs become unavailable, this transition tests based on TTD are unrunnable
|
||||
bt.skipLoad(`.*bcArrowGlacierToParis/powToPosBlockRejection.json`)
|
||||
|
||||
// This directory contains no test.
|
||||
bt.skipLoad(`.*\.meta/.*`)
|
||||
|
||||
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
config, ok := Forks[test.json.Network]
|
||||
if !ok {
|
||||
t.Fatalf("unsupported fork: %s\n", test.json.Network)
|
||||
}
|
||||
gspec := test.genesis(config)
|
||||
// skip any tests which are not past the cancun fork (selfdestruct removal)
|
||||
if gspec.Config.CancunTime == nil || *gspec.Config.CancunTime != 0 {
|
||||
return
|
||||
}
|
||||
execBlockTest(t, bt, test, true)
|
||||
})
|
||||
// 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.
|
||||
}
|
||||
|
||||
// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests.
|
||||
// TODO: rename this to reflect that it tests creating/verifying BALs on pre-amsterdam tests
|
||||
func TestExecutionSpecBlocktestsBAL(t *testing.T) {
|
||||
if !common.FileExist(executionSpecBlockchainTestDir) {
|
||||
t.Skipf("directory %s does not exist", executionSpecBlockchainTestDir)
|
||||
}
|
||||
bt := new(testMatcher)
|
||||
|
||||
bt.skipLoad(".*prague/eip7251_consolidations/contract_deployment/system_contract_deployment.json")
|
||||
bt.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/contract_deployment/system_contract_deployment.json")
|
||||
|
||||
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
config, ok := Forks[test.json.Network]
|
||||
if !ok {
|
||||
t.Fatalf("unsupported fork: %s\n", test.json.Network)
|
||||
}
|
||||
gspec := test.genesis(config)
|
||||
// skip any tests which are not past the cancun fork (selfdestruct removal)
|
||||
if gspec.Config.CancunTime == nil || *gspec.Config.CancunTime != 0 {
|
||||
return
|
||||
}
|
||||
execBlockTest(t, bt, test, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecutionSpecBlocktestsAmsterdam(t *testing.T) {
|
||||
var executionSpecAmsterdamBlockchainTestDir = filepath.Join(".", "fixtures-amsterdam-bal", "blockchain_tests")
|
||||
if !common.FileExist(executionSpecAmsterdamBlockchainTestDir) {
|
||||
t.Skipf("directory %s does not exist", executionSpecAmsterdamBlockchainTestDir)
|
||||
}
|
||||
bt := new(testMatcher)
|
||||
|
||||
bt.walk(t, executionSpecAmsterdamBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
config, ok := Forks[test.json.Network]
|
||||
if !ok {
|
||||
t.Fatalf("unsupported fork: %s\n", test.json.Network)
|
||||
}
|
||||
gspec := test.genesis(config)
|
||||
// skip any tests which are not past the cancun fork (selfdestruct removal)
|
||||
if gspec.Config.CancunTime == nil || *gspec.Config.CancunTime != 0 {
|
||||
return
|
||||
}
|
||||
// TODO: skip any tests that aren't amsterdam
|
||||
execBlockTest(t, bt, test, false)
|
||||
})
|
||||
}
|
||||
|
||||
// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests.
|
||||
func TestExecutionSpecBlocktests(t *testing.T) {
|
||||
if !common.FileExist(executionSpecBlockchainTestDir) {
|
||||
|
|
@ -86,11 +193,11 @@ func TestExecutionSpecBlocktests(t *testing.T) {
|
|||
bt.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/test_system_contract_deployment.json")
|
||||
|
||||
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
execBlockTest(t, bt, test)
|
||||
execBlockTest(t, bt, test, false)
|
||||
})
|
||||
}
|
||||
|
||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest, buildAndVerifyBAL bool) {
|
||||
// Define all the different flag combinations we should run the tests with,
|
||||
// picking only one for short tests.
|
||||
//
|
||||
|
|
@ -104,9 +211,11 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
|||
snapshotConf = []bool{snapshotConf[rand.Int()%2]}
|
||||
dbschemeConf = []string{dbschemeConf[rand.Int()%2]}
|
||||
}
|
||||
|
||||
for _, snapshot := range snapshotConf {
|
||||
for _, dbscheme := range dbschemeConf {
|
||||
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil {
|
||||
//tracer := logger.NewJSONLogger(&logger.Config{}, os.Stdout)
|
||||
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, false, buildAndVerifyBAL, nil, nil)); err != nil {
|
||||
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
stdmath "math"
|
||||
"math/big"
|
||||
"os"
|
||||
|
|
@ -71,6 +72,7 @@ type btBlock struct {
|
|||
ExpectException string
|
||||
Rlp string
|
||||
UncleHeaders []*btHeader
|
||||
AccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type btHeader -field-override btHeaderMarshaling -out gen_btheader.go
|
||||
|
|
@ -97,6 +99,7 @@ type btHeader struct {
|
|||
BlobGasUsed *uint64
|
||||
ExcessBlobGas *uint64
|
||||
ParentBeaconBlockRoot *common.Hash
|
||||
BlockAccessListHash *common.Hash
|
||||
}
|
||||
|
||||
type btHeaderMarshaling struct {
|
||||
|
|
@ -111,11 +114,7 @@ type btHeaderMarshaling struct {
|
|||
ExcessBlobGas *math.HexOrDecimal64
|
||||
}
|
||||
|
||||
func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
||||
config, ok := Forks[t.json.Network]
|
||||
if !ok {
|
||||
return UnsupportedForkError{t.json.Network}
|
||||
}
|
||||
func (t *BlockTest) createTestBlockChain(config *params.ChainConfig, snapshotter bool, scheme string, witness, createAndVerifyBAL bool, tracer *tracing.Hooks) (*core.BlockChain, error) {
|
||||
// import pre accounts & construct test genesis block & state root
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
|
|
@ -128,7 +127,6 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
|||
} else {
|
||||
tconf.HashDB = hashdb.Defaults
|
||||
}
|
||||
// Commit genesis state
|
||||
gspec := t.genesis(config)
|
||||
|
||||
// if ttd is not specified, set an arbitrary huge value
|
||||
|
|
@ -138,15 +136,15 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
|||
triedb := triedb.NewDatabase(db, tconf)
|
||||
gblock, err := gspec.Commit(db, triedb)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
triedb.Close() // close the db to prevent memory leak
|
||||
|
||||
if gblock.Hash() != t.json.Genesis.Hash {
|
||||
return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6])
|
||||
return nil, fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6])
|
||||
}
|
||||
if gblock.Root() != t.json.Genesis.StateRoot {
|
||||
return fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6])
|
||||
return nil, fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6])
|
||||
}
|
||||
// Wrap the original engine within the beacon-engine
|
||||
engine := beacon.New(ethash.NewFaker())
|
||||
|
|
@ -160,12 +158,28 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
|||
Tracer: tracer,
|
||||
StatelessSelfValidation: witness,
|
||||
},
|
||||
NoPrefetch: true,
|
||||
EnableBAL: createAndVerifyBAL,
|
||||
}
|
||||
if snapshotter {
|
||||
options.SnapshotLimit = 1
|
||||
options.SnapshotWait = true
|
||||
}
|
||||
chain, err := core.NewBlockChain(db, gspec, engine, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, createAndVerifyBAL bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
||||
config, ok := Forks[t.json.Network]
|
||||
if !ok {
|
||||
return UnsupportedForkError{t.json.Network}
|
||||
}
|
||||
// import pre accounts & construct test genesis block & state root
|
||||
|
||||
chain, err := t.createTestBlockChain(config, snapshotter, scheme, witness, createAndVerifyBAL, tracer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -199,7 +213,50 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
|||
}
|
||||
}
|
||||
}
|
||||
return t.validateImportedHeaders(chain, validBlocks)
|
||||
err = t.validateImportedHeaders(chain, validBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createAndVerifyBAL {
|
||||
newChain, _ := t.createTestBlockChain(config, snapshotter, scheme, witness, createAndVerifyBAL, tracer)
|
||||
defer newChain.Stop()
|
||||
|
||||
var blocksWithBAL types.Blocks
|
||||
for i := uint64(1); i <= chain.CurrentBlock().Number.Uint64(); i++ {
|
||||
block := chain.GetBlockByNumber(i)
|
||||
if block.Body().AccessList == nil {
|
||||
return fmt.Errorf("block missing BAL")
|
||||
}
|
||||
blocksWithBAL = append(blocksWithBAL, block)
|
||||
}
|
||||
|
||||
amt, err := newChain.InsertChain(blocksWithBAL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = amt
|
||||
newDB, err := newChain.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.validatePostState(newDB); err != nil {
|
||||
return fmt.Errorf("post state validation failed: %v", err)
|
||||
}
|
||||
// Cross-check the snapshot-to-hash against the trie hash
|
||||
if snapshotter {
|
||||
if newChain.Snapshots() != nil {
|
||||
if err := chain.Snapshots().Verify(chain.CurrentBlock().Root); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = t.validateImportedHeaders(newChain, validBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
|
||||
|
|
@ -218,6 +275,7 @@ func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
|
|||
BaseFee: t.json.Genesis.BaseFeePerGas,
|
||||
BlobGasUsed: t.json.Genesis.BlobGasUsed,
|
||||
ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
|
||||
BlockAccessListHash: t.json.Genesis.BlockAccessListHash,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
27
tests/fixtures-amsterdam-bal/.meta/fixtures.ini
Normal file
27
tests/fixtures-amsterdam-bal/.meta/fixtures.ini
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
; This file describes fixture build properties
|
||||
|
||||
[fixtures]
|
||||
timestamp = 2025-09-15T15:45:15.672258
|
||||
command_line_args = fill -c /Users/jwasinger/projects/execution-spec-tests/src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini --rootdir . --until=Amsterdam tests/amsterdam
|
||||
|
||||
[packages]
|
||||
pytest = 8.4.1
|
||||
pluggy = 1.6.0
|
||||
|
||||
[plugins]
|
||||
regex = 0.2.0
|
||||
html = 4.1.1
|
||||
xdist = 3.8.0
|
||||
json-report = 1.5.0
|
||||
metadata = 3.1.1
|
||||
cov = 4.1.0
|
||||
custom-report = 1.0.1
|
||||
|
||||
[tools]
|
||||
eels resolutions = /Users/jwasinger/projects/execution-spec-tests/src/pytest_plugins/eels_resolutions.json
|
||||
t8n = ethereum-spec-evm-resolver 0.0.5
|
||||
|
||||
[environment]
|
||||
python = 3.11.13
|
||||
platform = macOS-13.0-arm64-arm-64bit
|
||||
|
||||
254
tests/fixtures-amsterdam-bal/.meta/index.json
Normal file
254
tests/fixtures-amsterdam-bal/.meta/index.json
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"root_hash": "0x4bbf7997c738c7f87e8d08aa8d2c43bc51daf72c82b9f7b25d36791a6ef48638",
|
||||
"created_at": "2025-09-15T15:45:17.133354",
|
||||
"test_count": 30,
|
||||
"forks": [
|
||||
"Amsterdam"
|
||||
],
|
||||
"fixture_formats": [
|
||||
"blockchain_test_engine",
|
||||
"blockchain_test"
|
||||
],
|
||||
"test_cases": [
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_complex_corruption[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x91d4d6d462a761950c83d3b30a947b915cfabf6051078efe014ff8a216477045",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_complex_corruption.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_duplicate_account[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x8cc2569fedefe68987b7e5e0490c56247dc140c742a4c1eef0876964e122123e",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_duplicate_account.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_tx_order[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x3edf472ff5b91642ac66276c7993ba31fe53a08b289cd69d92386bf855e73f61",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_tx_order.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_missing_nonce[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x14c40aa5f07c4252937bffad8a6f7ad196d4c00fddfccca2858fa9ebd00d9cbf",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_missing_nonce.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_balance_value[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xb6c1da9621633f00db1bd84120283762f020bc0b0f1da9a5eef05c300a07d956",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_balance_value.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_storage_value[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x60b01e4ace6086973fafdac8cab9cd114c08c9755b0b9894cb4a2a6e4c136fd4",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_storage_value.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_account[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xd331e205d1f148f873f06bda9adb51245f1f7bb7280ca2274d7cc02c9ade8806",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_account.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_account_order[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x909b911f94a6162ef47c46398c22852ebacfad49285bef18029b80cc027aac46",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_account_order.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_nonce_value[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x186280e177e67f5b4de00e9037b64b5eeb27298a82960cc73a0dffa74fd01d4b",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_nonce_value.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_missing_account[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xd20ae4fc80b9ab084830436f81446fa69cd75e483524fa7b4c686ac1ae726fbf",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_missing_account.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_code_changes[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xd3784eba180519598f28c99a7702e75ef1f395da4a57522acdae9633a3f6222d",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_code_changes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_storage_reads[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xf4b38f2a673943b71e12d18102fef37769fc50e16ec6729bd1a4aaf21b38dd27",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_storage_reads.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_storage_writes[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xa44a63948175b6311a68138cdd79c04728a7f08f8130273f96d7eae2766235c",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_storage_writes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_balance_changes[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0xb06ec3b0851444b6519080364b182fec83be1a1b084a0445eaea846b2e095443",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_balance_changes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_nonce_changes[fork_Amsterdam-blockchain_test_engine]",
|
||||
"fixture_hash": "0x558fec2c1d0d7d544804011044c936bc378262d30b42a7c623c63825df54d10c",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test_engine",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests_engine/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_nonce_changes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_complex_corruption[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x6cbd5ee299221511528f6916c5e6df0369cebd8dceba4225c8eb85dbd24d93ad",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_complex_corruption.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_duplicate_account[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x684d8557b425f5b73c8bd4573719f5c464519d9783d6b69059381b64415503a8",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_duplicate_account.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_tx_order[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0xf177592b49c585371cb2129b65ce7d992ce9f443cac65e7db44bb268daaf281b",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_tx_order.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_missing_nonce[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x6602720a2523736926724a6dcb51520fafe13d36fc2c70fffeba8575d2511f57",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_missing_nonce.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_balance_value[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x5b8c21d88d684e37705f5af59acb6ecc5098c6e4526d172f7670e45f236bc3d9",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_balance_value.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_storage_value[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x85bea9663f1bf3b20cd80e6e1ca7bbf310ebe2da80cf87ff5b171f612fd05ed4",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_storage_value.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_account[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0xf254712914d6689c42f84940edf1e4eedb621f172f9c5fd25e5a36ca3d7bab57",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_account.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_account_order[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x58d6e7bebe449d438cf216123b8850a7d20958a41cc11493807b768d18f1608e",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_account_order.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_nonce_value[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x7e5909fd80c7352b970fde17619b15627c822798ac7dbe712465b484771a754b",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_nonce_value.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py::test_bal_invalid_missing_account[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x52443eb0df938af8ec0f007e8e54c05004a1cf3e04bace7d666b44c2990779b8",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists_invalid/bal_invalid_missing_account.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_code_changes[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x47353aad1d1868a5a1d7acc4251cd26bef11ac90ff64c0ba63f19800224674c9",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_code_changes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_storage_reads[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x89cf1419f76bd33195b33bd5b3012a8216f6c86207f378cecb9e461be2432a9a",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_storage_reads.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_storage_writes[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0x4b103ea30781985f7e1e8bde58b5957499348aeed901f46088ba6d7c3eb9842a",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_storage_writes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_balance_changes[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0xbb44eb925afd3e3ea06255284950d73820364c366d475a5dc6d040fc8624daa9",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_balance_changes.json"
|
||||
},
|
||||
{
|
||||
"id": "tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_nonce_changes[fork_Amsterdam-blockchain_test]",
|
||||
"fixture_hash": "0xb23fd1678ba8f66079f4dd4b3481817f3322562cc3c60aa4d1278d932cbf1165",
|
||||
"fork": "Amsterdam",
|
||||
"format": "blockchain_test",
|
||||
"pre_hash": null,
|
||||
"json_path": "blockchain_tests/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_nonce_changes.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -38,6 +38,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
|
|||
BlobGasUsed *math.HexOrDecimal64
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
ParentBeaconBlockRoot *common.Hash
|
||||
BlockAccessListHash *common.Hash
|
||||
}
|
||||
var enc btHeader
|
||||
enc.Bloom = b.Bloom
|
||||
|
|
@ -61,6 +62,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
|
|||
enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas)
|
||||
enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot
|
||||
enc.BlockAccessListHash = b.BlockAccessListHash
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +90,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
|
|||
BlobGasUsed *math.HexOrDecimal64
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
ParentBeaconBlockRoot *common.Hash
|
||||
BlockAccessListHash *common.Hash
|
||||
}
|
||||
var dec btHeader
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -156,5 +159,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
|
|||
if dec.ParentBeaconBlockRoot != nil {
|
||||
b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
|
||||
}
|
||||
if dec.BlockAccessListHash != nil {
|
||||
b.BlockAccessListHash = dec.BlockAccessListHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -493,6 +493,38 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BPO1: bpo1BlobConfig,
|
||||
},
|
||||
},
|
||||
"Amsterdam": {
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
EIP150Block: big.NewInt(0),
|
||||
EIP155Block: big.NewInt(0),
|
||||
EIP158Block: big.NewInt(0),
|
||||
ByzantiumBlock: big.NewInt(0),
|
||||
ConstantinopleBlock: big.NewInt(0),
|
||||
PetersburgBlock: big.NewInt(0),
|
||||
IstanbulBlock: big.NewInt(0),
|
||||
MuirGlacierBlock: big.NewInt(0),
|
||||
BerlinBlock: big.NewInt(0),
|
||||
LondonBlock: big.NewInt(0),
|
||||
ArrowGlacierBlock: big.NewInt(0),
|
||||
MergeNetsplitBlock: big.NewInt(0),
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
ShanghaiTime: u64(0),
|
||||
CancunTime: u64(0),
|
||||
PragueTime: u64(0),
|
||||
OsakaTime: u64(0),
|
||||
BPO1Time: u64(0),
|
||||
BPO2Time: u64(0),
|
||||
AmsterdamTime: u64(0),
|
||||
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
Osaka: params.DefaultOsakaBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
},
|
||||
},
|
||||
"OsakaToBPO1AtTime15k": {
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
|
|
|
|||
|
|
@ -210,6 +210,29 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateStorageBatch attempts to update a list storages in the batch manner.
|
||||
func (t *StateTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error {
|
||||
var (
|
||||
hkeys = make([][]byte, 0, len(keys))
|
||||
evals = make([][]byte, 0, len(values))
|
||||
)
|
||||
for _, key := range keys {
|
||||
hk := crypto.Keccak256(key)
|
||||
if t.preimages != nil {
|
||||
t.secKeyCache[common.Hash(hk)] = key
|
||||
}
|
||||
hkeys = append(hkeys, hk)
|
||||
}
|
||||
for _, val := range values {
|
||||
data, err := rlp.EncodeToBytes(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evals = append(evals, data)
|
||||
}
|
||||
return t.trie.UpdateBatch(hkeys, evals)
|
||||
}
|
||||
|
||||
// UpdateAccount will abstract the write of an account to the secure trie.
|
||||
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
|
||||
hk := crypto.Keccak256(address.Bytes())
|
||||
|
|
@ -226,6 +249,29 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateAccountBatch attempts to update a list accounts in the batch manner.
|
||||
func (t *StateTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error {
|
||||
var (
|
||||
hkeys = make([][]byte, 0, len(addresses))
|
||||
values = make([][]byte, 0, len(accounts))
|
||||
)
|
||||
for _, addr := range addresses {
|
||||
hk := crypto.Keccak256(addr.Bytes())
|
||||
if t.preimages != nil {
|
||||
t.secKeyCache[common.Hash(hk)] = addr.Bytes()
|
||||
}
|
||||
hkeys = append(hkeys, hk)
|
||||
}
|
||||
for _, acc := range accounts {
|
||||
data, err := rlp.EncodeToBytes(acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values = append(values, data)
|
||||
}
|
||||
return t.trie.UpdateBatch(hkeys, values)
|
||||
}
|
||||
|
||||
func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,10 @@ import (
|
|||
// while the latter is inserted/deleted in order to follow the rule of trie.
|
||||
// This tool can track all of them no matter the node is embedded in its
|
||||
// parent or not, but valueNode is never tracked.
|
||||
//
|
||||
// Note opTracer is not thread-safe, callers should be responsible for handling
|
||||
// the concurrency issues by themselves.
|
||||
type opTracer struct {
|
||||
inserts map[string]struct{}
|
||||
deletes map[string]struct{}
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// newOpTracer initializes the tracer for capturing trie changes.
|
||||
|
|
@ -53,6 +51,9 @@ func newOpTracer() *opTracer {
|
|||
// in the deletion set (resurrected node), then just wipe it from
|
||||
// the deletion set as it's "untouched".
|
||||
func (t *opTracer) onInsert(path []byte) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if _, present := t.deletes[string(path)]; present {
|
||||
delete(t.deletes, string(path))
|
||||
return
|
||||
|
|
@ -64,6 +65,9 @@ func (t *opTracer) onInsert(path []byte) {
|
|||
// in the addition set, then just wipe it from the addition set
|
||||
// as it's untouched.
|
||||
func (t *opTracer) onDelete(path []byte) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if _, present := t.inserts[string(path)]; present {
|
||||
delete(t.inserts, string(path))
|
||||
return
|
||||
|
|
@ -73,12 +77,18 @@ func (t *opTracer) onDelete(path []byte) {
|
|||
|
||||
// reset clears the content tracked by tracer.
|
||||
func (t *opTracer) reset() {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
clear(t.inserts)
|
||||
clear(t.deletes)
|
||||
}
|
||||
|
||||
// copy returns a deep copied tracer instance.
|
||||
func (t *opTracer) copy() *opTracer {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return &opTracer{
|
||||
inserts: maps.Clone(t.inserts),
|
||||
deletes: maps.Clone(t.deletes),
|
||||
|
|
@ -87,6 +97,9 @@ func (t *opTracer) copy() *opTracer {
|
|||
|
||||
// deletedList returns a list of node paths which are deleted from the trie.
|
||||
func (t *opTracer) deletedList() [][]byte {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
paths := make([][]byte, 0, len(t.deletes))
|
||||
for path := range t.deletes {
|
||||
paths = append(paths, []byte(path))
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@ func NewTransitionTrie(base *SecureTrie, overlay *VerkleTrie, st bool) *Transiti
|
|||
}
|
||||
}
|
||||
|
||||
func (t *TransitionTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (t *TransitionTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Base returns the base trie.
|
||||
func (t *TransitionTrie) Base() *SecureTrie {
|
||||
return t.base
|
||||
|
|
|
|||
66
trie/trie.go
66
trie/trie.go
|
|
@ -480,6 +480,72 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
|||
}
|
||||
}
|
||||
|
||||
// UpdateBatch updates a batch of entries concurrently.
|
||||
func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error {
|
||||
// Short circuit if the trie is already committed and unusable.
|
||||
if t.committed {
|
||||
return ErrCommitted
|
||||
}
|
||||
if len(keys) != len(values) {
|
||||
return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
|
||||
}
|
||||
// Insert the entries sequentially if there are not too many
|
||||
// trie nodes in the trie.
|
||||
fn, ok := t.root.(*fullNode)
|
||||
|
||||
if !ok || len(keys) < 4 { // TODO(rjl493456442) the parallelism threshold should be twisted
|
||||
for i, key := range keys {
|
||||
err := t.Update(key, values[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
ikeys = make(map[byte][][]byte)
|
||||
ivals = make(map[byte][][]byte)
|
||||
eg errgroup.Group
|
||||
)
|
||||
for i, key := range keys {
|
||||
hkey := keybytesToHex(key)
|
||||
ikeys[hkey[0]] = append(ikeys[hkey[0]], hkey)
|
||||
ivals[hkey[0]] = append(ivals[hkey[0]], values[i])
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
fn.flags = t.newFlag()
|
||||
}
|
||||
for p, k := range ikeys {
|
||||
pos := p
|
||||
ks := k
|
||||
eg.Go(func() error {
|
||||
vs := ivals[pos]
|
||||
for i, k := range ks {
|
||||
if len(vs[i]) != 0 {
|
||||
_, n, err := t.insert(fn.Children[pos], []byte{pos}, k[1:], valueNode(vs[i]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fn.Children[pos] = n
|
||||
} else {
|
||||
_, n, err := t.delete(fn.Children[pos], []byte{pos}, k[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fn.Children[pos] = n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.unhashed += len(keys)
|
||||
t.uncommitted += len(keys)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustDelete is a wrapper of Delete and will omit any encountered error but
|
||||
// just print out an error message.
|
||||
func (t *Trie) MustDelete(key []byte) {
|
||||
|
|
|
|||
|
|
@ -1500,82 +1500,56 @@ func testTrieCopyNewTrie(t *testing.T, entries []kv) {
|
|||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/trie
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkTriePrefetch
|
||||
// BenchmarkTriePrefetch-8 9961 100706 ns/op
|
||||
func BenchmarkTriePrefetch(b *testing.B) {
|
||||
db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
|
||||
tr := NewEmpty(db)
|
||||
vals := make(map[string]*kv)
|
||||
for i := 0; i < 3000; i++ {
|
||||
value := &kv{
|
||||
k: randBytes(32),
|
||||
v: randBytes(20),
|
||||
t: false,
|
||||
}
|
||||
tr.MustUpdate(value.k, value.v)
|
||||
vals[string(value.k)] = value
|
||||
}
|
||||
root, nodes := tr.Commit(false)
|
||||
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||
b.ResetTimer()
|
||||
func TestUpdateBatch(t *testing.T) {
|
||||
testUpdateBatch(t, []kv{
|
||||
{k: []byte("do"), v: []byte("verb")},
|
||||
{k: []byte("ether"), v: []byte("wookiedoo")},
|
||||
{k: []byte("horse"), v: []byte("stallion")},
|
||||
{k: []byte("shaman"), v: []byte("horse")},
|
||||
{k: []byte("doge"), v: []byte("coin")},
|
||||
{k: []byte("dog"), v: []byte("puppy")},
|
||||
})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr, err := New(TrieID(root), db)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to open the trie")
|
||||
}
|
||||
var keys [][]byte
|
||||
for k := range vals {
|
||||
keys = append(keys, []byte(k))
|
||||
if len(keys) > 64 {
|
||||
break
|
||||
}
|
||||
}
|
||||
tr.Prefetch(keys)
|
||||
var entries []kv
|
||||
for i := 0; i < 256; i++ {
|
||||
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
|
||||
}
|
||||
testUpdateBatch(t, entries)
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/trie
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkTrieSeqPrefetch
|
||||
// BenchmarkTrieSeqPrefetch-8 12879 96710 ns/op
|
||||
func BenchmarkTrieSeqPrefetch(b *testing.B) {
|
||||
db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
|
||||
tr := NewEmpty(db)
|
||||
vals := make(map[string]*kv)
|
||||
for i := 0; i < 3000; i++ {
|
||||
value := &kv{
|
||||
k: randBytes(32),
|
||||
v: randBytes(20),
|
||||
t: false,
|
||||
func testUpdateBatch(t *testing.T, entries []kv) {
|
||||
var (
|
||||
base = NewEmpty(nil)
|
||||
keys [][]byte
|
||||
vals [][]byte
|
||||
)
|
||||
for _, entry := range entries {
|
||||
base.Update(entry.k, entry.v)
|
||||
keys = append(keys, entry.k)
|
||||
vals = append(vals, entry.v)
|
||||
}
|
||||
tr.MustUpdate(value.k, value.v)
|
||||
vals[string(value.k)] = value
|
||||
for i := 0; i < 10; i++ {
|
||||
k, v := testrand.Bytes(32), testrand.Bytes(32)
|
||||
base.Update(k, v)
|
||||
keys = append(keys, k)
|
||||
vals = append(vals, v)
|
||||
}
|
||||
root, nodes := tr.Commit(false)
|
||||
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr, err := New(TrieID(root), db)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to open the trie")
|
||||
cmp := NewEmpty(nil)
|
||||
if err := cmp.UpdateBatch(keys, vals); err != nil {
|
||||
t.Fatalf("Failed to update batch, %v", err)
|
||||
}
|
||||
var keys [][]byte
|
||||
for k := range vals {
|
||||
keys = append(keys, []byte(k))
|
||||
if len(keys) > 64 {
|
||||
break
|
||||
|
||||
// Traverse the original tree, the changes made on the copy one shouldn't
|
||||
// affect the old one
|
||||
for _, key := range keys {
|
||||
v1, _ := base.Get(key)
|
||||
v2, _ := cmp.Get(key)
|
||||
if !bytes.Equal(v1, v2) {
|
||||
t.Errorf("Unexpected data, key: %v, want: %v, got: %v", key, v1, v2)
|
||||
}
|
||||
}
|
||||
for _, k := range keys {
|
||||
tr.Get(k)
|
||||
}
|
||||
if base.Hash() != cmp.Hash() {
|
||||
t.Errorf("Hash mismatch: want %x, got %x", base.Hash(), cmp.Hash())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,22 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount,
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateAccountBatch attempts to update a list accounts in the batch manner.
|
||||
func (t *VerkleTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLens []int) error {
|
||||
if len(addresses) != len(accounts) {
|
||||
return fmt.Errorf("address and accounts length mismatch: %d != %d", len(addresses), len(accounts))
|
||||
}
|
||||
if len(addresses) != len(codeLens) {
|
||||
return fmt.Errorf("address and code length mismatch: %d != %d", len(addresses), len(codeLens))
|
||||
}
|
||||
for i, addr := range addresses {
|
||||
if err := t.UpdateAccount(addr, accounts[i], codeLens[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStorage implements state.Trie, writing the provided storage slot into
|
||||
// the tree. If the tree is corrupted, an error will be returned.
|
||||
func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
||||
|
|
@ -191,6 +207,19 @@ func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) er
|
|||
return t.root.Insert(k, v[:], t.nodeResolver)
|
||||
}
|
||||
|
||||
// UpdateStorageBatch attempts to update a list storages in the batch manner.
|
||||
func (t *VerkleTrie) UpdateStorageBatch(address common.Address, keys [][]byte, values [][]byte) error {
|
||||
if len(keys) != len(values) {
|
||||
return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
|
||||
}
|
||||
for i, key := range keys {
|
||||
if err := t.UpdateStorage(address, key, values[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAccount leaves the account untouched, as no account deletion can happen
|
||||
// in verkle.
|
||||
// There is a special corner case, in which an account that is prefunded, CREATE2-d
|
||||
|
|
|
|||
Loading…
Reference in a new issue