This commit is contained in:
Kukks
2023-02-27 13:06:16 +01:00
parent ccabbf1a81
commit c447c97028
11 changed files with 230 additions and 127 deletions

View File

@@ -13,7 +13,7 @@
<PropertyGroup>
<Product>Wabisabi Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.15</Version>
<Version>1.0.16</Version>
</PropertyGroup>
<!-- Plugin development properties -->
@@ -43,7 +43,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.21" />
<PackageReference Include="NNostr.Client" Version="0.0.23" />
</ItemGroup>
<Target Name="DeleteExampleFile" AfterTargets="Publish">
<RemoveDir Directories="$(PublishDir)\Microservices" />

View File

@@ -110,7 +110,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public int AnonScoreTarget => WabisabiStoreSettings.PlebMode? 2: WabisabiStoreSettings.AnonymitySetTarget;
public bool ConsolidationMode => !WabisabiStoreSettings.PlebMode && WabisabiStoreSettings.ConsolidationMode;
public TimeSpan FeeRateMedianTimeFrame { get; } = TimeSpan.FromHours(KeyManager.DefaultFeeRateMedianTimeFrameHours);
public TimeSpan FeeRateMedianTimeFrame => TimeSpan.FromHours(WabisabiStoreSettings.PlebMode?
KeyManager.DefaultFeeRateMedianTimeFrameHours: WabisabiStoreSettings.FeeRateMedianTimeFrameHours);
public bool RedCoinIsolation => !WabisabiStoreSettings.PlebMode &&WabisabiStoreSettings.RedCoinIsolation;
public bool BatchPayments => WabisabiStoreSettings.PlebMode || WabisabiStoreSettings.BatchPayments;

View File

@@ -3,11 +3,13 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Common;
using BTCPayServer.Configuration;
using BTCPayServer.Plugins.Wabisabi;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -33,6 +35,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
private readonly IMemoryCache _memoryCache;
private readonly WabisabiCoordinatorClientInstanceManager _instanceManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly LinkGenerator _linkGenerator;
public readonly IdempotencyRequestCache IdempotencyRequestCache;
@@ -44,7 +47,8 @@ public class WabisabiCoordinatorService : PeriodicRunner
IOptions<DataDirectories> dataDirectories, IExplorerClientProvider clientProvider, IMemoryCache memoryCache,
WabisabiCoordinatorClientInstanceManager instanceManager,
IHttpClientFactory httpClientFactory,
IServiceProvider serviceProvider) : base(TimeSpan.FromMinutes(15))
IServiceProvider serviceProvider,
LinkGenerator linkGenerator) : base(TimeSpan.FromMinutes(15))
{
_settingsRepository = settingsRepository;
_dataDirectories = dataDirectories;
@@ -52,6 +56,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
_memoryCache = memoryCache;
_instanceManager = instanceManager;
_httpClientFactory = httpClientFactory;
_linkGenerator = linkGenerator;
_socks5HttpClientHandler = serviceProvider.GetRequiredService<Socks5HttpClientHandler>();
IdempotencyRequestCache = new(memoryCache);
}
@@ -196,12 +201,13 @@ public class WabisabiCoordinatorService : PeriodicRunner
s.UriToAdvertise is not null)
{
var uri = new Uri(s.UriToAdvertise, "plugins/wabisabi-coordinator/wabisabi");
await Nostr.Publish(s.NostrRelay,
new[]
{
await Nostr.CreateCoordinatorDiscoveryEvent(network, s.NostrIdentity, s.UriToAdvertise,
await Nostr.CreateCoordinatorDiscoveryEvent(network, s.NostrIdentity, uri,
s.CoordinatorDescription)
}, _socks5HttpClientHandler, cancel);
},s.UriToAdvertise.IsOnion()? _socks5HttpClientHandler: null, cancel);
}
}
}

View File

