From edbb47fd2a221866f3de654bfb4a98415058654b Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 19 Oct 2023 14:41:13 +0200 Subject: [PATCH] native nostr coinjoins --- .../BTCPayCoinjoinCoinSelector.cs | 2 +- .../Coordinator/WabisabiCoordinatorService.cs | 24 ++- .../NostrWabiSabiApiClient.cs | 75 ++++++- .../NostrWabisabiApiServer.cs | 59 ++++-- .../UpdateWabisabiSettings.cshtml | 27 ++- .../WabisabiCoordinatorClientInstance.cs | 185 +++++++++++++----- submodules/btcpayserver | 2 +- submodules/walletwasabi | 2 +- 8 files changed, 297 insertions(+), 79 deletions(-) diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs index c2b7045..2bf3855 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs @@ -206,7 +206,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector //if we're less than the max output registration, we should be more aggressive in adding coins var chance = consolidationMode ? (isLessThanMaxOutputRegistration? 100: 90 ): 100m - Math.Min(maxCoinCapacityPercentage, isLessThanMaxOutputRegistration ? 10m : maxCoinCapacityPercentage); _logger.LogDebug( - $"coin selection: no payms left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} <= {rand} (random 0-100) "); + $"coin selection: no payms left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} <= {rand} (random 0-100) {chance <= rand} "); if (chance <= rand) { break; diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs index 23ca820..f5cc3d6 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs @@ -93,10 +93,21 @@ public class WabisabiCoordinatorService : PeriodicRunner break; } } - else if (existing.Enabled && - _instanceManager.HostedServices.TryGetValue("local", out var instance)) + else if (existing.Enabled) { - instance.TermsConditions = wabisabiCoordinatorSettings.TermsConditions; + if (_instanceManager.HostedServices.TryGetValue("local", out var instance)) + { + instance.TermsConditions = wabisabiCoordinatorSettings.TermsConditions; + } + if(wabisabiCoordinatorSettings.Enabled && + (existing.NostrIdentity != wabisabiCoordinatorSettings.NostrIdentity || existing.NostrRelay != wabisabiCoordinatorSettings.NostrRelay)) + { + var nostr = HostedServices.Get(); + nostr.UpdateSettings(wabisabiCoordinatorSettings); + await nostr.StopAsync(CancellationToken.None); + await nostr.StartAsync(CancellationToken.None); + } + } @@ -202,8 +213,11 @@ public class WabisabiCoordinatorService : PeriodicRunner WabiSabiCoordinator = new WabiSabiCoordinator(coordinatorParameters, rpc, coinJoinIdStore, coinJoinScriptStore, _httpClientFactory); HostedServices.Register(() => WabiSabiCoordinator, "WabiSabi Coordinator"); - var settings = await GetSettings(); + var settings = await GetSettings(); + WabisabiApiServer = new NostrWabisabiApiServer(WabiSabiCoordinator.Arena, settings, _logger); + HostedServices.Register(() => WabisabiApiServer, "WabiSabi Coordinator Nostr"); + if (settings.Enabled) { _ = StartCoordinator(cancellationToken); @@ -218,6 +232,8 @@ public class WabisabiCoordinatorService : PeriodicRunner await base.StartAsync(cancellationToken); } + public NostrWabisabiApiServer WabisabiApiServer { get; set; } + public async Task StartCoordinator(CancellationToken cancellationToken) { _logger.LogInformation("Starting local coordinator"); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs index c148359..0cb867b 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs @@ -1,6 +1,8 @@ using System; using System.Linq; +using System.Net; using System.Net.Http; +using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -10,6 +12,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NNostr.Client; using WalletWasabi.Logging; +using WalletWasabi.Tor.Socks5.Pool.Circuits; using WalletWasabi.WabiSabi; using WalletWasabi.WabiSabi.Backend.Models; using WalletWasabi.WabiSabi.Backend.PostRequests; @@ -18,36 +21,65 @@ using WalletWasabi.WabiSabi.Models.Serialization; namespace BTCPayServer.Plugins.Wabisabi; -public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService +public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService, IDisposable { public static int RoundStateKind = 15750; public static int CommunicationKind = 25750; - private readonly NostrClient _client; + private NostrClient _client; + private readonly Uri _relay; + private readonly WebProxy _webProxy; private readonly ECXOnlyPubKey _coordinatorKey; + private readonly INamedCircuit _circuit; private string _coordinatorKeyHex => _coordinatorKey.ToHex(); private readonly string _coordinatorFilterId; - public NostrWabiSabiApiClient(NostrClient client, ECXOnlyPubKey coordinatorKey) + public NostrWabiSabiApiClient(Uri relay, WebProxy webProxy , ECXOnlyPubKey coordinatorKey, INamedCircuit? circuit) { - _client = client; + _relay = relay; + _webProxy = webProxy; _coordinatorKey = coordinatorKey; + _circuit = circuit; _coordinatorFilterId = new Guid().ToString(); } public async Task StartAsync(CancellationToken cancellationToken) { + if (!_circuit.IsActive) + { + Dispose(); + } + + if (_circuit is OneOffCircuit) + { + return; + // we dont bootstrap, we do it on demand for a request instead + } + + await Init(cancellationToken, _circuit); + } + + private async Task Init(CancellationToken cancellationToken, INamedCircuit circuit) + { + _client = CreateClient(_relay, _webProxy, circuit); + _ = _client.ListenForMessages(); var filter = new NostrSubscriptionFilter() { Authors = new[] {_coordinatorKey.ToHex()}, Kinds = new[] {RoundStateKind, CommunicationKind}, - Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)) + Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)), + Limit = 0 }; await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken); _client.EventsReceived += EventsReceived; await _client.ConnectAndWaitUntilConnected(cancellationToken); + _circuit.IsolationIdChanged += (_, _) => + { + _client.Dispose(); + _ = StartAsync(CancellationToken.None); + }; } private RoundStateResponse _lastRoundState { get; set; } @@ -77,6 +109,16 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService private async Task SendAndWaitForReply(RemoteAction action, TRequest request, CancellationToken cancellationToken) { + + if(_circuit is OneOffCircuit) + { + using var subClient = + new NostrWabiSabiApiClient(_relay, _webProxy, _coordinatorKey, new PersonCircuit()); + await subClient.StartAsync(cancellationToken); + return await subClient.SendAndWaitForReply(action, request, cancellationToken); + } + + var newKey = ECPrivKey.Create(RandomUtils.GetBytes(32)); var pubkey = newKey.CreateXOnlyPubKey(); var evt = new NostrEvent() @@ -164,14 +206,14 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService catch (OperationCanceledException e) { _client.EventsReceived -= OnClientEventsReceived; + _circuit.IncrementIsolationId(); throw; } } public async Task StopAsync(CancellationToken cancellationToken) { - await _client.CloseSubscription(_coordinatorFilterId, cancellationToken); - _client.EventsReceived -= EventsReceived; + Dispose(); } public async Task GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken) @@ -237,4 +279,23 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService GetStatus, ReadyToSign } + + public void Dispose() + { + _client?.Dispose(); + } + + public static NostrClient CreateClient(Uri relay, WebProxy webProxy, INamedCircuit namedCircuit ) + { + return new NostrClient(relay, socket => + { + if (socket is ClientWebSocket clientWebSocket && webProxy is { }) + { + var proxy = new WebProxy(webProxy.Address, true, null, + new NetworkCredential(namedCircuit.Name, + namedCircuit.IsolationId.ToString())); + clientWebSocket.Options.Proxy = proxy; + } + }); + } } \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs index 52afb23..c54534b 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs @@ -5,12 +5,14 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using NBitcoin; using NBitcoin.Secp256k1; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NNostr.Client; using WabiSabi.Crypto; +using WalletWasabi.Backend.Controllers; using WalletWasabi.Logging; using WalletWasabi.WabiSabi; using WalletWasabi.WabiSabi.Backend.Models; @@ -26,17 +28,18 @@ public class NostrWabisabiApiServer: IHostedService public static int RoundStateKind = 15750; public static int CommunicationKind = 25750; private readonly Arena _arena; - private readonly NostrClient _client; - private readonly ECPrivKey _coordinatorKey; - private string _coordinatorKeyHex => _coordinatorKey.CreateXOnlyPubKey().ToHex(); + private WabisabiCoordinatorSettings _coordinatorSettings; + private NostrClient _client; + private readonly ILogger _logger; private readonly string _coordinatorFilterId; private Channel PendingEvents { get; } = Channel.CreateUnbounded(); - public NostrWabisabiApiServer(Arena arena,NostrClient client, ECPrivKey coordinatorKey) + public NostrWabisabiApiServer(Arena arena, WabisabiCoordinatorSettings coordinatorSettings, + ILogger logger) { _arena = arena; - _client = client; - _coordinatorKey = coordinatorKey; + _coordinatorSettings = coordinatorSettings; + _logger = logger; _coordinatorFilterId = new Guid().ToString(); _serializer = JsonSerializer.Create(JsonSerializationOptions.Default.Settings); } @@ -44,12 +47,20 @@ public class NostrWabisabiApiServer: IHostedService public async Task StartAsync(CancellationToken cancellationToken) { - _ = _client.ListenForMessages(); + if (_coordinatorSettings.NostrRelay is null || string.IsNullOrEmpty(_coordinatorSettings.NostrIdentity)) + { + _logger.LogInformation("NOSTR SERVER: No Nostr relay/identity configured, skipping"); + return; + } + + _client = new NostrClient(_coordinatorSettings.NostrRelay); + await _client.Connect(cancellationToken); + _logger.LogInformation($"NOSTR SERVER: CONNECTED TO {_coordinatorSettings.NostrRelay}"); var filter = new NostrSubscriptionFilter() { - ReferencedPublicKeys = new[] {_coordinatorKey.ToHex()}, + ReferencedPublicKeys = new[] {_coordinatorSettings.GetPubKey().ToHex()}, Kinds = new[] { CommunicationKind}, - Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)) + Limit = 0 }; await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken); _client.EventsReceived += EventsReceived; @@ -65,7 +76,7 @@ public class NostrWabisabiApiServer: IHostedService if (e.subscriptionId != _coordinatorFilterId) return; var requests = e.events.Where(evt => evt.Kind == CommunicationKind && - evt.GetTaggedData("p").Any(s => s == _coordinatorKeyHex) && evt.Verify()); + evt.GetTaggedData("p").Any(s => s == _coordinatorSettings.GetPubKey().ToHex()) && evt.Verify()); foreach (var request in requests) PendingEvents.Writer.TryWrite(request); } @@ -79,11 +90,12 @@ public class NostrWabisabiApiServer: IHostedService var nostrEvent = new NostrEvent() { Kind = RoundStateKind, - PublicKey = _coordinatorKeyHex, + PublicKey = _coordinatorSettings.GetPubKey().ToHex(), CreatedAt = DateTimeOffset.Now, Content = Serialize(response) }; await _client.PublishEvent(nostrEvent, cancellationToken); + _logger.LogInformation($"NOSTR SERVER: PUBLISHED ROUND STATE {nostrEvent.Id}"); await Task.Delay(1000, cancellationToken); } } @@ -95,7 +107,7 @@ public class NostrWabisabiApiServer: IHostedService PendingEvents.Reader.TryRead(out var evt)) { evt.Kind = 4; - var content = JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorKey)); + var content = JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorSettings.GetKey())); if (content.TryGetValue("action", out var actionJson) && actionJson.Value(actionJson) is { } actionString && Enum.TryParse(actionString, out var action) && @@ -103,6 +115,7 @@ public class NostrWabisabiApiServer: IHostedService { try { + _logger.LogInformation($"NOSTR SERVER: Received request {evt.Id} {action}"); switch (action) { case RemoteAction.GetStatus: @@ -191,26 +204,33 @@ public class NostrWabisabiApiServer: IHostedService private async Task Reply(NostrEvent originaltEvent,TResponse response, CancellationToken cancellationToken) { + + _logger.LogInformation($"NOSTR SERVER: REPLYING TO {originaltEvent.Id} WITH {response}"); var evt = new NostrEvent() { Content = Serialize(response), - PublicKey = _coordinatorKeyHex, + PublicKey = _coordinatorSettings.GetPubKey().ToHex(), Kind = 4, CreatedAt = DateTimeOffset.Now }; evt.SetTag("p", originaltEvent.PublicKey); evt.SetTag("e", originaltEvent.Id); - await evt.EncryptNip04EventAsync(_coordinatorKey); + await evt.EncryptNip04EventAsync(_coordinatorSettings.GetKey()); evt.Kind = CommunicationKind; - await evt.ComputeIdAndSignAsync(_coordinatorKey); + await evt.ComputeIdAndSignAsync(_coordinatorSettings.GetKey()); await _client.PublishEvent(evt, cancellationToken); } public async Task StopAsync(CancellationToken cancellationToken) { - await _client.CloseSubscription(_coordinatorFilterId, cancellationToken); - _client.EventsReceived -= EventsReceived; + if (_client is not null) + { + + await _client.CloseSubscription(_coordinatorFilterId, cancellationToken); + _client.EventsReceived -= EventsReceived; + _client = null; + } } private static string Serialize(T obj) @@ -226,4 +246,9 @@ public class NostrWabisabiApiServer: IHostedService GetStatus, ReadyToSign } + + public void UpdateSettings(WabisabiCoordinatorSettings wabisabiCoordinatorSettings) + { + _coordinatorSettings = wabisabiCoordinatorSettings; + } } \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml index 655187e..2203d5d 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml @@ -1,7 +1,10 @@  @using BTCPayServer.Plugins.Wabisabi +@using NNostr.Client +@using NNostr.Client.Protocols +@using WalletWasabi.Backend.Controllers @model WalletWasabi.Backend.Controllers.WabisabiCoordinatorSettings - +@inject WabisabiCoordinatorService WabisabiCoordinatorService @{ Layout = "../Shared/_NavLayout.cshtml"; ViewData["NavPartialName"] = "../UIServer/_Nav"; @@ -59,6 +62,28 @@ + @if(WabisabiCoordinatorService.Started && WabisabiCoordinatorService.WabisabiApiServer != null && Model.NostrRelay is not null && Model.NostrIdentity is not null) + { + var nprofile = new NIP19.NosteProfileNote() + { + PubKey = Model.GetPubKey().ToHex(), + Relays = new[] {Model.NostrRelay.ToString()} + }.ToNIP19(); + +
+ +
+ + + +
+

