sanity checks
MODIFY
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
HiddenWallet.ChaumianTumbler/Controllers/TumblerController.cs
@@ -118,10 +118,49 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
if (txOutResponse.Result.Value<int>("confirmations") <= 0)
{
if (!Global.CoinJoinStore.Transactions
- .Any(x => x.State == CoinJoinTransactionState.Succeeded && x.Transaction.GetHash() == op.Hash))
+ .Any(x => x.State >= CoinJoinTransactionState.Succeeded && x.Transaction.GetHash() == op.Hash))
{
throw new ArgumentException("Provided input is not confirmed, nor spends a previous CJ transaction");
}
+ else
+ {
+ // after 24 unconfirmed cj in the mempool dont't let unconfirmed coinjoin to be registered
+ var unconfirmedCoinJoins = Global.CoinJoinStore.Transactions.Where(x => x.State == CoinJoinTransactionState.Succeeded);
+ if(unconfirmedCoinJoins.Count() >= 24)
+ {
+ var toFailed = new HashSet<uint256>();
+ var toConfirmed = new HashSet<uint256>();
+ foreach (var tx in unconfirmedCoinJoins)
+ {
+ RPCResponse getRawTransactionResponse = (await Global.RpcClient.SendCommandAsync("getrawtransaction", tx.Transaction.GetHash().ToString(), true));
+ if (string.IsNullOrWhiteSpace(getRawTransactionResponse?.ResultString))
+ {
+ toFailed.Add(tx.Transaction.GetHash());
+ }
+ if (getRawTransactionResponse.Result.Value<int>("confirmations") > 0)
+ {
+ toConfirmed.Add(tx.Transaction.GetHash());
+ }
+ }
+ foreach(var tx in toFailed)
+ {
+ Global.CoinJoinStore.TryUpdateState(tx, CoinJoinTransactionState.Failed);
+ }
+ foreach (var tx in toConfirmed)
+ {
+ Global.CoinJoinStore.TryUpdateState(tx, CoinJoinTransactionState.Confirmed);
+ }
+ if(toFailed.Count + toConfirmed.Count > 0)
+ {
+ await Global.CoinJoinStore.ToFileAsync(Global.CoinJoinStorePath);
+ }
+ // if couldn't remove any unconfirmed tx then refuse registration
+ if (Global.CoinJoinStore.Transactions.Count(x => x.State == CoinJoinTransactionState.Succeeded) >= 24)
+ {
+ throw new ArgumentException("Registering unconfirmed CJ transaction output is currently not allowed due to too long mempool chain");
+ }
+ }
+ }
}
// Check coinbase > 100
if (txOutResponse.Result.Value<int>("confirmations") < 100)
@@ -190,6 +229,12 @@ namespace HiddenWallet.ChaumianTumbler.Controllers
{
Global.StateMachine.UpdatePhase(TumblerPhase.ConnectionConfirmation);
}
+ TumblerStateMachine.EstimateInputAndOutputSizes(out int inputSizeInBytes, out int outputSizeInBytes);
+ int estimatedTxSize = Global.StateMachine.Alices.SelectMany(x => x.Inputs).Count() * inputSizeInBytes + 2 * outputSizeInBytes;
+ if(estimatedTxSize >= 90000) // standard transaction is < 100KB
+ {
+ Global.StateMachine.UpdatePhase(TumblerPhase.ConnectionConfirmation);
+ }
return new ObjectResult(new InputsResponse()
{

MODIFY
HiddenWallet.ChaumianTumbler/Store/CoinJoinTransactionState.cs
HiddenWallet.ChaumianTumbler/Store/CoinJoinTransactionState.cs
@@ -8,6 +8,7 @@ namespace HiddenWallet.ChaumianTumbler.Store
public enum CoinJoinTransactionState
{
Failed,
- Succeeded
+ Succeeded,
+ Confirmed
}
}

MODIFY
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
@@ -280,8 +280,7 @@ namespace HiddenWallet.ChaumianTumbler
private async Task SetFeesAsync(CancellationToken cancel)
{
- int inputSizeInBytes = (int)Math.Ceiling(((3 * Constants.P2wpkhInputSizeInBytes) + Constants.P2pkhInputSizeInBytes) / 4m); // vSize
- int outputSizeInBytes = Constants.OutputSizeInBytes;
+ EstimateInputAndOutputSizes(out int inputSizeInBytes, out int outputSizeInBytes);
try
{
@@ -310,6 +309,12 @@ namespace HiddenWallet.ChaumianTumbler
}
}
+ public static void EstimateInputAndOutputSizes(out int inputSizeInBytes, out int outputSizeInBytes)
+ {
+ inputSizeInBytes = (int)Math.Ceiling(((3 * Constants.P2wpkhInputSizeInBytes) + Constants.P2pkhInputSizeInBytes) / 4m);
+ outputSizeInBytes = Constants.OutputSizeInBytes;
+ }
+
private object _aliceLock = new object();
public Alice FindAlice(string uniqueId, bool throwException)
{

MODIFY
HiddenWallet.Daemon/Controllers/TumblerController.cs
HiddenWallet.Daemon/Controllers/TumblerController.cs
@@ -116,7 +116,7 @@ namespace HiddenWallet.Daemon.Controllers
}
}
- return new ObjectResult(new SuccessResponse());
+ return new ObjectResult(new TumbleResponse() { Transactions = string.Join(Environment.NewLine, txIds.Select(a => a.ToString())) });
}
catch (OperationCanceledException)
{

MODIFY
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
@@ -280,7 +280,14 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
throw new InvalidOperationException($"Maximum {status.MaximumInputsPerAlices} can be registered");
}
- Money needed = Denomination + (feePerInputs * i) + (feePerOutputs * 2);
+ Money fee = (feePerInputs * i) + (feePerOutputs * 2);
+ // sanity check
+ if((fee.ToDecimal(MoneyUnit.Satoshi) / Denomination.ToDecimal(MoneyUnit.Satoshi)) * 100 > 5 )
+ {
+ throw new NotSupportedException($"The fee required by the coordinator is too high: {fee.ToString(false, true)} BTC");
+ }
+
+ Money needed = Denomination + fee;
Inputs.Add(coin);
var input = coin.Outpoint.ToHex();

