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> </PropertyGroup>
<ItemGroup> <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" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

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

View File

@@ -18,13 +18,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Hangfire" Version="1.6.17" /> <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="Hangfire.PostgreSql" Version="1.4.8.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" /> <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="NBitpayClient" Version="1.0.0.13" />
<PackageReference Include="DBreeze" Version="1.87.0" /> <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" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" /> <PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" /> <PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
@@ -34,9 +34,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -55,14 +55,9 @@ namespace BTCPayServer.Configuration
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl); Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile); CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
RequireHttps = conf.GetOrDefault<bool>("requirehttps", false);
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null); PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null); ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
} InternalUrl = conf.GetOrDefault<Uri>("internalurl", null);
public bool RequireHttps
{
get; set;
} }
public string PostgresConnectionString public string PostgresConnectionString
{ {
@@ -74,5 +69,6 @@ namespace BTCPayServer.Configuration
get; get;
set; 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("-n | --network", $"Set the network among ({NetworkInformation.ToStringAll()}) (default: {Network.Main.ToString()})", CommandOptionType.SingleValue);
app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue); app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
app.Option("--regtest | -regtest", $"Use regtest", 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("--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("--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("--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; return app;
} }
@@ -77,7 +77,6 @@ namespace BTCPayServer.Configuration
builder.AppendLine("#regtest=0"); builder.AppendLine("#regtest=0");
builder.AppendLine(); builder.AppendLine();
builder.AppendLine("### Server settings ###"); builder.AppendLine("### Server settings ###");
builder.AppendLine("#requirehttps=0");
builder.AppendLine("#port=" + network.DefaultPort); builder.AppendLine("#port=" + network.DefaultPort);
builder.AppendLine("#bind=127.0.0.1"); builder.AppendLine("#bind=127.0.0.1");
builder.AppendLine(); builder.AppendLine();

View File

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

View File

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

View File

@@ -71,10 +71,10 @@ namespace BTCPayServer
return res; 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()); 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 BTCPayServer.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -34,6 +35,7 @@ using System.Threading;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using BTCPayServer.Authentication; using BTCPayServer.Authentication;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using BTCPayServer.Logging;
namespace BTCPayServer.Hosting 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) public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
{ {
services.AddDbContext<ApplicationDbContext>((provider, o) => services.AddDbContext<ApplicationDbContext>((provider, o) =>
@@ -106,18 +95,35 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<SettingsRepository>(); services.TryAddSingleton<SettingsRepository>();
services.TryAddSingleton<InvoicePaymentNotification>(); services.TryAddSingleton<InvoicePaymentNotification>();
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value); services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
services.TryAddSingleton<IConfigureOptions<MvcOptions>, BTCPayServerConfigureOptions>(); services.TryAddSingleton<InvoiceRepository>(o =>
services.TryAddSingleton(o =>
{ {
var runtime = new BTCPayServerRuntime(); var opts = o.GetRequiredService<BTCPayServerOptions>();
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>()); var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
return runtime; 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.AddSingleton<BTCPayServerEnvironment>();
services.TryAddSingleton<TokenRepository>(); services.TryAddSingleton<TokenRepository>();
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network); 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<StoreRepository>();
services.TryAddSingleton<BTCPayWallet>(); services.TryAddSingleton<BTCPayWallet>();
services.TryAddSingleton<CurrencyNameTable>(); services.TryAddSingleton<CurrencyNameTable>();
@@ -127,10 +133,16 @@ namespace BTCPayServer.Hosting
BlockTarget = 20, BlockTarget = 20,
ExplorerClient = o.GetRequiredService<ExplorerClient>() ExplorerClient = o.GetRequiredService<ExplorerClient>()
}); });
services.TryAddSingleton<NBXplorerWaiterAccessor>();
services.AddSingleton<IHostedService, NBXplorerWaiter>();
services.TryAddSingleton<ExplorerClient>(o => services.TryAddSingleton<ExplorerClient>(o =>
{ {
var runtime = o.GetRequiredService<BTCPayServerRuntime>(); var opts = o.GetRequiredService<BTCPayServerOptions>();
return runtime.Explorer; var explorer = new ExplorerClient(opts.Network, opts.Explorer);
if (!explorer.SetCookieAuth(opts.CookieFile))
explorer.SetNoAuth();
return explorer;
}); });
services.TryAddSingleton<Bitpay>(o => services.TryAddSingleton<Bitpay>(o =>
{ {
@@ -145,9 +157,12 @@ namespace BTCPayServer.Hosting
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))); 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) }; 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<InvoiceNotificationManager>();
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
services.TryAddSingleton<InvoiceWatcherAccessor>();
services.AddSingleton<IHostedService, InvoiceWatcher>();
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>(); services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>(); services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
services.AddTransient<AccessTokenController>(); services.AddTransient<AccessTokenController>();
@@ -174,12 +189,6 @@ namespace BTCPayServer.Hosting
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app) 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()) using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{ {
//Wait the DB is ready //Wait the DB is ready

View File

@@ -31,71 +31,28 @@ namespace BTCPayServer.Hosting
RequestDelegate _Next; RequestDelegate _Next;
CallbackController _CallbackController; CallbackController _CallbackController;
BTCPayServerOptions _Options; BTCPayServerOptions _Options;
private NBXplorerWaiterAccessor _NbxplorerAwaiter;
public BTCPayMiddleware(RequestDelegate next, public BTCPayMiddleware(RequestDelegate next,
TokenRepository tokenRepo, TokenRepository tokenRepo,
BTCPayServerOptions options, BTCPayServerOptions options,
NBXplorerWaiterAccessor nbxplorerAwaiter,
CallbackController callbackController) CallbackController callbackController)
{ {
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo)); _TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
_Next = next ?? throw new ArgumentNullException(nameof(next)); _Next = next ?? throw new ArgumentNullException(nameof(next));
_CallbackController = callbackController; _CallbackController = callbackController;
_Options = options ?? throw new ArgumentNullException(nameof(options)); _Options = options ?? throw new ArgumentNullException(nameof(options));
_NbxplorerAwaiter = (nbxplorerAwaiter ?? throw new ArgumentNullException(nameof(nbxplorerAwaiter)));
} }
bool _Registered; bool _Registered;
public async Task Invoke(HttpContext httpContext) public async Task Invoke(HttpContext httpContext)
{ {
if (!_Registered) RewriteHostIfNeeded(httpContext);
{ await EnsureBlockCallbackRegistered(httpContext);
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);
}
}
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values); httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
var sig = values.FirstOrDefault(); 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) private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
{ {
httpContext.Response.StatusCode = ex.StatusCode; 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 namespace BTCPayServer.Services.Invoices
{ {
public class InvoiceWatcherAccessor
{
public InvoiceWatcher Instance { get; set; }
}
public class InvoiceWatcher : IHostedService public class InvoiceWatcher : IHostedService
{ {
InvoiceRepository _InvoiceRepository; InvoiceRepository _InvoiceRepository;
@@ -23,11 +27,13 @@ namespace BTCPayServer.Services.Invoices
DerivationStrategyFactory _DerivationFactory; DerivationStrategyFactory _DerivationFactory;
InvoiceNotificationManager _NotificationManager; InvoiceNotificationManager _NotificationManager;
BTCPayWallet _Wallet; BTCPayWallet _Wallet;
public InvoiceWatcher(ExplorerClient explorerClient, public InvoiceWatcher(ExplorerClient explorerClient,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
BTCPayWallet wallet, BTCPayWallet wallet,
InvoiceNotificationManager notificationManager) InvoiceNotificationManager notificationManager,
InvoiceWatcherAccessor accessor)
{ {
LongPollingMode = explorerClient.Network == Network.RegTest; LongPollingMode = explorerClient.Network == Network.RegTest;
PollInterval = explorerClient.Network == Network.RegTest ? TimeSpan.FromSeconds(10.0) : TimeSpan.FromMinutes(1.0); 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); _DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository)); _InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager)); _NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
accessor.Instance = this;
} }
public bool LongPollingMode public bool LongPollingMode

View File

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

View File

@@ -78,6 +78,33 @@
</nav> </nav>
@RenderBody() @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"> <footer class="bg-dark">
<div class="container text-right"><span style="font-size:8px;">@env.ToString()</span></div> <div class="container text-right"><span style="font-size:8px;">@env.ToString()</span></div>
</footer> </footer>

View File

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