+
+ }
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs index e88f850..18e954c 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; @@ -7,10 +8,13 @@ using System.Threading; using System.Threading.Tasks; using BTCPayServer.Payments.PayJoin; using BTCPayServer.Services; +using ExchangeSharp; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using NNostr.Client; +using NNostr.Client.Protocols; using WalletWasabi.Backend.Controllers; using WalletWasabi.Tor.Socks5.Pool.Circuits; using WalletWasabi.Userfacing; @@ -21,6 +25,7 @@ using WalletWasabi.WabiSabi.Client.RoundStateAwaiters; using WalletWasabi.WabiSabi.Client.StatusChangedEvents; using WalletWasabi.Wallets; using WalletWasabi.WebClients.Wasabi; +using ClientWebSocket = System.Net.WebSockets.ClientWebSocket; namespace BTCPayServer.Plugins.Wabisabi; @@ -105,7 +110,7 @@ public class WabisabiCoordinatorClientInstanceManager:IHostedService var instance = new WabisabiCoordinatorClientInstance( displayName, name, url is null? null: new Uri(url), _provider.GetService(), _provider, UTXOLocker, - _provider.GetService(), termsConditions, description); + _provider.GetService(), termsConditions, description,_provider.GetRequiredService()); if (HostedServices.TryAdd(instance.CoordinatorName, instance)) { if(started) @@ -127,7 +132,82 @@ public class WabisabiCoordinatorClientInstanceManager:IHostedService } } -public class WabisabiCoordinatorClientInstance +public class NostrWabisabiClientFactory: IWasabiHttpClientFactory, IHostedService +{ + private readonly Socks5HttpClientHandler _socks5HttpClientHandler; + private readonly NIP19.NosteProfileNote _nostrProfileNote; + + public NostrWabisabiClientFactory(Socks5HttpClientHandler socks5HttpClientHandler, + NIP19.NosteProfileNote nostrProfileNote) + { + _socks5HttpClientHandler = socks5HttpClientHandler; + _nostrProfileNote = nostrProfileNote; + } + + private ConcurrentDictionary _clients = new(); + + private bool _started = false; + + public async Task StartAsync(CancellationToken cancellationToken) + { + await Task.WhenAll(_clients.Select(pair => pair.Value.StartAsync(cancellationToken))); + + _started = true; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + foreach (var nostrWabiSabiApiClient in _clients) + { + nostrWabiSabiApiClient.Value.Dispose(); + } + _clients.Clear(); + _started = false; + return Task.CompletedTask; + } + + public IWabiSabiApiRequestHandler NewWabiSabiApiRequestHandler(Mode mode, ICircuit circuit = null) + { + if (mode == Mode.DefaultCircuit || _socks5HttpClientHandler.Proxy is null) + { + circuit = DefaultCircuit.Instance; + } + + if (mode == Mode.NewCircuitPerRequest) + { + circuit = new OneOffCircuit(); + } + + if (circuit is not INamedCircuit namedCircuit) + throw new ArgumentException("circuit must be a INamedCircuit"); + var result = _clients.GetOrAdd(namedCircuit.Name, name => + { + var result = new NostrWabiSabiApiClient(new Uri(_nostrProfileNote.Relays.First()), + _socks5HttpClientHandler.Proxy as WebProxy, NostrExtensions.ParsePubKey(_nostrProfileNote.PubKey), + namedCircuit); + return result; + }); + return result; + } +} + + + +public class LocalWabisabiClientFactory: IWasabiHttpClientFactory +{ + private readonly WabiSabiController _wabiSabiController; + + public LocalWabisabiClientFactory(WabiSabiController wabiSabiController) + { + _wabiSabiController = wabiSabiController; + } + public IWabiSabiApiRequestHandler NewWabiSabiApiRequestHandler(Mode mode, ICircuit circuit = null) + { + return _wabiSabiController; + } +} + +public class WabisabiCoordinatorClientInstance:IHostedService { private readonly IUTXOLocker _utxoLocker; private readonly ILogger _logger; @@ -136,12 +216,13 @@ public class WabisabiCoordinatorClientInstance public Uri Coordinator { get; set; } public WalletProvider WalletProvider { get; } public string TermsConditions { get; set; } - public WasabiHttpClientFactory WasabiHttpClientFactory { get; set; } + public IWasabiHttpClientFactory WasabiHttpClientFactory { get; set; } public RoundStateUpdater RoundStateUpdater { get; set; } public CoinPrison CoinPrison { get; private set; } public WasabiCoordinatorStatusFetcher WasabiCoordinatorStatusFetcher { get; set; } public CoinJoinManager CoinJoinManager { get; set; } public string Description { get; set; } + private readonly WalletWasabi.Services.HostedServices _hostedServices = new(); public WabisabiCoordinatorClientInstance(string coordinatorDisplayName, string coordinatorName, @@ -149,17 +230,20 @@ public class WabisabiCoordinatorClientInstance ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IUTXOLocker utxoLocker, - WalletProvider walletProvider, string termsConditions, string description,string coordinatorIdentifier = "CoinJoinCoordinatorIdentifier") + WalletProvider walletProvider, string termsConditions, string description, + Socks5HttpClientHandler socks5HttpClientHandler, string coordinatorIdentifier = "CoinJoinCoordinatorIdentifier" + ) { - + _utxoLocker = utxoLocker; var config = serviceProvider.GetService(); var socksEndpoint = config.GetValue("socksendpoint"); - EndPointParser.TryParse(socksEndpoint,9050, out var torEndpoint); + EndPointParser.TryParse(socksEndpoint, 9050, out var torEndpoint); if (torEndpoint is not null && torEndpoint is DnsEndPoint dnsEndPoint) { torEndpoint = new IPEndPoint(Dns.GetHostAddresses(dnsEndPoint.Host).First(), dnsEndPoint.Port); } + CoordinatorDisplayName = coordinatorDisplayName; CoordinatorName = coordinatorName; Coordinator = coordinator; @@ -167,53 +251,63 @@ public class WabisabiCoordinatorClientInstance TermsConditions = termsConditions; Description = description; _logger = loggerFactory.CreateLogger(coordinatorName); - IWabiSabiApiRequestHandler sharedWabisabiClient; + IWabiSabiApiRequestHandler sharedWabisabiClient = null; + + var roundStateUpdaterCircuit = new PersonCircuit(); + if (coordinatorName == "local") { - sharedWabisabiClient = serviceProvider.GetRequiredService(); - + WasabiHttpClientFactory = new LocalWabisabiClientFactory( serviceProvider.GetRequiredService()); + + } + else if (coordinator.Scheme == "nostr" && + coordinator.Host.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote) + { + var factory = new NostrWabisabiClientFactory(socks5HttpClientHandler, nostrProfileNote); + WasabiHttpClientFactory = factory; + _hostedServices.Register(() => factory, "NostrWabisabiClientFactory"); } else { WasabiHttpClientFactory = new WasabiHttpClientFactory(torEndpoint, () => Coordinator); - var roundStateUpdaterCircuit = new PersonCircuit(); - var roundStateUpdaterHttpClient = - WasabiHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, roundStateUpdaterCircuit); - if (termsConditions is null) - { - _ = new WasabiClient(roundStateUpdaterHttpClient) - .GetLegalDocumentsAsync(CancellationToken.None) - .ContinueWith(task => + + + } + + sharedWabisabiClient = + WasabiHttpClientFactory.NewWabiSabiApiRequestHandler(Mode.SingleCircuitPerLifetime, + roundStateUpdaterCircuit); + + if (termsConditions is null && sharedWabisabiClient is WabiSabiHttpApiClient wabiSabiHttpApiClient) + { + + _ = wabiSabiHttpApiClient.GetLegalDocumentsAsync(CancellationToken.None) + .ContinueWith(task => + { + if (task.Status == TaskStatus.RanToCompletion) { - if (task.Status == TaskStatus.RanToCompletion) - { - TermsConditions = task.Result; - } - }); - } - sharedWabisabiClient = new WabiSabiHttpApiClient(roundStateUpdaterHttpClient); - + TermsConditions = task.Result; + } + }); } - + WasabiCoordinatorStatusFetcher = new WasabiCoordinatorStatusFetcher(sharedWabisabiClient, _logger); - - RoundStateUpdater = new RoundStateUpdater(TimeSpan.FromSeconds(5),sharedWabisabiClient, WasabiCoordinatorStatusFetcher); - CoinPrison = SettingsCoinPrison.CreateFromCoordinatorName(serviceProvider.GetRequiredService(), + RoundStateUpdater = + new RoundStateUpdater(TimeSpan.FromSeconds(5), sharedWabisabiClient, WasabiCoordinatorStatusFetcher); + + CoinPrison = SettingsCoinPrison.CreateFromCoordinatorName( + serviceProvider.GetRequiredService(), CoordinatorName).GetAwaiter().GetResult(); - if (coordinatorName == "local") - { - CoinJoinManager = new CoinJoinManager(coordinatorName, WalletProvider, RoundStateUpdater, - sharedWabisabiClient, null, - WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison); - } - else - { - CoinJoinManager = new CoinJoinManager(coordinatorName,WalletProvider, RoundStateUpdater,null, WasabiHttpClientFactory, - WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison); - } - + + CoinJoinManager = new CoinJoinManager(coordinatorName, WalletProvider, RoundStateUpdater, + WasabiHttpClientFactory, + WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison); CoinJoinManager.StatusChanged += OnStatusChanged; + + _hostedServices.Register(() => RoundStateUpdater, "RoundStateUpdater"); + _hostedServices.Register(() => WasabiCoordinatorStatusFetcher, "WasabiCoordinatorStatusFetcher"); + _hostedServices.Register(() => CoinJoinManager, "WasabiCoordinatorStatusFetcher"); } public async Task StopWallet(IWallet wallet) @@ -274,17 +368,14 @@ public class WabisabiCoordinatorClientInstance public Task StartAsync(CancellationToken cancellationToken) { - RoundStateUpdater.StartAsync(cancellationToken); - WasabiCoordinatorStatusFetcher.StartAsync(cancellationToken); - CoinJoinManager.StartAsync(cancellationToken); + _ = _hostedServices.StartAllAsync(cancellationToken); + return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { - RoundStateUpdater.StopAsync(cancellationToken); - WasabiCoordinatorStatusFetcher.StopAsync(cancellationToken); - CoinJoinManager.StopAsync(cancellationToken); + _ = _hostedServices.StopAllAsync(cancellationToken); return Task.CompletedTask; } } diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 25af9c4..a921504 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 25af9c42276740fe7e92985eeb691e5635955689 +Subproject commit a921504bcf619c5e845813b8f994b39147694a97 diff --git a/submodules/walletwasabi b/submodules/walletwasabi index e3b22a8..39b3c00 160000 --- a/submodules/walletwasabi +++ b/submodules/walletwasabi @@ -1 +1 @@ -Subproject commit e3b22a8b0e48595bc8153aada5df261fc2a5bc7b +Subproject commit 39b3c00afdd0665828d7054da8b4c85e69175bee