start working on this
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -172,7 +172,7 @@ namespace WalletWasabi.Backend.Controllers
// Check if inputs are unspent.
if (getTxOutResponse is null)
{
- return BadRequest($"Provided input is not unspent: {inputProof.Input.Index}:{inputProof.Input.TransactionId}");
+ return BadRequest($"Provided input is not unspent: {inputProof.Input.Index}:{inputProof.Input.TransactionId}.");
}
// Check if unconfirmed.

MODIFY
WalletWasabi/Models/SmartCoin.cs
WalletWasabi/Models/SmartCoin.cs
@@ -157,8 +157,14 @@ namespace WalletWasabi.Models
}
}
- public bool SpentOrCoinJoinInProgress => !(SpenderTransactionId is null) || CoinJoinInProgress;
- public bool Unspent => SpenderTransactionId is null;
+ /// <summary>
+ /// If the backend thinks it's spent, but Wasabi doesn't yet know.
+ /// </summary>
+ [JsonProperty(Order = 13)]
+ public bool SpentAccordingToBackend { get; set; }
+
+ public bool SpentOrCoinJoinInProgress => !(SpenderTransactionId is null) || CoinJoinInProgress || SpentAccordingToBackend;
+ public bool Unspent => SpenderTransactionId is null && !SpentAccordingToBackend;
public bool Confirmed => Height != Height.MemPool && Height != Height.Unknown;
public bool IsBanned => BannedUntilUtc != null && BannedUntilUtc > DateTimeOffset.UtcNow;
@@ -173,7 +179,7 @@ namespace WalletWasabi.Models
public ISecret Secret { get; set; }
[JsonConstructor]
- public SmartCoin(uint256 transactionId, uint index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool rbf, int mixin, string label = "", uint256 spenderTransactionId = null, bool coinJoinInProgress = false)
+ public SmartCoin(uint256 transactionId, uint index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool rbf, int mixin, string label = "", uint256 spenderTransactionId = null, bool coinJoinInProgress = false, bool spentAccordingToBackend = false)
{
TransactionId = Guard.NotNull(nameof(transactionId), transactionId);
Index = Guard.NotNull(nameof(index), index);
@@ -188,9 +194,10 @@ namespace WalletWasabi.Models
CoinJoinInProgress = coinJoinInProgress;
Secret = null;
BannedUntilUtc = null;
+ SpentAccordingToBackend = spentAccordingToBackend;
}
- public SmartCoin(Coin coin, TxoRef[] spentOutputs, Height height, bool rbf, int mixin, string label = "", uint256 spenderTransactionId = null, bool coinJoinInProgress = false)
+ public SmartCoin(Coin coin, TxoRef[] spentOutputs, Height height, bool rbf, int mixin, string label = "", uint256 spenderTransactionId = null, bool coinJoinInProgress = false, bool spentAccordingToBackend = false)
{
Guard.NotNull(nameof(coin), coin);
TransactionId = coin.Outpoint.Hash;
@@ -205,6 +212,7 @@ namespace WalletWasabi.Models
RBF = rbf;
CoinJoinInProgress = coinJoinInProgress;
Secret = null;
+ SpentAccordingToBackend = spentAccordingToBackend;
}
public event PropertyChangedEventHandler PropertyChanged;

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -525,6 +525,21 @@ namespace WalletWasabi.Services
await DequeueCoinsFromMixNoLockAsync(coinReference);
return;
}
+ catch (HttpRequestException ex) when (ex.Message.StartsWith("Provided input is not unspent", StringComparison.InvariantCultureIgnoreCase))
+ {
+ string[] parts = ex.Message.Split(new[] { "Provided input is not unspent: " }, StringSplitOptions.RemoveEmptyEntries);
+ string spentInputString = parts[1].TrimEnd('.');
+ string[] bannedInputStringParts = spentInputString.Split(':', StringSplitOptions.RemoveEmptyEntries);
+ (uint256 txid, uint index) coinReference = (new uint256(bannedInputStringParts[1]), uint.Parse(bannedInputStringParts[0]));
+ SmartCoin coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
+ if (coin is null) throw new NotSupportedException("This is impossible.");
+ coin.SpentAccordingToBackend = true;
+
+ Logger.LogWarning<CcjClient>(ex.Message.Split('\n')[1]);
+
+ await DequeueCoinsFromMixNoLockAsync(coinReference);
+ return;
+ }
byte[] unblindedSignature = CoordinatorPubKey.UnblindSignature(aliceClient.BlindedOutputSignature, blind.BlindingFactor);

start working on this