mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 03:10:48 +00:00
Merge branch 'master' into bs/cell-blobpool/sparse-v2
This commit is contained in:
commit
2f3a1628fe
78 changed files with 3521 additions and 941 deletions
|
|
@ -10,7 +10,6 @@ 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)
|
||||
|
|
@ -18,25 +17,25 @@ 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"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
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"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
var enc ExecutableData
|
||||
enc.ParentHash = e.ParentHash
|
||||
|
|
@ -69,25 +68,25 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
// 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"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
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"`
|
||||
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *hexutil.Bytes `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
var dec ExecutableData
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -165,7 +164,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
e.SlotNumber = (*uint64)(dec.SlotNumber)
|
||||
}
|
||||
if dec.BlockAccessList != nil {
|
||||
e.BlockAccessList = dec.BlockAccessList
|
||||
e.BlockAccessList = *dec.BlockAccessList
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
|
|
@ -25,7 +26,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
|
|
@ -83,40 +86,41 @@ 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"`
|
||||
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
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"`
|
||||
SlotNumber *uint64 `json:"slotNumber,omitempty"`
|
||||
BlockAccessList []byte `json:"blockAccessList,omitempty"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
type executableDataMarshaling struct {
|
||||
Number hexutil.Uint64
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
BaseFeePerGas *hexutil.Big
|
||||
ExtraData hexutil.Bytes
|
||||
LogsBloom hexutil.Bytes
|
||||
Transactions []hexutil.Bytes
|
||||
BlobGasUsed *hexutil.Uint64
|
||||
ExcessBlobGas *hexutil.Uint64
|
||||
SlotNumber *hexutil.Uint64
|
||||
Number hexutil.Uint64
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
BaseFeePerGas *hexutil.Big
|
||||
ExtraData hexutil.Bytes
|
||||
LogsBloom hexutil.Bytes
|
||||
Transactions []hexutil.Bytes
|
||||
BlobGasUsed *hexutil.Uint64
|
||||
ExcessBlobGas *hexutil.Uint64
|
||||
SlotNumber *hexutil.Uint64
|
||||
BlockAccessList hexutil.Bytes
|
||||
}
|
||||
|
||||
// StatelessPayloadStatusV1 is the result of a stateless payload execution.
|
||||
|
|
@ -325,7 +329,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
// to be nil.
|
||||
var blockAccessListHash *common.Hash
|
||||
if data.BlockAccessList != nil {
|
||||
hash := data.BlockAccessList.Hash()
|
||||
hash := crypto.Keccak256Hash(data.BlockAccessList)
|
||||
blockAccessListHash = &hash
|
||||
}
|
||||
header := &types.Header{
|
||||
|
|
@ -352,32 +356,45 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
SlotNumber: data.SlotNumber,
|
||||
BlockAccessListHash: blockAccessListHash,
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}), nil
|
||||
body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}
|
||||
if data.BlockAccessList != nil {
|
||||
var accessList bal.BlockAccessList
|
||||
if err := rlp.DecodeBytes(data.BlockAccessList, &accessList); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode BAL: %w", err)
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(body).WithAccessListUnsafe(&accessList), nil
|
||||
}
|
||||
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(),
|
||||
SlotNumber: block.SlotNumber(),
|
||||
BlockAccessList: block.AccessList(),
|
||||
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(),
|
||||
SlotNumber: block.SlotNumber(),
|
||||
}
|
||||
if al := block.AccessList(); al != nil {
|
||||
var buf bytes.Buffer
|
||||
if err := rlp.Encode(&buf, al); err == nil {
|
||||
data.BlockAccessList = buf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Add blobs.
|
||||
|
|
@ -420,6 +437,12 @@ type ExecutionPayloadBody struct {
|
|||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
}
|
||||
|
||||
// ExecutionPayloadBodyV2 extends ExecutionPayloadBody with the block access list.
|
||||
type ExecutionPayloadBodyV2 struct {
|
||||
ExecutionPayloadBody
|
||||
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
|
||||
}
|
||||
|
||||
// Client identifiers to support ClientVersionV1.
|
||||
const (
|
||||
ClientCode = "GE"
|
||||
|
|
|
|||
|
|
@ -1,53 +1,53 @@
|
|||
# This file contains sha256 checksums of optional build dependencies.
|
||||
|
||||
# version:spec-tests v5.1.0
|
||||
# https://github.com/ethereum/execution-spec-tests/releases
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||
# version:spec-tests tests@v20.0.0
|
||||
# https://github.com/ethereum/execution-specs/releases
|
||||
# https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0
|
||||
b183702a5b447b465873865357ced9eb342315922e52e31b714e2de115dd0bb4 fixtures.tar.gz
|
||||
|
||||
# version:golang 1.25.10
|
||||
# version:golang 1.25.12
|
||||
# https://go.dev/dl/
|
||||
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz
|
||||
a194e767c2ab4216a60acc068b9dbe6bf4fae05c14bb52d6bbdcb5b3ea521308 go1.25.10.aix-ppc64.tar.gz
|
||||
52321165a3146cd91865ef98371506a846ed4dc4f9f1c9323e5ad90d2a411e06 go1.25.10.darwin-amd64.tar.gz
|
||||
795691a425de7e7cdba3544f354dcd2cebcf52e87dc6898193878f34eb6d634f go1.25.10.darwin-arm64.tar.gz
|
||||
e37b4544ba9e9e9a7ab2ed3116b3fc4d39a88da854baa5a566d9d6d3a9de7d4c go1.25.10.dragonfly-amd64.tar.gz
|
||||
2a70d1fdabab637aa442ca94599a56e381238efa20cb995d5433b8579bfe482c go1.25.10.freebsd-386.tar.gz
|
||||
9cdf522d87d47d82fec4a313cc4f8c3c94a7770426e8d443e4150a1f330cba71 go1.25.10.freebsd-amd64.tar.gz
|
||||
6da6183633e9e59ffd9edefab68b5059c89b605596d94aaba650b1681fccd35f go1.25.10.freebsd-arm.tar.gz
|
||||
7adcefeebdd05331f4d45f1ad2dddb5c53537cff6552e82f6595b3b833b95371 go1.25.10.freebsd-arm64.tar.gz
|
||||
285f80a1ace21a7d94035cd753196eeada8cacd48e6396fd116ad5eb67aea957 go1.25.10.freebsd-riscv64.tar.gz
|
||||
de7461bf0e5068a4f6e7f8713026d70516be6dbd5de5d21f9ced1c182f2f326e go1.25.10.illumos-amd64.tar.gz
|
||||
2f574f2e2e19ead5b280fec0e7af5c81b76632685f03b6ac42dfa34c4b773c52 go1.25.10.linux-386.tar.gz
|
||||
42d4f7a32316aa66591eca7e89867256057a4264451aca10570a715b3637ba70 go1.25.10.linux-amd64.tar.gz
|
||||
654da1f9b50a5d1c2a85ccf8ed405aa89c06e94d18384628bf186f7712677b08 go1.25.10.linux-arm64.tar.gz
|
||||
39f168f158e693887d3ad006168af1b1a3007b19c5993cae4d9d57f82f52aaf8 go1.25.10.linux-armv6l.tar.gz
|
||||
05401fe5ea50ad2bafb9c797ef9bf21574b0661f19ef4d0dd66af8a0fb7323f3 go1.25.10.linux-loong64.tar.gz
|
||||
d5bc2d6155d394a3aae41f21eb7c60da5595a6147aa0f30ed6b27da25e06c3f7 go1.25.10.linux-mips.tar.gz
|
||||
8c64e7493e5953c3ba3153487d2fddd7f8ed142392c77f138e6792a6c1930db4 go1.25.10.linux-mips64.tar.gz
|
||||
bd53aa2d558b7c1eadfc6bf01132e1859203a92f458ed7ba75b7f3230f14b095 go1.25.10.linux-mips64le.tar.gz
|
||||
120b254e2e2980bb06687175db5c4064a85696c53001dc9f59934ad18f74a6bc go1.25.10.linux-mipsle.tar.gz
|
||||
8a6acb21295b0ec974a44608361920ea8dbff5666631a6f556bd7d5f1d56535f go1.25.10.linux-ppc64.tar.gz
|
||||
778925fdcdf9a272f823d147fad51545c3334b7ccd8652b2ccaaf2b01800280a go1.25.10.linux-ppc64le.tar.gz
|
||||
b4f04ad0db48bcfea946db5323919cd21034e0bd2821a557dacd29c1b1013a4b go1.25.10.linux-riscv64.tar.gz
|
||||
936b953e43921a64c12da871f76871ebbeb6d2092a7b8bdc307f5246f3c662cc go1.25.10.linux-s390x.tar.gz
|
||||
061470e0bc7132146a5925a3cc28d5bc498eb1b1ff09dedcfaae10f781ff2274 go1.25.10.netbsd-386.tar.gz
|
||||
63b2d50d7f8f269a9c82d42a4060e90cffb7f9102299818bb071b067aac8da8f go1.25.10.netbsd-amd64.tar.gz
|
||||
c35129f68796526aa4dc4b6f481e2d995ef312aedadc88b659b945cc00e1f8f0 go1.25.10.netbsd-arm.tar.gz
|
||||
2f541da4e2b298154d992d1f11bbb38c89d0821d91cc50a46776d42bb5e63bca go1.25.10.netbsd-arm64.tar.gz
|
||||
2d42e569b07f1b99fdbfd008e7c22f967d165e2ce02464f46818fbed2aec43f5 go1.25.10.openbsd-386.tar.gz
|
||||
0ad05960e8c9f867328151308c87f938433bec8f22f6a9437a896e22169fc840 go1.25.10.openbsd-amd64.tar.gz
|
||||
099cc11473f99461c77161912740945308f08f6834980afb262c72bdc915f2d7 go1.25.10.openbsd-arm.tar.gz
|
||||
bdf3335d5008c1ddc81fa94892283e4f1fee22566f5351d4e726d9f55a67c838 go1.25.10.openbsd-arm64.tar.gz
|
||||
0933d418da0a61e0f29de717a77498f16b9b5b50dbe2205e20b2ed7fd4067f75 go1.25.10.openbsd-ppc64.tar.gz
|
||||
191e6f3e75712f8c13d189d53b668e2cac6449f26474c1d86fbd04f6e9846f9c go1.25.10.openbsd-riscv64.tar.gz
|
||||
68c053c8acd76c50fc430e92f4a86110ec3d97dd03d27b9339b4eaf793caff5f go1.25.10.plan9-386.tar.gz
|
||||
42e2c46638ae22d93402e79efb40faee5c42cf7c56a01bb3ab47c6bb2512b745 go1.25.10.plan9-amd64.tar.gz
|
||||
3ef1d5838b1648da16724a07b72e839ccbd7cb8899c3e0426afd6b79d494b91c go1.25.10.plan9-arm.tar.gz
|
||||
631e3716017fbec06500a628d97e1155daec3593f0a7812c2ebfe8fc8c96b2ab go1.25.10.solaris-amd64.tar.gz
|
||||
ddc693d2d9d7cc671ebb72d1d50aa05670f95b059b7d90440611af57976871d5 go1.25.10.windows-386.zip
|
||||
ca37af2dadd8544464f1a9ca7c3886499d1cdfcb263855d0a1d71f194b2bd222 go1.25.10.windows-amd64.zip
|
||||
38be57e0398bd93673d65bcae6dc7ee3cf151d7038d0dba5c60a5153022872da go1.25.10.windows-arm64.zip
|
||||
f90dcee4bd023fa376374ea0a5a6ebe553537b39c426ffd8c689469b45519932 go1.25.12.src.tar.gz
|
||||
70b4b6509ed60735eff6ed9496824ad9f96201fdf5bd190184123f5908f224ef go1.25.12.aix-ppc64.tar.gz
|
||||
00a2e743b82bccec03c51c4b0f7e46d5fec52184075fd6c5183c3bb39ae9fb00 go1.25.12.darwin-amd64.tar.gz
|
||||
fa2c88bbcf64bd3b2aef355f026cfec6d3a4a01c132f999c8f8c964eb767164f go1.25.12.darwin-arm64.tar.gz
|
||||
2124cfbc1cf0b949eb819478365d4d665f84297a60f327cc65f75f61b2629b96 go1.25.12.dragonfly-amd64.tar.gz
|
||||
bdb8ab506428e9633653e67c13d582a6230406f01037023b327427c66b058ff2 go1.25.12.freebsd-386.tar.gz
|
||||
4433a424d466d47d1716df69e6a77c65b1a34d82121488014e4656d73740ebb0 go1.25.12.freebsd-amd64.tar.gz
|
||||
429fbcb46468a66d1cd24129a1b6163167676aaae8e0989a08ba95ba6aae65c4 go1.25.12.freebsd-arm.tar.gz
|
||||
c0e31a4cb827fd20fac950bcb4688fd94cdd1491dbdcff7c3754ac8f3b136eb1 go1.25.12.freebsd-arm64.tar.gz
|
||||
0ea50c6d43e809d46c2c5504e97ef11822f7ca137625171924cda971449b5373 go1.25.12.freebsd-riscv64.tar.gz
|
||||
d74c214e0c3ae8f9db23f4ec6f6b2f6cb300c3e4b9d840b82fa517af629c455d go1.25.12.illumos-amd64.tar.gz
|
||||
b71e88ae779850dc2afcd0f2e2208798652311dfda72fdef5d05721d601b8fd3 go1.25.12.linux-386.tar.gz
|
||||
234828b7a89e0e303d2556310ee549fbcf253d28de937bac3da13d6294262ac1 go1.25.12.linux-amd64.tar.gz
|
||||
8b5884aef89600aef5b0b051fb971f11f49bb996521e911f30f02a66884f7bd2 go1.25.12.linux-arm64.tar.gz
|
||||
6cd7311c02c73ba0b482a1cf8c885268edf23519261bf4b5cef3353ad934d1f1 go1.25.12.linux-armv6l.tar.gz
|
||||
0c6b2b7db8509d1df189433709ddc8fe84c12843e3713508e9ca04dc9e75ddeb go1.25.12.linux-loong64.tar.gz
|
||||
5abc63471425ab8b3408f596cd547d28d374f6dc860a4cbd6de79497123db4f3 go1.25.12.linux-mips.tar.gz
|
||||
a18f7a5ac799ed8ac264b63707e7fa475b1b81da6bbdd41c394fcfd69cc6b736 go1.25.12.linux-mips64.tar.gz
|
||||
5bde8a5d4c05b428fca3e6022dc41d4c735858be9c6945e57473da00fcddd0cc go1.25.12.linux-mips64le.tar.gz
|
||||
33c243d2b0700589e75a410ebab5b64580a477e908872abc46eef840e6ea535a go1.25.12.linux-mipsle.tar.gz
|
||||
b5288a7adb38540177146b47aa43bc75f1936c5e14026d6b6e531b534a49eb1a go1.25.12.linux-ppc64.tar.gz
|
||||
64adb4ddefef4f0a6f11af550547f39bf510350da69ab308438a21eacfde97ad go1.25.12.linux-ppc64le.tar.gz
|
||||
26919d62d21b0bee9c5c67ad76ace462edd16c2c128a983d942cb45b0bf7693c go1.25.12.linux-riscv64.tar.gz
|
||||
09875de1d6cb3237437112271b9df3a96bac9fcd10cbf9bc777c00e67f4e3e3d go1.25.12.linux-s390x.tar.gz
|
||||
2f19f4afc3d6551804228484569ec1ebf4a57a91cda75d746384aa71d5016be7 go1.25.12.netbsd-386.tar.gz
|
||||
d2237237d34058658187455d4f05f8f4f5f2118b33827308ad8313ab0f00a693 go1.25.12.netbsd-amd64.tar.gz
|
||||
4d44c8141a829541c3499db6665a47be2c6fdb97903575058809a2a8be53af89 go1.25.12.netbsd-arm.tar.gz
|
||||
fc96791f8b9cdaea544e827dc15574731afd28b7378053e801b3bd466df6dd9d go1.25.12.netbsd-arm64.tar.gz
|
||||
e9ced39c191409207a211c6013b9a156bd90dbd5a76ebd8c66dc3bf69bcb2f9e go1.25.12.openbsd-386.tar.gz
|
||||
bae7c016f0aff806c9af08ecff96b59a232144c96b1d25f4c5f6c85c8e0dbf01 go1.25.12.openbsd-amd64.tar.gz
|
||||
8517c4bca20e975acdd5a2f5e425cd2bec737bbb6d7ee18d11e3a1545014267e go1.25.12.openbsd-arm.tar.gz
|
||||
ba4ac29243f43b85a52f12ed7dd02b767e4524785fb6d04fececb7ce125aaa82 go1.25.12.openbsd-arm64.tar.gz
|
||||
dca067f41d00ba805342062ab21b88831a06a5682e9aaec1c990c175a5656c6b go1.25.12.openbsd-ppc64.tar.gz
|
||||
4f1f5376464fc91f32cd253dadfe3535ff57d1729b24e6ab014fb83964dfe10f go1.25.12.openbsd-riscv64.tar.gz
|
||||
c938792ec65ba33592deea6673265d509aaf9b5297119bc91ae973fb6beb62b9 go1.25.12.plan9-386.tar.gz
|
||||
5aec7ab115c1c6cd049a64f1617ef91d24ea07c8b0f40436d531b42d546f5e37 go1.25.12.plan9-amd64.tar.gz
|
||||
efe1dcb9750507260a28688a749c72f35c3160646f08a80606f38c3d30d74a12 go1.25.12.plan9-arm.tar.gz
|
||||
83c42cfedcc0bb03fd601d692d75b085406c9a7f5ff505bf41030f2734b62ed2 go1.25.12.solaris-amd64.tar.gz
|
||||
18abdf76719f5f84eaa35eeaf87b467a02ed075a8632ad941f10aa7a3d0de713 go1.25.12.windows-386.zip
|
||||
d5dc82da351b00e5eedd04f41356817d674cc4308131f0f638a5b14c5c3af4cb go1.25.12.windows-amd64.zip
|
||||
054f046a5fa31fdcc9491cc19065cbf43bf521d805bbe298ae8d65dd981fca84 go1.25.12.windows-arm64.zip
|
||||
|
||||
# version:golangci 2.10.1
|
||||
# https://github.com/golangci/golangci-lint/releases/
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ func doTest(cmdline []string) {
|
|||
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||
func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
ext := ".tar.gz"
|
||||
base := "fixtures_develop"
|
||||
base := "fixtures"
|
||||
archivePath := filepath.Join(cachedir, base+ext)
|
||||
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -140,15 +140,15 @@ func Transaction(ctx *cli.Context) error {
|
|||
value = uint256.NewInt(1)
|
||||
}
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules, params.CostPerStateByte)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
continue
|
||||
}
|
||||
r.IntrinsicGas = cost.RegularGas
|
||||
if tx.Gas() < cost.RegularGas {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas)
|
||||
r.IntrinsicGas = cost
|
||||
if tx.Gas() < cost {
|
||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost)
|
||||
results = append(results, r)
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ var (
|
|||
utils.RPCGlobalEVMTimeoutFlag,
|
||||
utils.RPCGlobalTxFeeCapFlag,
|
||||
utils.RPCGlobalLogQueryLimit,
|
||||
utils.EngineMaxReorgDepthFlag,
|
||||
utils.AllowUnprotectedTxs,
|
||||
utils.BatchRequestLimit,
|
||||
utils.BatchResponseMaxSize,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/emicklei/dot v1.6.2 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.8 // indirect
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect
|
||||
github.com/ferranbt/fastssz v0.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
|
||||
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.8 h1:oQ48q/TMe2SKU8qBE3N7e4/HlG3EpJftom6EsPQgJ58=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.8/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
||||
|
|
|
|||
|
|
@ -686,6 +686,12 @@ var (
|
|||
Value: ethconfig.Defaults.RangeLimit,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
EngineMaxReorgDepthFlag = &cli.Uint64Flag{
|
||||
Name: "engine.maxreorgdepth",
|
||||
Usage: "Maximum depth the chain head can be rewound to a canonical ancestor via engine forkchoiceUpdated (0 = no limit)",
|
||||
Value: ethconfig.Defaults.EngineMaxReorgDepth,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
// Authenticated RPC HTTP settings
|
||||
AuthListenFlag = &cli.StringFlag{
|
||||
Name: "authrpc.addr",
|
||||
|
|
@ -1925,6 +1931,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(RPCGlobalTxFeeCapFlag.Name) {
|
||||
cfg.RPCTxFeeCap = ctx.Float64(RPCGlobalTxFeeCapFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EngineMaxReorgDepthFlag.Name) {
|
||||
cfg.EngineMaxReorgDepth = ctx.Uint64(EngineMaxReorgDepthFlag.Name)
|
||||
}
|
||||
if cfg.EngineMaxReorgDepth != 0 {
|
||||
log.Info("Engine API maximum reorg depth", "depth", cfg.EngineMaxReorgDepth)
|
||||
} else {
|
||||
log.Info("Engine API reorg depth limit disabled")
|
||||
}
|
||||
if ctx.Bool(NoDiscoverFlag.Name) {
|
||||
cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
|
||||
} else if ctx.IsSet(DNSDiscoveryFlag.Name) {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte)
|
||||
cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{})
|
||||
signer := gen.Signer()
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
|
|
@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
Nonce: gen.TxNonce(benchRootAddr),
|
||||
To: &toaddr,
|
||||
Value: big.NewInt(1),
|
||||
Gas: cost.RegularGas,
|
||||
Gas: cost,
|
||||
Data: data,
|
||||
GasPrice: gasPrice,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -65,12 +65,12 @@ var (
|
|||
func TestProcessUBT(t *testing.T) {
|
||||
var (
|
||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true})
|
||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||
// will not contain that copied data.
|
||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true})
|
||||
signer = types.LatestSigner(testUBTChainConfig)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||
|
|
@ -102,11 +102,11 @@ func TestProcessUBT(t *testing.T) {
|
|||
|
||||
txCost1 := params.TxGas
|
||||
txCost2 := params.TxGas
|
||||
contractCreationCost := intrinsicContractCreationGas.RegularGas +
|
||||
contractCreationCost := intrinsicContractCreationGas +
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
|
||||
739 /* execution costs */
|
||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas.RegularGas +
|
||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas +
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
|
||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
|
||||
|
|
|
|||
|
|
@ -913,7 +913,7 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
|
|||
|
||||
// noState represents if the target state requested for search
|
||||
// is unavailable and impossible to be recovered.
|
||||
noState = !bc.HasState(root) && !bc.stateRecoverable(root)
|
||||
noState = !bc.HasState(root) && !bc.StateRecoverable(root)
|
||||
|
||||
start = time.Now() // Timestamp the rewinding is restarted
|
||||
logged = time.Now() // Timestamp last progress log was printed
|
||||
|
|
@ -940,7 +940,7 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
|
|||
}
|
||||
// Check if the associated state is available or recoverable if
|
||||
// the requested root has already been crossed.
|
||||
if beyondRoot && (bc.HasState(head.Root) || bc.stateRecoverable(head.Root)) {
|
||||
if beyondRoot && (bc.HasState(head.Root) || bc.StateRecoverable(head.Root)) {
|
||||
break
|
||||
}
|
||||
// If pivot block is reached, return the genesis block as the
|
||||
|
|
@ -966,14 +966,11 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
|
|||
return head, rootNumber
|
||||
}
|
||||
}
|
||||
// Recover if the target state if it's not available yet.
|
||||
if !bc.HasState(head.Root) {
|
||||
if err := bc.triedb.Recover(head.Root); err != nil {
|
||||
log.Error("Failed to rollback state, resetting to genesis", "err", err)
|
||||
return bc.genesisBlock.Header(), rootNumber
|
||||
}
|
||||
}
|
||||
log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash())
|
||||
// Note, the state of the located head may not be physically present yet if
|
||||
// it's only recoverable. The actual recovery is intentionally deferred once
|
||||
// the new head is finalized, so that a deep rewind rolls the state back in
|
||||
// one shot.
|
||||
log.Info("Rewound to block with available state", "number", head.Number, "hash", head.Hash())
|
||||
return head, rootNumber
|
||||
}
|
||||
|
||||
|
|
@ -1034,17 +1031,9 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||
bc.currentBlock.Store(newHeadBlock)
|
||||
headBlockGauge.Update(int64(newHeadBlock.Number.Uint64()))
|
||||
|
||||
// The head state is missing, which is only possible in the path-based
|
||||
// scheme. This situation occurs when the chain head is rewound below
|
||||
// the pivot point. In this scenario, there is no possible recovery
|
||||
// approach except for rerunning a snap sync. Do nothing here until the
|
||||
// state syncer picks it up.
|
||||
if !bc.HasState(newHeadBlock.Root) {
|
||||
if newHeadBlock.Number.Uint64() != 0 {
|
||||
log.Crit("Chain is stateless at a non-genesis block")
|
||||
}
|
||||
log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash())
|
||||
}
|
||||
// Note, the located head state might not be physically present yet; in
|
||||
// the path-based scheme a recoverable state is materialized in a single
|
||||
// shot once the rewind is finalized.
|
||||
}
|
||||
// Rewind the snap block in a simpleton way to the target head
|
||||
if currentSnapBlock := bc.CurrentSnapBlock(); currentSnapBlock != nil && header.Number.Uint64() < currentSnapBlock.Number.Uint64() {
|
||||
|
|
@ -1113,6 +1102,31 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
}
|
||||
}
|
||||
// In the path-based scheme, the rewind loop above only locates the new head
|
||||
// without materializing its state, so the potentially deep rollback is done
|
||||
// here in a single shot. This rolls back the whole rewound range at once,
|
||||
// performing a single fsync rather than one per block, which is critical when
|
||||
// rewinding a large number of blocks.
|
||||
if newHeadBlock := bc.CurrentBlock(); !bc.HasState(newHeadBlock.Root) {
|
||||
switch {
|
||||
case bc.StateRecoverable(newHeadBlock.Root):
|
||||
if err := bc.triedb.Recover(newHeadBlock.Root); err != nil {
|
||||
// The state was confirmed recoverable just above, so a failure here
|
||||
// can only stem from an unexpected I/O error. There is no safe way to
|
||||
// continue with a half-rolled-back state, hence crash hard.
|
||||
log.Crit("Failed to recover state", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash(), "err", err)
|
||||
}
|
||||
case newHeadBlock.Number.Uint64() != 0:
|
||||
// rewindHead only returns a non-genesis head when its state is present
|
||||
// or recoverable, so this branch should be unreachable.
|
||||
log.Crit("Chain is stateless at a non-genesis block", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash())
|
||||
default:
|
||||
// The chain head was rewound below the snap-sync pivot to a stateless
|
||||
// genesis. There is no recovery approach except rerunning a snap sync;
|
||||
// do nothing here until the state syncer picks it up.
|
||||
log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash())
|
||||
}
|
||||
}
|
||||
// Clear out any stale content from the caches
|
||||
bc.bodyCache.Purge()
|
||||
bc.bodyRLPCache.Purge()
|
||||
|
|
@ -2402,7 +2416,7 @@ func (bc *BlockChain) insertSideChain(ctx context.Context, block *types.Block, i
|
|||
)
|
||||
parent := it.previous()
|
||||
for parent != nil && !bc.HasState(parent.Root) {
|
||||
if bc.stateRecoverable(parent.Root) {
|
||||
if bc.StateRecoverable(parent.Root) {
|
||||
if err := bc.triedb.Recover(parent.Root); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
|
@ -2464,7 +2478,7 @@ func (bc *BlockChain) recoverAncestors(ctx context.Context, block *types.Block,
|
|||
parent = block
|
||||
)
|
||||
for parent != nil && !bc.HasState(parent.Root()) {
|
||||
if bc.stateRecoverable(parent.Root()) {
|
||||
if bc.StateRecoverable(parent.Root()) {
|
||||
if err := bc.triedb.Recover(parent.Root()); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -395,11 +395,11 @@ func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool {
|
|||
return bc.HasState(block.Root())
|
||||
}
|
||||
|
||||
// stateRecoverable checks if the specified state is recoverable.
|
||||
// StateRecoverable checks if the specified state is recoverable.
|
||||
// Note, this function assumes the state is not present, because
|
||||
// state is not treated as recoverable if it's available, thus
|
||||
// false will be returned in this case.
|
||||
func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
|
||||
func (bc *BlockChain) StateRecoverable(root common.Hash) bool {
|
||||
if bc.triedb.Scheme() == rawdb.HashScheme {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -37,59 +38,67 @@ func TestEIP2780Intrinsic(t *testing.T) {
|
|||
name string
|
||||
to *common.Address
|
||||
value *uint256.Int
|
||||
want vm.GasCosts
|
||||
auths []types.SetCodeAuthorization
|
||||
want uint64
|
||||
}{
|
||||
{
|
||||
name: "self-transfer",
|
||||
to: &from,
|
||||
value: uint256.NewInt(1),
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
|
||||
want: params.TxBaseCost2780, // 12,000
|
||||
},
|
||||
{
|
||||
name: "self-transfer/zero-value",
|
||||
to: &from,
|
||||
value: uint256.NewInt(0),
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
|
||||
want: params.TxBaseCost2780, // 12,000
|
||||
},
|
||||
{
|
||||
name: "zero-value call",
|
||||
to: &to,
|
||||
value: uint256.NewInt(0),
|
||||
// TxBaseCost + ColdAccountAccess = 15,000
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780},
|
||||
// TxBaseCost + ColdAccountAccess = 15,000; the recipient touch is
|
||||
// charged at the cold rate unconditionally at the intrinsic phase.
|
||||
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam,
|
||||
},
|
||||
{
|
||||
name: "value transfer to existing EOA",
|
||||
to: &to,
|
||||
value: uint256.NewInt(1),
|
||||
// TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
params.TxValueCost2780 + params.TransferLogCost2780},
|
||||
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||
params.TxValueCost2780 + params.TransferLogCost2780,
|
||||
},
|
||||
{
|
||||
name: "contract creation, value = 0",
|
||||
to: nil,
|
||||
value: uint256.NewInt(0),
|
||||
// TxBaseCost + CreateAccess = 23,000 regular, plus one account creation in state.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
// TxBaseCost + CreateAccess = 23,000 regular. The new-account state
|
||||
// charge depends on whether the deployment target exists and is
|
||||
// charged at runtime, not intrinsically.
|
||||
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam,
|
||||
},
|
||||
{
|
||||
name: "contract creation, value > 0",
|
||||
to: nil,
|
||||
value: uint256.NewInt(1),
|
||||
// TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular, plus account creation.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
// TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular.
|
||||
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780,
|
||||
},
|
||||
{
|
||||
name: "value transfer with authorizations",
|
||||
to: &to,
|
||||
value: uint256.NewInt(1),
|
||||
auths: make([]types.SetCodeAuthorization, 3),
|
||||
// Each authorization adds the state-independent per-auth base
|
||||
// (cold authority access included).
|
||||
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||
params.TxValueCost2780 + params.TransferLogCost2780 + 3*params.RegularPerAuthBaseCost,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := IntrinsicGas(nil, nil, nil, from, tc.to, tc.value, rules8037, params.CostPerStateByte)
|
||||
got, err := IntrinsicGas(nil, nil, tc.auths, from, tc.to, tc.value, rules8037)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -105,7 +114,7 @@ func TestEIP2780Intrinsic(t *testing.T) {
|
|||
// (intrinsic + top-level + execution) recorded in the block gas pool.
|
||||
func TestEIP2780Gas(t *testing.T) {
|
||||
const (
|
||||
cold = params.ColdAccountAccess2780
|
||||
cold = params.ColdAccountAccessAmsterdam
|
||||
base = params.TxBaseCost2780
|
||||
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
|
||||
)
|
||||
|
|
@ -154,9 +163,9 @@ func TestEIP2780Gas(t *testing.T) {
|
|||
// case 8: ETH transfer creating a new account.
|
||||
{"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState},
|
||||
// case 9: contract-creation transaction, value = 0.
|
||||
{"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccess2780, newAccountState},
|
||||
{"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccessAmsterdam, newAccountState},
|
||||
// case 10: contract-creation transaction, value > 0.
|
||||
{"create/value", valueCreateTx(1), base + params.CreateAccess2780 + params.TransferLogCost2780, newAccountState},
|
||||
{"create/value", valueCreateTx(1), base + params.CreateAccessAmsterdam + params.TransferLogCost2780, newAccountState},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -177,16 +186,191 @@ func TestEIP2780Gas(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestEIP2780NewAccountFunded verifies that a value transfer creating a new
|
||||
// account both materializes and funds the recipient.
|
||||
func TestEIP2780NewAccountFunded(t *testing.T) {
|
||||
fresh := common.HexToAddress("0xbeef000000000000000000000000000000000002")
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
if _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 300_000, nil)); err != nil {
|
||||
// callTxAL builds a signed dynamic-fee call carrying an access list.
|
||||
func callTxAL(nonce uint64, to common.Address, value int64, gas uint64, al types.AccessList) *types.Transaction {
|
||||
return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
|
||||
ChainID: cfg8037.ChainID, Nonce: nonce, To: &to, Value: big.NewInt(value),
|
||||
Gas: gas, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), AccessList: al,
|
||||
})
|
||||
}
|
||||
|
||||
// accessListEntryCost is the total intrinsic cost of one address-only access
|
||||
// list entry: the EIP-8038 per-address charge plus the EIP-7981 data charge.
|
||||
const accessListEntryCost = params.TxAccessListAddressGasAmsterdam +
|
||||
common.AddressLength*params.TxCostFloorPerToken7976*params.TxTokenPerNonZeroByte
|
||||
|
||||
// TestEIP2780WarmRecipientStillChargedCold verifies that a recipient warmed by
|
||||
// the transaction's access list is still charged the recipient at the cold rate.
|
||||
func TestEIP2780WarmRecipientStillChargedCold(t *testing.T) {
|
||||
to := common.HexToAddress("0xe0a0000000000000000000000000000000000009")
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{to: {Balance: big.NewInt(1)}}))
|
||||
al := types.AccessList{{Address: to}}
|
||||
res, gp, err := applyMsg(t, sdb, callTxAL(0, to, 0, 100_000, al))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !sdb.Exist(fresh) || sdb.GetBalance(fresh).Cmp(uint256.NewInt(1)) != 0 {
|
||||
t.Fatalf("recipient not funded: exist=%v balance=%v", sdb.Exist(fresh), sdb.GetBalance(fresh))
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + accessListEntryCost
|
||||
if gp.cumulativeRegular != want {
|
||||
t.Errorf("regular gas = %d, want %d (cold recipient, no access-list discount)", gp.cumulativeRegular, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780DelegatedWarmTarget verifies that resolving the recipient's
|
||||
// delegation is charged at the warm rate when the target was warmed by the
|
||||
// access list, rather than the flat cold rate.
|
||||
func TestEIP2780DelegatedWarmTarget(t *testing.T) {
|
||||
var (
|
||||
target = common.HexToAddress("0x7a76000000000000000000000000000000000002") // codeless
|
||||
delegated = common.HexToAddress("0xde1e000000000000000000000000000000000002")
|
||||
)
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||
delegated: {Code: types.AddressToDelegation(target)},
|
||||
}))
|
||||
al := types.AccessList{{Address: target}}
|
||||
res, gp, err := applyMsg(t, sdb, callTxAL(0, delegated, 0, 100_000, al))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + accessListEntryCost + // recipient cold access (intrinsic)
|
||||
params.WarmAccountAccessAmsterdam // warm delegation-target access (runtime)
|
||||
if gp.cumulativeRegular != want {
|
||||
t.Errorf("regular gas = %d, want %d (warm delegation target)", gp.cumulativeRegular, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780RuntimeOOGRevertsDelegations verifies that running out of gas on
|
||||
// a runtime authorization charge halts the transaction and reverts all state
|
||||
// changes, including the already applied EIP-7702 delegations — while the
|
||||
// sender's nonce increment persists.
|
||||
//
|
||||
// The halt burns the regular dimension in full; the state dimension is
|
||||
// refilled by the revert and the reservoir — if any — is preserved and
|
||||
// returned to the sender rather than burnt.
|
||||
func TestEIP2780RuntimeOOGRevertsDelegations(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
gas uint64
|
||||
numAuths int
|
||||
wantUsed uint64 // = gas − reservoir: all regular burnt, reservoir returned
|
||||
}{
|
||||
// No state reservoir (gas below MaxTxGas). Gas covers the intrinsic
|
||||
// cost (TX_BASE_COST + the cold-inclusive per-authorization base for
|
||||
// a self-call) but not the runtime authorization charges
|
||||
// (ACCOUNT_WRITE + account + indicator bytes): everything is burnt.
|
||||
{"no-reservoir", 30_000, 1, 30_000},
|
||||
|
||||
// A 100,000 state reservoir (gas above MaxTxGas). The 100
|
||||
// authorizations' state charges (~21.9M) overwhelm the reservoir and
|
||||
// the regular budget they spill into. The reservoir is made whole by
|
||||
// the halt-refill and returned to the sender.
|
||||
{"with-reservoir", params.MaxTxGas + 100_000, 100, params.MaxTxGas},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
auths = make([]types.SetCodeAuthorization, tc.numAuths)
|
||||
authorities = make([]common.Address, tc.numAuths)
|
||||
)
|
||||
for i := range auths {
|
||||
key, _ := crypto.GenerateKey()
|
||||
auth, err := types.SignSetCode(key, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(cfg8037.ChainID), Address: delegate8037, Nonce: 0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("sign auth: %v", err)
|
||||
}
|
||||
auths[i], authorities[i] = auth, crypto.PubkeyToAddress(key.PublicKey)
|
||||
}
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
tx := types.MustSignNewTx(senderKey, signer8037,
|
||||
&types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(cfg8037.ChainID),
|
||||
Nonce: 0,
|
||||
To: senderAddr,
|
||||
Value: new(uint256.Int),
|
||||
Gas: tc.gas,
|
||||
GasFeeCap: new(uint256.Int),
|
||||
GasTipCap: new(uint256.Int),
|
||||
AuthList: auths,
|
||||
})
|
||||
res, gp, err := applyMsg(t, sdb, tx)
|
||||
if err != nil {
|
||||
t.Fatalf("transaction should remain valid: %v", err)
|
||||
}
|
||||
if res.Err != vm.ErrOutOfGas {
|
||||
t.Fatalf("expected out of gas, got %v", res.Err)
|
||||
}
|
||||
if res.UsedGas != tc.wantUsed {
|
||||
t.Fatalf("used gas = %d, want %d", res.UsedGas, tc.wantUsed)
|
||||
}
|
||||
// The charged state gas was refilled on the halt: the receipt is
|
||||
// all regular, burnt in full, and only the reservoir survives.
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Fatalf("state gas = %d, want 0 (refilled on halt)", gp.cumulativeState)
|
||||
}
|
||||
if gp.cumulativeRegular != tc.wantUsed {
|
||||
t.Fatalf("regular gas = %d, want %d (burnt in full)", gp.cumulativeRegular, tc.wantUsed)
|
||||
}
|
||||
for i, authority := range authorities {
|
||||
if code := sdb.GetCode(authority); len(code) != 0 {
|
||||
t.Fatalf("delegation %d persisted despite runtime OOG: %x", i, code)
|
||||
}
|
||||
if sdb.GetNonce(authority) != 0 {
|
||||
t.Fatalf("authority %d nonce persisted despite runtime OOG", i)
|
||||
}
|
||||
}
|
||||
if sdb.GetNonce(senderAddr) != 1 {
|
||||
t.Fatal("sender nonce not consumed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780SelfTransferDelegated verifies that a self-transfer incurs no
|
||||
// recipient touch or value charges, while resolving the sender's own
|
||||
// delegation is still paid for.
|
||||
func TestEIP2780SelfTransferDelegated(t *testing.T) {
|
||||
target := common.HexToAddress("0x7a76000000000000000000000000000000000003") // codeless
|
||||
sdb := mkState(types.GenesisAlloc{
|
||||
senderAddr: {Balance: big.NewInt(1e18), Code: types.AddressToDelegation(target)},
|
||||
})
|
||||
res, gp, err := applyMsg(t, sdb, callTx(0, senderAddr, 1, 100_000, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam // base + cold delegation target
|
||||
if gp.cumulativeRegular != want {
|
||||
t.Errorf("regular gas = %d, want %d (base + delegation resolution)", gp.cumulativeRegular, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780CreateInsufficientStateGas verifies that a contract-creation
|
||||
// transaction funded for its intrinsic gas but not the runtime new-account
|
||||
// state charge is included, halts out of gas and consumes the nonce.
|
||||
func TestEIP2780CreateInsufficientStateGas(t *testing.T) {
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
intrinsic := params.TxBaseCost2780 + params.CreateAccessAmsterdam // 23,000
|
||||
res, _, err := applyMsg(t, sdb, createTx(0, intrinsic, nil))
|
||||
if err != nil {
|
||||
t.Fatalf("transaction should remain valid: %v", err)
|
||||
}
|
||||
if res.Err != vm.ErrOutOfGas {
|
||||
t.Fatalf("expected out of gas, got %v", res.Err)
|
||||
}
|
||||
if res.UsedGas != intrinsic {
|
||||
t.Fatalf("used gas = %d, want %d", res.UsedGas, intrinsic)
|
||||
}
|
||||
if sdb.GetNonce(senderAddr) != 1 {
|
||||
t.Fatal("sender nonce not consumed")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -211,4 +395,405 @@ func TestEIP2780InsufficientGasForCallCharge(t *testing.T) {
|
|||
if sdb.Exist(fresh) {
|
||||
t.Fatal("recipient should not be created when the call charge cannot be paid")
|
||||
}
|
||||
if sdb.GetNonce(senderAddr) != 1 {
|
||||
t.Fatal("sender nonce not consumed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780FirstFrameHaltPreservesPreExecution verifies the gas and state
|
||||
// semantics when the top-most frame — message call or creation — halts
|
||||
// exceptionally after the pre-execution phase completed:
|
||||
//
|
||||
// - state changes applied before the frame was entered persist together
|
||||
// with their state-gas charge (the EIP-7702 delegations of a call tx);
|
||||
// - state gas pre-charged for the frame itself is refilled when the halt
|
||||
// voids it (the account-creation charge of a creation tx);
|
||||
// - after the refill the regular dimension is burnt in full, while any
|
||||
// remaining state reservoir is preserved and returned to the sender.
|
||||
func TestEIP2780FirstFrameHaltPreservesPreExecution(t *testing.T) {
|
||||
halting := common.HexToAddress("0xbad0000000000000000000000000000000000002")
|
||||
cases := []struct {
|
||||
name string
|
||||
create bool
|
||||
gas uint64
|
||||
wantUsed uint64 // = gas − preserved reservoir
|
||||
wantRegular uint64
|
||||
wantState uint64
|
||||
}{
|
||||
// Message call carrying one authorization: the delegation and its
|
||||
// state charge (account + indicator) survive the halt.
|
||||
//
|
||||
// Without a reservoir the charge spills from regular gas and everything is
|
||||
// burnt;
|
||||
//
|
||||
// With a reservoir, the reservoir remainder is preserved.
|
||||
{"call/no-reservoir", false, 1_000_000, 1_000_000, 1_000_000 - authWorstState, authWorstState},
|
||||
{"call/with-reservoir", false, params.MaxTxGas + 300_000, params.MaxTxGas + authWorstState, params.MaxTxGas, authWorstState},
|
||||
|
||||
// Creation whose init code halts: no durable account is created, so
|
||||
// the pre-charged account creation is refilled and no state gas
|
||||
// remains.
|
||||
//
|
||||
// Without a reservoir the refill repays spilled regular gas, which the
|
||||
// halt then burns along with the rest;
|
||||
//
|
||||
// With a reservoir, the refill makes the reservoir whole again and it
|
||||
// is preserved.
|
||||
{"create/no-reservoir", true, 1_000_000, 1_000_000, 1_000_000, 0},
|
||||
{"create/with-reservoir", true, params.MaxTxGas + 100_000, params.MaxTxGas, params.MaxTxGas, 0},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||
halting: {Code: []byte{0xfe}}, // INVALID
|
||||
}))
|
||||
var (
|
||||
tx *types.Transaction
|
||||
authority common.Address
|
||||
)
|
||||
if tc.create {
|
||||
tx = createTx(0, tc.gas, []byte{0xfe}) // init code: INVALID
|
||||
} else {
|
||||
var auth types.SetCodeAuthorization
|
||||
auth, authority = signAuth(t, authKeyA, delegate8037, 0)
|
||||
tx = types.MustSignNewTx(senderKey, signer8037,
|
||||
&types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(cfg8037.ChainID),
|
||||
Nonce: 0,
|
||||
To: halting,
|
||||
Value: new(uint256.Int),
|
||||
Gas: tc.gas,
|
||||
GasFeeCap: new(uint256.Int),
|
||||
GasTipCap: new(uint256.Int),
|
||||
AuthList: []types.SetCodeAuthorization{auth},
|
||||
})
|
||||
}
|
||||
res, gp, err := applyMsg(t, sdb, tx)
|
||||
if err != nil {
|
||||
t.Fatalf("transaction should remain valid: %v", err)
|
||||
}
|
||||
if res.Err == nil {
|
||||
t.Fatal("expected the frame to halt")
|
||||
}
|
||||
if res.UsedGas != tc.wantUsed {
|
||||
t.Fatalf("used gas = %d, want %d", res.UsedGas, tc.wantUsed)
|
||||
}
|
||||
if gp.cumulativeRegular != tc.wantRegular {
|
||||
t.Fatalf("regular gas = %d, want %d (burnt in full)", gp.cumulativeRegular, tc.wantRegular)
|
||||
}
|
||||
if gp.cumulativeState != tc.wantState {
|
||||
t.Fatalf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
|
||||
}
|
||||
if tc.create {
|
||||
// The halted creation is fully reverted: no durable account.
|
||||
derived := crypto.CreateAddress(senderAddr, 0)
|
||||
if code := sdb.GetCode(derived); len(code) != 0 {
|
||||
t.Fatalf("created code persisted despite halt: %x", code)
|
||||
}
|
||||
if sdb.GetNonce(derived) != 0 {
|
||||
t.Fatal("created account nonce persisted despite halt")
|
||||
}
|
||||
} else {
|
||||
// The delegation applied before the frame was entered persists.
|
||||
if code := sdb.GetCode(authority); len(code) == 0 {
|
||||
t.Fatal("delegation should persist through an in-frame halt")
|
||||
}
|
||||
}
|
||||
if sdb.GetNonce(senderAddr) != 1 {
|
||||
t.Fatal("sender nonce not consumed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780CreatePreExecutionOOGPreservesReservoir verifies that when a
|
||||
// creation transaction cannot afford the pre-execution account-creation state
|
||||
// charge (before the init-code frame is entered), the transaction halts with
|
||||
// all regular gas burnt while the state reservoir — never touched, since the
|
||||
// charge is atomic and was not applied — is preserved and returned to the
|
||||
// sender.
|
||||
func TestEIP2780CreatePreExecutionOOGPreservesReservoir(t *testing.T) {
|
||||
// Regular gas left for the pre-execution charge; together with the
|
||||
// reservoir it must not cover the account-creation cost.
|
||||
const (
|
||||
regularLeft = 100_000
|
||||
reservoir = 50_000
|
||||
)
|
||||
// Plain creation intrinsic: TX_BASE_COST + CREATE_ACCESS.
|
||||
plainIntrinsic, err := IntrinsicGas(nil, nil, nil, senderAddr, nil, new(uint256.Int), rules8037)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// For the reservoir case the gas limit must exceed MaxTxGas, which leaves
|
||||
// a huge regular budget by default. A big access list drives the intrinsic
|
||||
// cost close to MaxTxGas, shrinking the regular budget back down to
|
||||
// roughly regularLeft. Storage keys work because their intrinsic charge
|
||||
// exceeds their EIP-7623/7976 floor contribution.
|
||||
al := types.AccessList{{Address: common.HexToAddress("0xa1")}}
|
||||
baseIntrinsic, err := IntrinsicGas(nil, al, nil, senderAddr, nil, new(uint256.Int), rules8037)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
perKey := params.TxAccessListStorageKeyGasAmsterdam + uint64(common.HashLength)*params.TxCostFloorPerToken7976*params.TxTokenPerNonZeroByte
|
||||
|
||||
// Fill the transaction with accessList, drain the gas and make it
|
||||
// insufficient for account-creation cost.
|
||||
al[0].StorageKeys = make([]common.Hash, (params.MaxTxGas-regularLeft-baseIntrinsic)/perKey)
|
||||
alIntrinsic, err := IntrinsicGas(nil, al, nil, senderAddr, nil, new(uint256.Int), rules8037)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if left := params.MaxTxGas - alIntrinsic; left+reservoir >= newAccountState {
|
||||
t.Fatalf("setup: regular %d + reservoir %d must not cover the creation charge %d", left, reservoir, newAccountState)
|
||||
}
|
||||
alCreateTx := types.MustSignNewTx(senderKey, signer8037,
|
||||
&types.DynamicFeeTx{
|
||||
ChainID: cfg8037.ChainID,
|
||||
Nonce: 0,
|
||||
To: nil,
|
||||
Value: big.NewInt(0),
|
||||
Gas: params.MaxTxGas + reservoir,
|
||||
GasFeeCap: big.NewInt(0),
|
||||
GasTipCap: big.NewInt(0),
|
||||
AccessList: al,
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
tx *types.Transaction
|
||||
wantUsed uint64 // = gas − preserved reservoir
|
||||
}{
|
||||
// Gas below MaxTxGas: no reservoir, the whole limit is burnt.
|
||||
{"no-reservoir", createTx(0, plainIntrinsic+regularLeft, nil), plainIntrinsic + regularLeft},
|
||||
|
||||
// Gas above MaxTxGas: the reservoir survives the halt untouched and
|
||||
// is returned to the sender.
|
||||
{"with-reservoir", alCreateTx, params.MaxTxGas},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
res, gp, err := applyMsg(t, sdb, tc.tx)
|
||||
if err != nil {
|
||||
t.Fatalf("transaction should remain valid: %v", err)
|
||||
}
|
||||
if res.Err != vm.ErrOutOfGas {
|
||||
t.Fatalf("expected out of gas, got %v", res.Err)
|
||||
}
|
||||
if res.UsedGas != tc.wantUsed {
|
||||
t.Fatalf("used gas = %d, want %d", res.UsedGas, tc.wantUsed)
|
||||
}
|
||||
if gp.cumulativeRegular != tc.wantUsed {
|
||||
t.Fatalf("regular gas = %d, want %d (burnt in full)", gp.cumulativeRegular, tc.wantUsed)
|
||||
}
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Fatalf("state gas = %d, want 0 (charge never applied)", gp.cumulativeState)
|
||||
}
|
||||
if derived := crypto.CreateAddress(senderAddr, 0); sdb.Exist(derived) {
|
||||
t.Fatal("target account should not be created when the charge cannot be paid")
|
||||
}
|
||||
if sdb.GetNonce(senderAddr) != 1 {
|
||||
t.Fatal("sender nonce not consumed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780AuthorityAccountWrite pins the first-write ACCOUNT_WRITE rule for
|
||||
// authorities: the surcharge applies to the first paid write to the account
|
||||
// within the transaction, regardless of whether the account exists, and is
|
||||
// skipped when the write is already paid for: by TX_BASE_COST for the sender,
|
||||
// by TX_VALUE_COST for the recipient of a value-bearing transaction, or by a
|
||||
// preceding valid authorization.
|
||||
func TestEIP2780AuthorityAccountWrite(t *testing.T) {
|
||||
const (
|
||||
base = params.TxBaseCost2780
|
||||
cold = params.ColdAccountAccessAmsterdam
|
||||
aw = params.AccountWriteAmsterdam
|
||||
perAuth = params.RegularPerAuthBaseCost
|
||||
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
|
||||
)
|
||||
existingEOA := common.HexToAddress("0xe0a0000000000000000000000000000000000002")
|
||||
|
||||
auth0, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
auth1, _ := signAuth(t, authKeyA, delegate8037, 1)
|
||||
authBadNonce, _ := signAuth(t, authKeyA, delegate8037, 5)
|
||||
|
||||
// Self-sponsored authorization: the sender's nonce is bumped before the
|
||||
// authorization list is processed, hence nonce 1.
|
||||
senderAuth, err := types.SignSetCode(senderKey, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(cfg8037.ChainID), Address: delegate8037, Nonce: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// tx builds a SetCode transaction with an explicit value.
|
||||
tx := func(to common.Address, value uint64, auths ...types.SetCodeAuthorization) *types.Transaction {
|
||||
return types.MustSignNewTx(senderKey, signer8037, &types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(cfg8037.ChainID), Nonce: 0, To: to,
|
||||
Value: uint256.NewInt(value), Gas: 1_000_000,
|
||||
GasFeeCap: new(uint256.Int), GasTipCap: new(uint256.Int), AuthList: auths,
|
||||
})
|
||||
}
|
||||
fundedAuthority := types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
alloc types.GenesisAlloc
|
||||
tx *types.Transaction
|
||||
wantRegular, wantState uint64
|
||||
}{
|
||||
{
|
||||
// Materializing a fresh authority pays the first-write surcharge
|
||||
// alongside the new-account state gas and the indicator bytes.
|
||||
name: "fresh authority",
|
||||
tx: tx(existingEOA, 0, auth0),
|
||||
wantRegular: base + cold + perAuth + aw,
|
||||
wantState: authWorstState,
|
||||
},
|
||||
{
|
||||
// An existing authority still pays the surcharge: the nonce and
|
||||
// indicator stores are the first write to the account within the
|
||||
// transaction.
|
||||
name: "existing authority",
|
||||
alloc: fundedAuthority,
|
||||
tx: tx(existingEOA, 0, auth0),
|
||||
wantRegular: base + cold + perAuth + aw,
|
||||
wantState: authBaseState,
|
||||
},
|
||||
{
|
||||
// Self-sponsored: the sender's account write is prepaid by
|
||||
// TX_BASE_COST, no surcharge.
|
||||
name: "authority is sender",
|
||||
tx: tx(existingEOA, 0, senderAuth),
|
||||
wantRegular: base + cold + perAuth,
|
||||
wantState: authBaseState,
|
||||
},
|
||||
{
|
||||
// authority == tx.to with zero value: no TX_VALUE_COST was paid,
|
||||
// so the authorization write is the first paid write and the
|
||||
// surcharge applies. The recipient becomes delegated, adding a
|
||||
// cold delegation-target access at runtime.
|
||||
name: "authority is recipient, zero value",
|
||||
alloc: fundedAuthority,
|
||||
tx: tx(authority, 0, auth0),
|
||||
wantRegular: base + cold + perAuth + aw + cold,
|
||||
wantState: authBaseState,
|
||||
},
|
||||
{
|
||||
// authority == tx.to with value: TX_VALUE_COST prepaid the
|
||||
// recipient write, so no surcharge is due.
|
||||
name: "authority is recipient, value",
|
||||
alloc: fundedAuthority,
|
||||
tx: tx(authority, 1, auth0),
|
||||
wantRegular: base + cold + valueCst + perAuth + cold,
|
||||
wantState: authBaseState,
|
||||
},
|
||||
{
|
||||
// Fresh authority == tx.to with value: the authorization pays the
|
||||
// new-account state gas, and the recipient charge then sees an
|
||||
// existing account, so the leaf is not paid for twice.
|
||||
name: "authority is fresh recipient, value",
|
||||
tx: tx(authority, 1, auth0),
|
||||
wantRegular: base + cold + valueCst + perAuth + cold,
|
||||
wantState: authWorstState,
|
||||
},
|
||||
{
|
||||
// The same authority twice: only the first valid authorization
|
||||
// carries the surcharge, the account creation and the indicator.
|
||||
name: "same authority twice",
|
||||
tx: tx(existingEOA, 0, auth0, auth1),
|
||||
wantRegular: base + cold + 2*perAuth + aw,
|
||||
wantState: authWorstState,
|
||||
},
|
||||
{
|
||||
// An invalid authorization performs no write and does not count
|
||||
// as the first write; the following valid one pays in full. The
|
||||
// per-auth intrinsic base is still paid for the invalid tuple.
|
||||
name: "invalid then valid",
|
||||
tx: tx(existingEOA, 0, authBadNonce, auth0),
|
||||
wantRegular: base + cold + 2*perAuth + aw,
|
||||
wantState: authWorstState,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
alloc := types.GenesisAlloc{existingEOA: {Balance: big.NewInt(1)}}
|
||||
for addr, acc := range tc.alloc {
|
||||
alloc[addr] = acc
|
||||
}
|
||||
res, gp, err := applyMsg(t, mkState(senderAlloc(alloc)), tc.tx)
|
||||
if err != nil {
|
||||
t.Fatalf("consensus error: %v", err)
|
||||
}
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
if gp.cumulativeRegular != tc.wantRegular {
|
||||
t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, tc.wantRegular)
|
||||
}
|
||||
if gp.cumulativeState != tc.wantState {
|
||||
t.Errorf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780DelegationTargetPrewarmed pins the warm rate for delegation
|
||||
// targets that are already in accessed_addresses when the recipient is
|
||||
// loaded.
|
||||
func TestEIP2780DelegationTargetPrewarmed(t *testing.T) {
|
||||
const (
|
||||
base = params.TxBaseCost2780
|
||||
cold = params.ColdAccountAccessAmsterdam
|
||||
warm = params.WarmAccountAccessAmsterdam
|
||||
aw = params.AccountWriteAmsterdam
|
||||
perAuth = params.RegularPerAuthBaseCost
|
||||
)
|
||||
delegatedAcct := common.HexToAddress("0xde1e000000000000000000000000000000000002")
|
||||
|
||||
t.Run("target is sender", func(t *testing.T) {
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||
delegatedAcct: {Code: types.AddressToDelegation(senderAddr)},
|
||||
}))
|
||||
res, gp, err := applyMsg(t, sdb, callTx(0, delegatedAcct, 0, 100_000, nil))
|
||||
if err != nil {
|
||||
t.Fatalf("consensus error: %v", err)
|
||||
}
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
if want := base + cold + warm; gp.cumulativeRegular != want {
|
||||
t.Errorf("regular gas = %d, want %d (warm delegation target)", gp.cumulativeRegular, want)
|
||||
}
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Errorf("state gas = %d, want 0", gp.cumulativeState)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("target warmed by authorization", func(t *testing.T) {
|
||||
// A clearing authorization from a fresh authority: it creates the
|
||||
// authority account (nonce bump) and warms it, without installing an
|
||||
// indicator.
|
||||
//
|
||||
// The recipient's pre-existing delegation then resolves to
|
||||
// the freshly warmed, codeless authority at the warm rate.
|
||||
authClear, authority := signAuth(t, authKeyA, common.Address{}, 0)
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||
delegatedAcct: {Code: types.AddressToDelegation(authority)},
|
||||
}))
|
||||
res, gp, err := applyMsg(t, sdb, setCodeTx(0, delegatedAcct, []types.SetCodeAuthorization{authClear}))
|
||||
if err != nil {
|
||||
t.Fatalf("consensus error: %v", err)
|
||||
}
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
if want := base + cold + perAuth + aw + warm; gp.cumulativeRegular != want {
|
||||
t.Errorf("regular gas = %d, want %d (auth-warmed delegation target)", gp.cumulativeRegular, want)
|
||||
}
|
||||
if gp.cumulativeState != newAccountState {
|
||||
t.Errorf("state gas = %d, want %d (authority account created)", gp.cumulativeState, newAccountState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
|
|
@ -72,6 +73,40 @@ func mkState(alloc types.GenesisAlloc) *state.StateDB {
|
|||
return sdb
|
||||
}
|
||||
|
||||
// mkCommittedState is mkState with the allocation committed to disk and
|
||||
// reloaded. EIP-161-empty accounts carrying only storage do not survive an
|
||||
// in-memory Finalise; committing without empty-account deletion reproduces
|
||||
// the synthesized prestate an EIP-7610 fixture would load from disk.
|
||||
func mkCommittedState(t *testing.T, alloc types.GenesisAlloc) *state.StateDB {
|
||||
t.Helper()
|
||||
db := state.NewDatabaseForTesting()
|
||||
sdb, _ := state.New(types.EmptyRootHash, db)
|
||||
for addr, acc := range alloc {
|
||||
sdb.CreateAccount(addr)
|
||||
if acc.Balance != nil {
|
||||
sdb.AddBalance(addr, uint256.MustFromBig(acc.Balance), tracing.BalanceChangeUnspecified)
|
||||
}
|
||||
if acc.Nonce != 0 {
|
||||
sdb.SetNonce(addr, acc.Nonce, tracing.NonceChangeGenesis)
|
||||
}
|
||||
if len(acc.Code) != 0 {
|
||||
sdb.SetCode(addr, acc.Code, tracing.CodeChangeUnspecified)
|
||||
}
|
||||
for k, v := range acc.Storage {
|
||||
sdb.SetState(addr, k, v)
|
||||
}
|
||||
}
|
||||
root, err := sdb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("commit prestate: %v", err)
|
||||
}
|
||||
sdb, err = state.New(root, db)
|
||||
if err != nil {
|
||||
t.Fatalf("reopen prestate: %v", err)
|
||||
}
|
||||
return sdb
|
||||
}
|
||||
|
||||
// amsterdamCoreEVM builds an Amsterdam EVM over statedb with fees disabled.
|
||||
func amsterdamCoreEVM(sdb *state.StateDB) *vm.EVM {
|
||||
ctx := vm.BlockContext{
|
||||
|
|
@ -99,15 +134,24 @@ func applyMsg(t *testing.T, sdb *state.StateDB, tx *types.Transaction) (*Executi
|
|||
t.Fatalf("to message: %v", err)
|
||||
}
|
||||
gp := NewGasPool(evm.Context.GasLimit)
|
||||
// Drive the stateTransition directly (as ApplyMessage does) so the test can
|
||||
// inspect the final tx-level GasBudget vector via st.gasRemaining.
|
||||
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
st := newStateTransition(evm, msg, gp)
|
||||
res, err := st.execute()
|
||||
if err == nil && res != nil {
|
||||
assertPoolSane(t, res, gp)
|
||||
limit := min(msg.GasLimit, params.MaxTxGas)
|
||||
assertBudgetSane(t, vm.NewGasBudget(limit, msg.GasLimit-limit), st.gasRemaining)
|
||||
floor, ferr := FloorDataGas(rules8037, msg.From, msg.To, msg.Value, msg.Data, msg.AccessList)
|
||||
if ferr != nil {
|
||||
t.Fatalf("floor data gas: %v", ferr)
|
||||
}
|
||||
assertPoolSane(t, res, gp, floor)
|
||||
|
||||
intrinsic, ierr := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules8037)
|
||||
if ierr != nil {
|
||||
t.Fatalf("intrinsic gas: %v", ierr)
|
||||
}
|
||||
executionGas := msg.GasLimit - intrinsic
|
||||
gasLeft := min(params.MaxTxGas-intrinsic, executionGas)
|
||||
assertBudgetSane(t, vm.NewGasBudget(gasLeft, executionGas-gasLeft), st.gasRemaining)
|
||||
}
|
||||
return res, gp, err
|
||||
}
|
||||
|
|
@ -136,9 +180,11 @@ func assertBudgetSane(t *testing.T, initial, got vm.GasBudget) {
|
|||
// assertPoolSane validates the whole 2D block-gas-pool vector after a single tx.
|
||||
//
|
||||
// receipt: cumulativeUsed == res.UsedGas <= res.MaxUsedGas
|
||||
// pre-refund: cumulativeRegular + cumulativeState <= res.MaxUsedGas (peak)
|
||||
// regular: cumulativeRegular <= max(res.MaxUsedGas - cumulativeState, floor)
|
||||
// (the calldata floor pads the regular dimension alone, so the
|
||||
// dimension sum may exceed the pre-refund peak when it binds)
|
||||
// bottleneck: Used() == max(cumulativeRegular, cumulativeState) <= initial
|
||||
func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool) {
|
||||
func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool, floor uint64) {
|
||||
t.Helper()
|
||||
if gp.cumulativeUsed != res.UsedGas {
|
||||
t.Fatalf("receipt scalar = %d, want UsedGas %d", gp.cumulativeUsed, res.UsedGas)
|
||||
|
|
@ -146,8 +192,12 @@ func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool) {
|
|||
if res.UsedGas > res.MaxUsedGas {
|
||||
t.Fatalf("post-refund gas %d exceeds peak %d", res.UsedGas, res.MaxUsedGas)
|
||||
}
|
||||
if sum := gp.cumulativeRegular + gp.cumulativeState; sum > res.MaxUsedGas {
|
||||
t.Fatalf("regular+state %d exceeds peak %d", sum, res.MaxUsedGas)
|
||||
if gp.cumulativeState > res.MaxUsedGas {
|
||||
t.Fatalf("state %d exceeds peak %d", gp.cumulativeState, res.MaxUsedGas)
|
||||
}
|
||||
if cap := max(res.MaxUsedGas-gp.cumulativeState, floor); gp.cumulativeRegular > cap {
|
||||
t.Fatalf("regular %d exceeds pre-refund cap %d (peak %d, state %d, floor %d)",
|
||||
gp.cumulativeRegular, cap, res.MaxUsedGas, gp.cumulativeState, floor)
|
||||
}
|
||||
if gp.Used() != max(gp.cumulativeRegular, gp.cumulativeState) {
|
||||
t.Fatalf("block used %d != max(%d,%d)", gp.Used(), gp.cumulativeRegular, gp.cumulativeState)
|
||||
|
|
@ -185,23 +235,26 @@ func createTx(nonce, gas uint64, initCode []byte) *types.Transaction {
|
|||
var (
|
||||
deploy3 = []byte{0x60, 0x03, 0x60, 0x00, 0xf3} // init: return 3 bytes of code
|
||||
revertI = []byte{0x60, 0x00, 0x60, 0x00, 0xfd} // init: REVERT
|
||||
haltI = []byte{0xfe, 0x00, 0x00, 0x00, 0x00} // init: INVALID, exceptional halt
|
||||
)
|
||||
|
||||
// ===================== Top-level create transaction ======================
|
||||
|
||||
// A creation tx's intrinsic gas pre-charges one account creation as state gas.
|
||||
func TestCreateTxIntrinsicChargesAccountUnconditionally(t *testing.T) {
|
||||
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte)
|
||||
// A creation tx's intrinsic gas is state-independent: the new-account state
|
||||
// charge depends on whether the deployment target exists and is charged at
|
||||
// runtime (EIP-2780), not intrinsically.
|
||||
func TestCreateTxIntrinsicNoStateGas(t *testing.T) {
|
||||
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cost.StateGas != newAccountState {
|
||||
t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, newAccountState)
|
||||
if want := params.TxBaseCost2780 + params.CreateAccessAmsterdam; cost != want {
|
||||
t.Fatalf("intrinsic gas = %d, want %d", cost, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Creating onto a pre-existing (balance-only) address refills the account
|
||||
// portion; only the code deposit is charged as state gas.
|
||||
// Creating onto a pre-existing (balance-only) address incurs no new-account
|
||||
// runtime charge; only the code deposit is charged as state gas.
|
||||
func TestCreateTxPreexistingDestRefill(t *testing.T) {
|
||||
derived := crypto.CreateAddress(senderAddr, 0)
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{derived: {Balance: big.NewInt(1)}}))
|
||||
|
|
@ -214,7 +267,8 @@ func TestCreateTxPreexistingDestRefill(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// A creation tx that reverts refills the account-creation charge.
|
||||
// A creation tx that reverts refills the account-creation charge applied at
|
||||
// runtime.
|
||||
func TestCreateTxRevertRefill(t *testing.T) {
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
res, gp, err := applyMsg(t, sdb, createTx(0, 1_000_000, revertI))
|
||||
|
|
@ -229,7 +283,8 @@ func TestCreateTxRevertRefill(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// An address collision burns gas_left while refilling the account charge.
|
||||
// An address collision burns gas_left. The colliding target exists, so no
|
||||
// new-account state gas is charged at runtime in the first place.
|
||||
func TestCreateTxCollisionConsumesGasLeft(t *testing.T) {
|
||||
const gas = 1_000_000
|
||||
derived := crypto.CreateAddress(senderAddr, 0)
|
||||
|
|
@ -241,14 +296,185 @@ func TestCreateTxCollisionConsumesGasLeft(t *testing.T) {
|
|||
if !res.Failed() {
|
||||
t.Fatal("expected collision failure")
|
||||
}
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Fatalf("state gas = %d, want 0 (never charged)", gp.cumulativeState)
|
||||
}
|
||||
// All forwarded gas_left is burned: the whole gas limit is consumed as
|
||||
// regular gas.
|
||||
if want := uint64(gas); gp.cumulativeRegular != want {
|
||||
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want)
|
||||
}
|
||||
}
|
||||
|
||||
// An account can exist yet be EIP-161-empty in the middle of a transaction,
|
||||
// e.g. after being touched as the zero-balance beneficiary of a SELFDESTRUCT.
|
||||
// Deploying onto such an account should charge account-creation cost.
|
||||
func TestCreate2TransientEmptyDestNoRefill(t *testing.T) {
|
||||
var (
|
||||
orchestrator = common.HexToAddress("0xc0de000000000000000000000000000000000002")
|
||||
destructor = common.HexToAddress("0xc0de000000000000000000000000000000000003")
|
||||
target = crypto.CreateAddress2(orchestrator, [32]byte{}, crypto.Keccak256(deploy3))
|
||||
)
|
||||
// destructor: SELFDESTRUCT with zero balance to the future CREATE2 target,
|
||||
// leaving it existing but EIP-161-empty for the rest of the transaction.
|
||||
destructorCode := append(append([]byte{0x73}, target.Bytes()...), 0xff) // PUSH20 target, SELFDESTRUCT
|
||||
|
||||
// orchestrator: CALL destructor (persist the success flag in slot 0),
|
||||
// then CREATE2 deploy3 with salt 0, targeting the touched address.
|
||||
code := []byte{
|
||||
0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, // ret/arg sizes and offsets, value = 0
|
||||
0x73, // PUSH20 destructor
|
||||
}
|
||||
code = append(code, destructor.Bytes()...)
|
||||
code = append(code,
|
||||
0x62, 0x03, 0x0d, 0x40, // PUSH3 200,000 call gas
|
||||
0xf1, // CALL
|
||||
0x60, 0x00, 0x55, // SSTORE the call result at slot 0
|
||||
0x64, 0x60, 0x03, 0x60, 0x00, 0xf3, // PUSH5 deploy3 init code
|
||||
0x60, 0x00, 0x52, // MSTORE at word 0 (right-aligned, code at offset 27)
|
||||
0x60, 0x00, // salt = 0
|
||||
0x60, 0x05, // size = 5
|
||||
0x60, 0x1b, // offset = 27
|
||||
0x60, 0x00, // endowment = 0
|
||||
0xf5, 0x50, // CREATE2, POP
|
||||
0x00, // STOP
|
||||
)
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||
orchestrator: {Code: code},
|
||||
destructor: {Code: destructorCode},
|
||||
}))
|
||||
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, 2_000_000, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Failed() {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
// The inner call must have succeeded, so the target was touched into an
|
||||
// existing-but-empty account before the CREATE2 executed.
|
||||
if flag := sdb.GetState(orchestrator, common.Hash{}); flag != common.BigToHash(big.NewInt(1)) {
|
||||
t.Fatalf("destructor call flag = %v, want 1", flag)
|
||||
}
|
||||
if code := sdb.GetCode(target); len(code) != 3 {
|
||||
t.Fatalf("deployed code length = %d, want 3", len(code))
|
||||
}
|
||||
// State gas: the orchestrator's flag slot, the created contract account
|
||||
// (charged, not refilled) and the 3-byte code deposit.
|
||||
want := newSlotState + newAccountState + uint64(3*params.CostPerStateByte)
|
||||
if gp.cumulativeState != want {
|
||||
t.Fatalf("state gas = %d, want %d (account creation must not be refilled)", gp.cumulativeState, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Storage-only (EIP-7610-shaped) deployment destination ===========
|
||||
//
|
||||
// A destination carrying storage while having zero nonce, zero balance and
|
||||
// empty code is EIP-161-empty, so the account-creation state gas is
|
||||
// pre-charged in the parent frame.
|
||||
|
||||
// create2Orchestrator returns runtime code that CREATE2-deploys the given
|
||||
// 5-byte init code with salt 0 and stores the result address at slot 0.
|
||||
func create2Orchestrator(initCode []byte) []byte {
|
||||
code := append([]byte{0x64}, initCode...) // PUSH5 init code
|
||||
return append(code,
|
||||
0x60, 0x00, 0x52, // MSTORE at word 0 (right-aligned, code at offset 27)
|
||||
0x60, 0x00, // salt = 0
|
||||
0x60, 0x05, // size = 5
|
||||
0x60, 0x1b, // offset = 27
|
||||
0x60, 0x00, // endowment = 0
|
||||
0xf5, // CREATE2
|
||||
0x60, 0x00, 0x55, // SSTORE the result address at slot 0
|
||||
0x00, // STOP
|
||||
)
|
||||
}
|
||||
|
||||
// storageOnlyAlloc allocates the orchestrator and its CREATE2 target, the
|
||||
// latter carrying a single storage slot while remaining EIP-161-empty.
|
||||
func storageOnlyAlloc(orchestrator common.Address, initCode []byte) (types.GenesisAlloc, common.Address) {
|
||||
target := crypto.CreateAddress2(orchestrator, [32]byte{}, crypto.Keccak256(initCode))
|
||||
return types.GenesisAlloc{
|
||||
orchestrator: {Code: create2Orchestrator(initCode)},
|
||||
target: {Storage: map[common.Hash]common.Hash{{}: common.BigToHash(big.NewInt(1))}},
|
||||
}, target
|
||||
}
|
||||
|
||||
// Deploying onto a storage-only destination pre-charges the account creation.
|
||||
// Under the registry-based EIP-7610 check the creation proceeds, so the
|
||||
// charge is consumed like any other creation.
|
||||
func TestCreate2StorageOnlyDestCharged(t *testing.T) {
|
||||
orchestrator := common.HexToAddress("0xc0de000000000000000000000000000000000004")
|
||||
alloc, target := storageOnlyAlloc(orchestrator, deploy3)
|
||||
sdb := mkCommittedState(t, senderAlloc(alloc))
|
||||
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, 1_000_000, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Failed() {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
if code := sdb.GetCode(target); len(code) != 3 {
|
||||
t.Fatalf("deployed code length = %d, want 3", len(code))
|
||||
}
|
||||
// The created account (charged, consumed), the orchestrator's result slot
|
||||
// and the 3-byte code deposit.
|
||||
want := newAccountState + newSlotState + uint64(3*params.CostPerStateByte)
|
||||
if gp.cumulativeState != want {
|
||||
t.Fatalf("state gas = %d, want %d", gp.cumulativeState, want)
|
||||
}
|
||||
}
|
||||
|
||||
// If the pre-charge succeeds and the create frame then fails, only the create
|
||||
// frame halts: the forwarded regular gas is burnt, the account-creation
|
||||
// charge is refilled, and the parent frame continues.
|
||||
func TestCreate2StorageOnlyDestRefillOnFrameHalt(t *testing.T) {
|
||||
const gas = 1_000_000
|
||||
orchestrator := common.HexToAddress("0xc0de000000000000000000000000000000000005")
|
||||
alloc, target := storageOnlyAlloc(orchestrator, haltI)
|
||||
sdb := mkCommittedState(t, senderAlloc(alloc))
|
||||
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, gas, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Failed() {
|
||||
t.Fatalf("parent frame must survive the create-frame halt: %v", res.Err)
|
||||
}
|
||||
// The CREATE2 pushed zero and nothing was deployed.
|
||||
if flag := sdb.GetState(orchestrator, common.Hash{}); flag != (common.Hash{}) {
|
||||
t.Fatalf("create result = %v, want 0", flag)
|
||||
}
|
||||
if code := sdb.GetCode(target); len(code) != 0 {
|
||||
t.Fatalf("deployed code length = %d, want 0", len(code))
|
||||
}
|
||||
// The account-creation charge was refilled in full.
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Fatalf("state gas = %d, want 0 (refilled)", gp.cumulativeState)
|
||||
}
|
||||
// All forwarded gas_left is burned; only the refilled account charge (which
|
||||
// had spilled into regular) returns to gas_left. So regular gas consumed is
|
||||
// exactly tx.gas - newAccountState, with no other refund.
|
||||
if want := uint64(gas) - newAccountState; gp.cumulativeRegular != want {
|
||||
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want)
|
||||
if res.UsedGas > gas-newAccountState {
|
||||
t.Fatalf("used gas = %d, want at most %d (charge not refilled?)", res.UsedGas, gas-newAccountState)
|
||||
}
|
||||
}
|
||||
|
||||
// If the remaining gas cannot cover the account-creation pre-charge, the
|
||||
// parent frame itself halts with out-of-gas instead of the create frame.
|
||||
func TestCreate2StorageOnlyDestPrechargeOOG(t *testing.T) {
|
||||
// Enough for the CREATE2 constant cost, short of the 183,600 pre-charge.
|
||||
const gas = 150_000
|
||||
orchestrator := common.HexToAddress("0xc0de000000000000000000000000000000000006")
|
||||
alloc, _ := storageOnlyAlloc(orchestrator, deploy3)
|
||||
sdb := mkCommittedState(t, senderAlloc(alloc))
|
||||
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, gas, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !res.Failed() || !errors.Is(res.Err, vm.ErrOutOfGas) {
|
||||
t.Fatalf("err = %v, want out of gas in the parent frame", res.Err)
|
||||
}
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Fatalf("state gas = %d, want 0 (charge never applied)", gp.cumulativeState)
|
||||
}
|
||||
// The parent is the topmost frame, so its halt burns the whole gas limit.
|
||||
if gp.cumulativeRegular != gas {
|
||||
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, gas)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -300,15 +526,50 @@ func TestValidationIntrinsicRegularCap(t *testing.T) {
|
|||
for i := range al {
|
||||
al[i].Address = common.BigToAddress(big.NewInt(int64(i + 1)))
|
||||
}
|
||||
tx := types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
|
||||
ChainID: cfg8037.ChainID, Nonce: 0, To: &senderAddr, Value: big.NewInt(0),
|
||||
Gas: 25_000_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), AccessList: al,
|
||||
})
|
||||
tx := types.MustSignNewTx(senderKey, signer8037,
|
||||
&types.DynamicFeeTx{
|
||||
ChainID: cfg8037.ChainID,
|
||||
Nonce: 0,
|
||||
To: &senderAddr,
|
||||
Value: big.NewInt(0),
|
||||
Gas: 25_000_000,
|
||||
GasFeeCap: big.NewInt(0),
|
||||
GasTipCap: big.NewInt(0),
|
||||
AccessList: al,
|
||||
})
|
||||
if _, _, err := applyMsg(t, mkState(senderAlloc(nil)), tx); err == nil {
|
||||
t.Fatal("expected rejection for intrinsic regular over MaxTxGas")
|
||||
}
|
||||
}
|
||||
|
||||
// The EIP-7623/7976 calldata floor is capped by MaxTxGas even when the gas
|
||||
// limit covers it: a transaction whose floor cost exceeds the cap is rejected
|
||||
// regardless of its (much smaller) intrinsic gas.
|
||||
func TestValidationFloorCostCap(t *testing.T) {
|
||||
// All-zero calldata: the floor charges 64/byte while the intrinsic
|
||||
// charges only 4/byte, so the floor crosses the cap long before the
|
||||
// intrinsic does.
|
||||
data := make([]byte, 300_000) // floor ~19.2M > 16.77M cap, intrinsic ~1.2M
|
||||
floor, err := FloorDataGas(rules8037, senderAddr, &senderAddr, new(uint256.Int), data, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
intrinsic, err := IntrinsicGas(data, nil, nil, senderAddr, &senderAddr, new(uint256.Int), rules8037)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if floor <= params.MaxTxGas || intrinsic > params.MaxTxGas {
|
||||
t.Fatalf("setup: floor %d must exceed cap %d while intrinsic %d stays below",
|
||||
floor, params.MaxTxGas, intrinsic)
|
||||
}
|
||||
// The gas limit covers the floor, so the rejection can only come from
|
||||
// the MaxTxGas cap on the floor cost.
|
||||
tx := callTx(0, senderAddr, 0, floor+1_000_000, data)
|
||||
if _, _, err := applyMsg(t, mkState(senderAlloc(nil)), tx); !errors.Is(err, ErrFloorDataGas) {
|
||||
t.Fatalf("expected ErrFloorDataGas, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= Refund and gas used ===========================
|
||||
|
||||
// clearSlots deploys a contract that zeroes slots 1..n, each preset to 1.
|
||||
|
|
@ -472,18 +733,23 @@ const authKeyA = "02020202020202020202020202020202020202020202020202020020202020
|
|||
|
||||
var delegate8037 = common.HexToAddress("0xde1e8a7e")
|
||||
|
||||
// Intrinsic gas pre-charges the worst-case (account + indicator) per auth.
|
||||
func TestAuthIntrinsicWorstCase(t *testing.T) {
|
||||
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte)
|
||||
// Intrinsic gas charges only the state-independent per-authorization base;
|
||||
// the state-dependent charges are applied at runtime (EIP-2780).
|
||||
func TestAuthIntrinsicBaseOnly(t *testing.T) {
|
||||
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cost.StateGas != authWorstState {
|
||||
t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, authWorstState)
|
||||
// The recipient touch and the per-authorization authority access (priced
|
||||
// into RegularPerAuthBaseCost) are both charged at the cold rate
|
||||
// unconditionally at the intrinsic phase (EIP-2780).
|
||||
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + params.RegularPerAuthBaseCost
|
||||
if cost != want {
|
||||
t.Fatalf("intrinsic gas = %d, want %d", cost, want)
|
||||
}
|
||||
}
|
||||
|
||||
// An invalid authorization refills its entire intrinsic state-gas charge.
|
||||
// An invalid authorization incurs no runtime state-gas charge.
|
||||
func TestAuthInvalidRefillFull(t *testing.T) {
|
||||
k, _ := crypto.HexToECDSA(authKeyA)
|
||||
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
||||
|
|
@ -499,7 +765,8 @@ func TestAuthInvalidRefillFull(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// A pre-existing authority refills the account portion (indicator stands).
|
||||
// A pre-existing authority is not charged for an account leaf; only the
|
||||
// net-new indicator bytes are charged at runtime.
|
||||
func TestAuthAccountExistsRefill(t *testing.T) {
|
||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
sdb := mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}))
|
||||
|
|
@ -508,12 +775,12 @@ func TestAuthAccountExistsRefill(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if gp.cumulativeState != authBaseState {
|
||||
t.Fatalf("state gas = %d, want %d (account refilled)", gp.cumulativeState, authBaseState)
|
||||
t.Fatalf("state gas = %d, want %d (indicator only)", gp.cumulativeState, authBaseState)
|
||||
}
|
||||
}
|
||||
|
||||
// Setting a delegation on an already-delegated authority refills the indicator
|
||||
// portion (and the account portion, since the authority already exists).
|
||||
// Setting a delegation on an already-delegated authority writes no net-new
|
||||
// bytes (and no account leaf, since the authority exists): no state charge.
|
||||
func TestAuthSetOnDelegatedRefillBase(t *testing.T) {
|
||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
pre := types.AddressToDelegation(common.HexToAddress("0xabcd"))
|
||||
|
|
@ -523,11 +790,12 @@ func TestAuthSetOnDelegatedRefillBase(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if gp.cumulativeState != 0 {
|
||||
t.Fatalf("state gas = %d, want 0 (account+indicator refilled)", gp.cumulativeState)
|
||||
t.Fatalf("state gas = %d, want 0 (nothing net-new)", gp.cumulativeState)
|
||||
}
|
||||
}
|
||||
|
||||
// A net-new delegation on a fresh authority keeps the full worst-case charge.
|
||||
// A net-new delegation on a fresh authority is charged the account leaf plus
|
||||
// the indicator bytes at runtime.
|
||||
func TestAuthSetNetNewNoRefill(t *testing.T) {
|
||||
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
|
|
@ -536,11 +804,12 @@ func TestAuthSetNetNewNoRefill(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if gp.cumulativeState != authWorstState {
|
||||
t.Fatalf("state gas = %d, want %d (no refill)", gp.cumulativeState, authWorstState)
|
||||
t.Fatalf("state gas = %d, want %d (leaf + indicator)", gp.cumulativeState, authWorstState)
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing a delegation writes no indicator, so the indicator portion refills.
|
||||
// Clearing a delegation writes no indicator, so only the (new) account leaf is
|
||||
// charged at runtime.
|
||||
func TestAuthClearRefillBase(t *testing.T) {
|
||||
auth, _ := signAuth(t, authKeyA, common.Address{}, 0) // clear (address ZERO)
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
|
|
@ -549,13 +818,14 @@ func TestAuthClearRefillBase(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if want := newAccountState; gp.cumulativeState != want {
|
||||
t.Fatalf("state gas = %d, want %d (indicator refilled)", gp.cumulativeState, want)
|
||||
t.Fatalf("state gas = %d, want %d (account leaf only)", gp.cumulativeState, want)
|
||||
}
|
||||
}
|
||||
|
||||
// 0->a->0 in one tx: the indicator created by an earlier auth and cleared by a
|
||||
// later one writes zero net bytes, so both indicator charges refill.
|
||||
func TestAuthClearSameTxDoubleRefill(t *testing.T) {
|
||||
// 0->a->0 in one tx: the indicator charge applies when the delegation is set
|
||||
// and is never credited back when a later auth clears it in the same
|
||||
// transaction.
|
||||
func TestAuthClearSameTxNoRefill(t *testing.T) {
|
||||
set, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
|
|
@ -564,8 +834,28 @@ func TestAuthClearSameTxDoubleRefill(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
_ = authority
|
||||
if want := newAccountState; gp.cumulativeState != want {
|
||||
t.Fatalf("state gas = %d, want %d (net-zero delegation)", gp.cumulativeState, want)
|
||||
if want := authWorstState; gp.cumulativeState != want {
|
||||
t.Fatalf("state gas = %d, want %d (indicator charge kept on clear)", gp.cumulativeState, want)
|
||||
}
|
||||
}
|
||||
|
||||
// 0->a->0->b in one tx: the indicator charge applies at most once per
|
||||
// authority — re-installing a delegation after an intra-tx clear is free.
|
||||
func TestAuthSetClearSetChargedOnce(t *testing.T) {
|
||||
set, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||
clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
|
||||
set2, authority := signAuth(t, authKeyA, common.HexToAddress("0xde1e8a7f"), 2)
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
_, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{set, clr, set2}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The final delegation is installed and the indicator was paid exactly once.
|
||||
if _, delegated := types.ParseDelegation(sdb.GetCode(authority)); !delegated {
|
||||
t.Fatal("final delegation not installed")
|
||||
}
|
||||
if want := authWorstState; gp.cumulativeState != want {
|
||||
t.Fatalf("state gas = %d, want %d (leaf + indicator exactly once)", gp.cumulativeState, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,14 +14,6 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// EIP-8038 authorization accounting tests. The per-authorization intrinsic gas
|
||||
// pre-charges ACCOUNT_WRITE (regular) on top of REGULAR_PER_AUTH_BASE_COST.
|
||||
// applyAuthorization refunds that ACCOUNT_WRITE to the refund counter in exactly
|
||||
// the cases where no new account leaf is written: an invalid authorization, or
|
||||
// an authority whose account already exists. These white-box tests invoke
|
||||
// applyAuthorization directly and read the raw refund counter, so they observe
|
||||
// the refund before the EIP-3529 cap is applied.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
|
|
@ -37,74 +29,117 @@ import (
|
|||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// newAuthTestTransition builds a minimal stateTransition with a state reservoir,
|
||||
// suitable for calling applyAuthorization directly.
|
||||
// newAuthTestTransition builds a minimal stateTransition with a runtime gas
|
||||
// budget, suitable for calling applyAuthorization directly.
|
||||
func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
|
||||
st := newStateTransition(amsterdamCoreEVM(sdb), &Message{}, NewGasPool(30_000_000))
|
||||
st.gasRemaining = vm.NewGasBudget(0, 1_000_000) // reservoir for state-gas refills
|
||||
st.gasRemaining = vm.NewGasBudget(1_000_000, 1_000_000)
|
||||
return st
|
||||
}
|
||||
|
||||
// A net-new delegation on a fresh authority writes a new account leaf, so the
|
||||
// intrinsic ACCOUNT_WRITE stands (no refund).
|
||||
func TestAuthAccountWriteNetNewNoRefund(t *testing.T) {
|
||||
// A net-new delegation on a fresh, cold authority is charged ACCOUNT_WRITE in
|
||||
// regular gas (the authority's cold access is paid unconditionally at the
|
||||
// intrinsic phase, not here), plus the account leaf and the indicator bytes in
|
||||
// state gas.
|
||||
func TestAuthRuntimeChargeNetNew(t *testing.T) {
|
||||
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != 0 {
|
||||
t.Fatalf("refund = %d, want 0 (net-new account write)", got)
|
||||
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||
t.Fatalf("regular charged = %d, want %d", st.gasRemaining.UsedRegularGas, want)
|
||||
}
|
||||
if want := int64(authWorstState); st.gasRemaining.UsedStateGas != want {
|
||||
t.Fatalf("state charged = %d, want %d", st.gasRemaining.UsedStateGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A pre-existing authority writes no new account leaf, so the intrinsic
|
||||
// ACCOUNT_WRITE is refunded.
|
||||
func TestAuthAccountWriteExistsRefund(t *testing.T) {
|
||||
// A pre-existing authority writes no new account leaf, but its first write in
|
||||
// the transaction still carries ACCOUNT_WRITE; the authority's cold access is
|
||||
// paid at the intrinsic phase, so only the net-new indicator bytes are charged
|
||||
// as state gas here.
|
||||
func TestAuthRuntimeChargeExistingAccount(t *testing.T) {
|
||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
||||
t.Fatalf("refund = %d, want %d (account already exists)", got, params.AccountWriteAmsterdam)
|
||||
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||
t.Fatalf("regular charged = %d, want %d", st.gasRemaining.UsedRegularGas, want)
|
||||
}
|
||||
if want := int64(authBaseState); st.gasRemaining.UsedStateGas != want {
|
||||
t.Fatalf("state charged = %d, want %d", st.gasRemaining.UsedStateGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// An invalid authorization is skipped without writing any account leaf, so its
|
||||
// intrinsic ACCOUNT_WRITE is refunded.
|
||||
func TestAuthAccountWriteInvalidRefund(t *testing.T) {
|
||||
// No cold surcharge is ever charged at runtime — the authority access is priced
|
||||
// at the intrinsic phase — so an authority already warmed by the access list or
|
||||
// an earlier authorization pays only the first-write surcharge, as it would
|
||||
// whether warm or cold.
|
||||
func TestAuthRuntimeChargeWarmAuthority(t *testing.T) {
|
||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
|
||||
st.state.AddAddressToAccessList(authority)
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||
t.Fatalf("regular charged = %d, want %d (warm authority)", st.gasRemaining.UsedRegularGas, want)
|
||||
}
|
||||
if want := int64(authBaseState); st.gasRemaining.UsedStateGas != want {
|
||||
t.Fatalf("state charged = %d, want %d", st.gasRemaining.UsedStateGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// An invalid authorization is skipped without any runtime charge.
|
||||
func TestAuthRuntimeInvalidNoCharge(t *testing.T) {
|
||||
k, _ := crypto.HexToECDSA(authKeyA)
|
||||
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
|
||||
})
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil {
|
||||
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]*authTracking{}); err == nil {
|
||||
t.Fatal("expected invalid-authorization error")
|
||||
}
|
||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
||||
t.Fatalf("refund = %d, want %d (invalid authorization)", got, params.AccountWriteAmsterdam)
|
||||
if st.gasRemaining.UsedRegularGas != 0 || st.gasRemaining.UsedStateGas != 0 {
|
||||
t.Fatalf("charged = <%d,%d>, want <0,0> (invalid authorization)",
|
||||
st.gasRemaining.UsedRegularGas, st.gasRemaining.UsedStateGas)
|
||||
}
|
||||
}
|
||||
|
||||
// The same authority across two authorizations writes its account leaf only
|
||||
// once: the first auth pays ACCOUNT_WRITE, the second (which now sees the
|
||||
// account as existing) is refunded.
|
||||
func TestAuthAccountWriteDuplicateOnce(t *testing.T) {
|
||||
// The same authority across two authorizations is charged once: the first auth
|
||||
// warms the authority, materializes the account and installs the indicator, so
|
||||
// the second incurs no further charge.
|
||||
func TestAuthRuntimeDuplicateAuthorityOnce(t *testing.T) {
|
||||
a0, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||
a1, _ := signAuth(t, authKeyA, delegate8037, 1)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
delegates := map[common.Address]bool{}
|
||||
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
|
||||
authorities := map[common.Address]*authTracking{}
|
||||
if err := st.applyAuthorization(rules8037, &a0, authorities); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != 0 {
|
||||
t.Fatalf("refund after first auth = %d, want 0", got)
|
||||
}
|
||||
if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil {
|
||||
if err := st.applyAuthorization(rules8037, &a1, authorities); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
||||
t.Fatalf("refund after duplicate auth = %d, want %d", got, params.AccountWriteAmsterdam)
|
||||
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||
t.Fatalf("regular charged = %d, want %d (once)", st.gasRemaining.UsedRegularGas, want)
|
||||
}
|
||||
if want := int64(authWorstState); st.gasRemaining.UsedStateGas != want {
|
||||
t.Fatalf("state charged = %d, want %d (once)", st.gasRemaining.UsedStateGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A budget that cannot cover the runtime charge aborts authorization
|
||||
// processing with ErrOutOfGasRuntime, without mutating the authority.
|
||||
func TestAuthRuntimeOutOfGas(t *testing.T) {
|
||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
st.gasRemaining = vm.NewGasBudget(10_000, 0) // covers neither leaf nor indicator
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != ErrOutOfGasRuntime {
|
||||
t.Fatalf("err = %v, want ErrOutOfGasRuntime", err)
|
||||
}
|
||||
if st.state.GetNonce(authority) != 0 || len(st.state.GetCode(authority)) != 0 {
|
||||
t.Fatal("authority mutated despite out-of-gas runtime charge")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,4 +137,9 @@ var (
|
|||
ErrAuthorizationInvalidSignature = errors.New("EIP-7702 authorization has invalid signature")
|
||||
ErrAuthorizationDestinationHasCode = errors.New("EIP-7702 authorization destination is a contract")
|
||||
ErrAuthorizationNonceMismatch = errors.New("EIP-7702 authorization nonce does not match current account nonce")
|
||||
|
||||
// ErrOutOfGasRuntime is returned when the transaction's gas budget cannot
|
||||
// cover an EIP-2780 runtime charge. The transaction remains valid: the top
|
||||
// frame halts out of gas and its state changes are reverted.
|
||||
ErrOutOfGasRuntime = errors.New("out of gas covering EIP-2780 runtime charge")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -176,8 +176,8 @@ func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
|
|||
|
||||
// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
|
||||
// has never attempted snap sync, the last pivot will always be nil. The marker
|
||||
// is written during snap sync and never cleared, so that a rollback past the
|
||||
// pivot can re-enable snap sync.
|
||||
// is written during snap sync and never cleared, so that a rewind below the
|
||||
// pivot can be detected.
|
||||
func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(lastPivotKey)
|
||||
if len(data) == 0 {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func InitDatabaseFromFreezer(db ethdb.Database) {
|
|||
i += uint64(len(data))
|
||||
// If we've spent too much time already, notify the user of what we're doing
|
||||
if time.Since(logged) > 8*time.Second {
|
||||
log.Info("Initializing database from freezer", "total", frozen, "number", i, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
log.Info("Initializing database from freezer", "total", frozen, "number", i-1, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
logged = time.Now()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ var (
|
|||
// persistentStateIDKey tracks the id of latest stored state(for path-based only).
|
||||
persistentStateIDKey = []byte("LastStateID")
|
||||
|
||||
// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
|
||||
// lastPivotKey tracks the last pivot block used by snap sync (to reject sethead below it).
|
||||
lastPivotKey = []byte("LastPivot")
|
||||
|
||||
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -68,33 +69,28 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules) (uint64, error) {
|
||||
isContractCreation := to == nil
|
||||
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas vm.GasCosts
|
||||
var gas uint64
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value)
|
||||
if isContractCreation {
|
||||
// New-account creation is charged as state gas (EIP-8037).
|
||||
gas.StateGas = params.AccountCreationSize * costPerStateByte
|
||||
}
|
||||
gas = intrinsicBaseGasEIP2780(from, to, value)
|
||||
} else if isContractCreation && rules.IsHomestead {
|
||||
gas.RegularGas = params.TxGasContractCreation
|
||||
gas = params.TxGasContractCreation
|
||||
} else {
|
||||
gas.RegularGas = params.TxGas
|
||||
gas = params.TxGas
|
||||
}
|
||||
// Add gas for authorizations
|
||||
if authList != nil {
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas += uint64(len(authList)) * (params.AccountWriteAmsterdam + params.RegularPerAuthBaseCost)
|
||||
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
|
||||
gas += uint64(len(authList)) * params.RegularPerAuthBaseCost
|
||||
} else {
|
||||
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
gas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
}
|
||||
}
|
||||
dataLen := uint64(len(data))
|
||||
// Bump the required gas by the amount of transactional data
|
||||
dataLen := uint64(len(data))
|
||||
if dataLen > 0 {
|
||||
// Zero and non-zero bytes are priced differently
|
||||
z := uint64(bytes.Count(data, []byte{0}))
|
||||
|
|
@ -105,24 +101,25 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
if rules.IsIstanbul {
|
||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||
}
|
||||
if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += nz * nonZeroGas
|
||||
gas += nz * nonZeroGas
|
||||
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += z * params.TxDataZeroGas
|
||||
gas += z * params.TxDataZeroGas
|
||||
|
||||
if isContractCreation && rules.IsShanghai {
|
||||
lenWords := toWordSize(dataLen)
|
||||
if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += lenWords * params.InitCodeWordGas
|
||||
gas += lenWords * params.InitCodeWordGas
|
||||
}
|
||||
}
|
||||
// Add the gas for accessList
|
||||
if accessList != nil {
|
||||
addresses := uint64(len(accessList))
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
|
|
@ -134,14 +131,14 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
addressCost = params.TxAccessListAddressGasAmsterdam
|
||||
storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam
|
||||
}
|
||||
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += addresses * addressCost
|
||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
gas += addresses * addressCost
|
||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += storageKeys * storageKeyCost
|
||||
gas += storageKeys * storageKeyCost
|
||||
|
||||
// EIP-7981: access list data is charged in addition to the base charge.
|
||||
if rules.IsAmsterdam {
|
||||
|
|
@ -149,38 +146,41 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
)
|
||||
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += addresses * addressCost
|
||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
gas += addresses * addressCost
|
||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += storageKeys * storageKeyCost
|
||||
gas += storageKeys * storageKeyCost
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
// intrinsicBaseGasEIP2780 computes the regular-gas portion of the EIP-2780
|
||||
// intrinsic base cost: the per-resource decomposition of the legacy flat 21,000.
|
||||
// intrinsicBaseGasEIP2780 computes the intrinsic base cost of the transaction.
|
||||
func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 {
|
||||
var (
|
||||
isContractCreation = to == nil
|
||||
isSelfTransfer = to != nil && *to == from
|
||||
hasValue = value != nil && !value.IsZero()
|
||||
)
|
||||
// tx.sender: signature recovery plus the sender account access and write.
|
||||
// tx.sender: signature recovery, the sender account's access and write,
|
||||
// and the inclusion of the transaction in the block (which is transient
|
||||
// and expires with history).
|
||||
gas := params.TxBaseCost2780
|
||||
|
||||
// tx.to charge.
|
||||
// tx.to charge. Per EIP-2780 the recipient touch is charged at the cold
|
||||
// rate unconditionally at the intrinsic phase, independent of the account's
|
||||
// warm/cold state.
|
||||
switch {
|
||||
case isSelfTransfer:
|
||||
// The recipient account is already accessed and written as the sender.
|
||||
case isContractCreation:
|
||||
gas += params.CreateAccess2780
|
||||
gas += params.CreateAccessAmsterdam
|
||||
default:
|
||||
gas += params.ColdAccountAccess2780
|
||||
gas += params.ColdAccountAccessAmsterdam
|
||||
}
|
||||
|
||||
// tx.value charge.
|
||||
|
|
@ -242,10 +242,12 @@ func FloorDataGas(rules params.Rules, from common.Address, to *common.Address, v
|
|||
tokenCost = params.TxCostFloorPerToken
|
||||
}
|
||||
|
||||
// The floor is anchored to the transaction base cost.
|
||||
// The floor is anchored to the transaction base cost. Under EIP-2780 that
|
||||
// base is the per-resource decomposition (the same one used by the intrinsic
|
||||
// gas), so the floor never undercuts the transaction's own base.
|
||||
floorBase := params.TxGas
|
||||
if rules.IsAmsterdam {
|
||||
floorBase = params.TxBaseCost2780
|
||||
floorBase = intrinsicBaseGasEIP2780(from, to, value)
|
||||
}
|
||||
// Check for overflow
|
||||
if (math.MaxUint64-floorBase)/tokenCost < tokens {
|
||||
|
|
@ -260,7 +262,6 @@ func toWordSize(size uint64) uint64 {
|
|||
if size > math.MaxUint64-31 {
|
||||
return math.MaxUint64/32 + 1
|
||||
}
|
||||
|
||||
return (size + 31) / 32
|
||||
}
|
||||
|
||||
|
|
@ -423,24 +424,16 @@ func (st *stateTransition) to() common.Address {
|
|||
return *st.msg.To
|
||||
}
|
||||
|
||||
// buyGas pre-pays gas from the sender's balance and initializes the
|
||||
// transaction's gas budget. It is invoked at the tail of preCheck.
|
||||
// buyGas pre-pays gas from the sender's balance.
|
||||
//
|
||||
// The balance requirement is the worst-case ETH the tx may need to lock
|
||||
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
|
||||
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
|
||||
// returns ErrInsufficientFunds. After the check, the sender is actually
|
||||
// debited `msg.GasLimit × msg.GasPrice` (plus `blobGas × blobBaseFee`
|
||||
// under Cancun), the cap-vs-tip differential is settled at tx end.
|
||||
// returns ErrInsufficientFunds.
|
||||
//
|
||||
// The gas budget is seeded into both `initialBudget` (frozen snapshot
|
||||
// for tx-end accounting) and `gasRemaining` (live running balance):
|
||||
//
|
||||
// - Pre-Amsterdam: one-dimensional regular budget equal to
|
||||
// `msg.GasLimit`; the state-gas reservoir is zero.
|
||||
// - Amsterdam+ (EIP-8037): two-dimensional budget. Regular gas is
|
||||
// capped at `MaxTxGas` (EIP-7825, 16_777_216); any excess from
|
||||
// `msg.GasLimit` above that cap becomes the state-gas reservoir.
|
||||
// After the check, the sender is actually debited `msg.GasLimit × msg.GasPrice`
|
||||
// (plus `blobGas × blobBaseFee` under Cancun), the cap-vs-tip differential
|
||||
// is settled at tx end.
|
||||
func (st *stateTransition) buyGas() error {
|
||||
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
||||
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
||||
|
|
@ -493,54 +486,63 @@ func (st *stateTransition) buyGas() error {
|
|||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
|
||||
// Reserve the gas budget in the block gas pool
|
||||
var err error
|
||||
if isAmsterdam {
|
||||
err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit)
|
||||
} else {
|
||||
err = st.gp.CheckGasLegacy(st.msg.GasLimit)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// After Amsterdam we limit the regular gas to 16M, the data gas to the transaction limit
|
||||
limit := st.msg.GasLimit
|
||||
if isAmsterdam {
|
||||
limit = min(st.msg.GasLimit, params.MaxTxGas)
|
||||
}
|
||||
st.gasRemaining = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
|
||||
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
|
||||
}
|
||||
// Deduct the gas cost from the sender's balance
|
||||
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||
return nil
|
||||
}
|
||||
|
||||
// initRuntimeGasBudget initializes the transaction's running gas budget with the
|
||||
// gas remaining after the intrinsic cost has been deducted.
|
||||
//
|
||||
// After Amsterdam (EIP-8037) the intrinsic cost counts towards the EIP-7825
|
||||
// regular-gas cap:
|
||||
//
|
||||
// execution_gas = tx.gas - intrinsic_gas
|
||||
// regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic_gas
|
||||
// gas_left = min(regular_gas_budget, execution_gas)
|
||||
// state_gas_reservoir = execution_gas - gas_left
|
||||
func (st *stateTransition) initRuntimeGasBudget(rules params.Rules, intrinsicGas uint64) {
|
||||
executionGas := st.msg.GasLimit - intrinsicGas
|
||||
gasLeft := executionGas
|
||||
if rules.IsAmsterdam {
|
||||
gasLeft = min(params.MaxTxGas-intrinsicGas, executionGas)
|
||||
}
|
||||
st.gasRemaining = vm.NewGasBudget(gasLeft, executionGas-gasLeft)
|
||||
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: st.msg.GasLimit}, st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
}
|
||||
|
||||
// preCheck performs all pre-execution validation that does not require
|
||||
// the EVM to run, then ends by calling buyGas to lock in the gas budget.
|
||||
// the EVM to run, then ends by calling buyGas to lock ether for prepay.
|
||||
// It returns a consensus error if any of the following fail:
|
||||
//
|
||||
// - Sender nonce matches state and is not at 2^64-1 (EIP-2681).
|
||||
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam
|
||||
// (the cap also bounds the regular dimension after Amsterdam, but
|
||||
// it is enforced there via the two-dimensional budget in buyGas).
|
||||
//
|
||||
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam.
|
||||
//
|
||||
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
|
||||
// EIP-7702 delegation designator.
|
||||
//
|
||||
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
|
||||
//
|
||||
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
|
||||
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
|
||||
//
|
||||
// - Blob fee-cap not below the current blob base fee (Cancun+).
|
||||
//
|
||||
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
|
||||
// authorization list.
|
||||
//
|
||||
// - EIP-3860 init code size cap on create transactions (Shanghai+,
|
||||
// with the raised Amsterdam cap).
|
||||
//
|
||||
// - Insufficient block gas budget for including the transaction.
|
||||
//
|
||||
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
|
||||
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
|
||||
func (st *stateTransition) preCheck() error {
|
||||
func (st *stateTransition) preCheck(rules params.Rules) error {
|
||||
// Only check transactions that are not fake
|
||||
msg := st.msg
|
||||
if !msg.SkipNonceChecks {
|
||||
|
|
@ -557,13 +559,9 @@ func (st *stateTransition) preCheck() error {
|
|||
msg.From.Hex(), stNonce)
|
||||
}
|
||||
}
|
||||
var (
|
||||
isOsaka = st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
isAmsterdam = st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
)
|
||||
if !msg.SkipTransactionChecks {
|
||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
if !rules.IsAmsterdam && rules.IsOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
}
|
||||
// Make sure the sender is an EOA
|
||||
|
|
@ -574,7 +572,7 @@ func (st *stateTransition) preCheck() error {
|
|||
}
|
||||
}
|
||||
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
||||
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
if rules.IsLondon {
|
||||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||
if !skipCheck {
|
||||
|
|
@ -601,7 +599,7 @@ func (st *stateTransition) preCheck() error {
|
|||
if len(msg.BlobHashes) == 0 {
|
||||
return ErrMissingBlobHashes
|
||||
}
|
||||
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
||||
if rules.IsOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
||||
return ErrTooManyBlobs
|
||||
}
|
||||
for i, hash := range msg.BlobHashes {
|
||||
|
|
@ -611,7 +609,7 @@ func (st *stateTransition) preCheck() error {
|
|||
}
|
||||
}
|
||||
// Check that the user is paying at least the current blob fee
|
||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if rules.IsCancun {
|
||||
if st.blobGasUsed() > 0 {
|
||||
// Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0
|
||||
|
|
@ -634,6 +632,22 @@ func (st *stateTransition) preCheck() error {
|
|||
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
||||
}
|
||||
}
|
||||
// Check whether the init code size has been exceeded (EIP-3860).
|
||||
if msg.To == nil {
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Reserve the gas budget in the block gas pool
|
||||
var err error
|
||||
if rules.IsAmsterdam {
|
||||
err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit)
|
||||
} else {
|
||||
err = st.gp.CheckGasLegacy(st.msg.GasLimit)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return st.buyGas()
|
||||
}
|
||||
|
||||
|
|
@ -649,32 +663,25 @@ func (st *stateTransition) preCheck() error {
|
|||
// If a consensus error is encountered, it is returned directly with a
|
||||
// nil EVM execution result.
|
||||
func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||
// Validate the message and pre-pay gas.
|
||||
if err := st.preCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Charge intrinsic gas (with overflow detection inside IntrinsicGas).
|
||||
// Under Amsterdam the cost is two-dimensional and Charge debits both
|
||||
// regular and state in one step.
|
||||
var (
|
||||
msg = st.msg
|
||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
||||
contractCreation = msg.To == nil
|
||||
floorDataGas uint64
|
||||
)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
|
||||
// Validate the message and pre-pay gas.
|
||||
if err := st.preCheck(rules); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Calculate the intrinsic gas of this transaction and make sure the gas limit
|
||||
// is sufficient to cover that.
|
||||
intrinsicGas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prior, sufficient := st.gasRemaining.Charge(cost)
|
||||
if !sufficient {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
|
||||
if msg.GasLimit < intrinsicGas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, intrinsicGas)
|
||||
}
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
|
||||
// Validate the EIP-7623 calldata floor against the gas limit. The floor inflates
|
||||
// the total gas usage at tx end, so the gas limit must be sufficient to cover that.
|
||||
if rules.IsPrague {
|
||||
|
|
@ -687,13 +694,15 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
if msg.GasLimit < floorDataGas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
||||
}
|
||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||
if rules.IsAmsterdam && max(cost.RegularGas, floorDataGas) > params.MaxTxGas {
|
||||
return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas)
|
||||
}
|
||||
}
|
||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||
// params.MaxTxGas, but the intrinsic cost and calldata floor
|
||||
// cost is still capped by it.
|
||||
if rules.IsAmsterdam && max(intrinsicGas, floorDataGas) > params.MaxTxGas {
|
||||
return nil, fmt.Errorf("%w: intrinsic cost %v, floor: %v", ErrFloorDataGas, intrinsicGas, floorDataGas)
|
||||
}
|
||||
|
||||
// EIP-4762 setup
|
||||
if rules.IsEIP4762 {
|
||||
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||
|
||||
|
|
@ -718,56 +727,18 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// - enable block-level accessList construction (EIP-7928)
|
||||
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
||||
|
||||
// Initialize the running gas budget with the post-intrinsic remainder.
|
||||
st.initRuntimeGasBudget(rules, intrinsicGas)
|
||||
|
||||
// Execute the top-most frame
|
||||
var (
|
||||
ret []byte
|
||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||
result vm.GasBudget
|
||||
ret []byte
|
||||
vmerr error // vm errors do not effect consensus
|
||||
)
|
||||
if contractCreation {
|
||||
// Check whether the init code size has been exceeded.
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Execute the transaction's creation.
|
||||
var creation bool
|
||||
ret, _, result, creation, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||
st.gasRemaining.Absorb(result)
|
||||
|
||||
// If the contract creation failed, or the destination was pre-existing,
|
||||
// refund the account-creation state gas pre-charged in IntrinsicGas.
|
||||
if rules.IsAmsterdam && !creation {
|
||||
st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
ret, vmerr = st.executeCreate(rules, value)
|
||||
} else {
|
||||
// Increment the nonce for the next transaction.
|
||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||
|
||||
// Apply EIP-7702 authorizations.
|
||||
st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
|
||||
|
||||
// Perform convenience warming of sender's delegation target. Although the
|
||||
// sender is already warmed in Prepare(..), it's possible a delegation to
|
||||
// the account was deployed during this transaction. To handle correctly,
|
||||
// simply wait until the final state of delegations is determined before
|
||||
// performing the resolution and warming.
|
||||
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
||||
st.state.AddAddressToAccessList(addr)
|
||||
// Record in BAL
|
||||
if rules.IsAmsterdam {
|
||||
st.state.GetCode(addr)
|
||||
}
|
||||
}
|
||||
// EIP-2780: charge the transaction's top-level recipient costs. If the
|
||||
// budget cannot cover the charge, the top frame halts out of gas.
|
||||
if rules.IsAmsterdam && !st.chargeCallRecipientEIP2780(value) {
|
||||
vmerr = vm.ErrOutOfGas
|
||||
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||
} else {
|
||||
// Execute the transaction's call.
|
||||
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||
st.gasRemaining.Absorb(result)
|
||||
}
|
||||
ret, vmerr = st.executeCall(rules, value)
|
||||
}
|
||||
|
||||
// Settle down the gas usage and refund the ETH back if any remaining
|
||||
|
|
@ -808,43 +779,150 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// chargeCallRecipientEIP2780 applies the EIP-2780 transaction top-level gas costs for
|
||||
// a message-call transaction, charged before any opcode executes:
|
||||
//
|
||||
// - if the recipient is EIP-161 non-existent and the transaction carries value,
|
||||
// charge for account creation.
|
||||
//
|
||||
// - if the recipient is an EIP-7702 delegated account, resolving the delegation
|
||||
// loads the target's code, charged an additional cold account access in
|
||||
// regular gas.
|
||||
func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
||||
var (
|
||||
cost vm.GasCosts
|
||||
to = *st.msg.To
|
||||
)
|
||||
// This runs in the topmost frame before any bytecode executes, so unlike the
|
||||
// execution-level checks which must use StateDB.Empty because SELFDESTRUCT can
|
||||
// leave a transient EIP-161-empty account, no empty account can exist here, and
|
||||
// !Exist is equivalent to Empty.
|
||||
if !value.IsZero() && !st.state.Exist(to) {
|
||||
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
|
||||
// executeCreate runs the top-level frame of a contract-creation transaction
|
||||
// and returns the EVM return data and the frame-level execution error.
|
||||
func (st *stateTransition) executeCreate(rules params.Rules, value *uint256.Int) ([]byte, error) {
|
||||
msg := st.msg
|
||||
|
||||
var chargedCreation bool
|
||||
if rules.IsAmsterdam {
|
||||
addr := crypto.CreateAddress(msg.From, st.state.GetNonce(msg.From))
|
||||
if st.state.Empty(addr) {
|
||||
if !st.chargeRuntimeGas(vm.GasCosts{StateGas: params.AccountCreationSize * st.evm.Context.CostPerStateByte}) {
|
||||
// The nonce increment normally performed inside evm.Create
|
||||
// must still happen for the included transaction.
|
||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeContractCreator)
|
||||
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||
return nil, vm.ErrOutOfGas
|
||||
}
|
||||
chargedCreation = true
|
||||
}
|
||||
}
|
||||
if _, ok := types.ParseDelegation(st.state.GetCode(to)); ok {
|
||||
// EIP-2780: The tx.sender, tx.to, and (where applicable) delegation-target
|
||||
// charges above are always at the cold rate.
|
||||
//
|
||||
// The delegation-target is already warmed before, no double warming here.
|
||||
cost.RegularGas += params.ColdAccountAccess2780
|
||||
// The first frame is entered with the gas remaining after the runtime
|
||||
// charges.
|
||||
ret, _, result, vmerr := st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||
st.gasRemaining.Absorb(result)
|
||||
|
||||
// If the contract creation failed (e.g. the initcode reverted or halted),
|
||||
// refill the account-creation state gas charged at runtime.
|
||||
if rules.IsAmsterdam && chargedCreation && vmerr != nil {
|
||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
if cost == (vm.GasCosts{}) {
|
||||
return true
|
||||
// If the top-most frame halted, drain the leftover regular gas rather
|
||||
// than returning it to the sender. The frame exit itself already burned
|
||||
// its gas left, but the refill above repays the regular gas the charge
|
||||
// originally borrowed, and on a halt that repayment must be burned as
|
||||
// well. The state dimension is left untouched.
|
||||
if rules.IsAmsterdam && vmerr != nil && vmerr != vm.ErrExecutionReverted {
|
||||
st.gasRemaining.DrainRegular()
|
||||
}
|
||||
return ret, vmerr
|
||||
}
|
||||
|
||||
// executeCall runs the top-level frame of a message-call transaction and
|
||||
// returns the EVM return data and the frame-level execution error.
|
||||
func (st *stateTransition) executeCall(rules params.Rules, value *uint256.Int) ([]byte, error) {
|
||||
msg := st.msg
|
||||
|
||||
// Increment the nonce for the next transaction.
|
||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||
|
||||
if rules.IsAmsterdam {
|
||||
snapshot := st.state.Snapshot()
|
||||
if !st.applyAuthorizations(rules, st.msg.SetCodeAuthorizations) {
|
||||
st.state.RevertToSnapshot(snapshot)
|
||||
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||
return nil, vm.ErrOutOfGas
|
||||
}
|
||||
if !st.chargeCallRecipientEIP2780(value) {
|
||||
st.state.RevertToSnapshot(snapshot)
|
||||
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||
return nil, vm.ErrOutOfGas
|
||||
}
|
||||
} else {
|
||||
// Apply EIP-7702 authorizations.
|
||||
st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
|
||||
|
||||
// Perform convenience warming of sender's delegation target. Although the
|
||||
// sender is already warmed in Prepare(..), it's possible a delegation to
|
||||
// the account was deployed during this transaction. To handle correctly,
|
||||
// simply wait until the final state of delegations is determined before
|
||||
// performing the resolution and warming.
|
||||
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
||||
st.state.AddAddressToAccessList(addr)
|
||||
}
|
||||
}
|
||||
ret, result, vmerr := st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||
st.gasRemaining.Absorb(result)
|
||||
|
||||
// If the call frame reverts or halts exceptionally, the charged state-gas
|
||||
// is refilled back to the state reservoir in Amsterdam.
|
||||
if rules.IsAmsterdam && vmerr != nil && !value.IsZero() && st.evm.StateDB.Empty(st.to()) {
|
||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
// If the top-most frame halted, drain the leftover regular gas rather
|
||||
// than returning it to the sender. The frame exit itself already burned
|
||||
// its gas left, but the refill above repays the regular gas the charge
|
||||
// originally borrowed, and on a halt that repayment must be burned as
|
||||
// well.
|
||||
if rules.IsAmsterdam && vmerr != nil && vmerr != vm.ErrExecutionReverted {
|
||||
st.gasRemaining.DrainRegular()
|
||||
}
|
||||
return ret, vmerr
|
||||
}
|
||||
|
||||
// chargeRuntimeGas deducts an EIP-2780 runtime charge from the transaction's
|
||||
// gas budget and reports whether the budget covered it.
|
||||
func (st *stateTransition) chargeRuntimeGas(cost vm.GasCosts) bool {
|
||||
prior, ok := st.gasRemaining.Charge(cost)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxRuntimeGas)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// chargeCallRecipientEIP2780 applies the EIP-2780 runtime charges for the
|
||||
// top-level recipient of a message-call transaction, before the first frame is
|
||||
// entered:
|
||||
//
|
||||
// - if the recipient is EIP-161 empty and the transaction carries value,
|
||||
// the durable state growth of the new account;
|
||||
//
|
||||
// - if the recipient is an EIP-7702 delegated account, resolving the
|
||||
// delegation loads the target's code: a cold account access, or a warm
|
||||
// access if the target is already warm.
|
||||
//
|
||||
// Each charge is deducted before the state access it prices is performed:
|
||||
// under EIP-7928 every account load is recorded in the block access list, so
|
||||
// an access the budget cannot cover must not happen at all.
|
||||
func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
||||
to := *st.msg.To
|
||||
|
||||
// This runs in the topmost frame before any bytecode executes, non-existence
|
||||
// is equivalent with EIP-161-empty, as no preceding operation can leave a
|
||||
// transient EIP-161-empty account (such as zero-value transfer).
|
||||
if !value.IsZero() && st.state.Empty(to) {
|
||||
if !st.chargeRuntimeGas(vm.GasCosts{StateGas: params.AccountCreationSize * st.evm.Context.CostPerStateByte}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if target, delegated := types.ParseDelegation(st.state.GetCode(to)); delegated {
|
||||
// Pay the delegation-target access before the target is warmed and
|
||||
// its code resolved (loaded).
|
||||
cost := vm.GasCosts{RegularGas: params.ColdAccountAccessAmsterdam}
|
||||
if st.state.AddressInAccessList(target) {
|
||||
cost.RegularGas = params.WarmAccountAccessAmsterdam
|
||||
}
|
||||
if !st.chargeRuntimeGas(cost) {
|
||||
return false
|
||||
}
|
||||
st.state.AddAddressToAccessList(target)
|
||||
|
||||
// Record the delegation in the block level accessList explicitly
|
||||
st.state.GetCode(target)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -852,27 +930,11 @@ func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
|||
// settleGas finalizes the per-tx gas accounting after EVM execution:
|
||||
//
|
||||
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
|
||||
// tx_state_gas) before any refund or floor:
|
||||
//
|
||||
// tx_gas_used_before_refund = tx.gas - gas_left - state_gas_reservoir
|
||||
// tx_state_gas = state_gas_used
|
||||
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
||||
//
|
||||
// tx_state_gas) before any refund.
|
||||
// - Computes the receipt scalar tx_gas_used by applying the EIP-3529
|
||||
// refund (capped at tx_gas_used_before_refund/5) and the EIP-7623
|
||||
// calldata floor:
|
||||
//
|
||||
// tx_gas_used = max(tx_gas_used_before_refund - tx_gas_refund, calldata_floor)
|
||||
//
|
||||
// refund and the EIP-7623 calldata floor.
|
||||
// - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam).
|
||||
//
|
||||
// - Refunds the leftover gas to the sender as ETH.
|
||||
//
|
||||
// Returns the receipt-level tx_gas_used and the pre-refund peak (consumed
|
||||
// by gas-estimation callers via ExecutionResult.MaxUsedGas). UsedStateGas
|
||||
// should never become negative in the top-most frame, since state-gas
|
||||
// refunds occur only when state creation is reverted within the same
|
||||
// transaction and clearing pre-existing state is never refunded.
|
||||
func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) {
|
||||
if st.gasRemaining.UsedStateGas < 0 {
|
||||
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
||||
|
|
@ -881,15 +943,15 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
|||
|
||||
// EIP-8037:
|
||||
// tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir
|
||||
// tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used
|
||||
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
||||
// tx_state_gas = tx_output.execution_state_gas_used
|
||||
// tx_regular_gas = max(tx_gas_used_before_refund - tx_state_gas, calldata_floor_gas_cost)
|
||||
gasLeft := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
|
||||
gasUsedBeforeRefund := st.msg.GasLimit - gasLeft
|
||||
|
||||
if gasUsedBeforeRefund < txStateGas {
|
||||
return 0, 0, fmt.Errorf("negative topmost frame regular gas usage, total: %d, state: %d", gasUsedBeforeRefund, txStateGas)
|
||||
}
|
||||
txRegularGas := gasUsedBeforeRefund - txStateGas
|
||||
txRegularGas := max(gasUsedBeforeRefund-txStateGas, floorDataGas)
|
||||
|
||||
// EIP-3529: tx_gas_refund = min(tx_gas_used_before_refund/5, refund_counter).
|
||||
refund := st.calcRefund(gasUsedBeforeRefund)
|
||||
|
|
@ -911,6 +973,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
|||
peakUsed = max(peakUsed, floorDataGas)
|
||||
}
|
||||
|
||||
// Settle down the final gas consumption in the block-level pool
|
||||
if rules.IsAmsterdam {
|
||||
if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil {
|
||||
return 0, 0, err
|
||||
|
|
@ -921,7 +984,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
|||
}
|
||||
}
|
||||
|
||||
// Refund leftover gas to the sender as ETH.
|
||||
// Refund leftover gas to the sender
|
||||
if gasLeft > 0 {
|
||||
refund := new(uint256.Int).Mul(uint256.NewInt(gasLeft), st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, refund, tracing.BalanceIncreaseGasReturn)
|
||||
|
|
@ -964,51 +1027,71 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
|||
return authority, nil
|
||||
}
|
||||
|
||||
// applyAuthorization applies an EIP-7702 code delegation to the state and,
|
||||
// adjust the pre-charged intrinsic cost accordingly.
|
||||
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
|
||||
// authTracking tracks the charges already paid for an authority by earlier
|
||||
// authorizations in the same transaction.
|
||||
type authTracking struct {
|
||||
written bool // first-write ACCOUNT_WRITE surcharge paid
|
||||
authBaseCovered bool // indicator exists at tx start, or paid earlier
|
||||
}
|
||||
|
||||
// applyAuthorization applies an EIP-7702 code delegation to the state.
|
||||
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, authorities map[common.Address]*authTracking) error {
|
||||
authority, err := st.validateAuthorization(auth)
|
||||
if err != nil {
|
||||
if rules.IsAmsterdam {
|
||||
st.gasRemaining.RefundStateToReservoir((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
|
||||
st.state.AddRefund(params.AccountWriteAmsterdam)
|
||||
}
|
||||
return err
|
||||
}
|
||||
prevDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||
oldDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||
|
||||
if !rules.IsAmsterdam {
|
||||
if st.state.Exist(authority) {
|
||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||
}
|
||||
} else {
|
||||
if st.state.Exist(authority) {
|
||||
st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
st.state.AddRefund(params.AccountWriteAmsterdam)
|
||||
}
|
||||
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||
// EIP-2780: charge the state-dependent authorization costs at runtime.
|
||||
// The authority's cold access was already charged unconditionally at the
|
||||
// intrinsic phase, so only state-dependent costs remain here.
|
||||
var cost vm.GasCosts
|
||||
|
||||
preDelegated, ok := delegates[authority]
|
||||
if !ok {
|
||||
preDelegated = curDelegated
|
||||
delegates[authority] = preDelegated
|
||||
track := authorities[authority]
|
||||
if track == nil {
|
||||
track = &authTracking{authBaseCovered: curDelegated}
|
||||
authorities[authority] = track
|
||||
}
|
||||
if auth.Address == (common.Address{}) {
|
||||
// Clearing writes no indicator, refill this auth's state charge.
|
||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
||||
|
||||
// The indicator was created by an earlier auth within the same
|
||||
// transaction, refill the state charge as it's no longer justified.
|
||||
if curDelegated && !preDelegated {
|
||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
||||
}
|
||||
} else if curDelegated || preDelegated {
|
||||
// The 23-byte slot is already occupied, overwriting it writes no
|
||||
// new bytes, refill the state charge.
|
||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
||||
// Every valid authorization writes the authority account: the
|
||||
// nonce bump, and possibly the delegation indicator. The first
|
||||
// write to an account within the transaction carries the
|
||||
// first-write surcharge. At this point the accounts whose write
|
||||
// has already been paid for are:
|
||||
//
|
||||
// - the sender: TX_BASE_COST prices its account write, and the
|
||||
// gas prepayment and nonce bump have already happened;
|
||||
//
|
||||
// - authorities written by preceding valid authorizations in
|
||||
// this list, which carried the surcharge themselves;
|
||||
//
|
||||
// - tx.to, but only when the transaction carries value:
|
||||
// TX_VALUE_COST prepaid the recipient write at the intrinsic
|
||||
// phase. A zero-value transaction pays no TX_VALUE_COST, so a
|
||||
// write to tx.to here is still the first paid write.
|
||||
hasValue := st.msg.Value != nil && !st.msg.Value.IsZero()
|
||||
if !track.written && authority != st.msg.From && (authority != st.to() || !hasValue) {
|
||||
cost.RegularGas += params.AccountWriteAmsterdam
|
||||
track.written = true
|
||||
}
|
||||
// Durable state growth of the new account
|
||||
if st.state.Empty(authority) {
|
||||
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
|
||||
}
|
||||
// Charge the net-new indicator bytes at most once per authority;
|
||||
// clearing within the same transaction refunds nothing.
|
||||
if auth.Address != (common.Address{}) && !track.authBaseCovered {
|
||||
cost.StateGas += params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||
track.authBaseCovered = true
|
||||
}
|
||||
if !st.chargeRuntimeGas(cost) {
|
||||
return ErrOutOfGasRuntime
|
||||
}
|
||||
}
|
||||
|
||||
// Update nonce and account code.
|
||||
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
||||
|
||||
|
|
@ -1020,18 +1103,23 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
|
|||
return nil
|
||||
}
|
||||
// Install delegation to auth.Address if the delegation changed
|
||||
if !curDelegated || auth.Address != prevDelegation {
|
||||
if !curDelegated || auth.Address != oldDelegation {
|
||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyAuthorizations applies an EIP-7702 code delegation to the state.
|
||||
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
|
||||
preDelegated := make(map[common.Address]bool)
|
||||
// applyAuthorizations applies the EIP-7702 code delegations to the state.
|
||||
// It reports whether the transaction budget covered all runtime authorization
|
||||
// charges.
|
||||
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) bool {
|
||||
authorities := make(map[common.Address]*authTracking)
|
||||
for _, auth := range auths {
|
||||
st.applyAuthorization(rules, &auth, preDelegated)
|
||||
if err := st.applyAuthorization(rules, &auth, authorities); err == ErrOutOfGasRuntime {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// calcRefund computes the EIP-3529 refund cap against tx_gas_used_before_refund.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -158,50 +157,50 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP3860 bool
|
||||
isAmsterdam bool
|
||||
value *uint256.Int
|
||||
want vm.GasCosts
|
||||
want uint64
|
||||
}{
|
||||
{
|
||||
name: "frontier/empty-call",
|
||||
want: vm.GasCosts{RegularGas: params.TxGas},
|
||||
want: params.TxGas,
|
||||
},
|
||||
{
|
||||
name: "frontier/contract-creation-pre-homestead",
|
||||
creation: true,
|
||||
isHomestead: false,
|
||||
// pre-homestead, contract creation still uses TxGas
|
||||
want: vm.GasCosts{RegularGas: params.TxGas},
|
||||
want: params.TxGas,
|
||||
},
|
||||
{
|
||||
name: "homestead/contract-creation",
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation},
|
||||
want: params.TxGasContractCreation,
|
||||
},
|
||||
{
|
||||
name: "frontier/non-zero-data",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
// 100 nz bytes * 68 (frontier)
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasFrontier},
|
||||
want: params.TxGas + 100*params.TxDataNonZeroGasFrontier,
|
||||
},
|
||||
{
|
||||
name: "istanbul/non-zero-data",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
isEIP2028: true,
|
||||
// 100 nz bytes * 16 (post-EIP2028)
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasEIP2028},
|
||||
want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028,
|
||||
},
|
||||
{
|
||||
name: "istanbul/zero-data",
|
||||
data: bytes.Repeat([]byte{0x00}, 100),
|
||||
isEIP2028: true,
|
||||
// 100 zero bytes * 4
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataZeroGas},
|
||||
want: params.TxGas + 100*params.TxDataZeroGas,
|
||||
},
|
||||
{
|
||||
name: "istanbul/mixed-data",
|
||||
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
|
||||
isEIP2028: true,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028},
|
||||
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028,
|
||||
},
|
||||
{
|
||||
name: "shanghai/init-code-word-gas",
|
||||
|
|
@ -211,7 +210,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
|
||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
||||
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
},
|
||||
{
|
||||
name: "shanghai/init-code-non-multiple-of-32",
|
||||
|
|
@ -220,7 +219,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
||||
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
},
|
||||
{
|
||||
name: "berlin/access-list",
|
||||
|
|
@ -230,7 +229,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
// 2 addrs * 2400 + 3 keys * 1900
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas},
|
||||
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-extra-cost",
|
||||
|
|
@ -241,10 +240,12 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess
|
||||
// (15,000). Plus base access-list charge + EIP-7981 extra.
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
// (15,000); the recipient touch is charged at the cold rate
|
||||
// unconditionally at the intrinsic phase. Plus base access-list
|
||||
// charge + EIP-7981 extra.
|
||||
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost,
|
||||
},
|
||||
{
|
||||
name: "prague/auth-list",
|
||||
|
|
@ -255,7 +256,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
// 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple)
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 3*params.CallNewAccountGas},
|
||||
want: params.TxGas + 3*params.CallNewAccountGas,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/contract-creation-empty",
|
||||
|
|
@ -263,12 +264,9 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000),
|
||||
// and account-creation cost is charged as state gas.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
// EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000);
|
||||
// the new-account state charge is applied at runtime.
|
||||
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/contract-creation-init-code",
|
||||
|
|
@ -278,11 +276,8 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isEIP3860: true, // Shanghai gates init-code word gas
|
||||
isAmsterdam: true,
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
||||
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam +
|
||||
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/contract-creation-with-access-list",
|
||||
|
|
@ -295,13 +290,10 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
isAmsterdam: true,
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
||||
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam +
|
||||
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/combined",
|
||||
|
|
@ -314,18 +306,15 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// EIP-8037 splits the auth-tuple charge into regular + state gas, with
|
||||
// the values finalized by EIP-8038:
|
||||
// regular: ACCOUNT_WRITE (8,000) + REGULAR_PER_AUTH_BASE_COST (7,500) per auth
|
||||
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
100*params.TxDataNonZeroGasEIP2028 +
|
||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||
1*(params.AccountWriteAmsterdam+params.RegularPerAuthBaseCost),
|
||||
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
|
||||
},
|
||||
// EIP-2780: the recipient touch and the per-authorization authority
|
||||
// access (priced into RegularPerAuthBaseCost) are both charged at the
|
||||
// cold rate unconditionally at the intrinsic phase; the account leaf
|
||||
// and indicator bytes are charged at runtime.
|
||||
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||
100*params.TxDataNonZeroGasEIP2028 +
|
||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||
1*params.RegularPerAuthBaseCost,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/value-transfer-call",
|
||||
|
|
@ -333,8 +322,8 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isAmsterdam: true,
|
||||
value: uint256.NewInt(1),
|
||||
// EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000.
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
params.TransferLogCost2780 + params.TxValueCost2780},
|
||||
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||
params.TransferLogCost2780 + params.TxValueCost2780,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/value-bearing-contract-creation",
|
||||
|
|
@ -343,11 +332,9 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
value: uint256.NewInt(1),
|
||||
// EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756, plus account-creation state gas.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
// EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756;
|
||||
// the new-account state charge is applied at runtime.
|
||||
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
@ -363,7 +350,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
to = &addr1
|
||||
}
|
||||
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
||||
common.Address{}, to, tt.value, rules, params.CostPerStateByte)
|
||||
common.Address{}, to, tt.value, rules)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,21 +29,23 @@ func _() {
|
|||
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
||||
_ = x[GasChangeTxDataFloor-19]
|
||||
_ = x[GasChangeRefundAccountCreation-20]
|
||||
_ = x[GasChangeTxRuntimeGas-21]
|
||||
_ = x[GasChangeAccountCreation-22]
|
||||
_ = x[GasChangeIgnored-255]
|
||||
}
|
||||
|
||||
const (
|
||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreation"
|
||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreationTxRuntimeGasAccountCreation"
|
||||
_GasChangeReason_name_1 = "Ignored"
|
||||
)
|
||||
|
||||
var (
|
||||
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 374}
|
||||
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 374, 386, 401}
|
||||
)
|
||||
|
||||
func (i GasChangeReason) String() string {
|
||||
switch {
|
||||
case i <= 20:
|
||||
case i <= 22:
|
||||
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
|
||||
case i == 255:
|
||||
return _GasChangeReason_name_1
|
||||
|
|
|
|||
|
|
@ -476,6 +476,15 @@ const (
|
|||
// pre-charged account-creation cost when no account is created.
|
||||
GasChangeRefundAccountCreation GasChangeReason = 20
|
||||
|
||||
// GasChangeTxRuntimeGas is the amount of gas charged for the state-dependent
|
||||
// costs of the transaction per EIP-2780.
|
||||
GasChangeTxRuntimeGas GasChangeReason = 21
|
||||
|
||||
// GasChangeAccountCreation represents the conditional account-creation
|
||||
// state cost charged in the creating frame when a CREATE/CREATE2 is about
|
||||
// to create a new account (EIP-8037).
|
||||
GasChangeAccountCreation GasChangeReason = 22
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -129,12 +129,12 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules, params.CostPerStateByte)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Gas() < intrGas.RegularGas {
|
||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas)
|
||||
if tx.Gas() < intrGas {
|
||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
|
||||
}
|
||||
// Ensure the transaction can cover floor data gas.
|
||||
if rules.IsPrague {
|
||||
|
|
@ -149,8 +149,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||
if rules.IsAmsterdam && max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas {
|
||||
return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas)
|
||||
if rules.IsAmsterdam && max(intrGas, floorDataGas) > params.MaxTxGas {
|
||||
return fmt.Errorf("%w: intrinsic cost %v, floor: %v", core.ErrFloorDataGas, intrGas, floorDataGas)
|
||||
}
|
||||
}
|
||||
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ func CheckMaxInitCodeSize(rules *params.Rules, size uint64) error {
|
|||
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSize)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,8 @@ func activePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
|||
switch {
|
||||
case rules.IsUBT:
|
||||
return PrecompiledContractsVerkle
|
||||
case rules.IsBogota:
|
||||
return PrecompiledContractsOsaka
|
||||
case rules.IsOsaka:
|
||||
return PrecompiledContractsOsaka
|
||||
case rules.IsPrague:
|
||||
|
|
@ -240,6 +242,8 @@ func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
|||
// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
|
||||
func ActivePrecompiles(rules params.Rules) []common.Address {
|
||||
switch {
|
||||
case rules.IsBogota:
|
||||
return PrecompiledAddressesOsaka
|
||||
case rules.IsOsaka:
|
||||
return PrecompiledAddressesOsaka
|
||||
case rules.IsPrague:
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
|
|||
evm.precompiles = activePrecompiledContracts(evm.chainRules)
|
||||
|
||||
switch {
|
||||
case evm.chainRules.IsBogota:
|
||||
evm.table = &bogotaInstructionSet
|
||||
case evm.chainRules.IsAmsterdam:
|
||||
evm.table = &amsterdamInstructionSet
|
||||
case evm.chainRules.IsOsaka:
|
||||
|
|
@ -473,20 +475,57 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
|||
return ret, exitGas, err
|
||||
}
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, creation bool, err error) {
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
var nonce uint64
|
||||
// createFramePreCheck the precondition before executing the contract deployment,
|
||||
// halts the create frame if fails with any check below.
|
||||
func (evm *EVM) createFramePreCheck(caller common.Address, value *uint256.Int) error {
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
err = ErrDepth
|
||||
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
err = ErrInsufficientBalance
|
||||
} else {
|
||||
nonce = evm.StateDB.GetNonce(caller)
|
||||
if nonce+1 < nonce {
|
||||
err = ErrNonceUintOverflow
|
||||
}
|
||||
return ErrDepth
|
||||
}
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
return ErrInsufficientBalance
|
||||
}
|
||||
nonce := evm.StateDB.GetNonce(caller)
|
||||
if nonce+1 < nonce {
|
||||
return ErrNonceUintOverflow
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// chargeAccountCreation runs the create-frame precheck and charges the
|
||||
// account-creation state gas since Amsterdam, before the 63/64ths split.
|
||||
//
|
||||
// The charge only applies if the destination is empty, skipping pre-funded
|
||||
// deployment destinations. Note, a destination colliding on storage alone
|
||||
// (zero nonce, zero balance, empty code) is still empty and is charged.
|
||||
//
|
||||
// If halt is true, the caller must terminate with the returned error:
|
||||
// - a failed precheck halts the create frame only and parent frame continues,
|
||||
// - an insufficient charge halts the parent frame with ErrOutOfGas.
|
||||
func (evm *EVM) chargeAccountCreation(scope *ScopeContext, contractAddr common.Address, value *uint256.Int) (charged, halt bool, err error) {
|
||||
if !evm.chainRules.IsAmsterdam {
|
||||
return false, false, nil
|
||||
}
|
||||
if err := evm.createFramePreCheck(scope.Contract.Address(), value); err != nil {
|
||||
scope.Stack.get().Clear()
|
||||
evm.returnData = nil
|
||||
return false, true, nil
|
||||
}
|
||||
if !evm.StateDB.Empty(contractAddr) {
|
||||
return false, false, nil
|
||||
}
|
||||
cost := params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||
if !scope.Contract.chargeState(cost, evm.Config.Tracer, tracing.GasChangeAccountCreation) {
|
||||
return false, true, ErrOutOfGas
|
||||
}
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, err error) {
|
||||
// Since Amsterdam, the precheck has been folded into the parent frame
|
||||
// due to account-creation determination, so skip the duplicate check here.
|
||||
if !evm.chainRules.IsAmsterdam {
|
||||
err = evm.createFramePreCheck(caller, value)
|
||||
}
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
||||
|
|
@ -495,17 +534,17 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
}(gas)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, common.Address{}, gas, false, err
|
||||
return nil, common.Address{}, gas, err
|
||||
}
|
||||
// Increment the caller's nonce after passing all validations
|
||||
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
||||
evm.StateDB.SetNonce(caller, evm.StateDB.GetNonce(caller)+1, tracing.NonceChangeContractCreator)
|
||||
|
||||
// Charge the contract creation init gas in verkle mode
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
|
||||
prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas})
|
||||
if !ok {
|
||||
return nil, common.Address{}, gas.ExitHalt(), false, ErrOutOfGas
|
||||
return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas
|
||||
}
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
|
||||
|
|
@ -532,7 +571,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
}
|
||||
// EIP-8037 collision rule: the state reservoir is fully preserved on
|
||||
// address collision while regular gas is burnt.
|
||||
return nil, common.Address{}, halt, false, ErrContractAddressCollision
|
||||
return nil, common.Address{}, halt, ErrContractAddressCollision
|
||||
}
|
||||
// Create a new account on the state only if the object was not present.
|
||||
// It might be possible the contract code is deployed to a pre-existent
|
||||
|
|
@ -540,7 +579,6 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
snapshot := evm.StateDB.Snapshot()
|
||||
if !evm.StateDB.Exist(address) {
|
||||
evm.StateDB.CreateAccount(address)
|
||||
creation = true
|
||||
}
|
||||
// CreateContract means that regardless of whether the account previously existed
|
||||
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
||||
|
|
@ -555,7 +593,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
if evm.chainRules.IsEIP4762 {
|
||||
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
|
||||
if consumed < wanted {
|
||||
return nil, common.Address{}, gas.ExitHalt(), false, ErrOutOfGas
|
||||
return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas
|
||||
}
|
||||
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
|
|
@ -586,11 +624,11 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
}
|
||||
return ret, address, exit, false, err
|
||||
return ret, address, exit, err
|
||||
}
|
||||
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
|
||||
// Both packaged as a success-form GasBudget.
|
||||
return ret, address, contract.Gas.ExitSuccess(), creation, err
|
||||
return ret, address, contract.Gas.ExitSuccess(), err
|
||||
}
|
||||
|
||||
// initNewContract runs a new contract's creation code, performs checks on the
|
||||
|
|
@ -646,7 +684,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
|||
}
|
||||
|
||||
// Create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, creation bool, err error) {
|
||||
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
|
||||
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
|
||||
return evm.create(caller, code, gas, value, contractAddr, CREATE)
|
||||
}
|
||||
|
|
@ -655,7 +693,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value
|
|||
//
|
||||
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
|
||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, creation bool, err error) {
|
||||
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
|
||||
inithash := crypto.Keccak256Hash(code)
|
||||
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
|
||||
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
|
||||
|
|
|
|||
|
|
@ -560,12 +560,10 @@ func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
|||
words := (size + 31) / 32
|
||||
wordGas := params.InitCodeWordGas * words
|
||||
|
||||
// Unconditionally pre-charge the account creation and refunds if the creation
|
||||
// doesn't happen after the create-frame.
|
||||
return GasCosts{
|
||||
RegularGas: gas + wordGas,
|
||||
StateGas: params.AccountCreationSize * evm.Context.CostPerStateByte,
|
||||
}, nil
|
||||
// The account-creation state gas is not part of the opcode cost: it is
|
||||
// charged conditionally at the destination access, in the creating frame,
|
||||
// right before the 63/64ths split (see opCreate).
|
||||
return GasCosts{RegularGas: gas + wordGas}, nil
|
||||
}
|
||||
|
||||
func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
|
|
@ -590,12 +588,10 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
|
|||
// (for address hashing).
|
||||
wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words
|
||||
|
||||
// Unconditionally pre-charge the account creation and refunds if the creation
|
||||
// doesn't happen after the create-frame.
|
||||
return GasCosts{
|
||||
RegularGas: gas + wordGas,
|
||||
StateGas: params.AccountCreationSize * evm.Context.CostPerStateByte,
|
||||
}, nil
|
||||
// The account-creation state gas is not part of the opcode cost: it is
|
||||
// charged conditionally at the destination access, in the creating frame,
|
||||
// right before the 63/64ths split (see opCreate2).
|
||||
return GasCosts{RegularGas: gas + wordGas}, nil
|
||||
}
|
||||
|
||||
// regularGasCall8038 is the intrinsic regular-gas calculator for CALL in
|
||||
|
|
@ -635,19 +631,13 @@ func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error
|
|||
transfersValue = !stack.back(2).IsZero()
|
||||
address = common.Address(stack.back(1).Bytes20())
|
||||
)
|
||||
// TODO(rjl, marius), can EIP8037 implicitly means the EIP158 is also activated?
|
||||
// It's technically possible to skip the EIP158 but very unlikely in practice.
|
||||
if evm.chainRules.IsEIP158 {
|
||||
// Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist
|
||||
// in the current state yet still be considered non-existent by EIP-161 if its
|
||||
// nonce, balance, and code are all zero. Such accounts can appear temporarily
|
||||
// during execution (e.g. via SELFDESTRUCT) and are removed at tx end.
|
||||
//
|
||||
// Funding such an account makes it permanent state growth and must be charged.
|
||||
if transfersValue && evm.StateDB.Empty(address) {
|
||||
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||
}
|
||||
} else if !evm.StateDB.Exist(address) {
|
||||
// Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist
|
||||
// in the current state yet still be considered non-existent by EIP-161 if its
|
||||
// nonce, balance, and code are all zero. Such accounts can appear temporarily
|
||||
// during execution (e.g. via SELFDESTRUCT) and are removed at tx end.
|
||||
//
|
||||
// Funding such an account makes it permanent state growth and must be charged.
|
||||
if transfersValue && evm.StateDB.Empty(address) {
|
||||
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||
}
|
||||
return gas, nil
|
||||
|
|
@ -698,22 +688,26 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
|||
var (
|
||||
y, x = stack.back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
value = common.Hash(y.Bytes32())
|
||||
stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte
|
||||
)
|
||||
// Check slot presence in the access list
|
||||
access := params.WarmStorageReadCostEIP2929
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
access := params.WarmStorageAccessAmsterdam
|
||||
_, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot)
|
||||
if !slotPresent {
|
||||
access = params.ColdStorageAccessAmsterdam
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
}
|
||||
// Check access cost affordability before reading slot
|
||||
if contract.Gas.RegularGas < access {
|
||||
return GasCosts{}, errors.New("not enough gas for slot access")
|
||||
}
|
||||
if !slotPresent {
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
}
|
||||
// Read the slot value for gas cost measurement
|
||||
current, original := evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
|
||||
var (
|
||||
value = common.Hash(y.Bytes32())
|
||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
)
|
||||
if current == value { // noop (1)
|
||||
return GasCosts{RegularGas: access}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,18 +48,6 @@ func (g GasCosts) String() string {
|
|||
// - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross
|
||||
// consumption. UsedStateGas is signed so it can be decremented by inline
|
||||
// state-gas refunds (e.g., SSTORE 0->A->0).
|
||||
//
|
||||
// The same struct serves three roles:
|
||||
//
|
||||
// - During execution: Charge / ChargeRegular / ChargeState / RefundState
|
||||
// and RefundRegular mutate the running balance and the usage accumulators
|
||||
// in lockstep.
|
||||
//
|
||||
// - At frame exit: ExitSuccess / ExitRevert / ExitHalt produce a new
|
||||
// GasBudget in "leftover" form that packages the result for the caller.
|
||||
//
|
||||
// - At absorption: the caller's Absorb method merges the child's leftover
|
||||
// budget into its own running budget.
|
||||
type GasBudget struct {
|
||||
RegularGas uint64 // remaining regular-gas balance (or leftover for caller to absorb)
|
||||
StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
|
||||
|
|
@ -78,9 +66,7 @@ func NewGasBudget(regular, state uint64) GasBudget {
|
|||
return GasBudget{RegularGas: regular, StateGas: state}
|
||||
}
|
||||
|
||||
// Used returns the total scalar gas consumed relative to an initial budget
|
||||
// (= (initial.regular + initial.state) − (current.regular + current.state)).
|
||||
// This is the payment scalar (EIP-8037's tx_gas_used_before_refund).
|
||||
// Used returns the total scalar gas consumed relative to an initial budget.
|
||||
func (g GasBudget) Used(initial GasBudget) uint64 {
|
||||
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
|
||||
}
|
||||
|
|
@ -91,16 +77,16 @@ func (g GasBudget) String() string {
|
|||
}
|
||||
|
||||
// Charge deducts a combined regular+state cost from the running balance and
|
||||
// updates the usage accumulators. State-gas in excess of the reservoir spills
|
||||
// into regular_gas.
|
||||
// updates the usage accumulators.
|
||||
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
||||
prior := *g
|
||||
ok := g.charge(cost)
|
||||
return prior, ok
|
||||
}
|
||||
|
||||
// chargeRegularOnly deducts a regular-only cost.
|
||||
func (g *GasBudget) chargeRegularOnly(r uint64) bool {
|
||||
// ChargeRegularOnly deducts a regular-only cost. It's always preferred for
|
||||
// performance consideration if the opcode doesn't have any state cost.
|
||||
func (g *GasBudget) ChargeRegularOnly(r uint64) bool {
|
||||
if g.RegularGas < r {
|
||||
return false
|
||||
}
|
||||
|
|
@ -110,9 +96,7 @@ func (g *GasBudget) chargeRegularOnly(r uint64) bool {
|
|||
}
|
||||
|
||||
// CanAfford reports whether the running budget can cover the given cost vector
|
||||
// without going out of gas. The regular cost must fit in the regular balance,
|
||||
// and any state gas in excess of the reservoir must be coverable by the
|
||||
// remaining regular gas (the spillover), mirroring charge without mutating.
|
||||
// without going out of gas.
|
||||
func (g GasBudget) CanAfford(cost GasCosts) bool {
|
||||
if g.RegularGas < cost.RegularGas {
|
||||
return false
|
||||
|
|
@ -162,8 +146,7 @@ func (g *GasBudget) ChargeRegular(r uint64) (GasBudget, bool) {
|
|||
return g.Charge(GasCosts{RegularGas: r})
|
||||
}
|
||||
|
||||
// ChargeState is a convenience that deducts a state-only cost (spills to
|
||||
// regular when the reservoir is exhausted). Returns false on OOG.
|
||||
// ChargeState is a convenience that deducts a state-only cost.
|
||||
func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) {
|
||||
return g.Charge(GasCosts{StateGas: s})
|
||||
}
|
||||
|
|
@ -174,56 +157,24 @@ func (g *GasBudget) IsZero() bool {
|
|||
}
|
||||
|
||||
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0).
|
||||
//
|
||||
// Per EIP-8037, the refund repays the regular gas previously borrowed for
|
||||
// state-gas spillover (tracked by Spilled) before crediting the
|
||||
// reservoir: it is returned to RegularGas up to the outstanding borrowed
|
||||
// amount, and only the remainder tops up StateGas.
|
||||
//
|
||||
// The signed usage counter is decremented by the full refund regardless of the
|
||||
// split, preserving the per-frame invariant:
|
||||
//
|
||||
// StateGas + UsedStateGas == initialStateGas + Spilled
|
||||
//
|
||||
// which the revert and halt paths rely on for the correct gross refund.
|
||||
func (g *GasBudget) RefundState(s uint64) {
|
||||
repay := min(s, g.Spilled)
|
||||
g.RegularGas += repay
|
||||
g.Spilled -= repay
|
||||
|
||||
// Whatever is left tops up the reservoir.
|
||||
g.StateGas += s - repay
|
||||
g.UsedStateGas -= int64(s)
|
||||
}
|
||||
|
||||
// RefundStateToReservoir credits a state-gas refund directly to the
|
||||
// reservoir, without repaying spilled regular gas first.
|
||||
//
|
||||
// Per the spec's set_delegation, authorization refunds (and the post-create
|
||||
// new-account refund) are added to message.state_gas_reservoir directly, in
|
||||
// contrast to the LIFO inline refunds handled by RefundState. The usage
|
||||
// counter is decremented by the full amount, matching the spec's
|
||||
// tx_state_gas = intrinsic_state + state_gas_used - state_refund and
|
||||
// preserving the per-frame invariant:
|
||||
//
|
||||
// StateGas + UsedStateGas == initialStateGas + Spilled
|
||||
func (g *GasBudget) RefundStateToReservoir(s uint64) {
|
||||
g.StateGas += s
|
||||
g.UsedStateGas -= int64(s)
|
||||
// DrainRegular burns the remaining regular-gas.
|
||||
func (g *GasBudget) DrainRegular() {
|
||||
g.UsedRegularGas += g.RegularGas
|
||||
g.RegularGas = 0
|
||||
}
|
||||
|
||||
// Forward drains `regular` regular gas and the entire state reservoir from
|
||||
// the parent's running budget and returns the initial GasBudget for a child
|
||||
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
|
||||
// that the absorb-on-return path correctly reclaims the unused portion.
|
||||
//
|
||||
// Used by frame boundaries where the regular forward has NOT been pre-
|
||||
// deducted: tx-level dispatch (state_transition) and CREATE / CREATE2. The
|
||||
// CALL family pre-deducts the forward via the dynamic gas table for tracer-
|
||||
// reporting reasons and therefore constructs its child budget directly.
|
||||
//
|
||||
// Caller must ensure `regular` does not exceed the running balance and
|
||||
// apply any EIP-150 1/64 retention before calling Forward.
|
||||
func (g *GasBudget) Forward(regular uint64) GasBudget {
|
||||
g.RegularGas -= regular
|
||||
g.UsedRegularGas += regular
|
||||
|
|
@ -249,19 +200,15 @@ func (g *GasBudget) ForwardAll() GasBudget {
|
|||
// absorb to update its own state.
|
||||
// ============================================================================
|
||||
|
||||
// ExitSuccess produces the leftover form for a successful frame. Inline
|
||||
// state-gas refunds have already been folded into StateGas / UsedStateGas
|
||||
// during execution; the running budget IS the exit budget on success.
|
||||
// ExitSuccess produces the leftover form for a successful frame.
|
||||
func (g GasBudget) ExitSuccess() GasBudget {
|
||||
return g
|
||||
}
|
||||
|
||||
// ExitRevert produces the leftover for a REVERT exit. The frame's state
|
||||
// changes are discarded, so all state gas it charged is refilled to its origin
|
||||
// (EIP-8037): up to Spilled is returned to RegularGas (the regular
|
||||
// gas it borrowed), and the remainder restores the reservoir. Because the
|
||||
// borrowed regular gas is repaid first, the reservoir is made whole back to its
|
||||
// start-of-frame value.
|
||||
// changes are discarded, so all state gas it charged is refilled with LIFO
|
||||
// mechanism: up to Spilled is returned to RegularGas (the regular gas it
|
||||
// borrowed), and the remainder restores the reservoir.
|
||||
func (g GasBudget) ExitRevert() GasBudget {
|
||||
reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled)
|
||||
if reservoir < 0 {
|
||||
|
|
@ -280,10 +227,10 @@ func (g GasBudget) ExitRevert() GasBudget {
|
|||
}
|
||||
|
||||
// ExitHalt produces the leftover for an exceptional halt. As with a revert, the
|
||||
// frame's state changes are rolled back and its state gas is refilled to origin
|
||||
// (EIP-8037); the difference is that the frame's gas_left is consumed rather
|
||||
// frame's state changes are rolled back and its state gas is refilled with LIFO
|
||||
// mechanism. The difference is that the frame's regular gas is consumed rather
|
||||
// than returned. The portion refilled to RegularGas is therefore burned along
|
||||
// with the rest of gas_left, leaving only the reservoir portion to survive,
|
||||
// with the rest of regular gas, leaving only the reservoir portion to survive,
|
||||
// which equals the reservoir's value at the start of the frame.
|
||||
func (g GasBudget) ExitHalt() GasBudget {
|
||||
reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled)
|
||||
|
|
|
|||
|
|
@ -635,7 +635,12 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
value = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||
contractAddr = crypto.CreateAddress(scope.Contract.Address(), evm.StateDB.GetNonce(scope.Contract.Address()))
|
||||
)
|
||||
creationCharged, halt, err := evm.chargeAccountCreation(scope, contractAddr, &value)
|
||||
if halt {
|
||||
return nil, err
|
||||
}
|
||||
// Apply EIP-150 to the regular gas left after the state charge.
|
||||
forward := scope.Contract.Gas.RegularGas
|
||||
if evm.chainRules.IsEIP150 {
|
||||
|
|
@ -646,7 +651,8 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
stackvalue := size
|
||||
|
||||
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
||||
res, addr, result, creation, suberr := evm.Create(scope.Contract.Address(), input, child, &value)
|
||||
res, addr, result, suberr := evm.create(scope.Contract.Address(), input, child, &value, contractAddr, CREATE)
|
||||
|
||||
// Push item on the stack based on the returned error. If the ruleset is
|
||||
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
||||
// rule) and treat as an error, if the ruleset is frontier we must
|
||||
|
|
@ -663,8 +669,11 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// Refund the leftover gas back to current frame
|
||||
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
// Refund the state gas of account-creation if creation doesn't happen
|
||||
if evm.GetRules().IsAmsterdam && !creation {
|
||||
// Refill the account-creation charge if the create frame failed (reverted,
|
||||
// halted exceptionally, or collided); a successful creation consumes it.
|
||||
// This rule is only applied since the Amsterdam, therefore all non-nil vm
|
||||
// error can be interpreted as deployment failure.
|
||||
if creationCharged && suberr != nil {
|
||||
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation)
|
||||
}
|
||||
if suberr == ErrExecutionReverted {
|
||||
|
|
@ -681,7 +690,13 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
salt = scope.Stack.pop()
|
||||
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||
inithash = crypto.Keccak256Hash(input)
|
||||
contractAddr = crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash[:])
|
||||
)
|
||||
creationCharged, halt, err := evm.chargeAccountCreation(scope, contractAddr, &endowment)
|
||||
if halt {
|
||||
return nil, err
|
||||
}
|
||||
// Apply EIP-150 to the regular gas left after the state charge.
|
||||
forward := scope.Contract.Gas.RegularGas
|
||||
forward -= forward / 64
|
||||
|
|
@ -689,7 +704,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||
res, addr, result, creation, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt)
|
||||
res, addr, result, suberr := evm.create(scope.Contract.Address(), input, child, &endowment, contractAddr, CREATE2)
|
||||
// Push item on the stack based on the returned error.
|
||||
if suberr != nil {
|
||||
stackvalue.Clear()
|
||||
|
|
@ -701,8 +716,11 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// Refund the leftover gas back to current frame
|
||||
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
// Refund the state gas of account-creation if creation doesn't happen
|
||||
if evm.GetRules().IsAmsterdam && !creation {
|
||||
// Refill the account-creation charge if the create frame failed (reverted,
|
||||
// halted exceptionally, or collided); a successful creation consumes it.
|
||||
// This rule is only applied since the Amsterdam, therefore all non-nil vm
|
||||
// error can be interpreted as deployment failure.
|
||||
if creationCharged && suberr != nil {
|
||||
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation)
|
||||
}
|
||||
if suberr == ErrExecutionReverted {
|
||||
|
|
|
|||
|
|
@ -77,9 +77,11 @@ type StateDB interface {
|
|||
|
||||
AddressInAccessList(addr common.Address) bool
|
||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||
|
||||
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddAddressToAccessList(addr common.Address)
|
||||
|
||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
// for tracing: this gas consumption event is emitted below in the debug section.
|
||||
if !contract.Gas.chargeRegularOnly(cost) {
|
||||
if !contract.Gas.ChargeRegularOnly(cost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
||||
}
|
||||
if dynamicCost.StateGas == 0 {
|
||||
if !contract.Gas.chargeRegularOnly(dynamicCost.RegularGas) {
|
||||
if !contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
} else if !contract.Gas.charge(dynamicCost) {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ var (
|
|||
pragueInstructionSet = newPragueInstructionSet()
|
||||
osakaInstructionSet = newOsakaInstructionSet()
|
||||
amsterdamInstructionSet = newAmsterdamInstructionSet()
|
||||
bogotaInstructionSet = newBogotaInstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
|
@ -91,6 +92,11 @@ func validate(jt JumpTable) JumpTable {
|
|||
return jt
|
||||
}
|
||||
|
||||
func newBogotaInstructionSet() JumpTable {
|
||||
instructionSet := newOsakaInstructionSet()
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newVerkleInstructionSet() JumpTable {
|
||||
instructionSet := newShanghaiInstructionSet()
|
||||
enable4762(&instructionSet)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
|||
switch {
|
||||
case rules.IsUBT:
|
||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||
case rules.IsBogota:
|
||||
return newBogotaInstructionSet(), nil
|
||||
case rules.IsAmsterdam:
|
||||
return newAmsterdamInstructionSet(), nil
|
||||
case rules.IsOsaka:
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
|
|||
// EIP-7702 delegation check.
|
||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||
if evm.StateDB.AddressInAccessList(target) {
|
||||
eip7702Cost = params.WarmStorageReadCostEIP2929
|
||||
eip7702Cost = params.WarmAccountAccessAmsterdam
|
||||
} else {
|
||||
evm.StateDB.AddAddressToAccessList(target)
|
||||
eip7702Cost = coldCost
|
||||
|
|
|
|||
|
|
@ -143,12 +143,16 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
|||
cfg.State.CreateAccount(address)
|
||||
// set the receiver's (the executing contract) code for execution.
|
||||
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
|
||||
limit := cfg.GasLimit
|
||||
if rules.IsAmsterdam {
|
||||
limit = min(cfg.GasLimit, params.MaxTxGas)
|
||||
}
|
||||
// Call the code with the given configuration.
|
||||
ret, result, err := vmenv.Call(
|
||||
cfg.Origin,
|
||||
common.BytesToAddress([]byte("contract")),
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||
vm.NewGasBudget(limit, cfg.GasLimit-limit),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
|
|
@ -178,11 +182,15 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
|||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(eip 1153)
|
||||
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
|
||||
limit := cfg.GasLimit
|
||||
if rules.IsAmsterdam {
|
||||
limit = min(cfg.GasLimit, params.MaxTxGas)
|
||||
}
|
||||
// Call the code with the given configuration.
|
||||
code, address, result, _, err := vmenv.Create(
|
||||
code, address, result, err := vmenv.Create(
|
||||
cfg.Origin,
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||
vm.NewGasBudget(limit, cfg.GasLimit-limit),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
|
|
@ -212,12 +220,16 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
|||
// - reset transient storage(eip 1153)
|
||||
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
|
||||
|
||||
limit := cfg.GasLimit
|
||||
if rules.IsAmsterdam {
|
||||
limit = min(cfg.GasLimit, params.MaxTxGas)
|
||||
}
|
||||
// Call the code with the given configuration.
|
||||
ret, result, err := vmenv.Call(
|
||||
cfg.Origin,
|
||||
address,
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||
vm.NewGasBudget(limit, cfg.GasLimit-limit),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package eth
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
|
|
@ -64,6 +65,24 @@ func (b *EthAPIBackend) CurrentBlock() *types.Header {
|
|||
}
|
||||
|
||||
func (b *EthAPIBackend) SetHead(number uint64) error {
|
||||
// Reject rewinding to a point before the snap-sync pivot. The earliest
|
||||
// recoverable state is the pivot block, so a target below it would simply
|
||||
// reset the chain to genesis, which is almost never the intent behind a
|
||||
// manual debug_setHead.
|
||||
if pivot := rawdb.ReadLastPivotNumber(b.eth.ChainDb()); pivot != nil && number < *pivot {
|
||||
return fmt.Errorf("rewind target %d is before the snap-sync pivot %d", number, *pivot)
|
||||
}
|
||||
// In path mode the deepest reachable state is bounded by the amount of state
|
||||
// histories retained. If the reverse diffs for the target have already been
|
||||
// pruned, its state is no longer recoverable.
|
||||
bc := b.eth.blockchain
|
||||
if bc.TrieDB().Scheme() == rawdb.PathScheme {
|
||||
if header := bc.GetHeaderByNumber(number); header != nil {
|
||||
if !bc.HasState(header.Root) && !bc.StateRecoverable(header.Root) {
|
||||
return errors.New("rewind target is not recoverable")
|
||||
}
|
||||
}
|
||||
}
|
||||
b.eth.handler.downloader.Cancel()
|
||||
return b.eth.blockchain.SetHead(number)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downlo
|
|||
func (s *Ethereum) Synced() bool { return s.handler.synced.Load() }
|
||||
func (s *Ethereum) SetSynced() { s.handler.enableSyncedFeatures() }
|
||||
func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
|
||||
func (s *Ethereum) EngineMaxReorgDepth() uint64 { return s.config.EngineMaxReorgDepth }
|
||||
|
||||
// Protocols returns all the currently configured
|
||||
// network protocols to start.
|
||||
|
|
@ -477,8 +478,8 @@ func (s *Ethereum) Start() error {
|
|||
// Start the networking layer
|
||||
s.handler.Start(s.p2pServer.MaxPeers)
|
||||
|
||||
// Start the connection manager
|
||||
s.dropper.Start(s.p2pServer, func() bool { return !s.Synced() })
|
||||
// Start the connection manager with inclusion-based peer protection.
|
||||
s.dropper.Start(s.p2pServer, func() bool { return !s.Synced() }, s.handler.txTracker.GetAllPeerStats)
|
||||
|
||||
// Subscribe to chain events for the filterMaps head updater.
|
||||
s.fmHeadSub = s.blockchain.SubscribeChainEvent(s.fmHeadEventCh)
|
||||
|
|
@ -604,6 +605,7 @@ func (s *Ethereum) Stop() error {
|
|||
// Stop all the peer-related stuff first.
|
||||
s.discmix.Close()
|
||||
s.dropper.Stop()
|
||||
s.handler.txTracker.Stop()
|
||||
s.handler.Stop()
|
||||
|
||||
// Then stop everything else.
|
||||
|
|
|
|||
|
|
@ -83,14 +83,15 @@ const (
|
|||
// beaconUpdateWarnFrequency is the frequency at which to warn the user that
|
||||
// the beacon client is offline.
|
||||
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||
|
||||
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated.
|
||||
maxReorgDepth = 32
|
||||
)
|
||||
|
||||
type ConsensusAPI struct {
|
||||
eth *eth.Ethereum
|
||||
|
||||
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated
|
||||
// (0 = no limit). Configured via ethconfig.Config.EngineMaxReorgDepth.
|
||||
maxReorgDepth uint64
|
||||
|
||||
remoteBlocks *headerQueue // Cache of remote payloads received
|
||||
localBlocks *payloadQueue // Cache of local payloads generated
|
||||
|
||||
|
|
@ -146,6 +147,7 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
|
|||
}
|
||||
api := &ConsensusAPI{
|
||||
eth: eth,
|
||||
maxReorgDepth: eth.EngineMaxReorgDepth(),
|
||||
remoteBlocks: newHeaderQueue(),
|
||||
localBlocks: newPayloadQueue(),
|
||||
invalidBlocksHits: make(map[common.Hash]int),
|
||||
|
|
@ -334,9 +336,9 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
|
|||
return valid(nil), nil
|
||||
}
|
||||
depth := api.eth.BlockChain().CurrentBlock().Number.Uint64() - block.NumberU64()
|
||||
if depth >= maxReorgDepth {
|
||||
if api.maxReorgDepth > 0 && depth >= api.maxReorgDepth {
|
||||
log.Warn("Refusing too deep reorg", "depth", depth, "head", update.HeadBlockHash)
|
||||
return engine.STATUS_INVALID, engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds limit %d", depth, maxReorgDepth))
|
||||
return engine.STATUS_INVALID, engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds limit %d", depth, api.maxReorgDepth))
|
||||
}
|
||||
if !api.eth.Synced() {
|
||||
log.Info("Ignoring beacon update to old head while syncing", "number", block.NumberU64(), "hash", update.HeadBlockHash)
|
||||
|
|
@ -867,6 +869,8 @@ func (api *ConsensusAPI) NewPayloadV5(ctx context.Context, params engine.Executa
|
|||
return invalidStatus, paramsErr("nil executionRequests post-prague")
|
||||
case params.SlotNumber == nil:
|
||||
return invalidStatus, paramsErr("nil slotnumber post-amsterdam")
|
||||
case params.BlockAccessList == nil:
|
||||
return invalidStatus, paramsErr("nil block access list post-amsterdam")
|
||||
case !api.checkFork(params.Timestamp, forks.Amsterdam):
|
||||
return invalidStatus, unsupportedForkErr("newPayloadV5 must only be called for amsterdam payloads")
|
||||
}
|
||||
|
|
@ -1246,13 +1250,13 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin
|
|||
return bodies
|
||||
}
|
||||
|
||||
// GetPayloadBodiesByHashV2 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
|
||||
// GetPayloadBodiesByHashV2 implements engine_getPayloadBodiesByHashV2 which allows for retrieval of a list
|
||||
// of block bodies by the engine api.
|
||||
func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engine.ExecutionPayloadBody {
|
||||
bodies := make([]*engine.ExecutionPayloadBody, len(hashes))
|
||||
func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engine.ExecutionPayloadBodyV2 {
|
||||
bodies := make([]*engine.ExecutionPayloadBodyV2, len(hashes))
|
||||
for i, hash := range hashes {
|
||||
block := api.eth.BlockChain().GetBlockByHash(hash)
|
||||
bodies[i] = getBody(block)
|
||||
bodies[i] = getBodyV2(block)
|
||||
}
|
||||
return bodies
|
||||
}
|
||||
|
|
@ -1260,16 +1264,16 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engin
|
|||
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
|
||||
// of block bodies by the engine api.
|
||||
func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
|
||||
return api.getBodiesByRange(start, count)
|
||||
return getBodiesByRange(api, start, count, getBody)
|
||||
}
|
||||
|
||||
// GetPayloadBodiesByRangeV2 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
|
||||
// GetPayloadBodiesByRangeV2 implements engine_getPayloadBodiesByRangeV2 which allows for retrieval of a range
|
||||
// of block bodies by the engine api.
|
||||
func (api *ConsensusAPI) GetPayloadBodiesByRangeV2(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
|
||||
return api.getBodiesByRange(start, count)
|
||||
func (api *ConsensusAPI) GetPayloadBodiesByRangeV2(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV2, error) {
|
||||
return getBodiesByRange(api, start, count, getBodyV2)
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) getBodiesByRange(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
|
||||
func getBodiesByRange[T any](api *ConsensusAPI, start, count hexutil.Uint64, getBody func(*types.Block) *T) ([]*T, error) {
|
||||
if start == 0 || count == 0 {
|
||||
return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
|
||||
}
|
||||
|
|
@ -1282,7 +1286,7 @@ func (api *ConsensusAPI) getBodiesByRange(start, count hexutil.Uint64) ([]*engin
|
|||
if last > current {
|
||||
last = current
|
||||
}
|
||||
bodies := make([]*engine.ExecutionPayloadBody, 0, uint64(count))
|
||||
bodies := make([]*T, 0, uint64(count))
|
||||
for i := uint64(start); i <= last; i++ {
|
||||
block := api.eth.BlockChain().GetBlockByNumber(i)
|
||||
bodies = append(bodies, getBody(block))
|
||||
|
|
@ -1311,6 +1315,17 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody {
|
|||
return &result
|
||||
}
|
||||
|
||||
func getBodyV2(block *types.Block) *engine.ExecutionPayloadBodyV2 {
|
||||
body := getBody(block)
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
return &engine.ExecutionPayloadBodyV2{
|
||||
ExecutionPayloadBody: *body,
|
||||
BlockAccessList: block.AccessList(),
|
||||
}
|
||||
}
|
||||
|
||||
// convertRequests converts a hex requests slice to plain [][]byte.
|
||||
func convertRequests(hex []hexutil.Bytes) [][]byte {
|
||||
if hex == nil {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
|
|
@ -427,8 +428,9 @@ func TestEth2DeepReorg(t *testing.T) {
|
|||
*/
|
||||
}
|
||||
|
||||
// startEthService creates a full node instance for testing.
|
||||
func startEthService(t testing.TB, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
|
||||
// startEthService creates a full node instance for testing. The default test
|
||||
// configuration can be adjusted through optional modifier functions.
|
||||
func startEthService(t testing.TB, genesis *core.Genesis, blocks []*types.Block, mods ...func(*ethconfig.Config)) (*node.Node, *eth.Ethereum) {
|
||||
t.Helper()
|
||||
|
||||
n, err := node.New(&node.Config{
|
||||
|
|
@ -449,6 +451,9 @@ func startEthService(t testing.TB, genesis *core.Genesis, blocks []*types.Block)
|
|||
TrieCleanCache: 256,
|
||||
Miner: miner.DefaultConfig,
|
||||
}
|
||||
for _, mod := range mods {
|
||||
mod(ethcfg)
|
||||
}
|
||||
ethservice, err := eth.New(n, ethcfg)
|
||||
if err != nil {
|
||||
t.Fatal("can't create eth service:", err)
|
||||
|
|
@ -468,6 +473,54 @@ func startEthService(t testing.TB, genesis *core.Genesis, blocks []*types.Block)
|
|||
return n, ethservice
|
||||
}
|
||||
|
||||
// TestForkchoiceUpdatedReorgDepthLimit tests that forkchoiceUpdated refuses to
|
||||
// rewind the chain head to a canonical ancestor deeper than the configured
|
||||
// EngineMaxReorgDepth, and that the limit can be lifted via the configuration.
|
||||
func TestForkchoiceUpdatedReorgDepthLimit(t *testing.T) {
|
||||
genesis, blocks := generateMergeChain(10, true)
|
||||
|
||||
t.Run("limited", func(t *testing.T) {
|
||||
n, ethservice := startEthService(t, genesis, blocks, func(cfg *ethconfig.Config) {
|
||||
cfg.EngineMaxReorgDepth = 5
|
||||
})
|
||||
defer n.Close()
|
||||
|
||||
api := newConsensusAPIWithoutHeartbeat(ethservice)
|
||||
|
||||
// Rewinding the head a few blocks within the limit is accepted.
|
||||
shallow := engine.ForkchoiceStateV1{HeadBlockHash: blocks[6].Hash()}
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), shallow, nil); err != nil {
|
||||
t.Fatalf("rewind within reorg depth limit failed: %v", err)
|
||||
}
|
||||
if head := ethservice.BlockChain().CurrentBlock().Number.Uint64(); head != blocks[6].NumberU64() {
|
||||
t.Fatalf("chain head not rewound: have %d, want %d", head, blocks[6].NumberU64())
|
||||
}
|
||||
// Rewinding beyond the limit is refused.
|
||||
deep := engine.ForkchoiceStateV1{HeadBlockHash: genesis.ToBlock().Hash()}
|
||||
_, err := api.ForkchoiceUpdatedV1(context.Background(), deep, nil)
|
||||
var apiErr *engine.EngineAPIError
|
||||
if !errors.As(err, &apiErr) || apiErr.ErrorCode() != engine.TooDeepReorg.ErrorCode() {
|
||||
t.Fatalf("rewind beyond reorg depth limit: have error %v, want %v", err, engine.TooDeepReorg)
|
||||
}
|
||||
})
|
||||
t.Run("unlimited", func(t *testing.T) {
|
||||
n, ethservice := startEthService(t, genesis, blocks, func(cfg *ethconfig.Config) {
|
||||
cfg.EngineMaxReorgDepth = 0 // no limit
|
||||
})
|
||||
defer n.Close()
|
||||
|
||||
api := newConsensusAPIWithoutHeartbeat(ethservice)
|
||||
|
||||
update := engine.ForkchoiceStateV1{HeadBlockHash: genesis.ToBlock().Hash()}
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), update, nil); err != nil {
|
||||
t.Fatalf("rewind with disabled reorg depth limit failed: %v", err)
|
||||
}
|
||||
if head := ethservice.BlockChain().CurrentBlock().Number.Uint64(); head != 0 {
|
||||
t.Fatalf("chain head not rewound to genesis: have %d, want 0", head)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullAPI(t *testing.T) {
|
||||
genesis, preMergeBlocks := generateMergeChain(10, false)
|
||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||
|
|
@ -1367,7 +1420,7 @@ func TestGetBlockBodiesByHash(t *testing.T) {
|
|||
for k, test := range tests {
|
||||
result := api.GetPayloadBodiesByHashV2(test.hashes)
|
||||
for i, r := range result {
|
||||
if err := checkEqualBody(test.results[i], r); err != nil {
|
||||
if err := checkEqualBodyV2(test.results[i], r); err != nil {
|
||||
t.Fatalf("test %v: invalid response: %v\nexpected %+v\ngot %+v", k, err, test.results[i], r)
|
||||
}
|
||||
}
|
||||
|
|
@ -1445,7 +1498,7 @@ func TestGetBlockBodiesByRange(t *testing.T) {
|
|||
}
|
||||
if len(result) == len(test.results) {
|
||||
for i, r := range result {
|
||||
if err := checkEqualBody(test.results[i], r); err != nil {
|
||||
if err := checkEqualBodyV2(test.results[i], r); err != nil {
|
||||
t.Fatalf("test %d: invalid response: %v\nexpected %+v\ngot %+v", k, err, test.results[i], r)
|
||||
}
|
||||
}
|
||||
|
|
@ -1521,6 +1574,75 @@ func checkEqualBody(a *types.Body, b *engine.ExecutionPayloadBody) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkEqualBodyV2(a *types.Body, b *engine.ExecutionPayloadBodyV2) error {
|
||||
if b == nil {
|
||||
return checkEqualBody(a, nil)
|
||||
}
|
||||
return checkEqualBody(a, &b.ExecutionPayloadBody)
|
||||
}
|
||||
|
||||
func TestGetPayloadBodyV2BlockAccessList(t *testing.T) {
|
||||
empty := bal.BlockAccessList{}
|
||||
emptyHash := empty.Hash()
|
||||
tests := []struct {
|
||||
name string
|
||||
header *types.Header
|
||||
accessList *bal.BlockAccessList
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "retained empty BAL",
|
||||
header: &types.Header{BlockAccessListHash: &emptyHash},
|
||||
accessList: &empty,
|
||||
want: "[]",
|
||||
},
|
||||
{
|
||||
name: "pruned BAL",
|
||||
header: &types.Header{BlockAccessListHash: &emptyHash},
|
||||
want: "null",
|
||||
},
|
||||
{
|
||||
name: "pre-Amsterdam block",
|
||||
header: new(types.Header),
|
||||
want: "null",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
block := types.NewBlockWithHeader(test.header).WithAccessListUnsafe(test.accessList)
|
||||
body := getBodyV2(block)
|
||||
encoded, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(encoded, &fields); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := string(fields["blockAccessList"]); got != test.want {
|
||||
t.Fatalf("unexpected blockAccessList: got %s, want %s", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPayloadBodyV1OmitsBlockAccessList(t *testing.T) {
|
||||
empty := bal.BlockAccessList{}
|
||||
emptyHash := empty.Hash()
|
||||
block := types.NewBlockWithHeader(&types.Header{BlockAccessListHash: &emptyHash}).WithAccessListUnsafe(&empty)
|
||||
encoded, err := json.Marshal(getBody(block))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(encoded, &fields); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := fields["blockAccessList"]; ok {
|
||||
t.Fatal("V1 payload body contains blockAccessList")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockToPayloadWithBlobs(t *testing.T) {
|
||||
header := types.Header{}
|
||||
var txs []*types.Transaction
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(ctx context.Context, upd
|
|||
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.Bogota):
|
||||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads")
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(ctx context.Context, params eng
|
|||
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
|
||||
case executionRequests == nil:
|
||||
return invalidStatus, paramsErr("nil executionRequests post-prague")
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5, forks.Bogota):
|
||||
return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads")
|
||||
}
|
||||
requests := convertRequests(executionRequests)
|
||||
|
|
@ -259,7 +259,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData,
|
|||
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
|
||||
case executionRequests == nil:
|
||||
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5, forks.Bogota):
|
||||
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads")
|
||||
}
|
||||
requests := convertRequests(executionRequests)
|
||||
|
|
|
|||
|
|
@ -333,8 +333,8 @@ func (d *Downloader) fetchHeaders(from uint64) error {
|
|||
return errNoPivotHeader
|
||||
}
|
||||
// Write out the pivot into the database so a rollback beyond
|
||||
// it will reenable snap sync and update the state root that
|
||||
// the state syncer will be downloading
|
||||
// it can be detected, and update the state root that the
|
||||
// state syncer will be downloading
|
||||
rawdb.WriteLastPivotNumber(d.stateDB, d.pivotHeader.Number.Uint64())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -542,8 +542,8 @@ func (d *Downloader) syncToHead() (err error) {
|
|||
if pivotNumber <= origin {
|
||||
origin = pivotNumber - 1
|
||||
}
|
||||
// Write out the pivot into the database so a rollback beyond it will
|
||||
// reenable snap sync
|
||||
// Write out the pivot into the database so a rollback beyond it
|
||||
// can be detected
|
||||
rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ func newSyncModer(mode ethconfig.SyncMode, chain BlockChain, disk ethdb.KeyValue
|
|||
// The database seems empty as the current block is the genesis. Yet the snap
|
||||
// block is ahead, so snap sync was enabled for this node at a certain point.
|
||||
// The scenarios where this can happen is
|
||||
// * if the user manually (or via a bad block) rolled back a snap sync node
|
||||
// below the sync point.
|
||||
// * if an internal reset (a fork config change or corruption recovery)
|
||||
// rolled a snap sync node back below the sync point.
|
||||
// * the last snap sync is not finished while user specifies a full sync this
|
||||
// time. But we don't have any recent state for full sync.
|
||||
// In these cases however it's safe to reenable snap sync.
|
||||
|
|
@ -87,8 +87,8 @@ func (m *syncModer) get(report bool) ethconfig.SyncMode {
|
|||
if report {
|
||||
logger = log.Info
|
||||
}
|
||||
// We are probably in full sync, but we might have rewound to before the
|
||||
// snap sync pivot, check if we should re-enable snap sync.
|
||||
// We are probably in full sync, but an internal reset may have rewound the
|
||||
// chain below the snap sync pivot, check if we should re-enable snap sync.
|
||||
head := m.chain.CurrentBlock()
|
||||
if pivot := rawdb.ReadLastPivotNumber(m.disk); pivot != nil {
|
||||
if head.Number.Uint64() < *pivot {
|
||||
|
|
|
|||
162
eth/dropper.go
162
eth/dropper.go
|
|
@ -17,6 +17,7 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
mrand "math/rand"
|
||||
"slices"
|
||||
"sync"
|
||||
|
|
@ -24,6 +25,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/eth/txtracker"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
|
|
@ -40,6 +42,10 @@ const (
|
|||
// dropping when no more peers can be added. Larger numbers result in more
|
||||
// aggressive drop behavior.
|
||||
peerDropThreshold = 0
|
||||
// Fraction of inbound/dialed peers to protect based on inclusion stats.
|
||||
// The top inclusionProtectionFrac of each category (by score) are
|
||||
// shielded from random dropping. 0.1 = top 10%.
|
||||
inclusionProtectionFrac = 0.1
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -47,18 +53,55 @@ var (
|
|||
droppedInbound = metrics.NewRegisteredMeter("eth/dropper/inbound", nil)
|
||||
// droppedOutbound is the number of outbound peers dropped
|
||||
droppedOutbound = metrics.NewRegisteredMeter("eth/dropper/outbound", nil)
|
||||
// dropSkipped counts times a drop was attempted but no peer was dropped,
|
||||
// for any reason (pool has headroom, all candidates trusted/static/young,
|
||||
// or protected by inclusion stats).
|
||||
dropSkipped = metrics.NewRegisteredMeter("eth/dropper/skipped", nil)
|
||||
)
|
||||
|
||||
// dropper monitors the state of the peer pool and makes changes as follows:
|
||||
// - during sync the Downloader handles peer connections, so dropper is disabled
|
||||
// - if not syncing and the peer count is close to the limit, it drops peers
|
||||
// randomly every peerDropInterval to make space for new peers
|
||||
// - peers are dropped separately from the inbound pool and from the dialed pool
|
||||
// Callback type to get per-peer inclusion statistics.
|
||||
type getPeerStatsFunc func() map[string]txtracker.PeerStats
|
||||
|
||||
// protectionCategory defines a peer scoring function and the fraction of peers
|
||||
// to protect per inbound/dialed category. Multiple categories are unioned.
|
||||
type protectionCategory struct {
|
||||
score func(txtracker.PeerStats) float64
|
||||
frac float64 // fraction of max peers to protect (0.0–1.0)
|
||||
}
|
||||
|
||||
// protectionCategories is the list of protection criteria. Each category
|
||||
// independently selects its top-N peers per pool; the union is protected.
|
||||
var protectionCategories = []protectionCategory{
|
||||
{func(s txtracker.PeerStats) float64 { return s.RecentFinalized }, inclusionProtectionFrac}, // Recent finalized
|
||||
{func(s txtracker.PeerStats) float64 { return s.RecentIncluded }, inclusionProtectionFrac}, // Recent included
|
||||
}
|
||||
|
||||
// dropper monitors the state of the peer pool and introduces churn by
|
||||
// periodically disconnecting a random peer to make room for new connections.
|
||||
// The main goal is to allow new peers to join the network and to facilitate
|
||||
// continuous topology adaptation.
|
||||
//
|
||||
// Behavior:
|
||||
// - During sync the Downloader handles peer connections, so dropper is disabled.
|
||||
// - When not syncing and a peer category (inbound or dialed) is close to its
|
||||
// limit, a random peer from that category is disconnected every 3–7 minutes.
|
||||
// - Trusted and static peers are never dropped.
|
||||
// - Recently connected peers are also protected from dropping to give them time
|
||||
// to prove their value before being at risk of disconnection.
|
||||
// - Some peers are protected from dropping based on their contribution
|
||||
// to the tx pool. Each pool (inbound/dialed) independently selects its
|
||||
// top fraction of peers by a per-peer EMA score — a slow EMA of
|
||||
// finalized inclusions (~1-day half-life, rewards sustained long-term
|
||||
// contribution) and a fast EMA of recent block inclusions (rewards
|
||||
// current activity). The union of all protected sets is shielded from
|
||||
// random dropping, and the drop target is chosen randomly from the
|
||||
// remainder.
|
||||
type dropper struct {
|
||||
maxDialPeers int // maximum number of dialed peers
|
||||
maxInboundPeers int // maximum number of inbound peers
|
||||
peersFunc getPeersFunc
|
||||
syncingFunc getSyncingFunc
|
||||
peerStatsFunc getPeerStatsFunc // optional: inclusion stats for protection
|
||||
|
||||
// peerDropTimer introduces churn if we are close to limit capacity.
|
||||
// We handle Dialed and Inbound connections separately
|
||||
|
|
@ -88,10 +131,12 @@ func newDropper(maxDialPeers, maxInboundPeers int) *dropper {
|
|||
return cm
|
||||
}
|
||||
|
||||
// Start the dropper.
|
||||
func (cm *dropper) Start(srv *p2p.Server, syncingFunc getSyncingFunc) {
|
||||
// Start the dropper. peerStatsFunc is optional (nil disables inclusion
|
||||
// protection).
|
||||
func (cm *dropper) Start(srv *p2p.Server, syncingFunc getSyncingFunc, peerStatsFunc getPeerStatsFunc) {
|
||||
cm.peersFunc = srv.Peers
|
||||
cm.syncingFunc = syncingFunc
|
||||
cm.peerStatsFunc = peerStatsFunc
|
||||
cm.wg.Add(1)
|
||||
go cm.loop()
|
||||
}
|
||||
|
|
@ -114,30 +159,101 @@ func (cm *dropper) dropRandomPeer() bool {
|
|||
}
|
||||
numDialed := len(peers) - numInbound
|
||||
|
||||
// Fast path: if neither pool is near capacity, every non-trusted/non-static
|
||||
// peer is already do-not-drop by pool-threshold rules. No point computing
|
||||
// inclusion protection.
|
||||
if cm.maxDialPeers-numDialed > peerDropThreshold &&
|
||||
cm.maxInboundPeers-numInbound > peerDropThreshold {
|
||||
dropSkipped.Mark(1)
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the set of inclusion-protected peers before filtering.
|
||||
protected := cm.protectedPeers(peers)
|
||||
|
||||
selectDoNotDrop := func(p *p2p.Peer) bool {
|
||||
// Avoid dropping trusted and static peers, or recent peers.
|
||||
// Only drop peers if their respective category (dialed/inbound)
|
||||
// is close to limit capacity.
|
||||
return p.Trusted() || p.StaticDialed() ||
|
||||
p.Lifetime() < mclock.AbsTime(doNotDropBefore) ||
|
||||
(p.DynDialed() && cm.maxDialPeers-numDialed > peerDropThreshold) ||
|
||||
(p.Inbound() && cm.maxInboundPeers-numInbound > peerDropThreshold)
|
||||
(p.Inbound() && cm.maxInboundPeers-numInbound > peerDropThreshold) ||
|
||||
protected[p]
|
||||
}
|
||||
|
||||
droppable := slices.DeleteFunc(peers, selectDoNotDrop)
|
||||
if len(droppable) > 0 {
|
||||
p := droppable[mrand.Intn(len(droppable))]
|
||||
log.Debug("Dropping random peer", "inbound", p.Inbound(),
|
||||
"id", p.ID(), "duration", common.PrettyDuration(p.Lifetime()), "peercountbefore", len(peers))
|
||||
p.Disconnect(p2p.DiscUselessPeer)
|
||||
if p.Inbound() {
|
||||
droppedInbound.Mark(1)
|
||||
} else {
|
||||
droppedOutbound.Mark(1)
|
||||
}
|
||||
return true
|
||||
if len(droppable) == 0 {
|
||||
dropSkipped.Mark(1)
|
||||
return false
|
||||
}
|
||||
return false
|
||||
p := droppable[mrand.Intn(len(droppable))]
|
||||
log.Debug("Dropping random peer", "inbound", p.Inbound(),
|
||||
"id", p.ID(), "duration", common.PrettyDuration(p.Lifetime()), "peercountbefore", len(peers))
|
||||
p.Disconnect(p2p.DiscUselessPeer)
|
||||
if p.Inbound() {
|
||||
droppedInbound.Mark(1)
|
||||
} else {
|
||||
droppedOutbound.Mark(1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// protectedPeers computes the set of peers that should not be dropped based
|
||||
// on inclusion stats. Each protection category independently selects its
|
||||
// top-N peers per inbound/dialed pool; the union is returned.
|
||||
func (cm *dropper) protectedPeers(peers []*p2p.Peer) map[*p2p.Peer]bool {
|
||||
if cm.peerStatsFunc == nil {
|
||||
return nil
|
||||
}
|
||||
stats := cm.peerStatsFunc()
|
||||
if len(stats) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Split peers by direction.
|
||||
var inbound, dialed []*p2p.Peer
|
||||
for _, p := range peers {
|
||||
if p.Inbound() {
|
||||
inbound = append(inbound, p)
|
||||
} else {
|
||||
dialed = append(dialed, p)
|
||||
}
|
||||
}
|
||||
result := protectedPeersByPool(inbound, dialed, stats)
|
||||
if len(result) > 0 {
|
||||
log.Debug("Protecting high-value peers from drop", "protected", len(result))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// protectedPeersByPool selects the union of top-N peers per protection
|
||||
// category across the given already-split inbound and dialed pools.
|
||||
// Factored from protectedPeers so tests can exercise the per-pool
|
||||
// selection logic without needing to construct direction-flagged
|
||||
// *p2p.Peer instances (which require unexported p2p types).
|
||||
func protectedPeersByPool(inbound, dialed []*p2p.Peer, stats map[string]txtracker.PeerStats) map[*p2p.Peer]bool {
|
||||
result := make(map[*p2p.Peer]bool)
|
||||
// protectPool selects the top-frac peers from pool by score and adds them to result.
|
||||
protectPool := func(pool []*p2p.Peer, cat protectionCategory) {
|
||||
n := int(float64(len(pool)) * cat.frac)
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
sorted := slices.SortedFunc(slices.Values(pool), func(a, b *p2p.Peer) int {
|
||||
// descending
|
||||
scoreB := cat.score(stats[b.ID().String()])
|
||||
scoreA := cat.score(stats[a.ID().String()])
|
||||
return cmp.Compare(scoreB, scoreA)
|
||||
})
|
||||
// select top n peers excluding 0
|
||||
for _, p := range sorted[:min(n, len(sorted))] {
|
||||
if cat.score(stats[p.ID().String()]) > 0 {
|
||||
result[p] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cat := range protectionCategories {
|
||||
protectPool(inbound, cat)
|
||||
protectPool(dialed, cat)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// randomDuration generates a random duration between min and max.
|
||||
|
|
|
|||
234
eth/dropper_test.go
Normal file
234
eth/dropper_test.go
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// 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
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth/txtracker"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
func makePeers(n int) []*p2p.Peer {
|
||||
peers := make([]*p2p.Peer, n)
|
||||
for i := range peers {
|
||||
id := enode.ID{byte(i)}
|
||||
peers[i] = p2p.NewPeer(id, fmt.Sprintf("peer%d", i), nil)
|
||||
}
|
||||
return peers
|
||||
}
|
||||
|
||||
func TestProtectedPeersNoStats(t *testing.T) {
|
||||
cm := &dropper{maxDialPeers: 20, maxInboundPeers: 30}
|
||||
cm.peerStatsFunc = func() map[string]txtracker.PeerStats { return nil }
|
||||
|
||||
peers := makePeers(10)
|
||||
protected := cm.protectedPeers(peers)
|
||||
if len(protected) != 0 {
|
||||
t.Fatalf("expected no protected peers with nil stats, got %d", len(protected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedPeersEmptyStats(t *testing.T) {
|
||||
cm := &dropper{maxDialPeers: 20, maxInboundPeers: 30}
|
||||
cm.peerStatsFunc = func() map[string]txtracker.PeerStats {
|
||||
return map[string]txtracker.PeerStats{}
|
||||
}
|
||||
|
||||
peers := makePeers(10)
|
||||
protected := cm.protectedPeers(peers)
|
||||
if len(protected) != 0 {
|
||||
t.Fatalf("expected no protected peers with empty stats, got %d", len(protected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedPeersTopPeer(t *testing.T) {
|
||||
// 20 peers, 10% of 20 = 2 protected per category.
|
||||
cm := &dropper{maxDialPeers: 20, maxInboundPeers: 30}
|
||||
|
||||
peers := makePeers(20)
|
||||
stats := make(map[string]txtracker.PeerStats)
|
||||
stats[peers[0].ID().String()] = txtracker.PeerStats{RecentFinalized: 100}
|
||||
stats[peers[1].ID().String()] = txtracker.PeerStats{RecentIncluded: 5.0}
|
||||
|
||||
cm.peerStatsFunc = func() map[string]txtracker.PeerStats { return stats }
|
||||
|
||||
protected := cm.protectedPeers(peers)
|
||||
if len(protected) != 2 {
|
||||
t.Fatalf("expected 2 protected peers, got %d", len(protected))
|
||||
}
|
||||
if !protected[peers[0]] {
|
||||
t.Fatal("peer 0 should be protected (top RecentFinalized)")
|
||||
}
|
||||
if !protected[peers[1]] {
|
||||
t.Fatal("peer 1 should be protected (top RecentIncluded)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedPeersZeroScore(t *testing.T) {
|
||||
cm := &dropper{maxDialPeers: 20, maxInboundPeers: 30}
|
||||
|
||||
peers := makePeers(10)
|
||||
stats := make(map[string]txtracker.PeerStats)
|
||||
for _, p := range peers {
|
||||
stats[p.ID().String()] = txtracker.PeerStats{}
|
||||
}
|
||||
cm.peerStatsFunc = func() map[string]txtracker.PeerStats { return stats }
|
||||
|
||||
protected := cm.protectedPeers(peers)
|
||||
if len(protected) != 0 {
|
||||
t.Fatalf("expected no protection with zero scores, got %d", len(protected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedPeersOverlap(t *testing.T) {
|
||||
// One peer is top in both categories — counted once.
|
||||
cm := &dropper{maxDialPeers: 20, maxInboundPeers: 30}
|
||||
|
||||
peers := makePeers(20)
|
||||
stats := make(map[string]txtracker.PeerStats)
|
||||
stats[peers[0].ID().String()] = txtracker.PeerStats{RecentFinalized: 100, RecentIncluded: 5.0}
|
||||
|
||||
cm.peerStatsFunc = func() map[string]txtracker.PeerStats { return stats }
|
||||
|
||||
protected := cm.protectedPeers(peers)
|
||||
if len(protected) != 1 {
|
||||
t.Fatalf("expected 1 protected peer (overlap), got %d", len(protected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedPeersNilFunc(t *testing.T) {
|
||||
cm := &dropper{maxDialPeers: 20, maxInboundPeers: 30}
|
||||
// peerStatsFunc is nil (default).
|
||||
|
||||
peers := makePeers(10)
|
||||
protected := cm.protectedPeers(peers)
|
||||
if protected != nil {
|
||||
t.Fatalf("expected nil with nil stats func, got %v", protected)
|
||||
}
|
||||
}
|
||||
|
||||
// TestProtectedByPoolPerPoolTopN verifies that the top-N selection runs
|
||||
// independently in each of the inbound and dialed pools, not globally.
|
||||
// With 10 peers per pool and inclusionProtectionFrac=0.1, exactly 1 peer
|
||||
// is protected per pool per category — so 2 total (one per pool), both
|
||||
// for the RecentFinalized category since we don't set RecentIncluded.
|
||||
func TestProtectedByPoolPerPoolTopN(t *testing.T) {
|
||||
inbound := makePeers(10)
|
||||
dialed := makePeers(10)
|
||||
// Distinguish dialed peer IDs from inbound so stats maps don't collide.
|
||||
for i := range dialed {
|
||||
id := enode.ID{byte(100 + i)}
|
||||
dialed[i] = p2p.NewPeer(id, fmt.Sprintf("dialed%d", i), nil)
|
||||
}
|
||||
// Strictly increasing scores: highest wins in each pool.
|
||||
stats := make(map[string]txtracker.PeerStats)
|
||||
for i, p := range inbound {
|
||||
stats[p.ID().String()] = txtracker.PeerStats{RecentFinalized: float64(1 + i)}
|
||||
}
|
||||
for i, p := range dialed {
|
||||
stats[p.ID().String()] = txtracker.PeerStats{RecentFinalized: float64(1 + i)}
|
||||
}
|
||||
|
||||
protected := protectedPeersByPool(inbound, dialed, stats)
|
||||
|
||||
// Expect top 1 of inbound (inbound[9]) and top 1 of dialed (dialed[9]).
|
||||
if len(protected) != 2 {
|
||||
t.Fatalf("expected 2 protected peers (1 per pool), got %d", len(protected))
|
||||
}
|
||||
if !protected[inbound[9]] {
|
||||
t.Error("expected top inbound peer to be protected")
|
||||
}
|
||||
if !protected[dialed[9]] {
|
||||
t.Error("expected top dialed peer to be protected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestProtectedByPoolCrossCategoryOverlap verifies that the union across
|
||||
// protection categories is correctly deduplicated: a peer that wins in
|
||||
// multiple categories appears once, and category winners are all
|
||||
// protected. Uses a pool large enough that frac*len yields n=2 per
|
||||
// category, so cross-category overlap is observable.
|
||||
func TestProtectedByPoolCrossCategoryOverlap(t *testing.T) {
|
||||
// 20 dialed peers so 0.1 * 20 = 2 protected per category.
|
||||
dialed := makePeers(20)
|
||||
// P0: high RecentFinalized only. P1: high RecentIncluded only. P2: high both.
|
||||
// With n=2 per category:
|
||||
// RecentFinalized winners: P2 (tie-broken-ok), P0
|
||||
// RecentIncluded winners: P2, P1
|
||||
// Union: {P0, P1, P2}.
|
||||
stats := make(map[string]txtracker.PeerStats)
|
||||
stats[dialed[0].ID().String()] = txtracker.PeerStats{RecentFinalized: 100, RecentIncluded: 0}
|
||||
stats[dialed[1].ID().String()] = txtracker.PeerStats{RecentFinalized: 0, RecentIncluded: 5.0}
|
||||
stats[dialed[2].ID().String()] = txtracker.PeerStats{RecentFinalized: 200, RecentIncluded: 10.0}
|
||||
|
||||
protected := protectedPeersByPool(nil, dialed, stats)
|
||||
|
||||
if len(protected) != 3 {
|
||||
t.Fatalf("expected 3 protected peers (union of category winners), got %d", len(protected))
|
||||
}
|
||||
for _, idx := range []int{0, 1, 2} {
|
||||
if !protected[dialed[idx]] {
|
||||
t.Errorf("peer %d should be protected", idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestProtectedByPoolPerPoolIndependence locks in that selection runs
|
||||
// per-pool, not globally. Every inbound peer scores higher than every
|
||||
// dialed peer, so a global top-N would pick only inbound peers. Per-pool
|
||||
// top-N must still protect the top dialed peers.
|
||||
func TestProtectedByPoolPerPoolIndependence(t *testing.T) {
|
||||
// 20 inbound, 20 dialed — frac=0.1 → 2 protected per pool per category.
|
||||
// Global top-4 of RecentFinalized would be inbound[16..19] — zero dialed.
|
||||
inbound := makePeers(20)
|
||||
dialed := make([]*p2p.Peer, 20)
|
||||
for i := range dialed {
|
||||
id := enode.ID{byte(100 + i)}
|
||||
dialed[i] = p2p.NewPeer(id, fmt.Sprintf("dialed%d", i), nil)
|
||||
}
|
||||
stats := make(map[string]txtracker.PeerStats)
|
||||
// Every inbound peer outscores every dialed peer.
|
||||
for i, p := range inbound {
|
||||
stats[p.ID().String()] = txtracker.PeerStats{RecentFinalized: float64(1000 + i)}
|
||||
}
|
||||
for i, p := range dialed {
|
||||
stats[p.ID().String()] = txtracker.PeerStats{RecentFinalized: float64(1 + i)}
|
||||
}
|
||||
|
||||
protected := protectedPeersByPool(inbound, dialed, stats)
|
||||
|
||||
// Per-pool top-2 of RecentFinalized:
|
||||
// inbound: inbound[18], inbound[19]
|
||||
// dialed: dialed[18], dialed[19]
|
||||
// Global top-N would contain zero dialed peers, so asserting the top
|
||||
// dialed peers are protected enforces per-pool independence.
|
||||
if !protected[dialed[19]] {
|
||||
t.Fatal("top dialed peer must be protected regardless of globally-higher inbound peers")
|
||||
}
|
||||
if !protected[dialed[18]] {
|
||||
t.Fatal("second-top dialed peer must be protected regardless of globally-higher inbound peers")
|
||||
}
|
||||
if !protected[inbound[19]] || !protected[inbound[18]] {
|
||||
t.Fatal("top inbound peers must also be protected")
|
||||
}
|
||||
if len(protected) != 4 {
|
||||
t.Fatalf("expected 4 protected peers (top-2 of each pool), got %d", len(protected))
|
||||
}
|
||||
}
|
||||
|
|
@ -75,6 +75,7 @@ var Defaults = Config{
|
|||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
RPCTxFeeCap: 1, // 1 ether
|
||||
EngineMaxReorgDepth: 32,
|
||||
TxSyncDefaultTimeout: 20 * time.Second,
|
||||
TxSyncMaxTimeout: 1 * time.Minute,
|
||||
SlowBlockThreshold: -1, // Disabled by default; set via --debug.logslowblock flag
|
||||
|
|
@ -203,6 +204,11 @@ type Config struct {
|
|||
// send-transaction variants. The unit is ether.
|
||||
RPCTxFeeCap float64
|
||||
|
||||
// EngineMaxReorgDepth is the maximum depth the chain head can be rewound
|
||||
// to an already-canonical ancestor by engine API forkchoiceUpdated calls
|
||||
// (0 = no limit).
|
||||
EngineMaxReorgDepth uint64 `toml:",omitempty"`
|
||||
|
||||
// OverrideOsaka (TODO: remove after the fork)
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
RPCGasCap uint64
|
||||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
EngineMaxReorgDepth uint64 `toml:",omitempty"`
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideAmsterdam *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
|
|
@ -119,6 +120,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.RPCGasCap = c.RPCGasCap
|
||||
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
||||
enc.RPCTxFeeCap = c.RPCTxFeeCap
|
||||
enc.EngineMaxReorgDepth = c.EngineMaxReorgDepth
|
||||
enc.OverrideOsaka = c.OverrideOsaka
|
||||
enc.OverrideAmsterdam = c.OverrideAmsterdam
|
||||
enc.OverrideBPO1 = c.OverrideBPO1
|
||||
|
|
@ -179,6 +181,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
RPCGasCap *uint64
|
||||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
EngineMaxReorgDepth *uint64 `toml:",omitempty"`
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideAmsterdam *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
|
|
@ -330,6 +333,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.RPCTxFeeCap != nil {
|
||||
c.RPCTxFeeCap = *dec.RPCTxFeeCap
|
||||
}
|
||||
if dec.EngineMaxReorgDepth != nil {
|
||||
c.EngineMaxReorgDepth = *dec.EngineMaxReorgDepth
|
||||
}
|
||||
if dec.OverrideOsaka != nil {
|
||||
c.OverrideOsaka = dec.OverrideOsaka
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,10 +183,11 @@ type TxFetcher struct {
|
|||
alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
|
||||
|
||||
// Callbacks
|
||||
validateMeta func(common.Hash, byte) error // Validate a tx metadata based on the local txpool
|
||||
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
|
||||
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
||||
dropPeer func(string) // Drops a peer in case of announcement violation
|
||||
validateMeta func(common.Hash, byte) error // Validate a tx metadata based on the local txpool
|
||||
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
|
||||
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
||||
dropPeer func(string) // Drops a peer in case of announcement violation
|
||||
onAccepted func(peer string, hashes []common.Hash) // Optional: notified with accepted tx hashes per peer
|
||||
|
||||
buffer *blobpool.BlobBuffer
|
||||
|
||||
|
|
@ -200,15 +201,15 @@ type TxFetcher struct {
|
|||
// based on hash announcements.
|
||||
// Chain can be nil to disable on-chain checks.
|
||||
func NewTxFetcher(chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
|
||||
dropPeer func(string), buffer *blobpool.BlobBuffer) *TxFetcher {
|
||||
return NewTxFetcherForTests(chain, validateMeta, addTxs, fetchTxs, dropPeer, buffer, mclock.System{}, time.Now, nil)
|
||||
dropPeer func(string), onAccepted func(string, []common.Hash), buffer *blobpool.BlobBuffer) *TxFetcher {
|
||||
return NewTxFetcherForTests(chain, validateMeta, addTxs, fetchTxs, dropPeer, onAccepted, buffer, mclock.System{}, time.Now, nil)
|
||||
}
|
||||
|
||||
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
||||
// a simulated version and the internal randomness with a deterministic one.
|
||||
// Chain can be nil to disable on-chain checks.
|
||||
func NewTxFetcherForTests(
|
||||
chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
|
||||
chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string), onAccepted func(string, []common.Hash),
|
||||
buffer *blobpool.BlobBuffer, clock mclock.Clock, realTime func() time.Time, rand *mrand.Rand) *TxFetcher {
|
||||
return &TxFetcher{
|
||||
notify: make(chan *txAnnounce),
|
||||
|
|
@ -231,6 +232,7 @@ func NewTxFetcherForTests(
|
|||
fetchTxs: fetchTxs,
|
||||
dropPeer: dropPeer,
|
||||
buffer: buffer,
|
||||
onAccepted: onAccepted,
|
||||
clock: clock,
|
||||
realTime: realTime,
|
||||
rand: rand,
|
||||
|
|
@ -387,7 +389,11 @@ func (f *TxFetcher) Enqueue(peer string, version uint, txs []*types.Transaction,
|
|||
for j := range batch {
|
||||
hashes[j] = batch[j].Hash()
|
||||
}
|
||||
var accepted []common.Hash
|
||||
for j, err := range errs {
|
||||
if err == nil {
|
||||
accepted = append(accepted, batch[j].Hash())
|
||||
}
|
||||
if errors.Is(err, txpool.ErrKZGVerificationError) || errors.Is(err, txpool.ErrSidecarFormatError) {
|
||||
// KZG verification failed, terminate transaction processing immediately.
|
||||
// Since KZG verification is computationally expensive, this acts as a
|
||||
|
|
@ -406,6 +412,11 @@ func (f *TxFetcher) Enqueue(peer string, version uint, txs []*types.Transaction,
|
|||
}
|
||||
}
|
||||
otherreject := f.handleAddErrors(hashes, errs, metrics)
|
||||
|
||||
// Notify the tracker which txs from this peer were accepted.
|
||||
if f.onAccepted != nil && len(accepted) > 0 {
|
||||
f.onAccepted(peer, accepted)
|
||||
}
|
||||
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit
|
||||
// to throttle the misbehaving peer.
|
||||
if otherreject > int64((len(hashes)+3)/4) {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ func newTestTxFetcher() *TxFetcher {
|
|||
},
|
||||
func(string, []common.Hash) error { return nil },
|
||||
nil,
|
||||
nil,
|
||||
newTestBlobBuffer(),
|
||||
)
|
||||
}
|
||||
|
|
@ -2217,6 +2218,7 @@ func TestTransactionForgotten(t *testing.T) {
|
|||
},
|
||||
func(string, []common.Hash) error { return nil },
|
||||
func(string) {},
|
||||
nil,
|
||||
newTestBlobBuffer(),
|
||||
mockClock,
|
||||
mockTime,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/fetcher"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/eth/txtracker"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -140,6 +141,7 @@ type handler struct {
|
|||
downloader *downloader.Downloader
|
||||
txFetcher *fetcher.TxFetcher
|
||||
blobFetcher *fetcher.BlobFetcher
|
||||
txTracker *txtracker.Tracker
|
||||
peers *peerSet
|
||||
txBroadcastKey [16]byte
|
||||
|
||||
|
|
@ -208,7 +210,8 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
h.txFetcher = fetcher.NewTxFetcher(h.chain, validateMeta, addTxs, fetchTx, h.removePeer, blobBuffer)
|
||||
h.txTracker = txtracker.New()
|
||||
h.txFetcher = fetcher.NewTxFetcher(h.chain, validateMeta, addTxs, fetchTx, h.removePeer, h.txTracker.NotifyAccepted, blobBuffer)
|
||||
|
||||
// Construct the blob fetcher for cell-based blob data availability
|
||||
blobCallbacks := fetcher.BlobFetcherFunctions{
|
||||
|
|
@ -453,6 +456,7 @@ func (h *handler) unregisterPeer(id string) {
|
|||
h.downloader.UnregisterPeer(id)
|
||||
h.txFetcher.Drop(id)
|
||||
h.blobFetcher.Drop(id)
|
||||
h.txTracker.NotifyPeerDrop(id)
|
||||
|
||||
if err := h.peers.unregisterPeer(id); err != nil {
|
||||
logger.Error("Ethereum peer removal failed", "err", err)
|
||||
|
|
@ -477,6 +481,9 @@ func (h *handler) Start(maxPeers int) {
|
|||
h.txFetcher.Start()
|
||||
h.blobFetcher.Start()
|
||||
|
||||
// Start the transaction tracker (records tx deliveries, credits peer inclusions).
|
||||
h.txTracker.Start(h.chain)
|
||||
|
||||
// start peer handler tracker
|
||||
h.wg.Add(1)
|
||||
go h.protoTracker()
|
||||
|
|
|
|||
|
|
@ -798,11 +798,22 @@ func catchUpExceedsRetention(prev, curr *types.Header) bool {
|
|||
// diffs to roll flat state forward.
|
||||
func (s *syncerV2) catchUp(target *types.Header, cancel chan struct{}) error {
|
||||
s.lock.RLock()
|
||||
from := s.pivot.Number.Uint64() + 1
|
||||
to := target.Number.Uint64()
|
||||
prev := s.pivot
|
||||
s.lock.RUnlock()
|
||||
|
||||
var (
|
||||
from = prev.Number.Uint64() + 1
|
||||
to = target.Number.Uint64()
|
||||
)
|
||||
log.Info("Starting BAL catch-up", "from", from, "to", to, "blocks", to-from+1)
|
||||
|
||||
// Resolve the hash chain of the whole gap upfront by walking the parent
|
||||
// hashes backward from the trusted target header.
|
||||
hashes, err := s.gapHashChain(prev, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.lock.Lock()
|
||||
s.accessListTotal = to - from + 1
|
||||
s.accessListSynced = 0
|
||||
|
|
@ -819,26 +830,26 @@ func (s *syncerV2) catchUp(target *types.Header, cancel chan struct{}) error {
|
|||
if end > to {
|
||||
end = to
|
||||
}
|
||||
// Collect block hashes and headers for this window.
|
||||
// Collect the headers for this window.
|
||||
var (
|
||||
hashes = make([]common.Hash, 0, end-start+1)
|
||||
headers = make(map[common.Hash]*types.Header, end-start+1)
|
||||
winHashes = hashes[start-from : end-from+1]
|
||||
headers = make(map[common.Hash]*types.Header, len(winHashes))
|
||||
)
|
||||
for num := start; num <= end; num++ {
|
||||
hash := rawdb.ReadCanonicalHash(s.db, num)
|
||||
if hash == (common.Hash{}) {
|
||||
return fmt.Errorf("missing canonical hash for block %d during catch-up", num)
|
||||
for i, hash := range winHashes {
|
||||
num := start + uint64(i)
|
||||
if num == to {
|
||||
headers[hash] = target
|
||||
continue
|
||||
}
|
||||
header := rawdb.ReadHeader(s.db, hash, num)
|
||||
header := s.gapHeader(num, hash)
|
||||
if header == nil {
|
||||
return fmt.Errorf("missing header for block %d (hash %v) during catch-up", num, hash)
|
||||
}
|
||||
hashes = append(hashes, hash)
|
||||
headers[hash] = header
|
||||
}
|
||||
|
||||
// Fetch this window's BALs from peers.
|
||||
rawBALs, err := s.fetchAccessLists(hashes, headers, cancel)
|
||||
rawBALs, err := s.fetchAccessLists(winHashes, headers, cancel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -851,7 +862,7 @@ func (s *syncerV2) catchUp(target *types.Header, cancel chan struct{}) error {
|
|||
default:
|
||||
}
|
||||
num := start + uint64(i)
|
||||
hash := hashes[i]
|
||||
hash := winHashes[i]
|
||||
|
||||
// Decode the raw RLP into a BAL.
|
||||
var (
|
||||
|
|
@ -883,6 +894,44 @@ func (s *syncerV2) catchUp(target *types.Header, cancel chan struct{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// gapHashChain resolves the hashes of the catch-up gap (prev, target] by
|
||||
// walking the parent hashes backward from the trusted target header.
|
||||
// Out-of-memory is not expected as only the 32 bytes hashes are kept.
|
||||
func (s *syncerV2) gapHashChain(prev, target *types.Header) ([]common.Hash, error) {
|
||||
var (
|
||||
from = prev.Number.Uint64() + 1
|
||||
to = target.Number.Uint64()
|
||||
hashes = make([]common.Hash, to-from+1)
|
||||
want = target.ParentHash
|
||||
)
|
||||
hashes[to-from] = target.Hash()
|
||||
for num := to - 1; num >= from; num-- {
|
||||
header := s.gapHeader(num, want)
|
||||
if header == nil {
|
||||
return nil, fmt.Errorf("missing header for block %d (hash %v) during catch-up", num, want)
|
||||
}
|
||||
hashes[num-from] = want
|
||||
want = header.ParentHash
|
||||
}
|
||||
if want != prev.Hash() {
|
||||
return nil, fmt.Errorf("catch-up target %d (hash %v) does not descend from pivot %d (hash %v)", to, target.Hash(), prev.Number, prev.Hash())
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// gapHeader reads the header of a gap block with the given number and
|
||||
// expected hash. Two data sources are checked: skeleton or the canonical
|
||||
// header, as the headers will be kept in skeleton until the full block
|
||||
// is fully downloaded and inserted.
|
||||
func (s *syncerV2) gapHeader(num uint64, hash common.Hash) *types.Header {
|
||||
if header := rawdb.ReadSkeletonHeader(s.db, num); header != nil && header.Hash() == hash {
|
||||
return header
|
||||
}
|
||||
// If the header is outside the skeleton scope, read it from
|
||||
// canonical source.
|
||||
return rawdb.ReadHeader(s.db, hash, num)
|
||||
}
|
||||
|
||||
// fetchAccessLists fetches BALs for the given block hashes from
|
||||
// remote peers. It runs its own event loop to assign requests
|
||||
// to idle peers and process responses asynchronously. Each BAL is verified
|
||||
|
|
@ -914,6 +963,7 @@ func (s *syncerV2) fetchAccessLists(hashes []common.Hash, headers map[common.Has
|
|||
accessListReqFails = make(chan *accessListRequest)
|
||||
accessListResps = make(chan *accessListResponse)
|
||||
lastStallLog = time.Now()
|
||||
lastFetched = 0
|
||||
)
|
||||
for len(fetched) < len(hashes) {
|
||||
if err := s.checkAccessListProgress(pending, refused); err != nil {
|
||||
|
|
@ -951,7 +1001,8 @@ func (s *syncerV2) fetchAccessLists(hashes []common.Hash, headers map[common.Has
|
|||
s.processAccessListResponse(res, headers, pending, fetched, refused)
|
||||
}
|
||||
s.lock.Lock()
|
||||
s.accessListSynced += uint64(len(fetched))
|
||||
s.accessListSynced += uint64(len(fetched) - lastFetched)
|
||||
lastFetched = len(fetched)
|
||||
s.refreshProgressLocked()
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1797,8 +1797,11 @@ func testCatchUpPersistsIncrementally(t *testing.T, scheme string) {
|
|||
|
||||
// Build three sequential BAL blocks (A+1, A+2, A+3). The first two touch
|
||||
// goodAddr, the third touches corruptAddr so that block's apply fails
|
||||
// once we've corrupted that account's snapshot.
|
||||
// once we've corrupted that account's snapshot. The headers link up via
|
||||
// their parent hashes, as the catch-up walks the chain backward from the
|
||||
// target down to the previous pivot.
|
||||
blocks := make([]balBlock, 3)
|
||||
parent := pivotAHeader.Hash()
|
||||
for i := 0; i < 3; i++ {
|
||||
blockNum := numA + uint64(i) + 1
|
||||
target := goodAddr
|
||||
|
|
@ -1819,7 +1822,8 @@ func testCatchUpPersistsIncrementally(t *testing.T, scheme string) {
|
|||
}
|
||||
balHash := b.Hash()
|
||||
header := &types.Header{
|
||||
Number: new(big.Int).SetUint64(blockNum), Difficulty: common.Big0,
|
||||
ParentHash: parent,
|
||||
Number: new(big.Int).SetUint64(blockNum), Difficulty: common.Big0,
|
||||
BaseFee: common.Big0, WithdrawalsHash: &emptyHash,
|
||||
BlobGasUsed: &zero, ExcessBlobGas: &zero,
|
||||
ParentBeaconRoot: &emptyHash, RequestsHash: &emptyHash,
|
||||
|
|
@ -1828,6 +1832,7 @@ func testCatchUpPersistsIncrementally(t *testing.T, scheme string) {
|
|||
rawdb.WriteHeader(db, header)
|
||||
rawdb.WriteCanonicalHash(db, header.Hash(), blockNum)
|
||||
blocks[i] = balBlock{header: header, bal: buf.Bytes()}
|
||||
parent = header.Hash()
|
||||
}
|
||||
|
||||
// First sync: complete sync to A so persisted state has pivot=A,
|
||||
|
|
@ -1929,12 +1934,15 @@ func testCatchUpWindowed(t *testing.T, scheme string) {
|
|||
rawdb.WriteCanonicalHash(db, pivotA.Hash(), numA)
|
||||
|
||||
// Build a 5-block gap, each block bumping targetAddr's balance. The last
|
||||
// block's balance is the expected final state.
|
||||
// block's balance is the expected final state. The headers link up via
|
||||
// their parent hashes, as the catch-up walks the chain backward from the
|
||||
// target down to the previous pivot.
|
||||
const gap = 5
|
||||
var (
|
||||
lastHeader *types.Header
|
||||
lastBalance *uint256.Int
|
||||
balsByHash = make(map[common.Hash]rlp.RawValue, gap)
|
||||
parent = pivotA.Hash()
|
||||
)
|
||||
for i := 0; i < gap; i++ {
|
||||
blockNum := numA + uint64(i) + 1
|
||||
|
|
@ -1952,7 +1960,8 @@ func testCatchUpWindowed(t *testing.T, scheme string) {
|
|||
}
|
||||
balHash := b.Hash()
|
||||
header := &types.Header{
|
||||
Number: new(big.Int).SetUint64(blockNum), Difficulty: common.Big0,
|
||||
ParentHash: parent,
|
||||
Number: new(big.Int).SetUint64(blockNum), Difficulty: common.Big0,
|
||||
BaseFee: common.Big0, WithdrawalsHash: &emptyHash,
|
||||
BlobGasUsed: &zero, ExcessBlobGas: &zero,
|
||||
ParentBeaconRoot: &emptyHash, RequestsHash: &emptyHash,
|
||||
|
|
@ -1962,6 +1971,7 @@ func testCatchUpWindowed(t *testing.T, scheme string) {
|
|||
rawdb.WriteCanonicalHash(db, header.Hash(), blockNum)
|
||||
balsByHash[header.Hash()] = buf.Bytes()
|
||||
lastHeader, lastBalance = header, balance
|
||||
parent = header.Hash()
|
||||
}
|
||||
|
||||
// Seed sync to A: persisted state ends with pivot=A and full flat state.
|
||||
|
|
@ -3008,7 +3018,8 @@ func testCatchUpAppliesStorageBALs(t *testing.T, scheme string) {
|
|||
rawdb.WriteCanonicalHash(db, hdrA.Hash(), numA)
|
||||
|
||||
hdrB := &types.Header{
|
||||
Number: new(big.Int).SetUint64(numA + 1), Root: rootB, Difficulty: common.Big0,
|
||||
ParentHash: hdrA.Hash(),
|
||||
Number: new(big.Int).SetUint64(numA + 1), Root: rootB, Difficulty: common.Big0,
|
||||
BaseFee: common.Big0, WithdrawalsHash: &emptyH,
|
||||
BlobGasUsed: &zero, ExcessBlobGas: &zero,
|
||||
ParentBeaconRoot: &emptyH, RequestsHash: &emptyH,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,19 @@ type Config struct {
|
|||
Overrides *params.ChainConfig `json:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
// countingWriter wraps an io.Writer and records how many bytes have been
|
||||
// written through it.
|
||||
type countingWriter struct {
|
||||
w io.Writer
|
||||
n int
|
||||
}
|
||||
|
||||
func (c *countingWriter) Write(p []byte) (int, error) {
|
||||
n, err := c.w.Write(p)
|
||||
c.n += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
||||
|
||||
// StructLog is emitted to the EVM each cycle and lists information about the
|
||||
|
|
@ -227,7 +240,7 @@ type StructLogger struct {
|
|||
|
||||
writer io.Writer // If set, the logger will stream instead of store logs
|
||||
logs []json.RawMessage // buffer of json-encoded logs
|
||||
resultSize int
|
||||
resultSize int // total bytes of trace output
|
||||
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
|
|
@ -237,7 +250,7 @@ type StructLogger struct {
|
|||
// NewStreamingStructLogger returns a new streaming logger.
|
||||
func NewStreamingStructLogger(cfg *Config, writer io.Writer) *StructLogger {
|
||||
l := NewStructLogger(cfg)
|
||||
l.writer = writer
|
||||
l.writer = &countingWriter{w: writer}
|
||||
return l
|
||||
}
|
||||
|
||||
|
|
@ -334,6 +347,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
|
|||
return
|
||||
}
|
||||
log.Write(l.writer)
|
||||
l.resultSize = l.writer.(*countingWriter).n
|
||||
}
|
||||
|
||||
// OnExit is called a call frame finishes processing.
|
||||
|
|
|
|||
|
|
@ -59,12 +59,14 @@ type jsonLogger struct {
|
|||
cfg *Config
|
||||
env *tracing.VMContext
|
||||
hooks *tracing.Hooks
|
||||
written *countingWriter
|
||||
}
|
||||
|
||||
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
|
||||
// into the provided stream.
|
||||
func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
|
||||
l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg}
|
||||
cw := &countingWriter{w: writer}
|
||||
l := &jsonLogger{encoder: json.NewEncoder(cw), cfg: cfg, written: cw}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &Config{}
|
||||
}
|
||||
|
|
@ -81,7 +83,8 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
|
|||
// NewJSONLoggerWithCallFrames creates a new EVM tracer that prints execution steps as JSON objects
|
||||
// into the provided stream. It also includes call frames in the output.
|
||||
func NewJSONLoggerWithCallFrames(cfg *Config, writer io.Writer) *tracing.Hooks {
|
||||
l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg}
|
||||
cw := &countingWriter{w: writer}
|
||||
l := &jsonLogger{encoder: json.NewEncoder(cw), cfg: cfg, written: cw}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &Config{}
|
||||
}
|
||||
|
|
@ -102,6 +105,9 @@ func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope
|
|||
}
|
||||
|
||||
func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
|
||||
if l.cfg.Limit != 0 && l.written.n > l.cfg.Limit {
|
||||
return
|
||||
}
|
||||
memory := scope.MemoryData()
|
||||
stack := scope.StackData()
|
||||
|
||||
|
|
|
|||
320
eth/txtracker/tracker.go
Normal file
320
eth/txtracker/tracker.go
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
// 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
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package txtracker provides minimal per-peer transaction inclusion tracking.
|
||||
//
|
||||
// It records which peer delivered each accepted transaction (via NotifyAccepted)
|
||||
// and monitors the chain for inclusion and finalization events. When a
|
||||
// delivered transaction is finalized on chain, the delivering peer is
|
||||
// credited. A per-block exponential moving average (EMA) of inclusions
|
||||
// tracks recent peer productivity.
|
||||
//
|
||||
// The primary consumer is the peer dropper (eth/dropper.go), which uses
|
||||
// these stats to protect high-value peers from random disconnection.
|
||||
package txtracker
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// Maximum number of tx→deliverer mappings to retain.
|
||||
maxTracked = 262144
|
||||
// EMA smoothing factor for per-block inclusion rate.
|
||||
emaAlpha = 0.05
|
||||
// EMA smoothing factor for per-block finalization rate. Very slow on
|
||||
// purpose: finalization is permanent, and the score should reflect
|
||||
// sustained contribution over long windows, not recent bursts.
|
||||
// Half-life ≈ 6930 chain heads (~23 hours on 12s blocks).
|
||||
finalizedEMAAlpha = 0.0001
|
||||
)
|
||||
|
||||
// PeerStats holds the per-peer inclusion data.
|
||||
type PeerStats struct {
|
||||
RecentFinalized float64 // EMA of per-block finalization credits (slow)
|
||||
RecentIncluded float64 // EMA of per-block inclusions (fast)
|
||||
}
|
||||
|
||||
// Chain is the blockchain interface needed by the tracker.
|
||||
type Chain interface {
|
||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||
GetBlock(hash common.Hash, number uint64) *types.Block
|
||||
GetCanonicalHash(number uint64) common.Hash
|
||||
CurrentFinalBlock() *types.Header
|
||||
}
|
||||
|
||||
type peerStats struct {
|
||||
recentFinalized float64
|
||||
recentIncluded float64
|
||||
}
|
||||
|
||||
// TxInfo records the per-transaction state the tracker maintains.
|
||||
//
|
||||
// Deliverer is the peer that first handed us this tx via NotifyAccepted.
|
||||
// AddedAt is the unix-seconds wall-clock at that moment; it is compared
|
||||
// against block.Time() to suppress credit for txs delivered at or after
|
||||
// the slot of their inclusion block (re-broadcasts of just-mined txs).
|
||||
//
|
||||
// BlockNum / BlockHash are populated when the tracker first sees the tx
|
||||
// in a head block (BlockNum == 0 means not yet seen on chain). BlockHash
|
||||
// is re-checked against canonical-at-height at finalization time so
|
||||
// reorgs do not yield credit.
|
||||
type TxInfo struct {
|
||||
Deliverer string
|
||||
AddedAt uint64
|
||||
BlockNum uint64
|
||||
BlockHash common.Hash
|
||||
}
|
||||
|
||||
// Tracker records which peer delivered each transaction and credits peers
|
||||
// when their transactions appear on chain.
|
||||
type Tracker struct {
|
||||
mu sync.Mutex
|
||||
txs lru.BasicLRU[common.Hash, *TxInfo] // tx hash -> tx info with lru eviction
|
||||
peers map[string]*peerStats
|
||||
|
||||
chain Chain
|
||||
lastFinalNum uint64 // last finalized block number processed
|
||||
headCh chan core.ChainHeadEvent
|
||||
sub event.Subscription
|
||||
|
||||
quit chan struct{}
|
||||
stopOnce sync.Once
|
||||
step chan struct{} // test sync: sent after each event is processed
|
||||
now func() uint64 // unix-seconds clock; overridable in tests
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New creates a new tracker.
|
||||
func New() *Tracker {
|
||||
return &Tracker{
|
||||
txs: lru.NewBasicLRU[common.Hash, *TxInfo](maxTracked),
|
||||
peers: make(map[string]*peerStats),
|
||||
quit: make(chan struct{}),
|
||||
step: make(chan struct{}, 1),
|
||||
now: func() uint64 { return uint64(time.Now().Unix()) },
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins listening for chain head events.
|
||||
func (t *Tracker) Start(chain Chain) {
|
||||
t.chain = chain
|
||||
// Seed lastFinalNum so checkFinalization doesn't backfill from genesis.
|
||||
if fh := chain.CurrentFinalBlock(); fh != nil {
|
||||
t.lastFinalNum = fh.Number.Uint64()
|
||||
}
|
||||
t.headCh = make(chan core.ChainHeadEvent, 128)
|
||||
t.sub = chain.SubscribeChainHeadEvent(t.headCh)
|
||||
t.wg.Add(1)
|
||||
go t.loop()
|
||||
}
|
||||
|
||||
// NotifyPeerDrop removes a disconnected peer's stats to prevent unbounded
|
||||
// growth. Safe to call from any goroutine.
|
||||
func (t *Tracker) NotifyPeerDrop(peer string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
delete(t.peers, peer)
|
||||
}
|
||||
|
||||
// Stop shuts down the tracker.
|
||||
func (t *Tracker) Stop() {
|
||||
t.stopOnce.Do(func() {
|
||||
if t.sub != nil {
|
||||
t.sub.Unsubscribe()
|
||||
}
|
||||
close(t.quit)
|
||||
})
|
||||
t.wg.Wait()
|
||||
}
|
||||
|
||||
// NotifyAccepted records that a peer delivered transactions that were accepted
|
||||
// by the pool. Only accepted (not rejected/duplicate) txs should be recorded
|
||||
// to prevent attribution poisoning from replayed or invalid txs.
|
||||
// Safe to call from any goroutine.
|
||||
func (t *Tracker) NotifyAccepted(peer string, hashes []common.Hash) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
addedAt := t.now()
|
||||
for _, hash := range hashes {
|
||||
if t.txs.Contains(hash) {
|
||||
continue // already tracked, keep first deliverer
|
||||
}
|
||||
t.txs.Add(hash, &TxInfo{Deliverer: peer, AddedAt: addedAt})
|
||||
}
|
||||
// Ensure the delivering peer has a stats entry.
|
||||
if len(hashes) > 0 && t.peers[peer] == nil {
|
||||
t.peers[peer] = &peerStats{}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllPeerStats returns a snapshot of per-peer inclusion statistics.
|
||||
// Safe to call from any goroutine.
|
||||
func (t *Tracker) GetAllPeerStats() map[string]PeerStats {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
result := make(map[string]PeerStats, len(t.peers))
|
||||
for id, ps := range t.peers {
|
||||
result[id] = PeerStats{
|
||||
RecentFinalized: ps.recentFinalized,
|
||||
RecentIncluded: ps.recentIncluded,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (t *Tracker) loop() {
|
||||
defer t.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-t.headCh:
|
||||
t.handleChainHead(ev)
|
||||
select {
|
||||
case t.step <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case <-t.sub.Err():
|
||||
return
|
||||
case <-t.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tracker) handleChainHead(ev core.ChainHeadEvent) {
|
||||
// Fetch the head block by hash (not just number) to avoid using a
|
||||
// reorged block if the tracker goroutine lags behind the chain.
|
||||
block := t.chain.GetBlock(ev.Header.Hash(), ev.Header.Number.Uint64())
|
||||
if block == nil {
|
||||
return
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
// Count per-peer inclusions in this block for the inclusion EMA, and
|
||||
// record (BlockNum, BlockHash) on first inclusion so the iterate-t.txs
|
||||
// finalization scan can find the entry later without re-reading the
|
||||
// block. Skip txs whose delivery arrived at or after this block's slot
|
||||
// — those are likely post-slot re-broadcasts of an already-mined tx,
|
||||
// not genuine relay work.
|
||||
blockTime := block.Time()
|
||||
blockNum := block.Number().Uint64()
|
||||
blockHash := block.Hash()
|
||||
blockIncl := make(map[string]int)
|
||||
for _, tx := range block.Transactions() {
|
||||
ti, ok := t.txs.Peek(tx.Hash())
|
||||
if !ok || ti.AddedAt >= blockTime {
|
||||
continue
|
||||
}
|
||||
blockIncl[ti.Deliverer]++
|
||||
if ti.BlockNum == 0 {
|
||||
ti.BlockNum = blockNum
|
||||
ti.BlockHash = blockHash
|
||||
}
|
||||
}
|
||||
// Accumulate per-peer finalization credits over the newly-finalized
|
||||
// range (possibly zero blocks). Only counts peers still tracked.
|
||||
blockFinal := t.collectFinalizationCredits()
|
||||
|
||||
// Update both EMAs for all tracked peers (decays inactive ones).
|
||||
// Don't create entries for unknown peers — they may have been
|
||||
// removed by NotifyPeerDrop and should not be resurrected.
|
||||
for peer, ps := range t.peers {
|
||||
ps.recentIncluded = (1-emaAlpha)*ps.recentIncluded + emaAlpha*float64(blockIncl[peer])
|
||||
ps.recentFinalized = (1-finalizedEMAAlpha)*ps.recentFinalized + finalizedEMAAlpha*float64(blockFinal[peer])
|
||||
}
|
||||
}
|
||||
|
||||
// collectFinalizationCredits accumulates per-peer finalization credits for
|
||||
// blocks newly finalized since lastFinalNum, and advances lastFinalNum.
|
||||
// Returns a (possibly empty) credits map keyed by peer ID. Must be called
|
||||
// with t.mu held.
|
||||
//
|
||||
// The pivot here is to iterate t.txs (which we already maintain by hash
|
||||
// with BlockNum + BlockHash recorded at inclusion time) rather than
|
||||
// walking each newly-finalized block from disk. The walk over chain
|
||||
// blocks was the dominant cost during catch-up after a restart: every
|
||||
// block called GetBlockByNumber (cold-disk RLP-decode) and then per-tx
|
||||
// tx.Hash() and types.Sender() against fresh cache-cold *Transaction
|
||||
// instances. By inverting, the only chain query is one cheap canonical-
|
||||
// hash lookup per unique BlockNum that has tracked entries, used to
|
||||
// confirm the recorded BlockHash is still on the canonical chain (and
|
||||
// thus the tx really is finalized). No tx iteration, no hashing, no
|
||||
// sender derivation against cold blocks.
|
||||
func (t *Tracker) collectFinalizationCredits() map[string]int {
|
||||
credits := make(map[string]int)
|
||||
finalHeader := t.chain.CurrentFinalBlock()
|
||||
if finalHeader == nil {
|
||||
return credits
|
||||
}
|
||||
finalNum := finalHeader.Number.Uint64()
|
||||
if finalNum <= t.lastFinalNum {
|
||||
return credits
|
||||
}
|
||||
|
||||
// Group entries by their recorded BlockNum so the canonical-hash
|
||||
// lookup happens once per height, not once per tx. The BlockNum range
|
||||
// check filters both "not yet seen on chain" (BlockNum == 0) and
|
||||
// "already credited in a prior pass" (BlockNum <= lastFinalNum); no
|
||||
// separate status bookkeeping is needed.
|
||||
buckets := make(map[uint64][]*TxInfo)
|
||||
for _, hash := range t.txs.Keys() {
|
||||
ti, ok := t.txs.Peek(hash)
|
||||
if !ok || ti.BlockNum <= t.lastFinalNum || ti.BlockNum > finalNum {
|
||||
continue
|
||||
}
|
||||
buckets[ti.BlockNum] = append(buckets[ti.BlockNum], ti)
|
||||
}
|
||||
|
||||
total := 0
|
||||
for num, tis := range buckets {
|
||||
canonHash := t.chain.GetCanonicalHash(num)
|
||||
if canonHash == (common.Hash{}) {
|
||||
continue
|
||||
}
|
||||
for _, ti := range tis {
|
||||
// BlockHash was recorded when the entry was first seen
|
||||
// on chain. If it doesn't match the canonical hash now,
|
||||
// the entry's recorded inclusion is in an orphaned
|
||||
// block; skip rather than misreport finality.
|
||||
if ti.BlockHash != canonHash {
|
||||
continue
|
||||
}
|
||||
if ti.Deliverer != "" {
|
||||
credits[ti.Deliverer]++
|
||||
total++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total > 0 {
|
||||
log.Trace("Accumulated finalization credits",
|
||||
"from", t.lastFinalNum+1, "to", finalNum, "txs", total)
|
||||
}
|
||||
t.lastFinalNum = finalNum
|
||||
return credits
|
||||
}
|
||||
501
eth/txtracker/tracker_test.go
Normal file
501
eth/txtracker/tracker_test.go
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
// 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
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package txtracker
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// mockChain implements the Chain interface for testing.
|
||||
//
|
||||
// Blocks are stored by hash to exercise the reorg-safe lookup path in
|
||||
// tracker.handleChainHead (which calls GetBlock(hash, number)). A separate
|
||||
// canonicalByNum index maps each height to its canonical block hash, used
|
||||
// by GetCanonicalHash in the finalization-credit path.
|
||||
type mockChain struct {
|
||||
mu sync.Mutex
|
||||
headFeed event.Feed
|
||||
blocksByHash map[common.Hash]*types.Block
|
||||
canonicalByNum map[uint64]common.Hash
|
||||
finalNum uint64
|
||||
}
|
||||
|
||||
func newMockChain() *mockChain {
|
||||
return &mockChain{
|
||||
blocksByHash: make(map[common.Hash]*types.Block),
|
||||
canonicalByNum: make(map[uint64]common.Hash),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
||||
return c.headFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
func (c *mockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.blocksByHash[hash]
|
||||
}
|
||||
|
||||
func (c *mockChain) GetCanonicalHash(number uint64) common.Hash {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.canonicalByNum[number]
|
||||
}
|
||||
|
||||
func (c *mockChain) CurrentFinalBlock() *types.Header {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.finalNum == 0 {
|
||||
return nil
|
||||
}
|
||||
return &types.Header{Number: new(big.Int).SetUint64(c.finalNum)}
|
||||
}
|
||||
|
||||
// addBlock adds a canonical block at the given height. Overwrites any
|
||||
// prior canonical block at that height.
|
||||
func (c *mockChain) addBlock(num uint64, txs []*types.Transaction) *types.Block {
|
||||
return c.addBlockAtHeight(num, num, txs, true)
|
||||
}
|
||||
|
||||
// addBlockAtHeight adds a block at the given height. The salt parameter
|
||||
// ensures distinct block hashes for two blocks at the same height (used
|
||||
// for reorg tests). If canonical is true, the block becomes the canonical
|
||||
// block for that height (looked up by GetCanonicalHash).
|
||||
func (c *mockChain) addBlockAtHeight(num, salt uint64, txs []*types.Transaction, canonical bool) *types.Block {
|
||||
return c.addBlockAtHeightWithTime(num, salt, txs, canonical, uint64(time.Now().Unix()+3600))
|
||||
}
|
||||
|
||||
// addBlockAtHeightWithTime is like addBlockAtHeight but takes an explicit
|
||||
// block time. Used by the pre-slot gate test, which needs a block whose
|
||||
// slot start is BEFORE the moment NotifyAccepted recorded its tx.
|
||||
func (c *mockChain) addBlockAtHeightWithTime(num, salt uint64, txs []*types.Transaction, canonical bool, blockTime uint64) *types.Block {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
// Mix salt into Extra so siblings at the same height get distinct hashes.
|
||||
header := &types.Header{
|
||||
Number: new(big.Int).SetUint64(num),
|
||||
Extra: big.NewInt(int64(salt)).Bytes(),
|
||||
Time: blockTime,
|
||||
}
|
||||
block := types.NewBlock(header, &types.Body{Transactions: txs}, nil, trie.NewListHasher())
|
||||
c.blocksByHash[block.Hash()] = block
|
||||
if canonical {
|
||||
c.canonicalByNum[num] = block.Hash()
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
func (c *mockChain) setFinalBlock(num uint64) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.finalNum = num
|
||||
}
|
||||
|
||||
// sendHead emits a chain head event for the canonical block at the given
|
||||
// height. The emitted header carries the real block's hash so the
|
||||
// tracker's GetBlock(hash, number) lookup resolves correctly.
|
||||
func (c *mockChain) sendHead(num uint64) {
|
||||
c.mu.Lock()
|
||||
hash := c.canonicalByNum[num]
|
||||
block := c.blocksByHash[hash]
|
||||
c.mu.Unlock()
|
||||
if block == nil {
|
||||
panic("sendHead: no canonical block at height")
|
||||
}
|
||||
c.headFeed.Send(core.ChainHeadEvent{Header: block.Header()})
|
||||
}
|
||||
|
||||
// sendHeadBlock emits a chain head event for the given block (may be
|
||||
// non-canonical). Used for reorg tests.
|
||||
func (c *mockChain) sendHeadBlock(block *types.Block) {
|
||||
c.headFeed.Send(core.ChainHeadEvent{Header: block.Header()})
|
||||
}
|
||||
|
||||
func hashTxs(txs []*types.Transaction) []common.Hash {
|
||||
hashes := make([]common.Hash, len(txs))
|
||||
for i, tx := range txs {
|
||||
hashes[i] = tx.Hash()
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
|
||||
func makeTx(nonce uint64) *types.Transaction {
|
||||
return types.NewTx(&types.LegacyTx{Nonce: nonce, GasPrice: big.NewInt(1), Gas: 21000})
|
||||
}
|
||||
|
||||
// waitStep blocks until the tracker has processed one event.
|
||||
func waitStep(t *testing.T, tr *Tracker) {
|
||||
t.Helper()
|
||||
select {
|
||||
case <-tr.step:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for tracker step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyReceived(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
txs := []*types.Transaction{makeTx(1), makeTx(2), makeTx(3)}
|
||||
hashes := hashTxs(txs)
|
||||
tr.NotifyAccepted("peerA", hashes)
|
||||
|
||||
// Public surface: peer entry was created with zero stats before any
|
||||
// chain events. Map lookups would return a zero value for a missing
|
||||
// key, so assert presence explicitly.
|
||||
stats := tr.GetAllPeerStats()
|
||||
if len(stats) != 1 {
|
||||
t.Fatalf("expected 1 peer entry, got %d", len(stats))
|
||||
}
|
||||
ps, ok := stats["peerA"]
|
||||
if !ok {
|
||||
t.Fatal("expected peerA entry, not found")
|
||||
}
|
||||
if ps.RecentFinalized != 0 || ps.RecentIncluded != 0 {
|
||||
t.Fatalf("expected zero stats before chain events, got %+v", ps)
|
||||
}
|
||||
|
||||
// Internal state: all tx→deliverer mappings recorded.
|
||||
tr.mu.Lock()
|
||||
defer tr.mu.Unlock()
|
||||
if tr.txs.Len() != 3 {
|
||||
t.Fatalf("expected 3 tracked txs, got %d", tr.txs.Len())
|
||||
}
|
||||
for i, h := range hashes {
|
||||
got, ok := tr.txs.Peek(h)
|
||||
if !ok {
|
||||
t.Fatalf("tx %d: not tracked", i)
|
||||
}
|
||||
if got.Deliverer != "peerA" {
|
||||
t.Fatalf("tx %d: expected deliverer=peerA, got %q", i, got.Deliverer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInclusionEMA(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
|
||||
// Block 1 includes peerA's tx.
|
||||
chain.addBlock(1, []*types.Transaction{tx})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
stats := tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentIncluded <= 0 {
|
||||
t.Fatalf("expected RecentIncluded > 0 after inclusion, got %f", stats["peerA"].RecentIncluded)
|
||||
}
|
||||
ema1 := stats["peerA"].RecentIncluded
|
||||
|
||||
// Block 2 has no txs from peerA — EMA should decay.
|
||||
chain.addBlock(2, nil)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
stats = tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentIncluded >= ema1 {
|
||||
t.Fatalf("expected EMA to decay, got %f >= %f", stats["peerA"].RecentIncluded, ema1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalization(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
|
||||
// Include in block 1.
|
||||
chain.addBlock(1, []*types.Transaction{tx})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
// Not finalized yet.
|
||||
stats := tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentFinalized != 0 {
|
||||
t.Fatalf("expected RecentFinalized=0 before finalization, got %f", stats["peerA"].RecentFinalized)
|
||||
}
|
||||
|
||||
// Finalize block 1, then send head 2 to trigger the finalization EMA update.
|
||||
chain.setFinalBlock(1)
|
||||
chain.addBlock(2, nil)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
stats = tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentFinalized <= 0 {
|
||||
t.Fatalf("expected RecentFinalized>0 after finalization, got %f", stats["peerA"].RecentFinalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiplePeers(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx1 := makeTx(1)
|
||||
tx2 := makeTx(2)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx1.Hash()})
|
||||
tr.NotifyAccepted("peerB", []common.Hash{tx2.Hash()})
|
||||
|
||||
// Both included in block 1.
|
||||
chain.addBlock(1, []*types.Transaction{tx1, tx2})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
// Finalize.
|
||||
chain.setFinalBlock(1)
|
||||
chain.addBlock(2, nil)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
stats := tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentFinalized <= 0 {
|
||||
t.Fatalf("peerA: expected RecentFinalized>0, got %f", stats["peerA"].RecentFinalized)
|
||||
}
|
||||
if stats["peerB"].RecentFinalized <= 0 {
|
||||
t.Fatalf("peerB: expected RecentFinalized>0, got %f", stats["peerB"].RecentFinalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstDelivererWins(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
tr.NotifyAccepted("peerB", []common.Hash{tx.Hash()}) // duplicate, should be ignored
|
||||
|
||||
chain.addBlock(1, []*types.Transaction{tx})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
chain.setFinalBlock(1)
|
||||
chain.addBlock(2, nil)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
stats := tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentFinalized <= 0 {
|
||||
t.Fatalf("peerA should be credited, got RecentFinalized=%f", stats["peerA"].RecentFinalized)
|
||||
}
|
||||
if stats["peerB"].RecentFinalized != 0 {
|
||||
t.Fatalf("peerB should NOT be credited, got RecentFinalized=%f", stats["peerB"].RecentFinalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoFinalizationCredit(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
|
||||
// Include but don't finalize.
|
||||
chain.addBlock(1, []*types.Transaction{tx})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
// Send more heads without finalization.
|
||||
chain.addBlock(2, nil)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
stats := tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentFinalized != 0 {
|
||||
t.Fatalf("expected RecentFinalized=0 without finalization, got %f", stats["peerA"].RecentFinalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEMADecay(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
|
||||
// Include in block 1.
|
||||
chain.addBlock(1, []*types.Transaction{tx})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
// Send 30 empty blocks — EMA should decay close to zero.
|
||||
for i := uint64(2); i <= 31; i++ {
|
||||
chain.addBlock(i, nil)
|
||||
chain.sendHead(i)
|
||||
waitStep(t, tr)
|
||||
}
|
||||
|
||||
stats := tr.GetAllPeerStats()
|
||||
if stats["peerA"].RecentIncluded > 0.02 {
|
||||
t.Fatalf("expected RecentIncluded near zero after 30 empty blocks, got %f", stats["peerA"].RecentIncluded)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReorgSafety verifies that handleChainHead resolves the head block by
|
||||
// HASH (not just by number), so a head event announcing a sibling block at
|
||||
// the same height does not credit transactions from the canonical block.
|
||||
//
|
||||
// Regression check: handleChainHead uses GetBlock(hash, number) so a head
|
||||
// event announcing sibling B fetches B, not the canonical block A.
|
||||
func TestReorgSafety(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
|
||||
// Two blocks at height 1: canonical A contains tx; sibling B does not.
|
||||
blockA := chain.addBlockAtHeight(1, 1, []*types.Transaction{tx}, true)
|
||||
blockB := chain.addBlockAtHeight(1, 2, nil, false)
|
||||
if blockA.Hash() == blockB.Hash() {
|
||||
t.Fatal("sibling blocks ended up with the same hash")
|
||||
}
|
||||
|
||||
// Head announces sibling B. A hash-aware tracker fetches B, sees no
|
||||
// peerA txs, and leaves the EMA at zero. A number-only tracker would
|
||||
// instead fetch A and credit peerA.
|
||||
chain.sendHeadBlock(blockB)
|
||||
waitStep(t, tr)
|
||||
|
||||
if got := tr.GetAllPeerStats()["peerA"].RecentIncluded; got != 0 {
|
||||
t.Fatalf("expected RecentIncluded=0 after sibling-B head event, got %f (tracker followed the wrong block)", got)
|
||||
}
|
||||
|
||||
// Now announce canonical A; peerA should be credited.
|
||||
chain.sendHeadBlock(blockA)
|
||||
waitStep(t, tr)
|
||||
|
||||
if got := tr.GetAllPeerStats()["peerA"].RecentIncluded; got <= 0 {
|
||||
t.Fatalf("expected RecentIncluded>0 after canonical-A head event, got %f", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecentFinalizedDecays verifies that the finalization EMA decays
|
||||
// for a peer that earned credits in the past but has no new
|
||||
// finalization activity. The decay is slow (α=0.0001), so we
|
||||
// just assert monotonic decrease, not convergence to zero.
|
||||
func TestRecentFinalizedDecays(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
tx := makeTx(1)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{tx.Hash()})
|
||||
|
||||
// Include and finalize in block 1.
|
||||
chain.addBlock(1, []*types.Transaction{tx})
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
chain.setFinalBlock(1)
|
||||
chain.addBlock(2, nil)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
peak := tr.GetAllPeerStats()["peerA"].RecentFinalized
|
||||
if peak <= 0 {
|
||||
t.Fatalf("expected RecentFinalized>0 after finalization, got %f", peak)
|
||||
}
|
||||
|
||||
// Send many empty heads — peer contributes zero each block,
|
||||
// EMA should decay monotonically.
|
||||
for i := uint64(3); i <= 50; i++ {
|
||||
chain.addBlock(i, nil)
|
||||
chain.sendHead(i)
|
||||
waitStep(t, tr)
|
||||
}
|
||||
|
||||
after := tr.GetAllPeerStats()["peerA"].RecentFinalized
|
||||
if after >= peak {
|
||||
t.Fatalf("expected RecentFinalized to decay, got %f >= peak %f", after, peak)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPreSlotGate verifies that a tx delivered at or after the slot of its
|
||||
// inclusion block earns no credit. This blocks the simple
|
||||
// post-block-propagation re-broadcast attribution attack: a peer that
|
||||
// learns a tx from the just-mined block and re-broadcasts it to our pool
|
||||
// should not gain credit when that block is processed. The finalization
|
||||
// path applies the same gate (ti.AddedAt >= blockTime) and is exercised
|
||||
// by the existing TestFinalization with the new clock semantics.
|
||||
func TestPreSlotGate(t *testing.T) {
|
||||
tr := New()
|
||||
chain := newMockChain()
|
||||
tr.Start(chain)
|
||||
defer tr.Stop()
|
||||
|
||||
// Pin the tracker's clock so NotifyAccepted records a known addedAt.
|
||||
const delivery = uint64(1_000_000)
|
||||
tr.now = func() uint64 { return delivery }
|
||||
|
||||
// peerA delivers two txs at the same instant.
|
||||
preTx := makeTx(1)
|
||||
postTx := makeTx(2)
|
||||
tr.NotifyAccepted("peerA", []common.Hash{preTx.Hash(), postTx.Hash()})
|
||||
|
||||
// Block 1: slot strictly AFTER delivery — pre-slot, credit allowed.
|
||||
chain.addBlockAtHeightWithTime(1, 1, []*types.Transaction{preTx}, true, delivery+100)
|
||||
chain.sendHead(1)
|
||||
waitStep(t, tr)
|
||||
|
||||
preEMA := tr.GetAllPeerStats()["peerA"].RecentIncluded
|
||||
if preEMA <= 0 {
|
||||
t.Fatalf("expected RecentIncluded>0 after pre-slot delivery, got %f", preEMA)
|
||||
}
|
||||
|
||||
// Block 2: slot strictly BEFORE delivery — post-slot, must NOT credit.
|
||||
chain.addBlockAtHeightWithTime(2, 2, []*types.Transaction{postTx}, true, delivery-1)
|
||||
chain.sendHead(2)
|
||||
waitStep(t, tr)
|
||||
|
||||
// With the gate, only EMA decay occurs (no contribution this block).
|
||||
// Without the gate, RecentIncluded would have ticked up again.
|
||||
after := tr.GetAllPeerStats()["peerA"].RecentIncluded
|
||||
if after >= preEMA {
|
||||
t.Fatalf("expected EMA to decay (no credit for post-slot tx), got %f >= preEMA %f", after, preEMA)
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -22,7 +22,7 @@ require (
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
|
||||
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
|
||||
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.8
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab
|
||||
github.com/ethereum/hid v1.0.1-0.20260421154323-c2ab8d9bf68a
|
||||
github.com/fatih/color v1.16.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -127,8 +127,8 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA
|
|||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
|
||||
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.8 h1:oQ48q/TMe2SKU8qBE3N7e4/HlG3EpJftom6EsPQgJ58=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.8/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ethereum/hid v1.0.1-0.20260421154323-c2ab8d9bf68a h1:eIFUceK3U/z9UV0D/kAI6cxA27eH7MPqt2ks7fbzj/k=
|
||||
|
|
|
|||
|
|
@ -195,6 +195,12 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend, head
|
|||
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
||||
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||
}
|
||||
// An EIP-7702 set-code transaction cannot be a legacy transaction, so gasPrice
|
||||
// is incompatible with an authorization list. Reject the combination instead of
|
||||
// silently dropping the authorization list in ToTransaction.
|
||||
if args.GasPrice != nil && args.AuthorizationList != nil {
|
||||
return errors.New("both gasPrice and authorizationList specified")
|
||||
}
|
||||
// If the tx has completely specified a fee mechanism, no default is needed.
|
||||
// This allows users who are not yet synced past London to get defaults for
|
||||
// other tx values. See https://github.com/ethereum/go-ethereum/pull/23274
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ func TestSetFeeDefaults(t *testing.T) {
|
|||
fortytwo = (*hexutil.Big)(big.NewInt(42))
|
||||
maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
|
||||
al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
|
||||
authList = []types.SetCodeAuthorization{{Address: common.Address{0xbb}}}
|
||||
)
|
||||
|
||||
tests := []test{
|
||||
|
|
@ -208,6 +209,13 @@ func TestSetFeeDefaults(t *testing.T) {
|
|||
nil,
|
||||
errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"),
|
||||
},
|
||||
{
|
||||
"set gas price and authorization list",
|
||||
"london",
|
||||
&TransactionArgs{GasPrice: fortytwo, AuthorizationList: authList},
|
||||
nil,
|
||||
errors.New("both gasPrice and authorizationList specified"),
|
||||
},
|
||||
// EIP-4844
|
||||
{
|
||||
"set gas price and maxFee for blob transaction",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -76,7 +77,7 @@ func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|||
copy(records[:], h.buf[:])
|
||||
return &bufHandler{
|
||||
buf: records,
|
||||
attrs: append(h.attrs, attrs...),
|
||||
attrs: append(slices.Clone(h.attrs), attrs...),
|
||||
level: h.level,
|
||||
}
|
||||
}
|
||||
|
|
@ -186,7 +187,7 @@ func (h *bufHandler) terminalFormat(r slog.Record) string {
|
|||
return true
|
||||
})
|
||||
|
||||
attrs = append(h.attrs, attrs...)
|
||||
attrs = append(slices.Clone(h.attrs), attrs...)
|
||||
|
||||
fmt.Fprintf(buf, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Message)
|
||||
if length := len(r.Message); length < 40 {
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ var (
|
|||
// Users can specify the maximum number of blobs per block if necessary.
|
||||
func (miner *Miner) maxBlobsPerBlock(time uint64) int {
|
||||
maxBlobs := eip4844.MaxBlobsPerBlock(miner.chainConfig, time)
|
||||
if miner.config.MaxBlobsPerBlock != 0 {
|
||||
maxBlobs = miner.config.MaxBlobsPerBlock
|
||||
if configured := miner.config.MaxBlobsPerBlock; configured != 0 && configured < maxBlobs {
|
||||
maxBlobs = configured
|
||||
}
|
||||
return maxBlobs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ var (
|
|||
OsakaTime: newUint64(1764798551),
|
||||
BPO1Time: newUint64(1765290071),
|
||||
BPO2Time: newUint64(1767747671),
|
||||
BogotaTime: nil,
|
||||
DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"),
|
||||
Ethash: new(EthashConfig),
|
||||
BlobScheduleConfig: &BlobScheduleConfig{
|
||||
|
|
@ -99,6 +100,7 @@ var (
|
|||
OsakaTime: newUint64(1759308480),
|
||||
BPO1Time: newUint64(1759800000),
|
||||
BPO2Time: newUint64(1760389824),
|
||||
BogotaTime: nil,
|
||||
DepositContractAddress: common.HexToAddress("0x4242424242424242424242424242424242424242"),
|
||||
Ethash: new(EthashConfig),
|
||||
BlobScheduleConfig: &BlobScheduleConfig{
|
||||
|
|
@ -134,6 +136,7 @@ var (
|
|||
OsakaTime: newUint64(1760427360),
|
||||
BPO1Time: newUint64(1761017184),
|
||||
BPO2Time: newUint64(1761607008),
|
||||
BogotaTime: nil,
|
||||
DepositContractAddress: common.HexToAddress("0x7f02c3e3c98b133055b8b348b2ac625669ed295d"),
|
||||
Ethash: new(EthashConfig),
|
||||
BlobScheduleConfig: &BlobScheduleConfig{
|
||||
|
|
@ -169,6 +172,7 @@ var (
|
|||
OsakaTime: newUint64(1761677592),
|
||||
BPO1Time: newUint64(1762365720),
|
||||
BPO2Time: newUint64(1762955544),
|
||||
BogotaTime: nil,
|
||||
DepositContractAddress: common.HexToAddress("0x00000000219ab540356cBB839Cbe05303d7705Fa"),
|
||||
Ethash: new(EthashConfig),
|
||||
BlobScheduleConfig: &BlobScheduleConfig{
|
||||
|
|
@ -203,6 +207,7 @@ var (
|
|||
CancunTime: nil,
|
||||
PragueTime: nil,
|
||||
OsakaTime: nil,
|
||||
BogotaTime: nil,
|
||||
UBTTime: nil,
|
||||
Ethash: new(EthashConfig),
|
||||
Clique: nil,
|
||||
|
|
@ -228,6 +233,7 @@ var (
|
|||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
PragueTime: newUint64(0),
|
||||
OsakaTime: newUint64(0),
|
||||
BogotaTime: newUint64(0),
|
||||
BlobScheduleConfig: &BlobScheduleConfig{
|
||||
Cancun: DefaultCancunBlobConfig,
|
||||
Prague: DefaultPragueBlobConfig,
|
||||
|
|
@ -258,6 +264,7 @@ var (
|
|||
CancunTime: nil,
|
||||
PragueTime: nil,
|
||||
OsakaTime: nil,
|
||||
BogotaTime: nil,
|
||||
UBTTime: nil,
|
||||
TerminalTotalDifficulty: big.NewInt(math.MaxInt64),
|
||||
Ethash: nil,
|
||||
|
|
@ -288,6 +295,7 @@ var (
|
|||
CancunTime: nil,
|
||||
PragueTime: nil,
|
||||
OsakaTime: nil,
|
||||
BogotaTime: nil,
|
||||
UBTTime: nil,
|
||||
TerminalTotalDifficulty: big.NewInt(math.MaxInt64),
|
||||
Ethash: new(EthashConfig),
|
||||
|
|
@ -318,6 +326,7 @@ var (
|
|||
CancunTime: newUint64(0),
|
||||
PragueTime: newUint64(0),
|
||||
OsakaTime: newUint64(0),
|
||||
BogotaTime: nil,
|
||||
UBTTime: nil,
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
Ethash: new(EthashConfig),
|
||||
|
|
@ -352,6 +361,7 @@ var (
|
|||
CancunTime: nil,
|
||||
PragueTime: nil,
|
||||
OsakaTime: nil,
|
||||
BogotaTime: nil,
|
||||
UBTTime: nil,
|
||||
TerminalTotalDifficulty: big.NewInt(math.MaxInt64),
|
||||
Ethash: new(EthashConfig),
|
||||
|
|
@ -453,6 +463,7 @@ type ChainConfig struct {
|
|||
BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4)
|
||||
BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5)
|
||||
AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam)
|
||||
BogotaTime *uint64 `json:"bogotaTime,omitempty"` // Bogota switch time (nil = no fork, 0 = already on bogota)
|
||||
UBTTime *uint64 `json:"ubtTime,omitempty"` // UBT switch time (nil = no fork, 0 = already on UBT)
|
||||
|
||||
// TerminalTotalDifficulty is the amount of total difficulty reached by
|
||||
|
|
@ -582,6 +593,9 @@ func (c *ChainConfig) String() string {
|
|||
if c.AmsterdamTime != nil {
|
||||
result += fmt.Sprintf(", AmsterdamTime: %v", *c.AmsterdamTime)
|
||||
}
|
||||
if c.BogotaTime != nil {
|
||||
result += fmt.Sprintf(", BogotaTime: %v", *c.BogotaTime)
|
||||
}
|
||||
if c.UBTTime != nil {
|
||||
result += fmt.Sprintf(", UBTTime: %v", *c.UBTTime)
|
||||
}
|
||||
|
|
@ -677,6 +691,9 @@ func (c *ChainConfig) Description() string {
|
|||
if c.AmsterdamTime != nil {
|
||||
banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime)
|
||||
}
|
||||
if c.BogotaTime != nil {
|
||||
banner += fmt.Sprintf(" - Bogota: @%-10v\n", *c.BogotaTime)
|
||||
}
|
||||
if c.UBTTime != nil {
|
||||
banner += fmt.Sprintf(" - UBT: @%-10v\n", *c.UBTTime)
|
||||
}
|
||||
|
|
@ -854,6 +871,11 @@ func (c *ChainConfig) IsAmsterdam(num *big.Int, time uint64) bool {
|
|||
return c.IsLondon(num) && isTimestampForked(c.AmsterdamTime, time)
|
||||
}
|
||||
|
||||
// IsBogota returns whether time is either equal to the Bogota fork time or greater.
|
||||
func (c *ChainConfig) IsBogota(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.BogotaTime, time)
|
||||
}
|
||||
|
||||
// IsUBT returns whether time is either equal to the Verkle fork time or greater.
|
||||
func (c *ChainConfig) IsUBT(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.UBTTime, time)
|
||||
|
|
@ -940,6 +962,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
|
|||
{name: "bpo4", timestamp: c.BPO4Time, optional: true},
|
||||
{name: "bpo5", timestamp: c.BPO5Time, optional: true},
|
||||
{name: "amsterdam", timestamp: c.AmsterdamTime, optional: true},
|
||||
{name: "bogota", timestamp: c.BogotaTime, optional: true},
|
||||
} {
|
||||
if lastFork.name != "" {
|
||||
switch {
|
||||
|
|
@ -1111,6 +1134,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int,
|
|||
if isForkTimestampIncompatible(c.AmsterdamTime, newcfg.AmsterdamTime, headTimestamp) {
|
||||
return newTimestampCompatError("Amsterdam fork timestamp", c.AmsterdamTime, newcfg.AmsterdamTime)
|
||||
}
|
||||
if isForkTimestampIncompatible(c.BogotaTime, newcfg.BogotaTime, headTimestamp) {
|
||||
return newTimestampCompatError("Bogota fork timestamp", c.BogotaTime, newcfg.BogotaTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1130,6 +1156,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
|
|||
london := c.LondonBlock
|
||||
|
||||
switch {
|
||||
case c.IsBogota(london, time):
|
||||
return forks.Bogota
|
||||
case c.IsAmsterdam(london, time):
|
||||
return forks.Amsterdam
|
||||
case c.IsBPO5(london, time):
|
||||
|
|
@ -1213,6 +1241,10 @@ 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.Bogota:
|
||||
return c.BogotaTime
|
||||
case fork == forks.Amsterdam:
|
||||
return c.AmsterdamTime
|
||||
case fork == forks.BPO5:
|
||||
return c.BPO5Time
|
||||
case fork == forks.BPO4:
|
||||
|
|
@ -1231,8 +1263,6 @@ func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
|
|||
return c.CancunTime
|
||||
case fork == forks.Shanghai:
|
||||
return c.ShanghaiTime
|
||||
case fork == forks.Amsterdam:
|
||||
return c.AmsterdamTime
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1378,7 +1408,7 @@ type Rules struct {
|
|||
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
||||
IsBerlin, IsLondon bool
|
||||
IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool
|
||||
IsAmsterdam, IsUBT bool
|
||||
IsAmsterdam, IsBogota, IsUBT bool
|
||||
}
|
||||
|
||||
// Rules ensures c's ChainID is not nil.
|
||||
|
|
@ -1404,6 +1434,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
IsPrague: isMerge && c.IsPrague(num, timestamp),
|
||||
IsOsaka: isMerge && c.IsOsaka(num, timestamp),
|
||||
IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp),
|
||||
IsBogota: isMerge && c.IsBogota(num, timestamp),
|
||||
IsUBT: isUBT,
|
||||
IsEIP4762: isUBT,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const (
|
|||
BPO4
|
||||
BPO5
|
||||
Amsterdam
|
||||
Bogota
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
|
|
@ -84,4 +85,5 @@ var forkToString = map[Fork]string{
|
|||
BPO4: "BPO4",
|
||||
BPO5: "BPO5",
|
||||
Amsterdam: "Amsterdam",
|
||||
Bogota: "Bogota",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ const (
|
|||
MaxTxGas uint64 = 1 << 24 // Maximum transaction gas limit after eip-7825 (16,777,216).
|
||||
|
||||
MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis.
|
||||
ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction.
|
||||
SloadGas uint64 = 50 //
|
||||
CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero.
|
||||
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
|
||||
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
|
||||
|
|
@ -75,8 +73,7 @@ const (
|
|||
// Which becomes: 5000 - 2100 + 1900 = 4800
|
||||
SstoreClearsScheduleRefundEIP3529 uint64 = SstoreResetGasEIP2200 - ColdSloadCostEIP2929 + TxAccessListStorageKeyGas
|
||||
|
||||
JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
|
||||
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
|
||||
JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
|
||||
|
||||
CreateDataGas uint64 = 200 //
|
||||
CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack.
|
||||
|
|
@ -84,7 +81,6 @@ const (
|
|||
LogGas uint64 = 375 // Per LOG* operation.
|
||||
CopyGas uint64 = 3 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added.
|
||||
StackLimit uint64 = 1024 // Maximum size of VM stack allowed.
|
||||
TierStepGas uint64 = 0 // Once per operation, for a selection of them.
|
||||
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
|
||||
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
|
||||
Create2Gas uint64 = 32000 // Once per CREATE2 operation
|
||||
|
|
@ -101,20 +97,27 @@ const (
|
|||
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
|
||||
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
|
||||
|
||||
RegularPerAuthBaseCost uint64 = 7816 // As defined by EIP-8037 and EIP-8038
|
||||
// RegularPerAuthBaseCost is the state-independent per-authorization floor,
|
||||
// defined in EIP-8037 as the sum of:
|
||||
//
|
||||
// - Calldata cost for the authorization tuple
|
||||
// - ECDSA recovery of the authority address
|
||||
// - Cold authority access (COLD_ACCOUNT_ACCESS)
|
||||
// - Warm writes to the authority account
|
||||
RegularPerAuthBaseCost uint64 = 7816
|
||||
|
||||
// EIP-2780: resource-based intrinsic transaction gas.
|
||||
TxBaseCost2780 uint64 = 12000
|
||||
ColdAccountAccess2780 uint64 = 3000
|
||||
CreateAccess2780 uint64 = 11000
|
||||
TxValueCost2780 uint64 = 4244
|
||||
TransferLogCost2780 uint64 = 1756
|
||||
TxBaseCost2780 uint64 = 12000
|
||||
TxValueCost2780 uint64 = 4244
|
||||
TransferLogCost2780 uint64 = 1756
|
||||
|
||||
// EIP-8038: state-access gas cost update (Amsterdam).
|
||||
ColdAccountAccessAmsterdam uint64 = 3000 // COLD_ACCOUNT_ACCESS: cold touch of an account
|
||||
WarmAccountAccessAmsterdam uint64 = 100 // WARM_ACCESS: warm touch of an account
|
||||
AccountWriteAmsterdam uint64 = 8000 // ACCOUNT_WRITE: surcharge for first-time write to an account
|
||||
CallValueTransferAmsterdam uint64 = 10300 // CALL_VALUE = ACCOUNT_WRITE + CallStipend (2300)
|
||||
ColdStorageAccessAmsterdam uint64 = 3000 // COLD_STORAGE_ACCESS: cold touch of a storage slot
|
||||
WarmStorageAccessAmsterdam uint64 = 100 // WARM_STORAGE_ACCESS: warm touch of a storage slot
|
||||
StorageWriteAmsterdam uint64 = 10000 // STORAGE_WRITE: surcharge for first-time write to a storage slot
|
||||
StorageClearRefundAmsterdam uint64 = 12480 // STORAGE_CLEAR_REFUND: refund for clearing a storage slot
|
||||
CreateAccessAmsterdam uint64 = 11000 // CREATE_ACCESS = ACCOUNT_WRITE + COLD_STORAGE_ACCESS
|
||||
|
|
@ -260,10 +263,10 @@ var (
|
|||
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
|
||||
|
||||
// EIP-8282 - Builder Execution Requests
|
||||
BuilderDepositAddress = common.HexToAddress("0x0000884d2AA32eAa155F59A2f24eFa73D9008282")
|
||||
BuilderDepositCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe146101065760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023457600182026001905f5b5f82111560695781019083028483029004916001019190604e565b90939004925050503660b814608957366102345734610234575f5260205ff35b8034106102345760383567ffffffffffffffff1680633b9aca001161023457633b9aca00029034031061023457600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b600354600254808203806101001161011d57506101005b5f5b8181146101c3578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360200181600301548152602001816004015481526020019060050154905260010161011f565b91018092146101d557906002556101e0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561020d57505f5b6001546020828201116102225750505f610228565b01602090035b5f555f60015560b8025ff35b5f5ffd")
|
||||
BuilderExitAddress = common.HexToAddress("0x000014574A74c805590AFF9499fc7A690f008282")
|
||||
BuilderExitCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd")
|
||||
BuilderDepositAddress = common.HexToAddress("0x0000BFF46984E3725691FA540A8C7589300D8282")
|
||||
BuilderDepositCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1461011c575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146102705760015460088111605257506058565b60089003015b601190600182026001905f5b5f821115607f57810190830284830290049160010191906064565b90939004925050503660b814609f57366102705734610270575f5260205ff35b8034106102705760383567ffffffffffffffff1680633b9aca001161027057633b9aca00029034031061027057600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b60035460025480820380604011610131575060405b5f5b8181146101d7578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c816001015353602001816003015481526020018160040154815260200190600501549052600101610133565b91018092146101e957906002556101f4565b90505f6002555f6003555b36610242575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023057600882820111610238575b50505f610264565b0160089003610264565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f60015560b8025ff35b5f5ffd")
|
||||
BuilderExitAddress = common.HexToAddress("0x000064D678505AD48F8CCB093BC65613800E8282")
|
||||
BuilderExitCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1460e1575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101c65760015460028111605157506057565b60029003015b601190600182026001905f5b5f821115607e57810190830284830290049160010191906063565b909390049250505036603014609e57366101c657346101c6575f5260205ff35b34106101c657600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160f5575060105b5f5b81811461012d5782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160f7565b910180921461013f579060025561014a565b90505f6002555f6003555b36610198575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101865760028282011161018e575b50505f6101ba565b01600290036101ba565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f6001556044025ff35b5f5ffd")
|
||||
|
||||
// EIP-7997 - Deterministic deployment factory (keyless CREATE2 factory)
|
||||
DeterministicFactoryAddress = common.HexToAddress("0x4e59b44847b379578588920cA78FbF26c0B4956C")
|
||||
|
|
|
|||
|
|
@ -88,14 +88,12 @@ func TestExecutionSpecBlocktests(t *testing.T) {
|
|||
bt := new(testMatcher)
|
||||
|
||||
// These tests require us to handle scenarios where a system contract is not deployed at a fork
|
||||
bt.skipLoad(".*prague/eip7251_consolidations/test_system_contract_deployment.json")
|
||||
bt.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/test_system_contract_deployment.json")
|
||||
bt.skipLoad(`.*eip7251_consolidations/contract_deployment/system_contract_deployment\.json`)
|
||||
bt.skipLoad(`.*eip7002_el_triggerable_withdrawals/contract_deployment/system_contract_deployment\.json`)
|
||||
|
||||
// Broken tests
|
||||
bt.skipLoad(`RevertInCreateInInit`)
|
||||
bt.skipLoad(`InitCollisionParis`)
|
||||
bt.skipLoad(`dynamicAccountOverwriteEmpty_Paris`)
|
||||
bt.skipLoad(`create2collisionStorageParis`)
|
||||
bt.skipLoad(`.*eip7610_create_collision/initcollision/.*`)
|
||||
bt.skipLoad(`.*eip7610_create_collision/revert_in_create/.*`)
|
||||
|
||||
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
execBlockTest(t, bt, test)
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func fuzz(input []byte) int {
|
|||
},
|
||||
func(string, []common.Hash) error { return nil },
|
||||
nil,
|
||||
|
||||
nil,
|
||||
blobpool.NewBlobBuffer(blobpool.BlobBufferFunctions{
|
||||
ValidateTx: func(*types.Transaction) error { return nil },
|
||||
AddToPool: func(*blobpool.BlobTxForPool) error { return nil },
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
|
|||
Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"`
|
||||
SlotNumber *math.HexOrDecimal64 `json:"slotNumber" gencodec:"optional"`
|
||||
}
|
||||
var enc stEnv
|
||||
enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
|
||||
|
|
@ -34,6 +35,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
|
|||
enc.Timestamp = math.HexOrDecimal64(s.Timestamp)
|
||||
enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee)
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas)
|
||||
enc.SlotNumber = (*math.HexOrDecimal64)(s.SlotNumber)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
|
|||
Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"`
|
||||
SlotNumber *math.HexOrDecimal64 `json:"slotNumber" gencodec:"optional"`
|
||||
}
|
||||
var dec stEnv
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -81,5 +84,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
|
|||
if dec.ExcessBlobGas != nil {
|
||||
s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.SlotNumber != nil {
|
||||
s.SlotNumber = (*uint64)(dec.SlotNumber)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
|
|||
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
|
||||
MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"`
|
||||
MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Nonce *math.HexOrDecimal256 `json:"nonce"`
|
||||
To string `json:"to"`
|
||||
Data []string `json:"data"`
|
||||
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
||||
|
|
@ -36,7 +36,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
|
|||
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
|
||||
enc.MaxFeePerGas = (*math.HexOrDecimal256)(s.MaxFeePerGas)
|
||||
enc.MaxPriorityFeePerGas = (*math.HexOrDecimal256)(s.MaxPriorityFeePerGas)
|
||||
enc.Nonce = math.HexOrDecimal64(s.Nonce)
|
||||
enc.Nonce = (*math.HexOrDecimal256)(s.Nonce)
|
||||
enc.To = s.To
|
||||
enc.Data = s.Data
|
||||
enc.AccessLists = s.AccessLists
|
||||
|
|
@ -61,7 +61,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
|
|||
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
|
||||
MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"`
|
||||
MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Nonce *math.HexOrDecimal256 `json:"nonce"`
|
||||
To *string `json:"to"`
|
||||
Data []string `json:"data"`
|
||||
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
||||
|
|
@ -87,7 +87,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
|
|||
s.MaxPriorityFeePerGas = (*big.Int)(dec.MaxPriorityFeePerGas)
|
||||
}
|
||||
if dec.Nonce != nil {
|
||||
s.Nonce = uint64(*dec.Nonce)
|
||||
s.Nonce = (*big.Int)(dec.Nonce)
|
||||
}
|
||||
if dec.To != nil {
|
||||
s.To = *dec.To
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
},
|
||||
},
|
||||
"OsakaToBPO1AtTime15k": {
|
||||
|
|
@ -515,7 +515,7 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
},
|
||||
},
|
||||
"BPO2": {
|
||||
|
|
@ -544,8 +544,8 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
},
|
||||
},
|
||||
"BPO1ToBPO2AtTime15k": {
|
||||
|
|
@ -574,8 +574,8 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
},
|
||||
},
|
||||
"BPO3": {
|
||||
|
|
@ -605,8 +605,8 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
BPO3: params.DefaultBPO3BlobConfig,
|
||||
},
|
||||
},
|
||||
|
|
@ -637,8 +637,8 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
BPO3: params.DefaultBPO3BlobConfig,
|
||||
},
|
||||
},
|
||||
|
|
@ -670,8 +670,8 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
BPO3: params.DefaultBPO3BlobConfig,
|
||||
BPO4: params.DefaultBPO4BlobConfig,
|
||||
},
|
||||
|
|
@ -704,8 +704,8 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
BPO3: params.DefaultBPO3BlobConfig,
|
||||
BPO4: params.DefaultBPO4BlobConfig,
|
||||
},
|
||||
|
|
@ -732,17 +732,44 @@ var Forks = map[string]*params.ChainConfig{
|
|||
OsakaTime: u64(0),
|
||||
BPO1Time: u64(0),
|
||||
BPO2Time: u64(0),
|
||||
BPO3Time: u64(0),
|
||||
BPO4Time: u64(0),
|
||||
AmsterdamTime: u64(0),
|
||||
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: bpo1BlobConfig,
|
||||
BPO2: bpo2BlobConfig,
|
||||
BPO3: params.DefaultBPO3BlobConfig,
|
||||
BPO4: params.DefaultBPO4BlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
},
|
||||
},
|
||||
"BPO2ToAmsterdamAtTime15k": {
|
||||
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(15_000),
|
||||
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
BPO1: params.DefaultBPO1BlobConfig,
|
||||
BPO2: params.DefaultBPO2BlobConfig,
|
||||
},
|
||||
},
|
||||
"Verkle": {
|
||||
|
|
@ -793,16 +820,11 @@ var Forks = map[string]*params.ChainConfig{
|
|||
},
|
||||
}
|
||||
|
||||
var bpo1BlobConfig = ¶ms.BlobConfig{
|
||||
Target: 9,
|
||||
Max: 14,
|
||||
UpdateFraction: 8832827,
|
||||
}
|
||||
|
||||
var bpo2BlobConfig = ¶ms.BlobConfig{
|
||||
Target: 14,
|
||||
Max: 21,
|
||||
UpdateFraction: 13739630,
|
||||
func init() {
|
||||
// Execution-spec-tests fixtures use the historical upgrade names for
|
||||
// the EIP150 and EIP158 rulesets.
|
||||
Forks["TangerineWhistle"] = Forks["EIP150"]
|
||||
Forks["SpuriousDragon"] = Forks["EIP158"]
|
||||
}
|
||||
|
||||
// AvailableForks returns the set of defined fork names
|
||||
|
|
|
|||
|
|
@ -97,10 +97,8 @@ func TestExecutionSpecState(t *testing.T) {
|
|||
st := new(testMatcher)
|
||||
|
||||
// Broken tests
|
||||
st.skipLoad(`RevertInCreateInInit`)
|
||||
st.skipLoad(`InitCollisionParis`)
|
||||
st.skipLoad(`dynamicAccountOverwriteEmpty_Paris`)
|
||||
st.skipLoad(`create2collisionStorageParis`)
|
||||
st.skipLoad(`.*eip7610_create_collision/initcollision/.*`)
|
||||
st.skipLoad(`.*eip7610_create_collision/revert_in_create/.*`)
|
||||
|
||||
st.walk(t, executionSpecStateTestDir, func(t *testing.T, name string, test *StateTest) {
|
||||
execStateTest(t, st, test)
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ type stEnv struct {
|
|||
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
|
||||
BaseFee *big.Int `json:"currentBaseFee" gencodec:"optional"`
|
||||
ExcessBlobGas *uint64 `json:"currentExcessBlobGas" gencodec:"optional"`
|
||||
SlotNumber *uint64 `json:"slotNumber" gencodec:"optional"`
|
||||
}
|
||||
|
||||
type stEnvMarshaling struct {
|
||||
|
|
@ -106,6 +107,7 @@ type stEnvMarshaling struct {
|
|||
Timestamp math.HexOrDecimal64
|
||||
BaseFee *math.HexOrDecimal256
|
||||
ExcessBlobGas *math.HexOrDecimal64
|
||||
SlotNumber *math.HexOrDecimal64
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go
|
||||
|
|
@ -114,7 +116,7 @@ type stTransaction struct {
|
|||
GasPrice *big.Int `json:"gasPrice"`
|
||||
MaxFeePerGas *big.Int `json:"maxFeePerGas"`
|
||||
MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Nonce *big.Int `json:"nonce"`
|
||||
To string `json:"to"`
|
||||
Data []string `json:"data"`
|
||||
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
||||
|
|
@ -131,7 +133,7 @@ type stTransactionMarshaling struct {
|
|||
GasPrice *math.HexOrDecimal256
|
||||
MaxFeePerGas *math.HexOrDecimal256
|
||||
MaxPriorityFeePerGas *math.HexOrDecimal256
|
||||
Nonce math.HexOrDecimal64
|
||||
Nonce *math.HexOrDecimal256
|
||||
GasLimit []math.HexOrDecimal64
|
||||
PrivateKey hexutil.Bytes
|
||||
BlobGasFeeCap *math.HexOrDecimal256
|
||||
|
|
@ -378,6 +380,7 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis {
|
|||
GasLimit: t.json.Env.GasLimit,
|
||||
Number: t.json.Env.Number,
|
||||
Timestamp: t.json.Env.Timestamp,
|
||||
SlotNumber: t.json.Env.SlotNumber,
|
||||
Alloc: t.json.Pre,
|
||||
}
|
||||
if t.json.Env.Random != nil {
|
||||
|
|
@ -389,6 +392,16 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis {
|
|||
}
|
||||
|
||||
func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) {
|
||||
// The nonce is parsed as an arbitrary-precision integer so that fixtures
|
||||
// probing the EIP-2681 limit can be loaded; such a transaction can never
|
||||
// be RLP-decoded and must be rejected here.
|
||||
var nonce uint64
|
||||
if tx.Nonce != nil {
|
||||
if !tx.Nonce.IsUint64() {
|
||||
return nil, fmt.Errorf("nonce %v exceeds 2^64-1 (EIP-2681)", tx.Nonce)
|
||||
}
|
||||
nonce = tx.Nonce.Uint64()
|
||||
}
|
||||
var from common.Address
|
||||
// If 'sender' field is present, use that
|
||||
if tx.Sender != nil {
|
||||
|
|
@ -478,7 +491,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
|
|||
msg := &core.Message{
|
||||
From: from,
|
||||
To: to,
|
||||
Nonce: tx.Nonce,
|
||||
Nonce: nonce,
|
||||
Value: uint256.MustFromBig(value),
|
||||
GasLimit: gasLimit,
|
||||
GasPrice: uint256.MustFromBig(gasPrice),
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func TestExecutionSpecTransaction(t *testing.T) {
|
|||
st := new(testMatcher)
|
||||
|
||||
// Emptiness of authorization list is only validated during the tx precheck
|
||||
st.skipLoad("^prague/eip7702_set_code_tx/test_empty_authorization_list.json")
|
||||
st.skipLoad(`eip7702_set_code_tx/invalid_tx/empty_authorization_list\.json`)
|
||||
|
||||
st.walk(t, executionSpecTransactionTestDir, func(t *testing.T, name string, test *TransactionTest) {
|
||||
if err := st.checkFailure(t, test.Run()); err != nil {
|
||||
|
|
|
|||
|
|
@ -86,11 +86,11 @@ func (tt *TransactionTest) Run() error {
|
|||
if overflow {
|
||||
return sender, hash, 0, errors.New("value exceeds 256 bits")
|
||||
}
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules, params.CostPerStateByte)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
requiredGas = cost.RegularGas
|
||||
requiredGas = cost
|
||||
if requiredGas > tx.Gas() {
|
||||
return sender, hash, 0, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas)
|
||||
}
|
||||
|
|
@ -125,6 +125,8 @@ func (tt *TransactionTest) Run() error {
|
|||
{"Shanghai", true},
|
||||
{"Cancun", true},
|
||||
{"Prague", true},
|
||||
{"Osaka", true},
|
||||
{"Amsterdam", true},
|
||||
} {
|
||||
expected := tt.Result[testcase.name]
|
||||
if expected == nil {
|
||||
|
|
|
|||
|
|
@ -543,15 +543,15 @@ func (db *Database) Recoverable(root common.Hash) bool {
|
|||
if db.stateFreezer == nil {
|
||||
return false
|
||||
}
|
||||
// Ensure the requested state is a canonical state and all state
|
||||
// histories in range [id+1, dl.ID] are present and complete.
|
||||
return checkStateHistories(db.stateFreezer, *id+1, dl.stateID()-*id, func(m *meta) error {
|
||||
if m.parent != root {
|
||||
return errors.New("unexpected state history")
|
||||
}
|
||||
root = m.root
|
||||
return nil
|
||||
}) == nil
|
||||
blob := rawdb.ReadStateHistoryMeta(db.stateFreezer, *id+1)
|
||||
if len(blob) == 0 {
|
||||
return false // pruned from the tail or otherwise unavailable
|
||||
}
|
||||
var m meta
|
||||
if err := m.decode(blob); err != nil {
|
||||
return false
|
||||
}
|
||||
return m.parent == root
|
||||
}
|
||||
|
||||
// Close closes the trie database and the held freezer.
|
||||
|
|
|
|||
|
|
@ -612,30 +612,3 @@ func writeStateHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkStateHistories retrieves a batch of metadata objects with the specified
|
||||
// range and performs the callback on each item.
|
||||
func checkStateHistories(reader ethdb.AncientReader, start, count uint64, check func(*meta) error) error {
|
||||
for count > 0 {
|
||||
number := count
|
||||
if number > 10000 {
|
||||
number = 10000 // split the big read into small chunks
|
||||
}
|
||||
blobs, err := rawdb.ReadStateHistoryMetaList(reader, start, number)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, blob := range blobs {
|
||||
var dec meta
|
||||
if err := dec.decode(blob); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := check(&dec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
count -= uint64(len(blobs))
|
||||
start += uint64(len(blobs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue