mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-04-07 03:28:05 +02:00
Merge pull request #3679 from cfromknecht/bolt-11-payment-addr
bolt 11 payment addr + single shot mpp for SendPayment
This commit is contained in:
commit
8ce9e15f4a
@ -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()
|
||||
|
||||
if _, err := r.Read(scratch[:]); err != nil {
|
||||
return LightningNode{}, err
|
||||
}
|
||||
@ -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
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -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
|
||||
|
@ -254,10 +254,15 @@ type SendPaymentRequest struct {
|
||||
//must be encoded as base64.
|
||||
DestCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
/// If set, circular payments to self are permitted.
|
||||
AllowSelfPayment bool `protobuf:"varint,15,opt,name=allow_self_payment,json=allowSelfPayment,proto3" json:"allow_self_payment,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
AllowSelfPayment bool `protobuf:"varint,15,opt,name=allow_self_payment,json=allowSelfPayment,proto3" json:"allow_self_payment,omitempty"`
|
||||
//*
|
||||
//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.
|
||||
DestFeatures []lnrpc.FeatureBit `protobuf:"varint,16,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) Reset() { *m = SendPaymentRequest{} }
|
||||
@ -390,6 +395,13 @@ func (m *SendPaymentRequest) GetAllowSelfPayment() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) GetDestFeatures() []lnrpc.FeatureBit {
|
||||
if m != nil {
|
||||
return m.DestFeatures
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TrackPaymentRequest struct {
|
||||
/// The hash of the payment to look up.
|
||||
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
|
||||
@ -1512,137 +1524,138 @@ func init() {
|
||||
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
|
||||
|
||||
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
||||
// 2065 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xdd, 0x72, 0xdb, 0xc6,
|
||||
0x15, 0x0e, 0xc4, 0xff, 0x43, 0x52, 0x84, 0x56, 0x8a, 0x4c, 0x53, 0x56, 0xa2, 0x20, 0xae, 0xc3,
|
||||
0xf1, 0x38, 0x92, 0xab, 0x36, 0x19, 0x4f, 0x2f, 0xda, 0xa1, 0x48, 0x30, 0x82, 0x4c, 0x82, 0xf2,
|
||||
0x92, 0x74, 0xe2, 0xe6, 0x62, 0x67, 0x45, 0xae, 0x44, 0x8c, 0x40, 0x80, 0x01, 0x96, 0x8e, 0xf5,
|
||||
0x0e, 0x7d, 0x8f, 0xf6, 0xa2, 0xed, 0x4d, 0x9f, 0xa0, 0x2f, 0xd3, 0xde, 0x77, 0xa6, 0xf7, 0x9d,
|
||||
0xdd, 0x05, 0x48, 0x90, 0xa2, 0x9c, 0x5e, 0x89, 0xfb, 0x9d, 0x6f, 0xcf, 0x59, 0xec, 0x39, 0xfb,
|
||||
0xed, 0x59, 0xc1, 0x7e, 0xe0, 0xcf, 0x39, 0x0b, 0x82, 0xd9, 0xe8, 0x44, 0xfd, 0x3a, 0x9e, 0x05,
|
||||
0x3e, 0xf7, 0x51, 0x61, 0x81, 0xd7, 0x0a, 0xc1, 0x6c, 0xa4, 0x50, 0xe3, 0x9f, 0x19, 0x40, 0x7d,
|
||||
0xe6, 0x8d, 0x2f, 0xe9, 0xdd, 0x94, 0x79, 0x1c, 0xb3, 0x9f, 0xe6, 0x2c, 0xe4, 0x08, 0x41, 0x7a,
|
||||
0xcc, 0x42, 0x5e, 0xd5, 0x8e, 0xb4, 0x7a, 0x09, 0xcb, 0xdf, 0x48, 0x87, 0x14, 0x9d, 0xf2, 0xea,
|
||||
0xd6, 0x91, 0x56, 0x4f, 0x61, 0xf1, 0x13, 0x3d, 0x86, 0x3c, 0x9d, 0x72, 0x32, 0x0d, 0x29, 0xaf,
|
||||
0x96, 0x24, 0x9c, 0xa3, 0x53, 0xde, 0x0d, 0x29, 0x47, 0x5f, 0x40, 0x69, 0xa6, 0x5c, 0x92, 0x09,
|
||||
0x0d, 0x27, 0xd5, 0x94, 0x74, 0x54, 0x8c, 0xb0, 0x73, 0x1a, 0x4e, 0x50, 0x1d, 0xf4, 0x6b, 0xc7,
|
||||
0xa3, 0x2e, 0x19, 0xb9, 0xfc, 0x3d, 0x19, 0x33, 0x97, 0xd3, 0x6a, 0xfa, 0x48, 0xab, 0x67, 0xf0,
|
||||
0xb6, 0xc4, 0x9b, 0x2e, 0x7f, 0xdf, 0x12, 0x28, 0xfa, 0x0a, 0x2a, 0xb1, 0xb3, 0x40, 0x2d, 0xb0,
|
||||
0x9a, 0x39, 0xd2, 0xea, 0x05, 0xbc, 0x3d, 0x5b, 0x5d, 0xf6, 0x57, 0x50, 0xe1, 0xce, 0x94, 0xf9,
|
||||
0x73, 0x4e, 0x42, 0x36, 0xf2, 0xbd, 0x71, 0x58, 0xcd, 0x2a, 0x8f, 0x11, 0xdc, 0x57, 0x28, 0x32,
|
||||
0xa0, 0x7c, 0xcd, 0x18, 0x71, 0x9d, 0xa9, 0xc3, 0x89, 0x58, 0x7e, 0x4e, 0x2e, 0xbf, 0x78, 0xcd,
|
||||
0x58, 0x47, 0x60, 0x7d, 0xca, 0xd1, 0x53, 0xd8, 0x5e, 0x72, 0xe4, 0x37, 0x96, 0x25, 0xa9, 0x14,
|
||||
0x93, 0xe4, 0x87, 0xbe, 0x00, 0xdd, 0x9f, 0xf3, 0x1b, 0xdf, 0xf1, 0x6e, 0xc8, 0x68, 0x42, 0x3d,
|
||||
0xe2, 0x8c, 0xab, 0xf9, 0x23, 0xad, 0x9e, 0x3e, 0xdb, 0x7a, 0xa9, 0xe1, 0xed, 0xd8, 0xd6, 0x9c,
|
||||
0x50, 0xcf, 0x1a, 0xa3, 0x67, 0x50, 0x71, 0x69, 0xc8, 0xc9, 0xc4, 0x9f, 0x91, 0xd9, 0xfc, 0xea,
|
||||
0x96, 0xdd, 0x55, 0xb7, 0xe5, 0xce, 0x94, 0x05, 0x7c, 0xee, 0xcf, 0x2e, 0x25, 0x88, 0x0e, 0x01,
|
||||
0xe4, 0xae, 0xc8, 0xe0, 0xd5, 0x82, 0xfc, 0x86, 0x82, 0x40, 0x64, 0x60, 0x74, 0x0a, 0x45, 0x99,
|
||||
0x4d, 0x32, 0x71, 0x3c, 0x1e, 0x56, 0xe1, 0x28, 0x55, 0x2f, 0x9e, 0xea, 0xc7, 0xae, 0x27, 0x12,
|
||||
0x8b, 0x85, 0xe5, 0xdc, 0xf1, 0x38, 0x4e, 0x92, 0xd0, 0x18, 0x76, 0x45, 0x1a, 0xc9, 0x68, 0x1e,
|
||||
0x72, 0x7f, 0x4a, 0x02, 0x36, 0xf2, 0x83, 0x71, 0x58, 0x2d, 0xca, 0xb9, 0xbf, 0x3d, 0x5e, 0x54,
|
||||
0xc7, 0xf1, 0xfd, 0x72, 0x38, 0x6e, 0xb1, 0x90, 0x37, 0xe5, 0x3c, 0xac, 0xa6, 0x99, 0x1e, 0x0f,
|
||||
0xee, 0xf0, 0xce, 0x78, 0x1d, 0x47, 0x2f, 0x00, 0x51, 0xd7, 0xf5, 0x7f, 0x26, 0x21, 0x73, 0xaf,
|
||||
0x49, 0x94, 0x9e, 0x6a, 0xe5, 0x48, 0xab, 0xe7, 0xb1, 0x2e, 0x2d, 0x7d, 0xe6, 0x5e, 0x47, 0xee,
|
||||
0x6b, 0x2d, 0xd8, 0xdf, 0xec, 0x5a, 0x14, 0x9b, 0xd8, 0x1c, 0x51, 0x7f, 0x69, 0x2c, 0x7e, 0xa2,
|
||||
0x3d, 0xc8, 0xbc, 0xa7, 0xee, 0x9c, 0xc9, 0x02, 0x2c, 0x61, 0x35, 0xf8, 0xdd, 0xd6, 0x2b, 0xcd,
|
||||
0x78, 0x05, 0xbb, 0x83, 0x80, 0x8e, 0x6e, 0xd7, 0x6a, 0x78, 0xbd, 0x04, 0xb5, 0x7b, 0x25, 0x68,
|
||||
0xfc, 0x45, 0x83, 0x72, 0x34, 0xab, 0xcf, 0x29, 0x9f, 0x87, 0xe8, 0x6b, 0xc8, 0x84, 0x9c, 0x72,
|
||||
0x26, 0xd9, 0xdb, 0xa7, 0x8f, 0x12, 0xfb, 0x92, 0x20, 0x32, 0xac, 0x58, 0xa8, 0x06, 0xf9, 0x59,
|
||||
0xc0, 0x9c, 0x29, 0xbd, 0x89, 0xd7, 0xb5, 0x18, 0x23, 0x03, 0x32, 0x72, 0xb2, 0xac, 0xfd, 0xe2,
|
||||
0x69, 0x29, 0x99, 0x1e, 0xac, 0x4c, 0xa8, 0x0e, 0x99, 0x09, 0x77, 0x47, 0x61, 0x35, 0x2d, 0xd3,
|
||||
0x80, 0x22, 0xce, 0xf9, 0xa0, 0xd3, 0x6c, 0x70, 0xce, 0xa6, 0x33, 0x8e, 0x15, 0xc1, 0xf8, 0x3d,
|
||||
0x54, 0xe4, 0xcc, 0x36, 0x63, 0x1f, 0x3b, 0xa4, 0x8f, 0x40, 0x1c, 0x41, 0x59, 0xd2, 0xea, 0xa0,
|
||||
0x66, 0xe9, 0x54, 0x54, 0xb3, 0x31, 0x06, 0x7d, 0x39, 0x3f, 0x9c, 0xf9, 0x5e, 0x28, 0xa2, 0xeb,
|
||||
0x62, 0x19, 0xa2, 0x74, 0x45, 0xa5, 0xcb, 0x1a, 0xd7, 0xe4, 0xac, 0xed, 0x08, 0x6f, 0x33, 0x26,
|
||||
0xab, 0xfc, 0x99, 0x3a, 0x58, 0xc4, 0xf5, 0x47, 0xb7, 0xe2, 0xa8, 0xd2, 0xbb, 0xc8, 0x7d, 0x59,
|
||||
0xc0, 0x1d, 0x7f, 0x74, 0xdb, 0x12, 0xa0, 0xf1, 0xa3, 0x52, 0x93, 0x81, 0xaf, 0xbe, 0xf2, 0xff,
|
||||
0xce, 0xc4, 0x72, 0xb3, 0xb6, 0x1e, 0xdc, 0x2c, 0x83, 0xc0, 0xee, 0x8a, 0xf3, 0xe8, 0x2b, 0x92,
|
||||
0x39, 0xd0, 0xd6, 0x72, 0xf0, 0x02, 0x72, 0xd7, 0xd4, 0x71, 0xe7, 0x41, 0xec, 0x18, 0x25, 0x12,
|
||||
0xda, 0x56, 0x16, 0x1c, 0x53, 0x8c, 0xff, 0xe6, 0x20, 0x17, 0x81, 0xe8, 0x14, 0xd2, 0x23, 0x7f,
|
||||
0x1c, 0xd7, 0xc1, 0x67, 0xf7, 0xa7, 0xc5, 0x7f, 0x9b, 0xfe, 0x98, 0x61, 0xc9, 0x45, 0x7f, 0x80,
|
||||
0x6d, 0x21, 0x01, 0x1e, 0x73, 0xc9, 0x7c, 0x36, 0xa6, 0x8b, 0xd4, 0x57, 0x13, 0xb3, 0x9b, 0x8a,
|
||||
0x30, 0x94, 0x76, 0x5c, 0x1e, 0x25, 0x87, 0xe8, 0x00, 0x0a, 0x22, 0xdb, 0x2a, 0x13, 0x69, 0x59,
|
||||
0xfb, 0x79, 0x01, 0xc8, 0x1c, 0x18, 0x50, 0xf6, 0x3d, 0xc7, 0xf7, 0x48, 0x38, 0xa1, 0xe4, 0xf4,
|
||||
0x9b, 0x6f, 0xa5, 0x06, 0x96, 0x70, 0x51, 0x82, 0xfd, 0x09, 0x3d, 0xfd, 0xe6, 0x5b, 0xf4, 0x39,
|
||||
0x14, 0xa5, 0x6e, 0xb0, 0x0f, 0x33, 0x27, 0xb8, 0x93, 0xe2, 0x57, 0xc6, 0x52, 0x4a, 0x4c, 0x89,
|
||||
0x88, 0x53, 0x74, 0xed, 0xd2, 0x9b, 0x50, 0x0a, 0x5e, 0x19, 0xab, 0x01, 0x7a, 0x09, 0x7b, 0xd1,
|
||||
0x1e, 0x90, 0xd0, 0x9f, 0x07, 0x23, 0x46, 0x1c, 0x6f, 0xcc, 0x3e, 0x48, 0x21, 0x2b, 0x63, 0x14,
|
||||
0xd9, 0xfa, 0xd2, 0x64, 0x09, 0x0b, 0xda, 0x87, 0xec, 0x84, 0x39, 0x37, 0x13, 0x25, 0x4e, 0x65,
|
||||
0x1c, 0x8d, 0x8c, 0xbf, 0x66, 0xa0, 0x98, 0xd8, 0x18, 0x54, 0x82, 0x3c, 0x36, 0xfb, 0x26, 0x7e,
|
||||
0x6b, 0xb6, 0xf4, 0x4f, 0x50, 0x1d, 0x9e, 0x5a, 0x76, 0xb3, 0x87, 0xb1, 0xd9, 0x1c, 0x90, 0x1e,
|
||||
0x26, 0x43, 0xfb, 0xb5, 0xdd, 0xfb, 0xde, 0x26, 0x97, 0x8d, 0x77, 0x5d, 0xd3, 0x1e, 0x90, 0x96,
|
||||
0x39, 0x68, 0x58, 0x9d, 0xbe, 0xae, 0xa1, 0x27, 0x50, 0x5d, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b,
|
||||
0xda, 0x03, 0x7d, 0x0b, 0x7d, 0x0e, 0x07, 0x6d, 0xcb, 0x6e, 0x74, 0xc8, 0x92, 0xd3, 0xec, 0x0c,
|
||||
0xde, 0x12, 0xf3, 0x87, 0x4b, 0x0b, 0xbf, 0xd3, 0x53, 0x9b, 0x08, 0xe2, 0x4c, 0xc5, 0x1e, 0xd2,
|
||||
0xe8, 0x31, 0x7c, 0xaa, 0x08, 0x6a, 0x0a, 0x19, 0xf4, 0x7a, 0xa4, 0xdf, 0xeb, 0xd9, 0x7a, 0x06,
|
||||
0xed, 0x40, 0xd9, 0xb2, 0xdf, 0x36, 0x3a, 0x56, 0x8b, 0x60, 0xb3, 0xd1, 0xe9, 0xea, 0x59, 0xb4,
|
||||
0x0b, 0x95, 0x75, 0x5e, 0x4e, 0xb8, 0x88, 0x79, 0x3d, 0xdb, 0xea, 0xd9, 0xe4, 0xad, 0x89, 0xfb,
|
||||
0x56, 0xcf, 0xd6, 0xf3, 0x68, 0x1f, 0xd0, 0xaa, 0xe9, 0xbc, 0xdb, 0x68, 0xea, 0x05, 0xf4, 0x29,
|
||||
0xec, 0xac, 0xe2, 0xaf, 0xcd, 0x77, 0x3a, 0xa0, 0x2a, 0xec, 0xa9, 0x85, 0x91, 0x33, 0xb3, 0xd3,
|
||||
0xfb, 0x9e, 0x74, 0x2d, 0xdb, 0xea, 0x0e, 0xbb, 0x7a, 0x11, 0xed, 0x81, 0xde, 0x36, 0x4d, 0x62,
|
||||
0xd9, 0xfd, 0x61, 0xbb, 0x6d, 0x35, 0x2d, 0xd3, 0x1e, 0xe8, 0x25, 0x15, 0x79, 0xd3, 0x87, 0x97,
|
||||
0xc5, 0x84, 0xe6, 0x79, 0xc3, 0xb6, 0xcd, 0x0e, 0x69, 0x59, 0xfd, 0xc6, 0x59, 0xc7, 0x6c, 0xe9,
|
||||
0xdb, 0xe8, 0x10, 0x1e, 0x0f, 0xcc, 0xee, 0x65, 0x0f, 0x37, 0xf0, 0x3b, 0x12, 0xdb, 0xdb, 0x0d,
|
||||
0xab, 0x33, 0xc4, 0xa6, 0x5e, 0x41, 0x5f, 0xc0, 0x21, 0x36, 0xdf, 0x0c, 0x2d, 0x6c, 0xb6, 0x88,
|
||||
0xdd, 0x6b, 0x99, 0xa4, 0x6d, 0x36, 0x06, 0x43, 0x6c, 0x92, 0xae, 0xd5, 0xef, 0x5b, 0xf6, 0x77,
|
||||
0xba, 0x8e, 0x9e, 0xc2, 0xd1, 0x82, 0xb2, 0x70, 0xb0, 0xc6, 0xda, 0x11, 0xdf, 0x17, 0xa7, 0xd4,
|
||||
0x36, 0x7f, 0x18, 0x90, 0x4b, 0xd3, 0xc4, 0x3a, 0x42, 0x35, 0xd8, 0x5f, 0x86, 0x57, 0x01, 0xa2,
|
||||
0xd8, 0xbb, 0xc2, 0x76, 0x69, 0xe2, 0x6e, 0xc3, 0x16, 0x09, 0x5e, 0xb1, 0xed, 0x89, 0x65, 0x2f,
|
||||
0x6d, 0xeb, 0xcb, 0xfe, 0x14, 0x21, 0xd8, 0x4e, 0x64, 0xa5, 0xdd, 0xc0, 0xfa, 0x3e, 0xda, 0x83,
|
||||
0x4a, 0xbc, 0x82, 0x98, 0xf8, 0xaf, 0x1c, 0x7a, 0x04, 0x68, 0x68, 0x63, 0xb3, 0xd1, 0x12, 0x1b,
|
||||
0xb2, 0x30, 0xfc, 0x3b, 0x77, 0x91, 0xce, 0x6f, 0xe9, 0x29, 0xe3, 0x1f, 0x29, 0x28, 0xaf, 0x9c,
|
||||
0x4b, 0xf4, 0x04, 0x0a, 0xa1, 0x73, 0xe3, 0x51, 0x2e, 0x94, 0x43, 0x89, 0xca, 0x12, 0x90, 0xb7,
|
||||
0xf3, 0x84, 0x3a, 0x9e, 0x52, 0x33, 0xa5, 0xfb, 0x05, 0x89, 0x48, 0x2d, 0x3b, 0x80, 0x5c, 0xdc,
|
||||
0x09, 0xa4, 0x16, 0x9d, 0x40, 0x76, 0xa4, 0x3a, 0x80, 0x27, 0x50, 0x10, 0x92, 0x19, 0x72, 0x3a,
|
||||
0x9d, 0xc9, 0x23, 0x5e, 0xc6, 0x4b, 0x00, 0x7d, 0x09, 0xe5, 0x29, 0x0b, 0x43, 0x7a, 0xc3, 0x88,
|
||||
0x3a, 0xa6, 0x20, 0x19, 0xa5, 0x08, 0x6c, 0xcb, 0xd3, 0xfa, 0x25, 0xc4, 0xb2, 0x11, 0x91, 0x32,
|
||||
0x8a, 0x14, 0x81, 0x8a, 0xb4, 0xae, 0xd8, 0x9c, 0x46, 0x6a, 0x90, 0x54, 0x6c, 0x4e, 0xd1, 0x73,
|
||||
0xd8, 0x51, 0x92, 0xe3, 0x78, 0xce, 0x74, 0x3e, 0x55, 0xd2, 0x93, 0x93, 0xd2, 0x53, 0x91, 0xd2,
|
||||
0xa3, 0x70, 0xa9, 0x40, 0x8f, 0x21, 0x7f, 0x45, 0x43, 0x26, 0x2e, 0x8b, 0x48, 0x1a, 0x72, 0x62,
|
||||
0xdc, 0x66, 0x4c, 0x98, 0xc4, 0x15, 0x12, 0x08, 0xd1, 0x53, 0x8a, 0x90, 0xbb, 0x66, 0x0c, 0x8b,
|
||||
0xbd, 0x5c, 0x44, 0xa0, 0x1f, 0x96, 0x11, 0x8a, 0x89, 0x08, 0x0a, 0x97, 0x11, 0x9e, 0xc3, 0x0e,
|
||||
0xfb, 0xc0, 0x03, 0x4a, 0xfc, 0x19, 0xfd, 0x69, 0xce, 0xc8, 0x98, 0x72, 0x2a, 0x5b, 0xcb, 0x12,
|
||||
0xae, 0x48, 0x43, 0x4f, 0xe2, 0x2d, 0xca, 0xa9, 0xf1, 0x04, 0x6a, 0x98, 0x85, 0x8c, 0x77, 0x9d,
|
||||
0x30, 0x74, 0x7c, 0xaf, 0xe9, 0x7b, 0x3c, 0xf0, 0xdd, 0xe8, 0xce, 0x31, 0x0e, 0xe1, 0x60, 0xa3,
|
||||
0x55, 0x5d, 0x1a, 0x62, 0xf2, 0x9b, 0x39, 0x0b, 0xee, 0x36, 0x4f, 0x7e, 0x03, 0x07, 0x1b, 0xad,
|
||||
0xd1, 0x8d, 0xf3, 0x02, 0x32, 0x33, 0xea, 0x04, 0x61, 0x75, 0x4b, 0xde, 0xda, 0xfb, 0x2b, 0x4d,
|
||||
0x82, 0x13, 0x9c, 0x3b, 0x21, 0xf7, 0x83, 0x3b, 0xac, 0x48, 0x17, 0xe9, 0xbc, 0xa6, 0x6f, 0x19,
|
||||
0x7f, 0xd2, 0xa0, 0x98, 0x30, 0x8a, 0x3a, 0xf0, 0xfc, 0x31, 0x23, 0xd7, 0x81, 0x3f, 0x8d, 0x2b,
|
||||
0x6c, 0x01, 0xa0, 0x2a, 0xe4, 0xe4, 0x80, 0xfb, 0x51, 0x79, 0xc5, 0x43, 0xf4, 0x35, 0xe4, 0x26,
|
||||
0xca, 0x85, 0xcc, 0x52, 0xf1, 0x74, 0x77, 0x2d, 0xba, 0xd8, 0x1b, 0x1c, 0x73, 0x2e, 0xd2, 0xf9,
|
||||
0x94, 0x9e, 0xbe, 0x48, 0xe7, 0xd3, 0x7a, 0xe6, 0x22, 0x9d, 0xcf, 0xe8, 0xd9, 0x8b, 0x74, 0x3e,
|
||||
0xab, 0xe7, 0x8c, 0xff, 0x68, 0x90, 0x8f, 0xd9, 0x62, 0x2d, 0x42, 0xe2, 0x89, 0xa8, 0x8c, 0xa8,
|
||||
0x01, 0x58, 0x02, 0xc8, 0x80, 0x92, 0x1c, 0xac, 0xf6, 0x15, 0x2b, 0x18, 0x7a, 0x0a, 0xe5, 0xc5,
|
||||
0x78, 0x71, 0x79, 0xa5, 0xf0, 0x2a, 0x28, 0x3c, 0x85, 0xf3, 0xd1, 0x88, 0x85, 0xa1, 0x0a, 0x95,
|
||||
0x51, 0x9e, 0x92, 0x18, 0xaa, 0x43, 0x25, 0x1e, 0xc7, 0x01, 0xb3, 0x92, 0xb6, 0x0e, 0xa3, 0xe7,
|
||||
0xa0, 0x27, 0xa1, 0xe9, 0xb2, 0x8d, 0xbf, 0x87, 0xab, 0x6d, 0x30, 0xa6, 0xf0, 0x48, 0xa6, 0xf5,
|
||||
0x32, 0xf0, 0xaf, 0xe8, 0x95, 0xe3, 0x3a, 0xfc, 0x2e, 0x6e, 0x51, 0xc4, 0x16, 0x04, 0xfe, 0x94,
|
||||
0x78, 0xf1, 0x9d, 0x5f, 0xc2, 0x4b, 0x40, 0xa4, 0x83, 0xfb, 0xca, 0x16, 0xa5, 0x23, 0x1a, 0x8a,
|
||||
0xe6, 0x63, 0x11, 0x3c, 0x25, 0x83, 0x2f, 0xc6, 0xc6, 0x2d, 0x54, 0xef, 0x87, 0x8b, 0x4a, 0xe8,
|
||||
0x08, 0x8a, 0xb3, 0x25, 0x2c, 0x23, 0x6a, 0x38, 0x09, 0x25, 0x13, 0xbd, 0xf5, 0xcb, 0x89, 0x36,
|
||||
0xfe, 0xac, 0xc1, 0xce, 0xd9, 0xdc, 0x71, 0xc7, 0x2b, 0x9d, 0x57, 0xf2, 0x85, 0xa6, 0xad, 0xbe,
|
||||
0xd0, 0x36, 0x3d, 0xbf, 0xb6, 0x36, 0x3e, 0xbf, 0x36, 0x3d, 0x71, 0x52, 0x0f, 0x3e, 0x71, 0x3e,
|
||||
0x87, 0xe2, 0xf2, 0x75, 0xa3, 0x1a, 0xdb, 0x12, 0x86, 0x49, 0xfc, 0xb4, 0x09, 0x8d, 0x57, 0x80,
|
||||
0x92, 0x0b, 0x8d, 0x36, 0x64, 0xd1, 0x00, 0x6a, 0x0f, 0x36, 0x80, 0xcf, 0xff, 0xae, 0x41, 0x29,
|
||||
0xd9, 0x85, 0xa3, 0x32, 0x14, 0x2c, 0x9b, 0xb4, 0x3b, 0xd6, 0x77, 0xe7, 0x03, 0xfd, 0x13, 0x31,
|
||||
0xec, 0x0f, 0x9b, 0x4d, 0xd3, 0x6c, 0x99, 0x2d, 0x5d, 0x13, 0xf7, 0x83, 0x90, 0x7a, 0xb3, 0x45,
|
||||
0x06, 0x56, 0xd7, 0xec, 0x0d, 0x45, 0xe7, 0xb0, 0x0b, 0x95, 0x08, 0xb3, 0x7b, 0x04, 0xf7, 0x86,
|
||||
0x03, 0x53, 0x4f, 0x21, 0x1d, 0x4a, 0x11, 0x68, 0x62, 0xdc, 0xc3, 0x7a, 0x5a, 0x5c, 0x77, 0x11,
|
||||
0x72, 0xbf, 0x0b, 0x89, 0x9b, 0x94, 0x8c, 0xec, 0x32, 0x62, 0xd6, 0xf2, 0x82, 0x26, 0x67, 0x8d,
|
||||
0x4e, 0xc3, 0x6e, 0x9a, 0x7a, 0xf6, 0xf4, 0x6f, 0x19, 0xc8, 0xca, 0x2f, 0x08, 0xd0, 0x39, 0x14,
|
||||
0x13, 0x0f, 0x2b, 0x74, 0xf8, 0xd1, 0x07, 0x57, 0xad, 0xba, 0xf9, 0xdd, 0x31, 0x0f, 0x5f, 0x6a,
|
||||
0xe8, 0x02, 0x4a, 0xc9, 0xe7, 0x0e, 0x4a, 0xf6, 0xa6, 0x1b, 0xde, 0x41, 0x1f, 0xf5, 0xf5, 0x1a,
|
||||
0x74, 0x33, 0xe4, 0xce, 0x54, 0xf4, 0xa2, 0xd1, 0xeb, 0x00, 0xd5, 0x12, 0xfc, 0xb5, 0x27, 0x47,
|
||||
0xed, 0x60, 0xa3, 0x2d, 0x4a, 0x61, 0x47, 0x7d, 0x62, 0xd4, 0x9f, 0xdf, 0xfb, 0xc4, 0xd5, 0x47,
|
||||
0x41, 0xed, 0xb3, 0x87, 0xcc, 0x91, 0xb7, 0x31, 0xec, 0x6e, 0x10, 0x70, 0xf4, 0xab, 0xe4, 0x0a,
|
||||
0x1e, 0x94, 0xff, 0xda, 0xb3, 0x5f, 0xa2, 0x2d, 0xa3, 0x6c, 0x50, 0xfa, 0x95, 0x28, 0x0f, 0xdf,
|
||||
0x13, 0x2b, 0x51, 0x3e, 0x76, 0x61, 0xfc, 0x08, 0xfa, 0xba, 0x12, 0x20, 0x63, 0x7d, 0xee, 0x7d,
|
||||
0x55, 0xaa, 0x7d, 0xf9, 0x51, 0x4e, 0xe4, 0xdc, 0x02, 0x58, 0x9e, 0x27, 0xf4, 0x24, 0x31, 0xe5,
|
||||
0x9e, 0x1e, 0xd4, 0x0e, 0x1f, 0xb0, 0x2a, 0x57, 0x67, 0xbf, 0xfe, 0xe3, 0xc9, 0x8d, 0xc3, 0x27,
|
||||
0xf3, 0xab, 0xe3, 0x91, 0x3f, 0x3d, 0x71, 0x45, 0x47, 0xef, 0x39, 0xde, 0x8d, 0xc7, 0xf8, 0xcf,
|
||||
0x7e, 0x70, 0x7b, 0xe2, 0x7a, 0xe3, 0x13, 0x79, 0x2c, 0x4f, 0x16, 0x5e, 0xae, 0xb2, 0xf2, 0xff,
|
||||
0x48, 0xbf, 0xf9, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x56, 0xfb, 0xd9, 0x67, 0x77, 0x12, 0x00,
|
||||
0x00,
|
||||
// 2093 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0xdb, 0xb8,
|
||||
0x15, 0x5f, 0x5a, 0xff, 0x9f, 0xfe, 0xd1, 0xb0, 0xe3, 0x30, 0x72, 0xbc, 0xf1, 0x32, 0x69, 0x56,
|
||||
0x93, 0xc9, 0xda, 0xa9, 0xdb, 0xcd, 0x64, 0x7a, 0x68, 0x47, 0x96, 0xa8, 0x35, 0x1d, 0x89, 0x72,
|
||||
0x20, 0x29, 0xbb, 0xe9, 0x1e, 0x30, 0xb4, 0x04, 0x5b, 0x1c, 0x53, 0xa4, 0x96, 0x84, 0xb2, 0xf1,
|
||||
0x77, 0xe8, 0xf7, 0x68, 0x0f, 0x6d, 0x2f, 0xfd, 0x4e, 0xed, 0xbd, 0x33, 0x3d, 0xf4, 0xd6, 0x01,
|
||||
0x40, 0x4a, 0x94, 0x2c, 0x67, 0x7b, 0xb2, 0xf0, 0x7b, 0x3f, 0xbc, 0x07, 0xe2, 0xe1, 0xfd, 0xf0,
|
||||
0x60, 0xd8, 0x0b, 0xfc, 0x39, 0xa3, 0x41, 0x30, 0x1b, 0x1d, 0xcb, 0x5f, 0x47, 0xb3, 0xc0, 0x67,
|
||||
0x3e, 0x2a, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd,
|
||||
0xf1, 0x85, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3,
|
||||
0x90, 0x69, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x1b, 0xa9, 0x90, 0xb2, 0xa7, 0x4c, 0xdb, 0x3a,
|
||||
0x54, 0xea, 0x29, 0xcc, 0x7f, 0xa2, 0x47, 0x90, 0xb7, 0xa7, 0x8c, 0x4c, 0x43, 0x9b, 0x69, 0x25,
|
||||
0x01, 0xe7, 0xec, 0x29, 0xeb, 0x86, 0x36, 0x43, 0x5f, 0x41, 0x69, 0x26, 0x5d, 0x92, 0x89, 0x1d,
|
||||
0x4e, 0xb4, 0x94, 0x70, 0x54, 0x8c, 0xb0, 0x33, 0x3b, 0x9c, 0xa0, 0x3a, 0xa8, 0x57, 0x8e, 0x67,
|
||||
0xbb, 0x64, 0xe4, 0xb2, 0x8f, 0x64, 0x4c, 0x5d, 0x66, 0x6b, 0xe9, 0x43, 0xa5, 0x9e, 0xc1, 0x15,
|
||||
0x81, 0x37, 0x5d, 0xf6, 0xb1, 0xc5, 0x51, 0xf4, 0x35, 0x54, 0x63, 0x67, 0x81, 0x5c, 0xa0, 0x96,
|
||||
0x39, 0x54, 0xea, 0x05, 0x5c, 0x99, 0xad, 0x2e, 0xfb, 0x6b, 0xa8, 0x32, 0x67, 0x4a, 0xfd, 0x39,
|
||||
0x23, 0x21, 0x1d, 0xf9, 0xde, 0x38, 0xd4, 0xb2, 0xd2, 0x63, 0x04, 0xf7, 0x25, 0x8a, 0x74, 0x28,
|
||||
0x5f, 0x51, 0x4a, 0x5c, 0x67, 0xea, 0x30, 0xc2, 0x97, 0x9f, 0x13, 0xcb, 0x2f, 0x5e, 0x51, 0xda,
|
||||
0xe1, 0x58, 0xdf, 0x66, 0xe8, 0x19, 0x54, 0x96, 0x1c, 0xf1, 0x8d, 0x65, 0x41, 0x2a, 0xc5, 0x24,
|
||||
0xf1, 0xa1, 0x2f, 0x41, 0xf5, 0xe7, 0xec, 0xda, 0x77, 0xbc, 0x6b, 0x32, 0x9a, 0xd8, 0x1e, 0x71,
|
||||
0xc6, 0x5a, 0xfe, 0x50, 0xa9, 0xa7, 0x4f, 0xb7, 0x5e, 0x29, 0xb8, 0x12, 0xdb, 0x9a, 0x13, 0xdb,
|
||||
0x33, 0xc7, 0xe8, 0x39, 0x54, 0x5d, 0x3b, 0x64, 0x64, 0xe2, 0xcf, 0xc8, 0x6c, 0x7e, 0x79, 0x43,
|
||||
0x6f, 0xb5, 0x8a, 0xd8, 0x99, 0x32, 0x87, 0xcf, 0xfc, 0xd9, 0x85, 0x00, 0xd1, 0x01, 0x80, 0xd8,
|
||||
0x15, 0x11, 0x5c, 0x2b, 0x88, 0x6f, 0x28, 0x70, 0x44, 0x04, 0x46, 0x27, 0x50, 0x14, 0xd9, 0x24,
|
||||
0x13, 0xc7, 0x63, 0xa1, 0x06, 0x87, 0xa9, 0x7a, 0xf1, 0x44, 0x3d, 0x72, 0x3d, 0x9e, 0x58, 0xcc,
|
||||
0x2d, 0x67, 0x8e, 0xc7, 0x70, 0x92, 0x84, 0xc6, 0xb0, 0xc3, 0xd3, 0x48, 0x46, 0xf3, 0x90, 0xf9,
|
||||
0x53, 0x12, 0xd0, 0x91, 0x1f, 0x8c, 0x43, 0xad, 0x28, 0xe6, 0xfe, 0xf6, 0x68, 0x71, 0x3a, 0x8e,
|
||||
0xee, 0x1e, 0x87, 0xa3, 0x16, 0x0d, 0x59, 0x53, 0xcc, 0xc3, 0x72, 0x9a, 0xe1, 0xb1, 0xe0, 0x16,
|
||||
0x6f, 0x8f, 0xd7, 0x71, 0xf4, 0x12, 0x90, 0xed, 0xba, 0xfe, 0xcf, 0x24, 0xa4, 0xee, 0x15, 0x89,
|
||||
0xd2, 0xa3, 0x55, 0x0f, 0x95, 0x7a, 0x1e, 0xab, 0xc2, 0xd2, 0xa7, 0xee, 0x55, 0xe4, 0x1e, 0xbd,
|
||||
0x86, 0xb2, 0x58, 0xd3, 0x15, 0xb5, 0xd9, 0x3c, 0xa0, 0xa1, 0xa6, 0x1e, 0xa6, 0xea, 0x95, 0x93,
|
||||
0xed, 0xe8, 0x4b, 0xda, 0x12, 0x3e, 0x75, 0x18, 0x2e, 0x71, 0x5e, 0x34, 0x0e, 0x6b, 0x2d, 0xd8,
|
||||
0xdb, 0xbc, 0x24, 0x7e, 0x48, 0xf9, 0xa6, 0xf2, 0x73, 0x9b, 0xc6, 0xfc, 0x27, 0xda, 0x85, 0xcc,
|
||||
0x47, 0xdb, 0x9d, 0x53, 0x71, 0x70, 0x4b, 0x58, 0x0e, 0x7e, 0xb7, 0xf5, 0x46, 0xd1, 0xdf, 0xc0,
|
||||
0xce, 0x20, 0xb0, 0x47, 0x37, 0x6b, 0x67, 0x7f, 0xfd, 0xe8, 0x2a, 0x77, 0x8e, 0xae, 0xfe, 0x17,
|
||||
0x05, 0xca, 0xd1, 0xac, 0x3e, 0xb3, 0xd9, 0x3c, 0x44, 0xdf, 0x40, 0x26, 0x64, 0x36, 0xa3, 0x82,
|
||||
0x5d, 0x39, 0x79, 0x98, 0xd8, 0xcf, 0x04, 0x91, 0x62, 0xc9, 0x42, 0x35, 0xc8, 0xcf, 0x02, 0xea,
|
||||
0x4c, 0xed, 0xeb, 0x78, 0x5d, 0x8b, 0x31, 0xd2, 0x21, 0x23, 0x26, 0x8b, 0x9a, 0x29, 0x9e, 0x94,
|
||||
0x92, 0x69, 0xc5, 0xd2, 0x84, 0xea, 0x90, 0x99, 0x30, 0x77, 0x14, 0x6a, 0x69, 0x91, 0x3e, 0x14,
|
||||
0x71, 0xce, 0x06, 0x9d, 0x66, 0x83, 0x31, 0x3a, 0x9d, 0x31, 0x2c, 0x09, 0xfa, 0xef, 0xa1, 0x2a,
|
||||
0x66, 0xb6, 0x29, 0xfd, 0x5c, 0x71, 0x3f, 0x04, 0x5e, 0xba, 0xa2, 0x14, 0x64, 0x81, 0x67, 0xed,
|
||||
0x29, 0xaf, 0x02, 0x7d, 0x0c, 0xea, 0x72, 0x7e, 0x38, 0xf3, 0xbd, 0x90, 0x47, 0x57, 0xf9, 0x32,
|
||||
0xf8, 0x91, 0xe7, 0x15, 0x22, 0x6a, 0x43, 0x11, 0xb3, 0x2a, 0x11, 0xde, 0xa6, 0x54, 0x54, 0xc7,
|
||||
0x73, 0x59, 0x90, 0xc4, 0xf5, 0x47, 0x37, 0xbc, 0xc4, 0xed, 0xdb, 0xc8, 0x7d, 0x99, 0xc3, 0x1d,
|
||||
0x7f, 0x74, 0xd3, 0xe2, 0xa0, 0xfe, 0xa3, 0x54, 0xa1, 0x81, 0x2f, 0xbf, 0xf2, 0xff, 0xce, 0xc4,
|
||||
0x72, 0xb3, 0xb6, 0xee, 0xdd, 0x2c, 0x9d, 0xc0, 0xce, 0x8a, 0xf3, 0xe8, 0x2b, 0x92, 0x39, 0x50,
|
||||
0xd6, 0x72, 0xf0, 0x12, 0x72, 0x57, 0xb6, 0xe3, 0xce, 0x83, 0xd8, 0x31, 0x4a, 0x24, 0xb4, 0x2d,
|
||||
0x2d, 0x38, 0xa6, 0xe8, 0xff, 0xc9, 0x41, 0x2e, 0x02, 0xd1, 0x09, 0xa4, 0x47, 0xfe, 0x38, 0x3e,
|
||||
0x07, 0x5f, 0xde, 0x9d, 0x16, 0xff, 0x6d, 0xfa, 0x63, 0x8a, 0x05, 0x17, 0xfd, 0x01, 0x2a, 0x5c,
|
||||
0x3a, 0x3c, 0xea, 0x92, 0xf9, 0x6c, 0x6c, 0x2f, 0x52, 0xaf, 0x25, 0x66, 0x37, 0x25, 0x61, 0x28,
|
||||
0xec, 0xb8, 0x3c, 0x4a, 0x0e, 0xd1, 0x3e, 0x14, 0x78, 0xb6, 0x65, 0x26, 0xd2, 0xe2, 0xec, 0xe7,
|
||||
0x39, 0x20, 0x72, 0xa0, 0x43, 0xd9, 0xf7, 0x1c, 0xdf, 0x23, 0xe1, 0xc4, 0x26, 0x27, 0xdf, 0xbe,
|
||||
0x16, 0xda, 0x59, 0xc2, 0x45, 0x01, 0xf6, 0x27, 0xf6, 0xc9, 0xb7, 0xaf, 0xd1, 0x13, 0x28, 0x0a,
|
||||
0xbd, 0xa1, 0x9f, 0x66, 0x4e, 0x70, 0x2b, 0x44, 0xb3, 0x8c, 0x85, 0x04, 0x19, 0x02, 0xe1, 0x55,
|
||||
0x74, 0xe5, 0xda, 0xd7, 0xa1, 0x10, 0xca, 0x32, 0x96, 0x03, 0xf4, 0x0a, 0x76, 0xa3, 0x3d, 0x20,
|
||||
0xa1, 0x3f, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x02, 0x58, 0xc6, 0x28, 0xb2, 0xf5,
|
||||
0x85, 0xc9, 0xe4, 0x16, 0xb4, 0x07, 0xd9, 0x09, 0x75, 0xae, 0x27, 0x52, 0xd4, 0xca, 0x38, 0x1a,
|
||||
0xe9, 0x7f, 0xcd, 0x40, 0x31, 0xb1, 0x31, 0xa8, 0x04, 0x79, 0x6c, 0xf4, 0x0d, 0xfc, 0xde, 0x68,
|
||||
0xa9, 0x5f, 0xa0, 0x3a, 0x3c, 0x33, 0xad, 0x66, 0x0f, 0x63, 0xa3, 0x39, 0x20, 0x3d, 0x4c, 0x86,
|
||||
0xd6, 0x5b, 0xab, 0xf7, 0xbd, 0x45, 0x2e, 0x1a, 0x1f, 0xba, 0x86, 0x35, 0x20, 0x2d, 0x63, 0xd0,
|
||||
0x30, 0x3b, 0x7d, 0x55, 0x41, 0x8f, 0x41, 0x5b, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b, 0x5a, 0x03,
|
||||
0x75, 0x0b, 0x3d, 0x81, 0xfd, 0xb6, 0x69, 0x35, 0x3a, 0x64, 0xc9, 0x69, 0x76, 0x06, 0xef, 0x89,
|
||||
0xf1, 0xc3, 0x85, 0x89, 0x3f, 0xa8, 0xa9, 0x4d, 0x04, 0x5e, 0x53, 0xb1, 0x87, 0x34, 0x7a, 0x04,
|
||||
0x0f, 0x24, 0x41, 0x4e, 0x21, 0x83, 0x5e, 0x8f, 0xf4, 0x7b, 0x3d, 0x4b, 0xcd, 0xa0, 0x6d, 0x28,
|
||||
0x9b, 0xd6, 0xfb, 0x46, 0xc7, 0x6c, 0x11, 0x6c, 0x34, 0x3a, 0x5d, 0x35, 0x8b, 0x76, 0xa0, 0xba,
|
||||
0xce, 0xcb, 0x71, 0x17, 0x31, 0xaf, 0x67, 0x99, 0x3d, 0x8b, 0xbc, 0x37, 0x70, 0xdf, 0xec, 0x59,
|
||||
0x6a, 0x1e, 0xed, 0x01, 0x5a, 0x35, 0x9d, 0x75, 0x1b, 0x4d, 0xb5, 0x80, 0x1e, 0xc0, 0xf6, 0x2a,
|
||||
0xfe, 0xd6, 0xf8, 0xa0, 0x02, 0xd2, 0x60, 0x57, 0x2e, 0x8c, 0x9c, 0x1a, 0x9d, 0xde, 0xf7, 0xa4,
|
||||
0x6b, 0x5a, 0x66, 0x77, 0xd8, 0x55, 0x8b, 0x68, 0x17, 0xd4, 0xb6, 0x61, 0x10, 0xd3, 0xea, 0x0f,
|
||||
0xdb, 0x6d, 0xb3, 0x69, 0x1a, 0xd6, 0x40, 0x2d, 0xc9, 0xc8, 0x9b, 0x3e, 0xbc, 0xcc, 0x27, 0x34,
|
||||
0xcf, 0x1a, 0x96, 0x65, 0x74, 0x48, 0xcb, 0xec, 0x37, 0x4e, 0x3b, 0x46, 0x4b, 0xad, 0xa0, 0x03,
|
||||
0x78, 0x34, 0x30, 0xba, 0x17, 0x3d, 0xdc, 0xc0, 0x1f, 0x48, 0x6c, 0x6f, 0x37, 0xcc, 0xce, 0x10,
|
||||
0x1b, 0x6a, 0x15, 0x7d, 0x05, 0x07, 0xd8, 0x78, 0x37, 0x34, 0xb1, 0xd1, 0x22, 0x56, 0xaf, 0x65,
|
||||
0x90, 0xb6, 0xd1, 0x18, 0x0c, 0xb1, 0x41, 0xba, 0x66, 0xbf, 0x6f, 0x5a, 0xdf, 0xa9, 0x2a, 0x7a,
|
||||
0x06, 0x87, 0x0b, 0xca, 0xc2, 0xc1, 0x1a, 0x6b, 0x9b, 0x7f, 0x5f, 0x9c, 0x52, 0xcb, 0xf8, 0x61,
|
||||
0x40, 0x2e, 0x0c, 0x03, 0xab, 0x08, 0xd5, 0x60, 0x6f, 0x19, 0x5e, 0x06, 0x88, 0x62, 0xef, 0x70,
|
||||
0xdb, 0x85, 0x81, 0xbb, 0x0d, 0x8b, 0x27, 0x78, 0xc5, 0xb6, 0xcb, 0x97, 0xbd, 0xb4, 0xad, 0x2f,
|
||||
0xfb, 0x01, 0x42, 0x50, 0x49, 0x64, 0xa5, 0xdd, 0xc0, 0xea, 0x1e, 0xda, 0x85, 0x6a, 0xbc, 0x82,
|
||||
0x98, 0xf8, 0xcf, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x1b, 0xb2, 0x30, 0xfc,
|
||||
0x2b, 0x77, 0x9e, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0xd4, 0x25, 0x7a, 0x0c,
|
||||
0x85, 0xd0, 0xb9, 0xf6, 0xc4, 0x35, 0x15, 0x89, 0xca, 0x12, 0x10, 0xb7, 0xfa, 0xc4, 0x76, 0x3c,
|
||||
0xa9, 0x66, 0x52, 0xf7, 0x0b, 0x02, 0x11, 0x5a, 0xb6, 0x0f, 0xb9, 0xb8, 0x83, 0x48, 0x2d, 0x3a,
|
||||
0x88, 0xec, 0x48, 0x76, 0x0e, 0x8f, 0xa1, 0xc0, 0x25, 0x33, 0x64, 0xf6, 0x74, 0x26, 0x4a, 0xbc,
|
||||
0x8c, 0x97, 0x00, 0x7a, 0x0a, 0xe5, 0x29, 0x0d, 0x43, 0xfb, 0x9a, 0x12, 0x59, 0xa6, 0x20, 0x18,
|
||||
0xa5, 0x08, 0x6c, 0x8b, 0x6a, 0x7d, 0x0a, 0xb1, 0x6c, 0x44, 0xa4, 0x8c, 0x24, 0x45, 0xa0, 0x24,
|
||||
0xad, 0x2b, 0x36, 0xb3, 0x23, 0x35, 0x48, 0x2a, 0x36, 0xb3, 0xd1, 0x0b, 0xd8, 0x96, 0x92, 0xe3,
|
||||
0x78, 0xce, 0x74, 0x3e, 0x95, 0xd2, 0x93, 0x13, 0xd2, 0x53, 0x15, 0xd2, 0x23, 0x71, 0xa1, 0x40,
|
||||
0x8f, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x5f, 0x16, 0x91, 0x34, 0xe4, 0xf8, 0xb8, 0x4d, 0x29, 0x37,
|
||||
0xf1, 0x2b, 0x24, 0xe0, 0xa2, 0x27, 0x15, 0x21, 0x77, 0x45, 0x29, 0xe6, 0x7b, 0xb9, 0x88, 0x60,
|
||||
0x7f, 0x5a, 0x46, 0x28, 0x26, 0x22, 0x48, 0x5c, 0x44, 0x78, 0x01, 0xdb, 0xf4, 0x13, 0x0b, 0x6c,
|
||||
0xe2, 0xcf, 0xec, 0x9f, 0xe6, 0x94, 0x8c, 0x6d, 0x66, 0x8b, 0x96, 0xb4, 0x84, 0xab, 0xc2, 0xd0,
|
||||
0x13, 0x78, 0xcb, 0x66, 0xb6, 0xfe, 0x18, 0x6a, 0x98, 0x86, 0x94, 0x75, 0x9d, 0x30, 0x74, 0x7c,
|
||||
0xaf, 0xe9, 0x7b, 0x2c, 0xf0, 0xdd, 0xe8, 0xce, 0xd1, 0x0f, 0x60, 0x7f, 0xa3, 0x55, 0x5e, 0x1a,
|
||||
0x7c, 0xf2, 0xbb, 0x39, 0x0d, 0x6e, 0x37, 0x4f, 0x7e, 0x07, 0xfb, 0x1b, 0xad, 0xd1, 0x8d, 0xf3,
|
||||
0x12, 0x32, 0x33, 0xdb, 0x09, 0x42, 0x6d, 0x4b, 0xdc, 0xda, 0x7b, 0x2b, 0x4d, 0x82, 0x13, 0x9c,
|
||||
0x39, 0x21, 0xf3, 0x83, 0x5b, 0x2c, 0x49, 0xe7, 0xe9, 0xbc, 0xa2, 0x6e, 0xe9, 0x7f, 0x52, 0xa0,
|
||||
0x98, 0x30, 0xf2, 0x73, 0xe0, 0xf9, 0x63, 0x4a, 0xae, 0x02, 0x7f, 0x1a, 0x9f, 0xb0, 0x05, 0x80,
|
||||
0x34, 0xc8, 0x89, 0x01, 0xf3, 0xa3, 0xe3, 0x15, 0x0f, 0xd1, 0x37, 0x90, 0x9b, 0x48, 0x17, 0x22,
|
||||
0x4b, 0xc5, 0x93, 0x9d, 0xb5, 0xe8, 0x7c, 0x6f, 0x70, 0xcc, 0x39, 0x4f, 0xe7, 0x53, 0x6a, 0xfa,
|
||||
0x3c, 0x9d, 0x4f, 0xab, 0x99, 0xf3, 0x74, 0x3e, 0xa3, 0x66, 0xcf, 0xd3, 0xf9, 0xac, 0x9a, 0xd3,
|
||||
0xff, 0xad, 0x40, 0x3e, 0x66, 0xf3, 0xb5, 0x70, 0x89, 0x27, 0xfc, 0x64, 0x44, 0x0d, 0xc0, 0x12,
|
||||
0x40, 0x3a, 0x94, 0xc4, 0x60, 0xb5, 0xaf, 0x58, 0xc1, 0xd0, 0x33, 0x28, 0x2f, 0xc6, 0x8b, 0xcb,
|
||||
0x2b, 0x85, 0x57, 0x41, 0xee, 0x29, 0x9c, 0x8f, 0x46, 0x34, 0x0c, 0x65, 0xa8, 0x8c, 0xf4, 0x94,
|
||||
0xc4, 0x50, 0x1d, 0xaa, 0xf1, 0x38, 0x0e, 0x98, 0x15, 0xb4, 0x75, 0x18, 0xbd, 0x00, 0x35, 0x09,
|
||||
0x4d, 0x97, 0xed, 0xff, 0x1d, 0x5c, 0x6e, 0x83, 0x3e, 0x85, 0x87, 0x22, 0xad, 0x17, 0x81, 0x7f,
|
||||
0x69, 0x5f, 0x3a, 0xae, 0xc3, 0x6e, 0xe3, 0x16, 0x85, 0x6f, 0x41, 0xe0, 0x4f, 0x89, 0x17, 0xdf,
|
||||
0xf9, 0x25, 0xbc, 0x04, 0x78, 0x3a, 0x98, 0x2f, 0x6d, 0x51, 0x3a, 0xa2, 0x21, 0x6f, 0x3e, 0x16,
|
||||
0xc1, 0x53, 0x22, 0xf8, 0x62, 0xac, 0xdf, 0x80, 0x76, 0x37, 0x5c, 0x74, 0x84, 0x0e, 0xa1, 0x38,
|
||||
0x5b, 0xc2, 0x22, 0xa2, 0x82, 0x93, 0x50, 0x32, 0xd1, 0x5b, 0xbf, 0x9c, 0x68, 0xfd, 0xcf, 0x0a,
|
||||
0x6c, 0x9f, 0xce, 0x1d, 0x77, 0xbc, 0xd2, 0x79, 0x25, 0x5f, 0x76, 0xca, 0xea, 0xcb, 0x6e, 0xd3,
|
||||
0xb3, 0x6d, 0x6b, 0xe3, 0xb3, 0x6d, 0xd3, 0xd3, 0x28, 0x75, 0xef, 0xd3, 0xe8, 0x09, 0x14, 0x97,
|
||||
0xaf, 0x22, 0xd9, 0xd8, 0x96, 0x30, 0x4c, 0xe2, 0x27, 0x51, 0xa8, 0xbf, 0x01, 0x94, 0x5c, 0x68,
|
||||
0xb4, 0x21, 0x8b, 0x06, 0x50, 0xb9, 0xb7, 0x01, 0x7c, 0xf1, 0x77, 0x05, 0x4a, 0xc9, 0x2e, 0x1c,
|
||||
0x95, 0xa1, 0x60, 0x5a, 0xa4, 0xdd, 0x31, 0xbf, 0x3b, 0x1b, 0xa8, 0x5f, 0xf0, 0x61, 0x7f, 0xd8,
|
||||
0x6c, 0x1a, 0x46, 0xcb, 0x68, 0xa9, 0x0a, 0xbf, 0x1f, 0xb8, 0xd4, 0x1b, 0x2d, 0x32, 0x30, 0xbb,
|
||||
0x46, 0x6f, 0xc8, 0x3b, 0x87, 0x1d, 0xa8, 0x46, 0x98, 0xd5, 0x23, 0xb8, 0x37, 0x1c, 0x18, 0x6a,
|
||||
0x0a, 0xa9, 0x50, 0x8a, 0x40, 0x03, 0xe3, 0x1e, 0x56, 0xd3, 0xfc, 0xba, 0x8b, 0x90, 0xbb, 0x5d,
|
||||
0x48, 0xdc, 0xa4, 0x64, 0x44, 0x97, 0x11, 0xb3, 0x96, 0x17, 0x34, 0x39, 0x6d, 0x74, 0x1a, 0x56,
|
||||
0xd3, 0x50, 0xb3, 0x27, 0x7f, 0xcb, 0x40, 0x56, 0x7c, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x78, 0x90,
|
||||
0xa1, 0x83, 0xcf, 0x3e, 0xd4, 0x6a, 0xda, 0xe6, 0x77, 0xc7, 0x3c, 0x7c, 0xa5, 0xa0, 0x73, 0x28,
|
||||
0x25, 0x9f, 0x3b, 0x28, 0xd9, 0x9b, 0x6e, 0x78, 0x07, 0x7d, 0xd6, 0xd7, 0x5b, 0x50, 0x8d, 0x90,
|
||||
0x39, 0x53, 0xde, 0x8b, 0x46, 0xaf, 0x03, 0x54, 0x4b, 0xf0, 0xd7, 0x9e, 0x1c, 0xb5, 0xfd, 0x8d,
|
||||
0xb6, 0x28, 0x85, 0x1d, 0xf9, 0x89, 0x51, 0x7f, 0x7e, 0xe7, 0x13, 0x57, 0x1f, 0x05, 0xb5, 0x2f,
|
||||
0xef, 0x33, 0x47, 0xde, 0xc6, 0xb0, 0xb3, 0x41, 0xc0, 0xd1, 0xaf, 0x92, 0x2b, 0xb8, 0x57, 0xfe,
|
||||
0x6b, 0xcf, 0x7f, 0x89, 0xb6, 0x8c, 0xb2, 0x41, 0xe9, 0x57, 0xa2, 0xdc, 0x7f, 0x4f, 0xac, 0x44,
|
||||
0xf9, 0xdc, 0x85, 0xf1, 0x23, 0xa8, 0xeb, 0x4a, 0x80, 0xf4, 0xf5, 0xb9, 0x77, 0x55, 0xa9, 0xf6,
|
||||
0xf4, 0xb3, 0x9c, 0xc8, 0xb9, 0x09, 0xb0, 0xac, 0x27, 0xf4, 0x38, 0x31, 0xe5, 0x8e, 0x1e, 0xd4,
|
||||
0x0e, 0xee, 0xb1, 0x4a, 0x57, 0xa7, 0xbf, 0xfe, 0xe3, 0xf1, 0xb5, 0xc3, 0x26, 0xf3, 0xcb, 0xa3,
|
||||
0x91, 0x3f, 0x3d, 0x76, 0x79, 0x47, 0xef, 0x39, 0xde, 0xb5, 0x47, 0xd9, 0xcf, 0x7e, 0x70, 0x73,
|
||||
0xec, 0x7a, 0xe3, 0x63, 0x51, 0x96, 0xc7, 0x0b, 0x2f, 0x97, 0x59, 0xf1, 0xff, 0xa7, 0xdf, 0xfc,
|
||||
0x2f, 0x00, 0x00, 0xff, 0xff, 0x42, 0x87, 0x8c, 0xeb, 0xaf, 0x12, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
@ -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.
|
||||
*/
|
||||
repeated lnrpc.FeatureBit dest_features = 16;
|
||||
}
|
||||
|
||||
message TrackPaymentRequest {
|
||||
|
@ -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
|
||||
// (and a custom route wasn't specified), construct the payment
|
||||
@ -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
|
||||
@ -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 {
|
||||
|
1264
lnrpc/rpc.pb.go
1264
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -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 {
|
||||
@ -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;
|
||||
}
|
||||
|
||||
message Feature {
|
||||
string name = 2 [json_name = "name"];
|
||||
bool is_required = 3 [json_name = "is_required"];
|
||||
|
@ -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": {
|
||||
@ -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."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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
|
||||
@ -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.
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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.
|
||||
@ -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
|
||||
@ -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)
|
||||
|
@ -76,3 +76,23 @@ func Invariant(statement func() bool, timeout time.Duration) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InvariantNoError is a wrapper around Invariant that waits out the duration
|
||||
// specified by timeout. It fails if the predicate ever returns an error during
|
||||
// that time.
|
||||
func InvariantNoError(f func() error, timeout time.Duration) error {
|
||||
var predErr error
|
||||
pred := func() bool {
|
||||
if err := f(); err != nil {
|
||||
predErr = err
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if err := Invariant(pred, timeout); err != nil {
|
||||
return predErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -134,6 +134,10 @@ var Features = map[FeatureBit]string{
|
||||
TLVOnionPayloadOptional: "tlv-onion",
|
||||
StaticRemoteKeyOptional: "static-remote-key",
|
||||
StaticRemoteKeyRequired: "static-remote-key",
|
||||
PaymentAddrOptional: "payment-addr",
|
||||
PaymentAddrRequired: "payment-addr",
|
||||
MPPOptional: "multi-path-payments",
|
||||
MPPRequired: "multi-path-payments",
|
||||
}
|
||||
|
||||
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
|
||||
@ -357,6 +361,11 @@ func NewFeatureVector(featureVector *RawFeatureVector,
|
||||
}
|
||||
}
|
||||
|
||||
// EmptyFeatureVector returns a feature vector with no bits set.
|
||||
func EmptyFeatureVector() *FeatureVector {
|
||||
return NewFeatureVector(nil, Features)
|
||||
}
|
||||
|
||||
// HasFeature returns whether a particular feature is included in the set. The
|
||||
// feature can be seen as set either if the bit is set directly OR the queried
|
||||
// bit has the same meaning as its corresponding even/odd bit, which is set
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
@ -69,6 +70,11 @@ var (
|
||||
errNoTlvPayload = errors.New("destination hop doesn't " +
|
||||
"understand new TLV payloads")
|
||||
|
||||
// errNoPaymentAddr is returned when the destination hop does not
|
||||
// support payment addresses.
|
||||
errNoPaymentAddr = errors.New("destination hop doesn't " +
|
||||
"understand payment addresses")
|
||||
|
||||
// errNoPathFound is returned when a path to the target destination does
|
||||
// not exist in the graph.
|
||||
errNoPathFound = errors.New("unable to find a path to destination")
|
||||
@ -90,17 +96,30 @@ type edgePolicyWithSource struct {
|
||||
edge *channeldb.ChannelEdgePolicy
|
||||
}
|
||||
|
||||
// newRoute returns a fully valid route between the source and target that's
|
||||
// capable of supporting a payment of `amtToSend` after fees are fully
|
||||
// computed. If the route is too long, or the selected path cannot support the
|
||||
// fully payment including fees, then a non-nil error is returned.
|
||||
// finalHopParams encapsulates various parameters for route construction that
|
||||
// apply to the final hop in a route. These features include basic payment data
|
||||
// such as amounts and cltvs, as well as more complex features like destination
|
||||
// custom records and payment address.
|
||||
type finalHopParams struct {
|
||||
amt lnwire.MilliSatoshi
|
||||
cltvDelta uint16
|
||||
records record.CustomSet
|
||||
paymentAddr *[32]byte
|
||||
}
|
||||
|
||||
// newRoute constructs a route using the provided path and final hop constraints.
|
||||
// Any destination specific fields from the final hop params will be attached
|
||||
// assuming the destination's feature vector signals support, otherwise this
|
||||
// method will fail. If the route is too long, or the selected path cannot
|
||||
// support the fully payment including fees, then a non-nil error is returned.
|
||||
//
|
||||
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
|
||||
// the source to the target node of the path finding attempt.
|
||||
func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
||||
// the source to the target node of the path finding attempt. It is assumed that
|
||||
// any feature vectors on all hops have been validated for transitive
|
||||
// dependencies.
|
||||
func newRoute(sourceVertex route.Vertex,
|
||||
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
|
||||
finalCLTVDelta uint16,
|
||||
destCustomRecords record.CustomSet) (*route.Route, error) {
|
||||
finalHop finalHopParams) (*route.Route, error) {
|
||||
|
||||
var (
|
||||
hops []*route.Hop
|
||||
@ -123,18 +142,73 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
||||
// payload for the hop this edge is leading to.
|
||||
edge := pathEdges[i]
|
||||
|
||||
// If this is the last hop, then the hop payload will contain
|
||||
// the exact amount. In BOLT #4: Onion Routing
|
||||
// Protocol / "Payload for the Last Node", this is detailed.
|
||||
amtToForward := amtToSend
|
||||
// We'll calculate the amounts, timelocks, and fees for each hop
|
||||
// in the route. The base case is the final hop which includes
|
||||
// their amount and timelocks. These values will accumulate
|
||||
// contributions from the preceding hops back to the sender as
|
||||
// we compute the route in reverse.
|
||||
var (
|
||||
amtToForward lnwire.MilliSatoshi
|
||||
fee lnwire.MilliSatoshi
|
||||
outgoingTimeLock uint32
|
||||
tlvPayload bool
|
||||
customRecords record.CustomSet
|
||||
mpp *record.MPP
|
||||
)
|
||||
|
||||
// Fee is not part of the hop payload, but only used for
|
||||
// reporting through RPC. Set to zero for the final hop.
|
||||
fee := lnwire.MilliSatoshi(0)
|
||||
// Define a helper function that checks this edge's feature
|
||||
// vector for support for a given feature. We assume at this
|
||||
// point that the feature vectors transitive dependencies have
|
||||
// been validated.
|
||||
supports := edge.Node.Features.HasFeature
|
||||
|
||||
// If the current hop isn't the last hop, then add enough funds
|
||||
// to pay for transit over the next link.
|
||||
if i != len(pathEdges)-1 {
|
||||
// We start by assuming the node doesn't support TLV. We'll now
|
||||
// inspect the node's feature vector to see if we can promote
|
||||
// the hop. We assume already that the feature vector's
|
||||
// transitive dependencies have already been validated by path
|
||||
// finding or some other means.
|
||||
tlvPayload = supports(lnwire.TLVOnionPayloadOptional)
|
||||
|
||||
if i == len(pathEdges)-1 {
|
||||
// If this is the last hop, then the hop payload will
|
||||
// contain the exact amount. In BOLT #4: Onion Routing
|
||||
// Protocol / "Payload for the Last Node", this is
|
||||
// detailed.
|
||||
amtToForward = finalHop.amt
|
||||
|
||||
// Fee is not part of the hop payload, but only used for
|
||||
// reporting through RPC. Set to zero for the final hop.
|
||||
fee = lnwire.MilliSatoshi(0)
|
||||
|
||||
// As this is the last hop, we'll use the specified
|
||||
// final CLTV delta value instead of the value from the
|
||||
// last link in the route.
|
||||
totalTimeLock += uint32(finalHop.cltvDelta)
|
||||
outgoingTimeLock = totalTimeLock
|
||||
|
||||
// Attach any custom records to the final hop if the
|
||||
// receiver supports TLV.
|
||||
if !tlvPayload && finalHop.records != nil {
|
||||
return nil, errors.New("cannot attach " +
|
||||
"custom records")
|
||||
}
|
||||
customRecords = finalHop.records
|
||||
|
||||
// If we're attaching a payment addr but the receiver
|
||||
// doesn't support both TLV and payment addrs, fail.
|
||||
payAddr := supports(lnwire.PaymentAddrOptional)
|
||||
if !payAddr && finalHop.paymentAddr != nil {
|
||||
return nil, errors.New("cannot attach " +
|
||||
"payment addr")
|
||||
}
|
||||
|
||||
// Otherwise attach the mpp record if it exists.
|
||||
if finalHop.paymentAddr != nil {
|
||||
mpp = record.NewMPP(
|
||||
finalHop.amt, *finalHop.paymentAddr,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// The amount that the current hop needs to forward is
|
||||
// equal to the incoming amount of the next hop.
|
||||
amtToForward = nextIncomingAmount
|
||||
@ -145,32 +219,12 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
||||
// is stored as part of the incoming channel of
|
||||
// the next hop.
|
||||
fee = pathEdges[i+1].ComputeFee(amtToForward)
|
||||
}
|
||||
|
||||
// If this is the last hop, then for verification purposes, the
|
||||
// value of the outgoing time-lock should be _exactly_ the
|
||||
// absolute time out they'd expect in the HTLC.
|
||||
var outgoingTimeLock uint32
|
||||
if i == len(pathEdges)-1 {
|
||||
// As this is the last hop, we'll use the specified
|
||||
// final CLTV delta value instead of the value from the
|
||||
// last link in the route.
|
||||
totalTimeLock += uint32(finalCLTVDelta)
|
||||
|
||||
outgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||
} else {
|
||||
// Next, increment the total timelock of the entire
|
||||
// route such that each hops time lock increases as we
|
||||
// walk backwards in the route, using the delta of the
|
||||
// previous hop.
|
||||
delta := uint32(pathEdges[i+1].TimeLockDelta)
|
||||
totalTimeLock += delta
|
||||
|
||||
// Otherwise, the value of the outgoing time-lock will
|
||||
// be the value of the time-lock for the _outgoing_
|
||||
// HTLC, so we factor in their specified grace period
|
||||
// (time lock delta).
|
||||
outgoingTimeLock = totalTimeLock - delta
|
||||
// We'll take the total timelock of the preceding hop as
|
||||
// the outgoing timelock or this hop. Then we'll
|
||||
// increment the total timelock incurred by this hop.
|
||||
outgoingTimeLock = totalTimeLock
|
||||
totalTimeLock += uint32(pathEdges[i+1].TimeLockDelta)
|
||||
}
|
||||
|
||||
// Since we're traversing the path backwards atm, we prepend
|
||||
@ -181,25 +235,9 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
||||
ChannelID: edge.ChannelID,
|
||||
AmtToForward: amtToForward,
|
||||
OutgoingTimeLock: outgoingTimeLock,
|
||||
LegacyPayload: true,
|
||||
}
|
||||
|
||||
// We start out above by assuming that this node needs the
|
||||
// legacy payload, as if we don't have the full
|
||||
// NodeAnnouncement information for this node, then we can't
|
||||
// assume it knows the latest features. If we do have a feature
|
||||
// vector for this node, then we'll update the info now.
|
||||
if edge.Node.Features != nil {
|
||||
features := edge.Node.Features
|
||||
currentHop.LegacyPayload = !features.HasFeature(
|
||||
lnwire.TLVOnionPayloadOptional,
|
||||
)
|
||||
}
|
||||
|
||||
// If this is the last hop, then we'll populate any TLV records
|
||||
// destined for it.
|
||||
if i == len(pathEdges)-1 && len(destCustomRecords) != 0 {
|
||||
currentHop.CustomRecords = destCustomRecords
|
||||
LegacyPayload: !tlvPayload,
|
||||
CustomRecords: customRecords,
|
||||
MPP: mpp,
|
||||
}
|
||||
|
||||
hops = append([]*route.Hop{currentHop}, hops...)
|
||||
@ -292,6 +330,16 @@ type RestrictParams struct {
|
||||
// DestCustomRecords contains the custom records to drop off at the
|
||||
// final hop, if any.
|
||||
DestCustomRecords record.CustomSet
|
||||
|
||||
// DestFeatures is a feature vector describing what the final hop
|
||||
// supports. If none are provided, pathfinding will try to inspect any
|
||||
// features on the node announcement instead.
|
||||
DestFeatures *lnwire.FeatureVector
|
||||
|
||||
// PaymentAddr is a random 32-byte value generated by the receiver to
|
||||
// mitigate probing vectors and payment sniping attacks on overpaid
|
||||
// invoices.
|
||||
PaymentAddr *[32]byte
|
||||
}
|
||||
|
||||
// PathFindingConfig defines global parameters that control the trade-off in
|
||||
@ -395,29 +443,60 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
||||
defer tx.Rollback()
|
||||
}
|
||||
|
||||
if len(r.DestCustomRecords) > 0 {
|
||||
// Check if the target has TLV enabled
|
||||
|
||||
// If no destination features are provided, we will load what features
|
||||
// we have for the target node from our graph.
|
||||
features := r.DestFeatures
|
||||
if features == nil {
|
||||
targetKey, err := btcec.ParsePubKey(target[:], btcec.S256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetNode, err := g.graph.FetchLightningNode(targetKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
|
||||
if targetNode.Features != nil {
|
||||
supportsTLV := targetNode.Features.HasFeature(
|
||||
lnwire.TLVOnionPayloadOptional,
|
||||
)
|
||||
if !supportsTLV {
|
||||
return nil, errNoTlvPayload
|
||||
}
|
||||
// If the node exists and has features, use them directly.
|
||||
case err == nil:
|
||||
features = targetNode.Features
|
||||
|
||||
// If an error other than the node not existing is hit, abort.
|
||||
case err != channeldb.ErrGraphNodeNotFound:
|
||||
return nil, err
|
||||
|
||||
// Otherwise, we couldn't find a node announcement, populate a
|
||||
// blank feature vector.
|
||||
default:
|
||||
features = lnwire.EmptyFeatureVector()
|
||||
}
|
||||
}
|
||||
|
||||
// With the destination's feature vector selected, ensure that all
|
||||
// transitive depdencies are set.
|
||||
err = feature.ValidateDeps(features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we know the feature vector is well formed, we'll proceed in
|
||||
// checking that it supports the features we need, given our
|
||||
// restrictions on the final hop.
|
||||
|
||||
// If the caller needs to send custom records, check that our
|
||||
// destination feature vector supports TLV.
|
||||
if len(r.DestCustomRecords) > 0 &&
|
||||
!features.HasFeature(lnwire.TLVOnionPayloadOptional) {
|
||||
|
||||
return nil, errNoTlvPayload
|
||||
}
|
||||
|
||||
// If the caller has a payment address to attach, check that our
|
||||
// destination feature vector supports them.
|
||||
if r.PaymentAddr != nil &&
|
||||
!features.HasFeature(lnwire.PaymentAddrOptional) {
|
||||
|
||||
return nil, errNoPaymentAddr
|
||||
}
|
||||
|
||||
// If we are routing from ourselves, check that we have enough local
|
||||
// balance available.
|
||||
if source == self {
|
||||
@ -597,6 +676,36 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
||||
edge.ChannelID)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
// If this edge takes us to the final hop, we'll set the node
|
||||
// features to those determined above. These are either taken
|
||||
// from the destination features, e.g. virtual or invoice
|
||||
// features, or loaded as a fallback from the graph. The
|
||||
// transitive dependencies were already validated above, so no
|
||||
// need to do so now.
|
||||
//
|
||||
// NOTE: This may overwrite features loaded from the graph if
|
||||
// destination features were provided. This is fine though,
|
||||
// since our route construction does not care where the features
|
||||
// are actually taken from. In the future we may wish to do
|
||||
// route construction within findPath, and avoid using
|
||||
// ChannelEdgePolicy altogether.
|
||||
case edge.Node.PubKeyBytes == target:
|
||||
edge.Node.Features = features
|
||||
|
||||
// Otherwise, this is some other intermediary node. Verify the
|
||||
// transitive feature dependencies for this node, and skip the
|
||||
// channel if they are invalid.
|
||||
default:
|
||||
err := feature.ValidateDeps(edge.Node.Features)
|
||||
if err != nil {
|
||||
log.Tracef("Node %x has invalid features",
|
||||
edge.Node.PubKeyBytes)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// All conditions are met and this new tentative distance is
|
||||
// better than the current best known distance to this node.
|
||||
// The new better distance is recorded, and also our "next hop"
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -22,7 +23,9 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
)
|
||||
@ -58,6 +61,25 @@ var (
|
||||
}
|
||||
|
||||
testPathFindingConfig = &PathFindingConfig{}
|
||||
|
||||
tlvFeatures = lnwire.NewFeatureVector(
|
||||
lnwire.NewRawFeatureVector(
|
||||
lnwire.TLVOnionPayloadOptional,
|
||||
), lnwire.Features,
|
||||
)
|
||||
|
||||
payAddrFeatures = lnwire.NewFeatureVector(
|
||||
lnwire.NewRawFeatureVector(
|
||||
lnwire.PaymentAddrOptional,
|
||||
), lnwire.Features,
|
||||
)
|
||||
|
||||
tlvPayAddrFeatures = lnwire.NewFeatureVector(
|
||||
lnwire.NewRawFeatureVector(
|
||||
lnwire.TLVOnionPayloadOptional,
|
||||
lnwire.PaymentAddrOptional,
|
||||
), lnwire.Features,
|
||||
)
|
||||
)
|
||||
|
||||
var (
|
||||
@ -318,6 +340,7 @@ type testChannelPolicy struct {
|
||||
LastUpdate time.Time
|
||||
Disabled bool
|
||||
Direction bool
|
||||
Features *lnwire.FeatureVector
|
||||
}
|
||||
|
||||
type testChannelEnd struct {
|
||||
@ -325,7 +348,7 @@ type testChannelEnd struct {
|
||||
*testChannelPolicy
|
||||
}
|
||||
|
||||
func symmetricTestChannel(alias1 string, alias2 string, capacity btcutil.Amount,
|
||||
func symmetricTestChannel(alias1, alias2 string, capacity btcutil.Amount,
|
||||
policy *testChannelPolicy, chanID ...uint64) *testChannel {
|
||||
|
||||
// Leaving id zero will result in auto-generation of a channel id during
|
||||
@ -335,18 +358,26 @@ func symmetricTestChannel(alias1 string, alias2 string, capacity btcutil.Amount,
|
||||
id = chanID[0]
|
||||
}
|
||||
|
||||
node2Policy := *policy
|
||||
node2Policy.Direction = !policy.Direction
|
||||
policy2 := *policy
|
||||
policy2.Direction = !policy.Direction
|
||||
|
||||
return asymmetricTestChannel(
|
||||
alias1, alias2, capacity, policy, &policy2, id,
|
||||
)
|
||||
}
|
||||
|
||||
func asymmetricTestChannel(alias1, alias2 string, capacity btcutil.Amount,
|
||||
policy1, policy2 *testChannelPolicy, id uint64) *testChannel {
|
||||
|
||||
return &testChannel{
|
||||
Capacity: capacity,
|
||||
Node1: &testChannelEnd{
|
||||
Alias: alias1,
|
||||
testChannelPolicy: policy,
|
||||
testChannelPolicy: policy1,
|
||||
},
|
||||
Node2: &testChannelEnd{
|
||||
Alias: alias2,
|
||||
testChannelPolicy: &node2Policy,
|
||||
testChannelPolicy: policy2,
|
||||
},
|
||||
ChannelID: id,
|
||||
}
|
||||
@ -401,7 +432,9 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
||||
privKeyMap := make(map[string]*btcec.PrivateKey)
|
||||
|
||||
nodeIndex := byte(0)
|
||||
addNodeWithAlias := func(alias string) (*channeldb.LightningNode, error) {
|
||||
addNodeWithAlias := func(alias string, features *lnwire.FeatureVector) (
|
||||
*channeldb.LightningNode, error) {
|
||||
|
||||
keyBytes := make([]byte, 32)
|
||||
keyBytes = []byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
@ -413,13 +446,17 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
||||
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
keyBytes)
|
||||
|
||||
if features == nil {
|
||||
features = lnwire.EmptyFeatureVector()
|
||||
}
|
||||
|
||||
dbNode := &channeldb.LightningNode{
|
||||
HaveNodeAnnouncement: true,
|
||||
AuthSigBytes: testSig.Serialize(),
|
||||
LastUpdate: testTime,
|
||||
Addresses: testAddrs,
|
||||
Alias: alias,
|
||||
Features: testFeatures,
|
||||
Features: features,
|
||||
}
|
||||
|
||||
copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed())
|
||||
@ -439,7 +476,7 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
||||
}
|
||||
|
||||
// Add the source node.
|
||||
dbNode, err := addNodeWithAlias(source)
|
||||
dbNode, err := addNodeWithAlias(source, lnwire.EmptyFeatureVector())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -453,12 +490,19 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
||||
nextUnassignedChannelID := uint64(100000)
|
||||
|
||||
for _, testChannel := range testChannels {
|
||||
for _, alias := range []string{
|
||||
testChannel.Node1.Alias, testChannel.Node2.Alias} {
|
||||
for _, node := range []*testChannelEnd{
|
||||
testChannel.Node1, testChannel.Node2} {
|
||||
|
||||
_, exists := aliasMap[alias]
|
||||
_, exists := aliasMap[node.Alias]
|
||||
if !exists {
|
||||
_, err := addNodeWithAlias(alias)
|
||||
var features *lnwire.FeatureVector
|
||||
if node.testChannelPolicy != nil {
|
||||
features =
|
||||
node.testChannelPolicy.Features
|
||||
}
|
||||
_, err := addNodeWithAlias(
|
||||
node.Alias, features,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -637,8 +681,12 @@ func TestFindLowestFeePath(t *testing.T) {
|
||||
t.Fatalf("unable to find path: %v", err)
|
||||
}
|
||||
route, err := newRoute(
|
||||
paymentAmt, ctx.source, path, startingHeight,
|
||||
finalHopCLTV, nil,
|
||||
ctx.source, path, startingHeight,
|
||||
finalHopParams{
|
||||
amt: paymentAmt,
|
||||
cltvDelta: finalHopCLTV,
|
||||
records: nil,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
@ -787,8 +835,12 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
||||
}
|
||||
|
||||
route, err := newRoute(
|
||||
paymentAmt, sourceVertex, path, startingHeight,
|
||||
finalHopCLTV, nil,
|
||||
sourceVertex, path, startingHeight,
|
||||
finalHopParams{
|
||||
amt: paymentAmt,
|
||||
cltvDelta: finalHopCLTV,
|
||||
records: nil,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
@ -895,6 +947,10 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
||||
}
|
||||
}
|
||||
|
||||
// TestPathFindingWithAdditionalEdges asserts that we are able to find paths to
|
||||
// nodes that do not exist in the graph by way of hop hints. We also test that
|
||||
// the path can support custom TLV records for the receiver under the
|
||||
// appropriate circumstances.
|
||||
func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -946,15 +1002,21 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
||||
graph.aliasMap["songoku"]: {songokuToDoge},
|
||||
}
|
||||
|
||||
find := func(r *RestrictParams) (
|
||||
[]*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
return findPath(
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
additionalEdges: additionalEdges,
|
||||
},
|
||||
r, testPathFindingConfig,
|
||||
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
||||
)
|
||||
}
|
||||
|
||||
// We should now be able to find a path from roasbeef to doge.
|
||||
path, err := findPath(
|
||||
&graphParams{
|
||||
graph: graph.graph,
|
||||
additionalEdges: additionalEdges,
|
||||
},
|
||||
noRestrictions, testPathFindingConfig,
|
||||
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
||||
)
|
||||
path, err := find(noRestrictions)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find private path to doge: %v", err)
|
||||
}
|
||||
@ -962,6 +1024,35 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
||||
// The path should represent the following hops:
|
||||
// roasbeef -> songoku -> doge
|
||||
assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
|
||||
|
||||
// Now, set custom records for the final hop. This should fail since no
|
||||
// dest features are set, and we won't have a node ann to fall back on.
|
||||
restrictions := *noRestrictions
|
||||
restrictions.DestCustomRecords = record.CustomSet{70000: []byte{}}
|
||||
|
||||
_, err = find(&restrictions)
|
||||
if err != errNoTlvPayload {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
|
||||
// Set empty dest features so we don't try the fallback. We should still
|
||||
// fail since the tlv feature isn't set.
|
||||
restrictions.DestFeatures = lnwire.EmptyFeatureVector()
|
||||
|
||||
_, err = find(&restrictions)
|
||||
if err != errNoTlvPayload {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
|
||||
// Finally, set the tlv feature in the payload and assert we found the
|
||||
// same path as before.
|
||||
restrictions.DestFeatures = tlvFeatures
|
||||
|
||||
path, err = find(&restrictions)
|
||||
if err != nil {
|
||||
t.Fatalf("path should have been found: %v", err)
|
||||
}
|
||||
assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
|
||||
}
|
||||
|
||||
// TestNewRoute tests whether the construction of hop payloads by newRoute
|
||||
@ -971,6 +1062,8 @@ func TestNewRoute(t *testing.T) {
|
||||
var sourceKey [33]byte
|
||||
sourceVertex := route.Vertex(sourceKey)
|
||||
|
||||
testPaymentAddr := [32]byte{0x01, 0x02, 0x03}
|
||||
|
||||
const (
|
||||
startingHeight = 100
|
||||
finalHopCLTV = 1
|
||||
@ -1005,6 +1098,12 @@ func TestNewRoute(t *testing.T) {
|
||||
// indicated by hops.
|
||||
paymentAmount lnwire.MilliSatoshi
|
||||
|
||||
// destFeatures is a feature vector, that if non-nil, will
|
||||
// overwrite the final hop's feature vector in the graph.
|
||||
destFeatures *lnwire.FeatureVector
|
||||
|
||||
paymentAddr *[32]byte
|
||||
|
||||
// expectedFees is a list of fees that every hop is expected
|
||||
// to charge for forwarding.
|
||||
expectedFees []lnwire.MilliSatoshi
|
||||
@ -1033,6 +1132,10 @@ func TestNewRoute(t *testing.T) {
|
||||
// expectedErrorCode indicates the expected error code when
|
||||
// expectError is true.
|
||||
expectedErrorCode errorCode
|
||||
|
||||
expectedTLVPayload bool
|
||||
|
||||
expectedMPP *record.MPP
|
||||
}{
|
||||
{
|
||||
// For a single hop payment, no fees are expected to be paid.
|
||||
@ -1059,6 +1162,42 @@ func TestNewRoute(t *testing.T) {
|
||||
expectedTimeLocks: []uint32{1, 1},
|
||||
expectedTotalAmount: 100130,
|
||||
expectedTotalTimeLock: 6,
|
||||
}, {
|
||||
// For a two hop payment, only the fee for the first hop
|
||||
// needs to be paid. The destination hop does not require
|
||||
// a fee to receive the payment.
|
||||
name: "two hop tlv onion feature",
|
||||
destFeatures: tlvFeatures,
|
||||
paymentAmount: 100000,
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(0, 1000, 1000000, 10),
|
||||
createHop(30, 1000, 1000000, 5),
|
||||
},
|
||||
expectedFees: []lnwire.MilliSatoshi{130, 0},
|
||||
expectedTimeLocks: []uint32{1, 1},
|
||||
expectedTotalAmount: 100130,
|
||||
expectedTotalTimeLock: 6,
|
||||
expectedTLVPayload: true,
|
||||
}, {
|
||||
// For a two hop payment, only the fee for the first hop
|
||||
// needs to be paid. The destination hop does not require
|
||||
// a fee to receive the payment.
|
||||
name: "two hop single shot mpp",
|
||||
destFeatures: tlvPayAddrFeatures,
|
||||
paymentAddr: &testPaymentAddr,
|
||||
paymentAmount: 100000,
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(0, 1000, 1000000, 10),
|
||||
createHop(30, 1000, 1000000, 5),
|
||||
},
|
||||
expectedFees: []lnwire.MilliSatoshi{130, 0},
|
||||
expectedTimeLocks: []uint32{1, 1},
|
||||
expectedTotalAmount: 100130,
|
||||
expectedTotalTimeLock: 6,
|
||||
expectedTLVPayload: true,
|
||||
expectedMPP: record.NewMPP(
|
||||
100000, testPaymentAddr,
|
||||
),
|
||||
}, {
|
||||
// A three hop payment where the first and second hop
|
||||
// will both charge 1 msat. The fee for the first hop
|
||||
@ -1115,6 +1254,15 @@ func TestNewRoute(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
|
||||
// Overwrite the final hop's features if the test requires a
|
||||
// custom feature vector.
|
||||
if testCase.destFeatures != nil {
|
||||
finalHop := testCase.hops[len(testCase.hops)-1]
|
||||
finalHop.Node.Features = testCase.destFeatures
|
||||
}
|
||||
|
||||
assertRoute := func(t *testing.T, route *route.Route) {
|
||||
if route.TotalAmount != testCase.expectedTotalAmount {
|
||||
t.Errorf("Expected total amount is be %v"+
|
||||
@ -1158,13 +1306,35 @@ func TestNewRoute(t *testing.T) {
|
||||
route.Hops[i].OutgoingTimeLock)
|
||||
}
|
||||
}
|
||||
|
||||
finalHop := route.Hops[len(route.Hops)-1]
|
||||
if !finalHop.LegacyPayload !=
|
||||
testCase.expectedTLVPayload {
|
||||
|
||||
t.Errorf("Expected final hop tlv payload: %t, "+
|
||||
"but got: %t instead",
|
||||
testCase.expectedTLVPayload,
|
||||
!finalHop.LegacyPayload)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(
|
||||
finalHop.MPP, testCase.expectedMPP,
|
||||
) {
|
||||
t.Errorf("Expected final hop mpp field: %v, "+
|
||||
" but got: %v instead",
|
||||
testCase.expectedMPP, finalHop.MPP)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
route, err := newRoute(
|
||||
testCase.paymentAmount, sourceVertex,
|
||||
testCase.hops, startingHeight, finalHopCLTV,
|
||||
nil,
|
||||
sourceVertex, testCase.hops, startingHeight,
|
||||
finalHopParams{
|
||||
amt: testCase.paymentAmount,
|
||||
cltvDelta: finalHopCLTV,
|
||||
records: nil,
|
||||
paymentAddr: testCase.paymentAddr,
|
||||
},
|
||||
)
|
||||
|
||||
if testCase.expectError {
|
||||
@ -1274,6 +1444,275 @@ func TestPathNotAvailable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDestTLVGraphFallback asserts that we properly detect when we can send TLV
|
||||
// records to a receiver, and also that we fallback to the receiver's node
|
||||
// announcement if we don't have an invoice features.
|
||||
func TestDestTLVGraphFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testChannels := []*testChannel{
|
||||
asymmetricTestChannel("roasbeef", "luoji", 100000,
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
}, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
}, 0),
|
||||
asymmetricTestChannel("roasbeef", "satoshi", 100000,
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
}, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
Features: tlvFeatures,
|
||||
}, 0),
|
||||
}
|
||||
|
||||
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
|
||||
defer ctx.cleanup()
|
||||
|
||||
sourceNode, err := ctx.graphParams.graph.SourceNode()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch source node: %v", err)
|
||||
|
||||
}
|
||||
|
||||
find := func(r *RestrictParams,
|
||||
target route.Vertex) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
return findPath(
|
||||
&graphParams{
|
||||
graph: ctx.graphParams.graph,
|
||||
},
|
||||
r, testPathFindingConfig,
|
||||
sourceNode.PubKeyBytes, target, 100,
|
||||
)
|
||||
}
|
||||
|
||||
// Luoji's node ann has an empty feature vector.
|
||||
luoji := ctx.testGraphInstance.aliasMap["luoji"]
|
||||
|
||||
// Satoshi's node ann supports TLV.
|
||||
satoshi := ctx.testGraphInstance.aliasMap["satoshi"]
|
||||
|
||||
restrictions := *noRestrictions
|
||||
|
||||
// Add custom records w/o any dest features.
|
||||
restrictions.DestCustomRecords = record.CustomSet{70000: []byte{}}
|
||||
|
||||
// Path to luoji should fail because his node ann features are empty.
|
||||
_, err = find(&restrictions, luoji)
|
||||
if err != errNoTlvPayload {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
|
||||
// However, path to satoshi should succeed via the fallback because his
|
||||
// node ann features have the TLV bit.
|
||||
path, err := find(&restrictions, satoshi)
|
||||
if err != nil {
|
||||
t.Fatalf("path should have been found: %v", err)
|
||||
}
|
||||
assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "satoshi")
|
||||
|
||||
// Add empty destination features. This should cause both paths to fail,
|
||||
// since this override anything in the graph.
|
||||
restrictions.DestFeatures = lnwire.EmptyFeatureVector()
|
||||
|
||||
_, err = find(&restrictions, luoji)
|
||||
if err != errNoTlvPayload {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
_, err = find(&restrictions, satoshi)
|
||||
if err != errNoTlvPayload {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
|
||||
// Finally, set the TLV dest feature. We should succeed in finding a
|
||||
// path to luoji.
|
||||
restrictions.DestFeatures = tlvFeatures
|
||||
|
||||
path, err = find(&restrictions, luoji)
|
||||
if err != nil {
|
||||
t.Fatalf("path should have been found: %v", err)
|
||||
}
|
||||
assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji")
|
||||
}
|
||||
|
||||
// TestMissingFeatureDep asserts that we fail path finding when the
|
||||
// destination's features are broken, in that the feature vector doesn't signal
|
||||
// all transitive dependencies.
|
||||
func TestMissingFeatureDep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testChannels := []*testChannel{
|
||||
asymmetricTestChannel("roasbeef", "conner", 100000,
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
},
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
Features: payAddrFeatures,
|
||||
}, 0,
|
||||
),
|
||||
asymmetricTestChannel("conner", "joost", 100000,
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
Features: payAddrFeatures,
|
||||
},
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
}, 0,
|
||||
),
|
||||
}
|
||||
|
||||
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
|
||||
defer ctx.cleanup()
|
||||
|
||||
sourceNode, err := ctx.graphParams.graph.SourceNode()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch source node: %v", err)
|
||||
|
||||
}
|
||||
|
||||
find := func(r *RestrictParams,
|
||||
target route.Vertex) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
return findPath(
|
||||
&graphParams{
|
||||
graph: ctx.graphParams.graph,
|
||||
},
|
||||
r, testPathFindingConfig,
|
||||
sourceNode.PubKeyBytes, target, 100,
|
||||
)
|
||||
}
|
||||
|
||||
// Conner's node in the graph has a broken feature vector, since it
|
||||
// signals payment addresses without signaling tlv onions. Pathfinding
|
||||
// should fail since we validate transitive feature dependencies for the
|
||||
// final node.
|
||||
conner := ctx.testGraphInstance.aliasMap["conner"]
|
||||
|
||||
restrictions := *noRestrictions
|
||||
|
||||
_, err = find(&restrictions, conner)
|
||||
if err != feature.NewErrMissingFeatureDep(
|
||||
lnwire.TLVOnionPayloadOptional,
|
||||
) {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
|
||||
// Now, set the TLV and payment addresses features to override the
|
||||
// broken features found in the graph. We should succeed in finding a
|
||||
// path to conner.
|
||||
restrictions.DestFeatures = tlvPayAddrFeatures
|
||||
|
||||
path, err := find(&restrictions, conner)
|
||||
if err != nil {
|
||||
t.Fatalf("path should have been found: %v", err)
|
||||
}
|
||||
assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "conner")
|
||||
|
||||
// Finally, try to find a route to joost through conner. The
|
||||
// destination features are set properly from the previous assertions,
|
||||
// but conner's feature vector in the graph is still broken. We expect
|
||||
// errNoPathFound and not the missing feature dep err above since
|
||||
// intermediate hops are simply skipped if they have invalid feature
|
||||
// vectors, leaving no possible route to joost.
|
||||
joost := ctx.testGraphInstance.aliasMap["joost"]
|
||||
|
||||
_, err = find(&restrictions, joost)
|
||||
if err != errNoPathFound {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDestPaymentAddr asserts that we properly detect when we can send a
|
||||
// payment address to a receiver, and also that we fallback to the receiver's
|
||||
// node announcement if we don't have an invoice features.
|
||||
func TestDestPaymentAddr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testChannels := []*testChannel{
|
||||
symmetricTestChannel("roasbeef", "luoji", 100000,
|
||||
&testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: 100000000,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
|
||||
defer ctx.cleanup()
|
||||
|
||||
sourceNode, err := ctx.graphParams.graph.SourceNode()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch source node: %v", err)
|
||||
|
||||
}
|
||||
|
||||
find := func(r *RestrictParams,
|
||||
target route.Vertex) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
return findPath(
|
||||
&graphParams{
|
||||
graph: ctx.graphParams.graph,
|
||||
},
|
||||
r, testPathFindingConfig,
|
||||
sourceNode.PubKeyBytes, target, 100,
|
||||
)
|
||||
}
|
||||
|
||||
luoji := ctx.testGraphInstance.aliasMap["luoji"]
|
||||
|
||||
restrictions := *noRestrictions
|
||||
|
||||
// Add payment address w/o any invoice features.
|
||||
restrictions.PaymentAddr = &[32]byte{1}
|
||||
|
||||
// Add empty destination features. This should cause us to fail, since
|
||||
// this overrides anything in the graph.
|
||||
restrictions.DestFeatures = lnwire.EmptyFeatureVector()
|
||||
|
||||
_, err = find(&restrictions, luoji)
|
||||
if err != errNoPaymentAddr {
|
||||
t.Fatalf("path shouldn't have been found: %v", err)
|
||||
}
|
||||
|
||||
// Now, set the TLV and payment address features for the destination. We
|
||||
// should succeed in finding a path to luoji.
|
||||
restrictions.DestFeatures = tlvPayAddrFeatures
|
||||
|
||||
path, err := find(&restrictions, luoji)
|
||||
if err != nil {
|
||||
t.Fatalf("path should have been found: %v", err)
|
||||
}
|
||||
assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji")
|
||||
}
|
||||
|
||||
func TestPathInsufficientCapacity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -1859,8 +2298,12 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
||||
t.Fatalf("unable to find path: %v", err)
|
||||
}
|
||||
route, err := newRoute(
|
||||
paymentAmt, ctx.source, path, startingHeight,
|
||||
finalHopCLTV, nil,
|
||||
ctx.source, path, startingHeight,
|
||||
finalHopParams{
|
||||
amt: paymentAmt,
|
||||
cltvDelta: finalHopCLTV,
|
||||
records: nil,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
@ -1985,8 +2428,12 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
||||
finalHopCLTV = 1
|
||||
)
|
||||
route, err := newRoute(
|
||||
paymentAmt, ctx.source, path, startingHeight, finalHopCLTV,
|
||||
nil,
|
||||
ctx.source, path, startingHeight,
|
||||
finalHopParams{
|
||||
amt: paymentAmt,
|
||||
cltvDelta: finalHopCLTV,
|
||||
records: nil,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
@ -2272,8 +2719,12 @@ func TestNoCycle(t *testing.T) {
|
||||
t.Fatalf("unable to find path: %v", err)
|
||||
}
|
||||
route, err := newRoute(
|
||||
paymentAmt, ctx.source, path, startingHeight,
|
||||
finalHopCLTV, nil,
|
||||
ctx.source, path, startingHeight,
|
||||
finalHopParams{
|
||||
amt: paymentAmt,
|
||||
cltvDelta: finalHopCLTV,
|
||||
records: nil,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
|
@ -192,7 +192,11 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) {
|
||||
// payment-level failure.
|
||||
func errorToPaymentFailure(err error) channeldb.FailureReason {
|
||||
switch err {
|
||||
case errNoTlvPayload, errNoPathFound, errMaxHopsExceeded,
|
||||
case
|
||||
errNoTlvPayload,
|
||||
errNoPaymentAddr,
|
||||
errNoPathFound,
|
||||
errMaxHopsExceeded,
|
||||
errPrebuiltRouteTried:
|
||||
|
||||
return channeldb.FailureReasonNoRoute
|
||||
|
@ -98,6 +98,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
||||
LastHop: payment.LastHop,
|
||||
CltvLimit: cltvLimit,
|
||||
DestCustomRecords: payment.DestCustomRecords,
|
||||
DestFeatures: payment.DestFeatures,
|
||||
PaymentAddr: payment.PaymentAddr,
|
||||
}
|
||||
|
||||
// We'll also obtain a set of bandwidthHints from the lower layer for
|
||||
@ -129,8 +131,13 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
||||
// a route by applying the time-lock and fee requirements.
|
||||
sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes)
|
||||
route, err := newRoute(
|
||||
payment.Amount, sourceVertex, path, height, finalCltvDelta,
|
||||
payment.DestCustomRecords,
|
||||
sourceVertex, path, height,
|
||||
finalHopParams{
|
||||
amt: payment.Amount,
|
||||
cltvDelta: finalCltvDelta,
|
||||
records: payment.DestCustomRecords,
|
||||
paymentAddr: payment.PaymentAddr,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// TODO(roasbeef): return which edge/vertex didn't work
|
||||
|
@ -1454,8 +1454,12 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
|
||||
|
||||
// Create the route with absolute time lock values.
|
||||
route, err := newRoute(
|
||||
amt, source, path, uint32(currentHeight), finalCLTVDelta,
|
||||
destCustomRecords,
|
||||
source, path, uint32(currentHeight),
|
||||
finalHopParams{
|
||||
amt: amt,
|
||||
cltvDelta: finalCLTVDelta,
|
||||
records: destCustomRecords,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1597,6 +1601,19 @@ type LightningPayment struct {
|
||||
// is reached. If nil, any node may be used.
|
||||
LastHop *route.Vertex
|
||||
|
||||
// DestFeatures specifies the set of features we assume the final node
|
||||
// has for pathfinding. Typically these will be taken directly from an
|
||||
// invoice, but they can also be manually supplied or assumed by the
|
||||
// sender. If a nil feature vector is provided, the router will try to
|
||||
// fallback to the graph in order to load a feature vector for a node in
|
||||
// the public graph.
|
||||
DestFeatures *lnwire.FeatureVector
|
||||
|
||||
// PaymentAddr is the payment address specified by the receiver. This
|
||||
// field should be a random 32-byte nonce presented in the receiver's
|
||||
// invoice to prevent probing of the destination.
|
||||
PaymentAddr *[32]byte
|
||||
|
||||
// PaymentRequest is an optional payment request that this payment is
|
||||
// attempting to complete.
|
||||
PaymentRequest []byte
|
||||
@ -2403,7 +2420,11 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
||||
}
|
||||
|
||||
return newRoute(
|
||||
receiverAmt, source, pathEdges, uint32(height),
|
||||
uint16(finalCltvDelta), nil,
|
||||
source, pathEdges, uint32(height),
|
||||
finalHopParams{
|
||||
amt: receiverAmt,
|
||||
cltvDelta: uint16(finalCltvDelta),
|
||||
records: nil,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
14
rpcserver.go
14
rpcserver.go
@ -3241,6 +3241,8 @@ type rpcPaymentIntent struct {
|
||||
routeHints [][]zpay32.HopHint
|
||||
outgoingChannelID *uint64
|
||||
lastHop *route.Vertex
|
||||
destFeatures *lnwire.FeatureVector
|
||||
paymentAddr *[32]byte
|
||||
payReq []byte
|
||||
|
||||
destCustomRecords record.CustomSet
|
||||
@ -3370,6 +3372,8 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
|
||||
payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
||||
payIntent.routeHints = payReq.RouteHints
|
||||
payIntent.payReq = []byte(rpcPayReq.PaymentRequest)
|
||||
payIntent.destFeatures = payReq.Features
|
||||
payIntent.paymentAddr = payReq.PaymentAddr
|
||||
|
||||
if err := validateDest(payIntent.dest); err != nil {
|
||||
return payIntent, err
|
||||
@ -3438,6 +3442,14 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
|
||||
copy(payIntent.rHash[:], rpcPayReq.PaymentHash)
|
||||
}
|
||||
|
||||
// Unmarshal any custom destination features.
|
||||
payIntent.destFeatures, err = routerrpc.UnmarshalFeatures(
|
||||
rpcPayReq.DestFeatures,
|
||||
)
|
||||
if err != nil {
|
||||
return payIntent, err
|
||||
}
|
||||
|
||||
// Currently, within the bootstrap phase of the network, we limit the
|
||||
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
|
||||
// satoshis.
|
||||
@ -3492,6 +3504,8 @@ func (r *rpcServer) dispatchPaymentIntent(
|
||||
PaymentRequest: payIntent.payReq,
|
||||
PayAttemptTimeout: routing.DefaultPayAttemptTimeout,
|
||||
DestCustomRecords: payIntent.destCustomRecords,
|
||||
DestFeatures: payIntent.destFeatures,
|
||||
PaymentAddr: payIntent.paymentAddr,
|
||||
}
|
||||
|
||||
preImage, route, routerErr = r.server.chanRouter.SendPayment(
|
||||
|
Loading…
x
Reference in New Issue
Block a user