nostr fixes

This commit is contained in:
Kukks
2024-05-15 09:32:27 +02:00
parent 0af456a851
commit 7836504196
8 changed files with 278 additions and 215 deletions

View File

@@ -15,4 +15,33 @@
&lt;/SessionState&gt;</s:String> &lt;/SessionState&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nostr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Nostr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Nostr</Product> <Product>Nostr</Product>
<Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description> <Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description>
<Version>1.1.6</Version> <Version>1.1.7</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->
@@ -36,7 +36,7 @@
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" /> <ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.45" /> <PackageReference Include="NNostr.Client" Version="0.0.47" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources" /> <Folder Include="Resources" />

View File

@@ -13,7 +13,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Coinjoin</Product> <Product>Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description> <Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.81</Version> <Version>1.0.82</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
@@ -44,7 +44,7 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.45"></PackageReference> <PackageReference Include="NNostr.Client" Version="0.0.47"></PackageReference>
<PackageReference Include="WabiSabi" Version="1.0.1.2"/> <PackageReference Include="WabiSabi" Version="1.0.1.2"/>
</ItemGroup> </ItemGroup>
<Target Name="DeleteExampleFile" AfterTargets="Publish"> <Target Name="DeleteExampleFile" AfterTargets="Publish">

View File

@@ -268,6 +268,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
var network = _clientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork; var network = _clientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork;
var s = await GetSettings(); var s = await GetSettings();
Uri uri = s.UriToAdvertise;
if (s.Enabled && !string.IsNullOrEmpty(s.NostrIdentity) && s.NostrRelay is not null && if (s.Enabled && !string.IsNullOrEmpty(s.NostrIdentity) && s.NostrRelay is not null &&
s.UriToAdvertise is not null) s.UriToAdvertise is not null)
{ {
@@ -276,29 +277,44 @@ public class WabisabiCoordinatorService : PeriodicRunner
if(s.UriToAdvertise.Scheme.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) if(s.UriToAdvertise.Scheme.StartsWith("http", StringComparison.InvariantCultureIgnoreCase))
{ {
//make sure the end is with plugins/wabisabi-coordinator/ //make sure the end is with plugins/wabisabi-coordinator/
var uri = new UriBuilder(s.UriToAdvertise); var uriB = new UriBuilder(uri);
uri.Path = uri.Path.Replace("plugins/wabisabi-coordinator/", "").TrimEnd('/') + "/plugins/wabisabi-coordinator/";
uriB.Path = uriB.Path.Replace("plugins/wabisabi-coordinator", "").TrimEnd('/') + "/plugins/wabisabi-coordinator/";
uri= uriB.Uri;
} }
//verify the url //verify the url
IWasabiHttpClientFactory factory = null; IWasabiHttpClientFactory factory = null;
if (s.UriToAdvertise.Scheme == "nostr" && if (uri.Scheme == "nostr" &&
s.UriToAdvertise.Host.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote) uri.AbsolutePath.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote)
{ {
factory = new NostrWabisabiClientFactory(null, nostrProfileNote); factory = new NostrWabisabiClientFactory(null, nostrProfileNote);
} }
else else
{ {
factory = new WasabiHttpClientFactory(null, () => s.UriToAdvertise); factory = new WasabiHttpClientFactory(null, () => uri);
} }
try try
{ {
var handler = factory.NewWabiSabiApiRequestHandler(Mode.SingleCircuitPerLifetime); if (factory is IHostedService hs)
var resp = await handler.GetStatusAsync(RoundStateRequest.Empty, cancel); {
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 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, await Nostr.Publish(s.NostrRelay,
new[] new[]
{ {
await Nostr.CreateCoordinatorDiscoveryEvent(network, k, s.UriToAdvertise, await Nostr.CreateCoordinatorDiscoveryEvent(network, k, uri,
s.CoordinatorDescription) s.CoordinatorDescription)
},s.UriToAdvertise.IsOnion()? _socks5HttpClientHandler: null, cancel); },s.UriToAdvertise.IsOnion()? _socks5HttpClientHandler: null, cancel);
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
@@ -31,7 +32,7 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
private readonly ECXOnlyPubKey _coordinatorKey; private readonly ECXOnlyPubKey _coordinatorKey;
private readonly INamedCircuit _circuit; private readonly INamedCircuit _circuit;
private string _coordinatorKeyHex => _coordinatorKey.ToHex(); private string _coordinatorKeyHex => _coordinatorKey.ToHex();
private readonly string _coordinatorFilterId; // private readonly string _coordinatorFilterId;
public NostrWabiSabiApiClient(Uri relay, WebProxy webProxy , ECXOnlyPubKey coordinatorKey, INamedCircuit? circuit) public NostrWabiSabiApiClient(Uri relay, WebProxy webProxy , ECXOnlyPubKey coordinatorKey, INamedCircuit? circuit)
{ {
@@ -39,66 +40,79 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
_webProxy = webProxy; _webProxy = webProxy;
_coordinatorKey = coordinatorKey; _coordinatorKey = coordinatorKey;
_circuit = circuit; _circuit = circuit;
_coordinatorFilterId = new Guid().ToString(); // _coordinatorFilterId = new Guid().ToString();
} }
private CancellationTokenSource _cts;
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
if (!_circuit.IsActive) if (!_circuit.IsActive)
{ {
Dispose(); Dispose();
} }
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
if (_circuit is OneOffCircuit) if (_circuit is OneOffCircuit)
{ {
return; return;
// we dont bootstrap, we do it on demand for a request instead // 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) private async Task Init(CancellationToken cancellationToken, INamedCircuit circuit)
{ {
_client = CreateClient(_relay, _webProxy, circuit); while (cancellationToken.IsCancellationRequested == false)
_ = _client.ListenForMessages();
var filter = new NostrSubscriptionFilter()
{ {
Authors = new[] {_coordinatorKey.ToHex()}, _client = CreateClient(_relay, _webProxy, circuit);
Kinds = new[] {RoundStateKind, CommunicationKind},
Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)),
Limit = 0
};
await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken);
_client.EventsReceived += EventsReceived;
await _client.ConnectAndWaitUntilConnected(cancellationToken); await _client.ConnectAndWaitUntilConnected(cancellationToken);
_circuit.IsolationIdChanged += (_, _) =>
{
_client.Dispose();
_ = StartAsync(CancellationToken.None);
};
}
private RoundStateResponse _lastRoundState { get; set; } _circuit.IsolationIdChanged += (_, _) =>
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)
{ {
_lastRoundState = Deserialize<RoundStateResponse>(roundState.Content); Dispose();
_lastRoundStateTask.TrySetResult(); _ = 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<NostrEvent> 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<RoundStateResponse>(evt.Content);
_lastRoundStateTask.TrySetResult();
}
}
private NostrEvent _lastRoundStateEvent { get; set; }
private RoundStateResponse _lastRoundState { get; set; }
private readonly TaskCompletionSource _lastRoundStateTask = new();
private async Task SendAndWaitForReply<TRequest>(RemoteAction action, TRequest request, private async Task SendAndWaitForReply<TRequest>(RemoteAction action, TRequest request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@@ -129,37 +143,19 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
Request = request Request = request
}), }),
PublicKey = pubkey.ToHex(), PublicKey = pubkey.ToHex(),
Kind = 4, Kind = CommunicationKind,
CreatedAt = DateTimeOffset.Now CreatedAt = DateTimeOffset.Now
}; };
evt.SetTag("p", _coordinatorKeyHex); evt.SetTag("p", _coordinatorKeyHex);
await evt.EncryptNip04EventAsync(newKey); await evt.EncryptNip04EventAsync(newKey, null, true);
evt.Kind = CommunicationKind; evt = await evt.ComputeIdAndSignAsync(newKey, false);
await evt.ComputeIdAndSignAsync(newKey);
var tcs = new TaskCompletionSource<NostrEvent>(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;
try try
{ {
var replyEvent = await tcs.Task;
replyEvent.Kind = 4; var replyEvent = await _client.SendEventAndWaitForReply(evt, cancellationToken);
var response = await replyEvent.DecryptNip04EventAsync(newKey); var response = await replyEvent.DecryptNip04EventAsync(newKey, null, true);
var jobj = JObject.Parse(response); var jobj = JObject.Parse(response);
if (jobj.TryGetValue("error", out var errorJson)) if (jobj.TryGetValue("error", out var errorJson))
{ {
@@ -205,7 +201,6 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
} }
catch (OperationCanceledException e) catch (OperationCanceledException e)
{ {
_client.EventsReceived -= OnClientEventsReceived;
_circuit.IncrementIsolationId(); _circuit.IncrementIsolationId();
throw; throw;
} }
@@ -283,6 +278,10 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
public void Dispose() public void Dispose()
{ {
_client?.Dispose(); _client?.Dispose();
_cts?.Cancel();
_client = null;
_cts = null;
} }
public static NostrClient CreateClient(Uri relay, WebProxy webProxy, INamedCircuit namedCircuit ) public static NostrClient CreateClient(Uri relay, WebProxy webProxy, INamedCircuit namedCircuit )

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -20,20 +22,21 @@ using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Crypto; using WalletWasabi.WabiSabi.Crypto;
using WalletWasabi.WabiSabi.Models; using WalletWasabi.WabiSabi.Models;
using WalletWasabi.WabiSabi.Models.Serialization; using WalletWasabi.WabiSabi.Models.Serialization;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
namespace BTCPayServer.Plugins.Wabisabi; namespace BTCPayServer.Plugins.Wabisabi;
public class NostrWabisabiApiServer: IHostedService public class NostrWabisabiApiServer : IHostedService
{ {
public static int RoundStateKind = 15750; public static int RoundStateKind = 15750;
public static int CommunicationKind = 25750; public static int CommunicationKind = 25750;
private readonly Arena _arena; private readonly Arena _arena;
private WabisabiCoordinatorSettings _coordinatorSettings; private WabisabiCoordinatorSettings _coordinatorSettings;
private NostrClient _client; private NostrClient _client;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly string _coordinatorFilterId; private readonly string _coordinatorFilterId;
private Channel<NostrEvent> PendingEvents { get; } = Channel.CreateUnbounded<NostrEvent>(); // private Channel<NostrEvent> PendingEvents { get; } = Channel.CreateUnbounded<NostrEvent>();
public NostrWabisabiApiServer(Arena arena, WabisabiCoordinatorSettings coordinatorSettings, public NostrWabisabiApiServer(Arena arena, WabisabiCoordinatorSettings coordinatorSettings,
ILogger logger) ILogger logger)
{ {
@@ -44,6 +47,7 @@ public class NostrWabisabiApiServer: IHostedService
_serializer = JsonSerializer.Create(JsonSerializationOptions.Default.Settings); _serializer = JsonSerializer.Create(JsonSerializationOptions.Default.Settings);
} }
private CancellationTokenSource _cts;
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
@@ -54,31 +58,24 @@ public class NostrWabisabiApiServer: IHostedService
} }
_client = new NostrClient(_coordinatorSettings.NostrRelay); _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); await _client.ConnectAndWaitUntilConnected(cancellationToken);
_ = RoutinelyUpdateRoundEvent(cancellationToken); _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_ = ProcessRequests(cancellationToken); _logger.LogInformation($"NOSTR SERVER: CONNECTED TO {_coordinatorSettings.NostrRelay}");
} _evtSubscriptions = _client.SubscribeForEvents(new[]
{
new NostrSubscriptionFilter()
{
ReferencedPublicKeys = new[] {_coordinatorSettings.GetPubKey().ToHex()},
Kinds = new[] {CommunicationKind},
Limit = 0
}
}, false, _cts.Token);
private void EventsReceived(object sender, (string subscriptionId, NostrEvent[] events) e)
{ _ = RoutinelyUpdateRoundEvent(_cts.Token);
if (e.subscriptionId != _coordinatorFilterId) return; _ = ProcessRequests(_cts.Token);
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);
} }
@@ -86,155 +83,169 @@ public class NostrWabisabiApiServer: IHostedService
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var response = await _arena.GetStatusAsync(RoundStateRequest.Empty, cancellationToken); try
{
var response = await _arena.GetStatusAsync(RoundStateRequest.Empty, cancellationToken);
var nostrEvent = new NostrEvent() var nostrEvent = new NostrEvent()
{ {
Kind = RoundStateKind, Kind = RoundStateKind,
PublicKey = _coordinatorSettings.GetPubKey().ToHex(), Content =Serialize(response)
CreatedAt = DateTimeOffset.Now,
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}"); _logger.LogDebug($"NOSTR SERVER: PUBLISHED ROUND STATE {nostrEvent.Id}");
await Task.Delay(1000, cancellationToken); await Task.Delay(1000, cancellationToken);
}
catch (Exception e)
{
Console.WriteLine(e);
}
} }
} }
private async Task ProcessRequests(CancellationToken cancellationToken) private async Task ProcessRequests(CancellationToken cancellationToken)
{ {
while (!cancellationToken.IsCancellationRequested && await foreach (var evt in _evtSubscriptions.WithCancellation(cancellationToken))
await PendingEvents.Reader.WaitToReadAsync(cancellationToken) &&
PendingEvents.Reader.TryRead(out var evt))
{ {
evt.Kind = 4; try
var content = JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorSettings.GetKey()));
if (content.TryGetValue("action", out var actionJson) &&
actionJson.Value<string>(actionJson) is { } actionString &&
Enum.TryParse<RemoteAction>(actionString, out var action) &&
content.ContainsKey("request"))
{ {
try var content =
JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorSettings.GetKey(), null, true));
if (content.TryGetValue("action", out var actionJson) &&
actionJson.Value<string>(actionJson) is { } actionString &&
Enum.TryParse<RemoteAction>(actionString, out var action) &&
content.ContainsKey("request"))
{ {
_logger.LogDebug($"NOSTR SERVER: Received request {evt.Id} {action}"); try
switch (action)
{ {
case RemoteAction.GetStatus: _logger.LogDebug($"NOSTR SERVER: Received request {evt.Id} {action}");
// ignored as we use a dedicated public event for this to not spam switch (action)
break; {
case RemoteAction.RegisterInput: case RemoteAction.GetStatus:
var registerInputRequest = // ignored as we use a dedicated public event for this to not spam
content["request"].ToObject<InputRegistrationRequest>(_serializer); break;
var registerInputResponse = case RemoteAction.RegisterInput:
await _arena.RegisterInputAsync(registerInputRequest, CancellationToken.None); var registerInputRequest =
await Reply(evt, registerInputResponse, CancellationToken.None); content["request"].ToObject<InputRegistrationRequest>(_serializer);
break; var registerInputResponse =
case RemoteAction.RegisterOutput: await _arena.RegisterInputAsync(registerInputRequest, CancellationToken.None);
var registerOutputRequest = await Reply(evt, registerInputResponse, CancellationToken.None);
content["request"].ToObject<OutputRegistrationRequest>(_serializer); break;
await _arena.RegisterOutputAsync(registerOutputRequest, CancellationToken.None); case RemoteAction.RegisterOutput:
break; var registerOutputRequest =
case RemoteAction.RemoveInput: content["request"].ToObject<OutputRegistrationRequest>(_serializer);
var removeInputRequest = content["request"].ToObject<InputsRemovalRequest>(_serializer); await _arena.RegisterOutputAsync(registerOutputRequest, CancellationToken.None);
await _arena.RemoveInputAsync(removeInputRequest, CancellationToken.None); break;
break; case RemoteAction.RemoveInput:
case RemoteAction.ConfirmConnection: var removeInputRequest = content["request"].ToObject<InputsRemovalRequest>(_serializer);
var connectionConfirmationRequest = await _arena.RemoveInputAsync(removeInputRequest, CancellationToken.None);
content["request"].ToObject<ConnectionConfirmationRequest>(_serializer); break;
var connectionConfirmationResponse = case RemoteAction.ConfirmConnection:
await _arena.ConfirmConnectionAsync(connectionConfirmationRequest, var connectionConfirmationRequest =
CancellationToken.None); content["request"].ToObject<ConnectionConfirmationRequest>(_serializer);
await Reply(evt, connectionConfirmationResponse, CancellationToken.None); var connectionConfirmationResponse =
break; await _arena.ConfirmConnectionAsync(connectionConfirmationRequest,
case RemoteAction.ReissueCredential: CancellationToken.None);
var reissueCredentialRequest = await Reply(evt, connectionConfirmationResponse, CancellationToken.None);
content["request"].ToObject<ReissueCredentialRequest>(_serializer); break;
var reissueCredentialResponse = case RemoteAction.ReissueCredential:
await _arena.ReissuanceAsync(reissueCredentialRequest, CancellationToken.None); var reissueCredentialRequest =
await Reply(evt, reissueCredentialResponse, CancellationToken.None); content["request"].ToObject<ReissueCredentialRequest>(_serializer);
break; var reissueCredentialResponse =
case RemoteAction.SignTransaction: await _arena.ReissuanceAsync(reissueCredentialRequest, CancellationToken.None);
var transactionSignaturesRequest = await Reply(evt, reissueCredentialResponse, CancellationToken.None);
content["request"].ToObject<TransactionSignaturesRequest>(_serializer); break;
await _arena.SignTransactionAsync(transactionSignaturesRequest, CancellationToken.None); case RemoteAction.SignTransaction:
break; var transactionSignaturesRequest =
case RemoteAction.ReadyToSign: content["request"].ToObject<TransactionSignaturesRequest>(_serializer);
var readyToSignRequest = await _arena.SignTransactionAsync(transactionSignaturesRequest, CancellationToken.None);
content["request"].ToObject<ReadyToSignRequestRequest>(_serializer); break;
await _arena.ReadyToSignAsync(readyToSignRequest, CancellationToken.None); case RemoteAction.ReadyToSign:
break; var readyToSignRequest =
content["request"].ToObject<ReadyToSignRequestRequest>(_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 readonly JsonSerializer _serializer;
private IAsyncEnumerable<NostrEvent> _evtSubscriptions;
private async Task Reply<TResponse>(NostrEvent originaltEvent, TResponse response,
private async Task Reply<TResponse>(NostrEvent originaltEvent,TResponse response,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
_logger.LogDebug($"NOSTR SERVER: REPLYING TO {originaltEvent.Id} WITH {response}"); _logger.LogDebug($"NOSTR SERVER: REPLYING TO {originaltEvent.Id} WITH {response}");
var evt = new NostrEvent() var evt = new NostrEvent()
{ {
Content = Serialize(response), Content = Serialize(response),
PublicKey = _coordinatorSettings.GetPubKey().ToHex(), PublicKey = _coordinatorSettings.GetPubKey().ToHex(),
Kind = 4, Kind = CommunicationKind,
CreatedAt = DateTimeOffset.Now CreatedAt = DateTimeOffset.Now
}; };
evt.SetTag("p", originaltEvent.PublicKey); evt.SetTag("p", originaltEvent.PublicKey);
evt.SetTag("e", originaltEvent.Id); evt.SetTag("e", originaltEvent.Id);
await evt.EncryptNip04EventAsync(_coordinatorSettings.GetKey()); await evt.EncryptNip04EventAsync(_coordinatorSettings.GetKey(), null, true);
evt.Kind = CommunicationKind;
await evt.ComputeIdAndSignAsync(_coordinatorSettings.GetKey()); await evt.ComputeIdAndSignAsync(_coordinatorSettings.GetKey());
await _client.PublishEvent(evt, cancellationToken); await _client.PublishEvent(evt, cancellationToken);
} }
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)
{ {
if (_client is not null) if(_cts is not null)
{ await _cts?.CancelAsync();
await _client.CloseSubscription(_coordinatorFilterId, cancellationToken); _client?.Dispose();
_client.EventsReceived -= EventsReceived;
_client = null;
}
} }
private static string Serialize<T>(T obj) private static string Serialize<T>(T obj)
=> JsonConvert.SerializeObject(obj, JsonSerializationOptions.Default.Settings); {
var raw = JsonConvert.SerializeObject(obj, JsonSerializationOptions.Default.Settings);
return raw;
}
private enum RemoteAction private enum RemoteAction
{ {
RegisterInput, RegisterInput,

View File

@@ -194,7 +194,7 @@ public class NostrWabisabiClientFactory: IWasabiHttpClientFactory, IHostedServic
public IWabiSabiApiRequestHandler NewWabiSabiApiRequestHandler(Mode mode, ICircuit circuit = null) 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; circuit = DefaultCircuit.Instance;
} }
@@ -209,10 +209,16 @@ public class NostrWabisabiClientFactory: IWasabiHttpClientFactory, IHostedServic
var result = _clients.GetOrAdd(namedCircuit.Name, name => var result = _clients.GetOrAdd(namedCircuit.Name, name =>
{ {
var result = new NostrWabiSabiApiClient(new Uri(_nostrProfileNote.Relays.First()), 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); namedCircuit);
if (_started)
{
result.StartAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
}
return result; return result;
}); });
return result; return result;
} }
} }
@@ -287,7 +293,7 @@ public class WabisabiCoordinatorClientInstance:IHostedService
} }
else if (coordinator.Scheme == "nostr" && 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); var factory = new NostrWabisabiClientFactory(socks5HttpClientHandler, nostrProfileNote);
WasabiHttpClientFactory = factory; WasabiHttpClientFactory = factory;

