Merge pull request #3679 from cfromknecht/bolt-11-payment-addr
bolt 11 payment addr + single shot mpp for SendPayment
This commit is contained in:
18 files changed
+1757
-898
No files matched your search
+5
-3
@@ -3461,6 +3461,10 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
|
|||||||
err 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 {
|
if _, err := r.Read(scratch[:]); err != nil {
|
||||||
return LightningNode{}, err
|
return LightningNode{}, err
|
||||||
}
|
}
|
||||||
@@ -3506,12 +3510,10 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
|
|||||||
return LightningNode{}, err
|
return LightningNode{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fv := lnwire.NewFeatureVector(nil, lnwire.Features)
|
err = node.Features.Decode(r)
|
||||||
err = fv.Decode(r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LightningNode{}, err
|
return LightningNode{}, err
|
||||||
}
|
}
|
||||||
node.Features = fv
|
|
||||||
|
|
||||||
if _, err := r.Read(scratch[:2]); err != nil {
|
if _, err := r.Read(scratch[:2]); err != nil {
|
||||||
return LightningNode{}, err
|
return LightningNode{}, err
|
||||||
|
|||||||
@@ -33,4 +33,14 @@ var defaultSetDesc = setDesc{
|
|||||||
SetInit: {}, // I
|
SetInit: {}, // I
|
||||||
SetNodeAnn: {}, // N
|
SetNodeAnn: {}, // N
|
||||||
},
|
},
|
||||||
|
lnwire.PaymentAddrOptional: {
|
||||||
|
SetInit: {}, // I
|
||||||
|
SetNodeAnn: {}, // N
|
||||||
|
SetInvoice: {}, // 9
|
||||||
|
},
|
||||||
|
lnwire.MPPOptional: {
|
||||||
|
SetInit: {}, // I
|
||||||
|
SetNodeAnn: {}, // N
|
||||||
|
SetInvoice: {}, // 9
|
||||||
|
},
|
||||||
}
|
}
|
||||||
+6
-1
@@ -30,6 +30,11 @@ type ErrMissingFeatureDep struct {
|
|||||||
dep lnwire.FeatureBit
|
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.
|
// Error returns a human-readable description of the missing dep error.
|
||||||
func (e ErrMissingFeatureDep) Error() string {
|
func (e ErrMissingFeatureDep) Error() string {
|
||||||
return fmt.Sprintf("missing feature dependency: %v", e.dep)
|
return fmt.Sprintf("missing feature dependency: %v", e.dep)
|
||||||
@@ -74,7 +79,7 @@ func validateDeps(features featureSet, supported supportedFeatures) error {
|
|||||||
// vector is invalid.
|
// vector is invalid.
|
||||||
checked, ok := supported[bit]
|
checked, ok := supported[bit]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrMissingFeatureDep{bit}
|
return NewErrMissingFeatureDep(bit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternatively, if we know that this depdendency is valid, we
|
// Alternatively, if we know that this depdendency is valid, we
|
||||||
|
|||||||
+148
-135
@@ -254,10 +254,15 @@ type SendPaymentRequest struct {
|
|||||||
//must be encoded as base64.
|
//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"`
|
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.
|
/// 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"`
|
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:"-"`
|
//Features assumed to be supported by the final node. All transitive feature
|
||||||
XXX_sizecache int32 `json:"-"`
|
//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{} }
|
func (m *SendPaymentRequest) Reset() { *m = SendPaymentRequest{} }
|
||||||
@@ -390,6 +395,13 @@ func (m *SendPaymentRequest) GetAllowSelfPayment() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SendPaymentRequest) GetDestFeatures() []lnrpc.FeatureBit {
|
||||||
|
if m != nil {
|
||||||
|
return m.DestFeatures
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type TrackPaymentRequest struct {
|
type TrackPaymentRequest struct {
|
||||||
/// The hash of the payment to look up.
|
/// The hash of the payment to look up.
|
||||||
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
|
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) }
|
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
|
||||||
|
|
||||||
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
||||||
// 2065 bytes of a gzipped FileDescriptorProto
|
// 2093 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xdd, 0x72, 0xdb, 0xc6,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0xdb, 0xb8,
|
||||||
0x15, 0x0e, 0xc4, 0xff, 0x43, 0x52, 0x84, 0x56, 0x8a, 0x4c, 0x53, 0x56, 0xa2, 0x20, 0xae, 0xc3,
|
0x15, 0x5f, 0x5a, 0xff, 0x9f, 0xfe, 0xd1, 0xb0, 0xe3, 0x30, 0x72, 0xbc, 0xf1, 0x32, 0x69, 0x56,
|
||||||
0xf1, 0x38, 0x92, 0xab, 0x36, 0x19, 0x4f, 0x2f, 0xda, 0xa1, 0x48, 0x30, 0x82, 0x4c, 0x82, 0xf2,
|
0x93, 0xc9, 0xda, 0xa9, 0xdb, 0xcd, 0x64, 0x7a, 0x68, 0x47, 0x96, 0xa8, 0x35, 0x1d, 0x89, 0x72,
|
||||||
0x92, 0x74, 0xe2, 0xe6, 0x62, 0x67, 0x45, 0xae, 0x44, 0x8c, 0x40, 0x80, 0x01, 0x96, 0x8e, 0xf5,
|
0x20, 0x29, 0xbb, 0xe9, 0x1e, 0x30, 0xb4, 0x04, 0x5b, 0x1c, 0x53, 0xa4, 0x96, 0x84, 0xb2, 0xf1,
|
||||||
0x0e, 0x7d, 0x8f, 0xf6, 0xa2, 0xed, 0x4d, 0x9f, 0xa0, 0x2f, 0xd3, 0xde, 0x77, 0xa6, 0xf7, 0x9d,
|
0x77, 0xe8, 0xf7, 0x68, 0x0f, 0x6d, 0x2f, 0xfd, 0x4e, 0xed, 0xbd, 0x33, 0x3d, 0xf4, 0xd6, 0x01,
|
||||||
0xdd, 0x05, 0x48, 0x90, 0xa2, 0x9c, 0x5e, 0x89, 0xfb, 0x9d, 0x6f, 0xcf, 0x59, 0xec, 0x39, 0xfb,
|
0x40, 0x4a, 0x94, 0x2c, 0x67, 0x7b, 0xb2, 0xf0, 0x7b, 0x3f, 0xbc, 0x07, 0xe2, 0xe1, 0xfd, 0xf0,
|
||||||
0xed, 0x59, 0xc1, 0x7e, 0xe0, 0xcf, 0x39, 0x0b, 0x82, 0xd9, 0xe8, 0x44, 0xfd, 0x3a, 0x9e, 0x05,
|
0x60, 0xd8, 0x0b, 0xfc, 0x39, 0xa3, 0x41, 0x30, 0x1b, 0x1d, 0xcb, 0x5f, 0x47, 0xb3, 0xc0, 0x67,
|
||||||
0x3e, 0xf7, 0x51, 0x61, 0x81, 0xd7, 0x0a, 0xc1, 0x6c, 0xa4, 0x50, 0xe3, 0x9f, 0x19, 0x40, 0x7d,
|
0x3e, 0x2a, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd,
|
||||||
0xe6, 0x8d, 0x2f, 0xe9, 0xdd, 0x94, 0x79, 0x1c, 0xb3, 0x9f, 0xe6, 0x2c, 0xe4, 0x08, 0x41, 0x7a,
|
0xf1, 0x85, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3,
|
||||||
0xcc, 0x42, 0x5e, 0xd5, 0x8e, 0xb4, 0x7a, 0x09, 0xcb, 0xdf, 0x48, 0x87, 0x14, 0x9d, 0xf2, 0xea,
|
0x90, 0x69, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x1b, 0xa9, 0x90, 0xb2, 0xa7, 0x4c, 0xdb, 0x3a,
|
||||||
0xd6, 0x91, 0x56, 0x4f, 0x61, 0xf1, 0x13, 0x3d, 0x86, 0x3c, 0x9d, 0x72, 0x32, 0x0d, 0x29, 0xaf,
|
0x54, 0xea, 0x29, 0xcc, 0x7f, 0xa2, 0x47, 0x90, 0xb7, 0xa7, 0x8c, 0x4c, 0x43, 0x9b, 0x69, 0x25,
|
||||||
0x96, 0x24, 0x9c, 0xa3, 0x53, 0xde, 0x0d, 0x29, 0x47, 0x5f, 0x40, 0x69, 0xa6, 0x5c, 0x92, 0x09,
|
0x01, 0xe7, 0xec, 0x29, 0xeb, 0x86, 0x36, 0x43, 0x5f, 0x41, 0x69, 0x26, 0x5d, 0x92, 0x89, 0x1d,
|
||||||
0x0d, 0x27, 0xd5, 0x94, 0x74, 0x54, 0x8c, 0xb0, 0x73, 0x1a, 0x4e, 0x50, 0x1d, 0xf4, 0x6b, 0xc7,
|
0x4e, 0xb4, 0x94, 0x70, 0x54, 0x8c, 0xb0, 0x33, 0x3b, 0x9c, 0xa0, 0x3a, 0xa8, 0x57, 0x8e, 0x67,
|
||||||
0xa3, 0x2e, 0x19, 0xb9, 0xfc, 0x3d, 0x19, 0x33, 0x97, 0xd3, 0x6a, 0xfa, 0x48, 0xab, 0x67, 0xf0,
|
0xbb, 0x64, 0xe4, 0xb2, 0x8f, 0x64, 0x4c, 0x5d, 0x66, 0x6b, 0xe9, 0x43, 0xa5, 0x9e, 0xc1, 0x15,
|
||||||
0xb6, 0xc4, 0x9b, 0x2e, 0x7f, 0xdf, 0x12, 0x28, 0xfa, 0x0a, 0x2a, 0xb1, 0xb3, 0x40, 0x2d, 0xb0,
|
0x81, 0x37, 0x5d, 0xf6, 0xb1, 0xc5, 0x51, 0xf4, 0x35, 0x54, 0x63, 0x67, 0x81, 0x5c, 0xa0, 0x96,
|
||||||
0x9a, 0x39, 0xd2, 0xea, 0x05, 0xbc, 0x3d, 0x5b, 0x5d, 0xf6, 0x57, 0x50, 0xe1, 0xce, 0x94, 0xf9,
|
0x39, 0x54, 0xea, 0x05, 0x5c, 0x99, 0xad, 0x2e, 0xfb, 0x6b, 0xa8, 0x32, 0x67, 0x4a, 0xfd, 0x39,
|
||||||
0x73, 0x4e, 0x42, 0x36, 0xf2, 0xbd, 0x71, 0x58, 0xcd, 0x2a, 0x8f, 0x11, 0xdc, 0x57, 0x28, 0x32,
|
0x23, 0x21, 0x1d, 0xf9, 0xde, 0x38, 0xd4, 0xb2, 0xd2, 0x63, 0x04, 0xf7, 0x25, 0x8a, 0x74, 0x28,
|
||||||
0xa0, 0x7c, 0xcd, 0x18, 0x71, 0x9d, 0xa9, 0xc3, 0x89, 0x58, 0x7e, 0x4e, 0x2e, 0xbf, 0x78, 0xcd,
|
0x5f, 0x51, 0x4a, 0x5c, 0x67, 0xea, 0x30, 0xc2, 0x97, 0x9f, 0x13, 0xcb, 0x2f, 0x5e, 0x51, 0xda,
|
||||||
0x58, 0x47, 0x60, 0x7d, 0xca, 0xd1, 0x53, 0xd8, 0x5e, 0x72, 0xe4, 0x37, 0x96, 0x25, 0xa9, 0x14,
|
0xe1, 0x58, 0xdf, 0x66, 0xe8, 0x19, 0x54, 0x96, 0x1c, 0xf1, 0x8d, 0x65, 0x41, 0x2a, 0xc5, 0x24,
|
||||||
0x93, 0xe4, 0x87, 0xbe, 0x00, 0xdd, 0x9f, 0xf3, 0x1b, 0xdf, 0xf1, 0x6e, 0xc8, 0x68, 0x42, 0x3d,
|
0xf1, 0xa1, 0x2f, 0x41, 0xf5, 0xe7, 0xec, 0xda, 0x77, 0xbc, 0x6b, 0x32, 0x9a, 0xd8, 0x1e, 0x71,
|
||||||
0xe2, 0x8c, 0xab, 0xf9, 0x23, 0xad, 0x9e, 0x3e, 0xdb, 0x7a, 0xa9, 0xe1, 0xed, 0xd8, 0xd6, 0x9c,
|
0xc6, 0x5a, 0xfe, 0x50, 0xa9, 0xa7, 0x4f, 0xb7, 0x5e, 0x29, 0xb8, 0x12, 0xdb, 0x9a, 0x13, 0xdb,
|
||||||
0x50, 0xcf, 0x1a, 0xa3, 0x67, 0x50, 0x71, 0x69, 0xc8, 0xc9, 0xc4, 0x9f, 0x91, 0xd9, 0xfc, 0xea,
|
0x33, 0xc7, 0xe8, 0x39, 0x54, 0x5d, 0x3b, 0x64, 0x64, 0xe2, 0xcf, 0xc8, 0x6c, 0x7e, 0x79, 0x43,
|
||||||
0x96, 0xdd, 0x55, 0xb7, 0xe5, 0xce, 0x94, 0x05, 0x7c, 0xee, 0xcf, 0x2e, 0x25, 0x88, 0x0e, 0x01,
|
0x6f, 0xb5, 0x8a, 0xd8, 0x99, 0x32, 0x87, 0xcf, 0xfc, 0xd9, 0x85, 0x00, 0xd1, 0x01, 0x80, 0xd8,
|
||||||
0xe4, 0xae, 0xc8, 0xe0, 0xd5, 0x82, 0xfc, 0x86, 0x82, 0x40, 0x64, 0x60, 0x74, 0x0a, 0x45, 0x99,
|
0x15, 0x11, 0x5c, 0x2b, 0x88, 0x6f, 0x28, 0x70, 0x44, 0x04, 0x46, 0x27, 0x50, 0x14, 0xd9, 0x24,
|
||||||
0x4d, 0x32, 0x71, 0x3c, 0x1e, 0x56, 0xe1, 0x28, 0x55, 0x2f, 0x9e, 0xea, 0xc7, 0xae, 0x27, 0x12,
|
0x13, 0xc7, 0x63, 0xa1, 0x06, 0x87, 0xa9, 0x7a, 0xf1, 0x44, 0x3d, 0x72, 0x3d, 0x9e, 0x58, 0xcc,
|
||||||
0x8b, 0x85, 0xe5, 0xdc, 0xf1, 0x38, 0x4e, 0x92, 0xd0, 0x18, 0x76, 0x45, 0x1a, 0xc9, 0x68, 0x1e,
|
0x2d, 0x67, 0x8e, 0xc7, 0x70, 0x92, 0x84, 0xc6, 0xb0, 0xc3, 0xd3, 0x48, 0x46, 0xf3, 0x90, 0xf9,
|
||||||
0x72, 0x7f, 0x4a, 0x02, 0x36, 0xf2, 0x83, 0x71, 0x58, 0x2d, 0xca, 0xb9, 0xbf, 0x3d, 0x5e, 0x54,
|
0x53, 0x12, 0xd0, 0x91, 0x1f, 0x8c, 0x43, 0xad, 0x28, 0xe6, 0xfe, 0xf6, 0x68, 0x71, 0x3a, 0x8e,
|
||||||
0xc7, 0xf1, 0xfd, 0x72, 0x38, 0x6e, 0xb1, 0x90, 0x37, 0xe5, 0x3c, 0xac, 0xa6, 0x99, 0x1e, 0x0f,
|
0xee, 0x1e, 0x87, 0xa3, 0x16, 0x0d, 0x59, 0x53, 0xcc, 0xc3, 0x72, 0x9a, 0xe1, 0xb1, 0xe0, 0x16,
|
||||||
0xee, 0xf0, 0xce, 0x78, 0x1d, 0x47, 0x2f, 0x00, 0x51, 0xd7, 0xf5, 0x7f, 0x26, 0x21, 0x73, 0xaf,
|
0x6f, 0x8f, 0xd7, 0x71, 0xf4, 0x12, 0x90, 0xed, 0xba, 0xfe, 0xcf, 0x24, 0xa4, 0xee, 0x15, 0x89,
|
||||||
0x49, 0x94, 0x9e, 0x6a, 0xe5, 0x48, 0xab, 0xe7, 0xb1, 0x2e, 0x2d, 0x7d, 0xe6, 0x5e, 0x47, 0xee,
|
0xd2, 0xa3, 0x55, 0x0f, 0x95, 0x7a, 0x1e, 0xab, 0xc2, 0xd2, 0xa7, 0xee, 0x55, 0xe4, 0x1e, 0xbd,
|
||||||
0x6b, 0x2d, 0xd8, 0xdf, 0xec, 0x5a, 0x14, 0x9b, 0xd8, 0x1c, 0x51, 0x7f, 0x69, 0x2c, 0x7e, 0xa2,
|
0x86, 0xb2, 0x58, 0xd3, 0x15, 0xb5, 0xd9, 0x3c, 0xa0, 0xa1, 0xa6, 0x1e, 0xa6, 0xea, 0x95, 0x93,
|
||||||
0x3d, 0xc8, 0xbc, 0xa7, 0xee, 0x9c, 0xc9, 0x02, 0x2c, 0x61, 0x35, 0xf8, 0xdd, 0xd6, 0x2b, 0xcd,
|
0xed, 0xe8, 0x4b, 0xda, 0x12, 0x3e, 0x75, 0x18, 0x2e, 0x71, 0x5e, 0x34, 0x0e, 0x6b, 0x2d, 0xd8,
|
||||||
0x78, 0x05, 0xbb, 0x83, 0x80, 0x8e, 0x6e, 0xd7, 0x6a, 0x78, 0xbd, 0x04, 0xb5, 0x7b, 0x25, 0x68,
|
0xdb, 0xbc, 0x24, 0x7e, 0x48, 0xf9, 0xa6, 0xf2, 0x73, 0x9b, 0xc6, 0xfc, 0x27, 0xda, 0x85, 0xcc,
|
||||||
0xfc, 0x45, 0x83, 0x72, 0x34, 0xab, 0xcf, 0x29, 0x9f, 0x87, 0xe8, 0x6b, 0xc8, 0x84, 0x9c, 0x72,
|
0x47, 0xdb, 0x9d, 0x53, 0x71, 0x70, 0x4b, 0x58, 0x0e, 0x7e, 0xb7, 0xf5, 0x46, 0xd1, 0xdf, 0xc0,
|
||||||
0x26, 0xd9, 0xdb, 0xa7, 0x8f, 0x12, 0xfb, 0x92, 0x20, 0x32, 0xac, 0x58, 0xa8, 0x06, 0xf9, 0x59,
|
0xce, 0x20, 0xb0, 0x47, 0x37, 0x6b, 0x67, 0x7f, 0xfd, 0xe8, 0x2a, 0x77, 0x8e, 0xae, 0xfe, 0x17,
|
||||||
0xc0, 0x9c, 0x29, 0xbd, 0x89, 0xd7, 0xb5, 0x18, 0x23, 0x03, 0x32, 0x72, 0xb2, 0xac, 0xfd, 0xe2,
|
0x05, 0xca, 0xd1, 0xac, 0x3e, 0xb3, 0xd9, 0x3c, 0x44, 0xdf, 0x40, 0x26, 0x64, 0x36, 0xa3, 0x82,
|
||||||
0x69, 0x29, 0x99, 0x1e, 0xac, 0x4c, 0xa8, 0x0e, 0x99, 0x09, 0x77, 0x47, 0x61, 0x35, 0x2d, 0xd3,
|
0x5d, 0x39, 0x79, 0x98, 0xd8, 0xcf, 0x04, 0x91, 0x62, 0xc9, 0x42, 0x35, 0xc8, 0xcf, 0x02, 0xea,
|
||||||
0x80, 0x22, 0xce, 0xf9, 0xa0, 0xd3, 0x6c, 0x70, 0xce, 0xa6, 0x33, 0x8e, 0x15, 0xc1, 0xf8, 0x3d,
|
0x4c, 0xed, 0xeb, 0x78, 0x5d, 0x8b, 0x31, 0xd2, 0x21, 0x23, 0x26, 0x8b, 0x9a, 0x29, 0x9e, 0x94,
|
||||||
0x54, 0xe4, 0xcc, 0x36, 0x63, 0x1f, 0x3b, 0xa4, 0x8f, 0x40, 0x1c, 0x41, 0x59, 0xd2, 0xea, 0xa0,
|
0x92, 0x69, 0xc5, 0xd2, 0x84, 0xea, 0x90, 0x99, 0x30, 0x77, 0x14, 0x6a, 0x69, 0x91, 0x3e, 0x14,
|
||||||
0x66, 0xe9, 0x54, 0x54, 0xb3, 0x31, 0x06, 0x7d, 0x39, 0x3f, 0x9c, 0xf9, 0x5e, 0x28, 0xa2, 0xeb,
|
0x71, 0xce, 0x06, 0x9d, 0x66, 0x83, 0x31, 0x3a, 0x9d, 0x31, 0x2c, 0x09, 0xfa, 0xef, 0xa1, 0x2a,
|
||||||
0x62, 0x19, 0xa2, 0x74, 0x45, 0xa5, 0xcb, 0x1a, 0xd7, 0xe4, 0xac, 0xed, 0x08, 0x6f, 0x33, 0x26,
|
0x66, 0xb6, 0x29, 0xfd, 0x5c, 0x71, 0x3f, 0x04, 0x5e, 0xba, 0xa2, 0x14, 0x64, 0x81, 0x67, 0xed,
|
||||||
0xab, 0xfc, 0x99, 0x3a, 0x58, 0xc4, 0xf5, 0x47, 0xb7, 0xe2, 0xa8, 0xd2, 0xbb, 0xc8, 0x7d, 0x59,
|
0x29, 0xaf, 0x02, 0x7d, 0x0c, 0xea, 0x72, 0x7e, 0x38, 0xf3, 0xbd, 0x90, 0x47, 0x57, 0xf9, 0x32,
|
||||||
0xc0, 0x1d, 0x7f, 0x74, 0xdb, 0x12, 0xa0, 0xf1, 0xa3, 0x52, 0x93, 0x81, 0xaf, 0xbe, 0xf2, 0xff,
|
0xf8, 0x91, 0xe7, 0x15, 0x22, 0x6a, 0x43, 0x11, 0xb3, 0x2a, 0x11, 0xde, 0xa6, 0x54, 0x54, 0xc7,
|
||||||
0xce, 0xc4, 0x72, 0xb3, 0xb6, 0x1e, 0xdc, 0x2c, 0x83, 0xc0, 0xee, 0x8a, 0xf3, 0xe8, 0x2b, 0x92,
|
0x73, 0x59, 0x90, 0xc4, 0xf5, 0x47, 0x37, 0xbc, 0xc4, 0xed, 0xdb, 0xc8, 0x7d, 0x99, 0xc3, 0x1d,
|
||||||
0x39, 0xd0, 0xd6, 0x72, 0xf0, 0x02, 0x72, 0xd7, 0xd4, 0x71, 0xe7, 0x41, 0xec, 0x18, 0x25, 0x12,
|
0x7f, 0x74, 0xd3, 0xe2, 0xa0, 0xfe, 0xa3, 0x54, 0xa1, 0x81, 0x2f, 0xbf, 0xf2, 0xff, 0xce, 0xc4,
|
||||||
0xda, 0x56, 0x16, 0x1c, 0x53, 0x8c, 0xff, 0xe6, 0x20, 0x17, 0x81, 0xe8, 0x14, 0xd2, 0x23, 0x7f,
|
0x72, 0xb3, 0xb6, 0xee, 0xdd, 0x2c, 0x9d, 0xc0, 0xce, 0x8a, 0xf3, 0xe8, 0x2b, 0x92, 0x39, 0x50,
|
||||||
0x1c, 0xd7, 0xc1, 0x67, 0xf7, 0xa7, 0xc5, 0x7f, 0x9b, 0xfe, 0x98, 0x61, 0xc9, 0x45, 0x7f, 0x80,
|
0xd6, 0x72, 0xf0, 0x12, 0x72, 0x57, 0xb6, 0xe3, 0xce, 0x83, 0xd8, 0x31, 0x4a, 0x24, 0xb4, 0x2d,
|
||||||
0x6d, 0x21, 0x01, 0x1e, 0x73, 0xc9, 0x7c, 0x36, 0xa6, 0x8b, 0xd4, 0x57, 0x13, 0xb3, 0x9b, 0x8a,
|
0x2d, 0x38, 0xa6, 0xe8, 0xff, 0xc9, 0x41, 0x2e, 0x02, 0xd1, 0x09, 0xa4, 0x47, 0xfe, 0x38, 0x3e,
|
||||||
0x30, 0x94, 0x76, 0x5c, 0x1e, 0x25, 0x87, 0xe8, 0x00, 0x0a, 0x22, 0xdb, 0x2a, 0x13, 0x69, 0x59,
|
0x07, 0x5f, 0xde, 0x9d, 0x16, 0xff, 0x6d, 0xfa, 0x63, 0x8a, 0x05, 0x17, 0xfd, 0x01, 0x2a, 0x5c,
|
||||||
0xfb, 0x79, 0x01, 0xc8, 0x1c, 0x18, 0x50, 0xf6, 0x3d, 0xc7, 0xf7, 0x48, 0x38, 0xa1, 0xe4, 0xf4,
|
0x3a, 0x3c, 0xea, 0x92, 0xf9, 0x6c, 0x6c, 0x2f, 0x52, 0xaf, 0x25, 0x66, 0x37, 0x25, 0x61, 0x28,
|
||||||
0x9b, 0x6f, 0xa5, 0x06, 0x96, 0x70, 0x51, 0x82, 0xfd, 0x09, 0x3d, 0xfd, 0xe6, 0x5b, 0xf4, 0x39,
|
0xec, 0xb8, 0x3c, 0x4a, 0x0e, 0xd1, 0x3e, 0x14, 0x78, 0xb6, 0x65, 0x26, 0xd2, 0xe2, 0xec, 0xe7,
|
||||||
0x14, 0xa5, 0x6e, 0xb0, 0x0f, 0x33, 0x27, 0xb8, 0x93, 0xe2, 0x57, 0xc6, 0x52, 0x4a, 0x4c, 0x89,
|
0x39, 0x20, 0x72, 0xa0, 0x43, 0xd9, 0xf7, 0x1c, 0xdf, 0x23, 0xe1, 0xc4, 0x26, 0x27, 0xdf, 0xbe,
|
||||||
0x88, 0x53, 0x74, 0xed, 0xd2, 0x9b, 0x50, 0x0a, 0x5e, 0x19, 0xab, 0x01, 0x7a, 0x09, 0x7b, 0xd1,
|
0x16, 0xda, 0x59, 0xc2, 0x45, 0x01, 0xf6, 0x27, 0xf6, 0xc9, 0xb7, 0xaf, 0xd1, 0x13, 0x28, 0x0a,
|
||||||
0x1e, 0x90, 0xd0, 0x9f, 0x07, 0x23, 0x46, 0x1c, 0x6f, 0xcc, 0x3e, 0x48, 0x21, 0x2b, 0x63, 0x14,
|
0xbd, 0xa1, 0x9f, 0x66, 0x4e, 0x70, 0x2b, 0x44, 0xb3, 0x8c, 0x85, 0x04, 0x19, 0x02, 0xe1, 0x55,
|
||||||
0xd9, 0xfa, 0xd2, 0x64, 0x09, 0x0b, 0xda, 0x87, 0xec, 0x84, 0x39, 0x37, 0x13, 0x25, 0x4e, 0x65,
|
0x74, 0xe5, 0xda, 0xd7, 0xa1, 0x10, 0xca, 0x32, 0x96, 0x03, 0xf4, 0x0a, 0x76, 0xa3, 0x3d, 0x20,
|
||||||
0x1c, 0x8d, 0x8c, 0xbf, 0x66, 0xa0, 0x98, 0xd8, 0x18, 0x54, 0x82, 0x3c, 0x36, 0xfb, 0x26, 0x7e,
|
0xa1, 0x3f, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x02, 0x58, 0xc6, 0x28, 0xb2, 0xf5,
|
||||||
0x6b, 0xb6, 0xf4, 0x4f, 0x50, 0x1d, 0x9e, 0x5a, 0x76, 0xb3, 0x87, 0xb1, 0xd9, 0x1c, 0x90, 0x1e,
|
0x85, 0xc9, 0xe4, 0x16, 0xb4, 0x07, 0xd9, 0x09, 0x75, 0xae, 0x27, 0x52, 0xd4, 0xca, 0x38, 0x1a,
|
||||||
0x26, 0x43, 0xfb, 0xb5, 0xdd, 0xfb, 0xde, 0x26, 0x97, 0x8d, 0x77, 0x5d, 0xd3, 0x1e, 0x90, 0x96,
|
0xe9, 0x7f, 0xcd, 0x40, 0x31, 0xb1, 0x31, 0xa8, 0x04, 0x79, 0x6c, 0xf4, 0x0d, 0xfc, 0xde, 0x68,
|
||||||
0x39, 0x68, 0x58, 0x9d, 0xbe, 0xae, 0xa1, 0x27, 0x50, 0x5d, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b,
|
0xa9, 0x5f, 0xa0, 0x3a, 0x3c, 0x33, 0xad, 0x66, 0x0f, 0x63, 0xa3, 0x39, 0x20, 0x3d, 0x4c, 0x86,
|
||||||
0xda, 0x03, 0x7d, 0x0b, 0x7d, 0x0e, 0x07, 0x6d, 0xcb, 0x6e, 0x74, 0xc8, 0x92, 0xd3, 0xec, 0x0c,
|
0xd6, 0x5b, 0xab, 0xf7, 0xbd, 0x45, 0x2e, 0x1a, 0x1f, 0xba, 0x86, 0x35, 0x20, 0x2d, 0x63, 0xd0,
|
||||||
0xde, 0x12, 0xf3, 0x87, 0x4b, 0x0b, 0xbf, 0xd3, 0x53, 0x9b, 0x08, 0xe2, 0x4c, 0xc5, 0x1e, 0xd2,
|
0x30, 0x3b, 0x7d, 0x55, 0x41, 0x8f, 0x41, 0x5b, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b, 0x5a, 0x03,
|
||||||
0xe8, 0x31, 0x7c, 0xaa, 0x08, 0x6a, 0x0a, 0x19, 0xf4, 0x7a, 0xa4, 0xdf, 0xeb, 0xd9, 0x7a, 0x06,
|
0x75, 0x0b, 0x3d, 0x81, 0xfd, 0xb6, 0x69, 0x35, 0x3a, 0x64, 0xc9, 0x69, 0x76, 0x06, 0xef, 0x89,
|
||||||
0xed, 0x40, 0xd9, 0xb2, 0xdf, 0x36, 0x3a, 0x56, 0x8b, 0x60, 0xb3, 0xd1, 0xe9, 0xea, 0x59, 0xb4,
|
0xf1, 0xc3, 0x85, 0x89, 0x3f, 0xa8, 0xa9, 0x4d, 0x04, 0x5e, 0x53, 0xb1, 0x87, 0x34, 0x7a, 0x04,
|
||||||
0x0b, 0x95, 0x75, 0x5e, 0x4e, 0xb8, 0x88, 0x79, 0x3d, 0xdb, 0xea, 0xd9, 0xe4, 0xad, 0x89, 0xfb,
|
0x0f, 0x24, 0x41, 0x4e, 0x21, 0x83, 0x5e, 0x8f, 0xf4, 0x7b, 0x3d, 0x4b, 0xcd, 0xa0, 0x6d, 0x28,
|
||||||
0x56, 0xcf, 0xd6, 0xf3, 0x68, 0x1f, 0xd0, 0xaa, 0xe9, 0xbc, 0xdb, 0x68, 0xea, 0x05, 0xf4, 0x29,
|
0x9b, 0xd6, 0xfb, 0x46, 0xc7, 0x6c, 0x11, 0x6c, 0x34, 0x3a, 0x5d, 0x35, 0x8b, 0x76, 0xa0, 0xba,
|
||||||
0xec, 0xac, 0xe2, 0xaf, 0xcd, 0x77, 0x3a, 0xa0, 0x2a, 0xec, 0xa9, 0x85, 0x91, 0x33, 0xb3, 0xd3,
|
0xce, 0xcb, 0x71, 0x17, 0x31, 0xaf, 0x67, 0x99, 0x3d, 0x8b, 0xbc, 0x37, 0x70, 0xdf, 0xec, 0x59,
|
||||||
0xfb, 0x9e, 0x74, 0x2d, 0xdb, 0xea, 0x0e, 0xbb, 0x7a, 0x11, 0xed, 0x81, 0xde, 0x36, 0x4d, 0x62,
|
0x6a, 0x1e, 0xed, 0x01, 0x5a, 0x35, 0x9d, 0x75, 0x1b, 0x4d, 0xb5, 0x80, 0x1e, 0xc0, 0xf6, 0x2a,
|
||||||
0xd9, 0xfd, 0x61, 0xbb, 0x6d, 0x35, 0x2d, 0xd3, 0x1e, 0xe8, 0x25, 0x15, 0x79, 0xd3, 0x87, 0x97,
|
0xfe, 0xd6, 0xf8, 0xa0, 0x02, 0xd2, 0x60, 0x57, 0x2e, 0x8c, 0x9c, 0x1a, 0x9d, 0xde, 0xf7, 0xa4,
|
||||||
0xc5, 0x84, 0xe6, 0x79, 0xc3, 0xb6, 0xcd, 0x0e, 0x69, 0x59, 0xfd, 0xc6, 0x59, 0xc7, 0x6c, 0xe9,
|
0x6b, 0x5a, 0x66, 0x77, 0xd8, 0x55, 0x8b, 0x68, 0x17, 0xd4, 0xb6, 0x61, 0x10, 0xd3, 0xea, 0x0f,
|
||||||
0xdb, 0xe8, 0x10, 0x1e, 0x0f, 0xcc, 0xee, 0x65, 0x0f, 0x37, 0xf0, 0x3b, 0x12, 0xdb, 0xdb, 0x0d,
|
0xdb, 0x6d, 0xb3, 0x69, 0x1a, 0xd6, 0x40, 0x2d, 0xc9, 0xc8, 0x9b, 0x3e, 0xbc, 0xcc, 0x27, 0x34,
|
||||||
0xab, 0x33, 0xc4, 0xa6, 0x5e, 0x41, 0x5f, 0xc0, 0x21, 0x36, 0xdf, 0x0c, 0x2d, 0x6c, 0xb6, 0x88,
|
0xcf, 0x1a, 0x96, 0x65, 0x74, 0x48, 0xcb, 0xec, 0x37, 0x4e, 0x3b, 0x46, 0x4b, 0xad, 0xa0, 0x03,
|
||||||
0xdd, 0x6b, 0x99, 0xa4, 0x6d, 0x36, 0x06, 0x43, 0x6c, 0x92, 0xae, 0xd5, 0xef, 0x5b, 0xf6, 0x77,
|
0x78, 0x34, 0x30, 0xba, 0x17, 0x3d, 0xdc, 0xc0, 0x1f, 0x48, 0x6c, 0x6f, 0x37, 0xcc, 0xce, 0x10,
|
||||||
0xba, 0x8e, 0x9e, 0xc2, 0xd1, 0x82, 0xb2, 0x70, 0xb0, 0xc6, 0xda, 0x11, 0xdf, 0x17, 0xa7, 0xd4,
|
0x1b, 0x6a, 0x15, 0x7d, 0x05, 0x07, 0xd8, 0x78, 0x37, 0x34, 0xb1, 0xd1, 0x22, 0x56, 0xaf, 0x65,
|
||||||
0x36, 0x7f, 0x18, 0x90, 0x4b, 0xd3, 0xc4, 0x3a, 0x42, 0x35, 0xd8, 0x5f, 0x86, 0x57, 0x01, 0xa2,
|
0x90, 0xb6, 0xd1, 0x18, 0x0c, 0xb1, 0x41, 0xba, 0x66, 0xbf, 0x6f, 0x5a, 0xdf, 0xa9, 0x2a, 0x7a,
|
||||||
0xd8, 0xbb, 0xc2, 0x76, 0x69, 0xe2, 0x6e, 0xc3, 0x16, 0x09, 0x5e, 0xb1, 0xed, 0x89, 0x65, 0x2f,
|
0x06, 0x87, 0x0b, 0xca, 0xc2, 0xc1, 0x1a, 0x6b, 0x9b, 0x7f, 0x5f, 0x9c, 0x52, 0xcb, 0xf8, 0x61,
|
||||||
0x6d, 0xeb, 0xcb, 0xfe, 0x14, 0x21, 0xd8, 0x4e, 0x64, 0xa5, 0xdd, 0xc0, 0xfa, 0x3e, 0xda, 0x83,
|
0x40, 0x2e, 0x0c, 0x03, 0xab, 0x08, 0xd5, 0x60, 0x6f, 0x19, 0x5e, 0x06, 0x88, 0x62, 0xef, 0x70,
|
||||||
0x4a, 0xbc, 0x82, 0x98, 0xf8, 0xaf, 0x1c, 0x7a, 0x04, 0x68, 0x68, 0x63, 0xb3, 0xd1, 0x12, 0x1b,
|
0xdb, 0x85, 0x81, 0xbb, 0x0d, 0x8b, 0x27, 0x78, 0xc5, 0xb6, 0xcb, 0x97, 0xbd, 0xb4, 0xad, 0x2f,
|
||||||
0xb2, 0x30, 0xfc, 0x3b, 0x77, 0x91, 0xce, 0x6f, 0xe9, 0x29, 0xe3, 0x1f, 0x29, 0x28, 0xaf, 0x9c,
|
0xfb, 0x01, 0x42, 0x50, 0x49, 0x64, 0xa5, 0xdd, 0xc0, 0xea, 0x1e, 0xda, 0x85, 0x6a, 0xbc, 0x82,
|
||||||
0x4b, 0xf4, 0x04, 0x0a, 0xa1, 0x73, 0xe3, 0x51, 0x2e, 0x94, 0x43, 0x89, 0xca, 0x12, 0x90, 0xb7,
|
0x98, 0xf8, 0xcf, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x1b, 0xb2, 0x30, 0xfc,
|
||||||
0xf3, 0x84, 0x3a, 0x9e, 0x52, 0x33, 0xa5, 0xfb, 0x05, 0x89, 0x48, 0x2d, 0x3b, 0x80, 0x5c, 0xdc,
|
0x2b, 0x77, 0x9e, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0xd4, 0x25, 0x7a, 0x0c,
|
||||||
0x09, 0xa4, 0x16, 0x9d, 0x40, 0x76, 0xa4, 0x3a, 0x80, 0x27, 0x50, 0x10, 0x92, 0x19, 0x72, 0x3a,
|
0x85, 0xd0, 0xb9, 0xf6, 0xc4, 0x35, 0x15, 0x89, 0xca, 0x12, 0x10, 0xb7, 0xfa, 0xc4, 0x76, 0x3c,
|
||||||
0x9d, 0xc9, 0x23, 0x5e, 0xc6, 0x4b, 0x00, 0x7d, 0x09, 0xe5, 0x29, 0x0b, 0x43, 0x7a, 0xc3, 0x88,
|
0xa9, 0x66, 0x52, 0xf7, 0x0b, 0x02, 0x11, 0x5a, 0xb6, 0x0f, 0xb9, 0xb8, 0x83, 0x48, 0x2d, 0x3a,
|
||||||
0x3a, 0xa6, 0x20, 0x19, 0xa5, 0x08, 0x6c, 0xcb, 0xd3, 0xfa, 0x25, 0xc4, 0xb2, 0x11, 0x91, 0x32,
|
0x88, 0xec, 0x48, 0x76, 0x0e, 0x8f, 0xa1, 0xc0, 0x25, 0x33, 0x64, 0xf6, 0x74, 0x26, 0x4a, 0xbc,
|
||||||
0x8a, 0x14, 0x81, 0x8a, 0xb4, 0xae, 0xd8, 0x9c, 0x46, 0x6a, 0x90, 0x54, 0x6c, 0x4e, 0xd1, 0x73,
|
0x8c, 0x97, 0x00, 0x7a, 0x0a, 0xe5, 0x29, 0x0d, 0x43, 0xfb, 0x9a, 0x12, 0x59, 0xa6, 0x20, 0x18,
|
||||||
0xd8, 0x51, 0x92, 0xe3, 0x78, 0xce, 0x74, 0x3e, 0x55, 0xd2, 0x93, 0x93, 0xd2, 0x53, 0x91, 0xd2,
|
0xa5, 0x08, 0x6c, 0x8b, 0x6a, 0x7d, 0x0a, 0xb1, 0x6c, 0x44, 0xa4, 0x8c, 0x24, 0x45, 0xa0, 0x24,
|
||||||
0xa3, 0x70, 0xa9, 0x40, 0x8f, 0x21, 0x7f, 0x45, 0x43, 0x26, 0x2e, 0x8b, 0x48, 0x1a, 0x72, 0x62,
|
0xad, 0x2b, 0x36, 0xb3, 0x23, 0x35, 0x48, 0x2a, 0x36, 0xb3, 0xd1, 0x0b, 0xd8, 0x96, 0x92, 0xe3,
|
||||||
0xdc, 0x66, 0x4c, 0x98, 0xc4, 0x15, 0x12, 0x08, 0xd1, 0x53, 0x8a, 0x90, 0xbb, 0x66, 0x0c, 0x8b,
|
0x78, 0xce, 0x74, 0x3e, 0x95, 0xd2, 0x93, 0x13, 0xd2, 0x53, 0x15, 0xd2, 0x23, 0x71, 0xa1, 0x40,
|
||||||
0xbd, 0x5c, 0x44, 0xa0, 0x1f, 0x96, 0x11, 0x8a, 0x89, 0x08, 0x0a, 0x97, 0x11, 0x9e, 0xc3, 0x0e,
|
0x8f, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x5f, 0x16, 0x91, 0x34, 0xe4, 0xf8, 0xb8, 0x4d, 0x29, 0x37,
|
||||||
0xfb, 0xc0, 0x03, 0x4a, 0xfc, 0x19, 0xfd, 0x69, 0xce, 0xc8, 0x98, 0x72, 0x2a, 0x5b, 0xcb, 0x12,
|
0xf1, 0x2b, 0x24, 0xe0, 0xa2, 0x27, 0x15, 0x21, 0x77, 0x45, 0x29, 0xe6, 0x7b, 0xb9, 0x88, 0x60,
|
||||||
0xae, 0x48, 0x43, 0x4f, 0xe2, 0x2d, 0xca, 0xa9, 0xf1, 0x04, 0x6a, 0x98, 0x85, 0x8c, 0x77, 0x9d,
|
0x7f, 0x5a, 0x46, 0x28, 0x26, 0x22, 0x48, 0x5c, 0x44, 0x78, 0x01, 0xdb, 0xf4, 0x13, 0x0b, 0x6c,
|
||||||
0x30, 0x74, 0x7c, 0xaf, 0xe9, 0x7b, 0x3c, 0xf0, 0xdd, 0xe8, 0xce, 0x31, 0x0e, 0xe1, 0x60, 0xa3,
|
0xe2, 0xcf, 0xec, 0x9f, 0xe6, 0x94, 0x8c, 0x6d, 0x66, 0x8b, 0x96, 0xb4, 0x84, 0xab, 0xc2, 0xd0,
|
||||||
0x55, 0x5d, 0x1a, 0x62, 0xf2, 0x9b, 0x39, 0x0b, 0xee, 0x36, 0x4f, 0x7e, 0x03, 0x07, 0x1b, 0xad,
|
0x13, 0x78, 0xcb, 0x66, 0xb6, 0xfe, 0x18, 0x6a, 0x98, 0x86, 0x94, 0x75, 0x9d, 0x30, 0x74, 0x7c,
|
||||||
0xd1, 0x8d, 0xf3, 0x02, 0x32, 0x33, 0xea, 0x04, 0x61, 0x75, 0x4b, 0xde, 0xda, 0xfb, 0x2b, 0x4d,
|
0xaf, 0xe9, 0x7b, 0x2c, 0xf0, 0xdd, 0xe8, 0xce, 0xd1, 0x0f, 0x60, 0x7f, 0xa3, 0x55, 0x5e, 0x1a,
|
||||||
0x82, 0x13, 0x9c, 0x3b, 0x21, 0xf7, 0x83, 0x3b, 0xac, 0x48, 0x17, 0xe9, 0xbc, 0xa6, 0x6f, 0x19,
|
0x7c, 0xf2, 0xbb, 0x39, 0x0d, 0x6e, 0x37, 0x4f, 0x7e, 0x07, 0xfb, 0x1b, 0xad, 0xd1, 0x8d, 0xf3,
|
||||||
0x7f, 0xd2, 0xa0, 0x98, 0x30, 0x8a, 0x3a, 0xf0, 0xfc, 0x31, 0x23, 0xd7, 0x81, 0x3f, 0x8d, 0x2b,
|
0x12, 0x32, 0x33, 0xdb, 0x09, 0x42, 0x6d, 0x4b, 0xdc, 0xda, 0x7b, 0x2b, 0x4d, 0x82, 0x13, 0x9c,
|
||||||
0x6c, 0x01, 0xa0, 0x2a, 0xe4, 0xe4, 0x80, 0xfb, 0x51, 0x79, 0xc5, 0x43, 0xf4, 0x35, 0xe4, 0x26,
|
0x39, 0x21, 0xf3, 0x83, 0x5b, 0x2c, 0x49, 0xe7, 0xe9, 0xbc, 0xa2, 0x6e, 0xe9, 0x7f, 0x52, 0xa0,
|
||||||
0xca, 0x85, 0xcc, 0x52, 0xf1, 0x74, 0x77, 0x2d, 0xba, 0xd8, 0x1b, 0x1c, 0x73, 0x2e, 0xd2, 0xf9,
|
0x98, 0x30, 0xf2, 0x73, 0xe0, 0xf9, 0x63, 0x4a, 0xae, 0x02, 0x7f, 0x1a, 0x9f, 0xb0, 0x05, 0x80,
|
||||||
0x94, 0x9e, 0xbe, 0x48, 0xe7, 0xd3, 0x7a, 0xe6, 0x22, 0x9d, 0xcf, 0xe8, 0xd9, 0x8b, 0x74, 0x3e,
|
0x34, 0xc8, 0x89, 0x01, 0xf3, 0xa3, 0xe3, 0x15, 0x0f, 0xd1, 0x37, 0x90, 0x9b, 0x48, 0x17, 0x22,
|
||||||
0xab, 0xe7, 0x8c, 0xff, 0x68, 0x90, 0x8f, 0xd9, 0x62, 0x2d, 0x42, 0xe2, 0x89, 0xa8, 0x8c, 0xa8,
|
0x4b, 0xc5, 0x93, 0x9d, 0xb5, 0xe8, 0x7c, 0x6f, 0x70, 0xcc, 0x39, 0x4f, 0xe7, 0x53, 0x6a, 0xfa,
|
||||||
0x01, 0x58, 0x02, 0xc8, 0x80, 0x92, 0x1c, 0xac, 0xf6, 0x15, 0x2b, 0x18, 0x7a, 0x0a, 0xe5, 0xc5,
|
0x3c, 0x9d, 0x4f, 0xab, 0x99, 0xf3, 0x74, 0x3e, 0xa3, 0x66, 0xcf, 0xd3, 0xf9, 0xac, 0x9a, 0xd3,
|
||||||
0x78, 0x71, 0x79, 0xa5, 0xf0, 0x2a, 0x28, 0x3c, 0x85, 0xf3, 0xd1, 0x88, 0x85, 0xa1, 0x0a, 0x95,
|
0xff, 0xad, 0x40, 0x3e, 0x66, 0xf3, 0xb5, 0x70, 0x89, 0x27, 0xfc, 0x64, 0x44, 0x0d, 0xc0, 0x12,
|
||||||
0x51, 0x9e, 0x92, 0x18, 0xaa, 0x43, 0x25, 0x1e, 0xc7, 0x01, 0xb3, 0x92, 0xb6, 0x0e, 0xa3, 0xe7,
|
0x40, 0x3a, 0x94, 0xc4, 0x60, 0xb5, 0xaf, 0x58, 0xc1, 0xd0, 0x33, 0x28, 0x2f, 0xc6, 0x8b, 0xcb,
|
||||||
0xa0, 0x27, 0xa1, 0xe9, 0xb2, 0x8d, 0xbf, 0x87, 0xab, 0x6d, 0x30, 0xa6, 0xf0, 0x48, 0xa6, 0xf5,
|
0x2b, 0x85, 0x57, 0x41, 0xee, 0x29, 0x9c, 0x8f, 0x46, 0x34, 0x0c, 0x65, 0xa8, 0x8c, 0xf4, 0x94,
|
||||||
0x32, 0xf0, 0xaf, 0xe8, 0x95, 0xe3, 0x3a, 0xfc, 0x2e, 0x6e, 0x51, 0xc4, 0x16, 0x04, 0xfe, 0x94,
|
0xc4, 0x50, 0x1d, 0xaa, 0xf1, 0x38, 0x0e, 0x98, 0x15, 0xb4, 0x75, 0x18, 0xbd, 0x00, 0x35, 0x09,
|
||||||
0x78, 0xf1, 0x9d, 0x5f, 0xc2, 0x4b, 0x40, 0xa4, 0x83, 0xfb, 0xca, 0x16, 0xa5, 0x23, 0x1a, 0x8a,
|
0x4d, 0x97, 0xed, 0xff, 0x1d, 0x5c, 0x6e, 0x83, 0x3e, 0x85, 0x87, 0x22, 0xad, 0x17, 0x81, 0x7f,
|
||||||
0xe6, 0x63, 0x11, 0x3c, 0x25, 0x83, 0x2f, 0xc6, 0xc6, 0x2d, 0x54, 0xef, 0x87, 0x8b, 0x4a, 0xe8,
|
0x69, 0x5f, 0x3a, 0xae, 0xc3, 0x6e, 0xe3, 0x16, 0x85, 0x6f, 0x41, 0xe0, 0x4f, 0x89, 0x17, 0xdf,
|
||||||
0x08, 0x8a, 0xb3, 0x25, 0x2c, 0x23, 0x6a, 0x38, 0x09, 0x25, 0x13, 0xbd, 0xf5, 0xcb, 0x89, 0x36,
|
0xf9, 0x25, 0xbc, 0x04, 0x78, 0x3a, 0x98, 0x2f, 0x6d, 0x51, 0x3a, 0xa2, 0x21, 0x6f, 0x3e, 0x16,
|
||||||
0xfe, 0xac, 0xc1, 0xce, 0xd9, 0xdc, 0x71, 0xc7, 0x2b, 0x9d, 0x57, 0xf2, 0x85, 0xa6, 0xad, 0xbe,
|
0xc1, 0x53, 0x22, 0xf8, 0x62, 0xac, 0xdf, 0x80, 0x76, 0x37, 0x5c, 0x74, 0x84, 0x0e, 0xa1, 0x38,
|
||||||
0xd0, 0x36, 0x3d, 0xbf, 0xb6, 0x36, 0x3e, 0xbf, 0x36, 0x3d, 0x71, 0x52, 0x0f, 0x3e, 0x71, 0x3e,
|
0x5b, 0xc2, 0x22, 0xa2, 0x82, 0x93, 0x50, 0x32, 0xd1, 0x5b, 0xbf, 0x9c, 0x68, 0xfd, 0xcf, 0x0a,
|
||||||
0x87, 0xe2, 0xf2, 0x75, 0xa3, 0x1a, 0xdb, 0x12, 0x86, 0x49, 0xfc, 0xb4, 0x09, 0x8d, 0x57, 0x80,
|
0x6c, 0x9f, 0xce, 0x1d, 0x77, 0xbc, 0xd2, 0x79, 0x25, 0x5f, 0x76, 0xca, 0xea, 0xcb, 0x6e, 0xd3,
|
||||||
0x92, 0x0b, 0x8d, 0x36, 0x64, 0xd1, 0x00, 0x6a, 0x0f, 0x36, 0x80, 0xcf, 0xff, 0xae, 0x41, 0x29,
|
0xb3, 0x6d, 0x6b, 0xe3, 0xb3, 0x6d, 0xd3, 0xd3, 0x28, 0x75, 0xef, 0xd3, 0xe8, 0x09, 0x14, 0x97,
|
||||||
0xd9, 0x85, 0xa3, 0x32, 0x14, 0x2c, 0x9b, 0xb4, 0x3b, 0xd6, 0x77, 0xe7, 0x03, 0xfd, 0x13, 0x31,
|
0xaf, 0x22, 0xd9, 0xd8, 0x96, 0x30, 0x4c, 0xe2, 0x27, 0x51, 0xa8, 0xbf, 0x01, 0x94, 0x5c, 0x68,
|
||||||
0xec, 0x0f, 0x9b, 0x4d, 0xd3, 0x6c, 0x99, 0x2d, 0x5d, 0x13, 0xf7, 0x83, 0x90, 0x7a, 0xb3, 0x45,
|
0xb4, 0x21, 0x8b, 0x06, 0x50, 0xb9, 0xb7, 0x01, 0x7c, 0xf1, 0x77, 0x05, 0x4a, 0xc9, 0x2e, 0x1c,
|
||||||
0x06, 0x56, 0xd7, 0xec, 0x0d, 0x45, 0xe7, 0xb0, 0x0b, 0x95, 0x08, 0xb3, 0x7b, 0x04, 0xf7, 0x86,
|
0x95, 0xa1, 0x60, 0x5a, 0xa4, 0xdd, 0x31, 0xbf, 0x3b, 0x1b, 0xa8, 0x5f, 0xf0, 0x61, 0x7f, 0xd8,
|
||||||
0x03, 0x53, 0x4f, 0x21, 0x1d, 0x4a, 0x11, 0x68, 0x62, 0xdc, 0xc3, 0x7a, 0x5a, 0x5c, 0x77, 0x11,
|
0x6c, 0x1a, 0x46, 0xcb, 0x68, 0xa9, 0x0a, 0xbf, 0x1f, 0xb8, 0xd4, 0x1b, 0x2d, 0x32, 0x30, 0xbb,
|
||||||
0x72, 0xbf, 0x0b, 0x89, 0x9b, 0x94, 0x8c, 0xec, 0x32, 0x62, 0xd6, 0xf2, 0x82, 0x26, 0x67, 0x8d,
|
0x46, 0x6f, 0xc8, 0x3b, 0x87, 0x1d, 0xa8, 0x46, 0x98, 0xd5, 0x23, 0xb8, 0x37, 0x1c, 0x18, 0x6a,
|
||||||
0x4e, 0xc3, 0x6e, 0x9a, 0x7a, 0xf6, 0xf4, 0x6f, 0x19, 0xc8, 0xca, 0x2f, 0x08, 0xd0, 0x39, 0x14,
|
0x0a, 0xa9, 0x50, 0x8a, 0x40, 0x03, 0xe3, 0x1e, 0x56, 0xd3, 0xfc, 0xba, 0x8b, 0x90, 0xbb, 0x5d,
|
||||||
0x13, 0x0f, 0x2b, 0x74, 0xf8, 0xd1, 0x07, 0x57, 0xad, 0xba, 0xf9, 0xdd, 0x31, 0x0f, 0x5f, 0x6a,
|
0x48, 0xdc, 0xa4, 0x64, 0x44, 0x97, 0x11, 0xb3, 0x96, 0x17, 0x34, 0x39, 0x6d, 0x74, 0x1a, 0x56,
|
||||||
0xe8, 0x02, 0x4a, 0xc9, 0xe7, 0x0e, 0x4a, 0xf6, 0xa6, 0x1b, 0xde, 0x41, 0x1f, 0xf5, 0xf5, 0x1a,
|
0xd3, 0x50, 0xb3, 0x27, 0x7f, 0xcb, 0x40, 0x56, 0x7c, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x78, 0x90,
|
||||||
0x74, 0x33, 0xe4, 0xce, 0x54, 0xf4, 0xa2, 0xd1, 0xeb, 0x00, 0xd5, 0x12, 0xfc, 0xb5, 0x27, 0x47,
|
0xa1, 0x83, 0xcf, 0x3e, 0xd4, 0x6a, 0xda, 0xe6, 0x77, 0xc7, 0x3c, 0x7c, 0xa5, 0xa0, 0x73, 0x28,
|
||||||
0xed, 0x60, 0xa3, 0x2d, 0x4a, 0x61, 0x47, 0x7d, 0x62, 0xd4, 0x9f, 0xdf, 0xfb, 0xc4, 0xd5, 0x47,
|
0x25, 0x9f, 0x3b, 0x28, 0xd9, 0x9b, 0x6e, 0x78, 0x07, 0x7d, 0xd6, 0xd7, 0x5b, 0x50, 0x8d, 0x90,
|
||||||
0x41, 0xed, 0xb3, 0x87, 0xcc, 0x91, 0xb7, 0x31, 0xec, 0x6e, 0x10, 0x70, 0xf4, 0xab, 0xe4, 0x0a,
|
0x39, 0x53, 0xde, 0x8b, 0x46, 0xaf, 0x03, 0x54, 0x4b, 0xf0, 0xd7, 0x9e, 0x1c, 0xb5, 0xfd, 0x8d,
|
||||||
0x1e, 0x94, 0xff, 0xda, 0xb3, 0x5f, 0xa2, 0x2d, 0xa3, 0x6c, 0x50, 0xfa, 0x95, 0x28, 0x0f, 0xdf,
|
0xb6, 0x28, 0x85, 0x1d, 0xf9, 0x89, 0x51, 0x7f, 0x7e, 0xe7, 0x13, 0x57, 0x1f, 0x05, 0xb5, 0x2f,
|
||||||
0x13, 0x2b, 0x51, 0x3e, 0x76, 0x61, 0xfc, 0x08, 0xfa, 0xba, 0x12, 0x20, 0x63, 0x7d, 0xee, 0x7d,
|
0xef, 0x33, 0x47, 0xde, 0xc6, 0xb0, 0xb3, 0x41, 0xc0, 0xd1, 0xaf, 0x92, 0x2b, 0xb8, 0x57, 0xfe,
|
||||||
0x55, 0xaa, 0x7d, 0xf9, 0x51, 0x4e, 0xe4, 0xdc, 0x02, 0x58, 0x9e, 0x27, 0xf4, 0x24, 0x31, 0xe5,
|
0x6b, 0xcf, 0x7f, 0x89, 0xb6, 0x8c, 0xb2, 0x41, 0xe9, 0x57, 0xa2, 0xdc, 0x7f, 0x4f, 0xac, 0x44,
|
||||||
0x9e, 0x1e, 0xd4, 0x0e, 0x1f, 0xb0, 0x2a, 0x57, 0x67, 0xbf, 0xfe, 0xe3, 0xc9, 0x8d, 0xc3, 0x27,
|
0xf9, 0xdc, 0x85, 0xf1, 0x23, 0xa8, 0xeb, 0x4a, 0x80, 0xf4, 0xf5, 0xb9, 0x77, 0x55, 0xa9, 0xf6,
|
||||||
0xf3, 0xab, 0xe3, 0x91, 0x3f, 0x3d, 0x71, 0x45, 0x47, 0xef, 0x39, 0xde, 0x8d, 0xc7, 0xf8, 0xcf,
|
0xf4, 0xb3, 0x9c, 0xc8, 0xb9, 0x09, 0xb0, 0xac, 0x27, 0xf4, 0x38, 0x31, 0xe5, 0x8e, 0x1e, 0xd4,
|
||||||
0x7e, 0x70, 0x7b, 0xe2, 0x7a, 0xe3, 0x13, 0x79, 0x2c, 0x4f, 0x16, 0x5e, 0xae, 0xb2, 0xf2, 0xff,
|
0x0e, 0xee, 0xb1, 0x4a, 0x57, 0xa7, 0xbf, 0xfe, 0xe3, 0xf1, 0xb5, 0xc3, 0x26, 0xf3, 0xcb, 0xa3,
|
||||||
0x48, 0xbf, 0xf9, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x56, 0xfb, 0xd9, 0x67, 0x77, 0x12, 0x00,
|
0x91, 0x3f, 0x3d, 0x76, 0x79, 0x47, 0xef, 0x39, 0xde, 0xb5, 0x47, 0xd9, 0xcf, 0x7e, 0x70, 0x73,
|
||||||
0x00,
|
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.
|
// 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.
|
/// If set, circular payments to self are permitted.
|
||||||
bool allow_self_payment = 15;
|
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 {
|
message TrackPaymentRequest {
|
||||||
|
|||||||
@@ -602,6 +602,8 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||||||
payIntent.RouteHints = append(
|
payIntent.RouteHints = append(
|
||||||
payIntent.RouteHints, payReq.RouteHints...,
|
payIntent.RouteHints, payReq.RouteHints...,
|
||||||
)
|
)
|
||||||
|
payIntent.DestFeatures = payReq.Features
|
||||||
|
payIntent.PaymentAddr = payReq.PaymentAddr
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, If the payment request field was not specified
|
// Otherwise, If the payment request field was not specified
|
||||||
// (and a custom route wasn't specified), construct the payment
|
// (and a custom route wasn't specified), construct the payment
|
||||||
@@ -631,6 +633,14 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||||||
|
|
||||||
// Payment hash.
|
// Payment hash.
|
||||||
copy(payIntent.PaymentHash[:], rpcPayReq.PaymentHash)
|
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
|
// Currently, within the bootstrap phase of the network, we limit the
|
||||||
@@ -697,6 +707,21 @@ func unmarshallHopHint(rpcHint *lnrpc.HopHint) (zpay32.HopHint, error) {
|
|||||||
}, nil
|
}, 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
|
// ValidatePayReqExpiry checks if the passed payment request has expired. In
|
||||||
// the case it has expired, an error will be returned.
|
// the case it has expired, an error will be returned.
|
||||||
func ValidatePayReqExpiry(payReq *zpay32.Invoice) error {
|
func ValidatePayReqExpiry(payReq *zpay32.Invoice) error {
|
||||||
|
|||||||
+681
-583
File diff suppressed because it is too large.
Load diff
@@ -967,6 +967,13 @@ message SendRequest {
|
|||||||
|
|
||||||
/// If set, circular payments to self are permitted.
|
/// If set, circular payments to self are permitted.
|
||||||
bool allow_self_payment = 14;
|
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 {
|
message SendResponse {
|
||||||
@@ -2696,6 +2703,26 @@ message PayReq {
|
|||||||
map<uint32, Feature> features = 13 [json_name = "features"];
|
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 {
|
message Feature {
|
||||||
string name = 2 [json_name = "name"];
|
string name = 2 [json_name = "name"];
|
||||||
bool is_required = 3 [json_name = "is_required"];
|
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": {
|
"lnrpcFeeLimit": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -3845,6 +3868,13 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"format": "boolean",
|
"format": "boolean",
|
||||||
"description": "/ If set, circular payments to self are permitted."
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+68
-61
@@ -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
|
// testSphinxReplayPersistence verifies that replayed onion packets are rejected
|
||||||
// by a remote peer after a restart. We use a combination of unsafe
|
// 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
|
// 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
|
// Now that the channel is open, create an invoice for Dave which
|
||||||
// expects a payment of 1000 satoshis from Carol paid via a particular
|
// expects a payment of 1000 satoshis from Carol paid via a particular
|
||||||
// preimage.
|
// preimage.
|
||||||
@@ -3844,8 +3859,12 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
// With the payment sent but hedl, all balance related stats should not
|
// With the payment sent but hedl, all balance related stats should not
|
||||||
// have changed.
|
// have changed.
|
||||||
time.Sleep(time.Millisecond * 200)
|
err = wait.InvariantNoError(
|
||||||
assertAmountSent(0)
|
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
|
// With the first payment sent, restart dave to make sure he is
|
||||||
// persisting the information required to detect replayed sphinx
|
// 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
|
// Since the payment failed, the balance should still be left
|
||||||
// unaltered.
|
// 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)
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
closeChannelAndAssert(ctxt, t, net, carol, chanPoint, true)
|
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
|
// Now that the channel is open, create an invoice for Bob which
|
||||||
// expects a payment of 1000 satoshis from Alice paid via a particular
|
// expects a payment of 1000 satoshis from Alice paid via a particular
|
||||||
// preimage.
|
// preimage.
|
||||||
@@ -3989,8 +3986,13 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
// With the payment completed all balance related stats should be
|
// With the payment completed all balance related stats should be
|
||||||
// properly updated.
|
// properly updated.
|
||||||
time.Sleep(time.Millisecond * 200)
|
err = wait.NoError(
|
||||||
assertAmountSent(paymentAmt)
|
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
|
// Create another invoice for Bob, this time leaving off the preimage
|
||||||
// to one will be randomly generated. We'll test the proper
|
// 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
|
// The second payment should also have succeeded, with the balances
|
||||||
// being update accordingly.
|
// being update accordingly.
|
||||||
time.Sleep(time.Millisecond * 200)
|
err = wait.NoError(
|
||||||
assertAmountSent(paymentAmt * 2)
|
assertAmountSent(2*paymentAmt, net.Alice, net.Bob),
|
||||||
|
3*time.Second,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
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",
|
TLVOnionPayloadOptional: "tlv-onion",
|
||||||
StaticRemoteKeyOptional: "static-remote-key",
|
StaticRemoteKeyOptional: "static-remote-key",
|
||||||
StaticRemoteKeyRequired: "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
|
// 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
|
// 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
|
// 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
|
// bit has the same meaning as its corresponding even/odd bit, which is set
|
||||||
|
|||||||
+184
-75
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/coreos/bbolt"
|
"github.com/coreos/bbolt"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/feature"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/record"
|
"github.com/lightningnetwork/lnd/record"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
@@ -69,6 +70,11 @@ var (
|
|||||||
errNoTlvPayload = errors.New("destination hop doesn't " +
|
errNoTlvPayload = errors.New("destination hop doesn't " +
|
||||||
"understand new TLV payloads")
|
"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
|
// errNoPathFound is returned when a path to the target destination does
|
||||||
// not exist in the graph.
|
// not exist in the graph.
|
||||||
errNoPathFound = errors.New("unable to find a path to destination")
|
errNoPathFound = errors.New("unable to find a path to destination")
|
||||||
@@ -90,17 +96,30 @@ type edgePolicyWithSource struct {
|
|||||||
edge *channeldb.ChannelEdgePolicy
|
edge *channeldb.ChannelEdgePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRoute returns a fully valid route between the source and target that's
|
// finalHopParams encapsulates various parameters for route construction that
|
||||||
// capable of supporting a payment of `amtToSend` after fees are fully
|
// apply to the final hop in a route. These features include basic payment data
|
||||||
// computed. If the route is too long, or the selected path cannot support the
|
// such as amounts and cltvs, as well as more complex features like destination
|
||||||
// fully payment including fees, then a non-nil error is returned.
|
// 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
|
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
|
||||||
// the source to the target node of the path finding attempt.
|
// the source to the target node of the path finding attempt. It is assumed that
|
||||||
func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
// any feature vectors on all hops have been validated for transitive
|
||||||
|
// dependencies.
|
||||||
|
func newRoute(sourceVertex route.Vertex,
|
||||||
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
|
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
|
||||||
finalCLTVDelta uint16,
|
finalHop finalHopParams) (*route.Route, error) {
|
||||||
destCustomRecords record.CustomSet) (*route.Route, error) {
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hops []*route.Hop
|
hops []*route.Hop
|
||||||
@@ -123,18 +142,73 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
|||||||
// payload for the hop this edge is leading to.
|
// payload for the hop this edge is leading to.
|
||||||
edge := pathEdges[i]
|
edge := pathEdges[i]
|
||||||
|
|
||||||
// If this is the last hop, then the hop payload will contain
|
// We'll calculate the amounts, timelocks, and fees for each hop
|
||||||
// the exact amount. In BOLT #4: Onion Routing
|
// in the route. The base case is the final hop which includes
|
||||||
// Protocol / "Payload for the Last Node", this is detailed.
|
// their amount and timelocks. These values will accumulate
|
||||||
amtToForward := amtToSend
|
// 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
|
// Define a helper function that checks this edge's feature
|
||||||
// reporting through RPC. Set to zero for the final hop.
|
// vector for support for a given feature. We assume at this
|
||||||
fee := lnwire.MilliSatoshi(0)
|
// 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
|
// We start by assuming the node doesn't support TLV. We'll now
|
||||||
// to pay for transit over the next link.
|
// inspect the node's feature vector to see if we can promote
|
||||||
if i != len(pathEdges)-1 {
|
// 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
|
// The amount that the current hop needs to forward is
|
||||||
// equal to the incoming amount of the next hop.
|
// equal to the incoming amount of the next hop.
|
||||||
amtToForward = nextIncomingAmount
|
amtToForward = nextIncomingAmount
|
||||||
@@ -145,32 +219,12 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
|||||||
// is stored as part of the incoming channel of
|
// is stored as part of the incoming channel of
|
||||||
// the next hop.
|
// the next hop.
|
||||||
fee = pathEdges[i+1].ComputeFee(amtToForward)
|
fee = pathEdges[i+1].ComputeFee(amtToForward)
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the last hop, then for verification purposes, the
|
// We'll take the total timelock of the preceding hop as
|
||||||
// value of the outgoing time-lock should be _exactly_ the
|
// the outgoing timelock or this hop. Then we'll
|
||||||
// absolute time out they'd expect in the HTLC.
|
// increment the total timelock incurred by this hop.
|
||||||
var outgoingTimeLock uint32
|
outgoingTimeLock = totalTimeLock
|
||||||
if i == len(pathEdges)-1 {
|
totalTimeLock += uint32(pathEdges[i+1].TimeLockDelta)
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we're traversing the path backwards atm, we prepend
|
// 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,
|
ChannelID: edge.ChannelID,
|
||||||
AmtToForward: amtToForward,
|
AmtToForward: amtToForward,
|
||||||
OutgoingTimeLock: outgoingTimeLock,
|
OutgoingTimeLock: outgoingTimeLock,
|
||||||
LegacyPayload: true,
|
LegacyPayload: !tlvPayload,
|
||||||
}
|
CustomRecords: customRecords,
|
||||||
|
MPP: mpp,
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hops = append([]*route.Hop{currentHop}, hops...)
|
hops = append([]*route.Hop{currentHop}, hops...)
|
||||||
@@ -292,6 +330,16 @@ type RestrictParams struct {
|
|||||||
// DestCustomRecords contains the custom records to drop off at the
|
// DestCustomRecords contains the custom records to drop off at the
|
||||||
// final hop, if any.
|
// final hop, if any.
|
||||||
DestCustomRecords record.CustomSet
|
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
|
// 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()
|
defer tx.Rollback()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.DestCustomRecords) > 0 {
|
// If no destination features are provided, we will load what features
|
||||||
// Check if the target has TLV enabled
|
// we have for the target node from our graph.
|
||||||
|
features := r.DestFeatures
|
||||||
|
if features == nil {
|
||||||
targetKey, err := btcec.ParsePubKey(target[:], btcec.S256())
|
targetKey, err := btcec.ParsePubKey(target[:], btcec.S256())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetNode, err := g.graph.FetchLightningNode(targetKey)
|
targetNode, err := g.graph.FetchLightningNode(targetKey)
|
||||||
if err != nil {
|
switch {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetNode.Features != nil {
|
// If the node exists and has features, use them directly.
|
||||||
supportsTLV := targetNode.Features.HasFeature(
|
case err == nil:
|
||||||
lnwire.TLVOnionPayloadOptional,
|
features = targetNode.Features
|
||||||
)
|
|
||||||
if !supportsTLV {
|
// If an error other than the node not existing is hit, abort.
|
||||||
return nil, errNoTlvPayload
|
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
|
// If we are routing from ourselves, check that we have enough local
|
||||||
// balance available.
|
// balance available.
|
||||||
if source == self {
|
if source == self {
|
||||||
@@ -597,6 +676,36 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
edge.ChannelID)
|
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
|
// All conditions are met and this new tentative distance is
|
||||||
// better than the current best known distance to this node.
|
// better than the current best known distance to this node.
|
||||||
// The new better distance is recorded, and also our "next hop"
|
// The new better distance is recorded, and also our "next hop"
|
||||||
|
|||||||
+484
-33
@@ -13,6 +13,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -22,7 +23,9 @@ import (
|
|||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/feature"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/record"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
"github.com/lightningnetwork/lnd/zpay32"
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
)
|
)
|
||||||
@@ -58,6 +61,25 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
testPathFindingConfig = &PathFindingConfig{}
|
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 (
|
var (
|
||||||
@@ -318,6 +340,7 @@ type testChannelPolicy struct {
|
|||||||
LastUpdate time.Time
|
LastUpdate time.Time
|
||||||
Disabled bool
|
Disabled bool
|
||||||
Direction bool
|
Direction bool
|
||||||
|
Features *lnwire.FeatureVector
|
||||||
}
|
}
|
||||||
|
|
||||||
type testChannelEnd struct {
|
type testChannelEnd struct {
|
||||||
@@ -325,7 +348,7 @@ type testChannelEnd struct {
|
|||||||
*testChannelPolicy
|
*testChannelPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func symmetricTestChannel(alias1 string, alias2 string, capacity btcutil.Amount,
|
func symmetricTestChannel(alias1, alias2 string, capacity btcutil.Amount,
|
||||||
policy *testChannelPolicy, chanID ...uint64) *testChannel {
|
policy *testChannelPolicy, chanID ...uint64) *testChannel {
|
||||||
|
|
||||||
// Leaving id zero will result in auto-generation of a channel id during
|
// 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]
|
id = chanID[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
node2Policy := *policy
|
policy2 := *policy
|
||||||
node2Policy.Direction = !policy.Direction
|
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{
|
return &testChannel{
|
||||||
Capacity: capacity,
|
Capacity: capacity,
|
||||||
Node1: &testChannelEnd{
|
Node1: &testChannelEnd{
|
||||||
Alias: alias1,
|
Alias: alias1,
|
||||||
testChannelPolicy: policy,
|
testChannelPolicy: policy1,
|
||||||
},
|
},
|
||||||
Node2: &testChannelEnd{
|
Node2: &testChannelEnd{
|
||||||
Alias: alias2,
|
Alias: alias2,
|
||||||
testChannelPolicy: &node2Policy,
|
testChannelPolicy: policy2,
|
||||||
},
|
},
|
||||||
ChannelID: id,
|
ChannelID: id,
|
||||||
}
|
}
|
||||||
@@ -401,7 +432,9 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
|||||||
privKeyMap := make(map[string]*btcec.PrivateKey)
|
privKeyMap := make(map[string]*btcec.PrivateKey)
|
||||||
|
|
||||||
nodeIndex := byte(0)
|
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 := make([]byte, 32)
|
||||||
keyBytes = []byte{
|
keyBytes = []byte{
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
@@ -413,13 +446,17 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
|||||||
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(),
|
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||||
keyBytes)
|
keyBytes)
|
||||||
|
|
||||||
|
if features == nil {
|
||||||
|
features = lnwire.EmptyFeatureVector()
|
||||||
|
}
|
||||||
|
|
||||||
dbNode := &channeldb.LightningNode{
|
dbNode := &channeldb.LightningNode{
|
||||||
HaveNodeAnnouncement: true,
|
HaveNodeAnnouncement: true,
|
||||||
AuthSigBytes: testSig.Serialize(),
|
AuthSigBytes: testSig.Serialize(),
|
||||||
LastUpdate: testTime,
|
LastUpdate: testTime,
|
||||||
Addresses: testAddrs,
|
Addresses: testAddrs,
|
||||||
Alias: alias,
|
Alias: alias,
|
||||||
Features: testFeatures,
|
Features: features,
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed())
|
copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed())
|
||||||
@@ -439,7 +476,7 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the source node.
|
// Add the source node.
|
||||||
dbNode, err := addNodeWithAlias(source)
|
dbNode, err := addNodeWithAlias(source, lnwire.EmptyFeatureVector())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -453,12 +490,19 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
|
|||||||
nextUnassignedChannelID := uint64(100000)
|
nextUnassignedChannelID := uint64(100000)
|
||||||
|
|
||||||
for _, testChannel := range testChannels {
|
for _, testChannel := range testChannels {
|
||||||
for _, alias := range []string{
|
for _, node := range []*testChannelEnd{
|
||||||
testChannel.Node1.Alias, testChannel.Node2.Alias} {
|
testChannel.Node1, testChannel.Node2} {
|
||||||
|
|
||||||
_, exists := aliasMap[alias]
|
_, exists := aliasMap[node.Alias]
|
||||||
if !exists {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -637,8 +681,12 @@ func TestFindLowestFeePath(t *testing.T) {
|
|||||||
t.Fatalf("unable to find path: %v", err)
|
t.Fatalf("unable to find path: %v", err)
|
||||||
}
|
}
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
paymentAmt, ctx.source, path, startingHeight,
|
ctx.source, path, startingHeight,
|
||||||
finalHopCLTV, nil,
|
finalHopParams{
|
||||||
|
amt: paymentAmt,
|
||||||
|
cltvDelta: finalHopCLTV,
|
||||||
|
records: nil,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create path: %v", err)
|
t.Fatalf("unable to create path: %v", err)
|
||||||
@@ -787,8 +835,12 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
paymentAmt, sourceVertex, path, startingHeight,
|
sourceVertex, path, startingHeight,
|
||||||
finalHopCLTV, nil,
|
finalHopParams{
|
||||||
|
amt: paymentAmt,
|
||||||
|
cltvDelta: finalHopCLTV,
|
||||||
|
records: nil,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create path: %v", err)
|
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) {
|
func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -946,15 +1002,21 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
|||||||
graph.aliasMap["songoku"]: {songokuToDoge},
|
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.
|
// We should now be able to find a path from roasbeef to doge.
|
||||||
path, err := findPath(
|
path, err := find(noRestrictions)
|
||||||
&graphParams{
|
|
||||||
graph: graph.graph,
|
|
||||||
additionalEdges: additionalEdges,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
|
||||||
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find private path to doge: %v", err)
|
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:
|
// The path should represent the following hops:
|
||||||
// roasbeef -> songoku -> doge
|
// roasbeef -> songoku -> doge
|
||||||
assertExpectedPath(t, graph.aliasMap, path, "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
|
// TestNewRoute tests whether the construction of hop payloads by newRoute
|
||||||
@@ -971,6 +1062,8 @@ func TestNewRoute(t *testing.T) {
|
|||||||
var sourceKey [33]byte
|
var sourceKey [33]byte
|
||||||
sourceVertex := route.Vertex(sourceKey)
|
sourceVertex := route.Vertex(sourceKey)
|
||||||
|
|
||||||
|
testPaymentAddr := [32]byte{0x01, 0x02, 0x03}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
startingHeight = 100
|
startingHeight = 100
|
||||||
finalHopCLTV = 1
|
finalHopCLTV = 1
|
||||||
@@ -1005,6 +1098,12 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// indicated by hops.
|
// indicated by hops.
|
||||||
paymentAmount lnwire.MilliSatoshi
|
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
|
// expectedFees is a list of fees that every hop is expected
|
||||||
// to charge for forwarding.
|
// to charge for forwarding.
|
||||||
expectedFees []lnwire.MilliSatoshi
|
expectedFees []lnwire.MilliSatoshi
|
||||||
@@ -1033,6 +1132,10 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// expectedErrorCode indicates the expected error code when
|
// expectedErrorCode indicates the expected error code when
|
||||||
// expectError is true.
|
// expectError is true.
|
||||||
expectedErrorCode errorCode
|
expectedErrorCode errorCode
|
||||||
|
|
||||||
|
expectedTLVPayload bool
|
||||||
|
|
||||||
|
expectedMPP *record.MPP
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// For a single hop payment, no fees are expected to be paid.
|
// 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},
|
expectedTimeLocks: []uint32{1, 1},
|
||||||
expectedTotalAmount: 100130,
|
expectedTotalAmount: 100130,
|
||||||
expectedTotalTimeLock: 6,
|
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
|
// A three hop payment where the first and second hop
|
||||||
// will both charge 1 msat. The fee for the first 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 {
|
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) {
|
assertRoute := func(t *testing.T, route *route.Route) {
|
||||||
if route.TotalAmount != testCase.expectedTotalAmount {
|
if route.TotalAmount != testCase.expectedTotalAmount {
|
||||||
t.Errorf("Expected total amount is be %v"+
|
t.Errorf("Expected total amount is be %v"+
|
||||||
@@ -1158,13 +1306,35 @@ func TestNewRoute(t *testing.T) {
|
|||||||
route.Hops[i].OutgoingTimeLock)
|
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) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
testCase.paymentAmount, sourceVertex,
|
sourceVertex, testCase.hops, startingHeight,
|
||||||
testCase.hops, startingHeight, finalHopCLTV,
|
finalHopParams{
|
||||||
nil,
|
amt: testCase.paymentAmount,
|
||||||
|
cltvDelta: finalHopCLTV,
|
||||||
|
records: nil,
|
||||||
|
paymentAddr: testCase.paymentAddr,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if testCase.expectError {
|
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) {
|
func TestPathInsufficientCapacity(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -1859,8 +2298,12 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
|||||||
t.Fatalf("unable to find path: %v", err)
|
t.Fatalf("unable to find path: %v", err)
|
||||||
}
|
}
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
paymentAmt, ctx.source, path, startingHeight,
|
ctx.source, path, startingHeight,
|
||||||
finalHopCLTV, nil,
|
finalHopParams{
|
||||||
|
amt: paymentAmt,
|
||||||
|
cltvDelta: finalHopCLTV,
|
||||||
|
records: nil,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create path: %v", err)
|
t.Fatalf("unable to create path: %v", err)
|
||||||
@@ -1985,8 +2428,12 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
|||||||
finalHopCLTV = 1
|
finalHopCLTV = 1
|
||||||
)
|
)
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
paymentAmt, ctx.source, path, startingHeight, finalHopCLTV,
|
ctx.source, path, startingHeight,
|
||||||
nil,
|
finalHopParams{
|
||||||
|
amt: paymentAmt,
|
||||||
|
cltvDelta: finalHopCLTV,
|
||||||
|
records: nil,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create path: %v", err)
|
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)
|
t.Fatalf("unable to find path: %v", err)
|
||||||
}
|
}
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
paymentAmt, ctx.source, path, startingHeight,
|
ctx.source, path, startingHeight,
|
||||||
finalHopCLTV, nil,
|
finalHopParams{
|
||||||
|
amt: paymentAmt,
|
||||||
|
cltvDelta: finalHopCLTV,
|
||||||
|
records: nil,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create path: %v", err)
|
t.Fatalf("unable to create path: %v", err)
|
||||||
|
|||||||
@@ -192,7 +192,11 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) {
|
|||||||
// payment-level failure.
|
// payment-level failure.
|
||||||
func errorToPaymentFailure(err error) channeldb.FailureReason {
|
func errorToPaymentFailure(err error) channeldb.FailureReason {
|
||||||
switch err {
|
switch err {
|
||||||
case errNoTlvPayload, errNoPathFound, errMaxHopsExceeded,
|
case
|
||||||
|
errNoTlvPayload,
|
||||||
|
errNoPaymentAddr,
|
||||||
|
errNoPathFound,
|
||||||
|
errMaxHopsExceeded,
|
||||||
errPrebuiltRouteTried:
|
errPrebuiltRouteTried:
|
||||||
|
|
||||||
return channeldb.FailureReasonNoRoute
|
return channeldb.FailureReasonNoRoute
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
|||||||
LastHop: payment.LastHop,
|
LastHop: payment.LastHop,
|
||||||
CltvLimit: cltvLimit,
|
CltvLimit: cltvLimit,
|
||||||
DestCustomRecords: payment.DestCustomRecords,
|
DestCustomRecords: payment.DestCustomRecords,
|
||||||
|
DestFeatures: payment.DestFeatures,
|
||||||
|
PaymentAddr: payment.PaymentAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll also obtain a set of bandwidthHints from the lower layer for
|
// 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.
|
// a route by applying the time-lock and fee requirements.
|
||||||
sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes)
|
sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes)
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
payment.Amount, sourceVertex, path, height, finalCltvDelta,
|
sourceVertex, path, height,
|
||||||
payment.DestCustomRecords,
|
finalHopParams{
|
||||||
|
amt: payment.Amount,
|
||||||
|
cltvDelta: finalCltvDelta,
|
||||||
|
records: payment.DestCustomRecords,
|
||||||
|
paymentAddr: payment.PaymentAddr,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(roasbeef): return which edge/vertex didn't work
|
// TODO(roasbeef): return which edge/vertex didn't work
|
||||||
|
|||||||
+25
-4
@@ -1454,8 +1454,12 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
|
|||||||
|
|
||||||
// Create the route with absolute time lock values.
|
// Create the route with absolute time lock values.
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
amt, source, path, uint32(currentHeight), finalCLTVDelta,
|
source, path, uint32(currentHeight),
|
||||||
destCustomRecords,
|
finalHopParams{
|
||||||
|
amt: amt,
|
||||||
|
cltvDelta: finalCLTVDelta,
|
||||||
|
records: destCustomRecords,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1597,6 +1601,19 @@ type LightningPayment struct {
|
|||||||
// is reached. If nil, any node may be used.
|
// is reached. If nil, any node may be used.
|
||||||
LastHop *route.Vertex
|
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
|
// PaymentRequest is an optional payment request that this payment is
|
||||||
// attempting to complete.
|
// attempting to complete.
|
||||||
PaymentRequest []byte
|
PaymentRequest []byte
|
||||||
@@ -2403,7 +2420,11 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return newRoute(
|
return newRoute(
|
||||||
receiverAmt, source, pathEdges, uint32(height),
|
source, pathEdges, uint32(height),
|
||||||
uint16(finalCltvDelta), nil,
|
finalHopParams{
|
||||||
|
amt: receiverAmt,
|
||||||
|
cltvDelta: uint16(finalCltvDelta),
|
||||||
|
records: nil,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -3241,6 +3241,8 @@ type rpcPaymentIntent struct {
|
|||||||
routeHints [][]zpay32.HopHint
|
routeHints [][]zpay32.HopHint
|
||||||
outgoingChannelID *uint64
|
outgoingChannelID *uint64
|
||||||
lastHop *route.Vertex
|
lastHop *route.Vertex
|
||||||
|
destFeatures *lnwire.FeatureVector
|
||||||
|
paymentAddr *[32]byte
|
||||||
payReq []byte
|
payReq []byte
|
||||||
|
|
||||||
destCustomRecords record.CustomSet
|
destCustomRecords record.CustomSet
|
||||||
@@ -3370,6 +3372,8 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
|
|||||||
payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
||||||
payIntent.routeHints = payReq.RouteHints
|
payIntent.routeHints = payReq.RouteHints
|
||||||
payIntent.payReq = []byte(rpcPayReq.PaymentRequest)
|
payIntent.payReq = []byte(rpcPayReq.PaymentRequest)
|
||||||
|
payIntent.destFeatures = payReq.Features
|
||||||
|
payIntent.paymentAddr = payReq.PaymentAddr
|
||||||
|
|
||||||
if err := validateDest(payIntent.dest); err != nil {
|
if err := validateDest(payIntent.dest); err != nil {
|
||||||
return payIntent, err
|
return payIntent, err
|
||||||
@@ -3438,6 +3442,14 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
|
|||||||
copy(payIntent.rHash[:], rpcPayReq.PaymentHash)
|
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
|
// Currently, within the bootstrap phase of the network, we limit the
|
||||||
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
|
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
|
||||||
// satoshis.
|
// satoshis.
|
||||||
@@ -3492,6 +3504,8 @@ func (r *rpcServer) dispatchPaymentIntent(
|
|||||||
PaymentRequest: payIntent.payReq,
|
PaymentRequest: payIntent.payReq,
|
||||||
PayAttemptTimeout: routing.DefaultPayAttemptTimeout,
|
PayAttemptTimeout: routing.DefaultPayAttemptTimeout,
|
||||||
DestCustomRecords: payIntent.destCustomRecords,
|
DestCustomRecords: payIntent.destCustomRecords,
|
||||||
|
DestFeatures: payIntent.destFeatures,
|
||||||
|
PaymentAddr: payIntent.paymentAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
preImage, route, routerErr = r.server.chanRouter.SendPayment(
|
preImage, route, routerErr = r.server.chanRouter.SendPayment(
|
||||||
|
|||||||
Reference in new issue
Block a user