mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
native nostr coinjoins
This commit is contained in:
@@ -206,7 +206,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
||||
//if we're less than the max output registration, we should be more aggressive in adding coins
|
||||
var chance = consolidationMode ? (isLessThanMaxOutputRegistration? 100: 90 ): 100m - Math.Min(maxCoinCapacityPercentage, isLessThanMaxOutputRegistration ? 10m : maxCoinCapacityPercentage);
|
||||
_logger.LogDebug(
|
||||
$"coin selection: no payms left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} <= {rand} (random 0-100) ");
|
||||
$"coin selection: no payms left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} <= {rand} (random 0-100) {chance <= rand} ");
|
||||
if (chance <= rand)
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -93,11 +93,22 @@ public class WabisabiCoordinatorService : PeriodicRunner
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (existing.Enabled &&
|
||||
_instanceManager.HostedServices.TryGetValue("local", out var instance))
|
||||
else if (existing.Enabled)
|
||||
{
|
||||
if (_instanceManager.HostedServices.TryGetValue("local", out var instance))
|
||||
{
|
||||
instance.TermsConditions = wabisabiCoordinatorSettings.TermsConditions;
|
||||
}
|
||||
if(wabisabiCoordinatorSettings.Enabled &&
|
||||
(existing.NostrIdentity != wabisabiCoordinatorSettings.NostrIdentity || existing.NostrRelay != wabisabiCoordinatorSettings.NostrRelay))
|
||||
{
|
||||
var nostr = HostedServices.Get<NostrWabisabiApiServer>();
|
||||
nostr.UpdateSettings(wabisabiCoordinatorSettings);
|
||||
await nostr.StopAsync(CancellationToken.None);
|
||||
await nostr.StartAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
await _settingsRepository.UpdateSetting(wabisabiCoordinatorSettings, nameof(WabisabiCoordinatorSettings));
|
||||
@@ -202,7 +213,10 @@ public class WabisabiCoordinatorService : PeriodicRunner
|
||||
WabiSabiCoordinator = new WabiSabiCoordinator(coordinatorParameters, rpc, coinJoinIdStore, coinJoinScriptStore,
|
||||
_httpClientFactory);
|
||||
HostedServices.Register<WabiSabiCoordinator>(() => WabiSabiCoordinator, "WabiSabi Coordinator");
|
||||
|
||||
var settings = await GetSettings();
|
||||
WabisabiApiServer = new NostrWabisabiApiServer(WabiSabiCoordinator.Arena, settings, _logger);
|
||||
HostedServices.Register<NostrWabisabiApiServer>(() => WabisabiApiServer, "WabiSabi Coordinator Nostr");
|
||||
|
||||
if (settings.Enabled)
|
||||
{
|
||||
@@ -218,6 +232,8 @@ public class WabisabiCoordinatorService : PeriodicRunner
|
||||
await base.StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public NostrWabisabiApiServer WabisabiApiServer { get; set; }
|
||||
|
||||
public async Task StartCoordinator(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Starting local coordinator");
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -10,6 +12,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NNostr.Client;
|
||||
using WalletWasabi.Logging;
|
||||
using WalletWasabi.Tor.Socks5.Pool.Circuits;
|
||||
using WalletWasabi.WabiSabi;
|
||||
using WalletWasabi.WabiSabi.Backend.Models;
|
||||
using WalletWasabi.WabiSabi.Backend.PostRequests;
|
||||
@@ -18,36 +21,65 @@ using WalletWasabi.WabiSabi.Models.Serialization;
|
||||
|
||||
namespace BTCPayServer.Plugins.Wabisabi;
|
||||
|
||||
public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
|
||||
public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService, IDisposable
|
||||
{
|
||||
public static int RoundStateKind = 15750;
|
||||
public static int CommunicationKind = 25750;
|
||||
private readonly NostrClient _client;
|
||||
private NostrClient _client;
|
||||
private readonly Uri _relay;
|
||||
private readonly WebProxy _webProxy;
|
||||
private readonly ECXOnlyPubKey _coordinatorKey;
|
||||
private readonly INamedCircuit _circuit;
|
||||
private string _coordinatorKeyHex => _coordinatorKey.ToHex();
|
||||
private readonly string _coordinatorFilterId;
|
||||
|
||||
public NostrWabiSabiApiClient(NostrClient client, ECXOnlyPubKey coordinatorKey)
|
||||
public NostrWabiSabiApiClient(Uri relay, WebProxy webProxy , ECXOnlyPubKey coordinatorKey, INamedCircuit? circuit)
|
||||
{
|
||||
_client = client;
|
||||
_relay = relay;
|
||||
_webProxy = webProxy;
|
||||
_coordinatorKey = coordinatorKey;
|
||||
_circuit = circuit;
|
||||
_coordinatorFilterId = new Guid().ToString();
|
||||
}
|
||||
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_circuit.IsActive)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
if (_circuit is OneOffCircuit)
|
||||
{
|
||||
return;
|
||||
// we dont bootstrap, we do it on demand for a request instead
|
||||
}
|
||||
|
||||
await Init(cancellationToken, _circuit);
|
||||
}
|
||||
|
||||
private async Task Init(CancellationToken cancellationToken, INamedCircuit circuit)
|
||||
{
|
||||
_client = CreateClient(_relay, _webProxy, circuit);
|
||||
|
||||
_ = _client.ListenForMessages();
|
||||
var filter = new NostrSubscriptionFilter()
|
||||
{
|
||||
Authors = new[] {_coordinatorKey.ToHex()},
|
||||
Kinds = new[] {RoundStateKind, CommunicationKind},
|
||||
Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1))
|
||||
Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)),
|
||||
Limit = 0
|
||||
};
|
||||
await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken);
|
||||
_client.EventsReceived += EventsReceived;
|
||||
|
||||
await _client.ConnectAndWaitUntilConnected(cancellationToken);
|
||||
_circuit.IsolationIdChanged += (_, _) =>
|
||||
{
|
||||
_client.Dispose();
|
||||
_ = StartAsync(CancellationToken.None);
|
||||
};
|
||||
}
|
||||
|
||||
private RoundStateResponse _lastRoundState { get; set; }
|
||||
@@ -77,6 +109,16 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
|
||||
private async Task<TResponse> SendAndWaitForReply<TRequest, TResponse>(RemoteAction action, TRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
if(_circuit is OneOffCircuit)
|
||||
{
|
||||
using var subClient =
|
||||
new NostrWabiSabiApiClient(_relay, _webProxy, _coordinatorKey, new PersonCircuit());
|
||||
await subClient.StartAsync(cancellationToken);
|
||||
return await subClient.SendAndWaitForReply<TRequest, TResponse>(action, request, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
var newKey = ECPrivKey.Create(RandomUtils.GetBytes(32));
|
||||
var pubkey = newKey.CreateXOnlyPubKey();
|
||||
var evt = new NostrEvent()
|
||||
@@ -164,14 +206,14 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
|
||||
catch (OperationCanceledException e)
|
||||
{
|
||||
_client.EventsReceived -= OnClientEventsReceived;
|
||||
_circuit.IncrementIsolationId();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _client.CloseSubscription(_coordinatorFilterId, cancellationToken);
|
||||
_client.EventsReceived -= EventsReceived;
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public async Task<RoundStateResponse> GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken)
|
||||
@@ -237,4 +279,23 @@ public class NostrWabiSabiApiClient : IWabiSabiApiRequestHandler, IHostedService
|
||||
GetStatus,
|
||||
ReadyToSign
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
|
||||
public static NostrClient CreateClient(Uri relay, WebProxy webProxy, INamedCircuit namedCircuit )
|
||||
{
|
||||
return new NostrClient(relay, socket =>
|
||||
{
|
||||
if (socket is ClientWebSocket clientWebSocket && webProxy is { })
|
||||
{
|
||||
var proxy = new WebProxy(webProxy.Address, true, null,
|
||||
new NetworkCredential(namedCircuit.Name,
|
||||
namedCircuit.IsolationId.ToString()));
|
||||
clientWebSocket.Options.Proxy = proxy;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@ using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Secp256k1;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NNostr.Client;
|
||||
using WabiSabi.Crypto;
|
||||
using WalletWasabi.Backend.Controllers;
|
||||
using WalletWasabi.Logging;
|
||||
using WalletWasabi.WabiSabi;
|
||||
using WalletWasabi.WabiSabi.Backend.Models;
|
||||
@@ -26,17 +28,18 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
public static int RoundStateKind = 15750;
|
||||
public static int CommunicationKind = 25750;
|
||||
private readonly Arena _arena;
|
||||
private readonly NostrClient _client;
|
||||
private readonly ECPrivKey _coordinatorKey;
|
||||
private string _coordinatorKeyHex => _coordinatorKey.CreateXOnlyPubKey().ToHex();
|
||||
private WabisabiCoordinatorSettings _coordinatorSettings;
|
||||
private NostrClient _client;
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _coordinatorFilterId;
|
||||
|
||||
private Channel<NostrEvent> PendingEvents { get; } = Channel.CreateUnbounded<NostrEvent>();
|
||||
public NostrWabisabiApiServer(Arena arena,NostrClient client, ECPrivKey coordinatorKey)
|
||||
public NostrWabisabiApiServer(Arena arena, WabisabiCoordinatorSettings coordinatorSettings,
|
||||
ILogger logger)
|
||||
{
|
||||
_arena = arena;
|
||||
_client = client;
|
||||
_coordinatorKey = coordinatorKey;
|
||||
_coordinatorSettings = coordinatorSettings;
|
||||
_logger = logger;
|
||||
_coordinatorFilterId = new Guid().ToString();
|
||||
_serializer = JsonSerializer.Create(JsonSerializationOptions.Default.Settings);
|
||||
}
|
||||
@@ -44,12 +47,20 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_ = _client.ListenForMessages();
|
||||
if (_coordinatorSettings.NostrRelay is null || string.IsNullOrEmpty(_coordinatorSettings.NostrIdentity))
|
||||
{
|
||||
_logger.LogInformation("NOSTR SERVER: No Nostr relay/identity configured, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
_client = new NostrClient(_coordinatorSettings.NostrRelay);
|
||||
await _client.Connect(cancellationToken);
|
||||
_logger.LogInformation($"NOSTR SERVER: CONNECTED TO {_coordinatorSettings.NostrRelay}");
|
||||
var filter = new NostrSubscriptionFilter()
|
||||
{
|
||||
ReferencedPublicKeys = new[] {_coordinatorKey.ToHex()},
|
||||
ReferencedPublicKeys = new[] {_coordinatorSettings.GetPubKey().ToHex()},
|
||||
Kinds = new[] { CommunicationKind},
|
||||
Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1))
|
||||
Limit = 0
|
||||
};
|
||||
await _client.CreateSubscription(_coordinatorFilterId, new[] {filter}, cancellationToken);
|
||||
_client.EventsReceived += EventsReceived;
|
||||
@@ -65,7 +76,7 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
if (e.subscriptionId != _coordinatorFilterId) return;
|
||||
var requests = e.events.Where(evt =>
|
||||
evt.Kind == CommunicationKind &&
|
||||
evt.GetTaggedData("p").Any(s => s == _coordinatorKeyHex) && evt.Verify());
|
||||
evt.GetTaggedData("p").Any(s => s == _coordinatorSettings.GetPubKey().ToHex()) && evt.Verify());
|
||||
foreach (var request in requests)
|
||||
PendingEvents.Writer.TryWrite(request);
|
||||
}
|
||||
@@ -79,11 +90,12 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
var nostrEvent = new NostrEvent()
|
||||
{
|
||||
Kind = RoundStateKind,
|
||||
PublicKey = _coordinatorKeyHex,
|
||||
PublicKey = _coordinatorSettings.GetPubKey().ToHex(),
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
Content = Serialize(response)
|
||||
};
|
||||
await _client.PublishEvent(nostrEvent, cancellationToken);
|
||||
_logger.LogInformation($"NOSTR SERVER: PUBLISHED ROUND STATE {nostrEvent.Id}");
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +107,7 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
PendingEvents.Reader.TryRead(out var evt))
|
||||
{
|
||||
evt.Kind = 4;
|
||||
var content = JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorKey));
|
||||
var content = JObject.Parse(await evt.DecryptNip04EventAsync(_coordinatorSettings.GetKey()));
|
||||
if (content.TryGetValue("action", out var actionJson) &&
|
||||
actionJson.Value<string>(actionJson) is { } actionString &&
|
||||
Enum.TryParse<RemoteAction>(actionString, out var action) &&
|
||||
@@ -103,6 +115,7 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"NOSTR SERVER: Received request {evt.Id} {action}");
|
||||
switch (action)
|
||||
{
|
||||
case RemoteAction.GetStatus:
|
||||
@@ -191,26 +204,33 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
private async Task Reply<TResponse>(NostrEvent originaltEvent,TResponse response,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
_logger.LogInformation($"NOSTR SERVER: REPLYING TO {originaltEvent.Id} WITH {response}");
|
||||
var evt = new NostrEvent()
|
||||
{
|
||||
Content = Serialize(response),
|
||||
PublicKey = _coordinatorKeyHex,
|
||||
PublicKey = _coordinatorSettings.GetPubKey().ToHex(),
|
||||
Kind = 4,
|
||||
CreatedAt = DateTimeOffset.Now
|
||||
};
|
||||
evt.SetTag("p", originaltEvent.PublicKey);
|
||||
evt.SetTag("e", originaltEvent.Id);
|
||||
|
||||
await evt.EncryptNip04EventAsync(_coordinatorKey);
|
||||
await evt.EncryptNip04EventAsync(_coordinatorSettings.GetKey());
|
||||
evt.Kind = CommunicationKind;
|
||||
await evt.ComputeIdAndSignAsync(_coordinatorKey);
|
||||
await evt.ComputeIdAndSignAsync(_coordinatorSettings.GetKey());
|
||||
await _client.PublishEvent(evt, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_client is not null)
|
||||
{
|
||||
|
||||
await _client.CloseSubscription(_coordinatorFilterId, cancellationToken);
|
||||
_client.EventsReceived -= EventsReceived;
|
||||
_client = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string Serialize<T>(T obj)
|
||||
@@ -226,4 +246,9 @@ public class NostrWabisabiApiServer: IHostedService
|
||||
GetStatus,
|
||||
ReadyToSign
|
||||
}
|
||||
|
||||
public void UpdateSettings(WabisabiCoordinatorSettings wabisabiCoordinatorSettings)
|
||||
{
|
||||
_coordinatorSettings = wabisabiCoordinatorSettings;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
@using BTCPayServer.Plugins.Wabisabi
|
||||
@using NNostr.Client
|
||||
@using NNostr.Client.Protocols
|
||||
@using WalletWasabi.Backend.Controllers
|
||||
@model WalletWasabi.Backend.Controllers.WabisabiCoordinatorSettings
|
||||
|
||||
@inject WabisabiCoordinatorService WabisabiCoordinatorService
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["NavPartialName"] = "../UIServer/_Nav";
|
||||
@@ -59,6 +62,28 @@
|
||||
<button name="command" value="generate-nostr-key" type="submit" class="btn btn-secondary btn-sm">Generate</button>
|
||||
</div>
|
||||
</div>
|
||||
@if(WabisabiCoordinatorService.Started && WabisabiCoordinatorService.WabisabiApiServer != null && Model.NostrRelay is not null && Model.NostrIdentity is not null)
|
||||
{
|
||||
var nprofile = new NIP19.NosteProfileNote()
|
||||
{
|
||||
PubKey = Model.GetPubKey().ToHex(),
|
||||
Relays = new[] {Model.NostrRelay.ToString()}
|
||||
}.ToNIP19();
|
||||
|
||||
<div class="form-group mt-4" >
|
||||
<label class="form-label">EXPERIMENTAL: NOSTR COORDINATOR ENDPOINT ACTIVE</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="ss-result-txt" class="form-control" readonly="readonly" value="nostr:@nprofile"/>
|
||||
<button type="button" class="btn btn-secondary" data-clipboard-target="#ss-result-txt">
|
||||
<vc:icon symbol="copy"/>
|
||||
</button> <button type="button" class="btn btn-secondary" id="advertise-nostr" onclick="document.getElementById('UriToAdvertise').value='nostr:@nprofile'">
|
||||
Set as url to advertise
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<p id="ss-result-additional-info"></p>
|
||||
</div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="CoordinatorDescription" class="form-label">Description</label>
|
||||
<textarea asp-for="CoordinatorDescription" class="form-control"></textarea>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -7,10 +8,13 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Services;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NNostr.Client;
|
||||
using NNostr.Client.Protocols;
|
||||
using WalletWasabi.Backend.Controllers;
|
||||
using WalletWasabi.Tor.Socks5.Pool.Circuits;
|
||||
using WalletWasabi.Userfacing;
|
||||
@@ -21,6 +25,7 @@ using WalletWasabi.WabiSabi.Client.RoundStateAwaiters;
|
||||
using WalletWasabi.WabiSabi.Client.StatusChangedEvents;
|
||||
using WalletWasabi.Wallets;
|
||||
using WalletWasabi.WebClients.Wasabi;
|
||||
using ClientWebSocket = System.Net.WebSockets.ClientWebSocket;
|
||||
|
||||
namespace BTCPayServer.Plugins.Wabisabi;
|
||||
|
||||
@@ -105,7 +110,7 @@ public class WabisabiCoordinatorClientInstanceManager:IHostedService
|
||||
var instance = new WabisabiCoordinatorClientInstance(
|
||||
displayName,
|
||||
name, url is null? null: new Uri(url), _provider.GetService<ILoggerFactory>(), _provider, UTXOLocker,
|
||||
_provider.GetService<WalletProvider>(), termsConditions, description);
|
||||
_provider.GetService<WalletProvider>(), termsConditions, description,_provider.GetRequiredService<Socks5HttpClientHandler>());
|
||||
if (HostedServices.TryAdd(instance.CoordinatorName, instance))
|
||||
{
|
||||
if(started)
|
||||
@@ -127,7 +132,82 @@ public class WabisabiCoordinatorClientInstanceManager:IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
public class WabisabiCoordinatorClientInstance
|
||||
public class NostrWabisabiClientFactory: IWasabiHttpClientFactory, IHostedService
|
||||
{
|
||||
private readonly Socks5HttpClientHandler _socks5HttpClientHandler;
|
||||
private readonly NIP19.NosteProfileNote _nostrProfileNote;
|
||||
|
||||
public NostrWabisabiClientFactory(Socks5HttpClientHandler socks5HttpClientHandler,
|
||||
NIP19.NosteProfileNote nostrProfileNote)
|
||||
{
|
||||
_socks5HttpClientHandler = socks5HttpClientHandler;
|
||||
_nostrProfileNote = nostrProfileNote;
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<string, NostrWabiSabiApiClient> _clients = new();
|
||||
|
||||
private bool _started = false;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.WhenAll(_clients.Select(pair => pair.Value.StartAsync(cancellationToken)));
|
||||
|
||||
_started = true;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var nostrWabiSabiApiClient in _clients)
|
||||
{
|
||||
nostrWabiSabiApiClient.Value.Dispose();
|
||||
}
|
||||
_clients.Clear();
|
||||
_started = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public IWabiSabiApiRequestHandler NewWabiSabiApiRequestHandler(Mode mode, ICircuit circuit = null)
|
||||
{
|
||||
if (mode == Mode.DefaultCircuit || _socks5HttpClientHandler.Proxy is null)
|
||||
{
|
||||
circuit = DefaultCircuit.Instance;
|
||||
}
|
||||
|
||||
if (mode == Mode.NewCircuitPerRequest)
|
||||
{
|
||||
circuit = new OneOffCircuit();
|
||||
}
|
||||
|
||||
if (circuit is not INamedCircuit namedCircuit)
|
||||
throw new ArgumentException("circuit must be a INamedCircuit");
|
||||
var result = _clients.GetOrAdd(namedCircuit.Name, name =>
|
||||
{
|
||||
var result = new NostrWabiSabiApiClient(new Uri(_nostrProfileNote.Relays.First()),
|
||||
_socks5HttpClientHandler.Proxy as WebProxy, NostrExtensions.ParsePubKey(_nostrProfileNote.PubKey),
|
||||
namedCircuit);
|
||||
return result;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class LocalWabisabiClientFactory: IWasabiHttpClientFactory
|
||||
{
|
||||
private readonly WabiSabiController _wabiSabiController;
|
||||
|
||||
public LocalWabisabiClientFactory(WabiSabiController wabiSabiController)
|
||||
{
|
||||
_wabiSabiController = wabiSabiController;
|
||||
}
|
||||
public IWabiSabiApiRequestHandler NewWabiSabiApiRequestHandler(Mode mode, ICircuit circuit = null)
|
||||
{
|
||||
return _wabiSabiController;
|
||||
}
|
||||
}
|
||||
|
||||
public class WabisabiCoordinatorClientInstance:IHostedService
|
||||
{
|
||||
private readonly IUTXOLocker _utxoLocker;
|
||||
private readonly ILogger _logger;
|
||||
@@ -136,12 +216,13 @@ public class WabisabiCoordinatorClientInstance
|
||||
public Uri Coordinator { get; set; }
|
||||
public WalletProvider WalletProvider { get; }
|
||||
public string TermsConditions { get; set; }
|
||||
public WasabiHttpClientFactory WasabiHttpClientFactory { get; set; }
|
||||
public IWasabiHttpClientFactory WasabiHttpClientFactory { get; set; }
|
||||
public RoundStateUpdater RoundStateUpdater { get; set; }
|
||||
public CoinPrison CoinPrison { get; private set; }
|
||||
public WasabiCoordinatorStatusFetcher WasabiCoordinatorStatusFetcher { get; set; }
|
||||
public CoinJoinManager CoinJoinManager { get; set; }
|
||||
public string Description { get; set; }
|
||||
private readonly WalletWasabi.Services.HostedServices _hostedServices = new();
|
||||
|
||||
public WabisabiCoordinatorClientInstance(string coordinatorDisplayName,
|
||||
string coordinatorName,
|
||||
@@ -149,17 +230,20 @@ public class WabisabiCoordinatorClientInstance
|
||||
ILoggerFactory loggerFactory,
|
||||
IServiceProvider serviceProvider,
|
||||
IUTXOLocker utxoLocker,
|
||||
WalletProvider walletProvider, string termsConditions, string description,string coordinatorIdentifier = "CoinJoinCoordinatorIdentifier")
|
||||
WalletProvider walletProvider, string termsConditions, string description,
|
||||
Socks5HttpClientHandler socks5HttpClientHandler, string coordinatorIdentifier = "CoinJoinCoordinatorIdentifier"
|
||||
)
|
||||
{
|
||||
|
||||
_utxoLocker = utxoLocker;
|
||||
var config = serviceProvider.GetService<IConfiguration>();
|
||||
var socksEndpoint = config.GetValue<string>("socksendpoint");
|
||||
EndPointParser.TryParse(socksEndpoint,9050, out var torEndpoint);
|
||||
EndPointParser.TryParse(socksEndpoint, 9050, out var torEndpoint);
|
||||
if (torEndpoint is not null && torEndpoint is DnsEndPoint dnsEndPoint)
|
||||
{
|
||||
torEndpoint = new IPEndPoint(Dns.GetHostAddresses(dnsEndPoint.Host).First(), dnsEndPoint.Port);
|
||||
}
|
||||
|
||||
CoordinatorDisplayName = coordinatorDisplayName;
|
||||
CoordinatorName = coordinatorName;
|
||||
Coordinator = coordinator;
|
||||
@@ -167,22 +251,37 @@ public class WabisabiCoordinatorClientInstance
|
||||
TermsConditions = termsConditions;
|
||||
Description = description;
|
||||
_logger = loggerFactory.CreateLogger(coordinatorName);
|
||||
IWabiSabiApiRequestHandler sharedWabisabiClient;
|
||||
IWabiSabiApiRequestHandler sharedWabisabiClient = null;
|
||||
|
||||
var roundStateUpdaterCircuit = new PersonCircuit();
|
||||
|
||||
if (coordinatorName == "local")
|
||||
{
|
||||
sharedWabisabiClient = serviceProvider.GetRequiredService<WabiSabiController>();
|
||||
WasabiHttpClientFactory = new LocalWabisabiClientFactory( serviceProvider.GetRequiredService<WabiSabiController>());
|
||||
|
||||
}
|
||||
else if (coordinator.Scheme == "nostr" &&
|
||||
coordinator.Host.FromNIP19Note() is NIP19.NosteProfileNote nostrProfileNote)
|
||||
{
|
||||
var factory = new NostrWabisabiClientFactory(socks5HttpClientHandler, nostrProfileNote);
|
||||
WasabiHttpClientFactory = factory;
|
||||
_hostedServices.Register<NostrWabisabiClientFactory>(() => factory, "NostrWabisabiClientFactory");
|
||||
}
|
||||
else
|
||||
{
|
||||
WasabiHttpClientFactory = new WasabiHttpClientFactory(torEndpoint, () => Coordinator);
|
||||
var roundStateUpdaterCircuit = new PersonCircuit();
|
||||
var roundStateUpdaterHttpClient =
|
||||
WasabiHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, roundStateUpdaterCircuit);
|
||||
if (termsConditions is null)
|
||||
|
||||
|
||||
}
|
||||
|
||||
sharedWabisabiClient =
|
||||
WasabiHttpClientFactory.NewWabiSabiApiRequestHandler(Mode.SingleCircuitPerLifetime,
|
||||
roundStateUpdaterCircuit);
|
||||
|
||||
if (termsConditions is null && sharedWabisabiClient is WabiSabiHttpApiClient wabiSabiHttpApiClient)
|
||||
{
|
||||
_ = new WasabiClient(roundStateUpdaterHttpClient)
|
||||
.GetLegalDocumentsAsync(CancellationToken.None)
|
||||
|
||||
_ = wabiSabiHttpApiClient.GetLegalDocumentsAsync(CancellationToken.None)
|
||||
.ContinueWith(task =>
|
||||
{
|
||||
if (task.Status == TaskStatus.RanToCompletion)
|
||||
@@ -191,29 +290,24 @@ public class WabisabiCoordinatorClientInstance
|
||||
}
|
||||
});
|
||||
}
|
||||
sharedWabisabiClient = new WabiSabiHttpApiClient(roundStateUpdaterHttpClient);
|
||||
|
||||
}
|
||||
|
||||
WasabiCoordinatorStatusFetcher = new WasabiCoordinatorStatusFetcher(sharedWabisabiClient, _logger);
|
||||
|
||||
RoundStateUpdater = new RoundStateUpdater(TimeSpan.FromSeconds(5),sharedWabisabiClient, WasabiCoordinatorStatusFetcher);
|
||||
RoundStateUpdater =
|
||||
new RoundStateUpdater(TimeSpan.FromSeconds(5), sharedWabisabiClient, WasabiCoordinatorStatusFetcher);
|
||||
|
||||
CoinPrison = SettingsCoinPrison.CreateFromCoordinatorName(serviceProvider.GetRequiredService<SettingsRepository>(),
|
||||
CoinPrison = SettingsCoinPrison.CreateFromCoordinatorName(
|
||||
serviceProvider.GetRequiredService<SettingsRepository>(),
|
||||
CoordinatorName).GetAwaiter().GetResult();
|
||||
if (coordinatorName == "local")
|
||||
{
|
||||
CoinJoinManager = new CoinJoinManager(coordinatorName, WalletProvider, RoundStateUpdater,
|
||||
sharedWabisabiClient, null,
|
||||
WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison);
|
||||
}
|
||||
else
|
||||
{
|
||||
CoinJoinManager = new CoinJoinManager(coordinatorName,WalletProvider, RoundStateUpdater,null, WasabiHttpClientFactory,
|
||||
WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison);
|
||||
}
|
||||
|
||||
CoinJoinManager = new CoinJoinManager(coordinatorName, WalletProvider, RoundStateUpdater,
|
||||
WasabiHttpClientFactory,
|
||||
WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison);
|
||||
CoinJoinManager.StatusChanged += OnStatusChanged;
|
||||
|
||||
_hostedServices.Register<RoundStateUpdater>(() => RoundStateUpdater, "RoundStateUpdater");
|
||||
_hostedServices.Register<WasabiCoordinatorStatusFetcher>(() => WasabiCoordinatorStatusFetcher, "WasabiCoordinatorStatusFetcher");
|
||||
_hostedServices.Register<CoinJoinManager>(() => CoinJoinManager, "WasabiCoordinatorStatusFetcher");
|
||||
}
|
||||
|
||||
public async Task StopWallet(IWallet wallet)
|
||||
@@ -274,17 +368,14 @@ public class WabisabiCoordinatorClientInstance
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RoundStateUpdater.StartAsync(cancellationToken);
|
||||
WasabiCoordinatorStatusFetcher.StartAsync(cancellationToken);
|
||||
CoinJoinManager.StartAsync(cancellationToken);
|
||||
_ = _hostedServices.StartAllAsync(cancellationToken);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RoundStateUpdater.StopAsync(cancellationToken);
|
||||
WasabiCoordinatorStatusFetcher.StopAsync(cancellationToken);
|
||||
CoinJoinManager.StopAsync(cancellationToken);
|
||||
_ = _hostedServices.StopAllAsync(cancellationToken);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Submodule submodules/btcpayserver updated: 25af9c4227...a921504bcf
Submodule submodules/walletwasabi updated: e3b22a8b0e...39b3c00afd
Reference in New Issue
Block a user