Handling extra edges
MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -62,15 +62,16 @@ namespace WalletWasabi.WabiSabi.Client
List<AliceClient> aliceClients = CreateAliceClients(roundState);
+ DependencyGraph dependencyGraph = DependencyGraph.ResolveCredentialDependencies(aliceClients.Select(a => a.Coin), outputTxOuts, roundState.FeeRate, roundState.MaxVsizeAllocationPerAlice);
+ DependencyGraphResolver dgr = new(dependencyGraph, ZeroAmountCredentialPool, ZeroVsizeCredentialPool);
+
// Register coins.
aliceClients = await RegisterCoinsAsync(aliceClients, cancellationToken).ConfigureAwait(false);
// Confirm coins.
- aliceClients = await ConfirmConnectionsAsync(aliceClients, roundState.MaxVsizeAllocationPerAlice, roundState.ConnectionConfirmationTimeout, cancellationToken).ConfigureAwait(false);
+ await dgr.StartConfirmConnectionsAsync(aliceClients, dependencyGraph, roundState.ConnectionConfirmationTimeout, cancellationToken).ConfigureAwait(false);
// Re-issuances.
- DependencyGraph dependencyGraph = DependencyGraph.ResolveCredentialDependencies(aliceClients.Select(a => a.Coin), outputTxOuts, roundState.FeeRate, roundState.MaxVsizeAllocationPerAlice);
- DependencyGraphResolver dgr = new(dependencyGraph, ZeroAmountCredentialPool, ZeroVsizeCredentialPool);
var bobClient = CreateBobClient(roundState);
await dgr.StartReissuancesAsync(aliceClients, bobClient, cancellationToken).ConfigureAwait(false);

MODIFY
WalletWasabi/WabiSabi/Client/CredentialDependencies/InputNode.cs
WalletWasabi/WabiSabi/Client/CredentialDependencies/InputNode.cs
+using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,5 +12,9 @@ namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
public InputNode(IEnumerable<long> values) : base(values, 0, DependencyGraph.K, DependencyGraph.K * (DependencyGraph.K - 1))
{
}
+
+ public Money EffectiveValue => Money.Satoshis(InitialBalance(CredentialType.Amount));
+
+ public int VsizeRemainingAllocation => (int)InitialBalance(CredentialType.Vsize);
}
}

