Make ActionAsync protected
MODIFY
WalletWasabi/Bases/PeriodicRunner.cs
WalletWasabi/Bases/PeriodicRunner.cs
@@ -18,6 +18,8 @@ namespace WalletWasabi.Bases
}
private CancellationTokenSource Stop { get; set; }
+ private CancellationTokenSource Trigger { get; set; }
+ private object TriggerLock { get; }
public TimeSpan Period { get; }
private Task ForeverTask { get; set; }
public Exception LastException { get; set; }
@@ -27,6 +29,8 @@ namespace WalletWasabi.Bases
protected PeriodicRunner(TimeSpan period, T defaultResult)
{
Stop = new CancellationTokenSource();
+ Trigger = new CancellationTokenSource();
+ TriggerLock = new object();
Period = period;
ForeverTask = Task.CompletedTask;
Status = defaultResult;
@@ -40,7 +44,21 @@ namespace WalletWasabi.Bases
LastExceptionFirstAppeared = DateTimeOffset.MinValue;
}
- public abstract Task<T> ActionAsync(CancellationToken cancel);
+ public void TriggerRound()
+ {
+ lock (TriggerLock)
+ {
+ if (Trigger != null)
+ {
+ if (!Trigger.IsCancellationRequested)
+ {
+ Trigger.Cancel();
+ }
+ }
+ }
+ }
+
+ protected abstract Task<T> ActionAsync(CancellationToken cancel);
public void Start()
{
@@ -85,12 +103,25 @@ namespace WalletWasabi.Bases
{
try
{
- await Task.Delay(Period, Stop.Token).ConfigureAwait(false);
+ using var linked = CancellationTokenSource.CreateLinkedTokenSource(Stop.Token, Trigger.Token);
+ await Task.Delay(Period, linked.Token).ConfigureAwait(false);
}
catch (TaskCanceledException ex)
{
Logger.LogTrace(ex);
}
+ finally
+ {
+ lock (TriggerLock)
+ {
+ if (Trigger.IsCancellationRequested)
+ {
+ Trigger?.Dispose();
+ Trigger = null;
+ Trigger = new CancellationTokenSource();
+ }
+ }
+ }
}
}
}
@@ -110,6 +141,8 @@ namespace WalletWasabi.Bases
await ForeverTask.ConfigureAwait(false);
Stop?.Dispose();
Stop = null;
+ Trigger?.Dispose();
+ Trigger = null;
}
}
}

