Compare commits

...

6 Commits

28 changed files with 334 additions and 220 deletions

View File

@ -4,7 +4,7 @@
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
<NoWarn>NU1701</NoWarn>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@ -18,6 +18,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Threading;
using BTCPayServer.Eclair;
using System.Globalization;
namespace BTCPayServer.Tests
{
@ -60,7 +61,7 @@ namespace BTCPayServer.Tests
LTCNBXplorerUri = LTCExplorerClient.Address,
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver")
};
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString()));
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
PayTester.Start();

View File

@ -26,6 +26,7 @@ using BTCPayServer.Eclair;
using System.Collections.Generic;
using BTCPayServer.Models.StoreViewModels;
using System.Threading.Tasks;
using System.Globalization;
namespace BTCPayServer.Tests
{
@ -312,7 +313,7 @@ namespace BTCPayServer.Tests
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
var store2 = acc.CreateStore();
store2.Pair(pairingCode.ToString(), store2.CreatedStoreId).GetAwaiter().GetResult();
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage);
Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage, StringComparison.CurrentCultureIgnoreCase);
}
}
@ -334,7 +335,7 @@ namespace BTCPayServer.Tests
var payment2 = Money.Coins(0.08m);
var tx1 = new uint256(tester.ExplorerNode.SendCommand("sendtoaddress", new object[]
{
invoice.BitcoinAddress.ToString(),
invoice.BitcoinAddress,
payment1.ToString(),
null, //comment
null, //comment_to
@ -565,7 +566,7 @@ namespace BTCPayServer.Tests
var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC");
Assert.NotNull(ltcCryptoInfo);
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due));
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
cashCow.Generate(2); // LTC is not worth a lot, so just to make sure we have money...
cashCow.SendToAddress(invoiceAddress, secondPayment);
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
@ -667,9 +668,9 @@ namespace BTCPayServer.Tests
Assert.True(IsMapped(localInvoice, ctx));
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress.ToString());
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress);
Assert.NotNull(historical1.UnAssigned);
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress.ToString());
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress);
Assert.Null(historical2.UnAssigned);
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
secondPayment = localInvoice.BtcDue;

View File

