Add "noted" status for first round disrupting
MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -17,6 +17,7 @@ using WalletWasabi.Backend;
using WalletWasabi.Backend.Models;
using WalletWasabi.Backend.Models.Requests;
using WalletWasabi.Backend.Models.Responses;
+using WalletWasabi.Crypto;
using WalletWasabi.Exceptions;
using WalletWasabi.KeyManagement;
using WalletWasabi.Logging;
@@ -75,6 +76,7 @@ namespace WalletWasabi.Tests
{
await Global.RpcClient.GenerateAsync(numberOfBlocksToGenerate); // Make sure everything is confirmed.
}
+ Global.Coordinator.UtxoReferee.Clear();
return ("password", Global.RpcClient, Global.RpcClient.Network, Global.Coordinator);
}
@@ -2062,6 +2064,82 @@ namespace WalletWasabi.Tests
}
}
+ [Fact, TestPriority(19)]
+ [Trait("Category", "RunOnCi")]
+ public async Task NotingTestsAsync()
+ {
+ (string password, RPCClient rpc, Network network, CcjCoordinator coordinator) = await InitializeTestEnvironmentAsync(1);
+
+ BlindingRsaKey blindingKey = coordinator.RsaKey;
+ Money denomination = Money.Coins(1m);
+ decimal coordinatorFeePercent = 0.1m;
+ int anonymitySet = 2;
+ int connectionConfirmationTimeout = 1;
+ bool doesNoteBeforeBan = true;
+ CcjRoundConfig roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 1, 1, 1, 24, doesNoteBeforeBan);
+ coordinator.UpdateRoundConfig(roundConfig);
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
+
+ Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);
+
+ var registerRequests = new List<(BitcoinWitPubKeyAddress changeOutputAddress, byte[] blindedData, InputProofModel[] inputsProofs)>();
+ for (int i = 0; i < roundConfig.AnonymitySet; i++)
+ {
+ BitcoinWitPubKeyAddress activeOutputAddress = new Key().PubKey.GetSegwitAddress(network);
+ BitcoinWitPubKeyAddress changeOutputAddress = new Key().PubKey.GetSegwitAddress(network);
+ Key inputKey = new Key();
+ BitcoinWitPubKeyAddress inputAddress = inputKey.PubKey.GetSegwitAddress(network);
+ var blinded = blindingKey.PubKey.Blind(activeOutputAddress.ScriptPubKey.ToBytes());
+
+ uint256 txHash = await rpc.SendToAddressAsync(inputAddress, Money.Coins(2));
+ await rpc.GenerateAsync(1);
+ Transaction transaction = await rpc.GetRawTransactionAsync(txHash);
+ Coin coin = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();
+ OutPoint input = coin.Outpoint;
+
+ InputProofModel inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = inputKey.SignMessage(ByteHelpers.ToHex(blinded.BlindedData)) };
+ InputProofModel[] inputsProofs = new InputProofModel[] { inputProof };
+ registerRequests.Add((changeOutputAddress, blinded.BlindedData, inputsProofs));
+ await AliceClient.CreateNewAsync(network, changeOutputAddress, blinded.BlindedData, inputsProofs, baseUri);
+ }
+
+ await WaitForTimeoutAsync(baseUri);
+
+ int bannedCount = coordinator.UtxoReferee.CountBanned(false);
+ Assert.Equal(0, bannedCount);
+ int notedCount = coordinator.UtxoReferee.CountBanned(true);
+ Assert.Equal(anonymitySet, notedCount);
+
+ foreach (var registerRequest in registerRequests)
+ {
+ await AliceClient.CreateNewAsync(network, registerRequest.changeOutputAddress, registerRequest.blindedData, registerRequest.inputsProofs, baseUri);
+ }
+
+ await WaitForTimeoutAsync(baseUri);
+
+ bannedCount = coordinator.UtxoReferee.CountBanned(false);
+ Assert.Equal(anonymitySet, bannedCount);
+ notedCount = coordinator.UtxoReferee.CountBanned(true);
+ Assert.Equal(anonymitySet, notedCount);
+ }
+
+ private static async Task WaitForTimeoutAsync(Uri baseUri)
+ {
+ using (var satoshiClient = new SatoshiClient(baseUri))
+ {
+ var times = 0;
+ while (!(await satoshiClient.GetAllRoundStatesAsync()).All(x => x.Phase == CcjRoundPhase.InputRegistration))
+ {
+ await Task.Delay(100);
+ if (times > 50) // 5 sec, 3 should be enough
+ {
+ throw new TimeoutException("Not all rouns were in InputRegistration.");
+ }
+ times++;
+ }
+ }
+ }
+
[Fact, TestPriority(19)]
[Trait("Category", "RunOnCi")]
public async Task BanningTestsAsync()

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
@@ -157,7 +157,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
Logger.LogInfo<CcjRound>($"New round ({RoundId}) is created.\n\t" +
$"{nameof(Denomination)}: {Denomination.ToString(false, true)} BTC.\n\t" +
$"{nameof(ConfirmationTarget)}: {ConfirmationTarget}.\n\t" +
- $"{nameof(CoordinatorFeePercent)}: {CoordinatorFeePercent}.\n\t" +
+ $"{nameof(CoordinatorFeePercent)}: {CoordinatorFeePercent}%.\n\t" +
$"{nameof(AnonymitySet)}: {AnonymitySet}.");
}
catch (Exception ex)

