Add litecoin to docker-compose fix bugs when two networks generate same address

This commit is contained in:
nicolas.dorier 2018-01-09 01:56:37 +09:00
parent a048494f34
commit 31672a2587
10 changed files with 136 additions and 83 deletions

View file

@ -383,9 +383,9 @@ namespace BTCPayServer.Tests
Assert.True(IsMapped(localInvoice, ctx)); Assert.True(IsMapped(localInvoice, ctx));
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult(); invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == invoice.BitcoinAddress.ToString()); var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress.ToString());
Assert.NotNull(historical1.UnAssigned); Assert.NotNull(historical1.UnAssigned);
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == localInvoice.BitcoinAddress.ToString()); var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress.ToString());
Assert.Null(historical2.UnAssigned); Assert.Null(historical2.UnAssigned);
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network); invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
secondPayment = localInvoice.BtcDue; secondPayment = localInvoice.BtcDue;
@ -473,8 +473,8 @@ namespace BTCPayServer.Tests
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx) private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
{ {
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString(); var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash;
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.Address == h) != null; return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetHash() == h) != null;
} }
private void Eventually(Action act) private void Eventually(Action act)

View file

@ -11,19 +11,17 @@ services:
dockerfile: BTCPayServer.Tests/Dockerfile dockerfile: BTCPayServer.Tests/Dockerfile
environment: environment:
TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3 TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_NBXPLORERURL: http://nbxplorer:32838/ TESTS_BTCNBXPLORERURL: http://bitcoin-nbxplorer:32838/
TESTS_LTCNBXPLORERURL: http://litecoin-nbxplorer:32839/
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
TESTS_PORT: 80 TESTS_PORT: 80
TESTS_HOSTNAME: tests TESTS_HOSTNAME: tests
# TEST_ECLAIR1: http://eclair1:8080/
# TEST_ECLAIR2: http://eclair2:8080/
expose: expose:
- "80" - "80"
links: links:
- bitcoind - bitcoin-nbxplorer
- nbxplorer - litecoin-nbxplorer
# - eclair1 - postgres
# - eclair2
extra_hosts: extra_hosts:
- "tests:127.0.0.1" - "tests:127.0.0.1"
@ -35,12 +33,11 @@ services:
regtest=1 regtest=1
connect=bitcoind:39388 connect=bitcoind:39388
links: links:
- bitcoind - bitcoin-nbxplorer
- nbxplorer - litecoin-nbxplorer
# - eclair1 - postgres
# - eclair2
nbxplorer: bitcoin-nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.39 image: nicolasdorier/nbxplorer:1.0.0.39
ports: ports:
- "32838:32838" - "32838:32838"
@ -57,49 +54,24 @@ services:
NBXPLORER_NOAUTH: 1 NBXPLORER_NOAUTH: 1
links: links:
- bitcoind - bitcoind
- postgres
eclair1: litecoin-nbxplorer:
image: acinq/eclair:latest image: nicolasdorier/nbxplorer:1.0.0.39
environment:
JAVA_OPTS: >
-Xmx512m
-Declair.printToConsole
-Declair.bitcoind.host=bitcoind
-Declair.bitcoind.rpcport=43782
-Declair.bitcoind.rpcuser=ceiwHEbqWI83
-Declair.bitcoind.rpcpassword=DwubwWsoo3
-Declair.bitcoind.zmq=tcp://bitcoind:29000
-Declair.chain=regtest
-Declair.api.binding-ip=0.0.0.0
links:
- bitcoind
ports: ports:
- "30992:8080" # api port - "32839:32839"
expose: expose:
- "9735" # server port - "32839"
- "8080" # api port
eclair2:
image: acinq/eclair:latest
environment: environment:
JAVA_OPTS: > NBXPLORER_NETWORK: ltc-regtest
-Xmx512m NBXPLORER_RPCURL: http://litecoind:43782/
-Declair.printToConsole NBXPLORER_RPCUSER: ceiwHEbqWI83
-Declair.bitcoind.host=bitcoind NBXPLORER_RPCPASSWORD: DwubwWsoo3
-Declair.bitcoind.rpcport=43782 NBXPLORER_NODEENDPOINT: litecoind:39388
-Declair.bitcoind.rpcuser=ceiwHEbqWI83 NBXPLORER_BIND: 0.0.0.0:32839
-Declair.bitcoind.rpcpassword=DwubwWsoo3 NBXPLORER_VERBOSE: 1
-Declair.bitcoind.zmq=tcp://bitcoind:29000 NBXPLORER_NOAUTH: 1
-Declair.chain=regtest
-Declair.api.binding-ip=0.0.0.0
links: links:
- bitcoind - litecoind
ports:
- "30993:8080" # api port
expose:
- "9735" # server port
- "8080" # api port
bitcoind: bitcoind:
container_name: btcpayserver_dev_bitcoind container_name: btcpayserver_dev_bitcoind
@ -116,8 +88,30 @@ services:
zmqpubrawblock=tcp://0.0.0.0:29000 zmqpubrawblock=tcp://0.0.0.0:29000
zmqpubrawtx=tcp://0.0.0.0:29000 zmqpubrawtx=tcp://0.0.0.0:29000
txindex=1 txindex=1
ports: ports:
- "43782:43782" # RPC - "43782:43782"
expose:
- "43782" # RPC
- "39388" # P2P
- "29000" # zmq
litecoind:
container_name: btcpayserver_dev_litecoind
image: nicolasdorier/docker-litecoin:0.14.2
environment:
BITCOIN_EXTRA_ARGS: |
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
regtest=1
server=1
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: expose:
- "43782" # RPC - "43782" # RPC
- "39388" # P2P - "39388" # P2P

