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 WalletRepository _walletRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler;
private readonly IPayoutHandler _bitcoinLikePayoutHandler;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly Services.Wallets.BTCPayWallet _btcPayWallet;
private readonly PullPaymentHostedService _pullPaymentHostedService;
@@ -63,7 +63,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
WalletRepository walletRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
BitcoinLikePayoutHandler bitcoinLikePayoutHandler,
IPayoutHandler bitcoinLikePayoutHandler,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
Services.Wallets.BTCPayWallet btcPayWallet,
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.Configuration;
using BTCPayServer.Plugins.Wabisabi.Coordinator;
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.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.Models.Serialization;
@@ -13,16 +25,16 @@ public static class CoordinatorExtensions
{
public static void AddWabisabiCoordinator(this IServiceCollection services)
{
services.AddSingleton<WabisabiCoordinatorService>();
services.AddSingleton<WabiSabiConfig.CoordinatorScriptResolver, WabisabiScriptResolver>();
services.AddTransient(provider =>
{
var s = provider.GetRequiredService<WabisabiCoordinatorService>();
if (!s.Started )
if (!s.Started)
{
return null;
}
return new WabiSabiController(s.IdempotencyRequestCache, s.WabiSabiCoordinator.Arena,
s.WabiSabiCoordinator.CoinJoinFeeRateStatStore);
});
@@ -34,7 +46,6 @@ public static class CoordinatorExtensions
services.AddHttpClient("wabisabi-coordinator-scripts-no-redirect.onion")
.ConfigurePrimaryHttpMessageHandler(provider =>
{
var handler = new Socks5HttpClientHandler(provider.GetRequiredService<BTCPayServerOptions>());
handler.AllowAutoRedirect = false;
return handler;
@@ -47,15 +58,83 @@ public static class CoordinatorExtensions
return handler;
});
//inside Startup.ConfigureServices
services.AddControllerBasedJsonInputFormatter(formatter => {
formatter.ForControllersWithAttribute<UseWasabiJsonInputFormatterAttribute>()
.ForActionsWithAttribute<UseWasabiJsonInputFormatterAttribute>()
.WithSerializerSettingsConfigurer(settings =>
{
settings.Converters = JsonSerializationOptions.Default.Settings.Converters;
});
});
services.ConfigureOptions<WasabiInputFormatterJsonMvcOptionsSetup>();
}
}
public class WasabiInputFormatter : NewtonsoftJsonInputFormatter
{
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]
[ExceptionTranslate]
[LateResponseLoggerFilter]
[WasabiSpecificJsonSerializerFilter]
[AllowAnonymous]
[UseWasabiJsonInputFormatter ]
[Route("plugins/wabisabi-coordinator/wabisabi")]
[Produces("application/json")]
public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
@@ -35,13 +33,14 @@ public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
private Arena Arena { get; }
private CoinJoinFeeRateStatStore CoinJoinFeeRateStatStore { get; }
[HttpPost("status")]
public async Task<RoundStateResponse> GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken)
{
var response = await Arena.GetStatusAsync(request, cancellationToken);
var medians = CoinJoinFeeRateStatStore.GetDefaultMedians();
var ret = new RoundStateResponse(response.RoundStates, medians);
return ret;
}

View File

@@ -96,7 +96,7 @@ Reputation risks: as the coordinator, the user may be associated with illegal ac
}
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 }";
await _wabisabiCoordinatorService.UpdateSettings( vm);
return RedirectToAction(nameof(UpdateWabisabiSettings));

View File

@@ -2,6 +2,7 @@
@using BTCPayServer.Abstractions.Contracts
@using NBitcoin
@using System.Security.Claims
@using System.Web.NBitcoin
@using BTCPayServer
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Client
@@ -344,13 +345,15 @@
continue;
}
var safeName = HttpUtility.UrlEncode(s.Coordinator);
<div class="card mt-3">
<div class="card-header d-flex justify-content-between">
<div>
<div class="d-flex">
<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>
}
@@ -369,7 +372,7 @@
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 modal-lg fade" id="config-@safeName">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@@ -426,7 +429,7 @@
</div>
</div>
<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
</a>
@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"
data-bs-toggle="modal" data-bs-target="#terms-@s.Coordinator"
data-bs-toggle="modal" data-bs-target="#terms-@safeName"
style="
right: 0;
text-align: right;
@@ -465,7 +468,7 @@
</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-content">
<div class="modal-header">

View File

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

View File

