Skip to content

Commit 49426fa

Browse files
authored
Gas floor (#3618)
* update contracts for internal batch posting report fields * batch-gas-cost: check for floor-gas-cost in arbos-50 * replace calldataprice with parentGasFloorPerToken * internal_tx: use floorGasPerToken * update acts and support legacy feed * contracts: revert to version on master * precompiles: updates for parent-gas-floor * internal_tx: add per-batch-gas even if using floor * eth-config: update forkid * Add a test for gas floor * Enable delayed sequencer, satisfy finality requirements * internal_tx: use a constant instead of PerBatchGasCost * update gas floor price test * incomingmessage: fix comments * Remove leftover comment
1 parent 5284935 commit 49426fa

26 files changed

+298
-195
lines changed

arbnode/delayed.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func (b *DelayedBridge) logsToDeliveredMessages(ctx context.Context, logs []type
258258
},
259259
ParentChainBlockNumber: parsedLog.Raw.BlockNumber,
260260
}
261-
err := msg.Message.FillInBatchGasCost(batchFetcher)
261+
err := msg.Message.FillInBatchGasFields(batchFetcher)
262262
if err != nil {
263263
return nil, err
264264
}

arbnode/delayed_sequencer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (d *DelayedSequencer) sequenceWithoutLockout(ctx context.Context, lastBlock
174174
}
175175
}
176176
lastDelayedAcc = acc
177-
err = msg.FillInBatchGasCost(func(batchNum uint64) ([]byte, error) {
177+
err = msg.FillInBatchGasFields(func(batchNum uint64) ([]byte, error) {
178178
data, _, err := d.reader.GetSequencerMessageBytes(ctx, batchNum)
179179
return data, err
180180
})

arbnode/inbox_tracker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ func (t *InboxTracker) legacyGetDelayedMessageAndAccumulator(ctx context.Context
338338
return nil, common.Hash{}, err
339339
}
340340

341-
err = msg.FillInBatchGasCost(func(batchNum uint64) ([]byte, error) {
341+
err = msg.FillInBatchGasFields(func(batchNum uint64) ([]byte, error) {
342342
data, _, err := t.txStreamer.inboxReader.GetSequencerMessageBytes(ctx, batchNum)
343343
return data, err
344344
})
@@ -371,7 +371,7 @@ func (t *InboxTracker) GetDelayedMessageAccumulatorAndParentChainBlockNumber(ctx
371371
return msg, acc, 0, err
372372
}
373373

374-
err = msg.FillInBatchGasCost(func(batchNum uint64) ([]byte, error) {
374+
err = msg.FillInBatchGasFields(func(batchNum uint64) ([]byte, error) {
375375
data, _, err := t.txStreamer.inboxReader.GetSequencerMessageBytes(ctx, batchNum)
376376
return data, err
377377
})

arbnode/mel/extraction/message_extraction_function.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,8 @@ func extractMessagesImpl(
187187
batch.SequenceNumber,
188188
)
189189
}
190-
gas := arbostypes.ComputeBatchGasCost(serialized)
191-
192-
// Fill in the batch gas cost into the batch posting report.
193-
batchPostReport.Message.BatchGasCost = &gas
190+
// Fill in the batch gas stats into the batch posting report.
191+
batchPostReport.Message.BatchDataStats = arbostypes.GetDataStats(serialized)
194192
} else if !(inputState.DelayedMessagedSeen == 0 && i == 0 && delayedMessages[i] == batchPostReport) {
195193
return nil, nil, nil, errors.New("encountered initialize message that is not the first delayed message and the first batch ")
196194
}

arbnode/transaction_streamer.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ func (s *TransactionStreamer) GetMessage(msgIdx arbutil.MessageIndex) (*arbostyp
482482
return nil, err
483483
}
484484

485-
err = message.Message.FillInBatchGasCost(func(batchNum uint64) ([]byte, error) {
485+
err = message.Message.FillInBatchGasFields(func(batchNum uint64) ([]byte, error) {
486486
ctx, err := s.GetContextSafe()
487487
if err != nil {
488488
return nil, err
@@ -830,19 +830,22 @@ func (s *TransactionStreamer) countDuplicateMessages(
830830
}
831831
var duplicateMessage bool
832832
if nextMessage.MessageWithMeta.Message != nil {
833-
if dbMessageParsed.Message.BatchGasCost == nil || nextMessage.MessageWithMeta.Message.BatchGasCost == nil {
833+
if dbMessageParsed.Message.BatchDataStats == nil || nextMessage.MessageWithMeta.Message.BatchDataStats == nil {
834834
// Remove both of the batch gas costs and see if the messages still differ
835835
nextMessageCopy := nextMessage.MessageWithMeta
836836
nextMessageCopy.Message = new(arbostypes.L1IncomingMessage)
837837
*nextMessageCopy.Message = *nextMessage.MessageWithMeta.Message
838-
batchGasCostBkup := dbMessageParsed.Message.BatchGasCost
839-
dbMessageParsed.Message.BatchGasCost = nil
840-
nextMessageCopy.Message.BatchGasCost = nil
838+
batchGasCostBkup := dbMessageParsed.Message.LegacyBatchGasCost
839+
statsBkup := dbMessageParsed.Message.BatchDataStats
840+
dbMessageParsed.Message.LegacyBatchGasCost = nil
841+
nextMessageCopy.Message.LegacyBatchGasCost = nil
842+
dbMessageParsed.Message.BatchDataStats = nil
843+
nextMessageCopy.Message.BatchDataStats = nil
841844
if reflect.DeepEqual(dbMessageParsed, nextMessageCopy) {
842845
// Actually this isn't a reorg; only the batch gas costs differed
843846
duplicateMessage = true
844847
// If possible - update the message in the database to add the gas cost cache.
845-
if batch != nil && nextMessage.MessageWithMeta.Message.BatchGasCost != nil {
848+
if batch != nil && nextMessage.MessageWithMeta.Message.BatchDataStats != nil {
846849
if *batch == nil {
847850
*batch = s.db.NewBatch()
848851
}
@@ -851,7 +854,8 @@ func (s *TransactionStreamer) countDuplicateMessages(
851854
}
852855
}
853856
}
854-
dbMessageParsed.Message.BatchGasCost = batchGasCostBkup
857+
dbMessageParsed.Message.LegacyBatchGasCost = batchGasCostBkup
858+
dbMessageParsed.Message.BatchDataStats = statsBkup
855859
}
856860
}
857861

arbos/arbosState/arbosstate.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,6 @@ func (state *ArbosState) UpgradeArbosVersion(
391391
ensure(err)
392392
ensure(p.UpgradeToArbosVersion(nextArbosVersion))
393393
ensure(p.Save())
394-
chainId, err := state.ChainId()
395-
ensure(err)
396-
if chainId.Cmp(chaininfo.ArbitrumOneChainConfig().ChainID) == 0 || chainId.Cmp(chaininfo.ArbitrumNovaChainConfig().ChainID) == 0 {
397-
ensure(state.l1PricingState.SetCalldataPrice(big.NewInt(int64(params.TxCostFloorPerToken))))
398-
} else {
399-
ensure(state.l1PricingState.SetCalldataPrice(big.NewInt(int64(params.TxDataNonZeroGasEIP2028))))
400-
}
401394
ensure(state.l2PricingState.SetMaxPerTxGasLimit(l2pricing.InitialPerTxGasLimitV50))
402395
oldBlockGasLimit, err := state.l2PricingState.PerBlockGasLimit()
403396
ensure(err)

arbos/arbostypes/incomingmessage.go

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,20 @@ func (h L1IncomingMessageHeader) SeqNum() (uint64, error) {
5555
return seqNumBig.Uint64(), nil
5656
}
5757

58+
type BatchDataStats struct {
59+
Length uint64 `json:"length"`
60+
NonZeros uint64 `json:"nonzeros"`
61+
}
62+
5863
type L1IncomingMessage struct {
5964
Header *L1IncomingMessageHeader `json:"header"`
6065
L2msg []byte `json:"l2Msg"`
6166

6267
// Only used for `L1MessageType_BatchPostingReport`
63-
BatchGasCost *uint64 `json:"batchGasCost,omitempty" rlp:"optional"`
68+
// note: the legacy field is used in json to support older clients
69+
// in rlp it's used to distinguish old from new (old will load into first arg)
70+
LegacyBatchGasCost *uint64 `json:"batchGasCost,omitempty" rlp:"optional"`
71+
BatchDataStats *BatchDataStats `json:"batchDataTokens,omitempty" rlp:"optional"`
6472
}
6573

6674
var EmptyTestIncomingMessage = L1IncomingMessage{
@@ -145,41 +153,52 @@ func (h *L1IncomingMessageHeader) Equals(other *L1IncomingMessageHeader) bool {
145153
arbmath.BigEquals(h.L1BaseFee, other.L1BaseFee)
146154
}
147155

148-
func ComputeBatchGasCost(data []byte) uint64 {
149-
var gas uint64
156+
func GetDataStats(data []byte) *BatchDataStats {
157+
nonZeros := uint64(0)
150158
for _, b := range data {
151-
if b == 0 {
152-
gas += params.TxDataZeroGas
153-
} else {
154-
gas += params.TxDataNonZeroGasEIP2028
159+
if b != 0 {
160+
nonZeros += 1
155161
}
156162
}
163+
return &BatchDataStats{
164+
Length: uint64(len(data)),
165+
NonZeros: nonZeros,
166+
}
167+
}
157168

169+
func LegacyCostForStats(stats *BatchDataStats) uint64 {
170+
gas := params.TxDataZeroGas*(stats.Length-stats.NonZeros) + params.TxDataNonZeroGasEIP2028*stats.NonZeros
158171
// the poster also pays to keccak the batch and place it and a batch-posting report into the inbox
159-
keccakWords := arbmath.WordsForBytes(uint64(len(data)))
172+
keccakWords := arbmath.WordsForBytes(stats.Length)
160173
gas += params.Keccak256Gas + (keccakWords * params.Keccak256WordGas)
161174
gas += 2 * params.SstoreSetGasEIP2200
162175
return gas
163176
}
164177

165-
func (msg *L1IncomingMessage) FillInBatchGasCost(batchFetcher FallibleBatchFetcher) error {
166-
if batchFetcher == nil || msg.Header.Kind != L1MessageType_BatchPostingReport || msg.BatchGasCost != nil {
178+
func (msg *L1IncomingMessage) FillInBatchGasFields(batchFetcher FallibleBatchFetcher) error {
179+
if batchFetcher == nil || msg.Header.Kind != L1MessageType_BatchPostingReport {
167180
return nil
168181
}
169-
_, _, batchHash, batchNum, _, _, err := ParseBatchPostingReportMessageFields(bytes.NewReader(msg.L2msg))
170-
if err != nil {
171-
return fmt.Errorf("failed to parse batch posting report: %w", err)
172-
}
173-
batchData, err := batchFetcher(batchNum)
174-
if err != nil {
175-
return fmt.Errorf("failed to fetch batch mentioned by batch posting report: %w", err)
182+
if msg.BatchDataStats != nil && msg.LegacyBatchGasCost != nil {
183+
return nil
176184
}
177-
gotHash := crypto.Keccak256Hash(batchData)
178-
if gotHash != batchHash {
179-
return fmt.Errorf("batch fetcher returned incorrect data hash %v (wanted %v for batch %v)", gotHash, batchHash, batchNum)
185+
if msg.BatchDataStats == nil {
186+
_, _, batchHash, batchNum, _, _, err := ParseBatchPostingReportMessageFields(bytes.NewReader(msg.L2msg))
187+
if err != nil {
188+
return fmt.Errorf("failed to parse batch posting report: %w", err)
189+
}
190+
batchData, err := batchFetcher(batchNum)
191+
if err != nil {
192+
return fmt.Errorf("failed to fetch batch mentioned by batch posting report: %w", err)
193+
}
194+
gotHash := crypto.Keccak256Hash(batchData)
195+
if gotHash != batchHash {
196+
return fmt.Errorf("batch fetcher returned incorrect data hash %v (wanted %v for batch %v)", gotHash, batchHash, batchNum)
197+
}
198+
msg.BatchDataStats = GetDataStats(batchData)
180199
}
181-
gas := ComputeBatchGasCost(batchData)
182-
msg.BatchGasCost = &gas
200+
legacyCost := LegacyCostForStats(msg.BatchDataStats)
201+
msg.LegacyBatchGasCost = &legacyCost
183202
return nil
184203
}
185204

@@ -233,18 +252,19 @@ func ParseIncomingL1Message(rd io.Reader, batchFetcher FallibleBatchFetcher) (*L
233252
}
234253

235254
msg := &L1IncomingMessage{
236-
&L1IncomingMessageHeader{
255+
Header: &L1IncomingMessageHeader{
237256
Kind: kind,
238257
Poster: sender,
239258
BlockNumber: blockNumber,
240259
Timestamp: timestamp,
241260
RequestId: &requestId,
242261
L1BaseFee: baseFeeL1.Big(),
243262
},
244-
data,
245-
nil,
263+
L2msg: data,
264+
LegacyBatchGasCost: nil,
265+
BatchDataStats: nil,
246266
}
247-
err = msg.FillInBatchGasCost(batchFetcher)
267+
err = msg.FillInBatchGasFields(batchFetcher)
248268
if err != nil {
249269
return nil, err
250270
}

arbos/incomingmessage_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ func TestSerializeAndParseL1Message(t *testing.T) {
2525
L1BaseFee: big.NewInt(10000000000000),
2626
}
2727
msg := arbostypes.L1IncomingMessage{
28-
Header: &header,
29-
L2msg: []byte{3, 2, 1},
30-
BatchGasCost: nil,
28+
Header: &header,
29+
L2msg: []byte{3, 2, 1},
30+
LegacyBatchGasCost: nil,
31+
BatchDataStats: nil,
3132
}
3233
serialized, err := msg.Serialize()
3334
if err != nil {

arbos/internal_tx.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/ethereum/go-ethereum/params"
1717

1818
"github.com/offchainlabs/nitro/arbos/arbosState"
19+
"github.com/offchainlabs/nitro/arbos/arbostypes"
1920
"github.com/offchainlabs/nitro/arbos/util"
2021
"github.com/offchainlabs/nitro/util/arbmath"
2122
)
@@ -102,16 +103,49 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos
102103
}
103104
batchTimestamp := util.SafeMapGet[*big.Int](inputs, "batchTimestamp")
104105
batchPosterAddress := util.SafeMapGet[common.Address](inputs, "batchPosterAddress")
105-
batchDataGas := util.SafeMapGet[uint64](inputs, "batchDataGas")
106+
batchCalldataLength := util.SafeMapGet[uint64](inputs, "batchCalldataLength")
107+
batchCalldataNonZeros := util.SafeMapGet[uint64](inputs, "batchCalldataNonZeros")
108+
batchLegacyGas := util.SafeMapGet[uint64](inputs, "batchLegacyGas")
109+
batchExtraGas := util.SafeMapGet[uint64](inputs, "batchExtraGas")
106110
l1BaseFeeWei := util.SafeMapGet[*big.Int](inputs, "l1BaseFeeWei")
107111

112+
var gasSpent uint64
113+
if batchCalldataLength == ^uint64(0) {
114+
if state.ArbOSVersion() >= params.ArbosVersion_50 {
115+
return fmt.Errorf("missing batch calldata stats for arbos >= 50")
116+
}
117+
gasSpent = batchLegacyGas
118+
} else {
119+
gasSpent = arbostypes.LegacyCostForStats(&arbostypes.BatchDataStats{
120+
Length: batchCalldataLength,
121+
NonZeros: batchCalldataNonZeros,
122+
})
123+
gasSpent = arbmath.SaturatingUAdd(gasSpent, batchExtraGas)
124+
if batchLegacyGas != ^uint64(0) && batchLegacyGas != gasSpent {
125+
log.Error("legacy gas doesn't fit local compute", "local", gasSpent, "legacy", batchLegacyGas, "timestamp", batchTimestamp)
126+
}
127+
}
128+
108129
l1p := state.L1PricingState()
130+
109131
perBatchGas, err := l1p.PerBatchGasCost()
110132
if err != nil {
111133
log.Warn("L1Pricing PerBatchGas failed", "err", err)
112134
}
113-
gasSpent := arbmath.SaturatingAdd(perBatchGas, arbmath.SaturatingCast[int64](batchDataGas))
114-
weiSpent := arbmath.BigMulByUint(l1BaseFeeWei, arbmath.SaturatingUCast[uint64](gasSpent))
135+
gasSpent = arbmath.SaturatingUAdd(gasSpent, arbmath.SaturatingUCast[uint64](perBatchGas))
136+
137+
if state.ArbOSVersion() >= params.ArbosVersion_50 {
138+
gasFloorPerToken, err := l1p.ParentGasFloorPerToken()
139+
if err != nil {
140+
log.Warn("failed reading gasFloorPerToken", "err", err)
141+
}
142+
floorGasSpent := gasFloorPerToken*(batchCalldataLength+batchCalldataNonZeros*3) + params.TxGas
143+
if floorGasSpent > gasSpent {
144+
gasSpent = floorGasSpent
145+
}
146+
}
147+
148+
weiSpent := arbmath.BigMulByUint(l1BaseFeeWei, gasSpent)
115149
err = l1p.UpdateForBatchPosterSpending(
116150
evm.StateDB,
117151
evm,

arbos/l1pricing/l1pricing.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ type L1PricingState struct {
3939
// funds collected since update are recorded as the balance in account L1PricerFundsPoolAddress
4040
unitsSinceUpdate storage.StorageBackedUint64 // calldata units collected for since last update
4141
pricePerUnit storage.StorageBackedBigUint // current price per calldata unit
42-
calldataPrice storage.StorageBackedBigInt // gas per non-zero calldata byte
4342
lastSurplus storage.StorageBackedBigInt // introduced in ArbOS version 2
4443
perBatchGasCost storage.StorageBackedInt64 // introduced in ArbOS version 3
4544
amortizedCostCapBips storage.StorageBackedUint64 // in basis points; introduced in ArbOS version 3
4645
l1FeesAvailable storage.StorageBackedBigUint
46+
gasFloorPerToken storage.StorageBackedUint64 // introduced in arbos version 50, default 0
4747

4848
ArbosVersion uint64
4949
}
@@ -70,7 +70,7 @@ const (
7070
perBatchGasCostOffset
7171
amortizedCostCapBipsOffset
7272
l1FeesAvailableOffset
73-
calldataPriceOffset
73+
gasFloorPerTokenOffset
7474
)
7575

7676
const (
@@ -129,11 +129,11 @@ func OpenL1PricingState(sto *storage.Storage, arbosVersion uint64) *L1PricingSta
129129
fundsDueForRewards: sto.OpenStorageBackedBigInt(fundsDueForRewardsOffset),
130130
unitsSinceUpdate: sto.OpenStorageBackedUint64(unitsSinceOffset),
131131
pricePerUnit: sto.OpenStorageBackedBigUint(pricePerUnitOffset),
132-
calldataPrice: sto.OpenStorageBackedBigInt(calldataPriceOffset),
133132
lastSurplus: sto.OpenStorageBackedBigInt(lastSurplusOffset),
134133
perBatchGasCost: sto.OpenStorageBackedInt64(perBatchGasCostOffset),
135134
amortizedCostCapBips: sto.OpenStorageBackedUint64(amortizedCostCapBipsOffset),
136135
l1FeesAvailable: sto.OpenStorageBackedBigUint(l1FeesAvailableOffset),
136+
gasFloorPerToken: sto.OpenStorageBackedUint64(gasFloorPerTokenOffset),
137137
ArbosVersion: arbosVersion,
138138
}
139139
}
@@ -243,15 +243,18 @@ func (ps *L1PricingState) SetPricePerUnit(price *big.Int) error {
243243
return ps.pricePerUnit.SetChecked(price)
244244
}
245245

246-
func (ps *L1PricingState) CalldataPrice() (*big.Int, error) {
246+
func (ps *L1PricingState) SetParentGasFloorPerToken(floor uint64) error {
247247
if ps.ArbosVersion < params.ArbosVersion_50 {
248-
return big.NewInt(int64(params.TxDataNonZeroGasEIP2028)), nil
248+
return fmt.Errorf("not supported")
249249
}
250-
return ps.calldataPrice.Get()
250+
return ps.gasFloorPerToken.Set(floor)
251251
}
252252

253-
func (ps *L1PricingState) SetCalldataPrice(price *big.Int) error {
254-
return ps.calldataPrice.SetChecked(price)
253+
func (ps *L1PricingState) ParentGasFloorPerToken() (uint64, error) {
254+
if ps.ArbosVersion < params.ArbosVersion_50 {
255+
return 0, nil
256+
}
257+
return ps.gasFloorPerToken.Get()
255258
}
256259

257260
func (ps *L1PricingState) PerBatchGasCost() (int64, error) {
@@ -528,11 +531,7 @@ func (ps *L1PricingState) getPosterUnitsWithoutCache(tx *types.Transaction, post
528531
if err != nil {
529532
panic(fmt.Sprintf("failed to compress tx: %v", err))
530533
}
531-
l1CalldataPrice, err := ps.CalldataPrice()
532-
if err != nil {
533-
return 0
534-
}
535-
return am.BigMulByUint(l1CalldataPrice, l1Bytes).Uint64()
534+
return am.SaturatingUMul(params.TxDataNonZeroGasEIP2028, l1Bytes)
536535
}
537536

538537
// GetPosterInfo returns the poster cost and the calldata units for a transaction
@@ -555,7 +554,7 @@ func (ps *L1PricingState) GetPosterInfo(tx *types.Transaction, poster common.Add
555554
}
556555

557556
// We don't have the full tx in gas estimation, so we assume it might be a bit bigger in practice.
558-
var estimationPaddingUnits uint64 = 16
557+
var estimationPaddingUnits uint64 = 16 * params.TxDataNonZeroGasEIP2028
559558

560559
const estimationPaddingBasisPoints = 100
561560

@@ -612,8 +611,7 @@ func (ps *L1PricingState) PosterDataCost(message *core.Message, poster common.Ad
612611
// We'll instead make a fake tx from the message info we do have, and then pad our cost a bit to be safe.
613612
tx = makeFakeTxForMessage(message)
614613
units := ps.getPosterUnitsWithoutCache(tx, poster, brotliCompressionLevel)
615-
l1CalldataPrice, _ := ps.CalldataPrice()
616-
units = am.UintMulByBips(units+l1CalldataPrice.Uint64()*estimationPaddingUnits, am.OneInBips+estimationPaddingBasisPoints)
614+
units = am.UintMulByBips(units+estimationPaddingUnits, am.OneInBips+estimationPaddingBasisPoints)
617615
pricePerUnit, _ := ps.PricePerUnit()
618616
return am.BigMulByUint(pricePerUnit, units), units
619617
}

0 commit comments

Comments
 (0)