Defend too-long-mempool-chain attack.
fixes
fixes
MODIFY
WalletWasabi/Extensions/RPCClientExtensions.cs
WalletWasabi/Extensions/RPCClientExtensions.cs
+using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Models;
@@ -124,5 +126,33 @@ namespace NBitcoin.RPC
return resp;
}
+
+ /// <returns>(allowed, reject-reason)</returns>
+ public static async Task<(bool accept, string rejectReason)> TestMempoolAcceptAsync(this RPCClient rpc, Coin coin)
+ {
+ // 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 fakeTransaction = rpc.Network.CreateTransaction();
+ fakeTransaction.Inputs.Add(new TxIn(coin.Outpoint));
+ Money fakeOutputValue = NBitcoinHelpers.TakeAReasonableFee(coin.TxOut.Value);
+ fakeTransaction.Outputs.Add(fakeOutputValue, new Key());
+ RPCResponse testMempoolAcceptResponse = await rpc.TestMempoolAcceptAsync(allowHighFees: true, fakeTransaction);
+
+ JToken first = testMempoolAcceptResponse.Result[0];
+ bool allowed = first["allowed"].Value<bool>();
+
+ var rejectedReason = string.Empty;
+ if (!allowed)
+ {
+ string rejected = first["reject-reason"].Value<string>();
+
+ if (!(rejected.Contains("mandatory-script-verify-flag-failed", StringComparison.OrdinalIgnoreCase)
+ || rejected.Contains("non-mandatory-script-verify-flag", StringComparison.OrdinalIgnoreCase)))
+ {
+ return (false, rejected);
+ }
+ }
+ return (true, "");
+ }
}
}

MODIFY
WalletWasabi/Helpers/NBitcoinHelpers.cs
WalletWasabi/Helpers/NBitcoinHelpers.cs
@@ -37,5 +37,23 @@ namespace WalletWasabi.Helpers
}
}
}
+
+ public static Money TakeAReasonableFee(Money outputValue)
+ {
+ Money fee = Money.Coins(0.002m);
+ var remaining = Money.Zero;
+
+ while (true)
+ {
+ remaining = outputValue - fee;
+ if (remaining > Money.Coins(0.00001m))
+ {
+ break;
+ }
+ fee = fee.Percentange(50);
+ }
+
+ return remaining;
+ }
}
}

MODIFY
WalletWasabi/Services/CcjCoordinator.cs
WalletWasabi/Services/CcjCoordinator.cs
@@ -19,7 +19,6 @@ namespace WalletWasabi.Services
private List<CcjRound> Rounds { get; }
private AsyncLock RoundsListLock { get; }
- private List<uint256> UnconfirmedCoinJoins { get; }
private List<uint256> CoinJoins { get; }
public string CoinJoinsFilePath => Path.Combine(FolderPath, $"CoinJoins{Network}.txt");
private AsyncLock CoinJoinsLock { get; }
@@ -49,7 +48,6 @@ namespace WalletWasabi.Services
RoundsListLock = new AsyncLock();
CoinJoins = new List<uint256>();
- UnconfirmedCoinJoins = new List<uint256>();
CoinJoinsLock = new AsyncLock();
Directory.CreateDirectory(FolderPath);
@@ -83,10 +81,6 @@ namespace WalletWasabi.Services
uint256 txHash = new uint256(line);
RPCResponse getRawTransactionResponse = RpcClient.SendCommand(RPCOperations.getrawtransaction, txHash.ToString(), true);
CoinJoins.Add(txHash);
- if (getRawTransactionResponse.Result.Value<int>("confirmations") <= 0)
- {
- UnconfirmedCoinJoins.Add(txHash);
- }
}
catch (Exception ex)
{
@@ -348,40 +342,6 @@ namespace WalletWasabi.Services
return CoinJoins.Count;
}
- public async Task<bool> IsUnconfirmedCoinJoinLimitReachedAsync()
- {
- using (await CoinJoinsLock.LockAsync())
- {
- if (UnconfirmedCoinJoins.Count < 24)
- {
- return false;
- }
- foreach (var cjHash in UnconfirmedCoinJoins.ToArray())
- {
- try
- {
- var txInfo = await RpcClient.GetRawTransactionInfoAsync(cjHash);
-
- // if confirmed remove only from unconfirmed
- if (txInfo.Confirmations > 0)
- {
- UnconfirmedCoinJoins.Remove(cjHash);
- }
- }
- catch (Exception ex)
- {
- // If aborted remove from everywhere (should not happen normally).
- UnconfirmedCoinJoins.Remove(cjHash);
- CoinJoins.Remove(cjHash);
- await File.WriteAllLinesAsync(CoinJoinsFilePath, CoinJoins.Select(x => x.ToString()));
- Logger.LogWarning<CcjCoordinator>(ex);
- }
- }
-
- return UnconfirmedCoinJoins.Count >= 24;
- }
- }
-
#region IDisposable Support
private volatile bool _disposedValue = false; // To detect redundant calls

Defend too-long-mempool-chain attack.

fixes https://github.com/zkSNACKs/WalletWasabi/issues/904
fixes https://github.com/zkSNACKs/WalletWasabi/pull/936