This commit is contained in:
Kukks
2024-10-18 12:09:39 +02:00
parent 9b90e10b66
commit cb98e301da
16 changed files with 150 additions and 412 deletions

View File

@@ -49,7 +49,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary; private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly WalletRepository _walletRepository; private readonly WalletRepository _walletRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler; private readonly IPayoutHandler _bitcoinLikePayoutHandler;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly Services.Wallets.BTCPayWallet _btcPayWallet; private readonly Services.Wallets.BTCPayWallet _btcPayWallet;
private readonly PullPaymentHostedService _pullPaymentHostedService; private readonly PullPaymentHostedService _pullPaymentHostedService;
@@ -63,7 +63,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
WalletRepository walletRepository, WalletRepository walletRepository,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
BitcoinLikePayoutHandler bitcoinLikePayoutHandler, IPayoutHandler bitcoinLikePayoutHandler,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
Services.Wallets.BTCPayWallet btcPayWallet, Services.Wallets.BTCPayWallet btcPayWallet,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,

View File

@@ -1,9 +1,21 @@
using BTCPayServer.Abstractions.Contracts; using System;
using System.Buffers;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Services; using BTCPayServer.Abstractions.Services;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Plugins.Wabisabi.Coordinator; using BTCPayServer.Plugins.Wabisabi.Coordinator;
using BTCPayServer.Services; using BTCPayServer.Services;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletWasabi.WabiSabi.Backend; using WalletWasabi.WabiSabi.Backend;
using WalletWasabi.WabiSabi.Models.Serialization; using WalletWasabi.WabiSabi.Models.Serialization;
@@ -13,7 +25,6 @@ public static class CoordinatorExtensions
{ {
public static void AddWabisabiCoordinator(this IServiceCollection services) public static void AddWabisabiCoordinator(this IServiceCollection services)
{ {
services.AddSingleton<WabisabiCoordinatorService>(); services.AddSingleton<WabisabiCoordinatorService>();
services.AddSingleton<WabiSabiConfig.CoordinatorScriptResolver, WabisabiScriptResolver>(); services.AddSingleton<WabiSabiConfig.CoordinatorScriptResolver, WabisabiScriptResolver>();
services.AddTransient(provider => services.AddTransient(provider =>
@@ -23,6 +34,7 @@ public static class CoordinatorExtensions
{ {
return null; return null;
} }
return new WabiSabiController(s.IdempotencyRequestCache, s.WabiSabiCoordinator.Arena, return new WabiSabiController(s.IdempotencyRequestCache, s.WabiSabiCoordinator.Arena,
s.WabiSabiCoordinator.CoinJoinFeeRateStatStore); s.WabiSabiCoordinator.CoinJoinFeeRateStatStore);
}); });
@@ -34,7 +46,6 @@ public static class CoordinatorExtensions
services.AddHttpClient("wabisabi-coordinator-scripts-no-redirect.onion") services.AddHttpClient("wabisabi-coordinator-scripts-no-redirect.onion")
.ConfigurePrimaryHttpMessageHandler(provider => .ConfigurePrimaryHttpMessageHandler(provider =>
{ {
var handler = new Socks5HttpClientHandler(provider.GetRequiredService<BTCPayServerOptions>()); var handler = new Socks5HttpClientHandler(provider.GetRequiredService<BTCPayServerOptions>());
handler.AllowAutoRedirect = false; handler.AllowAutoRedirect = false;
return handler; return handler;
@@ -47,15 +58,83 @@ public static class CoordinatorExtensions
return handler; return handler;
}); });
services.ConfigureOptions<WasabiInputFormatterJsonMvcOptionsSetup>();
}
}
//inside Startup.ConfigureServices public class WasabiInputFormatter : NewtonsoftJsonInputFormatter
services.AddControllerBasedJsonInputFormatter(formatter => {
formatter.ForControllersWithAttribute<UseWasabiJsonInputFormatterAttribute>()
.ForActionsWithAttribute<UseWasabiJsonInputFormatterAttribute>()
.WithSerializerSettingsConfigurer(settings =>
{ {
settings.Converters = JsonSerializationOptions.Default.Settings.Converters; public WasabiInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool,
}); ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcNewtonsoftJsonOptions jsonOptions) : base(logger,
}); serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
}
public override bool CanRead(InputFormatterContext context)
{
var controllerName = context.HttpContext.Request.RouteValues["controller"]?.ToString();
return controllerName == "WabiSabi";
}
}
public class WasabiOutputFormatter : NewtonsoftJsonOutputFormatter
{
public WasabiOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<char> charPool,
MvcOptions options, MvcNewtonsoftJsonOptions jsonOptions) : base(serializerSettings, charPool, options,
jsonOptions)
{
}
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
{
var controllerName = context.HttpContext.Request.RouteValues["controller"]?.ToString();
return controllerName == "WabiSabi";
}
}
internal sealed class WasabiInputFormatterJsonMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly ILoggerFactory _loggerFactory;
private readonly MvcNewtonsoftJsonOptions _jsonOptions;
private readonly ArrayPool<char> _charPool;
private readonly ObjectPoolProvider _objectPoolProvider;
private readonly JsonSerializerSettings _settings;
public WasabiInputFormatterJsonMvcOptionsSetup(
ILoggerFactory loggerFactory,
IOptions<MvcNewtonsoftJsonOptions> jsonOptions,
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider)
{
ArgumentNullException.ThrowIfNull(loggerFactory);
ArgumentNullException.ThrowIfNull(jsonOptions);
ArgumentNullException.ThrowIfNull(charPool);
ArgumentNullException.ThrowIfNull(objectPoolProvider);
_loggerFactory = loggerFactory;
_jsonOptions = jsonOptions.Value;
_charPool = charPool;
_objectPoolProvider = objectPoolProvider;
_settings = JsonSerializationOptions.Default.Settings;
}
public void Configure(MvcOptions options)
{
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(BitcoinAddress)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Script)));
options.InputFormatters.Insert(0, new WasabiInputFormatter(
_loggerFactory.CreateLogger<WasabiInputFormatter>(),
_settings,
_charPool,
_objectPoolProvider,
options,
_jsonOptions));
options.OutputFormatters.Insert(0, new WasabiOutputFormatter(
_settings,
_charPool,
options,
_jsonOptions));
} }
} }

