Merge pull request #3795 from joostjager/keysend

lncli+invoices: experimental key send mode
This commit is contained in:
Olaoluwa Osuntokun 2019-12-24 17:38:46 -06:00 committed by GitHub
commit f289a39c1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1120 additions and 789 deletions

View File

@ -219,8 +219,8 @@ type Invoice struct {
// or any other message which fits within the size constraints.
Memo []byte
// PaymentRequest is an optional field where a payment request created
// for this invoice can be stored.
// PaymentRequest is the encoded payment request for this invoice. For
// spontaneous (key send) payments, this field will be empty.
PaymentRequest []byte
// CreationDate is the exact time the invoice was created.

View File

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
@ -24,6 +25,8 @@ import (
"github.com/lightninglabs/protobuf-hex-display/proto"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/urfave/cli"
@ -2144,12 +2147,6 @@ var sendPaymentCommand = cli.Command{
* --amt=A
* --final_cltv_delta=T
* --payment_hash=H
The --debug_send flag is provided for usage *purely* in test
environments. If specified, then the payment hash isn't required, as
it'll use the hash of all zeroes. This mode allows one to quickly test
payment connectivity without having to create an invoice at the
destination.
`,
ArgsUsage: "dest amt payment_hash final_cltv_delta | --pay_req=[payment request]",
Flags: append(paymentFlags(),
@ -2166,14 +2163,14 @@ var sendPaymentCommand = cli.Command{
Name: "payment_hash, r",
Usage: "the hash to use within the payment's HTLC",
},
cli.BoolFlag{
Name: "debug_send",
Usage: "use the debug rHash when sending the HTLC",
},
cli.Int64Flag{
Name: "final_cltv_delta",
Usage: "the number of blocks the last hop has to reveal the preimage",
},
cli.BoolFlag{
Name: "key_send",
Usage: "will generate a pre-image and encode it in the sphinx packet, a dest must be set [experimental]",
},
),
Action: sendPayment,
}
@ -2276,11 +2273,25 @@ func sendPayment(ctx *cli.Context) error {
Amt: amount,
}
if ctx.Bool("debug_send") && (ctx.IsSet("payment_hash") || args.Present()) {
return fmt.Errorf("do not provide a payment hash with debug send")
} else if !ctx.Bool("debug_send") {
var rHash []byte
var rHash []byte
if ctx.Bool("key_send") {
if ctx.IsSet("payment_hash") {
return errors.New("cannot set payment hash when using " +
"key send")
}
var preimage lntypes.Preimage
if _, err := rand.Read(preimage[:]); err != nil {
return err
}
req.DestCustomRecords = map[uint64][]byte{
record.KeySendType: preimage[:],
}
hash := preimage.Hash()
rHash = hash[:]
} else {
switch {
case ctx.IsSet("payment_hash"):
rHash, err = hex.DecodeString(ctx.String("payment_hash"))
@ -2290,26 +2301,26 @@ func sendPayment(ctx *cli.Context) error {
default:
return fmt.Errorf("payment hash argument missing")
}
}
if err != nil {
return err
}
if len(rHash) != 32 {
return fmt.Errorf("payment hash must be exactly 32 "+
"bytes, is instead %v", len(rHash))
}
req.PaymentHash = rHash
switch {
case ctx.IsSet("final_cltv_delta"):
req.FinalCltvDelta = int32(ctx.Int64("final_cltv_delta"))
case args.Present():
delta, err := strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return err
}
if len(rHash) != 32 {
return fmt.Errorf("payment hash must be exactly 32 "+
"bytes, is instead %v", len(rHash))
}
req.PaymentHash = rHash
switch {
case ctx.IsSet("final_cltv_delta"):
req.FinalCltvDelta = int32(ctx.Int64("final_cltv_delta"))
case args.Present():
delta, err := strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return err
}
req.FinalCltvDelta = int32(delta)
}
req.FinalCltvDelta = int32(delta)
}
return sendPaymentRequest(ctx, req)

View File

@ -326,6 +326,8 @@ type config struct {
EnableUpfrontShutdown bool `long:"enable-upfront-shutdown" description:"If true, option upfront shutdown script will be enabled. If peers that we open channels with support this feature, we will automatically set the script to which cooperative closes should be paid out to on channel open. This offers the partial protection of a channel peer disconnecting from us if cooperative close is attempted with a different script."`
AcceptKeySend bool `long:"accept-key-send" description:"If true, spontaneous payments through key send will be accepted. [experimental]"`
Routing *routing.Conf `group:"routing" namespace:"routing"`
Workers *lncfg.Workers `group:"workers" namespace:"workers"`

View File

@ -13,6 +13,7 @@ import (
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/record"
)
var (
@ -94,6 +95,10 @@ type RegistryConfig struct {
// Now() and TickAfter() and is useful to stub out the clock functions
// during testing.
Clock clock.Clock
// AcceptKeySend indicates whether we want to accept spontaneous key
// send payments.
AcceptKeySend bool
}
// htlcReleaseEvent describes an htlc auto-release event. It is used to release
@ -690,6 +695,68 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
return nil
}
// processKeySend just-in-time inserts an invoice if this htlc is a key send
// htlc.
func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx,
hash lntypes.Hash) error {
// Retrieve key send record if present.
preimageSlice, ok := ctx.customRecords[record.KeySendType]
if !ok {
return nil
}
// Cancel htlc is preimage is invalid.
preimage, err := lntypes.MakePreimage(preimageSlice)
if err != nil || preimage.Hash() != hash {
return errors.New("invalid key send preimage")
}
// Don't accept zero preimages as those have a special meaning in our
// database for hodl invoices.
if preimage == channeldb.UnknownPreimage {
return errors.New("invalid key send preimage")
}
// Only allow key send for non-mpp payments.
if ctx.mpp != nil {
return errors.New("no mpp key send supported")
}
// Create an invoice for the htlc amount.
amt := ctx.amtPaid
// Set tlv optional feature vector on the invoice. Otherwise we wouldn't
// be able to pay to it with key send.
rawFeatures := lnwire.NewRawFeatureVector(
lnwire.TLVOnionPayloadOptional,
)
features := lnwire.NewFeatureVector(rawFeatures, lnwire.Features)
// Use the minimum block delta that we require for settling htlcs.
finalCltvDelta := i.cfg.FinalCltvRejectDelta
// Create placeholder invoice.
invoice := &channeldb.Invoice{
CreationDate: i.cfg.Clock.Now(),
Terms: channeldb.ContractTerm{
FinalCltvDelta: finalCltvDelta,
Value: amt,
PaymentPreimage: preimage,
Features: features,
},
}
// Insert invoice into database. Ignore duplicates, because this
// may be a replay.
_, err = i.AddInvoice(invoice, hash)
if err != nil && err != channeldb.ErrDuplicateInvoice {
return err
}
return nil
}
// NotifyExitHopHtlc attempts to mark an invoice as settled. The return value
// describes how the htlc should be resolved.
//
@ -710,9 +777,6 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload Payload) (*HtlcResolution, error) {
i.Lock()
defer i.Unlock()
mpp := payload.MultiPath()
debugLog := func(s string) {
@ -732,6 +796,23 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
mpp: mpp,
}
// Process key send if present. Do this outside of the lock, because
// AddInvoice obtains its own lock. This is no problem, because the
// operation is idempotent.
if i.cfg.AcceptKeySend {
err := i.processKeySend(updateCtx, rHash)
if err != nil {
debugLog(fmt.Sprintf("key send error: %v", err))
return NewFailureResolution(
circuitKey, currentHeight, ResultKeySendError,
), nil
}
}
i.Lock()
defer i.Unlock()
// We'll attempt to settle an invoice matching this rHash on disk (if
// one exists). The callback will update the invoice state and/or htlcs.
var (

View File

@ -598,6 +598,110 @@ func TestUnknownInvoice(t *testing.T) {
}
}
// TestKeySend tests receiving a spontaneous payment with and without key send
// enabled.
func TestKeySend(t *testing.T) {
t.Run("enabled", func(t *testing.T) {
testKeySend(t, true)
})
t.Run("disabled", func(t *testing.T) {
testKeySend(t, false)
})
}
// testKeySend is the inner test function that tests key send for a particular
// enabled state on the receiver end.
func testKeySend(t *testing.T, keySendEnabled bool) {
defer timeout()()
ctx := newTestContext(t)
defer ctx.cleanup()
ctx.registry.cfg.AcceptKeySend = keySendEnabled
allSubscriptions := ctx.registry.SubscribeNotifications(0, 0)
defer allSubscriptions.Cancel()
hodlChan := make(chan interface{}, 1)
amt := lnwire.MilliSatoshi(1000)
expiry := uint32(testCurrentHeight + 20)
// Create key for key send.
preimage := lntypes.Preimage{1, 2, 3}
hash := preimage.Hash()
// Try to settle invoice with an invalid key send htlc.
invalidKeySendPayload := &mockPayload{
customRecords: map[uint64][]byte{
record.KeySendType: {1, 2, 3},
},
}
resolution, err := ctx.registry.NotifyExitHopHtlc(
hash, amt, expiry,
testCurrentHeight, getCircuitKey(10), hodlChan,
invalidKeySendPayload,
)
if err != nil {
t.Fatal(err)
}
// Expect a cancel resolution with the correct outcome.
if resolution.Preimage != nil {
t.Fatal("expected cancel resolution")
}
switch {
case !keySendEnabled && resolution.Outcome != ResultInvoiceNotFound:
t.Fatal("expected invoice not found outcome")
case keySendEnabled && resolution.Outcome != ResultKeySendError:
t.Fatal("expected key send error")
}
// Try to settle invoice with a valid key send htlc.
keySendPayload := &mockPayload{
customRecords: map[uint64][]byte{
record.KeySendType: preimage[:],
},
}
resolution, err = ctx.registry.NotifyExitHopHtlc(
hash, amt, expiry,
testCurrentHeight, getCircuitKey(10), hodlChan, keySendPayload,
)
if err != nil {
t.Fatal(err)
}
// Expect a cancel resolution if key send is disabled.
if !keySendEnabled {
if resolution.Outcome != ResultInvoiceNotFound {
t.Fatal("expected key send payment not to be accepted")
}
return
}
// Otherwise we expect no error and a settle resolution for the htlc.
if resolution.Preimage == nil || *resolution.Preimage != preimage {
t.Fatal("expected valid settle event")
}
// We expect a new invoice notification to be sent out.
newInvoice := <-allSubscriptions.NewInvoices
if newInvoice.State != channeldb.ContractOpen {
t.Fatalf("expected state ContractOpen, but got %v",
newInvoice.State)
}
// We expect a settled notification to be sent out.
settledInvoice := <-allSubscriptions.SettledInvoices
if settledInvoice.State != channeldb.ContractSettled {
t.Fatalf("expected state ContractOpen, but got %v",
settledInvoice.State)
}
}
// TestMppPayment tests settling of an invoice with multiple partial payments.
// It covers the case where there is a mpp timeout before the whole invoice is
// paid and the case where the invoice is settled in time.
@ -770,7 +874,7 @@ func TestInvoiceExpiryWithRegistry(t *testing.T) {
testClock.SetTime(testTime.Add(24 * time.Hour))
// Give some time to the watcher to cancel everything.
time.Sleep(testTimeout)
time.Sleep(500 * time.Millisecond)
registry.Stop()
// Create the expected cancellation set before the final check.

View File

@ -21,7 +21,8 @@ import (
)
type mockPayload struct {
mpp *record.MPP
mpp *record.MPP
customRecords record.CustomSet
}
func (p *mockPayload) MultiPath() *record.MPP {
@ -29,7 +30,13 @@ func (p *mockPayload) MultiPath() *record.MPP {
}
func (p *mockPayload) CustomRecords() record.CustomSet {
return make(record.CustomSet)
// This function should always return a map instance, but for mock
// configuration we do accept nil.
if p.customRecords == nil {
return make(record.CustomSet)
}
return p.customRecords
}
var (

View File

@ -85,6 +85,10 @@ const (
// ResultInvoiceNotFound is returned when an attempt is made to pay an
// invoice that is unknown to us.
ResultInvoiceNotFound
// ResultKeySendError is returned when we receive invalid key send
// parameters.
ResultKeySendError
)
// String returns a human-readable representation of the invoice update result.
@ -145,6 +149,9 @@ func (u ResolutionResult) String() string {
case ResultHtlcSetOverpayment:
return "mpp is overpaying set total"
case ResultKeySendError:
return "invalid key send parameters"
default:
return "unknown"
}

View File

@ -2,6 +2,7 @@ package invoicesrpc
import (
"encoding/hex"
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
@ -11,15 +12,42 @@ import (
"github.com/lightningnetwork/lnd/zpay32"
)
// decodePayReq decodes the invoice payment request if present. This is needed,
// because not all information is stored in dedicated invoice fields. If there
// is no payment request present, a dummy request will be returned. This can
// happen with just-in-time inserted key send invoices.
func decodePayReq(invoice *channeldb.Invoice,
activeNetParams *chaincfg.Params) (*zpay32.Invoice, error) {
paymentRequest := string(invoice.PaymentRequest)
if paymentRequest == "" {
preimage := invoice.Terms.PaymentPreimage
if preimage == channeldb.UnknownPreimage {
return nil, errors.New("cannot reconstruct pay req")
}
hash := [32]byte(preimage.Hash())
return &zpay32.Invoice{
PaymentHash: &hash,
}, nil
}
var err error
decoded, err := zpay32.Decode(paymentRequest, activeNetParams)
if err != nil {
return nil, fmt.Errorf("unable to decode payment "+
"request: %v", err)
}
return decoded, nil
}
// CreateRPCInvoice creates an *lnrpc.Invoice from the *channeldb.Invoice.
func CreateRPCInvoice(invoice *channeldb.Invoice,
activeNetParams *chaincfg.Params) (*lnrpc.Invoice, error) {
paymentRequest := string(invoice.PaymentRequest)
decoded, err := zpay32.Decode(paymentRequest, activeNetParams)
decoded, err := decodePayReq(invoice, activeNetParams)
if err != nil {
return nil, fmt.Errorf("unable to decode payment request: %v",
err)
return nil, err
}
var descHash []byte
@ -103,7 +131,7 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
CreationDate: invoice.CreationDate.Unix(),
SettleDate: settleDate,
Settled: isSettled,
PaymentRequest: paymentRequest,
PaymentRequest: string(invoice.PaymentRequest),
DescriptionHash: descHash,
Expiry: int64(invoice.Terms.Expiry.Seconds()),
CltvExpiry: uint64(invoice.Terms.FinalCltvDelta),
@ -118,6 +146,7 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
State: state,
Htlcs: rpcHtlcs,
Features: CreateRPCFeatures(invoice.Terms.Features),
IsKeySend: len(invoice.PaymentRequest) == 0,
}
if preimage != channeldb.UnknownPreimage {

File diff suppressed because it is too large Load Diff

View File

@ -2545,6 +2545,12 @@ message Invoice {
/// List of features advertised on the invoice.
map<uint32, Feature> features = 24 [json_name = "features"];
/**
Indicates if this invoice was a spontaneous payment that arrived via keysend
[EXPERIMENTAL].
*/
bool is_key_send = 25 [json_name = "is_key_send"];
}
enum InvoiceHTLCState {

View File

@ -2896,6 +2896,11 @@
"$ref": "#/definitions/lnrpcFeature"
},
"description": "/ List of features advertised on the invoice."
},
"is_key_send": {
"type": "boolean",
"format": "boolean",
"description": "*\nIndicates if this invoice was a spontaneous payment that arrived via keysend\n[EXPERIMENTAL]."
}
}
},

View File

@ -0,0 +1,177 @@
// +build rpctest
package itest
import (
"bytes"
"context"
"time"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/record"
)
func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// Open a channel with 100k satoshis between Alice and Bob with Alice being
// the sole funder of the channel.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanAmt := btcutil.Amount(100000)
chanPoint := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Now that the channel is open, create an invoice for Bob which
// expects a payment of 1000 satoshis from Alice paid via a particular
// preimage.
const paymentAmt = 1000
preimage := bytes.Repeat([]byte("A"), 32)
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
// Wait for Alice to recognize and advertise the new channel generated
// above.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("alice didn't advertise channel before "+
"timeout: %v", err)
}
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("bob didn't advertise channel before "+
"timeout: %v", err)
}
// With the invoice for Bob added, send a payment towards Alice paying
// to the above generated invoice.
sendReq := &lnrpc.SendRequest{
PaymentRequest: invoiceResp.PaymentRequest,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := net.Alice.SendPaymentSync(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
// Ensure we obtain the proper preimage in the response.
if resp.PaymentError != "" {
t.Fatalf("error when attempting recv: %v", resp.PaymentError)
} else if !bytes.Equal(preimage, resp.PaymentPreimage) {
t.Fatalf("preimage mismatch: expected %v, got %v", preimage,
resp.GetPaymentPreimage())
}
// Bob's invoice should now be found and marked as settled.
payHash := &lnrpc.PaymentHash{
RHash: invoiceResp.RHash,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
dbInvoice, err := net.Bob.LookupInvoice(ctxt, payHash)
if err != nil {
t.Fatalf("unable to lookup invoice: %v", err)
}
if !dbInvoice.Settled {
t.Fatalf("bob's invoice should be marked as settled: %v",
spew.Sdump(dbInvoice))
}
// With the payment completed all balance related stats should be
// properly updated.
err = wait.NoError(
assertAmountSent(paymentAmt, net.Alice, net.Bob),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
// Create another invoice for Bob, this time leaving off the preimage
// to one will be randomly generated. We'll test the proper
// encoding/decoding of the zpay32 payment requests.
invoice = &lnrpc.Invoice{
Memo: "test3",
Value: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
// Next send another payment, but this time using a zpay32 encoded
// invoice rather than manually specifying the payment details.
sendReq = &lnrpc.SendRequest{
PaymentRequest: invoiceResp.PaymentRequest,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err = net.Alice.SendPaymentSync(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
if resp.PaymentError != "" {
t.Fatalf("error when attempting recv: %v", resp.PaymentError)
}
// The second payment should also have succeeded, with the balances
// being update accordingly.
err = wait.NoError(
assertAmountSent(2*paymentAmt, net.Alice, net.Bob),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
// Next send a key send payment.
keySendPreimage := lntypes.Preimage{3, 4, 5, 11}
keySendHash := keySendPreimage.Hash()
sendReq = &lnrpc.SendRequest{
Dest: net.Bob.PubKey[:],
Amt: paymentAmt,
FinalCltvDelta: 40,
PaymentHash: keySendHash[:],
DestCustomRecords: map[uint64][]byte{
record.KeySendType: keySendPreimage[:],
},
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err = net.Alice.SendPaymentSync(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
if resp.PaymentError != "" {
t.Fatalf("error when attempting recv: %v", resp.PaymentError)
}
// The key send payment should also have succeeded, with the balances
// being update accordingly.
err = wait.NoError(
assertAmountSent(3*paymentAmt, net.Alice, net.Bob),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
}

View File

@ -3910,134 +3910,6 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {
cleanupForceClose(t, net, carol, chanPoint)
}
func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// Open a channel with 100k satoshis between Alice and Bob with Alice being
// the sole funder of the channel.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanAmt := btcutil.Amount(100000)
chanPoint := openChannelAndAssert(
ctxt, t, net, net.Alice, net.Bob,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Now that the channel is open, create an invoice for Bob which
// expects a payment of 1000 satoshis from Alice paid via a particular
// preimage.
const paymentAmt = 1000
preimage := bytes.Repeat([]byte("A"), 32)
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
// Wait for Alice to recognize and advertise the new channel generated
// above.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("alice didn't advertise channel before "+
"timeout: %v", err)
}
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("bob didn't advertise channel before "+
"timeout: %v", err)
}
// With the invoice for Bob added, send a payment towards Alice paying
// to the above generated invoice.
sendReq := &lnrpc.SendRequest{
PaymentRequest: invoiceResp.PaymentRequest,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := net.Alice.SendPaymentSync(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
// Ensure we obtain the proper preimage in the response.
if resp.PaymentError != "" {
t.Fatalf("error when attempting recv: %v", resp.PaymentError)
} else if !bytes.Equal(preimage, resp.PaymentPreimage) {
t.Fatalf("preimage mismatch: expected %v, got %v", preimage,
resp.GetPaymentPreimage())
}
// Bob's invoice should now be found and marked as settled.
payHash := &lnrpc.PaymentHash{
RHash: invoiceResp.RHash,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
dbInvoice, err := net.Bob.LookupInvoice(ctxt, payHash)
if err != nil {
t.Fatalf("unable to lookup invoice: %v", err)
}
if !dbInvoice.Settled {
t.Fatalf("bob's invoice should be marked as settled: %v",
spew.Sdump(dbInvoice))
}
// With the payment completed all balance related stats should be
// properly updated.
err = wait.NoError(
assertAmountSent(paymentAmt, net.Alice, net.Bob),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
// Create another invoice for Bob, this time leaving off the preimage
// to one will be randomly generated. We'll test the proper
// encoding/decoding of the zpay32 payment requests.
invoice = &lnrpc.Invoice{
Memo: "test3",
Value: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
// Next send another payment, but this time using a zpay32 encoded
// invoice rather than manually specifying the payment details.
sendReq = &lnrpc.SendRequest{
PaymentRequest: invoiceResp.PaymentRequest,
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err = net.Alice.SendPaymentSync(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
if resp.PaymentError != "" {
t.Fatalf("error when attempting recv: %v", resp.PaymentError)
}
// The second payment should also have succeeded, with the balances
// being update accordingly.
err = wait.NoError(
assertAmountSent(2*paymentAmt, net.Alice, net.Bob),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
}
func testListPayments(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()

View File

@ -152,6 +152,8 @@ type nodeConfig struct {
RPCPort int
RESTPort int
ProfilePort int
AcceptKeySend bool
}
func (cfg nodeConfig) P2PAddr() string {
@ -225,6 +227,10 @@ func (cfg nodeConfig) genArgs() []string {
args = append(args, cfg.ExtraArgs...)
}
if cfg.AcceptKeySend {
args = append(args, "--accept-key-send")
}
return args
}
@ -304,6 +310,11 @@ func newNode(cfg nodeConfig) (*HarnessNode, error) {
cfg.P2PPort, cfg.RPCPort, cfg.RESTPort, cfg.ProfilePort = generateListeningPorts()
// Run all tests with accept key send. The key send code is very
// isolated and it is highly unlikely that it would affect regular
// itests when enabled.
cfg.AcceptKeySend = true
numActiveNodesMtx.Lock()
nodeNum := numActiveNodes
numActiveNodes++

6
record/experimental.go Normal file
View File

@ -0,0 +1,6 @@
package record
const (
// KeySendType is the custom record identifier for key send preimages.
KeySendType uint64 = 5482373484
)

View File

@ -384,6 +384,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
FinalCltvRejectDelta: defaultFinalCltvRejectDelta,
HtlcHoldDuration: invoices.DefaultHtlcHoldDuration,
Clock: clock.NewDefaultClock(),
AcceptKeySend: cfg.AcceptKeySend,
}
s := &server{