From 609b4cf43a2279703282cdd779c6ac01fa106868 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Mon, 22 Jan 2024 23:59:07 -0700 Subject: [PATCH 01/10] Add 4844 blob tx support to data poster (rbf is wip) --- arbnode/batch_poster.go | 1 + arbnode/dataposter/data_poster.go | 178 ++++++++++++++++++++------ arbnode/dataposter/storage/storage.go | 10 +- arbnode/dataposter/storage_test.go | 2 +- staker/validatorwallet/contract.go | 7 +- staker/validatorwallet/eoa.go | 3 +- 6 files changed, 152 insertions(+), 49 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 07034ee6f8..01a84b1c43 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -1071,6 +1071,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) data, gasLimit, new(big.Int), + nil, // TODO: use blobs accessList, ) if err != nil { diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 09f3e218b1..425dba8e18 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -23,7 +23,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -31,12 +33,14 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/go-redis/redis/v8" + "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbnode/dataposter/dbstorage" "github.com/offchainlabs/nitro/arbnode/dataposter/noop" "github.com/offchainlabs/nitro/arbnode/dataposter/slice" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/blobs" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -64,6 +68,7 @@ type DataPoster struct { metadataRetriever func(ctx context.Context, blockNum *big.Int) ([]byte, error) extraBacklog func() uint64 parentChainID *big.Int + parentChainID256 *uint256.Int // These fields are protected by the mutex. // TODO: factor out these fields into separate structure, since now one @@ -177,6 +182,11 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro extraBacklog: opts.ExtraBacklog, parentChainID: opts.ParentChainID, } + var overflow bool + dp.parentChainID256, overflow = uint256.FromBig(opts.ParentChainID) + if overflow { + return nil, fmt.Errorf("parent chain ID %v overflows uint256 (necessary for blob transactions)", opts.ParentChainID) + } if dp.extraBacklog == nil { dp.extraBacklog = func() uint64 { return 0 } } @@ -363,7 +373,7 @@ func (p *DataPoster) getNextNonceAndMaybeMeta(ctx context.Context) (uint64, []by return 0, nil, false, fmt.Errorf("fetching last element from queue: %w", err) } if lastQueueItem != nil { - nextNonce := lastQueueItem.Data.Nonce + 1 + nextNonce := lastQueueItem.FullTx.Nonce() + 1 if err := p.canPostWithNonce(ctx, nextNonce); err != nil { return 0, nil, false, err } @@ -442,27 +452,34 @@ func (p *DataPoster) evalMaxFeeCapExpr(backlogOfBatches uint64, elapsed time.Dur var big4 = big.NewInt(4) // The dataPosterBacklog argument should *not* include extraBacklog (it's added in in this function) -func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit uint64, lastFeeCap *big.Int, lastTipCap *big.Int, dataCreatedAt time.Time, dataPosterBacklog uint64) (*big.Int, *big.Int, error) { +func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit uint64, numBlobs int, lastFeeCap *big.Int, lastTipCap *big.Int, dataCreatedAt time.Time, dataPosterBacklog uint64) (*big.Int, *big.Int, *big.Int, error) { config := p.config() dataPosterBacklog += p.extraBacklog() latestHeader, err := p.headerReader.LastHeader(ctx) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if latestHeader.BaseFee == nil { - return nil, nil, fmt.Errorf("latest parent chain block %v missing BaseFee (either the parent chain does not have EIP-1559 or the parent chain node is not synced)", latestHeader.Number) + return nil, nil, nil, fmt.Errorf("latest parent chain block %v missing BaseFee (either the parent chain does not have EIP-1559 or the parent chain node is not synced)", latestHeader.Number) + } + newBlobFeeCap := big.NewInt(0) + if latestHeader.ExcessBlobGas != nil { + newBlobFeeCap = eip4844.CalcBlobFee(*latestHeader.ExcessBlobGas) + newBlobFeeCap.Mul(newBlobFeeCap, common.Big2) + } else if numBlobs > 0 { + return nil, nil, nil, fmt.Errorf("latest parent chain block %v missing ExcessBlobGas but blobs were specified in data poster transaction (either the parent chain node is not synced or EIP-4844 was improperly activated)", latestHeader.Number) } softConfBlock := arbmath.BigSubByUint(latestHeader.Number, config.NonceRbfSoftConfs) softConfNonce, err := p.client.NonceAt(ctx, p.Sender(), softConfBlock) if err != nil { - return nil, nil, fmt.Errorf("failed to get latest nonce %v blocks ago (block %v): %w", config.NonceRbfSoftConfs, softConfBlock, err) + return nil, nil, nil, fmt.Errorf("failed to get latest nonce %v blocks ago (block %v): %w", config.NonceRbfSoftConfs, softConfBlock, err) } - newFeeCap := new(big.Int).Mul(latestHeader.BaseFee, big.NewInt(2)) + newFeeCap := new(big.Int).Mul(latestHeader.BaseFee, common.Big2) newFeeCap = arbmath.BigMax(newFeeCap, arbmath.FloatToBig(config.MinFeeCapGwei*params.GWei)) newTipCap, err := p.client.SuggestGasTipCap(ctx) if err != nil { - return nil, nil, err + return nil, nil, nil, err } newTipCap = arbmath.BigMax(newTipCap, arbmath.FloatToBig(config.MinTipCapGwei*params.GWei)) newTipCap = arbmath.BigMin(newTipCap, arbmath.FloatToBig(config.MaxTipCapGwei*params.GWei)) @@ -481,10 +498,13 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u newFeeCap = arbmath.BigMax(newFeeCap, arbmath.BigMulByBips(lastFeeCap, minRbfIncrease)) } + // TODO: if we're significantly increasing the blob fee cap, we also need to increase the fee cap my minRbfIncrease + // TODO: look more into geth's blob mempool and make sure this behavior conforms (I think minRbfIncrease might be higher there) + elapsed := time.Since(dataCreatedAt) maxFeeCap, err := p.evalMaxFeeCapExpr(dataPosterBacklog, elapsed) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if arbmath.BigGreaterThan(newFeeCap, maxFeeCap) { log.Warn( @@ -496,6 +516,8 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u newFeeCap = maxFeeCap } + // TODO: also have an expression limiting the max blob fee cap + latestBalance := p.balance balanceForTx := new(big.Int).Set(latestBalance) if config.AllocateMempoolBalance && !p.usingNoOpStorage { @@ -525,6 +547,7 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u balanceForTx.Div(balanceForTx, arbmath.UintToBig(config.MaxMempoolTransactions-1)) } } + // TODO: take into account blob costs balanceFeeCap := arbmath.BigDivByUint(balanceForTx, gasLimit) if arbmath.BigGreaterThan(newFeeCap, balanceFeeCap) { log.Warn( @@ -550,10 +573,14 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u newTipCap = new(big.Int).Set(newFeeCap) } - return newFeeCap, newTipCap, nil + return newFeeCap, newTipCap, newBlobFeeCap, nil } -func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Time, nonce uint64, meta []byte, to common.Address, calldata []byte, gasLimit uint64, value *big.Int, accessList types.AccessList) (*types.Transaction, error) { +func (p *DataPoster) PostSimpleTransaction(ctx context.Context, nonce uint64, to common.Address, calldata []byte, gasLimit uint64, value *big.Int) (*types.Transaction, error) { + return p.PostTransaction(ctx, time.Now(), nonce, nil, to, calldata, gasLimit, value, nil, nil) +} + +func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Time, nonce uint64, meta []byte, to common.Address, calldata []byte, gasLimit uint64, value *big.Int, kzgBlobs []kzg4844.Blob, accessList types.AccessList) (*types.Transaction, error) { p.mutex.Lock() defer p.mutex.Unlock() @@ -570,27 +597,65 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim return nil, fmt.Errorf("failed to update data poster balance: %w", err) } - feeCap, tipCap, err := p.feeAndTipCaps(ctx, nonce, gasLimit, nil, nil, dataCreatedAt, 0) + feeCap, tipCap, blobFeeCap, err := p.feeAndTipCaps(ctx, nonce, gasLimit, len(kzgBlobs), nil, nil, dataCreatedAt, 0) if err != nil { return nil, err } - inner := types.DynamicFeeTx{ - Nonce: nonce, - GasTipCap: tipCap, - GasFeeCap: feeCap, - Gas: gasLimit, - To: &to, - Value: value, - Data: calldata, - AccessList: accessList, - ChainID: p.parentChainID, - } - fullTx, err := p.signer(ctx, p.Sender(), types.NewTx(&inner)) + + var deprecatedData types.DynamicFeeTx + var inner types.TxData + if len(kzgBlobs) > 0 { + value256, overflow := uint256.FromBig(value) + if overflow { + return nil, fmt.Errorf("blob transaction callvalue %v overflows uint256", value) + } + // Intentionally break out of date data poster redis clients, + // so they don't try to replace by fee a tx they don't understand + deprecatedData.Nonce = ^uint64(0) + commitments, proofs, blobHashes, err := blobs.ComputeCommitmentsProofsAndHashes(kzgBlobs) + if err != nil { + return nil, fmt.Errorf("failed to compute KZG metadata: %w", err) + } + inner = &types.BlobTx{ + Nonce: nonce, + Gas: gasLimit, + To: to, + Value: value256, + Data: calldata, + Sidecar: &types.BlobTxSidecar{ + Blobs: kzgBlobs, + Commitments: commitments, + Proofs: proofs, + }, + BlobHashes: blobHashes, + AccessList: accessList, + ChainID: p.parentChainID256, + } + // reuse the code to convert gas fee and tip caps to uint256s + inner, err = updateTxDataGasCaps(inner, feeCap, tipCap, blobFeeCap) + if err != nil { + return nil, err + } + } else { + deprecatedData = types.DynamicFeeTx{ + Nonce: nonce, + GasFeeCap: feeCap, + GasTipCap: tipCap, + Gas: gasLimit, + To: &to, + Value: value, + Data: calldata, + AccessList: accessList, + ChainID: p.parentChainID, + } + inner = &deprecatedData + } + fullTx, err := p.signer(ctx, p.Sender(), types.NewTx(inner)) if err != nil { return nil, fmt.Errorf("signing transaction: %w", err) } queuedTx := storage.QueuedTransaction{ - Data: inner, + DeprecatedData: deprecatedData, FullTx: fullTx, Meta: meta, Sent: false, @@ -603,8 +668,8 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim // the mutex must be held by the caller func (p *DataPoster) saveTx(ctx context.Context, prevTx, newTx *storage.QueuedTransaction) error { if prevTx != nil { - if prevTx.Data.Nonce != newTx.Data.Nonce { - return fmt.Errorf("prevTx nonce %v doesn't match newTx nonce %v", prevTx.Data.Nonce, newTx.Data.Nonce) + if prevTx.FullTx.Nonce() != newTx.FullTx.Nonce() { + return fmt.Errorf("prevTx nonce %v doesn't match newTx nonce %v", prevTx.FullTx.Nonce(), newTx.FullTx.Nonce()) } // Check if prevTx is the same as newTx and we don't need to do anything @@ -621,7 +686,7 @@ func (p *DataPoster) saveTx(ctx context.Context, prevTx, newTx *storage.QueuedTr return nil } } - if err := p.queue.Put(ctx, newTx.Data.Nonce, prevTx, newTx); err != nil { + if err := p.queue.Put(ctx, newTx.FullTx.Nonce(), prevTx, newTx); err != nil { return fmt.Errorf("putting new tx in the queue: %w", err) } return nil @@ -645,22 +710,57 @@ func (p *DataPoster) sendTx(ctx context.Context, prevTx *storage.QueuedTransacti return p.saveTx(ctx, newTx, &newerTx) } +func updateTxDataGasCaps(data types.TxData, newFeeCap, newTipCap, newBlobFeeCap *big.Int) (types.TxData, error) { + switch data := data.(type) { + case *types.DynamicFeeTx: + data.GasFeeCap = newFeeCap + data.GasTipCap = newTipCap + return data, nil + case *types.BlobTx: + var overflow bool + data.GasFeeCap, overflow = uint256.FromBig(newFeeCap) + if overflow { + return nil, fmt.Errorf("blob tx fee cap %v exceeds uint256", newFeeCap) + } + data.GasTipCap, overflow = uint256.FromBig(newTipCap) + if overflow { + return nil, fmt.Errorf("blob tx tip cap %v exceeds uint256", newTipCap) + } + data.BlobFeeCap, overflow = uint256.FromBig(newBlobFeeCap) + if overflow { + return nil, fmt.Errorf("blob tx blob fee cap %v exceeds uint256", newBlobFeeCap) + } + return data, nil + default: + return nil, fmt.Errorf("unexpected transaction data type %T", data) + } +} + +func updateGasCaps(tx *types.Transaction, newFeeCap, newTipCap, newBlobFeeCap *big.Int) (*types.Transaction, error) { + data, err := updateTxDataGasCaps(tx.GetInner(), newFeeCap, newTipCap, newBlobFeeCap) + if err != nil { + return nil, err + } + return types.NewTx(data), nil +} + // The mutex must be held by the caller. func (p *DataPoster) replaceTx(ctx context.Context, prevTx *storage.QueuedTransaction, backlogOfBatches uint64) error { - newFeeCap, newTipCap, err := p.feeAndTipCaps(ctx, prevTx.Data.Nonce, prevTx.Data.Gas, prevTx.Data.GasFeeCap, prevTx.Data.GasTipCap, prevTx.Created, backlogOfBatches) + newFeeCap, newTipCap, newBlobFeeCap, err := p.feeAndTipCaps(ctx, prevTx.FullTx.Nonce(), prevTx.FullTx.Gas(), len(prevTx.FullTx.BlobHashes()), prevTx.FullTx.GasFeeCap(), prevTx.FullTx.GasTipCap(), prevTx.Created, backlogOfBatches) if err != nil { return err } - minNewFeeCap := arbmath.BigMulByBips(prevTx.Data.GasFeeCap, minRbfIncrease) + minNewFeeCap := arbmath.BigMulByBips(prevTx.FullTx.GasFeeCap(), minRbfIncrease) newTx := *prevTx + // TODO: also look at the blob fee cap if newFeeCap.Cmp(minNewFeeCap) < 0 { log.Debug( "no need to replace by fee transaction", - "nonce", prevTx.Data.Nonce, - "lastFeeCap", prevTx.Data.GasFeeCap, + "nonce", prevTx.FullTx.Nonce(), + "lastFeeCap", prevTx.FullTx.GasFeeCap(), "recommendedFeeCap", newFeeCap, - "lastTipCap", prevTx.Data.GasTipCap, + "lastTipCap", prevTx.FullTx.GasTipCap(), "recommendedTipCap", newTipCap, ) newTx.NextReplacement = time.Now().Add(time.Minute) @@ -676,9 +776,13 @@ func (p *DataPoster) replaceTx(ctx context.Context, prevTx *storage.QueuedTransa break } newTx.Sent = false - newTx.Data.GasFeeCap = newFeeCap - newTx.Data.GasTipCap = newTipCap - newTx.FullTx, err = p.signer(ctx, p.Sender(), types.NewTx(&newTx.Data)) + newTx.DeprecatedData.GasFeeCap = newFeeCap + newTx.DeprecatedData.GasTipCap = newTipCap + unsignedTx, err := updateGasCaps(newTx.FullTx, newFeeCap, newTipCap, newBlobFeeCap) + if err != nil { + return err + } + newTx.FullTx, err = p.signer(ctx, p.Sender(), unsignedTx) if err != nil { return err } @@ -750,7 +854,7 @@ func (p *DataPoster) updateBalance(ctx context.Context) error { const maxConsecutiveIntermittentErrors = 10 func (p *DataPoster) maybeLogError(err error, tx *storage.QueuedTransaction, msg string) { - nonce := tx.Data.Nonce + nonce := tx.FullTx.Nonce() if err == nil { delete(p.errorCount, nonce) return @@ -764,7 +868,7 @@ func (p *DataPoster) maybeLogError(err error, tx *storage.QueuedTransaction, msg } else { delete(p.errorCount, nonce) } - logLevel(msg, "err", err, "nonce", nonce, "feeCap", tx.Data.GasFeeCap, "tipCap", tx.Data.GasTipCap, "gas", tx.Data.Gas) + logLevel(msg, "err", err, "nonce", nonce, "feeCap", tx.FullTx.GasFeeCap(), "tipCap", tx.FullTx.GasTipCap(), "gas", tx.FullTx.Gas()) } const minWait = time.Second * 10 diff --git a/arbnode/dataposter/storage/storage.go b/arbnode/dataposter/storage/storage.go index a9e78fcc58..9586b9c9a9 100644 --- a/arbnode/dataposter/storage/storage.go +++ b/arbnode/dataposter/storage/storage.go @@ -27,7 +27,7 @@ var ( type QueuedTransaction struct { FullTx *types.Transaction - Data types.DynamicFeeTx + DeprecatedData types.DynamicFeeTx // FullTx should be used instead Meta []byte Sent bool Created time.Time // may be earlier than the tx was given to the tx poster @@ -46,7 +46,7 @@ type queuedTransactionForEncoding struct { func (qt *QueuedTransaction) EncodeRLP(w io.Writer) error { return rlp.Encode(w, queuedTransactionForEncoding{ FullTx: qt.FullTx, - Data: qt.Data, + Data: qt.DeprecatedData, Meta: qt.Meta, Sent: qt.Sent, Created: (RlpTime)(qt.Created), @@ -60,7 +60,7 @@ func (qt *QueuedTransaction) DecodeRLP(s *rlp.Stream) error { return err } qt.FullTx = qtEnc.FullTx - qt.Data = qtEnc.Data + qt.DeprecatedData = qtEnc.Data qt.Meta = qtEnc.Meta qt.Sent = qtEnc.Sent qt.Created = time.Time(qtEnc.Created) @@ -107,7 +107,7 @@ func LegacyToQueuedTransaction(legacyQT *LegacyQueuedTransaction) (*QueuedTransa } return &QueuedTransaction{ FullTx: legacyQT.FullTx, - Data: legacyQT.Data, + DeprecatedData: legacyQT.Data, Meta: meta, Sent: legacyQT.Sent, Created: legacyQT.Created, @@ -127,7 +127,7 @@ func QueuedTransactionToLegacy(qt *QueuedTransaction) (*LegacyQueuedTransaction, } return &LegacyQueuedTransaction{ FullTx: qt.FullTx, - Data: qt.Data, + Data: qt.DeprecatedData, Meta: meta, Sent: qt.Sent, Created: qt.Created, diff --git a/arbnode/dataposter/storage_test.go b/arbnode/dataposter/storage_test.go index cf9918941e..f98c120f38 100644 --- a/arbnode/dataposter/storage_test.go +++ b/arbnode/dataposter/storage_test.go @@ -84,7 +84,7 @@ func valueOf(t *testing.T, i int) *storage.QueuedTransaction { big.NewInt(int64(i)), []byte{byte(i)}), Meta: meta, - Data: types.DynamicFeeTx{ + DeprecatedData: types.DynamicFeeTx{ ChainID: big.NewInt(int64(i)), Nonce: uint64(i), GasTipCap: big.NewInt(int64(i)), diff --git a/staker/validatorwallet/contract.go b/staker/validatorwallet/contract.go index 774e9ab407..deed7942ab 100644 --- a/staker/validatorwallet/contract.go +++ b/staker/validatorwallet/contract.go @@ -10,7 +10,6 @@ import ( "math/big" "strings" "sync/atomic" - "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -177,7 +176,7 @@ func (v *Contract) executeTransaction(ctx context.Context, tx *types.Transaction if err != nil { return nil, fmt.Errorf("getting gas for tx data: %w", err) } - return v.dataPoster.PostTransaction(ctx, time.Now(), auth.Nonce.Uint64(), nil, *v.Address(), data, gas, auth.Value, nil) + return v.dataPoster.PostSimpleTransaction(ctx, auth.Nonce.Uint64(), *v.Address(), data, gas, auth.Value) } func (v *Contract) populateWallet(ctx context.Context, createIfMissing bool) error { @@ -288,7 +287,7 @@ func (v *Contract) ExecuteTransactions(ctx context.Context, builder *txbuilder.B if err != nil { return nil, fmt.Errorf("getting gas for tx data: %w", err) } - arbTx, err := v.dataPoster.PostTransaction(ctx, time.Now(), auth.Nonce.Uint64(), nil, *v.Address(), txData, gas, auth.Value, nil) + arbTx, err := v.dataPoster.PostSimpleTransaction(ctx, auth.Nonce.Uint64(), *v.Address(), txData, gas, auth.Value) if err != nil { return nil, err } @@ -338,7 +337,7 @@ func (v *Contract) TimeoutChallenges(ctx context.Context, challenges []uint64) ( if err != nil { return nil, fmt.Errorf("getting gas for tx data: %w", err) } - return v.dataPoster.PostTransaction(ctx, time.Now(), auth.Nonce.Uint64(), nil, *v.Address(), data, gas, auth.Value, nil) + return v.dataPoster.PostSimpleTransaction(ctx, auth.Nonce.Uint64(), *v.Address(), data, gas, auth.Value) } // gasForTxData returns auth.GasLimit if it's nonzero, otherwise returns estimate. diff --git a/staker/validatorwallet/eoa.go b/staker/validatorwallet/eoa.go index 44af5e2b60..3ae305b36c 100644 --- a/staker/validatorwallet/eoa.go +++ b/staker/validatorwallet/eoa.go @@ -6,7 +6,6 @@ package validatorwallet import ( "context" "fmt" - "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -95,7 +94,7 @@ func (w *EOA) postTransaction(ctx context.Context, baseTx *types.Transaction) (* return nil, err } gas := baseTx.Gas() + w.getExtraGas() - newTx, err := w.dataPoster.PostTransaction(ctx, time.Now(), nonce, nil, *baseTx.To(), baseTx.Data(), gas, baseTx.Value(), nil) + newTx, err := w.dataPoster.PostSimpleTransaction(ctx, nonce, *baseTx.To(), baseTx.Data(), gas, baseTx.Value()) if err != nil { return nil, fmt.Errorf("post transaction: %w", err) } From d3d0e0fb1270ce82f82a4a12c57b589fa18e24b7 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 00:25:54 -0700 Subject: [PATCH 02/10] Add support for 4844 batch posting --- arbnode/batch_poster.go | 138 ++++++++++++++++++++++++------ arbnode/dataposter/data_poster.go | 10 ++- arbutil/wait_for_l1.go | 2 + util/blobs/blobs.go | 28 ++++-- 4 files changed, 140 insertions(+), 38 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 01a84b1c43..2dc9bac340 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -18,15 +18,18 @@ import ( "github.com/andybalholm/brotli" "github.com/spf13/pflag" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" @@ -40,6 +43,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/blobs" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -48,7 +52,16 @@ import ( var ( batchPosterWalletBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/wallet/balanceether", nil) batchPosterGasRefunderBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/gasrefunder/balanceether", nil) + + usableBytesInBlob = big.NewInt(int64(len(kzg4844.Blob{}) * 31 / 32)) + blobTxBlobGasPerBlob = big.NewInt(params.BlobTxBlobGasPerBlob) +) + +const ( batchPosterSimpleRedisLockKey = "node.batch-poster.redis-lock.simple-lock-key" + + sequencerBatchPostMethodName = "addSequencerL2BatchFromOrigin0" + sequencerBatchPostWithBlobsMethodName = "addSequencerL2BatchFromBlob" // TODO: this will probably get updated to have a plural name ) type batchPosterPosition struct { @@ -119,6 +132,7 @@ type BatchPosterConfig struct { RedisUrl string `koanf:"redis-url"` RedisLock redislock.SimpleCfg `koanf:"redis-lock" reload:"hot"` ExtraBatchGas uint64 `koanf:"extra-batch-gas" reload:"hot"` + Post4844Blobs bool `koanf:"post-4844-blobs" reload:"hot"` ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"` L1BlockBound string `koanf:"l1-block-bound" reload:"hot"` L1BlockBoundBypass time.Duration `koanf:"l1-block-bound-bypass" reload:"hot"` @@ -166,6 +180,7 @@ func BatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Duration(prefix+".das-retention-period", DefaultBatchPosterConfig.DASRetentionPeriod, "In AnyTrust mode, the period which DASes are requested to retain the stored batches.") f.String(prefix+".gas-refunder-address", DefaultBatchPosterConfig.GasRefunderAddress, "The gas refunder contract address (optional)") f.Uint64(prefix+".extra-batch-gas", DefaultBatchPosterConfig.ExtraBatchGas, "use this much more gas than estimation says is necessary to post batches") + f.Bool(prefix+".post-4844-blobs", DefaultBatchPosterConfig.Post4844Blobs, "if the parent chain supports 4844 blobs and they're well priced, post EIP-4844 blobs") f.String(prefix+".redis-url", DefaultBatchPosterConfig.RedisUrl, "if non-empty, the Redis URL to store queued transactions in") f.String(prefix+".l1-block-bound", DefaultBatchPosterConfig.L1BlockBound, "only post messages to batches when they're within the max future block/timestamp as of this L1 block tag (\"safe\", \"finalized\", \"latest\", or \"ignore\" to ignore this check)") f.Duration(prefix+".l1-block-bound-bypass", DefaultBatchPosterConfig.L1BlockBoundBypass, "post batches even if not within the layer 1 future bounds if we're within this margin of the max delay") @@ -188,6 +203,7 @@ var DefaultBatchPosterConfig = BatchPosterConfig{ DASRetentionPeriod: time.Hour * 24 * 15, GasRefunderAddress: "", ExtraBatchGas: 50_000, + Post4844Blobs: true, DataPoster: dataposter.DefaultDataPosterConfig, ParentChainWallet: DefaultBatchPosterL1WalletConfig, L1BlockBound: "", @@ -215,6 +231,7 @@ var TestBatchPosterConfig = BatchPosterConfig{ DASRetentionPeriod: time.Hour * 24 * 15, GasRefunderAddress: "", ExtraBatchGas: 10_000, + Post4844Blobs: true, DataPoster: dataposter.TestDataPosterConfig, ParentChainWallet: DefaultBatchPosterL1WalletConfig, L1BlockBound: "", @@ -753,30 +770,73 @@ func (s *batchSegments) CloseAndGetBytes() ([]byte, error) { return fullMsg, nil } -func (b *BatchPoster) encodeAddBatch(seqNum *big.Int, prevMsgNum arbutil.MessageIndex, newMsgNum arbutil.MessageIndex, message []byte, delayedMsg uint64) ([]byte, error) { - method, ok := b.seqInboxABI.Methods["addSequencerL2BatchFromOrigin0"] +func (b *BatchPoster) encodeAddBatch( + seqNum *big.Int, + prevMsgNum arbutil.MessageIndex, + newMsgNum arbutil.MessageIndex, + l2MessageData []byte, + delayedMsg uint64, + use4844 bool, +) ([]byte, []kzg4844.Blob, error) { + methodName := sequencerBatchPostMethodName + if use4844 { + methodName = sequencerBatchPostWithBlobsMethodName + } + method, ok := b.seqInboxABI.Methods[methodName] if !ok { - return nil, errors.New("failed to find add batch method") - } - inputData, err := method.Inputs.Pack( - seqNum, - message, - new(big.Int).SetUint64(delayedMsg), - b.config().gasRefunder, - new(big.Int).SetUint64(uint64(prevMsgNum)), - new(big.Int).SetUint64(uint64(newMsgNum)), - ) + return nil, nil, errors.New("failed to find add batch method") + } + var calldata []byte + var kzgBlobs []kzg4844.Blob + var err error + if use4844 { + kzgBlobs, err = blobs.EncodeBlobs(l2MessageData) + if err != nil { + return nil, nil, fmt.Errorf("failed to encode blobs: %w", err) + } + // EIP4844 transactions to the sequencer inbox will not use transaction calldata for L2 info. + calldata, err = method.Inputs.Pack( + seqNum, + new(big.Int).SetUint64(delayedMsg), + b.config().gasRefunder, + new(big.Int).SetUint64(uint64(prevMsgNum)), + new(big.Int).SetUint64(uint64(newMsgNum)), + ) + } else { + calldata, err = method.Inputs.Pack( + seqNum, + l2MessageData, + new(big.Int).SetUint64(delayedMsg), + b.config().gasRefunder, + new(big.Int).SetUint64(uint64(prevMsgNum)), + new(big.Int).SetUint64(uint64(newMsgNum)), + ) + } if err != nil { - return nil, err + return nil, nil, err } - fullData := append([]byte{}, method.ID...) - fullData = append(fullData, inputData...) - return fullData, nil + fullCalldata := append([]byte{}, method.ID...) + fullCalldata = append(fullCalldata, calldata...) + return fullCalldata, kzgBlobs, nil } var ErrNormalGasEstimationFailed = errors.New("normal gas estimation failed") -func (b *BatchPoster) estimateGas(ctx context.Context, sequencerMessage []byte, delayedMessages uint64, realData []byte, realNonce uint64, realAccessList types.AccessList) (uint64, error) { +type estimateGasParams struct { + From common.Address `json:"from"` + To *common.Address `json:"to"` + Data []byte `json:"data"` + AccessList types.AccessList `json:"accessList"` + BlobHashes []common.Hash `json:"blobVersionedHashes"` +} + +func estimateGas(client rpc.ClientInterface, ctx context.Context, params estimateGasParams) (uint64, error) { + var gas uint64 + err := client.CallContext(ctx, &gas, "eth_estimateGas", params) + return gas, err +} + +func (b *BatchPoster) estimateGas(ctx context.Context, sequencerMessage []byte, delayedMessages uint64, realData []byte, realBlobs []kzg4844.Blob, realNonce uint64, realAccessList types.AccessList) (uint64, error) { config := b.config() useNormalEstimation := b.dataPoster.MaxMempoolTransactions() == 1 if !useNormalEstimation { @@ -787,12 +847,18 @@ func (b *BatchPoster) estimateGas(ctx context.Context, sequencerMessage []byte, } useNormalEstimation = latestNonce == realNonce } + rawRpcClient := b.l1Reader.Client().Client() if useNormalEstimation { + _, realBlobHashes, err := blobs.ComputeCommitmentsAndHashes(realBlobs) + if err != nil { + return 0, fmt.Errorf("failed to compute real blob commitments: %w", err) + } // If we're at the latest nonce, we can skip the special future tx estimate stuff - gas, err := b.l1Reader.Client().EstimateGas(ctx, ethereum.CallMsg{ + gas, err := estimateGas(rawRpcClient, ctx, estimateGasParams{ From: b.dataPoster.Sender(), To: &b.seqInboxAddr, Data: realData, + BlobHashes: realBlobHashes, AccessList: realAccessList, }) if err != nil { @@ -805,14 +871,19 @@ func (b *BatchPoster) estimateGas(ctx context.Context, sequencerMessage []byte, // However, we set nextMsgNum to 1 because it is necessary for a correct estimation for the final to be non-zero. // Because we're likely estimating against older state, this might not be the actual next message, // but the gas used should be the same. - data, err := b.encodeAddBatch(abi.MaxUint256, 0, 1, sequencerMessage, delayedMessages) + data, kzgBlobs, err := b.encodeAddBatch(abi.MaxUint256, 0, 1, sequencerMessage, delayedMessages, len(realBlobs) > 0) if err != nil { return 0, err } - gas, err := b.l1Reader.Client().EstimateGas(ctx, ethereum.CallMsg{ - From: b.dataPoster.Sender(), - To: &b.seqInboxAddr, - Data: data, + _, blobHashes, err := blobs.ComputeCommitmentsAndHashes(kzgBlobs) + if err != nil { + return 0, fmt.Errorf("failed to compute blob commitments: %w", err) + } + gas, err := estimateGas(rawRpcClient, ctx, estimateGasParams{ + From: b.dataPoster.Sender(), + To: &b.seqInboxAddr, + Data: data, + BlobHashes: blobHashes, // This isn't perfect because we're probably estimating the batch at a different sequence number, // but it should overestimate rather than underestimate which is fine. AccessList: realAccessList, @@ -1039,7 +1110,20 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } } - data, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg) + latestHeader, err := b.l1Reader.LastHeader(ctx) + if err != nil { + return false, err + } + var use4844 bool + if config.Post4844Blobs && latestHeader.ExcessBlobGas != nil { + blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) + blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) + blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) + + calldataFeePerByte := arbmath.BigMulByUint(latestHeader.BaseFee, 16) + use4844 = arbmath.BigLessThan(blobFeePerByte, calldataFeePerByte) + } + data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, use4844) if err != nil { return false, err } @@ -1051,7 +1135,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) // In theory, this might reduce gas usage, but only by a factor that's already // accounted for in `config.ExtraBatchGas`, as that same factor can appear if a user // posts a new delayed message that we didn't see while gas estimating. - gasLimit, err := b.estimateGas(ctx, sequencerMsg, lastPotentialMsg.DelayedMessagesRead, data, nonce, accessList) + gasLimit, err := b.estimateGas(ctx, sequencerMsg, lastPotentialMsg.DelayedMessagesRead, data, kzgBlobs, nonce, accessList) if err != nil { return false, err } @@ -1071,7 +1155,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) data, gasLimit, new(big.Int), - nil, // TODO: use blobs + kzgBlobs, accessList, ) if err != nil { diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 425dba8e18..ba9c278ba5 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -464,7 +464,7 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u } newBlobFeeCap := big.NewInt(0) if latestHeader.ExcessBlobGas != nil { - newBlobFeeCap = eip4844.CalcBlobFee(*latestHeader.ExcessBlobGas) + newBlobFeeCap = eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) newBlobFeeCap.Mul(newBlobFeeCap, common.Big2) } else if numBlobs > 0 { return nil, nil, nil, fmt.Errorf("latest parent chain block %v missing ExcessBlobGas but blobs were specified in data poster transaction (either the parent chain node is not synced or EIP-4844 was improperly activated)", latestHeader.Number) @@ -612,9 +612,13 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim // Intentionally break out of date data poster redis clients, // so they don't try to replace by fee a tx they don't understand deprecatedData.Nonce = ^uint64(0) - commitments, proofs, blobHashes, err := blobs.ComputeCommitmentsProofsAndHashes(kzgBlobs) + commitments, blobHashes, err := blobs.ComputeCommitmentsAndHashes(kzgBlobs) if err != nil { - return nil, fmt.Errorf("failed to compute KZG metadata: %w", err) + return nil, fmt.Errorf("failed to compute KZG commitments: %w", err) + } + proofs, err := blobs.ComputeBlobProofs(kzgBlobs, commitments) + if err != nil { + return nil, fmt.Errorf("failed to compute KZG proofs: %w", err) } inner = &types.BlobTx{ Nonce: nonce, diff --git a/arbutil/wait_for_l1.go b/arbutil/wait_for_l1.go index b66710dbf0..9fb2cd10f8 100644 --- a/arbutil/wait_for_l1.go +++ b/arbutil/wait_for_l1.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/rpc" ) type L1Interface interface { @@ -25,6 +26,7 @@ type L1Interface interface { BlockNumber(ctx context.Context) (uint64, error) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) ChainID(ctx context.Context) (*big.Int, error) + Client() rpc.ClientInterface } func SendTxAsCall(ctx context.Context, client L1Interface, tx *types.Transaction, from common.Address, blockNum *big.Int, unlimitedGas bool) ([]byte, error) { diff --git a/util/blobs/blobs.go b/util/blobs/blobs.go index c8025dc253..60cc898751 100644 --- a/util/blobs/blobs.go +++ b/util/blobs/blobs.go @@ -5,6 +5,7 @@ package blobs import ( "crypto/sha256" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/kzg4844" @@ -52,20 +53,15 @@ func DecodeBlobs(blobs []kzg4844.Blob) ([]byte, error) { } // Return KZG commitments, proofs, and versioned hashes that corresponds to these blobs -func ComputeCommitmentsProofsAndHashes(blobs []kzg4844.Blob) ([]kzg4844.Commitment, []kzg4844.Proof, []common.Hash, error) { +func ComputeCommitmentsAndHashes(blobs []kzg4844.Blob) ([]kzg4844.Commitment, []common.Hash, error) { commitments := make([]kzg4844.Commitment, len(blobs)) - proofs := make([]kzg4844.Proof, len(blobs)) versionedHashes := make([]common.Hash, len(blobs)) for i := range blobs { var err error commitments[i], err = kzg4844.BlobToCommitment(blobs[i]) if err != nil { - return nil, nil, nil, err - } - proofs[i], err = kzg4844.ComputeBlobProof(blobs[i], commitments[i]) - if err != nil { - return nil, nil, nil, err + return nil, nil, err } // As per the EIP-4844 spec, the versioned hash is the SHA-256 hash of the commitment with the first byte set to 1. hash := sha256.Sum256(commitments[i][:]) @@ -73,5 +69,21 @@ func ComputeCommitmentsProofsAndHashes(blobs []kzg4844.Blob) ([]kzg4844.Commitme versionedHashes[i] = hash } - return commitments, proofs, versionedHashes, nil + return commitments, versionedHashes, nil +} + +func ComputeBlobProofs(blobs []kzg4844.Blob, commitments []kzg4844.Commitment) ([]kzg4844.Proof, error) { + if len(blobs) != len(commitments) { + return nil, fmt.Errorf("ComputeBlobProofs got %v blobs but %v commitments", len(blobs), len(commitments)) + } + proofs := make([]kzg4844.Proof, len(blobs)) + for i := range blobs { + var err error + proofs[i], err = kzg4844.ComputeBlobProof(blobs[i], commitments[i]) + if err != nil { + return nil, err + } + } + + return proofs, nil } From d229f3c306fb89f5c7193ebbbeb2b8aa165433ce Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 00:30:51 -0700 Subject: [PATCH 03/10] Use updated contracts --- arbnode/batch_poster.go | 2 +- contracts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 2dc9bac340..87170caa8a 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -61,7 +61,7 @@ const ( batchPosterSimpleRedisLockKey = "node.batch-poster.redis-lock.simple-lock-key" sequencerBatchPostMethodName = "addSequencerL2BatchFromOrigin0" - sequencerBatchPostWithBlobsMethodName = "addSequencerL2BatchFromBlob" // TODO: this will probably get updated to have a plural name + sequencerBatchPostWithBlobsMethodName = "addSequencerL2BatchFromBlobs" ) type batchPosterPosition struct { diff --git a/contracts b/contracts index a8e7709bfc..00d4d62578 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit a8e7709bfc918f9b8e2888d47f2fd8454779fd11 +Subproject commit 00d4d6257835ba58bb381ce8d884a819d7ce9448 From 863911649278001d7324e4a000be0f02e871fbb0 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 12:26:19 -0700 Subject: [PATCH 04/10] Fix Data field type in estimateGasParams --- arbnode/batch_poster.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 87170caa8a..b4bf4c807b 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" @@ -825,7 +826,7 @@ var ErrNormalGasEstimationFailed = errors.New("normal gas estimation failed") type estimateGasParams struct { From common.Address `json:"from"` To *common.Address `json:"to"` - Data []byte `json:"data"` + Data hexutil.Bytes `json:"data"` AccessList types.AccessList `json:"accessList"` BlobHashes []common.Hash `json:"blobVersionedHashes"` } From 5cdcde12efbce6fe425df4bdebd7cef842135892 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 16:14:32 -0700 Subject: [PATCH 05/10] Fix raw estimateGas in batch poster --- arbnode/batch_poster.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index b4bf4c807b..28c248043a 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -832,9 +832,9 @@ type estimateGasParams struct { } func estimateGas(client rpc.ClientInterface, ctx context.Context, params estimateGasParams) (uint64, error) { - var gas uint64 + var gas hexutil.Uint64 err := client.CallContext(ctx, &gas, "eth_estimateGas", params) - return gas, err + return uint64(gas), err } func (b *BatchPoster) estimateGas(ctx context.Context, sequencerMessage []byte, delayedMessages uint64, realData []byte, realBlobs []kzg4844.Blob, realNonce uint64, realAccessList types.AccessList) (uint64, error) { From 54ee5c31ffdba4119a124dcd9bce09813f670e52 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 16:14:49 -0700 Subject: [PATCH 06/10] Misc refactors while I was tracking down the previous bug --- arbnode/batch_poster.go | 11 ++++++----- arbnode/dataposter/data_poster.go | 19 ++++++++++--------- system_tests/batch_poster_test.go | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 28c248043a..53d3e7f403 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -828,7 +828,7 @@ type estimateGasParams struct { To *common.Address `json:"to"` Data hexutil.Bytes `json:"data"` AccessList types.AccessList `json:"accessList"` - BlobHashes []common.Hash `json:"blobVersionedHashes"` + BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"` } func estimateGas(client rpc.ClientInterface, ctx context.Context, params estimateGasParams) (uint64, error) { @@ -1164,12 +1164,13 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } log.Info( "BatchPoster: batch sent", - "sequence nr.", batchPosition.NextSeqNum, + "sequenceNumber", batchPosition.NextSeqNum, "from", batchPosition.MessageCount, "to", b.building.msgCount, - "prev delayed", batchPosition.DelayedMessageCount, - "current delayed", b.building.segments.delayedMsg, - "total segments", len(b.building.segments.rawSegments), + "prevDelayed", batchPosition.DelayedMessageCount, + "currentDelayed", b.building.segments.delayedMsg, + "totalSegments", len(b.building.segments.rawSegments), + "numBlobs", len(kzgBlobs), ) recentlyHitL1Bounds := time.Since(b.lastHitL1Bounds) < config.PollInterval*3 postedMessages := b.building.msgCount - batchPosition.MessageCount diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index ba9c278ba5..4f3f514d11 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -636,7 +636,7 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim ChainID: p.parentChainID256, } // reuse the code to convert gas fee and tip caps to uint256s - inner, err = updateTxDataGasCaps(inner, feeCap, tipCap, blobFeeCap) + err = updateTxDataGasCaps(inner, feeCap, tipCap, blobFeeCap) if err != nil { return nil, err } @@ -714,34 +714,35 @@ func (p *DataPoster) sendTx(ctx context.Context, prevTx *storage.QueuedTransacti return p.saveTx(ctx, newTx, &newerTx) } -func updateTxDataGasCaps(data types.TxData, newFeeCap, newTipCap, newBlobFeeCap *big.Int) (types.TxData, error) { +func updateTxDataGasCaps(data types.TxData, newFeeCap, newTipCap, newBlobFeeCap *big.Int) error { switch data := data.(type) { case *types.DynamicFeeTx: data.GasFeeCap = newFeeCap data.GasTipCap = newTipCap - return data, nil + return nil case *types.BlobTx: var overflow bool data.GasFeeCap, overflow = uint256.FromBig(newFeeCap) if overflow { - return nil, fmt.Errorf("blob tx fee cap %v exceeds uint256", newFeeCap) + return fmt.Errorf("blob tx fee cap %v exceeds uint256", newFeeCap) } data.GasTipCap, overflow = uint256.FromBig(newTipCap) if overflow { - return nil, fmt.Errorf("blob tx tip cap %v exceeds uint256", newTipCap) + return fmt.Errorf("blob tx tip cap %v exceeds uint256", newTipCap) } data.BlobFeeCap, overflow = uint256.FromBig(newBlobFeeCap) if overflow { - return nil, fmt.Errorf("blob tx blob fee cap %v exceeds uint256", newBlobFeeCap) + return fmt.Errorf("blob tx blob fee cap %v exceeds uint256", newBlobFeeCap) } - return data, nil + return nil default: - return nil, fmt.Errorf("unexpected transaction data type %T", data) + return fmt.Errorf("unexpected transaction data type %T", data) } } func updateGasCaps(tx *types.Transaction, newFeeCap, newTipCap, newBlobFeeCap *big.Int) (*types.Transaction, error) { - data, err := updateTxDataGasCaps(tx.GetInner(), newFeeCap, newTipCap, newBlobFeeCap) + data := tx.GetInner() + err := updateTxDataGasCaps(data, newFeeCap, newTipCap, newBlobFeeCap) if err != nil { return nil, err } diff --git a/system_tests/batch_poster_test.go b/system_tests/batch_poster_test.go index f7bf74f699..cacbe3cee4 100644 --- a/system_tests/batch_poster_test.go +++ b/system_tests/batch_poster_test.go @@ -180,7 +180,7 @@ func testBatchPosterParallel(t *testing.T, useRedis bool) { } lastTxHash := txs[len(txs)-1].Hash() - for i := 90; i > 0; i-- { + for i := 90; i >= 0; i-- { builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ builder.L1Info.PrepareTx("Faucet", "User", 30000, big.NewInt(1e12), nil), }) From 4925e63acaafc651c64302062f976cd9d4c754fe Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 16:23:23 -0700 Subject: [PATCH 07/10] Add option to force posting 4844 blobs --- arbnode/batch_poster.go | 20 ++++++++++++++------ arbnode/dataposter/data_poster.go | 8 ++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 53d3e7f403..4a07d36521 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -134,6 +134,7 @@ type BatchPosterConfig struct { RedisLock redislock.SimpleCfg `koanf:"redis-lock" reload:"hot"` ExtraBatchGas uint64 `koanf:"extra-batch-gas" reload:"hot"` Post4844Blobs bool `koanf:"post-4844-blobs" reload:"hot"` + ForcePost4844Blobs bool `koanf:"force-post-4844-blobs" reload:"hot"` ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"` L1BlockBound string `koanf:"l1-block-bound" reload:"hot"` L1BlockBoundBypass time.Duration `koanf:"l1-block-bound-bypass" reload:"hot"` @@ -182,6 +183,7 @@ func BatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".gas-refunder-address", DefaultBatchPosterConfig.GasRefunderAddress, "The gas refunder contract address (optional)") f.Uint64(prefix+".extra-batch-gas", DefaultBatchPosterConfig.ExtraBatchGas, "use this much more gas than estimation says is necessary to post batches") f.Bool(prefix+".post-4844-blobs", DefaultBatchPosterConfig.Post4844Blobs, "if the parent chain supports 4844 blobs and they're well priced, post EIP-4844 blobs") + f.Bool(prefix+".force-post-4844-blobs", DefaultBatchPosterConfig.ForcePost4844Blobs, "if the parent chain supports 4844 blobs and post-4844-blobs is true, post 4844 blobs even if it's not price efficient") f.String(prefix+".redis-url", DefaultBatchPosterConfig.RedisUrl, "if non-empty, the Redis URL to store queued transactions in") f.String(prefix+".l1-block-bound", DefaultBatchPosterConfig.L1BlockBound, "only post messages to batches when they're within the max future block/timestamp as of this L1 block tag (\"safe\", \"finalized\", \"latest\", or \"ignore\" to ignore this check)") f.Duration(prefix+".l1-block-bound-bypass", DefaultBatchPosterConfig.L1BlockBoundBypass, "post batches even if not within the layer 1 future bounds if we're within this margin of the max delay") @@ -205,6 +207,7 @@ var DefaultBatchPosterConfig = BatchPosterConfig{ GasRefunderAddress: "", ExtraBatchGas: 50_000, Post4844Blobs: true, + ForcePost4844Blobs: false, DataPoster: dataposter.DefaultDataPosterConfig, ParentChainWallet: DefaultBatchPosterL1WalletConfig, L1BlockBound: "", @@ -233,6 +236,7 @@ var TestBatchPosterConfig = BatchPosterConfig{ GasRefunderAddress: "", ExtraBatchGas: 10_000, Post4844Blobs: true, + ForcePost4844Blobs: false, DataPoster: dataposter.TestDataPosterConfig, ParentChainWallet: DefaultBatchPosterL1WalletConfig, L1BlockBound: "", @@ -1116,13 +1120,17 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) return false, err } var use4844 bool - if config.Post4844Blobs && latestHeader.ExcessBlobGas != nil { - blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) - blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) - blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) + if config.Post4844Blobs && latestHeader.ExcessBlobGas != nil && latestHeader.BlobGasUsed != nil { + if config.ForcePost4844Blobs { + use4844 = true + } else { + blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) + blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) + blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) - calldataFeePerByte := arbmath.BigMulByUint(latestHeader.BaseFee, 16) - use4844 = arbmath.BigLessThan(blobFeePerByte, calldataFeePerByte) + calldataFeePerByte := arbmath.BigMulByUint(latestHeader.BaseFee, 16) + use4844 = arbmath.BigLessThan(blobFeePerByte, calldataFeePerByte) + } } data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, use4844) if err != nil { diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 4f3f514d11..1415f78140 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -463,11 +463,15 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u return nil, nil, nil, fmt.Errorf("latest parent chain block %v missing BaseFee (either the parent chain does not have EIP-1559 or the parent chain node is not synced)", latestHeader.Number) } newBlobFeeCap := big.NewInt(0) - if latestHeader.ExcessBlobGas != nil { + if latestHeader.ExcessBlobGas != nil && latestHeader.BlobGasUsed != nil { newBlobFeeCap = eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) newBlobFeeCap.Mul(newBlobFeeCap, common.Big2) } else if numBlobs > 0 { - return nil, nil, nil, fmt.Errorf("latest parent chain block %v missing ExcessBlobGas but blobs were specified in data poster transaction (either the parent chain node is not synced or EIP-4844 was improperly activated)", latestHeader.Number) + return nil, nil, nil, fmt.Errorf( + "latest parent chain block %v missing ExcessBlobGas or BlobGasUsed but blobs were specified in data poster transaction "+ + "(either the parent chain node is not synced or the EIP-4844 was improperly activated)", + latestHeader.Number, + ) } softConfBlock := arbmath.BigSubByUint(latestHeader.Number, config.NonceRbfSoftConfs) softConfNonce, err := p.client.NonceAt(ctx, p.Sender(), softConfBlock) From 2ba1c490c3fb0fef695ade891e102da167350c0e Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 18:14:17 -0700 Subject: [PATCH 08/10] Update contracts to support zero basefee for gas estimation --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 00d4d62578..e253b8b1b5 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 00d4d6257835ba58bb381ce8d884a819d7ce9448 +Subproject commit e253b8b1b5865f135ac63ea3d3cea1bfe8ef2ad7 From fe9fce1c995734b2fcedb4c0d8658a578156a43e Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 23 Jan 2024 18:55:45 -0700 Subject: [PATCH 09/10] Update go-ethereum pin to fix trusted setup --- go-ethereum | 2 +- go.mod | 6 +++--- go.sum | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go-ethereum b/go-ethereum index c4b3aa68a0..eca6e11dad 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit c4b3aa68a05f468e0c30147f9383bfc76d82388f +Subproject commit eca6e11dad2c7f8cd1276e38678afec271323422 diff --git a/go.mod b/go.mod index e38f3209dd..d50090f6c1 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,6 @@ require ( github.com/libp2p/go-libp2p v0.27.8 github.com/multiformats/go-multiaddr v0.12.1 github.com/multiformats/go-multihash v0.2.3 - github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v3 v3.0.1 github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703 github.com/spf13/pflag v1.0.5 @@ -90,7 +89,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect - github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect @@ -103,7 +102,7 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -234,6 +233,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/openzipkin/zipkin-go v0.4.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect diff --git a/go.sum b/go.sum index 872afcafbf..d066f85214 100644 --- a/go.sum +++ b/go.sum @@ -264,8 +264,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= -github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= -github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= @@ -338,8 +338,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= -github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= From 03b2fe8a801d5429b759603a1fd12d9f8d4186f6 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Wed, 24 Jan 2024 08:13:20 -0700 Subject: [PATCH 10/10] Add separate max batch size for 4844 --- arbnode/batch_poster.go | 66 ++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 4a07d36521..65ef76e0a0 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -118,6 +118,8 @@ type BatchPosterConfig struct { DisableDasFallbackStoreDataOnChain bool `koanf:"disable-das-fallback-store-data-on-chain" reload:"hot"` // Max batch size. MaxSize int `koanf:"max-size" reload:"hot"` + // Maximum 4844 blob enabled batch size. + Max4844BatchSize int `koanf:"max-4844-batch-size" reload:"hot"` // Max batch post delay. MaxDelay time.Duration `koanf:"max-delay" reload:"hot"` // Wait for max BatchPost delay. @@ -174,6 +176,7 @@ func BatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBatchPosterConfig.Enable, "enable posting batches to l1") f.Bool(prefix+".disable-das-fallback-store-data-on-chain", DefaultBatchPosterConfig.DisableDasFallbackStoreDataOnChain, "If unable to batch to DAS, disable fallback storing data on chain") f.Int(prefix+".max-size", DefaultBatchPosterConfig.MaxSize, "maximum batch size") + f.Int(prefix+".max-4844-batch-size", DefaultBatchPosterConfig.Max4844BatchSize, "maximum 4844 blob enabled batch size") f.Duration(prefix+".max-delay", DefaultBatchPosterConfig.MaxDelay, "maximum batch posting delay") f.Bool(prefix+".wait-for-max-delay", DefaultBatchPosterConfig.WaitForMaxDelay, "wait for the max batch delay, even if the batch is full") f.Duration(prefix+".poll-interval", DefaultBatchPosterConfig.PollInterval, "how long to wait after no batches are ready to be posted before checking again") @@ -197,7 +200,9 @@ var DefaultBatchPosterConfig = BatchPosterConfig{ Enable: false, DisableDasFallbackStoreDataOnChain: false, // This default is overridden for L3 chains in applyChainParameters in cmd/nitro/nitro.go - MaxSize: 100000, + MaxSize: 100000, + // TODO: is 1000 bytes an appropriate margin for error vs blob space efficiency? + Max4844BatchSize: (254 * params.BlobTxFieldElementsPerBlob / 8 * (params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob)) - 1000, PollInterval: time.Second * 10, ErrorDelay: time.Second * 10, MaxDelay: time.Hour, @@ -227,6 +232,7 @@ var DefaultBatchPosterL1WalletConfig = genericconf.WalletConfig{ var TestBatchPosterConfig = BatchPosterConfig{ Enable: true, MaxSize: 100000, + Max4844BatchSize: DefaultBatchPosterConfig.Max4844BatchSize, PollInterval: time.Millisecond * 10, ErrorDelay: time.Millisecond * 10, MaxDelay: 0, @@ -552,13 +558,20 @@ type buildingBatch struct { startMsgCount arbutil.MessageIndex msgCount arbutil.MessageIndex haveUsefulMessage bool + use4844 bool } -func newBatchSegments(firstDelayed uint64, config *BatchPosterConfig, backlog uint64) *batchSegments { - compressedBuffer := bytes.NewBuffer(make([]byte, 0, config.MaxSize*2)) - if config.MaxSize <= 40 { - panic("MaxBatchSize too small") +func newBatchSegments(firstDelayed uint64, config *BatchPosterConfig, backlog uint64, use4844 bool) *batchSegments { + maxSize := config.MaxSize + if use4844 { + maxSize = config.Max4844BatchSize + } else { + if maxSize <= 40 { + panic("Maximum batch size too small") + } + maxSize -= 40 } + compressedBuffer := bytes.NewBuffer(make([]byte, 0, maxSize*2)) compressionLevel := config.CompressionLevel recompressionLevel := config.CompressionLevel if backlog > 20 { @@ -582,7 +595,7 @@ func newBatchSegments(firstDelayed uint64, config *BatchPosterConfig, backlog ui return &batchSegments{ compressedBuffer: compressedBuffer, compressedWriter: brotli.NewWriterLevel(compressedBuffer, compressionLevel), - sizeLimit: config.MaxSize - 40, // TODO + sizeLimit: maxSize, recompressionLevel: recompressionLevel, rawSegments: make([][]byte, 0, 128), delayedMsg: firstDelayed, @@ -936,10 +949,29 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } if b.building == nil || b.building.startMsgCount != batchPosition.MessageCount { + latestHeader, err := b.l1Reader.LastHeader(ctx) + if err != nil { + return false, err + } + var use4844 bool + config := b.config() + if config.Post4844Blobs && latestHeader.ExcessBlobGas != nil && latestHeader.BlobGasUsed != nil { + if config.ForcePost4844Blobs { + use4844 = true + } else { + blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) + blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) + blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) + + calldataFeePerByte := arbmath.BigMulByUint(latestHeader.BaseFee, 16) + use4844 = arbmath.BigLessThan(blobFeePerByte, calldataFeePerByte) + } + } b.building = &buildingBatch{ - segments: newBatchSegments(batchPosition.DelayedMessageCount, b.config(), b.GetBacklogEstimate()), + segments: newBatchSegments(batchPosition.DelayedMessageCount, b.config(), b.GetBacklogEstimate(), use4844), msgCount: batchPosition.MessageCount, startMsgCount: batchPosition.MessageCount, + use4844: use4844, } } msgCount, err := b.streamer.GetMessageCount() @@ -1115,26 +1147,12 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } } - latestHeader, err := b.l1Reader.LastHeader(ctx) + data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, b.building.use4844) if err != nil { return false, err } - var use4844 bool - if config.Post4844Blobs && latestHeader.ExcessBlobGas != nil && latestHeader.BlobGasUsed != nil { - if config.ForcePost4844Blobs { - use4844 = true - } else { - blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*latestHeader.ExcessBlobGas, *latestHeader.BlobGasUsed)) - blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) - blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) - - calldataFeePerByte := arbmath.BigMulByUint(latestHeader.BaseFee, 16) - use4844 = arbmath.BigLessThan(blobFeePerByte, calldataFeePerByte) - } - } - data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, use4844) - if err != nil { - return false, err + if len(kzgBlobs)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { + return false, fmt.Errorf("produced %v blobs for batch but a block can only hold %v", len(kzgBlobs), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) } accessList := b.accessList(int(batchPosition.NextSeqNum), int(b.building.segments.delayedMsg)) // On restart, we may be trying to estimate gas for a batch whose successor has