txpool: activate the new max blobs per transaction post Osaka

This commit is contained in:
Mael Regnery 2025-09-09 12:20:02 +02:00
parent 5035064421
commit 62d7220631
No known key found for this signature in database
GPG key ID: 6B3C0E66C7D94A6F
3 changed files with 110 additions and 10 deletions

View file

@ -66,7 +66,7 @@ const (
// carry. We choose a smaller limit than the protocol-permitted MaxBlobsPerBlock // carry. We choose a smaller limit than the protocol-permitted MaxBlobsPerBlock
// in order to ensure network and txpool stability. // in order to ensure network and txpool stability.
// Note: if you increase this, validation will fail on txMaxSize. // 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 // maxTxsPerAccount is the maximum number of blob transactions admitted from
// a single account. The limit is enforced to minimize the DoS potential of // 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(), MinTip: p.gasTip.ToBig(),
MaxBlobCount: maxBlobsPerTx, 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) return txpool.ValidateTransaction(tx, p.head, p.signer, opts)
} }

View file

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

View file

@ -22,6 +22,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -30,11 +31,9 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
var (
// blobTxMinBlobGasPrice is the big.Int version of the configured protocol // blobTxMinBlobGasPrice is the big.Int version of the configured protocol
// parameter to avoid constructing a new big integer for every transaction. // parameter to avoid constructing a new big integer for every transaction.
blobTxMinBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) var blobTxMinBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
)
// ValidationOptions define certain differences between transaction validation // ValidationOptions define certain differences between transaction validation
// across the different pools without having to duplicate those checks. // 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 { if len(hashes) == 0 {
return errors.New("blobless blob transaction") 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) { if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", 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 { if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
return err 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 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) 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) return validateBlobSidecarLegacy(sidecar, hashes)
} }