Skip to content

Commit 0e1a19d

Browse files
s1naMariusVanDerWijdenlightclient
authored
core: implement eip-7623 floor data gas (#30946)
This PR builds on #29040 and updates it to the new version of the spec. I filled the EEST tests and they pass. Link to spec: https://eips.ethereum.org/EIPS/eip-7623 --------- Co-authored-by: Marius van der Wijden <[email protected]> Co-authored-by: lightclient <[email protected]> Co-authored-by: lightclient <[email protected]>
1 parent 9b9e7cc commit 0e1a19d

14 files changed

+125
-56
lines changed

cmd/evm/internal/t8ntool/transaction.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,29 @@ func Transaction(ctx *cli.Context) error {
133133
r.Address = sender
134134
}
135135
// Check intrinsic gas
136-
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil,
137-
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil {
136+
rules := chainConfig.Rules(common.Big0, true, 0)
137+
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
138+
if err != nil {
138139
r.Error = err
139140
results = append(results, r)
140141
continue
141-
} else {
142-
r.IntrinsicGas = gas
143-
if tx.Gas() < gas {
144-
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
142+
}
143+
r.IntrinsicGas = gas
144+
if tx.Gas() < gas {
145+
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas)
146+
results = append(results, r)
147+
continue
148+
}
149+
// For Prague txs, validate the floor data gas.
150+
if rules.IsPrague {
151+
floorDataGas, err := core.FloorDataGas(tx.Data())
152+
if err != nil {
153+
r.Error = err
154+
results = append(results, r)
155+
continue
156+
}
157+
if tx.Gas() < floorDataGas {
158+
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
145159
results = append(results, r)
146160
continue
147161
}

core/error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ var (
8080
// than required to start the invocation.
8181
ErrIntrinsicGas = errors.New("intrinsic gas too low")
8282

83+
// ErrFloorDataGas is returned if the transaction is specified to use less gas
84+
// than required for the data floor cost.
85+
ErrFloorDataGas = errors.New("insufficient gas for floor data gas cost")
86+
8387
// ErrTxTypeNotSupported is returned if a transaction is not supported in the
8488
// current network configuration.
8589
ErrTxTypeNotSupported = types.ErrTxTypeNotSupported

core/state_processor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ func TestStateProcessorErrors(t *testing.T) {
251251
},
252252
{ // ErrMaxInitCodeSizeExceeded
253253
txs: []*types.Transaction{
254-
mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
254+
mkDynamicCreationTx(0, 520000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]),
255255
},
256-
want: "could not apply tx 0 [0xd491405f06c92d118dd3208376fcee18a57c54bc52063ee4a26b1cf296857c25]: max initcode size exceeded: code size 49153 limit 49152",
256+
want: "could not apply tx 0 [0x3a30404d42d6ccc843d7c391fd0c87b9b9795a0c174261b46d2ac95ca17b81cd]: max initcode size exceeded: code size 49153 limit 49152",
257257
},
258258
{ // ErrIntrinsicGas: Not enough gas to cover init code
259259
txs: []*types.Transaction{

core/state_transition.go

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package core
1818

1919
import (
20+
"bytes"
2021
"fmt"
2122
"math"
2223
"math/big"
@@ -79,12 +80,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
7980
// Bump the required gas by the amount of transactional data
8081
if dataLen > 0 {
8182
// Zero and non-zero bytes are priced differently
82-
var nz uint64
83-
for _, byt := range data {
84-
if byt != 0 {
85-
nz++
86-
}
87-
}
83+
z := uint64(bytes.Count(data, []byte{0}))
84+
nz := dataLen - z
85+
8886
// Make sure we don't exceed uint64 for all data combinations
8987
nonZeroGas := params.TxDataNonZeroGasFrontier
9088
if isEIP2028 {
@@ -95,7 +93,6 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
9593
}
9694
gas += nz * nonZeroGas
9795

98-
z := dataLen - nz
9996
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
10097
return 0, ErrGasUintOverflow
10198
}
@@ -119,6 +116,21 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
119116
return gas, nil
120117
}
121118

119+
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
120+
func FloorDataGas(data []byte) (uint64, error) {
121+
var (
122+
z = uint64(bytes.Count(data, []byte{0}))
123+
nz = uint64(len(data)) - z
124+
tokens = nz*params.TxTokenPerNonZeroByte + z
125+
)
126+
// Check for overflow
127+
if (math.MaxUint64-params.TxGas)/params.TxCostFloorPerToken < tokens {
128+
return 0, ErrGasUintOverflow
129+
}
130+
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
131+
return params.TxGas + tokens*params.TxCostFloorPerToken, nil
132+
}
133+
122134
// toWordSize returns the ceiled word size required for init code payment calculation.
123135
func toWordSize(size uint64) uint64 {
124136
if size > math.MaxUint64-31 {
@@ -414,6 +426,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
414426
sender = vm.AccountRef(msg.From)
415427
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
416428
contractCreation = msg.To == nil
429+
floorDataGas uint64
417430
)
418431

419432
// Check clauses 4-5, subtract intrinsic gas if everything is correct
@@ -424,6 +437,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
424437
if st.gasRemaining < gas {
425438
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
426439
}
440+
// Gas limit suffices for the floor data cost (EIP-7623)
441+
if rules.IsPrague {
442+
floorDataGas, err = FloorDataGas(msg.Data)
443+
if err != nil {
444+
return nil, err
445+
}
446+
if msg.GasLimit < floorDataGas {
447+
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
448+
}
449+
}
427450
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
428451
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
429452
}
@@ -487,14 +510,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
487510
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
488511
}
489512

490-
var gasRefund uint64
491-
if !rules.IsLondon {
492-
// Before EIP-3529: refunds were capped to gasUsed / 2
493-
gasRefund = st.refundGas(params.RefundQuotient)
494-
} else {
495-
// After EIP-3529: refunds are capped to gasUsed / 5
496-
gasRefund = st.refundGas(params.RefundQuotientEIP3529)
513+
// Compute refund counter, capped to a refund quotient.
514+
gasRefund := st.calcRefund()
515+
st.gasRemaining += gasRefund
516+
if rules.IsPrague {
517+
// After EIP-7623: Data-heavy transactions pay the floor gas.
518+
if st.gasUsed() < floorDataGas {
519+
prev := st.gasRemaining
520+
st.gasRemaining = st.initialGas - floorDataGas
521+
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
522+
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
523+
}
524+
}
497525
}
526+
st.returnGas()
527+
498528
effectiveTip := msg.GasPrice
499529
if rules.IsLondon {
500530
effectiveTip = new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)
@@ -585,20 +615,28 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
585615
return nil
586616
}
587617

588-
func (st *stateTransition) refundGas(refundQuotient uint64) uint64 {
589-
// Apply refund counter, capped to a refund quotient
590-
refund := st.gasUsed() / refundQuotient
618+
// calcRefund computes refund counter, capped to a refund quotient.
619+
func (st *stateTransition) calcRefund() uint64 {
620+
var refund uint64
621+
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
622+
// Before EIP-3529: refunds were capped to gasUsed / 2
623+
refund = st.gasUsed() / params.RefundQuotient
624+
} else {
625+
// After EIP-3529: refunds are capped to gasUsed / 5
626+
refund = st.gasUsed() / params.RefundQuotientEIP3529
627+
}
591628
if refund > st.state.GetRefund() {
592629
refund = st.state.GetRefund()
593630
}
594-
595631
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
596632
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
597633
}
634+
return refund
635+
}
598636

599-
st.gasRemaining += refund
600-
601-
// Return ETH for remaining gas, exchanged at the original rate.
637+
// returnGas returns ETH for remaining gas,
638+
// exchanged at the original rate.
639+
func (st *stateTransition) returnGas() {
602640
remaining := uint256.NewInt(st.gasRemaining)
603641
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
604642
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
@@ -610,8 +648,6 @@ func (st *stateTransition) refundGas(refundQuotient uint64) uint64 {
610648
// Also return remaining gas to the block gas counter so it is
611649
// available for the next transaction.
612650
st.gp.AddGas(st.gasRemaining)
613-
614-
return refund
615651
}
616652

617653
// gasUsed returns the amount of gas used up by the state transition.

core/tracing/hooks.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ const (
313313
GasChangeWitnessCodeChunk GasChangeReason = 17
314314
// GasChangeWitnessContractCollisionCheck flags the event of adding to the witness when checking for contract address collision.
315315
GasChangeWitnessContractCollisionCheck GasChangeReason = 18
316+
// GasChangeTxDataFloor is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the
317+
// transaction data. This change will always be a negative change.
318+
GasChangeTxDataFloor GasChangeReason = 19
316319

317320
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
318321
// it will be "manually" tracked by a direct emit of the gas change event.

core/txpool/validation.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
115115
if tx.Gas() < intrGas {
116116
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
117117
}
118+
// Ensure the transaction can cover floor data gas.
119+
if opts.Config.IsPrague(head.Number, head.Time) {
120+
floorDataGas, err := core.FloorDataGas(tx.Data())
121+
if err != nil {
122+
return err
123+
}
124+
if tx.Gas() < floorDataGas {
125+
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
126+
}
127+
}
118128
// Ensure the gasprice is high enough to cover the requirement of the calling pool
119129
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
120130
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip)

core/verkle_witness_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func TestProcessVerkle(t *testing.T) {
147147
params.WitnessChunkWriteCost + /* SSTORE in constructor */
148148
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash for tx creation */
149149
15*(params.WitnessChunkReadCost+params.WitnessChunkWriteCost) + /* code chunks #0..#14 */
150-
4844 /* execution costs */
150+
uint64(4844) /* execution costs */
151151
blockGasUsagesExpected := []uint64{
152152
txCost1*2 + txCost2,
153153
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,

internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
{
33
"blobGasPrice": "0x1",
44
"blobGasUsed": "0x20000",
5-
"blockHash": "0x17124e31fb075a301b1d7d4135683b0a09fe4e6d453c54e2e734d5ee00744a49",
5+
"blockHash": "0x5f58514bcb3b216908f0aff6ced44666c3aa250df06093150ac850a7a7850f3c",
66
"blockNumber": "0x6",
77
"contractAddress": null,
88
"cumulativeGasUsed": "0x5208",
9-
"effectiveGasPrice": "0x1b09d63b",
9+
"effectiveGasPrice": "0x1b0a08c4",
1010
"from": "0x703c4b2bd70c169f5717101caee543299fc946c7",
1111
"gasUsed": "0x5208",
1212
"logs": [],
1313
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1414
"status": "0x1",
1515
"to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e",
16-
"transactionHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f",
16+
"transactionHash": "0x80348f994fb5f3b05bd2e5f58bbdc73485e449c028612a2c0680f9ac6ff70add",
1717
"transactionIndex": "0x0",
1818
"type": "0x3"
1919
}
20-
]
20+
]
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[
22
{
3-
"blockHash": "0x102e50de30318ee99a03a09db74387e79cad3165bf6840cc84249806a2a302f3",
3+
"blockHash": "0x47cd44027bb55856a175e36be0396bad221e52172529d9c1bf12bf5424a041ae",
44
"blockNumber": "0x4",
55
"contractAddress": null,
6-
"cumulativeGasUsed": "0x538d",
6+
"cumulativeGasUsed": "0x5564",
77
"effectiveGasPrice": "0x2325c42f",
88
"from": "0x703c4b2bd70c169f5717101caee543299fc946c7",
9-
"gasUsed": "0x538d",
9+
"gasUsed": "0x5564",
1010
"logs": [],
1111
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1212
"status": "0x0",
@@ -15,4 +15,4 @@
1515
"transactionIndex": "0x0",
1616
"type": "0x2"
1717
}
18-
]
18+
]

internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
{
33
"blobGasPrice": "0x1",
44
"blobGasUsed": "0x20000",
5-
"blockHash": "0x17124e31fb075a301b1d7d4135683b0a09fe4e6d453c54e2e734d5ee00744a49",
5+
"blockHash": "0x5f58514bcb3b216908f0aff6ced44666c3aa250df06093150ac850a7a7850f3c",
66
"blockNumber": "0x6",
77
"contractAddress": null,
88
"cumulativeGasUsed": "0x5208",
9-
"effectiveGasPrice": "0x1b09d63b",
9+
"effectiveGasPrice": "0x1b0a08c4",
1010
"from": "0x703c4b2bd70c169f5717101caee543299fc946c7",
1111
"gasUsed": "0x5208",
1212
"logs": [],
1313
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1414
"status": "0x1",
1515
"to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e",
16-
"transactionHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f",
16+
"transactionHash": "0x80348f994fb5f3b05bd2e5f58bbdc73485e449c028612a2c0680f9ac6ff70add",
1717
"transactionIndex": "0x0",
1818
"type": "0x3"
1919
}
20-
]
20+
]

0 commit comments

Comments
 (0)