View File

@@ -1,49 +0,0 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
public abstract class ContextAwareMultiPooledSerializerJsonInputFormatter : ContextAwareSerializerJsonInputFormatter
{
public ContextAwareMultiPooledSerializerJsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcNewtonsoftJsonOptions jsonOptions)
: base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
}
readonly IDictionary<object, ObjectPool<JsonSerializer>> _serializerPools = new ConcurrentDictionary<object, ObjectPool<JsonSerializer>>();
readonly AsyncLocal<object> _currentPoolKeyAsyncLocal = new AsyncLocal<object>();
protected object CurrentPoolKey => _currentPoolKeyAsyncLocal.Value;
protected abstract object GetSerializerPoolKey(InputFormatterContext context);
protected override JsonSerializer CreateJsonSerializer(InputFormatterContext context)
{
object poolKey = GetSerializerPoolKey(context) ?? "";
if(!_serializerPools.TryGetValue(poolKey, out var pool))
{
//clone the settings
var serializerSettings = new JsonSerializerSettings();
foreach(var prop in typeof(JsonSerializerSettings).GetProperties().Where(e => e.CanWrite))
{
prop.SetValue(serializerSettings, prop.GetValue(SerializerSettings));
}
ConfigureSerializerSettings(serializerSettings, poolKey, context);
pool = PoolProvider.Create(new JsonSerializerPooledPolicy(serializerSettings));
_serializerPools[poolKey] = pool;
}
_currentPoolKeyAsyncLocal.Value = poolKey;
return pool.Get();
}
protected override void ReleaseJsonSerializer(JsonSerializer serializer)
{
if(_serializerPools.TryGetValue(CurrentPoolKey ?? "", out var pool))
{
pool.Return(serializer);
}
}
protected virtual void ConfigureSerializerSettings(JsonSerializerSettings serializerSettings, object poolKey, InputFormatterContext context) { }
}

