Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bolt 11 payment addr + single shot mpp for SendPayment #3679

Merged
merged 19 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9025a30
lnwire/features: add EmptyFeatureVector constructor
cfromknecht Dec 19, 2019
80802d8
channeldb/graph: always populate LightningNode features
cfromknecht Dec 19, 2019
6188103
routing/pathfind_test: add asymmetric chan consructor
cfromknecht Dec 19, 2019
bd66c0d
routing/pathfind_test: allow custom node features
cfromknecht Dec 19, 2019
cfa3fe2
routing/pathfind: fix TLV fallback for unadvertised hops
cfromknecht Dec 19, 2019
acb7b83
routing/pathfind: validate final hop feature dependencies
cfromknecht Dec 19, 2019
990d55d
routing/pathfind: ensure final hop supports payment addrs
cfromknecht Dec 19, 2019
71e05e0
routing/pathfind: set final hop features used in pathfinding
cfromknecht Dec 19, 2019
495ae8c
routing: consolidate final hop params for newRoute
cfromknecht Dec 19, 2019
b97adf7
routing/pathfind: consolidate final vs non-final hop processing
cfromknecht Dec 19, 2019
e3a9603
routing/pathfind: simplify cltv calculation
cfromknecht Dec 19, 2019
cddb71e
routing/pathfind: evaluate TLV support sooner
cfromknecht Dec 19, 2019
7965cb0
routing/pathfind: consolidate population of custom records
cfromknecht Dec 19, 2019
0993256
routing/pathfind: set single-shot MPP if payment addr is known
cfromknecht Dec 19, 2019
f868bc1
rpcserver+routerrpc: thread features + payment addr to SendPayment
cfromknecht Dec 19, 2019
396a978
lntest+wait: replace sleeps in assertAmountSent
cfromknecht Dec 19, 2019
457fda6
routerrpc: add dest_features to SendPaymentRequest
cfromknecht Dec 19, 2019
35dd5f3
routerrpc: parse dest_features bits for manual SendPayment
cfromknecht Dec 19, 2019
1dbeb34
feature/default_sets: expose optional mpp and pay_addr features IN9
cfromknecht Dec 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions channeldb/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -3461,6 +3461,10 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
err error
)

// Always populate a feature vector, even if we don't have a node
// announcement and short circuit below.
node.Features = lnwire.EmptyFeatureVector()
cfromknecht marked this conversation as resolved.
Show resolved Hide resolved

if _, err := r.Read(scratch[:]); err != nil {
return LightningNode{}, err
}
Expand Down Expand Up @@ -3506,12 +3510,10 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
return LightningNode{}, err
}

fv := lnwire.NewFeatureVector(nil, lnwire.Features)
err = fv.Decode(r)
err = node.Features.Decode(r)
if err != nil {
return LightningNode{}, err
}
node.Features = fv

if _, err := r.Read(scratch[:2]); err != nil {
return LightningNode{}, err
Expand Down
10 changes: 10 additions & 0 deletions feature/default_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ var defaultSetDesc = setDesc{
SetInit: {}, // I
SetNodeAnn: {}, // N
},
lnwire.PaymentAddrOptional: {
SetInit: {}, // I
SetNodeAnn: {}, // N
SetInvoice: {}, // 9
},
lnwire.MPPOptional: {
SetInit: {}, // I
SetNodeAnn: {}, // N
SetInvoice: {}, // 9
},
}
7 changes: 6 additions & 1 deletion feature/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ type ErrMissingFeatureDep struct {
dep lnwire.FeatureBit
}

// NewErrMissingFeatureDep creates a new ErrMissingFeatureDep error.
func NewErrMissingFeatureDep(dep lnwire.FeatureBit) ErrMissingFeatureDep {
return ErrMissingFeatureDep{dep: dep}
}
cfromknecht marked this conversation as resolved.
Show resolved Hide resolved

