Skip to content

Commit 187f9ce

Browse files
s1naMariusVanDerWijdenlightclient
authored andcommitted
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 93a39c8 commit 187f9ce

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
@@ -84,6 +84,10 @@ var (
8484
// than required to start the invocation.
8585
ErrIntrinsicGas = errors.New("intrinsic gas too low")
8686

87+
// ErrFloorDataGas is returned if the transaction is specified to use less gas
88+
// than required for the data floor cost.
89+
ErrFloorDataGas = errors.New("insufficient gas for floor data gas cost")
90+
8791
// ErrTxTypeNotSupported is returned if a transaction is not supported in the
8892
// current network configuration.
8993
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"
@@ -81,12 +82,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
8182
// Bump the required gas by the amount of transactional data
8283
if dataLen > 0 {
8384
// Zero and non-zero bytes are priced differently
84-
var nz uint64
85-
for _, byt := range data {
86-
if byt != 0 {
87-
nz++
88-
}
89-
}
85+
z := uint64(bytes.Count(data, []byte{0}))
86+
nz := dataLen - z
87+
9088
// Make sure we don't exceed uint64 for all data combinations
9189
nonZeroGas := params.TxDataNonZeroGasFrontier
9290
if isEIP2028 {
@@ -97,7 +95,6 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
9795
}
9896
gas += nz * nonZeroGas
9997

100-
z := dataLen - nz
10198
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
10299
return 0, ErrGasUintOverflow
103100
}
@@ -121,6 +118,21 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
121118
return gas, nil
122119
}
123120

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

459472
// Check clauses 4-5, subtract intrinsic gas if everything is correct
@@ -464,6 +477,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
464477
if st.gasRemaining < gas {
465478
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
466479
}
480+
// Gas limit suffices for the floor data cost (EIP-7623)
481+
if rules.IsPrague {
482+
floorDataGas, err = FloorDataGas(msg.Data)
483+
if err != nil {
484+
return nil, err
485+
}
486+
if msg.GasLimit < floorDataGas {
487+
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
488+
}
489+
}
467490
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
468491
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
469492
}
@@ -527,14 +550,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
527550
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
528551
}
529552

530-
var gasRefund uint64
531-
if !rules.IsLondon {
532-
// Before EIP-3529: refunds were capped to gasUsed / 2
533-
gasRefund = st.refundGas(params.RefundQuotient)
534-
} else {
535-
// After EIP-3529: refunds are capped to gasUsed / 5
536-
gasRefund = st.refundGas(params.RefundQuotientEIP3529)
553+
// Compute refund counter, capped to a refund quotient.
554+
gasRefund := st.calcRefund()
555+
st.gasRemaining += gasRefund
556+
if rules.IsPrague {
557+
// After EIP-7623: Data-heavy transactions pay the floor gas.
558+
if st.gasUsed() < floorDataGas {
559+
prev := st.gasRemaining
560+
st.gasRemaining = st.initialGas - floorDataGas
561+
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
562+
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
563+
}
564+
}
537565
}
566+
st.returnGas()
567+
538568
effectiveTip := msg.GasPrice
539569
if rules.IsLondon {
540570
effectiveTip = new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)
@@ -625,20 +655,28 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
625655
return nil
626656
}
627657

628-
func (st *stateTransition) refundGas(refundQuotient uint64) uint64 {
629-
// Apply refund counter, capped to a refund quotient
630-
refund := st.gasUsed() / refundQuotient
658+
// calcRefund computes refund counter, capped to a refund quotient.
659+
func (st *stateTransition) calcRefund() uint64 {
660+
var refund uint64
661+
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
662+
// Before EIP-3529: refunds were capped to gasUsed / 2
663+
refund = st.gasUsed() / params.RefundQuotient
664+
} else {
665+
// After EIP-3529: refunds are capped to gasUsed / 5
666+
refund = st.gasUsed() / params.RefundQuotientEIP3529
667+
}
631668
if refund > st.state.GetRefund() {
632669
refund = st.state.GetRefund()
633670
}
634-
635671
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
636672
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
637673
}
674+
return refund
675+
}
638676

639-
st.gasRemaining += refund
640-
641-
// Return ETH for remaining gas, exchanged at the original rate.
677+
// returnGas returns ETH for remaining gas,
678+
// exchanged at the original rate.
679+
func (st *stateTransition) returnGas() {
642680
remaining := uint256.NewInt(st.gasRemaining)
643681
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
644682
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
@@ -650,8 +688,6 @@ func (st *stateTransition) refundGas(refundQuotient uint64) uint64 {
650688
// Also return remaining gas to the block gas counter so it is
651689
// available for the next transaction.
652690
st.gp.AddGas(st.gasRemaining)
653-
654-
return refund
655691
}
656692

657693
// 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
@@ -314,6 +314,9 @@ const (
314314
GasChangeWitnessCodeChunk GasChangeReason = 17
315315
// GasChangeWitnessContractCollisionCheck flags the event of adding to the witness when checking for contract address collision.
316316
GasChangeWitnessContractCollisionCheck GasChangeReason = 18
317+
// GasChangeTxDataFloor is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the
318+
// transaction data. This change will always be a negative change.
319+
GasChangeTxDataFloor GasChangeReason = 19
317320

318321
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
319322
// 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
@@ -117,6 +117,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
117117
if tx.Gas() < intrGas {
118118
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
119119
}
120+
// Ensure the transaction can cover floor data gas.
121+
if opts.Config.IsPrague(head.Number, head.Time) {
122+
floorDataGas, err := core.FloorDataGas(tx.Data())
123+
if err != nil {
124+
return err
125+
}
126+
if tx.Gas() < floorDataGas {
127+
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
128+
}
129+
}
120130
// Ensure the gasprice is high enough to cover the requirement of the calling pool
121131
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
122132
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)