Implement the usage and the tests for BlockNotifier
MODIFY
WalletWasabi.Backend/Global.cs
WalletWasabi.Backend/Global.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.CoinJoin.Coordinator;
using WalletWasabi.CoinJoin.Coordinator.Rounds;
using WalletWasabi.Helpers;
@@ -22,7 +23,7 @@ namespace WalletWasabi.Backend
public Node LocalNode { get; private set; }
- public TrustedNodeNotifyingBehavior TrustedNodeNotifyingBehavior { get; private set; }
+ public BlockNotifier BlockNotifier { get; private set; }
public IndexBuilderService IndexBuilderService { get; private set; }
@@ -59,8 +60,8 @@ namespace WalletWasabi.Backend
var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService");
var indexFilePath = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
var utxoSetFilePath = Path.Combine(indexBuilderServiceDir, $"UtxoSet{RpcClient.Network}.dat");
- IndexBuilderService = new IndexBuilderService(RpcClient, TrustedNodeNotifyingBehavior, indexFilePath, utxoSetFilePath);
- Coordinator = new Coordinator(RpcClient.Network, TrustedNodeNotifyingBehavior, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
+ IndexBuilderService = new IndexBuilderService(RpcClient, BlockNotifier, indexFilePath, utxoSetFilePath);
+ Coordinator = new Coordinator(RpcClient.Network, BlockNotifier, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
IndexBuilderService.Synchronize();
Logger.LogInfo($"{nameof(IndexBuilderService)} is successfully initialized and started synchronization.");
@@ -138,7 +139,7 @@ namespace WalletWasabi.Backend
nodeConnectionParameters.TemplateBehaviors.Add(new TrustedNodeNotifyingBehavior());
var node = await Node.ConnectAsync(network, endPoint, nodeConnectionParameters);
// We have to find it, because it's cloned by the node and not perfectly cloned (event handlers cannot be cloned.)
- TrustedNodeNotifyingBehavior = node.Behaviors.Find<TrustedNodeNotifyingBehavior>();
+ BlockNotifier = new BlockNotifier(TimeSpan.FromSeconds(7), RpcClient, node.Behaviors.Find<TrustedNodeNotifyingBehavior>());
try
{
Logger.LogInfo("TCP Connection succeeded, handshaking...");

MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -169,7 +169,7 @@ namespace WalletWasabi.Tests.IntegrationTests
var indexFilePath = Path.Combine(indexBuilderServiceDir, $"Index{rpc.Network}.dat");
var utxoSetFilePath = Path.Combine(indexBuilderServiceDir, $"UtxoSet{rpc.Network}.dat");
- var indexBuilderService = new IndexBuilderService(rpc, global.TrustedNodeNotifyingBehavior, indexFilePath, utxoSetFilePath);
+ var indexBuilderService = new IndexBuilderService(rpc, global.BlockNotifier, indexFilePath, utxoSetFilePath);
try
{
indexBuilderService.Synchronize();
@@ -1838,7 +1838,7 @@ namespace WalletWasabi.Tests.IntegrationTests
var cjfile = Path.Combine(folder, $"CoinJoins{network}.txt");
File.WriteAllLines(cjfile, new[] { coinbaseTxId.ToString(), offchainTxId.ToString(), mempoolTxId.ToString() });
- using (var coordinatorToTest = new Coordinator(network, global.TrustedNodeNotifyingBehavior, folder, rpc, coordinator.RoundConfig))
+ using (var coordinatorToTest = new Coordinator(network, global.BlockNotifier, folder, rpc, coordinator.RoundConfig))
{
var txIds = await File.ReadAllLinesAsync(cjfile);
@@ -1850,7 +1850,7 @@ namespace WalletWasabi.Tests.IntegrationTests
Directory.CreateDirectory(folder);
File.WriteAllLines(cjfile, new[] { coinbaseTxId.ToString(), "This line is invalid (the file is corrupted)", offchainTxId.ToString() });
- var coordinatorToTest2 = new Coordinator(network, global.TrustedNodeNotifyingBehavior, folder, rpc, coordinatorToTest.RoundConfig);
+ var coordinatorToTest2 = new Coordinator(network, global.BlockNotifier, folder, rpc, coordinatorToTest.RoundConfig);
coordinatorToTest2?.Dispose();
txIds = await File.ReadAllLinesAsync(cjfile);
Assert.Single(txIds);

MODIFY
WalletWasabi.Tests/UnitTests/BitcoinCore/P2pBasedTests.cs
WalletWasabi.Tests/UnitTests/BitcoinCore/P2pBasedTests.cs
@@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.BitcoinCore;
+using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.Helpers;
using WalletWasabi.Stores;
@@ -122,47 +123,80 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
}
}
- //[Fact]
- //public async Task TrustedNotifierNotifiesBlockAsync()
- //{
- // var coreNode = await TestNodeBuilder.CreateAsync();
- // try
- // {
- // var rpc = coreNode.RpcClient;
-
- // var addr = new Key().PubKey.GetSegwitAddress(rpc.Network);
- // var notifier = coreNode.TrustedNodeNotifyingBehavior;
-
- // var blockNum = 10;
- // var blockInvEventAwaiter = new EventsAwaiter<uint256>(
- // h => notifier.BlockInv += h,
- // h => notifier.BlockInv -= h,
- // blockNum);
-
- // var blockEventAwaiter = new EventsAwaiter<Block>(
- // h => notifier.Block += h,
- // h => notifier.Block -= h,
- // blockNum);
-
- // var hashes = await rpc.GenerateToAddressAsync(blockNum, addr);
-
- // var aht = blockInvEventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
- // var arrivedBlocks = await blockEventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
- // var arrivedHashes = await aht;
-
- // foreach (var hash in arrivedHashes)
- // {
- // Assert.Contains(hash, hashes);
- // }
- // foreach (var hash in arrivedBlocks.Select(x => x.GetHash()))
- // {
- // Assert.Contains(hash, hashes);
- // }
- // }
- // finally
- // {
- // await coreNode.TryStopAsync();
- // }
- //}
+ [Fact]
+ public async Task BlockNotifierTestsAsync()
+ {
+ var coreNode = await TestNodeBuilder.CreateAsync();
+ try
+ {
+ var rpc = coreNode.RpcClient;
+ BlockNotifier notifier = coreNode.BlockNotifier;
+
+ // Make we get notification for one block.
+ var blockEventAwaiter = new EventAwaiter<Block>(
+ h => notifier.OnBlock += h,
+ h => notifier.OnBlock -= h);
+
+ var hash = (await rpc.GenerateAsync(1)).First();
+ var block = await blockEventAwaiter.WaitAsync(TimeSpan.FromSeconds(7));
+ Assert.Equal(hash, block.GetHash());
+
+ // Make sure we get notifications about 10 blocks created the same time.
+ var blockNum = 10;
+ var blockEventsAwaiter = new EventsAwaiter<Block>(
+ h => notifier.OnBlock += h,
+ h => notifier.OnBlock -= h,
+ blockNum);
+
+ var hashes = (await rpc.GenerateAsync(blockNum)).ToArray();
+
+ var arrivedBlocks = (await blockEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();
+
+ for (int i = 0; i < hashes.Length; i++)
+ {
+ var expected = hashes[i];
+ var actual = arrivedBlocks[i].GetHash();
+ Assert.Equal(expected, actual);
+ }
+
+ // Make sure we get reorg notifications.
+ var reorgNum = 3;
+ var newBlockNum = reorgNum + 1;
+ var reorgEventsAwaiter = new EventsAwaiter<BlockHeader>(
+ h => notifier.OnReorg += h,
+ h => notifier.OnReorg -= h,
+ reorgNum);
+ blockNum = 10;
+ blockEventsAwaiter = new EventsAwaiter<Block>(
+ h => notifier.OnBlock += h,
+ h => notifier.OnBlock -= h,
+ newBlockNum);
+
+ var reorgedHashes = hashes.TakeLast(reorgNum).ToArray();
+ await rpc.InvalidateBlockAsync(reorgedHashes[0]);
+ var newHashes = (await rpc.GenerateAsync(newBlockNum)).ToArray();
+
+ var reorgedHeaders = (await reorgEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();
+ var newBlocks = (await blockEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();
+
+ for (int i = 0; i < reorgedHashes.Length; i++)
+ {
+ var expected = reorgedHashes[i];
+ var actual = reorgedHeaders[i].GetHash();
+ Assert.Equal(expected, actual);
+ }
+
+ for (int i = 0; i < newHashes.Length; i++)
+ {
+ var expected = newHashes[i];
+ var actual = newBlocks[i].GetHash();
+ Assert.Equal(expected, actual);
+ }
+ }
+ finally
+ {
+ await coreNode.TryStopAsync();
+ }
+ }
}
}

MODIFY
WalletWasabi/BitcoinCore/CoreNode.cs
WalletWasabi/BitcoinCore/CoreNode.cs
@@ -16,6 +16,7 @@ using System.Threading.Tasks;
using WalletWasabi.BitcoinCore.Configuration;
using WalletWasabi.BitcoinCore.Endpointing;
using WalletWasabi.BitcoinCore.Processes;
+using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Services;
@@ -34,6 +35,7 @@ namespace WalletWasabi.BitcoinCore
public CoreConfig Config { get; private set; }
public TrustedNodeNotifyingBehavior TrustedNodeNotifyingBehavior => P2pNode.Behaviors.Find<TrustedNodeNotifyingBehavior>();
public Node P2pNode { get; private set; }
+ public BlockNotifier BlockNotifier { get; private set; }
public static async Task<CoreNode> CreateAsync(CoreNodeParams coreNodeParams)
{
@@ -190,6 +192,9 @@ namespace WalletWasabi.BitcoinCore
coreNode.P2pNode = await Node.ConnectAsync(coreNode.Network, coreNode.P2pEndPoint, nodeConnectionParameters).ConfigureAwait(false);
coreNode.P2pNode.VersionHandshake();
+ coreNode.BlockNotifier = new BlockNotifier(TimeSpan.FromSeconds(7), coreNode.RpcClient, coreNode.TrustedNodeNotifyingBehavior);
+ coreNode.BlockNotifier.Start();
+
return coreNode;
}
}
@@ -227,6 +232,7 @@ namespace WalletWasabi.BitcoinCore
/// <param name="onlyOwned">Only stop if this node owns the process.</param>
public async Task<bool> TryStopAsync(bool onlyOwned = true)
{
+ await BlockNotifier.StopAsync().ConfigureAwait(false);
DisconnectDisposeNullP2pNode();
Exception exThrown = null;
if (Bridge != null)

ADD
WalletWasabi/Blockchain/Blocks/BlockNotifier.cs
+using NBitcoin;
+using NBitcoin.RPC;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Bases;
+using WalletWasabi.Helpers;
+using WalletWasabi.Services;
+
+namespace WalletWasabi.Blockchain.Blocks
+{
+ public class BlockNotifier : PeriodicRunner<uint256>
+ {
+ public event EventHandler<Block> OnBlock;
+
+ public event EventHandler<BlockHeader> OnReorg;
+
+ public RPCClient RpcClient { get; set; }
+ public TrustedNodeNotifyingBehavior P2pNotifier { get; }
+
+ public BlockNotifier(TimeSpan period, RPCClient rpcClient, TrustedNodeNotifyingBehavior p2pNotifier) : base(period, null)
+ {
+ RpcClient = Guard.NotNull(nameof(rpcClient), rpcClient);
+ P2pNotifier = Guard.NotNull(nameof(p2pNotifier), p2pNotifier);
+
+ P2pNotifier.BlockInv += P2pNotifier_BlockInv;
+ }
+
+ private void P2pNotifier_BlockInv(object sender, uint256 e)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override async Task<uint256> ActionAsync(CancellationToken cancel)
+ {
+ throw new NotImplementedException();
+ }
+
+ public new async Task StopAsync()
+ {
+ P2pNotifier.BlockInv -= P2pNotifier_BlockInv;
+ await base.StopAsync().ConfigureAwait(false);
+ }
+ }
+}

MODIFY
WalletWasabi/CoinJoin/Coordinator/Coordinator.cs
WalletWasabi/CoinJoin/Coordinator/Coordinator.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
+using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.CoinJoin.Common.Models;
using WalletWasabi.CoinJoin.Coordinator.Banning;
using WalletWasabi.CoinJoin.Coordinator.Participants;
@@ -36,16 +37,16 @@ namespace WalletWasabi.CoinJoin.Coordinator
public Network Network { get; }
- public TrustedNodeNotifyingBehavior TrustedNodeNotifyingBehavior { get; }
+ public BlockNotifier BlockNotifier { get; }
public string FolderPath { get; }
public UtxoReferee UtxoReferee { get; }
- public Coordinator(Network network, TrustedNodeNotifyingBehavior trustedNodeNotifyingBehavior, string folderPath, RPCClient rpc, CoordinatorRoundConfig roundConfig)
+ public Coordinator(Network network, BlockNotifier blockNotifier, string folderPath, RPCClient rpc, CoordinatorRoundConfig roundConfig)
{
Network = Guard.NotNull(nameof(network), network);
- TrustedNodeNotifyingBehavior = Guard.NotNull(nameof(trustedNodeNotifyingBehavior), trustedNodeNotifyingBehavior);
+ BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
FolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
RpcClient = Guard.NotNull(nameof(rpc), rpc);
RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
@@ -142,10 +143,10 @@ namespace WalletWasabi.CoinJoin.Coordinator
Logger.LogDebug(ex);
}
- TrustedNodeNotifyingBehavior.Block += TrustedNodeNotifyingBehavior_BlockAsync;
+ BlockNotifier.OnBlock += BlockNotifier_OnBlockAsync;
}
- private async void TrustedNodeNotifyingBehavior_BlockAsync(object sender, Block block)
+ private async void BlockNotifier_OnBlockAsync(object sender, Block block)
{
try
{
@@ -449,9 +450,9 @@ namespace WalletWasabi.CoinJoin.Coordinator
{
using (RoundsListLock.Lock())
{
- if (TrustedNodeNotifyingBehavior != null)
+ if (BlockNotifier != null)
{
- TrustedNodeNotifyingBehavior.Block -= TrustedNodeNotifyingBehavior_BlockAsync;
+ BlockNotifier.OnBlock -= BlockNotifier_OnBlockAsync;
}
foreach (CoordinatorRound round in Rounds)

MODIFY
WalletWasabi/Services/IndexBuilderService.cs
WalletWasabi/Services/IndexBuilderService.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Backend.Models;
+using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Models;
@@ -98,7 +99,7 @@ namespace WalletWasabi.Services
}
public RPCClient RpcClient { get; }
- public TrustedNodeNotifyingBehavior TrustedNodeNotifyingBehavior { get; }
+ public BlockNotifier BlockNotifier { get; }
public string IndexFilePath { get; }
public string Bech32UtxoSetFilePath { get; }
@@ -117,10 +118,10 @@ namespace WalletWasabi.Services
public bool IsRunning => Interlocked.Read(ref _running) == 1;
- public IndexBuilderService(RPCClient rpc, TrustedNodeNotifyingBehavior trustedNodeNotifyingBehavior, string indexFilePath, string bech32UtxoSetFilePath)
+ public IndexBuilderService(RPCClient rpc, BlockNotifier blockNotifier, string indexFilePath, string bech32UtxoSetFilePath)
{
RpcClient = Guard.NotNull(nameof(rpc), rpc);
- TrustedNodeNotifyingBehavior = Guard.NotNull(nameof(trustedNodeNotifyingBehavior), trustedNodeNotifyingBehavior);
+ BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
IndexFilePath = Guard.NotNullOrEmptyOrWhitespace(nameof(indexFilePath), indexFilePath);
Bech32UtxoSetFilePath = Guard.NotNullOrEmptyOrWhitespace(nameof(bech32UtxoSetFilePath), bech32UtxoSetFilePath);
@@ -173,7 +174,7 @@ namespace WalletWasabi.Services
}
}
- TrustedNodeNotifyingBehavior.BlockInv += TrustedNodeNotifyingBehavior_BlockInv;
+ BlockNotifier.OnBlock += BlockNotifier_OnBlock;
}
private long _runner;
@@ -387,7 +388,7 @@ namespace WalletWasabi.Services
return pbcinfo;
}
- private void TrustedNodeNotifyingBehavior_BlockInv(object sender, uint256 e)
+ private void BlockNotifier_OnBlock(object sender, Block e)
{
try
{
@@ -472,9 +473,9 @@ namespace WalletWasabi.Services
public async Task StopAsync()
{
- if (TrustedNodeNotifyingBehavior != null)
+ if (BlockNotifier != null)
{
- TrustedNodeNotifyingBehavior.BlockInv -= TrustedNodeNotifyingBehavior_BlockInv;
+ BlockNotifier.OnBlock -= BlockNotifier_OnBlock;
}
Interlocked.CompareExchange(ref _running, 2, 1); // If running, make it stopping.

MODIFY
WalletWasabi/Services/TrustedNodeNotifyingBehavior.cs
WalletWasabi/Services/TrustedNodeNotifyingBehavior.cs
@@ -19,8 +19,6 @@ namespace WalletWasabi.Services
public event EventHandler<SmartTransaction> Transaction;
- public event EventHandler<Block> Block;
-
public TrustedNodeNotifyingBehavior()
{
}
@@ -43,10 +41,6 @@ namespace WalletWasabi.Services
{
Transaction?.Invoke(this, new SmartTransaction(txPayload.Object, Height.Mempool));
}
- else if (message.Message.Payload is BlockPayload blockPayload)
- {
- Block?.Invoke(this, blockPayload.Object);
- }
else if (message.Message.Payload is InvPayload invPayload)
{
var getDataPayload = new GetDataPayload();
@@ -61,7 +55,6 @@ namespace WalletWasabi.Services
if (inv.Type.HasFlag(InventoryType.MSG_BLOCK))
{
BlockInv?.Invoke(this, inv.Hash);
- getDataPayload.Inventory.Add(inv);
}
}

Implement the usage and the tests for BlockNotifier