// Error returns a human-readable description of the missing dep error.
func (e ErrMissingFeatureDep) Error() string {
return fmt.Sprintf("missing feature dependency: %v", e.dep)
Expand Down Expand Up @@ -74,7 +79,7 @@ func validateDeps(features featureSet, supported supportedFeatures) error {
// vector is invalid.
checked, ok := supported[bit]
if !ok {
return ErrMissingFeatureDep{bit}
return NewErrMissingFeatureDep(bit)
}

// Alternatively, if we know that this depdendency is valid, we
Expand Down
283 changes: 148 additions & 135 deletions lnrpc/routerrpc/router.pb.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions lnrpc/routerrpc/router.proto
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ message SendPaymentRequest {

/// If set, circular payments to self are permitted.
bool allow_self_payment = 15;

/**
Features assumed to be supported by the final node. All transitive feature
depdencies must also be set properly. For a given feature bit pair, either
optional or remote may be set, but not both.
cfromknecht marked this conversation as resolved.
Show resolved Hide resolved
*/
repeated lnrpc.FeatureBit dest_features = 16;
}

message TrackPaymentRequest {
Expand Down
25 changes: 25 additions & 0 deletions lnrpc/routerrpc/router_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.RouteHints = append(
payIntent.RouteHints, payReq.RouteHints...,
)
payIntent.DestFeatures = payReq.Features
payIntent.PaymentAddr = payReq.PaymentAddr
} else {
// Otherwise, If the payment request field was not specified
cfromknecht marked this conversation as resolved.
Show resolved Hide resolved
// (and a custom route wasn't specified), construct the payment
Expand Down Expand Up @@ -631,6 +633,14 @@ func (r *RouterBackend) extractIntentFromSendRequest(

// Payment hash.
copy(payIntent.PaymentHash[:], rpcPayReq.PaymentHash)

// Parse destination feature bits.
features, err := UnmarshalFeatures(rpcPayReq.DestFeatures)
if err != nil {
return nil, err
}

payIntent.DestFeatures = features
}

// Currently, within the bootstrap phase of the network, we limit the
Expand Down Expand Up @@ -697,6 +707,21 @@ func unmarshallHopHint(rpcHint *lnrpc.HopHint) (zpay32.HopHint, error) {
}, nil
}

// UnmarshalFeatures converts a list of uint32's into a valid feature vector.
// This method checks that feature bit pairs aren't assigned toegether, and
// validates transitive depdencies.
func UnmarshalFeatures(rpcFeatures []lnrpc.FeatureBit) (*lnwire.FeatureVector, error) {
raw := lnwire.NewRawFeatureVector()
for _, bit := range rpcFeatures {
err := raw.SafeSet(lnwire.FeatureBit(bit))
if err != nil {
return nil, err
}
}

return lnwire.NewFeatureVector(raw, lnwire.Features), nil
}

// ValidatePayReqExpiry checks if the passed payment request has expired. In
// the case it has expired, an error will be returned.
func ValidatePayReqExpiry(payReq *zpay32.Invoice) error {
Expand Down
1,264 changes: 681 additions & 583 deletions lnrpc/rpc.pb.go

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions lnrpc/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,13 @@ message SendRequest {

/// If set, circular payments to self are permitted.
bool allow_self_payment = 14;

/**
Features assumed to be supported by the final node. All transitive feature
depdencies must also be set properly. For a given feature bit pair, either
optional or remote may be set, but not both.
*/
repeated FeatureBit dest_features = 15;
}

message SendResponse {
Expand Down Expand Up @@ -2696,6 +2703,26 @@ message PayReq {
map<uint32, Feature> features = 13 [json_name = "features"];
}

enum FeatureBit {
DATALOSS_PROTECT_REQ = 0;
DATALOSS_PROTECT_OPT = 1;
INITIAL_ROUING_SYNC = 3;
UPFRONT_SHUTDOWN_SCRIPT_REQ = 4;
UPFRONT_SHUTDOWN_SCRIPT_OPT = 5;
GOSSIP_QUERIES_REQ = 6;
GOSSIP_QUERIES_OPT = 7;
TLV_ONION_REQ = 8;
TLV_ONION_OPT = 9;
EXT_GOSSIP_QUERIES_REQ = 10;
EXT_GOSSIP_QUERIES_OPT = 11;
STATIC_REMOTE_KEY_REQ = 12;
STATIC_REMOTE_KEY_OPT = 13;
PAYMENT_ADDR_REQ = 14;
PAYMENT_ADDR_OPT = 15;
MPP_REQ = 16;
MPP_OPT = 17;
}
joostjager marked this conversation as resolved.
Show resolved Hide resolved

message Feature {
string name = 2 [json_name = "name"];
bool is_required = 3 [json_name = "is_required"];
Expand Down
30 changes: 30 additions & 0 deletions lnrpc/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2271,6 +2271,29 @@
}
}
},
"lnrpcFeatureBit": {
"type": "string",
"enum": [
"DATALOSS_PROTECT_REQ",
"DATALOSS_PROTECT_OPT",
"INITIAL_ROUING_SYNC",
"UPFRONT_SHUTDOWN_SCRIPT_REQ",
"UPFRONT_SHUTDOWN_SCRIPT_OPT",
"GOSSIP_QUERIES_REQ",
"GOSSIP_QUERIES_OPT",
"TLV_ONION_REQ",
"TLV_ONION_OPT",
"EXT_GOSSIP_QUERIES_REQ",
"EXT_GOSSIP_QUERIES_OPT",
"STATIC_REMOTE_KEY_REQ",
"STATIC_REMOTE_KEY_OPT",
"PAYMENT_ADDR_REQ",
"PAYMENT_ADDR_OPT",
"MPP_REQ",
"MPP_OPT"
],
"default": "DATALOSS_PROTECT_REQ"
},
"lnrpcFeeLimit": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -3845,6 +3868,13 @@
"type": "boolean",
"format": "boolean",
"description": "/ If set, circular payments to self are permitted."
},
"dest_features": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcFeatureBit"
},
"description": "*\nFeatures assumed to be supported by the final node. All transitive feature\ndepdencies must also be set properly. For a given feature bit pair, either\noptional or remote may be set, but not both."
}
}
},
Expand Down
129 changes: 68 additions & 61 deletions lntest/itest/lnd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3704,6 +3704,48 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
}
}