MODIFY
WalletWasabi/Services/UtxoReferee.cs
WalletWasabi/Services/UtxoReferee.cs
@@ -96,8 +96,7 @@ namespace WalletWasabi.Services
}
var lines = new List<string>();
- var append = false;
- var updatedOrAdded = false;
+ var updated = false;
foreach (var utxo in toBan)
{
var isNoted = true;
@@ -126,51 +125,32 @@ namespace WalletWasabi.Services
isNoted = false;
}
}
- if (!BannedUtxos.TryAdd(utxo, (severity, timeOfBan, isNoted, bannedForRound)))
+ if (BannedUtxos.TryAdd(utxo, (severity, timeOfBan, isNoted, bannedForRound)))
{
- var elem = BannedUtxos[utxo];
- if (elem.severity != severity)
- {
- elem.severity = severity;
- updatedOrAdded = true;
+ string line = $"{timeOfBan.ToString(CultureInfo.InvariantCulture)}:{severity}:{utxo.N}:{utxo.Hash}:{isNoted}:{bannedForRound}";
+ lines.Add(line);
}
- if (elem.isNoted != isNoted)
+ else
{
- elem.isNoted = isNoted;
- updatedOrAdded = true;
- }
- if (elem.bannedForRound != bannedForRound)
+ var elem = BannedUtxos[utxo];
+ if (elem.isNoted != isNoted)
{
- elem.bannedForRound = bannedForRound;
- updatedOrAdded = true;
+ BannedUtxos[utxo] = (elem.severity, elem.timeOfBan, isNoted, elem.bannedForRound);
+ updated = true;
}
}
- else
- {
- append = true;
- updatedOrAdded = true;
-
- string line = $"{timeOfBan.ToString(CultureInfo.InvariantCulture)}:{severity}:{utxo.N}:{utxo.Hash}:{isNoted}:{bannedForRound}";
- lines.Add(line);
- }
Logger.LogInfo<UtxoReferee>($"UTXO {(isNoted ? "noted" : "banned")} with severity: {severity}. UTXO: {utxo.N}:{utxo.Hash}.");
}
- if (updatedOrAdded)
- {
- if (append)
- {
- if (lines.Count != 0)
- {
- await File.AppendAllLinesAsync(BannedUtxosFilePath, lines);
- }
- }
- else
+ if (updated) // If at any time we set updated then we must update the whole thing.
{
var allLines = BannedUtxos.Select(x => $"{x.Value.timeOfBan.ToString(CultureInfo.InvariantCulture)}:{x.Value.severity}:{x.Key.N}:{x.Key.Hash}:{x.Value.isNoted}:{x.Value.bannedForRound}");
await File.WriteAllLinesAsync(BannedUtxosFilePath, allLines);
}
+ else if (lines.Count != 0) // If we don't have to update the whole thing, we must check if we added a line and so only append.
+ {
+ await File.AppendAllLinesAsync(BannedUtxosFilePath, lines);
}
}
@@ -228,5 +208,11 @@ namespace WalletWasabi.Services
return BannedUtxos.Count(x => !x.Value.isNoted);
}
}
+
+ public void Clear()
+ {
+ BannedUtxos.Clear();
+ File.Delete(BannedUtxosFilePath);
+ }
}
}

Add "noted" status for first round disrupting