Merge pull request #2457 from joostjager/cancelinvoice
invoices: CancelInvoice
This commit is contained in:
19 files changed
+1470
-811
No files matched your search
+73
-1
@@ -62,6 +62,10 @@ var (
|
|||||||
// ErrInvoiceAlreadySettled is returned when the invoice is already
|
// ErrInvoiceAlreadySettled is returned when the invoice is already
|
||||||
// settled.
|
// settled.
|
||||||
ErrInvoiceAlreadySettled = errors.New("invoice already settled")
|
ErrInvoiceAlreadySettled = errors.New("invoice already settled")
|
||||||
|
|
||||||
|
// ErrInvoiceAlreadyCanceled is returned when the invoice is already
|
||||||
|
// canceled.
|
||||||
|
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -90,6 +94,9 @@ const (
|
|||||||
// ContractSettled means the htlc is settled and the invoice has been
|
// ContractSettled means the htlc is settled and the invoice has been
|
||||||
// paid.
|
// paid.
|
||||||
ContractSettled ContractState = 1
|
ContractSettled ContractState = 1
|
||||||
|
|
||||||
|
// ContractCanceled means the invoice has been canceled.
|
||||||
|
ContractCanceled ContractState = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a human readable identifier for the ContractState type.
|
// String returns a human readable identifier for the ContractState type.
|
||||||
@@ -99,6 +106,8 @@ func (c ContractState) String() string {
|
|||||||
return "Open"
|
return "Open"
|
||||||
case ContractSettled:
|
case ContractSettled:
|
||||||
return "Settled"
|
return "Settled"
|
||||||
|
case ContractCanceled:
|
||||||
|
return "Canceled"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
@@ -641,6 +650,37 @@ func (d *DB) SettleInvoice(paymentHash [32]byte,
|
|||||||
return settledInvoice, err
|
return settledInvoice, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelInvoice attempts to cancel the invoice corresponding to the passed
|
||||||
|
// payment hash.
|
||||||
|
func (d *DB) CancelInvoice(paymentHash lntypes.Hash) (*Invoice, error) {
|
||||||
|
var canceledInvoice *Invoice
|
||||||
|
err := d.Update(func(tx *bbolt.Tx) error {
|
||||||
|
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
invoiceIndex, err := invoices.CreateBucketIfNotExists(
|
||||||
|
invoiceIndexBucket,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the invoice index to see if an invoice paying to this
|
||||||
|
// hash exists within the DB.
|
||||||
|
invoiceNum := invoiceIndex.Get(paymentHash[:])
|
||||||
|
if invoiceNum == nil {
|
||||||
|
return ErrInvoiceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
canceledInvoice, err = cancelInvoice(invoices, invoiceNum)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return canceledInvoice, err
|
||||||
|
}
|
||||||
|
|
||||||
// InvoicesSettledSince can be used by callers to catch up any settled invoices
|
// InvoicesSettledSince can be used by callers to catch up any settled invoices
|
||||||
// they missed within the settled invoice time series. We'll return all known
|
// they missed within the settled invoice time series. We'll return all known
|
||||||
// settled invoice that have a settle index higher than the passed
|
// settled invoice that have a settle index higher than the passed
|
||||||
@@ -896,8 +936,11 @@ func settleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if invoice.Terms.State == ContractSettled {
|
switch invoice.Terms.State {
|
||||||
|
case ContractSettled:
|
||||||
return &invoice, ErrInvoiceAlreadySettled
|
return &invoice, ErrInvoiceAlreadySettled
|
||||||
|
case ContractCanceled:
|
||||||
|
return &invoice, ErrInvoiceAlreadyCanceled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we know the invoice hasn't already been settled, we'll
|
// Now that we know the invoice hasn't already been settled, we'll
|
||||||
@@ -930,3 +973,32 @@ func settleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
|
|||||||
|
|
||||||
return &invoice, nil
|
return &invoice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cancelInvoice(invoices *bbolt.Bucket, invoiceNum []byte) (
|
||||||
|
*Invoice, error) {
|
||||||
|
|
||||||
|
invoice, err := fetchInvoice(invoiceNum, invoices)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch invoice.Terms.State {
|
||||||
|
case ContractSettled:
|
||||||
|
return &invoice, ErrInvoiceAlreadySettled
|
||||||
|
case ContractCanceled:
|
||||||
|
return &invoice, ErrInvoiceAlreadyCanceled
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.Terms.State = ContractCanceled
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := serializeInvoice(&buf, &invoice); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &invoice, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// +build invoicesrpc
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// invoicesCommands will return nil for non-invoicesrpc builds.
|
||||||
|
func invoicesCommands() []cli.Command {
|
||||||
|
return []cli.Command{
|
||||||
|
cancelInvoiceCommand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInvoicesClient(ctx *cli.Context) (invoicesrpc.InvoicesClient, func()) {
|
||||||
|
conn := getClientConn(ctx, false)
|
||||||
|
|
||||||
|
cleanUp := func() {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoicesrpc.NewInvoicesClient(conn), cleanUp
|
||||||
|
}
|
||||||
|
|
||||||
|
var cancelInvoiceCommand = cli.Command{
|
||||||
|
Name: "cancelinvoice",
|
||||||
|
Category: "Payments",
|
||||||
|
Usage: "Cancels a (hold) invoice",
|
||||||
|
Description: `
|
||||||
|
Todo.`,
|
||||||
|
ArgsUsage: "paymenthash",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "paymenthash",
|
||||||
|
Usage: "the hex-encoded payment hash (32 byte) for which the " +
|
||||||
|
"corresponding invoice will be canceled.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(cancelInvoice),
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelInvoice(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
paymentHash []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
client, cleanUp := getInvoicesClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
args := ctx.Args()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("paymenthash"):
|
||||||
|
paymentHash, err = hex.DecodeString(ctx.String("paymenthash"))
|
||||||
|
case args.Present():
|
||||||
|
paymentHash, err = hex.DecodeString(args.First())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse preimage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice := &invoicesrpc.CancelInvoiceMsg{
|
||||||
|
PaymentHash: paymentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.CancelInvoice(context.Background(), invoice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printJSON(resp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// +build !invoicesrpc
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
|
// invoicesCommands will return nil for non-invoicesrpc builds.
|
||||||
|
func invoicesCommands() []cli.Command {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -298,6 +298,7 @@ func main() {
|
|||||||
|
|
||||||
// Add any extra autopilot commands determined by build flags.
|
// Add any extra autopilot commands determined by build flags.
|
||||||
app.Commands = append(app.Commands, autopilotCommands()...)
|
app.Commands = append(app.Commands, autopilotCommands()...)
|
||||||
|
app.Commands = append(app.Commands, invoicesCommands()...)
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ type InvoiceDatabase interface {
|
|||||||
// SettleInvoice attempts to mark an invoice corresponding to the
|
// SettleInvoice attempts to mark an invoice corresponding to the
|
||||||
// passed payment hash as fully settled.
|
// passed payment hash as fully settled.
|
||||||
SettleInvoice(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi) error
|
SettleInvoice(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi) error
|
||||||
|
|
||||||
|
// CancelInvoice attempts to cancel the invoice corresponding to the
|
||||||
|
// passed payment hash.
|
||||||
|
CancelInvoice(payHash lntypes.Hash) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelLink is an interface which represents the subsystem for managing the
|
// ChannelLink is an interface which represents the subsystem for managing the
|
||||||
|
|||||||
@@ -2337,6 +2337,23 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reject htlcs for canceled invoices.
|
||||||
|
if invoice.Terms.State == channeldb.ContractCanceled {
|
||||||
|
l.errorf("Rejecting htlc due to canceled " +
|
||||||
|
"invoice")
|
||||||
|
|
||||||
|
failure := lnwire.NewFailUnknownPaymentHash(
|
||||||
|
pd.Amount,
|
||||||
|
)
|
||||||
|
l.sendHTLCError(
|
||||||
|
pd.HtlcIndex, failure, obfuscator,
|
||||||
|
pd.SourceRef,
|
||||||
|
)
|
||||||
|
|
||||||
|
needUpdate = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If the invoice is already settled, we choose to
|
// If the invoice is already settled, we choose to
|
||||||
// accept the payment to simplify failure recovery.
|
// accept the payment to simplify failure recovery.
|
||||||
//
|
//
|
||||||
|
|||||||
+78
-21
@@ -218,7 +218,7 @@ func TestChannelLinkSingleHopPayment(t *testing.T) {
|
|||||||
// * user notification to be sent.
|
// * user notification to be sent.
|
||||||
receiver := n.bobServer
|
receiver := n.bobServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -314,7 +314,7 @@ func TestChannelLinkBidirectionalOneHopPayments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, r.err = n.makePayment(
|
_, r.err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop,
|
n.aliceServer, n.bobServer, firstHop,
|
||||||
hopsForwards, amt, htlcAmt, totalTimelock,
|
hopsForwards, amt, htlcAmt, totalTimelock,
|
||||||
).Wait(5 * time.Minute)
|
).Wait(5 * time.Minute)
|
||||||
@@ -331,7 +331,7 @@ func TestChannelLinkBidirectionalOneHopPayments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
firstHop := n.aliceChannelLink.ShortChanID()
|
firstHop := n.aliceChannelLink.ShortChanID()
|
||||||
_, r.err = n.makePayment(
|
_, r.err = makePayment(
|
||||||
n.bobServer, n.aliceServer, firstHop,
|
n.bobServer, n.aliceServer, firstHop,
|
||||||
hopsBackwards, amt, htlcAmt, totalTimelock,
|
hopsBackwards, amt, htlcAmt, totalTimelock,
|
||||||
).Wait(5 * time.Minute)
|
).Wait(5 * time.Minute)
|
||||||
@@ -451,7 +451,7 @@ func TestChannelLinkMultiHopPayment(t *testing.T) {
|
|||||||
// * user notification to be sent.
|
// * user notification to be sent.
|
||||||
receiver := n.carolServer
|
receiver := n.carolServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -529,7 +529,7 @@ func TestExitNodeTimelockPayloadMismatch(t *testing.T) {
|
|||||||
// the receiving node, instead we set it to be a random value.
|
// the receiving node, instead we set it to be a random value.
|
||||||
hops[0].OutgoingCTLV = 500
|
hops[0].OutgoingCTLV = 500
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||||
htlcExpiry,
|
htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -582,7 +582,7 @@ func TestExitNodeAmountPayloadMismatch(t *testing.T) {
|
|||||||
// receiving node expects to receive.
|
// receiving node expects to receive.
|
||||||
hops[0].AmountToForward = 1
|
hops[0].AmountToForward = 1
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||||
htlcExpiry,
|
htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -630,7 +630,7 @@ func TestLinkForwardTimelockPolicyMismatch(t *testing.T) {
|
|||||||
// Next, we'll make the payment which'll send an HTLC with our
|
// Next, we'll make the payment which'll send an HTLC with our
|
||||||
// specified parameters to the first hop in the route.
|
// specified parameters to the first hop in the route.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||||
htlcExpiry,
|
htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -688,7 +688,7 @@ func TestLinkForwardFeePolicyMismatch(t *testing.T) {
|
|||||||
// Next, we'll make the payment which'll send an HTLC with our
|
// Next, we'll make the payment which'll send an HTLC with our
|
||||||
// specified parameters to the first hop in the route.
|
// specified parameters to the first hop in the route.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amountNoFee,
|
n.aliceServer, n.bobServer, firstHop, hops, amountNoFee,
|
||||||
amountNoFee, htlcExpiry,
|
amountNoFee, htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -746,7 +746,7 @@ func TestLinkForwardMinHTLCPolicyMismatch(t *testing.T) {
|
|||||||
// Next, we'll make the payment which'll send an HTLC with our
|
// Next, we'll make the payment which'll send an HTLC with our
|
||||||
// specified parameters to the first hop in the route.
|
// specified parameters to the first hop in the route.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amountNoFee,
|
n.aliceServer, n.bobServer, firstHop, hops, amountNoFee,
|
||||||
htlcAmt, htlcExpiry,
|
htlcAmt, htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -805,7 +805,7 @@ func TestUpdateForwardingPolicy(t *testing.T) {
|
|||||||
// First, send this 10 mSAT payment over the three hops, the payment
|
// First, send this 10 mSAT payment over the three hops, the payment
|
||||||
// should succeed, and all balances should be updated accordingly.
|
// should succeed, and all balances should be updated accordingly.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
payResp, err := n.makePayment(
|
payResp, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
||||||
htlcAmt, htlcExpiry,
|
htlcAmt, htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -856,7 +856,7 @@ func TestUpdateForwardingPolicy(t *testing.T) {
|
|||||||
// Next, we'll send the payment again, using the exact same per-hop
|
// Next, we'll send the payment again, using the exact same per-hop
|
||||||
// payload for each node. This payment should fail as it won't factor
|
// payload for each node. This payment should fail as it won't factor
|
||||||
// in Bob's new fee policy.
|
// in Bob's new fee policy.
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
||||||
htlcAmt, htlcExpiry,
|
htlcAmt, htlcExpiry,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -917,7 +917,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
|
|||||||
|
|
||||||
receiver := n.carolServer
|
receiver := n.carolServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -1094,7 +1094,7 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
|
|||||||
|
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
receiver := n.carolServer
|
receiver := n.carolServer
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock).Wait(30 * time.Second)
|
totalTimelock).Wait(30 * time.Second)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1201,7 +1201,7 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
|
|||||||
|
|
||||||
receiver := n.carolServer
|
receiver := n.carolServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -1287,7 +1287,7 @@ func TestChannelLinkExpiryTooSoonExitNode(t *testing.T) {
|
|||||||
|
|
||||||
// Now we'll send out the payment from Alice to Bob.
|
// Now we'll send out the payment from Alice to Bob.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -1347,7 +1347,7 @@ func TestChannelLinkExpiryTooSoonMidNode(t *testing.T) {
|
|||||||
|
|
||||||
// Now we'll send out the payment from Alice to Bob.
|
// Now we'll send out the payment from Alice to Bob.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -1446,7 +1446,7 @@ func TestChannelLinkSingleHopMessageOrdering(t *testing.T) {
|
|||||||
// * alice<->bob commitment state to be updated.
|
// * alice<->bob commitment state to be updated.
|
||||||
// * user notification to be sent.
|
// * user notification to be sent.
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -3283,7 +3283,7 @@ func TestChannelRetransmission(t *testing.T) {
|
|||||||
// TODO(roasbeef); increase timeout?
|
// TODO(roasbeef); increase timeout?
|
||||||
receiver := n.bobServer
|
receiver := n.bobServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, receiver, firstHop, hops, amount,
|
n.aliceServer, receiver, firstHop, hops, amount,
|
||||||
htlcAmt, totalTimelock,
|
htlcAmt, totalTimelock,
|
||||||
).Wait(time.Second * 5)
|
).Wait(time.Second * 5)
|
||||||
@@ -3578,7 +3578,7 @@ func TestChannelLinkShutdownDuringForward(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
n.makePayment(
|
makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
)
|
)
|
||||||
@@ -3814,7 +3814,7 @@ func TestChannelLinkAcceptOverpay(t *testing.T) {
|
|||||||
// invoice at Carol for only half of this amount.
|
// invoice at Carol for only half of this amount.
|
||||||
receiver := n.carolServer
|
receiver := n.carolServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
rhash, err := n.makePayment(
|
rhash, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount/2, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount/2, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -5119,7 +5119,7 @@ func TestForwardingAsymmetricTimeLockPolicies(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -5201,3 +5201,60 @@ func TestHtlcSatisfyPolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkCanceledInvoice in this test checks the interaction
|
||||||
|
// between Alice and Bob for a canceled invoice.
|
||||||
|
func TestChannelLinkCanceledInvoice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Setup a alice-bob network.
|
||||||
|
aliceChannel, bobChannel, cleanUp, err := createTwoClusterChannels(
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create channel: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
n := newTwoHopNetwork(t, aliceChannel, bobChannel, testStartingHeight)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
// Prepare an alice -> bob payment.
|
||||||
|
amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
|
htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight,
|
||||||
|
n.bobChannelLink)
|
||||||
|
|
||||||
|
firstHop := n.bobChannelLink.ShortChanID()
|
||||||
|
|
||||||
|
invoice, payFunc, err := preparePayment(
|
||||||
|
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||||
|
totalTimelock,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to prepare the payment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel the invoice at bob's end.
|
||||||
|
hash := invoice.Terms.PaymentPreimage.Hash()
|
||||||
|
err = n.bobServer.registry.CancelInvoice(hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have Alice fire the payment.
|
||||||
|
err = waitForPayFuncResult(payFunc, 30*time.Second)
|
||||||
|
|
||||||
|
// Because the invoice is canceled, we expect an unknown payment hash
|
||||||
|
// result.
|
||||||
|
fErr, ok := err.(*ForwardingError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected ForwardingError, but got %v", err)
|
||||||
|
}
|
||||||
|
_, ok = fErr.FailureMessage.(*lnwire.FailUnknownPaymentHash)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected unknown payment hash, but got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-1
@@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lightning-onion"
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
@@ -735,6 +735,25 @@ func (i *mockInvoiceRegistry) SettleInvoice(rhash lntypes.Hash,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *mockInvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
invoice, ok := i.invoices[payHash]
|
||||||
|
if !ok {
|
||||||
|
return channeldb.ErrInvoiceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoice.Terms.State == channeldb.ContractCanceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.Terms.State = channeldb.ContractCanceled
|
||||||
|
i.invoices[payHash] = invoice
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *mockInvoiceRegistry) AddInvoice(invoice channeldb.Invoice) error {
|
func (i *mockInvoiceRegistry) AddInvoice(invoice channeldb.Invoice) error {
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
|
|||||||
@@ -1847,7 +1847,7 @@ func TestLocalPaymentNoForwardingEvents(t *testing.T) {
|
|||||||
// proceeding.
|
// proceeding.
|
||||||
receiver := n.bobServer
|
receiver := n.bobServer
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
_, err = n.makePayment(
|
_, err = makePayment(
|
||||||
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
||||||
totalTimelock,
|
totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -1908,7 +1908,7 @@ func TestMultiHopPaymentForwardingEvents(t *testing.T) {
|
|||||||
)
|
)
|
||||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
for i := 0; i < numPayments/2; i++ {
|
for i := 0; i < numPayments/2; i++ {
|
||||||
_, err := n.makePayment(
|
_, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
||||||
htlcAmt, totalTimelock,
|
htlcAmt, totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
@@ -1961,7 +1961,7 @@ func TestMultiHopPaymentForwardingEvents(t *testing.T) {
|
|||||||
|
|
||||||
// Send the remaining payments.
|
// Send the remaining payments.
|
||||||
for i := numPayments / 2; i < numPayments; i++ {
|
for i := numPayments / 2; i < numPayments; i++ {
|
||||||
_, err := n.makePayment(
|
_, err := makePayment(
|
||||||
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
||||||
htlcAmt, totalTimelock,
|
htlcAmt, totalTimelock,
|
||||||
).Wait(30 * time.Second)
|
).Wait(30 * time.Second)
|
||||||
|
|||||||
+276
-197
@@ -585,9 +585,7 @@ type threeHopNetwork struct {
|
|||||||
carolServer *mockServer
|
carolServer *mockServer
|
||||||
carolChannelLink *channelLink
|
carolChannelLink *channelLink
|
||||||
|
|
||||||
feeEstimator *mockFeeEstimator
|
hopNetwork
|
||||||
|
|
||||||
globalPolicy ForwardingPolicy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateHops creates the per hop payload, the total amount to be sent, and
|
// generateHops creates the per hop payload, the total amount to be sent, and
|
||||||
@@ -657,15 +655,32 @@ type paymentResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *paymentResponse) Wait(d time.Duration) (lntypes.Hash, error) {
|
func (r *paymentResponse) Wait(d time.Duration) (lntypes.Hash, error) {
|
||||||
|
return r.rhash, waitForPaymentResult(r.err, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForPaymentResult waits for either an error to be received on c or a
|
||||||
|
// timeout.
|
||||||
|
func waitForPaymentResult(c chan error, d time.Duration) error {
|
||||||
select {
|
select {
|
||||||
case err := <-r.err:
|
case err := <-c:
|
||||||
close(r.err)
|
close(c)
|
||||||
return r.rhash, err
|
return err
|
||||||
case <-time.After(d):
|
case <-time.After(d):
|
||||||
return r.rhash, errors.New("htlc was no settled in time")
|
return errors.New("htlc was not settled in time")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitForPayFuncResult executes the given function and waits for a result with
|
||||||
|
// a timeout.
|
||||||
|
func waitForPayFuncResult(payFunc func() error, d time.Duration) error {
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errChan <- payFunc()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return waitForPaymentResult(errChan, d)
|
||||||
|
}
|
||||||
|
|
||||||
// makePayment takes the destination node and amount as input, sends the
|
// makePayment takes the destination node and amount as input, sends the
|
||||||
// payment and returns the error channel to wait for error to be received and
|
// payment and returns the error channel to wait for error to be received and
|
||||||
// invoice in order to check its status after the payment finished.
|
// invoice in order to check its status after the payment finished.
|
||||||
@@ -674,15 +689,45 @@ func (r *paymentResponse) Wait(d time.Duration) (lntypes.Hash, error) {
|
|||||||
// * from Alice to Bob
|
// * from Alice to Bob
|
||||||
// * from Alice to Carol through the Bob
|
// * from Alice to Carol through the Bob
|
||||||
// * from Alice to some another peer through the Bob
|
// * from Alice to some another peer through the Bob
|
||||||
func (n *threeHopNetwork) makePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
func makePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
||||||
firstHop lnwire.ShortChannelID, hops []ForwardingInfo,
|
firstHop lnwire.ShortChannelID, hops []ForwardingInfo,
|
||||||
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
||||||
timelock uint32) *paymentResponse {
|
timelock uint32) *paymentResponse {
|
||||||
|
|
||||||
paymentErr := make(chan error, 1)
|
paymentErr := make(chan error, 1)
|
||||||
|
|
||||||
var rhash lntypes.Hash
|
var rhash lntypes.Hash
|
||||||
|
|
||||||
|
invoice, payFunc, err := preparePayment(sendingPeer, receivingPeer,
|
||||||
|
firstHop, hops, invoiceAmt, htlcAmt, timelock,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
paymentErr <- err
|
||||||
|
return &paymentResponse{
|
||||||
|
rhash: rhash,
|
||||||
|
err: paymentErr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rhash = invoice.Terms.PaymentPreimage.Hash()
|
||||||
|
|
||||||
|
// Send payment and expose err channel.
|
||||||
|
go func() {
|
||||||
|
paymentErr <- payFunc()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &paymentResponse{
|
||||||
|
rhash: rhash,
|
||||||
|
err: paymentErr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// preparePayment creates an invoice at the receivingPeer and returns a function
|
||||||
|
// that, when called, launches the payment from the sendingPeer.
|
||||||
|
func preparePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
||||||
|
firstHop lnwire.ShortChannelID, hops []ForwardingInfo,
|
||||||
|
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
||||||
|
timelock uint32) (*channeldb.Invoice, func() error, error) {
|
||||||
|
|
||||||
sender := sendingPeer.(*mockServer)
|
sender := sendingPeer.(*mockServer)
|
||||||
receiver := receivingPeer.(*mockServer)
|
receiver := receivingPeer.(*mockServer)
|
||||||
|
|
||||||
@@ -690,45 +735,27 @@ func (n *threeHopNetwork) makePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
|||||||
// htlc add request.
|
// htlc add request.
|
||||||
blob, err := generateRoute(hops...)
|
blob, err := generateRoute(hops...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
paymentErr <- err
|
return nil, nil, err
|
||||||
return &paymentResponse{
|
|
||||||
rhash: rhash,
|
|
||||||
err: paymentErr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate payment: invoice and htlc.
|
// Generate payment: invoice and htlc.
|
||||||
invoice, htlc, err := generatePayment(invoiceAmt, htlcAmt, timelock, blob)
|
invoice, htlc, err := generatePayment(invoiceAmt, htlcAmt, timelock, blob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
paymentErr <- err
|
return nil, nil, err
|
||||||
return &paymentResponse{
|
|
||||||
rhash: rhash,
|
|
||||||
err: paymentErr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rhash = fastsha256.Sum256(invoice.Terms.PaymentPreimage[:])
|
|
||||||
|
|
||||||
// Check who is last in the route and add invoice to server registry.
|
// Check who is last in the route and add invoice to server registry.
|
||||||
if err := receiver.registry.AddInvoice(*invoice); err != nil {
|
if err := receiver.registry.AddInvoice(*invoice); err != nil {
|
||||||
paymentErr <- err
|
return nil, nil, err
|
||||||
return &paymentResponse{
|
|
||||||
rhash: rhash,
|
|
||||||
err: paymentErr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send payment and expose err channel.
|
// Send payment and expose err channel.
|
||||||
go func() {
|
return invoice, func() error {
|
||||||
_, err := sender.htlcSwitch.SendHTLC(
|
_, err := sender.htlcSwitch.SendHTLC(
|
||||||
firstHop, htlc, newMockDeobfuscator(),
|
firstHop, htlc, newMockDeobfuscator(),
|
||||||
)
|
)
|
||||||
paymentErr <- err
|
return err
|
||||||
}()
|
}, nil
|
||||||
|
|
||||||
return &paymentResponse{
|
|
||||||
rhash: rhash,
|
|
||||||
err: paymentErr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// start starts the three hop network alice,bob,carol servers.
|
// start starts the three hop network alice,bob,carol servers.
|
||||||
@@ -854,23 +881,23 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
|||||||
bobDb := firstBobChannel.State().Db
|
bobDb := firstBobChannel.State().Db
|
||||||
carolDb := carolChannel.State().Db
|
carolDb := carolChannel.State().Db
|
||||||
|
|
||||||
defaultDelta := uint32(6)
|
hopNetwork := newHopNetwork()
|
||||||
|
|
||||||
// Create three peers/servers.
|
// Create three peers/servers.
|
||||||
aliceServer, err := newMockServer(
|
aliceServer, err := newMockServer(
|
||||||
t, "alice", startingHeight, aliceDb, defaultDelta,
|
t, "alice", startingHeight, aliceDb, hopNetwork.defaultDelta,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create alice server: %v", err)
|
t.Fatalf("unable to create alice server: %v", err)
|
||||||
}
|
}
|
||||||
bobServer, err := newMockServer(
|
bobServer, err := newMockServer(
|
||||||
t, "bob", startingHeight, bobDb, defaultDelta,
|
t, "bob", startingHeight, bobDb, hopNetwork.defaultDelta,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create bob server: %v", err)
|
t.Fatalf("unable to create bob server: %v", err)
|
||||||
}
|
}
|
||||||
carolServer, err := newMockServer(
|
carolServer, err := newMockServer(
|
||||||
t, "carol", startingHeight, carolDb, defaultDelta,
|
t, "carol", startingHeight, carolDb, hopNetwork.defaultDelta,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create carol server: %v", err)
|
t.Fatalf("unable to create carol server: %v", err)
|
||||||
@@ -882,17 +909,78 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
|||||||
bobDecoder := newMockIteratorDecoder()
|
bobDecoder := newMockIteratorDecoder()
|
||||||
carolDecoder := newMockIteratorDecoder()
|
carolDecoder := newMockIteratorDecoder()
|
||||||
|
|
||||||
feeEstimator := &mockFeeEstimator{
|
aliceChannelLink, err := hopNetwork.createChannelLink(aliceServer,
|
||||||
byteFeeIn: make(chan lnwallet.SatPerKWeight),
|
bobServer, aliceChannel, aliceDecoder,
|
||||||
quit: make(chan struct{}),
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
firstBobChannelLink, err := hopNetwork.createChannelLink(bobServer,
|
||||||
batchTimeout = 50 * time.Millisecond
|
aliceServer, firstBobChannel, bobDecoder)
|
||||||
fwdPkgTimeout = 15 * time.Second
|
if err != nil {
|
||||||
minFeeUpdateTimeout = 30 * time.Minute
|
t.Fatal(err)
|
||||||
maxFeeUpdateTimeout = 40 * time.Minute
|
}
|
||||||
)
|
|
||||||
|
secondBobChannelLink, err := hopNetwork.createChannelLink(bobServer,
|
||||||
|
carolServer, secondBobChannel, bobDecoder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
carolChannelLink, err := hopNetwork.createChannelLink(carolServer,
|
||||||
|
bobServer, carolChannel, carolDecoder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &threeHopNetwork{
|
||||||
|
aliceServer: aliceServer,
|
||||||
|
aliceChannelLink: aliceChannelLink.(*channelLink),
|
||||||
|
|
||||||
|
bobServer: bobServer,
|
||||||
|
firstBobChannelLink: firstBobChannelLink.(*channelLink),
|
||||||
|
secondBobChannelLink: secondBobChannelLink.(*channelLink),
|
||||||
|
|
||||||
|
carolServer: carolServer,
|
||||||
|
carolChannelLink: carolChannelLink.(*channelLink),
|
||||||
|
|
||||||
|
hopNetwork: *hopNetwork,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTwoClusterChannels creates lightning channels which are needed for
|
||||||
|
// a 2 hop network cluster to be initialized.
|
||||||
|
func createTwoClusterChannels(aliceToBob, bobToCarol btcutil.Amount) (
|
||||||
|
*lnwallet.LightningChannel, *lnwallet.LightningChannel,
|
||||||
|
func(), error) {
|
||||||
|
|
||||||
|
_, _, firstChanID, _ := genIDs()
|
||||||
|
|
||||||
|
// Create lightning channels between Alice<->Bob and Bob<->Carol
|
||||||
|
aliceChannel, firstBobChannel, cleanAliceBob, _, err :=
|
||||||
|
createTestChannel(alicePrivKey, bobPrivKey, aliceToBob,
|
||||||
|
aliceToBob, 0, 0, firstChanID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, errors.Errorf("unable to create "+
|
||||||
|
"alice<->bob channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliceChannel, firstBobChannel, cleanAliceBob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hopNetwork is the base struct for two and three hop networks
|
||||||
|
type hopNetwork struct {
|
||||||
|
feeEstimator *mockFeeEstimator
|
||||||
|
globalPolicy ForwardingPolicy
|
||||||
|
obfuscator ErrorEncrypter
|
||||||
|
pCache *mockPreimageCache
|
||||||
|
|
||||||
|
defaultDelta uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHopNetwork() *hopNetwork {
|
||||||
|
defaultDelta := uint32(6)
|
||||||
|
|
||||||
pCache := &mockPreimageCache{
|
pCache := &mockPreimageCache{
|
||||||
// hash -> preimage
|
// hash -> preimage
|
||||||
@@ -906,22 +994,47 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
|||||||
}
|
}
|
||||||
obfuscator := NewMockObfuscator()
|
obfuscator := NewMockObfuscator()
|
||||||
|
|
||||||
aliceChannelLink := NewChannelLink(
|
feeEstimator := &mockFeeEstimator{
|
||||||
|
byteFeeIn: make(chan lnwallet.SatPerKWeight),
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &hopNetwork{
|
||||||
|
feeEstimator: feeEstimator,
|
||||||
|
globalPolicy: globalPolicy,
|
||||||
|
obfuscator: obfuscator,
|
||||||
|
pCache: pCache,
|
||||||
|
defaultDelta: defaultDelta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
||||||
|
channel *lnwallet.LightningChannel,
|
||||||
|
decoder *mockIteratorDecoder) (ChannelLink, error) {
|
||||||
|
|
||||||
|
const (
|
||||||
|
batchTimeout = 50 * time.Millisecond
|
||||||
|
fwdPkgTimeout = 15 * time.Second
|
||||||
|
minFeeUpdateTimeout = 30 * time.Minute
|
||||||
|
maxFeeUpdateTimeout = 40 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
link := NewChannelLink(
|
||||||
ChannelLinkConfig{
|
ChannelLinkConfig{
|
||||||
Switch: aliceServer.htlcSwitch,
|
Switch: server.htlcSwitch,
|
||||||
FwrdingPolicy: globalPolicy,
|
FwrdingPolicy: h.globalPolicy,
|
||||||
Peer: bobServer,
|
Peer: peer,
|
||||||
Circuits: aliceServer.htlcSwitch.CircuitModifier(),
|
Circuits: server.htlcSwitch.CircuitModifier(),
|
||||||
ForwardPackets: aliceServer.htlcSwitch.ForwardPackets,
|
ForwardPackets: server.htlcSwitch.ForwardPackets,
|
||||||
DecodeHopIterators: aliceDecoder.DecodeHopIterators,
|
DecodeHopIterators: decoder.DecodeHopIterators,
|
||||||
ExtractErrorEncrypter: func(*btcec.PublicKey) (
|
ExtractErrorEncrypter: func(*btcec.PublicKey) (
|
||||||
ErrorEncrypter, lnwire.FailCode) {
|
ErrorEncrypter, lnwire.FailCode) {
|
||||||
return obfuscator, lnwire.CodeNone
|
return h.obfuscator, lnwire.CodeNone
|
||||||
},
|
},
|
||||||
FetchLastChannelUpdate: mockGetChanUpdateMessage,
|
FetchLastChannelUpdate: mockGetChanUpdateMessage,
|
||||||
Registry: aliceServer.registry,
|
Registry: server.registry,
|
||||||
FeeEstimator: feeEstimator,
|
FeeEstimator: h.feeEstimator,
|
||||||
PreimageCache: pCache,
|
PreimageCache: h.pCache,
|
||||||
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
|
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -934,162 +1047,128 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
|||||||
MaxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
MaxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
||||||
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
|
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
|
||||||
},
|
},
|
||||||
aliceChannel,
|
channel,
|
||||||
)
|
)
|
||||||
if err := aliceServer.htlcSwitch.AddLink(aliceChannelLink); err != nil {
|
if err := server.htlcSwitch.AddLink(link); err != nil {
|
||||||
t.Fatalf("unable to add alice channel link: %v", err)
|
return nil, fmt.Errorf("unable to add channel link: %v", err)
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-aliceChannelLink.(*channelLink).htlcUpdates:
|
case <-link.(*channelLink).htlcUpdates:
|
||||||
case <-aliceChannelLink.(*channelLink).quit:
|
case <-link.(*channelLink).quit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
firstBobChannelLink := NewChannelLink(
|
return link, nil
|
||||||
ChannelLinkConfig{
|
}
|
||||||
Switch: bobServer.htlcSwitch,
|
|
||||||
FwrdingPolicy: globalPolicy,
|
|
||||||
Peer: aliceServer,
|
|
||||||
Circuits: bobServer.htlcSwitch.CircuitModifier(),
|
|
||||||
ForwardPackets: bobServer.htlcSwitch.ForwardPackets,
|
|
||||||
DecodeHopIterators: bobDecoder.DecodeHopIterators,
|
|
||||||
ExtractErrorEncrypter: func(*btcec.PublicKey) (
|
|
||||||
ErrorEncrypter, lnwire.FailCode) {
|
|
||||||
return obfuscator, lnwire.CodeNone
|
|
||||||
},
|
|
||||||
FetchLastChannelUpdate: mockGetChanUpdateMessage,
|
|
||||||
Registry: bobServer.registry,
|
|
||||||
FeeEstimator: feeEstimator,
|
|
||||||
PreimageCache: pCache,
|
|
||||||
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
ChainEvents: &contractcourt.ChainEventSubscription{},
|
|
||||||
SyncStates: true,
|
|
||||||
BatchSize: 10,
|
|
||||||
BatchTicker: ticker.MockNew(batchTimeout),
|
|
||||||
FwdPkgGCTicker: ticker.MockNew(fwdPkgTimeout),
|
|
||||||
MinFeeUpdateTimeout: minFeeUpdateTimeout,
|
|
||||||
MaxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
|
||||||
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
|
|
||||||
},
|
|
||||||
firstBobChannel,
|
|
||||||
)
|
|
||||||
if err := bobServer.htlcSwitch.AddLink(firstBobChannelLink); err != nil {
|
|
||||||
t.Fatalf("unable to add first bob channel link: %v", err)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-firstBobChannelLink.(*channelLink).htlcUpdates:
|
|
||||||
case <-firstBobChannelLink.(*channelLink).quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
secondBobChannelLink := NewChannelLink(
|
// twoHopNetwork is used for managing the created cluster of 2 hops.
|
||||||
ChannelLinkConfig{
|
type twoHopNetwork struct {
|
||||||
Switch: bobServer.htlcSwitch,
|
hopNetwork
|
||||||
FwrdingPolicy: globalPolicy,
|
|
||||||
Peer: carolServer,
|
|
||||||
Circuits: bobServer.htlcSwitch.CircuitModifier(),
|
|
||||||
ForwardPackets: bobServer.htlcSwitch.ForwardPackets,
|
|
||||||
DecodeHopIterators: bobDecoder.DecodeHopIterators,
|
|
||||||
ExtractErrorEncrypter: func(*btcec.PublicKey) (
|
|
||||||
ErrorEncrypter, lnwire.FailCode) {
|
|
||||||
return obfuscator, lnwire.CodeNone
|
|
||||||
},
|
|
||||||
FetchLastChannelUpdate: mockGetChanUpdateMessage,
|
|
||||||
Registry: bobServer.registry,
|
|
||||||
FeeEstimator: feeEstimator,
|
|
||||||
PreimageCache: pCache,
|
|
||||||
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
ChainEvents: &contractcourt.ChainEventSubscription{},
|
|
||||||
SyncStates: true,
|
|
||||||
BatchSize: 10,
|
|
||||||
BatchTicker: ticker.MockNew(batchTimeout),
|
|
||||||
FwdPkgGCTicker: ticker.MockNew(fwdPkgTimeout),
|
|
||||||
MinFeeUpdateTimeout: minFeeUpdateTimeout,
|
|
||||||
MaxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
|
||||||
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
|
|
||||||
},
|
|
||||||
secondBobChannel,
|
|
||||||
)
|
|
||||||
if err := bobServer.htlcSwitch.AddLink(secondBobChannelLink); err != nil {
|
|
||||||
t.Fatalf("unable to add second bob channel link: %v", err)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-secondBobChannelLink.(*channelLink).htlcUpdates:
|
|
||||||
case <-secondBobChannelLink.(*channelLink).quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
carolChannelLink := NewChannelLink(
|
aliceServer *mockServer
|
||||||
ChannelLinkConfig{
|
aliceChannelLink *channelLink
|
||||||
Switch: carolServer.htlcSwitch,
|
|
||||||
FwrdingPolicy: globalPolicy,
|
|
||||||
Peer: bobServer,
|
|
||||||
Circuits: carolServer.htlcSwitch.CircuitModifier(),
|
|
||||||
ForwardPackets: carolServer.htlcSwitch.ForwardPackets,
|
|
||||||
DecodeHopIterators: carolDecoder.DecodeHopIterators,
|
|
||||||
ExtractErrorEncrypter: func(*btcec.PublicKey) (
|
|
||||||
ErrorEncrypter, lnwire.FailCode) {
|
|
||||||
return obfuscator, lnwire.CodeNone
|
|
||||||
},
|
|
||||||
FetchLastChannelUpdate: mockGetChanUpdateMessage,
|
|
||||||
Registry: carolServer.registry,
|
|
||||||
FeeEstimator: feeEstimator,
|
|
||||||
PreimageCache: pCache,
|
|
||||||
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
ChainEvents: &contractcourt.ChainEventSubscription{},
|
|
||||||
SyncStates: true,
|
|
||||||
BatchSize: 10,
|
|
||||||
BatchTicker: ticker.MockNew(batchTimeout),
|
|
||||||
FwdPkgGCTicker: ticker.MockNew(fwdPkgTimeout),
|
|
||||||
MinFeeUpdateTimeout: minFeeUpdateTimeout,
|
|
||||||
MaxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
|
||||||
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
|
|
||||||
},
|
|
||||||
carolChannel,
|
|
||||||
)
|
|
||||||
if err := carolServer.htlcSwitch.AddLink(carolChannelLink); err != nil {
|
|
||||||
t.Fatalf("unable to add carol channel link: %v", err)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-carolChannelLink.(*channelLink).htlcUpdates:
|
|
||||||
case <-carolChannelLink.(*channelLink).quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &threeHopNetwork{
|
bobServer *mockServer
|
||||||
|
bobChannelLink *channelLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTwoHopNetwork function creates the following topology and returns the
|
||||||
|
// control object to manage this cluster:
|
||||||
|
//
|
||||||
|
// alice bob
|
||||||
|
// server - <-connection-> - server
|
||||||
|
// | |
|
||||||
|
// alice htlc bob htlc
|
||||||
|
// switch switch
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// alice bob
|
||||||
|
// channel link channel link
|
||||||
|
//
|
||||||
|
func newTwoHopNetwork(t testing.TB,
|
||||||
|
aliceChannel, bobChannel *lnwallet.LightningChannel,
|
||||||
|
startingHeight uint32) *twoHopNetwork {
|
||||||
|
|
||||||
|
aliceDb := aliceChannel.State().Db
|
||||||
|
bobDb := bobChannel.State().Db
|
||||||
|
|
||||||
|
hopNetwork := newHopNetwork()
|
||||||
|
|
||||||
|
// Create two peers/servers.
|
||||||
|
aliceServer, err := newMockServer(
|
||||||
|
t, "alice", startingHeight, aliceDb, hopNetwork.defaultDelta,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create alice server: %v", err)
|
||||||
|
}
|
||||||
|
bobServer, err := newMockServer(
|
||||||
|
t, "bob", startingHeight, bobDb, hopNetwork.defaultDelta,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create bob server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mock decoder instead of sphinx one in order to mock the route
|
||||||
|
// which htlc should follow.
|
||||||
|
aliceDecoder := newMockIteratorDecoder()
|
||||||
|
bobDecoder := newMockIteratorDecoder()
|
||||||
|
|
||||||
|
aliceChannelLink, err := hopNetwork.createChannelLink(
|
||||||
|
aliceServer, bobServer, aliceChannel, aliceDecoder,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bobChannelLink, err := hopNetwork.createChannelLink(
|
||||||
|
bobServer, aliceServer, bobChannel, bobDecoder,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &twoHopNetwork{
|
||||||
aliceServer: aliceServer,
|
aliceServer: aliceServer,
|
||||||
aliceChannelLink: aliceChannelLink.(*channelLink),
|
aliceChannelLink: aliceChannelLink.(*channelLink),
|
||||||
|
|
||||||
bobServer: bobServer,
|
bobServer: bobServer,
|
||||||
firstBobChannelLink: firstBobChannelLink.(*channelLink),
|
bobChannelLink: bobChannelLink.(*channelLink),
|
||||||
secondBobChannelLink: secondBobChannelLink.(*channelLink),
|
|
||||||
|
|
||||||
carolServer: carolServer,
|
hopNetwork: *hopNetwork,
|
||||||
carolChannelLink: carolChannelLink.(*channelLink),
|
}
|
||||||
|
}
|
||||||
feeEstimator: feeEstimator,
|
|
||||||
globalPolicy: globalPolicy,
|
// start starts the three hop network alice,bob,carol servers.
|
||||||
|
func (n *twoHopNetwork) start() error {
|
||||||
|
if err := n.aliceServer.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := n.bobServer.Start(); err != nil {
|
||||||
|
n.aliceServer.Stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops nodes and cleanup its databases.
|
||||||
|
func (n *twoHopNetwork) stop() {
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
n.aliceServer.Stop()
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
n.bobServer.Stop()
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
<-done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,11 @@ func (i *InvoiceRegistry) invoiceEventNotifier() {
|
|||||||
// A sub-systems has just modified the invoice state, so we'll
|
// A sub-systems has just modified the invoice state, so we'll
|
||||||
// dispatch notifications to all registered clients.
|
// dispatch notifications to all registered clients.
|
||||||
case event := <-i.invoiceEvents:
|
case event := <-i.invoiceEvents:
|
||||||
i.dispatchToClients(event)
|
// For backwards compatibility, do not notify all
|
||||||
|
// invoice subscribers of cancel events
|
||||||
|
if event.state != channeldb.ContractCanceled {
|
||||||
|
i.dispatchToClients(event)
|
||||||
|
}
|
||||||
i.dispatchToSingleClients(event)
|
i.dispatchToSingleClients(event)
|
||||||
|
|
||||||
case <-i.quit:
|
case <-i.quit:
|
||||||
@@ -256,7 +260,7 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
|||||||
case channeldb.ContractOpen:
|
case channeldb.ContractOpen:
|
||||||
client.addIndex = invoice.AddIndex
|
client.addIndex = invoice.AddIndex
|
||||||
default:
|
default:
|
||||||
log.Errorf("unknown invoice state: %v", event.state)
|
log.Errorf("unexpected invoice state: %v", event.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,6 +480,34 @@ func (i *InvoiceRegistry) SettleInvoice(rHash lntypes.Hash,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelInvoice attempts to cancel the invoice corresponding to the passed
|
||||||
|
// payment hash.
|
||||||
|
func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("Canceling invoice %v", payHash)
|
||||||
|
|
||||||
|
invoice, err := i.cdb.CancelInvoice(payHash)
|
||||||
|
|
||||||
|
// Implement idempotency by returning success if the invoice was already
|
||||||
|
// canceled.
|
||||||
|
if err == channeldb.ErrInvoiceAlreadyCanceled {
|
||||||
|
log.Debugf("Invoice %v already canceled", payHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Invoice %v canceled", payHash)
|
||||||
|
|
||||||
|
i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// notifyClients notifies all currently registered invoice notification clients
|
// notifyClients notifies all currently registered invoice notification clients
|
||||||
// of a newly added/settled invoice.
|
// of a newly added/settled invoice.
|
||||||
func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash,
|
func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash,
|
||||||
|
|||||||
@@ -150,6 +150,122 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
if inv.AmtPaid != amtPaid {
|
if inv.AmtPaid != amtPaid {
|
||||||
t.Fatal("expected amount to be unchanged")
|
t.Fatal("expected amount to be unchanged")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to cancel.
|
||||||
|
err = registry.CancelInvoice(hash)
|
||||||
|
if err != channeldb.ErrInvoiceAlreadySettled {
|
||||||
|
t.Fatal("expected cancelation of a settled invoice to fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCancelInvoice tests cancelation of an invoice and related notifications.
|
||||||
|
func TestCancelInvoice(t *testing.T) {
|
||||||
|
cdb, cleanup, err := newDB()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// Instantiate and start the invoice registry.
|
||||||
|
registry := NewRegistry(cdb, &chaincfg.MainNetParams)
|
||||||
|
|
||||||
|
err = registry.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer registry.Stop()
|
||||||
|
|
||||||
|
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
||||||
|
defer allSubscriptions.Cancel()
|
||||||
|
|
||||||
|
// Try to cancel the not yet existing invoice. This should fail.
|
||||||
|
err = registry.CancelInvoice(hash)
|
||||||
|
if err != channeldb.ErrInvoiceNotFound {
|
||||||
|
t.Fatalf("expected ErrInvoiceNotFound, but got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to the not yet existing invoice.
|
||||||
|
subscription := registry.SubscribeSingleInvoice(hash)
|
||||||
|
defer subscription.Cancel()
|
||||||
|
|
||||||
|
if subscription.hash != hash {
|
||||||
|
t.Fatalf("expected subscription for provided hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the invoice.
|
||||||
|
amt := lnwire.MilliSatoshi(100000)
|
||||||
|
invoice := &channeldb.Invoice{
|
||||||
|
Terms: channeldb.ContractTerm{
|
||||||
|
PaymentPreimage: preimage,
|
||||||
|
Value: amt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = registry.AddInvoice(invoice, hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect the open state to be sent to the single invoice subscriber.
|
||||||
|
select {
|
||||||
|
case update := <-subscription.Updates:
|
||||||
|
if update.Terms.State != channeldb.ContractOpen {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected state ContractOpen, but got %v",
|
||||||
|
update.Terms.State,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("no update received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect a new invoice notification to be sent out.
|
||||||
|
select {
|
||||||
|
case newInvoice := <-allSubscriptions.NewInvoices:
|
||||||
|
if newInvoice.Terms.State != channeldb.ContractOpen {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected state ContractOpen, but got %v",
|
||||||
|
newInvoice.Terms.State,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("no update received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel invoice.
|
||||||
|
err = registry.CancelInvoice(hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect the canceled state to be sent to the single invoice
|
||||||
|
// subscriber.
|
||||||
|
select {
|
||||||
|
case update := <-subscription.Updates:
|
||||||
|
if update.Terms.State != channeldb.ContractCanceled {
|
||||||
|
t.Fatalf(
|
||||||
|
"expected state ContractCanceled, but got %v",
|
||||||
|
update.Terms.State,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("no update received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect no cancel notification to be sent to all invoice
|
||||||
|
// subscribers (backwards compatibility).
|
||||||
|
|
||||||
|
// Try to cancel again.
|
||||||
|
err = registry.CancelInvoice(hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected cancelation of a canceled invoice to succeed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to settle. This should not be possible.
|
||||||
|
err = registry.SettleInvoice(hash, amt)
|
||||||
|
if err != channeldb.ErrInvoiceAlreadyCanceled {
|
||||||
|
t.Fatal("expected settlement of a canceled invoice to fail")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDB() (*channeldb.DB, func(), error) {
|
func newDB() (*channeldb.DB, func(), error) {
|
||||||
|
|||||||
@@ -25,6 +25,80 @@ var _ = math.Inf
|
|||||||
// proto package needs to be updated.
|
// proto package needs to be updated.
|
||||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type CancelInvoiceMsg struct {
|
||||||
|
// / Hash corresponding to the invoice to cancel.
|
||||||
|
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CancelInvoiceMsg) Reset() { *m = CancelInvoiceMsg{} }
|
||||||
|
func (m *CancelInvoiceMsg) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*CancelInvoiceMsg) ProtoMessage() {}
|
||||||
|
func (*CancelInvoiceMsg) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_invoices_1b708c9c030aea0e, []int{0}
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceMsg) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_CancelInvoiceMsg.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_CancelInvoiceMsg.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (dst *CancelInvoiceMsg) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_CancelInvoiceMsg.Merge(dst, src)
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceMsg) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_CancelInvoiceMsg.Size(m)
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceMsg) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_CancelInvoiceMsg.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_CancelInvoiceMsg proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *CancelInvoiceMsg) GetPaymentHash() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.PaymentHash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelInvoiceResp struct {
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CancelInvoiceResp) Reset() { *m = CancelInvoiceResp{} }
|
||||||
|
func (m *CancelInvoiceResp) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*CancelInvoiceResp) ProtoMessage() {}
|
||||||
|
func (*CancelInvoiceResp) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_invoices_1b708c9c030aea0e, []int{1}
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceResp) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_CancelInvoiceResp.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_CancelInvoiceResp.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (dst *CancelInvoiceResp) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_CancelInvoiceResp.Merge(dst, src)
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceResp) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_CancelInvoiceResp.Size(m)
|
||||||
|
}
|
||||||
|
func (m *CancelInvoiceResp) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_CancelInvoiceResp.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_CancelInvoiceResp proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*CancelInvoiceMsg)(nil), "invoicesrpc.CancelInvoiceMsg")
|
||||||
|
proto.RegisterType((*CancelInvoiceResp)(nil), "invoicesrpc.CancelInvoiceResp")
|
||||||
|
}
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
var _ context.Context
|
var _ context.Context
|
||||||
var _ grpc.ClientConn
|
var _ grpc.ClientConn
|
||||||
@@ -42,6 +116,11 @@ type InvoicesClient interface {
|
|||||||
// to notify the client of state transitions of the specified invoice.
|
// to notify the client of state transitions of the specified invoice.
|
||||||
// Initially the current invoice state is always sent out.
|
// Initially the current invoice state is always sent out.
|
||||||
SubscribeSingleInvoice(ctx context.Context, in *lnrpc.PaymentHash, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error)
|
SubscribeSingleInvoice(ctx context.Context, in *lnrpc.PaymentHash, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error)
|
||||||
|
// *
|
||||||
|
// CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||||
|
// canceled, this call will succeed. If the invoice is already settled, it will
|
||||||
|
// fail.
|
||||||
|
CancelInvoice(ctx context.Context, in *CancelInvoiceMsg, opts ...grpc.CallOption) (*CancelInvoiceResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type invoicesClient struct {
|
type invoicesClient struct {
|
||||||
@@ -84,6 +163,15 @@ func (x *invoicesSubscribeSingleInvoiceClient) Recv() (*lnrpc.Invoice, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *invoicesClient) CancelInvoice(ctx context.Context, in *CancelInvoiceMsg, opts ...grpc.CallOption) (*CancelInvoiceResp, error) {
|
||||||
|
out := new(CancelInvoiceResp)
|
||||||
|
err := c.cc.Invoke(ctx, "/invoicesrpc.Invoices/CancelInvoice", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// InvoicesServer is the server API for Invoices service.
|
// InvoicesServer is the server API for Invoices service.
|
||||||
type InvoicesServer interface {
|
type InvoicesServer interface {
|
||||||
// *
|
// *
|
||||||
@@ -91,6 +179,11 @@ type InvoicesServer interface {
|
|||||||
// to notify the client of state transitions of the specified invoice.
|
// to notify the client of state transitions of the specified invoice.
|
||||||
// Initially the current invoice state is always sent out.
|
// Initially the current invoice state is always sent out.
|
||||||
SubscribeSingleInvoice(*lnrpc.PaymentHash, Invoices_SubscribeSingleInvoiceServer) error
|
SubscribeSingleInvoice(*lnrpc.PaymentHash, Invoices_SubscribeSingleInvoiceServer) error
|
||||||
|
// *
|
||||||
|
// CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||||
|
// canceled, this call will succeed. If the invoice is already settled, it will
|
||||||
|
// fail.
|
||||||
|
CancelInvoice(context.Context, *CancelInvoiceMsg) (*CancelInvoiceResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterInvoicesServer(s *grpc.Server, srv InvoicesServer) {
|
func RegisterInvoicesServer(s *grpc.Server, srv InvoicesServer) {
|
||||||
@@ -118,10 +211,33 @@ func (x *invoicesSubscribeSingleInvoiceServer) Send(m *lnrpc.Invoice) error {
|
|||||||
return x.ServerStream.SendMsg(m)
|
return x.ServerStream.SendMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Invoices_CancelInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CancelInvoiceMsg)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(InvoicesServer).CancelInvoice(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/invoicesrpc.Invoices/CancelInvoice",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(InvoicesServer).CancelInvoice(ctx, req.(*CancelInvoiceMsg))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
var _Invoices_serviceDesc = grpc.ServiceDesc{
|
var _Invoices_serviceDesc = grpc.ServiceDesc{
|
||||||
ServiceName: "invoicesrpc.Invoices",
|
ServiceName: "invoicesrpc.Invoices",
|
||||||
HandlerType: (*InvoicesServer)(nil),
|
HandlerType: (*InvoicesServer)(nil),
|
||||||
Methods: []grpc.MethodDesc{},
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "CancelInvoice",
|
||||||
|
Handler: _Invoices_CancelInvoice_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
StreamName: "SubscribeSingleInvoice",
|
StreamName: "SubscribeSingleInvoice",
|
||||||
@@ -133,21 +249,25 @@ var _Invoices_serviceDesc = grpc.ServiceDesc{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto.RegisterFile("invoicesrpc/invoices.proto", fileDescriptor_invoices_c6414974947f2940)
|
proto.RegisterFile("invoicesrpc/invoices.proto", fileDescriptor_invoices_1b708c9c030aea0e)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptor_invoices_c6414974947f2940 = []byte{
|
var fileDescriptor_invoices_1b708c9c030aea0e = []byte{
|
||||||
// 177 bytes of a gzipped FileDescriptorProto
|
// 246 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0xb1, 0x8e, 0xc2, 0x30,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xbf, 0x4b, 0x43, 0x31,
|
||||||
0x10, 0x44, 0x75, 0xcd, 0xe9, 0x2e, 0x27, 0x5d, 0xe1, 0x82, 0xc2, 0xe2, 0x1b, 0xb2, 0x40, 0x7a,
|
0x10, 0xc7, 0x79, 0x8b, 0x68, 0x5a, 0x45, 0x23, 0x88, 0x04, 0x15, 0xed, 0xe4, 0x94, 0xa8, 0xc5,
|
||||||
0x0a, 0x2a, 0xa0, 0x42, 0x4a, 0x47, 0x67, 0x1b, 0xcb, 0x59, 0xe1, 0xec, 0x5a, 0xce, 0x06, 0xc4,
|
0xd5, 0x41, 0x17, 0x1d, 0x14, 0x69, 0x37, 0x17, 0xc9, 0x8b, 0x21, 0x39, 0x4c, 0xef, 0x42, 0x92,
|
||||||
0xdf, 0x23, 0x82, 0x91, 0xd2, 0x8d, 0x66, 0xe6, 0x49, 0xaf, 0xd2, 0x48, 0x37, 0x46, 0xe7, 0x87,
|
0x2a, 0xfe, 0x2b, 0xfe, 0xb5, 0xd2, 0x36, 0xe2, 0x7b, 0x42, 0xb7, 0xcb, 0x7d, 0x7f, 0xe4, 0x93,
|
||||||
0x9c, 0x1c, 0x7c, 0x72, 0x9d, 0x32, 0x0b, 0xab, 0xbf, 0xd9, 0xa6, 0x97, 0x81, 0x39, 0x44, 0x0f,
|
0x30, 0x01, 0xf8, 0x41, 0x60, 0x6c, 0x4e, 0xd1, 0xa8, 0xdf, 0x59, 0xc6, 0x44, 0x85, 0xf8, 0xa0,
|
||||||
0x26, 0x21, 0x18, 0x22, 0x16, 0x23, 0xc8, 0x54, 0xae, 0xfa, 0x37, 0x27, 0xf7, 0x8e, 0x9b, 0x63,
|
0xa3, 0x89, 0x23, 0x47, 0xe4, 0x82, 0x55, 0x3a, 0x82, 0xd2, 0x88, 0x54, 0x74, 0x01, 0xc2, 0x6a,
|
||||||
0xf5, 0x73, 0x28, 0x9c, 0xda, 0x56, 0x8b, 0x76, 0xb4, 0x83, 0xcb, 0x68, 0x7d, 0x8b, 0x14, 0xa2,
|
0x15, 0x5b, 0x29, 0x9a, 0xd5, 0x38, 0xba, 0x66, 0xbb, 0x77, 0x1a, 0x8d, 0x0d, 0x0f, 0xab, 0xf4,
|
||||||
0x2f, 0x93, 0x52, 0x75, 0xa4, 0x17, 0x73, 0x32, 0x8f, 0xde, 0x93, 0xec, 0xcd, 0xd0, 0xe9, 0xff,
|
0x63, 0x76, 0xfc, 0x8c, 0x0d, 0xa3, 0xfe, 0x9a, 0x59, 0x2c, 0xaf, 0x5e, 0x67, 0x7f, 0xd8, 0x9c,
|
||||||
0xd2, 0x95, 0xcf, 0xea, 0x6b, 0xd7, 0x9c, 0xd7, 0x01, 0xa5, 0x1b, 0x6d, 0xed, 0xb8, 0x87, 0x88,
|
0x36, 0xe7, 0xc3, 0xc9, 0xa0, 0xee, 0xee, 0x75, 0xf6, 0xa3, 0x7d, 0xb6, 0xd7, 0x8b, 0x4d, 0x6c,
|
||||||
0xa1, 0x13, 0x42, 0x0a, 0xe4, 0xe5, 0xce, 0xf9, 0x0a, 0x91, 0x2e, 0x30, 0x21, 0x30, 0x33, 0xb5,
|
0x8e, 0x57, 0xdf, 0x0d, 0xdb, 0xac, 0xe7, 0xcc, 0x6f, 0xd8, 0xc1, 0x74, 0xde, 0x66, 0x93, 0xa0,
|
||||||
0xdf, 0x93, 0x47, 0xf3, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x4e, 0x97, 0xf2, 0xdb, 0x00, 0x00,
|
0xb5, 0x53, 0x40, 0x17, 0x6c, 0x95, 0x38, 0x97, 0x01, 0x17, 0x00, 0xcf, 0x7f, 0x7d, 0x62, 0xa7,
|
||||||
0x00,
|
0xee, 0xaa, 0xe7, 0xa2, 0xe1, 0x4f, 0x6c, 0xbb, 0x77, 0x03, 0x3f, 0x96, 0x9d, 0x07, 0xca, 0xff,
|
||||||
|
0xd0, 0xe2, 0x64, 0xbd, 0xbc, 0x80, 0xbb, 0x1d, 0xbf, 0x5c, 0x3a, 0x28, 0x7e, 0xde, 0x4a, 0x43,
|
||||||
|
0x33, 0x15, 0xc0, 0xf9, 0x82, 0x80, 0x0e, 0x6d, 0xf9, 0xa4, 0xf4, 0xae, 0x02, 0xbe, 0xa9, 0x25,
|
||||||
|
0x82, 0xea, 0xd4, 0xb4, 0x1b, 0xcb, 0x4f, 0x1a, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd2, 0xc3,
|
||||||
|
0x7e, 0x3a, 0x78, 0x01, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
@@ -16,5 +16,17 @@ service Invoices {
|
|||||||
Initially the current invoice state is always sent out.
|
Initially the current invoice state is always sent out.
|
||||||
*/
|
*/
|
||||||
rpc SubscribeSingleInvoice (lnrpc.PaymentHash) returns (stream lnrpc.Invoice);
|
rpc SubscribeSingleInvoice (lnrpc.PaymentHash) returns (stream lnrpc.Invoice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||||
|
canceled, this call will succeed. If the invoice is already settled, it will
|
||||||
|
fail.
|
||||||
|
*/
|
||||||
|
rpc CancelInvoice(CancelInvoiceMsg) returns (CancelInvoiceResp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CancelInvoiceMsg {
|
||||||
|
/// Hash corresponding to the invoice to cancel.
|
||||||
|
bytes payment_hash = 1;
|
||||||
|
}
|
||||||
|
message CancelInvoiceResp {}
|
||||||
@@ -8,10 +8,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,6 +27,10 @@ var (
|
|||||||
// macaroonOps are the set of capabilities that our minted macaroon (if
|
// macaroonOps are the set of capabilities that our minted macaroon (if
|
||||||
// it doesn't already exist) will have.
|
// it doesn't already exist) will have.
|
||||||
macaroonOps = []bakery.Op{
|
macaroonOps = []bakery.Op{
|
||||||
|
{
|
||||||
|
Entity: "invoices",
|
||||||
|
Action: "write",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Entity: "invoices",
|
Entity: "invoices",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
@@ -38,6 +43,10 @@ var (
|
|||||||
Entity: "invoices",
|
Entity: "invoices",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
}},
|
}},
|
||||||
|
"/invoicesrpc.Invoices/CancelInvoice": {{
|
||||||
|
Entity: "invoices",
|
||||||
|
Action: "write",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInvoicesMacFilename is the default name of the invoices
|
// DefaultInvoicesMacFilename is the default name of the invoices
|
||||||
@@ -181,3 +190,24 @@ func (s *Server) SubscribeSingleInvoice(req *lnrpc.PaymentHash,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelInvoice cancels a currently open invoice. If the invoice is already
|
||||||
|
// canceled, this call will succeed. If the invoice is already settled, it will
|
||||||
|
// fail.
|
||||||
|
func (s *Server) CancelInvoice(ctx context.Context,
|
||||||
|
in *CancelInvoiceMsg) (*CancelInvoiceResp, error) {
|
||||||
|
|
||||||
|
paymentHash, err := lntypes.NewHash(in.PaymentHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.cfg.InvoiceRegistry.CancelInvoice(*paymentHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Canceled invoice %v", paymentHash)
|
||||||
|
|
||||||
|
return &CancelInvoiceResp{}, nil
|
||||||
|
}
|
||||||
@@ -59,8 +59,11 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
|||||||
state = lnrpc.Invoice_OPEN
|
state = lnrpc.Invoice_OPEN
|
||||||
case channeldb.ContractSettled:
|
case channeldb.ContractSettled:
|
||||||
state = lnrpc.Invoice_SETTLED
|
state = lnrpc.Invoice_SETTLED
|
||||||
|
case channeldb.ContractCanceled:
|
||||||
|
state = lnrpc.Invoice_CANCELED
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown invoice state")
|
return nil, fmt.Errorf("unknown invoice state %v",
|
||||||
|
invoice.Terms.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &lnrpc.Invoice{
|
return &lnrpc.Invoice{
|
||||||
|
|||||||
+569
-566
File diff suppressed because it is too large.
Load diff
@@ -1850,6 +1850,7 @@ message Invoice {
|
|||||||
enum InvoiceState {
|
enum InvoiceState {
|
||||||
OPEN = 0;
|
OPEN = 0;
|
||||||
SETTLED = 1;
|
SETTLED = 1;
|
||||||
|
CANCELED = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1141,7 +1141,8 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"OPEN",
|
"OPEN",
|
||||||
"SETTLED"
|
"SETTLED",
|
||||||
|
"CANCELED"
|
||||||
],
|
],
|
||||||
"default": "OPEN"
|
"default": "OPEN"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user