Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-4488 Prototype Implementation #23983

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/evm/internal/t8ntool/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int))); err != nil {
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)),
chainConfig.IsCalldataFork(new(big.Int))); err != nil {
r.Error = err
results = append(results, r)
continue
Expand Down
2 changes: 1 addition & 1 deletion core/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false)
gas, _ := IntrinsicGas(data, nil, false, false, false, false)
signer := types.MakeSigner(gen.config, big.NewInt(int64(i)))
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
Expand Down
20 changes: 20 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
}
return consensus.ErrPrunedAncestor
}
// if EIP-XXXX, check if calldata size is below cap
if v.config.IsCalldataFork(block.Number()) {
if err := VerifyCalldataCap(block); err != nil {
return err
}
}
return nil
}

Expand Down Expand Up @@ -127,3 +133,17 @@ func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 {
}
return limit
}

// VerifyCalldataCap calculates the total calldata size of the block's transactions and
// verifies that it is below the cap introduced by EIP-XXXX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this code using EIP4448 instead of EIPXXXX for variable names and comments?

func VerifyCalldataCap(block *types.Block) error {
calldataCap := params.TxDataBaseBlockCap + uint64(len(block.Transactions()))*params.TxDataCapStipend
for _, tx := range block.Transactions() {
l := uint64(len(tx.Data()))
if calldataCap < l {
return ErrTxDataAboveCap
}
calldataCap -= l
}
return nil
}
2 changes: 2 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ var (
ErrNoGenesis = errors.New("genesis not found in chain")

errSideChainReceipts = errors.New("side blocks can't be accepted as ancient chain data")

ErrTxDataAboveCap = errors.New("block transactions calldata above calldata cap")
)

// List of evm-call-message pre-checking errors. All state transition messages will
Expand Down
51 changes: 30 additions & 21 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (result *ExecutionResult) Revert() []byte {
}

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool, isEIPXXXX bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
Expand All @@ -125,28 +125,36 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
}
// Bump the required gas by the amount of transactional data
if len(data) > 0 {
// Zero and non-zero bytes are priced differently
var nz uint64
for _, byt := range data {
if byt != 0 {
nz++
if isEIPXXXX {
l := uint64(len(data))
if (math.MaxUint64-gas)/params.TxDataGasEIPXXXX < l {
return 0, ErrGasUintOverflow
}
}
// Make sure we don't exceed uint64 for all data combinations
nonZeroGas := params.TxDataNonZeroGasFrontier
if isEIP2028 {
nonZeroGas = params.TxDataNonZeroGasEIP2028
}
if (math.MaxUint64-gas)/nonZeroGas < nz {
return 0, ErrGasUintOverflow
}
gas += nz * nonZeroGas
gas += l * params.TxDataGasEIPXXXX
} else {
// Zero and non-zero bytes are priced differently
var nz uint64
for _, byt := range data {
if byt != 0 {
nz++
}
}
// Make sure we don't exceed uint64 for all data combinations
nonZeroGas := params.TxDataNonZeroGasFrontier
if isEIP2028 {
nonZeroGas = params.TxDataNonZeroGasEIP2028
}
if (math.MaxUint64-gas)/nonZeroGas < nz {
return 0, ErrGasUintOverflow
}
gas += nz * nonZeroGas

z := uint64(len(data)) - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow
z := uint64(len(data)) - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow
}
gas += z * params.TxDataZeroGas
}
gas += z * params.TxDataZeroGas
}
if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
Expand Down Expand Up @@ -292,10 +300,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber)
london := st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber)
calldataFork := st.evm.ChainConfig().IsCalldataFork(st.evm.Context.BlockNumber)
contractCreation := msg.To() == nil

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul)
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul, calldataFork)
if err != nil {
return nil, err
}
Expand Down
10 changes: 6 additions & 4 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,10 @@ type TxPool struct {
signer types.Signer
mu sync.RWMutex

istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
istanbul bool // Fork indicator whether we are in the istanbul stage.
calldataFork bool // Fork indicator whether we are in the calldata fork stage.
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.

currentState *state.StateDB // Current state in the blockchain head
pendingNonces *txNoncer // Pending state tracking virtual nonces
Expand Down Expand Up @@ -636,7 +637,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
return ErrInsufficientFunds
}
// Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.calldataFork)
if err != nil {
return err
}
Expand Down Expand Up @@ -1303,6 +1304,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
// Update all fork indicator by next pending block number.
next := new(big.Int).Add(newHead.Number, big.NewInt(1))
pool.istanbul = pool.chainconfig.IsIstanbul(next)
pool.calldataFork = pool.chainconfig.IsCalldataFork(next)
pool.eip2718 = pool.chainconfig.IsBerlin(next)
pool.eip1559 = pool.chainconfig.IsLondon(next)
}
Expand Down
3 changes: 2 additions & 1 deletion eth/tracers/js/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,8 @@ func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
// Compute intrinsic gas
isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
isCalldataFork := env.ChainConfig().IsCalldataFork(env.Context.BlockNumber)
intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, isCalldataFork)
if err != nil {
return
}
Expand Down
8 changes: 5 additions & 3 deletions light/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ type TxPool struct {
mined map[common.Hash][]*types.Transaction // mined transactions by block hash
clearIdx uint64 // earliest block nr that can contain mined tx info

istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are in the eip2718 stage.
istanbul bool // Fork indicator whether we are in the istanbul stage.
calldataFork bool // Fork indicator whether we are in the calldata fork stage.
eip2718 bool // Fork indicator whether we are in the eip2718 stage.
}

