Add mempoolreplacement and double spend test
MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -224,6 +224,7 @@ namespace WalletWasabi.Gui
EndPointStrategy.Default(Network, EndPointType.Rpc),
txIndex: null,
prune: null,
+ mempoolReplacement: "fee,optin",
userAgent: $"/WasabiClient:{Constants.ClientVersion}/",
fallbackFee: null, // ToDo: Maybe we should have it, not only for tests?
Cache),

MODIFY
WalletWasabi.Tests/Helpers/TestNodeBuilder.cs
WalletWasabi.Tests/Helpers/TestNodeBuilder.cs
@@ -33,6 +33,7 @@ namespace WalletWasabi.Tests.Helpers
EndPointStrategy.Random,
txIndex: 1,
prune: 0,
+ mempoolReplacement: "fee,optin",
userAgent: $"/WasabiClient:{Constants.ClientVersion}/",
fallbackFee: Money.Coins(0.0002m), // https://github.com/bitcoin/bitcoin/pull/16524
new MemoryCache(new MemoryCacheOptions())),

MODIFY
WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs
WalletWasabi.Tests/UnitTests/BitcoinCore/RpcBasedTests.cs
@@ -181,6 +181,46 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
}
}
+ [Fact]
+ public async Task CantDoubleSpendAsync()
+ {
+ using var services = new HostedServices();
+ var coreNode = await TestNodeBuilder.CreateAsync(services);
+ await services.StartAllAsync(CancellationToken.None);
+ try
+ {
+ var rpc = coreNode.RpcClient;
+ var network = rpc.Network;
+
+ var key = new Key();
+ var blockId = await rpc.GenerateToAddressAsync(1, key.PubKey.WitHash.GetAddress(network));
+ var block = await rpc.GetBlockAsync(blockId[0]);
+ var coinBaseTx = block.Transactions[0];
+
+ var tx = Transaction.Create(network);
+ tx.Inputs.Add(coinBaseTx, 0);
+ tx.Outputs.Add(Money.Coins(49.9999m), new Key().PubKey.WitHash.GetAddress(network));
+ tx.Sign(key.GetBitcoinSecret(network), coinBaseTx.Outputs.AsCoins().First());
+ var valid = tx.Check();
+
+ var doubleSpend = Transaction.Create(network);
+ doubleSpend.Inputs.Add(coinBaseTx, 0);
+ doubleSpend.Outputs.Add(Money.Coins(49.998m), new Key().PubKey.WitHash.GetAddress(network));
+ doubleSpend.Sign(key.GetBitcoinSecret(network), coinBaseTx.Outputs.AsCoins().First());
+ valid = doubleSpend.Check();
+
+ await rpc.GenerateAsync(101);
+
+ var txId = await rpc.SendRawTransactionAsync(tx);
+ await Assert.ThrowsAsync<RPCException>(async () => await rpc.SendRawTransactionAsync(doubleSpend));
+ }
+ finally
+ {
+ await services.StopAllAsync(CancellationToken.None);
+ await coreNode.TryStopAsync();
+ }
+ }
+
[Fact]
public async Task VerboseBlockInfoAsync()
{

MODIFY
WalletWasabi.Tests/UnitTests/MockRpcClient.cs
WalletWasabi.Tests/UnitTests/MockRpcClient.cs
@@ -168,5 +168,10 @@ namespace WalletWasabi.Tests.UnitTests
{
throw new NotImplementedException();
}
+
+ public Task<uint256[]> GenerateToAddressAsync(int nBlocks, BitcoinAddress address)
+ {
+ throw new NotImplementedException();
+ }
}
}

MODIFY
WalletWasabi/BitcoinCore/CoreNode.cs
WalletWasabi/BitcoinCore/CoreNode.cs
@@ -139,6 +139,11 @@ namespace WalletWasabi.BitcoinCore
desiredConfigLines.Add($"{configPrefix}.prune = {coreNodeParams.Prune}");
}
+ if (coreNodeParams.MempoolReplacement is { })
+ {
+ desiredConfigLines.Add($"{configPrefix}.mempoolreplacement = {coreNodeParams.MempoolReplacement}");
+ }
+
if (coreNodeParams.FallbackFee is { })
{
desiredConfigLines.Add($"{configPrefix}.fallbackfee = {coreNodeParams.FallbackFee.ToString(fplus: false, trimExcessZero: true)}");

MODIFY
WalletWasabi/BitcoinCore/CoreNodeParams.cs
WalletWasabi/BitcoinCore/CoreNodeParams.cs
@@ -24,6 +24,7 @@ namespace WalletWasabi.BitcoinCore
EndPointStrategy rpcEndPointStrategy,
int? txIndex,
int? prune,
+ string mempoolReplacement,
string userAgent,
Money fallbackFee,
IMemoryCache cache)
@@ -38,6 +39,7 @@ namespace WalletWasabi.BitcoinCore
RpcEndPointStrategy = Guard.NotNull(nameof(rpcEndPointStrategy), rpcEndPointStrategy);
TxIndex = txIndex;
Prune = prune;
+ MempoolReplacement = mempoolReplacement;
UserAgent = Guard.NotNullOrEmptyOrWhitespace(nameof(userAgent), userAgent, trim: true);
FallbackFee = fallbackFee;
Cache = Guard.NotNull(nameof(cache), cache);
@@ -51,6 +53,7 @@ namespace WalletWasabi.BitcoinCore
public bool TryDeleteDataDir { get; }
public int? TxIndex { get; }
public int? Prune { get; }
+ public string MempoolReplacement { get; }
public string UserAgent { get; }
public Money FallbackFee { get; }
public EndPointStrategy P2pEndPointStrategy { get; }

MODIFY
WalletWasabi/BitcoinCore/IRPCClient.cs
WalletWasabi/BitcoinCore/IRPCClient.cs
@@ -70,5 +70,7 @@ namespace WalletWasabi.BitcoinCore
Task<EstimateSmartFeeResponse> TryEstimateSmartFeeAsync(int confirmationTarget, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative);
Task<VerboseBlockInfo> GetVerboseBlockAsync(uint256 blockId);
+
+ Task<uint256[]> GenerateToAddressAsync(int nBlocks, BitcoinAddress address);
}
}

MODIFY
WalletWasabi/BitcoinCore/RpcClientBase.cs
WalletWasabi/BitcoinCore/RpcClientBase.cs
@@ -106,6 +106,11 @@ namespace WalletWasabi.BitcoinCore
return RpcParser.ParseVerboseBlockResponse(resp.Result.ToString());
}
+ public async Task<uint256[]> GenerateToAddressAsync(int nBlocks, BitcoinAddress address)
+ {
+ return await Rpc.GenerateToAddressAsync(nBlocks, address).ConfigureAwait(false);
+ }
+
#region For Testing Only
public virtual async Task<uint256> SendToAddressAsync(BitcoinAddress address, Money amount, string commentTx = null, string commentDest = null, bool subtractFeeFromAmount = false, bool replaceable = false)

Add mempoolreplacement and double spend test