mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
rework bal generation using the tracer-based approach
This commit is contained in:
parent
0a2c21acd5
commit
c2d883321d
39 changed files with 1871 additions and 626 deletions
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = (*executableDataMarshaling)(nil)
|
var _ = (*executableDataMarshaling)(nil)
|
||||||
|
|
@ -34,6 +35,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
|
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||||
}
|
}
|
||||||
var enc ExecutableData
|
var enc ExecutableData
|
||||||
|
|
@ -59,6 +61,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||||
enc.Withdrawals = e.Withdrawals
|
enc.Withdrawals = e.Withdrawals
|
||||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||||
|
enc.BlockAccessList = e.BlockAccessList
|
||||||
enc.ExecutionWitness = e.ExecutionWitness
|
enc.ExecutionWitness = e.ExecutionWitness
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +86,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
|
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
||||||
}
|
}
|
||||||
var dec ExecutableData
|
var dec ExecutableData
|
||||||
|
|
@ -157,6 +161,9 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||||
if dec.ExcessBlobGas != nil {
|
if dec.ExcessBlobGas != nil {
|
||||||
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||||
}
|
}
|
||||||
|
if dec.BlockAccessList != nil {
|
||||||
|
e.BlockAccessList = dec.BlockAccessList
|
||||||
|
}
|
||||||
if dec.ExecutionWitness != nil {
|
if dec.ExecutionWitness != nil {
|
||||||
e.ExecutionWitness = dec.ExecutionWitness
|
e.ExecutionWitness = dec.ExecutionWitness
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result := &testResult{Name: name, Pass: true}
|
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 ctx.Bool(DumpFlag.Name) {
|
||||||
if s, _ := chain.State(); s != nil {
|
if s, _ := chain.State(); s != nil {
|
||||||
result.State = dump(s)
|
result.State = dump(s)
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ var (
|
||||||
utils.BeaconGenesisTimeFlag,
|
utils.BeaconGenesisTimeFlag,
|
||||||
utils.BeaconCheckpointFlag,
|
utils.BeaconCheckpointFlag,
|
||||||
utils.BeaconCheckpointFileFlag,
|
utils.BeaconCheckpointFileFlag,
|
||||||
|
utils.ExperimentalBALFlag,
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -1009,6 +1009,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
||||||
Value: metrics.DefaultConfig.InfluxDBOrganization,
|
Value: metrics.DefaultConfig.InfluxDBOrganization,
|
||||||
Category: flags.MetricsCategory,
|
Category: flags.MetricsCategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block Access List flags
|
||||||
|
|
||||||
|
ExperimentalBALFlag = &cli.BoolFlag{
|
||||||
|
Name: "experimental.bal",
|
||||||
|
Usage: "Enable generation of EIP-7928 block access lists when importing post-Cancun blocks which lack them. When this flag is specified, importing blocks containing access lists triggers validation of their correctness and execution based off them. The header block access list field is not set with blocks created when this flag is specified, nor is it validated when importing blocks that contain access lists. This is used for development purposes only. Do not enable it otherwise.",
|
||||||
|
Category: flags.MiscCategory,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -1917,6 +1925,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name)
|
cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.ExperimentalBAL = ctx.Bool(ExperimentalBALFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeBeaconLightConfig constructs a beacon light client config based on the
|
// MakeBeaconLightConfig constructs a beacon light client config based on the
|
||||||
|
|
@ -2319,6 +2329,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
}
|
}
|
||||||
options.VmConfig = vmcfg
|
options.VmConfig = vmcfg
|
||||||
|
|
||||||
|
options.EnableBALForTesting = ctx.Bool(ExperimentalBALFlag.Name)
|
||||||
chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
|
chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("Can't create BlockChain: %v", err)
|
Fatalf("Can't create BlockChain: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -343,9 +343,9 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
||||||
|
|
||||||
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
||||||
// assembling the block.
|
// 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 *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalization func()) (*types.Block, error) {
|
||||||
if !beacon.IsPoSHeader(header) {
|
if !beacon.IsPoSHeader(header) {
|
||||||
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts)
|
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts, onFinalization)
|
||||||
}
|
}
|
||||||
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
|
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
|
||||||
if shanghai {
|
if shanghai {
|
||||||
|
|
@ -364,6 +364,10 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
||||||
// Assign the final state root to header.
|
// Assign the final state root to header.
|
||||||
header.Root = state.IntermediateRoot(true)
|
header.Root = state.IntermediateRoot(true)
|
||||||
|
|
||||||
|
if onFinalization != nil {
|
||||||
|
onFinalization()
|
||||||
|
}
|
||||||
|
|
||||||
// Assemble the final block.
|
// Assemble the final block.
|
||||||
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
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,
|
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||||
// nor block rewards given, and returns the final block.
|
// 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.StateDB, body *types.Body, receipts []*types.Receipt, onFinalize func()) (*types.Block, error) {
|
||||||
if len(body.Withdrawals) > 0 {
|
if len(body.Withdrawals) > 0 {
|
||||||
return nil, errors.New("clique does not support withdrawals")
|
return nil, errors.New("clique does not support withdrawals")
|
||||||
}
|
}
|
||||||
|
|
@ -589,6 +589,10 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
|
||||||
// Assign the final state root to header.
|
// Assign the final state root to header.
|
||||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||||
|
|
||||||
|
if onFinalize != nil {
|
||||||
|
onFinalize()
|
||||||
|
}
|
||||||
|
|
||||||
// Assemble and return the final block for sealing.
|
// Assemble and return the final block for sealing.
|
||||||
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
|
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ type Engine interface {
|
||||||
//
|
//
|
||||||
// Note: The block header and state database might be updated to reflect any
|
// Note: The block header and state database might be updated to reflect any
|
||||||
// consensus rules that happen at finalization (e.g. block rewards).
|
// 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 *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalization func()) (*types.Block, error)
|
||||||
|
|
||||||
// Seal generates a new sealing request for the given input block and pushes
|
// Seal generates a new sealing request for the given input block and pushes
|
||||||
// the result into the given channel.
|
// 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
|
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||||
// uncle rewards, setting the final state and assembling the block.
|
// 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.StateDB, body *types.Body, receipts []*types.Receipt, onFinalize func()) (*types.Block, error) {
|
||||||
if len(body.Withdrawals) > 0 {
|
if len(body.Withdrawals) > 0 {
|
||||||
return nil, errors.New("ethash does not support withdrawals")
|
return nil, errors.New("ethash does not support withdrawals")
|
||||||
}
|
}
|
||||||
|
|
@ -521,6 +521,9 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
||||||
// Assign the final state root to header.
|
// Assign the final state root to header.
|
||||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||||
|
|
||||||
|
if onFinalize != nil {
|
||||||
|
onFinalize()
|
||||||
|
}
|
||||||
// Header seems complete, assemble into a block and return
|
// Header seems complete, assemble into a block and return
|
||||||
return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
|
return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
|
||||||
bc = s.BPO2
|
bc = s.BPO2
|
||||||
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
||||||
bc = s.BPO1
|
bc = s.BPO1
|
||||||
|
case cfg.IsAmsterdam(london, time) && s.Amsterdam != nil:
|
||||||
|
bc = s.Amsterdam
|
||||||
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
||||||
bc = s.Osaka
|
bc = s.Osaka
|
||||||
case cfg.IsPrague(london, time) && s.Prague != nil:
|
case cfg.IsPrague(london, time) && s.Prague != nil:
|
||||||
|
|
|
||||||
102
core/block_access_list_tracer.go
Normal file
102
core/block_access_list_tracer.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockAccessListTracer is a tracer which gathers state accesses/mutations
|
||||||
|
// from the execution of a block. It is used for constructing and verifying
|
||||||
|
// EIP-7928 block access lists.
|
||||||
|
type BlockAccessListTracer struct {
|
||||||
|
builder *bal.AccessListBuilder
|
||||||
|
|
||||||
|
// the access list index that changes are currently being recorded into
|
||||||
|
balIdx uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockAccessListTracer returns an BlockAccessListTracer and a set of hooks
|
||||||
|
func NewBlockAccessListTracer() (*BlockAccessListTracer, *tracing.Hooks) {
|
||||||
|
balTracer := &BlockAccessListTracer{
|
||||||
|
builder: bal.NewAccessListBuilder(),
|
||||||
|
}
|
||||||
|
hooks := &tracing.Hooks{
|
||||||
|
OnBlockFinalization: balTracer.OnBlockFinalization,
|
||||||
|
OnPreTxExecutionDone: balTracer.OnPreTxExecutionDone,
|
||||||
|
OnTxEnd: balTracer.TxEndHook,
|
||||||
|
OnEnter: balTracer.OnEnter,
|
||||||
|
OnExit: balTracer.OnExit,
|
||||||
|
OnCodeChangeV2: balTracer.OnCodeChange,
|
||||||
|
OnBalanceChange: balTracer.OnBalanceChange,
|
||||||
|
OnNonceChangeV2: balTracer.OnNonceChange,
|
||||||
|
OnStorageChange: balTracer.OnStorageChange,
|
||||||
|
OnStorageRead: balTracer.OnStorageRead,
|
||||||
|
OnAccountRead: balTracer.OnAcountRead,
|
||||||
|
OnSelfDestructChange: balTracer.OnSelfDestruct,
|
||||||
|
}
|
||||||
|
wrappedHooks, _ := tracing.WrapWithJournal(hooks)
|
||||||
|
return balTracer, wrappedHooks
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessList returns the constructed access list.
|
||||||
|
// It is assumed that this is only called after all the block state changes
|
||||||
|
// have been executed and the block has been finalized.
|
||||||
|
func (a *BlockAccessListTracer) AccessList() *bal.AccessListBuilder {
|
||||||
|
return a.builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnPreTxExecutionDone() {
|
||||||
|
a.builder.FinaliseIdxChanges(0)
|
||||||
|
a.balIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) TxEndHook(receipt *types.Receipt, err error) {
|
||||||
|
a.builder.FinaliseIdxChanges(a.balIdx)
|
||||||
|
a.balIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||||
|
a.builder.EnterScope()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
|
||||||
|
a.builder.ExitScope(reverted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
|
||||||
|
a.builder.CodeChange(addr, prevCode, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnSelfDestruct(addr common.Address) {
|
||||||
|
a.builder.SelfDestruct(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnBlockFinalization() {
|
||||||
|
a.builder.FinaliseIdxChanges(a.balIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnBalanceChange(addr common.Address, prevBalance, newBalance *big.Int, _ tracing.BalanceChangeReason) {
|
||||||
|
newU256 := new(uint256.Int).SetBytes(newBalance.Bytes())
|
||||||
|
prevU256 := new(uint256.Int).SetBytes(prevBalance.Bytes())
|
||||||
|
a.builder.BalanceChange(addr, prevU256, newU256)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnNonceChange(addr common.Address, prev uint64, new uint64, reason tracing.NonceChangeReason) {
|
||||||
|
a.builder.NonceChange(addr, prev, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnStorageRead(addr common.Address, key common.Hash) {
|
||||||
|
a.builder.StorageRead(addr, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnAcountRead(addr common.Address) {
|
||||||
|
a.builder.AccountRead(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) {
|
||||||
|
a.builder.StorageWrite(addr, slot, prev, new)
|
||||||
|
}
|
||||||
|
|
@ -111,6 +111,31 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// block access lists must be present after the Amsterdam hard fork
|
||||||
|
if v.config.IsAmsterdam(block.Number(), block.Time()) {
|
||||||
|
if block.Body().AccessList == nil {
|
||||||
|
return fmt.Errorf("access list not present in block body")
|
||||||
|
} else if *block.Header().BlockAccessListHash != block.Body().AccessList.Hash() {
|
||||||
|
return fmt.Errorf("access list hash mismatch. local: %x. remote: %x\n", block.Body().AccessList.Hash(), *block.Header().BlockAccessListHash)
|
||||||
|
} else if err := block.Body().AccessList.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("invalid block access list: %v", err)
|
||||||
|
}
|
||||||
|
} else if !v.bc.cfg.EnableBALForTesting {
|
||||||
|
// if --experimental.bal is not enabled, block headers cannot have access list hash and bodies cannot have access lists.
|
||||||
|
if block.Body().AccessList != nil {
|
||||||
|
return fmt.Errorf("access list not allowed in block body if not in amsterdam or --experimental.bal is set")
|
||||||
|
} else if block.Header().BlockAccessListHash != nil {
|
||||||
|
return fmt.Errorf("access list hash in block header not allowed when --experimental.bal is set")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if --experimental.bal is enabled, the BAL hash is not allowed in the header.
|
||||||
|
// this is in order that Geth can import pre-existing chains augmented with BALs
|
||||||
|
// and not have a hash mismatch.
|
||||||
|
if block.Header().BlockAccessListHash != nil {
|
||||||
|
return fmt.Errorf("access list hash in block header not allowed pre-amsterdam")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ancestor block must be known.
|
// Ancestor block must be known.
|
||||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,11 @@ type BlockChainConfig struct {
|
||||||
// If the value is -1, indexing is disabled.
|
// If the value is -1, indexing is disabled.
|
||||||
TxLookupLimit int64
|
TxLookupLimit int64
|
||||||
|
|
||||||
|
// If EnableBALForTesting is enabled, block access lists will be created
|
||||||
|
// from block execution and embedded in the body. The block access list
|
||||||
|
// hash will not be set in the header.
|
||||||
|
EnableBALForTesting bool
|
||||||
|
|
||||||
// StateSizeTracking indicates whether the state size tracking is enabled.
|
// StateSizeTracking indicates whether the state size tracking is enabled.
|
||||||
StateSizeTracking bool
|
StateSizeTracking bool
|
||||||
}
|
}
|
||||||
|
|
@ -1911,7 +1916,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
||||||
}
|
}
|
||||||
// The traced section of block import.
|
// The traced section of block import.
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
|
enableBAL := (bc.cfg.EnableBALForTesting && bc.chainConfig.IsCancun(block.Number(), block.Time())) || bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
|
||||||
|
blockHasAccessList := block.Body().AccessList != nil
|
||||||
|
constructBAL := enableBAL && !blockHasAccessList
|
||||||
|
|
||||||
|
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, constructBAL, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
|
|
@ -1983,7 +1992,7 @@ func (bpr *blockProcessingResult) Witness() *stateless.Witness {
|
||||||
|
|
||||||
// ProcessBlock executes and validates the given block. If there was no error
|
// ProcessBlock executes and validates the given block. If there was no error
|
||||||
// it writes the block and associated state to database.
|
// it writes the block and associated state to database.
|
||||||
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
|
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, constructBALForTesting bool, validateBAL bool) (_ *blockProcessingResult, blockEndErr error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
|
|
@ -2079,6 +2088,14 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var balTracer *BlockAccessListTracer
|
||||||
|
// Process block using the parent state as reference point
|
||||||
|
if constructBALForTesting {
|
||||||
|
balTracer, bc.cfg.VmConfig.Tracer = NewBlockAccessListTracer()
|
||||||
|
defer func() {
|
||||||
|
bc.cfg.VmConfig.Tracer = nil
|
||||||
|
}()
|
||||||
|
}
|
||||||
// Process block using the parent state as reference point
|
// Process block using the parent state as reference point
|
||||||
pstart := time.Now()
|
pstart := time.Now()
|
||||||
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
|
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
|
||||||
|
|
@ -2088,11 +2105,24 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
}
|
}
|
||||||
ptime := time.Since(pstart)
|
ptime := time.Since(pstart)
|
||||||
|
|
||||||
|
if constructBALForTesting {
|
||||||
|
balTracer.OnBlockFinalization()
|
||||||
|
}
|
||||||
|
|
||||||
vstart := time.Now()
|
vstart := time.Now()
|
||||||
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
|
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
|
||||||
bc.reportBlock(block, res, err)
|
bc.reportBlock(block, res, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if constructBALForTesting {
|
||||||
|
// very ugly... deep-copy the block body before setting the block access
|
||||||
|
// list on it to prevent mutating the block instance passed by the caller.
|
||||||
|
existingBody := block.Body()
|
||||||
|
block = block.WithBody(*existingBody)
|
||||||
|
existingBody = block.Body()
|
||||||
|
existingBody.AccessList = balTracer.AccessList().ToEncodingObj()
|
||||||
|
block = block.WithBody(*existingBody)
|
||||||
|
}
|
||||||
vtime := time.Since(vstart)
|
vtime := time.Since(vstart)
|
||||||
|
|
||||||
// If witnesses was generated and stateless self-validation requested, do
|
// If witnesses was generated and stateless self-validation requested, do
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||||
}
|
}
|
||||||
|
|
||||||
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
||||||
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
|
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +520,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
|
||||||
Uncles: b.uncles,
|
Uncles: b.uncles,
|
||||||
Withdrawals: b.withdrawals,
|
Withdrawals: b.withdrawals,
|
||||||
}
|
}
|
||||||
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, body, b.receipts)
|
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, body, b.receipts, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ type StateDB struct {
|
||||||
// The tx context and all occurred logs in the scope of transaction.
|
// The tx context and all occurred logs in the scope of transaction.
|
||||||
thash common.Hash
|
thash common.Hash
|
||||||
txIndex int
|
txIndex int
|
||||||
|
|
||||||
logs map[common.Hash][]*types.Log
|
logs map[common.Hash][]*types.Log
|
||||||
logSize uint
|
logSize uint
|
||||||
|
|
||||||
|
|
@ -301,6 +302,13 @@ func (s *StateDB) Exist(addr common.Address) bool {
|
||||||
return s.getStateObject(addr) != nil
|
return s.getStateObject(addr) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExistBeforeCurTx returns true if a contract exists and was not created
|
||||||
|
// in the current transaction.
|
||||||
|
func (s *StateDB) ExistBeforeCurTx(addr common.Address) bool {
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
return obj != nil && !obj.newContract
|
||||||
|
}
|
||||||
|
|
||||||
// Empty returns whether the state object is either non-existent
|
// Empty returns whether the state object is either non-existent
|
||||||
// or empty according to the EIP161 specification (balance = nonce = code = 0)
|
// or empty according to the EIP161 specification (balance = nonce = code = 0)
|
||||||
func (s *StateDB) Empty(addr common.Address) bool {
|
func (s *StateDB) Empty(addr common.Address) bool {
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
|
@ -27,6 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
"github.com/ethereum/go-ethereum/trie/utils"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
// hookedStateDB represents a statedb which emits calls to tracing-hooks
|
// hookedStateDB represents a statedb which emits calls to tracing-hooks
|
||||||
|
|
@ -54,22 +53,37 @@ func (s *hookedStateDB) CreateContract(addr common.Address) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
|
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.GetBalance(addr)
|
return s.inner.GetBalance(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetNonce(addr common.Address) uint64 {
|
func (s *hookedStateDB) GetNonce(addr common.Address) uint64 {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.GetNonce(addr)
|
return s.inner.GetNonce(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash {
|
func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.GetCodeHash(addr)
|
return s.inner.GetCodeHash(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetCode(addr common.Address) []byte {
|
func (s *hookedStateDB) GetCode(addr common.Address) []byte {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.GetCode(addr)
|
return s.inner.GetCode(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
|
func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.GetCodeSize(addr)
|
return s.inner.GetCodeSize(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,14 +100,23 @@ func (s *hookedStateDB) GetRefund() uint64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetStateAndCommittedState(addr common.Address, hash common.Hash) (common.Hash, common.Hash) {
|
func (s *hookedStateDB) GetStateAndCommittedState(addr common.Address, hash common.Hash) (common.Hash, common.Hash) {
|
||||||
|
if s.hooks.OnStorageRead != nil {
|
||||||
|
s.hooks.OnStorageRead(addr, hash)
|
||||||
|
}
|
||||||
return s.inner.GetStateAndCommittedState(addr, hash)
|
return s.inner.GetStateAndCommittedState(addr, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
|
if s.hooks.OnStorageRead != nil {
|
||||||
|
s.hooks.OnStorageRead(addr, hash)
|
||||||
|
}
|
||||||
return s.inner.GetState(addr, hash)
|
return s.inner.GetState(addr, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash {
|
func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.GetStorageRoot(addr)
|
return s.inner.GetStorageRoot(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,14 +129,23 @@ func (s *hookedStateDB) SetTransientState(addr common.Address, key, value common
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool {
|
func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.HasSelfDestructed(addr)
|
return s.inner.HasSelfDestructed(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) Exist(addr common.Address) bool {
|
func (s *hookedStateDB) Exist(addr common.Address) bool {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.Exist(addr)
|
return s.inner.Exist(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) Empty(addr common.Address) bool {
|
func (s *hookedStateDB) Empty(addr common.Address) bool {
|
||||||
|
if s.hooks.OnAccountRead != nil {
|
||||||
|
s.hooks.OnAccountRead(addr)
|
||||||
|
}
|
||||||
return s.inner.Empty(addr)
|
return s.inner.Empty(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,57 +248,25 @@ func (s *hookedStateDB) SetState(address common.Address, key common.Hash, value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int {
|
func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int {
|
||||||
var prevCode []byte
|
|
||||||
var prevCodeHash common.Hash
|
|
||||||
|
|
||||||
if s.hooks.OnCodeChange != nil {
|
|
||||||
prevCode = s.inner.GetCode(address)
|
|
||||||
prevCodeHash = s.inner.GetCodeHash(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev := s.inner.SelfDestruct(address)
|
prev := s.inner.SelfDestruct(address)
|
||||||
|
|
||||||
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
|
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
|
||||||
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(prevCode) > 0 {
|
|
||||||
if s.hooks.OnCodeChangeV2 != nil {
|
|
||||||
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
|
|
||||||
} else if s.hooks.OnCodeChange != nil {
|
|
||||||
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, bool) {
|
func (s *hookedStateDB) SelfDestruct6780(src common.Address) (uint256.Int, bool) {
|
||||||
var prevCode []byte
|
prev, changed := s.inner.SelfDestruct6780(src)
|
||||||
var prevCodeHash common.Hash
|
|
||||||
|
|
||||||
if s.hooks.OnCodeChange != nil {
|
|
||||||
prevCodeHash = s.inner.GetCodeHash(address)
|
|
||||||
prevCode = s.inner.GetCode(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev, changed := s.inner.SelfDestruct6780(address)
|
|
||||||
|
|
||||||
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
|
|
||||||
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed && len(prevCode) > 0 {
|
|
||||||
if s.hooks.OnCodeChangeV2 != nil {
|
|
||||||
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
|
|
||||||
} else if s.hooks.OnCodeChange != nil {
|
|
||||||
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev, changed
|
return prev, changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) ExistBeforeCurTx(addr common.Address) bool {
|
||||||
|
return s.inner.ExistBeforeCurTx(addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) AddLog(log *types.Log) {
|
func (s *hookedStateDB) AddLog(log *types.Log) {
|
||||||
// The inner will modify the log (add fields), so invoke that first
|
// The inner will modify the log (add fields), so invoke that first
|
||||||
s.inner.AddLog(log)
|
s.inner.AddLog(log)
|
||||||
|
|
@ -277,16 +277,42 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
||||||
|
|
||||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
defer s.inner.Finalise(deleteEmptyObjects)
|
defer s.inner.Finalise(deleteEmptyObjects)
|
||||||
if s.hooks.OnBalanceChange == nil {
|
if s.hooks.OnSelfDestructChange != nil || s.hooks.OnBalanceChange != nil || s.hooks.OnNonceChangeV2 != nil || s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
for addr := range s.inner.journal.dirties {
|
for addr := range s.inner.journal.dirties {
|
||||||
obj := s.inner.stateObjects[addr]
|
obj := s.inner.stateObjects[addr]
|
||||||
if obj != nil && obj.selfDestructed {
|
if obj != nil && obj.selfDestructed {
|
||||||
|
if obj.selfDestructed && s.hooks.OnSelfDestructChange != nil {
|
||||||
|
// when executing, can we tell the difference between
|
||||||
|
s.hooks.OnSelfDestructChange(obj.address)
|
||||||
|
}
|
||||||
|
|
||||||
// If ether was sent to account post-selfdestruct it is burnt.
|
// If ether was sent to account post-selfdestruct it is burnt.
|
||||||
|
if s.hooks.OnBalanceChange != nil {
|
||||||
if bal := obj.Balance(); bal.Sign() != 0 {
|
if bal := obj.Balance(); bal.Sign() != 0 {
|
||||||
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
|
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if s.hooks.OnNonceChangeV2 != nil {
|
||||||
|
prevNonce := obj.Nonce()
|
||||||
|
s.hooks.OnNonceChangeV2(addr, prevNonce, 0, tracing.NonceChangeSelfdestruct)
|
||||||
|
}
|
||||||
|
prevCodeHash := s.inner.GetCodeHash(addr)
|
||||||
|
prevCode := s.inner.GetCode(addr)
|
||||||
|
|
||||||
|
// if an initcode invokes selfdestruct, do not emit a code change.
|
||||||
|
if prevCodeHash == types.EmptyCodeHash {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.hooks.OnCodeChangeV2 != nil {
|
||||||
|
s.hooks.OnCodeChangeV2(addr, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
|
||||||
|
} else if s.hooks.OnCodeChange != nil {
|
||||||
|
s.hooks.OnCodeChange(addr, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) TxIndex() int {
|
||||||
|
return s.inner.TxIndex()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hooks := cfg.Tracer; hooks != nil {
|
||||||
|
hooks.OnPreTxExecutionDone()
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate over and process the individual transactions
|
// Iterate over and process the individual transactions
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||||
|
|
@ -108,6 +112,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
receipts = append(receipts, receipt)
|
receipts = append(receipts, receipt)
|
||||||
allLogs = append(allLogs, receipt.Logs...)
|
allLogs = append(allLogs, receipt.Logs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read requests if Prague is enabled.
|
// Read requests if Prague is enabled.
|
||||||
var requests [][]byte
|
var requests [][]byte
|
||||||
if config.IsPrague(block.Number(), block.Time()) {
|
if config.IsPrague(block.Number(), block.Time()) {
|
||||||
|
|
@ -129,6 +134,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||||
|
|
||||||
|
if hooks := cfg.Tracer; hooks != nil {
|
||||||
|
hooks.OnBlockFinalization()
|
||||||
|
}
|
||||||
|
|
||||||
return &ProcessResult{
|
return &ProcessResult{
|
||||||
Receipts: receipts,
|
Receipts: receipts,
|
||||||
Requests: requests,
|
Requests: requests,
|
||||||
|
|
@ -213,7 +222,8 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create a new context to be used in the EVM environment
|
// Create a new context to be used in the EVM environment
|
||||||
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
|
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ package core
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -29,6 +26,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecutionResult includes all output after executing given evm
|
// ExecutionResult includes all output after executing given evm
|
||||||
|
|
@ -617,16 +616,22 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
|
||||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||||
|
|
||||||
// Update nonce and account code.
|
// Update nonce and account code.
|
||||||
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
||||||
if auth.Address == (common.Address{}) {
|
if auth.Address == (common.Address{}) {
|
||||||
// Delegation to zero address means clear.
|
// Delegation to zero address means clear.
|
||||||
|
if isDelegated {
|
||||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise install delegation to auth.Address.
|
// install delegation to auth.Address if the delegation changed
|
||||||
|
if !isDelegated || auth.Address != prevDelegation {
|
||||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,6 @@ type (
|
||||||
CloseHook = func()
|
CloseHook = func()
|
||||||
|
|
||||||
// BlockStartHook is called before executing `block`.
|
// BlockStartHook is called before executing `block`.
|
||||||
// `td` is the total difficulty prior to `block`.
|
|
||||||
BlockStartHook = func(event BlockEvent)
|
BlockStartHook = func(event BlockEvent)
|
||||||
|
|
||||||
// BlockEndHook is called after executing a block.
|
// BlockEndHook is called after executing a block.
|
||||||
|
|
@ -141,24 +140,25 @@ type (
|
||||||
// GenesisBlockHook is called when the genesis block is being processed.
|
// GenesisBlockHook is called when the genesis block is being processed.
|
||||||
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
|
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
|
||||||
|
|
||||||
// OnSystemCallStartHook is called when a system call is about to be executed. Today,
|
// OnSystemCallStartHook is called when a system call is about to be executed.
|
||||||
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
|
// Today, this hook is invoked when the EIP-4788 system call is about to be
|
||||||
// beacon block root.
|
// executed to set the beacon block root.
|
||||||
//
|
//
|
||||||
// After this hook, the EVM call tracing will happened as usual so you will receive a `OnEnter/OnExit`
|
// After this hook, the EVM call tracing will happened as usual so you will
|
||||||
// as well as state hooks between this hook and the `OnSystemCallEndHook`.
|
// receive a `OnEnter/OnExit` as well as state hooks between this hook and
|
||||||
|
// the `OnSystemCallEndHook`.
|
||||||
//
|
//
|
||||||
// Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks
|
// Note that system call happens outside normal transaction execution, so
|
||||||
// will not be invoked.
|
// the `OnTxStart/OnTxEnd` hooks will not be invoked.
|
||||||
OnSystemCallStartHook = func()
|
OnSystemCallStartHook = func()
|
||||||
|
|
||||||
// OnSystemCallStartHookV2 is called when a system call is about to be executed. Refer
|
// OnSystemCallStartHookV2 is called when a system call is about to be executed.
|
||||||
// to `OnSystemCallStartHook` for more information.
|
// Refer to `OnSystemCallStartHook` for more information.
|
||||||
OnSystemCallStartHookV2 = func(vm *VMContext)
|
OnSystemCallStartHookV2 = func(vm *VMContext)
|
||||||
|
|
||||||
// OnSystemCallEndHook is called when a system call has finished executing. Today,
|
// OnSystemCallEndHook is called when a system call has finished executing.
|
||||||
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
|
// Today, this hook is invoked when the EIP-4788 system call is about to be
|
||||||
// beacon block root.
|
// executed to set the beacon block root.
|
||||||
OnSystemCallEndHook = func()
|
OnSystemCallEndHook = func()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -183,9 +183,17 @@ type (
|
||||||
// StorageChangeHook is called when the storage of an account changes.
|
// StorageChangeHook is called when the storage of an account changes.
|
||||||
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash)
|
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash)
|
||||||
|
|
||||||
|
SelfDestructHook = func(address common.Address)
|
||||||
|
|
||||||
// LogHook is called when a log is emitted.
|
// LogHook is called when a log is emitted.
|
||||||
LogHook = func(log *types.Log)
|
LogHook = func(log *types.Log)
|
||||||
|
|
||||||
|
// AccountReadHook is called when the account is accessed.
|
||||||
|
AccountReadHook = func(addr common.Address)
|
||||||
|
|
||||||
|
// StorageReadHook is called when the storage slot is accessed.
|
||||||
|
StorageReadHook = func(addr common.Address, slot common.Hash)
|
||||||
|
|
||||||
// BlockHashReadHook is called when EVM reads the blockhash of a block.
|
// BlockHashReadHook is called when EVM reads the blockhash of a block.
|
||||||
BlockHashReadHook = func(blockNumber uint64, hash common.Hash)
|
BlockHashReadHook = func(blockNumber uint64, hash common.Hash)
|
||||||
)
|
)
|
||||||
|
|
@ -199,6 +207,7 @@ type Hooks struct {
|
||||||
OnOpcode OpcodeHook
|
OnOpcode OpcodeHook
|
||||||
OnFault FaultHook
|
OnFault FaultHook
|
||||||
OnGasChange GasChangeHook
|
OnGasChange GasChangeHook
|
||||||
|
|
||||||
// Chain events
|
// Chain events
|
||||||
OnBlockchainInit BlockchainInitHook
|
OnBlockchainInit BlockchainInitHook
|
||||||
OnClose CloseHook
|
OnClose CloseHook
|
||||||
|
|
@ -209,7 +218,11 @@ type Hooks struct {
|
||||||
OnSystemCallStart OnSystemCallStartHook
|
OnSystemCallStart OnSystemCallStartHook
|
||||||
OnSystemCallStartV2 OnSystemCallStartHookV2
|
OnSystemCallStartV2 OnSystemCallStartHookV2
|
||||||
OnSystemCallEnd OnSystemCallEndHook
|
OnSystemCallEnd OnSystemCallEndHook
|
||||||
// State events
|
|
||||||
|
OnPreTxExecutionDone func() // called after pre-tx system contracts are invoked
|
||||||
|
OnBlockFinalization func() // called after post-tx system contracts and consensus finalization are invoked
|
||||||
|
|
||||||
|
// State mutation events
|
||||||
OnBalanceChange BalanceChangeHook
|
OnBalanceChange BalanceChangeHook
|
||||||
OnNonceChange NonceChangeHook
|
OnNonceChange NonceChangeHook
|
||||||
OnNonceChangeV2 NonceChangeHookV2
|
OnNonceChangeV2 NonceChangeHookV2
|
||||||
|
|
@ -217,6 +230,12 @@ type Hooks struct {
|
||||||
OnCodeChangeV2 CodeChangeHookV2
|
OnCodeChangeV2 CodeChangeHookV2
|
||||||
OnStorageChange StorageChangeHook
|
OnStorageChange StorageChangeHook
|
||||||
OnLog LogHook
|
OnLog LogHook
|
||||||
|
OnSelfDestructChange SelfDestructHook
|
||||||
|
|
||||||
|
// State access events
|
||||||
|
OnAccountRead AccountReadHook
|
||||||
|
OnStorageRead StorageReadHook
|
||||||
|
|
||||||
// Block hash read
|
// Block hash read
|
||||||
OnBlockHashRead BlockHashReadHook
|
OnBlockHashRead BlockHashReadHook
|
||||||
}
|
}
|
||||||
|
|
@ -233,57 +252,74 @@ const (
|
||||||
// Issuance
|
// Issuance
|
||||||
// BalanceIncreaseRewardMineUncle is a reward for mining an uncle block.
|
// BalanceIncreaseRewardMineUncle is a reward for mining an uncle block.
|
||||||
BalanceIncreaseRewardMineUncle BalanceChangeReason = 1
|
BalanceIncreaseRewardMineUncle BalanceChangeReason = 1
|
||||||
|
|
||||||
// BalanceIncreaseRewardMineBlock is a reward for mining a block.
|
// BalanceIncreaseRewardMineBlock is a reward for mining a block.
|
||||||
BalanceIncreaseRewardMineBlock BalanceChangeReason = 2
|
BalanceIncreaseRewardMineBlock BalanceChangeReason = 2
|
||||||
|
|
||||||
// BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain.
|
// BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain.
|
||||||
BalanceIncreaseWithdrawal BalanceChangeReason = 3
|
BalanceIncreaseWithdrawal BalanceChangeReason = 3
|
||||||
|
|
||||||
// BalanceIncreaseGenesisBalance is ether allocated at the genesis block.
|
// BalanceIncreaseGenesisBalance is ether allocated at the genesis block.
|
||||||
BalanceIncreaseGenesisBalance BalanceChangeReason = 4
|
BalanceIncreaseGenesisBalance BalanceChangeReason = 4
|
||||||
|
|
||||||
// Transaction fees
|
// Transaction fees
|
||||||
// BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance.
|
// BalanceIncreaseRewardTransactionFee is the transaction tip increasing
|
||||||
|
// block builder's balance.
|
||||||
BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5
|
BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5
|
||||||
|
|
||||||
// BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction.
|
// BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction.
|
||||||
// Part of this gas will be burnt as per EIP-1559 rules.
|
// Part of this gas will be burnt as per EIP-1559 rules.
|
||||||
BalanceDecreaseGasBuy BalanceChangeReason = 6
|
BalanceDecreaseGasBuy BalanceChangeReason = 6
|
||||||
|
|
||||||
// BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution.
|
// BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution.
|
||||||
BalanceIncreaseGasReturn BalanceChangeReason = 7
|
BalanceIncreaseGasReturn BalanceChangeReason = 7
|
||||||
|
|
||||||
// DAO fork
|
// DAO fork
|
||||||
// BalanceIncreaseDaoContract is ether sent to the DAO refund contract.
|
// BalanceIncreaseDaoContract is ether sent to the DAO refund contract.
|
||||||
BalanceIncreaseDaoContract BalanceChangeReason = 8
|
BalanceIncreaseDaoContract BalanceChangeReason = 8
|
||||||
// BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract.
|
|
||||||
|
// BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved
|
||||||
|
// to the refund contract.
|
||||||
BalanceDecreaseDaoAccount BalanceChangeReason = 9
|
BalanceDecreaseDaoAccount BalanceChangeReason = 9
|
||||||
|
|
||||||
// BalanceChangeTransfer is ether transferred via a call.
|
// BalanceChangeTransfer is ether transferred via a call.
|
||||||
// it is a decrease for the sender and an increase for the recipient.
|
// it is a decrease for the sender and an increase for the recipient.
|
||||||
BalanceChangeTransfer BalanceChangeReason = 10
|
BalanceChangeTransfer BalanceChangeReason = 10
|
||||||
|
|
||||||
// BalanceChangeTouchAccount is a transfer of zero value. It is only there to
|
// BalanceChangeTouchAccount is a transfer of zero value. It is only there to
|
||||||
// touch-create an account.
|
// touch-create an account.
|
||||||
BalanceChangeTouchAccount BalanceChangeReason = 11
|
BalanceChangeTouchAccount BalanceChangeReason = 11
|
||||||
|
|
||||||
// BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account.
|
// BalanceIncreaseSelfdestruct is added to the recipient as indicated by a
|
||||||
|
// selfdestructing account.
|
||||||
BalanceIncreaseSelfdestruct BalanceChangeReason = 12
|
BalanceIncreaseSelfdestruct BalanceChangeReason = 12
|
||||||
|
|
||||||
// BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct.
|
// BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct.
|
||||||
BalanceDecreaseSelfdestruct BalanceChangeReason = 13
|
BalanceDecreaseSelfdestruct BalanceChangeReason = 13
|
||||||
|
|
||||||
// BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed
|
// BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed
|
||||||
// account within the same tx (captured at end of tx).
|
// account within the same tx (captured at end of tx).
|
||||||
// Note it doesn't account for a self-destruct which appoints itself as recipient.
|
// Note it doesn't account for a self-destruct which appoints itself as recipient.
|
||||||
BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14
|
BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14
|
||||||
|
|
||||||
// BalanceChangeRevert is emitted when the balance is reverted back to a previous value due to call failure.
|
// BalanceChangeRevert is emitted when the balance is reverted back to a
|
||||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
// previous value due to call failure.
|
||||||
|
//
|
||||||
|
// It is only emitted when the tracer has opted in to use the journaling
|
||||||
|
// wrapper (WrapWithJournal).
|
||||||
BalanceChangeRevert BalanceChangeReason = 15
|
BalanceChangeRevert BalanceChangeReason = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
// GasChangeReason is used to indicate the reason for a gas change, useful
|
// GasChangeReason is used to indicate the reason for a gas change, useful
|
||||||
// for tracing and reporting.
|
// for tracing and reporting.
|
||||||
//
|
//
|
||||||
// There is essentially two types of gas changes, those that can be emitted once per transaction
|
// There is essentially two types of gas changes, those that can be emitted
|
||||||
// and those that can be emitted on a call basis, so possibly multiple times per transaction.
|
// once per transaction and those that can be emitted on a call basis, so possibly
|
||||||
|
// multiple times per transaction.
|
||||||
//
|
//
|
||||||
// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted
|
// They can be recognized easily by their name, those that start with `GasChangeTx`
|
||||||
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
|
// are emitted once per transaction, while those that start with `GasChangeCall`
|
||||||
|
// are emitted on a call basis.
|
||||||
type GasChangeReason byte
|
type GasChangeReason byte
|
||||||
|
|
||||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=GasChangeReason -trimprefix=GasChange -output gen_gas_change_reason_stringer.go
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=GasChangeReason -trimprefix=GasChange -output gen_gas_change_reason_stringer.go
|
||||||
|
|
@ -291,61 +327,100 @@ type GasChangeReason byte
|
||||||
const (
|
const (
|
||||||
GasChangeUnspecified GasChangeReason = 0
|
GasChangeUnspecified GasChangeReason = 0
|
||||||
|
|
||||||
// GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
|
// GasChangeTxInitialBalance is the initial balance for the call which will
|
||||||
// one such gas change per transaction.
|
// be equal to the gasLimit of the call. There is only one such gas change
|
||||||
|
// per transaction.
|
||||||
GasChangeTxInitialBalance GasChangeReason = 1
|
GasChangeTxInitialBalance GasChangeReason = 1
|
||||||
// GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is
|
|
||||||
// always exactly one of those per transaction.
|
// GasChangeTxIntrinsicGas is the amount of gas that will be charged for the
|
||||||
|
// intrinsic cost of the transaction, there is always exactly one of those
|
||||||
|
// per transaction.
|
||||||
GasChangeTxIntrinsicGas GasChangeReason = 2
|
GasChangeTxIntrinsicGas GasChangeReason = 2
|
||||||
// GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared)
|
|
||||||
// this generates an increase in gas. There is at most one of such gas change per transaction.
|
// GasChangeTxRefunds is the sum of all refunds which happened during the tx
|
||||||
|
// execution (e.g. storage slot being cleared). this generates an increase in
|
||||||
|
// gas. There is at most one of such gas change per transaction.
|
||||||
GasChangeTxRefunds GasChangeReason = 3
|
GasChangeTxRefunds GasChangeReason = 3
|
||||||
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned
|
|
||||||
// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
|
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of
|
||||||
// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller.
|
// transaction's execution that will be returned to the chain. This change
|
||||||
// There is at most one of such gas change per transaction.
|
// will always be a negative change as we "drain" left over gas towards 0.
|
||||||
|
// If there was no gas left at the end of execution, no such even will be
|
||||||
|
// emitted. The returned gas's value in Wei is returned to caller. There is
|
||||||
|
// at most one of such gas change per transaction.
|
||||||
GasChangeTxLeftOverReturned GasChangeReason = 4
|
GasChangeTxLeftOverReturned GasChangeReason = 4
|
||||||
|
|
||||||
// GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
|
// GasChangeCallInitialBalance is the initial balance for the call which
|
||||||
// one such gas change per call.
|
// will be equal to the gasLimit of the call. There is only one such gas
|
||||||
|
// change per call.
|
||||||
GasChangeCallInitialBalance GasChangeReason = 5
|
GasChangeCallInitialBalance GasChangeReason = 5
|
||||||
// GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always
|
|
||||||
// be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even
|
// GasChangeCallLeftOverReturned is the amount of gas left over that will
|
||||||
// will be emitted.
|
// be returned to the caller, this change will always be a negative change
|
||||||
|
// as we "drain" left over gas towards 0. If there was no gas left at the
|
||||||
|
// end of execution, no such even will be emitted.
|
||||||
GasChangeCallLeftOverReturned GasChangeReason = 6
|
GasChangeCallLeftOverReturned GasChangeReason = 6
|
||||||
// GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it
|
|
||||||
// executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child.
|
// GasChangeCallLeftOverRefunded is the amount of gas that will be refunded
|
||||||
// If there was no gas left to be refunded, no such even will be emitted.
|
// to the call after the child call execution it executed completed. This
|
||||||
|
// value is always positive as we are giving gas back to the you, the left over
|
||||||
|
// gas of the child. If there was no gas left to be refunded, no such event
|
||||||
|
// will be emitted.
|
||||||
GasChangeCallLeftOverRefunded GasChangeReason = 7
|
GasChangeCallLeftOverRefunded GasChangeReason = 7
|
||||||
// GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE.
|
|
||||||
|
// GasChangeCallContractCreation is the amount of gas that will be burned
|
||||||
|
// for a CREATE.
|
||||||
GasChangeCallContractCreation GasChangeReason = 8
|
GasChangeCallContractCreation GasChangeReason = 8
|
||||||
// GasChangeCallContractCreation2 is the amount of gas that will be burned for a CREATE2.
|
|
||||||
|
// GasChangeCallContractCreation2 is the amount of gas that will be burned
|
||||||
|
// for a CREATE2.
|
||||||
GasChangeCallContractCreation2 GasChangeReason = 9
|
GasChangeCallContractCreation2 GasChangeReason = 9
|
||||||
// GasChangeCallCodeStorage is the amount of gas that will be charged for code storage.
|
|
||||||
|
// GasChangeCallCodeStorage is the amount of gas that will be charged for
|
||||||
|
// code storage.
|
||||||
GasChangeCallCodeStorage GasChangeReason = 10
|
GasChangeCallCodeStorage GasChangeReason = 10
|
||||||
// GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was
|
|
||||||
// performed can be check by `OnOpcode` handling.
|
// GasChangeCallOpCode is the amount of gas that will be charged for an opcode
|
||||||
|
// executed by the EVM, exact opcode that was performed can be check by
|
||||||
|
// `OnOpcode` handling.
|
||||||
GasChangeCallOpCode GasChangeReason = 11
|
GasChangeCallOpCode GasChangeReason = 11
|
||||||
// GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution.
|
|
||||||
|
// GasChangeCallPrecompiledContract is the amount of gas that will be charged
|
||||||
|
// for a precompiled contract execution.
|
||||||
GasChangeCallPrecompiledContract GasChangeReason = 12
|
GasChangeCallPrecompiledContract GasChangeReason = 12
|
||||||
// GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules.
|
|
||||||
|
// GasChangeCallStorageColdAccess is the amount of gas that will be charged
|
||||||
|
// for a cold storage access as controlled by EIP2929 rules.
|
||||||
GasChangeCallStorageColdAccess GasChangeReason = 13
|
GasChangeCallStorageColdAccess GasChangeReason = 13
|
||||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
|
||||||
|
// GasChangeCallFailedExecution is the burning of the remaining gas when the
|
||||||
|
// execution failed without a revert.
|
||||||
GasChangeCallFailedExecution GasChangeReason = 14
|
GasChangeCallFailedExecution GasChangeReason = 14
|
||||||
// GasChangeWitnessContractInit flags the event of adding to the witness during the contract creation initialization step.
|
|
||||||
|
// GasChangeWitnessContractInit flags the event of adding to the witness
|
||||||
|
// during the contract creation initialization step.
|
||||||
GasChangeWitnessContractInit GasChangeReason = 15
|
GasChangeWitnessContractInit GasChangeReason = 15
|
||||||
// GasChangeWitnessContractCreation flags the event of adding to the witness during the contract creation finalization step.
|
|
||||||
|
// GasChangeWitnessContractCreation flags the event of adding to the witness
|
||||||
|
// during the contract creation finalization step.
|
||||||
GasChangeWitnessContractCreation GasChangeReason = 16
|
GasChangeWitnessContractCreation GasChangeReason = 16
|
||||||
// GasChangeWitnessCodeChunk flags the event of adding one or more contract code chunks to the witness.
|
|
||||||
|
// GasChangeWitnessCodeChunk flags the event of adding one or more contract
|
||||||
|
// code chunks to the witness.
|
||||||
GasChangeWitnessCodeChunk GasChangeReason = 17
|
GasChangeWitnessCodeChunk GasChangeReason = 17
|
||||||
// GasChangeWitnessContractCollisionCheck flags the event of adding to the witness when checking for contract address collision.
|
|
||||||
|
// GasChangeWitnessContractCollisionCheck flags the event of adding to the
|
||||||
|
// witness when checking for contract address collision.
|
||||||
GasChangeWitnessContractCollisionCheck GasChangeReason = 18
|
GasChangeWitnessContractCollisionCheck GasChangeReason = 18
|
||||||
// GasChangeTxDataFloor is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the
|
|
||||||
// transaction data. This change will always be a negative change.
|
// GasChangeTxDataFloor is the amount of extra gas the transaction has to
|
||||||
|
// pay to reach the minimum gas requirement for the transaction data.
|
||||||
|
// This change will always be a negative change.
|
||||||
GasChangeTxDataFloor GasChangeReason = 19
|
GasChangeTxDataFloor GasChangeReason = 19
|
||||||
|
|
||||||
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
// GasChangeIgnored is a special value that can be used to indicate that
|
||||||
// it will be "manually" tracked by a direct emit of the gas change event.
|
// the gas change should be ignored as it will be "manually" tracked by
|
||||||
|
// a direct emit of the gas change event.
|
||||||
GasChangeIgnored GasChangeReason = 0xFF
|
GasChangeIgnored GasChangeReason = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -369,12 +444,16 @@ const (
|
||||||
// NonceChangeNewContract is the nonce change of a newly created contract.
|
// NonceChangeNewContract is the nonce change of a newly created contract.
|
||||||
NonceChangeNewContract NonceChangeReason = 4
|
NonceChangeNewContract NonceChangeReason = 4
|
||||||
|
|
||||||
// NonceChangeTransaction is the nonce change due to a EIP-7702 authorization.
|
// NonceChangeAuthorization is the nonce change due to a EIP-7702 authorization.
|
||||||
NonceChangeAuthorization NonceChangeReason = 5
|
NonceChangeAuthorization NonceChangeReason = 5
|
||||||
|
|
||||||
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
// NonceChangeRevert is emitted when the nonce is reverted back to a previous
|
||||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
// value due to call failure. It is only emitted when the tracer has opted in
|
||||||
|
// to use the journaling wrapper (WrapWithJournal).
|
||||||
NonceChangeRevert NonceChangeReason = 6
|
NonceChangeRevert NonceChangeReason = 6
|
||||||
|
|
||||||
|
// NonceChangeSelfdestruct is emitted when the nonce is reset to zero due to a self-destruct
|
||||||
|
NonceChangeSelfdestruct NonceChangeReason = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
// CodeChangeReason is used to indicate the reason for a code change.
|
// CodeChangeReason is used to indicate the reason for a code change.
|
||||||
|
|
@ -385,22 +464,26 @@ type CodeChangeReason byte
|
||||||
const (
|
const (
|
||||||
CodeChangeUnspecified CodeChangeReason = 0
|
CodeChangeUnspecified CodeChangeReason = 0
|
||||||
|
|
||||||
// CodeChangeContractCreation is when a new contract is deployed via CREATE/CREATE2 operations.
|
// CodeChangeContractCreation is when a new contract is deployed via
|
||||||
|
// CREATE/CREATE2 operations.
|
||||||
CodeChangeContractCreation CodeChangeReason = 1
|
CodeChangeContractCreation CodeChangeReason = 1
|
||||||
|
|
||||||
// CodeChangeGenesis is when contract code is set during blockchain genesis or initial setup.
|
// CodeChangeGenesis is when contract code is set during blockchain genesis
|
||||||
|
// or initial setup.
|
||||||
CodeChangeGenesis CodeChangeReason = 2
|
CodeChangeGenesis CodeChangeReason = 2
|
||||||
|
|
||||||
// CodeChangeAuthorization is when code is set via EIP-7702 Set Code Authorization.
|
// CodeChangeAuthorization is when code is set via EIP-7702 Set Code Authorization.
|
||||||
CodeChangeAuthorization CodeChangeReason = 3
|
CodeChangeAuthorization CodeChangeReason = 3
|
||||||
|
|
||||||
// CodeChangeAuthorizationClear is when EIP-7702 delegation is cleared by setting to zero address.
|
// CodeChangeAuthorizationClear is when EIP-7702 delegation is cleared by
|
||||||
|
// setting to zero address.
|
||||||
CodeChangeAuthorizationClear CodeChangeReason = 4
|
CodeChangeAuthorizationClear CodeChangeReason = 4
|
||||||
|
|
||||||
// CodeChangeSelfDestruct is when contract code is cleared due to self-destruct.
|
// CodeChangeSelfDestruct is when contract code is cleared due to self-destruct.
|
||||||
CodeChangeSelfDestruct CodeChangeReason = 5
|
CodeChangeSelfDestruct CodeChangeReason = 5
|
||||||
|
|
||||||
// CodeChangeRevert is emitted when the code is reverted back to a previous value due to call failure.
|
// CodeChangeRevert is emitted when the code is reverted back to a previous
|
||||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
// value due to call failure. It is only emitted when the tracer has opted
|
||||||
|
// in to use the journaling wrapper (WrapWithJournal).
|
||||||
CodeChangeRevert CodeChangeReason = 6
|
CodeChangeRevert CodeChangeReason = 6
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ func (t *testTracer) OnCodeChangeV2(addr common.Address, prevCodeHash common.Has
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) {
|
func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) {
|
||||||
t.t.Logf("OnStorageCodeChange(%v, %v, %v -> %v)", addr, slot, prev, new)
|
t.t.Logf("OnStorageChange(%v, %v, %v -> %v)", addr, slot, prev, new)
|
||||||
if t.storage == nil {
|
if t.storage == nil {
|
||||||
t.storage = make(map[common.Hash]common.Hash)
|
t.storage = make(map[common.Hash]common.Hash)
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,12 @@ func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev
|
||||||
|
|
||||||
func TestJournalIntegration(t *testing.T) {
|
func TestJournalIntegration(t *testing.T) {
|
||||||
tr := &testTracer{t: t}
|
tr := &testTracer{t: t}
|
||||||
wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange, OnCodeChange: tr.OnCodeChange, OnStorageChange: tr.OnStorageChange})
|
wr, err := WrapWithJournal(&Hooks{
|
||||||
|
OnBalanceChange: tr.OnBalanceChange,
|
||||||
|
OnNonceChange: tr.OnNonceChange,
|
||||||
|
OnCodeChange: tr.OnCodeChange,
|
||||||
|
OnStorageChange: tr.OnStorageChange,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to wrap test tracer: %v", err)
|
t.Fatalf("failed to wrap test tracer: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,143 +18,492 @@ package bal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"maps"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// idxAccessListBuilder is responsible for producing the state accesses and
|
||||||
|
// reads recorded within the scope of a single index in the access list.
|
||||||
|
type idxAccessListBuilder struct {
|
||||||
|
// stores the previous values of any account data that was modified in the
|
||||||
|
// current index.
|
||||||
|
prestates map[common.Address]*accountIdxPrestate
|
||||||
|
|
||||||
|
// a stack which maintains a set of state mutations/reads for each EVM
|
||||||
|
// execution frame. Entering a frame appends an intermediate access list
|
||||||
|
// and terminating a frame merges the accesses/modifications into the
|
||||||
|
// intermediate access list of the calling frame.
|
||||||
|
accessesStack []map[common.Address]*constructionAccountAccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAccessListBuilder() *idxAccessListBuilder {
|
||||||
|
return &idxAccessListBuilder{
|
||||||
|
make(map[common.Address]*accountIdxPrestate),
|
||||||
|
[]map[common.Address]*constructionAccountAccess{
|
||||||
|
make(map[common.Address]*constructionAccountAccess),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idxAccessListBuilder) storageRead(address common.Address, key common.Hash) {
|
||||||
|
if _, ok := c.accessesStack[len(c.accessesStack)-1][address]; !ok {
|
||||||
|
c.accessesStack[len(c.accessesStack)-1][address] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
acctAccesses := c.accessesStack[len(c.accessesStack)-1][address]
|
||||||
|
acctAccesses.StorageRead(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idxAccessListBuilder) accountRead(address common.Address) {
|
||||||
|
if _, ok := c.accessesStack[len(c.accessesStack)-1][address]; !ok {
|
||||||
|
c.accessesStack[len(c.accessesStack)-1][address] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idxAccessListBuilder) storageWrite(address common.Address, key, prevVal, newVal common.Hash) {
|
||||||
|
if _, ok := c.prestates[address]; !ok {
|
||||||
|
c.prestates[address] = &accountIdxPrestate{}
|
||||||
|
}
|
||||||
|
if c.prestates[address].storage == nil {
|
||||||
|
c.prestates[address].storage = make(map[common.Hash]common.Hash)
|
||||||
|
}
|
||||||
|
if _, ok := c.prestates[address].storage[key]; !ok {
|
||||||
|
c.prestates[address].storage[key] = prevVal
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.accessesStack[len(c.accessesStack)-1][address]; !ok {
|
||||||
|
c.accessesStack[len(c.accessesStack)-1][address] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
acctAccesses := c.accessesStack[len(c.accessesStack)-1][address]
|
||||||
|
acctAccesses.StorageWrite(key, prevVal, newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idxAccessListBuilder) balanceChange(address common.Address, prev, cur *uint256.Int) {
|
||||||
|
if _, ok := c.prestates[address]; !ok {
|
||||||
|
c.prestates[address] = &accountIdxPrestate{}
|
||||||
|
}
|
||||||
|
if c.prestates[address].balance == nil {
|
||||||
|
c.prestates[address].balance = prev
|
||||||
|
}
|
||||||
|
if _, ok := c.accessesStack[len(c.accessesStack)-1][address]; !ok {
|
||||||
|
c.accessesStack[len(c.accessesStack)-1][address] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
acctAccesses := c.accessesStack[len(c.accessesStack)-1][address]
|
||||||
|
acctAccesses.BalanceChange(cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idxAccessListBuilder) codeChange(address common.Address, prev, cur []byte) {
|
||||||
|
// auth unset and selfdestruct pass code change as 'nil'
|
||||||
|
// however, internally in the access list accumulation of state changes,
|
||||||
|
// a nil field on an account means that it was never modified in the block.
|
||||||
|
if cur == nil {
|
||||||
|
cur = []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.prestates[address]; !ok {
|
||||||
|
c.prestates[address] = &accountIdxPrestate{}
|
||||||
|
}
|
||||||
|
if c.prestates[address].code == nil {
|
||||||
|
c.prestates[address].code = prev
|
||||||
|
}
|
||||||
|
if _, ok := c.accessesStack[len(c.accessesStack)-1][address]; !ok {
|
||||||
|
c.accessesStack[len(c.accessesStack)-1][address] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
acctAccesses := c.accessesStack[len(c.accessesStack)-1][address]
|
||||||
|
|
||||||
|
acctAccesses.CodeChange(cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
// selfDestruct is invoked when an account which has been created and invoked
|
||||||
|
// SENDALL in the same transaction is removed as part of transaction finalization.
|
||||||
|
//
|
||||||
|
// Any storage accesses/modifications performed at the contract during execution
|
||||||
|
// are retained in the block access list as state reads.
|
||||||
|
func (c *idxAccessListBuilder) selfDestruct(address common.Address) {
|
||||||
|
// convert all the account storage writes to reads, preserve the existing reads
|
||||||
|
access := c.accessesStack[len(c.accessesStack)-1][address]
|
||||||
|
for key, _ := range access.storageMutations {
|
||||||
|
if access.storageReads == nil {
|
||||||
|
access.storageReads = make(map[common.Hash]struct{})
|
||||||
|
}
|
||||||
|
access.storageReads[key] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
access.storageMutations = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *idxAccessListBuilder) nonceChange(address common.Address, prev, cur uint64) {
|
||||||
|
if _, ok := c.prestates[address]; !ok {
|
||||||
|
c.prestates[address] = &accountIdxPrestate{}
|
||||||
|
}
|
||||||
|
if c.prestates[address].nonce == nil {
|
||||||
|
c.prestates[address].nonce = &prev
|
||||||
|
}
|
||||||
|
if _, ok := c.accessesStack[len(c.accessesStack)-1][address]; !ok {
|
||||||
|
c.accessesStack[len(c.accessesStack)-1][address] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
acctAccesses := c.accessesStack[len(c.accessesStack)-1][address]
|
||||||
|
acctAccesses.NonceChange(cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enterScope is called after a new EVM frame has been entered.
|
||||||
|
func (c *idxAccessListBuilder) enterScope() {
|
||||||
|
c.accessesStack = append(c.accessesStack, make(map[common.Address]*constructionAccountAccess))
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitScope is called after an EVM call scope terminates. If the call scope
|
||||||
|
// terminates with an error:
|
||||||
|
// * the scope's state accesses are added to the calling scope's access list
|
||||||
|
// * mutated accounts/storage are added into the calling scope's access list as state accesses
|
||||||
|
// * the state mutations tracked in the parent scope are un-modified
|
||||||
|
func (c *idxAccessListBuilder) exitScope(evmErr bool) {
|
||||||
|
// all storage writes in the child scope are converted into reads
|
||||||
|
// if there were no storage writes, the account is reported in the BAL as a read (if it wasn't already in the BAL and/or mutated previously)
|
||||||
|
childAccessList := c.accessesStack[len(c.accessesStack)-1]
|
||||||
|
parentAccessList := c.accessesStack[len(c.accessesStack)-2]
|
||||||
|
|
||||||
|
for addr, childAccess := range childAccessList {
|
||||||
|
if _, ok := parentAccessList[addr]; ok {
|
||||||
|
} else {
|
||||||
|
parentAccessList[addr] = &constructionAccountAccess{}
|
||||||
|
}
|
||||||
|
if evmErr {
|
||||||
|
parentAccessList[addr].MergeReads(childAccess)
|
||||||
|
} else {
|
||||||
|
parentAccessList[addr].Merge(childAccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.accessesStack = c.accessesStack[:len(c.accessesStack)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalise returns the net state mutations at the access list index as well as
|
||||||
|
// state which was accessed. The idxAccessListBuilder instance should be discarded
|
||||||
|
// after calling finalise.
|
||||||
|
func (a *idxAccessListBuilder) finalise() (*StateDiff, StateAccesses) {
|
||||||
|
diff := &StateDiff{make(map[common.Address]*AccountMutations)}
|
||||||
|
stateAccesses := make(StateAccesses)
|
||||||
|
|
||||||
|
for addr, access := range a.accessesStack[0] {
|
||||||
|
// remove any mutations from the access list with no net difference vs the tx prestate value
|
||||||
|
if access.nonce != nil && *a.prestates[addr].nonce == *access.nonce {
|
||||||
|
access.nonce = nil
|
||||||
|
}
|
||||||
|
if access.balance != nil && a.prestates[addr].balance.Eq(access.balance) {
|
||||||
|
access.balance = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if access.code != nil && bytes.Equal(access.code, a.prestates[addr].code) {
|
||||||
|
access.code = nil
|
||||||
|
}
|
||||||
|
if access.storageMutations != nil {
|
||||||
|
for key, val := range access.storageMutations {
|
||||||
|
if a.prestates[addr].storage[key] == val {
|
||||||
|
delete(access.storageMutations, key)
|
||||||
|
access.storageReads[key] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(access.storageMutations) == 0 {
|
||||||
|
access.storageMutations = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the account has no net mutations against the tx prestate, only include
|
||||||
|
// it in the state read set
|
||||||
|
if len(access.code) == 0 && access.nonce == nil && access.balance == nil && len(access.storageMutations) == 0 {
|
||||||
|
stateAccesses[addr] = make(map[common.Hash]struct{})
|
||||||
|
if access.storageReads != nil {
|
||||||
|
stateAccesses[addr] = access.storageReads
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stateAccesses[addr] = access.storageReads
|
||||||
|
diff.Mutations[addr] = &AccountMutations{
|
||||||
|
Balance: access.balance,
|
||||||
|
Nonce: access.nonce,
|
||||||
|
Code: access.code,
|
||||||
|
StorageWrites: access.storageMutations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff, stateAccesses
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinaliseIdxChanges records all pending state mutations/accesses in the
|
||||||
|
// access list at the given index. The set of pending state mutations/accesse are
|
||||||
|
// then emptied.
|
||||||
|
func (c *AccessListBuilder) FinaliseIdxChanges(idx uint16) {
|
||||||
|
pendingDiff, pendingAccesses := c.idxBuilder.finalise()
|
||||||
|
c.idxBuilder = newAccessListBuilder()
|
||||||
|
|
||||||
|
// if any of the newly-written storage slots were previously
|
||||||
|
// accessed, they must be removed from the accessed state set.
|
||||||
|
for addr, pendingAcctDiff := range pendingDiff.Mutations {
|
||||||
|
finalizedAcctChanges, ok := c.FinalizedAccesses[addr]
|
||||||
|
if !ok {
|
||||||
|
finalizedAcctChanges = &ConstructionAccountAccesses{}
|
||||||
|
c.FinalizedAccesses[addr] = finalizedAcctChanges
|
||||||
|
}
|
||||||
|
|
||||||
|
if pendingAcctDiff.Nonce != nil {
|
||||||
|
if finalizedAcctChanges.NonceChanges == nil {
|
||||||
|
finalizedAcctChanges.NonceChanges = make(map[uint16]uint64)
|
||||||
|
}
|
||||||
|
finalizedAcctChanges.NonceChanges[idx] = *pendingAcctDiff.Nonce
|
||||||
|
}
|
||||||
|
if pendingAcctDiff.Balance != nil {
|
||||||
|
if finalizedAcctChanges.BalanceChanges == nil {
|
||||||
|
finalizedAcctChanges.BalanceChanges = make(map[uint16]*uint256.Int)
|
||||||
|
}
|
||||||
|
finalizedAcctChanges.BalanceChanges[idx] = pendingAcctDiff.Balance
|
||||||
|
}
|
||||||
|
if pendingAcctDiff.Code != nil {
|
||||||
|
if finalizedAcctChanges.CodeChanges == nil {
|
||||||
|
finalizedAcctChanges.CodeChanges = make(map[uint16]CodeChange)
|
||||||
|
}
|
||||||
|
finalizedAcctChanges.CodeChanges[idx] = CodeChange{idx, pendingAcctDiff.Code}
|
||||||
|
}
|
||||||
|
if pendingAcctDiff.StorageWrites != nil {
|
||||||
|
if finalizedAcctChanges.StorageWrites == nil {
|
||||||
|
finalizedAcctChanges.StorageWrites = make(map[common.Hash]map[uint16]common.Hash)
|
||||||
|
}
|
||||||
|
for key, val := range pendingAcctDiff.StorageWrites {
|
||||||
|
if _, ok := finalizedAcctChanges.StorageWrites[key]; !ok {
|
||||||
|
finalizedAcctChanges.StorageWrites[key] = make(map[uint16]common.Hash)
|
||||||
|
}
|
||||||
|
finalizedAcctChanges.StorageWrites[key][idx] = val
|
||||||
|
|
||||||
|
// TODO: investigate why commenting out the check here, and the corresponding
|
||||||
|
// check under accesses causes GeneralStateTests blockchain tests to fail.
|
||||||
|
// They should only contain one tx per test.
|
||||||
|
//
|
||||||
|
// key could have been read in a previous tx, delete it from the read set here
|
||||||
|
if _, ok := finalizedAcctChanges.StorageReads[key]; ok {
|
||||||
|
delete(finalizedAcctChanges.StorageReads, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// record pending accesses in the BAL access set unless they were
|
||||||
|
// already written in a previous index
|
||||||
|
for addr, pendingAccountAccesses := range pendingAccesses {
|
||||||
|
finalizedAcctAccesses, ok := c.FinalizedAccesses[addr]
|
||||||
|
if !ok {
|
||||||
|
finalizedAcctAccesses = &ConstructionAccountAccesses{}
|
||||||
|
c.FinalizedAccesses[addr] = finalizedAcctAccesses
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range pendingAccountAccesses {
|
||||||
|
if _, ok := finalizedAcctAccesses.StorageWrites[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if finalizedAcctAccesses.StorageReads == nil {
|
||||||
|
finalizedAcctAccesses.StorageReads = make(map[common.Hash]struct{})
|
||||||
|
}
|
||||||
|
finalizedAcctAccesses.StorageReads[key] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.lastFinalizedMutations = pendingDiff
|
||||||
|
c.lastFinalizedAccesses = pendingAccesses
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessListBuilder) StorageRead(address common.Address, key common.Hash) {
|
||||||
|
c.idxBuilder.storageRead(address, key)
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) AccountRead(address common.Address) {
|
||||||
|
c.idxBuilder.accountRead(address)
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) StorageWrite(address common.Address, key, prevVal, newVal common.Hash) {
|
||||||
|
c.idxBuilder.storageWrite(address, key, prevVal, newVal)
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) BalanceChange(address common.Address, prev, cur *uint256.Int) {
|
||||||
|
c.idxBuilder.balanceChange(address, prev, cur)
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) NonceChange(address common.Address, prev, cur uint64) {
|
||||||
|
c.idxBuilder.nonceChange(address, prev, cur)
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) CodeChange(address common.Address, prev, cur []byte) {
|
||||||
|
c.idxBuilder.codeChange(address, prev, cur)
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) SelfDestruct(address common.Address) {
|
||||||
|
c.idxBuilder.selfDestruct(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessListBuilder) EnterScope() {
|
||||||
|
c.idxBuilder.enterScope()
|
||||||
|
}
|
||||||
|
func (c *AccessListBuilder) ExitScope(executionErr bool) {
|
||||||
|
c.idxBuilder.exitScope(executionErr)
|
||||||
|
}
|
||||||
|
|
||||||
// CodeChange contains the runtime bytecode deployed at an address and the
|
// CodeChange contains the runtime bytecode deployed at an address and the
|
||||||
// transaction index where the deployment took place.
|
// transaction index where the deployment took place.
|
||||||
type CodeChange struct {
|
type CodeChange struct {
|
||||||
TxIndex uint16
|
TxIdx uint16
|
||||||
Code []byte `json:"code,omitempty"`
|
Code []byte `json:"code,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConstructionAccountAccess contains post-block account state for mutations as well as
|
// ConstructionAccountAccesses contains post-block account state for mutations as well as
|
||||||
// all storage keys that were read during execution. It is used when building block
|
// all storage keys that were read during execution. It is used when building block
|
||||||
// access list during execution.
|
// access list during execution.
|
||||||
type ConstructionAccountAccess struct {
|
type ConstructionAccountAccesses struct {
|
||||||
// StorageWrites is the post-state values of an account's storage slots
|
// StorageWrites is the post-state values of an account's storage slots
|
||||||
// that were modified in a block, keyed by the slot key and the tx index
|
// that were modified in a block, keyed by the slot key and the tx index
|
||||||
// where the modification occurred.
|
// where the modification occurred.
|
||||||
StorageWrites map[common.Hash]map[uint16]common.Hash `json:"storageWrites,omitempty"`
|
StorageWrites map[common.Hash]map[uint16]common.Hash
|
||||||
|
|
||||||
// StorageReads is the set of slot keys that were accessed during block
|
// StorageReads is the set of slot keys that were accessed during block
|
||||||
// execution.
|
// execution.
|
||||||
//
|
//
|
||||||
// Storage slots which are both read and written (with changed values)
|
// Storage slots which are both read and written (with changed values)
|
||||||
// appear only in StorageWrites.
|
// appear only in StorageWrites.
|
||||||
StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"`
|
StorageReads map[common.Hash]struct{}
|
||||||
|
|
||||||
// BalanceChanges contains the post-transaction balances of an account,
|
// BalanceChanges contains the post-transaction balances of an account,
|
||||||
// keyed by transaction indices where it was changed.
|
// keyed by transaction indices where it was changed.
|
||||||
BalanceChanges map[uint16]*uint256.Int `json:"balanceChanges,omitempty"`
|
BalanceChanges map[uint16]*uint256.Int
|
||||||
|
|
||||||
// NonceChanges contains the post-state nonce values of an account keyed
|
// NonceChanges contains the post-state nonce values of an account keyed
|
||||||
// by tx index.
|
// by tx index.
|
||||||
NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"`
|
NonceChanges map[uint16]uint64
|
||||||
|
|
||||||
// CodeChange is only set for contract accounts which were deployed in
|
CodeChanges map[uint16]CodeChange
|
||||||
// the block.
|
|
||||||
CodeChange *CodeChange `json:"codeChange,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConstructionAccountAccess initializes the account access object.
|
// constructionAccountAccess contains fields for an account which were modified
|
||||||
func NewConstructionAccountAccess() *ConstructionAccountAccess {
|
// during execution of the current access list index.
|
||||||
return &ConstructionAccountAccess{
|
// It also accumulates a set of storage slots which were accessed but not
|
||||||
StorageWrites: make(map[common.Hash]map[uint16]common.Hash),
|
// modified.
|
||||||
StorageReads: make(map[common.Hash]struct{}),
|
type constructionAccountAccess struct {
|
||||||
BalanceChanges: make(map[uint16]*uint256.Int),
|
code []byte
|
||||||
NonceChanges: make(map[uint16]uint64),
|
nonce *uint64
|
||||||
|
balance *uint256.Int
|
||||||
|
|
||||||
|
storageMutations map[common.Hash]common.Hash
|
||||||
|
storageReads map[common.Hash]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge adds the accesses/mutations from other into the calling instance. If
|
||||||
|
func (c *constructionAccountAccess) Merge(other *constructionAccountAccess) {
|
||||||
|
if other.code != nil {
|
||||||
|
c.code = other.code
|
||||||
|
}
|
||||||
|
if other.nonce != nil {
|
||||||
|
c.nonce = other.nonce
|
||||||
|
}
|
||||||
|
if other.balance != nil {
|
||||||
|
c.balance = other.balance
|
||||||
|
}
|
||||||
|
if other.storageMutations != nil {
|
||||||
|
if c.storageMutations == nil {
|
||||||
|
c.storageMutations = make(map[common.Hash]common.Hash)
|
||||||
|
}
|
||||||
|
for key, val := range other.storageMutations {
|
||||||
|
c.storageMutations[key] = val
|
||||||
|
delete(c.storageReads, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if other.storageReads != nil {
|
||||||
|
if c.storageReads == nil {
|
||||||
|
c.storageReads = make(map[common.Hash]struct{})
|
||||||
|
}
|
||||||
|
// TODO: if the state was mutated in the caller, don't add it to the caller's reads.
|
||||||
|
// need to have a test case for this, verify it fails in the current state, and then fix this bug.
|
||||||
|
for key, val := range other.storageReads {
|
||||||
|
c.storageReads[key] = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConstructionBlockAccessList contains post-block modified state and some state accessed
|
// MergeReads merges accesses from a reverted execution from:
|
||||||
// in execution (account addresses and storage keys).
|
// * any reads/writes from the reverted frame which weren't mutated
|
||||||
type ConstructionBlockAccessList struct {
|
// in the current frame, are merged into the current frame as reads.
|
||||||
Accounts map[common.Address]*ConstructionAccountAccess
|
func (c *constructionAccountAccess) MergeReads(other *constructionAccountAccess) {
|
||||||
|
if other.storageMutations != nil {
|
||||||
|
if c.storageReads == nil {
|
||||||
|
c.storageReads = make(map[common.Hash]struct{})
|
||||||
|
}
|
||||||
|
for key, _ := range other.storageMutations {
|
||||||
|
if _, ok := c.storageMutations[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.storageReads[key] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if other.storageReads != nil {
|
||||||
|
if c.storageReads == nil {
|
||||||
|
c.storageReads = make(map[common.Hash]struct{})
|
||||||
|
}
|
||||||
|
for key := range other.storageReads {
|
||||||
|
if _, ok := c.storageMutations[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.storageReads[key] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConstructionBlockAccessList instantiates an empty access list.
|
|
||||||
func NewConstructionBlockAccessList() ConstructionBlockAccessList {
|
|
||||||
return ConstructionBlockAccessList{
|
|
||||||
Accounts: make(map[common.Address]*ConstructionAccountAccess),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountRead records the address of an account that has been read during execution.
|
func (c *constructionAccountAccess) StorageRead(key common.Hash) {
|
||||||
func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) {
|
if c.storageReads == nil {
|
||||||
if _, ok := b.Accounts[addr]; !ok {
|
c.storageReads = make(map[common.Hash]struct{})
|
||||||
b.Accounts[addr] = NewConstructionAccountAccess()
|
}
|
||||||
|
if _, ok := c.storageMutations[key]; !ok {
|
||||||
|
c.storageReads[key] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageRead records a storage key read during execution.
|
func (c *constructionAccountAccess) StorageWrite(key, prevVal, newVal common.Hash) {
|
||||||
func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) {
|
if c.storageMutations == nil {
|
||||||
if _, ok := b.Accounts[address]; !ok {
|
c.storageMutations = make(map[common.Hash]common.Hash)
|
||||||
b.Accounts[address] = NewConstructionAccountAccess()
|
|
||||||
}
|
}
|
||||||
if _, ok := b.Accounts[address].StorageWrites[key]; ok {
|
c.storageMutations[key] = newVal
|
||||||
return
|
// a key can be first read and later written, but it must only show up
|
||||||
}
|
// in either read or write sets, not both.
|
||||||
b.Accounts[address].StorageReads[key] = struct{}{}
|
//
|
||||||
|
// the caller should not
|
||||||
|
// call StorageRead on a slot that was already written
|
||||||
|
delete(c.storageReads, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageWrite records the post-transaction value of a mutated storage slot.
|
func (c *constructionAccountAccess) BalanceChange(cur *uint256.Int) {
|
||||||
// The storage slot is removed from the list of read slots.
|
c.balance = cur
|
||||||
func (b *ConstructionBlockAccessList) StorageWrite(txIdx uint16, address common.Address, key, value common.Hash) {
|
|
||||||
if _, ok := b.Accounts[address]; !ok {
|
|
||||||
b.Accounts[address] = NewConstructionAccountAccess()
|
|
||||||
}
|
|
||||||
if _, ok := b.Accounts[address].StorageWrites[key]; !ok {
|
|
||||||
b.Accounts[address].StorageWrites[key] = make(map[uint16]common.Hash)
|
|
||||||
}
|
|
||||||
b.Accounts[address].StorageWrites[key][txIdx] = value
|
|
||||||
|
|
||||||
delete(b.Accounts[address].StorageReads, key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeChange records the code of a newly-created contract.
|
func (c *constructionAccountAccess) CodeChange(cur []byte) {
|
||||||
func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) {
|
c.code = cur
|
||||||
if _, ok := b.Accounts[address]; !ok {
|
|
||||||
b.Accounts[address] = NewConstructionAccountAccess()
|
|
||||||
}
|
|
||||||
b.Accounts[address].CodeChange = &CodeChange{
|
|
||||||
TxIndex: txIndex,
|
|
||||||
Code: bytes.Clone(code),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NonceChange records tx post-state nonce of any contract-like accounts whose
|
func (c *constructionAccountAccess) NonceChange(cur uint64) {
|
||||||
// nonce was incremented.
|
c.nonce = &cur
|
||||||
func (b *ConstructionBlockAccessList) NonceChange(address common.Address, txIdx uint16, postNonce uint64) {
|
|
||||||
if _, ok := b.Accounts[address]; !ok {
|
|
||||||
b.Accounts[address] = NewConstructionAccountAccess()
|
|
||||||
}
|
|
||||||
b.Accounts[address].NonceChanges[txIdx] = postNonce
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceChange records the post-transaction balance of an account whose
|
// AccessListBuilder is used to build an EIP-7928 block access list
|
||||||
// balance changed.
|
type AccessListBuilder struct {
|
||||||
func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) {
|
FinalizedAccesses map[common.Address]*ConstructionAccountAccesses
|
||||||
if _, ok := b.Accounts[address]; !ok {
|
|
||||||
b.Accounts[address] = NewConstructionAccountAccess()
|
idxBuilder *idxAccessListBuilder
|
||||||
}
|
|
||||||
b.Accounts[address].BalanceChanges[txIdx] = balance.Clone()
|
lastFinalizedMutations *StateDiff
|
||||||
|
lastFinalizedAccesses StateAccesses
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrettyPrint returns a human-readable representation of the access list
|
// NewAccessListBuilder instantiates an empty access list.
|
||||||
func (b *ConstructionBlockAccessList) PrettyPrint() string {
|
func NewAccessListBuilder() *AccessListBuilder {
|
||||||
enc := b.toEncodingObj()
|
return &AccessListBuilder{
|
||||||
return enc.PrettyPrint()
|
make(map[common.Address]*ConstructionAccountAccesses),
|
||||||
|
newAccessListBuilder(),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a deep copy of the access list.
|
// Copy returns a deep copy of the access list.
|
||||||
func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
|
func (c *AccessListBuilder) Copy() *AccessListBuilder {
|
||||||
res := NewConstructionBlockAccessList()
|
res := NewAccessListBuilder()
|
||||||
for addr, aa := range b.Accounts {
|
for addr, aa := range c.FinalizedAccesses {
|
||||||
var aaCopy ConstructionAccountAccess
|
var aaCopy ConstructionAccountAccesses
|
||||||
|
|
||||||
slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites))
|
slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites))
|
||||||
for key, m := range aa.StorageWrites {
|
for key, m := range aa.StorageWrites {
|
||||||
|
|
@ -170,13 +519,187 @@ func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
|
||||||
aaCopy.BalanceChanges = balances
|
aaCopy.BalanceChanges = balances
|
||||||
aaCopy.NonceChanges = maps.Clone(aa.NonceChanges)
|
aaCopy.NonceChanges = maps.Clone(aa.NonceChanges)
|
||||||
|
|
||||||
if aa.CodeChange != nil {
|
codeChangesCopy := make(map[uint16]CodeChange)
|
||||||
aaCopy.CodeChange = &CodeChange{
|
for idx, codeChange := range aa.CodeChanges {
|
||||||
TxIndex: aa.CodeChange.TxIndex,
|
codeChangesCopy[idx] = CodeChange{
|
||||||
Code: bytes.Clone(aa.CodeChange.Code),
|
TxIdx: idx,
|
||||||
|
Code: bytes.Clone(codeChange.Code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.Accounts[addr] = &aaCopy
|
res.FinalizedAccesses[addr] = &aaCopy
|
||||||
}
|
}
|
||||||
return &res
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalizedIdxChanges returns the state mutations and accesses recorded in the latest
|
||||||
|
// access list index that was finalized.
|
||||||
|
func (c *AccessListBuilder) FinalizedIdxChanges() (*StateDiff, StateAccesses) {
|
||||||
|
return c.lastFinalizedMutations, c.lastFinalizedAccesses
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateDiff contains state mutations occuring over one or more access list
|
||||||
|
// index.
|
||||||
|
type StateDiff struct {
|
||||||
|
Mutations map[common.Address]*AccountMutations `json:"Mutations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateAccesses contains a set of accounts/storage that were accessed during the
|
||||||
|
// execution of one or more access list indices.
|
||||||
|
type StateAccesses map[common.Address]map[common.Hash]struct{}
|
||||||
|
|
||||||
|
// Merge combines adds the accesses from other into s.
|
||||||
|
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{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountIdxPrestate records the account prestate at a access list index
|
||||||
|
// for components which were modified at that index.
|
||||||
|
type accountIdxPrestate struct {
|
||||||
|
balance *uint256.Int
|
||||||
|
nonce *uint64
|
||||||
|
code ContractCode
|
||||||
|
storage map[common.Hash]common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountMutations contains mutations that were made to an account across
|
||||||
|
// one or more access list indices.
|
||||||
|
type AccountMutations struct {
|
||||||
|
Balance *uint256.Int `json:"Balance,omitempty"`
|
||||||
|
Nonce *uint64 `json:"Nonce,omitempty"`
|
||||||
|
Code ContractCode `json:"Code,omitempty"`
|
||||||
|
StorageWrites map[common.Hash]common.Hash `json:"StorageWrites,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a human-readable JSON representation of the account mutations.
|
||||||
|
func (a *AccountMutations) String() string {
|
||||||
|
var res bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&res)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
enc.Encode(a)
|
||||||
|
return res.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eq returns whether the calling instance is equal to the provided one.
|
||||||
|
func (a *AccountMutations) Eq(other *AccountMutations) bool {
|
||||||
|
if a.Balance != nil || other.Balance != nil {
|
||||||
|
if a.Balance == nil || other.Balance == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.Balance.Eq(other.Balance) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(a.Code) != 0 || len(other.Code) != 0) && !bytes.Equal(a.Code, other.Code) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Nonce != nil || other.Nonce != nil {
|
||||||
|
if a.Nonce == nil || other.Nonce == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if *a.Nonce != *other.Nonce {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.StorageWrites != nil || other.StorageWrites != nil {
|
||||||
|
if a.StorageWrites == nil || other.StorageWrites == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !maps.Equal(a.StorageWrites, other.StorageWrites) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a deep-copy of the instance.
|
||||||
|
func (a *AccountMutations) Copy() *AccountMutations {
|
||||||
|
res := &AccountMutations{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
if a.Nonce != nil {
|
||||||
|
res.Nonce = new(uint64)
|
||||||
|
*res.Nonce = *a.Nonce
|
||||||
|
}
|
||||||
|
if a.Code != nil {
|
||||||
|
res.Code = bytes.Clone(a.Code)
|
||||||
|
}
|
||||||
|
if a.Balance != nil {
|
||||||
|
res.Balance = new(uint256.Int).Set(a.Balance)
|
||||||
|
}
|
||||||
|
if a.StorageWrites != nil {
|
||||||
|
res.StorageWrites = maps.Clone(a.StorageWrites)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the state diff as a formatted JSON string.
|
||||||
|
func (s *StateDiff) String() string {
|
||||||
|
var res bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&res)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
enc.Encode(s)
|
||||||
|
return res.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges the state changes present in next into the caller. After,
|
||||||
|
// the state of the caller is the aggregate diff through next.
|
||||||
|
func (s *StateDiff) Merge(next *StateDiff) {
|
||||||
|
for account, diff := range next.Mutations {
|
||||||
|
if mut, ok := s.Mutations[account]; ok {
|
||||||
|
if diff.Balance != nil {
|
||||||
|
mut.Balance = diff.Balance
|
||||||
|
}
|
||||||
|
if diff.Code != nil {
|
||||||
|
mut.Code = diff.Code
|
||||||
|
}
|
||||||
|
if diff.Nonce != nil {
|
||||||
|
mut.Nonce = diff.Nonce
|
||||||
|
}
|
||||||
|
if len(diff.StorageWrites) > 0 {
|
||||||
|
if mut.StorageWrites == nil {
|
||||||
|
mut.StorageWrites = maps.Clone(diff.StorageWrites)
|
||||||
|
} else {
|
||||||
|
for key, val := range diff.StorageWrites {
|
||||||
|
mut.StorageWrites[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.Mutations[account] = diff.Copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a deep copy of the StateDiff
|
||||||
|
func (s *StateDiff) Copy() *StateDiff {
|
||||||
|
res := &StateDiff{make(map[common.Address]*AccountMutations)}
|
||||||
|
for addr, accountDiff := range s.Mutations {
|
||||||
|
cpy := accountDiff.Copy()
|
||||||
|
res.Mutations[addr] = cpy
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a deep copy of the access list
|
||||||
|
func (e BlockAccessList) Copy() (res BlockAccessList) {
|
||||||
|
for _, accountAccess := range e {
|
||||||
|
res = append(res, accountAccess.Copy())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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
|
|
@ -19,12 +19,12 @@ package bal
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -33,26 +33,59 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type BlockAccessList -decoder
|
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type AccountAccess -decoder
|
||||||
|
|
||||||
// These are objects used as input for the access list encoding. They mirror
|
// These are objects used as input for the access list encoding. They mirror
|
||||||
// the spec format.
|
// the spec format.
|
||||||
|
|
||||||
// BlockAccessList is the encoding format of ConstructionBlockAccessList.
|
// BlockAccessList is the encoding format of AccessListBuilder.
|
||||||
type BlockAccessList struct {
|
type BlockAccessList []AccountAccess
|
||||||
Accesses []AccountAccess `ssz-max:"300000"`
|
|
||||||
|
func (e BlockAccessList) EncodeRLP(_w io.Writer) error {
|
||||||
|
w := rlp.NewEncoderBuffer(_w)
|
||||||
|
l := w.List()
|
||||||
|
for _, access := range e {
|
||||||
|
access.EncodeRLP(w)
|
||||||
|
}
|
||||||
|
w.ListEnd(l)
|
||||||
|
return w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
|
||||||
|
if _, err := dec.List(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = (*e)[:0]
|
||||||
|
for dec.MoreDataInList() {
|
||||||
|
var access AccountAccess
|
||||||
|
if err := access.DecodeRLP(dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = append(*e, access)
|
||||||
|
}
|
||||||
|
dec.ListEnd()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BlockAccessList) String() string {
|
||||||
|
var res bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&res)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
// TODO: check error
|
||||||
|
enc.Encode(e)
|
||||||
|
return res.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate returns an error if the contents of the access list are not ordered
|
// Validate returns an error if the contents of the access list are not ordered
|
||||||
// according to the spec or any code changes are contained which exceed protocol
|
// according to the spec or any code changes are contained which exceed protocol
|
||||||
// max code size.
|
// max code size.
|
||||||
func (e *BlockAccessList) Validate() error {
|
func (e BlockAccessList) Validate() error {
|
||||||
if !slices.IsSortedFunc(e.Accesses, func(a, b AccountAccess) int {
|
if !slices.IsSortedFunc(e, func(a, b AccountAccess) int {
|
||||||
return bytes.Compare(a.Address[:], b.Address[:])
|
return bytes.Compare(a.Address[:], b.Address[:])
|
||||||
}) {
|
}) {
|
||||||
return errors.New("block access list accounts not in lexicographic order")
|
return errors.New("block access list accounts not in lexicographic order")
|
||||||
}
|
}
|
||||||
for _, entry := range e.Accesses {
|
for _, entry := range e {
|
||||||
if err := entry.validate(); err != nil {
|
if err := entry.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -73,39 +106,28 @@ func (e *BlockAccessList) Hash() common.Hash {
|
||||||
return crypto.Keccak256Hash(enc.Bytes())
|
return crypto.Keccak256Hash(enc.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeBalance encodes the provided balance into 16-bytes.
|
|
||||||
func encodeBalance(val *uint256.Int) [16]byte {
|
|
||||||
valBytes := val.Bytes()
|
|
||||||
if len(valBytes) > 16 {
|
|
||||||
panic("can't encode value that is greater than 16 bytes in size")
|
|
||||||
}
|
|
||||||
var enc [16]byte
|
|
||||||
copy(enc[16-len(valBytes):], valBytes[:])
|
|
||||||
return enc
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodingBalanceChange is the encoding format of BalanceChange.
|
// encodingBalanceChange is the encoding format of BalanceChange.
|
||||||
type encodingBalanceChange struct {
|
type encodingBalanceChange struct {
|
||||||
TxIdx uint16 `ssz-size:"2"`
|
TxIdx uint16 `json:"txIndex"`
|
||||||
Balance [16]byte `ssz-size:"16"`
|
Balance *uint256.Int `json:"balance"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodingAccountNonce is the encoding format of NonceChange.
|
// encodingAccountNonce is the encoding format of NonceChange.
|
||||||
type encodingAccountNonce struct {
|
type encodingAccountNonce struct {
|
||||||
TxIdx uint16 `ssz-size:"2"`
|
TxIdx uint16 `json:"txIndex"`
|
||||||
Nonce uint64 `ssz-size:"8"`
|
Nonce uint64 `json:"nonce"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodingStorageWrite is the encoding format of StorageWrites.
|
// encodingStorageWrite is the encoding format of StorageWrites.
|
||||||
type encodingStorageWrite struct {
|
type encodingStorageWrite struct {
|
||||||
TxIdx uint16
|
TxIdx uint16 `json:"txIndex"`
|
||||||
ValueAfter [32]byte `ssz-size:"32"`
|
ValueAfter common.Hash `json:"valueAfter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodingStorageWrite is the encoding format of SlotWrites.
|
// encodingStorageWrite is the encoding format of SlotWrites.
|
||||||
type encodingSlotWrites struct {
|
type encodingSlotWrites struct {
|
||||||
Slot [32]byte `ssz-size:"32"`
|
Slot common.Hash `json:"slot"`
|
||||||
Accesses []encodingStorageWrite `ssz-max:"300000"`
|
Accesses []encodingStorageWrite `json:"accesses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate returns an instance of the encoding-representation slot writes in
|
// validate returns an instance of the encoding-representation slot writes in
|
||||||
|
|
@ -119,14 +141,14 @@ func (e *encodingSlotWrites) validate() error {
|
||||||
return errors.New("storage write tx indices not in order")
|
return errors.New("storage write tx indices not in order")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountAccess is the encoding format of ConstructionAccountAccess.
|
// AccountAccess is the encoding format of ConstructionAccountAccesses.
|
||||||
type AccountAccess struct {
|
type AccountAccess struct {
|
||||||
Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address
|
Address common.Address `json:"address,omitempty"` // 20-byte Ethereum address
|
||||||
StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value])
|
StorageChanges []encodingSlotWrites `json:"storageChanges,omitempty"` // Storage changes (slot -> [tx_index -> new_value])
|
||||||
StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys
|
StorageReads []common.Hash `json:"storageReads,omitempty"` // Read-only storage keys
|
||||||
BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance])
|
BalanceChanges []encodingBalanceChange `json:"balanceChanges,omitempty"` // Balance changes ([tx_index -> post_balance])
|
||||||
NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce])
|
NonceChanges []encodingAccountNonce `json:"nonceChanges,omitempty"` // Nonce changes ([tx_index -> new_nonce])
|
||||||
Code []CodeChange `ssz-max:"1"` // Code changes ([tx_index -> new_code])
|
CodeChanges []CodeChange `json:"code,omitempty"` // CodeChanges changes ([tx_index -> new_code])
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate converts the account accesses out of encoding format.
|
// validate converts the account accesses out of encoding format.
|
||||||
|
|
@ -134,19 +156,42 @@ type AccountAccess struct {
|
||||||
// spec, an error is returned.
|
// spec, an error is returned.
|
||||||
func (e *AccountAccess) validate() error {
|
func (e *AccountAccess) validate() error {
|
||||||
// Check the storage write slots are sorted in order
|
// Check the storage write slots are sorted in order
|
||||||
if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int {
|
if !slices.IsSortedFunc(e.StorageChanges, func(a, b encodingSlotWrites) int {
|
||||||
return bytes.Compare(a.Slot[:], b.Slot[:])
|
return bytes.Compare(a.Slot[:], b.Slot[:])
|
||||||
}) {
|
}) {
|
||||||
return errors.New("storage writes slots not in lexicographic order")
|
return errors.New("storage writes slots not in lexicographic order")
|
||||||
}
|
}
|
||||||
for _, write := range e.StorageWrites {
|
for _, write := range e.StorageChanges {
|
||||||
if err := write.validate(); err != nil {
|
if err := write.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// test case ideas: keys in both read/writes, duplicate keys in either read/writes
|
||||||
|
// ensure that the read and write key sets are distinct
|
||||||
|
readKeys := make(map[common.Hash]struct{})
|
||||||
|
writeKeys := make(map[common.Hash]struct{})
|
||||||
|
for _, readKey := range e.StorageReads {
|
||||||
|
if _, ok := readKeys[readKey]; ok {
|
||||||
|
return errors.New("duplicate read key")
|
||||||
|
}
|
||||||
|
readKeys[readKey] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, write := range e.StorageChanges {
|
||||||
|
writeKey := write.Slot
|
||||||
|
if _, ok := writeKeys[writeKey]; ok {
|
||||||
|
return errors.New("duplicate write key")
|
||||||
|
}
|
||||||
|
writeKeys[writeKey] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for readKey := range readKeys {
|
||||||
|
if _, ok := writeKeys[readKey]; ok {
|
||||||
|
return errors.New("storage key reported in both read/write sets")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check the storage read slots are sorted in order
|
// Check the storage read slots are sorted in order
|
||||||
if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int {
|
if !slices.IsSortedFunc(e.StorageReads, func(a, b common.Hash) int {
|
||||||
return bytes.Compare(a[:], b[:])
|
return bytes.Compare(a[:], b[:])
|
||||||
}) {
|
}) {
|
||||||
return errors.New("storage read slots not in lexicographic order")
|
return errors.New("storage read slots not in lexicographic order")
|
||||||
|
|
@ -167,9 +212,9 @@ func (e *AccountAccess) validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert code change
|
// Convert code change
|
||||||
if len(e.Code) == 1 {
|
for _, codeChange := range e.CodeChanges {
|
||||||
if len(e.Code[0].Code) > params.MaxCodeSize {
|
if len(codeChange.Code) > params.MaxCodeSize {
|
||||||
return errors.New("code change contained oversized code")
|
return fmt.Errorf("code change contained oversized code")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -183,40 +228,39 @@ func (e *AccountAccess) Copy() AccountAccess {
|
||||||
BalanceChanges: slices.Clone(e.BalanceChanges),
|
BalanceChanges: slices.Clone(e.BalanceChanges),
|
||||||
NonceChanges: slices.Clone(e.NonceChanges),
|
NonceChanges: slices.Clone(e.NonceChanges),
|
||||||
}
|
}
|
||||||
for _, storageWrite := range e.StorageWrites {
|
for _, storageWrite := range e.StorageChanges {
|
||||||
res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{
|
res.StorageChanges = append(res.StorageChanges, encodingSlotWrites{
|
||||||
Slot: storageWrite.Slot,
|
Slot: storageWrite.Slot,
|
||||||
Accesses: slices.Clone(storageWrite.Accesses),
|
Accesses: slices.Clone(storageWrite.Accesses),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if len(e.Code) == 1 {
|
for _, codeChange := range e.CodeChanges {
|
||||||
res.Code = []CodeChange{
|
res.CodeChanges = append(res.CodeChanges,
|
||||||
{
|
CodeChange{
|
||||||
e.Code[0].TxIndex,
|
codeChange.TxIdx,
|
||||||
bytes.Clone(e.Code[0].Code),
|
bytes.Clone(codeChange.Code),
|
||||||
},
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeRLP returns the RLP-encoded access list
|
// EncodeRLP returns the RLP-encoded access list
|
||||||
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
|
func (c *AccessListBuilder) EncodeRLP(wr io.Writer) error {
|
||||||
return b.toEncodingObj().EncodeRLP(wr)
|
return c.ToEncodingObj().EncodeRLP(wr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ rlp.Encoder = &ConstructionBlockAccessList{}
|
var _ rlp.Encoder = &AccessListBuilder{}
|
||||||
|
|
||||||
// toEncodingObj creates an instance of the ConstructionAccountAccess of the type that is
|
// toEncodingObj creates an instance of the ConstructionAccountAccesses of the type that is
|
||||||
// used as input for the encoding.
|
// used as input for the encoding.
|
||||||
func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess {
|
func (a *ConstructionAccountAccesses) toEncodingObj(addr common.Address) AccountAccess {
|
||||||
res := AccountAccess{
|
res := AccountAccess{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
StorageWrites: make([]encodingSlotWrites, 0),
|
StorageChanges: make([]encodingSlotWrites, 0),
|
||||||
StorageReads: make([][32]byte, 0),
|
StorageReads: make([]common.Hash, 0),
|
||||||
BalanceChanges: make([]encodingBalanceChange, 0),
|
BalanceChanges: make([]encodingBalanceChange, 0),
|
||||||
NonceChanges: make([]encodingAccountNonce, 0),
|
NonceChanges: make([]encodingAccountNonce, 0),
|
||||||
Code: nil,
|
CodeChanges: make([]CodeChange, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert write slots
|
// Convert write slots
|
||||||
|
|
@ -237,7 +281,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
||||||
ValueAfter: slotWrites[index],
|
ValueAfter: slotWrites[index],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
res.StorageWrites = append(res.StorageWrites, obj)
|
res.StorageChanges = append(res.StorageChanges, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert read slots
|
// Convert read slots
|
||||||
|
|
@ -253,7 +297,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
||||||
for _, idx := range balanceIndices {
|
for _, idx := range balanceIndices {
|
||||||
res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{
|
res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{
|
||||||
TxIdx: idx,
|
TxIdx: idx,
|
||||||
Balance: encodeBalance(a.BalanceChanges[idx]),
|
Balance: new(uint256.Int).Set(a.BalanceChanges[idx]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,77 +312,31 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert code change
|
// Convert code change
|
||||||
if a.CodeChange != nil {
|
codeChangeIdxs := slices.Collect(maps.Keys(a.CodeChanges))
|
||||||
res.Code = []CodeChange{
|
slices.SortFunc(codeChangeIdxs, cmp.Compare[uint16])
|
||||||
{
|
for _, idx := range codeChangeIdxs {
|
||||||
a.CodeChange.TxIndex,
|
res.CodeChanges = append(res.CodeChanges, CodeChange{
|
||||||
bytes.Clone(a.CodeChange.Code),
|
idx,
|
||||||
},
|
bytes.Clone(a.CodeChanges[idx].Code),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// toEncodingObj returns an instance of the access list expressed as the type
|
// ToEncodingObj returns an instance of the access list expressed as the type
|
||||||
// which is used as input for the encoding/decoding.
|
// which is used as input for the encoding/decoding.
|
||||||
func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList {
|
func (c *AccessListBuilder) ToEncodingObj() *BlockAccessList {
|
||||||
var addresses []common.Address
|
var addresses []common.Address
|
||||||
for addr := range b.Accounts {
|
for addr := range c.FinalizedAccesses {
|
||||||
addresses = append(addresses, addr)
|
addresses = append(addresses, addr)
|
||||||
}
|
}
|
||||||
slices.SortFunc(addresses, common.Address.Cmp)
|
slices.SortFunc(addresses, common.Address.Cmp)
|
||||||
|
|
||||||
var res BlockAccessList
|
var res BlockAccessList
|
||||||
for _, addr := range addresses {
|
for _, addr := range addresses {
|
||||||
res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr))
|
res = append(res, c.FinalizedAccesses[addr].toEncodingObj(addr))
|
||||||
}
|
}
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BlockAccessList) PrettyPrint() string {
|
type ContractCode []byte
|
||||||
var res bytes.Buffer
|
|
||||||
printWithIndent := func(indent int, text string) {
|
|
||||||
fmt.Fprintf(&res, "%s%s\n", strings.Repeat(" ", indent), text)
|
|
||||||
}
|
|
||||||
for _, accountDiff := range e.Accesses {
|
|
||||||
printWithIndent(0, fmt.Sprintf("%x:", accountDiff.Address))
|
|
||||||
|
|
||||||
printWithIndent(1, "storage writes:")
|
|
||||||
for _, sWrite := range accountDiff.StorageWrites {
|
|
||||||
printWithIndent(2, fmt.Sprintf("%x:", sWrite.Slot))
|
|
||||||
for _, access := range sWrite.Accesses {
|
|
||||||
printWithIndent(3, fmt.Sprintf("%d: %x", access.TxIdx, access.ValueAfter))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printWithIndent(1, "storage reads:")
|
|
||||||
for _, slot := range accountDiff.StorageReads {
|
|
||||||
printWithIndent(2, fmt.Sprintf("%x", slot))
|
|
||||||
}
|
|
||||||
|
|
||||||
printWithIndent(1, "balance changes:")
|
|
||||||
for _, change := range accountDiff.BalanceChanges {
|
|
||||||
balance := new(uint256.Int).SetBytes(change.Balance[:]).String()
|
|
||||||
printWithIndent(2, fmt.Sprintf("%d: %s", change.TxIdx, balance))
|
|
||||||
}
|
|
||||||
|
|
||||||
printWithIndent(1, "nonce changes:")
|
|
||||||
for _, change := range accountDiff.NonceChanges {
|
|
||||||
printWithIndent(2, fmt.Sprintf("%d: %d", change.TxIdx, change.Nonce))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(accountDiff.Code) > 0 {
|
|
||||||
printWithIndent(1, "code:")
|
|
||||||
printWithIndent(2, fmt.Sprintf("%d: %x", accountDiff.Code[0].TxIndex, accountDiff.Code[0].Code))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a deep copy of the access list
|
|
||||||
func (e *BlockAccessList) Copy() (res BlockAccessList) {
|
|
||||||
for _, accountAccess := range e.Accesses {
|
|
||||||
res.Accesses = append(res.Accesses, accountAccess.Copy())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
||||||
107
core/types/bal/bal_encoding_json.go
Normal file
107
core/types/bal/bal_encoding_json.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package bal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ContractCode) MarshalJSON() ([]byte, error) {
|
||||||
|
hexStr := fmt.Sprintf("%x", *c)
|
||||||
|
return json.Marshal(hexStr)
|
||||||
|
}
|
||||||
|
func (e encodingBalanceChange) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias encodingBalanceChange
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
TxIdx string `json:"txIndex"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
TxIdx: fmt.Sprintf("0x%x", e.TxIdx),
|
||||||
|
Alias: (*Alias)(&e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encodingBalanceChange) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias encodingBalanceChange
|
||||||
|
aux := &struct {
|
||||||
|
TxIdx string `json:"txIndex"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(e),
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(aux.TxIdx) >= 2 && aux.TxIdx[:2] == "0x" {
|
||||||
|
if _, err := fmt.Sscanf(aux.TxIdx, "0x%x", &e.TxIdx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (e encodingAccountNonce) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias encodingAccountNonce
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
TxIdx string `json:"txIndex"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
TxIdx: fmt.Sprintf("0x%x", e.TxIdx),
|
||||||
|
Nonce: fmt.Sprintf("0x%x", e.Nonce),
|
||||||
|
Alias: (*Alias)(&e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encodingAccountNonce) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias encodingAccountNonce
|
||||||
|
aux := &struct {
|
||||||
|
TxIdx string `json:"txIndex"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(e),
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(aux.TxIdx) >= 2 && aux.TxIdx[:2] == "0x" {
|
||||||
|
if _, err := fmt.Sscanf(aux.TxIdx, "0x%x", &e.TxIdx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(aux.Nonce) >= 2 && aux.Nonce[:2] == "0x" {
|
||||||
|
if _, err := fmt.Sscanf(aux.Nonce, "0x%x", &e.Nonce); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler to decode from RLP hex bytes
|
||||||
|
func (b *BlockAccessList) UnmarshalJSON(input []byte) error {
|
||||||
|
// Handle both hex string and object formats
|
||||||
|
var hexBytes hexutil.Bytes
|
||||||
|
if err := json.Unmarshal(input, &hexBytes); err == nil {
|
||||||
|
// It's a hex string, decode from RLP
|
||||||
|
return rlp.DecodeBytes(hexBytes, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try to unmarshal as structured JSON
|
||||||
|
var tmp []AccountAccess
|
||||||
|
if err := json.Unmarshal(input, &tmp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*b = BlockAccessList(tmp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler to encode as RLP hex bytes
|
||||||
|
func (b BlockAccessList) MarshalJSON() ([]byte, error) {
|
||||||
|
// Encode to RLP then to hex
|
||||||
|
rlpBytes, err := rlp.EncodeToBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(hexutil.Bytes(rlpBytes))
|
||||||
|
}
|
||||||
|
|
@ -2,275 +2,254 @@
|
||||||
|
|
||||||
package bal
|
package bal
|
||||||
|
|
||||||
|
import "github.com/ethereum/go-ethereum/common"
|
||||||
import "github.com/ethereum/go-ethereum/rlp"
|
import "github.com/ethereum/go-ethereum/rlp"
|
||||||
|
import "github.com/holiman/uint256"
|
||||||
import "io"
|
import "io"
|
||||||
|
|
||||||
func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error {
|
func (obj *AccountAccess) EncodeRLP(_w io.Writer) error {
|
||||||
w := rlp.NewEncoderBuffer(_w)
|
w := rlp.NewEncoderBuffer(_w)
|
||||||
_tmp0 := w.List()
|
_tmp0 := w.List()
|
||||||
|
w.WriteBytes(obj.Address[:])
|
||||||
_tmp1 := w.List()
|
_tmp1 := w.List()
|
||||||
for _, _tmp2 := range obj.Accesses {
|
for _, _tmp2 := range obj.StorageChanges {
|
||||||
_tmp3 := w.List()
|
_tmp3 := w.List()
|
||||||
w.WriteBytes(_tmp2.Address[:])
|
w.WriteBytes(_tmp2.Slot[:])
|
||||||
_tmp4 := w.List()
|
_tmp4 := w.List()
|
||||||
for _, _tmp5 := range _tmp2.StorageWrites {
|
for _, _tmp5 := range _tmp2.Accesses {
|
||||||
_tmp6 := w.List()
|
_tmp6 := w.List()
|
||||||
w.WriteBytes(_tmp5.Slot[:])
|
w.WriteUint64(uint64(_tmp5.TxIdx))
|
||||||
_tmp7 := w.List()
|
w.WriteBytes(_tmp5.ValueAfter[:])
|
||||||
for _, _tmp8 := range _tmp5.Accesses {
|
|
||||||
_tmp9 := w.List()
|
|
||||||
w.WriteUint64(uint64(_tmp8.TxIdx))
|
|
||||||
w.WriteBytes(_tmp8.ValueAfter[:])
|
|
||||||
w.ListEnd(_tmp9)
|
|
||||||
}
|
|
||||||
w.ListEnd(_tmp7)
|
|
||||||
w.ListEnd(_tmp6)
|
w.ListEnd(_tmp6)
|
||||||
}
|
}
|
||||||
w.ListEnd(_tmp4)
|
w.ListEnd(_tmp4)
|
||||||
_tmp10 := w.List()
|
w.ListEnd(_tmp3)
|
||||||
for _, _tmp11 := range _tmp2.StorageReads {
|
|
||||||
w.WriteBytes(_tmp11[:])
|
|
||||||
}
|
}
|
||||||
w.ListEnd(_tmp10)
|
w.ListEnd(_tmp1)
|
||||||
|
_tmp7 := w.List()
|
||||||
|
for _, _tmp8 := range obj.StorageReads {
|
||||||
|
w.WriteBytes(_tmp8[:])
|
||||||
|
}
|
||||||
|
w.ListEnd(_tmp7)
|
||||||
|
_tmp9 := w.List()
|
||||||
|
for _, _tmp10 := range obj.BalanceChanges {
|
||||||
|
_tmp11 := w.List()
|
||||||
|
w.WriteUint64(uint64(_tmp10.TxIdx))
|
||||||
|
if _tmp10.Balance == nil {
|
||||||
|
w.Write(rlp.EmptyString)
|
||||||
|
} else {
|
||||||
|
w.WriteUint256(_tmp10.Balance)
|
||||||
|
}
|
||||||
|
w.ListEnd(_tmp11)
|
||||||
|
}
|
||||||
|
w.ListEnd(_tmp9)
|
||||||
_tmp12 := w.List()
|
_tmp12 := w.List()
|
||||||
for _, _tmp13 := range _tmp2.BalanceChanges {
|
for _, _tmp13 := range obj.NonceChanges {
|
||||||
_tmp14 := w.List()
|
_tmp14 := w.List()
|
||||||
w.WriteUint64(uint64(_tmp13.TxIdx))
|
w.WriteUint64(uint64(_tmp13.TxIdx))
|
||||||
w.WriteBytes(_tmp13.Balance[:])
|
w.WriteUint64(_tmp13.Nonce)
|
||||||
w.ListEnd(_tmp14)
|
w.ListEnd(_tmp14)
|
||||||
}
|
}
|
||||||
w.ListEnd(_tmp12)
|
w.ListEnd(_tmp12)
|
||||||
_tmp15 := w.List()
|
_tmp15 := w.List()
|
||||||
for _, _tmp16 := range _tmp2.NonceChanges {
|
for _, _tmp16 := range obj.CodeChanges {
|
||||||
_tmp17 := w.List()
|
_tmp17 := w.List()
|
||||||
w.WriteUint64(uint64(_tmp16.TxIdx))
|
w.WriteUint64(uint64(_tmp16.TxIdx))
|
||||||
w.WriteUint64(_tmp16.Nonce)
|
w.WriteBytes(_tmp16.Code)
|
||||||
w.ListEnd(_tmp17)
|
w.ListEnd(_tmp17)
|
||||||
}
|
}
|
||||||
w.ListEnd(_tmp15)
|
w.ListEnd(_tmp15)
|
||||||
_tmp18 := w.List()
|
|
||||||
for _, _tmp19 := range _tmp2.Code {
|
|
||||||
_tmp20 := w.List()
|
|
||||||
w.WriteUint64(uint64(_tmp19.TxIndex))
|
|
||||||
w.WriteBytes(_tmp19.Code)
|
|
||||||
w.ListEnd(_tmp20)
|
|
||||||
}
|
|
||||||
w.ListEnd(_tmp18)
|
|
||||||
w.ListEnd(_tmp3)
|
|
||||||
}
|
|
||||||
w.ListEnd(_tmp1)
|
|
||||||
w.ListEnd(_tmp0)
|
w.ListEnd(_tmp0)
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
|
func (obj *AccountAccess) DecodeRLP(dec *rlp.Stream) error {
|
||||||
var _tmp0 BlockAccessList
|
var _tmp0 AccountAccess
|
||||||
{
|
|
||||||
if _, err := dec.List(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Accesses:
|
|
||||||
var _tmp1 []AccountAccess
|
|
||||||
if _, err := dec.List(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for dec.MoreDataInList() {
|
|
||||||
var _tmp2 AccountAccess
|
|
||||||
{
|
{
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Address:
|
// Address:
|
||||||
var _tmp3 [20]byte
|
var _tmp1 common.Address
|
||||||
if err := dec.ReadBytes(_tmp3[:]); err != nil {
|
if err := dec.ReadBytes(_tmp1[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp2.Address = _tmp3
|
_tmp0.Address = _tmp1
|
||||||
// StorageWrites:
|
// StorageChanges:
|
||||||
var _tmp4 []encodingSlotWrites
|
var _tmp2 []encodingSlotWrites
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for dec.MoreDataInList() {
|
for dec.MoreDataInList() {
|
||||||
var _tmp5 encodingSlotWrites
|
var _tmp3 encodingSlotWrites
|
||||||
{
|
{
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Slot:
|
// Slot:
|
||||||
var _tmp6 [32]byte
|
var _tmp4 common.Hash
|
||||||
if err := dec.ReadBytes(_tmp6[:]); err != nil {
|
if err := dec.ReadBytes(_tmp4[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp5.Slot = _tmp6
|
_tmp3.Slot = _tmp4
|
||||||
// Accesses:
|
// Accesses:
|
||||||
var _tmp7 []encodingStorageWrite
|
var _tmp5 []encodingStorageWrite
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for dec.MoreDataInList() {
|
for dec.MoreDataInList() {
|
||||||
var _tmp8 encodingStorageWrite
|
var _tmp6 encodingStorageWrite
|
||||||
{
|
{
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TxIdx:
|
// TxIdx:
|
||||||
_tmp9, err := dec.Uint16()
|
_tmp7, err := dec.Uint16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp8.TxIdx = _tmp9
|
_tmp6.TxIdx = _tmp7
|
||||||
// ValueAfter:
|
// ValueAfter:
|
||||||
var _tmp10 [32]byte
|
var _tmp8 common.Hash
|
||||||
if err := dec.ReadBytes(_tmp10[:]); err != nil {
|
if err := dec.ReadBytes(_tmp8[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp8.ValueAfter = _tmp10
|
_tmp6.ValueAfter = _tmp8
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_tmp7 = append(_tmp7, _tmp8)
|
_tmp5 = append(_tmp5, _tmp6)
|
||||||
}
|
}
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp5.Accesses = _tmp7
|
_tmp3.Accesses = _tmp5
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_tmp4 = append(_tmp4, _tmp5)
|
_tmp2 = append(_tmp2, _tmp3)
|
||||||
}
|
}
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp2.StorageWrites = _tmp4
|
_tmp0.StorageChanges = _tmp2
|
||||||
// StorageReads:
|
// StorageReads:
|
||||||
var _tmp11 [][32]byte
|
var _tmp9 []common.Hash
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for dec.MoreDataInList() {
|
for dec.MoreDataInList() {
|
||||||
var _tmp12 [32]byte
|
var _tmp10 common.Hash
|
||||||
if err := dec.ReadBytes(_tmp12[:]); err != nil {
|
if err := dec.ReadBytes(_tmp10[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_tmp9 = append(_tmp9, _tmp10)
|
||||||
|
}
|
||||||
|
if err := dec.ListEnd(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_tmp0.StorageReads = _tmp9
|
||||||
|
// BalanceChanges:
|
||||||
|
var _tmp11 []encodingBalanceChange
|
||||||
|
if _, err := dec.List(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for dec.MoreDataInList() {
|
||||||
|
var _tmp12 encodingBalanceChange
|
||||||
|
{
|
||||||
|
if _, err := dec.List(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TxIdx:
|
||||||
|
_tmp13, err := dec.Uint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_tmp12.TxIdx = _tmp13
|
||||||
|
// Balance:
|
||||||
|
var _tmp14 uint256.Int
|
||||||
|
if err := dec.ReadUint256(&_tmp14); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_tmp12.Balance = &_tmp14
|
||||||
|
if err := dec.ListEnd(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
_tmp11 = append(_tmp11, _tmp12)
|
_tmp11 = append(_tmp11, _tmp12)
|
||||||
}
|
}
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp2.StorageReads = _tmp11
|
_tmp0.BalanceChanges = _tmp11
|
||||||
// BalanceChanges:
|
|
||||||
var _tmp13 []encodingBalanceChange
|
|
||||||
if _, err := dec.List(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for dec.MoreDataInList() {
|
|
||||||
var _tmp14 encodingBalanceChange
|
|
||||||
{
|
|
||||||
if _, err := dec.List(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// TxIdx:
|
|
||||||
_tmp15, err := dec.Uint16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_tmp14.TxIdx = _tmp15
|
|
||||||
// Balance:
|
|
||||||
var _tmp16 [16]byte
|
|
||||||
if err := dec.ReadBytes(_tmp16[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_tmp14.Balance = _tmp16
|
|
||||||
if err := dec.ListEnd(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_tmp13 = append(_tmp13, _tmp14)
|
|
||||||
}
|
|
||||||
if err := dec.ListEnd(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_tmp2.BalanceChanges = _tmp13
|
|
||||||
// NonceChanges:
|
// NonceChanges:
|
||||||
var _tmp17 []encodingAccountNonce
|
var _tmp15 []encodingAccountNonce
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for dec.MoreDataInList() {
|
for dec.MoreDataInList() {
|
||||||
var _tmp18 encodingAccountNonce
|
var _tmp16 encodingAccountNonce
|
||||||
{
|
{
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TxIdx:
|
// TxIdx:
|
||||||
_tmp19, err := dec.Uint16()
|
_tmp17, err := dec.Uint16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp18.TxIdx = _tmp19
|
_tmp16.TxIdx = _tmp17
|
||||||
// Nonce:
|
// Nonce:
|
||||||
_tmp20, err := dec.Uint64()
|
_tmp18, err := dec.Uint64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp18.Nonce = _tmp20
|
_tmp16.Nonce = _tmp18
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_tmp17 = append(_tmp17, _tmp18)
|
_tmp15 = append(_tmp15, _tmp16)
|
||||||
}
|
}
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp2.NonceChanges = _tmp17
|
_tmp0.NonceChanges = _tmp15
|
||||||
// Code:
|
// CodeChanges:
|
||||||
var _tmp21 []CodeChange
|
var _tmp19 []CodeChange
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for dec.MoreDataInList() {
|
for dec.MoreDataInList() {
|
||||||
var _tmp22 CodeChange
|
var _tmp20 CodeChange
|
||||||
{
|
{
|
||||||
if _, err := dec.List(); err != nil {
|
if _, err := dec.List(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TxIndex:
|
// TxIdx:
|
||||||
_tmp23, err := dec.Uint16()
|
_tmp21, err := dec.Uint16()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp22.TxIndex = _tmp23
|
_tmp20.TxIdx = _tmp21
|
||||||
// Code:
|
// Code:
|
||||||
_tmp24, err := dec.Bytes()
|
_tmp22, err := dec.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp22.Code = _tmp24
|
_tmp20.Code = _tmp22
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_tmp21 = append(_tmp21, _tmp22)
|
_tmp19 = append(_tmp19, _tmp20)
|
||||||
}
|
}
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_tmp2.Code = _tmp21
|
_tmp0.CodeChanges = _tmp19
|
||||||
if err := dec.ListEnd(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_tmp1 = append(_tmp1, _tmp2)
|
|
||||||
}
|
|
||||||
if err := dec.ListEnd(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_tmp0.Accesses = _tmp1
|
|
||||||
if err := dec.ListEnd(); err != nil {
|
if err := dec.ListEnd(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@ func equalBALs(a *BlockAccessList, b *BlockAccessList) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTestConstructionBAL() *ConstructionBlockAccessList {
|
func makeTestConstructionBAL() *AccessListBuilder {
|
||||||
return &ConstructionBlockAccessList{
|
return &AccessListBuilder{
|
||||||
map[common.Address]*ConstructionAccountAccess{
|
map[common.Address]*ConstructionAccountAccesses{
|
||||||
common.BytesToAddress([]byte{0xff, 0xff}): {
|
common.BytesToAddress([]byte{0xff, 0xff}): {
|
||||||
StorageWrites: map[common.Hash]map[uint16]common.Hash{
|
StorageWrites: map[common.Hash]map[uint16]common.Hash{
|
||||||
common.BytesToHash([]byte{0x01}): {
|
common.BytesToHash([]byte{0x01}): {
|
||||||
|
|
@ -60,10 +60,10 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
|
||||||
1: 2,
|
1: 2,
|
||||||
2: 6,
|
2: 6,
|
||||||
},
|
},
|
||||||
CodeChange: &CodeChange{
|
CodeChanges: map[uint16]CodeChange{0: {
|
||||||
TxIndex: 0,
|
TxIdx: 0,
|
||||||
Code: common.Hex2Bytes("deadbeef"),
|
Code: common.Hex2Bytes("deadbeef"),
|
||||||
},
|
}},
|
||||||
},
|
},
|
||||||
common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
|
common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
|
||||||
StorageWrites: map[common.Hash]map[uint16]common.Hash{
|
StorageWrites: map[common.Hash]map[uint16]common.Hash{
|
||||||
|
|
@ -102,10 +102,10 @@ func TestBALEncoding(t *testing.T) {
|
||||||
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil {
|
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil {
|
||||||
t.Fatalf("decoding failed: %v\n", err)
|
t.Fatalf("decoding failed: %v\n", err)
|
||||||
}
|
}
|
||||||
if dec.Hash() != bal.toEncodingObj().Hash() {
|
if dec.Hash() != bal.ToEncodingObj().Hash() {
|
||||||
t.Fatalf("encoded block hash doesn't match decoded")
|
t.Fatalf("encoded block hash doesn't match decoded")
|
||||||
}
|
}
|
||||||
if !equalBALs(bal.toEncodingObj(), &dec) {
|
if !equalBALs(bal.ToEncodingObj(), &dec) {
|
||||||
t.Fatal("decoded BAL doesn't match")
|
t.Fatal("decoded BAL doesn't match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ func TestBALEncoding(t *testing.T) {
|
||||||
func makeTestAccountAccess(sort bool) AccountAccess {
|
func makeTestAccountAccess(sort bool) AccountAccess {
|
||||||
var (
|
var (
|
||||||
storageWrites []encodingSlotWrites
|
storageWrites []encodingSlotWrites
|
||||||
storageReads [][32]byte
|
storageReads []common.Hash
|
||||||
balances []encodingBalanceChange
|
balances []encodingBalanceChange
|
||||||
nonces []encodingAccountNonce
|
nonces []encodingAccountNonce
|
||||||
)
|
)
|
||||||
|
|
@ -144,7 +144,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
||||||
storageReads = append(storageReads, testrand.Hash())
|
storageReads = append(storageReads, testrand.Hash())
|
||||||
}
|
}
|
||||||
if sort {
|
if sort {
|
||||||
slices.SortFunc(storageReads, func(a, b [32]byte) int {
|
slices.SortFunc(storageReads, func(a, b common.Hash) int {
|
||||||
return bytes.Compare(a[:], b[:])
|
return bytes.Compare(a[:], b[:])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +152,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
balances = append(balances, encodingBalanceChange{
|
balances = append(balances, encodingBalanceChange{
|
||||||
TxIdx: uint16(2 * i),
|
TxIdx: uint16(2 * i),
|
||||||
Balance: [16]byte(testrand.Bytes(16)),
|
Balance: new(uint256.Int).SetBytes(testrand.Bytes(32)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if sort {
|
if sort {
|
||||||
|
|
@ -175,13 +175,13 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
||||||
|
|
||||||
return AccountAccess{
|
return AccountAccess{
|
||||||
Address: [20]byte(testrand.Bytes(20)),
|
Address: [20]byte(testrand.Bytes(20)),
|
||||||
StorageWrites: storageWrites,
|
StorageChanges: storageWrites,
|
||||||
StorageReads: storageReads,
|
StorageReads: storageReads,
|
||||||
BalanceChanges: balances,
|
BalanceChanges: balances,
|
||||||
NonceChanges: nonces,
|
NonceChanges: nonces,
|
||||||
Code: []CodeChange{
|
CodeChanges: []CodeChange{
|
||||||
{
|
{
|
||||||
TxIndex: 100,
|
TxIdx: 100,
|
||||||
Code: testrand.Bytes(256),
|
Code: testrand.Bytes(256),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -191,10 +191,10 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
||||||
func makeTestBAL(sort bool) BlockAccessList {
|
func makeTestBAL(sort bool) BlockAccessList {
|
||||||
list := BlockAccessList{}
|
list := BlockAccessList{}
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
|
list = append(list, makeTestAccountAccess(sort))
|
||||||
}
|
}
|
||||||
if sort {
|
if sort {
|
||||||
slices.SortFunc(list.Accesses, func(a, b AccountAccess) int {
|
slices.SortFunc(list, func(a, b AccountAccess) int {
|
||||||
return bytes.Compare(a.Address[:], b.Address[:])
|
return bytes.Compare(a.Address[:], b.Address[:])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +214,7 @@ func TestBlockAccessListCopy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the mutations on copy won't affect the origin
|
// Make sure the mutations on copy won't affect the origin
|
||||||
for _, aa := range cpyCpy.Accesses {
|
for _, aa := range cpyCpy {
|
||||||
for i := 0; i < len(aa.StorageReads); i++ {
|
for i := 0; i < len(aa.StorageReads); i++ {
|
||||||
aa.StorageReads[i] = [32]byte(testrand.Bytes(32))
|
aa.StorageReads[i] = [32]byte(testrand.Bytes(32))
|
||||||
}
|
}
|
||||||
|
|
@ -245,8 +245,11 @@ func TestBlockAccessListValidation(t *testing.T) {
|
||||||
|
|
||||||
// Validate the derived block access list
|
// Validate the derived block access list
|
||||||
cBAL := makeTestConstructionBAL()
|
cBAL := makeTestConstructionBAL()
|
||||||
listB := cBAL.toEncodingObj()
|
listB := cBAL.ToEncodingObj()
|
||||||
if err := listB.Validate(); err != nil {
|
if err := listB.Validate(); err != nil {
|
||||||
t.Fatalf("Unexpected validation error: %v", err)
|
t.Fatalf("Unexpected validation error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BALReader test ideas
|
||||||
|
// * BAL which doesn't have any pre-tx system contracts should return an empty state diff at idx 0
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
@ -106,6 +108,9 @@ type Header struct {
|
||||||
|
|
||||||
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
|
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
|
||||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||||
|
|
||||||
|
// BlockAccessListHash was added by EIP-7928 and is ignored in legacy headers.
|
||||||
|
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// field type overrides for gencodec
|
// field type overrides for gencodec
|
||||||
|
|
@ -184,6 +189,7 @@ type Body struct {
|
||||||
Transactions []*Transaction
|
Transactions []*Transaction
|
||||||
Uncles []*Header
|
Uncles []*Header
|
||||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||||
|
AccessList *bal.BlockAccessList `rlp:"optional,nil"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block represents an Ethereum block.
|
// Block represents an Ethereum block.
|
||||||
|
|
@ -214,6 +220,8 @@ type Block struct {
|
||||||
// that process it.
|
// that process it.
|
||||||
witness *ExecutionWitness
|
witness *ExecutionWitness
|
||||||
|
|
||||||
|
accessList *bal.BlockAccessList
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
hash atomic.Pointer[common.Hash]
|
hash atomic.Pointer[common.Hash]
|
||||||
size atomic.Uint64
|
size atomic.Uint64
|
||||||
|
|
@ -230,6 +238,7 @@ type extblock struct {
|
||||||
Txs []*Transaction
|
Txs []*Transaction
|
||||||
Uncles []*Header
|
Uncles []*Header
|
||||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||||
|
AccessList *bal.BlockAccessList `rlp:"optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlock creates a new block. The input data is copied, changes to header and to the
|
// NewBlock creates a new block. The input data is copied, changes to header and to the
|
||||||
|
|
@ -290,6 +299,12 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher
|
||||||
b.withdrawals = slices.Clone(withdrawals)
|
b.withdrawals = slices.Clone(withdrawals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if body.AccessList != nil {
|
||||||
|
balHash := body.AccessList.Hash()
|
||||||
|
b.header.BlockAccessListHash = &balHash
|
||||||
|
b.accessList = body.AccessList
|
||||||
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,12 +349,14 @@ func CopyHeader(h *Header) *Header {
|
||||||
|
|
||||||
// DecodeRLP decodes a block from RLP.
|
// DecodeRLP decodes a block from RLP.
|
||||||
func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
||||||
var eb extblock
|
var (
|
||||||
|
eb extblock
|
||||||
|
)
|
||||||
_, size, _ := s.Kind()
|
_, size, _ := s.Kind()
|
||||||
if err := s.Decode(&eb); err != nil {
|
if err := s.Decode(&eb); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals
|
b.header, b.uncles, b.transactions, b.withdrawals, b.accessList = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals, eb.AccessList
|
||||||
b.size.Store(rlp.ListSize(size))
|
b.size.Store(rlp.ListSize(size))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -351,13 +368,14 @@ func (b *Block) EncodeRLP(w io.Writer) error {
|
||||||
Txs: b.transactions,
|
Txs: b.transactions,
|
||||||
Uncles: b.uncles,
|
Uncles: b.uncles,
|
||||||
Withdrawals: b.withdrawals,
|
Withdrawals: b.withdrawals,
|
||||||
|
AccessList: b.accessList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body returns the non-header content of the block.
|
// Body returns the non-header content of the block.
|
||||||
// Note the returned data is not an independent copy.
|
// Note the returned data is not an independent copy.
|
||||||
func (b *Block) Body() *Body {
|
func (b *Block) Body() *Body {
|
||||||
return &Body{b.transactions, b.uncles, b.withdrawals}
|
return &Body{b.transactions, b.uncles, b.withdrawals, b.accessList}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accessors for body data. These do not return a copy because the content
|
// Accessors for body data. These do not return a copy because the content
|
||||||
|
|
@ -508,6 +526,10 @@ func (b *Block) WithBody(body Body) *Block {
|
||||||
withdrawals: slices.Clone(body.Withdrawals),
|
withdrawals: slices.Clone(body.Withdrawals),
|
||||||
witness: b.witness,
|
witness: b.witness,
|
||||||
}
|
}
|
||||||
|
if body.AccessList != nil {
|
||||||
|
balCopy := body.AccessList.Copy()
|
||||||
|
block.accessList = &balCopy
|
||||||
|
}
|
||||||
for i := range body.Uncles {
|
for i := range body.Uncles {
|
||||||
block.uncles[i] = CopyHeader(body.Uncles[i])
|
block.uncles[i] = CopyHeader(body.Uncles[i])
|
||||||
}
|
}
|
||||||
|
|
@ -520,6 +542,7 @@ func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
|
||||||
transactions: b.transactions,
|
transactions: b.transactions,
|
||||||
uncles: b.uncles,
|
uncles: b.uncles,
|
||||||
withdrawals: b.withdrawals,
|
withdrawals: b.withdrawals,
|
||||||
|
accessList: b.accessList,
|
||||||
witness: witness,
|
witness: witness,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -470,25 +470,32 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
||||||
|
|
||||||
// create creates a new contract using code as deployment code.
|
// create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
|
||||||
|
// Depth check execution. Fail if we're trying to execute above the
|
||||||
|
// limit.
|
||||||
|
var nonce uint64
|
||||||
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
|
err = ErrDepth
|
||||||
|
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||||
|
err = ErrInsufficientBalance
|
||||||
|
} else {
|
||||||
|
nonce = evm.StateDB.GetNonce(caller)
|
||||||
|
if nonce+1 < nonce {
|
||||||
|
err = ErrNonceUintOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
||||||
|
}
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
||||||
defer func(startGas uint64) {
|
defer func(startGas uint64) {
|
||||||
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
|
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
|
||||||
}(gas)
|
}(gas)
|
||||||
}
|
}
|
||||||
// Depth check execution. Fail if we're trying to execute above the
|
if err != nil {
|
||||||
// limit.
|
return nil, common.Address{}, gas, err
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
|
||||||
return nil, common.Address{}, gas, ErrDepth
|
|
||||||
}
|
}
|
||||||
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
|
||||||
return nil, common.Address{}, gas, ErrInsufficientBalance
|
|
||||||
}
|
|
||||||
nonce := evm.StateDB.GetNonce(caller)
|
|
||||||
if nonce+1 < nonce {
|
|
||||||
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
|
||||||
}
|
|
||||||
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
|
||||||
|
|
||||||
// Charge the contract creation init gas in verkle mode
|
// Charge the contract creation init gas in verkle mode
|
||||||
if evm.chainRules.IsEIP4762 {
|
if evm.chainRules.IsEIP4762 {
|
||||||
|
|
@ -514,6 +521,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
|
||||||
// - the storage is non-empty
|
// - the storage is non-empty
|
||||||
contractHash := evm.StateDB.GetCodeHash(address)
|
contractHash := evm.StateDB.GetCodeHash(address)
|
||||||
storageRoot := evm.StateDB.GetStorageRoot(address)
|
storageRoot := evm.StateDB.GetStorageRoot(address)
|
||||||
|
|
||||||
if evm.StateDB.GetNonce(address) != 0 ||
|
if evm.StateDB.GetNonce(address) != 0 ||
|
||||||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
||||||
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage
|
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage
|
||||||
|
|
@ -601,7 +609,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ret) > 0 {
|
||||||
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
||||||
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -887,7 +887,9 @@ func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
}
|
}
|
||||||
beneficiary := scope.Stack.pop()
|
beneficiary := scope.Stack.pop()
|
||||||
balance := evm.StateDB.GetBalance(scope.Contract.Address())
|
balance := evm.StateDB.GetBalance(scope.Contract.Address())
|
||||||
|
if scope.Contract.Address() != common.BytesToAddress(beneficiary.Bytes()) {
|
||||||
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
|
}
|
||||||
evm.StateDB.SelfDestruct(scope.Contract.Address())
|
evm.StateDB.SelfDestruct(scope.Contract.Address())
|
||||||
if tracer := evm.Config.Tracer; tracer != nil {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
if tracer.OnEnter != nil {
|
if tracer.OnEnter != nil {
|
||||||
|
|
@ -906,8 +908,23 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
||||||
}
|
}
|
||||||
beneficiary := scope.Stack.pop()
|
beneficiary := scope.Stack.pop()
|
||||||
balance := evm.StateDB.GetBalance(scope.Contract.Address())
|
balance := evm.StateDB.GetBalance(scope.Contract.Address())
|
||||||
|
|
||||||
|
createdInTx := !evm.StateDB.ExistBeforeCurTx(scope.Contract.Address())
|
||||||
|
|
||||||
|
if createdInTx {
|
||||||
|
// if the contract is not preexisting, the balance is immediately burned on selfdestruct-to-self
|
||||||
|
evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
|
||||||
|
if scope.Contract.Address() != common.BytesToAddress(beneficiary.Bytes()) {
|
||||||
|
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the contract is preexisting, the balance isn't burned on selfdestruct-to-self
|
||||||
|
if scope.Contract.Address() != common.BytesToAddress(beneficiary.Bytes()) {
|
||||||
evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
|
evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
|
||||||
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
evm.StateDB.SelfDestruct6780(scope.Contract.Address())
|
evm.StateDB.SelfDestruct6780(scope.Contract.Address())
|
||||||
if tracer := evm.Config.Tracer; tracer != nil {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
if tracer.OnEnter != nil {
|
if tracer.OnEnter != nil {
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ type StateDB interface {
|
||||||
// Exist reports whether the given account exists in state.
|
// Exist reports whether the given account exists in state.
|
||||||
// Notably this also returns true for self-destructed accounts within the current transaction.
|
// Notably this also returns true for self-destructed accounts within the current transaction.
|
||||||
Exist(common.Address) bool
|
Exist(common.Address) bool
|
||||||
|
ExistBeforeCurTx(addr common.Address) bool
|
||||||
|
|
||||||
// Empty returns whether the given account is empty. Empty
|
// Empty returns whether the given account is empty. Empty
|
||||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||||
Empty(common.Address) bool
|
Empty(common.Address) bool
|
||||||
|
|
@ -99,6 +101,8 @@ type StateDB interface {
|
||||||
|
|
||||||
AccessEvents() *state.AccessEvents
|
AccessEvents() *state.AccessEvents
|
||||||
|
|
||||||
|
TxIndex() int
|
||||||
|
|
||||||
// Finalise must be invoked at the end of a transaction
|
// Finalise must be invoked at the end of a transaction
|
||||||
Finalise(bool)
|
Finalise(bool)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
||||||
switch {
|
switch {
|
||||||
case rules.IsVerkle:
|
case rules.IsVerkle:
|
||||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||||
|
case rules.IsAmsterdam:
|
||||||
|
return newPragueInstructionSet(), nil
|
||||||
case rules.IsOsaka:
|
case rules.IsOsaka:
|
||||||
return newOsakaInstructionSet(), nil
|
return newOsakaInstructionSet(), nil
|
||||||
case rules.IsPrague:
|
case rules.IsPrague:
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,11 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -505,7 +507,7 @@ func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumber) (*stateless.ExtWitness
|
||||||
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn)
|
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := bc.ProcessBlock(parent.Root, block, false, true)
|
result, err := bc.ProcessBlock(parent.Root, block, false, true, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -525,10 +527,40 @@ func (api *DebugAPI) ExecutionWitnessByHash(hash common.Hash) (*stateless.ExtWit
|
||||||
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
|
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := bc.ProcessBlock(parent.Root, block, false, true)
|
result, err := bc.ProcessBlock(parent.Root, block, false, true, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Witness().ToExtWitness(), nil
|
return result.Witness().ToExtWitness(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBlockAccessList returns a block access list for the given number/hash
|
||||||
|
// or nil if one does not exist.
|
||||||
|
func (api *DebugAPI) GetBlockAccessList(number rpc.BlockNumberOrHash) (*bal.BlockAccessList, error) {
|
||||||
|
var block *types.Block
|
||||||
|
if num := number.BlockNumber; num != nil {
|
||||||
|
block = api.eth.blockchain.GetBlockByNumber(uint64(num.Int64()))
|
||||||
|
} else if hash := number.BlockHash; hash != nil {
|
||||||
|
block = api.eth.blockchain.GetBlockByHash(*hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("block not found")
|
||||||
|
}
|
||||||
|
return block.Body().AccessList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEncodedBlockAccessList returns a block access list corresponding to a
|
||||||
|
// block number/hash in RLP-encoded form. It returns nil if one does not exist.
|
||||||
|
func (api *DebugAPI) GetEncodedBlockAccessList(number rpc.BlockNumberOrHash) ([]byte, error) {
|
||||||
|
bal, err := api.GetBlockAccessList(number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var enc bytes.Buffer
|
||||||
|
if err = bal.EncodeRLP(&enc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return enc.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
// - DATADIR/triedb/verkle.journal
|
// - DATADIR/triedb/verkle.journal
|
||||||
TrieJournalDirectory: stack.ResolvePath("triedb"),
|
TrieJournalDirectory: stack.ResolvePath("triedb"),
|
||||||
StateSizeTracking: config.EnableStateSizeTracking,
|
StateSizeTracking: config.EnableStateSizeTracking,
|
||||||
|
EnableBALForTesting: config.ExperimentalBAL,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if config.VMTrace != "" {
|
if config.VMTrace != "" {
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,13 @@ type Config struct {
|
||||||
// EIP-7966: eth_sendRawTransactionSync timeouts
|
// EIP-7966: eth_sendRawTransactionSync timeouts
|
||||||
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||||
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||||
|
|
||||||
|
// 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
||||||
|
|
|
||||||
|
|
@ -975,6 +975,9 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
|
||||||
if block.Withdrawals() != nil {
|
if block.Withdrawals() != nil {
|
||||||
fields["withdrawals"] = block.Withdrawals()
|
fields["withdrawals"] = block.Withdrawals()
|
||||||
}
|
}
|
||||||
|
if block.Body().AccessList != nil {
|
||||||
|
fields["accessList"] = block.Body().AccessList
|
||||||
|
}
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
||||||
}
|
}
|
||||||
blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals}
|
blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals}
|
||||||
chainHeadReader := &simChainHeadReader{ctx, sim.b}
|
chainHeadReader := &simChainHeadReader{ctx, sim.b}
|
||||||
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
|
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,16 @@ web3._extend({
|
||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [null],
|
inputFormatter: [null],
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getBlockAccessList',
|
||||||
|
call: 'debug_getBlockAccessList',
|
||||||
|
params: 1
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getEncodedBlockAccessList',
|
||||||
|
call: 'debug_getEncodedBlockAccessList',
|
||||||
|
params: 1
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
properties: []
|
properties: []
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -67,13 +68,119 @@ func TestBlockchain(t *testing.T) {
|
||||||
bt.skipLoad(`.*\.meta/.*`)
|
bt.skipLoad(`.*\.meta/.*`)
|
||||||
|
|
||||||
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
|
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
|
// There is also a LegacyTests folder, containing blockchain tests generated
|
||||||
// prior to Istanbul. However, they are all derived from GeneralStateTests,
|
// prior to Istanbul. However, they are all derived from GeneralStateTests,
|
||||||
// which run natively, so there's no reason to run them here.
|
// 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.
|
// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests.
|
||||||
func TestExecutionSpecBlocktests(t *testing.T) {
|
func TestExecutionSpecBlocktests(t *testing.T) {
|
||||||
if !common.FileExist(executionSpecBlockchainTestDir) {
|
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.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/test_system_contract_deployment.json")
|
||||||
|
|
||||||
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
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,
|
// Define all the different flag combinations we should run the tests with,
|
||||||
// picking only one for short tests.
|
// 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]}
|
snapshotConf = []bool{snapshotConf[rand.Int()%2]}
|
||||||
dbschemeConf = []string{dbschemeConf[rand.Int()%2]}
|
dbschemeConf = []string{dbschemeConf[rand.Int()%2]}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, snapshot := range snapshotConf {
|
for _, snapshot := range snapshotConf {
|
||||||
for _, dbscheme := range dbschemeConf {
|
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)
|
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
stdmath "math"
|
stdmath "math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -71,6 +72,7 @@ type btBlock struct {
|
||||||
ExpectException string
|
ExpectException string
|
||||||
Rlp string
|
Rlp string
|
||||||
UncleHeaders []*btHeader
|
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
|
//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
|
BlobGasUsed *uint64
|
||||||
ExcessBlobGas *uint64
|
ExcessBlobGas *uint64
|
||||||
ParentBeaconBlockRoot *common.Hash
|
ParentBeaconBlockRoot *common.Hash
|
||||||
|
BlockAccessListHash *common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
type btHeaderMarshaling struct {
|
type btHeaderMarshaling struct {
|
||||||
|
|
@ -111,11 +114,7 @@ type btHeaderMarshaling struct {
|
||||||
ExcessBlobGas *math.HexOrDecimal64
|
ExcessBlobGas *math.HexOrDecimal64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
func (t *BlockTest) createTestBlockChain(config *params.ChainConfig, snapshotter bool, scheme string, witness, createAndVerifyBAL bool, tracer *tracing.Hooks) (*core.BlockChain, error) {
|
||||||
config, ok := Forks[t.json.Network]
|
|
||||||
if !ok {
|
|
||||||
return UnsupportedForkError{t.json.Network}
|
|
||||||
}
|
|
||||||
// import pre accounts & construct test genesis block & state root
|
// import pre accounts & construct test genesis block & state root
|
||||||
var (
|
var (
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
|
@ -128,7 +127,6 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
||||||
} else {
|
} else {
|
||||||
tconf.HashDB = hashdb.Defaults
|
tconf.HashDB = hashdb.Defaults
|
||||||
}
|
}
|
||||||
// Commit genesis state
|
|
||||||
gspec := t.genesis(config)
|
gspec := t.genesis(config)
|
||||||
|
|
||||||
// if ttd is not specified, set an arbitrary huge value
|
// 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)
|
triedb := triedb.NewDatabase(db, tconf)
|
||||||
gblock, err := gspec.Commit(db, triedb)
|
gblock, err := gspec.Commit(db, triedb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
triedb.Close() // close the db to prevent memory leak
|
triedb.Close() // close the db to prevent memory leak
|
||||||
|
|
||||||
if gblock.Hash() != t.json.Genesis.Hash {
|
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 {
|
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
|
// Wrap the original engine within the beacon-engine
|
||||||
engine := beacon.New(ethash.NewFaker())
|
engine := beacon.New(ethash.NewFaker())
|
||||||
|
|
@ -160,12 +158,28 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
||||||
Tracer: tracer,
|
Tracer: tracer,
|
||||||
StatelessSelfValidation: witness,
|
StatelessSelfValidation: witness,
|
||||||
},
|
},
|
||||||
|
NoPrefetch: true,
|
||||||
|
EnableBALForTesting: createAndVerifyBAL,
|
||||||
}
|
}
|
||||||
if snapshotter {
|
if snapshotter {
|
||||||
options.SnapshotLimit = 1
|
options.SnapshotLimit = 1
|
||||||
options.SnapshotWait = true
|
options.SnapshotWait = true
|
||||||
}
|
}
|
||||||
chain, err := core.NewBlockChain(db, gspec, engine, options)
|
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 {
|
if err != nil {
|
||||||
return err
|
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 %d missing BAL", block.NumberU64())
|
||||||
|
}
|
||||||
|
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 {
|
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,
|
BaseFee: t.json.Genesis.BaseFeePerGas,
|
||||||
BlobGasUsed: t.json.Genesis.BlobGasUsed,
|
BlobGasUsed: t.json.Genesis.BlobGasUsed,
|
||||||
ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
|
ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
|
||||||
|
BlockAccessListHash: t.json.Genesis.BlockAccessListHash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -493,6 +493,38 @@ var Forks = map[string]*params.ChainConfig{
|
||||||
BPO1: bpo1BlobConfig,
|
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": {
|
"OsakaToBPO1AtTime15k": {
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
HomesteadBlock: big.NewInt(0),
|
HomesteadBlock: big.NewInt(0),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue