Compare commits

..

32 Commits

Author SHA1 Message Date
ecb82f2cc9 do not send IPN if not set 2018-01-15 00:22:40 +09:00
f340c6eb7f Add more events to invoice 2018-01-14 22:06:06 +09:00
ba0e080816 Invoices has events recorded 2018-01-14 21:48:23 +09:00
bb3d107309 Fix legacy rate not being correct for alt only payment 2018-01-14 15:26:14 +09:00
8517b222bf Add redirect url to the invoice page 2018-01-14 15:01:09 +09:00
aed32204b5 Fix crashing ManageController 2018-01-14 14:52:15 +09:00
6b4eeff3f1 add tests, do not returns bitcoinAddress field if not supported by the invoice 2018-01-13 22:01:09 +09:00
e3cc589ebb fix nbxplorer 2018-01-13 21:07:04 +09:00
4a152e8ffc fix build 2018-01-13 17:32:08 +09:00
d54a9474d1 Fixing exception thrown when invoice is paid and supporting only LTC 2018-01-13 17:23:09 +09:00
98472211fc bump 2018-01-13 12:55:05 +09:00
099c9fa1f9 Fix balance calculation when there is unconfirmed tx 2018-01-13 12:53:56 +09:00
5226b77ffc Fix bug happening when removing data of nbxplorer 2018-01-13 02:28:23 +09:00
290779ee39 bump 2018-01-13 01:48:43 +09:00
4f39a8060c Fixing bug of uncorrectly unassigned addresses 2018-01-13 01:48:19 +09:00
92caa98dfb bump 2018-01-13 01:06:18 +09:00
df7bb9e2f8 Add info about nbxplorer synching 2018-01-13 01:05:38 +09:00
02a039d695 Fixing bug when targetting testnet 2018-01-12 22:36:13 +09:00
b5e4c803aa fix bad network throwing nullreferenceexception 2018-01-12 22:08:20 +09:00
2b7c70622f bump 2018-01-12 21:55:13 +09:00
88779e7129 Make sure websockets does not throw, fix annying warning of emails 2018-01-12 18:32:46 +09:00
6beb7abfd2 fix test 2018-01-12 17:04:47 +09:00
a1ebedc0d1 Fix unit test 2018-01-12 16:54:57 +09:00
d5ad0cdb39 Fix a edge case "The instance of entity type 'HistoricalAddressInvoiceData'" 2018-01-12 16:42:10 +09:00
39fb8dbb6a better handle case when BTC is not supported by a store 2018-01-12 16:30:34 +09:00
58194cb060 Fix tests, clean code of Options 2018-01-12 16:00:31 +09:00
ef165e15bf Fix config discovery when datadir is set 2018-01-12 13:45:25 +09:00
bf871c46ec update nbxplorer 2018-01-12 13:32:27 +09:00
52331e057f update nbxplorer 2018-01-12 13:16:17 +09:00
b59021a0be Cleanup the design for multi currencies support, use a single nbxplorer instead of two 2018-01-12 11:54:57 +09:00
8596e16feb make default configuration just work with multi chains 2018-01-11 22:52:28 +09:00
223558c01d Adding transaction caching 2018-01-11 21:01:00 +09:00
49 changed files with 1487 additions and 497 deletions

View File

@ -45,21 +45,13 @@ namespace BTCPayServer.Tests
}
public Uri LTCNBXplorerUri { get; set; }
public string CookieFile
{
get; set;
}
public Uri ServerUri
{
get;
set;
}
public ExtKey HDPrivateKey
{
get; set;
}
public string Postgres
{
get; set;
@ -75,23 +67,31 @@ namespace BTCPayServer.Tests
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
string chain = ChainType.Regtest.ToNetwork().Name;
string chainDirectory = Path.Combine(_Directory, chain);
if (!Directory.Exists(chainDirectory))
Directory.CreateDirectory(chainDirectory);
HDPrivateKey = new ExtKey();
StringBuilder config = new StringBuilder();
config.AppendLine($"regtest=1");
config.AppendLine($"{chain.ToLowerInvariant()}=1");
config.AppendLine($"port={Port}");
config.AppendLine($"explorer.url={NBXplorerUri.AbsoluteUri}");
config.AppendLine($"chains=btc,ltc");
config.AppendLine($"btc.explorer.url={NBXplorerUri.AbsoluteUri}");
config.AppendLine($"btc.explorer.cookiefile=0");
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"explorer.cookiefile={CookieFile}");
config.AppendLine($"ltc.explorer.cookiefile={CookieFile}");
config.AppendLine($"hdpubkey={HDPrivateKey.Neuter().ToString(Network.RegTest)}");
config.AppendLine($"ltc.explorer.cookiefile=0");
if (Postgres != null)
config.AppendLine($"postgres=" + Postgres);
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
var confPath = Path.Combine(chainDirectory, "settings.config");
File.WriteAllText(confPath, config.ToString());
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath });
_Host = new WebHostBuilder()
.UseConfiguration(conf)

View File

@ -12,7 +12,7 @@ namespace BTCPayServer.Tests
public EclairTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost)
{
this.parent = parent;
RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), parent.Network);
//RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), parent.Network);
P2PHost = parent.GetEnvironment(environmentName + "_HOST", defaultHost);
}

View File

@ -47,12 +47,12 @@ namespace BTCPayServer.Tests
Directory.CreateDirectory(_Directory);
var network = new BTCPayNetworkProvider(Network);
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_RPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), network.GetNetwork("BTC").NBitcoinNetwork);
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), network.GetNetwork("LTC").NBitcoinNetwork);
NetworkProvider = new BTCPayNetworkProvider(ChainType.Regtest);
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork);
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork);
ExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_NBXPLORERURL", "http://127.0.0.1:32838/")));
LTCExplorerClient = new ExplorerClient(Network, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32839/")));
ExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("BTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_BTCNBXPLORERURL", "http://127.0.0.1:32838/")));
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
{
@ -108,6 +108,8 @@ namespace BTCPayServer.Tests
{
return new TestAccount(this);
}
public BTCPayNetworkProvider NetworkProvider { get; private set; }
public RPCClient ExplorerNode
{
get; set;
@ -224,12 +226,6 @@ namespace BTCPayServer.Tests
get; set;
}
public Network Network
{
get;
set;
} = Network.RegTest;
public void Dispose()
{
if (PayTester != null)

View File

@ -46,17 +46,21 @@ namespace BTCPayServer.Tests
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId);
}
public StoresController CreateStore()
public StoresController CreateStore(string cryptoCode = null)
{
return CreateStoreAsync().GetAwaiter().GetResult();
return CreateStoreAsync(cryptoCode).GetAwaiter().GetResult();
}
public async Task<StoresController> CreateStoreAsync()
public string CryptoCode { get; set; } = "BTC";
public async Task<StoresController> CreateStoreAsync(string cryptoCode = null)
{
ExtKey = new ExtKey().GetWif(parent.Network);
cryptoCode = cryptoCode ?? CryptoCode;
SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode);
ExtKey = new ExtKey().GetWif(SupportedNetwork.NBitcoinNetwork);
var store = parent.PayTester.GetController<StoresController>(UserId);
await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" });
StoreId = store.CreatedStoreId;
DerivationScheme = new DerivationStrategyFactory(parent.Network).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
await store.UpdateStore(StoreId, new StoreViewModel()
{
SpeedPolicy = SpeedPolicy.MediumSpeed
@ -64,13 +68,15 @@ namespace BTCPayServer.Tests
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
{
CryptoCurrency = "BTC",
CryptoCurrency = cryptoCode,
DerivationSchemeFormat = "BTCPay",
DerivationScheme = DerivationScheme.ToString(),
}, "Save");
return store;
}
public BTCPayNetwork SupportedNetwork { get; set; }
public void RegisterDerivationScheme(string crytoCode)
{
RegisterDerivationSchemeAsync(crytoCode).GetAwaiter().GetResult();

View File

@ -43,11 +43,19 @@ namespace BTCPayServer.Tests
entity.TxFee = Money.Coins(0.1m);
entity.Rate = 5000;
var cryptoData = entity.GetCryptoData("BTC", null);
Assert.NotNull(cryptoData); // Should use legacy data to build itself
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
entity.ProductInformation = new ProductInformation() { Price = 5000 };
// Some check that handling legacy stuff does not break things
var cryptoData = entity.GetCryptoData("BTC", null, true);
cryptoData.Calculate();
Assert.NotNull(cryptoData);
Assert.Null(entity.GetCryptoData("BTC", null, false));
entity.SetCryptoData(new CryptoData() { ParentEntity = entity, Rate = entity.Rate, CryptoCode = "BTC", TxFee = entity.TxFee });
Assert.NotNull(entity.GetCryptoData("BTC", null, false));
Assert.NotNull(entity.GetCryptoData("BTC", null, true));
////////////////////
var accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(1.1m), accounting.Due);
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
@ -330,13 +338,13 @@ namespace BTCPayServer.Tests
false, //subtractfeefromamount
true, //replaceable
}).ResultString);
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, tester.Network);
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork);
Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(payment1, invoice.BtcPaid);
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, tester.Network);
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork);
});
var tx = tester.ExplorerNode.GetRawTransaction(new uint256(tx1));
@ -384,6 +392,65 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanHaveLTCOnlyStore()
{
using (var tester = ServerTester.Create())
{
tester.Start();
var user = tester.NewAccount();
user.CryptoCode = "LTC";
user.GrantAccess();
// First we try payment with a merchant having only BTC
var invoice = user.BitPay.CreateInvoice(new Invoice()
{
Price = 500,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
Assert.Single(invoice.CryptoInfo);
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
var cashCow = tester.LTCExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = Money.Coins(0.1m);
cashCow.SendToAddress(invoiceAddress, firstPayment);
Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid);
});
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
var controller = tester.PayTester.GetController<InvoiceController>(null);
var checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null).GetAwaiter().GetResult()).Value;
Assert.Single(checkout.AvailableCryptos);
Assert.Equal("LTC", checkout.CryptoCode);
//////////////////////
// Despite it is called BitcoinAddress it should be LTC because BTC is not available
Assert.Null(invoice.BitcoinAddress);
Assert.NotEqual(1.0, invoice.Rate);
Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate
cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due);
Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status);
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null).GetAwaiter().GetResult()).Value;
Assert.Equal("paid", checkout.Status);
});
}
}
[Fact]
public void CanPayWithTwoCurrencies()
{

View File

@ -10,18 +10,17 @@ services:
context: ..
dockerfile: BTCPayServer.Tests/Dockerfile
environment:
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_NBXPLORERURL: http://bitcoin-nbxplorer:32838/
TESTS_LTCNBXPLORERURL: http://litecoin-nbxplorer:32839/
TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
TESTS_PORT: 80
TESTS_HOSTNAME: tests
expose:
- "80"
links:
- bitcoin-nbxplorer
- litecoin-nbxplorer
- nbxplorer
- postgres
extra_hosts:
- "tests:127.0.0.1"
@ -34,45 +33,32 @@ services:
regtest=1
connect=bitcoind:39388
links:
- bitcoin-nbxplorer
- litecoin-nbxplorer
- nbxplorer
- postgres
bitcoin-nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.45
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.70
ports:
- "32838:32838"
expose:
- "32838"
environment:
NBXPLORER_NETWORK: regtest
NBXPLORER_RPCURL: http://bitcoind:43782/
NBXPLORER_RPCUSER: ceiwHEbqWI83
NBXPLORER_RPCPASSWORD: DwubwWsoo3
NBXPLORER_NODEENDPOINT: bitcoind:39388
NBXPLORER_CHAINS: "btc,ltc"
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
NBXPLORER_LTCRPCURL: http://litecoind:43782/
NBXPLORER_LTCNODEENDPOINT: litecoind:39388
NBXPLORER_LTCRPCUSER: ceiwHEbqWI83
NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_VERBOSE: 1
NBXPLORER_NOAUTH: 1
links:
- bitcoind
litecoin-nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.45
ports:
- "32839:32839"
expose:
- "32839"
environment:
NBXPLORER_NETWORK: ltc-regtest
NBXPLORER_RPCURL: http://litecoind:43782/
NBXPLORER_RPCUSER: ceiwHEbqWI83
NBXPLORER_RPCPASSWORD: DwubwWsoo3
NBXPLORER_NODEENDPOINT: litecoind:39388
NBXPLORER_BIND: 0.0.0.0:32839
NBXPLORER_VERBOSE: 1
NBXPLORER_NOAUTH: 1
links:
- litecoind
- litecoind
bitcoind:
container_name: btcpayserver_dev_bitcoind
@ -86,15 +72,11 @@ services:
rpcport=43782
port=39388
whitelist=0.0.0.0/0
zmqpubrawblock=tcp://0.0.0.0:29000
zmqpubrawtx=tcp://0.0.0.0:29000
txindex=1
ports:
- "43782:43782"
expose:
- "43782" # RPC
- "39388" # P2P
- "29000" # zmq
litecoind:
container_name: btcpayserver_dev_litecoind
@ -108,15 +90,11 @@ services:
rpcport=43782
port=39388
whitelist=0.0.0.0/0
zmqpubrawblock=tcp://0.0.0.0:29000
zmqpubrawtx=tcp://0.0.0.0:29000
txindex=1
ports:
- "43783:43782"
expose:
- "43782" # RPC
- "39388" # P2P
- "29000" # zmq
postgres:
image: postgres:9.6.5

View File

@ -1,12 +1,48 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public class BTCPayDefaultSettings
{
static BTCPayDefaultSettings()
{
_Settings = new Dictionary<ChainType, BTCPayDefaultSettings>();
foreach (var chainType in new[] { ChainType.Main, ChainType.Test, ChainType.Regtest })
{
var btcNetwork = (chainType == ChainType.Main ? Network.Main :
chainType == ChainType.Regtest ? Network.RegTest :
chainType == ChainType.Test ? Network.TestNet : throw new NotSupportedException(chainType.ToString()));
var settings = new BTCPayDefaultSettings();
_Settings.Add(chainType, settings);
settings.ChainType = chainType;
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", btcNetwork.Name);
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultPort = (chainType == ChainType.Main ? 23000 :
chainType == ChainType.Regtest ? 23002 :
chainType == ChainType.Test ? 23001 : throw new NotSupportedException(chainType.ToString()));
}
}
static Dictionary<ChainType, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(ChainType chainType)
{
return _Settings[chainType];
}
public string DefaultDataDirectory { get; set; }
public string DefaultConfigurationFile { get; set; }
public ChainType ChainType { get; internal set; }
public int DefaultPort { get; set; }
}
public class BTCPayNetwork
{
public Network NBitcoinNetwork { get; set; }
@ -25,6 +61,10 @@ namespace BTCPayServer
}
public string CryptoImagePath { get; set; }
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public override string ToString()
{
return CryptoCode;

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using NBitcoin;
using NBitpayClient;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitBitcoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTC");
var coinaverage = new CoinAverageRateProvider("BTC");
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
var btcRate = new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay });
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoin",
DefaultRateProvider = btcRate,
CryptoImagePath = "imlegacy/bitcoin-symbol.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType)
});
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLitecoin()
{
NBXplorer.Altcoins.Litecoin.Networks.EnsureRegistered();
var ltcRate = new CoinAverageRateProvider("LTC");
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
BlockExplorerLink = "https://live.blockcypher.com/ltc/tx/{0}/",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "litecoin",
DefaultRateProvider = ltcRate,
CryptoImagePath = "imlegacy/litecoin-symbol.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType)
});
}
}
}

View File

@ -1,93 +1,35 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
using NBitpayClient;
using NBXplorer;
namespace BTCPayServer
{
public class BTCPayNetworkProvider
public partial class BTCPayNetworkProvider
{
static BTCPayNetworkProvider()
{
NBXplorer.Altcoins.Litecoin.Networks.EnsureRegistered();
}
Dictionary<string, BTCPayNetwork> _Networks = new Dictionary<string, BTCPayNetwork>();
public BTCPayNetworkProvider(Network network)
private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
public NBXplorerNetworkProvider NBXplorerNetworkProvider
{
var coinaverage = new CoinAverageRateProvider("BTC");
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
var btcRate = new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay });
var ltcRate = new CoinAverageRateProvider("LTC");
if (network == Network.Main)
get
{
Add(new BTCPayNetwork()
{
CryptoCode = "BTC",
BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}",
NBitcoinNetwork = Network.Main,
UriScheme = "bitcoin",
DefaultRateProvider = btcRate,
CryptoImagePath = "imlegacy/bitcoin-symbol.svg"
});
Add(new BTCPayNetwork()
{
CryptoCode = "LTC",
BlockExplorerLink = "https://live.blockcypher.com/ltc/tx/{0}/",
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Mainnet,
UriScheme = "litecoin",
DefaultRateProvider = ltcRate,
CryptoImagePath = "imlegacy/litecoin-symbol.svg"
});
return _NBXplorerNetworkProvider;
}
}
if (network == Network.TestNet)
{
Add(new BTCPayNetwork()
{
CryptoCode = "BTC",
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
NBitcoinNetwork = Network.TestNet,
UriScheme = "bitcoin",
DefaultRateProvider = btcRate,
CryptoImagePath = "imlegacy/bitcoin-symbol.svg"
});
Add(new BTCPayNetwork()
{
CryptoCode = "LTC",
BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}",
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Testnet,
UriScheme = "litecoin",
DefaultRateProvider = ltcRate,
CryptoImagePath = "imlegacy/litecoin-symbol.svg"
});
}
if (network == Network.RegTest)
{
Add(new BTCPayNetwork()
{
CryptoCode = "BTC",
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
NBitcoinNetwork = Network.RegTest,
UriScheme = "bitcoin",
DefaultRateProvider = btcRate,
CryptoImagePath = "imlegacy/bitcoin-symbol.svg"
});
Add(new BTCPayNetwork()
{
CryptoCode = "LTC",
BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}",
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Regtest,
UriScheme = "litecoin",
DefaultRateProvider = ltcRate,
CryptoImagePath = "imlegacy/litecoin-symbol.svg"
});
}
public BTCPayNetworkProvider(ChainType chainType)
{
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(chainType);
InitBitcoin();
InitLitecoin();
}
[Obsolete("To use only for legacy stuff")]

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.0.65</Version>
<Version>1.0.0.82</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Build\dockerfiles\**" />
@ -24,7 +24,7 @@
<PackageReference Include="NBitcoin" Version="4.0.0.51" />
<PackageReference Include="NBitpayClient" Version="1.0.0.16" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.28" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.33" />
<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" />

View File