@ -66,7 +66,7 @@ namespace BTCPayServer
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public int MaxTrackedConfirmation { get; internal set; } = 7;
public int MaxTrackedConfirmation { get; internal set; } = 6;
public override string ToString()
{

View File

@ -2,8 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.1.33</Version>
<NoWarn>NU1701</NoWarn>
<Version>1.0.1.35</Version>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Build\dockerfiles\**" />
@ -22,8 +22,10 @@
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
<PackageReference Include="LedgerWallet" Version="1.0.1.32" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.6.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="NBitcoin" Version="4.0.0.54" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
<PackageReference Include="NBitcoin" Version="4.0.0.55" />
<PackageReference Include="NBitpayClient" Version="1.0.0.16" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.1.9" />
@ -33,12 +35,12 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.1" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.0.11" />
<PackageReference Include="Text.Analyzers" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Microsoft Managed Recommended Rules" Description="These rules focus on the most critical problems in your code, including potential security holes, application crashes, and other important logic and design errors. It is recommended to include this rule set in any custom rule set you create for your projects." ToolsVersion="10.0">
<Localization ResourceAssembly="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.dll" ResourceBaseName="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.Localized">
<Name Resource="MinimumRecommendedRules_Name" />
<Description Resource="MinimumRecommendedRules_Description" />
</Localization>
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1001" Action="Warning" />
<Rule Id="CA1009" Action="Warning" />
<Rule Id="CA1016" Action="Warning" />
<Rule Id="CA1033" Action="Warning" />
<Rule Id="CA1049" Action="Warning" />
<Rule Id="CA1060" Action="Warning" />
<Rule Id="CA1061" Action="Warning" />
<Rule Id="CA1063" Action="Warning" />
<Rule Id="CA1065" Action="Warning" />
<Rule Id="CA1301" Action="Warning" />
<Rule Id="CA1400" Action="Warning" />
<Rule Id="CA1401" Action="Warning" />
<Rule Id="CA1403" Action="Warning" />
<Rule Id="CA1404" Action="Warning" />
<Rule Id="CA1405" Action="Warning" />
<Rule Id="CA1410" Action="Warning" />
<Rule Id="CA1415" Action="Warning" />
<Rule Id="CA1821" Action="Warning" />
<Rule Id="CA1900" Action="Warning" />
<Rule Id="CA1901" Action="Warning" />
<Rule Id="CA2002" Action="Warning" />
<Rule Id="CA2100" Action="Warning" />
<Rule Id="CA2101" Action="Warning" />
<Rule Id="CA2108" Action="Warning" />
<Rule Id="CA2111" Action="Warning" />
<Rule Id="CA2112" Action="Warning" />
<Rule Id="CA2114" Action="Warning" />
<Rule Id="CA2116" Action="Warning" />
<Rule Id="CA2117" Action="Warning" />
<Rule Id="CA2122" Action="Warning" />
<Rule Id="CA2123" Action="Warning" />
<Rule Id="CA2124" Action="Warning" />
<Rule Id="CA2126" Action="Warning" />
<Rule Id="CA2131" Action="Warning" />
<Rule Id="CA2132" Action="Warning" />
<Rule Id="CA2133" Action="Warning" />
<Rule Id="CA2134" Action="Warning" />
<Rule Id="CA2137" Action="Warning" />
<Rule Id="CA2138" Action="Warning" />
<Rule Id="CA2140" Action="Warning" />
<Rule Id="CA2141" Action="Warning" />
<Rule Id="CA2146" Action="Warning" />
<Rule Id="CA2147" Action="Warning" />
<Rule Id="CA2149" Action="Warning" />
<Rule Id="CA2200" Action="Warning" />
<Rule Id="CA2202" Action="Warning" />
<Rule Id="CA2207" Action="Warning" />
<Rule Id="CA2212" Action="Warning" />
<Rule Id="CA2213" Action="Warning" />
<Rule Id="CA2214" Action="Warning" />
<Rule Id="CA2216" Action="Warning" />
<Rule Id="CA2220" Action="Warning" />
<Rule Id="CA2229" Action="Warning" />
<Rule Id="CA2231" Action="Warning" />
<Rule Id="CA2232" Action="Warning" />
<Rule Id="CA2235" Action="Warning" />
<Rule Id="CA2236" Action="Warning" />
<Rule Id="CA2237" Action="Warning" />
<Rule Id="CA2238" Action="Warning" />
<Rule Id="CA2240" Action="Warning" />
<Rule Id="CA2241" Action="Warning" />
<Rule Id="CA2242" Action="Warning" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Analyzers" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Analyzers" />
</RuleSet>

View File

@ -13,7 +13,7 @@ namespace BTCPayServer.Configuration
{
public static T GetOrDefault<T>(this IConfiguration configuration, string key, T defaultValue)
{
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)];
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty, StringComparison.InvariantCulture)];
if (str == null)
return defaultValue;
if (typeof(T) == typeof(bool))
@ -32,12 +32,12 @@ namespace BTCPayServer.Configuration
return (T)(object)str;
else if (typeof(T) == typeof(IPEndPoint))
{
var separator = str.LastIndexOf(":");
var separator = str.LastIndexOf(":", StringComparison.InvariantCulture);
if (separator == -1)
throw new FormatException();
var ip = str.Substring(0, separator);
var port = str.Substring(separator + 1);
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port, CultureInfo.InvariantCulture));
}
else if (typeof(T) == typeof(int))
{

View File

@ -150,7 +150,7 @@ namespace BTCPayServer.Controllers
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty, StringComparison.InvariantCulture).Replace("-", string.Empty, StringComparison.InvariantCulture);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
@ -204,7 +204,7 @@ namespace BTCPayServer.Controllers
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty, StringComparison.InvariantCulture);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);

View File

@ -72,7 +72,7 @@ namespace BTCPayServer.Controllers
cryptoPayment.CryptoCode = paymentNetwork.CryptoCode;
cryptoPayment.Due = accounting.Due.ToString() + $" {paymentNetwork.CryptoCode}";
cryptoPayment.Paid = accounting.CryptoPaid.ToString() + $" {paymentNetwork.CryptoCode}";
cryptoPayment.Address = data.Value.DepositAddress.ToString();
cryptoPayment.Address = data.Value.DepositAddress;
cryptoPayment.Rate = FormatCurrency(data.Value);
cryptoPayment.PaymentUrl = cryptoInfo.PaymentUrls.BIP21;
model.CryptoPayments.Add(cryptoPayment);
@ -80,16 +80,36 @@ namespace BTCPayServer.Controllers
var payments = invoice
.GetPayments()
.Where(p => p.GetCryptoPaymentDataType() == BitcoinLikePaymentData.OnchainBitcoinType)
.Select(async payment =>
{
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
var m = new InvoiceDetailsModel.Payment();
var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode());
m.CryptoCode = payment.GetCryptoCode();
m.DepositAddress = payment.GetScriptPubKey().GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
m.Confirmations = (await _ExplorerClients.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
m.TransactionId = payment.Outpoint.Hash.ToString();
m.DepositAddress = paymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
int confirmationCount = 0;
if(paymentData.Legacy) // The confirmation count in the paymentData is not up to date
{
confirmationCount = (await _ExplorerClients.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(paymentData.Outpoint.Hash))?.Confirmations ?? 0;
}
else
{
confirmationCount = paymentData.ConfirmationCount;
}
if(confirmationCount >= paymentNetwork.MaxTrackedConfirmation)
{
m.Confirmations = "At least " + (paymentNetwork.MaxTrackedConfirmation);
}
else
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
m.TransactionId = paymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime;
m.TransactionLink = string.Format(paymentNetwork.BlockExplorerLink, m.TransactionId);
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, m.TransactionId);
m.Replaced = !payment.Accounted;
return m;
})
@ -207,10 +227,10 @@ namespace BTCPayServer.Controllers
{
StringBuilder builder = new StringBuilder();
if (expiration.Days >= 1)
builder.Append(expiration.Days.ToString());
builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture));
if (expiration.Hours >= 1)
builder.Append(expiration.Hours.ToString("00"));
builder.Append($"{expiration.Minutes.ToString("00")}:{expiration.Seconds.ToString("00")}");
builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture));
builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}");
return builder.ToString();
}