// assertAmountSent generates a closure which queries listchannels for sndr and
// rcvr, and asserts that sndr sent amt satoshis, and that rcvr received amt
// satoshis.
//
// NOTE: This method assumes that each node only has one channel, and it is the
// channel used to send the payment.
func assertAmountSent(amt btcutil.Amount, sndr, rcvr *lntest.HarnessNode) func() error {
return func() error {
// Both channels should also have properly accounted from the
// amount that has been sent/received over the channel.
listReq := &lnrpc.ListChannelsRequest{}
ctxb := context.Background()
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
sndrListChannels, err := sndr.ListChannels(ctxt, listReq)
if err != nil {
return fmt.Errorf("unable to query for %s's channel "+
"list: %v", sndr.Name(), err)
}
sndrSatoshisSent := sndrListChannels.Channels[0].TotalSatoshisSent
if sndrSatoshisSent != int64(amt) {
return fmt.Errorf("%s's satoshis sent is incorrect "+
"got %v, expected %v", sndr.Name(),
sndrSatoshisSent, amt)
}

ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
rcvrListChannels, err := rcvr.ListChannels(ctxt, listReq)
if err != nil {
return fmt.Errorf("unable to query for %s's channel "+
"list: %v", rcvr.Name(), err)
}
rcvrSatoshisReceived := rcvrListChannels.Channels[0].TotalSatoshisReceived
if rcvrSatoshisReceived != int64(amt) {
return fmt.Errorf("%s's satoshis received is "+
"incorrect got %v, expected %v", rcvr.Name(),
rcvrSatoshisReceived, amt)
}

return nil
}
}

// testSphinxReplayPersistence verifies that replayed onion packets are rejected
// by a remote peer after a restart. We use a combination of unsafe
// configuration arguments to force Carol to replay the same sphinx packet after
Expand Down Expand Up @@ -3753,33 +3795,6 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {
},
)

assertAmountSent := func(amt btcutil.Amount) {
// Both channels should also have properly accounted from the
// amount that has been sent/received over the channel.
listReq := &lnrpc.ListChannelsRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
carolListChannels, err := carol.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to query for alice's channel list: %v", err)
}
carolSatoshisSent := carolListChannels.Channels[0].TotalSatoshisSent
if carolSatoshisSent != int64(amt) {
t.Fatalf("Carol's satoshis sent is incorrect got %v, expected %v",
carolSatoshisSent, amt)
}

ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
daveListChannels, err := dave.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to query for Dave's channel list: %v", err)
}
daveSatoshisReceived := daveListChannels.Channels[0].TotalSatoshisReceived
if daveSatoshisReceived != int64(amt) {
t.Fatalf("Dave's satoshis received is incorrect got %v, expected %v",
daveSatoshisReceived, amt)
}
}

// Now that the channel is open, create an invoice for Dave which
// expects a payment of 1000 satoshis from Carol paid via a particular
// preimage.
Expand Down Expand Up @@ -3844,8 +3859,12 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {

// With the payment sent but hedl, all balance related stats should not
// have changed.
time.Sleep(time.Millisecond * 200)
assertAmountSent(0)
err = wait.InvariantNoError(
assertAmountSent(0, carol, dave), 3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}

// With the first payment sent, restart dave to make sure he is
// persisting the information required to detect replayed sphinx
Expand Down Expand Up @@ -3874,7 +3893,12 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {

// Since the payment failed, the balance should still be left
// unaltered.
assertAmountSent(0)
err = wait.InvariantNoError(
assertAmountSent(0, carol, dave), 3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}

ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, carol, chanPoint, true)
Expand All @@ -3897,33 +3921,6 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {
},
)

assertAmountSent := func(amt btcutil.Amount) {
// Both channels should also have properly accounted from the
// amount that has been sent/received over the channel.
listReq := &lnrpc.ListChannelsRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
aliceListChannels, err := net.Alice.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to query for alice's channel list: %v", err)
}
aliceSatoshisSent := aliceListChannels.Channels[0].TotalSatoshisSent
if aliceSatoshisSent != int64(amt) {
t.Fatalf("Alice's satoshis sent is incorrect got %v, expected %v",
aliceSatoshisSent, amt)
}

ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
bobListChannels, err := net.Bob.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to query for bob's channel list: %v", err)
}
bobSatoshisReceived := bobListChannels.Channels[0].TotalSatoshisReceived
if bobSatoshisReceived != int64(amt) {
t.Fatalf("Bob's satoshis received is incorrect got %v, expected %v",
bobSatoshisReceived, amt)
}
}

// 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.
Expand Down Expand Up @@ -3989,8 +3986,13 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {

// With the payment completed all balance related stats should be
// properly updated.
time.Sleep(time.Millisecond * 200)
assertAmountSent(paymentAmt)
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
Expand Down Expand Up @@ -4021,8 +4023,13 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {

// The second payment should also have succeeded, with the balances
// being update accordingly.
time.Sleep(time.Millisecond * 200)
assertAmountSent(paymentAmt * 2)
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)
Expand Down
Loading