Handle Input reg failures
MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
using NBitcoin;
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -22,7 +23,6 @@ namespace WalletWasabi.WabiSabi.Client
{
public CoinJoinClient(
IWabiSabiApiRequestHandler arenaRequestHandler,
- IEnumerable<Coin> coins,
Kitchen kitchen,
KeyManager keymanager,
RoundStateUpdater roundStatusUpdater)
@@ -32,18 +32,16 @@ namespace WalletWasabi.WabiSabi.Client
Keymanager = keymanager;
RoundStatusUpdater = roundStatusUpdater;
SecureRandom = new SecureRandom();
- Coins = coins;
}
- private IEnumerable<Coin> Coins { get; set; }
- private SecureRandom SecureRandom { get; } = new SecureRandom();
+ private SecureRandom SecureRandom { get; }
private Random Random { get; } = new();
public IWabiSabiApiRequestHandler ArenaRequestHandler { get; }
public Kitchen Kitchen { get; }
public KeyManager Keymanager { get; }
private RoundStateUpdater RoundStatusUpdater { get; }
- public async Task<bool> StartCoinJoinAsync(CancellationToken cancellationToken)
+ public async Task<bool> StartCoinJoinAsync(IEnumerable<Coin> coins, CancellationToken cancellationToken)
{
var currentRoundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState => roundState.Phase == Phase.InputRegistration, cancellationToken).ConfigureAwait(false);
@@ -56,7 +54,7 @@ namespace WalletWasabi.WabiSabi.Client
for (var tries = 0; tries < tryLimit; tries++)
{
- if (await StartRoundAsync(currentRoundState, cancellationToken))
+ if (await StartRoundAsync(coins, currentRoundState, cancellationToken).ConfigureAwait(false))
{
return true;
}
@@ -73,25 +71,27 @@ namespace WalletWasabi.WabiSabi.Client
/// <summary>Attempt to participate in a specified dround.</summary>
/// <param name="roundState">Defines the round parameter and state information to use.</param>
/// <returns>Whether or not the round resulted in a successful transaction.</returns>
- public async Task<bool> StartRoundAsync(RoundState roundState, CancellationToken cancellationToken)
+ public async Task<bool> StartRoundAsync(IEnumerable<Coin> coins, RoundState roundState, CancellationToken cancellationToken)
{
- var constructionState = roundState.Assert<ConstructionState>();
+ _ = roundState.Assert<ConstructionState>();
+
+ var aliceClients = CreateAliceClients(coins, roundState);
+
+ // Register coins.
+ aliceClients = await RegisterCoinsAsync(aliceClients, cancellationToken).ConfigureAwait(false);
+
+ //TODO: add sanity check here. What is the feasible limit to proceed forward?
// Calculate outputs values
- var outputValues = DecomposeAmounts(roundState.FeeRate, roundState.CoinjoinState.Parameters.AllowedOutputAmounts.Min);
+ var outputValues = DecomposeAmounts(aliceClients.Select(a => a.Coin), roundState.FeeRate, roundState.CoinjoinState.Parameters.AllowedOutputAmounts.Min);
// Get all locked internal keys we have and assert we have enough.
Keymanager.AssertLockedInternalKeysIndexed(howMany: outputValues.Count());
var allLockedInternalKeys = Keymanager.GetKeys(x => x.IsInternal && x.KeyState == KeyState.Locked);
var outputTxOuts = outputValues.Zip(allLockedInternalKeys, (amount, hdPubKey) => new TxOut(amount, hdPubKey.P2wpkhScript));
-
- List<AliceClient> aliceClients = CreateAliceClients(roundState);
DependencyGraph dependencyGraph = DependencyGraph.ResolveCredentialDependencies(aliceClients.Select(a => a.Coin), outputTxOuts, roundState.FeeRate, roundState.MaxVsizeAllocationPerAlice);
DependencyGraphTaskScheduler scheduler = new(dependencyGraph);
- // Register coins.
- await RegisterCoinsAsync(aliceClients, cancellationToken).ConfigureAwait(false);
-
// Confirm coins.
await scheduler.StartConfirmConnectionsAsync(aliceClients, dependencyGraph, roundState.ConnectionConfirmationTimeout, RoundStatusUpdater, cancellationToken).ConfigureAwait(false);
@@ -125,10 +125,10 @@ namespace WalletWasabi.WabiSabi.Client
return finalRoundState.WasTransactionBroadcast;
}
- private List<AliceClient> CreateAliceClients(RoundState roundState)
+ private ImmutableArray<AliceClient> CreateAliceClients(IEnumerable<Coin> coins, RoundState roundState)
{
- List<AliceClient> aliceClients = new();
- foreach (var coin in Coins)
+ var aliceClients = ImmutableArray.CreateBuilder<AliceClient>();
+ foreach (var coin in coins)
{
var aliceArenaClient = new ArenaClient(
roundState.CreateAmountCredentialClient(SecureRandom),
@@ -139,24 +139,33 @@ namespace WalletWasabi.WabiSabi.Client
var secret = hdKey.PrivateKey.GetBitcoinSecret(Keymanager.GetNetwork());
aliceClients.Add(new AliceClient(roundState.Id, aliceArenaClient, coin, roundState.FeeRate, secret));
}
- return aliceClients;
+ return aliceClients.MoveToImmutable();
}
- private async Task RegisterCoinsAsync(IEnumerable<AliceClient> aliceClients, CancellationToken cancellationToken)
+ private async Task<ImmutableArray<AliceClient>> RegisterCoinsAsync(ImmutableArray<AliceClient> aliceClients, CancellationToken cancellationToken)
{
- async Task RegisterInputTask(AliceClient aliceClient)
+ var successfulAlices = ImmutableArray.CreateBuilder<AliceClient>();
+
+ foreach (var aliceClient in aliceClients.OrderByDescending(x => x.Coin.Amount))
+ {
+ try
{
await aliceClient.RegisterInputAsync(cancellationToken).ConfigureAwait(false);
+ successfulAlices.Add(aliceClient);
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning($"Round ({aliceClient.RoundId}), Alice ({aliceClient.AliceId}): {nameof(AliceClient.RegisterInputAsync)} failed, reason:'{e}'.");
+ }
}
- var registerRequests = aliceClients.Select(RegisterInputTask);
- await Task.WhenAll(registerRequests).ConfigureAwait(false);
+ return successfulAlices.MoveToImmutable();
}
- private IEnumerable<Money> DecomposeAmounts(FeeRate feeRate, Money minimumOutputAmount)
+ private static IEnumerable<Money> DecomposeAmounts(IEnumerable<Coin> coins, FeeRate feeRate, Money minimumOutputAmount)
{
GreedyDecomposer greedyDecomposer = new(StandardDenomination.Values.Where(x => x >= minimumOutputAmount));
- var sum = Coins.Sum(c => c.EffectiveValue(feeRate));
+ var sum = coins.Sum(c => c.EffectiveValue(feeRate));
return greedyDecomposer.Decompose(sum, feeRate.GetFee(31));
}

Handle Input reg failures