@ -13,9 +13,16 @@ using NBXplorer;
namespace BTCPayServer.Configuration
{
public class NBXplorerConnectionSetting
{
public string CryptoCode { get; internal set; }
public Uri ExplorerUri { get; internal set; }
public string CookieFile { get; internal set; }
}
public class BTCPayServerOptions
{
public Network Network
public ChainType ChainType
{
get; set;
}
@ -35,56 +42,44 @@ namespace BTCPayServer.Configuration
set;
}
public List<NBXplorerConnectionSetting> NBXplorerConnectionSettings
{
get;
set;
} = new List<NBXplorerConnectionSetting>();
public void LoadArgs(IConfiguration conf)
{
var networkInfo = DefaultConfiguration.GetNetwork(conf);
Network = networkInfo?.Network;
if (Network == null)
throw new ConfigException("Invalid network");
ChainType = DefaultConfiguration.GetChainType(conf);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(ChainType);
DataDir = conf.GetOrDefault<string>("datadir", defaultSettings.DefaultDataDirectory);
Logs.Configuration.LogInformation("Network: " + ChainType.ToString());
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
Logs.Configuration.LogInformation("Network: " + Network);
bool btcHandled = false;
foreach (var net in new BTCPayNetworkProvider(Network).GetAll())
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.ToUpperInvariant());
var validChains = new List<string>();
foreach (var net in new BTCPayNetworkProvider(ChainType).GetAll())
{
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(net.NBitcoinNetwork.Name);
var explorer = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", null);
var cookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
if (explorer != null)
if (supportedChains.Contains(net.CryptoCode))
{
#pragma warning disable CS0618
if (net.IsBTC)
btcHandled = true;
#pragma warning restore CS0618
ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile));
validChains.Add(net.CryptoCode);
NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting();
setting.CryptoCode = net.CryptoCode;
setting.ExplorerUri = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", net.NBXplorerNetwork.DefaultSettings.DefaultUrl);
setting.CookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", net.NBXplorerNetwork.DefaultSettings.DefaultCookieFile);
NBXplorerConnectionSettings.Add(setting);
}
}
var invalidChains = String.Join(',', supportedChains.Where(s => !validChains.Contains(s)).ToArray());
if(!string.IsNullOrEmpty(invalidChains))
throw new ConfigException($"Invalid chains {invalidChains}");
// Handle legacy explorer.url and explorer.cookiefile
if (!btcHandled)
{
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(Network.Name); // Will get BTC info
var explorer = conf.GetOrDefault<Uri>($"explorer.url", new Uri(nbxplorer.GetDefaultExplorerUrl(), UriKind.Absolute));
var cookieFile = conf.GetOrDefault<string>($"explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
ExplorerFactories.Add("BTC", (n) => CreateExplorerClient(n, explorer, cookieFile));
}
//////
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
}
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
{
var explorer = new ExplorerClient(n.NBitcoinNetwork, uri);
if (!explorer.SetCookieAuth(cookieFile))
explorer.SetNoAuth();
return explorer;
}
public Dictionary<string, Func<BTCPayNetwork, ExplorerClient>> ExplorerFactories = new Dictionary<string, Func<BTCPayNetwork, ExplorerClient>>();
public string PostgresConnectionString
{
get;

View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using NBitcoin;
using System.Text;
using CommandLine;
using NBXplorer;
namespace BTCPayServer.Configuration
{
@ -17,20 +18,24 @@ namespace BTCPayServer.Configuration
{
protected override CommandLineApplication CreateCommandLineApplicationCore()
{
var provider = new BTCPayNetworkProvider(ChainType.Main);
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
CommandLineApplication app = new CommandLineApplication(true)
{
FullName = "BTCPay\r\nOpen source, self-hosted payment processor.",
Name = "BTCPay"
};
app.HelpOption("-? | -h | --help");
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("-n | --network", $"Set the network among (mainnet,testnet,regtest) (default: mainnet)", CommandOptionType.SingleValue);
app.Option("--testnet | -testnet", $"Use testnet (Deprecated, use --network instead)", CommandOptionType.BoolValue);
app.Option("--regtest | -regtest", $"Use regtest (Deprecated, use --network instead)", CommandOptionType.BoolValue);
app.Option("--chains | -c", $"Chains to support comma separated (default: btc, available: {chains})", CommandOptionType.SingleValue);
app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
foreach (var network in new BTCPayNetworkProvider(Network.Main).GetAll())
foreach (var network in provider.GetAll())
{
app.Option($"--{network.CryptoCode}explorerurl", $"Url of the NBxplorer for {network.CryptoCode} (default: If no explorer is specified, the default for Bitcoin will be selected)", CommandOptionType.SingleValue);
app.Option($"--{network.CryptoCode}explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
var crypto = network.CryptoCode.ToLowerInvariant();
app.Option($"--{crypto}explorerurl", $"Url of the NBxplorer for {network.CryptoCode} (default: {network.NBXplorerNetwork.DefaultSettings.DefaultUrl})", CommandOptionType.SingleValue);
app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", 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);
return app;
@ -40,57 +45,64 @@ namespace BTCPayServer.Configuration
protected override string GetDefaultDataDir(IConfiguration conf)
{
return GetNetwork(conf).DefaultDataDirectory;
return BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultDataDirectory;
}
protected override string GetDefaultConfigurationFile(IConfiguration conf)
{
var network = GetNetwork(conf);
var network = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf));
var dataDir = conf["datadir"];
if (dataDir == null)
return network.DefaultConfigurationFile;
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
return Path.Combine(dataDir, fileName);
var chainDir = Path.GetFileName(Path.GetDirectoryName(network.DefaultConfigurationFile));
chainDir = Path.Combine(dataDir, chainDir);
try
{
if (!Directory.Exists(chainDir))
Directory.CreateDirectory(chainDir);
}
catch { }
return Path.Combine(chainDir, fileName);
}
public static NetworkInformation GetNetwork(IConfiguration conf)
public static ChainType GetChainType(IConfiguration conf)
{
var network = conf.GetOrDefault<string>("network", null);
if (network != null)
{
var info = NetworkInformation.GetNetworkByName(network);
if (info == null)
throw new ConfigException($"Invalid network name {network}");
return info;
var n = Network.GetNetwork(network);
if (n == null)
{
throw new ConfigException($"Invalid network parameter '{network}'");
}
return n.ToChainType();
}
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainType.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? ChainType.Test : ChainType.Main;
var net = conf.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
conf.GetOrDefault<bool>("testnet", false) ? Network.TestNet : Network.Main;
return NetworkInformation.GetNetworkByName(net.Name);
return net;
}
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
{
var network = GetNetwork(conf);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf));
StringBuilder builder = new StringBuilder();
builder.AppendLine("### Global settings ###");
builder.AppendLine("#testnet=0");
builder.AppendLine("#regtest=0");
builder.AppendLine("#network=mainnet");
builder.AppendLine();
builder.AppendLine("### Server settings ###");
builder.AppendLine("#port=" + network.DefaultPort);
builder.AppendLine("#port=" + defaultSettings.DefaultPort);
builder.AppendLine("#bind=127.0.0.1");
builder.AppendLine();
builder.AppendLine("### Database ###");
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
builder.AppendLine();
builder.AppendLine("### NBXplorer settings ###");
foreach (var n in new BTCPayNetworkProvider(network.Network).GetAll())
foreach (var n in new BTCPayNetworkProvider(defaultSettings.ChainType).GetAll())
{
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(n.NBitcoinNetwork.ToString());
builder.AppendLine($"#{n.CryptoCode}.explorer.url={nbxplorer.GetDefaultExplorerUrl()}");
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ nbxplorer.GetDefaultCookieFile()}");
builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}");
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}");
}
return builder.ToString();
}
@ -99,7 +111,7 @@ namespace BTCPayServer.Configuration
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
{
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), BTCPayDefaultSettings.GetDefaultSettings(GetChainType(conf)).DefaultPort);
}
}
}

View File

@ -1,83 +0,0 @@
using Microsoft.Extensions.Configuration;
using NBitcoin;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Configuration
{
public class NetworkInformation
{
static NetworkInformation()
{
_Networks = new Dictionary<string, NetworkInformation>();
foreach (var network in new[] { Network.Main, Network.TestNet, Network.RegTest })
{
NetworkInformation info = new NetworkInformation();
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
info.Network = network;
info.DefaultPort = 23002;
_Networks.Add(network.Name, info);
if (network == Network.Main)
{
info.DefaultPort = 23000;
}
if (network == Network.TestNet)
{
info.DefaultPort = 23001;
}
}
}
static Dictionary<string, NetworkInformation> _Networks;
public static NetworkInformation GetNetworkByName(string name)
{
var value = _Networks.TryGet(name);
if (value != null)
return value;
//Maybe alias ?
var network = Network.GetNetwork(name);
if (network != null)
{
value = _Networks.TryGet(network.Name);
if (value != null)
return value;
}
return null;
}
public Network Network
{
get; set;
}
public string DefaultConfigurationFile
{
get;
set;
}
public string DefaultDataDirectory
{
get;
set;
}
public int DefaultPort
{
get;
private set;
}
public override string ToString()
{
return Network.ToString();
}
public static string ToStringAll()
{
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
}
}
}

View File

@ -15,6 +15,7 @@ using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Services;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Stores;
using BTCPayServer.Logging;
namespace BTCPayServer.Controllers
{
@ -25,10 +26,10 @@ namespace BTCPayServer.Controllers
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ILogger _logger;
StoreRepository storeRepository;
RoleManager<IdentityRole> _RoleManager;
SettingsRepository _SettingsRepository;
ILogger _logger;
public AccountController(
UserManager<ApplicationUser> userManager,
@ -36,16 +37,15 @@ namespace BTCPayServer.Controllers
StoreRepository storeRepository,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
SettingsRepository settingsRepository,
ILogger<AccountController> logger)
SettingsRepository settingsRepository)
{
this.storeRepository = storeRepository;
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_logger = logger;
_RoleManager = roleManager;
_SettingsRepository = settingsRepository;
_logger = Logs.PayServer;
}
[TempData]
@ -259,12 +259,10 @@ namespace BTCPayServer.Controllers
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
Logs.PayServer.LogInformation($"A new user just registered {user.Email} {(admin.Count == 0 ? "(admin)" : "")}");
if (admin.Count == 0)
{
_logger.LogInformation("Admin created.");
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
}
@ -291,7 +289,7 @@ namespace BTCPayServer.Controllers
return View(model);
}
/// <summary>
/// <summary>
/// Test property
/// </summary>
public string RegisteredUserId