@@ -1,11 +1,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client.Models;
using BTCPayServer.Common;
using BTCPayServer.Data;
@@ -28,7 +26,7 @@ using PayoutData = BTCPayServer.Data.PayoutData;
namespace BTCPayServer.Plugins.Wabisabi;
public class WalletProvider : PeriodicRunner,IWalletProvider
public class WalletProvider : PeriodicRunner, IWalletProvider
{
private ConcurrentDictionary<string, WabisabiStoreSettings>? _cachedSettings;
private readonly IServiceProvider _serviceProvider;
@@ -38,7 +36,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
private readonly ILoggerFactory _loggerFactory;
private readonly EventAggregator _eventAggregator;
private readonly ILogger<WalletProvider> _logger;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly IMemoryCache _memoryCache;
@@ -55,7 +52,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
IUTXOLocker utxoLocker,
EventAggregator eventAggregator,
ILogger<WalletProvider> logger,
BTCPayNetworkProvider networkProvider,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
IMemoryCache memoryCache) : base(TimeSpan.FromMinutes(5))
{
@@ -66,7 +62,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_loggerFactory = loggerFactory;
_eventAggregator = eventAggregator;
_logger = logger;
_networkProvider = networkProvider;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_memoryCache = memoryCache;
}
@@ -102,40 +97,44 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
WellknownMetadataKeys.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);
if (masterKey is null || accountKey is null || accountKeyPath is null)
{
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
{
var smartifier = new Smartifier(_memoryCache, _logger,_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
var smartifier = new Smartifier(_memoryCache, _logger,
_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
derivationStrategy, name, UtxoLocker, accountKeyPath);
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier);
}
var payoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
return new BTCPayWallet(
_serviceProvider.GetRequiredService<PaymentMethodHandlerDictionary>(),
_serviceProvider.GetRequiredService<WalletRepository>(),
_serviceProvider.GetRequiredService<BTCPayNetworkProvider>(),
_serviceProvider.GetRequiredService<BitcoinLikePayoutHandler>(),
_serviceProvider.GetRequiredService<PayoutMethodHandlerDictionary>()[payoutMethodId],
_serviceProvider.GetRequiredService<BTCPayNetworkJsonSerializerSettings>(),
_serviceProvider.GetRequiredService<Services.Wallets.BTCPayWalletProvider>().GetWallet("BTC"),
_serviceProvider.GetRequiredService<PullPaymentHostedService>(),derivationStrategy, explorerClient, keychain,
_serviceProvider.GetRequiredService<PullPaymentHostedService>(), derivationStrategy, explorerClient,
keychain,
name, wabisabiStoreSettings, UtxoLocker,
_loggerFactory,
_serviceProvider.GetRequiredService<StoreRepository>(),
_serviceProvider.GetRequiredService<IMemoryCache>()
);
}, _logger);
}
public async Task<IEnumerable<IWallet>> GetWalletsAsync()
{
var explorerClient = _explorerClientProvider.GetExplorerClient("BTC");
@@ -152,18 +151,17 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
}
public async Task ResetWabisabiStuckPayouts(string[] storeIds)
{
await _initialLoad.Task;
storeIds??= _cachedSettings?.Keys.ToArray() ?? Array.Empty<string>();
storeIds ??= _cachedSettings?.Keys.ToArray() ?? Array.Empty<string>();
if (!storeIds.Any())
{
return;
}
var pullPaymentHostedService = _serviceProvider.GetRequiredService<PullPaymentHostedService>();
var payouts = await pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
@@ -176,7 +174,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
});
var inProgressPayouts = payouts
.Where(data => data.GetProofBlobJson()?.Value<string>("proofType") == "Wabisabi").ToArray();
if(!inProgressPayouts.Any())
if (!inProgressPayouts.Any())
return;
_logger.LogInformation("Moving {count} stuck coinjoin payouts to AwaitingPayment", inProgressPayouts.Length);
foreach (PayoutData payout in inProgressPayouts)
@@ -197,7 +195,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
protected override async Task ActionAsync(CancellationToken cancel)
{
// var toCheck = LoadedWallets.Keys.ToList();
// while (toCheck.Any())
// {
@@ -214,7 +211,8 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
if (LoadedWallets.TryGetValue(storeId, out var currentWallet))
{
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);
}
@@ -232,13 +230,13 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
public async Task SettingsUpdated(string storeId, WabisabiStoreSettings 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);
}
if (LoadedWallets.TryGetValue(storeId, out var existingWallet))
{
var btcpayWallet = await existingWallet.Value;
@@ -258,6 +256,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
await UnloadWallet(storeId);
}
}
public override Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(async () =>
@@ -270,7 +269,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
{
await _initialLoad.Task;
await UnloadWallet(@event.StoreId);
}));
_disposables.Add(_eventAggregator.SubscribeAsync<WalletChangedEvent>(async @event =>
{
@@ -279,7 +277,6 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
await _initialLoad.Task;
await Check(@event.WalletId.StoreId, cancellationToken);
}
}));
return base.StartAsync(cancellationToken);