From 8288f26768c4ea5e89a7fffe15aac892fdb06a3a Mon Sep 17 00:00:00 2001 From: Somnath Date: Tue, 14 Jan 2025 18:20:32 +0400 Subject: [PATCH] Implement calldata cost increase per EIP-7623 (#13080) (#13407) See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7623.md Issue board - https://github.com/erigontech/erigon/issues/12401 Cherry pick https://github.com/erigontech/erigon/pull/13080 --- core/state_transition.go | 19 +++++--- erigon-lib/common/fixedgas/protocol.go | 1 + erigon-lib/txpool/pool.go | 12 +++++- erigon-lib/txpool/pool_test.go | 3 +- erigon-lib/txpool/txpoolcfg/txpoolcfg.go | 43 ++++++++++++------- erigon-lib/txpool/txpoolcfg/txpoolcfg_test.go | 32 ++++++++++++++ tests/transaction_test_util.go | 5 ++- 7 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 erigon-lib/txpool/txpoolcfg/txpoolcfg_test.go diff --git a/core/state_transition.go b/core/state_transition.go index cfeb100c073..b1e8d58eb70 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -104,7 +104,7 @@ type Message interface { // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // TODO: convert the input to a struct -func IntrinsicGas(data []byte, accessList types2.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool, authorizationsLen uint64) (uint64, error) { +func IntrinsicGas(data []byte, accessList types2.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860, isPrague bool, authorizationsLen uint64) (uint64, uint64, error) { // Zero and non-zero bytes are priced differently dataLen := uint64(len(data)) dataNonZeroLen := uint64(0) @@ -114,11 +114,11 @@ func IntrinsicGas(data []byte, accessList types2.AccessList, isContractCreation } } - gas, status := txpoolcfg.CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessList, isContractCreation, isHomestead, isEIP2028, isEIP3860) + gas, floorGas7623, status := txpoolcfg.CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessList, isContractCreation, isHomestead, isEIP2028, isEIP3860, isPrague) if status != txpoolcfg.Success { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, floorGas7623, nil } // NewStateTransition initialises and returns a new state transition object. @@ -417,12 +417,12 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*evmtype } // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(st.data, accessTuples, contractCreation, rules.IsHomestead, rules.IsIstanbul, isEIP3860, uint64(len(auths))) + gas, floorGas7623, err := IntrinsicGas(st.data, accessTuples, contractCreation, rules.IsHomestead, rules.IsIstanbul, isEIP3860, rules.IsPrague, uint64(len(auths))) if err != nil { return nil, err } - if st.gasRemaining < gas { - return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) + if st.gasRemaining < gas || st.gasRemaining < floorGas7623 { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, max(gas, floorGas7623)) } st.gasRemaining -= gas @@ -457,6 +457,11 @@ func (st *StateTransition) TransitionDb(refunds bool, gasBailout bool) (*evmtype } else { ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), st.data, st.gasRemaining, st.value, bailout) } + gasUsed := st.gasUsed() + if gasUsed < floorGas7623 && rules.IsPrague { + gasUsed = floorGas7623 + st.gasRemaining = st.initialGas - gasUsed + } if refunds && !gasBailout { if rules.IsLondon { // After EIP-3529: refunds are capped to gasUsed / 5 diff --git a/erigon-lib/common/fixedgas/protocol.go b/erigon-lib/common/fixedgas/protocol.go index 2518d0432af..d630e501687 100644 --- a/erigon-lib/common/fixedgas/protocol.go +++ b/erigon-lib/common/fixedgas/protocol.go @@ -24,6 +24,7 @@ const ( TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list + TxTotalCostFloorPerToken uint64 = 10 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7623) MaxCodeSize = 24576 // Maximum bytecode to permit for a contract diff --git a/erigon-lib/txpool/pool.go b/erigon-lib/txpool/pool.go index ed1a37d7a51..065915c1624 100644 --- a/erigon-lib/txpool/pool.go +++ b/erigon-lib/txpool/pool.go @@ -727,6 +727,7 @@ func (p *TxPool) best(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableG best := p.pending.best isShanghai := p.isShanghai() || p.isAgra() + isPrague := p.isPrague() txs.Resize(uint(cmp.Min(int(n), len(best.ms)))) var toRemove []*metaTx @@ -774,7 +775,10 @@ func (p *TxPool) best(n uint16, txs *types.TxsRlp, tx kv.Tx, onTopOf, availableG // not an exact science using intrinsic gas but as close as we could hope for at // this stage authorizationLen := uint64(len(mt.Tx.Authorizations)) - intrinsicGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.Tx.DataLen), uint64(mt.Tx.DataNonZeroLen), authorizationLen, nil, mt.Tx.Creation, true, true, isShanghai) + intrinsicGas, floorGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.Tx.DataLen), uint64(mt.Tx.DataNonZeroLen), authorizationLen, nil, mt.Tx.Creation, true, true, isShanghai, isPrague) + if isPrague && floorGas > intrinsicGas { + intrinsicGas = floorGas + } if intrinsicGas > availableGas { // we might find another TX with a low enough intrinsic gas to include so carry on continue @@ -918,7 +922,11 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. } return txpoolcfg.UnderPriced } - gas, reason := txpoolcfg.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), uint64(authorizationLen), nil, txn.Creation, true, true, isShanghai) + gas, floorGas, reason := txpoolcfg.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), uint64(authorizationLen), nil, txn.Creation, true, true, isShanghai, p.isPrague()) + if p.isPrague() && floorGas > gas { + gas = floorGas + } + if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas idHash=%x gas=%d", txn.IDHash, gas)) } diff --git a/erigon-lib/txpool/pool_test.go b/erigon-lib/txpool/pool_test.go index 467f3a1ff3a..70b707b52f3 100644 --- a/erigon-lib/txpool/pool_test.go +++ b/erigon-lib/txpool/pool_test.go @@ -634,7 +634,8 @@ func TestShanghaiIntrinsicGas(t *testing.T) { for name, c := range cases { t.Run(name, func(t *testing.T) { - gas, reason := txpoolcfg.CalcIntrinsicGas(c.dataLen, c.dataNonZeroLen, c.authorizationsLen, nil, c.creation, true, true, c.isShanghai) + // Todo (@somnathb1) - Factor in EIP-7623 + gas, _, reason := txpoolcfg.CalcIntrinsicGas(c.dataLen, c.dataNonZeroLen, c.authorizationsLen, nil, c.creation, true, true, c.isShanghai, false) if reason != txpoolcfg.Success { t.Errorf("expected success but got reason %v", reason) } diff --git a/erigon-lib/txpool/txpoolcfg/txpoolcfg.go b/erigon-lib/txpool/txpoolcfg/txpoolcfg.go index 689f7e26de8..148bc1f29bc 100644 --- a/erigon-lib/txpool/txpoolcfg/txpoolcfg.go +++ b/erigon-lib/txpool/txpoolcfg/txpoolcfg.go @@ -188,13 +188,13 @@ func (r DiscardReason) String() string { // CalcIntrinsicGas computes the 'intrinsic gas' for a message with the given data. // TODO: move input data to a struct -func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen uint64, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isShanghai bool) (uint64, DiscardReason) { +func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen uint64, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isShanghai, isPrague bool) (gas uint64, floorGas7623 uint64, d DiscardReason) { // Set the starting gas for the raw transaction - var gas uint64 if isContractCreation && isHomestead { gas = fixedgas.TxGasContractCreation } else { gas = fixedgas.TxGas + floorGas7623 = fixedgas.TxGas } // Bump the required gas by the amount of transactional data if dataLen > 0 { @@ -208,68 +208,81 @@ func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen uint64, accessL product, overflow := emath.SafeMul(nz, nonZeroGas) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } gas, overflow = emath.SafeAdd(gas, product) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } z := dataLen - nz product, overflow = emath.SafeMul(z, fixedgas.TxDataZeroGas) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } gas, overflow = emath.SafeAdd(gas, product) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } if isContractCreation && isShanghai { numWords := toWordSize(dataLen) product, overflow = emath.SafeMul(numWords, fixedgas.InitCodeWordGas) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } gas, overflow = emath.SafeAdd(gas, product) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow + } + } + + // EIP-7623 + if isPrague { + tokenLen := dataLen + 3*nz + dataGas, overflow := emath.SafeMul(tokenLen, fixedgas.TxTotalCostFloorPerToken) + if overflow { + return 0, 0, GasUintOverflow + } + floorGas7623, overflow = emath.SafeAdd(floorGas7623, dataGas) + if overflow { + return 0, 0, GasUintOverflow } } } if accessList != nil { product, overflow := emath.SafeMul(uint64(len(accessList)), fixedgas.TxAccessListAddressGas) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } gas, overflow = emath.SafeAdd(gas, product) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } product, overflow = emath.SafeMul(uint64(accessList.StorageKeys()), fixedgas.TxAccessListStorageKeyGas) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } gas, overflow = emath.SafeAdd(gas, product) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } } // Add the cost of authorizations product, overflow := emath.SafeMul(authorizationsLen, fixedgas.PerEmptyAccountCost) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } gas, overflow = emath.SafeAdd(gas, product) if overflow { - return 0, GasUintOverflow + return 0, 0, GasUintOverflow } - return gas, Success + return gas, floorGas7623, Success } // toWordSize returns the ceiled word size required for memory expansion. diff --git a/erigon-lib/txpool/txpoolcfg/txpoolcfg_test.go b/erigon-lib/txpool/txpoolcfg/txpoolcfg_test.go new file mode 100644 index 00000000000..c1159b8949e --- /dev/null +++ b/erigon-lib/txpool/txpoolcfg/txpoolcfg_test.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + +package txpoolcfg + +import ( + "testing" + + "github.com/erigontech/erigon-lib/common/fixedgas" + "github.com/stretchr/testify/assert" +) + +func TestZeroDataIntrinsicGas(t *testing.T) { + assert := assert.New(t) + gas, floorGas7623, discardReason := CalcIntrinsicGas(0, 0, 0, nil, false, true, true, true, true) + assert.Equal(discardReason, Success) + assert.Equal(gas, fixedgas.TxGas) + assert.Equal(floorGas7623, fixedgas.TxGas) +} diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index ae96bbdf430..a1aa1156a42 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -75,7 +75,10 @@ func (tt *TransactionTest) Run(chainID *big.Int) error { if stx, ok := tx.(*types.SetCodeTransaction); ok { authorizationsLen = uint64(len(stx.GetAuthorizations())) } - requiredGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), msg.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, authorizationsLen) + requiredGas, floorGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), msg.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsPrague, authorizationsLen) + if rules.IsPrague && floorGas > requiredGas { + requiredGas = floorGas + } if err != nil { return nil, nil, 0, err }