mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
all: implement eip-7928: block-level access lists
This commit is contained in:
parent
9a8e14e77e
commit
c5cc9c3e6f
46 changed files with 2292 additions and 918 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
)
|
||||
|
||||
var _ = (*executableDataMarshaling)(nil)
|
||||
|
|
@ -17,23 +18,24 @@ var _ = (*executableDataMarshaling)(nil)
|
|||
// MarshalJSON marshals as JSON.
|
||||
func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||
type ExecutableData struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
}
|
||||
var enc ExecutableData
|
||||
enc.ParentHash = e.ParentHash
|
||||
|
|
@ -58,29 +60,31 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
enc.Withdrawals = e.Withdrawals
|
||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||
enc.BlockAccessList = e.BlockAccessList
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||
type ExecutableData struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
}
|
||||
var dec ExecutableData
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -154,5 +158,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
if dec.ExcessBlobGas != nil {
|
||||
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.BlockAccessList != nil {
|
||||
e.BlockAccessList = dec.BlockAccessList
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package engine
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"math/big"
|
||||
"slices"
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ var (
|
|||
// ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new
|
||||
// fields: blobGasUsed and excessBlobGas.
|
||||
PayloadV3 PayloadVersion = 0x3
|
||||
PayloadV4 PayloadVersion = 0x4
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||
|
|
@ -73,23 +75,24 @@ type payloadAttributesMarshaling struct {
|
|||
|
||||
// ExecutableData is the data necessary to execute an EL payload.
|
||||
type ExecutableData struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
|
|
@ -292,54 +295,61 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
requestsHash = &h
|
||||
}
|
||||
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: data.FeeRecipient,
|
||||
Root: data.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: data.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(data.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(data.Number),
|
||||
GasLimit: data.GasLimit,
|
||||
GasUsed: data.GasUsed,
|
||||
Time: data.Timestamp,
|
||||
BaseFee: data.BaseFeePerGas,
|
||||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: data.ExcessBlobGas,
|
||||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
var blockAccessListHash *common.Hash
|
||||
body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, AccessList: data.BlockAccessList}
|
||||
if body.AccessList != nil {
|
||||
balHash := body.AccessList.Hash()
|
||||
blockAccessListHash = &balHash
|
||||
}
|
||||
return types.NewBlockWithHeader(header).
|
||||
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
|
||||
nil
|
||||
|
||||
header := &types.Header{
|
||||
ParentHash: data.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: data.FeeRecipient,
|
||||
Root: data.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: data.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(data.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(data.Number),
|
||||
GasLimit: data.GasLimit,
|
||||
GasUsed: data.GasUsed,
|
||||
Time: data.Timestamp,
|
||||
BaseFee: data.BaseFeePerGas,
|
||||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: data.ExcessBlobGas,
|
||||
BlobGasUsed: data.BlobGasUsed,
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
RequestsHash: requestsHash,
|
||||
BlockAccessListHash: blockAccessListHash,
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(body), nil
|
||||
}
|
||||
|
||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||
// fields from the given block. It assumes the given block is post-merge block.
|
||||
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
|
||||
data := &ExecutableData{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: block.MixDigest(),
|
||||
ExtraData: block.Extra(),
|
||||
Withdrawals: block.Withdrawals(),
|
||||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: block.MixDigest(),
|
||||
ExtraData: block.Extra(),
|
||||
Withdrawals: block.Withdrawals(),
|
||||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
BlockAccessList: block.Body().AccessList,
|
||||
}
|
||||
|
||||
// Add blobs.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||
|
||||
# version:spec-tests-bal v3.0.1
|
||||
# https://github.com/ethereum/execution-spec-tests/releases
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v3.0.1
|
||||
57d0f109f0557ec33d6ecd6cbd77b55415a658aefe583c4035157e1021ae512a fixtures_bal.tar.gz
|
||||
|
||||
# version:golang 1.25.1
|
||||
# https://go.dev/dl/
|
||||
d010c109cee94d80efe681eab46bdea491ac906bf46583c32e9f0dbb0bd1a594 go1.25.1.src.tar.gz
|
||||
|
|
|
|||
19
build/ci.go
19
build/ci.go
|
|
@ -174,6 +174,9 @@ var (
|
|||
|
||||
// This is where the tests should be unpacked.
|
||||
executionSpecTestsDir = "tests/spec-tests"
|
||||
|
||||
// This is where the bal-specific release of the tests should be unpacked.
|
||||
executionSpecTestsBALDir = "tests/spec-tests-bal"
|
||||
)
|
||||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
|
|
@ -381,7 +384,8 @@ func doTest(cmdline []string) {
|
|||
|
||||
// Get test fixtures.
|
||||
if !*short {
|
||||
downloadSpecTestFixtures(csdb, *cachedir)
|
||||
downloadMainnetTestFixtures(csdb, *cachedir)
|
||||
downloadBALSpecTestFixtures(csdb, *cachedir)
|
||||
}
|
||||
|
||||
// Configure the toolchain.
|
||||
|
|
@ -433,10 +437,8 @@ func doTest(cmdline []string) {
|
|||
}
|
||||
}
|
||||
|
||||
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||
func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
func downloadTestFixtures(csdb *download.ChecksumDB, cachedir, base string) string {
|
||||
ext := ".tar.gz"
|
||||
base := "fixtures_develop"
|
||||
archivePath := filepath.Join(cachedir, base+ext)
|
||||
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -447,6 +449,15 @@ func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string
|
|||
return filepath.Join(cachedir, base)
|
||||
}
|
||||
|
||||
// downloadMainnetTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||
func downloadMainnetTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
return downloadTestFixtures(csdb, cachedir, "fixtures")
|
||||
}
|
||||
|
||||
func downloadBALSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
return downloadTestFixtures(csdb, cachedir, "fixtures_bal")
|
||||
}
|
||||
|
||||
// doCheckGenerate ensures that re-generating generated files does not cause
|
||||
// any mutations in the source file tree.
|
||||
func doCheckGenerate() {
|
||||
|
|
|
|||
|
|
@ -272,6 +272,10 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
|
|||
return err
|
||||
}
|
||||
}
|
||||
// Verify the block access list hash is set for post-Amsterdam blocks
|
||||
if chain.Config().IsAmsterdam(header.Number, header.Time) && header.BlockAccessListHash == nil {
|
||||
return fmt.Errorf("block access list hash must be set post-Amsterdam")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -333,6 +337,10 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
}
|
||||
// Withdrawals processing.
|
||||
for _, w := range body.Withdrawals {
|
||||
// always read the target of the withdrawal regardless of the amount
|
||||
// because it must be included in the block access list.
|
||||
state.GetBalance(w.Address)
|
||||
|
||||
// Convert amount from gwei to wei.
|
||||
amount := new(uint256.Int).SetUint64(w.Amount)
|
||||
amount = amount.Mul(amount, uint256.NewInt(params.GWei))
|
||||
|
|
@ -343,9 +351,9 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
||||
// assembling the block.
|
||||
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalization func()) (*types.Block, error) {
|
||||
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)
|
||||
if shanghai {
|
||||
|
|
@ -364,6 +372,10 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
|||
// Assign the final state root to header.
|
||||
header.Root = state.IntermediateRoot(true)
|
||||
|
||||
if onFinalization != nil {
|
||||
onFinalization()
|
||||
}
|
||||
|
||||
// Assemble the final block.
|
||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||
// nor block rewards given, and returns the final block.
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalize func()) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
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.
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
if onFinalize != nil {
|
||||
onFinalize()
|
||||
}
|
||||
|
||||
// Assemble and return the final block for sealing.
|
||||
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,11 +88,13 @@ type Engine interface {
|
|||
Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body)
|
||||
|
||||
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
|
||||
// rewards or process withdrawals) and assembles the final block.
|
||||
// rewards or process withdrawals) and assembles the final block. onFinalize is
|
||||
// an optional callback that is called immediately before the block hash is
|
||||
// computed and embedded in the block header.
|
||||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *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
|
||||
// the result into the given channel.
|
||||
|
|
|
|||
|
|
@ -511,7 +511,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||
// uncle rewards, setting the final state and assembling the block.
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalize func()) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
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.
|
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
if onFinalize != nil {
|
||||
onFinalize()
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
|
|||
bc = s.BPO4
|
||||
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
|
||||
bc = s.BPO3
|
||||
case cfg.IsAmsterdam(london, time) && s.Amsterdam != nil:
|
||||
bc = s.Amsterdam
|
||||
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
|
||||
bc = s.BPO2
|
||||
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
||||
|
|
|
|||
114
core/block_access_list_tracer.go
Normal file
114
core/block_access_list_tracer.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
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 gathers state accesses/mutations during the execution
|
||||
// of a block. It is used for construction block access lists.
|
||||
//
|
||||
// TODO: enable the BlockAccessListTracer to wrap another tracer.
|
||||
type BlockAccessListTracer struct {
|
||||
builder *bal.AccessListBuilder
|
||||
|
||||
// the access list index that changes are currently being recorded into
|
||||
balIdx uint16
|
||||
|
||||
// the number of system calls that have been invoked, used when building
|
||||
// an access list to determine if the system calls being executed are
|
||||
// before/after the block transactions.
|
||||
sysCallCount int
|
||||
}
|
||||
|
||||
// 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,
|
||||
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,
|
||||
OnSystemCallEnd: balTracer.OnSystemCallEnd,
|
||||
}
|
||||
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.ConstructionBlockAccessList {
|
||||
return &a.builder.FinalizedAccesses
|
||||
}
|
||||
|
||||
func (a *BlockAccessListTracer) OnSystemCallEnd() {
|
||||
// finalize the post-block changes in OnBlockFinalization to account for
|
||||
// the EIP-4895 withdrawals which occur after the last system contracts
|
||||
// are executed.
|
||||
a.sysCallCount++
|
||||
if a.sysCallCount == 2 {
|
||||
a.builder.FinaliseIdxChanges(a.balIdx)
|
||||
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,18 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
|||
}
|
||||
}
|
||||
|
||||
// check that the block access list, if present:
|
||||
// * hashes to the same value reported in the block header
|
||||
// * is well-formed
|
||||
if v.config.IsAmsterdam(block.Number(), block.Time()) {
|
||||
if block.Body().AccessList != nil {
|
||||
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(len(block.Transactions())); err != nil {
|
||||
return fmt.Errorf("invalid block access list: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ancestor block must be known.
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
|
|
|
|||
|
|
@ -2173,6 +2173,17 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}()
|
||||
}
|
||||
|
||||
// BAL Tracer used for creating BALs in ProcessBlock in testing path only
|
||||
var balTracer *BlockAccessListTracer
|
||||
|
||||
isAmsterdam := bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
|
||||
// Process block using the parent state as reference point
|
||||
if isAmsterdam {
|
||||
balTracer, bc.cfg.VmConfig.Tracer = NewBlockAccessListTracer()
|
||||
defer func() {
|
||||
bc.cfg.VmConfig.Tracer = nil
|
||||
}()
|
||||
}
|
||||
// Process block using the parent state as reference point
|
||||
pstart := time.Now()
|
||||
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
|
||||
|
|
@ -2182,6 +2193,13 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}
|
||||
ptime := time.Since(pstart)
|
||||
|
||||
if isAmsterdam {
|
||||
balTracer.OnBlockFinalization()
|
||||
}
|
||||
|
||||
// unset the BAL-creation tracer (dirty)
|
||||
bc.cfg.VmConfig.Tracer = nil
|
||||
|
||||
vstart := time.Now()
|
||||
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
|
||||
bc.reportBadBlock(block, res, err)
|
||||
|
|
@ -2189,6 +2207,22 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}
|
||||
vtime := time.Since(vstart)
|
||||
|
||||
if isAmsterdam {
|
||||
computedAccessList := balTracer.AccessList().ToEncodingObj()
|
||||
computedAccessListHash := computedAccessList.Hash()
|
||||
|
||||
if *block.Header().BlockAccessListHash != computedAccessListHash {
|
||||
err := fmt.Errorf("block header access list hash mismatch with computed (header=%x computed=%x)", *block.Header().BlockAccessListHash, computedAccessListHash)
|
||||
bc.reportBadBlock(block, res, err)
|
||||
return nil, err
|
||||
}
|
||||
if block.Body().AccessList != nil && block.Body().AccessList.Hash() != computedAccessListHash {
|
||||
err := fmt.Errorf("block access list hash mismatch (remote=%x computed=%x)", block.Body().AccessList.Hash(), computedAccessListHash)
|
||||
bc.reportBadBlock(block, res, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If witnesses was generated and stateless self-validation requested, do
|
||||
// that now. Self validation should *never* run in production, it's more of
|
||||
// a tight integration to enable running *all* consensus tests through the
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,21 +19,22 @@ var _ = (*genesisSpecMarshaling)(nil)
|
|||
// MarshalJSON marshals as JSON.
|
||||
func (g Genesis) MarshalJSON() ([]byte, error) {
|
||||
type Genesis struct {
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash common.Hash `json:"mixHash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash common.Hash `json:"mixHash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"`
|
||||
}
|
||||
var enc Genesis
|
||||
enc.Config = g.Config
|
||||
|
|
@ -56,27 +57,29 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
|||
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas)
|
||||
enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed)
|
||||
enc.BlockAccessListHash = g.BlockAccessListHash
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
type Genesis struct {
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash *common.Hash `json:"mixHash"`
|
||||
Coinbase *common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash *common.Hash `json:"mixHash"`
|
||||
Coinbase *common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"`
|
||||
}
|
||||
var dec Genesis
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -133,5 +136,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
|||
if dec.BlobGasUsed != nil {
|
||||
g.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
|
||||
}
|
||||
if dec.BlockAccessListHash != nil {
|
||||
g.BlockAccessListHash = dec.BlockAccessListHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,12 +67,13 @@ type Genesis struct {
|
|||
|
||||
// These fields are used for consensus tests. Please don't use them
|
||||
// in actual genesis blocks.
|
||||
Number uint64 `json:"number"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
|
||||
Number uint64 `json:"number"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
|
||||
BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"` // EIP-7928
|
||||
}
|
||||
|
||||
// copy copies the genesis.
|
||||
|
|
@ -122,6 +123,7 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
|
|||
genesis.BaseFee = genesisHeader.BaseFee
|
||||
genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas
|
||||
genesis.BlobGasUsed = genesisHeader.BlobGasUsed
|
||||
genesis.BlockAccessListHash = genesisHeader.BlockAccessListHash
|
||||
|
||||
return &genesis, nil
|
||||
}
|
||||
|
|
@ -485,18 +487,19 @@ func (g *Genesis) ToBlock() *types.Block {
|
|||
// toBlockWithRoot constructs the genesis block with the given genesis state root.
|
||||
func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
|
||||
head := &types.Header{
|
||||
Number: new(big.Int).SetUint64(g.Number),
|
||||
Nonce: types.EncodeNonce(g.Nonce),
|
||||
Time: g.Timestamp,
|
||||
ParentHash: g.ParentHash,
|
||||
Extra: g.ExtraData,
|
||||
GasLimit: g.GasLimit,
|
||||
GasUsed: g.GasUsed,
|
||||
BaseFee: g.BaseFee,
|
||||
Difficulty: g.Difficulty,
|
||||
MixDigest: g.Mixhash,
|
||||
Coinbase: g.Coinbase,
|
||||
Root: root,
|
||||
Number: new(big.Int).SetUint64(g.Number),
|
||||
Nonce: types.EncodeNonce(g.Nonce),
|
||||
Time: g.Timestamp,
|
||||
ParentHash: g.ParentHash,
|
||||
Extra: g.ExtraData,
|
||||
GasLimit: g.GasLimit,
|
||||
GasUsed: g.GasUsed,
|
||||
BaseFee: g.BaseFee,
|
||||
Difficulty: g.Difficulty,
|
||||
MixDigest: g.Mixhash,
|
||||
Coinbase: g.Coinbase,
|
||||
BlockAccessListHash: g.BlockAccessListHash,
|
||||
Root: root,
|
||||
}
|
||||
if g.GasLimit == 0 {
|
||||
head.GasLimit = params.GenesisGasLimit
|
||||
|
|
|
|||
|
|
@ -59,22 +59,37 @@ func (s *hookedStateDB) IsNewContract(addr common.Address) bool {
|
|||
}
|
||||
|
||||
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.GetBalance(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetNonce(addr common.Address) uint64 {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.GetNonce(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.GetCodeHash(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetCode(addr common.Address) []byte {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.GetCode(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.GetCodeSize(addr)
|
||||
}
|
||||
|
||||
|
|
@ -91,14 +106,23 @@ func (s *hookedStateDB) GetRefund() uint64 {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.GetStorageRoot(addr)
|
||||
}
|
||||
|
||||
|
|
@ -111,14 +135,23 @@ func (s *hookedStateDB) SetTransientState(addr common.Address, key, value common
|
|||
}
|
||||
|
||||
func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.HasSelfDestructed(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Exist(addr common.Address) bool {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.Exist(addr)
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Empty(addr common.Address) bool {
|
||||
if s.hooks.OnAccountRead != nil {
|
||||
s.hooks.OnAccountRead(addr)
|
||||
}
|
||||
return s.inner.Empty(addr)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,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)
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
|
||||
if hooks := cfg.Tracer; hooks != nil && hooks.OnBlockFinalization != nil {
|
||||
hooks.OnBlockFinalization()
|
||||
}
|
||||
|
||||
return &ProcessResult{
|
||||
Receipts: receipts,
|
||||
Requests: requests,
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@ package core
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -29,6 +26,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// ExecutionResult includes all output after executing given evm
|
||||
|
|
@ -557,6 +556,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
} else {
|
||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTipU256)
|
||||
|
||||
// always read the coinbase account to include it in the BAL (TODO check this is actually part of the spec)
|
||||
st.state.GetBalance(st.evm.Context.Coinbase)
|
||||
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||
|
||||
// add the coinbase to the witness iff the fee is greater than 0
|
||||
|
|
@ -617,16 +620,22 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
|
|||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||
}
|
||||
|
||||
prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||
|
||||
// Update nonce and account code.
|
||||
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
||||
if auth.Address == (common.Address{}) {
|
||||
// Delegation to zero address means clear.
|
||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||
if isDelegated {
|
||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise install delegation to auth.Address.
|
||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||
// install delegation to auth.Address if the delegation changed
|
||||
if !isDelegated || auth.Address != prevDelegation {
|
||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,6 @@ type (
|
|||
CloseHook = func()
|
||||
|
||||
// BlockStartHook is called before executing `block`.
|
||||
// `td` is the total difficulty prior to `block`.
|
||||
BlockStartHook = func(event BlockEvent)
|
||||
|
||||
// BlockEndHook is called after executing a block.
|
||||
|
|
@ -192,24 +191,25 @@ type (
|
|||
// GenesisBlockHook is called when the genesis block is being processed.
|
||||
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
|
||||
|
||||
// OnSystemCallStartHook is called when a system call is about to be executed. Today,
|
||||
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
|
||||
// beacon block root.
|
||||
// OnSystemCallStartHook is called when a system call is about to be executed.
|
||||
// Today, this hook is invoked when the EIP-4788 system call is about to be
|
||||
// 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`
|
||||
// as well as state hooks between this hook and the `OnSystemCallEndHook`.
|
||||
// After this hook, the EVM call tracing will happened as usual so you will
|
||||
// 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
|
||||
// will not be invoked.
|
||||
// Note that system call happens outside normal transaction execution, so
|
||||
// the `OnTxStart/OnTxEnd` hooks will not be invoked.
|
||||
OnSystemCallStartHook = func()
|
||||
|
||||
// OnSystemCallStartHookV2 is called when a system call is about to be executed. Refer
|
||||
// to `OnSystemCallStartHook` for more information.
|
||||
// OnSystemCallStartHookV2 is called when a system call is about to be executed.
|
||||
// Refer to `OnSystemCallStartHook` for more information.
|
||||
OnSystemCallStartHookV2 = func(vm *VMContext)
|
||||
|
||||
// OnSystemCallEndHook is called when a system call has finished executing. Today,
|
||||
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
|
||||
// beacon block root.
|
||||
// OnSystemCallEndHook is called when a system call has finished executing.
|
||||
// Today, this hook is invoked when the EIP-4788 system call is about to be
|
||||
// executed to set the beacon block root.
|
||||
OnSystemCallEndHook = func()
|
||||
|
||||
// StateUpdateHook is called after state is committed for a block.
|
||||
|
|
@ -239,9 +239,17 @@ type (
|
|||
// StorageChangeHook is called when the storage of an account changes.
|
||||
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 = 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 = func(blockNumber uint64, hash common.Hash)
|
||||
)
|
||||
|
|
@ -255,6 +263,7 @@ type Hooks struct {
|
|||
OnOpcode OpcodeHook
|
||||
OnFault FaultHook
|
||||
OnGasChange GasChangeHook
|
||||
|
||||
// Chain events
|
||||
OnBlockchainInit BlockchainInitHook
|
||||
OnClose CloseHook
|
||||
|
|
@ -266,14 +275,23 @@ type Hooks struct {
|
|||
OnSystemCallStartV2 OnSystemCallStartHookV2
|
||||
OnSystemCallEnd OnSystemCallEndHook
|
||||
OnStateUpdate StateUpdateHook
|
||||
// State events
|
||||
OnBalanceChange BalanceChangeHook
|
||||
OnNonceChange NonceChangeHook
|
||||
OnNonceChangeV2 NonceChangeHookV2
|
||||
OnCodeChange CodeChangeHook
|
||||
OnCodeChangeV2 CodeChangeHookV2
|
||||
OnStorageChange StorageChangeHook
|
||||
OnLog LogHook
|
||||
|
||||
OnBlockFinalization func() // called after post-tx system contracts and consensus finalization are invoked
|
||||
|
||||
// State mutation events
|
||||
OnBalanceChange BalanceChangeHook
|
||||
OnNonceChange NonceChangeHook
|
||||
OnNonceChangeV2 NonceChangeHookV2
|
||||
OnCodeChange CodeChangeHook
|
||||
OnCodeChangeV2 CodeChangeHookV2
|
||||
OnStorageChange StorageChangeHook
|
||||
OnLog LogHook
|
||||
OnSelfDestructChange SelfDestructHook
|
||||
|
||||
// State access events
|
||||
OnAccountRead AccountReadHook
|
||||
OnStorageRead StorageReadHook
|
||||
|
||||
// Block hash read
|
||||
OnBlockHashRead BlockHashReadHook
|
||||
}
|
||||
|
|
@ -290,57 +308,74 @@ const (
|
|||
// Issuance
|
||||
// BalanceIncreaseRewardMineUncle is a reward for mining an uncle block.
|
||||
BalanceIncreaseRewardMineUncle BalanceChangeReason = 1
|
||||
|
||||
// BalanceIncreaseRewardMineBlock is a reward for mining a block.
|
||||
BalanceIncreaseRewardMineBlock BalanceChangeReason = 2
|
||||
|
||||
// BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain.
|
||||
BalanceIncreaseWithdrawal BalanceChangeReason = 3
|
||||
|
||||
// BalanceIncreaseGenesisBalance is ether allocated at the genesis block.
|
||||
BalanceIncreaseGenesisBalance BalanceChangeReason = 4
|
||||
|
||||
// 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
|
||||
|
||||
// BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction.
|
||||
// Part of this gas will be burnt as per EIP-1559 rules.
|
||||
BalanceDecreaseGasBuy BalanceChangeReason = 6
|
||||
|
||||
// BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution.
|
||||
BalanceIncreaseGasReturn BalanceChangeReason = 7
|
||||
|
||||
// DAO fork
|
||||
// BalanceIncreaseDaoContract is ether sent to the DAO refund contract.
|
||||
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
|
||||
|
||||
// BalanceChangeTransfer is ether transferred via a call.
|
||||
// it is a decrease for the sender and an increase for the recipient.
|
||||
BalanceChangeTransfer BalanceChangeReason = 10
|
||||
|
||||
// BalanceChangeTouchAccount is a transfer of zero value. It is only there to
|
||||
// touch-create an account.
|
||||
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
|
||||
|
||||
// BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct.
|
||||
BalanceDecreaseSelfdestruct BalanceChangeReason = 13
|
||||
|
||||
// BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed
|
||||
// account within the same tx (captured at end of tx).
|
||||
// Note it doesn't account for a self-destruct which appoints itself as recipient.
|
||||
BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14
|
||||
|
||||
// BalanceChangeRevert is emitted when the balance is reverted back to a previous value due to call failure.
|
||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
||||
// BalanceChangeRevert is emitted when the balance is reverted back to a
|
||||
// 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
|
||||
)
|
||||
|
||||
// GasChangeReason is used to indicate the reason for a gas change, useful
|
||||
// for tracing and reporting.
|
||||
//
|
||||
// There is essentially two types of gas changes, those that can be emitted once per transaction
|
||||
// and those that can be emitted on a call basis, so possibly multiple times per transaction.
|
||||
// There is essentially two types of gas changes, those that can be emitted
|
||||
// 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
|
||||
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
|
||||
// They can be recognized easily by their name, those that start with `GasChangeTx`
|
||||
// are emitted once per transaction, while those that start with `GasChangeCall`
|
||||
// are emitted on a call basis.
|
||||
type GasChangeReason byte
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=GasChangeReason -trimprefix=GasChange -output gen_gas_change_reason_stringer.go
|
||||
|
|
@ -348,61 +383,100 @@ type GasChangeReason byte
|
|||
const (
|
||||
GasChangeUnspecified GasChangeReason = 0
|
||||
|
||||
// GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
|
||||
// one such gas change per transaction.
|
||||
// GasChangeTxInitialBalance is the initial balance for the call which will
|
||||
// be equal to the gasLimit of the call. There is only one such gas change
|
||||
// per transaction.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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 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 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
|
||||
|
||||
// GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
|
||||
// one such gas change per call.
|
||||
// GasChangeCallInitialBalance is the initial balance for the call which
|
||||
// will be equal to the gasLimit of the call. There is only one such gas
|
||||
// change per call.
|
||||
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
|
||||
// will be emitted.
|
||||
|
||||
// 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 will be emitted.
|
||||
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.
|
||||
// If there was no gas left to be refunded, no such even will be emitted.
|
||||
|
||||
// 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. If there was no gas left to be refunded, no such event
|
||||
// will be emitted.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
|
||||
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
||||
// it will be "manually" tracked by a direct emit of the gas change event.
|
||||
// GasChangeIgnored is a special value that can be used to indicate that
|
||||
// the gas change should be ignored as it will be "manually" tracked by
|
||||
// a direct emit of the gas change event.
|
||||
GasChangeIgnored GasChangeReason = 0xFF
|
||||
)
|
||||
|
||||
|
|
@ -426,11 +500,12 @@ const (
|
|||
// NonceChangeNewContract is the nonce change of a newly created contract.
|
||||
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
|
||||
|
||||
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
||||
// NonceChangeRevert is emitted when the nonce is reverted back to a previous
|
||||
// value due to call failure. It is only emitted when the tracer has opted in
|
||||
// to use the journaling wrapper (WrapWithJournal).
|
||||
NonceChangeRevert NonceChangeReason = 6
|
||||
|
||||
// NonceChangeSelfdestruct is emitted when the nonce is reset to zero due to a self-destruct
|
||||
|
|
@ -445,22 +520,26 @@ type CodeChangeReason byte
|
|||
const (
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
// CodeChangeAuthorization is when code is set via EIP-7702 Set Code Authorization.
|
||||
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
|
||||
|
||||
// CodeChangeSelfDestruct is when contract code is cleared due to self-destruct.
|
||||
CodeChangeSelfDestruct CodeChangeReason = 5
|
||||
|
||||
// CodeChangeRevert is emitted when the code is reverted back to a previous value due to call failure.
|
||||
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
|
||||
// CodeChangeRevert is emitted when the code is reverted back to a previous
|
||||
// value due to call failure. It is only emitted when the tracer has opted
|
||||
// in to use the journaling wrapper (WrapWithJournal).
|
||||
CodeChangeRevert CodeChangeReason = 6
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
|
|||
return nil, errors.New("wrapping nil tracer")
|
||||
}
|
||||
// No state change to journal, return the wrapped hooks as is
|
||||
if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnCodeChangeV2 == nil && hooks.OnStorageChange == nil {
|
||||
if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil &&
|
||||
hooks.OnCodeChange == nil && hooks.OnCodeChangeV2 == nil && hooks.OnStorageChange == nil {
|
||||
// TODO(sina) hooks.OnLog should also be handled here
|
||||
return hooks, nil
|
||||
}
|
||||
if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil {
|
||||
|
|
@ -56,11 +58,14 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
|
|||
wrapped := *hooks
|
||||
|
||||
// Create journal
|
||||
j := &journal{hooks: hooks}
|
||||
j := &journal{
|
||||
hooks: hooks,
|
||||
}
|
||||
// Scope hooks need to be re-implemented.
|
||||
wrapped.OnTxEnd = j.OnTxEnd
|
||||
wrapped.OnEnter = j.OnEnter
|
||||
wrapped.OnExit = j.OnExit
|
||||
|
||||
// Wrap state change hooks.
|
||||
if hooks.OnBalanceChange != nil {
|
||||
wrapped.OnBalanceChange = j.OnBalanceChange
|
||||
|
|
@ -69,6 +74,7 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
|
|||
// Regardless of which hook version is used in the tracer,
|
||||
// the journal will want to capture the nonce change reason.
|
||||
wrapped.OnNonceChangeV2 = j.OnNonceChangeV2
|
||||
|
||||
// A precaution to ensure EVM doesn't call both hooks.
|
||||
wrapped.OnNonceChange = nil
|
||||
}
|
||||
|
|
@ -81,7 +87,6 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
|
|||
if hooks.OnStorageChange != nil {
|
||||
wrapped.OnStorageChange = j.OnStorageChange
|
||||
}
|
||||
|
||||
return &wrapped, nil
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +153,11 @@ func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, re
|
|||
}
|
||||
|
||||
func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) {
|
||||
j.entries = append(j.entries, balanceChange{addr: addr, prev: prev, new: new})
|
||||
j.entries = append(j.entries, balanceChange{
|
||||
addr: addr,
|
||||
prev: prev,
|
||||
new: new,
|
||||
})
|
||||
if j.hooks.OnBalanceChange != nil {
|
||||
j.hooks.OnBalanceChange(addr, prev, new, reason)
|
||||
}
|
||||
|
|
@ -158,7 +167,11 @@ func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason
|
|||
// When a contract is created, the nonce of the creator is incremented.
|
||||
// This change is not reverted when the creation fails.
|
||||
if reason != NonceChangeContractCreator {
|
||||
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
|
||||
j.entries = append(j.entries, nonceChange{
|
||||
addr: addr,
|
||||
prev: prev,
|
||||
new: new,
|
||||
})
|
||||
}
|
||||
if j.hooks.OnNonceChangeV2 != nil {
|
||||
j.hooks.OnNonceChangeV2(addr, prev, new, reason)
|
||||
|
|
@ -194,7 +207,12 @@ func (j *journal) OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash,
|
|||
}
|
||||
|
||||
func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) {
|
||||
j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new})
|
||||
j.entries = append(j.entries, storageChange{
|
||||
addr: addr,
|
||||
slot: slot,
|
||||
prev: prev,
|
||||
new: new,
|
||||
})
|
||||
if j.hooks.OnStorageChange != nil {
|
||||
j.hooks.OnStorageChange(addr, slot, prev, new)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
t.Fatalf("failed to wrap test tracer: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
|
@ -18,165 +18,602 @@ package bal
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"maps"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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 {
|
||||
// the prestate values for any state modified at this index. this is state
|
||||
// values before the execution of this 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 a constructionAccountAccess
|
||||
// and terminating a frame pops the last entry and merges it into the
|
||||
// constructionAccountAccess of the parent frame.
|
||||
accesses []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.accesses[len(c.accesses)-1][address]; !ok {
|
||||
c.accesses[len(c.accesses)-1][address] = &constructionAccountAccess{}
|
||||
}
|
||||
acctAccesses := c.accesses[len(c.accesses)-1][address]
|
||||
acctAccesses.StorageRead(key)
|
||||
}
|
||||
|
||||
func (c *idxAccessListBuilder) accountRead(address common.Address) {
|
||||
if _, ok := c.accesses[len(c.accesses)-1][address]; !ok {
|
||||
c.accesses[len(c.accesses)-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.accesses[len(c.accesses)-1][address]; !ok {
|
||||
c.accesses[len(c.accesses)-1][address] = &constructionAccountAccess{}
|
||||
}
|
||||
acctAccesses := c.accesses[len(c.accesses)-1][address]
|
||||
acctAccesses.StorageWrite(key, 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.accesses[len(c.accesses)-1][address]; !ok {
|
||||
c.accesses[len(c.accesses)-1][address] = &constructionAccountAccess{}
|
||||
}
|
||||
acctAccesses := c.accesses[len(c.accesses)-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 {
|
||||
if prev == nil {
|
||||
prev = []byte{}
|
||||
}
|
||||
c.prestates[address].code = prev
|
||||
}
|
||||
if _, ok := c.accesses[len(c.accesses)-1][address]; !ok {
|
||||
c.accesses[len(c.accesses)-1][address] = &constructionAccountAccess{}
|
||||
}
|
||||
acctAccesses := c.accesses[len(c.accesses)-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
|
||||
// of the current call are retained in the block access list as state reads.
|
||||
func (c *idxAccessListBuilder) selfDestruct(address common.Address) {
|
||||
access := c.accesses[len(c.accesses)-1][address]
|
||||
if len(access.storageMutations) != 0 && access.storageReads == nil {
|
||||
access.storageReads = make(map[common.Hash]struct{})
|
||||
}
|
||||
for key, _ := range access.storageMutations {
|
||||
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.accesses[len(c.accesses)-1][address]; !ok {
|
||||
c.accesses[len(c.accesses)-1][address] = &constructionAccountAccess{}
|
||||
}
|
||||
acctAccesses := c.accesses[len(c.accesses)-1][address]
|
||||
acctAccesses.NonceChange(cur)
|
||||
}
|
||||
|
||||
// enterScope is called after a new EVM call frame has been entered.
|
||||
func (c *idxAccessListBuilder) enterScope() {
|
||||
c.accesses = append(c.accesses, make(map[common.Address]*constructionAccountAccess))
|
||||
}
|
||||
|
||||
// exitCall 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
|
||||
func (c *idxAccessListBuilder) exitCall(evmErr bool) {
|
||||
childAccessList := c.accesses[len(c.accesses)-1]
|
||||
parentAccessList := c.accesses[len(c.accesses)-2]
|
||||
|
||||
for addr, childAccess := range childAccessList {
|
||||
if _, ok := parentAccessList[addr]; ok {
|
||||
} else {
|
||||
parentAccessList[addr] = &constructionAccountAccess{}
|
||||
}
|
||||
if evmErr {
|
||||
// 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)
|
||||
parentAccessList[addr].MergeReads(childAccess)
|
||||
} else {
|
||||
parentAccessList[addr].Merge(childAccess)
|
||||
}
|
||||
}
|
||||
|
||||
c.accesses = c.accesses[:len(c.accesses)-1]
|
||||
}
|
||||
|
||||
// finalise returns the net state mutations at the access list index as well as
|
||||
// state which was accessed. The idxAccessListBuilder instance must 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.accesses[0] {
|
||||
// remove any reported mutations from the access list with no net difference vs the index 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 index 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()
|
||||
|
||||
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
|
||||
|
||||
// if any of the newly-mutated storage slots were previously
|
||||
// accessed, they must be removed from the accessed state set.
|
||||
//
|
||||
// TODO: commenting this 'if' results in no test failures.
|
||||
// double-check that this edge-case was fixed by a future
|
||||
// release of the eest BAL tests.
|
||||
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.exitCall(executionErr)
|
||||
}
|
||||
|
||||
// CodeChange contains the runtime bytecode deployed at an address and the
|
||||
// transaction index where the deployment took place.
|
||||
type CodeChange struct {
|
||||
TxIndex uint16
|
||||
Code []byte `json:"code,omitempty"`
|
||||
TxIdx uint16
|
||||
Code []byte `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
// ConstructionAccountAccess contains post-block account state for mutations as well as
|
||||
// all storage keys that were read during execution. It is used when building block
|
||||
// access list during execution.
|
||||
type ConstructionAccountAccess struct {
|
||||
// ConstructionAccountAccesses contains post-block account state for mutated
|
||||
// values as well as all storage keys that were read during execution.
|
||||
// It is used when building block access list during execution.
|
||||
type ConstructionAccountAccesses struct {
|
||||
// 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
|
||||
// where the modification occurred.
|
||||
StorageWrites map[common.Hash]map[uint16]common.Hash `json:"storageWrites,omitempty"`
|
||||
// that were modified in a block, keyed by the slot key and the bal indices
|
||||
// where modifications occurred.
|
||||
StorageWrites map[common.Hash]map[uint16]common.Hash
|
||||
|
||||
// StorageReads is the set of slot keys that were accessed during block
|
||||
// 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.
|
||||
StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"`
|
||||
StorageReads map[common.Hash]struct{}
|
||||
|
||||
// BalanceChanges contains the post-transaction balances of an account,
|
||||
// keyed by transaction indices where it was changed.
|
||||
BalanceChanges map[uint16]*uint256.Int `json:"balanceChanges,omitempty"`
|
||||
// BalanceChanges contains the post-state balances of an account,
|
||||
// keyed by bal indices where it was changed.
|
||||
BalanceChanges map[uint16]*uint256.Int
|
||||
|
||||
// NonceChanges contains the post-state nonce values of an account keyed
|
||||
// by tx index.
|
||||
NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"`
|
||||
// by bal indices where it changed.
|
||||
NonceChanges map[uint16]uint64
|
||||
|
||||
// CodeChange is only set for contract accounts which were deployed in
|
||||
// the block.
|
||||
CodeChange *CodeChange `json:"codeChange,omitempty"`
|
||||
// NonceChanges contains the post-state code values of an account keyed
|
||||
// by bal indices where it changed.
|
||||
CodeChanges map[uint16]CodeChange
|
||||
}
|
||||
|
||||
// NewConstructionAccountAccess initializes the account access object.
|
||||
func NewConstructionAccountAccess() *ConstructionAccountAccess {
|
||||
return &ConstructionAccountAccess{
|
||||
StorageWrites: make(map[common.Hash]map[uint16]common.Hash),
|
||||
StorageReads: make(map[common.Hash]struct{}),
|
||||
BalanceChanges: make(map[uint16]*uint256.Int),
|
||||
NonceChanges: make(map[uint16]uint64),
|
||||
}
|
||||
// constructionAccountAccess contains fields for an account which were modified
|
||||
// during execution of the current access list index.
|
||||
// It also accumulates a set of storage slots which were accessed but not
|
||||
// modified.
|
||||
type constructionAccountAccess struct {
|
||||
code []byte
|
||||
nonce *uint64
|
||||
balance *uint256.Int
|
||||
|
||||
storageMutations map[common.Hash]common.Hash
|
||||
storageReads map[common.Hash]struct{}
|
||||
}
|
||||
|
||||
// ConstructionBlockAccessList contains post-block modified state and some state accessed
|
||||
// in execution (account addresses and storage keys).
|
||||
type ConstructionBlockAccessList struct {
|
||||
Accounts map[common.Address]*ConstructionAccountAccess
|
||||
}
|
||||
|
||||
// NewConstructionBlockAccessList instantiates an empty access list.
|
||||
func NewConstructionBlockAccessList() ConstructionBlockAccessList {
|
||||
return ConstructionBlockAccessList{
|
||||
Accounts: make(map[common.Address]*ConstructionAccountAccess),
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// AccountRead records the address of an account that has been read during execution.
|
||||
func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) {
|
||||
if _, ok := b.Accounts[addr]; !ok {
|
||||
b.Accounts[addr] = NewConstructionAccountAccess()
|
||||
if other.nonce != nil {
|
||||
c.nonce = other.nonce
|
||||
}
|
||||
}
|
||||
|
||||
// StorageRead records a storage key read during execution.
|
||||
func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) {
|
||||
if _, ok := b.Accounts[address]; !ok {
|
||||
b.Accounts[address] = NewConstructionAccountAccess()
|
||||
if other.balance != nil {
|
||||
c.balance = other.balance
|
||||
}
|
||||
if _, ok := b.Accounts[address].StorageWrites[key]; ok {
|
||||
return
|
||||
}
|
||||
b.Accounts[address].StorageReads[key] = struct{}{}
|
||||
}
|
||||
|
||||
// StorageWrite records the post-transaction value of a mutated storage slot.
|
||||
// The storage slot is removed from the list of read slots.
|
||||
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 (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) {
|
||||
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
|
||||
// nonce was incremented.
|
||||
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
|
||||
// balance changed.
|
||||
func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) {
|
||||
if _, ok := b.Accounts[address]; !ok {
|
||||
b.Accounts[address] = NewConstructionAccountAccess()
|
||||
}
|
||||
b.Accounts[address].BalanceChanges[txIdx] = balance.Clone()
|
||||
}
|
||||
|
||||
// PrettyPrint returns a human-readable representation of the access list
|
||||
func (b *ConstructionBlockAccessList) PrettyPrint() string {
|
||||
enc := b.toEncodingObj()
|
||||
return enc.PrettyPrint()
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the access list.
|
||||
func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
|
||||
res := NewConstructionBlockAccessList()
|
||||
for addr, aa := range b.Accounts {
|
||||
var aaCopy ConstructionAccountAccess
|
||||
|
||||
slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites))
|
||||
for key, m := range aa.StorageWrites {
|
||||
slotWrites[key] = maps.Clone(m)
|
||||
if other.storageMutations != nil {
|
||||
if c.storageMutations == nil {
|
||||
c.storageMutations = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
aaCopy.StorageWrites = slotWrites
|
||||
aaCopy.StorageReads = maps.Clone(aa.StorageReads)
|
||||
|
||||
balances := make(map[uint16]*uint256.Int, len(aa.BalanceChanges))
|
||||
for index, balance := range aa.BalanceChanges {
|
||||
balances[index] = balance.Clone()
|
||||
for key, val := range other.storageMutations {
|
||||
c.storageMutations[key] = val
|
||||
delete(c.storageReads, key)
|
||||
}
|
||||
aaCopy.BalanceChanges = balances
|
||||
aaCopy.NonceChanges = maps.Clone(aa.NonceChanges)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aa.CodeChange != nil {
|
||||
aaCopy.CodeChange = &CodeChange{
|
||||
TxIndex: aa.CodeChange.TxIndex,
|
||||
Code: bytes.Clone(aa.CodeChange.Code),
|
||||
// MergeReads merges accesses from a reverted execution from:
|
||||
// * any reads/writes from the reverted frame which weren't mutated
|
||||
// in the current frame, are merged into the current frame as reads.
|
||||
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{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *constructionAccountAccess) StorageRead(key common.Hash) {
|
||||
if c.storageReads == nil {
|
||||
c.storageReads = make(map[common.Hash]struct{})
|
||||
}
|
||||
if _, ok := c.storageMutations[key]; !ok {
|
||||
c.storageReads[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *constructionAccountAccess) StorageWrite(key, newVal common.Hash) {
|
||||
if c.storageMutations == nil {
|
||||
c.storageMutations = make(map[common.Hash]common.Hash)
|
||||
}
|
||||
c.storageMutations[key] = newVal
|
||||
// a key can be first read and later written, but it must only show up
|
||||
// in either read or write sets, not both.
|
||||
//
|
||||
// the caller should not
|
||||
// call StorageRead on a slot that was already written
|
||||
delete(c.storageReads, key)
|
||||
}
|
||||
|
||||
func (c *constructionAccountAccess) BalanceChange(cur *uint256.Int) {
|
||||
c.balance = cur
|
||||
}
|
||||
|
||||
func (c *constructionAccountAccess) CodeChange(cur []byte) {
|
||||
c.code = cur
|
||||
}
|
||||
|
||||
func (c *constructionAccountAccess) NonceChange(cur uint64) {
|
||||
c.nonce = &cur
|
||||
}
|
||||
|
||||
type ConstructionBlockAccessList map[common.Address]*ConstructionAccountAccesses
|
||||
|
||||
// AccessListBuilder is used to build an EIP-7928 block access list
|
||||
type AccessListBuilder struct {
|
||||
FinalizedAccesses ConstructionBlockAccessList
|
||||
|
||||
idxBuilder *idxAccessListBuilder
|
||||
|
||||
lastFinalizedMutations *StateDiff
|
||||
lastFinalizedAccesses StateAccesses
|
||||
}
|
||||
|
||||
// NewAccessListBuilder instantiates an empty access list.
|
||||
func NewAccessListBuilder() *AccessListBuilder {
|
||||
return &AccessListBuilder{
|
||||
make(map[common.Address]*ConstructionAccountAccesses),
|
||||
newAccessListBuilder(),
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 contains partial account state before the execution at an
|
||||
// index. nil values indicate that a state value is not resolved.
|
||||
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()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
res.Accounts[addr] = &aaCopy
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package bal
|
|||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -33,27 +35,86 @@ import (
|
|||
"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
|
||||
// the spec format.
|
||||
|
||||
// BlockAccessList is the encoding format of ConstructionBlockAccessList.
|
||||
type BlockAccessList struct {
|
||||
Accesses []AccountAccess `ssz-max:"300000"`
|
||||
// BlockAccessList is the encoding format of AccessListBuilder.
|
||||
type BlockAccessList []AccountAccess
|
||||
|
||||
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) JSONString() string {
|
||||
res, _ := json.MarshalIndent(e.StringableRepresentation(), "", " ")
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// StringableRepresentation returns an instance of the block access list
|
||||
// which can be converted to a human-readable JSON representation.
|
||||
func (e *BlockAccessList) StringableRepresentation() interface{} {
|
||||
res := []AccountAccess{}
|
||||
for _, aa := range *e {
|
||||
res = append(res, aa)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
func (e *BlockAccessList) String() string {
|
||||
var res bytes.Buffer
|
||||
enc := json.NewEncoder(&res)
|
||||
enc.SetIndent("", " ")
|
||||
// TODO: check error
|
||||
enc.Encode(e)
|
||||
return res.String()
|
||||
}
|
||||
|
||||
// TODO: check that no fields are nil in Validate (unless it's valid for them to be nil)
|
||||
// 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
|
||||
// max code size.
|
||||
func (e *BlockAccessList) Validate() error {
|
||||
if !slices.IsSortedFunc(e.Accesses, func(a, b AccountAccess) int {
|
||||
func (e BlockAccessList) Validate(blockTxCount int) error {
|
||||
if !slices.IsSortedFunc(e, func(a, b AccountAccess) int {
|
||||
return bytes.Compare(a.Address[:], b.Address[:])
|
||||
}) {
|
||||
return errors.New("block access list accounts not in lexicographic order")
|
||||
}
|
||||
for _, entry := range e.Accesses {
|
||||
if err := entry.validate(); err != nil {
|
||||
// check that the accounts are unique
|
||||
addrs := make(map[common.Address]struct{})
|
||||
for _, acct := range e {
|
||||
addr := acct.Address
|
||||
if _, ok := addrs[addr]; ok {
|
||||
return fmt.Errorf("duplicate account in block access list: %x", addr)
|
||||
}
|
||||
addrs[addr] = struct{}{}
|
||||
}
|
||||
|
||||
for _, entry := range e {
|
||||
if err := entry.validate(blockTxCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -70,106 +131,228 @@ func (e *BlockAccessList) Hash() common.Hash {
|
|||
// under reasonable conditions.
|
||||
panic(err)
|
||||
}
|
||||
/*
|
||||
bal, err := json.MarshalIndent(e.StringableRepresentation(), "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*/
|
||||
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")
|
||||
// Copy returns a deep copy of the access list
|
||||
func (e BlockAccessList) Copy() (res BlockAccessList) {
|
||||
for _, accountAccess := range e {
|
||||
res = append(res, accountAccess.Copy())
|
||||
}
|
||||
var enc [16]byte
|
||||
copy(enc[16-len(valBytes):], valBytes[:])
|
||||
return enc
|
||||
return
|
||||
}
|
||||
|
||||
// encodingBalanceChange is the encoding format of BalanceChange.
|
||||
type encodingBalanceChange struct {
|
||||
TxIdx uint16 `ssz-size:"2"`
|
||||
Balance [16]byte `ssz-size:"16"`
|
||||
TxIdx uint16 `json:"txIndex"`
|
||||
Balance *uint256.Int `json:"balance"`
|
||||
}
|
||||
|
||||
// encodingAccountNonce is the encoding format of NonceChange.
|
||||
type encodingAccountNonce struct {
|
||||
TxIdx uint16 `ssz-size:"2"`
|
||||
Nonce uint64 `ssz-size:"8"`
|
||||
TxIdx uint16 `json:"txIndex"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
}
|
||||
|
||||
// encodingStorageWrite is the encoding format of StorageWrites.
|
||||
type encodingStorageWrite struct {
|
||||
TxIdx uint16
|
||||
ValueAfter [32]byte `ssz-size:"32"`
|
||||
TxIdx uint16 `json:"txIndex"`
|
||||
ValueAfter *EncodedStorage `json:"valueAfter"`
|
||||
}
|
||||
|
||||
// EncodedStorage can represent either a storage key or value
|
||||
type EncodedStorage struct {
|
||||
inner *uint256.Int
|
||||
}
|
||||
|
||||
var _ rlp.Encoder = &EncodedStorage{}
|
||||
var _ rlp.Decoder = &EncodedStorage{}
|
||||
|
||||
func (e *EncodedStorage) ToHash() common.Hash {
|
||||
if e == nil {
|
||||
return common.Hash{}
|
||||
}
|
||||
return e.inner.Bytes32()
|
||||
}
|
||||
|
||||
func newEncodedStorageFromHash(hash common.Hash) *EncodedStorage {
|
||||
return &EncodedStorage{
|
||||
new(uint256.Int).SetBytes(hash[:]),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EncodedStorage) UnmarshalJSON(b []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(b, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str = strings.TrimLeft(str, "0x")
|
||||
if len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(str)%2 == 1 {
|
||||
str = "0" + str
|
||||
}
|
||||
|
||||
val, err := hex.DecodeString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(val) > 32 {
|
||||
return fmt.Errorf("storage key/value cannot be greater than 32 bytes")
|
||||
}
|
||||
|
||||
// TODO: check is s == nil ?? should be programmer error
|
||||
|
||||
*s = EncodedStorage{
|
||||
inner: new(uint256.Int).SetBytes(val),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s EncodedStorage) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.inner.Hex())
|
||||
}
|
||||
|
||||
func (s *EncodedStorage) EncodeRLP(_w io.Writer) error {
|
||||
return s.inner.EncodeRLP(_w)
|
||||
}
|
||||
|
||||
func (s *EncodedStorage) DecodeRLP(dec *rlp.Stream) error {
|
||||
if s == nil {
|
||||
*s = EncodedStorage{}
|
||||
}
|
||||
s.inner = uint256.NewInt(0)
|
||||
return dec.ReadUint256(s.inner)
|
||||
}
|
||||
|
||||
// encodingStorageWrite is the encoding format of SlotWrites.
|
||||
type encodingSlotWrites struct {
|
||||
Slot [32]byte `ssz-size:"32"`
|
||||
Accesses []encodingStorageWrite `ssz-max:"300000"`
|
||||
Slot *EncodedStorage `json:"slot"`
|
||||
Accesses []encodingStorageWrite `json:"accesses"`
|
||||
}
|
||||
|
||||
// validate returns an instance of the encoding-representation slot writes in
|
||||
// working representation.
|
||||
func (e *encodingSlotWrites) validate() error {
|
||||
if slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int {
|
||||
func (e *encodingSlotWrites) validate(blockTxCount int) error {
|
||||
if !slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int {
|
||||
return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
|
||||
}) {
|
||||
return nil
|
||||
return errors.New("storage write tx indices not in order")
|
||||
}
|
||||
return errors.New("storage write tx indices not in order")
|
||||
// TODO: add test that covers there are actually storage modifications here
|
||||
// if there aren't, it should be a bad block
|
||||
if len(e.Accesses) == 0 {
|
||||
return fmt.Errorf("empty storage writes")
|
||||
} else if int(e.Accesses[len(e.Accesses)-1].TxIdx) >= blockTxCount+2 {
|
||||
return fmt.Errorf("storage access reported index higher than allowed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AccountAccess is the encoding format of ConstructionAccountAccess.
|
||||
// AccountAccess is the encoding format of ConstructionAccountAccesses.
|
||||
type AccountAccess struct {
|
||||
Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address
|
||||
StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value])
|
||||
StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys
|
||||
BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance])
|
||||
NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce])
|
||||
Code []CodeChange `ssz-max:"1"` // Code changes ([tx_index -> new_code])
|
||||
Address common.Address `json:"address,omitempty"` // 20-byte Ethereum address
|
||||
StorageChanges []encodingSlotWrites `json:"storageChanges,omitempty"` // EncodedStorage changes (slot -> [tx_index -> new_value])
|
||||
StorageReads []*EncodedStorage `json:"storageReads,omitempty"` // Read-only storage keys
|
||||
BalanceChanges []encodingBalanceChange `json:"balanceChanges,omitempty"` // Balance changes ([tx_index -> post_balance])
|
||||
NonceChanges []encodingAccountNonce `json:"nonceChanges,omitempty"` // Nonce changes ([tx_index -> new_nonce])
|
||||
CodeChanges []CodeChange `json:"code,omitempty"` // CodeChanges changes ([tx_index -> new_code])
|
||||
}
|
||||
|
||||
// validate converts the account accesses out of encoding format.
|
||||
// If any of the keys in the encoding object are not ordered according to the
|
||||
// spec, an error is returned.
|
||||
func (e *AccountAccess) validate() error {
|
||||
func (e *AccountAccess) validate(blockTxCount int) error {
|
||||
// Check the storage write slots are sorted in order
|
||||
if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int {
|
||||
return bytes.Compare(a.Slot[:], b.Slot[:])
|
||||
if !slices.IsSortedFunc(e.StorageChanges, func(a, b encodingSlotWrites) int {
|
||||
aHash, bHash := a.Slot.ToHash(), b.Slot.ToHash()
|
||||
return bytes.Compare(aHash[:], bHash[:])
|
||||
}) {
|
||||
return errors.New("storage writes slots not in lexicographic order")
|
||||
}
|
||||
for _, write := range e.StorageWrites {
|
||||
if err := write.validate(); err != nil {
|
||||
for _, write := range e.StorageChanges {
|
||||
if err := write.validate(blockTxCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
readKeys := make(map[common.Hash]struct{})
|
||||
writeKeys := make(map[common.Hash]struct{})
|
||||
for _, readKey := range e.StorageReads {
|
||||
if _, ok := readKeys[readKey.ToHash()]; ok {
|
||||
return errors.New("duplicate read key")
|
||||
}
|
||||
readKeys[readKey.ToHash()] = struct{}{}
|
||||
}
|
||||
for _, write := range e.StorageChanges {
|
||||
writeKey := write.Slot
|
||||
if _, ok := writeKeys[writeKey.ToHash()]; ok {
|
||||
return errors.New("duplicate write key")
|
||||
}
|
||||
writeKeys[writeKey.ToHash()] = 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
|
||||
if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int {
|
||||
return bytes.Compare(a[:], b[:])
|
||||
if !slices.IsSortedFunc(e.StorageReads, func(a, b *EncodedStorage) int {
|
||||
aHash, bHash := a.ToHash(), b.ToHash()
|
||||
return bytes.Compare(aHash[:], bHash[:])
|
||||
}) {
|
||||
return errors.New("storage read slots not in lexicographic order")
|
||||
}
|
||||
|
||||
// Check the balance changes are sorted in order
|
||||
// and that none of them report an index above what is allowed
|
||||
if !slices.IsSortedFunc(e.BalanceChanges, func(a, b encodingBalanceChange) int {
|
||||
return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
|
||||
}) {
|
||||
return errors.New("balance changes not in ascending order by tx index")
|
||||
}
|
||||
|
||||
if len(e.BalanceChanges) > 0 && int(e.BalanceChanges[len(e.BalanceChanges)-1].TxIdx) > blockTxCount+2 {
|
||||
return errors.New("highest balance change index beyond what is allowed")
|
||||
}
|
||||
// Check the nonce changes are sorted in order
|
||||
// and that none of them report an index above what is allowed
|
||||
if !slices.IsSortedFunc(e.NonceChanges, func(a, b encodingAccountNonce) int {
|
||||
return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
|
||||
}) {
|
||||
return errors.New("nonce changes not in ascending order by tx index")
|
||||
}
|
||||
if len(e.CodeChanges) > 0 && int(e.NonceChanges[len(e.NonceChanges)-1].TxIdx) >= blockTxCount+2 {
|
||||
return errors.New("highest nonce change index beyond what is allowed")
|
||||
}
|
||||
|
||||
// Convert code change
|
||||
if len(e.Code) == 1 {
|
||||
if len(e.Code[0].Code) > params.MaxCodeSize {
|
||||
return errors.New("code change contained oversized code")
|
||||
// TODO: contact testing team to add a test case which has the code changes out of order,
|
||||
// as it wasn't checked here previously
|
||||
if !slices.IsSortedFunc(e.CodeChanges, func(a, b CodeChange) int {
|
||||
return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
|
||||
}) {
|
||||
return errors.New("code changes not in ascending order")
|
||||
}
|
||||
if len(e.CodeChanges) > 0 && int(e.CodeChanges[len(e.CodeChanges)-1].TxIdx) >= blockTxCount+2 {
|
||||
return errors.New("highest code change index beyond what is allowed")
|
||||
}
|
||||
|
||||
// validate that code changes could plausibly be correct (none exceed
|
||||
// max code size of a contract)
|
||||
for _, codeChange := range e.CodeChanges {
|
||||
if len(codeChange.Code) > params.MaxCodeSize {
|
||||
return fmt.Errorf("code change contained oversized code")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -183,40 +366,39 @@ func (e *AccountAccess) Copy() AccountAccess {
|
|||
BalanceChanges: slices.Clone(e.BalanceChanges),
|
||||
NonceChanges: slices.Clone(e.NonceChanges),
|
||||
}
|
||||
for _, storageWrite := range e.StorageWrites {
|
||||
res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{
|
||||
for _, storageWrite := range e.StorageChanges {
|
||||
res.StorageChanges = append(res.StorageChanges, encodingSlotWrites{
|
||||
Slot: storageWrite.Slot,
|
||||
Accesses: slices.Clone(storageWrite.Accesses),
|
||||
})
|
||||
}
|
||||
if len(e.Code) == 1 {
|
||||
res.Code = []CodeChange{
|
||||
{
|
||||
e.Code[0].TxIndex,
|
||||
bytes.Clone(e.Code[0].Code),
|
||||
},
|
||||
}
|
||||
for _, codeChange := range e.CodeChanges {
|
||||
res.CodeChanges = append(res.CodeChanges,
|
||||
CodeChange{
|
||||
codeChange.TxIdx,
|
||||
bytes.Clone(codeChange.Code),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// EncodeRLP returns the RLP-encoded access list
|
||||
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
|
||||
return b.toEncodingObj().EncodeRLP(wr)
|
||||
func (c ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
|
||||
return c.ToEncodingObj().EncodeRLP(wr)
|
||||
}
|
||||
|
||||
var _ rlp.Encoder = &ConstructionBlockAccessList{}
|
||||
|
||||
// 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.
|
||||
func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess {
|
||||
func (a *ConstructionAccountAccesses) toEncodingObj(addr common.Address) AccountAccess {
|
||||
res := AccountAccess{
|
||||
Address: addr,
|
||||
StorageWrites: make([]encodingSlotWrites, 0),
|
||||
StorageReads: make([][32]byte, 0),
|
||||
StorageChanges: make([]encodingSlotWrites, 0),
|
||||
StorageReads: make([]*EncodedStorage, 0),
|
||||
BalanceChanges: make([]encodingBalanceChange, 0),
|
||||
NonceChanges: make([]encodingAccountNonce, 0),
|
||||
Code: nil,
|
||||
CodeChanges: make([]CodeChange, 0),
|
||||
}
|
||||
|
||||
// Convert write slots
|
||||
|
|
@ -224,7 +406,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
|||
slices.SortFunc(writeSlots, common.Hash.Cmp)
|
||||
for _, slot := range writeSlots {
|
||||
var obj encodingSlotWrites
|
||||
obj.Slot = slot
|
||||
obj.Slot = newEncodedStorageFromHash(slot)
|
||||
|
||||
slotWrites := a.StorageWrites[slot]
|
||||
obj.Accesses = make([]encodingStorageWrite, 0, len(slotWrites))
|
||||
|
|
@ -234,17 +416,17 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
|||
for _, index := range indices {
|
||||
obj.Accesses = append(obj.Accesses, encodingStorageWrite{
|
||||
TxIdx: index,
|
||||
ValueAfter: slotWrites[index],
|
||||
ValueAfter: newEncodedStorageFromHash(slotWrites[index]),
|
||||
})
|
||||
}
|
||||
res.StorageWrites = append(res.StorageWrites, obj)
|
||||
res.StorageChanges = append(res.StorageChanges, obj)
|
||||
}
|
||||
|
||||
// Convert read slots
|
||||
readSlots := slices.Collect(maps.Keys(a.StorageReads))
|
||||
slices.SortFunc(readSlots, common.Hash.Cmp)
|
||||
for _, slot := range readSlots {
|
||||
res.StorageReads = append(res.StorageReads, slot)
|
||||
res.StorageReads = append(res.StorageReads, newEncodedStorageFromHash(slot))
|
||||
}
|
||||
|
||||
// Convert balance changes
|
||||
|
|
@ -253,7 +435,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
|||
for _, idx := range balanceIndices {
|
||||
res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{
|
||||
TxIdx: idx,
|
||||
Balance: encodeBalance(a.BalanceChanges[idx]),
|
||||
Balance: new(uint256.Int).Set(a.BalanceChanges[idx]),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -268,77 +450,31 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
|
|||
}
|
||||
|
||||
// Convert code change
|
||||
if a.CodeChange != nil {
|
||||
res.Code = []CodeChange{
|
||||
{
|
||||
a.CodeChange.TxIndex,
|
||||
bytes.Clone(a.CodeChange.Code),
|
||||
},
|
||||
}
|
||||
codeChangeIdxs := slices.Collect(maps.Keys(a.CodeChanges))
|
||||
slices.SortFunc(codeChangeIdxs, cmp.Compare[uint16])
|
||||
for _, idx := range codeChangeIdxs {
|
||||
res.CodeChanges = append(res.CodeChanges, CodeChange{
|
||||
idx,
|
||||
bytes.Clone(a.CodeChanges[idx].Code),
|
||||
})
|
||||
}
|
||||
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.
|
||||
func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList {
|
||||
func (c ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
|
||||
var addresses []common.Address
|
||||
for addr := range b.Accounts {
|
||||
for addr := range c {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
slices.SortFunc(addresses, common.Address.Cmp)
|
||||
|
||||
var res BlockAccessList
|
||||
for _, addr := range addresses {
|
||||
res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr))
|
||||
res = append(res, c[addr].toEncodingObj(addr))
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
func (e *BlockAccessList) PrettyPrint() string {
|
||||
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
|
||||
}
|
||||
type ContractCode []byte
|
||||
|
|
|
|||
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,260 @@
|
|||
|
||||
package bal
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
import "github.com/ethereum/go-ethereum/rlp"
|
||||
import "github.com/holiman/uint256"
|
||||
import "io"
|
||||
|
||||
func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error {
|
||||
func (obj *AccountAccess) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
_tmp0 := w.List()
|
||||
w.WriteBytes(obj.Address[:])
|
||||
_tmp1 := w.List()
|
||||
for _, _tmp2 := range obj.Accesses {
|
||||
for _, _tmp2 := range obj.StorageChanges {
|
||||
_tmp3 := w.List()
|
||||
w.WriteBytes(_tmp2.Address[:])
|
||||
if err := _tmp2.Slot.EncodeRLP(w); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp4 := w.List()
|
||||
for _, _tmp5 := range _tmp2.StorageWrites {
|
||||
for _, _tmp5 := range _tmp2.Accesses {
|
||||
_tmp6 := w.List()
|
||||
w.WriteBytes(_tmp5.Slot[:])
|
||||
_tmp7 := w.List()
|
||||
for _, _tmp8 := range _tmp5.Accesses {
|
||||
_tmp9 := w.List()
|
||||
w.WriteUint64(uint64(_tmp8.TxIdx))
|
||||
w.WriteBytes(_tmp8.ValueAfter[:])
|
||||
w.ListEnd(_tmp9)
|
||||
w.WriteUint64(uint64(_tmp5.TxIdx))
|
||||
if err := _tmp5.ValueAfter.EncodeRLP(w); err != nil {
|
||||
return err
|
||||
}
|
||||
w.ListEnd(_tmp7)
|
||||
w.ListEnd(_tmp6)
|
||||
}
|
||||
w.ListEnd(_tmp4)
|
||||
_tmp10 := w.List()
|
||||
for _, _tmp11 := range _tmp2.StorageReads {
|
||||
w.WriteBytes(_tmp11[:])
|
||||
}
|
||||
w.ListEnd(_tmp10)
|
||||
_tmp12 := w.List()
|
||||
for _, _tmp13 := range _tmp2.BalanceChanges {
|
||||
_tmp14 := w.List()
|
||||
w.WriteUint64(uint64(_tmp13.TxIdx))
|
||||
w.WriteBytes(_tmp13.Balance[:])
|
||||
w.ListEnd(_tmp14)
|
||||
}
|
||||
w.ListEnd(_tmp12)
|
||||
_tmp15 := w.List()
|
||||
for _, _tmp16 := range _tmp2.NonceChanges {
|
||||
_tmp17 := w.List()
|
||||
w.WriteUint64(uint64(_tmp16.TxIdx))
|
||||
w.WriteUint64(_tmp16.Nonce)
|
||||
w.ListEnd(_tmp17)
|
||||
}
|
||||
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)
|
||||
_tmp7 := w.List()
|
||||
for _, _tmp8 := range obj.StorageReads {
|
||||
if err := _tmp8.EncodeRLP(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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()
|
||||
for _, _tmp13 := range obj.NonceChanges {
|
||||
_tmp14 := w.List()
|
||||
w.WriteUint64(uint64(_tmp13.TxIdx))
|
||||
w.WriteUint64(_tmp13.Nonce)
|
||||
w.ListEnd(_tmp14)
|
||||
}
|
||||
w.ListEnd(_tmp12)
|
||||
_tmp15 := w.List()
|
||||
for _, _tmp16 := range obj.CodeChanges {
|
||||
_tmp17 := w.List()
|
||||
w.WriteUint64(uint64(_tmp16.TxIdx))
|
||||
w.WriteBytes(_tmp16.Code)
|
||||
w.ListEnd(_tmp17)
|
||||
}
|
||||
w.ListEnd(_tmp15)
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
|
||||
var _tmp0 BlockAccessList
|
||||
func (obj *AccountAccess) DecodeRLP(dec *rlp.Stream) error {
|
||||
var _tmp0 AccountAccess
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Accesses:
|
||||
var _tmp1 []AccountAccess
|
||||
// Address:
|
||||
var _tmp1 common.Address
|
||||
if err := dec.ReadBytes(_tmp1[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp0.Address = _tmp1
|
||||
// StorageChanges:
|
||||
var _tmp2 []encodingSlotWrites
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp2 AccountAccess
|
||||
var _tmp3 encodingSlotWrites
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Address:
|
||||
var _tmp3 [20]byte
|
||||
if err := dec.ReadBytes(_tmp3[:]); err != nil {
|
||||
// Slot:
|
||||
_tmp4 := new(EncodedStorage)
|
||||
if err := _tmp4.DecodeRLP(dec); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp2.Address = _tmp3
|
||||
// StorageWrites:
|
||||
var _tmp4 []encodingSlotWrites
|
||||
_tmp3.Slot = _tmp4
|
||||
// Accesses:
|
||||
var _tmp5 []encodingStorageWrite
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp5 encodingSlotWrites
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Slot:
|
||||
var _tmp6 [32]byte
|
||||
if err := dec.ReadBytes(_tmp6[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp5.Slot = _tmp6
|
||||
// Accesses:
|
||||
var _tmp7 []encodingStorageWrite
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp8 encodingStorageWrite
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TxIdx:
|
||||
_tmp9, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp8.TxIdx = _tmp9
|
||||
// ValueAfter:
|
||||
var _tmp10 [32]byte
|
||||
if err := dec.ReadBytes(_tmp10[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp8.ValueAfter = _tmp10
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp7 = append(_tmp7, _tmp8)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp5.Accesses = _tmp7
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp4 = append(_tmp4, _tmp5)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp2.StorageWrites = _tmp4
|
||||
// StorageReads:
|
||||
var _tmp11 [][32]byte
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp12 [32]byte
|
||||
if err := dec.ReadBytes(_tmp12[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp11 = append(_tmp11, _tmp12)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp2.StorageReads = _tmp11
|
||||
// BalanceChanges:
|
||||
var _tmp13 []encodingBalanceChange
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp14 encodingBalanceChange
|
||||
var _tmp6 encodingStorageWrite
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TxIdx:
|
||||
_tmp15, err := dec.Uint16()
|
||||
_tmp7, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp14.TxIdx = _tmp15
|
||||
// Balance:
|
||||
var _tmp16 [16]byte
|
||||
if err := dec.ReadBytes(_tmp16[:]); err != nil {
|
||||
_tmp6.TxIdx = _tmp7
|
||||
// ValueAfter:
|
||||
_tmp8 := new(EncodedStorage)
|
||||
if err := _tmp8.DecodeRLP(dec); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp14.Balance = _tmp16
|
||||
_tmp6.ValueAfter = _tmp8
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp13 = append(_tmp13, _tmp14)
|
||||
_tmp5 = append(_tmp5, _tmp6)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp2.BalanceChanges = _tmp13
|
||||
// NonceChanges:
|
||||
var _tmp17 []encodingAccountNonce
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp18 encodingAccountNonce
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TxIdx:
|
||||
_tmp19, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp18.TxIdx = _tmp19
|
||||
// Nonce:
|
||||
_tmp20, err := dec.Uint64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp18.Nonce = _tmp20
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp17 = append(_tmp17, _tmp18)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp2.NonceChanges = _tmp17
|
||||
// Code:
|
||||
var _tmp21 []CodeChange
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp22 CodeChange
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TxIndex:
|
||||
_tmp23, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp22.TxIndex = _tmp23
|
||||
// Code:
|
||||
_tmp24, err := dec.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp22.Code = _tmp24
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp21 = append(_tmp21, _tmp22)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp2.Code = _tmp21
|
||||
_tmp3.Accesses = _tmp5
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp1 = append(_tmp1, _tmp2)
|
||||
_tmp2 = append(_tmp2, _tmp3)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp0.Accesses = _tmp1
|
||||
_tmp0.StorageChanges = _tmp2
|
||||
// StorageReads:
|
||||
var _tmp9 []*EncodedStorage
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
_tmp10 := new(EncodedStorage)
|
||||
if err := _tmp10.DecodeRLP(dec); err != nil {
|
||||
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)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp0.BalanceChanges = _tmp11
|
||||
// NonceChanges:
|
||||
var _tmp15 []encodingAccountNonce
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp16 encodingAccountNonce
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TxIdx:
|
||||
_tmp17, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp16.TxIdx = _tmp17
|
||||
// Nonce:
|
||||
_tmp18, err := dec.Uint64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp16.Nonce = _tmp18
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp15 = append(_tmp15, _tmp16)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp0.NonceChanges = _tmp15
|
||||
// CodeChanges:
|
||||
var _tmp19 []CodeChange
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
for dec.MoreDataInList() {
|
||||
var _tmp20 CodeChange
|
||||
{
|
||||
if _, err := dec.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TxIdx:
|
||||
_tmp21, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp20.TxIdx = _tmp21
|
||||
// Code:
|
||||
_tmp22, err := dec.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp20.Code = _tmp22
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_tmp19 = append(_tmp19, _tmp20)
|
||||
}
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
_tmp0.CodeChanges = _tmp19
|
||||
if err := dec.ListEnd(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ func equalBALs(a *BlockAccessList, b *BlockAccessList) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func makeTestConstructionBAL() *ConstructionBlockAccessList {
|
||||
return &ConstructionBlockAccessList{
|
||||
map[common.Address]*ConstructionAccountAccess{
|
||||
func makeTestConstructionBAL() *AccessListBuilder {
|
||||
return &AccessListBuilder{
|
||||
FinalizedAccesses: map[common.Address]*ConstructionAccountAccesses{
|
||||
common.BytesToAddress([]byte{0xff, 0xff}): {
|
||||
StorageWrites: map[common.Hash]map[uint16]common.Hash{
|
||||
common.BytesToHash([]byte{0x01}): {
|
||||
|
|
@ -60,10 +60,10 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
|
|||
1: 2,
|
||||
2: 6,
|
||||
},
|
||||
CodeChange: &CodeChange{
|
||||
TxIndex: 0,
|
||||
Code: common.Hex2Bytes("deadbeef"),
|
||||
},
|
||||
CodeChanges: map[uint16]CodeChange{0: {
|
||||
TxIdx: 0,
|
||||
Code: common.Hex2Bytes("deadbeef"),
|
||||
}},
|
||||
},
|
||||
common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
if !equalBALs(bal.toEncodingObj(), &dec) {
|
||||
if !equalBALs(bal.ToEncodingObj(), &dec) {
|
||||
t.Fatal("decoded BAL doesn't match")
|
||||
}
|
||||
}
|
||||
|
|
@ -113,18 +113,18 @@ func TestBALEncoding(t *testing.T) {
|
|||
func makeTestAccountAccess(sort bool) AccountAccess {
|
||||
var (
|
||||
storageWrites []encodingSlotWrites
|
||||
storageReads [][32]byte
|
||||
storageReads []common.Hash
|
||||
balances []encodingBalanceChange
|
||||
nonces []encodingAccountNonce
|
||||
)
|
||||
for i := 0; i < 5; i++ {
|
||||
slot := encodingSlotWrites{
|
||||
Slot: testrand.Hash(),
|
||||
Slot: newEncodedStorageFromHash(testrand.Hash()),
|
||||
}
|
||||
for j := 0; j < 3; j++ {
|
||||
slot.Accesses = append(slot.Accesses, encodingStorageWrite{
|
||||
TxIdx: uint16(2 * j),
|
||||
ValueAfter: testrand.Hash(),
|
||||
ValueAfter: newEncodedStorageFromHash(testrand.Hash()),
|
||||
})
|
||||
}
|
||||
if sort {
|
||||
|
|
@ -144,7 +144,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
|||
storageReads = append(storageReads, testrand.Hash())
|
||||
}
|
||||
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[:])
|
||||
})
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
|||
for i := 0; i < 5; i++ {
|
||||
balances = append(balances, encodingBalanceChange{
|
||||
TxIdx: uint16(2 * i),
|
||||
Balance: [16]byte(testrand.Bytes(16)),
|
||||
Balance: new(uint256.Int).SetBytes(testrand.Bytes(32)),
|
||||
})
|
||||
}
|
||||
if sort {
|
||||
|
|
@ -173,16 +173,20 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
|||
})
|
||||
}
|
||||
|
||||
var encodedStorageReads []EncodedStorage
|
||||
for _, slot := range storageReads {
|
||||
encodedStorageReads = append(encodedStorageReads, newEncodedStorageFromHash(slot))
|
||||
}
|
||||
return AccountAccess{
|
||||
Address: [20]byte(testrand.Bytes(20)),
|
||||
StorageWrites: storageWrites,
|
||||
StorageReads: storageReads,
|
||||
StorageChanges: storageWrites,
|
||||
StorageReads: encodedStorageReads,
|
||||
BalanceChanges: balances,
|
||||
NonceChanges: nonces,
|
||||
Code: []CodeChange{
|
||||
CodeChanges: []CodeChange{
|
||||
{
|
||||
TxIndex: 100,
|
||||
Code: testrand.Bytes(256),
|
||||
TxIdx: 100,
|
||||
Code: testrand.Bytes(256),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -191,10 +195,10 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
|||
func makeTestBAL(sort bool) BlockAccessList {
|
||||
list := BlockAccessList{}
|
||||
for i := 0; i < 5; i++ {
|
||||
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
|
||||
list = append(list, makeTestAccountAccess(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[:])
|
||||
})
|
||||
}
|
||||
|
|
@ -214,9 +218,9 @@ func TestBlockAccessListCopy(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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++ {
|
||||
aa.StorageReads[i] = [32]byte(testrand.Bytes(32))
|
||||
aa.StorageReads[i] = testrand.Bytes(32)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(list, cpy) {
|
||||
|
|
@ -245,8 +249,11 @@ func TestBlockAccessListValidation(t *testing.T) {
|
|||
|
||||
// Validate the derived block access list
|
||||
cBAL := makeTestConstructionBAL()
|
||||
listB := cBAL.toEncodingObj()
|
||||
listB := cBAL.ToEncodingObj()
|
||||
if err := listB.Validate(); err != nil {
|
||||
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
|
||||
|
|
|
|||
32
core/types/bal_blocks_test.go
Normal file
32
core/types/bal_blocks_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBALDecoding(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
)
|
||||
data, err = os.ReadFile("blocks_bal_one.rlp")
|
||||
if err != nil {
|
||||
t.Fatalf("error opening file: %v", err)
|
||||
}
|
||||
reader := bytes.NewReader(data)
|
||||
stream := rlp.NewStream(reader, 0)
|
||||
var blocks Block
|
||||
for i := 0; err == nil; i++ {
|
||||
fmt.Printf("decode %d\n", i)
|
||||
err = stream.Decode(&blocks)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("error decoding blocks: %v", err)
|
||||
}
|
||||
fmt.Printf("block number is %d\n", blocks.NumberU64())
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,8 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
|
@ -98,6 +100,9 @@ type Header struct {
|
|||
|
||||
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
|
||||
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
|
||||
|
|
@ -175,7 +180,8 @@ func (h *Header) EmptyReceipts() bool {
|
|||
type Body struct {
|
||||
Transactions []*Transaction
|
||||
Uncles []*Header
|
||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
AccessList *bal.BlockAccessList `rlp:"-,nil"`
|
||||
}
|
||||
|
||||
// Block represents an Ethereum block.
|
||||
|
|
@ -200,6 +206,7 @@ type Block struct {
|
|||
uncles []*Header
|
||||
transactions Transactions
|
||||
withdrawals Withdrawals
|
||||
accessList *bal.BlockAccessList
|
||||
|
||||
// caches
|
||||
hash atomic.Pointer[common.Hash]
|
||||
|
|
@ -277,6 +284,12 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher
|
|||
b.withdrawals = slices.Clone(withdrawals)
|
||||
}
|
||||
|
||||
if body.AccessList != nil {
|
||||
balHash := body.AccessList.Hash()
|
||||
b.header.BlockAccessListHash = &balHash
|
||||
b.accessList = body.AccessList
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +357,7 @@ func (b *Block) EncodeRLP(w io.Writer) error {
|
|||
// Body returns the non-header content of the block.
|
||||
// Note the returned data is not an independent copy.
|
||||
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
|
||||
|
|
@ -490,6 +503,10 @@ func (b *Block) WithBody(body Body) *Block {
|
|||
uncles: make([]*Header, len(body.Uncles)),
|
||||
withdrawals: slices.Clone(body.Withdrawals),
|
||||
}
|
||||
if body.AccessList != nil {
|
||||
balCopy := body.AccessList.Copy()
|
||||
block.accessList = &balCopy
|
||||
}
|
||||
for i := range body.Uncles {
|
||||
block.uncles[i] = CopyHeader(body.Uncles[i])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,28 +16,29 @@ var _ = (*headerMarshaling)(nil)
|
|||
// MarshalJSON marshals as JSON.
|
||||
func (h Header) MarshalJSON() ([]byte, error) {
|
||||
type Header struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase common.Address `json:"miner"`
|
||||
Root common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest common.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase common.Address `json:"miner"`
|
||||
Root common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest common.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
var enc Header
|
||||
enc.ParentHash = h.ParentHash
|
||||
|
|
@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
|||
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
|
||||
enc.ParentBeaconRoot = h.ParentBeaconRoot
|
||||
enc.RequestsHash = h.RequestsHash
|
||||
enc.BlockAccessListHash = h.BlockAccessListHash
|
||||
enc.Hash = h.Hash()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
|
@ -68,27 +70,28 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
|||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
type Header struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase *common.Address `json:"miner"`
|
||||
Root *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest *common.Hash `json:"mixHash"`
|
||||
Nonce *BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase *common.Address `json:"miner"`
|
||||
Root *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest *common.Hash `json:"mixHash"`
|
||||
Nonce *BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||
}
|
||||
var dec Header
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
|||
if dec.RequestsHash != nil {
|
||||
h.RequestsHash = dec.RequestsHash
|
||||
}
|
||||
if dec.BlockAccessListHash != nil {
|
||||
h.BlockAccessListHash = dec.BlockAccessListHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
|||
_tmp4 := obj.ExcessBlobGas != nil
|
||||
_tmp5 := obj.ParentBeaconRoot != nil
|
||||
_tmp6 := obj.RequestsHash != nil
|
||||
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
|
||||
_tmp7 := obj.BlockAccessListHash != nil
|
||||
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.BaseFee == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
|
|
@ -53,41 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
|||
w.WriteBigInt(obj.BaseFee)
|
||||
}
|
||||
}
|
||||
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
|
||||
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.WithdrawalsHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.WithdrawalsHash[:])
|
||||
}
|
||||
}
|
||||
if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
|
||||
if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.BlobGasUsed == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteUint64((*obj.BlobGasUsed))
|
||||
}
|
||||
}
|
||||
if _tmp4 || _tmp5 || _tmp6 {
|
||||
if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.ExcessBlobGas == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteUint64((*obj.ExcessBlobGas))
|
||||
}
|
||||
}
|
||||
if _tmp5 || _tmp6 {
|
||||
if _tmp5 || _tmp6 || _tmp7 {
|
||||
if obj.ParentBeaconRoot == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.ParentBeaconRoot[:])
|
||||
}
|
||||
}
|
||||
if _tmp6 {
|
||||
if _tmp6 || _tmp7 {
|
||||
if obj.RequestsHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.RequestsHash[:])
|
||||
}
|
||||
}
|
||||
if _tmp7 {
|
||||
if obj.BlockAccessListHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.BlockAccessListHash[:])
|
||||
}
|
||||
}
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,7 +374,33 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
func gasCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gas uint64
|
||||
transfersValue = !stack.Back(2).IsZero()
|
||||
)
|
||||
|
||||
if transfersValue {
|
||||
if evm.readOnly {
|
||||
return 0, ErrWriteProtection
|
||||
} else if !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferGas
|
||||
}
|
||||
}
|
||||
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gas uint64
|
||||
transfersValue = !stack.Back(2).IsZero()
|
||||
|
|
@ -391,15 +417,22 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||
} else if !evm.StateDB.Exist(address) {
|
||||
gas += params.CallNewAccountGas
|
||||
}
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferGas
|
||||
}
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
stateless, err := gasCallStateless(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
|
||||
stateful, err := gasCallStateful(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
gas, overflow := math.SafeAdd(stateless, stateful)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
|
|
@ -410,25 +443,39 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
func gasCallCodeStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func gasCallCodeStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var (
|
||||
gas uint64
|
||||
overflow bool
|
||||
gas uint64
|
||||
overflow bool
|
||||
transfersValue = !stack.Back(2).IsZero()
|
||||
)
|
||||
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferGas
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var overflow bool
|
||||
gas, err := gasCallCodeStateless(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
@ -440,10 +487,16 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
|||
}
|
||||
|
||||
func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
var (
|
||||
err error
|
||||
gas uint64
|
||||
)
|
||||
|
||||
gas, err = gasDelegateCallStateless(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
@ -455,11 +508,36 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
func gasDelegateCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func gasDelegateCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasStaticCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasStaticCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := gasStaticCallStateless(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
@ -477,11 +555,16 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
|||
}
|
||||
|
||||
var gas uint64
|
||||
|
||||
// EIP150 homestead gas reprice fork:
|
||||
if evm.chainRules.IsEIP150 {
|
||||
gas = params.SelfdestructGasEIP150
|
||||
var address = common.Address(stack.Back(0).Bytes20())
|
||||
|
||||
if gas > contract.Gas {
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
if evm.chainRules.IsEIP158 {
|
||||
// if empty and transfers value
|
||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
|||
switch {
|
||||
case rules.IsVerkle:
|
||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||
case rules.IsAmsterdam:
|
||||
return newPragueInstructionSet(), nil
|
||||
case rules.IsOsaka:
|
||||
return newOsakaInstructionSet(), nil
|
||||
case rules.IsPrague:
|
||||
|
|
|
|||
|
|
@ -155,50 +155,12 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
addr := common.Address(stack.Back(addressPosition).Bytes20())
|
||||
// Check slot presence in the access list
|
||||
warmAccess := evm.StateDB.AddressInAccessList(addr)
|
||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
|
||||
// the cost to charge for cold access, if any, is Cold - Warm
|
||||
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||
if !warmAccess {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// Charge the remaining difference here already, to correctly calculate available
|
||||
// gas for call
|
||||
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
// Now call the old calculator, which takes into account
|
||||
// - create new account
|
||||
// - transfer value
|
||||
// - memory expansion
|
||||
// - 63/64ths rule
|
||||
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
||||
if warmAccess || err != nil {
|
||||
return gas, err
|
||||
}
|
||||
// In case of a cold access, we temporarily add the cold charge back, and also
|
||||
// add it to the returned gas. By adding it to the return, it will be charged
|
||||
// outside of this function, as part of the dynamic gas, and that will make it
|
||||
// also become correctly reported to tracers.
|
||||
contract.Gas += coldCost
|
||||
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, coldCost); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall, 1)
|
||||
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall, 1)
|
||||
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall, 1)
|
||||
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode, 1)
|
||||
// TODO: we can use the same functions already defined above for the 7702 gas handlers
|
||||
gasCallEIP2929 = makeCallVariantGasCall(gasCallStateless, gasCallStateful)
|
||||
gasDelegateCallEIP2929 = makeCallVariantGasCall(gasDelegateCallStateless, gasDelegateCallStateful)
|
||||
gasStaticCallEIP2929 = makeCallVariantGasCall(gasStaticCallStateless, gasStaticCallStateful)
|
||||
gasCallCodeEIP2929 = makeCallVariantGasCall(gasCallCodeStateless, gasCallCodeStateful)
|
||||
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
|
||||
// gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
|
||||
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
|
||||
|
|
@ -243,6 +205,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
|||
return 0, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
if contract.Gas < gas {
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
// if empty and transfers value
|
||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||
gas += params.CreateBySelfdestructGas
|
||||
|
|
@ -256,33 +222,25 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
|||
}
|
||||
|
||||
var (
|
||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
|
||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
|
||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
|
||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
|
||||
gasCallEIP7702 = makeCallVariantGasCall(gasCallStateful, gasCallStateless)
|
||||
gasDelegateCallEIP7702 = makeCallVariantGasCall(gasDelegateCallStateful, gasDelegateCallStateless)
|
||||
gasStaticCallEIP7702 = makeCallVariantGasCall(gasStaticCallStateful, gasStaticCallStateless)
|
||||
gasCallCodeEIP7702 = makeCallVariantGasCall(gasCallCodeStateful, gasCallCodeStateless)
|
||||
)
|
||||
|
||||
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
// Return early if this call attempts to transfer value in a static context.
|
||||
// Although it's checked in `gasCall`, EIP-7702 loads the target's code before
|
||||
// to determine if it is resolving a delegation. This could incorrectly record
|
||||
// the target in the block access list (BAL) if the call later fails.
|
||||
transfersValue := !stack.Back(2).IsZero()
|
||||
if evm.readOnly && transfersValue {
|
||||
return 0, ErrWriteProtection
|
||||
}
|
||||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
||||
func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
total uint64 // total dynamic gas used
|
||||
addr = common.Address(stack.Back(1).Bytes20())
|
||||
eip150BaseGas uint64 // gas used for memory expansion, transfer costs -> input to the 63/64 bounding
|
||||
eip7702Gas uint64
|
||||
eip2929Gas uint64
|
||||
addr = common.Address(stack.Back(1).Bytes20())
|
||||
overflow bool
|
||||
err error
|
||||
)
|
||||
|
||||
// Check slot presence in the access list
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
if evm.chainRules.IsEIP2929 && !evm.StateDB.AddressInAccessList(addr) {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
|
||||
// the cost to charge for cold access, if any, is Cold - Warm
|
||||
|
|
@ -292,44 +250,87 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
|||
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
total += coldCost
|
||||
eip2929Gas = coldCost
|
||||
}
|
||||
|
||||
// Check if code is a delegation and if so, charge for resolution.
|
||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||
var cost uint64
|
||||
if evm.StateDB.AddressInAccessList(target) {
|
||||
cost = params.WarmStorageReadCostEIP2929
|
||||
} else {
|
||||
evm.StateDB.AddAddressToAccessList(target)
|
||||
cost = params.ColdAccountAccessCostEIP2929
|
||||
}
|
||||
if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
total += cost
|
||||
}
|
||||
|
||||
// Now call the old calculator, which takes into account
|
||||
// - create new account
|
||||
// - transfer value
|
||||
// - memory expansion
|
||||
// - 63/64ths rule
|
||||
old, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
||||
eip150BaseGas, err = oldCalculatorStateless(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return old, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// ensure the portion of the call cost which doesn't depend on state lookups
|
||||
// is covered by the provided gas
|
||||
if contract.Gas < eip150BaseGas {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
|
||||
oldStateful, err := oldCalculatorStateful(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return oldStateful, err
|
||||
}
|
||||
|
||||
// this should cause BAL test failures if uncommented
|
||||
baseCost, overflow := math.SafeAdd(eip150BaseGas, oldStateful)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
} else if contract.Gas < baseCost {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
|
||||
if eip150BaseGas, overflow = math.SafeAdd(eip150BaseGas, oldStateful); overflow {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
|
||||
if evm.chainRules.IsPrague {
|
||||
// Check if code is a delegation and if so, charge for resolution.
|
||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||
if evm.StateDB.AddressInAccessList(target) {
|
||||
eip7702Gas = params.WarmStorageReadCostEIP2929
|
||||
} else {
|
||||
evm.StateDB.AddAddressToAccessList(target)
|
||||
eip7702Gas = params.ColdAccountAccessCostEIP2929
|
||||
}
|
||||
if !contract.UseGas(eip7702Gas, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, eip150BaseGas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// TODO: it's not clear what happens if there is enough gas to cover the stateless component
|
||||
// but not enough to cover the whole call: do all the state reads happen in this case, and
|
||||
// we fail at the very end?
|
||||
|
||||
// Temporarily add the gas charge back to the contract and return value. By
|
||||
// adding it to the return, it will be charged outside of this function, as
|
||||
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||
// tracers.
|
||||
contract.Gas += total
|
||||
|
||||
var overflow bool
|
||||
if total, overflow = math.SafeAdd(old, total); overflow {
|
||||
contract.Gas, overflow = math.SafeAdd(contract.Gas, eip2929Gas)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return total, nil
|
||||
contract.Gas, overflow = math.SafeAdd(contract.Gas, eip7702Gas)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
var totalCost uint64
|
||||
totalCost, overflow = math.SafeAdd(eip2929Gas, eip7702Gas)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
totalCost, overflow = math.SafeAdd(totalCost, eip150BaseGas)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return totalCost, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package eth
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
|
|
@ -499,3 +500,22 @@ func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
|||
func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration {
|
||||
return b.eth.config.TxSyncMaxTimeout
|
||||
}
|
||||
|
||||
// GetBlockAccessList returns a block access list for the given number/hash
|
||||
// or nil if one does not exist.
|
||||
func (b *EthAPIBackend) BlockAccessListByNumberOrHash(number rpc.BlockNumberOrHash) (interface{}, error) {
|
||||
var block *types.Block
|
||||
if num := number.BlockNumber; num != nil {
|
||||
block = b.eth.blockchain.GetBlockByNumber(uint64(num.Int64()))
|
||||
} else if hash := number.BlockHash; hash != nil {
|
||||
block = b.eth.blockchain.GetBlockByHash(*hash)
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("block not found")
|
||||
}
|
||||
if block.Body().AccessList == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return block.Body().AccessList.StringableRepresentation(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,9 +198,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
|
|||
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
|
||||
case params.BeaconRoot == nil:
|
||||
return engine.STATUS_INVALID, attributesErr("missing beacon root")
|
||||
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
|
||||
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5, forks.Amsterdam):
|
||||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads")
|
||||
}
|
||||
|
||||
if api.checkFork(params.Timestamp, forks.Amsterdam) {
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV4, false)
|
||||
}
|
||||
}
|
||||
// TODO(matt): the spec requires that fcu is applied when called on a valid
|
||||
// hash, even if params are wrong. To do this we need to split up
|
||||
|
|
@ -455,11 +459,23 @@ func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.Execu
|
|||
})
|
||||
}
|
||||
|
||||
// GetPayloadV6 returns a cached payload by id.
|
||||
func (api *ConsensusAPI) GetPayloadV6(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
|
||||
if !payloadID.Is(engine.PayloadV4) {
|
||||
return nil, engine.UnsupportedFork
|
||||
}
|
||||
return api.getPayload(payloadID,
|
||||
false,
|
||||
[]engine.PayloadVersion{engine.PayloadV4},
|
||||
nil)
|
||||
}
|
||||
|
||||
// getPayload will retrieve the specified payload and verify it conforms to the
|
||||
// endpoint's allowed payload versions and forks.
|
||||
//
|
||||
// Note passing nil `forks`, `versions` disables the respective check.
|
||||
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool, versions []engine.PayloadVersion, forks []forks.Fork) (*engine.ExecutionPayloadEnvelope, error) {
|
||||
|
||||
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
|
||||
if versions != nil && !payloadID.Is(versions...) {
|
||||
return nil, engine.UnsupportedFork
|
||||
|
|
@ -697,6 +713,33 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
|
|||
return api.newPayload(params, versionedHashes, beaconRoot, requests, false)
|
||||
}
|
||||
|
||||
// NewPayloadV5 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||
func (api *ConsensusAPI) NewPayloadV5(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
|
||||
switch {
|
||||
case params.Withdrawals == nil:
|
||||
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
|
||||
case params.ExcessBlobGas == nil:
|
||||
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
|
||||
case params.BlobGasUsed == nil:
|
||||
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
|
||||
case versionedHashes == nil:
|
||||
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
|
||||
case beaconRoot == nil:
|
||||
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
|
||||
case executionRequests == nil:
|
||||
return invalidStatus, paramsErr("nil executionRequests post-prague")
|
||||
case params.BlockAccessList == nil:
|
||||
return invalidStatus, paramsErr("nil block access list post-amsterdam")
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.Amsterdam):
|
||||
return invalidStatus, unsupportedForkErr("newPayloadV5 must only be called for amsterdam payloads")
|
||||
}
|
||||
requests := convertRequests(executionRequests)
|
||||
if err := validateRequests(requests); err != nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
|
||||
}
|
||||
return api.newPayload(params, versionedHashes, beaconRoot, requests, false)
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) {
|
||||
// The locking here is, strictly, not required. Without these locks, this can happen:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -967,6 +967,9 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
|
|||
if block.Withdrawals() != nil {
|
||||
fields["withdrawals"] = block.Withdrawals()
|
||||
}
|
||||
if block.Body().AccessList != nil {
|
||||
fields["accessList"] = block.Body().AccessList
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
|
|
@ -1336,6 +1339,18 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
|||
}
|
||||
}
|
||||
|
||||
// BlockAccessListByBlockNumber returns a block access list for the given block number
|
||||
// or nil if one does not exist.
|
||||
func (api *BlockChainAPI) BlockAccessListByBlockNumber(number rpc.BlockNumber) (interface{}, error) {
|
||||
return api.b.BlockAccessListByNumberOrHash(rpc.BlockNumberOrHash{BlockNumber: &number})
|
||||
}
|
||||
|
||||
// BlockAccessListByBlockHash returns a block access list for the given block hash
|
||||
// or nil if one does not exist.
|
||||
func (api *BlockChainAPI) BlockAccessListByBlockHash(hash common.Hash) (interface{}, error) {
|
||||
return api.b.BlockAccessListByNumberOrHash(rpc.BlockNumberOrHash{BlockHash: &hash})
|
||||
}
|
||||
|
||||
// TransactionAPI exposes methods for reading and creating transaction data.
|
||||
type TransactionAPI struct {
|
||||
b Backend
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ type Backend interface {
|
|||
GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM
|
||||
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||
BlockAccessListByNumberOrHash(number rpc.BlockNumberOrHash) (interface{}, error)
|
||||
|
||||
// Transaction pool API
|
||||
SendTx(ctx context.Context, signedTx *types.Transaction) error
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
}
|
||||
blockBody := &types.Body{Transactions: txes, Withdrawals: *block.BlockOverrides.Withdrawals}
|
||||
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 {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,6 +474,11 @@ web3._extend({
|
|||
params: 1,
|
||||
inputFormatter: [null],
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getEncodedBlockAccessList',
|
||||
call: 'debug_getEncodedBlockAccessList',
|
||||
params: 1
|
||||
}),
|
||||
],
|
||||
properties: []
|
||||
});
|
||||
|
|
@ -605,7 +610,17 @@ web3._extend({
|
|||
name: 'config',
|
||||
call: 'eth_config',
|
||||
params: 0,
|
||||
})
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getBlockAccessListByBlockNumber',
|
||||
call: 'eth_blockAccessListByBlockNumber',
|
||||
params: 1,
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getBlockAccessListByBlockHash',
|
||||
call: 'eth_blockAccessListByBlockHash',
|
||||
params: 1,
|
||||
}),
|
||||
],
|
||||
properties: [
|
||||
new web3._extend.Property({
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package miner
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -70,7 +71,8 @@ type environment struct {
|
|||
sidecars []*types.BlobTxSidecar
|
||||
blobs int
|
||||
|
||||
witness *stateless.Witness
|
||||
witness *stateless.Witness
|
||||
alTracer *core.BlockAccessListTracer
|
||||
}
|
||||
|
||||
// txFits reports whether the transaction fits into the block size limit.
|
||||
|
|
@ -144,6 +146,9 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay
|
|||
}
|
||||
}
|
||||
body := types.Body{Transactions: work.txs, Withdrawals: genParam.withdrawals}
|
||||
if work.alTracer != nil {
|
||||
body.AccessList = work.alTracer.AccessList().ToEncodingObj()
|
||||
}
|
||||
|
||||
allLogs := make([]*types.Log, 0)
|
||||
for _, r := range work.receipts {
|
||||
|
|
@ -172,10 +177,24 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay
|
|||
work.header.RequestsHash = &reqHash
|
||||
}
|
||||
|
||||
block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts)
|
||||
// set the block access list on the body after the block has finished executing
|
||||
// but before the header hash is computed (in FinalizeAndAssemble).
|
||||
//
|
||||
// I considered trying to instantiate the beacon consensus engine with a tracer.
|
||||
// however, the BAL tracer instance is used once per block, while the engine object
|
||||
// lives for the entire time the client is running.
|
||||
onBlockFinalization := func() {
|
||||
if miner.chainConfig.IsAmsterdam(work.header.Number, work.header.Time) {
|
||||
work.alTracer.OnBlockFinalization()
|
||||
body.AccessList = work.alTracer.AccessList().ToEncodingObj()
|
||||
}
|
||||
}
|
||||
|
||||
block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts, onBlockFinalization)
|
||||
if err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
|
||||
return &newPayloadResult{
|
||||
block: block,
|
||||
fees: totalFees(block, work.receipts),
|
||||
|
|
@ -266,13 +285,15 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
|
|||
if miner.chainConfig.IsPrague(header.Number, header.Time) {
|
||||
core.ProcessParentBlockHash(header.ParentHash, env.evm)
|
||||
}
|
||||
// TODO: verify that we can make blocks that correctly record the pre-tx system calls
|
||||
// TODO ^ comprehensive miner unit tests
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// makeEnv creates a new environment for the sealing block.
|
||||
func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address, witness bool) (*environment, error) {
|
||||
// Retrieve the parent state to execute on top.
|
||||
state, err := miner.chain.StateAt(parent.Root)
|
||||
sdb, err := miner.chain.StateAt(parent.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -281,17 +302,27 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.StartPrefetcher("miner", bundle, nil)
|
||||
sdb.StartPrefetcher("miner", bundle, nil)
|
||||
}
|
||||
var alTracer *core.BlockAccessListTracer
|
||||
var hooks *tracing.Hooks
|
||||
var hookedState vm.StateDB = sdb
|
||||
var vmConfig vm.Config
|
||||
if miner.chainConfig.IsAmsterdam(header.Number, header.Time) {
|
||||
alTracer, hooks = core.NewBlockAccessListTracer()
|
||||
hookedState = state.NewHookedState(sdb, hooks)
|
||||
vmConfig.Tracer = hooks
|
||||
}
|
||||
// Note the passed coinbase may be different with header.Coinbase.
|
||||
return &environment{
|
||||
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
||||
state: state,
|
||||
state: sdb,
|
||||
size: uint64(header.Size()),
|
||||
coinbase: coinbase,
|
||||
header: header,
|
||||
witness: state.Witness(),
|
||||
evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}),
|
||||
witness: sdb.Witness(),
|
||||
evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), hookedState, miner.chainConfig, vmConfig),
|
||||
alTracer: alTracer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,6 +236,8 @@ var (
|
|||
Cancun: DefaultCancunBlobConfig,
|
||||
Prague: DefaultPragueBlobConfig,
|
||||
Osaka: DefaultOsakaBlobConfig,
|
||||
BPO1: DefaultBPO1BlobConfig,
|
||||
BPO2: DefaultBPO2BlobConfig,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1172,6 +1174,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
|
|||
// BlobConfig returns the blob config associated with the provided fork.
|
||||
func (c *ChainConfig) BlobConfig(fork forks.Fork) *BlobConfig {
|
||||
switch fork {
|
||||
case forks.Amsterdam:
|
||||
return c.BlobScheduleConfig.Amsterdam
|
||||
case forks.BPO5:
|
||||
return c.BlobScheduleConfig.BPO5
|
||||
case forks.BPO4:
|
||||
|
|
@ -1217,6 +1221,8 @@ func (c *ChainConfig) ActiveSystemContracts(time uint64) map[string]common.Addre
|
|||
// the fork isn't defined or isn't a time-based fork.
|
||||
func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
|
||||
switch {
|
||||
case fork == forks.Amsterdam:
|
||||
return c.AmsterdamTime
|
||||
case fork == forks.BPO5:
|
||||
return c.BPO5Time
|
||||
case fork == forks.BPO4:
|
||||
|
|
|
|||
|
|
@ -74,10 +74,9 @@ func TestBlockchain(t *testing.T) {
|
|||
// which run natively, so there's no reason to run them here.
|
||||
}
|
||||
|
||||
// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests.
|
||||
func TestExecutionSpecBlocktests(t *testing.T) {
|
||||
if !common.FileExist(executionSpecBlockchainTestDir) {
|
||||
t.Skipf("directory %s does not exist", executionSpecBlockchainTestDir)
|
||||
func testExecutionSpecBlocktests(t *testing.T, testDir string) {
|
||||
if !common.FileExist(testDir) {
|
||||
t.Skipf("directory %s does not exist", testDir)
|
||||
}
|
||||
bt := new(testMatcher)
|
||||
|
||||
|
|
@ -85,11 +84,21 @@ func TestExecutionSpecBlocktests(t *testing.T) {
|
|||
bt.skipLoad(".*prague/eip7251_consolidations/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, testDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
execBlockTest(t, bt, test)
|
||||
})
|
||||
}
|
||||
|
||||
// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests.
|
||||
func TestExecutionSpecBlocktests(t *testing.T) {
|
||||
testExecutionSpecBlocktests(t, executionSpecBlockchainTestDir)
|
||||
}
|
||||
|
||||
// TestExecutionSpecBlocktestsBAL runs the BAL release test fixtures from execution-spec-tests.
|
||||
func TestExecutionSpecBlocktestsBAL(t *testing.T) {
|
||||
testExecutionSpecBlocktests(t, executionSpecBALBlockchainTestDir)
|
||||
}
|
||||
|
||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||
// Define all the different flag combinations we should run the tests with,
|
||||
// picking only one for short tests.
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ type btHeader struct {
|
|||
BlobGasUsed *uint64
|
||||
ExcessBlobGas *uint64
|
||||
ParentBeaconBlockRoot *common.Hash
|
||||
BlockAccessListHash *common.Hash
|
||||
}
|
||||
|
||||
type btHeaderMarshaling struct {
|
||||
|
|
@ -211,20 +212,21 @@ func (t *BlockTest) Network() string {
|
|||
|
||||
func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
|
||||
return &core.Genesis{
|
||||
Config: config,
|
||||
Nonce: t.json.Genesis.Nonce.Uint64(),
|
||||
Timestamp: t.json.Genesis.Timestamp,
|
||||
ParentHash: t.json.Genesis.ParentHash,
|
||||
ExtraData: t.json.Genesis.ExtraData,
|
||||
GasLimit: t.json.Genesis.GasLimit,
|
||||
GasUsed: t.json.Genesis.GasUsed,
|
||||
Difficulty: t.json.Genesis.Difficulty,
|
||||
Mixhash: t.json.Genesis.MixHash,
|
||||
Coinbase: t.json.Genesis.Coinbase,
|
||||
Alloc: t.json.Pre,
|
||||
BaseFee: t.json.Genesis.BaseFeePerGas,
|
||||
BlobGasUsed: t.json.Genesis.BlobGasUsed,
|
||||
ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
|
||||
Config: config,
|
||||
Nonce: t.json.Genesis.Nonce.Uint64(),
|
||||
Timestamp: t.json.Genesis.Timestamp,
|
||||
ParentHash: t.json.Genesis.ParentHash,
|
||||
ExtraData: t.json.Genesis.ExtraData,
|
||||
GasLimit: t.json.Genesis.GasLimit,
|
||||
GasUsed: t.json.Genesis.GasUsed,
|
||||
Difficulty: t.json.Genesis.Difficulty,
|
||||
Mixhash: t.json.Genesis.MixHash,
|
||||
Coinbase: t.json.Genesis.Coinbase,
|
||||
Alloc: t.json.Pre,
|
||||
BaseFee: t.json.Genesis.BaseFeePerGas,
|
||||
BlobGasUsed: t.json.Genesis.BlobGasUsed,
|
||||
ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
|
||||
BlockAccessListHash: t.json.Genesis.BlockAccessListHash,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
|
|||
BlobGasUsed *math.HexOrDecimal64
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
ParentBeaconBlockRoot *common.Hash
|
||||
BlockAccessListHash *common.Hash
|
||||
}
|
||||
var enc btHeader
|
||||
enc.Bloom = b.Bloom
|
||||
|
|
@ -88,6 +89,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
|
|||
BlobGasUsed *math.HexOrDecimal64
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
ParentBeaconBlockRoot *common.Hash
|
||||
BlockAccessListHash *common.Hash
|
||||
}
|
||||
var dec btHeader
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -156,5 +158,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
|
|||
if dec.ParentBeaconBlockRoot != nil {
|
||||
b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
|
||||
}
|
||||
if dec.BlockAccessListHash != nil {
|
||||
b.BlockAccessListHash = dec.BlockAccessListHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -493,6 +493,38 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BPO1: bpo1BlobConfig,
|
||||
},
|
||||
},
|
||||
"Amsterdam": {
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
EIP150Block: big.NewInt(0),
|
||||
EIP155Block: big.NewInt(0),
|
||||
EIP158Block: big.NewInt(0),
|
||||
ByzantiumBlock: big.NewInt(0),
|
||||
ConstantinopleBlock: big.NewInt(0),
|
||||
PetersburgBlock: big.NewInt(0),
|
||||
IstanbulBlock: big.NewInt(0),
|
||||
MuirGlacierBlock: big.NewInt(0),
|
||||
BerlinBlock: big.NewInt(0),
|
||||
LondonBlock: big.NewInt(0),
|
||||
ArrowGlacierBlock: big.NewInt(0),
|
||||
MergeNetsplitBlock: big.NewInt(0),
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
ShanghaiTime: u64(0),
|
||||
CancunTime: u64(0),
|
||||
PragueTime: u64(0),
|
||||
OsakaTime: u64(0),
|
||||
BPO1Time: u64(0),
|
||||
BPO2Time: u64(0),
|
||||
AmsterdamTime: u64(0),
|
||||
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
Osaka: params.DefaultOsakaBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
},
|
||||
},
|
||||
"OsakaToBPO1AtTime15k": {
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
|
|
|
|||
|
|
@ -34,17 +34,18 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
baseDir = filepath.Join(".", "testdata")
|
||||
blockTestDir = filepath.Join(baseDir, "BlockchainTests")
|
||||
stateTestDir = filepath.Join(baseDir, "GeneralStateTests")
|
||||
legacyStateTestDir = filepath.Join(baseDir, "LegacyTests", "Constantinople", "GeneralStateTests")
|
||||
transactionTestDir = filepath.Join(baseDir, "TransactionTests")
|
||||
rlpTestDir = filepath.Join(baseDir, "RLPTests")
|
||||
difficultyTestDir = filepath.Join(baseDir, "BasicTests")
|
||||
executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests")
|
||||
executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests")
|
||||
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests")
|
||||
benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks")
|
||||
baseDir = filepath.Join(".", "testdata")
|
||||
blockTestDir = filepath.Join(baseDir, "BlockchainTests")
|
||||
stateTestDir = filepath.Join(baseDir, "GeneralStateTests")
|
||||
legacyStateTestDir = filepath.Join(baseDir, "LegacyTests", "Constantinople", "GeneralStateTests")
|
||||
transactionTestDir = filepath.Join(baseDir, "TransactionTests")
|
||||
rlpTestDir = filepath.Join(baseDir, "RLPTests")
|
||||
difficultyTestDir = filepath.Join(baseDir, "BasicTests")
|
||||
executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests")
|
||||
executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests")
|
||||
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests")
|
||||
benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks")
|
||||
executionSpecBALBlockchainTestDir = filepath.Join(".", "spec-tests-bal", "fixtures", "blockchain_tests")
|
||||
)
|
||||
|
||||
func readJSON(reader io.Reader, value interface{}) error {
|
||||
|
|
|
|||
Loading…
Reference in a new issue