View File

@@ -1,44 +0,0 @@
using System.Buffers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
public abstract class ContextAwareSerializerJsonInputFormatter : NewtonsoftJsonInputFormatter
{
public ContextAwareSerializerJsonInputFormatter(ILogger logger,
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcNewtonsoftJsonOptions jsonOptions) : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
PoolProvider = objectPoolProvider;
}
readonly AsyncLocal<InputFormatterContext> _currentContextAsyncLocal = new AsyncLocal<InputFormatterContext>();
readonly AsyncLocal<ActionContext> _currentActionAsyncLocal = new AsyncLocal<ActionContext>();
protected InputFormatterContext CurrentContext => _currentContextAsyncLocal.Value;
protected ActionContext CurrentAction => _currentActionAsyncLocal.Value;
protected ObjectPoolProvider PoolProvider { get; }
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
_currentContextAsyncLocal.Value = context;
_currentActionAsyncLocal.Value = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>().ActionContext;
return base.ReadRequestBodyAsync(context, encoding);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
_currentContextAsyncLocal.Value = context;
_currentActionAsyncLocal.Value = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>().ActionContext;
return base.ReadRequestBodyAsync(context);
}
protected virtual JsonSerializer CreateJsonSerializer(InputFormatterContext context) => null;
protected override JsonSerializer CreateJsonSerializer()
{
var context = CurrentContext;
return (context == null ? null : CreateJsonSerializer(context)) ?? base.CreateJsonSerializer();
}
}

View File

@@ -1,80 +0,0 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
public class ControllerBasedJsonInputFormatter : ContextAwareMultiPooledSerializerJsonInputFormatter,
IControllerBasedJsonSerializerSettingsBuilder
{
public ControllerBasedJsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, MvcOptions options, MvcNewtonsoftJsonOptions jsonOptions) : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
}
readonly IDictionary<object, Action<JsonSerializerSettings>> _configureSerializerSettings
= new Dictionary<object, Action<JsonSerializerSettings>>();
readonly HashSet<object> _beingAppliedConfigurationKeys = new HashSet<object>();
protected override object GetSerializerPoolKey(InputFormatterContext context)
{
var routeValues = context.HttpContext.GetRouteData()?.Values;
var controllerName = routeValues == null ? null : routeValues["controller"]?.ToString();
if(controllerName != null && _configureSerializerSettings.ContainsKey(controllerName))
{
return controllerName;
}
var actionContext = CurrentAction;
if (actionContext != null && actionContext.ActionDescriptor is ControllerActionDescriptor actionDesc)
{
foreach (var attr in actionDesc.MethodInfo.GetCustomAttributes(true)
.Concat(actionDesc.ControllerTypeInfo.GetCustomAttributes(true)))
{
var key = attr.GetType();
if (_configureSerializerSettings.ContainsKey(key))
{
return key;
}
}
}
return null;
}
public IControllerBasedJsonSerializerSettingsBuilder ForControllers(params string[] controllerNames)
{
foreach(var controllerName in controllerNames ?? Enumerable.Empty<string>())
{
_beingAppliedConfigurationKeys.Add((controllerName ?? "").ToLowerInvariant());
}
return this;
}
public IControllerBasedJsonSerializerSettingsBuilder ForControllersWithAttribute<T>()
{
_beingAppliedConfigurationKeys.Add(typeof(T));
return this;
}
public IControllerBasedJsonSerializerSettingsBuilder ForActionsWithAttribute<T>()
{
_beingAppliedConfigurationKeys.Add(typeof(T));
return this;
}
ControllerBasedJsonInputFormatter IControllerBasedJsonSerializerSettingsBuilder.WithSerializerSettingsConfigurer(Action<JsonSerializerSettings> configurer)
{
if (configurer == null) throw new ArgumentNullException(nameof(configurer));
foreach(var key in _beingAppliedConfigurationKeys)
{
_configureSerializerSettings[key] = configurer;
}
_beingAppliedConfigurationKeys.Clear();
return this;
}
protected override void ConfigureSerializerSettings(JsonSerializerSettings serializerSettings, object poolKey, InputFormatterContext context)
{
if (_configureSerializerSettings.TryGetValue(poolKey, out var configurer))
{
configurer.Invoke(serializerSettings);
}
}
}

