Progress input registration
MODIFY
WalletWasabi/Backend/Models/Responses/CcjStatusResponse.cs
WalletWasabi/Backend/Models/Responses/CcjStatusResponse.cs
@@ -23,6 +23,8 @@ namespace WalletWasabi.Backend.Models.Responses
public int MaximumInputCountPerPeer { get; set; }
+ public int RegistrationTimeout { get; set; }
+
[JsonConverter(typeof(MoneySatoshiConverter))]
public Money FeePerInputs { get; set; }

MODIFY
WalletWasabi/ChaumianCoinJoin/Alice.cs
WalletWasabi/ChaumianCoinJoin/Alice.cs
@@ -15,23 +15,25 @@ namespace WalletWasabi.ChaumianCoinJoin
public Money InputSum { get; }
- public Money FeeToPay { get; }
+ public Money NetworkFeeToPay { get; }
public Money OutputSumWithoutCoordinatorFeeAndDenomination { get; }
- public Dictionary<OutPoint, TxOut> Inputs { get; }
+ public IEnumerable<(OutPoint OutPoint, TxOut Output)> Inputs { get; }
public Script ChangeOutputScript { get; }
+ public byte[] BlindedOutput { get; }
+
public Money GetChangeAmount(Money denomination, Money coordinatorFee) => OutputSumWithoutCoordinatorFeeAndDenomination - denomination - coordinatorFee;
public AliceState State { get; set; }
- public Alice(Dictionary<OutPoint, TxOut> inputs, Money feeToPay, Script changeOutputScript)
+ public Alice(IEnumerable<(OutPoint OutPoint, TxOut Output)> inputs, Money networkFeeToPay, Script changeOutputScript, byte[] blindedOutput)
{
- Guard.NotNullOrEmpty(nameof(inputs), inputs);
- Inputs = inputs;
- FeeToPay = Guard.NotNull(nameof(feeToPay), feeToPay);
+ Inputs = Guard.NotNullOrEmpty(nameof(inputs), inputs);
+ NetworkFeeToPay = Guard.NotNull(nameof(networkFeeToPay), networkFeeToPay);
+ BlindedOutput = Guard.NotNullOrEmpty(nameof(blindedOutput), blindedOutput);
Guard.NotNull(nameof(changeOutputScript), changeOutputScript);
// 33 bytes maximum: https://bitcoin.stackexchange.com/a/46379/26859
@@ -41,14 +43,13 @@ namespace WalletWasabi.ChaumianCoinJoin
throw new ArgumentOutOfRangeException(nameof(changeOutputScript), byteCount, $"Can be maximum 33 bytes.");
}
ChangeOutputScript = changeOutputScript;
-
LastSeen = DateTimeOffset.UtcNow;
UniqueId = Guid.NewGuid();
- InputSum = inputs.Sum(x => x.Value.Value);
+ InputSum = inputs.Sum(x => x.Output.Value);
- OutputSumWithoutCoordinatorFeeAndDenomination = InputSum - FeeToPay;
+ OutputSumWithoutCoordinatorFeeAndDenomination = InputSum - NetworkFeeToPay;
State = AliceState.InputsRegistered;
}

