Make sure coin knows of its spender
MODIFY
WalletWasabi.Tests/RegressionTests/BuildTests.cs
WalletWasabi.Tests/RegressionTests/BuildTests.cs
@@ -327,7 +327,7 @@ namespace WalletWasabi.Tests.RegressionTests
var success = bitcoinStore.TransactionStore.TryGetTransaction(fundingTxId, out SmartTransaction invalidSmartTransaction);
Assert.True(success);
var invalidCoin = Assert.Single(((CoinsRegistry)wallet.Coins).AsAllCoinsView().CreatedBy(invalidSmartTransaction.GetHash()));
- Assert.True(invalidCoin.SpenderTransactionId is { });
+ Assert.True(invalidCoin.SpenderTransaction is { });
Assert.True(invalidCoin.Confirmed);
var overwriteTx = Transaction.Create(network);

MODIFY
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
@@ -1287,10 +1287,12 @@ namespace WalletWasabi.Tests.RegressionTests
await Task.Delay(1000);
}
- var cj = (await rpc.GetRawMempoolAsync()).Single();
- smartCoin1.SpenderTransactionId = cj;
- smartCoin2.SpenderTransactionId = cj;
- smartCoin3.SpenderTransactionId = cj;
+ var cjHash = (await rpc.GetRawMempoolAsync()).Single();
+ var cj = await rpc.GetRawTransactionAsync(cjHash);
+ var sCj = new SmartTransaction(cj, Height.Mempool);
+ smartCoin1.SpenderTransaction = sCj;
+ smartCoin2.SpenderTransaction = sCj;
+ smartCoin3.SpenderTransaction = sCj;
// Make sure if times out, it tries again.
connectionConfirmationTimeout = 1;

MODIFY
WalletWasabi.Tests/RegressionTests/SendTests.cs
WalletWasabi.Tests/RegressionTests/SendTests.cs
@@ -130,7 +130,7 @@ namespace WalletWasabi.Tests.RegressionTests
{
Assert.False(coin.CoinJoinInProgress);
Assert.True(coin.Confirmed);
- Assert.Null(coin.SpenderTransactionId);
+ Assert.Null(coin.SpenderTransaction);
Assert.True(!coin.IsSpent());
}
@@ -600,7 +600,7 @@ namespace WalletWasabi.Tests.RegressionTests
// There is a coin destroyed
var allCoins = wallet.TransactionProcessor.Coins.AsAllCoinsView();
- Assert.Equal(1, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransactionId == tx1Res.Transaction.GetHash()));
+ Assert.Equal(1, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransaction?.GetHash() == tx1Res.Transaction.GetHash()));
// There is at least one coin created from the destruction of the first coin
Assert.Contains(wallet.Coins, x => x.SpentOutputs.Any(o => o.Hash == tx0Id));
@@ -629,7 +629,7 @@ namespace WalletWasabi.Tests.RegressionTests
// There is a coin destroyed
allCoins = wallet.TransactionProcessor.Coins.AsAllCoinsView();
- Assert.Equal(2, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransactionId == tx2Hash));
+ Assert.Equal(2, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransaction?.GetHash() == tx2Hash));
// There is at least one coin created from the destruction of the first coin
Assert.Contains(wallet.Coins, x => x.SpentOutputs.Any(o => o.Hash == tx1Res.Transaction.GetHash()));

MODIFY
WalletWasabi.Tests/RegressionTests/WalletTests.cs
WalletWasabi.Tests/RegressionTests/WalletTests.cs
@@ -289,7 +289,7 @@ namespace WalletWasabi.Tests.RegressionTests
Assert.True(firstCoin.IsAvailable());
Assert.Equal("foo label", firstCoin.Label);
Assert.Equal(key.P2wpkhScript, firstCoin.ScriptPubKey);
- Assert.Null(firstCoin.SpenderTransactionId);
+ Assert.Null(firstCoin.SpenderTransaction);
Assert.NotNull(firstCoin.SpentOutputs);
Assert.NotEmpty(firstCoin.SpentOutputs);
Assert.Equal(txId, firstCoin.TransactionId);
@@ -323,7 +323,7 @@ namespace WalletWasabi.Tests.RegressionTests
Assert.Equal(key.P2wpkhScript, firstCoin.ScriptPubKey);
Assert.Equal(key2.P2wpkhScript, secondCoin.ScriptPubKey);
Assert.Equal(key2.P2wpkhScript, thirdCoin.ScriptPubKey);
- Assert.Null(thirdCoin.SpenderTransactionId);
+ Assert.Null(thirdCoin.SpenderTransaction);
Assert.NotNull(firstCoin.SpentOutputs);
Assert.NotNull(secondCoin.SpentOutputs);
Assert.NotNull(thirdCoin.SpentOutputs);
@@ -375,7 +375,7 @@ namespace WalletWasabi.Tests.RegressionTests
Assert.True(rbfCoin.IsAvailable());
Assert.Equal("bar label", rbfCoin.Label);
Assert.Equal(key2.P2wpkhScript, rbfCoin.ScriptPubKey);
- Assert.Null(rbfCoin.SpenderTransactionId);
+ Assert.Null(rbfCoin.SpenderTransaction);
Assert.NotNull(rbfCoin.SpentOutputs);
Assert.NotEmpty(rbfCoin.SpentOutputs);
Assert.Equal(tx4bumpRes.TransactionId, rbfCoin.TransactionId);

