Test POST signatures
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -650,7 +650,7 @@ namespace WalletWasabi.Backend.Controllers
}
// Check duplicates.
- if(string.IsNullOrWhiteSpace(round.SignedCoinJoin.Inputs[index].WitScript?.ToString())) // Not sure why WitScript?.ToString() is needed, there was something wrong in previous HiddenWallet version if I didn't do this.
+ if(!string.IsNullOrWhiteSpace(round.SignedCoinJoin.Inputs[index].WitScript?.ToString())) // Not sure why WitScript?.ToString() is needed, there was something wrong in previous HiddenWallet version if I didn't do this.
{
return BadRequest($"Input is already signed.");
}

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1784,6 +1784,7 @@ namespace WalletWasabi.Tests
roundId = inputsResp.RoundId;
}
+ await Task.Delay(10);
using (var response = await client.SendAsync(HttpMethod.Get, "/api/v1/btc/chaumiancoinjoin/states/"))
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -2109,11 +2110,53 @@ namespace WalletWasabi.Tests
Assert.Contains(outputAddress1.ScriptPubKey, unsignedCoinJoin.Outputs.Select(x => x.ScriptPubKey));
Assert.Contains(outputAddress2.ScriptPubKey, unsignedCoinJoin.Outputs.Select(x => x.ScriptPubKey));
- Assert.Contains(Constants.GetCoordinatorAddress(Network.RegTest).ScriptPubKey, unsignedCoinJoin.Outputs.Select(x => x.ScriptPubKey));
- Assert.True(3 == unsignedCoinJoin.Outputs.Count); // Because the two input is equal, so change addresses won't be used.
+ Assert.True(2 == unsignedCoinJoin.Outputs.Count); // Because the two input is equal, so change addresses won't be used, nor coordinator fee will be taken.
Assert.Contains(input1, unsignedCoinJoin.Inputs.Select(x => x.PrevOut));
Assert.Contains(input2, unsignedCoinJoin.Inputs.Select(x => x.PrevOut));
Assert.True(2 == unsignedCoinJoin.Inputs.Count);
+
+ var partSignedCj1 = new Transaction(unsignedCoinJoin.ToHex());
+ var partSignedCj2 = new Transaction(unsignedCoinJoin.ToHex());
+ new TransactionBuilder()
+ .AddKeys(key1)
+ .AddCoins(new Coin(tx1, input1.N))
+ .SignTransactionInPlace(partSignedCj1, SigHash.All);
+ new TransactionBuilder()
+ .AddKeys(key2)
+ .AddCoins(new Coin(tx2, input2.N))
+ .SignTransactionInPlace(partSignedCj2, SigHash.All);
+
+ var myDic1 = new Dictionary<int, string>();
+ var myDic2 = new Dictionary<int, string>();
+
+ for(int i = 0; i < unsignedCoinJoin.Inputs.Count; i++)
+ {
+ var input = unsignedCoinJoin.Inputs[i];
+ if (input.PrevOut == input1)
+ {
+ myDic1.Add(i, partSignedCj1.Inputs[i].WitScript.ToString());
+ }
+ if (input.PrevOut == input2)
+ {
+ myDic2.Add(i, partSignedCj2.Inputs[i].WitScript.ToString());
+ }
+ }
+
+ var jsonSigs1 = JsonConvert.SerializeObject(myDic1, Formatting.None);
+ var jsonSigs2 = JsonConvert.SerializeObject(myDic2, Formatting.None);
+ var sigReqCont1 = new StringContent(jsonSigs1, Encoding.UTF8, "application/json");
+ var sigReqCont2 = new StringContent(jsonSigs2, Encoding.UTF8, "application/json");
+ using (var response = await client.SendAsync(HttpMethod.Post, $"/api/v1/btc/chaumiancoinjoin/signatures?uniqueId={uniqueAliceId1}&roundId={roundId}", sigReqCont1))
+ {
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ }
+ using (var response = await client.SendAsync(HttpMethod.Post, $"/api/v1/btc/chaumiancoinjoin/signatures?uniqueId={uniqueAliceId2}&roundId={roundId}", sigReqCont2))
+ {
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ }
+
+ uint256[] mempooltxs = await rpc.GetRawMempoolAsync();
+ Assert.Contains(unsignedCoinJoin.GetHash(), mempooltxs);
}
}

