Skip to content

Commit

Permalink
Broadcast local txs only if MaxFeePerGas is equal at least 70% of cur…
Browse files Browse the repository at this point in the history
…rent base fee (#6350)

* limit initial broadcast

* add BaseFeeThreshold to ChainHeadInfoProvider

* simplify broadcaster

* add simple test

* make BaseFeeThreshold configurable in TxPoolConfig

* remove from ChainHeadInfoProvider

* more tests

* cosmetic

* cosmetic

* fix posdao tests

* Update src/Nethermind/Nethermind.TxPool/ITxPoolConfig.cs

Co-authored-by: Ruben Buniatyan <rubo@users.noreply.github.com>

---------

Co-authored-by: Ruben Buniatyan <rubo@users.noreply.github.com>
  • Loading branch information
marcindsobczak and rubo committed Dec 14, 2023
1 parent 1bbd8b2 commit 82afd0f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 4 deletions.
63 changes: 63 additions & 0 deletions src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,36 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values(
expectedTxs.Should().BeEquivalentTo(pickedTxs);
}

[TestCase(0, false)]
[TestCase(69, false)]
[TestCase(70, true)]
[TestCase(100, true)]
[TestCase(150, true)]
public void should_not_broadcast_tx_with_MaxFeePerGas_lower_than_70_percent_of_CurrentBaseFee(int maxFeePerGas, bool shouldBroadcast)
{
_headInfo.CurrentBaseFee.Returns((UInt256)100);

_broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager);

ITxPoolPeer peer = Substitute.For<ITxPoolPeer>();
peer.Id.Returns(TestItem.PublicKeyA);
_broadcaster.AddPeer(peer);

Transaction transaction = Build.A.Transaction
.WithType(TxType.EIP1559)
.WithMaxFeePerGas((UInt256)maxFeePerGas)
.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA)
.TestObject;

_broadcaster.Broadcast(transaction, true);

// tx should be immediately broadcasted only if MaxFeePerGas is equal at least 70% of current base fee
peer.Received(shouldBroadcast ? 1 : 0).SendNewTransaction(Arg.Any<Transaction>());

// tx should always be added to persistent collection, without any fee restrictions
_broadcaster.GetSnapshot().Length.Should().Be(1);
}

[Test]
public void should_not_pick_1559_txs_with_MaxFeePerGas_lower_than_CurrentBaseFee([Values(1, 2, 99, 100, 101, 1000)] int threshold)
{
Expand Down Expand Up @@ -657,6 +687,39 @@ public void should_rebroadcast_all_persistent_transactions_if_PeerNotificationTh
}
}

[TestCase(0, 0)]
[TestCase(2, 1)]
[TestCase(3, 2)]
[TestCase(7, 4)]
[TestCase(8, 5)]
[TestCase(100, 70)]
[TestCase(9999, 6999)]
[TestCase(10000, 7000)]
public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expectedThreshold)
{
_headInfo.CurrentBaseFee.Returns((UInt256)baseFee);
_broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager);
_broadcaster.CalculateBaseFeeThreshold().Should().Be((UInt256)expectedThreshold);
}

[Test]
public void calculation_of_baseFeeThreshold_should_handle_overflow_correctly([Values(0, 70, 100, 101, 500)] int threshold, [Values(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] int divisor)
{
UInt256.Divide(UInt256.MaxValue, (UInt256)divisor, out UInt256 baseFee);
_headInfo.CurrentBaseFee.Returns(baseFee);

_txPoolConfig = new TxPoolConfig() { MinBaseFeeThreshold = threshold };
_broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager);

UInt256.Divide(baseFee, 100, out UInt256 onePercentOfBaseFee);
bool overflow = UInt256.MultiplyOverflow(onePercentOfBaseFee, (UInt256)threshold, out UInt256 lessAccurateBaseFeeThreshold);

_broadcaster.CalculateBaseFeeThreshold().Should().Be(
UInt256.MultiplyOverflow(baseFee, (UInt256)threshold, out UInt256 baseFeeThreshold)
? overflow ? UInt256.MaxValue : lessAccurateBaseFeeThreshold
: baseFeeThreshold);
}