// TxRelayBackend provides an interface to the mechanism that forwards transacions
Expand Down Expand Up @@ -314,6 +315,7 @@ func (pool *TxPool) setNewHead(head *types.Header) {
// Update fork indicator by next pending block number
next := new(big.Int).Add(head.Number, big.NewInt(1))
pool.istanbul = pool.config.IsIstanbul(next)
pool.calldataFork = pool.config.IsCalldataFork(next)
pool.eip2718 = pool.config.IsBerlin(next)
}

Expand Down Expand Up @@ -382,7 +384,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
}

// Should supply enough intrinsic gas
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.calldataFork)
if err != nil {
return err
}
Expand Down
17 changes: 17 additions & 0 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,11 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin

var coalescedLogs []*types.Log

var (
isCalldataFork = w.chainConfig.IsCalldataFork(w.current.header.Number)
remainingTxDataSize = params.TxDataBaseBlockCap + params.TxDataCapStipend
)

for {
// In the following three cases, we will interrupt the execution of the transaction.
// (1) new head block event arrival, the interrupt signal is 1
Expand Down Expand Up @@ -839,6 +844,14 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
txs.Pop()
continue
}

if isCalldataFork && remainingTxDataSize < uint64(len(tx.Data())) {
// Skip account if transaction calldata would exceed cap
log.Trace("Skipping account with calldata exceeding the cap", "sender", from)
txs.Pop()
continue
}

// Start executing the transaction
w.current.state.Prepare(tx.Hash(), w.current.tcount)

Expand All @@ -865,6 +878,10 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
w.current.tcount++
txs.Shift()

if isCalldataFork {
remainingTxDataSize = remainingTxDataSize - uint64(len(tx.Data())) + params.TxDataCapStipend
}

case errors.Is(err, core.ErrTxTypeNotSupported):
// Pop the unsupported transaction without shifting in the next from the account
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
Expand Down
19 changes: 15 additions & 4 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,16 +257,16 @@ var (
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}

// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}

TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int))
)

Expand Down Expand Up @@ -346,6 +346,7 @@ type ChainConfig struct {
BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin)
LondonBlock *big.Int `json:"londonBlock,omitempty"` // London switch block (nil = no fork, 0 = already on london)
ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated)
CalldataForkBlock *big.Int `json:"calldataForkBlock,omitempty"` // Eip-XXXX (calldata price decrease) switch block (nil = no fork, 0 = already activated)

// TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade.
Expand Down Expand Up @@ -386,7 +387,7 @@ func (c *ChainConfig) String() string {
default:
engine = "unknown"
}
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, Engine: %v}",
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, Calldata: %v, Engine: %v}",
c.ChainID,
c.HomesteadBlock,
c.DAOForkBlock,
Expand All @@ -402,6 +403,7 @@ func (c *ChainConfig) String() string {
c.BerlinBlock,
c.LondonBlock,
c.ArrowGlacierBlock,
c.CalldataForkBlock,
engine,
)
}
Expand Down Expand Up @@ -473,6 +475,11 @@ func (c *ChainConfig) IsArrowGlacier(num *big.Int) bool {
return isForked(c.ArrowGlacierBlock, num)
}

// IsCalldataFork returns whether num is either equal to the Calldata (EIP-XXXX) fork block or greater.
func (c *ChainConfig) IsCalldataFork(num *big.Int) bool {
return isForked(c.CalldataForkBlock, num)
}

// IsTerminalPoWBlock returns whether the given block is the last block of PoW stage.
func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool {
if c.TerminalTotalDifficulty == nil {
Expand Down Expand Up @@ -522,6 +529,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{name: "berlinBlock", block: c.BerlinBlock},
{name: "londonBlock", block: c.LondonBlock},
{name: "arrowGlacierBlock", block: c.ArrowGlacierBlock, optional: true},
{name: "calldataForkBlock", block: c.CalldataForkBlock},
} {
if lastFork.name != "" {
// Next one must be higher number
Expand Down Expand Up @@ -594,6 +602,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
if isForkIncompatible(c.ArrowGlacierBlock, newcfg.ArrowGlacierBlock, head) {
return newCompatError("Arrow Glacier fork block", c.ArrowGlacierBlock, newcfg.ArrowGlacierBlock)
}
if isForkIncompatible(c.CalldataForkBlock, newcfg.CalldataForkBlock, head) {
return newCompatError("Calldata fork block", c.CalldataForkBlock, newcfg.CalldataForkBlock)
}
return nil
}

Expand Down
4 changes: 4 additions & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const (

TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions.
TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)
TxDataGasEIPXXXX uint64 = 3 // Per byte of data attached to a transaction after EIP XXXX
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list

Expand Down Expand Up @@ -156,6 +157,9 @@ const (
// up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529
RefundQuotient uint64 = 2
RefundQuotientEIP3529 uint64 = 5

TxDataBaseBlockCap uint64 = 1048576 // Base block cap for transaction calldata in bytes
TxDataCapStipend uint64 = 300 // Per-tx cap stpiend for transaction calldata in bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TxDataCapStipend uint64 = 300 // Per-tx cap stpiend for transaction calldata in bytes
TxDataCapStipend uint64 = 300 // Per-tx cap stipend for transaction calldata in bytes

)

// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
Expand Down
2 changes: 1 addition & 1 deletion tests/transaction_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error {
return nil, nil, err
}
// Intrinsic gas
requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul)
requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false)
if err != nil {
return nil, nil, err
}
Expand Down