Add basic receive test
MODIFY
MagicalCryptoWallet.Tests/RegTests.cs
MagicalCryptoWallet.Tests/RegTests.cs
@@ -445,8 +445,8 @@ namespace MagicalCryptoWallet.Tests
var wallet = new WalletService(keyManager, indexDownloader, nodes, blocksFolderPath);
// Get some money, make it confirm.
- var key = keyManager.GenerateNewKey("", KeyState.Clean, isInternal: false);
- await Global.RpcClient.SendToAddressAsync(key.GetP2wpkhAddress(network), new Money(0.1m, MoneyUnit.BTC));
+ var key = keyManager.GenerateNewKey("foo Label", KeyState.Clean, isInternal: false);
+ var txid = await Global.RpcClient.SendToAddressAsync(key.GetP2wpkhAddress(network), new Money(0.1m, MoneyUnit.BTC));
await Global.RpcClient.GenerateAsync(1);
try
@@ -472,6 +472,18 @@ namespace MagicalCryptoWallet.Tests
await wallet.InitializeAsync(cts.Token); // Initialize wallet service.
}
Assert.Equal(1, await wallet.CountBlocksAsync());
+ Assert.Single(wallet.Coins);
+ var firstCoin = wallet.Coins.Single();
+ Assert.Equal(new Money(0.1m, MoneyUnit.BTC), firstCoin.Amount);
+ Assert.Equal(indexDownloader.GetBestFilter().BlockHeight, firstCoin.Height);
+ Assert.InRange(firstCoin.Index, 0, 1);
+ Assert.True(firstCoin.Unspent);
+ Assert.Equal("foo Label", firstCoin.Label);
+ Assert.Equal(key.GetP2wpkhScript(), firstCoin.ScriptPubKey);
+ Assert.Null(firstCoin.SpenderTransactionId);
+ Assert.NotNull(firstCoin.SpentOutputs);
+ Assert.NotEmpty(firstCoin.SpentOutputs);
+ Assert.Equal(txid, firstCoin.TransactionId);
// Get some money, make it confirm.
key = keyManager.GenerateNewKey("", KeyState.Clean, isInternal: false);

MODIFY
MagicalCryptoWallet.Tests/SmartModelTests.cs
MagicalCryptoWallet.Tests/SmartModelTests.cs
@@ -71,7 +71,7 @@ namespace MagicalCryptoWallet.Tests
Assert.Equal(coin.Height, deserialized.Height);
Assert.Equal(coin.Amount, deserialized.Amount);
Assert.Equal(coin.Index, deserialized.Index);
- Assert.Equal(coin.IsSpent, deserialized.IsSpent);
+ Assert.Equal(coin.Unspent, deserialized.Unspent);
Assert.Equal(coin.Label, deserialized.Label);
Assert.Equal(coin.ScriptPubKey, deserialized.ScriptPubKey);
Assert.Equal(coin.SpenderTransactionId, deserialized.SpenderTransactionId);

MODIFY
MagicalCryptoWallet/Models/SmartCoin.cs
MagicalCryptoWallet/Models/SmartCoin.cs
@@ -45,7 +45,7 @@ namespace MagicalCryptoWallet.Models
[JsonProperty(Order = 8)]
public TxoRef[] SpentOutputs { get; }
- public bool IsSpent => SpenderTransactionId == null;
+ public bool Unspent => SpenderTransactionId == null;
[JsonConstructor]
public SmartCoin(uint256 transactionId, int index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, string label = "", uint256 spenderTransactionId = null)

