Skip to content

Commit

Permalink
add ability to bound the max l1 data size
Browse files Browse the repository at this point in the history
  • Loading branch information
roberto-bayardo committed Oct 29, 2024
1 parent 11064f6 commit 15ecc21
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 10 deletions.
19 changes: 17 additions & 2 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,15 +546,19 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]

// Convert the new uint256.Int types to the old big.Int ones used by the legacy pool
var (
minTipBig *big.Int
baseFeeBig *big.Int
minTipBig *big.Int
baseFeeBig *big.Int
maxDASizeScaled *big.Int
)
if filter.MinTip != nil {
minTipBig = filter.MinTip.ToBig()
}
if filter.BaseFee != nil {
baseFeeBig = filter.BaseFee.ToBig()
}
if filter.MaxDATxSize != nil {
maxDASizeScaled = new(big.Int).Mul(filter.MaxDATxSize, big.NewInt(1e6))
}
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
for addr, list := range pool.pending {
txs := list.Flatten()
Expand All @@ -568,9 +572,19 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
}
}
}
if filter.MaxDATxSize != nil && !pool.locals.contains(addr) {
for i, tx := range txs {
if types.EstimatedL1SizeScaled(tx.RollupCostData()).Cmp(maxDASizeScaled) > 0 {
txs = txs[:i]
break
}
}
}
if len(txs) > 0 {
lazies := make([]*txpool.LazyTransaction, len(txs))
for i := 0; i < len(txs); i++ {
daBytes := types.EstimatedL1SizeScaled(txs[i].RollupCostData())
daBytes.Div(daBytes, big.NewInt(1e6))
lazies[i] = &txpool.LazyTransaction{
Pool: pool,
Hash: txs[i].Hash(),
Expand All @@ -580,6 +594,7 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()),
Gas: txs[i].Gas(),
BlobGas: txs[i].BlobGas(),
DABytes: daBytes,
}
}
pending[addr] = lazies
Expand Down
6 changes: 6 additions & 0 deletions core/txpool/subpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type LazyTransaction struct {

Gas uint64 // Amount of gas required by the transaction
BlobGas uint64 // Amount of blob gas required by the transaction

DABytes *big.Int // Amount of data availability bytes this transaction may require if this is a rollup
}

// Resolve retrieves the full transaction belonging to a lazy handle if it is still
Expand Down Expand Up @@ -83,6 +85,10 @@ type PendingFilter struct {

OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling)
OnlyBlobTxs bool // Return only blob transactions (block blob-space filling)

// OP stack addition: Maximum l1 data size allowed for an included transaction (for throttling
// when batcher is backlogged). Ignored if nil.
MaxDATxSize *big.Int
}

// SubPool represents a specialized transaction pool that lives on its own (e.g.
Expand Down
7 changes: 7 additions & 0 deletions eth/api_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool {
api.e.Miner().SetGasCeil(uint64(gasLimit))
return true
}

// SetMaxDASize sets the maximum data availability size of any tx allowed in a block, and the total max l1 data size of
// the block. 0 means no maximum.
func (api *MinerAPI) SetMaxDASize(maxTxSize hexutil.Big, maxBlockSize hexutil.Big) bool {
api.e.Miner().SetMaxDASize((*big.Int)(&maxTxSize), (*big.Int)(&maxBlockSize))
return true
}
6 changes: 6 additions & 0 deletions internal/web3ext/web3ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ web3._extend({
params: 1,
inputFormatter: [web3._extend.utils.fromDecimal]
}),
new web3._extend.Method({
name: 'setMaxDASize',
call: 'miner_setMaxDASize',
params: 2,
inputFormatter: [web3._extend.utils.fromDecimal, web3._extend.utils.fromDecimal]
}),
],
properties: []
});
Expand Down
20 changes: 19 additions & 1 deletion miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ type Config struct {
RollupComputePendingBlock bool // Compute the pending block from tx-pool, instead of copying the latest-block
RollupTransactionConditionalRateLimit int // Total number of conditional cost units allowed in a second

EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value
EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value
MaxDATxSize *big.Int // if non-nil, don't include any txs with data availability size larger than this in any built block
MaxDABlockSize *big.Int // if non-nil, then don't build a block requiring more than this amount of total data availability
}