private (IList<Transaction> expectedTxs, IList<Hash256> expectedHashes) GetTxsAndHashesExpectedToBroadcast(Transaction[] transactions, int expectedCountTotal)
{
List<Transaction> expectedTxs = new();
Expand Down
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.TxPool/ITxPoolConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public interface ITxPoolConfig : IConfig
[ConfigItem(DefaultValue = "5", Description = "The average percentage of transaction hashes from persistent broadcast sent to a peer together with hashes of the last added transactions.")]
int PeerNotificationThreshold { get; set; }

[ConfigItem(DefaultValue = "70", Description = "The minimal percentage of the current base fee that must be surpassed by the max fee (`max_fee_per_gas`) for the transaction to be broadcasted.")]
int MinBaseFeeThreshold { get; set; }

[ConfigItem(DefaultValue = "2048", Description = "The max number of transactions held in the mempool (the more transactions in the mempool, the more memory used).")]
int Size { get; set; }

Expand Down
42 changes: 39 additions & 3 deletions src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,18 @@ internal class TxBroadcaster : IDisposable
/// </summary>
private ResettableList<Transaction> _txsToSend;


/// <summary>
/// Minimal value of MaxFeePerGas of local tx to be broadcasted immediately after receiving it
/// </summary>
private UInt256 _baseFeeThreshold;

/// <summary>
/// Used to throttle tx broadcast. Particularly during forward sync where the head changes a lot which triggers
/// a lot of broadcast. There are no transaction in pool but its quite spammy on the log.
/// </summary>
private DateTimeOffset _lastPersistedTxBroadcast = DateTimeOffset.UnixEpoch;

private readonly TimeSpan _minTimeBetweenPersistedTxBroadcast = TimeSpan.FromSeconds(1);

private readonly ILogger _logger;
Expand All @@ -80,6 +87,8 @@ internal class TxBroadcaster : IDisposable
_timer.Elapsed += TimerOnElapsed;
_timer.AutoReset = false;
_timer.Start();

_baseFeeThreshold = CalculateBaseFeeThreshold();
}

// only for testing reasons
Expand All @@ -99,7 +108,13 @@ public void Broadcast(Transaction tx, bool isPersistent)

private void StartBroadcast(Transaction tx)
{
NotifyPeersAboutLocalTx(tx);
// broadcast local tx only if MaxFeePerGas is not lower than configurable percent of current base fee
// (70% by default). Otherwise only add to persistent txs and broadcast when tx will be ready for inclusion
if (tx.MaxFeePerGas >= _baseFeeThreshold || tx.IsFree())
{
NotifyPeersAboutLocalTx(tx);
}

if (tx.Hash is not null)
{
_persistentTxs.TryInsert(tx.Hash, tx.SupportsBlobs ? new LightTransaction(tx) : tx);
Expand All @@ -122,7 +137,29 @@ public void AnnounceOnce(ITxPoolPeer peer, Transaction[] txs)
}
}

public void BroadcastPersistentTxs()
public void OnNewHead()
{
_baseFeeThreshold = CalculateBaseFeeThreshold();
BroadcastPersistentTxs();
}

internal UInt256 CalculateBaseFeeThreshold()
{
bool overflow = UInt256.MultiplyOverflow(_headInfo.CurrentBaseFee, (UInt256)_txPoolConfig.MinBaseFeeThreshold, out UInt256 baseFeeThreshold);
UInt256.Divide(baseFeeThreshold, 100, out baseFeeThreshold);

if (overflow)
{
UInt256.Divide(_headInfo.CurrentBaseFee, 100, out baseFeeThreshold);
overflow = UInt256.MultiplyOverflow(baseFeeThreshold, (UInt256)_txPoolConfig.MinBaseFeeThreshold, out baseFeeThreshold);
}

// if there is still an overflow, it means that MinBaseFeeThreshold > 100
// we are returning max possible value of UInt256.MaxValue
return overflow ? UInt256.MaxValue : baseFeeThreshold;
}

internal void BroadcastPersistentTxs()
{
if (_persistentTxs.Count == 0)
{
Expand Down Expand Up @@ -274,7 +311,6 @@ private void Notify(ITxPoolPeer peer, IEnumerable<Transaction> txs, bool sendFul
{
try
{

peer.SendNewTransactions(txs.Where(t => _txGossipPolicy.ShouldGossipTransaction(t)), sendFullTx);
if (_logger.IsTrace) _logger.Trace($"Notified {peer} about transactions.");
}
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.TxPool/TxPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private void ProcessNewHeads()
ReAddReorganisedTransactions(args.PreviousBlock);
RemoveProcessedTransactions(args.Block.Transactions);
UpdateBuckets();
_broadcaster.BroadcastPersistentTxs();
_broadcaster.OnNewHead();
Metrics.TransactionCount = _transactions.Count;
Metrics.BlobTransactionCount = _blobTransactions.Count;
}
Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.TxPool/TxPoolConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Nethermind.TxPool
public class TxPoolConfig : ITxPoolConfig
{
public int PeerNotificationThreshold { get; set; } = 5;
public int MinBaseFeeThreshold { get; set; } = 70;
public int Size { get; set; } = 2048;
public bool BlobSupportEnabled { get; set; } = false;
public bool PersistentBlobStorageEnabled { get; set; } = false;
Expand Down

0 comments on commit 82afd0f

Please sign in to comment.