MODIFY
WalletWasabi/WabiSabi/Client/DependencyGraphResolver.cs
WalletWasabi/WabiSabi/Client/DependencyGraphResolver.cs
@@ -33,34 +33,69 @@ namespace WalletWasabi.WabiSabi.Client
public ZeroCredentialPool ZeroVsizeCredentialPool { get; }
private Dictionary<CredentialDependency, TaskCompletionSource<Credential>> DependencyTasks { get; }
- public async Task StartReissuancesAsync(IEnumerable<AliceClient> aliceClients, BobClient bobClient, CancellationToken cancellationToken)
+ public async Task StartConfirmConnectionsAsync(IEnumerable<AliceClient> aliceClients, DependencyGraph dependencyGraph, TimeSpan connectionConfirmationTimeout, CancellationToken cancellationToken)
{
var aliceNodePairs = PairAliceClientAndRequestNodes(aliceClients, Graph);
- // Set the result for the inputs.
+ List<SmartRequestNode> smartRequestNodes = new();
+ List<Task> connectionConfirmationTasks = new();
+
+ using CancellationTokenSource ctsOnError = new();
+ using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ctsOnError.Token);
+
foreach ((var aliceClient, var node) in aliceNodePairs)
{
- foreach ((var edge, var credential) in Enumerable.Zip(Graph.OutEdges(node, CredentialType.Amount).Where(edge => edge.Value > 0), aliceClient.RealAmountCredentials))
- {
- DependencyTasks[edge].SetResult(credential);
- }
+ var amountEdgeTaskCompSources = Graph.OutEdges(node, CredentialType.Amount).Select(edge => DependencyTasks[edge]);
+ var vsizeEdgeTaskCompSources = Graph.OutEdges(node, CredentialType.Vsize).Select(edge => DependencyTasks[edge]);
+ SmartRequestNode smartRequestNode = new(
+ Enumerable.Empty<Task<Credential>>(),
+ Enumerable.Empty<Task<Credential>>(),
+ amountEdgeTaskCompSources,
+ vsizeEdgeTaskCompSources,
+ ZeroAmountCredentialPool,
+ ZeroVsizeCredentialPool);
+
+ var amountsToRequest = dependencyGraph.OutEdges(node, CredentialType.Amount).Select(e => (long)e.Value);
+ var vsizesToRequest = dependencyGraph.OutEdges(node, CredentialType.Vsize).Select(e => (long)e.Value);
- foreach (var edge in Graph.OutEdges(node, CredentialType.Amount).Where(edge => edge.Value == 0))
+ var task = smartRequestNode
+ .ConfirmConnectionTaskAsync(
+ connectionConfirmationTimeout,
+ aliceClient,
+ amountsToRequest,
+ vsizesToRequest,
+ node.EffectiveValue,
+ node.VsizeRemainingAllocation,
+ linkedCts.Token)
+ .ContinueWith((t) =>
{
- DependencyTasks[edge].SetResult(ZeroAmountCredentialPool.GetZeroCredential());
+ if (t.IsFaulted && t.Exception is { } exception)
+ {
+ // If one task is failing, cancel all the tasks and throw.
+ ctsOnError.Cancel();
+ throw exception;
}
+ }, linkedCts.Token);
- foreach ((var edge, var credential) in Enumerable.Zip(Graph.OutEdges(node, CredentialType.Vsize).Where(edge => edge.Value > 0), aliceClient.RealVsizeCredentials))
- {
- DependencyTasks[edge].SetResult(credential);
+ connectionConfirmationTasks.Add(task);
}
- foreach (var edge in Graph.OutEdges(node, CredentialType.Vsize).Where(edge => edge.Value == 0))
+ await Task.WhenAll(connectionConfirmationTasks).ConfigureAwait(false);
+
+ var amountEdges = Graph.Inputs.SelectMany(node => Graph.OutEdges(node, CredentialType.Amount));
+ var vsizeEdges = Graph.Inputs.SelectMany(node => Graph.OutEdges(node, CredentialType.Vsize));
+
+ // Check if all tasks were finished, otherwise Task.Result will block.
+ if (!amountEdges.Concat(vsizeEdges).All(edge => DependencyTasks[edge].Task.IsCompletedSuccessfully))
{
- DependencyTasks[edge].SetResult(ZeroVsizeCredentialPool.GetZeroCredential());
+ throw new InvalidOperationException("All Input nodes' outedges should be completed.");
}
}
+ public async Task StartReissuancesAsync(IEnumerable<AliceClient> aliceClients, BobClient bobClient, CancellationToken cancellationToken)
+ {
+ var aliceNodePairs = PairAliceClientAndRequestNodes(aliceClients, Graph);
+
// Build tasks and link them together.
List<SmartRequestNode> smartRequestNodes = new();
List<Task> alltask = new();
@@ -109,7 +144,7 @@ namespace WalletWasabi.WabiSabi.Client
// Check if all tasks were finished, otherwise Task.Result will block.
if (!amountEdges.Concat(vsizeEdges).All(edge => DependencyTasks[edge].Task.IsCompletedSuccessfully))
{
- throw new InvalidOperationException("");
+ throw new InvalidOperationException("All Output nodes' inedges should be completed.");
}
}

