Merge pull request #9625 from MPins/issue-8161

Add deletecanceledinvoice RPC call
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-07-30 09:14:06 -06:00
commit f7efc15a9e
15 files changed
+2203 -1616

No files matched your search

+54
View File
@@ -8,6 +8,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/urfave/cli"
"google.golang.org/protobuf/proto"
)
var AddInvoiceCommand = cli.Command{
@@ -440,3 +441,56 @@ func decodePayReq(ctx *cli.Context) error {
printRespJSON(resp)
return nil
}
var deleteCanceledInvoiceCommand = cli.Command{
Name: "deletecanceledinvoice",
Category: "Invoices",
Usage: "Delete a canceled invoice from the database.",
ArgsUsage: "invoice_hash",
Description: `
This command deletes a canceled invoice from the database. Note that
expired invoices are automatically moved to the canceled state.
Once canceled, they can be deleted using this command.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "invoice_hash",
Usage: "the invoice hash to be deleted",
},
},
Action: actionDecorator(deleteCanceledInvoice),
}
func deleteCanceledInvoice(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
invoiceHash string
err error
resp proto.Message
)
switch {
case ctx.IsSet("invoice_hash"):
invoiceHash = ctx.String("invoice_hash")
case ctx.Args().Present():
invoiceHash = ctx.Args().First()
default:
return fmt.Errorf("invoice_hash argument missing")
}
req := &lnrpc.DelCanceledInvoiceReq{
InvoiceHash: invoiceHash,
}
resp, err = client.DeleteCanceledInvoice(ctxc, req)
if err != nil {
return err
}
printJSON(resp)
return nil
}
+1
View File
@@ -478,6 +478,7 @@ func Main() {
AddInvoiceCommand,
lookupInvoiceCommand,
listInvoicesCommand,
deleteCanceledInvoiceCommand,
ListChannelsCommand,
closedChannelsCommand,
listPaymentsCommand,
@@ -83,6 +83,11 @@ circuit. The indices are only available for forwarding events saved after v0.20.
1MB, and `gossip.msg-burst-bytes` has been increased from 200KB to 2MB.
* Added [`deletecanceledinvoices`](
https://github.com/lightningnetwork/lnd/pull/9625) RPC to allow the removal of
a canceled invoice. Supports deleting a canceled invoice by providing its
payment hash.
## lncli Additions
* [`lncli sendpayment` and `lncli queryroutes` now support the
+7
View File
@@ -14,6 +14,9 @@ var (
// canceled.
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled")
// ErrInvoiceNotCanceled is returned when the invoice is not canceled.
ErrInvoiceNotCanceled = errors.New("invoice not canceled")
// ErrInvoiceAlreadyAccepted is returned when the invoice is already
// accepted.
ErrInvoiceAlreadyAccepted = errors.New("invoice already accepted")
@@ -33,6 +36,10 @@ var (
// match the invoice hash.
ErrInvoicePreimageMismatch = errors.New("preimage does not match")
// ErrNoInvoiceHash is returned when an invoice hash is expected, and
// none is provided.
ErrNoInvoiceHash = errors.New("invoice hash must be provided")
// ErrHTLCPreimageMissing is returned when trying to accept/settle an
// AMP HTLC but the HTLC-level preimage has not been set.
ErrHTLCPreimageMissing = errors.New("AMP htlc missing preimage")
+4
View File
@@ -727,6 +727,10 @@ var allTestCases = []*lntest.TestCase{
Name: "grpc not found",
TestFunc: testGRPCNotFound,
},
{
Name: "delete canceled invoice",
TestFunc: testDeleteCanceledInvoice,
},
}
// appendPrefixed is used to add a prefix to each test name in the subtests
+99
View File
@@ -0,0 +1,99 @@
package itest
import (
"bytes"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
func testDeleteCanceledInvoice(ht *lntest.HarnessTest) {
bob := ht.NewNode("bob", nil)
// Create 2 invoices for Bob.
const numInvoices = 2
invoiceResp := make([]*lnrpc.AddInvoiceResponse, numInvoices)
for i := range numInvoices {
// Create a unique 32-byte preimage for each invoice.
preimage := bytes.Repeat([]byte{byte(i + 1)}, 32)
invoice := &lnrpc.Invoice{
RPreimage: preimage,
Value: paymentAmt,
}
invoiceResp[i] = bob.RPC.AddInvoice(invoice)
}
// Cancel all invoices except the last one.
for i := range numInvoices - 1 {
bob.RPC.CancelInvoice(invoiceResp[i].RHash)
}
// Let's assert an error while providing no invoice hash.
bob.RPC.DeleteCanceledInvoiceAssertErr(
&lnrpc.DelCanceledInvoiceReq{},
invoices.ErrNoInvoiceHash.Error(),
)
// Let's assert an error while providing a hash with an invalid length.
bob.RPC.DeleteCanceledInvoiceAssertErr(
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: "bb02fbfa62983b6b62"},
"invalid hash string length",
)
// Let's assert an error is returned for a hash with invalid encoding.
bob.RPC.DeleteCanceledInvoiceAssertErr(
&lnrpc.DelCanceledInvoiceReq{
InvoiceHash: "bb02fbfa62983b6b621376bf8230732dd3a6dce" +
"a9f5df803c0935ae6ce7440dg",
}, "encoding/hex: invalid byte",
)
// Let's delete one canceled invoice.
invoiceToDelete := lntypes.Hash(invoiceResp[0].RHash).String()
resp := bob.RPC.DeleteCanceledInvoice(
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: invoiceToDelete},
)
require.Contains(
ht, resp.String(), "canceled invoice deleted successfully",
)
respList := bob.RPC.ListInvoices(&lnrpc.ListInvoiceRequest{})
// Assert that the invoice was deleted.
require.Len(ht, respList.Invoices, numInvoices-1)
// Let's assert an error while deleting a non-existent invoice.
bob.RPC.DeleteCanceledInvoiceAssertErr(
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: invoiceToDelete},
invoices.ErrInvoiceNotFound.Error(),
)
// Let's assert an error while deleting a non canceled invoice.
notCanceledInvoice := lntypes.Hash(invoiceResp[numInvoices-1].RHash).
String()
bob.RPC.DeleteCanceledInvoiceAssertErr(
&lnrpc.DelCanceledInvoiceReq{InvoiceHash: notCanceledInvoice},
invoices.ErrInvoiceNotCanceled.Error(),
)
// Assert that the last invoice in the invoiceResp is still present and
// OPEN.
respList = bob.RPC.ListInvoices(&lnrpc.ListInvoiceRequest{})
require.Equal(
ht, invoiceResp[numInvoices-1].RHash,
respList.Invoices[0].RHash,
)
require.Equal(ht, lnrpc.Invoice_OPEN, respList.Invoices[0].State)
// Assert that just one invoice exists.
require.Len(ht, respList.Invoices, 1)
}
+1752 -1616
View File
File diff suppressed because it is too large. Load diff
+87
View File
@@ -1305,6 +1305,42 @@ func request_Lightning_SubscribeInvoices_0(ctx context.Context, marshaler runtim
}
var (
filter_Lightning_DeleteCanceledInvoice_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_DeleteCanceledInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DelCanceledInvoiceReq
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DeleteCanceledInvoice_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DeleteCanceledInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DeleteCanceledInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DelCanceledInvoiceReq
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DeleteCanceledInvoice_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DeleteCanceledInvoice(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_DecodePayReq_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PayReqString
var metadata runtime.ServerMetadata
@@ -3339,6 +3375,31 @@ func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux,
return
})
mux.Handle("DELETE", pattern_Lightning_DeleteCanceledInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DeleteCanceledInvoice", runtime.WithHTTPPathPattern("/v1/invoices"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DeleteCanceledInvoice_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeleteCanceledInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_DecodePayReq_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -4900,6 +4961,28 @@ func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("DELETE", pattern_Lightning_DeleteCanceledInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DeleteCanceledInvoice", runtime.WithHTTPPathPattern("/v1/invoices"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DeleteCanceledInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeleteCanceledInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_DecodePayReq_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -5680,6 +5763,8 @@ var (
pattern_Lightning_SubscribeInvoices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "invoices", "subscribe"}, ""))
pattern_Lightning_DeleteCanceledInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "invoices"}, ""))
pattern_Lightning_DecodePayReq_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "payreq", "pay_req"}, ""))
pattern_Lightning_ListPayments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payments"}, ""))
@@ -5818,6 +5903,8 @@ var (
forward_Lightning_SubscribeInvoices_0 = runtime.ForwardResponseStream
forward_Lightning_DeleteCanceledInvoice_0 = runtime.ForwardResponseMessage
forward_Lightning_DecodePayReq_0 = runtime.ForwardResponseMessage
forward_Lightning_ListPayments_0 = runtime.ForwardResponseMessage
+25
View File
@@ -973,6 +973,31 @@ func RegisterLightningJSONCallbacks(registry map[string]func(ctx context.Context
}()
}
registry["lnrpc.Lightning.DeleteCanceledInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DelCanceledInvoiceReq{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DeleteCanceledInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.DecodePayReq"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
+17
View File
@@ -341,6 +341,13 @@ service Lightning {
*/
rpc SubscribeInvoices (InvoiceSubscription) returns (stream Invoice);
/* lncli: `deletecanceledinvoice`
DeleteCanceledInvoice removes a canceled invoice from the database. If the
invoice is not in the canceled state, an error will be returned.
*/
rpc DeleteCanceledInvoice (DelCanceledInvoiceReq)
returns (DelCanceledInvoiceResp);
/* lncli: `decodepayreq`
DecodePayReq takes an encoded payment request string and attempts to decode
it, returning a full description of the conditions encoded within the
@@ -4234,6 +4241,16 @@ message InvoiceSubscription {
uint64 settle_index = 2;
}
message DelCanceledInvoiceReq {
// Invoice payment hash to delete.
string invoice_hash = 1;
}
message DelCanceledInvoiceResp {
// The status of the delete operation.
string status = 1;
}
enum PaymentFailureReason {
/*
Payment isn't failed (yet).
+39
View File
@@ -1874,6 +1874,36 @@
"Lightning"
]
},
"delete": {
"summary": "lncli: `deletecanceledinvoice`\nDeleteCanceledInvoice removes a canceled invoice from the database. If the\ninvoice is not in the canceled state, an error will be returned.",
"operationId": "Lightning_DeleteCanceledInvoice",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/lnrpcDelCanceledInvoiceResp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "invoice_hash",
"description": "Invoice payment hash to delete.",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"Lightning"
]
},
"post": {
"summary": "lncli: `addinvoice`\nAddInvoice attempts to add a new invoice to the invoice database. Any\nduplicated invoices are rejected, therefore all invoices *must* have a\nunique payment preimage.",
"operationId": "Lightning_AddInvoice",
@@ -4857,6 +4887,15 @@
}
}
},
"lnrpcDelCanceledInvoiceResp": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "The status of the delete operation."
}
}
},
"lnrpcDeleteAllPaymentsResponse": {
"type": "object",
"properties": {
+2
View File
@@ -95,6 +95,8 @@ http:
get: "/v1/invoice/{r_hash_str}"
- selector: lnrpc.Lightning.SubscribeInvoices
get: "/v1/invoices/subscribe"
- selector: lnrpc.Lightning.DeleteCanceledInvoice
delete: "/v1/invoices"
- selector: lnrpc.Lightning.DecodePayReq
get: "/v1/payreq/{pay_req}"
- selector: lnrpc.Lightning.DeletePayment
+42
View File
@@ -243,6 +243,10 @@ type LightningClient interface {
// of these fields can be set. If no fields are set, then we'll only send out
// the latest add/settle events.
SubscribeInvoices(ctx context.Context, in *InvoiceSubscription, opts ...grpc.CallOption) (Lightning_SubscribeInvoicesClient, error)
// lncli: `deletecanceledinvoice`
// DeleteCanceledInvoice removes a canceled invoice from the database. If the
// invoice is not in the canceled state, an error will be returned.
DeleteCanceledInvoice(ctx context.Context, in *DelCanceledInvoiceReq, opts ...grpc.CallOption) (*DelCanceledInvoiceResp, error)
// lncli: `decodepayreq`
// DecodePayReq takes an encoded payment request string and attempts to decode
// it, returning a full description of the conditions encoded within the
@@ -973,6 +977,15 @@ func (x *lightningSubscribeInvoicesClient) Recv() (*Invoice, error) {
return m, nil
}
func (c *lightningClient) DeleteCanceledInvoice(ctx context.Context, in *DelCanceledInvoiceReq, opts ...grpc.CallOption) (*DelCanceledInvoiceResp, error) {
out := new(DelCanceledInvoiceResp)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DeleteCanceledInvoice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) DecodePayReq(ctx context.Context, in *PayReqString, opts ...grpc.CallOption) (*PayReq, error) {
out := new(PayReq)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DecodePayReq", in, out, opts...)
@@ -1572,6 +1585,10 @@ type LightningServer interface {
// of these fields can be set. If no fields are set, then we'll only send out
// the latest add/settle events.
SubscribeInvoices(*InvoiceSubscription, Lightning_SubscribeInvoicesServer) error
// lncli: `deletecanceledinvoice`
// DeleteCanceledInvoice removes a canceled invoice from the database. If the
// invoice is not in the canceled state, an error will be returned.
DeleteCanceledInvoice(context.Context, *DelCanceledInvoiceReq) (*DelCanceledInvoiceResp, error)
// lncli: `decodepayreq`
// DecodePayReq takes an encoded payment request string and attempts to decode
// it, returning a full description of the conditions encoded within the
@@ -1869,6 +1886,9 @@ func (UnimplementedLightningServer) LookupInvoice(context.Context, *PaymentHash)
func (UnimplementedLightningServer) SubscribeInvoices(*InvoiceSubscription, Lightning_SubscribeInvoicesServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeInvoices not implemented")
}
func (UnimplementedLightningServer) DeleteCanceledInvoice(context.Context, *DelCanceledInvoiceReq) (*DelCanceledInvoiceResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteCanceledInvoice not implemented")
}
func (UnimplementedLightningServer) DecodePayReq(context.Context, *PayReqString) (*PayReq, error) {
return nil, status.Errorf(codes.Unimplemented, "method DecodePayReq not implemented")
}
@@ -2683,6 +2703,24 @@ func (x *lightningSubscribeInvoicesServer) Send(m *Invoice) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_DeleteCanceledInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DelCanceledInvoiceReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DeleteCanceledInvoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DeleteCanceledInvoice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DeleteCanceledInvoice(ctx, req.(*DelCanceledInvoiceReq))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_DecodePayReq_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PayReqString)
if err := dec(in); err != nil {
@@ -3377,6 +3415,10 @@ var Lightning_ServiceDesc = grpc.ServiceDesc{
MethodName: "LookupInvoice",
Handler: _Lightning_LookupInvoice_Handler,
},
{
MethodName: "DeleteCanceledInvoice",
Handler: _Lightning_DeleteCanceledInvoice_Handler,
},
{
MethodName: "DecodePayReq",
Handler: _Lightning_DecodePayReq_Handler,
+26
View File
@@ -831,3 +831,29 @@ func (h *HarnessRPC) SubscribePeerEvents(
return resp
}
// DeleteCanceledInvoice makes a RPC call to the node's DeleteCanceledInvoice
// and asserts.
func (h *HarnessRPC) DeleteCanceledInvoice(
req *lnrpc.DelCanceledInvoiceReq) *lnrpc.DelCanceledInvoiceResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.DeleteCanceledInvoice(ctxt, req)
h.NoError(err, "DeleteCanceledInvoice")
return resp
}
// DeleteCanceledInvoiceAssertErr makes a RPC call to the node's
// DeleteCanceledInvoice and asserts if an RPC error is returned.
func (h *HarnessRPC) DeleteCanceledInvoiceAssertErr(
req *lnrpc.DelCanceledInvoiceReq, errStr string) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.LN.DeleteCanceledInvoice(ctxt, req)
require.ErrorContains(h, err, errStr)
}
+43
View File
@@ -467,6 +467,10 @@ func MainRPCServerPermissions() map[string][]bakery.Op {
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/DeleteCanceledInvoice": {{
Entity: "invoices",
Action: "write",
}},
"/lnrpc.Lightning/ListPayments": {{
Entity: "offchain",
Action: "read",
@@ -7550,6 +7554,45 @@ func (r *rpcServer) ListPayments(ctx context.Context,
return paymentsResp, nil
}
// DeleteCanceledInvoice remove a canceled invoice from the database.
func (r *rpcServer) DeleteCanceledInvoice(ctx context.Context,
req *lnrpc.DelCanceledInvoiceReq) (*lnrpc.DelCanceledInvoiceResp,
error) {
if req.InvoiceHash == "" {
return nil, invoices.ErrNoInvoiceHash
}
hash, err := lntypes.MakeHashFromStr(req.InvoiceHash)
if err != nil {
return nil, err
}
invoice, err := r.server.invoices.LookupInvoice(ctx, hash)
if err != nil {
return nil, err
}
if invoice.State != invoices.ContractCanceled {
return nil, invoices.ErrInvoiceNotCanceled
}
err = r.server.invoicesDB.DeleteInvoice(ctx,
[]invoices.InvoiceDeleteRef{
{
PayHash: hash,
AddIndex: invoice.AddIndex,
},
},
)
if err != nil {
return nil, err
}
return &lnrpc.DelCanceledInvoiceResp{Status: fmt.Sprintf("canceled "+
"invoice deleted successfully: invoice hash %v", hash)}, nil
}
// DeletePayment deletes a payment from the DB given its payment hash. If
// failedHtlcsOnly is set, only failed HTLC attempts of the payment will be
// deleted.