View file

@ -0,0 +1 @@
docker exec -ti btcpayserver_dev_litecoind litecoin-cli -regtest -conf="/data/litecoin.conf" -datadir="/data" $args

View file

@ -45,11 +45,14 @@ namespace BTCPayServer.Configuration
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory); DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
Logs.Configuration.LogInformation("Network: " + Network); Logs.Configuration.LogInformation("Network: " + Network);
foreach (var net in new BTCPayNetworkProvider(Network).GetAll()) foreach (var net in new BTCPayNetworkProvider(Network).GetAll())
{ {
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(net.NBitcoinNetwork.Name);
var explorer = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", null); var explorer = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", null);
var cookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", null); var cookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
if (explorer != null && cookieFile != null) if (explorer != null)
{ {
ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile)); ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile));
} }
@ -58,7 +61,7 @@ namespace BTCPayServer.Configuration
// Handle legacy explorer.url and explorer.cookiefile // Handle legacy explorer.url and explorer.cookiefile
if (ExplorerFactories.Count == 0) if (ExplorerFactories.Count == 0)
{ {
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(Network.Name); 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 explorer = conf.GetOrDefault<Uri>($"explorer.url", new Uri(nbxplorer.GetDefaultExplorerUrl(), UriKind.Absolute));
var cookieFile = conf.GetOrDefault<string>($"explorer.cookiefile", nbxplorer.GetDefaultCookieFile()); var cookieFile = conf.GetOrDefault<string>($"explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
ExplorerFactories.Add("BTC", (n) => CreateExplorerClient(n, explorer, cookieFile)); ExplorerFactories.Add("BTC", (n) => CreateExplorerClient(n, explorer, cookieFile));

View file

@ -2,16 +2,49 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NBitcoin;
namespace BTCPayServer.Data namespace BTCPayServer.Data
{ {
public class AddressInvoiceData public class AddressInvoiceData
{ {
/// <summary>
/// Some crypto currencies share same address prefix
/// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE"
/// </summary>
[Obsolete("Use GetHash instead")]
public string Address public string Address
{ {
get; set; get; set;
} }
#pragma warning disable CS0618
public ScriptId GetHash()
{
if (Address == null)
return null;
var index = Address.IndexOf("#");
if (index == -1)
return new ScriptId(Address);
return new ScriptId(Address.Substring(0, index));
}
public AddressInvoiceData SetHash(ScriptId scriptId, string cryptoCode)
{
Address = scriptId + "#" + cryptoCode;
return this;
}
public string GetCryptoCode()
{
if (Address == null)
return null;
var index = Address.IndexOf("#");
if (index == -1)
return "BTC";
return Address.Substring(index + 1);
}
#pragma warning restore CS0618
public InvoiceData InvoiceData public InvoiceData InvoiceData
{ {
get; set; get; set;
@ -26,5 +59,6 @@ namespace BTCPayServer.Data
{ {
get; set; get; set;
} }
} }
} }

View file

@ -113,7 +113,9 @@ namespace BTCPayServer.Data
.HasForeignKey(pt => pt.StoreDataId); .HasForeignKey(pt => pt.StoreDataId);
builder.Entity<AddressInvoiceData>() builder.Entity<AddressInvoiceData>()
#pragma warning disable CS0618
.HasKey(o => o.Address); .HasKey(o => o.Address);
#pragma warning restore CS0618
builder.Entity<PairingCodeData>() builder.Entity<PairingCodeData>()
.HasKey(o => o.Id); .HasKey(o => o.Id);
@ -128,7 +130,9 @@ namespace BTCPayServer.Data
.HasKey(o => new .HasKey(o => new
{ {
o.InvoiceDataId, o.InvoiceDataId,
#pragma warning disable CS0618
o.Address o.Address
#pragma warning restore CS0618
}); });
} }
} }

View file

@ -12,6 +12,11 @@ namespace BTCPayServer.Data
get; set; get; set;
} }
/// <summary>
/// Some crypto currencies share same address prefix
/// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE"
/// </summary>
[Obsolete("Use GetCryptoCode instead")]
public string Address public string Address
{ {
get; set; get; set;
@ -26,6 +31,21 @@ namespace BTCPayServer.Data
{ {
return string.IsNullOrEmpty(CryptoCode) ? "BTC" : CryptoCode; return string.IsNullOrEmpty(CryptoCode) ? "BTC" : CryptoCode;
} }
public string GetAddress()
{
if (Address == null)
return null;
var index = Address.IndexOf("#");
if (index == -1)
return Address;
return Address.Substring(0, index);
}
public HistoricalAddressInvoiceData SetAddress(string depositAddress, string cryptoCode)
{
Address = depositAddress + "#" + cryptoCode;
CryptoCode = cryptoCode;
return this;
}
#pragma warning restore CS0618 #pragma warning restore CS0618
public DateTimeOffset Assigned public DateTimeOffset Assigned

View file

@ -63,9 +63,9 @@ namespace BTCPayServer.HostedServices
} }
CompositeDisposable leases = new CompositeDisposable(); CompositeDisposable leases = new CompositeDisposable();
async Task NotifyReceived(Script scriptPubKey) async Task NotifyReceived(Script scriptPubKey, BTCPayNetwork network)
{ {
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey); var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey, network.CryptoCode);
if (invoice != null) if (invoice != null)
_WatchRequests.Add(invoice); _WatchRequests.Add(invoice);
} }
@ -149,7 +149,7 @@ namespace BTCPayServer.HostedServices
var getCoinsResponses = getCoinsResponsesAsync.Select(g => g.Result).ToArray(); var getCoinsResponses = getCoinsResponsesAsync.Select(g => g.Result).ToArray();
foreach (var response in getCoinsResponses) foreach (var response in getCoinsResponses)
{ {
response.Coins = response.Coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString())).ToArray(); response.Coins = response.Coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString() + response.Strategy.Network.CryptoCode)).ToArray();
} }
var coins = getCoinsResponses.Where(s => s.Coins.Length != 0).FirstOrDefault(); var coins = getCoinsResponses.Where(s => s.Coins.Length != 0).FirstOrDefault();
bool dirtyAddress = false; bool dirtyAddress = false;
@ -208,7 +208,6 @@ namespace BTCPayServer.HostedServices
if (totalPaid < accounting.TotalDue && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial") if (totalPaid < accounting.TotalDue && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
{ {
Logs.PayServer.LogInformation("Paid to " + cryptoData.DepositAddress);
invoice.ExceptionStatus = "paidPartial"; invoice.ExceptionStatus = "paidPartial";
context.MarkDirty(); context.MarkDirty();
if (dirtyAddress) if (dirtyAddress)
@ -379,7 +378,7 @@ namespace BTCPayServer.HostedServices
}, null, 0, (int)PollInterval.TotalMilliseconds); }, null, 0, (int)PollInterval.TotalMilliseconds);
leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); })); leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); }));
leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey); })); leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey, b.Network); }));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceCreatedEvent>(b => { Watch(b.InvoiceId); })); leases.Add(_EventAggregator.Subscribe<Events.InvoiceCreatedEvent>(b => { Watch(b.InvoiceId); }));
return Task.CompletedTask; return Task.CompletedTask;