View File

@@ -1,70 +0,0 @@

using System;
using System.Buffers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
public class ControllerBasedJsonInputFormatterMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly ILoggerFactory _loggerFactory;
private readonly MvcNewtonsoftJsonOptions _jsonOptions;
private readonly ArrayPool<char> _charPool;
private readonly ObjectPoolProvider _objectPoolProvider;
public ControllerBasedJsonInputFormatterMvcOptionsSetup(
ILoggerFactory loggerFactory,
IOptions<MvcNewtonsoftJsonOptions> jsonOptions,
ArrayPool<char> charPool,
ObjectPoolProvider objectPoolProvider)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (jsonOptions == null)
{
throw new ArgumentNullException(nameof(jsonOptions));
}
if (charPool == null)
{
throw new ArgumentNullException(nameof(charPool));
}
if (objectPoolProvider == null)
{
throw new ArgumentNullException(nameof(objectPoolProvider));
}
_loggerFactory = loggerFactory;
_jsonOptions = jsonOptions.Value;
_charPool = charPool;
_objectPoolProvider = objectPoolProvider;
}
public void Configure(MvcOptions options)
{
//remove the default
options.InputFormatters.RemoveType<NewtonsoftJsonInputFormatter>();
//add our own
var jsonInputLogger = _loggerFactory.CreateLogger<ControllerBasedJsonInputFormatter>();
options.InputFormatters.Add(new ControllerBasedJsonInputFormatter(
jsonInputLogger,
_jsonOptions.SerializerSettings,
_charPool,
_objectPoolProvider,
options,
_jsonOptions));
}
}
//there is a similar class like this implemented by the framework
//but it's a pity that it's internal
//So we define our own class here (which is exactly the same from the source code)
//It's quite simple like this
//This attribute is used as a marker to decorate any controllers
//or actions that you want to apply your custom input formatter

View File

@@ -1,27 +0,0 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
public static class ControllerBasedJsonInputFormatterServiceCollectionExtensions
{
public static IServiceCollection AddControllerBasedJsonInputFormatter(this IServiceCollection services,
Action<ControllerBasedJsonInputFormatter> configureFormatter)
{
if(configureFormatter == null)
{
throw new ArgumentNullException(nameof(configureFormatter));
}
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
return services.ConfigureOptions<ControllerBasedJsonInputFormatterMvcOptionsSetup>()
.PostConfigure<MvcOptions>(o => {
var jsonInputFormatter = o.InputFormatters.OfType<ControllerBasedJsonInputFormatter>().FirstOrDefault();
if(jsonInputFormatter != null)
{
configureFormatter(jsonInputFormatter);
}
});
}
}

View File

@@ -1,10 +0,0 @@
using System;
using Newtonsoft.Json;
public interface IControllerBasedJsonSerializerSettingsBuilder
{
ControllerBasedJsonInputFormatter WithSerializerSettingsConfigurer(Action<JsonSerializerSettings> configurer);
IControllerBasedJsonSerializerSettingsBuilder ForControllers(params string[] controllerNames);
IControllerBasedJsonSerializerSettingsBuilder ForControllersWithAttribute<T>();
IControllerBasedJsonSerializerSettingsBuilder ForActionsWithAttribute<T>();
}