// DefaultConfig contains default settings for miner.
Expand Down Expand Up @@ -152,6 +154,22 @@ func (miner *Miner) SetGasTip(tip *big.Int) error {
return nil
}

// SetMaxDASize sets the maximum data availability size currently allowed for inclusion. 0 means no maximum.
func (miner *Miner) SetMaxDASize(maxTxSize, maxBlockSize *big.Int) {
miner.confMu.Lock()
if maxTxSize == nil || maxTxSize.BitLen() == 0 {
miner.config.MaxDATxSize = nil
} else {
miner.config.MaxDATxSize = new(big.Int).Set(maxTxSize)
}
if maxBlockSize == nil || maxBlockSize.BitLen() == 0 {
miner.config.MaxDABlockSize = nil
} else {
miner.config.MaxDABlockSize = new(big.Int).Set(maxBlockSize)
}
miner.confMu.Unlock()
}

// BuildPayload builds the payload according to the provided parameters.
func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload, error) {
return miner.buildPayload(args, witness)
Expand Down
59 changes: 53 additions & 6 deletions miner/payload_building_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package miner

import (
"bytes"
"crypto/rand"
"math/big"
"reflect"
"testing"
Expand Down Expand Up @@ -65,7 +66,7 @@ var (
testConfig = Config{
PendingFeeRecipient: testBankAddress,
Recommit: time.Second,
GasCeil: params.GenesisGasLimit,
GasCeil: 50_000_000,
}
)

Expand Down Expand Up @@ -150,7 +151,7 @@ func (b *testWorkerBackend) TxPool() *txpool.TxPool { return b.txPool }

func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*Miner, *testWorkerBackend) {
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
backend.txPool.Add(pendingTxs, true, true)
backend.txPool.Add(pendingTxs, false, true)
w := New(backend, testConfig, engine)
return w, backend
}
Expand All @@ -173,6 +174,15 @@ func TestBuildPayload(t *testing.T) {
t.Run("with-zero-params", func(t *testing.T) { testBuildPayload(t, true, false, zeroParams) })
}

func TestDAFilters(t *testing.T) {
t.Run("with-tx-filter-max-filters-all", func(t *testing.T) { testDAFilters(t, big.NewInt(1), nil, 0) })
t.Run("with-block-filter-max-filters-all", func(t *testing.T) { testDAFilters(t, nil, big.NewInt(1), 0) })
t.Run("with-tx-filter-max-too-high", func(t *testing.T) { testDAFilters(t, big.NewInt(1000000), nil, 257) })
t.Run("with-block-filter-max-too-high", func(t *testing.T) { testDAFilters(t, nil, big.NewInt(1000000), 257) })
t.Run("with-tx-filter-all-but-first", func(t *testing.T) { testDAFilters(t, big.NewInt(100), nil, 1) })
t.Run("with-block-filter-all-but-first", func(t *testing.T) { testDAFilters(t, nil, big.NewInt(100), 1) })
}

func holoceneConfig() *params.ChainConfig {
config := *params.TestChainConfig
config.LondonBlock = big.NewInt(0)
Expand Down Expand Up @@ -204,6 +214,7 @@ func newPayloadArgs(parentHash common.Hash, params1559 []byte) *BuildPayloadArgs
func testBuildPayload(t *testing.T, noTxPool, interrupt bool, params1559 []byte) {
t.Parallel()
db := rawdb.NewMemoryDatabase()

config := params.TestChainConfig
if len(params1559) != 0 {
config = holoceneConfig()
Expand All @@ -215,7 +226,7 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool, params1559 []byte)
// when doing interrupt testing, create a large pool so interruption will
// definitely be visible.
txs := genTxs(1, numInterruptTxs)
b.txPool.Add(txs, true, false)
b.txPool.Add(txs, false, false)
}

args := newPayloadArgs(b.chain.CurrentBlock().Hash(), params1559)
Expand Down Expand Up @@ -294,6 +305,31 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool, params1559 []byte)
}
}