View File

@ -77,7 +77,7 @@ namespace BTCPayServer.Controllers
return NotFound();
var payment = PaymentMessage.Load(Request.Body);
var unused = wallet.BroadcastTransactionsAsync(payment.Transactions);
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray());
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork);
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
}
}

View File

@ -33,7 +33,8 @@ namespace BTCPayServer.Controllers
{
UserId = GetUserId(),
InvoiceId = invoiceId,
IncludeAddresses = true
IncludeAddresses = true,
IncludeEvents = true
})).FirstOrDefault();
if (invoice == null)
return NotFound();
@ -55,8 +56,10 @@ namespace BTCPayServer.Controllers
BuyerInformation = invoice.BuyerInformation,
Fiat = FormatCurrency((decimal)dto.Price, dto.Currency),
NotificationUrl = invoice.NotificationURL,
RedirectUrl = invoice.RedirectURL,
ProductInformation = invoice.ProductInformation,
StatusException = invoice.ExceptionStatus
StatusException = invoice.ExceptionStatus,
Events = invoice.Events
};
foreach (var data in invoice.GetCryptoData(null))
@ -123,11 +126,22 @@ namespace BTCPayServer.Controllers
if (invoice == null)
return null;
var store = await _StoreRepository.FindStore(invoice.StoreId);
bool isDefaultCrypto = false;
if (cryptoCode == null)
{
cryptoCode = store.GetDefaultCrypto();
isDefaultCrypto = true;
}
var network = _NetworkProvider.GetNetwork(cryptoCode);
if (invoice == null || network == null || !invoice.Support(network))
if (invoice == null || network == null)
return null;
if(!invoice.Support(network))
{
if(!isDefaultCrypto)
return null;
network = invoice.GetCryptoData(_NetworkProvider).First().Value.Network;
}
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
var dto = invoice.EntityToDTO(_NetworkProvider);
@ -251,7 +265,7 @@ namespace BTCPayServer.Controllers
{
await webSocket.SendAsync(DummyBuffer, WebSocketMessageType.Binary, true, cts.Token);
}
catch { await CloseSocket(webSocket); }
catch { try { webSocket.Dispose(); } catch { } }
}
private static async Task CloseSocket(WebSocket webSocket)
@ -266,7 +280,7 @@ namespace BTCPayServer.Controllers
}
}
catch { }
finally { webSocket.Dispose(); }
finally { try { webSocket.Dispose(); } catch { } }
}
[HttpPost]

View File

@ -116,13 +116,13 @@ namespace BTCPayServer.Controllers
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
var queries = derivationStrategies
.Select(derivationStrategy => ( Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
.Select(derivationStrategy => (Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
DerivationStrategy: derivationStrategy.DerivationStrategyBase,
Network: derivationStrategy.Network,
RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network),
FeeRateProvider: _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network)))
.Where(_ => _.Wallet != null &&
_.FeeRateProvider != null &&
.Where(_ => _.Wallet != null &&
_.FeeRateProvider != null &&
_.RateProvider != null)
.Select(_ =>
{
@ -135,19 +135,21 @@ namespace BTCPayServer.Controllers
};
});
bool legacyBTCisSet = false;
var cryptoDatas = new Dictionary<string, CryptoData>();
foreach (var q in queries)
{
CryptoData cryptoData = new CryptoData();
cryptoData.CryptoCode = q.network.CryptoCode;
cryptoData.FeeRate = (await q.getFeeRate);
cryptoData.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : cryptoData.FeeRate.GetFee(100); // assume price for 100 bytes
cryptoData.TxFee = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes
cryptoData.Rate = await q.getRate;
cryptoData.DepositAddress = (await q.getAddress).ToString();
#pragma warning disable CS0618
if (q.network.IsBTC)
{
legacyBTCisSet = true;
entity.TxFee = cryptoData.TxFee;
entity.Rate = cryptoData.Rate;
entity.DepositAddress = cryptoData.DepositAddress;
@ -155,6 +157,24 @@ namespace BTCPayServer.Controllers
#pragma warning restore CS0618
cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
}
if (!legacyBTCisSet)
{
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
#pragma warning disable CS0618
var btc = _NetworkProvider.BTC;
var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc);
var rateProvider = _RateProviders.GetRateProvider(btc);
if (feeProvider != null && rateProvider != null)
{
var gettingFee = feeProvider.GetFeeRateAsync();
var gettingRate = rateProvider.GetRateAsync(invoice.Currency);
entity.TxFee = GetTxFee(storeBlob, await gettingFee);
entity.Rate = await gettingRate;
}
#pragma warning restore CS0618
}
entity.SetCryptoData(cryptoDatas);
entity.PosData = invoice.PosData;
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
@ -163,6 +183,11 @@ namespace BTCPayServer.Controllers
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
}
private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate)
{
return storeBlob.NetworkFeeDisabled ? Money.Zero : feeRate.GetFee(100);
}
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
{
if (transactionSpeed == null)

View File

@ -33,7 +33,6 @@ namespace BTCPayServer.Controllers
private readonly ILogger _logger;
private readonly UrlEncoder _urlEncoder;
TokenRepository _TokenRepository;
private readonly BTCPayWallet _Wallet;
IHostingEnvironment _Env;
StoreRepository _StoreRepository;
@ -47,7 +46,7 @@ namespace BTCPayServer.Controllers
ILogger<ManageController> logger,
UrlEncoder urlEncoder,
TokenRepository tokenRepository,
BTCPayWallet wallet,
BTCPayWalletProvider walletProvider,
StoreRepository storeRepository,
IHostingEnvironment env)
{
@ -57,7 +56,6 @@ namespace BTCPayServer.Controllers
_logger = logger;
_urlEncoder = urlEncoder;
_TokenRepository = tokenRepository;
_Wallet = wallet;
_Env = env;
_StoreRepository = storeRepository;
}

View File

@ -26,6 +26,11 @@ namespace BTCPayServer.Data
get; set;
}
public DbSet<InvoiceEventData> InvoiceEvents
{
get; set;
}
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
{
get; set;
@ -132,6 +137,15 @@ namespace BTCPayServer.Data
o.InvoiceDataId,
#pragma warning disable CS0618
o.Address
#pragma warning restore CS0618
});
builder.Entity<InvoiceEventData>()
.HasKey(o => new
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.UniqueId
#pragma warning restore CS0618
});
}

View File

@ -32,6 +32,11 @@ namespace BTCPayServer.Data
get; set;
}
public List<InvoiceEventData> Events
{
get; set;
}
public List<RefundAddressesData> RefundAddresses
{
get; set;

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class InvoiceEventData
{
public string InvoiceDataId
{
get; set;
}
public string UniqueId { get; internal set; }
public DateTimeOffset Timestamp
{
get; set;
}
public string Message { get; set; }
}
}

View File