View File

@@ -1,16 +0,0 @@
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
public class JsonSerializerPooledPolicy : IPooledObjectPolicy<JsonSerializer>
{
private readonly JsonSerializerSettings _serializerSettings;
public JsonSerializerPooledPolicy(JsonSerializerSettings serializerSettings)
{
_serializerSettings = serializerSettings;
}
public JsonSerializer Create() => JsonSerializer.Create(_serializerSettings);
public bool Return(JsonSerializer serializer) => true;
}

View File

@@ -1,6 +0,0 @@
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class UseWasabiJsonInputFormatterAttribute : Attribute
{
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Buffers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using WalletWasabi.WabiSabi.Models.Serialization;
namespace WalletWasabi.Backend.Filters;
public class WasabiSpecificJsonSerializerFilter: Attribute, IResultFilter{
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult objectResult)
{
var serializerSettings = new JsonSerializerSettings()
{
Converters = JsonSerializationOptions.Default.Settings.Converters
};
var mvoptions = new MvcOptions();
var jsonFormatter = new NewtonsoftJsonOutputFormatter(
serializerSettings,
ArrayPool<char>.Shared, mvoptions, new MvcNewtonsoftJsonOptions());
objectResult.Formatters.Add(jsonFormatter);
}
}
}

View File

@@ -16,9 +16,7 @@ namespace WalletWasabi.Backend.Controllers;
[ApiController] [ApiController]
[ExceptionTranslate] [ExceptionTranslate]
[LateResponseLoggerFilter] [LateResponseLoggerFilter]
[WasabiSpecificJsonSerializerFilter]
[AllowAnonymous] [AllowAnonymous]
[UseWasabiJsonInputFormatter ]
[Route("plugins/wabisabi-coordinator/wabisabi")] [Route("plugins/wabisabi-coordinator/wabisabi")]
[Produces("application/json")] [Produces("application/json")]
public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
@@ -35,13 +33,14 @@ public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
private Arena Arena { get; } private Arena Arena { get; }
private CoinJoinFeeRateStatStore CoinJoinFeeRateStatStore { get; } private CoinJoinFeeRateStatStore CoinJoinFeeRateStatStore { get; }
[HttpPost("status")] [HttpPost("status")]
public async Task<RoundStateResponse> GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken) public async Task<RoundStateResponse> GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken)
{ {
var response = await Arena.GetStatusAsync(request, cancellationToken); var response = await Arena.GetStatusAsync(request, cancellationToken);
var medians = CoinJoinFeeRateStatStore.GetDefaultMedians(); var medians = CoinJoinFeeRateStatStore.GetDefaultMedians();
var ret = new RoundStateResponse(response.RoundStates, medians); var ret = new RoundStateResponse(response.RoundStates, medians);
return ret; return ret;
} }

View File

