mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-04-01 00:19:54 +02:00
Merge pull request #2457 from joostjager/cancelinvoice
invoices: CancelInvoice
This commit is contained in:
commit
27cfdf944e
@ -62,6 +62,10 @@ var (
|
||||
// ErrInvoiceAlreadySettled is returned when the invoice is already
|
||||
// settled.
|
||||
ErrInvoiceAlreadySettled = errors.New("invoice already settled")
|
||||
|
||||
// ErrInvoiceAlreadyCanceled is returned when the invoice is already
|
||||
// canceled.
|
||||
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -90,6 +94,9 @@ const (
|
||||
// ContractSettled means the htlc is settled and the invoice has been
|
||||
// paid.
|
||||
ContractSettled ContractState = 1
|
||||
|
||||
// ContractCanceled means the invoice has been canceled.
|
||||
ContractCanceled ContractState = 2
|
||||
)
|
||||
|
||||
// String returns a human readable identifier for the ContractState type.
|
||||
@ -99,6 +106,8 @@ func (c ContractState) String() string {
|
||||
return "Open"
|
||||
case ContractSettled:
|
||||
return "Settled"
|
||||
case ContractCanceled:
|
||||
return "Canceled"
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
@ -641,6 +650,37 @@ func (d *DB) SettleInvoice(paymentHash [32]byte,
|
||||
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
|
||||
// they missed within the settled invoice time series. We'll return all known
|
||||
// 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
|
||||
}
|
||||
|
||||
if invoice.Terms.State == ContractSettled {
|
||||
switch invoice.Terms.State {
|
||||
case ContractSettled:
|
||||
return &invoice, ErrInvoiceAlreadySettled
|
||||
case ContractCanceled:
|
||||
return &invoice, ErrInvoiceAlreadyCanceled
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
82
cmd/lncli/invoicesrpc_active.go
Normal file
82
cmd/lncli/invoicesrpc_active.go
Normal file
@ -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
|
||||
}
|
10
cmd/lncli/invoicesrpc_default.go
Normal file
10
cmd/lncli/invoicesrpc_default.go
Normal file
@ -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.
|
||||
app.Commands = append(app.Commands, autopilotCommands()...)
|
||||
app.Commands = append(app.Commands, invoicesCommands()...)
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fatal(err)
|
||||
|
@ -20,6 +20,10 @@ type InvoiceDatabase interface {
|
||||
// SettleInvoice attempts to mark an invoice corresponding to the
|
||||
// passed payment hash as fully settled.
|
||||
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
|
||||
|
@ -2337,6 +2337,23 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
||||
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
|
||||
// accept the payment to simplify failure recovery.
|
||||
//
|
||||
|
@ -218,7 +218,7 @@ func TestChannelLinkSingleHopPayment(t *testing.T) {
|
||||
// * user notification to be sent.
|
||||
receiver := n.bobServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -314,7 +314,7 @@ func TestChannelLinkBidirectionalOneHopPayments(t *testing.T) {
|
||||
}
|
||||
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, r.err = n.makePayment(
|
||||
_, r.err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop,
|
||||
hopsForwards, amt, htlcAmt, totalTimelock,
|
||||
).Wait(5 * time.Minute)
|
||||
@ -331,7 +331,7 @@ func TestChannelLinkBidirectionalOneHopPayments(t *testing.T) {
|
||||
}
|
||||
|
||||
firstHop := n.aliceChannelLink.ShortChanID()
|
||||
_, r.err = n.makePayment(
|
||||
_, r.err = makePayment(
|
||||
n.bobServer, n.aliceServer, firstHop,
|
||||
hopsBackwards, amt, htlcAmt, totalTimelock,
|
||||
).Wait(5 * time.Minute)
|
||||
@ -451,7 +451,7 @@ func TestChannelLinkMultiHopPayment(t *testing.T) {
|
||||
// * user notification to be sent.
|
||||
receiver := n.carolServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).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.
|
||||
hops[0].OutgoingCTLV = 500
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||
htlcExpiry,
|
||||
).Wait(30 * time.Second)
|
||||
@ -582,7 +582,7 @@ func TestExitNodeAmountPayloadMismatch(t *testing.T) {
|
||||
// receiving node expects to receive.
|
||||
hops[0].AmountToForward = 1
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||
htlcExpiry,
|
||||
).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
|
||||
// specified parameters to the first hop in the route.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
htlcExpiry,
|
||||
).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
|
||||
// specified parameters to the first hop in the route.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amountNoFee,
|
||||
amountNoFee, htlcExpiry,
|
||||
).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
|
||||
// specified parameters to the first hop in the route.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amountNoFee,
|
||||
htlcAmt, htlcExpiry,
|
||||
).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
|
||||
// should succeed, and all balances should be updated accordingly.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
payResp, err := n.makePayment(
|
||||
payResp, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
||||
htlcAmt, htlcExpiry,
|
||||
).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
|
||||
// payload for each node. This payment should fail as it won't factor
|
||||
// in Bob's new fee policy.
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
||||
htlcAmt, htlcExpiry,
|
||||
).Wait(30 * time.Second)
|
||||
@ -917,7 +917,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
|
||||
|
||||
receiver := n.carolServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -1094,7 +1094,7 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
|
||||
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
receiver := n.carolServer
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock).Wait(30 * time.Second)
|
||||
if err == nil {
|
||||
@ -1201,7 +1201,7 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
|
||||
|
||||
receiver := n.carolServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -1287,7 +1287,7 @@ func TestChannelLinkExpiryTooSoonExitNode(t *testing.T) {
|
||||
|
||||
// Now we'll send out the payment from Alice to Bob.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -1347,7 +1347,7 @@ func TestChannelLinkExpiryTooSoonMidNode(t *testing.T) {
|
||||
|
||||
// Now we'll send out the payment from Alice to Bob.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -1446,7 +1446,7 @@ func TestChannelLinkSingleHopMessageOrdering(t *testing.T) {
|
||||
// * alice<->bob commitment state to be updated.
|
||||
// * user notification to be sent.
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -3283,7 +3283,7 @@ func TestChannelRetransmission(t *testing.T) {
|
||||
// TODO(roasbeef); increase timeout?
|
||||
receiver := n.bobServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, receiver, firstHop, hops, amount,
|
||||
htlcAmt, totalTimelock,
|
||||
).Wait(time.Second * 5)
|
||||
@ -3578,7 +3578,7 @@ func TestChannelLinkShutdownDuringForward(t *testing.T) {
|
||||
)
|
||||
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
n.makePayment(
|
||||
makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
)
|
||||
@ -3814,7 +3814,7 @@ func TestChannelLinkAcceptOverpay(t *testing.T) {
|
||||
// invoice at Carol for only half of this amount.
|
||||
receiver := n.carolServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := n.makePayment(
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount/2, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -5119,7 +5119,7 @@ func TestForwardingAsymmetricTimeLockPolicies(t *testing.T) {
|
||||
)
|
||||
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).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)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"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/channeldb"
|
||||
"github.com/lightningnetwork/lnd/contractcourt"
|
||||
@ -735,6 +735,25 @@ func (i *mockInvoiceRegistry) SettleInvoice(rhash lntypes.Hash,
|
||||
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 {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
@ -1847,7 +1847,7 @@ func TestLocalPaymentNoForwardingEvents(t *testing.T) {
|
||||
// proceeding.
|
||||
receiver := n.bobServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
_, err = n.makePayment(
|
||||
_, err = makePayment(
|
||||
n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -1908,7 +1908,7 @@ func TestMultiHopPaymentForwardingEvents(t *testing.T) {
|
||||
)
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
for i := 0; i < numPayments/2; i++ {
|
||||
_, err := n.makePayment(
|
||||
_, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
||||
htlcAmt, totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
@ -1961,7 +1961,7 @@ func TestMultiHopPaymentForwardingEvents(t *testing.T) {
|
||||
|
||||
// Send the remaining payments.
|
||||
for i := numPayments / 2; i < numPayments; i++ {
|
||||
_, err := n.makePayment(
|
||||
_, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
||||
htlcAmt, totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
|
@ -585,9 +585,7 @@ type threeHopNetwork struct {
|
||||
carolServer *mockServer
|
||||
carolChannelLink *channelLink
|
||||
|
||||
feeEstimator *mockFeeEstimator
|
||||
|
||||
globalPolicy ForwardingPolicy
|
||||
hopNetwork
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 {
|
||||
case err := <-r.err:
|
||||
close(r.err)
|
||||
return r.rhash, err
|
||||
case err := <-c:
|
||||
close(c)
|
||||
return err
|
||||
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
|
||||
// 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.
|
||||
@ -674,15 +689,45 @@ func (r *paymentResponse) Wait(d time.Duration) (lntypes.Hash, error) {
|
||||
// * from Alice to Bob
|
||||
// * from Alice to Carol 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,
|
||||
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
||||
timelock uint32) *paymentResponse {
|
||||
|
||||
paymentErr := make(chan error, 1)
|
||||
|
||||
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)
|
||||
receiver := receivingPeer.(*mockServer)
|
||||
|
||||
@ -690,45 +735,27 @@ func (n *threeHopNetwork) makePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
||||
// htlc add request.
|
||||
blob, err := generateRoute(hops...)
|
||||
if err != nil {
|
||||
paymentErr <- err
|
||||
return &paymentResponse{
|
||||
rhash: rhash,
|
||||
err: paymentErr,
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Generate payment: invoice and htlc.
|
||||
invoice, htlc, err := generatePayment(invoiceAmt, htlcAmt, timelock, blob)
|
||||
if err != nil {
|
||||
paymentErr <- err
|
||||
return &paymentResponse{
|
||||
rhash: rhash,
|
||||
err: paymentErr,
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
rhash = fastsha256.Sum256(invoice.Terms.PaymentPreimage[:])
|
||||
|
||||
// Check who is last in the route and add invoice to server registry.
|
||||
if err := receiver.registry.AddInvoice(*invoice); err != nil {
|
||||
paymentErr <- err
|
||||
return &paymentResponse{
|
||||
rhash: rhash,
|
||||
err: paymentErr,
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Send payment and expose err channel.
|
||||
go func() {
|
||||
return invoice, func() error {
|
||||
_, err := sender.htlcSwitch.SendHTLC(
|
||||
firstHop, htlc, newMockDeobfuscator(),
|
||||
)
|
||||
paymentErr <- err
|
||||
}()
|
||||
|
||||
return &paymentResponse{
|
||||
rhash: rhash,
|
||||
err: paymentErr,
|
||||
}
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
carolDb := carolChannel.State().Db
|
||||
|
||||
defaultDelta := uint32(6)
|
||||
hopNetwork := newHopNetwork()
|
||||
|
||||
// Create three peers/servers.
|
||||
aliceServer, err := newMockServer(
|
||||
t, "alice", startingHeight, aliceDb, defaultDelta,
|
||||
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, defaultDelta,
|
||||
t, "bob", startingHeight, bobDb, hopNetwork.defaultDelta,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bob server: %v", err)
|
||||
}
|
||||
carolServer, err := newMockServer(
|
||||
t, "carol", startingHeight, carolDb, defaultDelta,
|
||||
t, "carol", startingHeight, carolDb, hopNetwork.defaultDelta,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create carol server: %v", err)
|
||||
@ -882,17 +909,78 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
||||
bobDecoder := newMockIteratorDecoder()
|
||||
carolDecoder := newMockIteratorDecoder()
|
||||
|
||||
feeEstimator := &mockFeeEstimator{
|
||||
byteFeeIn: make(chan lnwallet.SatPerKWeight),
|
||||
quit: make(chan struct{}),
|
||||
aliceChannelLink, err := hopNetwork.createChannelLink(aliceServer,
|
||||
bobServer, aliceChannel, aliceDecoder,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const (
|
||||
batchTimeout = 50 * time.Millisecond
|
||||
fwdPkgTimeout = 15 * time.Second
|
||||
minFeeUpdateTimeout = 30 * time.Minute
|
||||
maxFeeUpdateTimeout = 40 * time.Minute
|
||||
)
|
||||
firstBobChannelLink, err := hopNetwork.createChannelLink(bobServer,
|
||||
aliceServer, firstBobChannel, bobDecoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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{
|
||||
// hash -> preimage
|
||||
@ -906,22 +994,47 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
||||
}
|
||||
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{
|
||||
Switch: aliceServer.htlcSwitch,
|
||||
FwrdingPolicy: globalPolicy,
|
||||
Peer: bobServer,
|
||||
Circuits: aliceServer.htlcSwitch.CircuitModifier(),
|
||||
ForwardPackets: aliceServer.htlcSwitch.ForwardPackets,
|
||||
DecodeHopIterators: aliceDecoder.DecodeHopIterators,
|
||||
Switch: server.htlcSwitch,
|
||||
FwrdingPolicy: h.globalPolicy,
|
||||
Peer: peer,
|
||||
Circuits: server.htlcSwitch.CircuitModifier(),
|
||||
ForwardPackets: server.htlcSwitch.ForwardPackets,
|
||||
DecodeHopIterators: decoder.DecodeHopIterators,
|
||||
ExtractErrorEncrypter: func(*btcec.PublicKey) (
|
||||
ErrorEncrypter, lnwire.FailCode) {
|
||||
return obfuscator, lnwire.CodeNone
|
||||
return h.obfuscator, lnwire.CodeNone
|
||||
},
|
||||
FetchLastChannelUpdate: mockGetChanUpdateMessage,
|
||||
Registry: aliceServer.registry,
|
||||
FeeEstimator: feeEstimator,
|
||||
PreimageCache: pCache,
|
||||
Registry: server.registry,
|
||||
FeeEstimator: h.feeEstimator,
|
||||
PreimageCache: h.pCache,
|
||||
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
|
||||
return nil
|
||||
},
|
||||
@ -934,162 +1047,128 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
|
||||
MaxFeeUpdateTimeout: maxFeeUpdateTimeout,
|
||||
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
|
||||
},
|
||||
aliceChannel,
|
||||
channel,
|
||||
)
|
||||
if err := aliceServer.htlcSwitch.AddLink(aliceChannelLink); err != nil {
|
||||
t.Fatalf("unable to add alice channel link: %v", err)
|
||||
if err := server.htlcSwitch.AddLink(link); err != nil {
|
||||
return nil, fmt.Errorf("unable to add channel link: %v", err)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-aliceChannelLink.(*channelLink).htlcUpdates:
|
||||
case <-aliceChannelLink.(*channelLink).quit:
|
||||
case <-link.(*channelLink).htlcUpdates:
|
||||
case <-link.(*channelLink).quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
firstBobChannelLink := NewChannelLink(
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
return link, nil
|
||||
}
|
||||
|
||||
secondBobChannelLink := NewChannelLink(
|
||||
ChannelLinkConfig{
|
||||
Switch: bobServer.htlcSwitch,
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
// twoHopNetwork is used for managing the created cluster of 2 hops.
|
||||
type twoHopNetwork struct {
|
||||
hopNetwork
|
||||
|
||||
carolChannelLink := NewChannelLink(
|
||||
ChannelLinkConfig{
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
aliceServer *mockServer
|
||||
aliceChannelLink *channelLink
|
||||
|
||||
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,
|
||||
aliceChannelLink: aliceChannelLink.(*channelLink),
|
||||
|
||||
bobServer: bobServer,
|
||||
firstBobChannelLink: firstBobChannelLink.(*channelLink),
|
||||
secondBobChannelLink: secondBobChannelLink.(*channelLink),
|
||||
bobServer: bobServer,
|
||||
bobChannelLink: bobChannelLink.(*channelLink),
|
||||
|
||||
carolServer: carolServer,
|
||||
carolChannelLink: carolChannelLink.(*channelLink),
|
||||
|
||||
feeEstimator: feeEstimator,
|
||||
globalPolicy: globalPolicy,
|
||||
hopNetwork: *hopNetwork,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// dispatch notifications to all registered clients.
|
||||
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)
|
||||
|
||||
case <-i.quit:
|
||||
@ -256,7 +260,7 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
case channeldb.ContractOpen:
|
||||
client.addIndex = invoice.AddIndex
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// of a newly added/settled invoice.
|
||||
func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash,
|
||||
|
@ -150,6 +150,122 @@ func TestSettleInvoice(t *testing.T) {
|
||||
if inv.AmtPaid != amtPaid {
|
||||
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) {
|
||||
|
@ -25,6 +25,80 @@ var _ = math.Inf
|
||||
// proto package needs to be updated.
|
||||
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.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
@ -42,6 +116,11 @@ type InvoicesClient interface {
|
||||
// to notify the client of state transitions of the specified invoice.
|
||||
// Initially the current invoice state is always sent out.
|
||||
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 {
|
||||
@ -84,6 +163,15 @@ func (x *invoicesSubscribeSingleInvoiceClient) Recv() (*lnrpc.Invoice, error) {
|
||||
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.
|
||||
type InvoicesServer interface {
|
||||
// *
|
||||
@ -91,6 +179,11 @@ type InvoicesServer interface {
|
||||
// to notify the client of state transitions of the specified invoice.
|
||||
// Initially the current invoice state is always sent out.
|
||||
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) {
|
||||
@ -118,10 +211,33 @@ func (x *invoicesSubscribeSingleInvoiceServer) Send(m *lnrpc.Invoice) error {
|
||||
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{
|
||||
ServiceName: "invoicesrpc.Invoices",
|
||||
HandlerType: (*InvoicesServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CancelInvoice",
|
||||
Handler: _Invoices_CancelInvoice_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SubscribeSingleInvoice",
|
||||
@ -133,21 +249,25 @@ var _Invoices_serviceDesc = grpc.ServiceDesc{
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("invoicesrpc/invoices.proto", fileDescriptor_invoices_c6414974947f2940)
|
||||
proto.RegisterFile("invoicesrpc/invoices.proto", fileDescriptor_invoices_1b708c9c030aea0e)
|
||||
}
|
||||
|
||||
var fileDescriptor_invoices_c6414974947f2940 = []byte{
|
||||
// 177 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0xb1, 0x8e, 0xc2, 0x30,
|
||||
0x10, 0x44, 0x75, 0xcd, 0xe9, 0x2e, 0x27, 0x5d, 0xe1, 0x82, 0xc2, 0xe2, 0x1b, 0xb2, 0x40, 0x7a,
|
||||
0x0a, 0x2a, 0xa0, 0x42, 0x4a, 0x47, 0x67, 0x1b, 0xcb, 0x59, 0xe1, 0xec, 0x5a, 0xce, 0x06, 0xc4,
|
||||
0xdf, 0x23, 0x82, 0x91, 0xd2, 0x8d, 0x66, 0xe6, 0x49, 0xaf, 0xd2, 0x48, 0x37, 0x46, 0xe7, 0x87,
|
||||
0x9c, 0x1c, 0x7c, 0x72, 0x9d, 0x32, 0x0b, 0xab, 0xbf, 0xd9, 0xa6, 0x97, 0x81, 0x39, 0x44, 0x0f,
|
||||
0x26, 0x21, 0x18, 0x22, 0x16, 0x23, 0xc8, 0x54, 0xae, 0xfa, 0x37, 0x27, 0xf7, 0x8e, 0x9b, 0x63,
|
||||
0xf5, 0x73, 0x28, 0x9c, 0xda, 0x56, 0x8b, 0x76, 0xb4, 0x83, 0xcb, 0x68, 0x7d, 0x8b, 0x14, 0xa2,
|
||||
0x2f, 0x93, 0x52, 0x75, 0xa4, 0x17, 0x73, 0x32, 0x8f, 0xde, 0x93, 0xec, 0xcd, 0xd0, 0xe9, 0xff,
|
||||
0xd2, 0x95, 0xcf, 0xea, 0x6b, 0xd7, 0x9c, 0xd7, 0x01, 0xa5, 0x1b, 0x6d, 0xed, 0xb8, 0x87, 0x88,
|
||||
0xa1, 0x13, 0x42, 0x0a, 0xe4, 0xe5, 0xce, 0xf9, 0x0a, 0x91, 0x2e, 0x30, 0x21, 0x30, 0x33, 0xb5,
|
||||
0xdf, 0x93, 0x47, 0xf3, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x4e, 0x97, 0xf2, 0xdb, 0x00, 0x00,
|
||||
0x00,
|
||||
var fileDescriptor_invoices_1b708c9c030aea0e = []byte{
|
||||
// 246 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xbf, 0x4b, 0x43, 0x31,
|
||||
0x10, 0xc7, 0x79, 0x8b, 0x68, 0x5a, 0x45, 0x23, 0x88, 0x04, 0x15, 0xed, 0xe4, 0x94, 0xa8, 0xc5,
|
||||
0xd5, 0x41, 0x17, 0x1d, 0x14, 0x69, 0x37, 0x17, 0xc9, 0x8b, 0x21, 0x39, 0x4c, 0xef, 0x42, 0x92,
|
||||
0x2a, 0xfe, 0x2b, 0xfe, 0xb5, 0xd2, 0x36, 0xe2, 0x7b, 0x42, 0xb7, 0xcb, 0x7d, 0x7f, 0xe4, 0x93,
|
||||
0x30, 0x01, 0xf8, 0x41, 0x60, 0x6c, 0x4e, 0xd1, 0xa8, 0xdf, 0x59, 0xc6, 0x44, 0x85, 0xf8, 0xa0,
|
||||
0xa3, 0x89, 0x23, 0x47, 0xe4, 0x82, 0x55, 0x3a, 0x82, 0xd2, 0x88, 0x54, 0x74, 0x01, 0xc2, 0x6a,
|
||||
0x15, 0x5b, 0x29, 0x9a, 0xd5, 0x38, 0xba, 0x66, 0xbb, 0x77, 0x1a, 0x8d, 0x0d, 0x0f, 0xab, 0xf4,
|
||||
0x63, 0x76, 0xfc, 0x8c, 0x0d, 0xa3, 0xfe, 0x9a, 0x59, 0x2c, 0xaf, 0x5e, 0x67, 0x7f, 0xd8, 0x9c,
|
||||
0x36, 0xe7, 0xc3, 0xc9, 0xa0, 0xee, 0xee, 0x75, 0xf6, 0xa3, 0x7d, 0xb6, 0xd7, 0x8b, 0x4d, 0x6c,
|
||||
0x8e, 0x57, 0xdf, 0x0d, 0xdb, 0xac, 0xe7, 0xcc, 0x6f, 0xd8, 0xc1, 0x74, 0xde, 0x66, 0x93, 0xa0,
|
||||
0xb5, 0x53, 0x40, 0x17, 0x6c, 0x95, 0x38, 0x97, 0x01, 0x17, 0x00, 0xcf, 0x7f, 0x7d, 0x62, 0xa7,
|
||||
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.
|
||||
*/
|
||||
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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"google.golang.org/grpc"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -26,6 +27,10 @@ var (
|
||||
// macaroonOps are the set of capabilities that our minted macaroon (if
|
||||
// it doesn't already exist) will have.
|
||||
macaroonOps = []bakery.Op{
|
||||
{
|
||||
Entity: "invoices",
|
||||
Action: "write",
|
||||
},
|
||||
{
|
||||
Entity: "invoices",
|
||||
Action: "read",
|
||||
@ -38,6 +43,10 @@ var (
|
||||
Entity: "invoices",
|
||||
Action: "read",
|
||||
}},
|
||||
"/invoicesrpc.Invoices/CancelInvoice": {{
|
||||
Entity: "invoices",
|
||||
Action: "write",
|
||||
}},
|
||||
}
|
||||
|
||||
// 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
|
||||
case channeldb.ContractSettled:
|
||||
state = lnrpc.Invoice_SETTLED
|
||||
case channeldb.ContractCanceled:
|
||||
state = lnrpc.Invoice_CANCELED
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown invoice state")
|
||||
return nil, fmt.Errorf("unknown invoice state %v",
|
||||
invoice.Terms.State)
|
||||
}
|
||||
|
||||
return &lnrpc.Invoice{
|
||||
|
1135
lnrpc/rpc.pb.go
1135
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -1850,6 +1850,7 @@ message Invoice {
|
||||
enum InvoiceState {
|
||||
OPEN = 0;
|
||||
SETTLED = 1;
|
||||
CANCELED = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1141,7 +1141,8 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"OPEN",
|
||||
"SETTLED"
|
||||
"SETTLED",
|
||||
"CANCELED"
|
||||
],
|
||||
"default": "OPEN"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user