View File

@@ -108,35 +108,37 @@ namespace BTCPayServer.Plugins.Wabisabi
vm.FeeRateMedianTimeFrameHours = Math.Max(0, vm.FeeRateMedianTimeFrameHours); vm.FeeRateMedianTimeFrameHours = Math.Max(0, vm.FeeRateMedianTimeFrameHours);
ModelState.Clear(); ModelState.Clear();
var coordx = string.Join(string.Empty,pieces.Skip(1).ToArray());
WabisabiCoordinatorSettings coordSettings; WabisabiCoordinatorSettings coordSettings;
switch (actualCommand) switch (actualCommand)
{ {
case "reset": case "reset":
var newS = new WabisabiStoreSettings(); var newS = new WabisabiStoreSettings();
newS.Settings = vm.Settings; newS.Settings = vm.Settings;
await _WabisabiService.SetWabisabiForStore(storeId, vm, commandIndex); await _WabisabiService.SetWabisabiForStore(storeId, vm, coordx);
TempData["SuccessMessage"] = $"Advanced settings reset to default"; TempData["SuccessMessage"] = $"Advanced settings reset to default";
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId}); return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
case "accept-terms": case "accept-terms":
var coord = vm.Settings.SingleOrDefault(settings => settings.Coordinator == commandIndex); var coord = vm.Settings.SingleOrDefault(settings => settings.Coordinator == coordx);
coord.RoundWhenEnabled = null; coord.RoundWhenEnabled = null;
await _WabisabiService.SetWabisabiForStore(storeId, vm, commandIndex); await _WabisabiService.SetWabisabiForStore(storeId, vm, coordx);
TempData["SuccessMessage"] = $"{commandIndex} terms accepted"; TempData["SuccessMessage"] = $"{coordx} terms accepted";
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId}); return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
case "remove-coordinator": case "remove-coordinator":
if (!(await _authorizationService.AuthorizeAsync(User, null, if (!(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded) new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded)
{ return View(vm); { return View(vm);
} }
coordSettings = await _wabisabiCoordinatorService.GetSettings(); coordSettings = await _wabisabiCoordinatorService.GetSettings();
if (coordSettings.DiscoveredCoordinators.RemoveAll(discoveredCoordinator => 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 _wabisabiCoordinatorService.UpdateSettings(coordSettings);
await _instanceManager.RemoveCoordinator(commandIndex); await _instanceManager.RemoveCoordinator(coordx);
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId}); return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
} }
else else