Fix mempool acceptance.
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -208,7 +208,7 @@ namespace WalletWasabi.Backend.Controllers
var inputs = new HashSet<Coin>();
- var assertMempoolAcceptance = false;
+ var allInputsConfirmed = true;
foreach (var responses in getTxOutResponses)
{
var (inputProof, getTxOutResponseTask) = responses;
@@ -223,14 +223,12 @@ namespace WalletWasabi.Backend.Controllers
// Check if unconfirmed.
if (getTxOutResponse.Confirmations <= 0)
{
+ allInputsConfirmed = false;
// If it spends a CJ then it may be acceptable to register.
if (!await Coordinator.ContainsUnconfirmedCoinJoinAsync(inputProof.Input.Hash))
{
return BadRequest("Provided input is neither confirmed, nor is from an unconfirmed coinjoin.");
}
-
- // If an unconfirmed UTXO is about to be registered, then assert mempool acceptance, but with all the registered coins from the user, not only with a single one.
- assertMempoolAcceptance = true;
}
// Check if immature.
@@ -257,7 +255,7 @@ namespace WalletWasabi.Backend.Controllers
inputs.Add(new Coin(inputProof.Input, txOut));
}
- if (assertMempoolAcceptance)
+ if (!allInputsConfirmed)
{
// Check if mempool would accept a fake transaction created with the registered inputs.
// Fake outputs: 200 + 400 because the size of a bech32 input is rougly the size of an outputs and this'd be a 200input + 400 output tx and 0.9/600=0.0015, which is well above the dust, so should be good.
@@ -306,7 +304,7 @@ namespace WalletWasabi.Backend.Controllers
}
// Make sure Alice checks work.
- var alice = new Alice(inputs, networkFeeToPayAfterBaseDenomination, request.ChangeOutputAddress, acceptedBlindedOutputScripts);
+ var alice = new Alice(inputs, allInputsConfirmed, networkFeeToPayAfterBaseDenomination, request.ChangeOutputAddress, acceptedBlindedOutputScripts);
foreach (Guid aliceToRemove in alicesToRemove)
{

MODIFY
WalletWasabi/CoinJoin/Coordinator/Participants/Alice.cs
WalletWasabi/CoinJoin/Coordinator/Participants/Alice.cs
@@ -8,9 +8,10 @@ namespace WalletWasabi.CoinJoin.Coordinator.Participants
{
public class Alice
{
- public Alice(IEnumerable<Coin> inputs, Money networkFeeToPayAfterBaseDenomination, BitcoinAddress changeOutputAddress, IEnumerable<uint256> blindedOutputScripts)
+ public Alice(IEnumerable<Coin> inputs, bool allInputsConfirmed, Money networkFeeToPayAfterBaseDenomination, BitcoinAddress changeOutputAddress, IEnumerable<uint256> blindedOutputScripts)
{
Inputs = Guard.NotNullOrEmpty(nameof(inputs), inputs);
+ AllInputsConfirmed = allInputsConfirmed;
NetworkFeeToPayAfterBaseDenomination = Guard.NotNull(nameof(networkFeeToPayAfterBaseDenomination), networkFeeToPayAfterBaseDenomination);
BlindedOutputScripts = blindedOutputScripts?.ToArray() ?? Array.Empty<uint256>();
@@ -44,5 +45,6 @@ namespace WalletWasabi.CoinJoin.Coordinator.Participants
public uint256[] BlindedOutputScripts { get; set; }
public uint256[] BlindedOutputSignatures { get; set; }
+ public bool AllInputsConfirmed { get; set; }
}
}

MODIFY
WalletWasabi/CoinJoin/Coordinator/Rounds/CoordinatorRound.cs
WalletWasabi/CoinJoin/Coordinator/Rounds/CoordinatorRound.cs
@@ -1187,42 +1187,24 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
var resultAll = await RpcClient.TestMempoolAcceptAsync(coinsToTest, fakeOutputsCount: outputsAll);
if (resultAll.accept)
{
- Logger.LogInfo($"Mempool acceptance successful! Number of Alices: {Alices.Count}.");
return Enumerable.Empty<Alice>();
}
+ Logger.LogInfo($"Mempool acceptance is unsuccessful! Number of Alices: {Alices.Count}.");
- var checkingTasks = new List<(Alice alice, Task<(bool accepted, string reason)> task)>();
- var batch = RpcClient.PrepareBatch();
- foreach (Alice alice in Alices)
+ // The created tx was not accepted. Let's only accept confirmed txs then.
+ foreach (var alice in Alices.Where(x => !x.AllInputsConfirmed))
{
- // Check if mempool would accept a fake transaction created with the registered inputs.
- // This will catch ascendant/descendant count and size limits for example.
- var outputs = EstimateBestMixingLevel(alice) + 1;
- checkingTasks.Add((alice, batch.TestMempoolAcceptAsync(alice.Inputs, fakeOutputsCount: outputs)));
- }
- var waiting = Task.WhenAll(checkingTasks.Select(t => t.task));
- await batch.SendBatchAsync().ConfigureAwait(false);
- await waiting.ConfigureAwait(false);
-
- foreach (var t in checkingTasks)
+ foreach (var input in alice.Inputs.Select(x => x.Outpoint))
{
- var result = await t.task.ConfigureAwait(false);
- if (!result.accepted)
+ var response = await RpcClient.GetTxOutAsync(input.Hash, (int)input.N, includeMempool: true);
+ if (response.Confirmations <= 0)
{
- alicesRemoved.Add(t.alice);
- Alices.Remove(t.alice);
- Logger.LogInfo($"Mempool acceptance failed: {result.reason ?? "no reason"}.");
- }
+ alicesRemoved.Add(alice);
+ Alices.Remove(alice);
+ Logger.LogInfo($"Round ({RoundId}): Alice ({alice.UniqueId}) removed, because of unconfirmed inputs.");
+ break;
}
-
- foreach (var alice in alicesRemoved)
- {
- Logger.LogInfo($"Round ({RoundId}): Alice ({alice.UniqueId}) removed.");
}
-
- if (alicesRemoved.Count == 0)
- {
- Logger.LogInfo($"Overall Mempool acceptance failed, but could not find guilty Alice: {resultAll.rejectReason ?? "no reason"}.");
}
}

MODIFY
WalletWasabi/Extensions/RPCClientExtensions.cs
WalletWasabi/Extensions/RPCClientExtensions.cs
@@ -214,7 +214,7 @@ namespace NBitcoin.RPC
return newEstimations;
}
- public static async Task<(bool accept, string rejectReason)> TestMempoolAcceptAsync(this IRPCClient rpc, IEnumerable<Coin> coins, int fakeOutputsCount = 1)
+ public static async Task<(bool accept, string rejectReason)> TestMempoolAcceptAsync(this IRPCClient rpc, IEnumerable<Coin> coins, int fakeOutputsCount)
{
// Check if mempool would accept a fake transaction created with the registered inputs.
// This will catch ascendant/descendant count and size limits for example.

Fix mempool acceptance.