Fix TransactionStore state after replacement txs
MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
@@ -210,7 +210,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
// | |
// | +--> (C)
// |
- // +--tx4 (replacement)---> (E)
+ // +--tx3 (replacement)---> (E)
//
var transactionProcessor = await CreateTransactionProcessorAsync();
@@ -231,16 +231,16 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
};
// A confirmed segwit transaction for us
- var tx = CreateCreditingTransaction(transactionProcessor.NewKey("A").P2wpkhScript, Money.Coins(1.0m), height: 54321);
- transactionProcessor.Process(tx);
+ var tx0 = CreateCreditingTransaction(transactionProcessor.NewKey("A").P2wpkhScript, Money.Coins(1.0m), height: 54321);
+ transactionProcessor.Process(tx0);
- var createdCoin = tx.Transaction.Outputs.AsCoins().First();
+ var createdCoin = tx0.Transaction.Outputs.AsCoins().First();
// Spend the received coin
- tx = CreateSpendingTransaction(createdCoin, transactionProcessor.NewKey("B").P2wpkhScript);
- tx.Transaction.Inputs[0].Sequence = Sequence.OptInRBF;
- tx.Transaction.Outputs[0].Value = Money.Coins(0.95m);
- tx.Transaction.Outputs.Add(Money.Coins(0.1m), transactionProcessor.NewKey("C").P2wpkhScript);
- var relevant = transactionProcessor.Process(tx);
+ var tx1 = CreateSpendingTransaction(createdCoin, transactionProcessor.NewKey("B").P2wpkhScript);
+ tx1.Transaction.Inputs[0].Sequence = Sequence.OptInRBF;
+ tx1.Transaction.Outputs[0].Value = Money.Coins(0.95m);
+ tx1.Transaction.Outputs.Add(Money.Coins(0.1m), transactionProcessor.NewKey("C").P2wpkhScript);
+ var relevant = transactionProcessor.Process(tx1);
Assert.True(relevant);
Assert.Equal(0, replaceTransactionReceivedCalled);
@@ -250,16 +250,16 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
Assert.True(unconfirmedCoin2.IsReplaceable);
// Spend the received coin
- tx = CreateSpendingTransaction(unconfirmedCoin1.GetCoin(), transactionProcessor.NewKey("D").P2wpkhScript);
- tx.Transaction.Outputs[0].Value = Money.Coins(0.7m);
- relevant = transactionProcessor.Process(tx);
+ var tx2 = CreateSpendingTransaction(unconfirmedCoin1.GetCoin(), transactionProcessor.NewKey("D").P2wpkhScript);
+ tx2.Transaction.Outputs[0].Value = Money.Coins(0.7m);
+ relevant = transactionProcessor.Process(tx2);
Assert.True(relevant);
Assert.Equal(0, replaceTransactionReceivedCalled);
// Spend it coin
- tx = CreateSpendingTransaction(createdCoin, transactionProcessor.NewKey("E").P2wpkhScript);
- tx.Transaction.Outputs[0].Value = Money.Coins(0.9m);
- relevant = transactionProcessor.Process(tx);
+ var tx3 = CreateSpendingTransaction(createdCoin, transactionProcessor.NewKey("E").P2wpkhScript);
+ tx3.Transaction.Outputs[0].Value = Money.Coins(0.9m);
+ relevant = transactionProcessor.Process(tx3);
Assert.True(relevant);
Assert.Equal(1, replaceTransactionReceivedCalled);
@@ -268,6 +268,14 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
Assert.Equal("E", finalCoin.HdPubKey.Label);
Assert.DoesNotContain(unconfirmedCoin1, transactionProcessor.Coins.AsAllCoinsView());
+
+ var matureTxs = transactionProcessor.TransactionStore.ConfirmedStore.GetTransactions();
+ Assert.Equal(tx0, matureTxs.First());
+
+ // All the replaced transactions tx1 and tx2 have to be removed because tx4 replaced tx1
+ var mempool = transactionProcessor.TransactionStore.MempoolStore.GetTransactions();
+ var txInMempool = Assert.Single(mempool);
+ Assert.Equal(tx3, txInMempool);
}
[Fact]
@@ -283,64 +291,93 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
transactionProcessor.SpenderConfirmed += (s, e) => confirmed++;
// A confirmed segwit transaction for us
- var tx = CreateCreditingTransaction(keys[0].PubKey.WitHash.ScriptPubKey, Money.Coins(1.0m));
- transactionProcessor.Process(tx);
+ var tx1 = CreateCreditingTransaction(keys[0].PubKey.WitHash.ScriptPubKey, Money.Coins(1.0m));
+ transactionProcessor.Process(tx1);
Assert.Equal(1, coinsReceived);
var coin = Assert.Single(transactionProcessor.Coins);
Assert.False(coin.Confirmed);
- var tx2 = new SmartTransaction(tx.Transaction.Clone(), new Height(54321));
+ var tx2 = new SmartTransaction(tx1.Transaction.Clone(), new Height(54321));
- Assert.Equal(tx.GetHash(), tx2.GetHash());
+ Assert.Equal(tx1.GetHash(), tx2.GetHash());
transactionProcessor.Process(tx2);
Assert.Equal(1, coinsReceived);
Assert.True(coin.Confirmed);
Assert.Equal(0, confirmed);
+
+ var mempool = transactionProcessor.TransactionStore.MempoolStore.GetTransactions();
+ Assert.Empty(mempool);
+
+ var matureTxs = transactionProcessor.TransactionStore.ConfirmedStore.GetTransactions();
+ var confirmedTx = Assert.Single(matureTxs);
+ Assert.Equal(tx1, confirmedTx);
+ Assert.Equal(tx2, confirmedTx);
}
[Fact]
public async Task HandlesBumpFeeAsync()
{
+ // --tx0---> (A) --+
+ // +--+-- tx2 (replaceable --+---> (myself)
+ // --tx1---> (B) --+ | |
+ // | +---> (change myself)
+ // |
+ // +--- tx4 (replaces tx3)
+ // |
+ // --tx3---> (C) -----+
+
// Replaces a previous RBF transaction by a new one that contains one more input (higher fee)
var transactionProcessor = await CreateTransactionProcessorAsync();
Script NewScript(string label) => transactionProcessor.NewKey(label).P2wpkhScript;
// A confirmed segwit transaction for us
- var tx = CreateCreditingTransaction(NewScript("A"), Money.Coins(1.0m), height: 54321);
- transactionProcessor.Process(tx);
+ var tx0 = CreateCreditingTransaction(NewScript("A"), Money.Coins(1.0m), height: 54321);
+ transactionProcessor.Process(tx0);
// Another confirmed segwit transaction for us
- tx = CreateCreditingTransaction(NewScript("B"), Money.Coins(1.0m), height: 54321);
- transactionProcessor.Process(tx);
+ var tx1 = CreateCreditingTransaction(NewScript("B"), Money.Coins(1.0m), height: 54321);
+ transactionProcessor.Process(tx1);
var createdCoins = transactionProcessor.Coins.Select(x => x.GetCoin()).ToArray(); // 2 coins of 1.0 btc each
// Spend the received coins
var destinationScript = NewScript("myself");
var changeScript = NewScript("Change myself");
- tx = CreateSpendingTransaction(createdCoins, destinationScript, changeScript); // spends 1.2btc
- tx.Transaction.Inputs[0].Sequence = Sequence.OptInRBF;
- var relevant = transactionProcessor.Process(tx);
+ var tx2 = CreateSpendingTransaction(createdCoins, destinationScript, changeScript); // spends 1.2btc
+ tx2.Transaction.Inputs[0].Sequence = Sequence.OptInRBF;
+ var relevant = transactionProcessor.Process(tx2);
Assert.True(relevant);
// Another confirmed segwit transaction for us
- tx = CreateCreditingTransaction(NewScript("C"), Money.Coins(1.0m), height: 54322);
- transactionProcessor.Process(tx);
+ var tx3 = CreateCreditingTransaction(NewScript("C"), Money.Coins(1.0m), height: 54322);
+ transactionProcessor.Process(tx3);
// At this moment we have one 1.2btc and one 0.8btc replaceable coins and one 1.0btc final coin
- var latestCreatedCoin = Assert.Single(transactionProcessor.Coins.CreatedBy(tx.Transaction.GetHash()));
+ var latestCreatedCoin = Assert.Single(transactionProcessor.Coins.CreatedBy(tx3.Transaction.GetHash()));
var coinsToSpend = createdCoins.Concat(new[] { latestCreatedCoin.GetCoin() }).ToArray();
// Spend them again with a different amount
destinationScript = new Key().PubKey.WitHash.ScriptPubKey; // spend to someone else
- tx = CreateSpendingTransaction(coinsToSpend, destinationScript, changeScript);
- relevant = transactionProcessor.Process(tx);
+ var tx4 = CreateSpendingTransaction(coinsToSpend, destinationScript, changeScript);
+ relevant = transactionProcessor.Process(tx4);
Assert.True(relevant);
var coin = Assert.Single(transactionProcessor.Coins);
Assert.True(coin.IsReplaceable);
+
+
+
+ var mempool = transactionProcessor.TransactionStore.MempoolStore.GetTransactions();
+ var inMempoolTx = Assert.Single(mempool);
+ Assert.Equal(tx4, inMempoolTx);
+
+ var matureTxs = transactionProcessor.TransactionStore.ConfirmedStore.GetTransactions().ToArray();
+ Assert.Equal(3, matureTxs.Length);
+ Assert.Equal(tx0, matureTxs[0]);
+ Assert.Equal(tx1, matureTxs[1]);
+ Assert.Equal(tx3, matureTxs[2]);
}
[Fact]

MODIFY
WalletWasabi/Blockchain/Transactions/TransactionProcessor.cs
WalletWasabi/Blockchain/Transactions/TransactionProcessor.cs
@@ -112,6 +112,11 @@ namespace WalletWasabi.Blockchain.Transactions
ReplaceTransactionReceived?.Invoke(this, new ReplaceTransactionReceivedEventArgs(tx, destroyed, restored));
+ foreach(var replacedTransactionId in destroyed.Select(coin => coin.TransactionId))
+ {
+ TransactionStore.MempoolStore.TryRemove(replacedTransactionId, out _);
+ }
+
tx.SetReplacement();
walletRelevant = true;
}

Fix TransactionStore state after replacement txs