@@ -11,7 +11,7 @@ public class WabisabiCoordinatorSettings
public bool Enabled { get; set; } = false;
public string NostrIdentity { get; set; }
public Uri NostrRelay { get; set; } = new Uri("wss://relay.nostr.info");
public Uri NostrRelay { get; set; } = new Uri("wss://nostr.mutinywallet.com");
public List<DiscoveredCoordinator> DiscoveredCoordinators { get; set; } = new();
@@ -45,5 +45,6 @@ public class DiscoveredCoordinator
{
public Uri Uri { get; set; }
public string Name { get; set; }
public string Relay { get; set; }
public string Description { get; set; }
}

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Services;
@@ -23,26 +25,37 @@ public class Nostr
public static async Task Publish(
Uri relayUri,
NostrEvent[] evts,
Socks5HttpClientHandler httpClientHandler,
Socks5HttpClientHandler? httpClientHandler,
CancellationToken cancellationToken )
{
if (!evts.Any())
return;
var ct = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token)
.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token)
.Token;
var client = new NostrClient(relayUri);
var client = new NostrClient(relayUri, socket => socket.Options.Proxy = httpClientHandler?.Proxy);
await client.ConnectAndWaitUntilConnected(ct);
_ = client.ListenForMessages();
var tcs = new TaskCompletionSource();
var ids = evts.Select(evt => evt.Id).ToHashSet();
client.InvalidMessageReceived += (sender, tuple) =>
{
Console.WriteLine(tuple);
};
client.OkReceived += (sender, tuple) =>
{
if (ids.RemoveWhere(s => s == tuple.eventId)> 0 && !ids.Any())
{
tcs.TrySetResult();
}
};
client.EventsReceived += (sender, tuple) =>
{
if (ids.RemoveWhere(s => tuple.events.Any(@event => @event.Id == s)) > 0 && !ids.Any())
{
tcs.TrySetResult();
tcs.TrySetResult();
}
};
await client.CreateSubscription("ack", new[]
@@ -86,18 +99,25 @@ public class Nostr
}
public static async Task<List<DiscoveredCoordinator>> Discover(
Socks5HttpClientHandler? httpClientHandler,
Uri relayUri,
Network currentNetwork,
string ourPubKey,
CancellationToken cancellationToken)
{
using var nostrClient = new NostrClient(relayUri);
var nostrClient = new NostrClient(relayUri, socket => socket.Options.Proxy = httpClientHandler?.Proxy);
await nostrClient.CreateSubscription("nostr-wabisabi-coordinators",
new[]
{
new NostrSubscriptionFilter()
{
Kinds = new[] {Kind}, Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)),
ExtensionData = new Dictionary<string, JsonElement>()
{
["type"] = JsonSerializer.SerializeToElement(TypeTagValue),
["network"] = JsonSerializer.SerializeToElement(currentNetwork.Name.ToLower())
}
}
}, cancellationToken);
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
@@ -107,12 +127,9 @@ public class Nostr
var tcs = new TaskCompletionSource();
Stopwatch stopwatch = new();
stopwatch.Start();
nostrClient.MessageReceived += (sender, s) =>
nostrClient.EoseReceived += (sender, s) =>
{
if (JArray.Parse(s).FirstOrDefault()?.Value<string>() == "EOSE")
{
tcs.SetResult();
}
tcs.SetResult();
};
nostrClient.EventsReceived += (sender, tuple) =>
{

View File

@@ -0,0 +1,81 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.TagHelpers
@using Newtonsoft.Json
@using WalletWasabi.Backend.Controllers
@model WalletWasabi.Backend.Controllers.DiscoveredCoordinator
@inject IScopeProvider ScopeProvider
<button type="button" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings"
data-bs-toggle="modal" data-bs-target="#discover-prompt">
Add Coordinator
</button>
<div class="modal fade" id="discover-prompt" permission="@Policies.CanModifyServerSettings">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Coordinator</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="nostr-tab" data-bs-toggle="tab" data-bs-target="#nostr-tab-pane" type="button" role="tab" aria-selected="true">Nostr</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="manual-tab" data-bs-toggle="tab" data-bs-target="#manual-tab-pane" type="button" role="tab" aria-selected="false">Manual</button>
</li>
</ul>
<div class="tab-content ">
<form asp-action="AddCoordinator" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
class="tab-pane fade show active " id="nostr-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Nostr Relay</label>
<input type="url" class="form-control" asp-for="Relay" required="required">
</div>
</div>
<div class="modal-footer">
<button name="command" type="submit" value="discover" class="btn btn-primary">Discover</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</form>
<form asp-action="AddCoordinator" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
class="tab-pane fade" id="manual-tab-pane" role="tabpanel" tabindex="0">
<div class="modal-body">
<div class="form-group">
<label asp-for="Name" class="form-label">Coordinator Name</label>
<input asp-for="Name" type="text" required class="form-control"/>
</div>
<div class="form-group">
<label asp-for="Uri" class="form-label">Coordinator URL</label>
<input asp-for="Uri" type="url" required class="form-control"/>
</div>
</div>
<div class="modal-footer">
<button name="command" type="submit" class="btn btn-primary">Add</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>
</div>
@if (TempData.TryGetValue("DiscoveredCoordinators" , out var v)&& v is string vs)
{
var discoveredCoordinators = JsonConvert.DeserializeObject<List<DiscoveredCoordinator>>(vs);
foreach (var coordinator in discoveredCoordinators)
{
<partial model="@coordinator" name="Wabisabi/AddCoordinator"/>
}
}

View File

@@ -1,36 +0,0 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Contracts
@model WalletWasabi.Backend.Controllers.DiscoveredCoordinator
@inject IScopeProvider ScopeProvider
<div class="mt-4" permission="@Policies.CanModifyServerSettings">
<button class="btn btn-link mt-4" type="button" data-bs-toggle="collapse" data-bs-target="#manual-coordinator">
Add coordinator manually
</button>
<div class="row collapse" id="manual-coordinator">
<div class="col col-xl-6 mb-4">
<form asp-action="AddCoordinator" asp-controller="WabisabiStore" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" method="post" class="card ">
<div class="card-body">
<h4 class="card-title">Add coordinator manually</h4>
<div class="form-group">
<label asp-for="Name" class="form-label">Coordinator Name</label>
<input asp-for="Name" type="text" required class="form-control"/>
</div>
<div class="form-group">
<label asp-for="Uri" class="form-label">Coordinator URL</label>
<input asp-for="Uri" type="url" required class="form-control"/>
</div>
</div>
<div class="card-footer">
<button class="btn btn-secondary" type="submit">Add</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -13,7 +13,7 @@
@inject WabisabiService WabisabiService;
@inject WalletProvider WalletProvider;
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
@inject ContentSecurityPolicies contentSecurityPolicies
@inject IExplorerClientProvider ExplorerClientProvider
@{
@@ -31,10 +31,9 @@
var storeId = ScopeProvider.GetCurrentStoreId();
// var methods = await Client.GetStoreOnChainPaymentMethods(storeId, true);
// var method = methods.FirstOrDefault(data => data.CryptoCode == "BTC");
var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32);
contentSecurityPolicies.Add("script-src", $"'nonce-{nonce}'");
contentSecurityPolicies.AllowUnsafeHashes();
var mainnet = ExplorerClientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork.ChainName == ChainName.Mainnet;
// var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32);
// contentSecurityPolicies.Add("script-src", $"'nonce-{nonce}'");
// contentSecurityPolicies.AllowUnsafeHashes();
}
@if (available)