View File

@ -20,6 +20,7 @@ using NBitcoin;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Services.Mails;
using System.Globalization;
namespace BTCPayServer.Controllers
{
@ -434,7 +435,7 @@ namespace BTCPayServer.Controllers
}
// Strip spaces and hypens
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var verificationCode = model.Code.Replace(" ", string.Empty, StringComparison.InvariantCulture).Replace("-", string.Empty, StringComparison.InvariantCulture);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
@ -524,7 +525,7 @@ namespace BTCPayServer.Controllers
private string GenerateQrCodeUri(string email, string unformattedKey)
{
return string.Format(
return string.Format(CultureInfo.InvariantCulture,
AuthenicatorUriFormat,
_urlEncoder.Encode("BTCPayServer"),
_urlEncoder.Encode(email),

View File

@ -21,6 +21,7 @@ using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.WebSockets;
@ -179,7 +180,7 @@ namespace BTCPayServer.Controllers
{
try
{
feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate)), 1);
feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1);
}
catch { }
if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero)
@ -315,7 +316,7 @@ namespace BTCPayServer.Controllers
.Select(d => ((Wallet: _WalletProvider.GetWallet(d.Network),
DerivationStrategy: d.DerivationStrategyBase)))
.Where(_ => _.Wallet != null)
.Select(async _ => (await GetBalanceString(_)).ToString() + " " + _.Wallet.Network.CryptoCode))
.Select(async _ => (await GetBalanceString(_)) + " " + _.Wallet.Network.CryptoCode))
.ToArray();
await Task.WhenAll(balances.SelectMany(_ => _));
@ -604,7 +605,7 @@ namespace BTCPayServer.Controllers
var p2wpkh_p2sh = 0x049d7cb2U;
electrumMapping.Add(p2wpkh_p2sh, new string[] { "p2sh" });
var p2wpkh = 0x4b24746U;
electrumMapping.Add(p2wpkh, new string[] { });
electrumMapping.Add(p2wpkh, Array.Empty<string>());
var data = Encoders.Base58Check.DecodeData(derivationScheme);
if (data.Length < 4)

View File