MODIFY
MagicalCryptoWallet/Services/WalletService.cs
MagicalCryptoWallet/Services/WalletService.cs
@@ -33,7 +33,7 @@ namespace MagicalCryptoWallet.Services
private HashSet<uint256> ProcessedBlocks { get; }
private AsyncLock WalletBlocksLock { get; }
- public ConcurrentHashSet<SmartCoin> KnownCoins { get; }
+ public ConcurrentHashSet<SmartCoin> Coins { get; }
/// <summary>
/// 0: Not started, 1: Running, 2: Stopping, 3: Stopped
@@ -53,7 +53,7 @@ namespace MagicalCryptoWallet.Services
WalletBlocksLock = new AsyncLock();
HandleFiltersLock = new AsyncLock();
- KnownCoins = new ConcurrentHashSet<SmartCoin>();
+ Coins = new ConcurrentHashSet<SmartCoin>();
BlocksFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(blocksFolderPath), blocksFolderPath, trim: true);
BlockFolderLock = new AsyncLock();
@@ -78,29 +78,6 @@ namespace MagicalCryptoWallet.Services
IndexDownloader.Reorged += IndexDownloader_ReorgedAsync;
}
- private void ProcessBlock(Block block)
- {
- using (WalletBlocksLock.Lock())
- {
- //Todo!
-
- if(ProcessedBlocks.Contains(block.GetHash()))
- {
- return;
- }
-
- if(block.GetHash() == WalletBlocks.Last().Value) // If this is the latest block then no need to look through everything.
- {
-
- ProcessedBlocks.Add(block.GetHash());
- }
- else // Do a deep reindexing.
- {
- ProcessedBlocks.Clear();
- }
- }
- }
-
private async void IndexDownloader_ReorgedAsync(object sender, uint256 invalidBlockHash)
{
using (HandleFiltersLock.Lock())
@@ -112,12 +89,25 @@ namespace MagicalCryptoWallet.Services
ProcessedBlocks.Remove(invalidBlockHash);
if (elem.Key != null)
{
- foreach(var toRemove in KnownCoins.Where(x => x.Height == elem.Key).ToHashSet())
+ foreach(var toRemove in Coins.Where(x => x.Height == elem.Key).ToHashSet())
{
- KnownCoins.TryRemove(toRemove);
+ RemoveCoinRecursively(toRemove);
+ }
}
}
}
+
+ private void RemoveCoinRecursively(SmartCoin toRemove)
+ {
+ if(toRemove.SpenderTransactionId != null)
+ {
+ foreach(var toAlsoRemove in Coins.Where(x=>x.TransactionId == toRemove.SpenderTransactionId).ToHashSet())
+ {
+ RemoveCoinRecursively(toAlsoRemove);
+ }
+ }
+
+ Coins.TryRemove(toRemove);
}
private async void IndexDownloader_NewFilterAsync(object sender, FilterModel filterModel)
@@ -154,10 +144,39 @@ namespace MagicalCryptoWallet.Services
private async Task ProcessFilterModelAsync(FilterModel filterModel, CancellationToken cancel)
{
+ if (ProcessedBlocks.Contains(filterModel.BlockHash))
+ {
+ return;
+ }
+
var matchFound = filterModel.Filter.MatchAny(KeyManager.GetKeys().Select(x => x.GetP2wpkhScript().ToCompressedBytes()), filterModel.FilterKey);
- if (matchFound)
+ if (!matchFound)
+ {
+ return;
+ }
+
+ Block currentBlock = await GetOrDownloadBlockAsync(filterModel.BlockHash, cancel); // Wait until not downloaded.
+
+ WalletBlocks.AddOrReplace(filterModel.BlockHeight, filterModel.BlockHash);
+
+ if (currentBlock.GetHash() == WalletBlocks.Last().Value) // If this is the latest block then no need for deep gothrough.
+ {
+ ProcessBlock(filterModel.BlockHeight, currentBlock);
+ }
+ else // must go through all the blocks in order
+ {
+ foreach(var blockRef in WalletBlocks)
+ {
+ var block = await GetOrDownloadBlockAsync(blockRef.Value, CancellationToken.None);
+ ProcessedBlocks.Clear();
+ Coins.Clear();
+ ProcessBlock(blockRef.Key, block);
+ }
+ }
+ }
+
+ private void ProcessBlock(Height height, Block block)
{
- Block block = await GetOrDownloadBlockAsync(filterModel.BlockHash, cancel); // Wait until not downloaded.
var keys = KeyManager.GetKeys().ToList();
foreach (var tx in block.Transactions)
@@ -170,7 +189,8 @@ namespace MagicalCryptoWallet.Services
if (foundKey != default)
{
foundKey.KeyState = KeyState.Used;
- var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, tx.Inputs.ToTxoRefs().ToArray(), filterModel.BlockHeight, foundKey.Label, null);
+ var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, tx.Inputs.ToTxoRefs().ToArray(), height, foundKey.Label, null);
+ Coins.Add(coin);
// Make sure there's always 21 clean keys generated and indexed.
while (KeyManager.GetKeys(KeyState.Clean, foundKey.IsInternal()).Count() < 21)
@@ -185,7 +205,7 @@ namespace MagicalCryptoWallet.Services
{
var input = tx.Inputs[i];
- var foundCoin = KnownCoins.SingleOrDefault(x => x.TransactionId == input.PrevOut.Hash && x.Index == input.PrevOut.N);
+ var foundCoin = Coins.SingleOrDefault(x => x.TransactionId == input.PrevOut.Hash && x.Index == input.PrevOut.N);
if (foundCoin != null)
{
foundCoin.SpenderTransactionId = tx.GetHash();
@@ -194,9 +214,6 @@ namespace MagicalCryptoWallet.Services
}
ProcessedBlocks.Add(block.GetHash());
-
- WalletBlocks.AddOrReplace(filterModel.BlockHeight, filterModel.BlockHash);
- }
}
/// <exception cref="OperationCanceledException"></exception>

Add basic receive test