From 62d72206317c2f781c6d8579f8d1c1a33e5f5f03 Mon Sep 17 00:00:00 2001 From: Mael Regnery Date: Tue, 9 Sep 2025 12:20:02 +0200 Subject: [PATCH] txpool: activate the new max blobs per transaction post Osaka --- core/txpool/blobpool/blobpool.go | 7 ++- core/txpool/blobpool/blobpool_test.go | 88 +++++++++++++++++++++++++++ core/txpool/validation.go | 25 +++++--- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index edc8eb3e55c8..2a619c986b5f 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -66,7 +66,7 @@ const ( // 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 = params.BlobTxMaxBlobs + 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 @@ -1102,6 +1102,11 @@ func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error { MinTip: p.gasTip.ToBig(), MaxBlobCount: maxBlobsPerTx, } + + if p.chain.Config().IsOsaka(p.head.Number, p.head.Time) { + opts.MaxBlobCount = params.BlobTxMaxBlobs + } + 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 8171ae294a9a..b91bca4a6f78 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -2036,3 +2036,91 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) { } } } + +// TestOsakaMaxBlobsPerTx verifies that the maxBlobsPerTx parameter is dynamic +// and changes according to the Osaka hardfork activation. +func TestOsakaMaxBlobsPerTx(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + ) + + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true, false) + + osakaTime := uint64(1) + pragueTime := uint64(0) + cancunTime := uint64(0) + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: &cancunTime, + PragueTime: &pragueTime, + OsakaTime: &osakaTime, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + }, + } + + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(params.InitialBaseFee), + blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), + statedb: statedb, + } + + tests := []struct { + name string + header *types.Header + numBlobs int + sidecarVer uint8 + expectErr error + }{ + { + name: "before Osaka, maxBlobsPerTx blobs", + header: &types.Header{Number: big.NewInt(0), Time: 0, Difficulty: common.Big0, GasLimit: 30_000_000, BaseFee: big.NewInt(int64(params.InitialBaseFee))}, + numBlobs: maxBlobsPerTx, + sidecarVer: types.BlobSidecarVersion0, + expectErr: nil, + }, + { + name: "after Osaka, maxBlobsPerTx blobs", + header: &types.Header{Number: big.NewInt(1), Time: 1, Difficulty: common.Big0, GasLimit: 30_000_000, BaseFee: big.NewInt(int64(params.InitialBaseFee))}, + numBlobs: maxBlobsPerTx, + sidecarVer: types.BlobSidecarVersion1, + expectErr: txpool.ErrTxBlobLimitExceeded, + }, + { + name: "after Osaka, BlobTxMaxBlobs blobs", + header: &types.Header{Number: big.NewInt(1), Time: 1, Difficulty: common.Big0, GasLimit: 30_000_000, BaseFee: big.NewInt(int64(params.InitialBaseFee))}, + numBlobs: params.BlobTxMaxBlobs, + sidecarVer: types.BlobSidecarVersion1, + expectErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pool := New(Config{Datadir: t.TempDir()}, chain, nil) + if err := pool.Init(1, tt.header, newReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + tx := makeMultiBlobTx(0, 1, 1, 1, tt.numBlobs, 0, key, tt.sidecarVer) + err := pool.add(tx) + if tt.expectErr != nil { + if !errors.Is(err, tt.expectErr) { + t.Errorf("expected error %v, got: %v", tt.expectErr, err) + } + } else { + if err != nil { + t.Errorf("expected success, got: %v", err) + } + } + pool.Close() + }) + } +} diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 46974fad3cbe..46128030be8d 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -30,11 +31,9 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var ( - // blobTxMinBlobGasPrice is the big.Int version of the configured protocol - // parameter to avoid constructing a new big integer for every transaction. - blobTxMinBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) -) +// blobTxMinBlobGasPrice is the big.Int version of the configured protocol +// parameter to avoid constructing a new big integer for every transaction. +var blobTxMinBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) // ValidationOptions define certain differences between transaction validation // across the different pools without having to duplicate those checks. @@ -166,19 +165,27 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO if len(hashes) == 0 { return errors.New("blobless blob transaction") } - if len(hashes) > params.BlobTxMaxBlobs { - return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobs) - } + if len(sidecar.Blobs) != len(hashes) { return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes)) } if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil { return err } - // Fork-specific sidecar checks, including proof verification. + // Post-Osaka checks, Fork-specific sidecar checks, including proof verification. if opts.Config.IsOsaka(head.Number, head.Time) { + if len(hashes) > params.BlobTxMaxBlobs { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobs) + } + return validateBlobSidecarOsaka(sidecar, hashes) } + + maxBlobs := eip4844.MaxBlobsPerBlock(opts.Config, head.Time) + if len(hashes) > maxBlobs { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs) + } + return validateBlobSidecarLegacy(sidecar, hashes) }