Skip to content
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

Merged
merged 170 commits into from Oct 17, 2023
Merged

TxPool: refactoring for blob txs #5953

merged 170 commits into from Oct 17, 2023

Conversation

marcindsobczak
Copy link
Contributor

@marcindsobczak marcindsobczak commented Jul 21, 2023

Changes

  • add to TxPoolConfig option of enabling blob pool BlobSupportEnabled (false by default for now)
  • add to TxPoolConfig option of enabling persistent blob txs storage PersistentBlobStorageEnabled (false by default for now)
  • add to TxPoolConfig configs of persistent blob storage - size of persistent blob db (16384 by default) and size of cache (256 by default)
  • add to TxPoolConfig size of in-memory pool InMemoryBlobPoolSize (512 by default) which is used if persistent storage is disabled
  • add to TxPoolConfig max number of pending transactions per one sender for blob txs (MaxPendingBlobTxsPerSender) and non-blob txs (MaxPendingTxsPerSender)
  • add db to store all blob txs ever received
  • add BlobTxStorage to handle reads/writes
  • add in-memory collection of light equivalents of blob txs and recreate it after restart
  • add collection BlobTxDistinctSortedPool extending TxDistinctSortedPool and overriding to make use of new components
  • add collection PersistentBlobTxDistinctSortedPool extending BlobTxDistinctSortedPool
  • add cache for recently used full blob txs
  • adjust filters, add new one - now it will be allowed for single sender to have pending blob tx OR other type tx, not both at once. After tx inclusion sender can send any type of tx again. It is inline with Geth behavior
  • add new filtering - reject blob txs if BlobSupportEnabled is false
  • add new filtering - reject blob txs if MaxPriorityFeePerGas is lower than 1 GWei
  • add new filtering - reject txs if number of already pending txs for sender is already equal MaxPendingTxsPerSender/(MaxPendingBlobTxsPerSender
  • add new replacement comparer dedicated for blob txs - requires at least 100% bump of all MaxFeePerGas, MaxPriorityFeePerGas and MaxFeePerDataGas + amount of blobs can't be lower
  • refactor TxBroadcaster - as blob txs are only announced, add only light equivalents of blob txs to broadcaster collection. Later, requested txs are coming from blob db (or cache) anyway
  • add plenty of blob-related metrics
  • add AcceptTxResult ReplacementNotAllowed dedicated for failed replacements - it returns FeeTooLowTooCompete before changes
  • add AcceptTxResult NonceTooFarInFuture for txs already having max number of txs in the pool (MaxPendingTxsPerSender/(MaxPendingBlobTxsPerSender)
  • add AcceptTxResult PendingTxsOfOtherType returned when sender has already tx of other type (only blob or non-blob ones are permitted, no mixing)
  • add AcceptTxResult NotSupportedTxType returned when there is incoming blob tx, but BlobSupportEnabled is false
  • update TxPool report

Potential improvements:

  • throttling for sending/receiving blob txs
  • evicting old blob txs based on timestamp (already saved to db)

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 of InMemoryBlobPoolSize. Is using different replacement comparer than TxDistinctSortedPool and except of it is behaving the same way. In TxBroadcaster there are only light equivalences of txs, as for picking and announcing there is no need of storing data and when requested, txs are coming from TxPool anyway

If PersistentBlobStorageEnabled is true, newly coming blob tx is added to 3 (or 4) collections

  • as full tx to persistent db
  • as full tx to cache
  • as light equivalences to standard sorted pool - let's name it LightPool
  • (optional) if tx is local, then light equivalence is added to broadcaster too

LightPool is responsible for:

  • sorting txs in optimal order
  • protecting from unsuccessful db reads - blob tx is asked from db only if is present in LightPool and it means that is present in persistent db as well
  • used in filters, when loading of other pending txs is needed
  • used for snapshot when building block - full tx is loaded from db only when is picked to the block

When blob tx is asked, it checks:

  • first in LightPool. If tx is not there, returning not found
  • if tx was in LightPool, checking cache
  • if not found in cache, checking persistent db. If there is no bug/error, it should be there

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

If yes, fill in the details here. Remove if not applicable.: ToDo

Comment on lines +31 to +40
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;
}
Copy link
Member

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?

Copy link
Contributor Author

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)

Copy link
Member

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.

src/Nethermind/Nethermind.TxPool/LightTransaction.cs Outdated Show resolved Hide resolved

namespace Nethermind.TxPool;

public class LightTxDecoder : TxDecoder<Transaction>
Copy link
Member

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 _))
Copy link
Member

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?

Comment on lines +54 to +91
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)
Copy link
Member

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}");
Copy link
Member

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

Comment on lines +46 to +53
foreach (byte[] txBytes in _lightBlobTxsDb.GetAllValues())
{
if (TryDecodeLightTx(txBytes, out LightTransaction? transaction))
{
yield return transaction!;
}
}
}
Copy link
Member

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.

Comment on lines +40 to +41
byte[]? txBytes = _fullBlobTxsDb.Get(txHashPrefixed);
return TryDecodeFullTx(txBytes, sender, out transaction);
Copy link
Member

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);
Copy link
Member

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

Comment on lines 175 to 184
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);
}
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

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?

@@ -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))
Copy link
Contributor

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.

Copy link
Contributor Author

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))
Copy link
Contributor

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?

Copy link
Contributor Author

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());
Copy link
Contributor

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?

Copy link
Contributor Author

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:

  1. 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 to SyncPeerProtocolHandlerBase 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

Copy link
Member

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);
Copy link
Contributor

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;
Copy link
Contributor

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

@MarekM25 MarekM25 merged commit 099e96f into master Oct 17, 2023
61 checks passed
@MarekM25 MarekM25 deleted the feature/txpool_4844txs branch October 17, 2023 13:01
brbrr pushed a commit that referenced this pull request Oct 17, 2023
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants