Skip to content

Commit f7efc15

Browse files
authored
Merge pull request #9625 from MPins/issue-8161
Add deletecanceledinvoice RPC call
2 parents 117035d + 4b521e1 commit f7efc15

15 files changed

+2197
-1610
lines changed

cmd/commands/cmd_invoice.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/lightningnetwork/lnd/lnrpc"
1010
"github.com/urfave/cli"
11+
"google.golang.org/protobuf/proto"
1112
)
1213

1314
var AddInvoiceCommand = cli.Command{
@@ -440,3 +441,56 @@ func decodePayReq(ctx *cli.Context) error {
440441
printRespJSON(resp)
441442
return nil
442443
}
444+
445+
var deleteCanceledInvoiceCommand = cli.Command{
446+
Name: "deletecanceledinvoice",
447+
Category: "Invoices",
448+
Usage: "Delete a canceled invoice from the database.",
449+
ArgsUsage: "invoice_hash",
450+
Description: `
451+
This command deletes a canceled invoice from the database. Note that
452+
expired invoices are automatically moved to the canceled state.
453+
Once canceled, they can be deleted using this command.
454+
`,
455+
Flags: []cli.Flag{
456+
cli.StringFlag{
457+
Name: "invoice_hash",
458+
Usage: "the invoice hash to be deleted",
459+
},
460+
},
461+
Action: actionDecorator(deleteCanceledInvoice),
462+
}
463+
464+
func deleteCanceledInvoice(ctx *cli.Context) error {
465+
ctxc := getContext()
466+
client, cleanUp := getClient(ctx)
467+
defer cleanUp()
468+
469+
var (
470+
invoiceHash string
471+
err error
472+
resp proto.Message
473+
)
474+
475+
switch {
476+
case ctx.IsSet("invoice_hash"):
477+
invoiceHash = ctx.String("invoice_hash")
478+
case ctx.Args().Present():
479+
invoiceHash = ctx.Args().First()
480+
default:
481+
return fmt.Errorf("invoice_hash argument missing")
482+
}
483+
484+
req := &lnrpc.DelCanceledInvoiceReq{
485+
InvoiceHash: invoiceHash,
486+
}
487+
488+
resp, err = client.DeleteCanceledInvoice(ctxc, req)
489+
if err != nil {
490+
return err
491+
}
492+
493+
printJSON(resp)
494+
495+
return nil
496+
}

cmd/commands/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ func Main() {
478478
AddInvoiceCommand,
479479
lookupInvoiceCommand,
480480
listInvoicesCommand,
481+
deleteCanceledInvoiceCommand,
481482
ListChannelsCommand,
482483
closedChannelsCommand,
483484
listPaymentsCommand,

docs/release-notes/release-notes-0.20.0.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ circuit. The indices are only available for forwarding events saved after v0.20.
8383
1MB, and `gossip.msg-burst-bytes` has been increased from 200KB to 2MB.
8484

8585

86+
* Added [`deletecanceledinvoices`](
87+
https://github.com/lightningnetwork/lnd/pull/9625) RPC to allow the removal of
88+
a canceled invoice. Supports deleting a canceled invoice by providing its
89+
payment hash.
90+
8691
## lncli Additions
8792

8893
* [`lncli sendpayment` and `lncli queryroutes` now support the

invoices/errors.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ var (
1414
// canceled.
1515
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled")
1616

17+
// ErrInvoiceNotCanceled is returned when the invoice is not canceled.
18+
ErrInvoiceNotCanceled = errors.New("invoice not canceled")
19+
1720
// ErrInvoiceAlreadyAccepted is returned when the invoice is already
1821
// accepted.
1922
ErrInvoiceAlreadyAccepted = errors.New("invoice already accepted")
@@ -33,6 +36,10 @@ var (
3336
// match the invoice hash.
3437
ErrInvoicePreimageMismatch = errors.New("preimage does not match")
3538

39+
// ErrNoInvoiceHash is returned when an invoice hash is expected, and
40+
// none is provided.
41+
ErrNoInvoiceHash = errors.New("invoice hash must be provided")
42+
3643
// ErrHTLCPreimageMissing is returned when trying to accept/settle an
3744
// AMP HTLC but the HTLC-level preimage has not been set.
3845
ErrHTLCPreimageMissing = errors.New("AMP htlc missing preimage")

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,10 @@ var allTestCases = []*lntest.TestCase{
727727
Name: "grpc not found",
728728
TestFunc: testGRPCNotFound,
729729
},
730+
{
731+
Name: "delete canceled invoice",
732+
TestFunc: testDeleteCanceledInvoice,
733+
},
730734
}
731735

732736
// appendPrefixed is used to add a prefix to each test name in the subtests
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package itest
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/lightningnetwork/lnd/invoices"
7+
"github.com/lightningnetwork/lnd/lnrpc"
8+
"github.com/lightningnetwork/lnd/lntest"
9+
"github.com/lightningnetwork/lnd/lntypes"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func testDeleteCanceledInvoice(ht *lntest.HarnessTest) {
14+
bob := ht.NewNode("bob", nil)
15+
16+
// Create 2 invoices for Bob.
17+
const numInvoices = 2
18+
invoiceResp := make([]*lnrpc.AddInvoiceResponse, numInvoices)
19+
20+
for i := range numInvoices {
21+
// Create a unique 32-byte preimage for each invoice.
22+
preimage := bytes.Repeat([]byte{byte(i + 1)}, 32)
23+
24+
invoice := &lnrpc.Invoice{
25+
RPreimage: preimage,
26+
Value: paymentAmt,
27+
}
28+
29+
invoiceResp[i] = bob.RPC.AddInvoice(invoice)
30+
}
31+
32+
// Cancel all invoices except the last one.
33+
for i := range numInvoices - 1 {
34+
bob.RPC.CancelInvoice(invoiceResp[i].RHash)
35+
}
36+
37+
// Let's assert an error while providing no invoice hash.
38+
bob.RPC.DeleteCanceledInvoiceAssertErr(
39+
&lnrpc.DelCanceledInvoiceReq{},
40+
invoices.ErrNoInvoiceHash.Error(),
41+
)
42+
43+
// Let's assert an error while providing a hash with an invalid length.
44+
bob.RPC.DeleteCanceledInvoiceAssertErr(
45+
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: "bb02fbfa62983b6b62"},
46+
"invalid hash string length",
47+
)
48+
49+
// Let's assert an error is returned for a hash with invalid encoding.
50+
bob.RPC.DeleteCanceledInvoiceAssertErr(
51+
&lnrpc.DelCanceledInvoiceReq{
52+
InvoiceHash: "bb02fbfa62983b6b621376bf8230732dd3a6dce" +
53+
"a9f5df803c0935ae6ce7440dg",
54+
}, "encoding/hex: invalid byte",
55+
)
56+
57+
// Let's delete one canceled invoice.
58+
invoiceToDelete := lntypes.Hash(invoiceResp[0].RHash).String()
59+
resp := bob.RPC.DeleteCanceledInvoice(
60+
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: invoiceToDelete},
61+
)
62+
63+
require.Contains(
64+
ht, resp.String(), "canceled invoice deleted successfully",
65+
)
66+
67+
respList := bob.RPC.ListInvoices(&lnrpc.ListInvoiceRequest{})
68+
69+
// Assert that the invoice was deleted.
70+
require.Len(ht, respList.Invoices, numInvoices-1)
71+
72+
// Let's assert an error while deleting a non-existent invoice.
73+
bob.RPC.DeleteCanceledInvoiceAssertErr(
74+
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: invoiceToDelete},
75+
invoices.ErrInvoiceNotFound.Error(),
76+
)
77+
78+
// Let's assert an error while deleting a non canceled invoice.
79+
notCanceledInvoice := lntypes.Hash(invoiceResp[numInvoices-1].RHash).
80+
String()
81+
82+
bob.RPC.DeleteCanceledInvoiceAssertErr(
83+
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: notCanceledInvoice},
84+
invoices.ErrInvoiceNotCanceled.Error(),
85+
)
86+
87+
// Assert that the last invoice in the invoiceResp is still present and
88+
// OPEN.
89+
respList = bob.RPC.ListInvoices(&lnrpc.ListInvoiceRequest{})
90+
require.Equal(
91+
ht, invoiceResp[numInvoices-1].RHash,
92+
respList.Invoices[0].RHash,
93+
)
94+
95+
require.Equal(ht, lnrpc.Invoice_OPEN, respList.Invoices[0].State)
96+
97+
// Assert that just one invoice exists.
98+
require.Len(ht, respList.Invoices, 1)
99+
}

0 commit comments

Comments
 (0)