@ -88,7 +88,7 @@ namespace BTCPayServer
}
}
Logs.Events.LogInformation($"New event: {evt.ToString()}");
Logs.Events.LogInformation(evt.ToString());
foreach (var sub in actionList)
{
try

View File

@ -8,10 +8,19 @@ namespace BTCPayServer.Events
public class InvoiceDataChangedEvent
{
public string InvoiceId { get; set; }
public string Status { get; internal set; }
public string ExceptionStatus { get; internal set; }
public override string ToString()
{
return $"Invoice {InvoiceId} data changed";
if (string.IsNullOrEmpty(ExceptionStatus) || ExceptionStatus == "false")
{
return $"Invoice status is {Status}";
}
else
{
return $"Invoice status is {Status} (Exception status: {ExceptionStatus})";
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Events
{
public class InvoiceIPNEvent
{
public InvoiceIPNEvent(string invoiceId)
{
InvoiceId = invoiceId;
}
public string InvoiceId { get; set; }
public string Error { get; set; }
public override string ToString()
{
if (Error == null)
return $"IPN sent for invoice {InvoiceId}";
return $"Error while sending IPN: {Error}";
}
}
}

View File

@ -9,16 +9,20 @@ namespace BTCPayServer.Events
public class InvoicePaymentEvent
{
public InvoicePaymentEvent(string invoiceId)
public InvoicePaymentEvent(string invoiceId, string cryptoCode, string address)
{
InvoiceId = invoiceId;
Address = address;
CryptoCode = cryptoCode;
}
public string Address { get; set; }
public string CryptoCode { get; private set; }
public string InvoiceId { get; set; }
public override string ToString()
{
return $"Invoice {InvoiceId} received a payment";
return $"{CryptoCode}: Invoice {InvoiceId} received a payment on {Address}";
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Events
{
public class InvoiceStopWatchedEvent
{
public string InvoiceId { get; set; }
public override string ToString()
{
return $"Invoice {InvoiceId} is not monitored anymore.";
}
}
}

View File

@ -6,6 +6,21 @@ using BTCPayServer.HostedServices;
namespace BTCPayServer.Events
{
public class NBXplorerErrorEvent
{
public NBXplorerErrorEvent(BTCPayNetwork network, string errorMessage)
{
Message = errorMessage;
Network = network;
}
public string Message { get; set; }
public BTCPayNetwork Network { get; set; }
public override string ToString()
{
return $"{Network.CryptoCode}: NBXplorer error `{Message}`";
}
}
public class NBXplorerStateChangedEvent
{
public NBXplorerStateChangedEvent(BTCPayNetwork network, NBXplorerState old, NBXplorerState newState)

View File

@ -7,9 +7,10 @@ namespace BTCPayServer.Events
{
public class NewBlockEvent
{
public string CryptoCode { get; set; }
public override string ToString()
{
return "New block";
return $"{CryptoCode}: New block";
}
}
}

View File

@ -1,8 +1,10 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
using NBXplorer;
namespace BTCPayServer
@ -18,18 +20,41 @@ namespace BTCPayServer
{
_NetworkProviders = networkProviders;
_Options = options;
foreach (var setting in options.NBXplorerConnectionSettings)
{
var cookieFile = setting.CookieFile;
if (cookieFile.Trim() == "0" || string.IsNullOrEmpty(cookieFile.Trim()))
cookieFile = null;
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Explorer url is {(setting.ExplorerUri.AbsoluteUri ?? "not set")}");
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Cookie file is {(setting.CookieFile ?? "not set")}");
if (setting.ExplorerUri != null)
{
_Clients.TryAdd(setting.CryptoCode, CreateExplorerClient(_NetworkProviders.GetNetwork(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
}
}
}
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
{
var explorer = new ExplorerClient(n.NBXplorerNetwork, uri);
if (cookieFile == null || !explorer.SetCookieAuth(cookieFile))
{
Logs.Configuration.LogWarning($"{n.CryptoCode}: Not using cookie authentication");
explorer.SetNoAuth();
}
return explorer;
}
Dictionary<string, ExplorerClient> _Clients = new Dictionary<string, ExplorerClient>();
public ExplorerClient GetExplorerClient(string cryptoCode)
{
var network = _NetworkProviders.GetNetwork(cryptoCode);
if (network == null)
return null;
if (_Options.ExplorerFactories.TryGetValue(network.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
{
return factory(network);
}
return null;
_Clients.TryGetValue(network.CryptoCode, out ExplorerClient client);
return client;
}
public ExplorerClient GetExplorerClient(BTCPayNetwork network)
@ -44,18 +69,18 @@ namespace BTCPayServer
var network = _NetworkProviders.GetNetwork(cryptoCode);
if (network == null)
return null;
if (_Options.ExplorerFactories.ContainsKey(network.CryptoCode))
if (_Clients.ContainsKey(network.CryptoCode))
return network;
return null;
}
public IEnumerable<(BTCPayNetwork, ExplorerClient)> GetAll()
{
foreach(var net in _NetworkProviders.GetAll())
foreach (var net in _NetworkProviders.GetAll())
{
if(_Options.ExplorerFactories.TryGetValue(net.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
if (_Clients.TryGetValue(net.CryptoCode, out ExplorerClient explorer))
{
yield return (net, factory(net));
yield return (net, explorer);
}
}
}

View File

@ -24,14 +24,6 @@ namespace BTCPayServer
{
public static class Extensions
{
public static string GetDefaultExplorerUrl(this NBXplorer.Configuration.NetworkInformation networkInfo)
{
return $"http://127.0.0.1:{networkInfo.DefaultExplorerPort}/";
}
public static string GetDefaultCookieFile(this NBXplorer.Configuration.NetworkInformation networkInfo)
{
return Path.Combine(networkInfo.DefaultDataDirectory, ".cookie");
}
public static bool SupportDropColumn(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
{
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";

View File

@ -68,11 +68,25 @@ namespace BTCPayServer.HostedServices
CancellationTokenSource cts = new CancellationTokenSource(10000);
try
{
if (string.IsNullOrEmpty(invoice.NotificationURL))
return;
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id));
await SendNotification(invoice, cts.Token);
return;
}
catch // It fails, it is OK because we try with hangfire after
catch(OperationCanceledException) when(cts.IsCancellationRequested)
{
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id)
{
Error = "Timeout"
});
}
catch(Exception ex) // It fails, it is OK because we try with hangfire after
{
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id)
{
Error = ex.Message
});
}
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice });
if (!string.IsNullOrEmpty(invoice.NotificationURL))
@ -93,12 +107,26 @@ namespace BTCPayServer.HostedServices
CancellationTokenSource cts = new CancellationTokenSource(10000);
try
{
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id));
HttpResponseMessage response = await SendNotification(job.Invoice, cts.Token);
reschedule = response.StatusCode != System.Net.HttpStatusCode.OK;
Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode);
}
catch (Exception ex)
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id)
{
Error = "Timeout"
});
reschedule = true;
Logger.LogInformation("Job " + jobId + " timed out");
}
catch (Exception ex) // It fails, it is OK because we try with hangfire after
{
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id)
{
Error = ex.Message
});
reschedule = true;
Logger.LogInformation("Job " + jobId + " threw exception " + ex.Message);
}
@ -184,10 +212,42 @@ namespace BTCPayServer.HostedServices
{
await Notify(invoice);
}
await SaveEvent(invoice.Id, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoiceCreatedEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoiceDataChangedEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoicePaymentEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoiceStopWatchedEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoiceIPNEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
return Task.CompletedTask;
}
private Task SaveEvent(string invoiceId, object evt)
{
return _InvoiceRepository.AddInvoiceEvent(invoiceId, evt);
}
public Task StopAsync(CancellationToken cancellationToken)
{
leases.Dispose();

View File

@ -17,6 +17,7 @@ using BTCPayServer.Controllers;
using BTCPayServer.Events;
using Microsoft.AspNetCore.Hosting;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services;
namespace BTCPayServer.HostedServices
{
@ -98,7 +99,7 @@ namespace BTCPayServer.HostedServices
if (updateContext.Dirty)
{
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
_EventAggregator.Publish(new InvoiceDataChangedEvent() { InvoiceId = invoice.Id });
updateContext.Events.Add(new InvoiceDataChangedEvent() { Status = invoice.Status, ExceptionStatus = invoice.ExceptionStatus, InvoiceId = invoice.Id });
}
var changed = stateBefore != invoice.Status;
@ -117,7 +118,7 @@ namespace BTCPayServer.HostedServices
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
{
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false))
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
_EventAggregator.Publish<InvoiceStopWatchedEvent>(new InvoiceStopWatchedEvent() { InvoiceId = invoice.Id });
break;
}
@ -150,7 +151,7 @@ namespace BTCPayServer.HostedServices
}
var derivationStrategies = invoice.GetDerivationStrategies(_NetworkProvider).ToArray();
var payments = await GetPaymentsWithTransaction(null, derivationStrategies, invoice);
var payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
foreach (Task<NetworkCoins> coinsAsync in GetCoinsPerNetwork(context, invoice, derivationStrategies))
{
var coins = await coinsAsync;
@ -168,12 +169,12 @@ namespace BTCPayServer.HostedServices
invoice.Payments.Add(payment);
#pragma warning restore CS0618
alreadyAccounted.Add(coin.Coin.Outpoint);
context.Events.Add(new InvoicePaymentEvent(invoice.Id));
context.Events.Add(new InvoicePaymentEvent(invoice.Id, coins.Wallet.Network.CryptoCode, coin.Coin.ScriptPubKey.GetDestinationAddress(coins.Wallet.Network.NBitcoinNetwork).ToString()));
dirtyAddress = true;
}
if (dirtyAddress)
{
payments = await GetPaymentsWithTransaction(payments, derivationStrategies, invoice);
payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
}
var network = coins.Wallet.Network;
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
@ -293,58 +294,23 @@ namespace BTCPayServer.HostedServices
}
class AccountedPaymentEntities : List<AccountedPaymentEntity>
{
public AccountedPaymentEntities(AccountedPaymentEntities existing)
{
if (existing != null)
_Transactions = existing._Transactions;
}
Dictionary<uint256, TransactionResult> _Transactions = new Dictionary<uint256, TransactionResult>();
public void AddToCache(IEnumerable<TransactionResult> transactions)
{
foreach (var tx in transactions)
_Transactions.TryAdd(tx.Transaction.GetHash(), tx);
}
public TransactionResult GetTransaction(uint256 txId)
{
_Transactions.TryGetValue(txId, out TransactionResult result);
return result;
}
internal IEnumerable<TransactionResult> GetTransactions()
{
return _Transactions.Values;
}
}
private async Task<AccountedPaymentEntities> GetPaymentsWithTransaction(AccountedPaymentEntities previous, DerivationStrategy[] derivations, InvoiceEntity invoice)
private async Task<IEnumerable<AccountedPaymentEntity>> GetPaymentsWithTransaction(DerivationStrategy[] derivations, InvoiceEntity invoice)
{
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
AccountedPaymentEntities accountedPayments = new AccountedPaymentEntities(previous);
List<AccountedPaymentEntity> accountedPayments = new List<AccountedPaymentEntity>();
foreach (var network in derivations.Select(d => d.Network))
{
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
continue;
var hashesToFetch = new HashSet<uint256>(invoice
.GetPayments(network)
var transactions = await wallet.GetTransactions(invoice.GetPayments(wallet.Network)
.Select(t => t.Outpoint.Hash)
.Where(h => accountedPayments?.GetTransaction(h) == null)
.ToList());
if (hashesToFetch.Count > 0)
{
accountedPayments.AddToCache((await wallet.GetTransactions(hashesToFetch.ToArray())).Select(t => t.Value));
}
var conflicts = GetConflicts(accountedPayments.GetTransactions());
.ToArray());
var conflicts = GetConflicts(transactions.Select(t => t.Value));
foreach (var payment in invoice.GetPayments(network))
{
TransactionResult tx = accountedPayments.GetTransaction(payment.Outpoint.Hash);
if (tx == null)
if (!transactions.TryGetValue(payment.Outpoint.Hash, out TransactionResult tx))
continue;
AccountedPaymentEntity accountedPayment = new AccountedPaymentEntity()
@ -370,7 +336,6 @@ namespace BTCPayServer.HostedServices
return accountedPayments;
}
class TransactionConflict
{
public Dictionary<uint256, TransactionResult> Transactions { get; set; } = new Dictionary<uint256, TransactionResult>();

View File

@ -12,6 +12,7 @@ using NBXplorer;
using System.Collections.Concurrent;
using NBXplorer.DerivationStrategy;
using BTCPayServer.Events;
using BTCPayServer.Services;
namespace BTCPayServer.HostedServices
{
@ -24,9 +25,11 @@ namespace BTCPayServer.HostedServices
private TaskCompletionSource<bool> _RunningTask;
private CancellationTokenSource _Cts;
NBXplorerDashboard _Dashboards;
TransactionCacheProvider _TxCache;
public NBXplorerListener(ExplorerClientProvider explorerClients,
NBXplorerDashboard dashboard,
TransactionCacheProvider cacheProvider,
InvoiceRepository invoiceRepository,
EventAggregator aggregator, IApplicationLifetime lifetime)
{
@ -36,6 +39,7 @@ namespace BTCPayServer.HostedServices
_ExplorerClients = explorerClients;
_Aggregator = aggregator;
_Lifetime = lifetime;
_TxCache = cacheProvider;
}
CompositeDisposable leases = new CompositeDisposable();
@ -130,11 +134,13 @@ namespace BTCPayServer.HostedServices
switch (newEvent)
{
case NBXplorer.Models.NewBlockEvent evt:
_Aggregator.Publish(new Events.NewBlockEvent());
_TxCache.GetTransactionCache(network).NewBlock(evt.Hash, evt.PreviousBlockHash);
_Aggregator.Publish(new Events.NewBlockEvent() { CryptoCode = evt.CryptoCode });
break;
case NBXplorer.Models.NewTransactionEvent evt:
foreach (var txout in evt.Match.Outputs)
foreach (var txout in evt.Outputs)
{
_TxCache.GetTransactionCache(network).AddToCache(evt.TransactionData);
_Aggregator.Publish(new Events.TxOutReceivedEvent()
{
Network = network,

View File

@ -27,11 +27,12 @@ namespace BTCPayServer.HostedServices
public BTCPayNetwork Network { get; set; }
public NBXplorerState State { get; set; }
public StatusResult Status { get; set; }
public string Error { get; set; }
}
ConcurrentDictionary<string, NBXplorerSummary> _Summaries = new ConcurrentDictionary<string, NBXplorerSummary>();
public void Publish(BTCPayNetwork network, NBXplorerState state, StatusResult status)
public void Publish(BTCPayNetwork network, NBXplorerState state, StatusResult status, string error)
{
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status };
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status, Error = error };
_Summaries.AddOrUpdate(network.CryptoCode, summary, (k, v) => summary);
}
@ -120,6 +121,7 @@ namespace BTCPayServer.HostedServices
private async Task<bool> StepAsync(CancellationToken cancellation)
{
var oldState = State;
string error = null;
StatusResult status = null;
try
{
@ -164,15 +166,28 @@ namespace BTCPayServer.HostedServices
}
}
catch when (cancellation.IsCancellationRequested)
catch (Exception ex) when (!cancellation.IsCancellationRequested)
{
error = ex.Message;
}
catch (Exception ex)
if(status == null && error == null)
error = $"{_Network.CryptoCode}: NBXplorer does not support this cryptocurrency";
if(status != null && error == null)
{
if(status.ChainType != _Network.NBXplorerNetwork.DefaultSettings.ChainType)
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.ChainType}, expected: {_Network.NBXplorerNetwork.DefaultSettings.ChainType})";
}
if (error != null)
{
State = NBXplorerState.NotConnected;
Logs.PayServer.LogError(ex, $"Error while trying to connect to NBXplorer ({_Network.CryptoCode})");
status = null;
_Aggregator.Publish(new NBXplorerErrorEvent(_Network, error));
}
if (oldState != State)
{
if (State == NBXplorerState.Synching)
@ -185,7 +200,7 @@ namespace BTCPayServer.HostedServices
}
_Aggregator.Publish(new NBXplorerStateChangedEvent(_Network, oldState, State));
}
_Dashboard.Publish(_Network, State, status);
_Dashboard.Publish(_Network, State, status, error);
return oldState != State;
}

View File

@ -103,7 +103,7 @@ namespace BTCPayServer.Hosting
var dbpath = Path.Combine(opts.DataDir, "InvoiceDB");
if (!Directory.Exists(dbpath))
Directory.CreateDirectory(dbpath);
return new InvoiceRepository(dbContext, dbpath, opts.Network);
return new InvoiceRepository(dbContext, dbpath);
});
services.AddSingleton<BTCPayServerEnvironment>();
services.TryAddSingleton<TokenRepository>();
@ -129,7 +129,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<BTCPayNetworkProvider>(o =>
{
var opts = o.GetRequiredService<BTCPayServerOptions>();
return new BTCPayNetworkProvider(opts.Network);
return new BTCPayNetworkProvider(opts.ChainType);
});
services.TryAddSingleton<NBXplorerDashboard>();
@ -142,6 +142,8 @@ namespace BTCPayServer.Hosting
BlockTarget = 20
});
services.AddSingleton<TransactionCacheProvider>();
services.AddSingleton<IHostedService, NBXplorerWaiters>();
services.AddSingleton<IHostedService, NBXplorerListener>();
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
@ -150,7 +152,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<ExplorerClientProvider>();
services.TryAddSingleton<Bitpay>(o =>
{
if (o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
if (o.GetRequiredService<BTCPayServerOptions>().ChainType == ChainType.Main)
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
else
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));

View File

@ -0,0 +1,508 @@
// <auto-generated />
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20180114123253_events")]
partial class events
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.ValueGeneratedOnAdd();
b.Property<DateTimeOffset?>("CreatedTime");
b.Property<string>("InvoiceDataId");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
{
b.Property<string>("InvoiceDataId");
b.Property<string>("Address");
b.Property<DateTimeOffset>("Assigned");
b.Property<string>("CryptoCode");
b.Property<DateTimeOffset?>("UnAssigned");
b.HasKey("InvoiceDataId", "Address");
b.ToTable("HistoricalAddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<DateTimeOffset>("Created");
b.Property<string>("CustomerEmail");
b.Property<string>("ExceptionStatus");
b.Property<string>("ItemCode");
b.Property<string>("OrderId");
b.Property<string>("Status");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("StoreDataId");
b.ToTable("Invoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
{
b.Property<string>("InvoiceDataId");
b.Property<string>("UniqueId");
b.Property<string>("Message");
b.Property<DateTimeOffset>("Timestamp");
b.HasKey("InvoiceDataId", "UniqueId");
b.ToTable("InvoiceEvents");
});
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<DateTimeOffset>("PairingTime");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.HasKey("Id");
b.HasIndex("SIN");
b.HasIndex("StoreDataId");
b.ToTable("PairedSINData");
});
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("DateCreated");
b.Property<DateTimeOffset>("Expiration");
b.Property<string>("Facade");
b.Property<string>("Label");
b.Property<string>("SIN");
b.Property<string>("StoreDataId");
b.Property<string>("TokenValue");
b.HasKey("Id");
b.ToTable("PairingCodes");
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Accounted");
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("Payments");
});
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.HasKey("Id");
b.ToTable("PendingInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<byte[]>("Blob");
b.Property<string>("InvoiceDataId");
b.HasKey("Id");
b.HasIndex("InvoiceDataId");
b.ToTable("RefundAddresses");
});
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("DefaultCrypto");
b.Property<string>("DerivationStrategies");
b.Property<string>("DerivationStrategy");
b.Property<int>("SpeedPolicy");
b.Property<byte[]>("StoreBlob");
b.Property<byte[]>("StoreCertificate");
b.Property<string>("StoreName");
b.Property<string>("StoreWebsite");
b.HasKey("Id");
b.ToTable("Stores");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.Property<string>("ApplicationUserId");
b.Property<string>("StoreDataId");
b.Property<string>("Role");
b.HasKey("ApplicationUserId", "StoreDataId");
b.HasIndex("StoreDataId");
b.ToTable("UserStore");
});
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<bool>("RequiresEmailConfirmation");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("AddressInvoices")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData")
.WithMany("HistoricalAddressInvoices")
.HasForeignKey("InvoiceDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany()
.HasForeignKey("StoreDataId");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData")
.WithMany("Events")
.HasForeignKey("InvoiceDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("Payments")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("RefundAddresses")
.HasForeignKey("InvoiceDataId");
});
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
.WithMany("UserStores")
.HasForeignKey("ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
.WithMany("UserStores")
.HasForeignKey("StoreDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BTCPayServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace BTCPayServer.Migrations
{
public partial class events : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "InvoiceEvents",
columns: table => new
{
InvoiceDataId = table.Column<string>(nullable: false),
UniqueId = table.Column<string>(nullable: false),
Message = table.Column<string>(nullable: true),
Timestamp = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_InvoiceEvents", x => new { x.InvoiceDataId, x.UniqueId });
table.ForeignKey(
name: "FK_InvoiceEvents_Invoices_InvoiceDataId",
column: x => x.InvoiceDataId,
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "InvoiceEvents");
}
}
}

View File

@ -81,6 +81,21 @@ namespace BTCPayServer.Migrations
b.ToTable("Invoices");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
{
b.Property<string>("InvoiceDataId");
b.Property<string>("UniqueId");
b.Property<string>("Message");
b.Property<DateTimeOffset>("Timestamp");
b.HasKey("InvoiceDataId", "UniqueId");
b.ToTable("InvoiceEvents");
});
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
{
b.Property<string>("Id")
@ -407,6 +422,14 @@ namespace BTCPayServer.Migrations
.HasForeignKey("StoreDataId");
});
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData")
.WithMany("Events")
.HasForeignKey("InvoiceDataId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")

