Skip to content

Commit d0e510b

Browse files
committed
fix(eip8141): route blob-carrying frame txs through the blob pool consistently
Address review follow-ups on the blob-network PR: - switch the admission filters (fee/balance/future-nonce/gap-nonce/size/ not-supported) to the instance blob predicate so a blob-carrying frame tx obeys blob-pool limits and is rejected when blobs are disabled - scan the blob pool in the frame-expiry eviction pass and feed its inserts/removals to the expiry counter - align the reorg re-add and processed-tx accounting with the routing predicate - withhold blob-carrying frame txs from the peer send path and simplify the gossip policy predicate - fold duplicate blob predicates onto CarriesBlobs and correct the type-6 validator comment - add blob-pool routing regression tests (expiry eviction, blobs-disabled rejection, per-sender blob limit)
1 parent f77dcc0 commit d0e510b

15 files changed

Lines changed: 105 additions & 33 deletions

File tree

src/Nethermind/Nethermind.Consensus/Comparers/BlobTxPriorityComparer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public int Compare(Transaction? x, Transaction? y)
2525
if (x is null) return TxComparisonResult.XFirst;
2626
if (y is null) return TxComparisonResult.YFirst;
2727

28-
return x.SupportsBlobs == y.SupportsBlobs ? TxComparisonResult.Equal :
29-
x.SupportsBlobs ? TxComparisonResult.XFirst : TxComparisonResult.YFirst;
28+
return x.CarriesBlobs == y.CarriesBlobs ? TxComparisonResult.Equal :
29+
x.CarriesBlobs ? TxComparisonResult.XFirst : TxComparisonResult.YFirst;
3030
}
3131
}