@ -24,7 +24,7 @@ namespace BTCPayServer.Data
{
if (Address == null)
return null;
var index = Address.IndexOf("#");
var index = Address.IndexOf("#", StringComparison.InvariantCulture);
if (index == -1)
return new ScriptId(Address);
return new ScriptId(Address.Substring(0, index));
@ -38,7 +38,7 @@ namespace BTCPayServer.Data
{
if (Address == null)
return null;
var index = Address.IndexOf("#");
var index = Address.IndexOf("#", StringComparison.InvariantCulture);
if (index == -1)
return "BTC";
return Address.Substring(index + 1);

View File

@ -35,7 +35,7 @@ namespace BTCPayServer.Data
{
if (Address == null)
return null;
var index = Address.IndexOf("#");
var index = Address.IndexOf("#", StringComparison.InvariantCulture);
if (index == -1)
return Address;
return Address.Substring(0, index);

View File

@ -33,7 +33,7 @@ namespace BTCPayServer.Eclair
public Task<GetInfoResponse> GetInfoAsync()
{
return SendCommandAsync<GetInfoResponse>(new RPCRequest("getinfo", new object[] { }));
return SendCommandAsync<GetInfoResponse>(new RPCRequest("getinfo", Array.Empty<object>()));
}
public async Task<T> SendCommandAsync<T>(RPCRequest request, bool throwIfRPCError = true)
@ -104,7 +104,7 @@ namespace BTCPayServer.Eclair
public async Task<AllChannelResponse[]> AllChannelsAsync()
{
return await SendCommandAsync<AllChannelResponse[]>(new RPCRequest("allchannels", new object[] { })).ConfigureAwait(false);
return await SendCommandAsync<AllChannelResponse[]>(new RPCRequest("allchannels", Array.Empty<object>())).ConfigureAwait(false);
}
public string[] Channels()
@ -114,7 +114,7 @@ namespace BTCPayServer.Eclair
public async Task<string[]> ChannelsAsync()
{
return await SendCommandAsync<string[]>(new RPCRequest("channels", new object[] { })).ConfigureAwait(false);
return await SendCommandAsync<string[]>(new RPCRequest("channels", Array.Empty<object>())).ConfigureAwait(false);
}
public void Close(string channelId)
@ -155,7 +155,7 @@ namespace BTCPayServer.Eclair
public async Task<string[]> AllNodesAsync()
{
return await SendCommandAsync<string[]>(new RPCRequest("allnodes", new object[] { })).ConfigureAwait(false);
return await SendCommandAsync<string[]>(new RPCRequest("allnodes", Array.Empty<object>())).ConfigureAwait(false);
}
public Uri Address { get; private set; }

View File

@ -139,7 +139,7 @@ namespace BTCPayServer.Eclair
public IEnumerable<LightMoney> Split(int parts)
{
if (parts <= 0)
throw new ArgumentOutOfRangeException("Parts should be more than 0", "parts");
throw new ArgumentOutOfRangeException(nameof(parts), "Parts should be more than 0");
long remain;
long result = DivRem(_MilliSatoshis, parts, out remain);
@ -431,7 +431,7 @@ namespace BTCPayServer.Eclair
/// <returns></returns>
public string ToString(bool fplus, bool trimExcessZero = true)
{
var fmt = string.Format("{{0:{0}{1}B}}",
var fmt = string.Format(CultureInfo.InvariantCulture, "{{0:{0}{1}B}}",
(fplus ? "+" : null),
(trimExcessZero ? "2" : "11"));
return string.Format(BitcoinFormatter.Formatter, fmt, _MilliSatoshis);
@ -479,7 +479,7 @@ namespace BTCPayServer.Eclair
unitToUseInCalc = LightMoneyUnit.BTC;
break;
}
var val = Convert.ToDecimal(arg) / (long)unitToUseInCalc;
var val = Convert.ToDecimal(arg, CultureInfo.InvariantCulture) / (long)unitToUseInCalc;
var zeros = new string('0', decPos);
var rest = new string('#', 11 - decPos);
var fmt = plus && val > 0 ? "+" : string.Empty;

View File

@ -2,11 +2,18 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Events
{
public class InvoiceDataChangedEvent
{
public InvoiceDataChangedEvent(InvoiceEntity invoice)
{
InvoiceId = invoice.Id;
Status = invoice.Status;
ExceptionStatus = invoice.ExceptionStatus;
}
public string InvoiceId { get; set; }
public string Status { get; internal set; }
public string ExceptionStatus { get; internal set; }

View File

@ -7,6 +7,10 @@ namespace BTCPayServer.Events
{
public class InvoiceStopWatchedEvent
{
public InvoiceStopWatchedEvent(string invoiceId)
{
this.InvoiceId = invoiceId;
}
public string InvoiceId { get; set; }
public override string ToString()
{

View File

@ -63,7 +63,7 @@ namespace BTCPayServer
}
public static string WithTrailingSlash(this string str)
{
if (str.EndsWith("/"))
if (str.EndsWith("/", StringComparison.InvariantCulture))
return str;
return str + "/";
}

View File

@ -25,9 +25,9 @@ namespace BTCPayServer.HostedServices
{
class UpdateInvoiceContext
{
public UpdateInvoiceContext()
public UpdateInvoiceContext(InvoiceEntity invoice)
{
Invoice = invoice;
}
public InvoiceEntity Invoice { get; set; }
public List<object> Events { get; set; } = new List<object>();
@ -55,62 +55,7 @@ namespace BTCPayServer.HostedServices
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_NetworkProvider = networkProvider;
}
CompositeDisposable leases = new CompositeDisposable();
private async Task UpdateInvoice(string invoiceId, CancellationToken cancellation)
{
Dictionary<BTCPayNetwork, KnownState> changes = new Dictionary<BTCPayNetwork, KnownState>();
int maxLoop = 5;
int loopCount = -1;
while (!cancellation.IsCancellationRequested && loopCount < maxLoop)
{
loopCount++;
try
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true).ConfigureAwait(false);
if (invoice == null)
break;
var stateBefore = invoice.Status;
var updateContext = new UpdateInvoiceContext()
{
Invoice = invoice
};
await UpdateInvoice(updateContext).ConfigureAwait(false);
if (updateContext.Dirty)
{
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
updateContext.Events.Add(new InvoiceDataChangedEvent() { Status = invoice.Status, ExceptionStatus = invoice.ExceptionStatus, InvoiceId = invoice.Id });
}
var changed = stateBefore != invoice.Status;
foreach (var evt in updateContext.Events)
{
_EventAggregator.Publish(evt, evt.GetType());
}
if (invoice.Status == "complete" ||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
{
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false))
_EventAggregator.Publish<InvoiceStopWatchedEvent>(new InvoiceStopWatchedEvent() { InvoiceId = invoice.Id });
break;
}
if (updateContext.Events.Count == 0 || cancellation.IsCancellationRequested)
break;
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
Logs.PayServer.LogError(ex, "Unhandled error on watching invoice " + invoiceId);
await Task.Delay(10000, cancellation).ConfigureAwait(false);
}
}
}
CompositeDisposable leases = new CompositeDisposable();
private async Task UpdateInvoice(UpdateInvoiceContext context)
@ -165,7 +110,7 @@ namespace BTCPayServer.HostedServices
if (invoice.Status == "paid")
{
var transactions = payments.Where(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
var transactions = payments.Where(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
var totalConfirmed = transactions.Select(t => t.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
@ -233,13 +178,13 @@ namespace BTCPayServer.HostedServices
{
await Task.Delay(invoice.ExpirationTime - now, _Cts.Token);
}
_WatchRequests.Add(invoiceId);
Watch(invoiceId);
now = DateTimeOffset.UtcNow;
if (invoice.MonitoringExpiration > now)
{
await Task.Delay(invoice.MonitoringExpiration - now, _Cts.Token);
}
_WatchRequests.Add(invoiceId);
Watch(invoiceId);
}
catch when (_Cts.IsCancellationRequested)
{ }
@ -258,9 +203,9 @@ namespace BTCPayServer.HostedServices
_Loop = StartLoop(_Cts.Token);
_WaitingInvoices = WaitPendingInvoices();
leases.Add(_EventAggregator.Subscribe<Events.InvoiceNeedUpdateEvent>(async b =>
leases.Add(_EventAggregator.Subscribe<Events.InvoiceNeedUpdateEvent>(b =>
{
Watch(b.InvoiceId);
Watch(b.InvoiceId);
}));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async b =>
{
@ -289,41 +234,63 @@ namespace BTCPayServer.HostedServices
{
Logs.PayServer.LogInformation("Start watching invoices");
await Task.Delay(1).ConfigureAwait(false); // Small hack so that the caller does not block on GetConsumingEnumerable
ConcurrentDictionary<string, Lazy<Task>> executing = new ConcurrentDictionary<string, Lazy<Task>>();
try
{
// This loop just make sure an invoice will not be updated at the same time by two tasks.
// If an update is happening while a request come, then the update is deferred when the executing task is over
foreach (var item in _WatchRequests.GetConsumingEnumerable(cancellation))
foreach (var invoiceId in _WatchRequests.GetConsumingEnumerable(cancellation))
{
var localItem = item;
var toExecute = new Lazy<Task>(async () =>
int maxLoop = 5;
int loopCount = -1;
while (!cancellation.IsCancellationRequested && loopCount < maxLoop)
{
loopCount++;
try
{
await UpdateInvoice(localItem, cancellation);
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
if (invoice == null)
break;
var updateContext = new UpdateInvoiceContext(invoice);
await UpdateInvoice(updateContext);
if (updateContext.Dirty)
{
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus);
updateContext.Events.Add(new InvoiceDataChangedEvent(invoice));
}
foreach (var evt in updateContext.Events)
{
_EventAggregator.Publish(evt, evt.GetType());
}
if (invoice.Status == "complete" ||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
{
if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id))
_EventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id));
break;
}
if (updateContext.Events.Count == 0 || cancellation.IsCancellationRequested)
break;
}
finally
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
{
executing.TryRemove(localItem, out Lazy<Task> unused);
break;
}
catch (Exception ex)
{
Logs.PayServer.LogError(ex, "Unhandled error on watching invoice " + invoiceId);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Delay(10000, cancellation)
.ContinueWith(t => _WatchRequests.Add(invoiceId), TaskScheduler.Default);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
break;
}
}, false);
var executingTask = executing.GetOrAdd(item, toExecute);
executingTask.Value.GetAwaiter(); // Make sure it run
if (executingTask != toExecute)
{
// What was planned can't run for now, rebook it when the executingTask finish
var unused = executingTask.Value.ContinueWith(t => _WatchRequests.Add(localItem));
}
}
}
catch when (cancellation.IsCancellationRequested)
{
}
finally
{
await Task.WhenAll(executing.Values.Select(v => v.Value).ToArray());
}
Logs.PayServer.LogInformation("Stop watching invoices");
}

View File

@ -137,7 +137,7 @@ namespace BTCPayServer.HostedServices
await session.ListenNewBlockAsync(_Cts.Token).ConfigureAwait(false);
await session.ListenDerivationSchemesAsync((await GetStrategies(network)).ToArray(), _Cts.Token).ConfigureAwait(false);
Logs.PayServer.LogInformation($"{network.CryptoCode}: if any pending invoice got paid while offline...");
Logs.PayServer.LogInformation($"{network.CryptoCode}: Checking if any pending invoice got paid while offline...");
int paymentCount = await FindPaymentViaPolling(wallet, network);
Logs.PayServer.LogInformation($"{network.CryptoCode}: {paymentCount} payments happened while offline");
@ -165,10 +165,11 @@ namespace BTCPayServer.HostedServices
var invoice = await _InvoiceRepository.GetInvoiceFromScriptPubKey(output.ScriptPubKey, network.CryptoCode);
if (invoice != null)
{
var payment = invoice.GetPayments().FirstOrDefault(p => p.Outpoint == txCoin.Outpoint);
if (payment == null)
var paymentData = new BitcoinLikePaymentData(txCoin, evt.TransactionData.Transaction.RBF);
var alreadyExist = GetAllBitcoinPaymentData(invoice).Where(c => c.GetPaymentId() == paymentData.GetPaymentId()).Any();
if (!alreadyExist)
{
payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, txCoin, network.CryptoCode);
var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network.CryptoCode);
await ReceivedPayment(wallet, invoice.Id, payment, evt.DerivationStrategy);
}
else
@ -205,17 +206,27 @@ namespace BTCPayServer.HostedServices
}
}
IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(InvoiceEntity invoice)
{
return invoice.GetPayments()
.Where(p => p.GetCryptoPaymentDataType() == BitcoinLikePaymentData.OnchainBitcoinType)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData());
}
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, false);
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
var transactions = await wallet.GetTransactions(invoice.GetPayments(wallet.Network)
.Select(t => t.Outpoint.Hash)
var transactions = await wallet.GetTransactions(GetAllBitcoinPaymentData(invoice)
.Select(p => p.Outpoint.Hash)
.ToArray());
var conflicts = GetConflicts(transactions.Select(t => t.Value));
foreach (var payment in invoice.GetPayments(wallet.Network))
{
if (!transactions.TryGetValue(payment.Outpoint.Hash, out TransactionResult tx))
if (payment.GetCryptoPaymentDataType() != BitcoinLikePaymentData.OnchainBitcoinType)
continue;
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
if (!transactions.TryGetValue(paymentData.Outpoint.Hash, out TransactionResult tx))
continue;
var txId = tx.Transaction.GetHash();
var txConflict = conflicts.GetConflict(txId);
@ -228,27 +239,12 @@ namespace BTCPayServer.HostedServices
payment.Accounted = accounted;
}
var bitcoinLike = payment.GetCryptoPaymentData() as BitcoinLikePaymentData;
// Legacy
if (bitcoinLike == null)
if (paymentData.ConfirmationCount != tx.Confirmations)
{
#pragma warning disable CS0618 // Type or member is obsolete
payment.CryptoPaymentDataType = "BTCLike";
#pragma warning restore CS0618 // Type or member is obsolete
bitcoinLike = new BitcoinLikePaymentData();
bitcoinLike.ConfirmationCount = tx.Confirmations;
bitcoinLike.RBF = tx.Transaction.RBF;
payment.SetCryptoPaymentData(bitcoinLike);
updated = true;
}
if (bitcoinLike.ConfirmationCount != tx.Confirmations)
{
if(wallet.Network.MaxTrackedConfirmation >= bitcoinLike.ConfirmationCount)
{
bitcoinLike.ConfirmationCount = tx.Confirmations;
payment.SetCryptoPaymentData(bitcoinLike);
if(wallet.Network.MaxTrackedConfirmation >= paymentData.ConfirmationCount)
{
paymentData.ConfirmationCount = tx.Confirmations;
payment.SetCryptoPaymentData(paymentData);
updated = true;
}
}
@ -328,7 +324,7 @@ namespace BTCPayServer.HostedServices
foreach (var invoiceId in invoices)
{
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true);
var alreadyAccounted = new HashSet<OutPoint>(invoice.GetPayments(network).Select(p => p.Outpoint));
var alreadyAccounted = GetAllBitcoinPaymentData(invoice).Select(p => p.Outpoint).ToHashSet();
var strategy = invoice.GetDerivationStrategy(network);
if (strategy == null)
continue;
@ -337,7 +333,9 @@ namespace BTCPayServer.HostedServices
.ToArray();
foreach (var coin in coins.Where(c => !alreadyAccounted.Contains(c.Coin.Outpoint)))
{
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, coin.Coin, network.CryptoCode).ConfigureAwait(false);
var transaction = await wallet.GetTransactionAsync(coin.Coin.Outpoint.Hash);
var paymentData = new BitcoinLikePaymentData(coin.Coin, transaction.Transaction.RBF);
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network.CryptoCode).ConfigureAwait(false);
alreadyAccounted.Add(coin.Coin.Outpoint);
invoice = await ReceivedPayment(wallet, invoice.Id, payment, strategy);
totalPayment++;
@ -348,9 +346,10 @@ namespace BTCPayServer.HostedServices
private async Task<InvoiceEntity> ReceivedPayment(BTCPayWallet wallet, string invoiceId, PaymentEntity payment, DerivationStrategyBase strategy)
{
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
var invoice = (await UpdatePaymentStates(wallet, invoiceId));
var cryptoData = invoice.GetCryptoData(wallet.Network, _ExplorerClients.NetworkProviders);
if (cryptoData.GetDepositAddress().ScriptPubKey == payment.GetScriptPubKey() && cryptoData.Calculate().Due > Money.Zero)
if (cryptoData.GetDepositAddress().ScriptPubKey == paymentData.Output.ScriptPubKey && cryptoData.Calculate().Due > Money.Zero)
{
var address = await wallet.ReserveAddressAsync(strategy);
await _InvoiceRepository.NewAddress(invoiceId, address, wallet.Network);

View File

@ -70,7 +70,7 @@ namespace BTCPayServer.Hosting
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
{
var bitid = new BitIdentity(key);
httpContext.User = new GenericPrincipal(bitid, new string[0]);
httpContext.User = new GenericPrincipal(bitid, Array.Empty<string>());
Logs.PayServer.LogDebug($"BitId signature check success for SIN {bitid.SIN}");
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BTCPayServer.Logging
@ -333,7 +334,8 @@ namespace BTCPayServer.Logging
_outputTask = Task.Factory.StartNew(
ProcessLogQueue,
this,
TaskCreationOptions.LongRunning);
default(CancellationToken),
TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public virtual void EnqueueMessage(LogMessageEntry message)

View File

@ -22,7 +22,7 @@ namespace BTCPayServer.Models.InvoicingModels
public class Payment
{
public string CryptoCode { get; set; }
public int Confirmations
public string Confirmations
{
get; set;
}

View File

@ -27,7 +27,7 @@ namespace BTCPayServer
{
if(filter.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Length == 2)
{
TextSearch = TextSearch.Replace(filter, string.Empty);
TextSearch = TextSearch.Replace(filter, string.Empty, StringComparison.InvariantCulture);
}
}
TextSearch = TextSearch.Trim();

View File

@ -113,7 +113,7 @@ namespace BTCPayServer.Services
public async Task<bool> SupportDerivation(BTCPayNetwork network, DirectDerivationStrategy strategy)
{
if (network == null)
throw new ArgumentNullException(nameof(Network));
throw new ArgumentNullException(nameof(network));
if (strategy == null)
throw new ArgumentNullException(nameof(strategy));
if (!strategy.Segwit)

View File

@ -537,7 +537,7 @@ namespace BTCPayServer.Services.Invoices
public BitcoinAddress GetDepositAddress()
{
if(string.IsNullOrEmpty(DepositAddress))
if (string.IsNullOrEmpty(DepositAddress))
{
return null;
}
@ -573,7 +573,7 @@ namespace BTCPayServer.Services.Invoices
paidEnough |= totalDue <= paid;
if (CryptoCode == _.GetCryptoCode())
{
cryptoPaid += _.GetValue();
cryptoPaid += _.GetCryptoPaymentData().GetValue();
txCount++;
}
return _;
@ -604,29 +604,25 @@ namespace BTCPayServer.Services.Invoices
{
get; set;
}
[Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Outpoint")]
public OutPoint Outpoint
{
get; set;
}
[Obsolete("Use GetValue() or GetScriptPubKey() instead")]
[Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Output")]
public TxOut Output
{
get; set;
}
public Script GetScriptPubKey()
{
#pragma warning disable CS0618
return Output.ScriptPubKey;
#pragma warning restore CS0618
}
public bool Accounted
{
get; set;
}
[Obsolete("Use GetCryptoCode() instead")]
public string CryptoCode
{
@ -636,44 +632,59 @@ namespace BTCPayServer.Services.Invoices
[Obsolete("Use GetCryptoPaymentData() instead")]
public string CryptoPaymentData { get; set; }
[Obsolete("Use GetCryptoPaymentData() instead")]
[Obsolete("Use GetCryptoPaymentDataType() instead")]
public string CryptoPaymentDataType { get; set; }
public string GetCryptoPaymentDataType()
{
#pragma warning disable CS0618 // Type or member is obsolete
return String.IsNullOrEmpty(CryptoPaymentDataType) ? BitcoinLikePaymentData.OnchainBitcoinType : CryptoPaymentDataType;
#pragma warning restore CS0618 // Type or member is obsolete
}
public CryptoPaymentData GetCryptoPaymentData()
{
#pragma warning disable CS0618
if (string.IsNullOrEmpty(CryptoPaymentDataType))
{
return NullPaymentData.Instance;
// In case this is a payment done before this update, consider it unconfirmed with RBF for safety
var paymentData = new BitcoinLikePaymentData();
paymentData.Outpoint = Outpoint;
paymentData.Output = Output;
paymentData.RBF = true;
paymentData.ConfirmationCount = 0;
paymentData.Legacy = true;
return paymentData;
}
if (CryptoPaymentDataType == "BTCLike")
if (CryptoPaymentDataType == BitcoinLikePaymentData.OnchainBitcoinType)
{
return JsonConvert.DeserializeObject<BitcoinLikePaymentData>(CryptoPaymentData);
var paymentData = JsonConvert.DeserializeObject<BitcoinLikePaymentData>(CryptoPaymentData);
// legacy
paymentData.Output = Output;
paymentData.Outpoint = Outpoint;
return paymentData;
}
else
return NullPaymentData.Instance;
throw new NotSupportedException(nameof(CryptoPaymentDataType) + " does not support " + CryptoPaymentDataType);
#pragma warning restore CS0618
}
public void SetCryptoPaymentData(CryptoPaymentData cryptoPaymentData)
{
#pragma warning disable CS0618
if (cryptoPaymentData is BitcoinLikePaymentData)
if (cryptoPaymentData is BitcoinLikePaymentData paymentData)
{
CryptoPaymentDataType = "BTCLike";
CryptoPaymentDataType = BitcoinLikePaymentData.OnchainBitcoinType;
// Legacy
Outpoint = paymentData.Outpoint;
Output = paymentData.Output;
///
}
else
throw new NotSupportedException(cryptoPaymentData.ToString());
CryptoPaymentData = JsonConvert.SerializeObject(cryptoPaymentData);
#pragma warning restore CS0618
}
public Money GetValue()
{
#pragma warning disable CS0618
return Output.Value;
#pragma warning restore CS0618
}
public Money GetValue(Dictionary<string, CryptoData> cryptoData, string cryptoCode, Money value = null)
{
#pragma warning disable CS0618
@ -701,41 +712,71 @@ namespace BTCPayServer.Services.Invoices
public interface CryptoPaymentData
{
/// <summary>
/// Returns an identifier which uniquely identify the payment
/// </summary>
/// <returns>The payment id</returns>
string GetPaymentId();
/// <summary>
/// Returns terms which will be indexed and searchable in the search bar of invoice
/// </summary>
/// <returns>The search terms</returns>
string[] GetSearchTerms();
/// <summary>
/// Get value of what as been paid
/// </summary>
/// <returns>The amount paid</returns>
Money GetValue();
bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network);
bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network);
}
public class NullPaymentData : CryptoPaymentData
{
private static readonly NullPaymentData _Instance = new NullPaymentData();
public static NullPaymentData Instance
{
get
{
return _Instance;
}
}
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network)
{
return false;
}
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network)
{
return false;
}
}
public class BitcoinLikePaymentData : CryptoPaymentData
{
public readonly static string OnchainBitcoinType = "BTCLike";
public BitcoinLikePaymentData()
{
}
public BitcoinLikePaymentData(Coin coin, bool rbf)
{
Outpoint = coin.Outpoint;
Output = coin.TxOut;
ConfirmationCount = 0;
RBF = rbf;
}
[JsonIgnore]
public OutPoint Outpoint { get; set; }
[JsonIgnore]
public TxOut Output { get; set; }
public int ConfirmationCount { get; set; }
public bool RBF { get; set; }
/// <summary>
/// This is set to true if the payment was created before CryptoPaymentData existed in BTCPayServer
/// </summary>
public bool Legacy { get; set; }
public string GetPaymentId()
{
return Outpoint.ToString();
}
public string[] GetSearchTerms()
{
return new[] { Outpoint.Hash.ToString() };
}
public Money GetValue()
{
return Output.Value;
}
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network)
{
return ConfirmationCount >= 6;
return ConfirmationCount >= network.MaxTrackedConfirmation;
}
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network)

View File

@ -384,7 +384,7 @@ namespace BTCPayServer.Services.Invoices
{
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch));
if (ids.Count == 0)
return new InvoiceEntity[0];
return Array.Empty<InvoiceEntity>();
query = query.Where(i => ids.Contains(i.Id));
}
@ -443,24 +443,24 @@ namespace BTCPayServer.Services.Invoices
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
}
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, Coin receivedCoin, string cryptoCode)
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, string cryptoCode)
{
using (var context = _ContextFactory.CreateContext())
{
PaymentEntity entity = new PaymentEntity
{
Outpoint = receivedCoin.Outpoint,
#pragma warning disable CS0618
Output = receivedCoin.TxOut,
CryptoCode = cryptoCode,
#pragma warning restore CS0618
ReceivedTime = date.UtcDateTime,
Accounted = false
};
entity.SetCryptoPaymentData(new BitcoinLikePaymentData());
entity.SetCryptoPaymentData(paymentData);
PaymentData data = new PaymentData
{
Id = receivedCoin.Outpoint.ToString(),
Id = paymentData.GetPaymentId(),
Blob = ToBytes(entity, null),
InvoiceDataId = invoiceId,
Accounted = false
@ -469,7 +469,7 @@ namespace BTCPayServer.Services.Invoices
context.Payments.Add(data);
await context.SaveChangesAsync().ConfigureAwait(false);
AddToTextSearch(invoiceId, receivedCoin.Outpoint.Hash.ToString());
AddToTextSearch(invoiceId, paymentData.GetSearchTerms());
return entity;
}
}
@ -482,8 +482,9 @@ namespace BTCPayServer.Services.Invoices
{
foreach (var payment in payments)
{
var paymentData = payment.GetCryptoPaymentData();
var data = new PaymentData();
data.Id = payment.Outpoint.ToString();
data.Id = paymentData.GetPaymentId();
data.Accounted = payment.Accounted;
data.Blob = ToBytes(payment, null);
context.Attach(data);

View File

@ -16,11 +16,6 @@ using System.Collections.Concurrent;
namespace BTCPayServer.Services.Wallets
{
public class KnownState
{
public UTXOChanges PreviousCall { get; set; }
}
public class ReceivedCoin
{
public Coin Coin { get; set; }