Throw correct
MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs
@@ -572,9 +572,7 @@ public class TransactionFactoryTests
Assert.Equal(ChangeStrategy.Auto, payment.ChangeStrategy);
- var result = transactionFactory.BuildTransaction(payment, new FeeRate(12m));
- Assert.Single(result.OuterWalletOutputs);
- Assert.False(result.Signed);
+ Assert.Throws<InsufficientBalanceException>(() => transactionFactory.BuildTransaction(payment, new FeeRate(12m)));
}
[Fact]

MODIFY
WalletWasabi/Blockchain/TransactionBuilding/SmartCoinSelector.cs
WalletWasabi/Blockchain/TransactionBuilding/SmartCoinSelector.cs
@@ -17,11 +17,12 @@ public class SmartCoinSelector : ICoinSelector
}
private IEnumerable<SmartCoin> UnspentCoins { get; }
+ private int IterationCount { get; set; }
- /// <param name="unused">Unused parameter, make it an empty list.</param>
- public IEnumerable<ICoin> Select(IEnumerable<ICoin> unused, IMoney target)
+ /// <param name="suggestion">We use this to detect if NBitcoin tries to suggest something different and indicate the error.</param>
+ public IEnumerable<ICoin> Select(IEnumerable<ICoin> suggestion, IMoney target)
{
- var targetMoney = target as Money;
+ var targetMoney = (Money)target;
long available = UnspentCoins.Sum(x => x.Amount);
if (available < targetMoney)
@@ -29,6 +30,21 @@ public class SmartCoinSelector : ICoinSelector
throw new InsufficientBalanceException(targetMoney, available);
}
+ // This is hacky safety check to make sure the first few iteration can go through.
+ if (IterationCount > 10)
+ {
+ var suggestedSum = suggestion.Sum(c => (Money)c.Amount);
+ if (suggestedSum < (Money)target)
+ {
+ throw new InsufficientBalanceException(targetMoney, suggestedSum);
+ }
+ }
+
+ if (IterationCount > 500)
+ {
+ throw new TimeoutException("Coin selection timed out.");
+ }
+
// Get unique clusters.
IEnumerable<Cluster> uniqueClusters = UnspentCoins
.Select(coin => coin.HdPubKey.Cluster)
@@ -83,6 +99,8 @@ public class SmartCoinSelector : ICoinSelector
.Where(x => x.Total >= targetMoney) // filter combinations below target
.OrderBy(x => x.Coins.Count());
+ IterationCount++;
+
// Select the best solution.
return candidates.First().Coins.Select(x => x.Coin);
}

Throw correct