feat(blobpool): add explicit max blob count limit

This commit is contained in:
Péter Garamvölgyi 2025-05-16 09:13:53 +02:00
parent 1f175347c4
commit fa08c910fb
No known key found for this signature in database
GPG key ID: 7FC8D069F7199070
4 changed files with 79 additions and 9 deletions

View file

@ -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)
}

View file

@ -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 := &params.ChainConfig{
ChainID: big.NewInt(1),
LondonBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
CancunTime: &cancunTime,
PragueTime: &pragueTime,
BlobScheduleConfig: &params.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.
//

View file

@ -571,8 +571,9 @@ func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error {
1<<types.AccessListTxType |
1<<types.DynamicFeeTxType |
1<<types.SetCodeTxType,
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
MaxBlobCount: 0,
}
return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts)
}

View file

@ -42,9 +42,10 @@ var (
type ValidationOptions struct {
Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules
Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool
MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool
MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
MaxBlobCount int // Maximum number of blobs allowed per transaction
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
}
// ValidationFunction is an method type which the pools use to perform the tx-validations which do not
@ -63,6 +64,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if opts.Accept&(1<<tx.Type()) == 0 {
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)
}
// 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 {