@@ -96,7 +96,7 @@ Reputation risks: as the coordinator, the user may be associated with illegal ac
} }
else else
{ {
vm.UriToAdvertise = Request.GetAbsoluteRootUri(); vm.UriToAdvertise = new Uri( Request.GetAbsoluteRootUri() + "plugins/wabisabi-coordinator");
TempData["SuccessMessage"] = $"Will create nostr events that point to { vm.UriToAdvertise }"; TempData["SuccessMessage"] = $"Will create nostr events that point to { vm.UriToAdvertise }";
await _wabisabiCoordinatorService.UpdateSettings( vm); await _wabisabiCoordinatorService.UpdateSettings( vm);
return RedirectToAction(nameof(UpdateWabisabiSettings)); return RedirectToAction(nameof(UpdateWabisabiSettings));

View File

@@ -2,6 +2,7 @@
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using NBitcoin @using NBitcoin
@using System.Security.Claims @using System.Security.Claims
@using System.Web.NBitcoin
@using BTCPayServer @using BTCPayServer
@using BTCPayServer.Abstractions.TagHelpers @using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Client @using BTCPayServer.Client
@@ -344,13 +345,15 @@
continue; continue;
} }
var safeName = HttpUtility.UrlEncode(s.Coordinator);
<div class="card mt-3"> <div class="card mt-3">
<div class="card-header d-flex justify-content-between"> <div class="card-header d-flex justify-content-between">
<div> <div>
<div class="d-flex"> <div class="d-flex">
<h3>@coordinator.CoordinatorDisplayName</h3> <h3>@coordinator.CoordinatorDisplayName</h3>
@if (coordinator.CoordinatorName != "local" && coordinator.CoordinatorName != "zksnacks") @if (coordinator.CoordinatorName != "local")
{ {
<button name="command" type="submit" value="remove-coordinator:@(coordinator.CoordinatorName)" class="btn btn-link txt-danger" permission="@Policies.CanModifyServerSettings">Remove</button> <button name="command" type="submit" value="remove-coordinator:@(coordinator.CoordinatorName)" class="btn btn-link txt-danger" permission="@Policies.CanModifyServerSettings">Remove</button>
} }
@@ -369,7 +372,7 @@
var round = coordinator.RoundStateUpdater.RoundStates.Last(pair => pair.Value.BlameOf == uint256.Zero).Value; var round = coordinator.RoundStateUpdater.RoundStates.Last(pair => pair.Value.BlameOf == uint256.Zero).Value;
var roundParameters = round.CoinjoinState.Parameters; var roundParameters = round.CoinjoinState.Parameters;
<div class="modal modal-lg fade" id="config-@s.Coordinator"> <div class="modal modal-lg fade" id="config-@safeName">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -426,7 +429,7 @@
</div> </div>
</div> </div>
<a class="px-2 cursor-pointer" <a class="px-2 cursor-pointer"
data-bs-toggle="modal" data-bs-target="#config-@s.Coordinator"> data-bs-toggle="modal" data-bs-target="#config-@safeName">
Coordinator Config Coordinator Config
</a> </a>
@if (Model.Settings[index].RoundWhenEnabled is not null && !BTCPayWallet.IsRoundOk(roundParameters, Model.Settings[index])) @if (Model.Settings[index].RoundWhenEnabled is not null && !BTCPayWallet.IsRoundOk(roundParameters, Model.Settings[index]))
@@ -436,7 +439,7 @@
} }
<a class="px-2 w-100 cursor-pointer" <a class="px-2 w-100 cursor-pointer"
data-bs-toggle="modal" data-bs-target="#terms-@s.Coordinator" data-bs-toggle="modal" data-bs-target="#terms-@safeName"
style=" style="
right: 0; right: 0;
text-align: right; text-align: right;
@@ -465,7 +468,7 @@
</div> </div>
</div> </div>
<div class="modal modal-lg fade" id="terms-@s.Coordinator"> <div class="modal modal-lg fade" id="terms-@safeName">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">

View File

@@ -48,7 +48,6 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
utxoLocker, utxoLocker,
provider.GetRequiredService<EventAggregator>(), provider.GetRequiredService<EventAggregator>(),
provider.GetRequiredService<ILogger<WalletProvider>>(), provider.GetRequiredService<ILogger<WalletProvider>>(),
provider.GetRequiredService<BTCPayNetworkProvider>(),
provider.GetRequiredService<PaymentMethodHandlerDictionary>(), provider.GetRequiredService<PaymentMethodHandlerDictionary>(),
provider.GetRequiredService<IMemoryCache>() provider.GetRequiredService<IMemoryCache>()
)); ));

View File

