New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TxPool: refactoring for blob txs #5953
Conversation
# Conflicts: # src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs
src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs
Show resolved
Hide resolved
bool overflow = UInt256.AddOverflow(currentNonce, (UInt256)relevantMaxPendingTxsPerSender, out UInt256 maxAcceptedNonce); | ||
|
||
// Overflow means that gap between current nonce of sender and UInt256.MaxValue is lower than allowed number | ||
// of pending transactions. As lower nonces were rejected earlier, here it means tx accepted. | ||
// So we are rejecting tx only if there is no overflow. | ||
if (tx.Nonce > maxAcceptedNonce && !overflow) | ||
{ | ||
Metrics.PendingTransactionsNonceTooFarInFuture++; | ||
return AcceptTxResult.NonceTooFarInFuture; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we kind of should never overflow this value, so I think we can ignore overflow here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is exactly what we are doing.
It is an edge case (not real life one) when current nonce is e.g. MaxValue - 10
.
So if we have overflow in maxAcceptedNonce = currentNonce + 16
it means, that we can accept this tx as it is not more than 16 nonces in future.
And we are doing it - returning AcceptTxResult.NonceTooFarInFuture
only when there is !overflow
(not overflow)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point we may simplify the code by just not tracking overflow at all.
|
||
namespace Nethermind.TxPool; | ||
|
||
public class LightTxDecoder : TxDecoder<Transaction> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be <LightTransaction>
?
Stopwatch stopwatch = Stopwatch.StartNew(); | ||
foreach (LightTransaction lightBlobTx in blobTxStorage.GetAll()) | ||
{ | ||
if (base.TryInsert(lightBlobTx.Hash, lightBlobTx, out _)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make this method [MethodImpl(MethodImplOptions.Synchronized)]
then each call to TryInsert
won't lock/unlock this object - maybe giving better performance?
public override bool TryInsert(ValueKeccak hash, Transaction fullBlobTx, out Transaction? removed) | ||
{ | ||
if (base.TryInsert(fullBlobTx.Hash, new LightTransaction(fullBlobTx), out removed)) | ||
{ | ||
_blobTxCache.Set(fullBlobTx.Hash, fullBlobTx); | ||
_blobTxStorage.Add(fullBlobTx); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public override bool TryGetValue(ValueKeccak hash, [NotNullWhen(true)] out Transaction? fullBlobTx) | ||
{ | ||
// Firstly check if tx is present in in-memory collection of light blob txs (without actual blobs). | ||
// If not, just return false | ||
if (base.TryGetValue(hash, out Transaction? lightTx)) | ||
{ | ||
// tx is present in light collection. Try to get full blob tx from cache | ||
if (_blobTxCache.TryGet(hash, out fullBlobTx)) | ||
{ | ||
return true; | ||
} | ||
|
||
// tx is present, but not cached, at this point we need to load it from db... | ||
if (_blobTxStorage.TryGet(hash, lightTx.SenderAddress!, lightTx.Timestamp, out fullBlobTx)) | ||
{ | ||
// ...and we are saving recently used blob tx to cache | ||
_blobTxCache.Set(hash, fullBlobTx); | ||
return true; | ||
} | ||
} | ||
|
||
fullBlobTx = default; | ||
return false; | ||
} | ||
|
||
protected override bool Remove(ValueKeccak hash, Transaction tx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are overrides auto-synchronized?
public virtual void VerifyCapacity() | ||
{ | ||
if (Count > _poolCapacity && _logger.IsWarn) | ||
_logger.Warn($"TxPool exceeds the config size {Count}/{_poolCapacity}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parametrize this in constructor and don't override everywhere
foreach (byte[] txBytes in _lightBlobTxsDb.GetAllValues()) | ||
{ | ||
if (TryDecodeLightTx(txBytes, out LightTransaction? transaction)) | ||
{ | ||
yield return transaction!; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
potential optimization: we could move to Spans saving on memory allocations.
byte[]? txBytes = _fullBlobTxsDb.Get(txHashPrefixed); | ||
return TryDecodeFullTx(txBytes, sender, out transaction); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
potential optimization: we could move to Spans saving on memory allocations.
if (comparer.Compare(blobTx, tx) > 0) | ||
{ | ||
yield return blobTx; | ||
selectedBlobTxs.Remove(blobTx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have to shift all the blob transactions each time, would be better to keep blob transactions in reverse order and remove last
private bool TryGetFullBlobTx(Transaction blobTx, [NotNullWhen(true)] out Transaction? fullBlobTx) | ||
{ | ||
if (blobTx.NetworkWrapper is not null) | ||
{ | ||
fullBlobTx = blobTx; | ||
return true; | ||
} | ||
fullBlobTx = null; | ||
return blobTx.Hash is not null && _transactionPool.TryGetPendingBlobTransaction(blobTx.Hash, out fullBlobTx); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to get full blob tx's here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because in TxPoolTxSource
we are working on light blob txs, without actual blobs. At the end we need to load full tx to include it to the block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to load full tx's?
src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs
Outdated
Show resolved
Hide resolved
@@ -77,52 +78,127 @@ public IEnumerable<Transaction> GetTransactions(BlockHeader parent, long gasLimi | |||
bool success = _txFilterPipeline.Execute(tx, parent); | |||
if (!success) continue; | |||
|
|||
if (tx.SupportsBlobs) | |||
foreach (Transaction blobTx in PickBlobTxsBetterThanCurrentTx(selectedBlobTxs, tx, comparer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's needed cos is already fast enough. But if we could sort SelectBlobTransactions firstly and pass it as iterator to PickBlobTxsBetterThanCurrentTx it might save some cycles, because it's seems more efficient to merge already sorted datas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is already sorted. PickBlobTxsBetterThanCurrentTx
in first step is comparing best blob tx with current std tx, if best blob tx is worse, than breaking the while
loop, not iterating and comparing more
{ | ||
int count = hashes.Count; | ||
for (int i = 0; i < count; i++) | ||
{ | ||
Keccak hash = hashes[i]; | ||
if (!_txPool.IsKnown(hash) && _pendingHashes.Set(hash)) | ||
if (!_txPool.IsKnown(hash) && !_txPool.ContainsTx(hash, (TxType)types[i]) && _pendingHashes.Set(hash)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!_txPool.IsKnown(hash)
and !_txPool.ContainsTx(hash, (TxType)types[i])
seems like the same thing. Can IsKnown cover what ContainsTx additionally covers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsKnown
is checking if tx hash is in cache:
public bool IsKnown(Keccak? hash) => hash != null ? _hashCache.Get(hash) : false;
ContainsTx
is checking if tx is in TxPool/BlobPool.
BlobTxs are persistent (saved to db), cache would be empty after restart or just after some time blob hash would be replaced by newer cached hash. It is to avoid rerequesting txs already present in the pool.
IsKnown
is used in other places (TxBundleSimulator
or in RemoveProcessedTransactions
in TxPool
to prepare metrics), so I didn't want to modify it
@@ -267,7 +295,8 @@ public void AddPeer(ITxPoolPeer peer) | |||
{ | |||
if (_broadcaster.AddPeer(peer)) | |||
{ | |||
_broadcaster.BroadcastOnce(peer, _transactionSnapshot ??= _transactions.GetSnapshot()); | |||
_broadcaster.AnnounceOnce(peer, _transactionSnapshot ??= _transactions.GetSnapshot()); | |||
_broadcaster.AnnounceOnce(peer, _blobTransactionSnapshot ??= _blobTransactions.GetSnapshot()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be one message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better, but it is not easy to implement it well. We would need to:
- concat collections. We will have one message, but we will introduce a lot of unnecessary allocations
or 2) send 2 collections further. But to iterate through them separately, it will need to be passed as far as up toSyncPeerProtocolHandlerBase
and all overrides. A lot of refactorings.
This part will be changed anyway, to not announce txs to peers with way bigger head block number (to avoid announcing already included txs when we are out of sync). Let's leave it as it is for now and I will try to figure something out when implementing described feature
And another change will be introduced when implementing eth/69, then probably we will need to iterate it in TxBroadcaster, so 2 messages problem will be solved as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notify in the end takes IEnumerable
, so AnnounceOnce
could take it to - and we can pass a lazy one without allocations.
{ | ||
// if tx.SupportsBlobs and has BlobVersionedHashes = null, it will throw on earlier step of validation, in TxValidator | ||
overflow |= UInt256.MultiplyOverflow(Eip4844Constants.BlobGasPerBlob, (UInt256)tx.BlobVersionedHashes!.Length, out UInt256 blobGas); | ||
overflow |= UInt256.MultiplyOverflow(blobGas, tx.MaxFeePerBlobGas ?? UInt256.MaxValue, out UInt256 blobGasCost); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two lines above may become a part of BlobGasCalculator just to have one place for such things I guess
public class PriorityFeeTooLowFilter : IIncomingTxFilter | ||
{ | ||
private readonly ILogger _logger; | ||
private const int OneGWei = 1_000_000_000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be a part Eip4844Constants along with replacement settings I guess
* add extensions * adjust filters * add tests * draft * fix metrics * fix tx type, add test for replacing * adjust blob replacement comparer * add blob tx db and metrics * fix test * resurrect old TxStorage class and adjust a bit for blobs * pass blob storage to tx pool, adjust tests * handle db writes/reads and add test * recreate light collection after restart * adjust broadcaster test * cosmetic * add broadcaster test * add todos * add MaxFeePerDataGas check when updating gas bottleneck * regression test for MaxFeePerDataGas check * cosmetic * unify & simplify tests * cosmetics in BlobTxStorage * add AcceptTxResult dedicated for rejected replacements * adjust tests, add test for blob tx replacements * simplify TxPool * refactor to reuse id of deprecated result * simplify filters * add configurable sizes of blob pool and blob cache * optimize TxType filter * add light tx as separate class to reuse * dont return blob txs from broadcaster and other refactorings * add test for broadcaster * add test for announcing txs with MaxFeePerDataGas >= current * add more tx pool metrics * update tx pool report * add metric to filter * improve tests * refactors * clean * add test * fix TxPool * fix test * fix EthereumTests * fix report * fix whitespaces * fix blob txs picking when building block * fix test * fix benchmarks solution * add filtering for MaxPriorityFeePerGas < 1GWei for blob transactions * adjust tests to min requirement of 1 gwei * add test * add more txpool config options * adjust storage * add non-persistent blob tx collection * one more * adjust tests, add new one * post merge fixes * cosmetics * fix bug in broadcasting - use size of full blob tx in light tx * cosmetics * lower default PersistentBlobStorageSize * Divide TxPoolTests * cosmetic - usings * more tests moved to TxPoolTests.Blobs * refactor ParityRpcModuleTests to reuse setup code * cosmetic * refactor picking txs for rebroadcasting/reannouncing * Add MaxBlobsPerBlock to Eip4844Constants and use in several places instead of calculating * whitespace * cosmetic in BlobTxStorage + add tests * make tx comparisons more readable * cosmetics in TxTypeTxFilter * filter blob txs in FeeTooLowFilter just as other types * adjust test * fix whitespaces and file encodings * simplification in TxPool * another whitespace fix * override base methods in blob collections instead of creating new ones + simplifications in TxPool * cosmetics * add BlobTxStorage parameterless constructor that uses MemDb and simplify tests * remove unused things * post-merge fix * move capacity insurances to inner classes * add test for pool capacity * clean * try with synchronization * refactor block building * fix whitespace * simplify decoding in BlobTxStorage * cosmetics * simplify tests * fix tx priority? * improve readability in NonceGapFilter * improve readability of TxPoolTxSource * save sender to db * fix recreating light collection * adjust EthStats to show sum of blob and non-blob txs * fix * make blob support configurable * optimize snapshots * fix blob support disabled * don't request blob txs if blob support disabled * fix tests * fix files encoding * fix test * cosmetics * add MaxPendingTxsPerSenderFilter * add test * disable blob pool by default * cosmetic refactoring * revert previous change * fix tests after making blob pool disabled by default * useBlobDb -> useBlobsDb * TxConfig descrptions fixes * add timestamp to db, drop linq * add missing metric * adjust txpool report * cosmetic * fix broadcaster to check in pending before requesting * fix * add metric of received hashes * improve txpool report * fix naming * make naming more relevant * stopwatch * add missing metric * move to ColumnsDb * optimize full blobs db by prefixing key with timestamp * fix files encoding * fix and improve blob storage * fix tests * adjust tests to require sender of full blob txs loaded from db * refactor future nonce filter * simplify and improve block building * refactor TxPoolTxSource * fix block building - move loading full tx from db after all filters * refactor low fee filtering * add new metric * adjust TxPool filters pipeline and TxPoolReport * change naming as requested in review * add size estimates to descriptions in ITxPoolConfig * rename AcceptTxResult PendingTxsOfOtherType -> PendingTxsOfConflictingType * add more comments to TxPool * add more comments to PersistentBlobTxDistinctSortedPool * fix file encoding * small refactors * cosmetic 1.GWei() * reduce spec lookup --------- Co-authored-by: MarekM25 <marekm2504@gmail.com> Co-authored-by: lukasz.rozmej <lukasz.rozmej@gmail.com>
Changes
TxPoolConfig
option of enabling blob poolBlobSupportEnabled
(false by default for now)TxPoolConfig
option of enabling persistent blob txs storagePersistentBlobStorageEnabled
(false by default for now)TxPoolConfig
configs of persistent blob storage - size of persistent blob db (16384 by default) and size of cache (256 by default)TxPoolConfig
size of in-memory poolInMemoryBlobPoolSize
(512 by default) which is used if persistent storage is disabledTxPoolConfig
max number of pending transactions per one sender for blob txs (MaxPendingBlobTxsPerSender
) and non-blob txs (MaxPendingTxsPerSender
)BlobTxStorage
to handle reads/writesBlobTxDistinctSortedPool
extendingTxDistinctSortedPool
and overriding to make use of new componentsPersistentBlobTxDistinctSortedPool
extendingBlobTxDistinctSortedPool
BlobSupportEnabled
isfalse
MaxPriorityFeePerGas
is lower than 1 GWeiMaxPendingTxsPerSender
/(MaxPendingBlobTxsPerSender
MaxFeePerGas
,MaxPriorityFeePerGas
andMaxFeePerDataGas
+ amount of blobs can't be lowerAcceptTxResult
ReplacementNotAllowed
dedicated for failed replacements - it returnsFeeTooLowTooCompete
before changesAcceptTxResult
NonceTooFarInFuture
for txs already having max number of txs in the pool (MaxPendingTxsPerSender
/(MaxPendingBlobTxsPerSender
)AcceptTxResult
PendingTxsOfOtherType
returned when sender has already tx of other type (only blob or non-blob ones are permitted, no mixing)AcceptTxResult
NotSupportedTxType
returned when there is incoming blob tx, butBlobSupportEnabled
isfalse
Potential improvements:
How it works?
If
BlobSupportEnabled
is false, we are not supporting blob transactions (not accepting in the pool).If
PersistentBlobStorageEnabled
is false, there is just another in-memory transactions collection dedicated for blobs, with max size ofInMemoryBlobPoolSize
. Is using different replacement comparer thanTxDistinctSortedPool
and except of it is behaving the same way. InTxBroadcaster
there are only light equivalences of txs, as for picking and announcing there is no need of storingdata
and when requested, txs are coming fromTxPool
anywayIf
PersistentBlobStorageEnabled
is true, newly coming blob tx is added to 3 (or 4) collectionsLightPool is responsible for:
When blob tx is asked, it checks:
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Documentation
Requires documentation update
Requires explanation in Release Notes
If yes, fill in the details here. Remove if not applicable.:
ToDo