mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
nostr fixes
This commit is contained in:
@@ -15,4 +15,33 @@
|
|||||||
</SessionState></s:String>
|
</SessionState></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>
|
||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,65 +40,78 @@ 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)
|
||||||
|
{
|
||||||
|
while (cancellationToken.IsCancellationRequested == false)
|
||||||
{
|
{
|
||||||
_client = CreateClient(_relay, _webProxy, 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)),
|
|
||||||
Limit = 0
|
|
||||||
};
|
|
||||||
await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken);
|
|
||||||
_client.EventsReceived += EventsReceived;
|
|
||||||
|
|
||||||
await _client.ConnectAndWaitUntilConnected(cancellationToken);
|
await _client.ConnectAndWaitUntilConnected(cancellationToken);
|
||||||
|
|
||||||
_circuit.IsolationIdChanged += (_, _) =>
|
_circuit.IsolationIdChanged += (_, _) =>
|
||||||
{
|
{
|
||||||
_client.Dispose();
|
Dispose();
|
||||||
_ = StartAsync(CancellationToken.None);
|
_ = 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 RoundStateResponse _lastRoundState { get; set; }
|
private async Task HandleStateEvents(IAsyncEnumerable<NostrEvent> subscriptions)
|
||||||
private TaskCompletionSource _lastRoundStateTask = new();
|
{
|
||||||
|
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);
|
||||||
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);
|
|
||||||
_lastRoundStateTask.TrySetResult();
|
_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 )
|
||||||
|
|||||||
@@ -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,6 +22,7 @@ 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;
|
||||||
|
|
||||||
@@ -33,7 +36,7 @@ public class NostrWabisabiApiServer: IHostedService
|
|||||||
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);
|
|
||||||
|
await _client.ConnectAndWaitUntilConnected(cancellationToken);
|
||||||
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
_logger.LogInformation($"NOSTR SERVER: CONNECTED TO {_coordinatorSettings.NostrRelay}");
|
_logger.LogInformation($"NOSTR SERVER: CONNECTED TO {_coordinatorSettings.NostrRelay}");
|
||||||
var filter = new NostrSubscriptionFilter()
|
|
||||||
|
_evtSubscriptions = _client.SubscribeForEvents(new[]
|
||||||
|
{
|
||||||
|
new NostrSubscriptionFilter()
|
||||||
{
|
{
|
||||||
ReferencedPublicKeys = new[] {_coordinatorSettings.GetPubKey().ToHex()},
|
ReferencedPublicKeys = new[] {_coordinatorSettings.GetPubKey().ToHex()},
|
||||||
Kinds = new[] {CommunicationKind},
|
Kinds = new[] {CommunicationKind},
|
||||||
Limit = 0
|
Limit = 0
|
||||||
};
|
|
||||||
await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken);
|
|
||||||
_client.EventsReceived += EventsReceived;
|
|
||||||
|
|
||||||
await _client.ConnectAndWaitUntilConnected(cancellationToken);
|
|
||||||
_ = RoutinelyUpdateRoundEvent(cancellationToken);
|
|
||||||
_ = ProcessRequests(cancellationToken);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}, 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,28 +83,38 @@ public class NostrWabisabiApiServer: IHostedService
|
|||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
var response = await _arena.GetStatusAsync(RoundStateRequest.Empty, cancellationToken);
|
var response = await _arena.GetStatusAsync(RoundStateRequest.Empty, cancellationToken);
|
||||||
var nostrEvent = new NostrEvent()
|
var nostrEvent = new NostrEvent()
|
||||||
{
|
{
|
||||||
Kind = RoundStateKind,
|
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}");
|
_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()));
|
{
|
||||||
|
var content =
|
||||||
|
JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorSettings.GetKey(), null, true));
|
||||||
if (content.TryGetValue("action", out var actionJson) &&
|
if (content.TryGetValue("action", out var actionJson) &&
|
||||||
actionJson.Value<string>(actionJson) is { } actionString &&
|
actionJson.Value<string>(actionJson) is { } actionString &&
|
||||||
Enum.TryParse<RemoteAction>(actionString, out var action) &&
|
Enum.TryParse<RemoteAction>(actionString, out var action) &&
|
||||||
@@ -193,48 +200,52 @@ public class NostrWabisabiApiServer: IHostedService
|
|||||||
await Reply(evt, response, CancellationToken.None);
|
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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user