Can start without NBXplorer being ready

This commit is contained in:
nicolas.dorier
2017-12-17 01:04:20 +09:00
parent d4dd6c84bc
commit c6959bb0bc
18 changed files with 384 additions and 231 deletions

View File

@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170720-02" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -106,19 +106,21 @@ namespace BTCPayServer.Tests
.UseStartup<Startup>()
.Build();
_Host.Start();
Runtime = (BTCPayServerRuntime)_Host.Services.GetService(typeof(BTCPayServerRuntime));
var watcher = (InvoiceWatcher)_Host.Services.GetService(typeof(InvoiceWatcher));
}
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
public BTCPayServerRuntime Runtime
{
get; set;
var waiter = ((NBXplorerWaiterAccessor)_Host.Services.GetService(typeof(NBXplorerWaiterAccessor))).Instance;
while(waiter.State != NBXplorerState.Ready)
{
Thread.Sleep(10);
}
}
public string HostName
{
get;
internal set;
}
public InvoiceRepository InvoiceRepository { get; private set; }
public T GetService<T>()
{

View File

@@ -307,13 +307,13 @@ namespace BTCPayServer.Tests
Eventually(() =>
{
var textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
{
StoreId = user.StoreId,
TextSearch = invoice.OrderId
}).GetAwaiter().GetResult();
Assert.Equal(1, textSearchResult.Length);
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
{
StoreId = user.StoreId,
TextSearch = invoice.Id

View File

@@ -13,12 +13,15 @@ services:
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_NBXPLORERURL: http://nbxplorer:32838/
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
TESTS_FAKECALLBACK: 'true'
TESTS_FAKECALLBACK: 'false'
TESTS_PORT: 80
TESTS_HOSTNAME: tests
TEST_ECLAIR1: http://eclair1:8080/
TEST_ECLAIR2: http://eclair2:8080/
expose:
- "80"
links:
- bitcoind
- nbxplorer
- eclair1
- eclair2
@@ -39,7 +42,7 @@ services:
- eclair2
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.29
image: nicolasdorier/nbxplorer:1.0.0.32
ports:
- "32838:32838"
expose:

View File

@@ -18,13 +18,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hangfire" Version="1.6.17" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.1" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="NBitcoin" Version="4.0.0.48" />
<PackageReference Include="NBitcoin" Version="4.0.0.50" />
<PackageReference Include="NBitpayClient" Version="1.0.0.13" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.18" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.20" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
@@ -34,9 +34,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@@ -55,14 +55,9 @@ namespace BTCPayServer.Configuration
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
RequireHttps = conf.GetOrDefault<bool>("requirehttps", false);
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
}
public bool RequireHttps
{
get; set;
InternalUrl = conf.GetOrDefault<Uri>("internalurl", null);
}
public string PostgresConnectionString
{
@@ -74,5 +69,6 @@ namespace BTCPayServer.Configuration
get;
set;
}
public Uri InternalUrl { get; private set; }
}
}

View File

@@ -1,110 +0,0 @@
using BTCPayServer.Authentication;
using Microsoft.Extensions.Logging;
using BTCPayServer.Logging;
using DBreeze;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Wallets;
namespace BTCPayServer.Configuration
{
public class BTCPayServerRuntime : IDisposable
{
public ExplorerClient Explorer
{
get;
private set;
}
public void Configure(BTCPayServerOptions opts)
{
ConfigureAsync(opts).GetAwaiter().GetResult();
}
public async Task ConfigureAsync(BTCPayServerOptions opts)
{
Network = opts.Network;
Explorer = new ExplorerClient(opts.Network, opts.Explorer);
if (!Explorer.SetCookieAuth(opts.CookieFile))
Explorer.SetNoAuth();
CancellationTokenSource cts = new CancellationTokenSource(30000);
try
{
Logs.Configuration.LogInformation("Trying to connect to explorer " + Explorer.Address.AbsoluteUri);
await Explorer.WaitServerStartedAsync(cts.Token).ConfigureAwait(false);
Logs.Configuration.LogInformation("Connection successfull");
}
catch (Exception ex)
{
throw new ConfigException($"Could not connect to NBXplorer, {ex.Message}");
}
ApplicationDbContextFactory dbContext = null;
if (opts.PostgresConnectionString == null)
{
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
}
else
{
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
}
DBFactory = dbContext;
InvoiceRepository = new InvoiceRepository(dbContext, CreateDBPath(opts, "InvoiceDB"), Network);
_Resources.Add(InvoiceRepository);
}
private static string CreateDBPath(BTCPayServerOptions opts, string name)
{
var dbpath = Path.Combine(opts.DataDir, name);
if (!Directory.Exists(dbpath))
Directory.CreateDirectory(dbpath);
return dbpath;
}
List<IDisposable> _Resources = new List<IDisposable>();
public void Dispose()
{
lock (_Resources)
{
foreach (var r in _Resources)
{
r.Dispose();
}
_Resources.Clear();
}
}
public Network Network
{
get;
private set;
}
public InvoiceRepository InvoiceRepository
{
get;
set;
}
public ApplicationDbContextFactory DBFactory
{
get;
set;
}
}
}