@@ -1,11 +1,9 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Common; using BTCPayServer.Common;
using BTCPayServer.Data; using BTCPayServer.Data;
@@ -38,7 +36,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly ILogger<WalletProvider> _logger; private readonly ILogger<WalletProvider> _logger;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary; private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
@@ -55,7 +52,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
IUTXOLocker utxoLocker, IUTXOLocker utxoLocker,
EventAggregator eventAggregator, EventAggregator eventAggregator,
ILogger<WalletProvider> logger, ILogger<WalletProvider> logger,
BTCPayNetworkProvider networkProvider,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
IMemoryCache memoryCache) : base(TimeSpan.FromMinutes(5)) IMemoryCache memoryCache) : base(TimeSpan.FromMinutes(5))
{ {
@@ -66,7 +62,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
_networkProvider = networkProvider;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary; _paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_memoryCache = memoryCache; _memoryCache = memoryCache;
} }
@@ -102,40 +97,44 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy, var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
WellknownMetadataKeys.AccountKeyPath); WellknownMetadataKeys.AccountKeyPath);
accountKeyPath = accountKeyPath2 ?? accountKeyPath; accountKeyPath = accountKeyPath2 ?? accountKeyPath;
var smartifier = new Smartifier(_memoryCache,_logger,_serviceProvider.GetRequiredService<WalletRepository>(), var smartifier = new Smartifier(_memoryCache, _logger,
_serviceProvider.GetRequiredService<WalletRepository>(),
explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath); explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath);
if (masterKey is null || accountKey is null || accountKeyPath is null) if (masterKey is null || accountKey is null || accountKeyPath is null)
{ {
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier); keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier);
}else }
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, masterKey, accountKey, smartifier); else
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, masterKey, accountKey,
smartifier);
} }
else else
{ {
var smartifier = new Smartifier(_memoryCache, _logger,_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient, var smartifier = new Smartifier(_memoryCache, _logger,
_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
derivationStrategy, name, UtxoLocker, accountKeyPath); derivationStrategy, name, UtxoLocker, accountKeyPath);
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier); keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier);
} }
var payoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
return new BTCPayWallet( return new BTCPayWallet(
_serviceProvider.GetRequiredService<PaymentMethodHandlerDictionary>(), _serviceProvider.GetRequiredService<PaymentMethodHandlerDictionary>(),
_serviceProvider.GetRequiredService<WalletRepository>(), _serviceProvider.GetRequiredService<WalletRepository>(),
_serviceProvider.GetRequiredService<BTCPayNetworkProvider>(), _serviceProvider.GetRequiredService<BTCPayNetworkProvider>(),
_serviceProvider.GetRequiredService<BitcoinLikePayoutHandler>(), _serviceProvider.GetRequiredService<PayoutMethodHandlerDictionary>()[payoutMethodId],
_serviceProvider.GetRequiredService<BTCPayNetworkJsonSerializerSettings>(), _serviceProvider.GetRequiredService<BTCPayNetworkJsonSerializerSettings>(),
_serviceProvider.GetRequiredService<Services.Wallets.BTCPayWalletProvider>().GetWallet("BTC"), _serviceProvider.GetRequiredService<Services.Wallets.BTCPayWalletProvider>().GetWallet("BTC"),
_serviceProvider.GetRequiredService<PullPaymentHostedService>(),derivationStrategy, explorerClient, keychain, _serviceProvider.GetRequiredService<PullPaymentHostedService>(), derivationStrategy, explorerClient,
keychain,
name, wabisabiStoreSettings, UtxoLocker, name, wabisabiStoreSettings, UtxoLocker,
_loggerFactory, _loggerFactory,
_serviceProvider.GetRequiredService<StoreRepository>(), _serviceProvider.GetRequiredService<StoreRepository>(),
_serviceProvider.GetRequiredService<IMemoryCache>() _serviceProvider.GetRequiredService<IMemoryCache>()
); );
}, _logger); }, _logger);
} }
public async Task<IEnumerable<IWallet>> GetWalletsAsync() public async Task<IEnumerable<IWallet>> GetWalletsAsync()
{ {
var explorerClient = _explorerClientProvider.GetExplorerClient("BTC"); var explorerClient = _explorerClientProvider.GetExplorerClient("BTC");
@@ -152,10 +151,8 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
} }
public async Task ResetWabisabiStuckPayouts(string[] storeIds) public async Task ResetWabisabiStuckPayouts(string[] storeIds)
{ {
await _initialLoad.Task; await _initialLoad.Task;
@@ -164,6 +161,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
{ {
return; return;
} }
var pullPaymentHostedService = _serviceProvider.GetRequiredService<PullPaymentHostedService>(); var pullPaymentHostedService = _serviceProvider.GetRequiredService<PullPaymentHostedService>();
var payouts = await pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery() var payouts = await pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{ {
@@ -197,7 +195,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
protected override async Task ActionAsync(CancellationToken cancel) protected override async Task ActionAsync(CancellationToken cancel)
{ {
// var toCheck = LoadedWallets.Keys.ToList(); // var toCheck = LoadedWallets.Keys.ToList();
// while (toCheck.Any()) // while (toCheck.Any())
// { // {
@@ -214,7 +211,8 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
if (LoadedWallets.TryGetValue(storeId, out var currentWallet)) if (LoadedWallets.TryGetValue(storeId, out var currentWallet))
{ {
await UnloadWallet(storeId); await UnloadWallet(storeId);
if(_cachedSettings.TryGetValue(storeId , out var settings) && settings.Settings.Any(coordinatorSettings => coordinatorSettings.Enabled)) if (_cachedSettings.TryGetValue(storeId, out var settings) &&
settings.Settings.Any(coordinatorSettings => coordinatorSettings.Enabled))
await GetWalletAsync(storeId); await GetWalletAsync(storeId);
await GetWalletAsync(storeId); await GetWalletAsync(storeId);
} }
@@ -232,13 +230,13 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
public async Task SettingsUpdated(string storeId, WabisabiStoreSettings wabisabiSettings) public async Task SettingsUpdated(string storeId, WabisabiStoreSettings wabisabiSettings)
{ {
_cachedSettings.AddOrReplace(storeId, wabisabiSettings); _cachedSettings.AddOrReplace(storeId, wabisabiSettings);
if (!wabisabiSettings.Active || wabisabiSettings.Settings.All(settings => !settings.Enabled)) if (!wabisabiSettings.Active || wabisabiSettings.Settings.All(settings => !settings.Enabled))
{ {
await UnloadWallet(storeId); await UnloadWallet(storeId);
} }
if (LoadedWallets.TryGetValue(storeId, out var existingWallet)) if (LoadedWallets.TryGetValue(storeId, out var existingWallet))
{ {
var btcpayWallet = await existingWallet.Value; var btcpayWallet = await existingWallet.Value;
@@ -258,6 +256,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
await UnloadWallet(storeId); await UnloadWallet(storeId);
} }
} }
public override Task StartAsync(CancellationToken cancellationToken) public override Task StartAsync(CancellationToken cancellationToken)
{ {
Task.Run(async () => Task.Run(async () =>
@@ -270,7 +269,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
{ {
await _initialLoad.Task; await _initialLoad.Task;
await UnloadWallet(@event.StoreId); await UnloadWallet(@event.StoreId);
})); }));
_disposables.Add(_eventAggregator.SubscribeAsync<WalletChangedEvent>(async @event => _disposables.Add(_eventAggregator.SubscribeAsync<WalletChangedEvent>(async @event =>
{ {
@@ -279,7 +277,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
await _initialLoad.Task; await _initialLoad.Task;
await Check(@event.WalletId.StoreId, cancellationToken); await Check(@event.WalletId.StoreId, cancellationToken);
} }
})); }));
return base.StartAsync(cancellationToken); return base.StartAsync(cancellationToken);