Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent creation of on-chain invoices below the dust limit #3082

Merged
merged 6 commits into from Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion BTCPayServer.Client/BTCPayServer.Client.csproj
Expand Up @@ -27,7 +27,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="6.0.15" />
<PackageReference Include="NBitcoin" Version="6.0.17" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions BTCPayServer.Tests/SeleniumTests.cs
Expand Up @@ -493,7 +493,7 @@ public async Task CanUsePairing()
var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
await client.CreateInvoiceAsync(
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
new NBitpayClient.Invoice() { Price = 1.000000012m, Currency = "USD", FullNotifications = true },
NBitpayClient.Facade.Merchant);

client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
Expand All @@ -503,7 +503,7 @@ public async Task CanUsePairing()
s.Driver.FindElement(By.Id("ApprovePairing")).Click();

await client.CreateInvoiceAsync(
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
new NBitpayClient.Invoice() { Price = 1.000000012m, Currency = "USD", FullNotifications = true },
NBitpayClient.Facade.Merchant);

s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
Expand Down
19 changes: 18 additions & 1 deletion BTCPayServer.Tests/UnitTest1.cs
Expand Up @@ -2984,15 +2984,31 @@ public async Task CanCreateAndDeleteApps()

[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanCreateStrangeInvoice()
{
using (var tester = ServerTester.Create())
{
tester.ActivateLightning();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.GrantAccess(true);
user.RegisterDerivationScheme("BTC");

DateTimeOffset expiration = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(21);

// This should fail, the amount is too low to be above the dust limit of bitcoin
var ex = Assert.Throws<BitPayException>(() => user.BitPay.CreateInvoice(
new Invoice()
{
Price = 0.000000012m,
Currency = "USD",
FullNotifications = true,
ExpirationTime = expiration
}, Facade.Merchant));
Assert.Contains("dust threshold", ex.Message);
await user.RegisterLightningNodeAsync("BTC");

var invoice1 = user.BitPay.CreateInvoice(
new Invoice()
{
Expand All @@ -3001,6 +3017,7 @@ public async Task CanCreateStrangeInvoice()
FullNotifications = true,
ExpirationTime = expiration
}, Facade.Merchant);

Assert.Equal(expiration.ToUnixTimeSeconds(), invoice1.ExpirationTime.ToUnixTimeSeconds());
var invoice2 = user.BitPay.CreateInvoice(new Invoice() { Price = 0.000000019m, Currency = "USD" },
Facade.Merchant);
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/BTCPayServer.csproj
@@ -1,4 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />

Expand Down
Expand Up @@ -136,7 +136,7 @@ public Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, ICl
claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
{
txout.ScriptPubKey = bitcoinLikeClaimDestination.Address.ScriptPubKey;
return Task.FromResult(txout.GetDustThreshold(new FeeRate(1.0m)).ToDecimal(MoneyUnit.BTC));
return Task.FromResult(txout.GetDustThreshold().ToDecimal(MoneyUnit.BTC));
}

return Task.FromResult(0m);
Expand Down
9 changes: 9 additions & 0 deletions BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs
Expand Up @@ -190,6 +190,15 @@ private string GetPaymentMethodName(BTCPayNetworkBase network)
}

var reserved = await prepare.ReserveAddress;
if (paymentMethod.ParentEntity.Type != InvoiceType.TopUp)
{
var txOut = network.NBitcoinNetwork.Consensus.ConsensusFactory.CreateTxOut();
txOut.ScriptPubKey = reserved.Address.ScriptPubKey;
var dust = txOut.GetDustThreshold();
var amount = paymentMethod.Calculate().Due;
if (amount < dust)
throw new PaymentMethodUnavailableException("Amount below the dust threshold. For amounts of this size, it is recommended to enable an off-chain (Lightning) payment method");
}
onchainMethod.DepositAddress = reserved.Address.ToString();
onchainMethod.KeyPath = reserved.KeyPath;
onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&
Expand Down
4 changes: 2 additions & 2 deletions BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs
Expand Up @@ -424,7 +424,7 @@ ObjectResult CreatePayjoinErrorAndLog(int httpCode, PayjoinReceiverWellknownErro
{
var outputContribution = Money.Min(additionalFee, -due);
outputContribution = Money.Min(outputContribution,
newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee));
newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold());
newTx.Outputs[i].Value -= outputContribution;
additionalFee -= outputContribution;
due += outputContribution;
Expand All @@ -437,7 +437,7 @@ ObjectResult CreatePayjoinErrorAndLog(int httpCode, PayjoinReceiverWellknownErro
{
var outputContribution = Money.Min(additionalFee, feeOutput.Value);
outputContribution = Money.Min(outputContribution,
feeOutput.Value - feeOutput.GetDustThreshold(minRelayTxFee));
feeOutput.Value - feeOutput.GetDustThreshold());
outputContribution = Money.Min(outputContribution, allowedSenderFeeContribution);
feeOutput.Value -= outputContribution;
additionalFee -= outputContribution;
Expand Down