View File

@@ -26,11 +26,11 @@ namespace BTCPayServer.Configuration
app.Option("-n | --network", $"Set the network among ({NetworkInformation.ToStringAll()}) (default: {Network.Main.ToString()})", CommandOptionType.SingleValue);
app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
app.Option("--regtest | -regtest", $"Use regtest", CommandOptionType.BoolValue);
app.Option("--requirehttps", $"Will redirect to https version of the website (default: false)", CommandOptionType.BoolValue);
app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
app.Option("--explorerurl", $"Url of the NBxplorer (default: : Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
app.Option("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
app.Option("--externalurl", $"The expected external url of this service, use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
app.Option("--externalurl", $"The expected external url of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
app.Option("--internalurl", $"The expected internal url of this service, this set NBXplorer callback addresses (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
return app;
}
@@ -77,7 +77,6 @@ namespace BTCPayServer.Configuration
builder.AppendLine("#regtest=0");
builder.AppendLine();
builder.AppendLine("### Server settings ###");
builder.AppendLine("#requirehttps=0");
builder.AppendLine("#port=" + network.DefaultPort);
builder.AppendLine("#bind=127.0.0.1");
builder.AppendLine();

View File

@@ -15,6 +15,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
namespace BTCPayServer.Controllers
{
@@ -31,16 +32,19 @@ namespace BTCPayServer.Controllers
Network _Network;
InvoiceWatcher _Watcher;
ExplorerClient _Explorer;
BTCPayServerOptions _Options;
public CallbackController(SettingsRepository repo,
ExplorerClient explorer,
InvoiceWatcher watcher,
InvoiceWatcherAccessor watcher,
BTCPayServerOptions options,
Network network)
{
_Settings = repo;
_Network = network;
_Watcher = watcher;
_Watcher = watcher.Instance;
_Explorer = explorer;
_Options = options;
}
[Route("callbacks/transactions")]
@@ -79,7 +83,7 @@ namespace BTCPayServer.Controllers
public async Task<Uri> GetCallbackUriAsync(HttpRequest request)
{
string token = await GetToken();
return new Uri(request.GetAbsoluteRoot() + "/callbacks/transactions?token=" + token);
return BuildCallbackUri(request, "callbacks/transactions?token=" + token);
}
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme, HttpRequest request)
@@ -103,12 +107,18 @@ namespace BTCPayServer.Controllers
public async Task<Uri> GetCallbackBlockUriAsync(HttpRequest request)
{
string token = await GetToken();
return new Uri(request.GetAbsoluteRoot() + "/callbacks/blocks?token=" + token);
return BuildCallbackUri(request, "callbacks/blocks?token=" + token);
}
public async Task<Uri> RegisterCallbackBlockUriAsync(HttpRequest request)
private Uri BuildCallbackUri(HttpRequest request, string callbackPath)
{
string baseUrl = _Options.InternalUrl == null ? request.GetAbsoluteRoot() : _Options.InternalUrl.AbsolutePath;
baseUrl = baseUrl.WithTrailingSlash();
return new Uri(baseUrl + callbackPath);
}
public async Task<Uri> RegisterCallbackBlockUriAsync(Uri uri)
{
var uri = await GetCallbackBlockUriAsync(request);
await _Explorer.SubscribeToBlocksAsync(uri);
return uri;
}

View File

@@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers
BTCPayWallet wallet,
IRateProvider rateProvider,
StoreRepository storeRepository,
InvoiceWatcher watcher,
InvoiceWatcherAccessor watcher,
ExplorerClient explorerClient,
IFeeProvider feeProvider)
{
@@ -73,7 +73,7 @@ namespace BTCPayServer.Controllers
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
_Watcher = watcher ?? throw new ArgumentNullException(nameof(watcher));
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
_UserManager = userManager;
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
}

View File

@@ -71,10 +71,10 @@ namespace BTCPayServer
return res;
}
public static HtmlString ToSrvModel(this object o)
public static HtmlString ToJSVariableModel(this object o, string variableName)
{
var encodedJson = JavaScriptEncoder.Default.Encode(o.ToJson());
return new HtmlString("var srvModel = JSON.parse('" + encodedJson + "');");
return new HtmlString($"var {variableName} = JSON.parse('" + encodedJson + "');");
}

View File

@@ -1,4 +1,5 @@
using BTCPayServer.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using System;
using System.Collections.Generic;
@@ -34,6 +35,7 @@ using System.Threading;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Authentication;
using Microsoft.Extensions.Caching.Memory;
using BTCPayServer.Logging;
namespace BTCPayServer.Hosting
{
@@ -83,19 +85,6 @@ namespace BTCPayServer.Hosting
}
}
}
class BTCPayServerConfigureOptions : IConfigureOptions<MvcOptions>
{
BTCPayServerOptions _Options;
public BTCPayServerConfigureOptions(BTCPayServerOptions options)
{
_Options = options;
}
public void Configure(MvcOptions options)
{
if (_Options.RequireHttps)
options.Filters.Add(new RequireHttpsAttribute());
}
}
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>((provider, o) =>
@@ -106,18 +95,35 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<SettingsRepository>();
services.TryAddSingleton<InvoicePaymentNotification>();
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
services.TryAddSingleton<IConfigureOptions<MvcOptions>, BTCPayServerConfigureOptions>();
services.TryAddSingleton(o =>
services.TryAddSingleton<InvoiceRepository>(o =>
{
var runtime = new BTCPayServerRuntime();
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
return runtime;
var opts = o.GetRequiredService<BTCPayServerOptions>();
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
var dbpath = Path.Combine(opts.DataDir, "InvoiceDB");
if (!Directory.Exists(dbpath))
Directory.CreateDirectory(dbpath);
return new InvoiceRepository(dbContext, dbpath, opts.Network);
});
services.AddSingleton<BTCPayServerEnvironment>();
services.TryAddSingleton<TokenRepository>();
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
{
var opts = o.GetRequiredService<BTCPayServerOptions>();
ApplicationDbContextFactory dbContext = null;
if (opts.PostgresConnectionString == null)
{
var connStr = "Data Source=" + Path.Combine(opts.DataDir, "sqllite.db");
Logs.Configuration.LogInformation($"SQLite DB used ({connStr})");
dbContext = new ApplicationDbContextFactory(DatabaseType.Sqlite, connStr);
}
else
{
Logs.Configuration.LogInformation($"Postgres DB used ({opts.PostgresConnectionString})");
dbContext = new ApplicationDbContextFactory(DatabaseType.Postgres, opts.PostgresConnectionString);
}
return dbContext;
});
services.TryAddSingleton<StoreRepository>();
services.TryAddSingleton<BTCPayWallet>();
services.TryAddSingleton<CurrencyNameTable>();
@@ -127,10 +133,16 @@ namespace BTCPayServer.Hosting
BlockTarget = 20,
ExplorerClient = o.GetRequiredService<ExplorerClient>()
});
services.TryAddSingleton<NBXplorerWaiterAccessor>();
services.AddSingleton<IHostedService, NBXplorerWaiter>();
services.TryAddSingleton<ExplorerClient>(o =>
{
var runtime = o.GetRequiredService<BTCPayServerRuntime>();
return runtime.Explorer;
var opts = o.GetRequiredService<BTCPayServerOptions>();
var explorer = new ExplorerClient(opts.Network, opts.Explorer);
if (!explorer.SetCookieAuth(opts.CookieFile))
explorer.SetNoAuth();
return explorer;
});
services.TryAddSingleton<Bitpay>(o =>
{
@@ -145,9 +157,12 @@ namespace BTCPayServer.Hosting
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
return new CachedRateProvider(new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay }), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
});
services.TryAddSingleton<InvoiceWatcher>();
services.TryAddSingleton<InvoiceNotificationManager>();
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
services.TryAddSingleton<InvoiceWatcherAccessor>();
services.AddSingleton<IHostedService, InvoiceWatcher>();
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
services.AddTransient<AccessTokenController>();
@@ -174,12 +189,6 @@ namespace BTCPayServer.Hosting
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)
{
if (app.ApplicationServices.GetRequiredService<BTCPayServerOptions>().RequireHttps)
{
var options = new RewriteOptions().AddRedirectToHttps();
app.UseRewriter(options);
}
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
//Wait the DB is ready

View File

@@ -31,71 +31,28 @@ namespace BTCPayServer.Hosting
RequestDelegate _Next;
CallbackController _CallbackController;
BTCPayServerOptions _Options;
private NBXplorerWaiterAccessor _NbxplorerAwaiter;
public BTCPayMiddleware(RequestDelegate next,
TokenRepository tokenRepo,
BTCPayServerOptions options,
NBXplorerWaiterAccessor nbxplorerAwaiter,
CallbackController callbackController)
{
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
_Next = next ?? throw new ArgumentNullException(nameof(next));
_CallbackController = callbackController;
_Options = options ?? throw new ArgumentNullException(nameof(options));
_NbxplorerAwaiter = (nbxplorerAwaiter ?? throw new ArgumentNullException(nameof(nbxplorerAwaiter)));
}
bool _Registered;
public async Task Invoke(HttpContext httpContext)
{
if (!_Registered)
{
var callback = await _CallbackController.RegisterCallbackBlockUriAsync(httpContext.Request);
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
_Registered = true;
}
// Make sure that code executing after this point think that the external url has been hit.
if(_Options.ExternalUrl != null)
{
httpContext.Request.Scheme = _Options.ExternalUrl.Scheme;
if(_Options.ExternalUrl.IsDefaultPort)
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host);
else
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host, _Options.ExternalUrl.Port);
}
// NGINX pass X-Forwarded-Proto and X-Forwarded-Port, so let's use that to have better guess of the real domain
else
{
ushort? p = null;
if(httpContext.Request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues proto))
{
var scheme = proto.SingleOrDefault();
if(scheme != null)
{
httpContext.Request.Scheme = scheme;
if (scheme == "http")
p = 80;
if (scheme == "https")
p = 443;
}
}
if (httpContext.Request.Headers.TryGetValue("X-Forwarded-Port", out StringValues port))
{
var portString = port.SingleOrDefault();
if(portString != null && ushort.TryParse(portString, out ushort pp))
{
p = pp;
}
}
if(p.HasValue)
{
bool isDefault = httpContext.Request.Scheme == "http" && p.Value == 80;
isDefault |= httpContext.Request.Scheme == "https" && p.Value == 443;
if (isDefault)
httpContext.Request.Host = new HostString(httpContext.Request.Host.Host);
else
httpContext.Request.Host = new HostString(httpContext.Request.Host.Host, p.Value);
}
}
RewriteHostIfNeeded(httpContext);
await EnsureBlockCallbackRegistered(httpContext);
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
var sig = values.FirstOrDefault();
@@ -150,6 +107,75 @@ namespace BTCPayServer.Hosting
}
}
private void RewriteHostIfNeeded(HttpContext httpContext)
{
// Make sure that code executing after this point think that the external url has been hit.
if (_Options.ExternalUrl != null)
{
httpContext.Request.Scheme = _Options.ExternalUrl.Scheme;
if (_Options.ExternalUrl.IsDefaultPort)
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host);
else
httpContext.Request.Host = new HostString(_Options.ExternalUrl.Host, _Options.ExternalUrl.Port);
}
// NGINX pass X-Forwarded-Proto and X-Forwarded-Port, so let's use that to have better guess of the real domain
else
{
ushort? p = null;
if (httpContext.Request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues proto))
{
var scheme = proto.SingleOrDefault();
if (scheme != null)
{
httpContext.Request.Scheme = scheme;
if (scheme == "http")
p = 80;
if (scheme == "https")
p = 443;
}
}
if (httpContext.Request.Headers.TryGetValue("X-Forwarded-Port", out StringValues port))
{
var portString = port.SingleOrDefault();
if (portString != null && ushort.TryParse(portString, out ushort pp))
{
p = pp;
}
}
if (p.HasValue)
{
bool isDefault = httpContext.Request.Scheme == "http" && p.Value == 80;
isDefault |= httpContext.Request.Scheme == "https" && p.Value == 443;
if (isDefault)
httpContext.Request.Host = new HostString(httpContext.Request.Host.Host);
else
httpContext.Request.Host = new HostString(httpContext.Request.Host.Host, p.Value);
}
}
}
private async Task EnsureBlockCallbackRegistered(HttpContext httpContext)
{
if (!_Registered)
{
var callback = await _CallbackController.GetCallbackBlockUriAsync(httpContext.Request);
var unused = _NbxplorerAwaiter.Instance.WhenReady(async c =>
{
try
{
await _CallbackController.RegisterCallbackBlockUriAsync(callback);
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
}
catch (Exception ex)
{
Logs.PayServer.LogError(ex, "Could not register block callback");
}
return true;
});
_Registered = true;
}
}
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
{
httpContext.Response.StatusCode = ex.StatusCode;

View File

@@ -0,0 +1,184 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Logging;
using Microsoft.Extensions.Hosting;
using NBXplorer;
using NBXplorer.Models;
using System.Collections.Concurrent;
namespace BTCPayServer
{
public class NBXplorerWaiterAccessor
{
public NBXplorerWaiter Instance { get; set; }
}
public enum NBXplorerState
{
NotConnected,
Synching,
Ready
}
public class NBXplorerWaiter : IHostedService
{
public NBXplorerWaiter(ExplorerClient client, NBXplorerWaiterAccessor accessor)
{
_Client = client;
accessor.Instance = this;
}
ExplorerClient _Client;
Timer _Timer;
ManualResetEventSlim _Idle = new ManualResetEventSlim(true);
public Task StartAsync(CancellationToken cancellationToken)
{
_Timer = new Timer(Callback, null, 0, (int)TimeSpan.FromMinutes(1.0).TotalMilliseconds);
return Task.CompletedTask;
}
void Callback(object state)
{
if (!_Idle.IsSet)
return;
try
{
_Idle.Reset();
CheckStatus().GetAwaiter().GetResult();
}
catch (Exception ex)
{
Logs.PayServer.LogError(ex, "Error while checking NBXplorer state");
}
finally
{
_Idle.Set();
}
}
async Task CheckStatus()
{
while (await StepAsync())
{
}
List<Task> tasks = new List<Task>();
if (State == NBXplorerState.Ready)
{
while (_WhenReady.TryDequeue(out Func<ExplorerClient, Task> act))
{
tasks.Add(act(_Client));
}
}
await Task.WhenAll(tasks);
}
private async Task<bool> StepAsync()
{
var oldState = State;
StatusResult status = null;
switch (State)
{
case NBXplorerState.NotConnected:
status = await GetStatusWithTimeout();
if (status != null)
{
if (status.IsFullySynched())
{
State = NBXplorerState.Ready;
}
else
{
State = NBXplorerState.Synching;
}
}
break;
case NBXplorerState.Synching:
status = await GetStatusWithTimeout();
if (status == null)
{
State = NBXplorerState.NotConnected;
}
else if (status.IsFullySynched())
{
State = NBXplorerState.Ready;
}
break;
case NBXplorerState.Ready:
status = await GetStatusWithTimeout();
if (status == null)
{
State = NBXplorerState.NotConnected;
}
else if (!status.IsFullySynched())
{
State = NBXplorerState.Synching;
}
break;
}
LastStatus = status;
if (oldState != State)
{
Logs.PayServer.LogInformation($"NBXplorerWaiter status changed: {oldState} => {State}");
}
return oldState != State;
}
public Task<T> WhenReady<T>(Func<ExplorerClient, Task<T>> act)
{
if (State == NBXplorerState.Ready)
return act(_Client);
TaskCompletionSource<T> completion = new TaskCompletionSource<T>();
_WhenReady.Enqueue(async client =>
{
try
{
var result = await act(client);
completion.SetResult(result);
}
catch (Exception ex)
{
completion.SetException(ex);
}
});
return completion.Task;
}
ConcurrentQueue<Func<ExplorerClient, Task>> _WhenReady = new ConcurrentQueue<Func<ExplorerClient, Task>>();
private async Task<StatusResult> GetStatusWithTimeout()
{
CancellationTokenSource cts = new CancellationTokenSource();
using (cts)
{
var cancellation = cts.Token;
while (!cancellation.IsCancellationRequested)
{
try
{
var status = await _Client.GetStatusAsync(cancellation).ConfigureAwait(false);
return status;
}
catch (OperationCanceledException) { throw; }
catch { }
}
}
return null;
}
public NBXplorerState State { get; private set; }
public StatusResult LastStatus { get; private set; }
public Task StopAsync(CancellationToken cancellationToken)
{
_Timer.Dispose();
_Timer = null;
_Idle.Wait();
return Task.CompletedTask;
}
}
}

View File

@@ -16,6 +16,10 @@ using BTCPayServer.Services.Wallets;
namespace BTCPayServer.Services.Invoices
{
public class InvoiceWatcherAccessor
{
public InvoiceWatcher Instance { get; set; }
}
public class InvoiceWatcher : IHostedService
{
InvoiceRepository _InvoiceRepository;
@@ -23,11 +27,13 @@ namespace BTCPayServer.Services.Invoices
DerivationStrategyFactory _DerivationFactory;
InvoiceNotificationManager _NotificationManager;
BTCPayWallet _Wallet;
public InvoiceWatcher(ExplorerClient explorerClient,
InvoiceRepository invoiceRepository,
BTCPayWallet wallet,
InvoiceNotificationManager notificationManager)
InvoiceNotificationManager notificationManager,
InvoiceWatcherAccessor accessor)
{
LongPollingMode = explorerClient.Network == Network.RegTest;
PollInterval = explorerClient.Network == Network.RegTest ? TimeSpan.FromSeconds(10.0) : TimeSpan.FromMinutes(1.0);
@@ -36,6 +42,7 @@ namespace BTCPayServer.Services.Invoices
_DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
accessor.Instance = this;
}
public bool LongPollingMode

View File

@@ -26,7 +26,7 @@
<script src="https://code.jquery.com/jquery-3.2.1.min.js"
crossorigin="anonymous"></script>
<script type="text/javascript">
@Model.ToSrvModel()
@Model.ToJSVariableModel("srvModel")
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.7.1/clipboard.min.js"></script>
<script src="~/js/vue.js" type="text/javascript" defer="defer"></script>

View File

@@ -78,6 +78,33 @@
</nav>
@RenderBody()
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<form method="post" action="/invoices/invalidatepaid">
<input id="invoiceId" name="invoiceId" type="hidden" />
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Set Invoice status to Invalid</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<p>Are you sure you want to invalidate this transaction? This action is NOT undoable!</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger">Yes, make invoice Invalid</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</form>
</div>
<footer class="bg-dark">
<div class="container text-right"><span style="font-size:8px;">@env.ToString()</span></div>
</footer>

View File

@@ -6,7 +6,7 @@ RUN dotnet restore
COPY BTCPayServer/. .
RUN dotnet publish --output /app/ --configuration Release
FROM microsoft/aspnetcore:2.0.0
FROM microsoft/aspnetcore:2.0.3
WORKDIR /app
RUN mkdir /datadir
@@ -14,4 +14,4 @@ ENV BTCPAY_DATADIR=/datadir
VOLUME /datadir
COPY --from=builder "/app" .
ENTRYPOINT ["dotnet", "BTCPayServer.dll"]
ENTRYPOINT ["dotnet", "BTCPayServer.dll"]