View File

@ -112,6 +112,8 @@ namespace BTCPayServer.Models.InvoicingModels
get;
internal set;
}
public string RedirectUrl { get; set; }
public string Fiat
{
get;
@ -124,5 +126,6 @@ namespace BTCPayServer.Models.InvoicingModels
}
public HistoricalAddressInvoiceData[] Addresses { get; set; }
public DateTimeOffset MonitoringDate { get; internal set; }
public List<Data.InvoiceEventData> Events { get; internal set; }
}
}

View File

@ -1,27 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14139/",
"sslPort": 0
}
},
"profiles": {
"Default": {
"commandName": "Project"
},
"Docker-Regtest": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32839/",
"BTCPAY_NETWORK": "regtest",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
},
"applicationUrl": "http://localhost:14142/"
}
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14139/",
"sslPort": 0
}
}
},
"profiles": {
"Default": {
"commandName": "Project",
"commandLineArgs": "--network testnet --chains ltc --ltcexplorerurl http://127.0.0.1:2727/"
},
"Docker-Regtest": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_NETWORK": "regtest",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_CHAINS": "btc,ltc",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
},
"applicationUrl": "http://localhost:14142/"
}
}
}

View File

@ -299,6 +299,7 @@ namespace BTCPayServer.Services.Invoices
set;
}
public bool ExtendedNotifications { get; set; }
public List<InvoiceEventData> Events { get; internal set; }
public bool IsExpired()
{
@ -323,7 +324,7 @@ namespace BTCPayServer.Services.Invoices
};
dto.CryptoInfo = new List<NBitpayClient.InvoiceCryptoInfo>();
foreach (var info in this.GetCryptoData(networkProvider).Values)
foreach (var info in this.GetCryptoData(networkProvider, true).Values)
{
var accounting = info.Calculate();
var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo();
@ -369,8 +370,8 @@ namespace BTCPayServer.Services.Invoices
dto.PaymentUrls = cryptoInfo.PaymentUrls;
}
#pragma warning restore CS0618
dto.CryptoInfo.Add(cryptoInfo);
if(!info.IsPhantomBTC)
dto.CryptoInfo.Add(cryptoInfo);
}
Populate(ProductInformation, dto);
@ -395,9 +396,9 @@ namespace BTCPayServer.Services.Invoices
return rates.TryGetValue(network.CryptoCode, out var data);
}
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider)
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
{
GetCryptoData(networkProvider).TryGetValue(cryptoCode, out var data);
GetCryptoData(networkProvider, alwaysIncludeBTC).TryGetValue(cryptoCode, out var data);
return data;
}
@ -407,26 +408,30 @@ namespace BTCPayServer.Services.Invoices
return data;
}
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider)
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
{
Dictionary<string, CryptoData> rates = new Dictionary<string, CryptoData>();
var serializer = new Serializer(Dummy);
CryptoData phantom = null;
#pragma warning disable CS0618
// Legacy
if (Rate != 0.0m)
if (alwaysIncludeBTC)
{
var btcNetwork = networkProvider?.GetNetwork("BTC");
rates.TryAdd("BTC", new CryptoData() { ParentEntity = this, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork });
phantom = new CryptoData() { ParentEntity = this, IsPhantomBTC = true, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork };
rates.Add("BTC", phantom);
}
if (CryptoData != null)
{
foreach (var prop in CryptoData.Properties())
{
if (prop.Name == "BTC" && phantom != null)
rates.Remove("BTC");
var r = serializer.ToObject<CryptoData>(prop.Value.ToString());
r.CryptoCode = prop.Name;
r.ParentEntity = this;
r.Network = networkProvider?.GetNetwork(r.CryptoCode);
rates.TryAdd(r.CryptoCode, r);
rates.Add(r.CryptoCode, r);
}
}
#pragma warning restore CS0618
@ -507,9 +512,12 @@ namespace BTCPayServer.Services.Invoices
[JsonProperty(PropertyName = "depositAddress")]
public string DepositAddress { get; set; }
[JsonIgnore]
public bool IsPhantomBTC { get; set; }
public CryptoDataAccounting Calculate()
{
var cryptoData = ParentEntity.GetCryptoData(null);
var cryptoData = ParentEntity.GetCryptoData(null, IsPhantomBTC);
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate);
var paid = Money.Zero;
var cryptoPaid = Money.Zero;

View File

@ -32,27 +32,12 @@ namespace BTCPayServer.Services.Invoices
}
}
Network _Network;
public Network Network
{
get
{
return _Network;
}
set
{
_Network = value;
}
}
private ApplicationDbContextFactory _ContextFactory;
private CustomThreadPool _IndexerThread;
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath, Network network)
public InvoiceRepository(ApplicationDbContextFactory contextFactory, string dbreezePath)
{
_Engine = new DBreezeEngine(dbreezePath);
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
_Network = network;
_ContextFactory = contextFactory;
}
@ -105,7 +90,7 @@ namespace BTCPayServer.Services.Invoices
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, BTCPayNetworkProvider networkProvider)
{
List<string> textSearch = new List<string>();
invoice = Clone(invoice);
invoice = Clone(invoice, null);
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
#pragma warning disable CS0618
invoice.Payments = new List<PaymentEntity>();
@ -118,7 +103,7 @@ namespace BTCPayServer.Services.Invoices
StoreDataId = storeId,
Id = invoice.Id,
Created = invoice.InvoiceTime,
Blob = ToBytes(invoice),
Blob = ToBytes(invoice, null),
OrderId = invoice.OrderId,
Status = invoice.Status,
ItemCode = invoice.ProductInformation.ItemCode,
@ -150,8 +135,8 @@ namespace BTCPayServer.Services.Invoices
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
textSearch.Add(invoice.OrderId);
textSearch.Add(ToString(invoice.BuyerInformation));
textSearch.Add(ToString(invoice.ProductInformation));
textSearch.Add(ToString(invoice.BuyerInformation, null));
textSearch.Add(ToString(invoice.ProductInformation, null));
textSearch.Add(invoice.StoreId);
AddToTextSearch(invoice.Id, textSearch.ToArray());
@ -167,7 +152,7 @@ namespace BTCPayServer.Services.Invoices
if (invoice == null)
return false;
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob, network.NBitcoinNetwork);
var currencyData = invoiceEntity.GetCryptoData(network, null);
if (currencyData == null)
return false;
@ -186,10 +171,13 @@ namespace BTCPayServer.Services.Invoices
}
#pragma warning restore CS0618
invoiceEntity.SetCryptoData(currencyData);
invoice.Blob = ToBytes(invoiceEntity);
invoice.Blob = ToBytes(invoiceEntity, network.NBitcoinNetwork);
context.AddressInvoices.Add(new AddressInvoiceData() {
InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow }
context.AddressInvoices.Add(new AddressInvoiceData()
{
InvoiceDataId = invoiceId,
CreatedTime = DateTimeOffset.UtcNow
}
.SetHash(bitcoinAddress.ScriptPubKey.Hash, network.CryptoCode));
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
{
@ -203,6 +191,21 @@ namespace BTCPayServer.Services.Invoices
}
}
public async Task AddInvoiceEvent(string invoiceId, object evt)
{
using (var context = _ContextFactory.CreateContext())
{
context.InvoiceEvents.Add(new InvoiceEventData()
{
InvoiceDataId = invoiceId,
Message = evt.ToString(),
Timestamp = DateTimeOffset.UtcNow,
UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10))
});
await context.SaveChangesAsync();
}
}
private static void MarkUnassigned(string invoiceId, InvoiceEntity entity, ApplicationDbContext context, string cryptoCode)
{
foreach (var address in entity.GetCryptoData(null))
@ -211,7 +214,7 @@ namespace BTCPayServer.Services.Invoices
continue;
var historical = new HistoricalAddressInvoiceData();
historical.InvoiceDataId = invoiceId;
historical.SetAddress(address.Value.DepositAddress, cryptoCode);
historical.SetAddress(address.Value.DepositAddress, address.Value.CryptoCode);
historical.UnAssigned = DateTimeOffset.UtcNow;
context.Attach(historical);
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
@ -225,7 +228,7 @@ namespace BTCPayServer.Services.Invoices
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
if (invoiceData == null)
return;
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob, null);
MarkUnassigned(invoiceId, invoiceEntity, context, null);
try
{
@ -308,11 +311,11 @@ namespace BTCPayServer.Services.Invoices
private InvoiceEntity ToEntity(InvoiceData invoice)
{
var entity = ToObject<InvoiceEntity>(invoice.Blob);
var entity = ToObject<InvoiceEntity>(invoice.Blob, null);
#pragma warning disable CS0618
entity.Payments = invoice.Payments.Select(p =>
{
var paymentEntity = ToObject<PaymentEntity>(p.Blob);
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
paymentEntity.Accounted = p.Accounted;
return paymentEntity;
}).ToList();
@ -329,6 +332,10 @@ namespace BTCPayServer.Services.Invoices
{
entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.GetHash() + a.GetCryptoCode()).ToHashSet();
}
if(invoice.Events != null)
{
entity.Events = invoice.Events.OrderBy(c => c.Timestamp).ToList();
}
return entity;
}
@ -341,8 +348,10 @@ namespace BTCPayServer.Services.Invoices
.Invoices
.Include(o => o.Payments)
.Include(o => o.RefundAddresses);
if(queryObject.IncludeAddresses)
if (queryObject.IncludeAddresses)
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
if (queryObject.IncludeEvents)
query = query.Include(o => o.Events);
if (!string.IsNullOrEmpty(queryObject.InvoiceId))
{
query = query.Where(i => i.Id == queryObject.InvoiceId);
@ -396,7 +405,7 @@ namespace BTCPayServer.Services.Invoices
}
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs)
public async Task AddRefundsAsync(string invoiceId, TxOut[] outputs, Network network)
{
if (outputs.Length == 0)
return;
@ -410,14 +419,14 @@ namespace BTCPayServer.Services.Invoices
{
Id = invoiceId + "-" + i,
InvoiceDataId = invoiceId,
Blob = ToBytes(output)
Blob = ToBytes(output, network)
});
i++;
}
await context.SaveChangesAsync().ConfigureAwait(false);
}
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(_Network)).Where(a => a != null).ToArray();
var addresses = outputs.Select(o => o.ScriptPubKey.GetDestinationAddress(network)).Where(a => a != null).ToArray();
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
}
@ -438,7 +447,7 @@ namespace BTCPayServer.Services.Invoices
PaymentData data = new PaymentData
{
Id = receivedCoin.Outpoint.ToString(),
Blob = ToBytes(entity),
Blob = ToBytes(entity, null),
InvoiceDataId = invoiceId
};
@ -468,24 +477,24 @@ namespace BTCPayServer.Services.Invoices
}
}
private T ToObject<T>(byte[] value)
private T ToObject<T>(byte[] value, Network network)
{
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), Network);
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ZipUtils.Unzip(value), network);
}
private byte[] ToBytes<T>(T obj)
private byte[] ToBytes<T>(T obj, Network network)
{
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj));
return ZipUtils.Zip(NBitcoin.JsonConverters.Serializer.ToString(obj, network));
}
private T Clone<T>(T invoice)
private T Clone<T>(T invoice, Network network)
{
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice), Network);
return NBitcoin.JsonConverters.Serializer.ToObject<T>(ToString(invoice, network), network);
}
private string ToString<T>(T data)
private string ToString<T>(T data, Network network)
{
return NBitcoin.JsonConverters.Serializer.ToString(data, Network);
return NBitcoin.JsonConverters.Serializer.ToString(data, network);
}
public void Dispose()
@ -551,5 +560,7 @@ namespace BTCPayServer.Services.Invoices
set;
}
public bool IncludeAddresses { get; set; }
public bool IncludeEvents { get; set; }
}
}

