Separate different types of RequestNodes
MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -72,11 +72,11 @@ namespace WalletWasabi.WabiSabi.Client
DependencyGraph dependencyGraph = DependencyGraph.ResolveCredentialDependencies(aliceClients.Select(a => a.Coin), outputTxOuts, roundState.FeeRate, roundState.MaxVsizeAllocationPerAlice);
DependencyGraphResolver dgr = new(dependencyGraph, ZeroAmountCredentialPool, ZeroVsizeCredentialPool);
var bobClient = CreateBobClient(roundState);
- var outputCredentials = await dgr.ResolveAsync(aliceClients, bobClient, cancellationToken).ConfigureAwait(false);
+ await dgr.ResolveAsync(aliceClients, bobClient, cancellationToken).ConfigureAwait(false);
// Output registration.
roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState.Id, rs => rs.Phase == Phase.OutputRegistration, cancellationToken).ConfigureAwait(false);
- await RegisterOutputsAsync(bobClient, outputTxOuts, outputCredentials, roundState.FeeRate, cancellationToken).ConfigureAwait(false);
+ await dgr.StartOutputRegistrationsAsync(outputTxOuts, bobClient, cancellationToken).ConfigureAwait(false);
// Signing.
roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState.Id, rs => rs.Phase == Phase.TransactionSigning, cancellationToken).ConfigureAwait(false);
@@ -170,44 +170,6 @@ namespace WalletWasabi.WabiSabi.Client
yield return realAmountCredentialValues.Zip(realVsizeCredentialValues, outputValues, (a, v, o) => (a, v, o));
}
- private async Task RegisterOutputsAsync(BobClient bobClient,
- IEnumerable<TxOut> outputTxOuts,
- List<(Money Amount, Credential[] AmounCreds, Credential[] VsizeCreds)> outputCredentials,
- FeeRate feeRate,
- CancellationToken cancellationToken)
- {
- async Task<TxOut?> RegisterOutputTask(BobClient bobClient, TxOut output, Credential[] realAmountCredentials, Credential[] realVsizeCredentials)
- {
- try
- {
- var effectiveCost = output.EffectiveCost(feeRate);
- await bobClient.RegisterOutputAsync(output.Value, output.ScriptPubKey, realAmountCredentials, realVsizeCredentials, cancellationToken).ConfigureAwait(false);
- return output;
- }
- catch (Exception e)
- {
- Logger.LogWarning($"Round ({bobClient.RoundId}), Bob ({{output.ScriptPubKey}}): {nameof(BobClient.RegisterOutputAsync)} failed, reason:'{e}'.");
- return default;
- }
- }
-
- List<(TxOut Output, Credential[] RealAmountCredentials, Credential[] RealVsizeCredentials)> outputWithCredentials = new();
- var remainingCredentials = outputCredentials.ToList();
-
- foreach (var txOut in outputTxOuts)
- {
- var creds = remainingCredentials.First(op => op.Amount == txOut.Value);
- outputWithCredentials.Add((txOut, creds.AmounCreds, creds.VsizeCreds));
-
- // Make sure to not use the same credentials twice.
- remainingCredentials.Remove(creds);
- }
-
- var outputRegisterRequests = outputWithCredentials.Select(output => RegisterOutputTask(bobClient, output.Output, output.RealAmountCredentials, output.RealVsizeCredentials));
-
- await Task.WhenAll(outputRegisterRequests).ConfigureAwait(false);
- }
-
private BobClient CreateBobClient(RoundState roundState)
{
return new BobClient(

MODIFY
WalletWasabi/WabiSabi/Client/CredentialDependencies/DependencyGraph.cs
WalletWasabi/WabiSabi/Client/CredentialDependencies/DependencyGraph.cs
@@ -15,13 +15,13 @@ namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
public ImmutableList<RequestNode> Vertices { get; init; } = ImmutableList<RequestNode>.Empty;
// The input nodes, in the order they were added
- public ImmutableList<RequestNode> Inputs { get; init; } = ImmutableList<RequestNode>.Empty;
+ public ImmutableList<InputNode> Inputs { get; init; } = ImmutableList<InputNode>.Empty;
// The output nodes, in the order they were added
- public ImmutableList<RequestNode> Outputs { get; init; } = ImmutableList<RequestNode>.Empty;
+ public ImmutableList<OutputNode> Outputs { get; init; } = ImmutableList<OutputNode>.Empty;
// The reissuance nodes, unsorted
- public ImmutableList<RequestNode> Reissuances { get; init; } = ImmutableList<RequestNode>.Empty;
+ public ImmutableList<ReissuanceNode> Reissuances { get; init; } = ImmutableList<ReissuanceNode>.Empty;
// Internal properties used to keep track of effective values and edges
public ImmutableSortedDictionary<CredentialType, CredentialEdgeSet> EdgeSets { get; init; } = ImmutableSortedDictionary<CredentialType, CredentialEdgeSet>.Empty
@@ -110,13 +110,13 @@ namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
// approach.
private DependencyGraph AddInput(IEnumerable<ulong> values)
{
- var node = new RequestNode(values.Select(y => (long)y), inDegree: 0, outDegree: K, zeroOnlyOutDegree: K * (K - 1));
+ var node = new InputNode(values.Select(y => (long)y));
return (this with { Inputs = Inputs.Add(node) }).AddNode(node);
}
private DependencyGraph AddOutput(IEnumerable<ulong> values)
{
- var node = new RequestNode(values.Select(y => -1 * (long)y), inDegree: K, outDegree: 0, zeroOnlyOutDegree: 0);
+ var node = new OutputNode(values.Select(y => -1 * (long)y));
return (this with { Outputs = Outputs.Add(node) }).AddNode(node);
}
private DependencyGraph AddInputs(IEnumerable<IEnumerable<ulong>> values) => values.Aggregate(this, (g, v) => g.AddInput(v));
@@ -125,7 +125,7 @@ namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
private (DependencyGraph, RequestNode) AddReissuance()
{
- var node = new RequestNode(Enumerable.Repeat(0L, K), inDegree: K, outDegree: K, zeroOnlyOutDegree: K * (K - 1));
+ var node = new ReissuanceNode();
return ((this with { Reissuances = Reissuances.Add(node) }).AddNode(node), node);
}

ADD
WalletWasabi/WabiSabi/Client/CredentialDependencies/InputNode.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
+{
+ public class InputNode : RequestNode
+ {
+ public InputNode(IEnumerable<long> values) : base(values, 0, DependencyGraph.K, DependencyGraph.K * (DependencyGraph.K - 1))
+ {
+ }
+ }
+}

ADD
WalletWasabi/WabiSabi/Client/CredentialDependencies/OutputNode.cs
+using NBitcoin;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
+{
+ public class OutputNode : RequestNode
+ {
+ public OutputNode(IEnumerable<long> values) : base(values, DependencyGraph.K, 0, 0)
+ {
+ }
+
+ public Money EffectiveCost => Money.Satoshis(Math.Abs(InitialBalance(CredentialType.Amount)));
+ }
+}

ADD
WalletWasabi/WabiSabi/Client/CredentialDependencies/ReissuanceNode.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
+{
+ public class ReissuanceNode : RequestNode
+ {
+ public ReissuanceNode() :
+ base(
+ Enumerable.Repeat(0L, DependencyGraph.CredentialTypes.Count()),
+ DependencyGraph.K,
+ DependencyGraph.K,
+ DependencyGraph.K * (DependencyGraph.K - 1))
+ {
+ }
+ }
+}

MODIFY
WalletWasabi/WabiSabi/Client/CredentialDependencies/RequestNode.cs
WalletWasabi/WabiSabi/Client/CredentialDependencies/RequestNode.cs
@@ -4,7 +4,7 @@ using WalletWasabi.Helpers;
namespace WalletWasabi.WabiSabi.Client.CredentialDependencies
{
- public class RequestNode
+ public abstract class RequestNode
{
public RequestNode(IEnumerable<long> values, int inDegree, int outDegree, int zeroOnlyOutDegree)
{

MODIFY
WalletWasabi/WabiSabi/Client/DependencyGraphResolver.cs
WalletWasabi/WabiSabi/Client/DependencyGraphResolver.cs
@@ -20,7 +20,7 @@ namespace WalletWasabi.WabiSabi.Client
{
Graph = graph;
var allInEdges = Enum.GetValues<CredentialType>()
- .SelectMany(type => Enumerable.Concat(Graph.Reissuances, Graph.Outputs)
+ .SelectMany(type => Enumerable.Concat<RequestNode>(Graph.Reissuances, Graph.Outputs)
.SelectMany(node => Graph.EdgeSets[type].InEdges(node)));
DependencyTasks = allInEdges.ToDictionary(edge => edge, _ => new TaskCompletionSource<Credential>(TaskCreationOptions.RunContinuationsAsynchronously));
@@ -33,7 +33,7 @@ namespace WalletWasabi.WabiSabi.Client
public ZeroCredentialPool ZeroVsizeCredentialPool { get; }
private Dictionary<CredentialDependency, TaskCompletionSource<Credential>> DependencyTasks { get; }
- public async Task<List<(Money Amount, Credential[] AmounCreds, Credential[] VsizeCreds)>> ResolveAsync(IEnumerable<AliceClient> aliceClients, BobClient bobClient, CancellationToken cancellationToken)
+ public async Task ResolveAsync(IEnumerable<AliceClient> aliceClients, BobClient bobClient, CancellationToken cancellationToken)
{
var aliceNodePairs = PairAliceClientAndRequestNodes(aliceClients, Graph);
@@ -83,7 +83,9 @@ namespace WalletWasabi.WabiSabi.Client
inputAmountEdgeTasks,
inputVsizeEdgeTasks,
outputAmountEdgeTaskCompSources,
- outputVsizeEdgeTaskCompSources);
+ outputVsizeEdgeTaskCompSources,
+ ZeroAmountCredentialPool,
+ ZeroVsizeCredentialPool);
var task = smartRequestNode
.StartAsync(bobClient, requestedAmounts, requestedVSizes, linkedCts.Token)
@@ -109,21 +111,47 @@ namespace WalletWasabi.WabiSabi.Client
{
throw new InvalidOperationException("");
}
+ }
- var amountCreds = amountEdges.Select(edge => DependencyTasks[edge].Task.Result).Where(cred => cred != null);
- var vsizeCreds = vsizeEdges.Select(edge => DependencyTasks[edge].Task.Result).Where(cred => cred != null);
+ public async Task StartOutputRegistrationsAsync(IEnumerable<TxOut> txOuts, BobClient bobClient, CancellationToken cancellationToken)
+ {
+ List<Task> outputTasks = new();
- List<(Money, Credential[], Credential[])> outputs = new();
+ using CancellationTokenSource ctsOnError = new();
+ using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ctsOnError.Token);
- foreach (var (amountCred, vsizeCred) in amountCreds.Zip(vsizeCreds))
+ foreach (var (node, txOut) in Enumerable.Zip(Graph.Outputs, txOuts))
{
- outputs.Add((amountCred.Amount.ToMoney(), new[] { amountCred }, new[] { vsizeCred }));
+ var amountCredsToPresentTasks = Graph.InEdges(node, CredentialType.Amount).Select(edge => DependencyTasks[edge].Task);
+ var vsizeCredsToPresentTasks = Graph.InEdges(node, CredentialType.Vsize).Select(edge => DependencyTasks[edge].Task);
+
+ SmartRequestNode smartRequestNode = new(
+ amountCredsToPresentTasks,
+ vsizeCredsToPresentTasks,
+ Array.Empty<TaskCompletionSource<Credential>>(),
+ Array.Empty<TaskCompletionSource<Credential>>(),
+ ZeroAmountCredentialPool,
+ ZeroVsizeCredentialPool
+ );
+
+ var task = smartRequestNode
+ .StartOutputRegistrationAsync(bobClient, node.EffectiveCost, txOut.ScriptPubKey, cancellationToken)
+ .ContinueWith((t) =>
+ {
+ if (t.IsFaulted && t.Exception is { } exception)
+ {
+ // If one task is failing, cancel all the tasks and throw.
+ ctsOnError.Cancel();
+ throw exception;
+ }
+ }, linkedCts.Token);
+ outputTasks.Add(task);
}
- return outputs;
+ await Task.WhenAll(outputTasks).ConfigureAwait(false);
}
- private IEnumerable<(AliceClient AliceClient, RequestNode Node)> PairAliceClientAndRequestNodes(IEnumerable<AliceClient> aliceClients, DependencyGraph graph)
+ private IEnumerable<(AliceClient AliceClient, InputNode Node)> PairAliceClientAndRequestNodes(IEnumerable<AliceClient> aliceClients, DependencyGraph graph)
{
var inputNodes = graph.Inputs;
@@ -132,7 +160,7 @@ namespace WalletWasabi.WabiSabi.Client
throw new InvalidOperationException("Graph vs Alice inputs mismatch");
}
- return aliceClients.OrderBy(alice => alice.Coin.Amount).Zip(inputNodes.OrderBy(node => node.Values[(int)CredentialType.Amount]));
+ return Enumerable.Zip(aliceClients, inputNodes);
}
}
}

MODIFY
WalletWasabi/WabiSabi/Client/SmartRequestNode.cs
WalletWasabi/WabiSabi/Client/SmartRequestNode.cs
+using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -5,6 +6,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Crypto.ZeroKnowledge;
+using WalletWasabi.WabiSabi.Crypto;
namespace WalletWasabi.WabiSabi.Client
{
@@ -14,24 +16,30 @@ namespace WalletWasabi.WabiSabi.Client
IEnumerable<Task<Credential>> inputAmountCredentialTasks,
IEnumerable<Task<Credential>> inputVsizeCredentialTasks,
IEnumerable<TaskCompletionSource<Credential>> outputAmountCredentialTasks,
- IEnumerable<TaskCompletionSource<Credential>> outputVsizeCredentialTasks)
+ IEnumerable<TaskCompletionSource<Credential>> outputVsizeCredentialTasks,
+ ZeroCredentialPool zeroAmountCredentialPool,
+ ZeroCredentialPool zeroVsizeCredentialPool)
{
- InputAmountCredentialTasks = inputAmountCredentialTasks;
- InputVsizeCredentialTasks = inputVsizeCredentialTasks;
- OutputAmountCredentialTasks = outputAmountCredentialTasks;
- OutputVsizeCredentialTasks = outputVsizeCredentialTasks;
+ AmountCredentialToPresentTasks = inputAmountCredentialTasks;
+ VsizeCredentialToPresentTasks = inputVsizeCredentialTasks;
+ AmountCredentialTasks = outputAmountCredentialTasks;
+ VsizeCredentialTasks = outputVsizeCredentialTasks;
+ ZeroAmountCredentialPool = zeroAmountCredentialPool;
+ ZeroVsizeCredentialPool = zeroVsizeCredentialPool;
}
- public IEnumerable<Task<Credential>> InputAmountCredentialTasks { get; }
- public IEnumerable<Task<Credential>> InputVsizeCredentialTasks { get; }
- public IEnumerable<TaskCompletionSource<Credential>> OutputAmountCredentialTasks { get; }
- public IEnumerable<TaskCompletionSource<Credential>> OutputVsizeCredentialTasks { get; }
+ public IEnumerable<Task<Credential>> AmountCredentialToPresentTasks { get; }
+ public IEnumerable<Task<Credential>> VsizeCredentialToPresentTasks { get; }
+ public IEnumerable<TaskCompletionSource<Credential>> AmountCredentialTasks { get; }
+ public IEnumerable<TaskCompletionSource<Credential>> VsizeCredentialTasks { get; }
+ public ZeroCredentialPool ZeroAmountCredentialPool { get; }
+ public ZeroCredentialPool ZeroVsizeCredentialPool { get; }
public async Task StartAsync(BobClient bobClient, IEnumerable<long> amounts, IEnumerable<long> vsizes, CancellationToken cancellationToken)
{
- await Task.WhenAll(InputAmountCredentialTasks.Concat(InputVsizeCredentialTasks)).ConfigureAwait(false);
- IEnumerable<Credential> inputAmountCredentials = InputAmountCredentialTasks.Select(x => x.Result).Where(x => x is { });
- IEnumerable<Credential> inputVsizeCredentials = InputVsizeCredentialTasks.Select(x => x.Result).Where(x => x is { });
+ await Task.WhenAll(AmountCredentialToPresentTasks.Concat(VsizeCredentialToPresentTasks)).ConfigureAwait(false);
+ IEnumerable<Credential> inputAmountCredentials = AmountCredentialToPresentTasks.Select(x => x.Result);
+ IEnumerable<Credential> inputVsizeCredentials = VsizeCredentialToPresentTasks.Select(x => x.Result);
var amountsToRequest = AddExtraCredential(amounts, inputAmountCredentials);
var vsizesToRequest = AddExtraCredential(vsizes, inputVsizeCredentials);
@@ -42,16 +50,30 @@ namespace WalletWasabi.WabiSabi.Client
inputVsizeCredentials,
cancellationToken).ConfigureAwait(false);
- foreach ((TaskCompletionSource<Credential> tcs, Credential credential) in OutputAmountCredentialTasks.Zip(result.RealAmountCredentials))
+ foreach ((TaskCompletionSource<Credential> tcs, Credential credential) in AmountCredentialTasks.Zip(result.RealAmountCredentials))
{
tcs.SetResult(credential);
}
- foreach ((TaskCompletionSource<Credential> tcs, Credential credential) in OutputVsizeCredentialTasks.Zip(result.RealVsizeCredentials))
+ foreach ((TaskCompletionSource<Credential> tcs, Credential credential) in VsizeCredentialTasks.Zip(result.RealVsizeCredentials))
{
tcs.SetResult(credential);
}
}
+ 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);
+ IEnumerable<Credential> inputVsizeCredentials = VsizeCredentialToPresentTasks.Select(x => x.Result);
+
+ await bobClient.RegisterOutputAsync(
+ effectiveCost, scriptPubKey,
+ inputAmountCredentials,
+ inputVsizeCredentials,
+ cancellationToken
+ ).ConfigureAwait(false);
+ }
+
private IEnumerable<long> AddExtraCredential(IEnumerable<long> valuesToRequest, IEnumerable<Credential> presentedCredentials)
{
var nonZeroValues = valuesToRequest.Where(v => v > 0);

Separate different types of RequestNodes