MODIFY
HiddenWallet.Gui/app/js/wallet-phase.ts
HiddenWallet.Gui/app/js/wallet-phase.ts
@@ -279,16 +279,22 @@ function mix() {
httpPostTumblerAsync("tumble", obj, function (json) {
try {
if (json.Success === false) {
- let alertMessage: string = `Couldn't finish all requested mixing rounds: ${json.Message}`;
+ let alertMessage: string = "Couldn't finish all requested mixing rounds:" + json.Message;
if (!isBlank(json.Details)) {
- alertMessage = `${alertMessage}
-
- ${json.Details}`;
+ alertMessage = alertMessage
+ + "\n"
+ + "\n" + json.Details;
}
alert(alertMessage);
}
+ else
+ {
+ alert("Successful mix! Transactions:"
+ + "\n"
+ + "\n" + json.Transactions);
+ }
} catch (err) {
}
@@ -309,13 +315,12 @@ function cancelMix()
httpGetTumblerAsync("cancel-mix", function (json) {
try {
if (json.Success === false) {
- let alertMessage: string = `Couldn't cancel the mix: ${json.Message}`;
+ let alertMessage: string = "Couldn't cancel the mix:" + json.Message;
if (!isBlank(json.Details)) {
- alertMessage = `${alertMessage}
-
- Details:
- ${json.Details}`;
+ alertMessage = alertMessage
+ + "\n"
+ + "\n" + json.Details;
}
alert(alertMessage);
@@ -543,13 +548,12 @@ function buildTransaction() {
httpPostWalletAsync(`build-transaction/${bobOrAlice}`, obj, function (json) {
if (json.Success === false) {
- let alertMessage: string = `Couldn't build the tansaction: ${json.Message}`;
+ let alertMessage: string = "Couldn't build the tansaction:" + json.Message;
if (!isBlank(json.Details)) {
- alertMessage = `${alertMessage}
-
- Details:
- ${json.Details}`;
+ alertMessage = alertMessage
+ + "\n"
+ + "\n" + json.Details;
}
alert(alertMessage);

MODIFY
HiddenWallet/ChaumianCoinJoin/ChaumianTumblerClient.cs
HiddenWallet/ChaumianCoinJoin/ChaumianTumblerClient.cs
@@ -50,7 +50,7 @@ namespace HiddenWallet.ChaumianCoinJoin
{
return await HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancel);
}
- catch(TorException e)
+ catch(TorException)
{
throw;
}

sanity checks