View File

@ -1,4 +1,6 @@
using Hangfire;
using BTCPayServer.Logging;
using Microsoft.Extensions.Logging;
using Hangfire;
using System;
using System.Collections.Generic;
using System.Linq;
@ -20,10 +22,16 @@ namespace BTCPayServer.Services.Mails
_JobClient = jobClient;
_Repository = repository;
}
public Task SendEmailAsync(string email, string subject, string message)
public async Task SendEmailAsync(string email, string subject, string message)
{
var settings = await _Repository.GetSettingAsync<EmailSettings>();
if (settings == null)
{
Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured");
return;
}
_JobClient.Schedule(() => SendMailCore(email, subject, message), TimeSpan.Zero);
return Task.CompletedTask;
return;
}
public async Task SendMailCore(string email, string subject, string message)

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using NBitcoin;
using NBXplorer.Models;
namespace BTCPayServer.Services
{
public class TransactionCacheProvider
{
IOptions<MemoryCacheOptions> _Options;
public TransactionCacheProvider(IOptions<MemoryCacheOptions> options)
{
_Options = options;
}
ConcurrentDictionary<string, TransactionCache> _TransactionCaches = new ConcurrentDictionary<string, TransactionCache>();
public TransactionCache GetTransactionCache(BTCPayNetwork network)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
return _TransactionCaches.GetOrAdd(network.CryptoCode, c => new TransactionCache(_Options, network));
}
}
public class TransactionCache : IDisposable
{
IOptions<MemoryCacheOptions> _Options;
public TransactionCache(IOptions<MemoryCacheOptions> options, BTCPayNetwork network)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
_Options = options;
_MemoryCache = new MemoryCache(_Options);
Network = network;
}
uint256 _LastHash;
int _ConfOffset;
IMemoryCache _MemoryCache;
public void NewBlock(uint256 newHash, uint256 previousHash)
{
if (_LastHash != previousHash)
{
var old = _MemoryCache;
_ConfOffset = 0;
_MemoryCache = new MemoryCache(_Options);
Thread.MemoryBarrier();
old.Dispose();
}
else
_ConfOffset++;
_LastHash = newHash;
}
public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(60);
public BTCPayNetwork Network { get; private set; }
public void AddToCache(TransactionResult tx)
{
_MemoryCache.Set(tx.Transaction.GetHash(), tx, DateTimeOffset.UtcNow + CacheSpan);
}
public TransactionResult GetTransaction(uint256 txId)
{
_MemoryCache.TryGetValue(txId.ToString(), out object tx);
var result = tx as TransactionResult;
var confOffset = _ConfOffset;
if (result != null && result.Confirmations > 0 && confOffset > 0)
{
var serializer = new NBXplorer.Serializer(Network.NBitcoinNetwork);
result = serializer.ToObject<TransactionResult>(serializer.ToString(result));
result.Confirmations += confOffset;
result.Height += confOffset;
}
return result;
}
public void Dispose()
{
_MemoryCache.Dispose();
}
}
}