func testDAFilters(t *testing.T, maxDATxSize, maxDABlockSize *big.Int, expectedTxCount int) {
t.Parallel()
db := rawdb.NewMemoryDatabase()
config := holoceneConfig()
w, b := newTestWorker(t, config, ethash.NewFaker(), db, 0)
w.SetMaxDASize(maxDATxSize, maxDABlockSize)
const numTxs = 256
txs := genTxs(1, numTxs)
b.txPool.Add(txs, false, false)

params1559 := []byte{0, 1, 2, 3, 4, 5, 6, 7}
args := newPayloadArgs(b.chain.CurrentBlock().Hash(), params1559)
args.NoTxPool = false

payload, err := w.buildPayload(args, false)
if err != nil {
t.Fatalf("Failed to build payload %v", err)
}
payload.WaitFull()
result := payload.ResolveFull().ExecutionPayload
if len(result.Transactions) != expectedTxCount {
t.Fatalf("Unexpected transaction set: got %d, expected %d", len(result.Transactions), expectedTxCount)
}
}

func testBuildPayloadWrongConfig(t *testing.T, params1559 []byte) {
t.Parallel()
db := rawdb.NewMemoryDatabase()
Expand Down Expand Up @@ -331,14 +367,25 @@ func genTxs(startNonce, count uint64) types.Transactions {
txs := make(types.Transactions, 0, count)
signer := types.LatestSigner(params.TestChainConfig)
for nonce := startNonce; nonce < startNonce+count; nonce++ {
txs = append(txs, types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{
// generate incompressible data to put in the tx for DA filter testing. each of these
// txs will be bigger than the 100 minimum.
randomBytes := make([]byte, 100)
_, err := rand.Read(randomBytes)
if err != nil {
panic(err)
}
tx := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{
ChainID: params.TestChainConfig.ChainID,
Nonce: nonce,
To: &testUserAddress,
Value: big.NewInt(1000),
Gas: params.TxGas,
Gas: params.TxGas + uint64(len(randomBytes))*16,
GasPrice: big.NewInt(params.InitialBaseFee),
}))
Data: randomBytes,
})
daBytes := types.EstimatedL1SizeScaled(tx.RollupCostData())
daBytes.Div(daBytes, big.NewInt(1e6))
txs = append(txs, tx)
}
return txs
}
Expand Down
13 changes: 12 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit)
}
blockDABytes := new(big.Int)
for {
// Check interruption signal and abort building if it's fired.
if interrupt != nil {
Expand Down Expand Up @@ -468,6 +469,14 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
txs.Pop()
continue
}
daBytesAfter := new(big.Int)
if ltx.DABytes != nil && miner.config.MaxDABlockSize != nil {
daBytesAfter.Add(blockDABytes, ltx.DABytes)
if daBytesAfter.Cmp(miner.config.MaxDABlockSize) > 0 {
txs.Pop()
continue
}
}
// Transaction seems to fit, pull it up from the pool
tx := ltx.Resolve()
if tx == nil {
Expand Down Expand Up @@ -507,6 +516,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran

case errors.Is(err, nil):
// Everything ok, collect the logs and shift in the next transaction from the same account
blockDABytes = daBytesAfter
txs.Shift()

default:
Expand All @@ -529,7 +539,8 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)

// Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees
filter := txpool.PendingFilter{
MinTip: uint256.MustFromBig(tip),
MinTip: uint256.MustFromBig(tip),
MaxDATxSize: miner.config.MaxDATxSize,
}
if env.header.BaseFee != nil {
filter.BaseFee = uint256.MustFromBig(env.header.BaseFee)
Expand Down

0 comments on commit 15ecc21

Please sign in to comment.