src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public virtual AddingTxEventArgs CanAddTransaction(Block block, Transaction curr
7474
// depth: a frame tx arriving here still carries no resolvable EIP-7594 sidecar, so producing
7575
// it would count towards header.BlobGasUsed yet leave the block's blobs bundle incomplete.
7676
// Exclude it until the sidecar wire format lands and its blob data can be published.
77-
if (currentTx.Type == TxType.FrameTx && currentTx.BlobVersionedHashes is { Length: > 0 })
77+
if (currentTx.Type == TxType.FrameTx && currentTx.CarriesBlobs)
7878
{
7979
return args.Set(TxAction.Skip, "Blob-carrying frame transaction not yet supported in block production");
8080
}
@@ -127,7 +127,7 @@ private static bool HasEnoughFunds(Transaction transaction, in UInt256 senderBal
127127
return false;
128128
}
129129

130-
if (transaction.BlobVersionedHashes is { Length: > 0 } && (
130+
if (transaction.CarriesBlobs && (
131131
!BlobGasCalculator.TryCalculateBlobBaseFee(block.Header, transaction, releaseSpec.BlobBaseFeeUpdateFraction, out UInt256 blobBaseFee) ||
132132
senderBalance < (maxFee += blobBaseFee)))
133133
{

src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,8 @@ protected virtual bool ValidateEip4844Fields(Block block, IReleaseSpec spec, ref
349349
Transaction transaction = transactions[txIndex];
350350

351351
// EIP-8141: a blob-carrying frame transaction (type 6) follows EIP-4844 too, so gate on the
352-
// presence of blob hashes rather than the type-level SupportsBlobs (which is type-3 only).
353-
if (transaction.BlobVersionedHashes is not { Length: > 0 })
352+
// instance-level blob predicate rather than the type-level SupportsBlobs (which is type-3 only).
353+
if (!transaction.CarriesBlobs)
354354
{
355355
continue;
356356
}

src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,9 @@ public TxValidator(ulong chainId)
8787
// Frame transactions have no envelope ECDSA signature (explicit sender, protocol-validated
8888
// signature list) — signature/intrinsic-gas validators do not apply; per-frame gas and
8989
// signature validation happen during processing.
90-
// EIP8141: a blob-carrying frame tx currently has no EIP-7594 sidecar network wrapper, so no
91-
// sidecar/proof validator is registered here (a wrapper on a frame tx is rejected as malformed
92-
// by NonBlobFieldsTxValidator's absence + the network-form gates). When the type-6 sidecar wire
93-
// format lands, add a MempoolBlobTxValidator-equivalent (and proof-version validator) to this chain.
90+
// EIP8141: FrameTxDecoder never decodes a network wrapper for a type-6 tx, so no sidecar/proof
91+
// validator is registered and no mempool form is required yet. Add a blob-sidecar and proof-version
92+
// validator here once the type-6 sidecar wire format lands.
9493
RegisterValidator(TxType.FrameTx, new CompositeTxValidator([
9594
new ReleaseSpecTxValidator(static spec => spec.IsEip8141Enabled),
9695
NonceCapTxValidator.Instance,

src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public void SendNewTransaction(Transaction tx)
196196

197197
protected virtual void SendNewTransactionCore(Transaction tx)
198198
{
199-
if (!tx.SupportsBlobs) //additional protection from sending full tx with blob
199+
if (!tx.CarriesBlobs) //additional protection from sending full tx with blob (incl. blob-carrying frame txs)
200200
{
201201
SendMessage(new ArrayPoolList<Transaction>(1) { tx });
202202
}
@@ -231,7 +231,7 @@ protected virtual void SendNewTransactionsCore(IEnumerable<Transaction> txs, boo
231231
packetSizeLeft = TransactionsMessage.MaxPacketSize;
232232
}
233233

234-
if (tx.Hash is not null && !tx.SupportsBlobs) //additional protection from sending full tx with blob
234+
if (tx.Hash is not null && !tx.CarriesBlobs) //additional protection from sending full tx with blob (incl. blob-carrying frame txs)
235235
{
236236
txsToSend.Add(tx);
237237
packetSizeLeft -= txSize;

src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: LGPL-3.0-only
33

44
using System;
5+
using System.Buffers.Binary;
56
using CkzgLib;
67
using Nethermind.Blockchain;
78
using Nethermind.Consensus.Comparers;
@@ -1476,9 +1477,61 @@ public void type3_blob_tx_routing_is_unchanged_alongside_frame_txs()
14761477
}
14771478
}
14781479

1480+
// EIP-8141: a blob-carrying frame tx lives in the blob pool, so the on-head expiry pass must scan it there
1481+
// (the blob pool's Inserted/Removed feed the same expiry counter as the normal pool).
1482+
[Test]
1483+
public async Task Expired_blob_carrying_frame_tx_is_evicted_from_blob_pool_on_new_head()
1484+
{
1485+
TxPoolConfig txPoolConfig = new() { BlobsSupport = BlobsSupportMode.InMemory };
1486+
_txPool = CreatePool(txPoolConfig, GetBogotaSpecProvider());
1487+
EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue);
1488+
1489+
Transaction tx = BuildBlobFrameTx(nonce: 0, blobCount: 1, deadline: 1_000);
1490+
Assert.That(_txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast), Is.EqualTo(AcceptTxResult.Accepted));
1491+
Assert.That(_txPool.GetPendingBlobTransactionsCount(), Is.EqualTo(1));
1492+
1493+
await RaiseBlockAddedToMainAndWaitForNewHead(Build.A.Block.WithNumber(1).WithTimestamp(1_500).TestObject);
1494+
1495+
Assert.That(_txPool.GetPendingBlobTransactionsCount(), Is.EqualTo(0),
1496+
"an expired blob-carrying frame tx must be evicted from the blob pool on a new head");
1497+
}
1498+
1499+
// EIP-8141: with blobs disabled the blob-pool routing has zero capacity, so a blob-carrying frame tx must be
1500+
// rejected as an unsupported type at ingress rather than silently dropped as too-low-fee.
1501+
[Test]
1502+
public void Blob_carrying_frame_tx_is_rejected_when_blobs_disabled()
1503+
{
1504+
TxPoolConfig txPoolConfig = new() { BlobsSupport = BlobsSupportMode.Disabled };
1505+
_txPool = CreatePool(txPoolConfig, GetBogotaSpecProvider());
1506+
EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue);
1507+
1508+
AcceptTxResult result = _txPool.SubmitTx(BuildBlobFrameTx(nonce: 0, blobCount: 1), TxHandlingOptions.None);
1509+
1510+
Assert.That(result, Is.EqualTo(AcceptTxResult.NotSupportedTxType));
1511+
}
1512+
1513+
// EIP-8141: a blob-carrying frame tx counts against the per-sender blob limit (MaxPendingBlobTxsPerSender),
1514+
// not the unlimited normal-pool default, so a nonce beyond that window is rejected as too far in the future.
1515+
[Test]
1516+
public void Blob_carrying_frame_tx_respects_per_sender_blob_limit()
1517+
{
1518+
TxPoolConfig txPoolConfig = new() { BlobsSupport = BlobsSupportMode.InMemory, MaxPendingBlobTxsPerSender = 2 };
1519+
_txPool = CreatePool(txPoolConfig, GetBogotaSpecProvider());
1520+
EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue);
1521+
1522+
using (Assert.EnterMultipleScope())
1523+
{
1524+
// Consecutive nonces within the window [current, current + 2] are admitted; the first beyond it is not.
1525+
Assert.That(_txPool.SubmitTx(BuildBlobFrameTx(nonce: 0, blobCount: 1), TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted));
1526+
Assert.That(_txPool.SubmitTx(BuildBlobFrameTx(nonce: 1, blobCount: 1), TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted));
1527+
Assert.That(_txPool.SubmitTx(BuildBlobFrameTx(nonce: 2, blobCount: 1), TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted));
1528+
Assert.That(_txPool.SubmitTx(BuildBlobFrameTx(nonce: 3, blobCount: 1), TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.NonceTooFarInFuture));
1529+
}
1530+
}
1531+
14791532
private static ISpecProvider GetBogotaSpecProvider() => new TestSpecProvider(Bogota.Instance);
14801533

1481-
private Transaction BuildBlobFrameTx(ulong nonce, int blobCount)
1534+
private Transaction BuildBlobFrameTx(ulong nonce, int blobCount, ulong? deadline = null)
14821535
{
14831536
byte[][] versionedHashes = null;
14841537
if (blobCount > 0)
@@ -1493,6 +1546,17 @@ private Transaction BuildBlobFrameTx(ulong nonce, int blobCount)
14931546
}
14941547
}
14951548

1549+
List<TxFrame> frames =
1550+
[
1551+
new TxFrame(TxFrame.ModeVerify, TxFrame.ApproveExecutionAndPayment, target: null, gasLimit: 100_000, UInt256.Zero, default),
1552+
];
1553+
if (deadline is not null)
1554+
{
1555+
byte[] expiryData = new byte[Eip8141Constants.ExpiryDataLength];
1556+
BinaryPrimitives.WriteUInt64BigEndian(expiryData, deadline.Value);
1557+
frames.Add(new TxFrame(TxFrame.ModeVerify, TxFrame.ApproveScopeNone, Eip8141Constants.ExpiryVerifierAddress, gasLimit: 50_000, UInt256.Zero, expiryData));
1558+
}
1559+
14961560
Transaction tx = new()
14971561
{
14981562
Type = TxType.FrameTx,
@@ -1503,10 +1567,7 @@ private Transaction BuildBlobFrameTx(ulong nonce, int blobCount)
15031567
GasPrice = 1,
15041568
DecodedMaxFeePerGas = 1.GWei,
15051569
MaxFeePerBlobGas = blobCount > 0 ? 1.GWei : UInt256.Zero,
1506-
Frames =
1507-
[
1508-
new TxFrame(TxFrame.ModeVerify, TxFrame.ApproveExecutionAndPayment, target: null, gasLimit: 100_000, UInt256.Zero, default),
1509-
],
1570+
Frames = [.. frames],
15101571
FrameSignatures = [],
15111572
BlobVersionedHashes = versionedHashes,
15121573
};

src/Nethermind/Nethermind.TxPool/Filters/BalanceTooLowFilter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl
3636
UInt256 balance = account.Balance;
3737

3838
BucketBalanceState bucketBalanceState = new(account.Nonce, tx.Nonce);
39-
TxDistinctSortedPool pool = tx.SupportsBlobs ? _blobTxs : _txs;
39+
TxDistinctSortedPool pool = tx.CarriesBlobs ? _blobTxs : _txs;
4040
// tx.SenderAddress! as unknownSenderFilter will run before this one
4141
pool.VisitBucket(tx.SenderAddress!, ref bucketBalanceState, static (Transaction otherTx, ref BucketBalanceState bucketState) =>
4242
{

src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl
4141
return AcceptTxResult.FeeTooLow;
4242
}
4343

44-
TxDistinctSortedPool relevantPool = (tx.SupportsBlobs ? _blobTxs : _txs);
44+
TxDistinctSortedPool relevantPool = (tx.CarriesBlobs ? _blobTxs : _txs);
4545
if (relevantPool.IsFull() && relevantPool.TryGetLast(out Transaction? lastTx)
4646
&& affordableGasPrice <= lastTx?.GasBottleneck)
4747
{

src/Nethermind/Nethermind.TxPool/Filters/FutureNonceFilter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class FutureNonceFilter(ITxPoolConfig txPoolConfig) : IIncomingTxFilter
1212

1313
public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions)
1414
{
15-
int relevantMaxPendingTxsPerSender = (tx.SupportsBlobs
15+
int relevantMaxPendingTxsPerSender = (tx.CarriesBlobs
1616
? _txPoolConfig.MaxPendingBlobTxsPerSender
1717
: _txPoolConfig.MaxPendingTxsPerSender);
1818

src/Nethermind/Nethermind.TxPool/Filters/GapNonceFilter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl
2121
{
2222
bool isLocal = (handlingOptions & TxHandlingOptions.PersistentBroadcast) != 0;
2323
bool nonceGapsAllowed = isLocal || !_txs.IsFull();
24-
if (nonceGapsAllowed && !tx.SupportsBlobs)
24+
if (nonceGapsAllowed && !tx.CarriesBlobs)
2525
{
2626
return AcceptTxResult.Accepted;
2727
}
2828

29-
int numberOfSenderTxsInPending = tx.SupportsBlobs
29+
int numberOfSenderTxsInPending = tx.CarriesBlobs
3030
? _blobTxs.GetBucketCount(tx.SenderAddress!)
3131
: _txs.GetBucketCount(tx.SenderAddress!); // since unknownSenderFilter will run before this one
3232
ulong currentNonce = state.SenderAccount.Nonce;

0 commit comments

Comments
 (0)