MODIFY
WalletWasabi/BitcoinCore/Monitoring/RpcFeeProvider.cs
WalletWasabi/BitcoinCore/Monitoring/RpcFeeProvider.cs
@@ -20,7 +20,7 @@ namespace WalletWasabi.BitcoinCore.Monitoring
RpcClient = Guard.NotNull(nameof(rpcClient), rpcClient);
}
- public override async Task<AllFeeEstimate> ActionAsync(CancellationToken cancel)
+ protected override async Task<AllFeeEstimate> ActionAsync(CancellationToken cancel)
{
try
{

MODIFY
WalletWasabi/BitcoinCore/Monitoring/RpcMonitor.cs
WalletWasabi/BitcoinCore/Monitoring/RpcMonitor.cs
@@ -18,7 +18,7 @@ namespace WalletWasabi.BitcoinCore.Monitoring
{
}
- public override async Task<RpcStatus> ActionAsync(CancellationToken cancel)
+ protected override async Task<RpcStatus> ActionAsync(CancellationToken cancel)
{
return await RpcClient.GetRpcStatusAsync(cancel).ConfigureAwait(false);
}

MODIFY
WalletWasabi/Blockchain/Blocks/BlockNotifier.cs
WalletWasabi/Blockchain/Blocks/BlockNotifier.cs
@@ -37,11 +37,12 @@ namespace WalletWasabi.Blockchain.Blocks
private void P2pNotifier_BlockInv(object sender, uint256 blockHash)
{
+ TriggerRound();
}
- public override async Task<uint256> ActionAsync(CancellationToken cancel)
+ protected override async Task<uint256> ActionAsync(CancellationToken cancel)
{
- using (await Lock.LockAsync().ConfigureAwait(false))
+ using (await Lock.LockAsync(cancel).ConfigureAwait(false))
{
var bestBlockHash = await RpcClient.GetBestBlockHashAsync().ConfigureAwait(false);
@@ -63,6 +64,75 @@ namespace WalletWasabi.Blockchain.Blocks
return bestBlockHash;
}
+ // If block was already processed return.
+ if (ProcessedBlocks.Any(x => x.GetHash() == arrivedHeader.GetHash()))
+ {
+ return bestBlockHash;
+ }
+
+ // If this block follows the proper order then add.
+ if (ProcessedBlocks.Last().GetHash() == arrivedHeader.HashPrevBlock)
+ {
+ AddBlock(arrivedBlock);
+ return bestBlockHash;
+ }
+
+ // Else let's sort out things.
+ var foundPrevBlock = ProcessedBlocks.FirstOrDefault(x => x.GetHash() == arrivedHeader.HashPrevBlock);
+ // Missed notifications on some previous blocks.
+ if (foundPrevBlock != null)
+ {
+ // Reorg happened.
+ ReorgToBlock(foundPrevBlock);
+ AddBlock(arrivedBlock);
+ return bestBlockHash;
+ }
+
+ var missedBlocks = new List<Block>
+ {
+ arrivedBlock
+ };
+ var currentHeader = arrivedHeader;
+ while (true)
+ {
+ Block missedBlock = missedBlock = await RpcClient.GetBlockAsync(currentHeader.HashPrevBlock).ConfigureAwait(false);
+
+ currentHeader = missedBlock.Header;
+ currentHeader.PrecomputeHash(false, true);
+ missedBlocks.Add(missedBlock);
+
+ if (missedBlocks.Count > 100)
+ {
+ var processedBlocksClone = ProcessedBlocks.ToList();
+ ProcessedBlocks.Clear();
+ foreach (var processedBlock in processedBlocksClone)
+ {
+ OnReorg?.Invoke(this, processedBlock);
+ }
+ Logger.LogCritical("A reorg detected over 100 blocks. Wasabi cannot handle that.");
+ break;
+ }
+
+ // If we found the proper chain.
+ foundPrevBlock = ProcessedBlocks.FirstOrDefault(x => x.GetHash() == currentHeader.HashPrevBlock);
+ if (foundPrevBlock != null)
+ {
+ // If the last block hash is not what we found, then we missed a reorg also.
+ if (foundPrevBlock.GetHash() != ProcessedBlocks.Last().GetHash())
+ {
+ ReorgToBlock(foundPrevBlock);
+ }
+
+ break;
+ }
+ }
+
+ missedBlocks.Reverse();
+ foreach (var b in missedBlocks)
+ {
+ AddBlock(b);
+ }
+
return bestBlockHash;
}
}
@@ -73,6 +143,18 @@ namespace WalletWasabi.Blockchain.Blocks
OnBlock?.Invoke(this, block);
}
+ private void ReorgToBlock(BlockHeader correctBlock)
+ {
+ var index = ProcessedBlocks.IndexOf(correctBlock);
+ int countToRemove = ProcessedBlocks.Count - (index + 1);
+ var toRemoves = ProcessedBlocks.TakeLast(countToRemove).ToList();
+ ProcessedBlocks.RemoveRange(index + 1, countToRemove);
+ foreach (var toRemove in toRemoves)
+ {
+ OnReorg?.Invoke(this, toRemove);
+ }
+ }
+
public new async Task StopAsync()
{
P2pNotifier.BlockInv -= P2pNotifier_BlockInv;

MODIFY
WalletWasabi/Services/ConfigWatcher.cs
WalletWasabi/Services/ConfigWatcher.cs
@@ -20,7 +20,7 @@ namespace WalletWasabi.Services
config.AssertFilePathSet();
}
- public override async Task<bool> ActionAsync(CancellationToken cancel)
+ protected override async Task<bool> ActionAsync(CancellationToken cancel)
{
if (await Config.CheckFileChangeAsync().ConfigureAwait(false))
{

MODIFY
WalletWasabi/Services/UpdateChecker.cs
WalletWasabi/Services/UpdateChecker.cs
@@ -19,7 +19,7 @@ namespace WalletWasabi.Services
WasabiClient = Guard.NotNull(nameof(client), client);
}
- public override async Task<UpdateStatus> ActionAsync(CancellationToken cancel)
+ protected override async Task<UpdateStatus> ActionAsync(CancellationToken cancel)
{
return await WasabiClient.CheckUpdatesAsync(cancel).ConfigureAwait(false);
}

Make ActionAsync protected