mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
wip
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,28 +46,95 @@ 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;
|
||||
});
|
||||
services.AddHttpClient("wabisabi-coordinator-scripts.onion")
|
||||
services.AddHttpClient("wabisabi-coordinator-scripts.onion")
|
||||
.ConfigurePrimaryHttpMessageHandler(provider =>
|
||||
{
|
||||
var handler = new Socks5HttpClientHandler(provider.GetRequiredService<BTCPayServerOptions>());
|
||||
handler.AllowAutoRedirect = false;
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using System;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class UseWasabiJsonInputFormatterAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -34,6 +32,8 @@ public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
|
||||
private IdempotencyRequestCache IdempotencyRequestCache { get; }
|
||||
private Arena Arena { get; }
|
||||
private CoinJoinFeeRateStatStore CoinJoinFeeRateStatStore { get; }
|
||||
|
||||
|
||||
|
||||
[HttpPost("status")]
|
||||
public async Task<RoundStateResponse> GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken)
|
||||
@@ -41,7 +41,6 @@ public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
|
||||
var response = await Arena.GetStatusAsync(request, cancellationToken);
|
||||
var medians = CoinJoinFeeRateStatStore.GetDefaultMedians();
|
||||
var ret = new RoundStateResponse(response.RoundStates, medians);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
@@ -343,6 +344,8 @@
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var safeName = HttpUtility.UrlEncode(s.Coordinator);
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header d-flex justify-content-between">
|
||||
@@ -350,7 +353,7 @@
|
||||
<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">
|
||||
|
||||
@@ -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>()
|
||||
));
|
||||
|
||||
@@ -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,24 +36,22 @@ 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;
|
||||
|
||||
public readonly ConcurrentDictionary<string, Lazy<Task<BTCPayWallet>>> LoadedWallets = new();
|
||||
|
||||
|
||||
public readonly ConcurrentDictionary<string, Lazy<Task<BTCPayWallet>>> LoadedWallets = new();
|
||||
|
||||
private readonly TaskCompletionSource _initialLoad = new();
|
||||
private readonly CompositeDisposable _disposables = new();
|
||||
|
||||
public WalletProvider(
|
||||
IServiceProvider serviceProvider,
|
||||
StoreRepository storeRepository,
|
||||
IExplorerClientProvider explorerClientProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IUTXOLocker utxoLocker,
|
||||
StoreRepository storeRepository,
|
||||
IExplorerClientProvider explorerClientProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
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;
|
||||
}
|
||||
@@ -99,43 +94,47 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
|
||||
WellknownMetadataKeys.MasterHDKey);
|
||||
var accountKey = await explorerClient.GetMetadataAsync<BitcoinExtKey>(derivationStrategy,
|
||||
WellknownMetadataKeys.AccountHDKey);
|
||||
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
|
||||
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
|
||||
WellknownMetadataKeys.AccountKeyPath);
|
||||
accountKeyPath = accountKeyPath2 ?? accountKeyPath;
|
||||
var smartifier = new Smartifier(_memoryCache,_logger,_serviceProvider.GetRequiredService<WalletRepository>(),
|
||||
explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath);
|
||||
accountKeyPath = accountKeyPath2 ?? accountKeyPath;
|
||||
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,
|
||||
_loggerFactory,
|
||||
_serviceProvider.GetRequiredService<StoreRepository>(),
|
||||
_serviceProvider.GetRequiredService<IMemoryCache>()
|
||||
);
|
||||
|
||||
);
|
||||
}, _logger);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IWallet>> GetWalletsAsync()
|
||||
{
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient("BTC");
|
||||
@@ -145,26 +144,25 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
|
||||
return Array.Empty<IWallet>();
|
||||
}
|
||||
|
||||
await _initialLoad.Task;
|
||||
await _initialLoad.Task;
|
||||
return (await Task.WhenAll(_cachedSettings
|
||||
.Select(pair => GetWalletAsync(pair.Key))))
|
||||
.Where(wallet => wallet is not null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 pullPaymentHostedService = _serviceProvider.GetRequiredService<PullPaymentHostedService>();
|
||||
var payouts = await pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
|
||||
{
|
||||
States = new[]
|
||||
@@ -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,16 +230,16 @@ 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;
|
||||
var btcpayWallet = await existingWallet.Value;
|
||||
if (btcpayWallet is null)
|
||||
{
|
||||
LoadedWallets.TryRemove(storeId, out _);
|
||||
@@ -251,13 +249,14 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
|
||||
btcpayWallet.WabisabiStoreSettings = wabisabiSettings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var w = await GetWalletAsync(storeId);
|
||||
if (w is null)
|
||||
{
|
||||
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,9 +277,8 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
|
||||
await _initialLoad.Task;
|
||||
await Check(@event.WalletId.StoreId, cancellationToken);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
|
||||
return base.StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -290,4 +287,4 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
|
||||
_disposables?.Dispose();
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user