View File

@ -33,13 +33,14 @@ namespace BTCPayServer.Services.Wallets
public class BTCPayWallet
{
private ExplorerClient _Client;
public BTCPayWallet(ExplorerClient client, BTCPayNetwork network)
private TransactionCache _Cache;
public BTCPayWallet(ExplorerClient client, TransactionCache cache, BTCPayNetwork network)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
_Client = client;
_Network = network;
_Cache = cache;
}
@ -56,8 +57,16 @@ namespace BTCPayServer.Services.Wallets
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
{
if (derivationStrategy == null)
throw new ArgumentNullException(nameof(derivationStrategy));
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
return pathInfo.ScriptPubKey.GetDestinationAddress(_Client.Network);
// Might happen on some broken install
if (pathInfo == null)
{
await _Client.TrackAsync(derivationStrategy).ConfigureAwait(false);
pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
}
return pathInfo.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork);
}
public async Task TrackAsync(DerivationStrategyBase derivationStrategy)
@ -65,11 +74,16 @@ namespace BTCPayServer.Services.Wallets
await _Client.TrackAsync(derivationStrategy);
}
public Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
public async Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
{
if (txId == null)
throw new ArgumentNullException(nameof(txId));
return _Client.GetTransactionAsync(txId, cancellation);
var tx = _Cache.GetTransaction(txId);
if (tx != null)
return tx;
tx = await _Client.GetTransactionAsync(txId, cancellation);
_Cache.AddToCache(tx);
return tx;
}
public async Task<NetworkCoins> GetCoins(DerivationStrategyBase strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
@ -94,9 +108,17 @@ namespace BTCPayServer.Services.Wallets
public async Task<Money> GetBalance(DerivationStrategyBase derivationStrategy)
{
var result = await _Client.SyncAsync(derivationStrategy, null, true);
return result.Confirmed.UTXOs.Select(u => u.Value)
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Value))
.Sum();
Dictionary<OutPoint, UTXO> received = new Dictionary<OutPoint, UTXO>();
foreach(var utxo in result.Confirmed.UTXOs.Concat(result.Unconfirmed.UTXOs))
{
received.TryAdd(utxo.Outpoint, utxo);
}
foreach (var utxo in result.Confirmed.SpentOutpoints.Concat(result.Unconfirmed.SpentOutpoints))
{
received.Remove(utxo);
}
return received.Values.Select(c => c.Value).Sum();
}
}
}

View File

@ -10,11 +10,15 @@ namespace BTCPayServer.Services.Wallets
{
private ExplorerClientProvider _Client;
BTCPayNetworkProvider _NetworkProvider;
public BTCPayWalletProvider(ExplorerClientProvider client, BTCPayNetworkProvider networkProvider)
TransactionCacheProvider _TransactionCacheProvider;
public BTCPayWalletProvider(ExplorerClientProvider client,
TransactionCacheProvider transactionCacheProvider,
BTCPayNetworkProvider networkProvider)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
_Client = client;
_TransactionCacheProvider = transactionCacheProvider;
_NetworkProvider = networkProvider;
}
@ -32,7 +36,7 @@ namespace BTCPayServer.Services.Wallets
var client = _Client.GetExplorerClient(cryptoCode);
if (network == null || client == null)
return null;
return new BTCPayWallet(client, network);
return new BTCPayWallet(client, _TransactionCacheProvider.GetTransactionCache(network), network);
}
}
}

View File

@ -77,6 +77,10 @@
<th>Notification Url</th>
<td>@Model.NotificationUrl</td>
</tr>
<tr>
<th>Redirect Url</th>
<td>@Model.RedirectUrl</td>
</tr>
</table>
</div>
@ -221,5 +225,28 @@
</table>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>Events</h3>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Date</th>
<th>Message</th>
</tr>
</thead>
<tbody>
@foreach(var evt in Model.Events)
{
<tr>
<td>@evt.Timestamp</td>
<td>@evt.Message</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</section>

View File

@ -103,7 +103,13 @@
<h4>@line.Network.CryptoCode</h4>
@if(line.Status == null)
{
<p>NBXplorer is offline</p>
<ul>
<li>The node is offline</li>
@if(line.Error != null)
{
<li>Last error: @line.Error</li>
}
</ul>
}
else
{
@ -118,11 +124,21 @@
else
{
<li>The node is offline</li>
@if(line.Error != null)
{
<li>Last error: @line.Error</li>
}
}
}
else if(line.Status.BitcoinStatus.IsSynched)
{
<li>The node is synched</li>
<li>The node is synched (Height: @line.Status.BitcoinStatus.Headers)</li>
@if(line.Status.BitcoinStatus.IsSynched &&
line.Status.SyncHeight.HasValue &&
line.Status.SyncHeight.Value < line.Status.BitcoinStatus.Headers)
{
<li>NBXplorer is synching... (Height: @line.Status.SyncHeight.Value)</li>
}
}
else
{