MODIFY
WalletWasabi/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/ChaumianCoinJoin/CcjRound.cs
@@ -286,10 +286,17 @@ namespace WalletWasabi.ChaumianCoinJoin
transaction.AddOutput(changeAmount, alice.ChangeOutputScript);
}
}
+ else
+ {
+ coordinatorFee -= coordinatorFeePerAlice;
+ }
}
- // 6. Add Coordinator fee.
+ // 6. Add Coordinator fee only if > about $3, else just let it to be miner fee.
+ if (coordinatorFee > new Money(0.0003m, MoneyUnit.BTC))
+ {
transaction.AddOutput(coordinatorFee, coordinatorAddress);
+ }
// 7. Create the unsigned transaction.
var builder = new TransactionBuilder();
@@ -560,13 +567,20 @@ namespace WalletWasabi.ChaumianCoinJoin
}
}
- public IEnumerable<Alice> GetAlicesByNot(AliceState state)
+ public IEnumerable<Alice> GetAlicesByNot(AliceState state, bool syncLock = true)
+ {
+ if (syncLock)
{
using (RoundSyncronizerLock.Lock())
{
return Alices.Where(x => x.State != state).ToList();
}
}
+ else
+ {
+ return Alices.Where(x => x.State != state).ToList();
+ }
+ }
public void StartAliceTimeout(Guid uniqueId)
{
@@ -632,7 +646,7 @@ namespace WalletWasabi.ChaumianCoinJoin
Coin[] spentCoins = Alices.SelectMany(x => x.Inputs.Select(y => new Coin(y.OutPoint, y.Output))).ToArray();
Money networkFee = SignedCoinJoin.GetFee(spentCoins);
Logger.LogInfo<CcjRound>($"Round ({RoundId}): Network Fee: {networkFee.ToString(false, false)} BTC.");
- Logger.LogInfo<CcjRound>($"Round ({RoundId}): Coordinator Fee: {SignedCoinJoin.Outputs.Single(x=>x.ScriptPubKey == Constants.GetCoordinatorAddress(RpcClient.Network).ScriptPubKey).Value.ToString(false, false)} BTC.");
+ Logger.LogInfo<CcjRound>($"Round ({RoundId}): Coordinator Fee: {SignedCoinJoin.Outputs.SingleOrDefault(x=>x.ScriptPubKey == Constants.GetCoordinatorAddress(RpcClient.Network).ScriptPubKey)?.Value?.ToString(false, false)?? "0"} BTC.");
FeeRate feeRate = SignedCoinJoin.GetFeeRate(spentCoins);
Logger.LogInfo<CcjRound>($"Round ({RoundId}): Network Fee Rate: {feeRate.FeePerK.ToDecimal(MoneyUnit.Satoshi) / 1000} satoshi/byte.");

MODIFY
WalletWasabi/Services/CcjCoordinator.cs
WalletWasabi/Services/CcjCoordinator.cs
@@ -194,7 +194,7 @@ namespace WalletWasabi.Services
// If failed in signing phase, then ban Alices those didn't sign.
if(status == CcjRoundStatus.Failed && round.Phase == CcjRoundPhase.Signing)
{
- foreach (Alice alice in round.GetAlicesByNot(AliceState.SignedCoinJoin))
+ foreach (Alice alice in round.GetAlicesByNot(AliceState.SignedCoinJoin, syncLock: false)) // Because the event sometimes is raised from inside the lock.
{
// If its from any coinjoin, then don't ban.
IEnumerable<OutPoint> utxosToBan = alice.Inputs.Select(x=>x.OutPoint);

Test POST signatures