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>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nostr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

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

View File

@@ -13,7 +13,7 @@
<PropertyGroup>
<Product>Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.81</Version>
<Version>1.0.82</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
@@ -44,7 +44,7 @@
</ProjectReference>
</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"/>
</ItemGroup>
<Target Name="DeleteExampleFile" AfterTargets="Publish">

View File

@@ -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);
}

View File

@@ -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,65 +40,78 @@ 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)
{
while (cancellationToken.IsCancellationRequested == false)
{
_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);
_circuit.IsolationIdChanged += (_, _) =>
{
_client.Dispose();
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 RoundStateResponse _lastRoundState { get; set; }
private TaskCompletionSource _lastRoundStateTask = new();
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;
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);
_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,
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<NostrEvent>(cancellationToken);
await evt.EncryptNip04EventAsync(newKey, null, true);
evt = await evt.ComputeIdAndSignAsync(newKey, false);
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
{
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 )

View File

@@ -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,6 +22,7 @@ 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;
@@ -33,7 +36,7 @@ public class NostrWabisabiApiServer: IHostedService
private readonly ILogger _logger;
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,
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)
{
@@ -54,31 +58,24 @@ public class NostrWabisabiApiServer: IHostedService
}
_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}");
var filter = new NostrSubscriptionFilter()
_evtSubscriptions = _client.SubscribeForEvents(new[]
{
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);
}
}, false, _cts.Token);
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);
_ = RoutinelyUpdateRoundEvent(_cts.Token);
_ = ProcessRequests(_cts.Token);
}
@@ -86,28 +83,38 @@ public class NostrWabisabiApiServer: IHostedService
{
while (!cancellationToken.IsCancellationRequested)
{
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)
};
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()));
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) &&
@@ -193,48 +200,52 @@ public class NostrWabisabiApiServer: IHostedService
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<NostrEvent> _evtSubscriptions;
private async Task Reply<TResponse>(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)
{
if(_cts is not null)
await _cts?.CancelAsync();
await _client.CloseSubscription(_coordinatorFilterId, cancellationToken);
_client.EventsReceived -= EventsReceived;
_client = null;
}
_client?.Dispose();
}
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
{
RegisterInput,

View File

@@ -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;

View File

@@ -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