MODIFY
WalletWasabi.Tests/UnitTests/SmartCoinTests.cs
WalletWasabi.Tests/UnitTests/SmartCoinTests.cs
@@ -15,7 +15,6 @@ namespace WalletWasabi.Tests.UnitTests
public void SmartCoinEquality()
{
var tx = Transaction.Parse("0100000000010176f521a178a4b394b647169a89b29bb0d95b6bce192fd686d533eb4ea98464a20000000000ffffffff02ed212d020000000016001442da650f25abde0fef57badd745df7346e3e7d46fbda6400000000001976a914bd2e4029ce7d6eca7d2c3779e8eac36c952afee488ac02483045022100ea43ccf95e1ac4e8b305c53761da7139dbf6ff164e137a6ce9c09e15f316c22902203957818bc505bbafc181052943d7ab1f3ae82c094bf749813e8f59108c6c268a012102e59e61f20c7789aa73faf5a92dc8c0424e538c635c55d64326d95059f0f8284200000000", Network.TestNet);
- var txId = tx.GetHash();
var index = 0U;
var km = KeyManager.CreateNew(out _, "");
var hdpk1 = km.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, false);
@@ -27,10 +26,10 @@ namespace WalletWasabi.Tests.UnitTests
var stx = new SmartTransaction(tx, height);
var label = "foo";
- var coin = new SmartCoin(stx, index, hdpk1, tx.GetAnonymitySet(index), label, txId);
+ var coin = new SmartCoin(stx, index, hdpk1, tx.GetAnonymitySet(index), label);
// If the txId or the index differ, equality should think it's a different coin.
- var differentCoin = new SmartCoin(stx, index + 1, hdpk2, tx.GetAnonymitySet(index + 1), label, txId);
+ var differentCoin = new SmartCoin(stx, index + 1, hdpk2, tx.GetAnonymitySet(index + 1), label);
// If the txId and the index are the same, equality should think it's the same coin.
var sameCoin = new SmartCoin(stx, index, hdpk1, tx.GetAnonymitySet(index), "boo");

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs
WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using NBitcoin;
using WalletWasabi.Blockchain.Analysis.Clustering;
+using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.Models;
namespace WalletWasabi.Blockchain.TransactionOutputs
@@ -162,15 +163,16 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
return coinsToRemove;
}
- public void Spend(SmartCoin spentCoin)
+ public void Spend(SmartCoin spentCoin, SmartTransaction tx)
{
+ spentCoin.SpenderTransaction = tx;
lock (Lock)
{
if (Coins.Remove(spentCoin))
{
InvalidateSnapshot = true;
SpentCoins.Add(spentCoin);
- var createdCoins = CreatedByNoLock(spentCoin.SpenderTransactionId);
+ var createdCoins = CreatedByNoLock(spentCoin.SpenderTransaction.GetHash());
foreach (var newCoin in createdCoins)
{
if (newCoin.AnonymitySet < PrivacyLevelThreshold)

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/CoinsView.cs
WalletWasabi/Blockchain/TransactionOutputs/CoinsView.cs
@@ -31,9 +31,9 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
public ICoinsView CreatedBy(uint256 txid) => new CoinsView(Coins.Where(x => x.TransactionId == txid));
- public ICoinsView SpentBy(uint256 txid) => new CoinsView(Coins.Where(x => x.SpenderTransactionId == txid));
+ public ICoinsView SpentBy(uint256 txid) => new CoinsView(Coins.Where(x => x.SpenderTransaction is { } && x.SpenderTransaction.GetHash() == txid));
- public ICoinsView ChildrenOf(SmartCoin coin) => new CoinsView(Coins.Where(x => x.TransactionId == coin.SpenderTransactionId));
+ public ICoinsView ChildrenOf(SmartCoin coin) => new CoinsView(Coins.Where(x => coin.SpenderTransaction is { } && x.TransactionId == coin.SpenderTransaction.GetHash()));
public ICoinsView DescendantOf(SmartCoin coin)
{

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs
WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs
@@ -20,7 +20,7 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
private Height _height;
private SmartLabel _label;
private int _anonymitySet;
- private uint256 _spenderTransactionId;
+ private SmartTransaction? _spenderTransaction;
private bool _coinJoinInProgress;
private DateTimeOffset? _bannedUntilUtc;
private bool _spentAccordingToBackend;
@@ -36,7 +36,7 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
#region Constructors
- public SmartCoin(SmartTransaction transaction, uint outputIndex, HdPubKey pubKey, int anonymitySet, SmartLabel label = null, uint256 spenderTransactionId = null, bool coinJoinInProgress = false, DateTimeOffset? bannedUntilUtc = null, bool spentAccordingToBackend = false)
+ public SmartCoin(SmartTransaction transaction, uint outputIndex, HdPubKey pubKey, int anonymitySet, SmartLabel label = null, bool coinJoinInProgress = false, DateTimeOffset? bannedUntilUtc = null, bool spentAccordingToBackend = false)
{
Transaction = transaction;
Coin = new Coin(transaction.Transaction, outputIndex);
@@ -46,8 +46,6 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
WasReplaceable = Transaction.IsRBF;
AnonymitySet = Guard.InRangeAndNotNull(nameof(anonymitySet), anonymitySet, 1, int.MaxValue);
- SpenderTransactionId = spenderTransactionId;
-
CoinJoinInProgress = coinJoinInProgress;
BannedUntilUtc = bannedUntilUtc;
SpentAccordingToBackend = spentAccordingToBackend;
@@ -111,10 +109,10 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
set => RaiseAndSetIfChanged(ref _anonymitySet, value);
}
- public uint256 SpenderTransactionId
+ public SmartTransaction? SpenderTransaction
{
- get => _spenderTransactionId;
- set => RaiseAndSetIfChanged(ref _spenderTransactionId, value);
+ get => _spenderTransaction;
+ set => RaiseAndSetIfChanged(ref _spenderTransaction, value);
}
public bool CoinJoinInProgress
@@ -192,12 +190,12 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
#region Methods
- public bool IsSpent() => SpenderTransactionId is { };
+ public bool IsSpent() => SpenderTransaction is { };
/// <summary>
/// IsUnspent() AND !SpentAccordingToBackend AND !CoinJoinInProgress
/// </summary>
- public bool IsAvailable() => SpenderTransactionId is null && !SpentAccordingToBackend && !CoinJoinInProgress;
+ public bool IsAvailable() => SpenderTransaction is null && !SpentAccordingToBackend && !CoinJoinInProgress;
public bool IsReplaceable() => WasReplaceable && !Confirmed;

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -201,7 +201,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
anonset += spentOwnCoins.Min(x => x.AnonymitySet) - 1;
}
- SmartCoin newCoin = new SmartCoin(tx, i, foundKey, anonset, foundKey.Label, spenderTransactionId: null, false); // Do not inherit locked status from key, that's different.
+ SmartCoin newCoin = new SmartCoin(tx, i, foundKey, anonset, foundKey.Label, false); // Do not inherit locked status from key, that's different.
result.ReceivedCoins.Add(newCoin);
// If we did not have it.
@@ -246,13 +246,12 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
// If spends any of our coin
if (prevOutSet.TryGetValue(coin.OutPoint, out OutPoint input))
{
- var alreadyKnown = coin.SpenderTransactionId == txId;
- coin.SpenderTransactionId = txId;
+ var alreadyKnown = coin.SpenderTransaction?.GetHash() == txId;
result.SpentCoins.Add(coin);
if (!alreadyKnown)
{
- Coins.Spend(coin);
+ Coins.Spend(coin, tx);
result.NewlySpentCoins.Add(coin);
}

MODIFY
WalletWasabi/Blockchain/Transactions/TransactionHistoryBuilder.cs
WalletWasabi/Blockchain/Transactions/TransactionHistoryBuilder.cs
@@ -53,15 +53,12 @@ namespace WalletWasabi.Blockchain.Transactions
});
}
- if (coin.IsSpent())
+ var foundSpenderTransaction = coin.SpenderTransaction;
+ if (foundSpenderTransaction is { })
{
- if (!wallet.BitcoinStore.TransactionStore.TryGetTransaction(coin.SpenderTransactionId, out SmartTransaction foundSpenderTransaction))
- {
- throw new InvalidOperationException($"Transaction {coin.SpenderTransactionId} not found.");
- }
-
+ var spenderTxId = foundSpenderTransaction.GetHash();
dateTime = foundSpenderTransaction.FirstSeen;
- var foundSpenderCoin = txRecordList.FirstOrDefault(x => x.TransactionId == coin.SpenderTransactionId);
+ var foundSpenderCoin = txRecordList.FirstOrDefault(x => x.TransactionId == spenderTxId);
if (foundSpenderCoin is { }) // if found
{
foundSpenderCoin.DateTime = foundSpenderCoin.DateTime < dateTime ? foundSpenderCoin.DateTime : dateTime;
@@ -75,7 +72,7 @@ namespace WalletWasabi.Blockchain.Transactions
Height = foundSpenderTransaction.Height,
Amount = Money.Zero - coin.Amount,
Label = foundSpenderTransaction.Label,
- TransactionId = coin.SpenderTransactionId,
+ TransactionId = spenderTxId,
BlockIndex = foundSpenderTransaction.BlockIndex
});
}

Make sure coin knows of its spender