Implement WalletManager
MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -35,6 +35,7 @@ using WalletWasabi.Logging;
using WalletWasabi.Services;
using WalletWasabi.Stores;
using WalletWasabi.TorSocks5;
+using WalletWasabi.Wallets;
namespace WalletWasabi.Gui
{
@@ -57,7 +58,8 @@ namespace WalletWasabi.Gui
public NodesGroup Nodes { get; private set; }
public WasabiSynchronizer Synchronizer { get; private set; }
public FeeProviders FeeProviders { get; private set; }
- public WalletService WalletService { get; private set; }
+ public WalletManager WalletManager { get; }
+ public WalletService WalletService => WalletManager?.GetFirstOrDefaultWallet();
public TransactionBroadcaster TransactionBroadcaster { get; set; }
public CoinJoinProcessor CoinJoinProcessor { get; set; }
public Node RegTestMempoolServingNode { get; private set; }
@@ -86,6 +88,10 @@ namespace WalletWasabi.Gui
Directory.CreateDirectory(WalletBackupsDir);
HostedServices = new HostedServices();
+ WalletManager = new WalletManager(WalletBackupsDir);
+
+ WalletManager.OnDequeue += WalletManager_OnDequeue;
+ WalletManager.WalletRelevantTransactionProcessed += WalletManager_WalletRelevantTransactionProcessed;
}
public async Task<bool> InitializeUiConfigAsync()
@@ -163,7 +169,6 @@ namespace WalletWasabi.Gui
try
{
- WalletService = null;
AddressManager = null;
TorManager = null;
var cancel = StoppingCts.Token;
@@ -376,8 +381,8 @@ namespace WalletWasabi.Gui
cancel.ThrowIfCancellationRequested();
- TransactionBroadcaster = new TransactionBroadcaster(Network, BitcoinStore, Synchronizer, Nodes, BitcoinCoreNode?.RpcClient);
- CoinJoinProcessor = new CoinJoinProcessor(Synchronizer, BitcoinCoreNode?.RpcClient);
+ TransactionBroadcaster = new TransactionBroadcaster(Network, BitcoinStore, Synchronizer, Nodes, WalletManager, BitcoinCoreNode?.RpcClient);
+ CoinJoinProcessor = new CoinJoinProcessor(Synchronizer, WalletManager, BitcoinCoreNode?.RpcClient);
#region JsonRpcServerInitialization
@@ -518,24 +523,13 @@ namespace WalletWasabi.Gui
await Task.Delay(100, token);
}
- WalletService = new WalletService(BitcoinStore, keyManager, Synchronizer, Nodes, DataDir, Config.ServiceConfiguration, FeeProviders, BitcoinCoreNode);
-
- Logger.LogInfo($"Starting {nameof(WalletService)}...");
- await WalletService.StartAsync(token);
- Logger.LogInfo($"{nameof(WalletService)} started.");
-
- token.ThrowIfCancellationRequested();
-
- TransactionBroadcaster.AddWalletService(WalletService);
- CoinJoinProcessor.AddWalletService(WalletService);
-
- WalletService.TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessed;
- WalletService.ChaumianClient.OnDequeue += ChaumianClient_OnDequeued;
+ var walletService = new WalletService(BitcoinStore, keyManager, Synchronizer, Nodes, DataDir, Config.ServiceConfiguration, FeeProviders, BitcoinCoreNode);
+ await WalletManager.AddAndStartAsync(walletService, token).ConfigureAwait(false);
}
_cancelWalletServiceInitialization = null; // Must make it null explicitly, because dispose won't make it null.
}
- private void ChaumianClient_OnDequeued(object sender, DequeueResult e)
+ private void WalletManager_OnDequeue(object sender, DequeueResult e)
{
try
{
@@ -571,7 +565,7 @@ namespace WalletWasabi.Gui
}
}
- private void TransactionProcessor_WalletRelevantTransactionProcessed(object sender, ProcessedResult e)
+ private void WalletManager_WalletRelevantTransactionProcessed(object sender, ProcessedResult e)
{
try
{
@@ -740,13 +734,6 @@ namespace WalletWasabi.Gui
public async Task DisposeInWalletDependentServicesAsync()
{
- var walletService = WalletService;
- if (walletService is { })
- {
- WalletService.TransactionProcessor.WalletRelevantTransactionProcessed -= TransactionProcessor_WalletRelevantTransactionProcessed;
- WalletService.ChaumianClient.OnDequeue -= ChaumianClient_OnDequeued;
- }
-
try
{
_cancelWalletServiceInitialization?.Cancel();
@@ -757,20 +744,7 @@ namespace WalletWasabi.Gui
}
_cancelWalletServiceInitialization = null;
- walletService = WalletService;
- if (walletService is { })
- {
- var keyManager = walletService.KeyManager;
- if (keyManager is { }) // This should never happen.
- {
- string backupWalletFilePath = Path.Combine(WalletBackupsDir, Path.GetFileName(keyManager.FilePath));
- keyManager.ToFile(backupWalletFilePath);
- Logger.LogInfo($"{nameof(walletService.KeyManager)} backup saved to `{backupWalletFilePath}`.");
- }
- await walletService.StopAsync(CancellationToken.None).ConfigureAwait(false);
- WalletService = null;
- Logger.LogInfo($"{nameof(WalletService)} is stopped.");
- }
+ await WalletManager.RemoveAndStopAllAsync().ConfigureAwait(false);
}
/// <summary>
@@ -812,6 +786,9 @@ namespace WalletWasabi.Gui
await DisposeInWalletDependentServicesAsync();
+ WalletManager.OnDequeue -= WalletManager_OnDequeue;
+ WalletManager.WalletRelevantTransactionProcessed -= WalletManager_WalletRelevantTransactionProcessed;
+
var rpcServer = RpcServer;
if (rpcServer is { })
{

MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -45,6 +45,7 @@ using WalletWasabi.Services;
using WalletWasabi.Stores;
using WalletWasabi.Tests.XunitConfiguration;
using WalletWasabi.TorSocks5;
+using WalletWasabi.Wallets;
using WalletWasabi.WebClients.Wasabi;
using Xunit;
using static NBitcoin.Crypto.SchnorrBlinding;
@@ -242,7 +243,7 @@ namespace WalletWasabi.Tests.IntegrationTests
[Fact]
public async Task StatusRequestTestAsync()
{
- const string request = "/api/v3/btc/Blockchain/status";
+ const string Request = "/api/v3/btc/Blockchain/status";
(string password, RPCClient rpc, Network network, Coordinator coordinator, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await InitializeTestEnvironmentAsync(1);
var indexBuilderService = global.IndexBuilderService;
@@ -263,7 +264,7 @@ namespace WalletWasabi.Tests.IntegrationTests
}
using var client = new WasabiClient(new Uri(RegTestFixture.BackendEndPoint), null);
- var response = await client.TorClient.SendAndRetryAsync(HttpMethod.Get, HttpStatusCode.OK, request);
+ var response = await client.TorClient.SendAndRetryAsync(HttpMethod.Get, HttpStatusCode.OK, Request);
using (HttpContent content = response.Content)
{
var resp = await content.ReadAsJsonAsync<StatusResponse>();
@@ -276,7 +277,7 @@ namespace WalletWasabi.Tests.IntegrationTests
await rpc.GenerateAsync(1);
- response = await client.TorClient.SendAndRetryAsync(HttpMethod.Get, HttpStatusCode.OK, request);
+ response = await client.TorClient.SendAndRetryAsync(HttpMethod.Get, HttpStatusCode.OK, Request);
using (HttpContent content = response.Content)
{
var resp = await content.ReadAsJsonAsync<StatusResponse>();
@@ -288,7 +289,7 @@ namespace WalletWasabi.Tests.IntegrationTests
var blockchainController = (BlockchainController)RegTestFixture.BackendHost.Services.GetService(typeof(BlockchainController));
blockchainController.Cache.Remove($"{nameof(BlockchainController.GetStatusAsync)}");
- response = await client.TorClient.SendAndRetryAsync(HttpMethod.Get, HttpStatusCode.OK, request);
+ response = await client.TorClient.SendAndRetryAsync(HttpMethod.Get, HttpStatusCode.OK, Request);
using (HttpContent content = response.Content)
{
var resp = await content.ReadAsJsonAsync<StatusResponse>();
@@ -752,6 +753,8 @@ namespace WalletWasabi.Tests.IntegrationTests
var wallet = new WalletService(bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer);
wallet.NewFilterProcessed += Wallet_NewFilterProcessed;
+ var walletManager = new WalletManager(null);
+
// Get some money, make it confirm.
var key = keyManager.GetNextReceiveKey("foo label", out _);
var key2 = keyManager.GetNextReceiveKey("foo label", out _);
@@ -775,10 +778,9 @@ namespace WalletWasabi.Tests.IntegrationTests
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
- await wallet.StartAsync(cts.Token); // Initialize wallet service.
+ await walletManager.AddAndStartAsync(wallet, cts.Token);
}
- var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, rpc);
- broadcaster.AddWalletService(wallet);
+ var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, walletManager, rpc);
var waitCount = 0;
while (wallet.Coins.Sum(x => x.Amount) == Money.Zero)
@@ -1191,7 +1193,7 @@ namespace WalletWasabi.Tests.IntegrationTests
finally
{
wallet.NewFilterProcessed -= Wallet_NewFilterProcessed;
- await wallet.StopAsync(CancellationToken.None);
+ await walletManager.RemoveAndStopAllAsync();
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -1383,6 +1385,8 @@ namespace WalletWasabi.Tests.IntegrationTests
var wallet = new WalletService(bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer);
wallet.NewFilterProcessed += Wallet_NewFilterProcessed;
+ var walletManager = new WalletManager(null);
+
Assert.Empty(wallet.Coins);
var baseTip = await rpc.GetBestBlockHashAsync();
@@ -1408,12 +1412,12 @@ namespace WalletWasabi.Tests.IntegrationTests
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
- await wallet.StartAsync(cts.Token); // Initialize wallet service.
+ await walletManager.AddAndStartAsync(wallet, cts.Token);
}
+
var coin = Assert.Single(wallet.Coins);
Assert.True(coin.Confirmed);
- var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, rpc);
- broadcaster.AddWalletService(wallet);
+ var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, walletManager, rpc);
// Send money before reorg.
var operations = new PaymentIntent(scp, Money.Coins(0.011m));
@@ -1562,7 +1566,7 @@ namespace WalletWasabi.Tests.IntegrationTests
}
finally
{
- await wallet.StopAsync(CancellationToken.None);
+ await walletManager.RemoveAndStopAllAsync();
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -1598,6 +1602,8 @@ namespace WalletWasabi.Tests.IntegrationTests
var wallet = new WalletService(bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, synchronizer);
wallet.NewFilterProcessed += Wallet_NewFilterProcessed;
+ var walletManager = new WalletManager(null);
+
Assert.Empty(wallet.Coins);
// Get some money, make it confirm.
@@ -1615,7 +1621,7 @@ namespace WalletWasabi.Tests.IntegrationTests
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
- await wallet.StartAsync(cts.Token); // Initialize wallet service.
+ await walletManager.AddAndStartAsync(wallet, cts.Token);
}
Assert.Empty(wallet.Coins);
@@ -1633,8 +1639,7 @@ namespace WalletWasabi.Tests.IntegrationTests
Assert.Equal(tx0Id, eventArgs.NewlyReceivedCoins.Single().TransactionId);
Assert.Single(wallet.Coins);
- var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, rpc);
- broadcaster.AddWalletService(wallet);
+ var broadcaster = new TransactionBroadcaster(network, bitcoinStore, synchronizer, nodes, walletManager, rpc);
var operations = new PaymentIntent(
new DestinationRequest(key.P2wpkhScript, Money.Coins(0.01m)),
@@ -1733,7 +1738,7 @@ namespace WalletWasabi.Tests.IntegrationTests
}
finally
{
- await wallet.StopAsync(CancellationToken.None);
+ await walletManager.RemoveAndStopAllAsync();
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.

MODIFY
WalletWasabi/Blockchain/TransactionBroadcasting/TransactionBroadcaster.cs
WalletWasabi/Blockchain/TransactionBroadcasting/TransactionBroadcaster.cs
@@ -16,6 +16,7 @@ using WalletWasabi.Logging;
using WalletWasabi.Models;
using WalletWasabi.Services;
using WalletWasabi.Stores;
+using WalletWasabi.Wallets;
using WalletWasabi.WebClients.Wasabi;
namespace WalletWasabi.Blockchain.TransactionBroadcasting
@@ -26,31 +27,19 @@ namespace WalletWasabi.Blockchain.TransactionBroadcasting
public WasabiSynchronizer Synchronizer { get; }
public Network Network { get; }
public NodesGroup Nodes { get; }
- public List<WalletService> WalletServices { get; }
- public IEnumerable<WalletService> AliveWalletServices => WalletServices.Where(x => x != null && !x.IsStoppingOrStopped);
- public object WalletServicesLock { get; }
public RPCClient RpcClient { get; private set; }
+ public WalletManager WalletManager { get; }
- public TransactionBroadcaster(Network network, BitcoinStore bitcoinStore, WasabiSynchronizer synchronizer, NodesGroup nodes, RPCClient rpc)
+ public TransactionBroadcaster(Network network, BitcoinStore bitcoinStore, WasabiSynchronizer synchronizer, NodesGroup nodes, WalletManager walletManager, RPCClient rpc)
{
Nodes = Guard.NotNull(nameof(nodes), nodes);
Network = Guard.NotNull(nameof(network), network);
BitcoinStore = Guard.NotNull(nameof(bitcoinStore), bitcoinStore);
Synchronizer = Guard.NotNull(nameof(synchronizer), synchronizer);
- WalletServices = new List<WalletService>();
- WalletServicesLock = new object();
+ WalletManager = Guard.NotNull(nameof(walletManager), walletManager);
RpcClient = rpc;
}
- public void AddWalletService(WalletService walletService)
- {
- Guard.NotNull(nameof(walletService), walletService);
- lock (WalletServicesLock)
- {
- WalletServices.Add(walletService);
- }
- }
-
private async Task BroadcastTransactionToNetworkNodeAsync(SmartTransaction transaction, Node node)
{
Logger.LogInfo($"Trying to broadcast transaction with random node ({node.RemoteSocketAddress}):{transaction.GetHash()}.");
@@ -113,18 +102,11 @@ namespace WalletWasabi.Blockchain.TransactionBroadcasting
if (transaction.Transaction.Inputs.Count == 1) // If we tried to only spend one coin, then we can mark it as spent. If there were more coins, then we do not know.
{
OutPoint input = transaction.Transaction.Inputs.First().PrevOut;
- lock (WalletServicesLock)
- {
- foreach (var walletService in AliveWalletServices)
- {
- SmartCoin coin = walletService.Coins.GetByOutPoint(input);
- if (coin != default)
+ foreach (var coin in WalletManager.CoinsByOutPoint(input))
{
coin.SpentAccordingToBackend = true;
}
}
- }
- }
throw;
}
@@ -142,13 +124,7 @@ namespace WalletWasabi.Blockchain.TransactionBroadcasting
transaction.SetUnconfirmed();
}
- lock (WalletServicesLock)
- {
- foreach (var walletService in AliveWalletServices)
- {
- walletService.TransactionProcessor.Process(transaction);
- }
- }
+ WalletManager.Process(transaction);
}
public async Task SendTransactionAsync(SmartTransaction transaction)