View File

@@ -1,10 +1,10 @@
@using BTCPayServer.Plugins.Wabisabi
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Security
@using NBitcoin
@using System.Security.Claims
@using BTCPayServer
@using BTCPayServer.Client
@using BTCPayServer.Configuration
@using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using WalletWasabi.Backend.Controllers
@@ -14,6 +14,7 @@
@inject StoreRepository StoreRepository
@inject WalletProvider WalletProvider
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject BTCPayServerOptions BtcPayServerOptions
@{
var storeId = _scopeProvider.GetCurrentStoreId();
Layout = "../Shared/_NavLayout.cshtml";
@@ -43,6 +44,14 @@
</div>
<form method="post">
@{
if (BtcPayServerOptions.SocksEndpoint is null)
{
<div class="alert alert-danger d-flex align-items-center" role="alert">
<vc:icon symbol="warning"/>
<span class="ms-3">TOR is not configured on this BTCPay Server instance. All communication will be over clearnet and therefore not private!</span>
</div>
}
var wallet = await WalletProvider.GetWalletAsync(storeId);
if (wallet is BTCPayWallet btcPayWallet)
{
@@ -98,6 +107,13 @@
<p class="text-muted">Scores your coinjoined utxos based on how many other utxos in the coinjoin (and other previous coinjoin rounds) had the same value.<br/> Anonset score computation is not an exact science, and when using coordinators with massive liquidity, is not that important as all rounds (past, present, future) contribute to your privacy.</p>
</div>
<div class="form-group">
<label asp-for="FeeRateMedianTimeFrameHours" class="form-label">Mining fee limits in hours</label>
<input type="number" class="form-control" asp-for="FeeRateMedianTimeFrameHours" placeholder="hours" min="0">
<p class="text-muted">Only coinjoin if the mining fee is below the median of the specified number of hours</p>
</div>
<div class="form-group form-check">
<label asp-for="ConsolidationMode" class="form-check-label">Coinsolidation mode</label>
<input asp-for="ConsolidationMode" type="checkbox" class="form-check-input"/>
@@ -217,9 +233,11 @@
</p>
}
@if (coordinator.RoundStateUpdater.AnyRound)
// make sure there is a round that is not a blame round
@if (coordinator.RoundStateUpdater.AnyRound && coordinator.RoundStateUpdater.RoundStates.Any(pair => pair.Value.BlameOf == uint256.Zero))
{
var round = coordinator.RoundStateUpdater.RoundStates.Last().Value.CoinjoinState.Parameters;
var round = coordinator.RoundStateUpdater.RoundStates.Last(pair => pair.Value.BlameOf == uint256.Zero).Value;
var roundParameters = round.CoinjoinState.Parameters;
<div class="modal modal-lg fade" id="config-@s.Coordinator">
<div class="modal-dialog">
@@ -234,7 +252,7 @@
<tr>
<th scope="row">Fee charged</th>
@{
var fee = $"{round.CoordinationFeeRate.Rate * 100}% + Free remixing {(round.CoordinationFeeRate.PlebsDontPayThreshold <= 0 ? string.Empty : $"+ Free under {round.CoordinationFeeRate.PlebsDontPayThreshold.ToDecimal(MoneyUnit.BTC)} BTC")}";
var fee = $"{roundParameters.CoordinationFeeRate.Rate * 100}% + Free remixing {(roundParameters.CoordinationFeeRate.PlebsDontPayThreshold <= 0 ? string.Empty : $"+ Free under {roundParameters.CoordinationFeeRate.PlebsDontPayThreshold.ToDecimal(MoneyUnit.BTC)} BTC")}";
}
<td>
@(fee)
@@ -242,30 +260,30 @@
</tr>
<tr>
<th scope="row">Allowed input amounts</th>
<td>@round.AllowedInputAmounts.Min.ToDecimal(MoneyUnit.BTC) BTC - @round.AllowedInputAmounts.Max.ToDecimal(MoneyUnit.BTC) BTC</td>
<td>@roundParameters.AllowedInputAmounts.Min.ToDecimal(MoneyUnit.BTC) BTC - @roundParameters.AllowedInputAmounts.Max.ToDecimal(MoneyUnit.BTC) BTC</td>
</tr>
<tr>
<th scope="row">Allowed input types</th>
<td>@string.Join(", ", round.AllowedInputTypes)</td>
<td>@string.Join(", ", roundParameters.AllowedInputTypes)</td>
</tr>
<tr>
<th scope="row">Allowed output amounts</th>
<td>@round.AllowedOutputAmounts.Min.ToDecimal(MoneyUnit.BTC) BTC - @round.AllowedOutputAmounts.Max.ToDecimal(MoneyUnit.BTC) BTC</td>
<td>@roundParameters.AllowedOutputAmounts.Min.ToDecimal(MoneyUnit.BTC) BTC - @roundParameters.AllowedOutputAmounts.Max.ToDecimal(MoneyUnit.BTC) BTC</td>
</tr>
<tr>
<th scope="row">Allowed output types</th>
<td>@string.Join(", ", round.AllowedOutputTypes)</td>
<td>@string.Join(", ", roundParameters.AllowedOutputTypes)</td>
</tr>
<tr>
<th scope="row">Minimum inputs</th><td>@round.MinInputCountByRound</td>
<th scope="row">Minimum inputs</th><td>@roundParameters.MinInputCountByRound</td>
</tr>
<tr>
<th scope="row">Maximum inputs</th><td>@round.MaxInputCountByRound</td>
<th scope="row">Maximum inputs</th><td>@roundParameters.MaxInputCountByRound</td>
</tr>
<tr>
<th scope="row">Maximum round registration time</th><td>@round.StandardInputRegistrationTimeout.ToString()</td>
<th scope="row">Maximum round registration time</th><td>@roundParameters.StandardInputRegistrationTimeout.ToString()</td>
</tr>
</table>
@@ -281,7 +299,7 @@
data-bs-toggle="modal" data-bs-target="#config-@s.Coordinator">
Coordinator Config
</a>
@if (Model.Settings[index].RoundWhenEnabled is not null && !BTCPayWallet.IsRoundOk(round, Model.Settings[index]))
@if (Model.Settings[index].RoundWhenEnabled is not null && !BTCPayWallet.IsRoundOk(roundParameters, Model.Settings[index]))
{
<div class="alert alert-danger w-100 mb-0 p-1">Round fees/parameters changed. Coinjoins will not occur unless you accept the new parameters.<button class="btn btn-link alert-link p-0" name="command" type="submit" value="accept-terms:@s.Coordinator"> Accept new terms</button></div>
}
@@ -300,7 +318,7 @@
@{
var canEnable = coordinator.WasabiCoordinatorStatusFetcher.Connected && coordinator.RoundStateUpdater.AnyRound;
}
<div class="form-group form-check form" data-bs-toggle="tooltip" title="@(!canEnable ? "You cannot enable this coordinator until it is connected and a round has been seen" : string.Empty)">
<div class="form-group form-check form" data-bs-toggle="tooltip" title="@(!canEnable ? "You cannot enable this coordinator until it is connected and a round has been seen" : string.Empty)">
@if (Model.Settings[index].RoundWhenEnabled is not null)
{
<input type="hidden" asp-for="Settings[index].RoundWhenEnabled.CoordinationFeeRate"/>
@@ -339,23 +357,15 @@
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Coordinator runner</a>
<a asp-controller="WabisabiStore" asp-action="ListCoinjoins" class="btn btn-secondary mt-2" asp-route-storeId="@storeId">Coinjoins</a>
<button name="command" type="submit" value="discover" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Discover coordinators over Nostr</button>
<a class="btn btn-secondary mt-2" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a>
</form>
@if (ViewBag.DiscoveredCoordinators is List<DiscoveredCoordinator> discoveredCoordinators)
{
foreach (var coordinator in discoveredCoordinators)
{
<partial model="@coordinator" name="Wabisabi/AddCoordinator"/>
}
}
<partial name="Wabisabi/AddManualCoordinator" model="@(new DiscoveredCoordinator())"/>
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Coordinator runner</a>
<partial name="Wabisabi/AddCoordinatorPrompt" model="@(new DiscoveredCoordinator())"/>
<a class="btn btn-secondary mt-2" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a>
@section PageFootContent {
<partial name="_ValidationScriptsPartial"/>

View File

@@ -12,6 +12,7 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Common;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Models.WalletViewModels;
@@ -26,6 +27,7 @@ using NBitcoin;
using NBitcoin.Payment;
using NBitcoin.Secp256k1;
using NBXplorer;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NNostr.Client;
using Org.BouncyCastle.Security;
@@ -46,6 +48,7 @@ namespace BTCPayServer.Plugins.Wabisabi
private readonly IExplorerClientProvider _explorerClientProvider;
private readonly WabisabiCoordinatorService _wabisabiCoordinatorService;
private readonly IAuthorizationService _authorizationService;
private readonly BTCPayServerOptions _options;
private readonly WabisabiCoordinatorClientInstanceManager _instanceManager;
private readonly Socks5HttpClientHandler _socks5HttpClientHandler;
@@ -55,7 +58,8 @@ namespace BTCPayServer.Plugins.Wabisabi
WabisabiCoordinatorService wabisabiCoordinatorService,
WabisabiCoordinatorClientInstanceManager instanceManager,
IAuthorizationService authorizationService,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
BTCPayServerOptions options)
{
_WabisabiService = WabisabiService;
_walletProvider = walletProvider;
@@ -63,6 +67,7 @@ namespace BTCPayServer.Plugins.Wabisabi
_explorerClientProvider = explorerClientProvider;
_wabisabiCoordinatorService = wabisabiCoordinatorService;
_authorizationService = authorizationService;
_options = options;
_instanceManager = instanceManager;
_socks5HttpClientHandler = serviceProvider.GetRequiredService<Socks5HttpClientHandler>();
}
@@ -91,8 +96,8 @@ namespace BTCPayServer.Plugins.Wabisabi
var pieces = command.Split(":");
var actualCommand = pieces[0];
var commandIndex = pieces.Length > 1 ? pieces[1] : null;
var coordinator = pieces.Length > 2 ? pieces[2] : null;
vm.AnonymitySetTarget = Math.Max(2, vm.AnonymitySetTarget);
vm.AnonymitySetTarget = Math.Max(0, vm.AnonymitySetTarget);
vm.FeeRateMedianTimeFrameHours = Math.Max(0, vm.FeeRateMedianTimeFrameHours);
ModelState.Clear();
WabisabiCoordinatorSettings coordSettings;
@@ -106,41 +111,6 @@ namespace BTCPayServer.Plugins.Wabisabi
await _WabisabiService.SetWabisabiForStore(storeId, vm, commandIndex);
TempData["SuccessMessage"] = $"{commandIndex} terms accepted";
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
case "discover":
coordSettings = await _wabisabiCoordinatorService.GetSettings();
var relay = commandIndex ??
coordSettings?.NostrRelay.ToString();
var network = _explorerClientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork;
if (Uri.TryCreate(relay, UriKind.Absolute, out var relayUri))
{
if (network.ChainName == ChainName.Regtest)
{
var evts = new List<NostrEvent>();
for (int i = 0; i < SecureRandom.Shared.Next(1, 10); i++)
{
ECPrivKey.TryCreate(new ReadOnlySpan<byte>(RandomNumberGenerator.GetBytes(32)),
out var key);
evts.Add(await Nostr.CreateCoordinatorDiscoveryEvent(network, key.ToHex(),
new Uri($"https://{Guid.NewGuid()}.com"), "fake regtest coord test"));
}
await Nostr.Publish(relayUri, evts.ToArray(),_socks5HttpClientHandler ,CancellationToken.None);
}
ViewBag.DiscoveredCoordinators = await Nostr.Discover(relayUri,
network,
coordSettings.Key?.CreateXOnlyPubKey().ToHex(), CancellationToken.None);
}
else
{
TempData["ErrorMessage"] = $"No relay uri was provided";
}
return View(vm);
case "remove-coordinator":
if (!(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded)
@@ -197,11 +167,64 @@ var network = _explorerClientProvider.GetExplorerClient("BTC").Network.NBitcoinN
[HttpPost("add-coordinator")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyServerSettings)]
public async Task<IActionResult> AddCoordinator(string storeId, [FromForm] DiscoveredCoordinator viewModel)
public async Task<IActionResult> AddCoordinator(string storeId, [FromForm] DiscoveredCoordinator viewModel, string command )
{
var coordSettings = await _wabisabiCoordinatorService.GetSettings();
if (command == "discover")
{
var network = _explorerClientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork;
if (Uri.TryCreate(viewModel.Relay, UriKind.Absolute, out var relayUri))
{
if (network.ChainName == ChainName.Regtest)
{
var eventsTomake = Random.Shared.Next(1, 5);
var evts = new List<NostrEvent>();
for (var i = 0; i < eventsTomake; i++)
{
ECPrivKey.TryCreate(new ReadOnlySpan<byte>(RandomNumberGenerator.GetBytes(32)),
out var key);
evts.Add(await Nostr.CreateCoordinatorDiscoveryEvent(network, key.ToHex(),
new Uri($"https://{Guid.NewGuid()}.com"), "fake regtest coord test"));
}
await Nostr.Publish(relayUri, evts.ToArray(),null,CancellationToken.None);
}
try
{
var result = await Nostr.Discover(
_socks5HttpClientHandler, relayUri,
network,
coordSettings.Key?.CreateXOnlyPubKey().ToHex(), CancellationToken.None);
if(result.Any())
TempData["DiscoveredCoordinators"] = JsonConvert.SerializeObject(result);
else
TempData["ErrorMessage"] = $"No coordinators found.";
}
catch (Exception e)
{
TempData["ErrorMessage"] = $"Could not discover coordinators: {e.Message}";
}
}
else
{
TempData["ErrorMessage"] = $"No relay uri was provided";
}
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
}
if (viewModel.Name is not null && viewModel.Uri is not null && coordSettings.DiscoveredCoordinators.All(discoveredCoordinator =>
if (viewModel.Name is not null &&
viewModel.Uri is not null &&
!new []{"local", "zksnacks"}.Contains(viewModel.Name.ToLowerInvariant()) &&
coordSettings.DiscoveredCoordinators.All(discoveredCoordinator =>
discoveredCoordinator.Name != viewModel.Name && discoveredCoordinator.Uri != viewModel.Uri))
{
coordSettings.DiscoveredCoordinators.Add(viewModel);

View File

@@ -24,6 +24,7 @@ public class WabisabiStoreSettings
public bool BatchPayments { get; set; } = true;
public int ExtraJoinProbability { get; set; } = 0;
public CrossMixMode CrossMixBetweenCoordinatorsMode { get; set; } = CrossMixMode.WhenFree;
public int FeeRateMedianTimeFrameHours { get; set; }
public enum CrossMixMode
{