From fa08c910fbd187d9e2848e25082992f99e58449a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 May 2025 09:13:53 +0200 Subject: [PATCH 1/6] feat(blobpool): add explicit max blob count limit --- core/txpool/blobpool/blobpool.go | 15 +++++-- core/txpool/blobpool/blobpool_test.go | 58 +++++++++++++++++++++++++++ core/txpool/legacypool/legacypool.go | 5 ++- core/txpool/validation.go | 10 +++-- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index e506da228d99..2602d821040a 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -62,6 +62,12 @@ const ( // limit can never hurt. txMaxSize = 1024 * 1024 + // maxBlobsPerTx is the maximum number of blobs that a single transaction can + // carry. We choose a smaller limit than the protocol-permitted MaxBlobsPerBlock + // in order to ensure network and txpool stability. + // Note: if you increase this, validation will fail on txMaxSize. + maxBlobsPerTx = 7 + // maxTxsPerAccount is the maximum number of blob transactions admitted from // a single account. The limit is enforced to minimize the DoS potential of // a private tx cancelling publicly propagated blobs. @@ -1095,10 +1101,11 @@ func (p *BlobPool) SetGasTip(tip *big.Int) { // and does not require the pool mutex to be held. func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error { opts := &txpool.ValidationOptions{ - Config: p.chain.Config(), - Accept: 1 << types.BlobTxType, - MaxSize: txMaxSize, - MinTip: p.gasTip.ToBig(), + Config: p.chain.Config(), + Accept: 1 << types.BlobTxType, + MaxSize: txMaxSize, + MinTip: p.gasTip.ToBig(), + MaxBlobCount: maxBlobsPerTx, } return txpool.ValidateTransaction(tx, p.head, p.signer, opts) } diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 0a323179a6af..d9236aee4404 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -43,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/billy" "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" ) var ( @@ -1142,6 +1143,63 @@ func TestChangingSlotterSize(t *testing.T) { } } +// TestBlobCountLimit tests the blobpool enforced limits on the max blob count. +func TestBlobCountLimit(t *testing.T) { + // Create a temporary folder for the persistent backend + storage := t.TempDir() + + // Create transactions from a few accounts. + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2) + ) + + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true, false) + + // Make Prague-enabled custom chain config. + cancunTime := uint64(0) + pragueTime := uint64(0) + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: &cancunTime, + PragueTime: &pragueTime, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + }, + } + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain, nil) + if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + + // Attempt to add transactions. + errs := pool.Add([]*types.Transaction{tx1, tx2}, true) + assert.Equal(t, 2, len(errs)) + assert.NoError(t, errs[0]) + assert.EqualError(t, errs[1], "too many blobs in transaction: have 8, permitted 7") + + verifyPoolInternals(t, pool) + pool.Close() +} + // Tests that adding transaction will correctly store it in the persistent store // and update all the indices. // diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 0223b456e659..e878eb4fbfca 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -571,8 +571,9 @@ func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error { 1< opts.MaxBlobCount { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", blobCount, opts.MaxBlobCount) + } // Before performing any expensive validations, sanity check that the tx is // smaller than the maximum limit the pool can meaningfully handle if tx.Size() > opts.MaxSize { From 27f0efeba127378eca689f740ec644e4552e6059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 19 May 2025 10:24:42 +0200 Subject: [PATCH 2/6] add new error --- core/txpool/errors.go | 4 ++++ core/txpool/validation.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 968c9d954238..9bc435d67ee7 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -58,6 +58,10 @@ var ( // making the transaction invalid, rather a DOS protection. ErrOversizedData = errors.New("oversized data") + // ErrTxBlobLimitExceeded is returned if a transaction would exceed the number + // of blobs allowed by blobpool. + ErrTxBlobLimitExceeded = errors.New("transaction blob limit exceeded") + // ErrAlreadyReserved is returned if the sender address has a pending transaction // in a different subpool. For example, this error is returned in response to any // input transaction of non-blob type when a blob transaction from this sender diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 5321164418cb..720d0d3b722c 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -65,7 +65,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type()) } if blobCount := len(tx.BlobHashes()); blobCount > opts.MaxBlobCount { - return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", blobCount, opts.MaxBlobCount) + return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, blobCount, opts.MaxBlobCount) } // Before performing any expensive validations, sanity check that the tx is // smaller than the maximum limit the pool can meaningfully handle From 77efa170ac89fef321b8fa96e206d234b43b7818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 19 May 2025 10:26:27 +0200 Subject: [PATCH 3/6] update error message in test --- core/txpool/blobpool/blobpool_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index d9236aee4404..97bb971a207f 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -1194,7 +1194,7 @@ func TestBlobCountLimit(t *testing.T) { errs := pool.Add([]*types.Transaction{tx1, tx2}, true) assert.Equal(t, 2, len(errs)) assert.NoError(t, errs[0]) - assert.EqualError(t, errs[1], "too many blobs in transaction: have 8, permitted 7") + assert.EqualError(t, errs[1], "transaction blob limit exceeded: blob count 8, limit 7") verifyPoolInternals(t, pool) pool.Close() From 3c3df888dccd1e2930902884532d7d6a81da30c4 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 21 May 2025 10:50:57 -0600 Subject: [PATCH 4/6] core/txpool/blobpool: remove use of assert in tests --- core/txpool/blobpool/blobpool_test.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 97bb971a207f..316ef0a40da8 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -43,7 +43,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/billy" "github.com/holiman/uint256" - "github.com/stretchr/testify/assert" ) var ( @@ -1155,9 +1154,6 @@ func TestBlobCountLimit(t *testing.T) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) - - tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1) - tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2) ) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) @@ -1190,11 +1186,19 @@ func TestBlobCountLimit(t *testing.T) { t.Fatalf("failed to create blob pool: %v", err) } + var ( + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2) + ) + // Attempt to add transactions. errs := pool.Add([]*types.Transaction{tx1, tx2}, true) - assert.Equal(t, 2, len(errs)) - assert.NoError(t, errs[0]) - assert.EqualError(t, errs[1], "transaction blob limit exceeded: blob count 8, limit 7") + if errs[0] != nil { + t.Fatalf("expected tx with 7 blobs to succeed") + } + if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) { + t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1]) + } verifyPoolInternals(t, pool) pool.Close() From 821604eb2090f97769bbae9b535f34e74552719b Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 21 May 2025 10:53:03 -0600 Subject: [PATCH 5/6] core/txpool/legacypool: remove explicit max blob count --- core/txpool/legacypool/legacypool.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index e878eb4fbfca..0223b456e659 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -571,9 +571,8 @@ func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error { 1< Date: Wed, 21 May 2025 10:58:03 -0600 Subject: [PATCH 6/6] core/txpool/blobpool: clean up test more --- core/txpool/blobpool/blobpool_test.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 316ef0a40da8..12b64bf67488 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -1144,10 +1144,6 @@ func TestChangingSlotterSize(t *testing.T) { // TestBlobCountLimit tests the blobpool enforced limits on the max blob count. func TestBlobCountLimit(t *testing.T) { - // Create a temporary folder for the persistent backend - storage := t.TempDir() - - // Create transactions from a few accounts. var ( key1, _ = crypto.GenerateKey() key2, _ = crypto.GenerateKey() @@ -1181,18 +1177,19 @@ func TestBlobCountLimit(t *testing.T) { blobfee: uint256.NewInt(105), statedb: statedb, } - pool := New(Config{Datadir: storage}, chain, nil) + pool := New(Config{Datadir: t.TempDir()}, chain, nil) if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { t.Fatalf("failed to create blob pool: %v", err) } + // Attempt to add transactions. var ( tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1) tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2) ) - - // Attempt to add transactions. errs := pool.Add([]*types.Transaction{tx1, tx2}, true) + + // Check that first succeeds second fails. if errs[0] != nil { t.Fatalf("expected tx with 7 blobs to succeed") }