MODIFY
WalletWasabi/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/ChaumianCoinJoin/CcjRound.cs
@@ -39,6 +39,8 @@ namespace WalletWasabi.ChaumianCoinJoin
public CcjRoundStatus Status { get; private set; }
+ public TimeSpan AliceRegistrationTimeout { get; set; }
+
public CcjRound(RPCClient rpc, CcjRoundConfig config)
{
Interlocked.Increment(ref RoundCount);
@@ -50,6 +52,7 @@ namespace WalletWasabi.ChaumianCoinJoin
ConfirmationTarget = (int)config.ConfirmationTarget;
CoordinatorFeePercent = (decimal)config.CoordinatorFeePercent;
AnonymitySet = (int)config.AnonymitySet;
+ AliceRegistrationTimeout = TimeSpan.FromSeconds((long)config.ConnectionConfirmationTimeout);
Phase = CcjRoundPhase.InputRegistration;
Status = CcjRoundStatus.NotStarted;
@@ -112,7 +115,7 @@ namespace WalletWasabi.ChaumianCoinJoin
}
else if (Phase == CcjRoundPhase.InputRegistration)
{
- RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(y => y.Key));
+ RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(y => y.OutPoint));
Phase = CcjRoundPhase.ConnectionConfirmation;
ret = CcjRoundPhase.ConnectionConfirmation;
@@ -152,7 +155,7 @@ namespace WalletWasabi.ChaumianCoinJoin
{
foreach (var input in alice.Inputs)
{
- transaction.AddInput(new TxIn(input.Key));
+ transaction.AddInput(new TxIn(input.OutPoint));
}
Money changeAmount = alice.GetChangeAmount(newDenomination, coordinatorFeePerAlice);
if (changeAmount > Money.Zero) // If the coordinator fee would make change amount to be negative or zero then no need to pay it.
@@ -241,7 +244,17 @@ namespace WalletWasabi.ChaumianCoinJoin
}
}
- public void RemoveAlicesByState(AliceState state)
+ public void AddAlice(Alice alice)
+ {
+ using (RoundSyncronizerLock.Lock())
+ {
+ Alices.Add(alice);
+ }
+
+ StartAliceTimeout(alice.UniqueId);
+ }
+
+ public void RemoveAlicesBy(AliceState state)
{
using (RoundSyncronizerLock.Lock())
{
@@ -249,6 +262,72 @@ namespace WalletWasabi.ChaumianCoinJoin
}
}
+ public void RemoveAlicesBy(Guid id)
+ {
+ using (RoundSyncronizerLock.Lock())
+ {
+ Alices.RemoveAll(x => x.UniqueId == id);
+ }
+ }
+
+ public void RemoveAliceIfContains(OutPoint input)
+ {
+ using (RoundSyncronizerLock.Lock())
+ {
+ Alices.RemoveAll(x => x.Inputs.Any(y => y.OutPoint == input));
+ }
+ }
+
+ public bool ContainsBlindedOutput(byte[] blindedOutput, out List<Alice> alices)
+ {
+ alices = new List<Alice>();
+
+ using (RoundSyncronizerLock.Lock())
+ {
+ foreach(Alice alice in Alices)
+ {
+ if(ByteHelpers.CompareFastUnsafe(alice.BlindedOutput, blindedOutput))
+ {
+ alices.Add(alice);
+ }
+ }
+ }
+
+ if (alices.Count > 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public bool ContainsInput(OutPoint input, out List<Alice> alices)
+ {
+ alices = new List<Alice>();
+
+ using (RoundSyncronizerLock.Lock())
+ {
+ foreach (Alice alice in Alices)
+ {
+ if (alice.Inputs.Any(x => x.OutPoint == input))
+ {
+ alices.Add(alice);
+ }
+ }
+ }
+
+ if (alices.Count > 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
public int CountBobs(bool syncronized = true)
{
if (syncronized)
@@ -272,7 +351,7 @@ namespace WalletWasabi.ChaumianCoinJoin
}
}
- public void SetAliceTimeout(Guid uniqueId, TimeSpan timeout)
+ public void StartAliceTimeout(Guid uniqueId)
{
// 1. Find Alice and set its LastSeen propery.
var foundAlice = false;
@@ -292,7 +371,7 @@ namespace WalletWasabi.ChaumianCoinJoin
Task.Run(async () =>
{
// 2. Delay asyncronously to the requested timeout
- await Task.Delay(timeout);
+ await Task.Delay(AliceRegistrationTimeout);
using (await RoundSyncronizerLock.LockAsync())
{

MODIFY
WalletWasabi/ChaumianCoinJoin/CcjRoundConfig.cs
WalletWasabi/ChaumianCoinJoin/CcjRoundConfig.cs
@@ -44,7 +44,6 @@ namespace WalletWasabi.ChaumianCoinJoin
[JsonProperty(PropertyName = "SigningTimeout")]
public long? SigningTimeout { get; private set; }
-
public CcjRoundConfig()
{

MODIFY
WalletWasabi/Converters/BitcoinEncryptedSecretNoECConverter.cs
WalletWasabi/Converters/BitcoinEncryptedSecretNoECConverter.cs
@@ -4,7 +4,7 @@ using System;
using System.Collections.Generic;
using System.Text;
-namespace WalletWasabi.JsonConverters
+namespace WalletWasabi.Converters
{
public class BitcoinEncryptedSecretNoECConverter : JsonConverter
{

MODIFY
WalletWasabi/Services/CcjCoordinator.cs
WalletWasabi/Services/CcjCoordinator.cs
@@ -58,7 +58,7 @@ namespace WalletWasabi.Services
{
// Only fail if less two one Alice is registered.
// Don't ban anyone, it's ok if they lost connection.
- round.RemoveAlicesByState(AliceState.InputsRegistered);
+ round.RemoveAlicesBy(AliceState.InputsRegistered);
int aliceCountAfterConnectionConfirmationTimeout = round.CountAlices();
if (aliceCountAfterConnectionConfirmationTimeout < 2)
{
@@ -132,6 +132,14 @@ namespace WalletWasabi.Services
}
}
+ public CcjRound GetCurrentInputRegisterableRound()
+ {
+ using (RoundsListLock.Lock())
+ {
+ return Rounds.First(x => x.Status == CcjRoundStatus.Running && x.Phase == CcjRoundPhase.InputRegistration); // not FirstOrDefault, it must always exist
+ }
+ }
+
public CcjRound GetNextRound()
{
using (RoundsListLock.Lock())

Progress input registration