MODIFY
WalletWasabi/WabiSabi/Client/SmartRequestNode.cs
WalletWasabi/WabiSabi/Client/SmartRequestNode.cs
@@ -6,6 +6,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Crypto.ZeroKnowledge;
+using WalletWasabi.WabiSabi.Client.CredentialDependencies;
using WalletWasabi.WabiSabi.Crypto;
namespace WalletWasabi.WabiSabi.Client
@@ -35,6 +36,36 @@ namespace WalletWasabi.WabiSabi.Client
public ZeroCredentialPool ZeroAmountCredentialPool { get; }
public ZeroCredentialPool ZeroVsizeCredentialPool { get; }
+ public async Task ConfirmConnectionTaskAsync(
+ TimeSpan connectionConfirmationTimeout,
+ AliceClient aliceClient,
+ IEnumerable<long> amounts,
+ IEnumerable<long> vsizes,
+ Money effectiveValue,
+ int vsizeValue,
+ CancellationToken cancellationToken)
+ {
+ var amountsToRequest = AddExtraCredential(amounts, effectiveValue.Satoshi);
+ var vsizesToRequest = AddExtraCredential(vsizes, vsizeValue);
+
+ await aliceClient.ConfirmConnectionAsync(connectionConfirmationTimeout, amountsToRequest, vsizesToRequest, cancellationToken).ConfigureAwait(false);
+
+ var amountCredentials = aliceClient.RealAmountCredentials.Take(amounts.Where(v => v != 0).Count());
+ var vsizeCredentials = aliceClient.RealVsizeCredentials.Take(vsizes.Where(v => v != 0).Count());
+
+ amountCredentials = amountCredentials.Concat(Enumerable.Range(0, AmountCredentialTasks.Count() - amountCredentials.Count()).Select(_ => ZeroAmountCredentialPool.GetZeroCredential()));
+ vsizeCredentials = vsizeCredentials.Concat(Enumerable.Range(0, VsizeCredentialTasks.Count() - vsizeCredentials.Count()).Select(_ => ZeroVsizeCredentialPool.GetZeroCredential()));
+
+ foreach ((TaskCompletionSource<Credential> tcs, Credential credential) in AmountCredentialTasks.Zip(amountCredentials))
+ {
+ tcs.SetResult(credential);
+ }
+ foreach ((TaskCompletionSource<Credential> tcs, Credential credential) in VsizeCredentialTasks.Zip(vsizeCredentials))
+ {
+ tcs.SetResult(credential);
+ }
+ }
+
public async Task StartReissuanceAsync(BobClient bobClient, IEnumerable<long> amounts, IEnumerable<long> vsizes, CancellationToken cancellationToken)
{
await Task.WhenAll(AmountCredentialToPresentTasks.Concat(VsizeCredentialToPresentTasks)).ConfigureAwait(false);
@@ -76,7 +107,11 @@ namespace WalletWasabi.WabiSabi.Client
}
}
- public async Task StartOutputRegistrationAsync(BobClient bobClient, Money effectiveCost, Script scriptPubKey, CancellationToken cancellationToken)
+ public async Task StartOutputRegistrationAsync(
+ BobClient bobClient,
+ Money effectiveCost,
+ Script scriptPubKey,
+ CancellationToken cancellationToken)
{
await Task.WhenAll(AmountCredentialToPresentTasks.Concat(VsizeCredentialToPresentTasks)).ConfigureAwait(false);
IEnumerable<Credential> inputAmountCredentials = AmountCredentialToPresentTasks.Select(x => x.Result);
@@ -110,5 +145,26 @@ namespace WalletWasabi.WabiSabi.Client
return nonZeroValues.Concat(Enumerable.Repeat(0L, additionalZeros));
}
+
+ private IEnumerable<long> AddExtraCredential(IEnumerable<long> valuesToRequest, long sum)
+ {
+ var nonZeroValues = valuesToRequest.Where(v => v > 0);
+
+ if (nonZeroValues.Count() == ProtocolConstants.CredentialNumber)
+ {
+ return nonZeroValues;
+ }
+
+ var missing = sum - valuesToRequest.Sum();
+
+ if (missing > 0)
+ {
+ nonZeroValues = nonZeroValues.Append(missing);
+ }
+
+ var additionalZeros = ProtocolConstants.CredentialNumber - nonZeroValues.Count();
+
+ return nonZeroValues.Concat(Enumerable.Repeat(0L, additionalZeros));
+ }
}
}

Handling extra edges