Add update operation to TransactionStore
MODIFY
WalletWasabi.Tests/UnitTests/Transactions/OperationMergerTests.cs
WalletWasabi.Tests/UnitTests/Transactions/OperationMergerTests.cs
@@ -30,6 +30,14 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var expectedRemoveOperationInstance = Assert.IsType<Remove>(expectedOperation);
var expectedTxHash = Assert.Single(expectedRemoveOperationInstance.Transactions);
Assert.Equal(expectedTxHash, tx.GetHash());
+
+ var update = new Update(tx);
+ var unmergedUpdateOperations = new[] { update };
+ mergedOperations = OperationMerger.Merge(unmergedUpdateOperations);
+ expectedOperation = Assert.Single(mergedOperations);
+ var expectedUpdateOperationInstance = Assert.IsType<Update>(expectedOperation);
+ expectedTx = Assert.Single(expectedUpdateOperationInstance.Transactions);
+ Assert.Equal(expectedTx, tx);
}
[Fact]
@@ -41,6 +49,8 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx4 = Global.GenerateRandomSmartTransaction();
var tx5 = Global.GenerateRandomSmartTransaction();
var tx6 = Global.GenerateRandomSmartTransaction();
+ var tx7 = Global.GenerateRandomSmartTransaction();
+ var tx8 = Global.GenerateRandomSmartTransaction();
var a1 = new Append(tx1, tx2);
var a2 = new Append(tx3, tx4, tx5);
@@ -49,28 +59,31 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var r2 = new Remove(tx1.GetHash(), tx2.GetHash());
var r3 = new Remove(tx6.GetHash());
var a4 = new Append(tx1);
+ var u1 = new Update(tx7);
+ var u2 = new Update(tx8);
- var unmergedOperations = new ITxStoreOperation[] { a1, a2, a3, a4, r1, r2, r3 };
+ var unmergedOperations = new ITxStoreOperation[] { a1, a2, a3, a4, r1, r2, r3, u1, u2 };
var mergedOperations = OperationMerger.Merge(unmergedOperations);
- Assert.Equal(2, mergedOperations.Count());
+ Assert.Equal(3, mergedOperations.Count());
- unmergedOperations = new ITxStoreOperation[] { r1, r2, r3, a1, a2, a3, a4 };
+ unmergedOperations = new ITxStoreOperation[] { u1, u2, r1, r2, r3, a1, a2, a3, a4 };
mergedOperations = OperationMerger.Merge(unmergedOperations);
- Assert.Equal(2, mergedOperations.Count());
+ Assert.Equal(3, mergedOperations.Count());
- unmergedOperations = new ITxStoreOperation[] { a1, r1, a2, r2, a3, r3, a4 };
+ unmergedOperations = new ITxStoreOperation[] { a1, r1, u1, a2, r2, a3, r3, u2, a4 };
mergedOperations = OperationMerger.Merge(unmergedOperations);
- Assert.Equal(7, mergedOperations.Count());
+ Assert.Equal(9, mergedOperations.Count());
- unmergedOperations = new ITxStoreOperation[] { a1, a2, r1, a3, r2, r3, a4 };
+ unmergedOperations = new ITxStoreOperation[] { a1, a2, r1, a3, r2, r3, a4, u1, u2 };
mergedOperations = OperationMerger.Merge(unmergedOperations);
- Assert.Equal(5, mergedOperations.Count());
+ Assert.Equal(6, mergedOperations.Count());
var mergedOperationsArray = mergedOperations.ToArray();
Assert.Equal(5, Assert.IsType<Append>(mergedOperationsArray[0]).Transactions.Count());
Assert.Single(Assert.IsType<Remove>(mergedOperationsArray[1]).Transactions);
Assert.Single(Assert.IsType<Append>(mergedOperationsArray[2]).Transactions);
Assert.Equal(3, Assert.IsType<Remove>(mergedOperationsArray[3]).Transactions.Count());
Assert.Single(Assert.IsType<Append>(mergedOperationsArray[4]).Transactions);
+ Assert.Equal(2, Assert.IsType<Update>(mergedOperationsArray[5]).Transactions.Count());
}
}
}

