Skip to content

Commit 5f6f330

Browse files
authored
Add FeeFilter capabilities protocol version 70013 (#127)
* Add FeeFilter protocol * Enable fee filter for Bitcoin * Small fix to disposing async loop * Remove old comments * Change filter broadcast to 10 min
1 parent 11eeb36 commit 5f6f330

11 files changed

Lines changed: 304 additions & 67 deletions

File tree

src/External/NBitcoin/Protocol/Versions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public static class ProtocolVersion
8080
/// </summary>
8181
public const uint SHORT_IDS_BLOCKS_VERSION = 70014;
8282

83+
/// <summary>
84+
/// "feefilter" tells peers to filter invs to you by fee starts with this version.
85+
/// </summary>
86+
public const uint FEEFILTER_VERSION = 70013;
87+
8388
/// <summary>
8489
/// Oldest supported version of the CirrusNode which this node can connect to.
8590
/// </summary>

src/Features/Blockcore.Features.MemoryPool/Blockcore.Features.MemoryPool.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
<ProjectReference Include="..\..\Blockcore\Blockcore.csproj" />
2525
</ItemGroup>
2626

27+
<ItemGroup>
28+
<Folder Include="Behaviours\" />
29+
<Folder Include="Payloads\" />
30+
</ItemGroup>
31+
2732
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
2833
<OutputType>Library</OutputType>
2934
</PropertyGroup>
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Blockcore.AsyncWork;
4+
using Blockcore.Interfaces;
5+
using Blockcore.P2P.Peer;
6+
using Blockcore.P2P.Protocol;
7+
using Blockcore.P2P.Protocol.Behaviors;
8+
using Blockcore.Utilities;
9+
using Microsoft.Extensions.Logging;
10+
using NBitcoin;
11+
using NBitcoin.Protocol;
12+
13+
namespace Blockcore.Features.MemoryPool.FeeFilter
14+
{
15+
/// <summary>
16+
/// Implementing FeeFilter as described in BIP0133
17+
/// https://github.com/bitcoin/bips/blob/master/bip-0133.mediawiki
18+
/// </summary>
19+
public class FeeFilterBehavior : NetworkPeerBehavior
20+
{
21+
private readonly ILogger logger;
22+
private readonly Network network;
23+
private readonly MempoolSettings settings;
24+
private readonly MempoolManager mempoolManager;
25+
private readonly IInitialBlockDownloadState initialBlockDownloadState;
26+
private readonly ILoggerFactory loggerFactory;
27+
private readonly INodeLifetime nodeLifetime;
28+
private readonly IAsyncProvider asyncProvider;
29+
private MempoolBehavior mempoolBehavior;
30+
private IAsyncLoop asyncLoop;
31+
32+
private Money lastSendFilter;
33+
34+
public FeeFilterBehavior(
35+
Network network,
36+
MempoolSettings settings,
37+
MempoolManager mempoolManager,
38+
IInitialBlockDownloadState initialBlockDownloadState,
39+
ILoggerFactory loggerFactory,
40+
INodeLifetime nodeLifetime,
41+
IAsyncProvider asyncProvider)
42+
{
43+
this.network = network;
44+
this.settings = settings;
45+
this.mempoolManager = mempoolManager;
46+
this.initialBlockDownloadState = initialBlockDownloadState;
47+
this.loggerFactory = loggerFactory;
48+
this.nodeLifetime = nodeLifetime;
49+
this.asyncProvider = asyncProvider;
50+
51+
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
52+
}
53+
54+
private async Task ProcessFeeFilterAsync(INetworkPeer peer, FeeFilterPayload feeFilter)
55+
{
56+
if (peer.PeerVersion.Relay)
57+
{
58+
if (feeFilter.NewFeeFilter > 0)
59+
{
60+
if (this.mempoolBehavior == null)
61+
this.mempoolBehavior = peer.Behavior<MempoolBehavior>();
62+
63+
this.mempoolBehavior.MinFeeFilter = feeFilter.NewFeeFilter;
64+
65+
this.logger.LogDebug("Received feefilter of `{0}` from peer id: {1}", feeFilter.NewFeeFilter, peer.Connection.Id);
66+
}
67+
}
68+
}
69+
70+
private void StartFeeFilterBroadcast(INetworkPeer sender)
71+
{
72+
if (this.settings.FeeFilter)
73+
{
74+
INetworkPeer peer = sender;
75+
76+
if (peer.PeerVersion != null
77+
&& peer.PeerVersion.Relay
78+
&& peer.PeerVersion.Version >= ProtocolVersion.FEEFILTER_VERSION)
79+
{
80+
if (this.asyncLoop != null)
81+
{
82+
this.asyncLoop = this.asyncProvider.CreateAndRunAsyncLoop($"MemoryPool.FeeFilter:{peer.Connection.Id}", async token =>
83+
{
84+
if (this.initialBlockDownloadState.IsInitialBlockDownload())
85+
{
86+
return;
87+
}
88+
89+
var feeRate = await this.mempoolManager.GetMempoolMinFeeAsync(MempoolValidator.DefaultMaxMempoolSize * 1000000).ConfigureAwait(false);
90+
var currentFilter = feeRate.FeePerK;
91+
92+
// We always have a fee filter of at least minRelayTxFee
93+
Money filterToSend = Math.Max(currentFilter, new FeeRate(this.network.MinRelayTxFee).FeePerK);
94+
95+
if (filterToSend != this.lastSendFilter)
96+
{
97+
INetworkPeer peer = this.AttachedPeer;
98+
99+
if (peer != null && peer.IsConnected)
100+
{
101+
this.logger.LogDebug("Sending for transaction data from peer '{0}'.", peer.RemoteSocketEndpoint);
102+
var filterPayload = new FeeFilterPayload() { NewFeeFilter = filterToSend };
103+
await peer.SendMessageAsync(filterPayload).ConfigureAwait(false);
104+
this.lastSendFilter = filterToSend;
105+
}
106+
}
107+
},
108+
this.nodeLifetime.ApplicationStopping,
109+
repeatEvery: TimeSpan.FromMinutes(10),
110+
startAfter: TimeSpans.TenSeconds);
111+
}
112+
}
113+
}
114+
}
115+
116+
private async Task ProcessMessageAsync(INetworkPeer peer, IncomingMessage message)
117+
{
118+
try
119+
{
120+
switch (message.Message.Payload)
121+
{
122+
case FeeFilterPayload feeFilter:
123+
await this.ProcessFeeFilterAsync(peer, feeFilter).ConfigureAwait(false);
124+
break;
125+
}
126+
}
127+
catch (OperationCanceledException)
128+
{
129+
this.logger.LogTrace("(-)[CANCELED_EXCEPTION]");
130+
return;
131+
}
132+
}
133+
134+
private async Task OnMessageReceivedAsync(INetworkPeer peer, IncomingMessage message)
135+
{
136+
try
137+
{
138+
await this.ProcessMessageAsync(peer, message).ConfigureAwait(false);
139+
}
140+
catch (OperationCanceledException)
141+
{
142+
this.logger.LogTrace("(-)[CANCELED_EXCEPTION]");
143+
return;
144+
}
145+
catch (Exception ex)
146+
{
147+
this.logger.LogError("Exception occurred: {0}", ex.ToString());
148+
throw;
149+
}
150+
}
151+
152+
public override object Clone()
153+
{
154+
return new FeeFilterBehavior(
155+
this.network,
156+
this.settings,
157+
this.mempoolManager,
158+
this.initialBlockDownloadState,
159+
this.loggerFactory,
160+
this.nodeLifetime,
161+
this.asyncProvider);
162+
}
163+
164+
protected override void AttachCore()
165+
{
166+
this.AttachedPeer.StateChanged.Register(this.OnStateChangedAsync);
167+
this.AttachedPeer.MessageReceived.Register(this.OnMessageReceivedAsync);
168+
}
169+
170+
private async Task OnStateChangedAsync(INetworkPeer sender, NetworkPeerState arg)
171+
{
172+
if (arg == NetworkPeerState.HandShaked)
173+
{
174+
this.StartFeeFilterBroadcast(sender);
175+
}
176+
177+
if (arg == NetworkPeerState.Disconnecting)
178+
{
179+
if (this.asyncLoop != null)
180+
{
181+
this.asyncLoop?.Dispose();
182+
this.asyncLoop = null;
183+
}
184+
}
185+
}
186+
187+
protected override void DetachCore()
188+
{
189+
this.AttachedPeer.StateChanged.Unregister(this.OnStateChangedAsync);
190+
this.AttachedPeer.MessageReceived.Unregister(this.OnMessageReceivedAsync);
191+
}
192+
}
193+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Blockcore.P2P.Protocol.Payloads;
2+
using NBitcoin;
3+
4+
namespace Blockcore.Features.MemoryPool.FeeFilter
5+
{
6+
[Payload("feefilter")]
7+
public class FeeFilterPayload : Payload
8+
{
9+
private long feeFilter;
10+
11+
public long NewFeeFilter
12+
{
13+
get => this.feeFilter;
14+
set => this.feeFilter = value;
15+
}
16+
17+
public FeeFilterPayload()
18+
{
19+
}
20+
21+
public override void ReadWriteCore(BitcoinStream stream)
22+
{
23+
stream.ReadWrite(ref this.feeFilter);
24+
}
25+
}
26+
}

src/Features/Blockcore.Features.MemoryPool/MempoolBehavior.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ public class MempoolBehavior : NetworkPeerBehavior
9191
/// </summary>
9292
private bool isBlocksOnlyMode;
9393

94+
/// <summary>
95+
/// The min fee the peer asks to relay transactions.
96+
/// </summary>
97+
public Money MinFeeFilter { get; set; }
98+
9499
public MempoolBehavior(
95100
IMempoolValidator validator,
96101
MempoolManager mempoolManager,
@@ -116,6 +121,8 @@ public MempoolBehavior(
116121
this.filterInventoryKnown = new HashSet<uint256>();
117122
this.isPeerWhitelistedForRelay = false;
118123
this.isBlocksOnlyMode = false;
124+
125+
this.MinFeeFilter = 0;
119126
}
120127

121128
/// <summary>Time of last memory pool request in unix time.</summary>
@@ -236,12 +243,6 @@ private async Task SendMempoolPayloadAsync(INetworkPeer peer, MempoolPayload mes
236243
List<TxMempoolInfo> transactionsInMempool = await this.mempoolManager.InfoAllAsync().ConfigureAwait(false);
237244
Money filterrate = Money.Zero;
238245

239-
// TODO: implement minFeeFilter
240-
//{
241-
// LOCK(pto->cs_feeFilter);
242-
// filterrate = pto->minFeeFilter;
243-
//}
244-
245246
var transactionsToSend = new List<uint256>();
246247
lock (this.lockObject)
247248
{
@@ -553,17 +554,15 @@ public async Task SendTrickleAsync()
553554
continue;
554555
}
555556

556-
//if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) // TODO:filterrate
557-
//{
558-
// continue;
559-
//}
560557
transactionsToSend.Add(hash);
561558
this.logger.LogDebug("Transaction ID '{0}' added to inventory list.", hash);
562559
}
563560

564561
this.logger.LogDebug("Transaction inventory list created.");
565562
}
566563

564+
FeeRate filterrate = new FeeRate(this.MinFeeFilter);
565+
567566
List<uint256> findInMempool = transactionsToSend.ToList();
568567
foreach (uint256 hash in findInMempool)
569568
{
@@ -574,6 +573,13 @@ public async Task SendTrickleAsync()
574573
this.logger.LogDebug("Transaction ID '{0}' not added to inventory list, no longer in mempool.", hash);
575574
transactionsToSend.Remove(hash);
576575
}
576+
577+
// Peer told you to not send transactions at that feerate? Don't bother sending it.
578+
if (txInfo.Fee < filterrate.GetFee((int)txInfo.Size))
579+
{
580+
this.logger.LogDebug("Transaction ID '{0}' not added to inventory list, bellow peers fee filter.", hash);
581+
transactionsToSend.Remove(hash);
582+
}
577583
}
578584

579585
if (transactionsToSend.Any())

0 commit comments

Comments
 (0)