MODIFY
WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs
WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs
@@ -15,6 +15,7 @@ using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Models;
using WalletWasabi.Services;
+using WalletWasabi.Wallets;
using WalletWasabi.WebClients.Wasabi;
namespace WalletWasabi.CoinJoin.Client
@@ -22,17 +23,14 @@ namespace WalletWasabi.CoinJoin.Client
public class CoinJoinProcessor : IDisposable
{
public WasabiSynchronizer Synchronizer { get; }
- public Dictionary<WalletService, HashSet<uint256>> WalletServices { get; }
- public IEnumerable<KeyValuePair<WalletService, HashSet<uint256>>> AliveWalletServices => WalletServices.Where(x => x.Key is { IsStoppingOrStopped: var isDisposed } && !isDisposed);
- public object WalletServicesLock { get; }
+ public WalletManager WalletManager { get; }
public RPCClient RpcClient { get; private set; }
private AsyncLock ProcessLock { get; }
- public CoinJoinProcessor(WasabiSynchronizer synchronizer, RPCClient rpc)
+ public CoinJoinProcessor(WasabiSynchronizer synchronizer, WalletManager walletManager, RPCClient rpc)
{
Synchronizer = Guard.NotNull(nameof(synchronizer), synchronizer);
- WalletServices = new Dictionary<WalletService, HashSet<uint256>>();
- WalletServicesLock = new object();
+ WalletManager = Guard.NotNull(nameof(walletManager), walletManager);
RpcClient = rpc;
ProcessLock = new AsyncLock();
Synchronizer.ResponseArrived += Synchronizer_ResponseArrivedAsync;
@@ -50,18 +48,7 @@ namespace WalletWasabi.CoinJoin.Client
return;
}
- var txsNotKnownByAWalletService = new HashSet<uint256>();
- lock (WalletServicesLock)
- {
- foreach (var pair in AliveWalletServices)
- {
- // If a wallet service doesn't know about the tx, then we add it for processing.
- foreach (var tx in unconfirmedCoinJoinHashes.Where(x => !pair.Value.Contains(x)))
- {
- txsNotKnownByAWalletService.Add(tx);
- }
- }
- }
+ var txsNotKnownByAWalletService = WalletManager.FilterUnknownCoinjoins(unconfirmedCoinJoinHashes);
using var client = new WasabiClient(Synchronizer.WasabiClient.TorClient.DestinationUriAction, Synchronizer.WasabiClient.TorClient.TorSocks5EndPoint);
@@ -73,7 +60,7 @@ namespace WalletWasabi.CoinJoin.Client
|| await TryBroadcastTransactionWithRpcAsync(tx).ConfigureAwait(false)
|| (await RpcClient.TestAsync().ConfigureAwait(false)) is { }) // If the test throws exception then I believe it, because RPC is down and the backend is the god.
{
- BelieveTransaction(tx);
+ WalletManager.ProcessCoinJoin(tx);
}
}
}
@@ -84,28 +71,6 @@ namespace WalletWasabi.CoinJoin.Client
}
}
- public void AddWalletService(WalletService walletService)
- {
- Guard.NotNull(nameof(walletService), walletService);
- lock (WalletServicesLock)
- {
- WalletServices.Add(walletService, new HashSet<uint256>());
- }
- }
-
- private void BelieveTransaction(SmartTransaction transaction)
- {
- lock (WalletServicesLock)
- {
- foreach (var pair in AliveWalletServices.Where(x => !x.Value.Contains(transaction.GetHash())))
- {
- var walletService = pair.Key;
- pair.Value.Add(transaction.GetHash());
- walletService.TransactionProcessor.Process(transaction);
- }
- }
- }
-
private async Task<bool> TryBroadcastTransactionWithRpcAsync(SmartTransaction transaction)
{
try

ADD
WalletWasabi/Wallets/WalletManager.cs
+using NBitcoin;
+using Nito.AsyncEx;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.Blockchain.TransactionProcessing;
+using WalletWasabi.Blockchain.Transactions;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+using WalletWasabi.Services;
+
+namespace WalletWasabi.Wallets
+{
+ public class WalletManager
+ {
+ public WalletManager(string walletBackupsDir)
+ {
+ WalletBackupsDir = walletBackupsDir;
+ Wallets = new Dictionary<WalletService, HashSet<uint256>>();
+ Lock = new object();
+ AddRemoveLock = new AsyncLock();
+ }
+
+ public event EventHandler<ProcessedResult> WalletRelevantTransactionProcessed;
+
+ public event EventHandler<DequeueResult> OnDequeue;
+
+ public Dictionary<WalletService, HashSet<uint256>> Wallets { get; }
+ public object Lock { get; }
+ public AsyncLock AddRemoveLock { get; }
+ public string WalletBackupsDir { get; }
+
+ public IEnumerable<KeyValuePair<WalletService, HashSet<uint256>>> AliveWalletsNoLock => Wallets.Where(x => x.Key is { IsStoppingOrStopped: var isDisposed } && !isDisposed);
+
+ public WalletService GetFirstOrDefaultWallet()
+ {
+ lock (Lock)
+ {
+ return Wallets.Keys.FirstOrDefault();
+ }
+ }
+
+ public async Task AddAndStartAsync(WalletService walletService, CancellationToken cancel)
+ {
+ using (await AddRemoveLock.LockAsync().ConfigureAwait(false))
+ {
+ lock (Lock)
+ {
+ Wallets.Add(walletService, new HashSet<uint256>());
+ }
+
+ Logger.LogInfo($"Starting {nameof(WalletService)}...");
+ await walletService.StartAsync(cancel).ConfigureAwait(false);
+ Logger.LogInfo($"{nameof(WalletService)} started.");
+
+ cancel.ThrowIfCancellationRequested();
+
+ walletService.TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessed;
+ walletService.ChaumianClient.OnDequeue += ChaumianClient_OnDequeue;
+ }
+ }
+
+ private void ChaumianClient_OnDequeue(object sender, DequeueResult e)
+ {
+ var handler = OnDequeue;
+ handler?.Invoke(sender, e);
+ }
+
+ private void TransactionProcessor_WalletRelevantTransactionProcessed(object sender, ProcessedResult e)
+ {
+ var handler = WalletRelevantTransactionProcessed;
+ handler?.Invoke(sender, e);
+ }
+
+ public async Task RemoveAndStopAllAsync()
+ {
+ using (await AddRemoveLock.LockAsync().ConfigureAwait(false))
+ {
+ List<WalletService> walletsListClone;
+ lock (Lock)
+ {
+ walletsListClone = Wallets.Keys.ToList();
+ }
+ foreach (var walletService in walletsListClone)
+ {
+ walletService.TransactionProcessor.WalletRelevantTransactionProcessed -= TransactionProcessor_WalletRelevantTransactionProcessed;
+ walletService.ChaumianClient.OnDequeue -= ChaumianClient_OnDequeue;
+
+ lock (Lock)
+ {
+ if (!Wallets.Remove(walletService))
+ {
+ throw new InvalidOperationException("Wallet service doesn't exist.");
+ }
+ }
+
+ var keyManager = walletService.KeyManager;
+ if (keyManager is { }) // This should never happen.
+ {
+ string backupWalletFilePath = Path.Combine(WalletBackupsDir, Path.GetFileName(keyManager.FilePath));
+ keyManager.ToFile(backupWalletFilePath);
+ Logger.LogInfo($"{nameof(walletService.KeyManager)} backup saved to `{backupWalletFilePath}`.");
+ }
+ await walletService.StopAsync(CancellationToken.None).ConfigureAwait(false);
+ Logger.LogInfo($"{nameof(WalletService)} is stopped.");
+ }
+ }
+ }
+
+ public void ProcessCoinJoin(SmartTransaction tx)
+ {
+ lock (Lock)
+ {
+ foreach (var pair in AliveWalletsNoLock.Where(x => !x.Value.Contains(tx.GetHash())))
+ {
+ var walletService = pair.Key;
+ pair.Value.Add(tx.GetHash());
+ walletService.TransactionProcessor.Process(tx);
+ }
+ }
+ }
+
+ public void Process(SmartTransaction transaction)
+ {
+ lock (Lock)
+ {
+ foreach (var wallet in AliveWalletsNoLock)
+ {
+ wallet.Key.TransactionProcessor.Process(transaction);
+ }
+ }
+ }
+
+ public IEnumerable<SmartCoin> CoinsByOutPoint(OutPoint input)
+ {
+ lock (Lock)
+ {
+ var res = new List<SmartCoin>();
+ foreach (var walletService in AliveWalletsNoLock)
+ {
+ SmartCoin coin = walletService.Key.Coins.GetByOutPoint(input);
+ res.Add(coin);
+ }
+
+ return res;
+ }
+ }
+
+ public ISet<uint256> FilterUnknownCoinjoins(IEnumerable<uint256> cjs)
+ {
+ lock (Lock)
+ {
+ var unknowns = new HashSet<uint256>();
+ foreach (var pair in AliveWalletsNoLock)
+ {
+ // If a wallet service doesn't know about the tx, then we add it for processing.
+ foreach (var tx in cjs.Where(x => !pair.Value.Contains(x)))
+ {
+ unknowns.Add(tx);
+ }
+ }
+ return unknowns;
+ }
+ }
+ }
+}

Implement WalletManager