MODIFY
WalletWasabi/Transactions/Operations/OperationMerger.cs
WalletWasabi/Transactions/Operations/OperationMerger.cs
@@ -16,37 +16,75 @@ namespace WalletWasabi.Transactions.Operations
{
var tempToAppends = new List<SmartTransaction>();
var tempToRemoves = new List<uint256>();
- bool wasLastOpAppend = operations.First() is Append;
+ var tempToUpdates = new List<SmartTransaction>();
+ ITxStoreOperation prevOperation = operations.First();
foreach (ITxStoreOperation op in operations)
{
- if (wasLastOpAppend)
+ if (prevOperation is Append)
{
if (op is Append opApp)
{
tempToAppends.AddRange(opApp.Transactions);
}
- else if (op is Remove opRem)
+ else
{
yield return new Append(tempToAppends);
tempToAppends = new List<SmartTransaction>();
+
+ if (op is Remove opRem)
+ {
tempToRemoves.AddRange(opRem.Transactions);
}
+ else if (op is Update opUpd)
+ {
+ tempToUpdates.AddRange(opUpd.Transactions);
}
- else
+ }
+ }
+ else if (prevOperation is Remove)
{
if (op is Remove opRem)
{
tempToRemoves.AddRange(opRem.Transactions);
}
- else if (op is Append opApp)
+ else
{
yield return new Remove(tempToRemoves);
tempToRemoves = new List<uint256>();
+
+ if (op is Append opApp)
+ {
tempToAppends.AddRange(opApp.Transactions);
}
+ else if (op is Update opUpd)
+ {
+ tempToUpdates.AddRange(opUpd.Transactions);
}
+ }
+ }
+ else if (prevOperation is Update)
+ {
+ if (op is Update opUpd)
+ {
+ tempToUpdates.AddRange(opUpd.Transactions);
+ }
+ else
+ {
+ yield return new Update(tempToUpdates);
+ tempToUpdates = new List<SmartTransaction>();
- wasLastOpAppend = op is Append;
+ if (op is Append opApp)
+ {
+ tempToAppends.AddRange(opApp.Transactions);
+ }
+ else if (op is Remove opRem)
+ {
+ tempToRemoves.AddRange(opRem.Transactions);
+ }
+ }
+ }
+
+ prevOperation = op;
}
if (tempToAppends.Any())
@@ -58,6 +96,11 @@ namespace WalletWasabi.Transactions.Operations
{
yield return new Remove(tempToRemoves);
}
+
+ if (tempToUpdates.Any())
+ {
+ yield return new Update(tempToUpdates);
+ }
}
}
}

ADD
WalletWasabi/Transactions/Operations/Update.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using WalletWasabi.Models;
+
+namespace WalletWasabi.Transactions.Operations
+{
+ public class Update : ITxStoreOperation
+ {
+ public IEnumerable<SmartTransaction> Transactions { get; }
+
+ public bool IsEmpty => Transactions is null || !Transactions.Any();
+
+ public Update(params SmartTransaction[] transactions) : this(transactions as IEnumerable<SmartTransaction>)
+ {
+ }
+
+ public Update(IEnumerable<SmartTransaction> transactions)
+ {
+ Transactions = transactions;
+ }
+ }
+}

MODIFY
WalletWasabi/Transactions/TransactionStore.cs
WalletWasabi/Transactions/TransactionStore.cs
@@ -63,15 +63,25 @@ namespace WalletWasabi.Transactions
.Select(x => SmartTransaction.FromLine(x, Network))
.OrderByBlockchain();
+ var added = false;
+ var updated = false;
lock (TransactionsLock)
{
foreach (var tx in allTransactions)
{
- TryAddOrUpdateNoLockNoSerialization(tx);
+ var res = TryAddOrUpdateNoLockNoSerialization(tx);
+ if (res.isAdded)
+ {
+ added = true;
+ }
+ if (res.isUpdated)
+ {
+ updated = true;
+ }
}
}
- if (allTransactions.Count() != Transactions.Count)
+ if (added || updated)
{
// Another process worked into the file and appended the same transaction into it.
// In this case we correct the file by serializing the unique set.
@@ -107,6 +117,11 @@ namespace WalletWasabi.Transactions
_ = TryAppendToFileAsync(tx);
}
+ if (ret.isUpdated)
+ {
+ _ = TryUpdateFileAsync(tx);
+ }
+
return ret;
}
@@ -219,6 +234,12 @@ namespace WalletWasabi.Transactions
private async Task TryRemoveFromFileAsync(IEnumerable<uint256> transactionIds)
=> await TryCommitToFileAsync(new Remove(transactionIds)).ConfigureAwait(false);
+ private async Task TryUpdateFileAsync(params SmartTransaction[] transactions)
+ => await TryUpdateFileAsync(transactions as IEnumerable<SmartTransaction>).ConfigureAwait(false);
+
+ private async Task TryUpdateFileAsync(IEnumerable<SmartTransaction> transactions)
+ => await TryCommitToFileAsync(new Update(transactions)).ConfigureAwait(false);
+
private List<ITxStoreOperation> Operations { get; } = new List<ITxStoreOperation>();
private object OperationsLock { get; } = new object();
@@ -308,6 +329,33 @@ namespace WalletWasabi.Transactions
await SerializeAllTransactionsNoMutexAsync().ConfigureAwait(false);
}
}
+ else if (op is Update updateOperation)
+ {
+ var toUpdates = updateOperation.Transactions;
+
+ string[] allLines = await TransactionsFileManager.ReadAllLinesAsync().ConfigureAwait(false);
+ IEnumerable<SmartTransaction> allTransactions = allLines.Select(x => SmartTransaction.FromLine(x, Network));
+ var toSerialize = new List<SmartTransaction>();
+
+ foreach (SmartTransaction tx in allTransactions)
+ {
+ var txsToUpdateWith = toUpdates.Where(x => x == tx);
+ foreach (var txToUpdateWith in txsToUpdateWith)
+ {
+ tx.TryUpdate(txToUpdateWith);
+ }
+ toSerialize.Add(tx);
+ }
+
+ try
+ {
+ await TransactionsFileManager.WriteAllLinesAsync(toSerialize.ToBlockchainOrderedLines()).ConfigureAwait(false);
+ }
+ catch
+ {
+ await SerializeAllTransactionsNoMutexAsync().ConfigureAwait(false);
+ }
+ }
else
{
throw new NotSupportedException();

Add update operation to TransactionStore