diff --git a/BTCPayServerPlugins.sln.DotSettings.user b/BTCPayServerPlugins.sln.DotSettings.user index 0cc0f59..053c8d0 100644 --- a/BTCPayServerPlugins.sln.DotSettings.user +++ b/BTCPayServerPlugins.sln.DotSettings.user @@ -15,4 +15,33 @@ </SessionState> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj b/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj index 0692222..01e9c73 100644 --- a/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj +++ b/Plugins/BTCPayServer.Plugins.NIP05/BTCPayServer.Plugins.NIP05.csproj @@ -11,7 +11,7 @@ Nostr NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support - 1.1.6 + 1.1.7 true @@ -36,7 +36,7 @@ - + diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj index 50a3acd..f006d43 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj @@ -13,7 +13,7 @@ Coinjoin Allows you to integrate your btcpayserver store with coinjoins. - 1.0.81 + 1.0.82 true @@ -44,7 +44,7 @@ - + diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs index e1eed77..869787c 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs @@ -268,6 +268,7 @@ public class WabisabiCoordinatorService : PeriodicRunner var network = _clientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork; var s = await GetSettings(); + Uri uri = s.UriToAdvertise; if (s.Enabled && !string.IsNullOrEmpty(s.NostrIdentity) && s.NostrRelay is not null && s.UriToAdvertise is not null) { @@ -276,29 +277,44 @@ public class WabisabiCoordinatorService : PeriodicRunner if(s.UriToAdvertise.Scheme.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) { //make sure the end is with plugins/wabisabi-coordinator/ - var uri = new UriBuilder(s.UriToAdvertise); - - uri.Path = uri.Path.Replace("plugins/wabisabi-coordinator/", "").TrimEnd('/') + "/plugins/wabisabi-coordinator/"; + var uriB = new UriBuilder(uri); + uriB.Path = uriB.Path.Replace("plugins/wabisabi-coordinator", "").TrimEnd('/') + "/plugins/wabisabi-coordinator/"; + uri= uriB.Uri; } + //verify the url IWasabiHttpClientFactory factory = null; - if (s.UriToAdvertise.Scheme == "nostr" && - s.UriToAdvertise.Host.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote) + if (uri.Scheme == "nostr" && + uri.AbsolutePath.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote) { factory = new NostrWabisabiClientFactory(null, nostrProfileNote); } else { - factory = new WasabiHttpClientFactory(null, () => s.UriToAdvertise); + factory = new WasabiHttpClientFactory(null, () => uri); } try { - var handler = factory.NewWabiSabiApiRequestHandler(Mode.SingleCircuitPerLifetime); - var resp = await handler.GetStatusAsync(RoundStateRequest.Empty, cancel); + if (factory is IHostedService hs) + { + await hs.StartAsync(cancel); + } + var handler = factory.NewWabiSabiApiRequestHandler(Mode.DefaultCircuit); + + var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel); + cts.CancelAfter(TimeSpan.FromSeconds(10)); + var resp = await handler.GetStatusAsync(RoundStateRequest.Empty, cts.Token); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not connect to the coordinator at {0}", uri); + await Task.Delay(TimeSpan.FromMinutes(1), cancel); + TriggerRound(); + return; } finally { @@ -309,13 +325,13 @@ public class WabisabiCoordinatorService : PeriodicRunner } - _logger.LogInformation("Publishing coordinator discovery event with url {0}", s.UriToAdvertise); + _logger.LogInformation("Publishing coordinator discovery event with url {0}", uri); await Nostr.Publish(s.NostrRelay, new[] { - await Nostr.CreateCoordinatorDiscoveryEvent(network, k, s.UriToAdvertise, + await Nostr.CreateCoordinatorDiscoveryEvent(network, k, uri, s.CoordinatorDescription) },s.UriToAdvertise.IsOnion()? _socks5HttpClientHandler: null, cancel); } diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs index 0cb867b..0fc5213 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabiSabiApiClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -31,7 +32,7 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService private readonly ECXOnlyPubKey _coordinatorKey; private readonly INamedCircuit _circuit; private string _coordinatorKeyHex => _coordinatorKey.ToHex(); - private readonly string _coordinatorFilterId; + // private readonly string _coordinatorFilterId; public NostrWabiSabiApiClient(Uri relay, WebProxy webProxy , ECXOnlyPubKey coordinatorKey, INamedCircuit? circuit) { @@ -39,66 +40,79 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService _webProxy = webProxy; _coordinatorKey = coordinatorKey; _circuit = circuit; - _coordinatorFilterId = new Guid().ToString(); + // _coordinatorFilterId = new Guid().ToString(); } + private CancellationTokenSource _cts; public async Task StartAsync(CancellationToken cancellationToken) { if (!_circuit.IsActive) { Dispose(); } - + _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); if (_circuit is OneOffCircuit) { return; // we dont bootstrap, we do it on demand for a request instead } - await Init(cancellationToken, _circuit); + _ = Init(_cts.Token, _circuit); } + private async Task Init(CancellationToken cancellationToken, INamedCircuit circuit) { - _client = CreateClient(_relay, _webProxy, circuit); - - _ = _client.ListenForMessages(); - var filter = new NostrSubscriptionFilter() + while (cancellationToken.IsCancellationRequested == false) { - Authors = new[] {_coordinatorKey.ToHex()}, - Kinds = new[] {RoundStateKind, CommunicationKind}, - Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)), - Limit = 0 - }; - await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken); - _client.EventsReceived += EventsReceived; + _client = CreateClient(_relay, _webProxy, circuit); - await _client.ConnectAndWaitUntilConnected(cancellationToken); - _circuit.IsolationIdChanged += (_, _) => - { - _client.Dispose(); - _ = StartAsync(CancellationToken.None); - }; - } + await _client.ConnectAndWaitUntilConnected(cancellationToken); - private RoundStateResponse _lastRoundState { get; set; } - private TaskCompletionSource _lastRoundStateTask = new(); - - - private void EventsReceived(object sender, (string subscriptionId, NostrEvent[] events) e) - { - if (e.subscriptionId == _coordinatorFilterId) - { - var roundState = e.events.Where(evt => evt.Kind == RoundStateKind).MaxBy(@event => @event.CreatedAt); - if (roundState != null) + _circuit.IsolationIdChanged += (_, _) => { - _lastRoundState = Deserialize(roundState.Content); - _lastRoundStateTask.TrySetResult(); - } + Dispose(); + _ = StartAsync(CancellationToken.None); + }; + + + var subscriptions = _client.SubscribeForEvents(new[] + { + new NostrSubscriptionFilter() + { + Authors = new[] {_coordinatorKey.ToHex()}, + Kinds = new[] {RoundStateKind}, + Limit = 1 + } + }, false, cancellationToken); + + await HandleStateEvents(subscriptions); + } } + private async Task HandleStateEvents(IAsyncEnumerable subscriptions) + { + await foreach (var evt in subscriptions) + { + if (evt.Kind != RoundStateKind) + continue; + if (_lastRoundStateEvent is not null && evt.CreatedAt <= _lastRoundStateEvent.CreatedAt) + continue; + _lastRoundStateEvent = evt; + + _lastRoundState = Deserialize(evt.Content); + _lastRoundStateTask.TrySetResult(); + + } + } + + private NostrEvent _lastRoundStateEvent { get; set; } + private RoundStateResponse _lastRoundState { get; set; } + private readonly TaskCompletionSource _lastRoundStateTask = new(); + + private async Task SendAndWaitForReply(RemoteAction action, TRequest request, CancellationToken cancellationToken) { @@ -129,37 +143,19 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService Request = request }), PublicKey = pubkey.ToHex(), - Kind = 4, + Kind = CommunicationKind, CreatedAt = DateTimeOffset.Now }; evt.SetTag("p", _coordinatorKeyHex); - await evt.EncryptNip04EventAsync(newKey); - evt.Kind = CommunicationKind; - await evt.ComputeIdAndSignAsync(newKey); - var tcs = new TaskCompletionSource(cancellationToken); - - void OnClientEventsReceived(object sender, (string subscriptionId, NostrEvent[] events) e) - { - foreach (var nostrEvent in e.events) - { - if (nostrEvent.PublicKey != _coordinatorKeyHex) continue; - var replyToEvent = evt.GetTaggedData("e"); - var replyToUser = evt.GetTaggedData("p"); - if (replyToEvent.All(s => s != evt.Id) || replyToUser.All(s => s != evt.PublicKey)) continue; - if (!nostrEvent.Verify()) continue; - _client.EventsReceived -= OnClientEventsReceived; - tcs.TrySetResult(nostrEvent); - break; - } - } - - _client.EventsReceived += OnClientEventsReceived; + await evt.EncryptNip04EventAsync(newKey, null, true); + evt = await evt.ComputeIdAndSignAsync(newKey, false); + try { - var replyEvent = await tcs.Task; - replyEvent.Kind = 4; - var response = await replyEvent.DecryptNip04EventAsync(newKey); + + var replyEvent = await _client.SendEventAndWaitForReply(evt, cancellationToken); + var response = await replyEvent.DecryptNip04EventAsync(newKey, null, true); var jobj = JObject.Parse(response); if (jobj.TryGetValue("error", out var errorJson)) { @@ -205,7 +201,6 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService } catch (OperationCanceledException e) { - _client.EventsReceived -= OnClientEventsReceived; _circuit.IncrementIsolationId(); throw; } @@ -283,6 +278,10 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService public void Dispose() { _client?.Dispose(); + _cts?.Cancel(); + _client = null; + _cts = null; + } public static NostrClient CreateClient(Uri relay, WebProxy webProxy, INamedCircuit namedCircuit ) diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs index 7c3d7e3..0871a7b 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/NostrWabisabiApiServer.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -20,20 +22,21 @@ using WalletWasabi.WabiSabi.Backend.Rounds; using WalletWasabi.WabiSabi.Crypto; using WalletWasabi.WabiSabi.Models; using WalletWasabi.WabiSabi.Models.Serialization; +using JsonSerializer = Newtonsoft.Json.JsonSerializer; namespace BTCPayServer.Plugins.Wabisabi; -public class NostrWabisabiApiServer: IHostedService +public class NostrWabisabiApiServer : IHostedService { public static int RoundStateKind = 15750; public static int CommunicationKind = 25750; private readonly Arena _arena; private WabisabiCoordinatorSettings _coordinatorSettings; - private NostrClient _client; + private NostrClient _client; private readonly ILogger _logger; private readonly string _coordinatorFilterId; - private Channel PendingEvents { get; } = Channel.CreateUnbounded(); + // private Channel PendingEvents { get; } = Channel.CreateUnbounded(); public NostrWabisabiApiServer(Arena arena, WabisabiCoordinatorSettings coordinatorSettings, ILogger logger) { @@ -44,6 +47,7 @@ public class NostrWabisabiApiServer: IHostedService _serializer = JsonSerializer.Create(JsonSerializationOptions.Default.Settings); } + private CancellationTokenSource _cts; public async Task StartAsync(CancellationToken cancellationToken) { @@ -52,33 +56,26 @@ public class NostrWabisabiApiServer: IHostedService _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[] {_coordinatorSettings.GetPubKey().ToHex()}, - Kinds = new[] { CommunicationKind}, - Limit = 0 - }; - await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken); - _client.EventsReceived += EventsReceived; await _client.ConnectAndWaitUntilConnected(cancellationToken); - _ = RoutinelyUpdateRoundEvent(cancellationToken); - _ = ProcessRequests(cancellationToken); - - } + _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + _logger.LogInformation($"NOSTR SERVER: CONNECTED TO {_coordinatorSettings.NostrRelay}"); - private void EventsReceived(object sender, (string subscriptionId, NostrEvent[] events) e) - { - if (e.subscriptionId != _coordinatorFilterId) return; - var requests = e.events.Where(evt => - evt.Kind == CommunicationKind && - evt.GetTaggedData("p").Any(s => s == _coordinatorSettings.GetPubKey().ToHex()) && evt.Verify()); - foreach (var request in requests) - PendingEvents.Writer.TryWrite(request); + _evtSubscriptions = _client.SubscribeForEvents(new[] + { + new NostrSubscriptionFilter() + { + ReferencedPublicKeys = new[] {_coordinatorSettings.GetPubKey().ToHex()}, + Kinds = new[] {CommunicationKind}, + Limit = 0 + } + }, false, _cts.Token); + + + _ = RoutinelyUpdateRoundEvent(_cts.Token); + _ = ProcessRequests(_cts.Token); } @@ -86,155 +83,169 @@ public class NostrWabisabiApiServer: IHostedService { while (!cancellationToken.IsCancellationRequested) { - var response = await _arena.GetStatusAsync(RoundStateRequest.Empty, cancellationToken); + try + { + + var response = await _arena.GetStatusAsync(RoundStateRequest.Empty, cancellationToken); var nostrEvent = new NostrEvent() { Kind = RoundStateKind, - PublicKey = _coordinatorSettings.GetPubKey().ToHex(), - CreatedAt = DateTimeOffset.Now, - Content = Serialize(response) + Content =Serialize(response) }; - await _client.PublishEvent(nostrEvent, cancellationToken); + + + nostrEvent = await nostrEvent.ComputeIdAndSignAsync(_coordinatorSettings.GetKey()); + await _client.SendEventsAndWaitUntilReceived(new[] {nostrEvent}, cancellationToken); _logger.LogDebug($"NOSTR SERVER: PUBLISHED ROUND STATE {nostrEvent.Id}"); await Task.Delay(1000, cancellationToken); + + } + catch (Exception e) + { + Console.WriteLine(e); + } } } private async Task ProcessRequests(CancellationToken cancellationToken) { - while (!cancellationToken.IsCancellationRequested && - await PendingEvents.Reader.WaitToReadAsync(cancellationToken) && - PendingEvents.Reader.TryRead(out var evt)) + await foreach (var evt in _evtSubscriptions.WithCancellation(cancellationToken)) { - evt.Kind = 4; - 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) && - content.ContainsKey("request")) + try { - try + var content = + JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorSettings.GetKey(), null, true)); + if (content.TryGetValue("action", out var actionJson) && + actionJson.Value(actionJson) is { } actionString && + Enum.TryParse(actionString, out var action) && + content.ContainsKey("request")) { - _logger.LogDebug($"NOSTR SERVER: Received request {evt.Id} {action}"); - switch (action) + try { - case RemoteAction.GetStatus: - // ignored as we use a dedicated public event for this to not spam - break; - case RemoteAction.RegisterInput: - var registerInputRequest = - content["request"].ToObject(_serializer); - var registerInputResponse = - await _arena.RegisterInputAsync(registerInputRequest, CancellationToken.None); - await Reply(evt, registerInputResponse, CancellationToken.None); - break; - case RemoteAction.RegisterOutput: - var registerOutputRequest = - content["request"].ToObject(_serializer); - await _arena.RegisterOutputAsync(registerOutputRequest, CancellationToken.None); - break; - case RemoteAction.RemoveInput: - var removeInputRequest = content["request"].ToObject(_serializer); - await _arena.RemoveInputAsync(removeInputRequest, CancellationToken.None); - break; - case RemoteAction.ConfirmConnection: - var connectionConfirmationRequest = - content["request"].ToObject(_serializer); - var connectionConfirmationResponse = - await _arena.ConfirmConnectionAsync(connectionConfirmationRequest, - CancellationToken.None); - await Reply(evt, connectionConfirmationResponse, CancellationToken.None); - break; - case RemoteAction.ReissueCredential: - var reissueCredentialRequest = - content["request"].ToObject(_serializer); - var reissueCredentialResponse = - await _arena.ReissuanceAsync(reissueCredentialRequest, CancellationToken.None); - await Reply(evt, reissueCredentialResponse, CancellationToken.None); - break; - case RemoteAction.SignTransaction: - var transactionSignaturesRequest = - content["request"].ToObject(_serializer); - await _arena.SignTransactionAsync(transactionSignaturesRequest, CancellationToken.None); - break; - case RemoteAction.ReadyToSign: - var readyToSignRequest = - content["request"].ToObject(_serializer); - await _arena.ReadyToSignAsync(readyToSignRequest, CancellationToken.None); - break; + _logger.LogDebug($"NOSTR SERVER: Received request {evt.Id} {action}"); + switch (action) + { + case RemoteAction.GetStatus: + // ignored as we use a dedicated public event for this to not spam + break; + case RemoteAction.RegisterInput: + var registerInputRequest = + content["request"].ToObject(_serializer); + var registerInputResponse = + await _arena.RegisterInputAsync(registerInputRequest, CancellationToken.None); + await Reply(evt, registerInputResponse, CancellationToken.None); + break; + case RemoteAction.RegisterOutput: + var registerOutputRequest = + content["request"].ToObject(_serializer); + await _arena.RegisterOutputAsync(registerOutputRequest, CancellationToken.None); + break; + case RemoteAction.RemoveInput: + var removeInputRequest = content["request"].ToObject(_serializer); + await _arena.RemoveInputAsync(removeInputRequest, CancellationToken.None); + break; + case RemoteAction.ConfirmConnection: + var connectionConfirmationRequest = + content["request"].ToObject(_serializer); + var connectionConfirmationResponse = + await _arena.ConfirmConnectionAsync(connectionConfirmationRequest, + CancellationToken.None); + await Reply(evt, connectionConfirmationResponse, CancellationToken.None); + break; + case RemoteAction.ReissueCredential: + var reissueCredentialRequest = + content["request"].ToObject(_serializer); + var reissueCredentialResponse = + await _arena.ReissuanceAsync(reissueCredentialRequest, CancellationToken.None); + await Reply(evt, reissueCredentialResponse, CancellationToken.None); + break; + case RemoteAction.SignTransaction: + var transactionSignaturesRequest = + content["request"].ToObject(_serializer); + await _arena.SignTransactionAsync(transactionSignaturesRequest, CancellationToken.None); + break; + case RemoteAction.ReadyToSign: + var readyToSignRequest = + content["request"].ToObject(_serializer); + await _arena.ReadyToSignAsync(readyToSignRequest, CancellationToken.None); + break; + } + } + catch (Exception ex) + { + object response = ex switch + { + WabiSabiProtocolException wabiSabiProtocolException => new + { + Error = new Error(Type: ProtocolConstants.ProtocolViolationType, + ErrorCode: wabiSabiProtocolException.ErrorCode.ToString(), + Description: wabiSabiProtocolException.Message, + ExceptionData: wabiSabiProtocolException.ExceptionData ?? + EmptyExceptionData.Instance) + }, + WabiSabiCryptoException wabiSabiCryptoException => new + { + Error = new Error(Type: ProtocolConstants.ProtocolViolationType, + ErrorCode: WabiSabiProtocolErrorCode.CryptoException.ToString(), + Description: wabiSabiCryptoException.Message, + ExceptionData: EmptyExceptionData.Instance) + }, + _ => new + { + Error = new Error(Type: "unknown", ErrorCode: ex.GetType().Name, + Description: ex.Message, ExceptionData: EmptyExceptionData.Instance) + } + }; + + await Reply(evt, response, CancellationToken.None); } } - catch (Exception ex) - { - object response = ex switch - { - WabiSabiProtocolException wabiSabiProtocolException => new - { - Error = new Error(Type: ProtocolConstants.ProtocolViolationType, - ErrorCode: wabiSabiProtocolException.ErrorCode.ToString(), - Description: wabiSabiProtocolException.Message, - ExceptionData: wabiSabiProtocolException.ExceptionData ?? - EmptyExceptionData.Instance) - }, - WabiSabiCryptoException wabiSabiCryptoException => new - { - Error = new Error(Type: ProtocolConstants.ProtocolViolationType, - ErrorCode: WabiSabiProtocolErrorCode.CryptoException.ToString(), - Description: wabiSabiCryptoException.Message, - ExceptionData: EmptyExceptionData.Instance) - }, - _ => new - { - Error = new Error(Type: "unknown", ErrorCode: ex.GetType().Name, - Description: ex.Message, ExceptionData: EmptyExceptionData.Instance) - } - }; - - await Reply(evt, response, CancellationToken.None); - } } - + catch (Exception e) + { + _logger.LogError(e, "NOSTR SERVER: Failed to process event {0}", evt.Id); + } } } private readonly JsonSerializer _serializer; + private IAsyncEnumerable _evtSubscriptions; - - private async Task Reply(NostrEvent originaltEvent,TResponse response, + private async Task Reply(NostrEvent originaltEvent, TResponse response, CancellationToken cancellationToken) { - _logger.LogDebug($"NOSTR SERVER: REPLYING TO {originaltEvent.Id} WITH {response}"); var evt = new NostrEvent() { Content = Serialize(response), PublicKey = _coordinatorSettings.GetPubKey().ToHex(), - Kind = 4, + Kind = CommunicationKind, CreatedAt = DateTimeOffset.Now }; evt.SetTag("p", originaltEvent.PublicKey); evt.SetTag("e", originaltEvent.Id); - await evt.EncryptNip04EventAsync(_coordinatorSettings.GetKey()); - evt.Kind = CommunicationKind; + await evt.EncryptNip04EventAsync(_coordinatorSettings.GetKey(), null, true); await evt.ComputeIdAndSignAsync(_coordinatorSettings.GetKey()); await _client.PublishEvent(evt, cancellationToken); } public async Task StopAsync(CancellationToken cancellationToken) { - if (_client is not null) - { - - await _client.CloseSubscription(_coordinatorFilterId, cancellationToken); - _client.EventsReceived -= EventsReceived; - _client = null; - } + if(_cts is not null) + await _cts?.CancelAsync(); + + _client?.Dispose(); } private static string Serialize(T obj) - => JsonConvert.SerializeObject(obj, JsonSerializationOptions.Default.Settings); + { + var raw = JsonConvert.SerializeObject(obj, JsonSerializationOptions.Default.Settings); + return raw; + + } + private enum RemoteAction { RegisterInput, diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs index f92e2e4..58336b3 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiCoordinatorClientInstance.cs @@ -194,7 +194,7 @@ public class NostrWabisabiClientFactory: IWasabiHttpClientFactory, IHostedServic public IWabiSabiApiRequestHandler NewWabiSabiApiRequestHandler(Mode mode, ICircuit circuit = null) { - if (mode == Mode.DefaultCircuit || _socks5HttpClientHandler.Proxy is null) + if (mode == Mode.DefaultCircuit || _socks5HttpClientHandler?.Proxy is null) { circuit = DefaultCircuit.Instance; } @@ -209,10 +209,16 @@ public class NostrWabisabiClientFactory: IWasabiHttpClientFactory, IHostedServic var result = _clients.GetOrAdd(namedCircuit.Name, name => { var result = new NostrWabiSabiApiClient(new Uri(_nostrProfileNote.Relays.First()), - _socks5HttpClientHandler.Proxy as WebProxy, NostrExtensions.ParsePubKey(_nostrProfileNote.PubKey), + _socks5HttpClientHandler?.Proxy as WebProxy, NostrExtensions.ParsePubKey(_nostrProfileNote.PubKey), namedCircuit); + if (_started) + { + + result.StartAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); + } return result; }); + return result; } } @@ -287,7 +293,7 @@ public class WabisabiCoordinatorClientInstance:IHostedService } else if (coordinator.Scheme == "nostr" && - coordinator.Host.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote) + coordinator.AbsolutePath.TrimEnd('/').FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote) { var factory = new NostrWabisabiClientFactory(socks5HttpClientHandler, nostrProfileNote); WasabiHttpClientFactory = factory; diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs index e8e9e01..e34d7ef 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs @@ -108,35 +108,37 @@ namespace BTCPayServer.Plugins.Wabisabi vm.FeeRateMedianTimeFrameHours = Math.Max(0, vm.FeeRateMedianTimeFrameHours); ModelState.Clear(); + var coordx = string.Join(string.Empty,pieces.Skip(1).ToArray()); WabisabiCoordinatorSettings coordSettings; switch (actualCommand) { case "reset": var newS = new WabisabiStoreSettings(); newS.Settings = vm.Settings; - await _WabisabiService.SetWabisabiForStore(storeId, vm, commandIndex); + await _WabisabiService.SetWabisabiForStore(storeId, vm, coordx); TempData["SuccessMessage"] = $"Advanced settings reset to default"; return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId}); case "accept-terms": - var coord = vm.Settings.SingleOrDefault(settings => settings.Coordinator == commandIndex); + var coord = vm.Settings.SingleOrDefault(settings => settings.Coordinator == coordx); coord.RoundWhenEnabled = null; - await _WabisabiService.SetWabisabiForStore(storeId, vm, commandIndex); - TempData["SuccessMessage"] = $"{commandIndex} terms accepted"; + await _WabisabiService.SetWabisabiForStore(storeId, vm, coordx); + TempData["SuccessMessage"] = $"{coordx} terms accepted"; return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId}); case "remove-coordinator": if (!(await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded) { return View(vm); } + coordSettings = await _wabisabiCoordinatorService.GetSettings(); if (coordSettings.DiscoveredCoordinators.RemoveAll(discoveredCoordinator => - discoveredCoordinator.Name == commandIndex) > 0) + discoveredCoordinator.Name == coordx) > 0) { - TempData["SuccessMessage"] = $"Coordinator {commandIndex} stopped and removed"; + TempData["SuccessMessage"] = $"Coordinator {coordx} stopped and removed"; await _wabisabiCoordinatorService.UpdateSettings(coordSettings); - await _instanceManager.RemoveCoordinator(commandIndex); + await _instanceManager.RemoveCoordinator(coordx); return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId}); } else