View file

@ -15,7 +15,8 @@
"commandName": "Project", "commandName": "Project",
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"BTCPAY_EXPLORERURL": "http://127.0.0.1:32838/", "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32839/",
"BTCPAY_NETWORK": "regtest", "BTCPAY_NETWORK": "regtest",
"ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver" "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
@ -23,4 +24,4 @@
"applicationUrl": "http://localhost:14142/" "applicationUrl": "http://localhost:14142/"
} }
} }
} }

View file

@ -70,11 +70,11 @@ namespace BTCPayServer.Services.Invoices
} }
} }
public async Task<string> GetInvoiceIdFromScriptPubKey(Script scriptPubKey) public async Task<string> GetInvoiceIdFromScriptPubKey(Script scriptPubKey, string cryptoCode)
{ {
using (var db = _ContextFactory.CreateContext()) using (var db = _ContextFactory.CreateContext())
{ {
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString()); var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString() + "#" + cryptoCode);
return result?.InvoiceDataId; return result?.InvoiceDataId;
} }
} }
@ -130,19 +130,14 @@ namespace BTCPayServer.Services.Invoices
throw new InvalidOperationException("CryptoCode unsupported"); throw new InvalidOperationException("CryptoCode unsupported");
context.AddressInvoices.Add(new AddressInvoiceData() context.AddressInvoices.Add(new AddressInvoiceData()
{ {
Address = BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash.ToString(),
InvoiceDataId = invoice.Id, InvoiceDataId = invoice.Id,
CreatedTime = DateTimeOffset.UtcNow, CreatedTime = DateTimeOffset.UtcNow,
}); }.SetHash(BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash, network.CryptoCode));
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData() context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
{ {
InvoiceDataId = invoice.Id, InvoiceDataId = invoice.Id,
Address = cryptoData.DepositAddress,
#pragma warning disable CS0618
CryptoCode = cryptoData.CryptoCode,
#pragma warning restore CS0618
Assigned = DateTimeOffset.UtcNow Assigned = DateTimeOffset.UtcNow
}); }.SetAddress(cryptoData.DepositAddress, cryptoData.CryptoCode));
textSearch.Add(cryptoData.DepositAddress); textSearch.Add(cryptoData.DepositAddress);
textSearch.Add(cryptoData.Calculate().TotalDue.ToString()); textSearch.Add(cryptoData.Calculate().TotalDue.ToString());
} }
@ -189,17 +184,18 @@ namespace BTCPayServer.Services.Invoices
{ {
invoiceEntity.DepositAddress = currencyData.DepositAddress; invoiceEntity.DepositAddress = currencyData.DepositAddress;
} }
#pragma warning disable CS0618 #pragma warning restore CS0618
invoiceEntity.SetCryptoData(cryptoData); invoiceEntity.SetCryptoData(cryptoData);
invoice.Blob = ToBytes(invoiceEntity); invoice.Blob = ToBytes(invoiceEntity);
context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), 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() context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
{ {
InvoiceDataId = invoiceId, InvoiceDataId = invoiceId,
Address = bitcoinAddress.ToString(),
Assigned = DateTimeOffset.UtcNow Assigned = DateTimeOffset.UtcNow
}); }.SetAddress(bitcoinAddress.ToString(), network.CryptoCode));
await context.SaveChangesAsync(); await context.SaveChangesAsync();
AddToTextSearch(invoice.Id, bitcoinAddress.ToString()); AddToTextSearch(invoice.Id, bitcoinAddress.ToString());
@ -215,7 +211,7 @@ namespace BTCPayServer.Services.Invoices
continue; continue;
var historical = new HistoricalAddressInvoiceData(); var historical = new HistoricalAddressInvoiceData();
historical.InvoiceDataId = invoiceId; historical.InvoiceDataId = invoiceId;
historical.Address = address.Value.DepositAddress; historical.SetAddress(address.Value.DepositAddress, cryptoCode);
historical.UnAssigned = DateTimeOffset.UtcNow; historical.UnAssigned = DateTimeOffset.UtcNow;
context.Attach(historical); context.Attach(historical);
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true; context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
@ -329,13 +325,12 @@ namespace BTCPayServer.Services.Invoices
} }
if (invoice.AddressInvoices != null) if (invoice.AddressInvoices != null)
{ {
entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.Address).ToHashSet(); entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.GetHash() + a.GetCryptoCode()).ToHashSet();
} }
return entity; return entity;
} }
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject) public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
{ {
using (var context = _ContextFactory.CreateContext()) using (var context = _ContextFactory.CreateContext())
@ -431,7 +426,9 @@ namespace BTCPayServer.Services.Invoices
PaymentEntity entity = new PaymentEntity PaymentEntity entity = new PaymentEntity
{ {
Outpoint = receivedCoin.Outpoint, Outpoint = receivedCoin.Outpoint,
#pragma warning disable CS0618
Output = receivedCoin.TxOut, Output = receivedCoin.TxOut,
#pragma warning restore CS0618
ReceivedTime = DateTime.UtcNow ReceivedTime = DateTime.UtcNow
}; };