From 20e61f11db97fb4561a78236e084fb3efaf3cc86 Mon Sep 17 00:00:00 2001 From: Somnath Date: Sat, 11 Jan 2025 14:08:39 +0400 Subject: [PATCH] Implement calldata cost increase per EIP-7623 (#13080) See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7623.md Issue board - https://github.com/erigontech/erigon/issues/12401 --- core/state_transition.go | 19 +++++--- erigon-lib/common/fixedgas/protocol.go | 1 + tests/transaction_test_util.go | 5 ++- txnprovider/txpool/pool.go | 12 +++++- txnprovider/txpool/pool_test.go | 3 +- txnprovider/txpool/txpoolcfg/txpoolcfg.go | 43 ++++++++++++------- .../txpool/txpoolcfg/txpoolcfg_test.go | 32 ++++++++++++++ 7 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 txnprovider/txpool/txpoolcfg/txpoolcfg_test.go diff --git a/core/state_transition.go b/core/state_transition.go index 52141f37dbe..0af8c519fab 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -108,7 +108,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 types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool, authorizationsLen uint64) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.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) @@ -118,11 +118,11 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b } } - 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. @@ -460,12 +460,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 @@ -505,6 +505,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 cc73737a733..c002d070cf3 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/tests/transaction_test_util.go b/tests/transaction_test_util.go index a78135aedce..e83b04382ff 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -78,7 +78,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 } diff --git a/txnprovider/txpool/pool.go b/txnprovider/txpool/pool.go index 04126223a0c..d79d8ccabf3 100644 --- a/txnprovider/txpool/pool.go +++ b/txnprovider/txpool/pool.go @@ -671,6 +671,7 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf, availa best := p.pending.best isShanghai := p.isShanghai() || p.isAgra() + isPrague := p.isPrague() txns.Resize(uint(min(n, len(best.ms)))) var toRemove []*metaTxn @@ -724,7 +725,10 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf, availa // not an exact science using intrinsic gas but as close as we could hope for at // this stage authorizationLen := uint64(len(mt.TxnSlot.Authorizations)) - intrinsicGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.TxnSlot.DataLen), uint64(mt.TxnSlot.DataNonZeroLen), authorizationLen, nil, mt.TxnSlot.Creation, true, true, isShanghai) + intrinsicGas, floorGas, _ := txpoolcfg.CalcIntrinsicGas(uint64(mt.TxnSlot.DataLen), uint64(mt.TxnSlot.DataNonZeroLen), authorizationLen, nil, mt.TxnSlot.Creation, true, true, isShanghai, isPrague) + if isPrague && floorGas > intrinsicGas { + intrinsicGas = floorGas + } if intrinsicGas > availableGas { // we might find another txn with a low enough intrinsic gas to include so carry on continue @@ -902,7 +906,11 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache 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/txnprovider/txpool/pool_test.go b/txnprovider/txpool/pool_test.go index 4c1cc236847..11fcc6a4306 100644 --- a/txnprovider/txpool/pool_test.go +++ b/txnprovider/txpool/pool_test.go @@ -631,7 +631,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/txnprovider/txpool/txpoolcfg/txpoolcfg.go b/txnprovider/txpool/txpoolcfg/txpoolcfg.go index 876f4a88d2d..24321d5e507 100644 --- a/txnprovider/txpool/txpoolcfg/txpoolcfg.go +++ b/txnprovider/txpool/txpoolcfg/txpoolcfg.go @@ -192,13 +192,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 { @@ -212,68 +212,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/txnprovider/txpool/txpoolcfg/txpoolcfg_test.go b/txnprovider/txpool/txpoolcfg/txpoolcfg_test.go new file mode 100644 index 00000000000..c1159b8949e --- /dev/null +++ b/txnprovider/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) +}