Merge pull request #3967 from joostjager/mpp-send
routing: multi-shard mpp send
This commit is contained in:
22 files changed
+1121
-544
No files matched your search
@@ -116,6 +116,9 @@ const (
|
|||||||
|
|
||||||
// FailureReasonInsufficientBalance indicates that we didn't have enough
|
// FailureReasonInsufficientBalance indicates that we didn't have enough
|
||||||
// balance to complete the payment.
|
// balance to complete the payment.
|
||||||
|
//
|
||||||
|
// This reason isn't assigned anymore, but may still exist for older
|
||||||
|
// payments.
|
||||||
FailureReasonInsufficientBalance FailureReason = 4
|
FailureReasonInsufficientBalance FailureReason = 4
|
||||||
|
|
||||||
// TODO(halseth): cancel state.
|
// TODO(halseth): cancel state.
|
||||||
|
|||||||
+10
-1
@@ -52,6 +52,13 @@ var (
|
|||||||
Usage: "if set, intermediate payment state updates will be " +
|
Usage: "if set, intermediate payment state updates will be " +
|
||||||
"displayed",
|
"displayed",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxHtlcsFlag = cli.UintFlag{
|
||||||
|
Name: "max_htlcs",
|
||||||
|
Usage: "the maximum number of partial payments that may be " +
|
||||||
|
"used",
|
||||||
|
Value: 1,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// paymentFlags returns common flags for sendpayment and payinvoice.
|
// paymentFlags returns common flags for sendpayment and payinvoice.
|
||||||
@@ -88,7 +95,7 @@ func paymentFlags() []cli.Flag {
|
|||||||
Name: "allow_self_payment",
|
Name: "allow_self_payment",
|
||||||
Usage: "allow sending a circular payment to self",
|
Usage: "allow sending a circular payment to self",
|
||||||
},
|
},
|
||||||
dataFlag, showInflightFlag,
|
dataFlag, showInflightFlag, maxHtlcsFlag,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +325,8 @@ func sendPaymentRequest(ctx *cli.Context,
|
|||||||
|
|
||||||
req.AllowSelfPayment = ctx.Bool("allow_self_payment")
|
req.AllowSelfPayment = ctx.Bool("allow_self_payment")
|
||||||
|
|
||||||
|
req.MaxHtlcs = uint32(ctx.Uint(maxHtlcsFlag.Name))
|
||||||
|
|
||||||
// Parse custom data records.
|
// Parse custom data records.
|
||||||
data := ctx.String(dataFlag.Name)
|
data := ctx.String(dataFlag.Name)
|
||||||
if data != "" {
|
if data != "" {
|
||||||
|
|||||||
+144
-132
@@ -221,10 +221,14 @@ type SendPaymentRequest struct {
|
|||||||
//optional or remote may be set, but not both. If this field is nil or empty,
|
//optional or remote may be set, but not both. If this field is nil or empty,
|
||||||
//the router will try to load destination features from the graph as a
|
//the router will try to load destination features from the graph as a
|
||||||
//fallback.
|
//fallback.
|
||||||
DestFeatures []lnrpc.FeatureBit `protobuf:"varint,16,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
|
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:"-"`
|
//The maximum number of partial payments that may be use to complete the full
|
||||||
XXX_sizecache int32 `json:"-"`
|
//amount.
|
||||||
|
MaxHtlcs uint32 `protobuf:"varint,17,opt,name=max_htlcs,json=maxHtlcs,proto3" json:"max_htlcs,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{} }
|
||||||
@@ -364,6 +368,13 @@ func (m *SendPaymentRequest) GetDestFeatures() []lnrpc.FeatureBit {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SendPaymentRequest) GetMaxHtlcs() uint32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.MaxHtlcs
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
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"`
|
||||||
@@ -1595,134 +1606,135 @@ 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{
|
||||||
// 2017 bytes of a gzipped FileDescriptorProto
|
// 2039 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x5d, 0x77, 0xdb, 0x48,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xcd, 0x76, 0xdb, 0xc6,
|
||||||
0x19, 0x5e, 0xc5, 0x72, 0x62, 0xbf, 0xfe, 0x52, 0xc6, 0xdd, 0xd4, 0x38, 0xed, 0xe2, 0x15, 0x6c,
|
0x15, 0x0e, 0x44, 0x50, 0x22, 0x2f, 0xff, 0xa0, 0xa1, 0x23, 0xb3, 0x54, 0x9c, 0x32, 0x68, 0x63,
|
||||||
0xeb, 0x53, 0x4a, 0xd2, 0x0d, 0x1c, 0xe8, 0x01, 0xb6, 0xe0, 0x58, 0xca, 0x46, 0x8d, 0x23, 0x79,
|
0xf3, 0xb8, 0xae, 0xe4, 0xa8, 0x3d, 0xad, 0x4f, 0xdb, 0xb8, 0xa5, 0x08, 0x28, 0x82, 0x45, 0x01,
|
||||||
0xc7, 0x4e, 0xdb, 0xa5, 0x17, 0x73, 0x14, 0x7b, 0x1c, 0x8b, 0xc8, 0x92, 0x91, 0xc6, 0xed, 0xc9,
|
0xcc, 0x10, 0xb2, 0x9d, 0x7a, 0x31, 0x07, 0x22, 0x87, 0x22, 0x2a, 0xfc, 0xb0, 0xc0, 0xd0, 0x8e,
|
||||||
0x25, 0xb7, 0xfc, 0x11, 0xfe, 0x04, 0xff, 0x85, 0x5b, 0xee, 0xb8, 0xe3, 0x70, 0xc9, 0x99, 0x91,
|
0x96, 0xdd, 0x76, 0xdf, 0x67, 0xe8, 0x4b, 0xf4, 0x5d, 0xba, 0xed, 0xae, 0xbb, 0xae, 0x7b, 0x66,
|
||||||
0xc6, 0x96, 0x13, 0xa7, 0xbb, 0x37, 0x89, 0xf4, 0xbc, 0xcf, 0xbc, 0x1f, 0xf3, 0x3e, 0xf3, 0x21,
|
0x00, 0x90, 0xa0, 0x44, 0x39, 0xd9, 0xd8, 0x9c, 0xef, 0x7e, 0x73, 0xe7, 0xde, 0xb9, 0xdf, 0x9d,
|
||||||
0xc3, 0x5e, 0x14, 0x2e, 0x18, 0x8d, 0xa2, 0xf9, 0xe8, 0x30, 0x79, 0x3a, 0x98, 0x47, 0x21, 0x0b,
|
0x19, 0x08, 0xf6, 0xa2, 0x70, 0xc1, 0x68, 0x14, 0xcd, 0xc7, 0x87, 0xc9, 0xaf, 0x83, 0x79, 0x14,
|
||||||
0x51, 0x71, 0x89, 0x37, 0x8b, 0xd1, 0x7c, 0x94, 0xa0, 0xfa, 0xff, 0xf2, 0x80, 0x06, 0x34, 0x18,
|
0xb2, 0x10, 0x95, 0x97, 0x78, 0xbb, 0x1c, 0xcd, 0xc7, 0x09, 0xaa, 0xfe, 0x63, 0x1b, 0xd0, 0x88,
|
||||||
0xf7, 0xdd, 0x9b, 0x19, 0x0d, 0x18, 0xa6, 0x7f, 0x5d, 0xd0, 0x98, 0x21, 0x04, 0xea, 0x98, 0xc6,
|
0x06, 0x93, 0xa1, 0x73, 0xe3, 0xd3, 0x80, 0x61, 0xfa, 0xd7, 0x05, 0x8d, 0x19, 0x42, 0x20, 0x4f,
|
||||||
0xac, 0xa1, 0xb4, 0x94, 0x76, 0x19, 0x8b, 0x67, 0xa4, 0x41, 0xce, 0x9d, 0xb1, 0xc6, 0x56, 0x4b,
|
0x68, 0xcc, 0x5a, 0x52, 0x47, 0xea, 0x56, 0xb1, 0xf8, 0x8d, 0x14, 0x28, 0x38, 0x3e, 0x6b, 0x6d,
|
||||||
0x69, 0xe7, 0x30, 0x7f, 0x44, 0x3f, 0x81, 0x82, 0x3b, 0x63, 0x64, 0x16, 0xbb, 0xac, 0x51, 0x16,
|
0x75, 0xa4, 0x6e, 0x01, 0xf3, 0x9f, 0xe8, 0x27, 0x50, 0x72, 0x7c, 0x46, 0xfc, 0xd8, 0x61, 0xad,
|
||||||
0xf0, 0x8e, 0x3b, 0x63, 0xe7, 0xb1, 0xcb, 0xd0, 0x97, 0x50, 0x9e, 0x27, 0x2e, 0xc9, 0xd4, 0x8d,
|
0xaa, 0x80, 0x77, 0x1c, 0x9f, 0x9d, 0xc7, 0x0e, 0x43, 0x5f, 0x40, 0x75, 0x9e, 0xb8, 0x24, 0x33,
|
||||||
0xa7, 0x8d, 0x9c, 0x70, 0x54, 0x4a, 0xb1, 0x53, 0x37, 0x9e, 0xa2, 0x36, 0x68, 0x13, 0x2f, 0x70,
|
0x27, 0x9e, 0xb5, 0x0a, 0xc2, 0x51, 0x25, 0xc5, 0x4e, 0x9d, 0x78, 0x86, 0xba, 0xa0, 0x4c, 0xdd,
|
||||||
0x7d, 0x32, 0xf2, 0xd9, 0x07, 0x32, 0xa6, 0x3e, 0x73, 0x1b, 0x6a, 0x4b, 0x69, 0xe7, 0x71, 0x55,
|
0xc0, 0xf1, 0xc8, 0xd8, 0x63, 0xef, 0xc9, 0x84, 0x7a, 0xcc, 0x69, 0xc9, 0x1d, 0xa9, 0x5b, 0xc4,
|
||||||
0xe0, 0x5d, 0x9f, 0x7d, 0x30, 0x38, 0x8a, 0x9e, 0x42, 0x4d, 0x3a, 0x8b, 0x92, 0x04, 0x1b, 0xf9,
|
0x75, 0x81, 0xf7, 0x3d, 0xf6, 0x5e, 0xe3, 0x28, 0x7a, 0x02, 0x8d, 0xcc, 0x59, 0x94, 0x04, 0xd8,
|
||||||
0x96, 0xd2, 0x2e, 0xe2, 0xea, 0x7c, 0x3d, 0xed, 0xa7, 0x50, 0x63, 0xde, 0x8c, 0x86, 0x0b, 0x46,
|
0x2a, 0x76, 0xa4, 0x6e, 0x19, 0xd7, 0xe7, 0xeb, 0x61, 0x3f, 0x81, 0x06, 0x73, 0x7d, 0x1a, 0x2e,
|
||||||
0x62, 0x3a, 0x0a, 0x83, 0x71, 0xdc, 0xd8, 0x4e, 0x3c, 0xa6, 0xf0, 0x20, 0x41, 0x91, 0x0e, 0x95,
|
0x18, 0x89, 0xe9, 0x38, 0x0c, 0x26, 0x71, 0x6b, 0x3b, 0xf1, 0x98, 0xc2, 0xa3, 0x04, 0x45, 0x2a,
|
||||||
0x09, 0xa5, 0xc4, 0xf7, 0x66, 0x1e, 0x23, 0x3c, 0xfd, 0x1d, 0x91, 0x7e, 0x69, 0x42, 0x69, 0x8f,
|
0xd4, 0xa6, 0x94, 0x12, 0xcf, 0xf5, 0x5d, 0x46, 0x78, 0xf8, 0x3b, 0x22, 0xfc, 0xca, 0x94, 0xd2,
|
||||||
0x63, 0x03, 0x97, 0xa1, 0x9f, 0x43, 0x75, 0xc5, 0x11, 0x35, 0x56, 0x04, 0xa9, 0x2c, 0x49, 0xa2,
|
0x01, 0xc7, 0x46, 0x0e, 0x43, 0x3f, 0x87, 0xfa, 0x8a, 0x23, 0x72, 0xac, 0x09, 0x52, 0x35, 0x23,
|
||||||
0xd0, 0xe7, 0xa0, 0x85, 0x0b, 0x76, 0x15, 0x7a, 0xc1, 0x15, 0x19, 0x4d, 0xdd, 0x80, 0x78, 0xe3,
|
0x89, 0x44, 0x9f, 0x81, 0x12, 0x2e, 0xd8, 0x55, 0xe8, 0x06, 0x57, 0x64, 0x3c, 0x73, 0x02, 0xe2,
|
||||||
0x46, 0xa1, 0xa5, 0xb4, 0xd5, 0xe3, 0xad, 0x17, 0x0a, 0xae, 0x4a, 0x5b, 0x77, 0xea, 0x06, 0xd6,
|
0x4e, 0x5a, 0xa5, 0x8e, 0xd4, 0x95, 0x8f, 0xb7, 0x9e, 0x4b, 0xb8, 0x9e, 0xd9, 0xfa, 0x33, 0x27,
|
||||||
0x18, 0x3d, 0x81, 0x9a, 0xef, 0xc6, 0x8c, 0x4c, 0xc3, 0x39, 0x99, 0x2f, 0x2e, 0xaf, 0xe9, 0x4d,
|
0x30, 0x26, 0xe8, 0x31, 0x34, 0x3c, 0x27, 0x66, 0x64, 0x16, 0xce, 0xc9, 0x7c, 0x71, 0x79, 0x4d,
|
||||||
0xa3, 0x2a, 0x66, 0xa6, 0xc2, 0xe1, 0xd3, 0x70, 0xde, 0x17, 0x20, 0x7a, 0x0c, 0x20, 0x66, 0x45,
|
0x6f, 0x5a, 0x75, 0xb1, 0x33, 0x35, 0x0e, 0x9f, 0x86, 0xf3, 0xa1, 0x00, 0xd1, 0x23, 0x00, 0xb1,
|
||||||
0x04, 0x6f, 0x14, 0x45, 0x0d, 0x45, 0x8e, 0x88, 0xc0, 0xe8, 0x6b, 0x28, 0x89, 0x6e, 0x92, 0xa9,
|
0x2b, 0x62, 0xf1, 0x56, 0x59, 0xe4, 0x50, 0xe6, 0x88, 0x58, 0x18, 0x7d, 0x05, 0x15, 0x51, 0x4d,
|
||||||
0x17, 0xb0, 0xb8, 0x01, 0xad, 0x5c, 0xbb, 0x74, 0xa4, 0x1d, 0xf8, 0x01, 0x6f, 0x2c, 0xe6, 0x96,
|
0x32, 0x73, 0x03, 0x16, 0xb7, 0xa0, 0x53, 0xe8, 0x56, 0x8e, 0x94, 0x03, 0x2f, 0xe0, 0x85, 0xc5,
|
||||||
0x53, 0x2f, 0x60, 0x18, 0x22, 0xf9, 0x18, 0xa3, 0x31, 0xd4, 0x79, 0x17, 0xc9, 0x68, 0x11, 0xb3,
|
0xdc, 0x72, 0xea, 0x06, 0x0c, 0x43, 0x94, 0xfd, 0x8c, 0xd1, 0x04, 0x9a, 0xbc, 0x8a, 0x64, 0xbc,
|
||||||
0x70, 0x46, 0x22, 0x3a, 0x0a, 0xa3, 0x71, 0xdc, 0x28, 0x89, 0xa1, 0xbf, 0x3e, 0x58, 0x8a, 0xe3,
|
0x88, 0x59, 0xe8, 0x93, 0x88, 0x8e, 0xc3, 0x68, 0x12, 0xb7, 0x2a, 0x62, 0xea, 0xaf, 0x0f, 0x96,
|
||||||
0xe0, 0xae, 0x1a, 0x0e, 0x0c, 0x1a, 0xb3, 0xae, 0x18, 0x87, 0x93, 0x61, 0x66, 0xc0, 0xa2, 0x1b,
|
0xe2, 0x38, 0xb8, 0xab, 0x86, 0x03, 0x8d, 0xc6, 0xac, 0x2f, 0xe6, 0xe1, 0x64, 0x9a, 0x1e, 0xb0,
|
||||||
0xbc, 0x3b, 0xbe, 0x8d, 0xa3, 0xe7, 0x80, 0x5c, 0xdf, 0x0f, 0x3f, 0x92, 0x98, 0xfa, 0x13, 0x92,
|
0xe8, 0x06, 0xef, 0x4e, 0x6e, 0xe3, 0xe8, 0x19, 0x20, 0xc7, 0xf3, 0xc2, 0x0f, 0x24, 0xa6, 0xde,
|
||||||
0x76, 0xa7, 0x51, 0x6b, 0x29, 0xed, 0x02, 0xd6, 0x84, 0x65, 0x40, 0xfd, 0x49, 0xea, 0x1e, 0xfd,
|
0x94, 0xa4, 0xd5, 0x69, 0x35, 0x3a, 0x52, 0xb7, 0x84, 0x15, 0x61, 0x19, 0x51, 0x6f, 0x9a, 0xba,
|
||||||
0x06, 0x2a, 0x22, 0xa7, 0x09, 0x75, 0xd9, 0x22, 0xa2, 0x71, 0x43, 0x6b, 0xe5, 0xda, 0xd5, 0xa3,
|
0x47, 0xbf, 0x81, 0x9a, 0x88, 0x69, 0x4a, 0x1d, 0xb6, 0x88, 0x68, 0xdc, 0x52, 0x3a, 0x85, 0x6e,
|
||||||
0xdd, 0xb4, 0x90, 0x93, 0x04, 0x3e, 0xf6, 0x18, 0x2e, 0x73, 0x5e, 0xfa, 0x1e, 0x37, 0x0d, 0xd8,
|
0xfd, 0x68, 0x37, 0x4d, 0xe4, 0x24, 0x81, 0x8f, 0x5d, 0x86, 0xab, 0x9c, 0x97, 0x8e, 0x63, 0xb4,
|
||||||
0xdb, 0x9c, 0x12, 0xd7, 0x28, 0x9f, 0x53, 0x2e, 0x5b, 0x15, 0xf3, 0x47, 0xf4, 0x00, 0xf2, 0x1f,
|
0x0f, 0x65, 0xdf, 0xf9, 0x9e, 0xcc, 0x98, 0x37, 0x8e, 0x5b, 0xbb, 0x1d, 0xa9, 0x5b, 0xc3, 0x25,
|
||||||
0x5c, 0x7f, 0x41, 0x85, 0x6e, 0xcb, 0x38, 0x79, 0xf9, 0xdd, 0xd6, 0x4b, 0x45, 0x7f, 0x09, 0xf5,
|
0xdf, 0xf9, 0xfe, 0x94, 0x8f, 0xdb, 0x1a, 0xec, 0x6d, 0x8e, 0x97, 0x0b, 0x98, 0x6f, 0x38, 0xd7,
|
||||||
0x61, 0xe4, 0x8e, 0xae, 0x6f, 0x49, 0xff, 0xb6, 0x72, 0x95, 0x3b, 0xca, 0xd5, 0x5f, 0x41, 0x4d,
|
0xb4, 0x8c, 0xf9, 0x4f, 0xf4, 0x00, 0x8a, 0xef, 0x1d, 0x6f, 0x41, 0x85, 0xa8, 0xab, 0x38, 0x19,
|
||||||
0x4c, 0xf2, 0x09, 0xa5, 0x9f, 0x5a, 0x30, 0x0f, 0x81, 0x2f, 0x07, 0x21, 0xaf, 0x64, 0xd1, 0x6c,
|
0xfc, 0x6e, 0xeb, 0x85, 0xa4, 0xbe, 0x80, 0xa6, 0x1d, 0x39, 0xe3, 0xeb, 0x5b, 0x7d, 0x71, 0x5b,
|
||||||
0xbb, 0x33, 0xae, 0x2c, 0x7d, 0x0c, 0xda, 0x6a, 0x7c, 0x3c, 0x0f, 0x83, 0x98, 0xf2, 0xd5, 0xc0,
|
0xd6, 0xd2, 0x1d, 0x59, 0xab, 0x2f, 0xa1, 0x21, 0x2a, 0x70, 0x42, 0xe9, 0xc7, 0xba, 0xe9, 0x21,
|
||||||
0x7b, 0xc0, 0x65, 0xc4, 0x55, 0x27, 0xf4, 0xa6, 0x88, 0x51, 0xd5, 0x14, 0x3f, 0xa1, 0x54, 0x28,
|
0xf0, 0x5e, 0x11, 0xda, 0x4b, 0x3a, 0x6a, 0xdb, 0xf1, 0xb9, 0xec, 0xd4, 0x09, 0x28, 0xab, 0xf9,
|
||||||
0xee, 0x49, 0x22, 0x72, 0xe2, 0x87, 0xa3, 0x6b, 0xbe, 0x6c, 0xdc, 0x9b, 0xd4, 0x7d, 0x85, 0xc3,
|
0xf1, 0x3c, 0x0c, 0x62, 0xca, 0x5b, 0x85, 0x17, 0x88, 0x6b, 0x8c, 0x4b, 0x52, 0x88, 0x51, 0x12,
|
||||||
0xbd, 0x70, 0x74, 0x6d, 0x70, 0x50, 0x7f, 0x9f, 0xac, 0xec, 0x61, 0x28, 0x62, 0xfd, 0xf8, 0xf2,
|
0xb3, 0xea, 0x29, 0x7e, 0x42, 0xa9, 0x90, 0xe3, 0xe3, 0xa4, 0x03, 0x88, 0x17, 0x8e, 0xaf, 0x79,
|
||||||
0x90, 0x0e, 0x79, 0x21, 0x07, 0xe1, 0xb6, 0x74, 0x54, 0xce, 0xea, 0x0a, 0x27, 0x26, 0xfd, 0x3d,
|
0x4f, 0x39, 0x37, 0xa9, 0xfb, 0x1a, 0x87, 0x07, 0xe1, 0xf8, 0x5a, 0xe3, 0xa0, 0xfa, 0x2e, 0x69,
|
||||||
0xd4, 0xd7, 0x9c, 0xa7, 0x55, 0x34, 0xa1, 0x30, 0x8f, 0xa8, 0x37, 0x73, 0xaf, 0x68, 0xea, 0x79,
|
0x7b, 0x3b, 0x14, 0x6b, 0xfd, 0xf8, 0xf4, 0x90, 0x0a, 0x45, 0xa1, 0x15, 0xe1, 0xb6, 0x72, 0x54,
|
||||||
0xf9, 0x8e, 0xda, 0xb0, 0x33, 0x71, 0x3d, 0x7f, 0x11, 0x49, 0xc7, 0x55, 0xd9, 0xe7, 0x04, 0xc5,
|
0xcd, 0x8b, 0x0e, 0x27, 0x26, 0xf5, 0x1d, 0x34, 0xd7, 0x9c, 0xa7, 0x59, 0xb4, 0xa1, 0x34, 0x8f,
|
||||||
0xd2, 0xac, 0x3f, 0x82, 0x26, 0xa6, 0x31, 0x65, 0xe7, 0x5e, 0x1c, 0x7b, 0x61, 0xd0, 0x0d, 0x03,
|
0xa8, 0xeb, 0x3b, 0x57, 0x34, 0xf5, 0xbc, 0x1c, 0xa3, 0x2e, 0xec, 0x4c, 0x1d, 0xd7, 0x5b, 0x44,
|
||||||
0x16, 0x85, 0x7e, 0x5a, 0x81, 0xfe, 0x18, 0xf6, 0x37, 0x5a, 0x93, 0x14, 0xf8, 0xe0, 0xef, 0x16,
|
0x99, 0xe3, 0x7a, 0x26, 0x82, 0x04, 0xc5, 0x99, 0x59, 0xfd, 0x0c, 0xda, 0x98, 0xc6, 0x94, 0x9d,
|
||||||
0x34, 0xba, 0xd9, 0x3c, 0xf8, 0x3b, 0xd8, 0xdf, 0x68, 0x4d, 0xf3, 0x7f, 0x0e, 0xf9, 0xb9, 0xeb,
|
0xbb, 0x71, 0xec, 0x86, 0x41, 0x3f, 0x0c, 0x58, 0x14, 0x7a, 0x69, 0x06, 0xea, 0x23, 0xd8, 0xdf,
|
||||||
0x45, 0x71, 0x63, 0x4b, 0xac, 0x8b, 0xbd, 0xcc, 0xba, 0xe8, 0xbb, 0x5e, 0x74, 0xea, 0xc5, 0x2c,
|
0x68, 0x4d, 0x42, 0xe0, 0x93, 0xbf, 0x5d, 0xd0, 0xe8, 0x66, 0xf3, 0xe4, 0x6f, 0x61, 0x7f, 0xa3,
|
||||||
0x8c, 0x6e, 0x70, 0x42, 0x7a, 0xad, 0x16, 0x14, 0x6d, 0x4b, 0xff, 0xbb, 0x02, 0xa5, 0x8c, 0x11,
|
0x35, 0x8d, 0xff, 0x19, 0x14, 0xe7, 0x8e, 0x1b, 0xc5, 0xad, 0x2d, 0xd1, 0x34, 0x7b, 0xb9, 0xa6,
|
||||||
0xed, 0x43, 0x31, 0x08, 0xc7, 0x94, 0x4c, 0xa2, 0x70, 0x26, 0x27, 0x81, 0x03, 0x27, 0x51, 0x38,
|
0x19, 0x3a, 0x6e, 0x74, 0xea, 0xc6, 0x2c, 0x8c, 0x6e, 0x70, 0x42, 0x7a, 0x25, 0x97, 0x24, 0x65,
|
||||||
0xe3, 0x9a, 0x10, 0x46, 0x16, 0xa6, 0x82, 0xdc, 0xe6, 0xaf, 0xc3, 0x10, 0xfd, 0x12, 0x76, 0xa6,
|
0x4b, 0xfd, 0xbb, 0x04, 0x95, 0x9c, 0x91, 0x4b, 0x37, 0x08, 0x27, 0x94, 0x4c, 0xa3, 0xd0, 0xcf,
|
||||||
0x89, 0x03, 0xb1, 0x17, 0x95, 0x8e, 0xea, 0xb7, 0x62, 0x1b, 0x2e, 0x73, 0xb1, 0xe4, 0xbc, 0x56,
|
0x36, 0x81, 0x03, 0x27, 0x51, 0xe8, 0x73, 0x4d, 0x08, 0x23, 0x0b, 0x53, 0x41, 0x6e, 0xf3, 0xa1,
|
||||||
0x0b, 0x39, 0x4d, 0x7d, 0xad, 0x16, 0x54, 0x2d, 0xff, 0x5a, 0x2d, 0xe4, 0xb5, 0xed, 0xd7, 0x6a,
|
0x1d, 0xa2, 0x5f, 0xc2, 0xce, 0x2c, 0x71, 0x20, 0x0e, 0xaa, 0xca, 0x51, 0xf3, 0xd6, 0xda, 0x9a,
|
||||||
0x61, 0x5b, 0xdb, 0xd1, 0xff, 0xad, 0x40, 0x41, 0xb2, 0x79, 0x26, 0x7c, 0x4a, 0x09, 0xd7, 0x45,
|
0xc3, 0x1c, 0x9c, 0x71, 0x5e, 0xc9, 0xa5, 0x82, 0x22, 0xbf, 0x92, 0x4b, 0xb2, 0x52, 0x7c, 0x25,
|
||||||
0x2a, 0xa6, 0x02, 0x07, 0x86, 0xde, 0x8c, 0xa2, 0x16, 0x94, 0x85, 0x71, 0x5d, 0xa2, 0xc0, 0xb1,
|
0x97, 0x8a, 0xca, 0xf6, 0x2b, 0xb9, 0xb4, 0xad, 0xec, 0xa8, 0xff, 0x91, 0xa0, 0x94, 0xb1, 0x79,
|
||||||
0x8e, 0x90, 0xa9, 0xd8, 0x24, 0x25, 0x43, 0xe8, 0x51, 0x4d, 0x37, 0xc9, 0x84, 0x22, 0xf7, 0xf9,
|
0x24, 0x7c, 0x4b, 0x09, 0xd7, 0x45, 0x2a, 0xa6, 0x12, 0x07, 0x6c, 0xd7, 0xa7, 0xa8, 0x03, 0x55,
|
||||||
0x78, 0x31, 0x1a, 0xd1, 0x38, 0x4e, 0xa2, 0xe4, 0x13, 0x4a, 0x8a, 0x89, 0x40, 0x4f, 0xa0, 0x26,
|
0x61, 0x5c, 0x97, 0x28, 0x70, 0xac, 0x27, 0x64, 0x2a, 0x4e, 0xd0, 0x8c, 0x21, 0xf4, 0x28, 0xa7,
|
||||||
0x29, 0x32, 0xd6, 0x76, 0xa2, 0xd7, 0x14, 0x4e, 0xc3, 0xb5, 0x41, 0xcb, 0xf2, 0x66, 0xab, 0x6d,
|
0x27, 0x68, 0x42, 0xc9, 0x2e, 0x81, 0x78, 0x31, 0x1e, 0xd3, 0x38, 0x4e, 0x56, 0x29, 0x26, 0x94,
|
||||||
0xb9, 0xba, 0x22, 0xf2, 0xa0, 0x49, 0xf1, 0xfa, 0x5f, 0xe0, 0xa1, 0x68, 0x65, 0x3f, 0x0a, 0x2f,
|
0x14, 0x13, 0x0b, 0x3d, 0x86, 0x46, 0x46, 0xc9, 0xd6, 0xda, 0x4e, 0xf4, 0x9a, 0xc2, 0xe9, 0x72,
|
||||||
0xdd, 0x4b, 0xcf, 0xf7, 0xd8, 0x8d, 0x14, 0x39, 0x2f, 0x3c, 0x0a, 0x67, 0x84, 0xcf, 0xad, 0x6c,
|
0x5d, 0x50, 0xf2, 0x3c, 0x7f, 0x75, 0x66, 0xd7, 0x57, 0x44, 0xbe, 0x68, 0x92, 0xbc, 0xfa, 0x17,
|
||||||
0x01, 0x07, 0xec, 0x70, 0x4c, 0x79, 0x0b, 0x58, 0x98, 0x98, 0xd2, 0x16, 0xb0, 0x50, 0x18, 0xb2,
|
0x78, 0x28, 0x4a, 0x39, 0x8c, 0xc2, 0x4b, 0xe7, 0xd2, 0xf5, 0x5c, 0x76, 0x93, 0x89, 0x9c, 0x27,
|
||||||
0xc7, 0x59, 0x6e, 0xed, 0x38, 0xd3, 0xaf, 0xa1, 0x71, 0x37, 0x56, 0xaa, 0x99, 0x16, 0x94, 0xe6,
|
0x1e, 0x85, 0x3e, 0xe1, 0x7b, 0x9b, 0x95, 0x80, 0x03, 0x66, 0x38, 0xa1, 0xbc, 0x04, 0x2c, 0x4c,
|
||||||
0x2b, 0x58, 0x84, 0x53, 0x70, 0x16, 0xca, 0xf6, 0x76, 0xeb, 0x87, 0x7b, 0xab, 0xff, 0x43, 0x81,
|
0x4c, 0x69, 0x09, 0x58, 0x28, 0x0c, 0xf9, 0xbb, 0xae, 0xb0, 0x76, 0xd7, 0xa9, 0xd7, 0xd0, 0xba,
|
||||||
0xdd, 0xe3, 0x85, 0xe7, 0x8f, 0xd7, 0x16, 0x6e, 0x36, 0x3b, 0x65, 0xfd, 0xb0, 0xdd, 0x74, 0x92,
|
0xbb, 0x56, 0xaa, 0x99, 0x0e, 0x54, 0xe6, 0x2b, 0x58, 0x2c, 0x27, 0xe1, 0x3c, 0x94, 0xaf, 0xed,
|
||||||
0x6e, 0x6d, 0x3c, 0x49, 0x37, 0x9d, 0x56, 0xb9, 0x7b, 0x4f, 0xab, 0x9f, 0x42, 0x69, 0x75, 0x50,
|
0xd6, 0x0f, 0xd7, 0x56, 0xfd, 0xa7, 0x04, 0xbb, 0xc7, 0x0b, 0xd7, 0x9b, 0xac, 0x35, 0x6e, 0x3e,
|
||||||
0xc5, 0x0d, 0xb5, 0x95, 0x6b, 0x97, 0x31, 0x4c, 0xe5, 0x29, 0x15, 0xeb, 0x2f, 0x01, 0x65, 0x13,
|
0x3a, 0x69, 0xfd, 0x26, 0xde, 0x74, 0xcd, 0x6e, 0x6d, 0xbc, 0x66, 0x37, 0x5d, 0x65, 0x85, 0x7b,
|
||||||
0x4d, 0x27, 0x64, 0xb9, 0x7f, 0x28, 0xf7, 0xef, 0x1f, 0x8f, 0xa0, 0x39, 0x58, 0x5c, 0xc6, 0xa3,
|
0xaf, 0xb2, 0x9f, 0x42, 0x65, 0x75, 0x8b, 0xc5, 0x2d, 0xb9, 0x53, 0xe8, 0x56, 0x31, 0xcc, 0xb2,
|
||||||
0xc8, 0xbb, 0xa4, 0xa7, 0xcc, 0x1f, 0x99, 0x1f, 0x68, 0xc0, 0x62, 0xb9, 0x4a, 0xff, 0xab, 0x42,
|
0x2b, 0x2c, 0x56, 0x5f, 0x00, 0xca, 0x07, 0x9a, 0x6e, 0xc8, 0xf2, 0xfc, 0x90, 0xee, 0x3f, 0x3f,
|
||||||
0x71, 0x89, 0xa2, 0x03, 0xa8, 0x7b, 0xc1, 0x28, 0x9c, 0xc9, 0xa4, 0x03, 0xea, 0xf3, 0xbc, 0x93,
|
0x3e, 0x83, 0xf6, 0x68, 0x71, 0x19, 0x8f, 0x23, 0xf7, 0x92, 0xf2, 0x43, 0x5d, 0x7f, 0x4f, 0x03,
|
||||||
0x4d, 0x7e, 0x57, 0x9a, 0xba, 0x89, 0xc5, 0x1a, 0x73, 0xfe, 0x5a, 0x91, 0x29, 0x7f, 0x2b, 0xe1,
|
0x16, 0x67, 0x5d, 0xfa, 0x3f, 0x19, 0xca, 0x4b, 0x14, 0x1d, 0x40, 0xd3, 0x0d, 0xc6, 0xa1, 0x9f,
|
||||||
0x67, 0x6b, 0x4c, 0xf8, 0x6d, 0xd0, 0x96, 0xfe, 0xa7, 0xcc, 0x1f, 0x2d, 0x27, 0x05, 0x57, 0x25,
|
0x05, 0x1d, 0x50, 0x8f, 0xc7, 0x9d, 0x1c, 0xf2, 0xbb, 0x99, 0xa9, 0x9f, 0x58, 0x8c, 0x09, 0xe7,
|
||||||
0xce, 0x93, 0x49, 0x98, 0x4b, 0xcf, 0x92, 0xa9, 0x26, 0x4c, 0x89, 0xa7, 0xcc, 0x2f, 0xa1, 0xcc,
|
0xaf, 0x25, 0x99, 0xf2, 0xb7, 0x12, 0x7e, 0x3e, 0xc7, 0x84, 0xdf, 0x05, 0x65, 0xe9, 0x9f, 0x5f,
|
||||||
0xd7, 0x43, 0xcc, 0xdc, 0xd9, 0x9c, 0x04, 0xb1, 0x58, 0x17, 0x2a, 0x2e, 0x2d, 0x31, 0x3b, 0x46,
|
0x38, 0xcb, 0x4d, 0xc1, 0xf5, 0x0c, 0xe7, 0xc1, 0x24, 0xcc, 0xa5, 0xe7, 0x8c, 0x29, 0x27, 0xcc,
|
||||||
0xdf, 0x00, 0x50, 0x5e, 0x1f, 0x61, 0x37, 0x73, 0x2a, 0x96, 0x44, 0xf5, 0xe8, 0x8b, 0x8c, 0x30,
|
0x0c, 0x4f, 0x99, 0x5f, 0x40, 0x95, 0xf7, 0x43, 0xcc, 0x1c, 0x7f, 0x4e, 0x82, 0x58, 0xf4, 0x85,
|
||||||
0x96, 0x13, 0x70, 0x20, 0xfe, 0x0e, 0x6f, 0xe6, 0x14, 0x17, 0xa9, 0x7c, 0x44, 0xaf, 0xa0, 0x32,
|
0x8c, 0x2b, 0x4b, 0xcc, 0x8c, 0xd1, 0xd7, 0x00, 0x94, 0xe7, 0x47, 0xd8, 0xcd, 0x9c, 0x8a, 0x96,
|
||||||
0x09, 0xa3, 0x8f, 0x6e, 0x34, 0x26, 0x02, 0x4c, 0xb7, 0x8d, 0x87, 0x19, 0x0f, 0x27, 0x89, 0x5d,
|
0xa8, 0x1f, 0x7d, 0x9e, 0x13, 0xc6, 0x72, 0x03, 0x0e, 0xc4, 0xbf, 0xf6, 0xcd, 0x9c, 0xe2, 0x32,
|
||||||
0x0c, 0x3f, 0xfd, 0x0c, 0x97, 0x27, 0x99, 0x77, 0x74, 0x06, 0x48, 0x8e, 0x17, 0xab, 0x3c, 0x71,
|
0xcd, 0x7e, 0xa2, 0x97, 0x50, 0x9b, 0x86, 0xd1, 0x07, 0x27, 0x9a, 0x10, 0x01, 0xa6, 0xc7, 0xc6,
|
||||||
0x52, 0x10, 0x4e, 0xf6, 0xef, 0x3a, 0xe1, 0x9b, 0xb4, 0x74, 0xa4, 0x4d, 0x6e, 0x61, 0xe8, 0xf7,
|
0xc3, 0x9c, 0x87, 0x93, 0xc4, 0x2e, 0xa6, 0x9f, 0x7e, 0x82, 0xab, 0xd3, 0xdc, 0x18, 0x9d, 0x01,
|
||||||
0x50, 0x8e, 0x29, 0x63, 0x3e, 0x4d, 0xdd, 0x14, 0x85, 0x9b, 0xbd, 0xb5, 0x6b, 0x05, 0x37, 0x4b,
|
0xca, 0xe6, 0x8b, 0x2e, 0x4f, 0x9c, 0x94, 0x84, 0x93, 0xfd, 0xbb, 0x4e, 0xf8, 0x21, 0x9d, 0x39,
|
||||||
0x0f, 0xa5, 0x78, 0xf5, 0x8a, 0x8e, 0xa1, 0xe6, 0x7b, 0xc1, 0x75, 0x36, 0x0d, 0x10, 0xe3, 0x1b,
|
0x52, 0xa6, 0xb7, 0x30, 0xf4, 0x7b, 0xa8, 0xc6, 0x94, 0x31, 0x8f, 0xa6, 0x6e, 0xca, 0xc2, 0xcd,
|
||||||
0x99, 0xf1, 0x3d, 0x2f, 0xb8, 0xce, 0xe6, 0x50, 0xf1, 0xb3, 0x80, 0xfe, 0x07, 0x28, 0x2e, 0x67,
|
0xde, 0xda, 0x9b, 0x83, 0x9b, 0x33, 0x0f, 0x95, 0x78, 0x35, 0x44, 0xc7, 0xd0, 0xf0, 0xdc, 0xe0,
|
||||||
0x09, 0x95, 0x60, 0xe7, 0xc2, 0x3e, 0xb3, 0x9d, 0xb7, 0xb6, 0xf6, 0x19, 0x2a, 0x80, 0x3a, 0x30,
|
0x3a, 0x1f, 0x06, 0x88, 0xf9, 0xad, 0xdc, 0xfc, 0x81, 0x1b, 0x5c, 0xe7, 0x63, 0xa8, 0x79, 0x79,
|
||||||
0x6d, 0x43, 0x53, 0x38, 0x8c, 0xcd, 0xae, 0x69, 0xbd, 0x31, 0xb5, 0x2d, 0xfe, 0x72, 0xe2, 0xe0,
|
0x40, 0xfd, 0x03, 0x94, 0x97, 0xbb, 0x84, 0x2a, 0xb0, 0x73, 0x61, 0x9e, 0x99, 0xd6, 0x1b, 0x53,
|
||||||
0xb7, 0x1d, 0x6c, 0x68, 0xb9, 0xe3, 0x1d, 0xc8, 0x8b, 0xb8, 0xfa, 0x3f, 0x15, 0x28, 0x88, 0x0e,
|
0xf9, 0x04, 0x95, 0x40, 0x1e, 0xe9, 0xa6, 0xa6, 0x48, 0x1c, 0xc6, 0x7a, 0x5f, 0x37, 0x5e, 0xeb,
|
||||||
0x06, 0x93, 0x10, 0xfd, 0x02, 0x96, 0xe2, 0x12, 0x9b, 0x1b, 0x3f, 0x70, 0x85, 0xea, 0x2a, 0x78,
|
0xca, 0x16, 0x1f, 0x9c, 0x58, 0xf8, 0x4d, 0x0f, 0x6b, 0x4a, 0xe1, 0x78, 0x07, 0x8a, 0x62, 0x5d,
|
||||||
0x29, 0x98, 0x61, 0x8a, 0x73, 0xf2, 0x52, 0x1a, 0x4b, 0xf2, 0x56, 0x42, 0x96, 0x86, 0x25, 0xf9,
|
0xf5, 0x5f, 0x12, 0x94, 0x44, 0x05, 0x83, 0x69, 0x88, 0x7e, 0x01, 0x4b, 0x71, 0x89, 0xc3, 0x8d,
|
||||||
0x59, 0xc6, 0xf3, 0xda, 0x96, 0xa3, 0xe2, 0x9a, 0x34, 0xc8, 0x1d, 0xf6, 0x59, 0xc6, 0xf1, 0xda,
|
0x5f, 0xb8, 0x42, 0x75, 0x35, 0xbc, 0x14, 0x8c, 0x9d, 0xe2, 0x9c, 0xbc, 0x94, 0xc6, 0x92, 0xbc,
|
||||||
0x4e, 0xac, 0xe2, 0x9a, 0x34, 0xa4, 0x5c, 0xfd, 0xb7, 0x50, 0xce, 0xf6, 0x1c, 0x3d, 0x05, 0xd5,
|
0x95, 0x90, 0x33, 0xc3, 0x92, 0xfc, 0x34, 0xe7, 0x79, 0xed, 0xc8, 0x91, 0x71, 0x23, 0x33, 0x64,
|
||||||
0x0b, 0x26, 0x61, 0xba, 0x10, 0xeb, 0xb7, 0xc4, 0xc5, 0x8b, 0xc4, 0x82, 0xa0, 0x23, 0xd0, 0x6e,
|
0x27, 0xec, 0xd3, 0x9c, 0xe3, 0xb5, 0x93, 0x58, 0xc6, 0x8d, 0xcc, 0x90, 0x72, 0xd5, 0xdf, 0x42,
|
||||||
0xf7, 0x59, 0xaf, 0x40, 0x29, 0xd3, 0x34, 0xfd, 0x5f, 0x0a, 0x54, 0xd6, 0x9a, 0xf0, 0xa3, 0xbd,
|
0x35, 0x5f, 0x73, 0xf4, 0x04, 0x64, 0x37, 0x98, 0x86, 0x69, 0x23, 0x36, 0x6f, 0x89, 0x8b, 0x27,
|
||||||
0xa3, 0x6f, 0xa0, 0xfc, 0xd1, 0x8b, 0x28, 0xc9, 0x1e, 0xff, 0xd5, 0xa3, 0xe6, 0xfa, 0xf1, 0x2f,
|
0x89, 0x05, 0x41, 0x45, 0xa0, 0xdc, 0xae, 0xb3, 0x5a, 0x83, 0x4a, 0xae, 0x68, 0xea, 0xbf, 0x25,
|
||||||
0xff, 0x77, 0xc3, 0x31, 0xc5, 0x25, 0xce, 0x4f, 0x01, 0xf4, 0x47, 0xa8, 0xa6, 0x23, 0xc9, 0x98,
|
0xa8, 0xad, 0x15, 0xe1, 0x47, 0x7b, 0x47, 0x5f, 0x43, 0xf5, 0x83, 0x1b, 0x51, 0x92, 0xbf, 0xfe,
|
||||||
0x32, 0xd7, 0xf3, 0xc5, 0x54, 0x55, 0xd7, 0xe4, 0x91, 0x72, 0x0d, 0x61, 0xc7, 0x95, 0x49, 0xf6,
|
0xeb, 0x47, 0xed, 0xf5, 0xeb, 0x3f, 0xfb, 0xbf, 0x1f, 0x4e, 0x28, 0xae, 0x70, 0x7e, 0x0a, 0xa0,
|
||||||
0x15, 0x7d, 0xb5, 0x72, 0x10, 0xb3, 0xc8, 0x0b, 0xae, 0xc4, 0xfc, 0x15, 0x97, 0xb4, 0x81, 0x00,
|
0x3f, 0x42, 0x3d, 0x9d, 0x49, 0x26, 0x94, 0x39, 0xae, 0x27, 0xb6, 0xaa, 0xbe, 0x26, 0x8f, 0x94,
|
||||||
0x9f, 0xfd, 0x4d, 0x85, 0xca, 0x9a, 0x9f, 0x75, 0x21, 0x55, 0xa0, 0x68, 0x3b, 0xc4, 0x30, 0x87,
|
0xab, 0x09, 0x3b, 0xae, 0x4d, 0xf3, 0x43, 0xf4, 0xe5, 0xca, 0x41, 0xcc, 0x22, 0x37, 0xb8, 0x12,
|
||||||
0x1d, 0xab, 0xa7, 0x29, 0x48, 0x83, 0xb2, 0x63, 0x5b, 0x8e, 0x4d, 0x0c, 0xb3, 0xeb, 0x18, 0x5c,
|
0xfb, 0x57, 0x5e, 0xd2, 0x46, 0x02, 0x7c, 0xfa, 0x37, 0x19, 0x6a, 0x6b, 0x7e, 0xd6, 0x85, 0x54,
|
||||||
0x52, 0x9f, 0xc3, 0x6e, 0xcf, 0xb2, 0xcf, 0x88, 0xed, 0x0c, 0x89, 0xd9, 0xb3, 0xbe, 0xb5, 0x8e,
|
0x83, 0xb2, 0x69, 0x11, 0x4d, 0xb7, 0x7b, 0xc6, 0x40, 0x91, 0x90, 0x02, 0x55, 0xcb, 0x34, 0x2c,
|
||||||
0x7b, 0xa6, 0x96, 0x43, 0x0f, 0x40, 0x73, 0x6c, 0xd2, 0x3d, 0xed, 0x58, 0x36, 0x19, 0x5a, 0xe7,
|
0x93, 0x68, 0x7a, 0xdf, 0xd2, 0xb8, 0xa4, 0x3e, 0x85, 0xdd, 0x81, 0x61, 0x9e, 0x11, 0xd3, 0xb2,
|
||||||
0xa6, 0x73, 0x31, 0xd4, 0x54, 0x8e, 0x9e, 0x0e, 0x7b, 0x5d, 0x62, 0xbe, 0xeb, 0x9a, 0xa6, 0x31,
|
0x89, 0x3e, 0x30, 0xbe, 0x31, 0x8e, 0x07, 0xba, 0x52, 0x40, 0x0f, 0x40, 0xb1, 0x4c, 0xd2, 0x3f,
|
||||||
0x20, 0xe7, 0x9d, 0x77, 0x5a, 0x1e, 0x35, 0xe0, 0x81, 0x65, 0x0f, 0x2e, 0x4e, 0x4e, 0xac, 0xae,
|
0xed, 0x19, 0x26, 0xb1, 0x8d, 0x73, 0xdd, 0xba, 0xb0, 0x15, 0x99, 0xa3, 0xa7, 0xf6, 0xa0, 0x4f,
|
||||||
0x65, 0xda, 0x43, 0x72, 0xdc, 0xe9, 0x75, 0xec, 0xae, 0xa9, 0x6d, 0xa3, 0x3d, 0x40, 0x96, 0xdd,
|
0xf4, 0xb7, 0x7d, 0x5d, 0xd7, 0x46, 0xe4, 0xbc, 0xf7, 0x56, 0x29, 0xa2, 0x16, 0x3c, 0x30, 0xcc,
|
||||||
0x75, 0xce, 0xfb, 0x3d, 0x73, 0x68, 0x12, 0x29, 0xdd, 0x1d, 0x54, 0x87, 0x9a, 0xf0, 0xd3, 0x31,
|
0xd1, 0xc5, 0xc9, 0x89, 0xd1, 0x37, 0x74, 0xd3, 0x26, 0xc7, 0xbd, 0x41, 0xcf, 0xec, 0xeb, 0xca,
|
||||||
0x0c, 0x72, 0xd2, 0xb1, 0x7a, 0xa6, 0xa1, 0x15, 0x78, 0x26, 0x29, 0x63, 0x40, 0x0c, 0x6b, 0xd0,
|
0x36, 0xda, 0x03, 0x64, 0x98, 0x7d, 0xeb, 0x7c, 0x38, 0xd0, 0x6d, 0x9d, 0x64, 0xd2, 0xdd, 0x41,
|
||||||
0x39, 0xe6, 0x70, 0x91, 0xc7, 0xb4, 0xec, 0x37, 0x8e, 0xd5, 0x35, 0x49, 0x97, 0xbb, 0xe5, 0x28,
|
0x4d, 0x68, 0x08, 0x3f, 0x3d, 0x4d, 0x23, 0x27, 0x3d, 0x63, 0xa0, 0x6b, 0x4a, 0x89, 0x47, 0x92,
|
||||||
0x70, 0xb2, 0x44, 0x2f, 0x6c, 0xc3, 0xc4, 0xfd, 0x8e, 0x65, 0x68, 0x25, 0xb4, 0x0f, 0x0f, 0x25,
|
0x32, 0x46, 0x44, 0x33, 0x46, 0xbd, 0x63, 0x0e, 0x97, 0xf9, 0x9a, 0x86, 0xf9, 0xda, 0x32, 0xfa,
|
||||||
0x6c, 0xbe, 0xeb, 0x5b, 0xf8, 0x7b, 0x32, 0x74, 0x1c, 0x32, 0x70, 0x1c, 0x5b, 0x2b, 0x67, 0x3d,
|
0x3a, 0xe9, 0x73, 0xb7, 0x1c, 0x05, 0x4e, 0xce, 0xd0, 0x0b, 0x53, 0xd3, 0xf1, 0xb0, 0x67, 0x68,
|
||||||
0xf1, 0x6a, 0x9d, 0xbe, 0x69, 0x6b, 0x15, 0xf4, 0x10, 0xea, 0xe7, 0xfd, 0x3e, 0x91, 0x16, 0x59,
|
0x4a, 0x05, 0xed, 0xc3, 0xc3, 0x0c, 0xd6, 0xdf, 0x0e, 0x0d, 0xfc, 0x1d, 0xb1, 0x2d, 0x8b, 0x8c,
|
||||||
0x6c, 0x95, 0xd3, 0x3b, 0x86, 0x81, 0xcd, 0xc1, 0x80, 0x9c, 0x5b, 0x83, 0xf3, 0xce, 0xb0, 0x7b,
|
0x2c, 0xcb, 0x54, 0xaa, 0x79, 0x4f, 0x3c, 0x5b, 0x6b, 0xa8, 0x9b, 0x4a, 0x0d, 0x3d, 0x84, 0xe6,
|
||||||
0xaa, 0xd5, 0x78, 0x49, 0x03, 0x73, 0x48, 0x86, 0xce, 0xb0, 0xd3, 0x5b, 0xe1, 0x1a, 0x4f, 0x68,
|
0xf9, 0x70, 0x48, 0x32, 0x4b, 0x96, 0x6c, 0x9d, 0xd3, 0x7b, 0x9a, 0x86, 0xf5, 0xd1, 0x88, 0x9c,
|
||||||
0x85, 0xf3, 0xa0, 0x3d, 0xe7, 0xad, 0xb6, 0xcb, 0x27, 0x9c, 0xc3, 0xce, 0x9b, 0x34, 0x45, 0xc4,
|
0x1b, 0xa3, 0xf3, 0x9e, 0xdd, 0x3f, 0x55, 0x1a, 0x3c, 0xa5, 0x91, 0x6e, 0x13, 0xdb, 0xb2, 0x7b,
|
||||||
0x6b, 0x4f, 0xdb, 0x23, 0x63, 0x6a, 0x75, 0x0e, 0x5a, 0xf6, 0x9b, 0x4e, 0xcf, 0x32, 0xc8, 0x99,
|
0x83, 0x15, 0xae, 0xf0, 0x80, 0x56, 0x38, 0x5f, 0x74, 0x60, 0xbd, 0x51, 0x76, 0xf9, 0x86, 0x73,
|
||||||
0xf9, 0xbd, 0x58, 0xfa, 0x0f, 0x38, 0x98, 0x64, 0x46, 0xfa, 0xd8, 0xf9, 0x96, 0x27, 0xa2, 0x7d,
|
0xd8, 0x7a, 0x9d, 0x86, 0x88, 0x78, 0xee, 0x69, 0x79, 0xb2, 0x35, 0x95, 0x26, 0x07, 0x0d, 0xf3,
|
||||||
0x8e, 0x10, 0x54, 0xbb, 0x16, 0xee, 0x5e, 0xf4, 0x3a, 0x98, 0x60, 0xe7, 0x62, 0x68, 0x6a, 0x7b,
|
0x75, 0x6f, 0x60, 0x68, 0xe4, 0x4c, 0xff, 0x4e, 0xb4, 0xfe, 0x03, 0x0e, 0x26, 0x91, 0x91, 0x21,
|
||||||
0x47, 0xff, 0xc9, 0xc3, 0xb6, 0x38, 0xa8, 0x22, 0xf4, 0x8a, 0xeb, 0x7f, 0xf9, 0x2d, 0x84, 0x1e,
|
0xb6, 0xbe, 0xe1, 0x81, 0x28, 0x9f, 0x22, 0x04, 0xf5, 0xbe, 0x81, 0xfb, 0x17, 0x83, 0x1e, 0x26,
|
||||||
0x7f, 0xf2, 0x1b, 0xa9, 0x29, 0x2f, 0xb3, 0x29, 0xfc, 0x42, 0x41, 0x7f, 0x82, 0x72, 0xf6, 0xfb,
|
0xd8, 0xba, 0xb0, 0x75, 0x65, 0xef, 0xe8, 0xbf, 0x45, 0xd8, 0x16, 0x17, 0x55, 0x84, 0x5e, 0x72,
|
||||||
0x02, 0x65, 0xf7, 0xf6, 0x0d, 0x1f, 0x1e, 0x1b, 0x3c, 0x9c, 0x81, 0x66, 0xc6, 0xcc, 0x9b, 0xb9,
|
0xfd, 0x2f, 0x3f, 0x94, 0xd0, 0xa3, 0x8f, 0x7e, 0x40, 0xb5, 0xb3, 0xc7, 0x6c, 0x0a, 0x3f, 0x97,
|
||||||
0x8c, 0xca, 0xef, 0x05, 0xd4, 0xcc, 0x78, 0xb9, 0xf5, 0x11, 0xd2, 0xdc, 0xdf, 0x68, 0x4b, 0x4f,
|
0xd0, 0x9f, 0xa0, 0x9a, 0xff, 0xbe, 0x40, 0xf9, 0xb3, 0x7d, 0xc3, 0x87, 0xc7, 0x06, 0x0f, 0x67,
|
||||||
0xe5, 0x5e, 0x52, 0x4e, 0x7a, 0x63, 0xbf, 0x53, 0xce, 0xfa, 0x67, 0x42, 0xf3, 0x8b, 0xfb, 0xcc,
|
0xa0, 0xe8, 0x31, 0x73, 0x7d, 0x87, 0xd1, 0xec, 0x7b, 0x01, 0xb5, 0x73, 0x5e, 0x6e, 0x7d, 0x84,
|
||||||
0xa9, 0xb7, 0x31, 0xd4, 0x37, 0x5c, 0xc2, 0xd1, 0x57, 0xd9, 0x0c, 0xee, 0xbd, 0xc2, 0x37, 0x9f,
|
0xb4, 0xf7, 0x37, 0xda, 0xd2, 0x5b, 0x79, 0x90, 0xa4, 0x93, 0xbe, 0xd8, 0xef, 0xa4, 0xb3, 0xfe,
|
||||||
0xfc, 0x10, 0x6d, 0x15, 0x65, 0xc3, 0x6d, 0x7d, 0x2d, 0xca, 0xfd, 0x77, 0xfd, 0xb5, 0x28, 0x9f,
|
0x99, 0xd0, 0xfe, 0xfc, 0x3e, 0x73, 0xea, 0x6d, 0x02, 0xcd, 0x0d, 0x8f, 0x70, 0xf4, 0x65, 0x3e,
|
||||||
0xba, 0xf4, 0xbf, 0x07, 0xed, 0xf6, 0xe5, 0x0e, 0xe9, 0xb7, 0xc7, 0xde, 0xbd, 0x65, 0x36, 0x7f,
|
0x82, 0x7b, 0x9f, 0xf0, 0xed, 0xc7, 0x3f, 0x44, 0x5b, 0xad, 0xb2, 0xe1, 0xb5, 0xbe, 0xb6, 0xca,
|
||||||
0xf6, 0x49, 0x4e, 0xea, 0xdc, 0x02, 0x58, 0x5d, 0x91, 0xd0, 0xa3, 0xcc, 0x90, 0x3b, 0x57, 0xbc,
|
0xfd, 0x6f, 0xfd, 0xb5, 0x55, 0x3e, 0xf6, 0xe8, 0x7f, 0x07, 0xca, 0xed, 0xc7, 0x1d, 0x52, 0x6f,
|
||||||
0xe6, 0xe3, 0x7b, 0xac, 0xa9, 0xab, 0x21, 0xd4, 0x37, 0xdc, 0x99, 0xd6, 0x66, 0xe3, 0xfe, 0x3b,
|
0xcf, 0xbd, 0xfb, 0xca, 0x6c, 0xff, 0xec, 0xa3, 0x9c, 0xd4, 0xb9, 0x01, 0xb0, 0x7a, 0x22, 0xa1,
|
||||||
0x55, 0xf3, 0xc1, 0xa6, 0xab, 0xc5, 0x0b, 0xe5, 0xf8, 0xeb, 0x3f, 0x1f, 0x5e, 0x79, 0x6c, 0xba,
|
0xcf, 0x72, 0x53, 0xee, 0x3c, 0xf1, 0xda, 0x8f, 0xee, 0xb1, 0xa6, 0xae, 0x6c, 0x68, 0x6e, 0x78,
|
||||||
0xb8, 0x3c, 0x18, 0x85, 0xb3, 0x43, 0xdf, 0xbb, 0x9a, 0xb2, 0xc0, 0x0b, 0xae, 0x02, 0xca, 0x3e,
|
0x33, 0xad, 0xed, 0xc6, 0xfd, 0x6f, 0xaa, 0xf6, 0x83, 0x4d, 0x4f, 0x8b, 0xe7, 0xd2, 0xf1, 0x57,
|
||||||
0x86, 0xd1, 0xf5, 0xa1, 0x1f, 0x8c, 0x0f, 0x85, 0x2e, 0x0f, 0x97, 0xc3, 0x2f, 0xb7, 0xc5, 0x6f,
|
0x7f, 0x3e, 0xbc, 0x72, 0xd9, 0x6c, 0x71, 0x79, 0x30, 0x0e, 0xfd, 0x43, 0xcf, 0xbd, 0x9a, 0xb1,
|
||||||
0x47, 0xbf, 0xfa, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x74, 0xbe, 0x98, 0x4d, 0x6b, 0x12, 0x00,
|
0xc0, 0x0d, 0xae, 0x02, 0xca, 0x3e, 0x84, 0xd1, 0xf5, 0xa1, 0x17, 0x4c, 0x0e, 0x85, 0x2e, 0x0f,
|
||||||
0x00,
|
0x97, 0xd3, 0x2f, 0xb7, 0xc5, 0x1f, 0x96, 0x7e, 0xf5, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5f,
|
||||||
|
0x5a, 0x7c, 0x81, 0x88, 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.
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ message SendPaymentRequest {
|
|||||||
fallback.
|
fallback.
|
||||||
*/
|
*/
|
||||||
repeated lnrpc.FeatureBit dest_features = 16;
|
repeated lnrpc.FeatureBit dest_features = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
The maximum number of partial payments that may be use to complete the full
|
||||||
|
amount.
|
||||||
|
*/
|
||||||
|
uint32 max_htlcs = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TrackPaymentRequest {
|
message TrackPaymentRequest {
|
||||||
|
|||||||
@@ -547,6 +547,14 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||||||
}
|
}
|
||||||
payIntent.CltvLimit = cltvLimit
|
payIntent.CltvLimit = cltvLimit
|
||||||
|
|
||||||
|
// Take max htlcs from the request. Map zero to one for backwards
|
||||||
|
// compatibility.
|
||||||
|
maxHtlcs := rpcPayReq.MaxHtlcs
|
||||||
|
if maxHtlcs == 0 {
|
||||||
|
maxHtlcs = 1
|
||||||
|
}
|
||||||
|
payIntent.MaxHtlcs = maxHtlcs
|
||||||
|
|
||||||
// Take fee limit from request.
|
// Take fee limit from request.
|
||||||
payIntent.FeeLimit, err = lnrpc.UnmarshallAmt(
|
payIntent.FeeLimit, err = lnrpc.UnmarshallAmt(
|
||||||
rpcPayReq.FeeLimitSat, rpcPayReq.FeeLimitMsat,
|
rpcPayReq.FeeLimitSat, rpcPayReq.FeeLimitMsat,
|
||||||
|
|||||||
+143
-102
@@ -22,6 +22,9 @@ import (
|
|||||||
func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) {
|
func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
ctx := newMppTestContext(t, net)
|
||||||
|
defer ctx.shutdownNodes()
|
||||||
|
|
||||||
// To ensure the payment goes through seperate paths, we'll set a
|
// To ensure the payment goes through seperate paths, we'll set a
|
||||||
// channel size that can only carry one shard at a time. We'll divide
|
// channel size that can only carry one shard at a time. We'll divide
|
||||||
// the payment into 3 shards.
|
// the payment into 3 shards.
|
||||||
@@ -38,113 +41,19 @@ func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// \ /
|
// \ /
|
||||||
// \__ Dave ____/
|
// \__ Dave ____/
|
||||||
//
|
//
|
||||||
//
|
ctx.openChannel(ctx.carol, ctx.bob, chanAmt)
|
||||||
// Create the three nodes in addition to Alice and Bob.
|
ctx.openChannel(ctx.dave, ctx.bob, chanAmt)
|
||||||
alice := net.Alice
|
ctx.openChannel(ctx.alice, ctx.dave, chanAmt)
|
||||||
bob := net.Bob
|
ctx.openChannel(ctx.eve, ctx.bob, chanAmt)
|
||||||
carol, err := net.NewNode("carol", nil)
|
ctx.openChannel(ctx.carol, ctx.eve, chanAmt)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create carol: %v", err)
|
|
||||||
}
|
|
||||||
defer shutdownAndAssert(net, t, carol)
|
|
||||||
|
|
||||||
dave, err := net.NewNode("dave", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create dave: %v", err)
|
|
||||||
}
|
|
||||||
defer shutdownAndAssert(net, t, dave)
|
|
||||||
|
|
||||||
eve, err := net.NewNode("eve", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create eve: %v", err)
|
|
||||||
}
|
|
||||||
defer shutdownAndAssert(net, t, eve)
|
|
||||||
|
|
||||||
nodes := []*lntest.HarnessNode{alice, bob, carol, dave, eve}
|
|
||||||
|
|
||||||
// Connect nodes to ensure propagation of channels.
|
|
||||||
for i := 0; i < len(nodes); i++ {
|
|
||||||
for j := i + 1; j < len(nodes); j++ {
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
if err := net.EnsureConnected(ctxt, nodes[i], nodes[j]); err != nil {
|
|
||||||
t.Fatalf("unable to connect nodes: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll send shards along three routes from Alice.
|
|
||||||
sendRoutes := [][]*lntest.HarnessNode{
|
|
||||||
{carol, bob},
|
|
||||||
{dave, bob},
|
|
||||||
{carol, eve, bob},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep a list of all our active channels.
|
|
||||||
var networkChans []*lnrpc.ChannelPoint
|
|
||||||
var closeChannelFuncs []func()
|
|
||||||
|
|
||||||
// openChannel is a helper to open a channel from->to.
|
|
||||||
openChannel := func(from, to *lntest.HarnessNode, chanSize btcutil.Amount) {
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err := net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, from)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to send coins : %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
|
|
||||||
chanPoint := openChannelAndAssert(
|
|
||||||
ctxt, t, net, from, to,
|
|
||||||
lntest.OpenChannelParams{
|
|
||||||
Amt: chanSize,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
closeChannelFuncs = append(closeChannelFuncs, func() {
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
|
|
||||||
closeChannelAndAssert(
|
|
||||||
ctxt, t, net, from, chanPoint, false,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
networkChans = append(networkChans, chanPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open channels between the nodes.
|
|
||||||
openChannel(carol, bob, chanAmt)
|
|
||||||
openChannel(dave, bob, chanAmt)
|
|
||||||
openChannel(alice, dave, chanAmt)
|
|
||||||
openChannel(eve, bob, chanAmt)
|
|
||||||
openChannel(carol, eve, chanAmt)
|
|
||||||
|
|
||||||
// Since the channel Alice-> Carol will have to carry two
|
// Since the channel Alice-> Carol will have to carry two
|
||||||
// shards, we make it larger.
|
// shards, we make it larger.
|
||||||
openChannel(alice, carol, chanAmt+shardAmt)
|
ctx.openChannel(ctx.alice, ctx.carol, chanAmt+shardAmt)
|
||||||
|
|
||||||
for _, f := range closeChannelFuncs {
|
defer ctx.closeChannels()
|
||||||
defer f()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for all nodes to have seen all channels.
|
ctx.waitForChannels()
|
||||||
for _, chanPoint := range networkChans {
|
|
||||||
for _, node := range nodes {
|
|
||||||
txid, err := lnd.GetChanPointFundingTxid(chanPoint)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get txid: %v", err)
|
|
||||||
}
|
|
||||||
point := wire.OutPoint{
|
|
||||||
Hash: *txid,
|
|
||||||
Index: chanPoint.OutputIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("(%d): timeout waiting for "+
|
|
||||||
"channel(%s) open: %v",
|
|
||||||
node.NodeID, point, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make Bob create an invoice for Alice to pay.
|
// Make Bob create an invoice for Alice to pay.
|
||||||
payReqs, rHashes, invoices, err := createPayReqs(
|
payReqs, rHashes, invoices, err := createPayReqs(
|
||||||
@@ -197,6 +106,13 @@ func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
return routeResp.Route, nil
|
return routeResp.Route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll send shards along three routes from Alice.
|
||||||
|
sendRoutes := [][]*lntest.HarnessNode{
|
||||||
|
{ctx.carol, ctx.bob},
|
||||||
|
{ctx.dave, ctx.bob},
|
||||||
|
{ctx.carol, ctx.eve, ctx.bob},
|
||||||
|
}
|
||||||
|
|
||||||
responses := make(chan *routerrpc.SendToRouteResponse, len(sendRoutes))
|
responses := make(chan *routerrpc.SendToRouteResponse, len(sendRoutes))
|
||||||
for _, hops := range sendRoutes {
|
for _, hops := range sendRoutes {
|
||||||
// Build a route for the specified hops.
|
// Build a route for the specified hops.
|
||||||
@@ -355,3 +271,128 @@ func testSendToRouteMultiPath(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// ...and in Bob's list of paid invoices.
|
// ...and in Bob's list of paid invoices.
|
||||||
assertSettledInvoice(net.Bob, rHash, 3)
|
assertSettledInvoice(net.Bob, rHash, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mppTestContext struct {
|
||||||
|
t *harnessTest
|
||||||
|
net *lntest.NetworkHarness
|
||||||
|
|
||||||
|
// Keep a list of all our active channels.
|
||||||
|
networkChans []*lnrpc.ChannelPoint
|
||||||
|
closeChannelFuncs []func()
|
||||||
|
|
||||||
|
alice, bob, carol, dave, eve *lntest.HarnessNode
|
||||||
|
nodes []*lntest.HarnessNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMppTestContext(t *harnessTest,
|
||||||
|
net *lntest.NetworkHarness) *mppTestContext {
|
||||||
|
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// Create a five-node context consisting of Alice, Bob and three new
|
||||||
|
// nodes.
|
||||||
|
carol, err := net.NewNode("carol", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create carol: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dave, err := net.NewNode("dave", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create dave: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
eve, err := net.NewNode("eve", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create eve: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect nodes to ensure propagation of channels.
|
||||||
|
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave, eve}
|
||||||
|
for i := 0; i < len(nodes); i++ {
|
||||||
|
for j := i + 1; j < len(nodes); j++ {
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.EnsureConnected(ctxt, nodes[i], nodes[j]); err != nil {
|
||||||
|
t.Fatalf("unable to connect nodes: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := mppTestContext{
|
||||||
|
t: t,
|
||||||
|
net: net,
|
||||||
|
alice: net.Alice,
|
||||||
|
bob: net.Bob,
|
||||||
|
carol: carol,
|
||||||
|
dave: dave,
|
||||||
|
eve: eve,
|
||||||
|
nodes: nodes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// openChannel is a helper to open a channel from->to.
|
||||||
|
func (c *mppTestContext) openChannel(from, to *lntest.HarnessNode, chanSize btcutil.Amount) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err := c.net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, from)
|
||||||
|
if err != nil {
|
||||||
|
c.t.Fatalf("unable to send coins : %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
|
||||||
|
chanPoint := openChannelAndAssert(
|
||||||
|
ctxt, c.t, c.net, from, to,
|
||||||
|
lntest.OpenChannelParams{
|
||||||
|
Amt: chanSize,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c.closeChannelFuncs = append(c.closeChannelFuncs, func() {
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
|
closeChannelAndAssert(
|
||||||
|
ctxt, c.t, c.net, from, chanPoint, false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
c.networkChans = append(c.networkChans, chanPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mppTestContext) closeChannels() {
|
||||||
|
for _, f := range c.closeChannelFuncs {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mppTestContext) shutdownNodes() {
|
||||||
|
shutdownAndAssert(c.net, c.t, c.carol)
|
||||||
|
shutdownAndAssert(c.net, c.t, c.dave)
|
||||||
|
shutdownAndAssert(c.net, c.t, c.eve)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mppTestContext) waitForChannels() {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// Wait for all nodes to have seen all channels.
|
||||||
|
for _, chanPoint := range c.networkChans {
|
||||||
|
for _, node := range c.nodes {
|
||||||
|
txid, err := lnd.GetChanPointFundingTxid(chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
c.t.Fatalf("unable to get txid: %v", err)
|
||||||
|
}
|
||||||
|
point := wire.OutPoint{
|
||||||
|
Hash: *txid,
|
||||||
|
Index: chanPoint.OutputIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = node.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
c.t.Fatalf("(%d): timeout waiting for "+
|
||||||
|
"channel(%s) open: %v",
|
||||||
|
node.NodeID, point, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// +build rpctest
|
||||||
|
|
||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testSendMultiPathPayment tests that we are able to successfully route a
|
||||||
|
// payment using multiple shards across different paths.
|
||||||
|
func testSendMultiPathPayment(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
ctx := newMppTestContext(t, net)
|
||||||
|
defer ctx.shutdownNodes()
|
||||||
|
|
||||||
|
const paymentAmt = btcutil.Amount(300000)
|
||||||
|
|
||||||
|
// Set up a network with three different paths Alice <-> Bob. Channel
|
||||||
|
// capacities are set such that the payment can only succeed if (at
|
||||||
|
// least) three paths are used.
|
||||||
|
//
|
||||||
|
// _ Eve _
|
||||||
|
// / \
|
||||||
|
// Alice -- Carol ---- Bob
|
||||||
|
// \ /
|
||||||
|
// \__ Dave ____/
|
||||||
|
//
|
||||||
|
ctx.openChannel(ctx.carol, ctx.bob, 135000)
|
||||||
|
ctx.openChannel(ctx.alice, ctx.carol, 235000)
|
||||||
|
ctx.openChannel(ctx.dave, ctx.bob, 135000)
|
||||||
|
ctx.openChannel(ctx.alice, ctx.dave, 135000)
|
||||||
|
ctx.openChannel(ctx.eve, ctx.bob, 135000)
|
||||||
|
ctx.openChannel(ctx.carol, ctx.eve, 135000)
|
||||||
|
|
||||||
|
defer ctx.closeChannels()
|
||||||
|
|
||||||
|
ctx.waitForChannels()
|
||||||
|
|
||||||
|
// Increase Dave's fee to make the test deterministic. Otherwise it
|
||||||
|
// would be unpredictable whether pathfinding would go through Charlie
|
||||||
|
// or Dave for the first shard.
|
||||||
|
_, err := ctx.dave.UpdateChannelPolicy(
|
||||||
|
context.Background(),
|
||||||
|
&lnrpc.PolicyUpdateRequest{
|
||||||
|
Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true},
|
||||||
|
BaseFeeMsat: 500000,
|
||||||
|
FeeRate: 0.001,
|
||||||
|
TimeLockDelta: 40,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("dave policy update: %v", err)
|
||||||
|
}
|
||||||
|
// Our first test will be Alice paying Bob using a SendPayment call.
|
||||||
|
// Let Bob create an invoice for Alice to pay.
|
||||||
|
payReqs, rHashes, invoices, err := createPayReqs(
|
||||||
|
net.Bob, paymentAmt, 1,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create pay reqs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rHash := rHashes[0]
|
||||||
|
payReq := payReqs[0]
|
||||||
|
|
||||||
|
payment := sendAndAssertSuccess(
|
||||||
|
t, net.Alice,
|
||||||
|
&routerrpc.SendPaymentRequest{
|
||||||
|
PaymentRequest: payReq,
|
||||||
|
MaxHtlcs: 10,
|
||||||
|
TimeoutSeconds: 60,
|
||||||
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make sure we got the preimage.
|
||||||
|
if payment.PaymentPreimage != hex.EncodeToString(invoices[0].RPreimage) {
|
||||||
|
t.Fatalf("preimage doesn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that Alice split the payment in at least three shards. Because
|
||||||
|
// the hand-off of the htlc to the link is asynchronous (via a mailbox),
|
||||||
|
// there is some non-determinism in the process. Depending on whether
|
||||||
|
// the new pathfinding round is started before or after the htlc is
|
||||||
|
// locked into the channel, different sharding may occur. Therefore we
|
||||||
|
// can only check if the number of shards isn't below the theoretical
|
||||||
|
// minimum.
|
||||||
|
succeeded := 0
|
||||||
|
for _, htlc := range payment.Htlcs {
|
||||||
|
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const minExpectedShards = 3
|
||||||
|
if succeeded < minExpectedShards {
|
||||||
|
t.Fatalf("expected at least %v shards, but got %v",
|
||||||
|
minExpectedShards, succeeded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure Bob show the invoice as settled for the full
|
||||||
|
// amount.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
inv, err := ctx.bob.LookupInvoice(
|
||||||
|
ctxt, &lnrpc.PaymentHash{
|
||||||
|
RHash: rHash,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error when obtaining invoice: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inv.AmtPaidSat != int64(paymentAmt) {
|
||||||
|
t.Fatalf("incorrect payment amt for invoice"+
|
||||||
|
"want: %d, got %d",
|
||||||
|
paymentAmt, inv.AmtPaidSat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inv.State != lnrpc.Invoice_SETTLED {
|
||||||
|
t.Fatalf("Invoice not settled: %v", inv.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
settled := 0
|
||||||
|
for _, htlc := range inv.Htlcs {
|
||||||
|
if htlc.State == lnrpc.InvoiceHTLCState_SETTLED {
|
||||||
|
settled++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if settled != succeeded {
|
||||||
|
t.Fatalf("expected invoice to be settled "+
|
||||||
|
"with %v HTLCs, had %v", succeeded, settled)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ const (
|
|||||||
channelCloseTimeout = lntest.ChannelCloseTimeout
|
channelCloseTimeout = lntest.ChannelCloseTimeout
|
||||||
itestLndBinary = "../../lnd-itest"
|
itestLndBinary = "../../lnd-itest"
|
||||||
anchorSize = 330
|
anchorSize = 330
|
||||||
|
noFeeLimitMsat = math.MaxInt64
|
||||||
)
|
)
|
||||||
|
|
||||||
// harnessTest wraps a regular testing.T providing enhanced error detection
|
// harnessTest wraps a regular testing.T providing enhanced error detection
|
||||||
@@ -14718,6 +14719,47 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
closeChannelAndAssert(ctxt, t, net, dave, chanPoint, false)
|
closeChannelAndAssert(ctxt, t, net, dave, chanPoint, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendAndAssertSuccess sends the given payment requests and asserts that the
|
||||||
|
// payment completes successfully.
|
||||||
|
func sendAndAssertSuccess(t *harnessTest, node *lntest.HarnessNode,
|
||||||
|
req *routerrpc.SendPaymentRequest) *lnrpc.Payment {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stream, err := node.RouterClient.SendPayment(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := getPaymentResult(stream)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get payment result: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Status != lnrpc.Payment_SUCCEEDED {
|
||||||
|
t.Fatalf("payment failed: %v", result.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPaymentResult reads a final result from the stream and returns it.
|
||||||
|
func getPaymentResult(stream routerrpc.Router_SendPaymentClient) (
|
||||||
|
*lnrpc.Payment, error) {
|
||||||
|
|
||||||
|
for {
|
||||||
|
payment, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payment.Status != lnrpc.Payment_IN_FLIGHT {
|
||||||
|
return payment, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
test func(net *lntest.NetworkHarness, t *harnessTest)
|
test func(net *lntest.NetworkHarness, t *harnessTest)
|
||||||
@@ -14959,6 +15001,10 @@ var testsCases = []*testCase{
|
|||||||
name: "sendtoroute multi path payment",
|
name: "sendtoroute multi path payment",
|
||||||
test: testSendToRouteMultiPath,
|
test: testSendToRouteMultiPath,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "send multi path payment",
|
||||||
|
test: testSendMultiPathPayment,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
// TestLightningNetworkDaemon performs a series of integration tests amongst a
|
||||||
@@ -15099,6 +15145,10 @@ func TestLightningNetworkDaemon(t *testing.T) {
|
|||||||
// Stop at the first failure. Mimic behavior of original test
|
// Stop at the first failure. Mimic behavior of original test
|
||||||
// framework.
|
// framework.
|
||||||
if !success {
|
if !success {
|
||||||
|
// Log failure time to help relate the lnd logs to the
|
||||||
|
// failure.
|
||||||
|
t.Logf("Failure time: %v",
|
||||||
|
time.Now().Format("2006-01-02 15:04:05.000"))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,6 +13,11 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sourceNodeID = 1
|
||||||
|
targetNodeID = 2
|
||||||
|
)
|
||||||
|
|
||||||
// integratedRoutingContext defines the context in which integrated routing
|
// integratedRoutingContext defines the context in which integrated routing
|
||||||
// tests run.
|
// tests run.
|
||||||
type integratedRoutingContext struct {
|
type integratedRoutingContext struct {
|
||||||
@@ -31,8 +38,8 @@ type integratedRoutingContext struct {
|
|||||||
// context with a source and a target node.
|
// context with a source and a target node.
|
||||||
func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
|
func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
|
||||||
// Instantiate a mock graph.
|
// Instantiate a mock graph.
|
||||||
source := newMockNode()
|
source := newMockNode(sourceNodeID)
|
||||||
target := newMockNode()
|
target := newMockNode(targetNodeID)
|
||||||
|
|
||||||
graph := newMockGraph(t)
|
graph := newMockGraph(t)
|
||||||
graph.addNode(source)
|
graph.addNode(source)
|
||||||
@@ -59,6 +66,7 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
|
|||||||
|
|
||||||
pathFindingCfg: PathFindingConfig{
|
pathFindingCfg: PathFindingConfig{
|
||||||
PaymentAttemptPenalty: 1000,
|
PaymentAttemptPenalty: 1000,
|
||||||
|
MinProbability: 0.01,
|
||||||
},
|
},
|
||||||
|
|
||||||
source: source,
|
source: source,
|
||||||
@@ -68,10 +76,25 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
|
|||||||
return &ctx
|
return &ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// htlcAttempt records the route and outcome of an attempted htlc.
|
||||||
|
type htlcAttempt struct {
|
||||||
|
route *route.Route
|
||||||
|
success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h htlcAttempt) String() string {
|
||||||
|
return fmt.Sprintf("success=%v, route=%v", h.success, h.route)
|
||||||
|
}
|
||||||
|
|
||||||
// testPayment launches a test payment and asserts that it is completed after
|
// testPayment launches a test payment and asserts that it is completed after
|
||||||
// the expected number of attempts.
|
// the expected number of attempts.
|
||||||
func (c *integratedRoutingContext) testPayment(expectedNofAttempts int) {
|
func (c *integratedRoutingContext) testPayment(maxHtlcs uint32) ([]htlcAttempt,
|
||||||
var nextPid uint64
|
error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
nextPid uint64
|
||||||
|
attempts []htlcAttempt
|
||||||
|
)
|
||||||
|
|
||||||
// Create temporary database for mission control.
|
// Create temporary database for mission control.
|
||||||
file, err := ioutil.TempFile("", "*.db")
|
file, err := ioutil.TempFile("", "*.db")
|
||||||
@@ -95,14 +118,46 @@ func (c *integratedRoutingContext) testPayment(expectedNofAttempts int) {
|
|||||||
c.t.Fatal(err)
|
c.t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instruct pathfinding to use mission control as a probabiltiy source.
|
getBandwidthHints := func() (map[uint64]lnwire.MilliSatoshi, error) {
|
||||||
restrictParams := RestrictParams{
|
// Create bandwidth hints based on local channel balances.
|
||||||
ProbabilitySource: mc.GetProbability,
|
bandwidthHints := map[uint64]lnwire.MilliSatoshi{}
|
||||||
FeeLimit: lnwire.MaxMilliSatoshi,
|
for _, ch := range c.graph.nodes[c.source.pubkey].channels {
|
||||||
|
bandwidthHints[ch.id] = ch.balance
|
||||||
|
}
|
||||||
|
|
||||||
|
return bandwidthHints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var paymentAddr [32]byte
|
||||||
|
payment := LightningPayment{
|
||||||
|
FinalCLTVDelta: uint16(c.finalExpiry),
|
||||||
|
FeeLimit: lnwire.MaxMilliSatoshi,
|
||||||
|
Target: c.target.pubkey,
|
||||||
|
PaymentAddr: &paymentAddr,
|
||||||
|
DestFeatures: lnwire.NewFeatureVector(mppFeatures, nil),
|
||||||
|
Amount: c.amt,
|
||||||
|
CltvLimit: math.MaxUint32,
|
||||||
|
MaxHtlcs: maxHtlcs,
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &paymentSession{
|
||||||
|
getBandwidthHints: getBandwidthHints,
|
||||||
|
payment: &payment,
|
||||||
|
pathFinder: findPath,
|
||||||
|
getRoutingGraph: func() (routingGraph, func(), error) {
|
||||||
|
return c.graph, func() {}, nil
|
||||||
|
},
|
||||||
|
pathFindingConfig: c.pathFindingCfg,
|
||||||
|
missionControl: mc,
|
||||||
|
minShardAmt: lnwire.NewMSatFromSatoshis(5000),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now the payment control loop starts. It will keep trying routes until
|
// Now the payment control loop starts. It will keep trying routes until
|
||||||
// the payment succeeds.
|
// the payment succeeds.
|
||||||
|
var (
|
||||||
|
amtRemaining = payment.Amount
|
||||||
|
inFlightHtlcs uint32
|
||||||
|
)
|
||||||
for {
|
for {
|
||||||
// Create bandwidth hints based on local channel balances.
|
// Create bandwidth hints based on local channel balances.
|
||||||
bandwidthHints := map[uint64]lnwire.MilliSatoshi{}
|
bandwidthHints := map[uint64]lnwire.MilliSatoshi{}
|
||||||
@@ -111,25 +166,11 @@ func (c *integratedRoutingContext) testPayment(expectedNofAttempts int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find a route.
|
// Find a route.
|
||||||
path, err := findPathInternal(
|
route, err := session.RequestRoute(
|
||||||
nil, bandwidthHints, c.graph,
|
amtRemaining, lnwire.MaxMilliSatoshi, inFlightHtlcs, 0,
|
||||||
&restrictParams,
|
|
||||||
&c.pathFindingCfg,
|
|
||||||
c.source.pubkey, c.target.pubkey,
|
|
||||||
c.amt, c.finalExpiry,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.t.Fatal(err)
|
return attempts, err
|
||||||
}
|
|
||||||
|
|
||||||
finalHop := finalHopParams{
|
|
||||||
amt: c.amt,
|
|
||||||
cltvDelta: uint16(c.finalExpiry),
|
|
||||||
}
|
|
||||||
|
|
||||||
route, err := newRoute(c.source.pubkey, path, 0, finalHop)
|
|
||||||
if err != nil {
|
|
||||||
c.t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send out the htlc on the mock graph.
|
// Send out the htlc on the mock graph.
|
||||||
@@ -140,21 +181,39 @@ func (c *integratedRoutingContext) testPayment(expectedNofAttempts int) {
|
|||||||
c.t.Fatal(err)
|
c.t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the result.
|
success := htlcResult.failure == nil
|
||||||
if htlcResult.failure == nil {
|
attempts = append(attempts, htlcAttempt{
|
||||||
|
route: route,
|
||||||
|
success: success,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process the result. In normal Lightning operations, the
|
||||||
|
// sender doesn't get an acknowledgement from the recipient that
|
||||||
|
// the htlc arrived. In integrated routing tests, this
|
||||||
|
// acknowledgement is available. It is a simplification of
|
||||||
|
// reality that still allows certain classes of tests to be
|
||||||
|
// performed.
|
||||||
|
if success {
|
||||||
|
inFlightHtlcs++
|
||||||
|
|
||||||
err := mc.ReportPaymentSuccess(pid, route)
|
err := mc.ReportPaymentSuccess(pid, route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.t.Fatal(err)
|
c.t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the payment is successful, the control loop can be
|
amtRemaining -= route.ReceiverAmt()
|
||||||
// broken out of.
|
|
||||||
break
|
// If the full amount has been paid, the payment is
|
||||||
|
// successful and the control loop can be terminated.
|
||||||
|
if amtRemaining == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try to send the remaining amount.
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failure, update mission control and retry.
|
// Failure, update mission control and retry.
|
||||||
c.t.Logf("fail: %v @ %v\n", htlcResult.failure, htlcResult.failureSource)
|
|
||||||
|
|
||||||
finalResult, err := mc.ReportPaymentFail(
|
finalResult, err := mc.ReportPaymentFail(
|
||||||
pid, route,
|
pid, route,
|
||||||
getNodeIndex(route, htlcResult.failureSource),
|
getNodeIndex(route, htlcResult.failureSource),
|
||||||
@@ -165,16 +224,11 @@ func (c *integratedRoutingContext) testPayment(expectedNofAttempts int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if finalResult != nil {
|
if finalResult != nil {
|
||||||
c.t.Logf("final result: %v\n", finalResult)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.t.Logf("Payment attempts: %v\n", nextPid)
|
return attempts, nil
|
||||||
if expectedNofAttempts != int(nextPid) {
|
|
||||||
c.t.Fatalf("expected %v attempts, but needed %v",
|
|
||||||
expectedNofAttempts, nextPid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNodeIndex returns the zero-based index of the given node in the route.
|
// getNodeIndex returns the zero-based index of the given node in the route.
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package routing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestProbabilityExtrapolation tests that probabilities for tried channels are
|
// TestProbabilityExtrapolation tests that probabilities for tried channels are
|
||||||
@@ -15,22 +18,25 @@ func TestProbabilityExtrapolation(t *testing.T) {
|
|||||||
// source -> intermediate1 (free routing) -> intermediate(1-10) (free routing) -> target
|
// source -> intermediate1 (free routing) -> intermediate(1-10) (free routing) -> target
|
||||||
g := ctx.graph
|
g := ctx.graph
|
||||||
|
|
||||||
expensiveNode := newMockNode()
|
const expensiveNodeID = 3
|
||||||
|
expensiveNode := newMockNode(expensiveNodeID)
|
||||||
expensiveNode.baseFee = 10000
|
expensiveNode.baseFee = 10000
|
||||||
g.addNode(expensiveNode)
|
g.addNode(expensiveNode)
|
||||||
|
|
||||||
g.addChannel(ctx.source, expensiveNode, 100000)
|
g.addChannel(100, sourceNodeID, expensiveNodeID, 100000)
|
||||||
g.addChannel(ctx.target, expensiveNode, 100000)
|
g.addChannel(101, targetNodeID, expensiveNodeID, 100000)
|
||||||
|
|
||||||
intermediate1 := newMockNode()
|
const intermediate1NodeID = 4
|
||||||
|
intermediate1 := newMockNode(intermediate1NodeID)
|
||||||
g.addNode(intermediate1)
|
g.addNode(intermediate1)
|
||||||
g.addChannel(ctx.source, intermediate1, 100000)
|
g.addChannel(102, sourceNodeID, intermediate1NodeID, 100000)
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
imNode := newMockNode()
|
imNodeID := byte(10 + i)
|
||||||
|
imNode := newMockNode(imNodeID)
|
||||||
g.addNode(imNode)
|
g.addNode(imNode)
|
||||||
g.addChannel(imNode, ctx.target, 100000)
|
g.addChannel(uint64(200+i), imNodeID, targetNodeID, 100000)
|
||||||
g.addChannel(imNode, intermediate1, 100000)
|
g.addChannel(uint64(300+i), imNodeID, intermediate1NodeID, 100000)
|
||||||
|
|
||||||
// The channels from intermediate1 all have insufficient balance.
|
// The channels from intermediate1 all have insufficient balance.
|
||||||
g.nodes[intermediate1.pubkey].channels[imNode.pubkey].balance = 0
|
g.nodes[intermediate1.pubkey].channels[imNode.pubkey].balance = 0
|
||||||
@@ -47,11 +53,231 @@ func TestProbabilityExtrapolation(t *testing.T) {
|
|||||||
// a specific number of attempts to safe-guard against accidental
|
// a specific number of attempts to safe-guard against accidental
|
||||||
// modifications anywhere in the chain of components that is involved in
|
// modifications anywhere in the chain of components that is involved in
|
||||||
// this test.
|
// this test.
|
||||||
ctx.testPayment(5)
|
attempts, err := ctx.testPayment(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("payment failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(attempts) != 5 {
|
||||||
|
t.Fatalf("expected 5 attempts, but needed %v", len(attempts))
|
||||||
|
}
|
||||||
|
|
||||||
// If we use a static value for the node probability (no extrapolation
|
// If we use a static value for the node probability (no extrapolation
|
||||||
// of data from other channels), all ten bad channels will be tried
|
// of data from other channels), all ten bad channels will be tried
|
||||||
// first before switching to the paid channel.
|
// first before switching to the paid channel.
|
||||||
ctx.mcCfg.AprioriWeight = 1
|
ctx.mcCfg.AprioriWeight = 1
|
||||||
ctx.testPayment(11)
|
attempts, err = ctx.testPayment(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("payment failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(attempts) != 11 {
|
||||||
|
t.Fatalf("expected 11 attempts, but needed %v", len(attempts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mppSendTestCase struct {
|
||||||
|
name string
|
||||||
|
amt btcutil.Amount
|
||||||
|
expectedAttempts int
|
||||||
|
|
||||||
|
// expectedSuccesses is a list of htlcs that made it to the receiver,
|
||||||
|
// regardless of whether the final set became complete or not.
|
||||||
|
expectedSuccesses []expectedHtlcSuccess
|
||||||
|
|
||||||
|
graph func(g *mockGraph)
|
||||||
|
expectedFailure bool
|
||||||
|
maxHtlcs uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
chanSourceIm1 = 13
|
||||||
|
chanIm1Target = 32
|
||||||
|
chanSourceIm2 = 14
|
||||||
|
chanIm2Target = 42
|
||||||
|
)
|
||||||
|
|
||||||
|
func onePathGraph(g *mockGraph) {
|
||||||
|
// Create the following network of nodes:
|
||||||
|
// source -> intermediate1 -> target
|
||||||
|
|
||||||
|
const im1NodeID = 3
|
||||||
|
intermediate1 := newMockNode(im1NodeID)
|
||||||
|
g.addNode(intermediate1)
|
||||||
|
|
||||||
|
g.addChannel(chanSourceIm1, sourceNodeID, im1NodeID, 200000)
|
||||||
|
g.addChannel(chanIm1Target, targetNodeID, im1NodeID, 100000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func twoPathGraph(g *mockGraph) {
|
||||||
|
// Create the following network of nodes:
|
||||||
|
// source -> intermediate1 -> target
|
||||||
|
// source -> intermediate2 -> target
|
||||||
|
|
||||||
|
const im1NodeID = 3
|
||||||
|
intermediate1 := newMockNode(im1NodeID)
|
||||||
|
g.addNode(intermediate1)
|
||||||
|
|
||||||
|
const im2NodeID = 4
|
||||||
|
intermediate2 := newMockNode(im2NodeID)
|
||||||
|
g.addNode(intermediate2)
|
||||||
|
|
||||||
|
g.addChannel(chanSourceIm1, sourceNodeID, im1NodeID, 200000)
|
||||||
|
g.addChannel(chanSourceIm2, sourceNodeID, im2NodeID, 200000)
|
||||||
|
g.addChannel(chanIm1Target, targetNodeID, im1NodeID, 100000)
|
||||||
|
g.addChannel(chanIm2Target, targetNodeID, im2NodeID, 100000)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mppTestCases = []mppSendTestCase{
|
||||||
|
// Test a two-path graph with sufficient liquidity. It is expected that
|
||||||
|
// pathfinding will try first try to send the full amount via the two
|
||||||
|
// available routes. When that fails, it will half the amount to 35k sat
|
||||||
|
// and retry. That attempt reaches the target successfully. Then the
|
||||||
|
// same route is tried again. Because the channel only had 50k sat, it
|
||||||
|
// will fail. Finally the second route is tried for 35k and it succeeds
|
||||||
|
// too. Mpp payment complete.
|
||||||
|
{
|
||||||
|
|
||||||
|
name: "sufficient inbound",
|
||||||
|
graph: twoPathGraph,
|
||||||
|
amt: 70000,
|
||||||
|
expectedAttempts: 5,
|
||||||
|
expectedSuccesses: []expectedHtlcSuccess{
|
||||||
|
{
|
||||||
|
amt: 35000,
|
||||||
|
chans: []uint64{chanSourceIm1, chanIm1Target},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amt: 35000,
|
||||||
|
chans: []uint64{chanSourceIm2, chanIm2Target},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxHtlcs: 1000,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test that a cap on the max htlcs makes it impossible to pay.
|
||||||
|
{
|
||||||
|
name: "no splitting",
|
||||||
|
graph: twoPathGraph,
|
||||||
|
amt: 70000,
|
||||||
|
expectedAttempts: 2,
|
||||||
|
expectedSuccesses: []expectedHtlcSuccess{},
|
||||||
|
expectedFailure: true,
|
||||||
|
maxHtlcs: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test that an attempt is made to split the payment in multiple parts
|
||||||
|
// that all use the same route if the full amount cannot be sent in a
|
||||||
|
// single htlc. The sender is effectively probing the receiver's
|
||||||
|
// incoming channel to see if it has sufficient balance. In this test
|
||||||
|
// case, the endeavour fails.
|
||||||
|
{
|
||||||
|
|
||||||
|
name: "one path split",
|
||||||
|
graph: onePathGraph,
|
||||||
|
amt: 70000,
|
||||||
|
expectedAttempts: 7,
|
||||||
|
expectedSuccesses: []expectedHtlcSuccess{
|
||||||
|
{
|
||||||
|
amt: 35000,
|
||||||
|
chans: []uint64{chanSourceIm1, chanIm1Target},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amt: 8750,
|
||||||
|
chans: []uint64{chanSourceIm1, chanIm1Target},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedFailure: true,
|
||||||
|
maxHtlcs: 1000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMppSend tests that a payment can be completed using multiple shards.
|
||||||
|
func TestMppSend(t *testing.T) {
|
||||||
|
for _, testCase := range mppTestCases {
|
||||||
|
testCase := testCase
|
||||||
|
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
testMppSend(t, &testCase)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMppSend(t *testing.T, testCase *mppSendTestCase) {
|
||||||
|
ctx := newIntegratedRoutingContext(t)
|
||||||
|
|
||||||
|
g := ctx.graph
|
||||||
|
testCase.graph(g)
|
||||||
|
|
||||||
|
ctx.amt = lnwire.NewMSatFromSatoshis(testCase.amt)
|
||||||
|
|
||||||
|
attempts, err := ctx.testPayment(testCase.maxHtlcs)
|
||||||
|
switch {
|
||||||
|
case err == nil && testCase.expectedFailure:
|
||||||
|
t.Fatal("expected payment to fail")
|
||||||
|
case err != nil && !testCase.expectedFailure:
|
||||||
|
t.Fatal("expected payment to succeed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attempts) != testCase.expectedAttempts {
|
||||||
|
t.Fatalf("expected %v attempts, but needed %v",
|
||||||
|
testCase.expectedAttempts, len(attempts),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSuccessAttempts(t, attempts, testCase.expectedSuccesses)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectedHtlcSuccess describes an expected successful htlc attempt.
|
||||||
|
type expectedHtlcSuccess struct {
|
||||||
|
amt btcutil.Amount
|
||||||
|
chans []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// equals matches the expectation with an actual attempt.
|
||||||
|
func (e *expectedHtlcSuccess) equals(a htlcAttempt) bool {
|
||||||
|
if a.route.TotalAmount !=
|
||||||
|
lnwire.NewMSatFromSatoshis(e.amt) {
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.route.Hops) != len(e.chans) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, h := range a.route.Hops {
|
||||||
|
if h.ChannelID != e.chans[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertSuccessAttempts asserts that the set of successful htlc attempts
|
||||||
|
// matches the given expectation.
|
||||||
|
func assertSuccessAttempts(t *testing.T, attempts []htlcAttempt,
|
||||||
|
expected []expectedHtlcSuccess) {
|
||||||
|
|
||||||
|
successCount := 0
|
||||||
|
loop:
|
||||||
|
for _, a := range attempts {
|
||||||
|
if !a.success {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
successCount++
|
||||||
|
|
||||||
|
for _, exp := range expected {
|
||||||
|
if exp.equals(a) {
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("htlc success %v not found", a)
|
||||||
|
}
|
||||||
|
|
||||||
|
if successCount != len(expected) {
|
||||||
|
t.Fatalf("expected %v successful htlcs, but got %v",
|
||||||
|
expected, successCount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+26
-18
@@ -11,14 +11,9 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nextTestPubkey is global variable that is used to deterministically generate
|
|
||||||
// test keys.
|
|
||||||
var nextTestPubkey byte
|
|
||||||
|
|
||||||
// createPubkey return a new test pubkey.
|
// createPubkey return a new test pubkey.
|
||||||
func createPubkey() route.Vertex {
|
func createPubkey(id byte) route.Vertex {
|
||||||
pubkey := route.Vertex{nextTestPubkey}
|
pubkey := route.Vertex{id}
|
||||||
nextTestPubkey++
|
|
||||||
return pubkey
|
return pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +33,8 @@ type mockNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newMockNode instantiates a new mock node with a newly generated pubkey.
|
// newMockNode instantiates a new mock node with a newly generated pubkey.
|
||||||
func newMockNode() *mockNode {
|
func newMockNode(id byte) *mockNode {
|
||||||
pubkey := createPubkey()
|
pubkey := createPubkey(id)
|
||||||
return &mockNode{
|
return &mockNode{
|
||||||
channels: make(map[route.Vertex]*mockChannel),
|
channels: make(map[route.Vertex]*mockChannel),
|
||||||
pubkey: pubkey,
|
pubkey: pubkey,
|
||||||
@@ -106,10 +101,9 @@ func (m *mockNode) fwd(from *mockNode, route *hop) (htlcResult, error) {
|
|||||||
|
|
||||||
// mockGraph contains a set of nodes that together for a mocked graph.
|
// mockGraph contains a set of nodes that together for a mocked graph.
|
||||||
type mockGraph struct {
|
type mockGraph struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
nodes map[route.Vertex]*mockNode
|
nodes map[route.Vertex]*mockNode
|
||||||
nextChanID uint64
|
source *mockNode
|
||||||
source *mockNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newMockGraph instantiates a new mock graph.
|
// newMockGraph instantiates a new mock graph.
|
||||||
@@ -122,6 +116,11 @@ func newMockGraph(t *testing.T) *mockGraph {
|
|||||||
|
|
||||||
// addNode adds the given mock node to the network.
|
// addNode adds the given mock node to the network.
|
||||||
func (m *mockGraph) addNode(node *mockNode) {
|
func (m *mockGraph) addNode(node *mockNode) {
|
||||||
|
m.t.Helper()
|
||||||
|
|
||||||
|
if _, exists := m.nodes[node.pubkey]; exists {
|
||||||
|
m.t.Fatal("node already exists")
|
||||||
|
}
|
||||||
m.nodes[node.pubkey] = node
|
m.nodes[node.pubkey] = node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,16 +130,25 @@ func (m *mockGraph) addNode(node *mockNode) {
|
|||||||
// Ignore linter error because addChannel isn't yet called with different
|
// Ignore linter error because addChannel isn't yet called with different
|
||||||
// capacities.
|
// capacities.
|
||||||
// nolint:unparam
|
// nolint:unparam
|
||||||
func (m *mockGraph) addChannel(node1, node2 *mockNode, capacity btcutil.Amount) {
|
func (m *mockGraph) addChannel(id uint64, node1id, node2id byte,
|
||||||
id := m.nextChanID
|
capacity btcutil.Amount) {
|
||||||
m.nextChanID++
|
|
||||||
|
|
||||||
m.nodes[node1.pubkey].channels[node2.pubkey] = &mockChannel{
|
node1pubkey := createPubkey(node1id)
|
||||||
|
node2pubkey := createPubkey(node2id)
|
||||||
|
|
||||||
|
if _, exists := m.nodes[node1pubkey].channels[node2pubkey]; exists {
|
||||||
|
m.t.Fatal("channel already exists")
|
||||||
|
}
|
||||||
|
if _, exists := m.nodes[node2pubkey].channels[node1pubkey]; exists {
|
||||||
|
m.t.Fatal("channel already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.nodes[node1pubkey].channels[node2pubkey] = &mockChannel{
|
||||||
capacity: capacity,
|
capacity: capacity,
|
||||||
id: id,
|
id: id,
|
||||||
balance: lnwire.NewMSatFromSatoshis(capacity / 2),
|
balance: lnwire.NewMSatFromSatoshis(capacity / 2),
|
||||||
}
|
}
|
||||||
m.nodes[node2.pubkey].channels[node1.pubkey] = &mockChannel{
|
m.nodes[node2pubkey].channels[node1pubkey] = &mockChannel{
|
||||||
capacity: capacity,
|
capacity: capacity,
|
||||||
id: id,
|
id: id,
|
||||||
balance: lnwire.NewMSatFromSatoshis(capacity / 2),
|
balance: lnwire.NewMSatFromSatoshis(capacity / 2),
|
||||||
|
|||||||
+14
-38
@@ -74,6 +74,7 @@ type edgePolicyWithSource struct {
|
|||||||
// custom records and payment address.
|
// custom records and payment address.
|
||||||
type finalHopParams struct {
|
type finalHopParams struct {
|
||||||
amt lnwire.MilliSatoshi
|
amt lnwire.MilliSatoshi
|
||||||
|
totalAmt lnwire.MilliSatoshi
|
||||||
cltvDelta uint16
|
cltvDelta uint16
|
||||||
records record.CustomSet
|
records record.CustomSet
|
||||||
paymentAddr *[32]byte
|
paymentAddr *[32]byte
|
||||||
@@ -177,7 +178,8 @@ func newRoute(sourceVertex route.Vertex,
|
|||||||
// Otherwise attach the mpp record if it exists.
|
// Otherwise attach the mpp record if it exists.
|
||||||
if finalHop.paymentAddr != nil {
|
if finalHop.paymentAddr != nil {
|
||||||
mpp = record.NewMPP(
|
mpp = record.NewMPP(
|
||||||
finalHop.amt, *finalHop.paymentAddr,
|
finalHop.totalAmt,
|
||||||
|
*finalHop.paymentAddr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -253,7 +255,7 @@ func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
|
|||||||
// graphParams wraps the set of graph parameters passed to findPath.
|
// graphParams wraps the set of graph parameters passed to findPath.
|
||||||
type graphParams struct {
|
type graphParams struct {
|
||||||
// graph is the ChannelGraph to be used during path finding.
|
// graph is the ChannelGraph to be used during path finding.
|
||||||
graph *channeldb.ChannelGraph
|
graph routingGraph
|
||||||
|
|
||||||
// additionalEdges is an optional set of edges that should be
|
// additionalEdges is an optional set of edges that should be
|
||||||
// considered during path finding, that is not already found in the
|
// considered during path finding, that is not already found in the
|
||||||
@@ -381,34 +383,8 @@ func getMaxOutgoingAmt(node route.Vertex, outgoingChan *uint64,
|
|||||||
// path and accurately check the amount to forward at every node against the
|
// path and accurately check the amount to forward at every node against the
|
||||||
// available bandwidth.
|
// available bandwidth.
|
||||||
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
||||||
source, target route.Vertex, amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
|
source, target route.Vertex, amt lnwire.MilliSatoshi,
|
||||||
[]*channeldb.ChannelEdgePolicy, error) {
|
finalHtlcExpiry int32) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
routingTx, err := newDbRoutingTx(g.graph)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := routingTx.close()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error closing db tx: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return findPathInternal(
|
|
||||||
g.additionalEdges, g.bandwidthHints, routingTx, r, cfg, source,
|
|
||||||
target, amt, finalHtlcExpiry,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// findPathInternal is the internal implementation of findPath.
|
|
||||||
func findPathInternal(
|
|
||||||
additionalEdges map[route.Vertex][]*channeldb.ChannelEdgePolicy,
|
|
||||||
bandwidthHints map[uint64]lnwire.MilliSatoshi,
|
|
||||||
graph routingGraph,
|
|
||||||
r *RestrictParams, cfg *PathFindingConfig,
|
|
||||||
source, target route.Vertex, amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
|
|
||||||
[]*channeldb.ChannelEdgePolicy, error) {
|
|
||||||
|
|
||||||
// Pathfinding can be a significant portion of the total payment
|
// Pathfinding can be a significant portion of the total payment
|
||||||
// latency, especially on low-powered devices. Log several metrics to
|
// latency, especially on low-powered devices. Log several metrics to
|
||||||
@@ -427,7 +403,7 @@ func findPathInternal(
|
|||||||
features := r.DestFeatures
|
features := r.DestFeatures
|
||||||
if features == nil {
|
if features == nil {
|
||||||
var err error
|
var err error
|
||||||
features, err = graph.fetchNodeFeatures(target)
|
features, err = g.graph.fetchNodeFeatures(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -468,17 +444,17 @@ func findPathInternal(
|
|||||||
|
|
||||||
// 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.
|
||||||
self := graph.sourceNode()
|
self := g.graph.sourceNode()
|
||||||
|
|
||||||
if source == self {
|
if source == self {
|
||||||
max, err := getMaxOutgoingAmt(
|
max, err := getMaxOutgoingAmt(
|
||||||
self, r.OutgoingChannelID, bandwidthHints, graph,
|
self, r.OutgoingChannelID, g.bandwidthHints, g.graph,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if max < amt {
|
if max < amt {
|
||||||
return nil, errInsufficientBalance
|
return nil, errNoPathFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +467,7 @@ func findPathInternal(
|
|||||||
distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
|
distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
|
||||||
|
|
||||||
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
|
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
|
||||||
for vertex, outgoingEdgePolicies := range additionalEdges {
|
for vertex, outgoingEdgePolicies := range g.additionalEdges {
|
||||||
// Build reverse lookup to find incoming edges. Needed because
|
// Build reverse lookup to find incoming edges. Needed because
|
||||||
// search is taken place from target to source.
|
// search is taken place from target to source.
|
||||||
for _, outgoingEdgePolicy := range outgoingEdgePolicies {
|
for _, outgoingEdgePolicy := range outgoingEdgePolicies {
|
||||||
@@ -739,7 +715,7 @@ func findPathInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch node features fresh from the graph.
|
// Fetch node features fresh from the graph.
|
||||||
fromFeatures, err := graph.fetchNodeFeatures(node)
|
fromFeatures, err := g.graph.fetchNodeFeatures(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -775,7 +751,7 @@ func findPathInternal(
|
|||||||
// Create unified policies for all incoming connections.
|
// Create unified policies for all incoming connections.
|
||||||
u := newUnifiedPolicies(self, pivot, r.OutgoingChannelID)
|
u := newUnifiedPolicies(self, pivot, r.OutgoingChannelID)
|
||||||
|
|
||||||
err := u.addGraphPolicies(graph)
|
err := u.addGraphPolicies(g.graph)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -806,7 +782,7 @@ func findPathInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
policy := unifiedPolicy.getPolicy(
|
policy := unifiedPolicy.getPolicy(
|
||||||
amtToSend, bandwidthHints,
|
amtToSend, g.bandwidthHints,
|
||||||
)
|
)
|
||||||
|
|
||||||
if policy == nil {
|
if policy == nil {
|
||||||
|
|||||||
+69
-95
@@ -81,6 +81,12 @@ var (
|
|||||||
), lnwire.Features,
|
), lnwire.Features,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mppFeatures = lnwire.NewRawFeatureVector(
|
||||||
|
lnwire.TLVOnionPayloadOptional,
|
||||||
|
lnwire.PaymentAddrOptional,
|
||||||
|
lnwire.MPPOptional,
|
||||||
|
)
|
||||||
|
|
||||||
unknownRequiredFeatures = lnwire.NewFeatureVector(
|
unknownRequiredFeatures = lnwire.NewFeatureVector(
|
||||||
lnwire.NewRawFeatureVector(100), lnwire.Features,
|
lnwire.NewRawFeatureVector(100), lnwire.Features,
|
||||||
)
|
)
|
||||||
@@ -811,10 +817,8 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
|
|
||||||
paymentAmt := lnwire.NewMSatFromSatoshis(test.paymentAmt)
|
paymentAmt := lnwire.NewMSatFromSatoshis(test.paymentAmt)
|
||||||
target := graphInstance.aliasMap[test.target]
|
target := graphInstance.aliasMap[test.target]
|
||||||
path, err := findPath(
|
path, err := dbFindPath(
|
||||||
&graphParams{
|
graphInstance.graph, nil, nil,
|
||||||
graph: graphInstance.graph,
|
|
||||||
},
|
|
||||||
&RestrictParams{
|
&RestrictParams{
|
||||||
FeeLimit: test.feeLimit,
|
FeeLimit: test.feeLimit,
|
||||||
ProbabilitySource: noProbabilitySource,
|
ProbabilitySource: noProbabilitySource,
|
||||||
@@ -1005,11 +1009,8 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
|||||||
find := func(r *RestrictParams) (
|
find := func(r *RestrictParams) (
|
||||||
[]*channeldb.ChannelEdgePolicy, error) {
|
[]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
return findPath(
|
return dbFindPath(
|
||||||
&graphParams{
|
graph.graph, additionalEdges, nil,
|
||||||
graph: graph.graph,
|
|
||||||
additionalEdges: additionalEdges,
|
|
||||||
},
|
|
||||||
r, testPathFindingConfig,
|
r, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
|
||||||
0,
|
0,
|
||||||
@@ -1332,6 +1333,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
sourceVertex, testCase.hops, startingHeight,
|
sourceVertex, testCase.hops, startingHeight,
|
||||||
finalHopParams{
|
finalHopParams{
|
||||||
amt: testCase.paymentAmount,
|
amt: testCase.paymentAmount,
|
||||||
|
totalAmt: testCase.paymentAmount,
|
||||||
cltvDelta: finalHopCLTV,
|
cltvDelta: finalHopCLTV,
|
||||||
records: nil,
|
records: nil,
|
||||||
paymentAddr: testCase.paymentAddr,
|
paymentAddr: testCase.paymentAddr,
|
||||||
@@ -1433,10 +1435,8 @@ func TestPathNotAvailable(t *testing.T) {
|
|||||||
var unknownNode route.Vertex
|
var unknownNode route.Vertex
|
||||||
copy(unknownNode[:], unknownNodeBytes)
|
copy(unknownNode[:], unknownNodeBytes)
|
||||||
|
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, unknownNode, 100, 0,
|
sourceNode.PubKeyBytes, unknownNode, 100, 0,
|
||||||
)
|
)
|
||||||
@@ -1482,7 +1482,7 @@ func TestDestTLVGraphFallback(t *testing.T) {
|
|||||||
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
|
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
|
||||||
defer ctx.cleanup()
|
defer ctx.cleanup()
|
||||||
|
|
||||||
sourceNode, err := ctx.graphParams.graph.SourceNode()
|
sourceNode, err := ctx.graph.SourceNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to fetch source node: %v", err)
|
t.Fatalf("unable to fetch source node: %v", err)
|
||||||
|
|
||||||
@@ -1491,10 +1491,8 @@ func TestDestTLVGraphFallback(t *testing.T) {
|
|||||||
find := func(r *RestrictParams,
|
find := func(r *RestrictParams,
|
||||||
target route.Vertex) ([]*channeldb.ChannelEdgePolicy, error) {
|
target route.Vertex) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
return findPath(
|
return dbFindPath(
|
||||||
&graphParams{
|
ctx.graph, nil, nil,
|
||||||
graph: ctx.graphParams.graph,
|
|
||||||
},
|
|
||||||
r, testPathFindingConfig,
|
r, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, 100, 0,
|
sourceNode.PubKeyBytes, target, 100, 0,
|
||||||
)
|
)
|
||||||
@@ -1765,10 +1763,8 @@ func TestPathInsufficientCapacity(t *testing.T) {
|
|||||||
target := graph.aliasMap["sophon"]
|
target := graph.aliasMap["sophon"]
|
||||||
|
|
||||||
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -1798,10 +1794,8 @@ func TestRouteFailMinHTLC(t *testing.T) {
|
|||||||
// attempt should fail.
|
// attempt should fail.
|
||||||
target := graph.aliasMap["songoku"]
|
target := graph.aliasMap["songoku"]
|
||||||
payAmt := lnwire.MilliSatoshi(10)
|
payAmt := lnwire.MilliSatoshi(10)
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -1897,10 +1891,8 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
// succeed without issue, and return a single path via phamnuwen
|
// succeed without issue, and return a single path via phamnuwen
|
||||||
target := graph.aliasMap["sophon"]
|
target := graph.aliasMap["sophon"]
|
||||||
payAmt := lnwire.NewMSatFromSatoshis(105000)
|
payAmt := lnwire.NewMSatFromSatoshis(105000)
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -1925,10 +1917,8 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
t.Fatalf("unable to update edge: %v", err)
|
t.Fatalf("unable to update edge: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -1950,10 +1940,8 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
|
|
||||||
// If we attempt to route through that edge, we should get a failure as
|
// If we attempt to route through that edge, we should get a failure as
|
||||||
// it is no longer eligible.
|
// it is no longer eligible.
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -1984,10 +1972,8 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
// cheapest path.
|
// cheapest path.
|
||||||
target := graph.aliasMap["sophon"]
|
target := graph.aliasMap["sophon"]
|
||||||
payAmt := lnwire.NewMSatFromSatoshis(50000)
|
payAmt := lnwire.NewMSatFromSatoshis(50000)
|
||||||
path, err := findPath(
|
path, err := dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, nil,
|
||||||
graph: graph.graph,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -2007,11 +1993,8 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
|
|
||||||
// Since both these edges has a bandwidth of zero, no path should be
|
// Since both these edges has a bandwidth of zero, no path should be
|
||||||
// found.
|
// found.
|
||||||
_, err = findPath(
|
_, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, bandwidths,
|
||||||
graph: graph.graph,
|
|
||||||
bandwidthHints: bandwidths,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -2025,11 +2008,8 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
|
|
||||||
// Now, if we attempt to route again, we should find the path via
|
// Now, if we attempt to route again, we should find the path via
|
||||||
// phamnuven, as the other source edge won't be considered.
|
// phamnuven, as the other source edge won't be considered.
|
||||||
path, err = findPath(
|
path, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, bandwidths,
|
||||||
graph: graph.graph,
|
|
||||||
bandwidthHints: bandwidths,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -2056,11 +2036,8 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
|
|||||||
|
|
||||||
// Since we ignore disable flags for local channels, a path should
|
// Since we ignore disable flags for local channels, a path should
|
||||||
// still be found.
|
// still be found.
|
||||||
path, err = findPath(
|
path, err = dbFindPath(
|
||||||
&graphParams{
|
graph.graph, nil, bandwidths,
|
||||||
graph: graph.graph,
|
|
||||||
bandwidthHints: bandwidths,
|
|
||||||
},
|
|
||||||
noRestrictions, testPathFindingConfig,
|
noRestrictions, testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, payAmt, 0,
|
sourceNode.PubKeyBytes, target, payAmt, 0,
|
||||||
)
|
)
|
||||||
@@ -2809,40 +2786,9 @@ func TestRouteToSelf(t *testing.T) {
|
|||||||
ctx.assertPath(path, []uint64{1, 3, 2})
|
ctx.assertPath(path, []uint64{1, 3, 2})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestInsufficientBalance tests that a dedicated error is returned for
|
|
||||||
// insufficient local balance.
|
|
||||||
func TestInsufficientBalance(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testChannels := []*testChannel{
|
|
||||||
symmetricTestChannel("source", "target", 100000, &testChannelPolicy{
|
|
||||||
Expiry: 144,
|
|
||||||
FeeBaseMsat: 500,
|
|
||||||
}, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := newPathFindingTestContext(t, testChannels, "source")
|
|
||||||
defer ctx.cleanup()
|
|
||||||
|
|
||||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
|
||||||
target := ctx.keyFromAlias("target")
|
|
||||||
|
|
||||||
ctx.graphParams.bandwidthHints = map[uint64]lnwire.MilliSatoshi{
|
|
||||||
1: lnwire.NewMSatFromSatoshis(50),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the best path to self. We expect this to be source->a->source,
|
|
||||||
// because a charges the lowest forwarding fee.
|
|
||||||
_, err := ctx.findPath(target, paymentAmt)
|
|
||||||
if err != errInsufficientBalance {
|
|
||||||
t.Fatalf("expected insufficient balance error, but got: %v",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathFindingTestContext struct {
|
type pathFindingTestContext struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
graphParams graphParams
|
graph *channeldb.ChannelGraph
|
||||||
restrictParams RestrictParams
|
restrictParams RestrictParams
|
||||||
pathFindingConfig PathFindingConfig
|
pathFindingConfig PathFindingConfig
|
||||||
testGraphInstance *testGraphInstance
|
testGraphInstance *testGraphInstance
|
||||||
@@ -2869,10 +2815,8 @@ func newPathFindingTestContext(t *testing.T, testChannels []*testChannel,
|
|||||||
testGraphInstance: testGraphInstance,
|
testGraphInstance: testGraphInstance,
|
||||||
source: route.Vertex(sourceNode.PubKeyBytes),
|
source: route.Vertex(sourceNode.PubKeyBytes),
|
||||||
pathFindingConfig: *testPathFindingConfig,
|
pathFindingConfig: *testPathFindingConfig,
|
||||||
graphParams: graphParams{
|
graph: testGraphInstance.graph,
|
||||||
graph: testGraphInstance.graph,
|
restrictParams: *noRestrictions,
|
||||||
},
|
|
||||||
restrictParams: *noRestrictions,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
@@ -2899,8 +2843,8 @@ func (c *pathFindingTestContext) findPath(target route.Vertex,
|
|||||||
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy,
|
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy,
|
||||||
error) {
|
error) {
|
||||||
|
|
||||||
return findPath(
|
return dbFindPath(
|
||||||
&c.graphParams, &c.restrictParams, &c.pathFindingConfig,
|
c.graph, nil, nil, &c.restrictParams, &c.pathFindingConfig,
|
||||||
c.source, target, amt, 0,
|
c.source, target, amt, 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2918,3 +2862,33 @@ func (c *pathFindingTestContext) assertPath(path []*channeldb.ChannelEdgePolicy,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dbFindPath calls findPath after getting a db transaction from the database
|
||||||
|
// graph.
|
||||||
|
func dbFindPath(graph *channeldb.ChannelGraph,
|
||||||
|
additionalEdges map[route.Vertex][]*channeldb.ChannelEdgePolicy,
|
||||||
|
bandwidthHints map[uint64]lnwire.MilliSatoshi,
|
||||||
|
r *RestrictParams, cfg *PathFindingConfig,
|
||||||
|
source, target route.Vertex, amt lnwire.MilliSatoshi,
|
||||||
|
finalHtlcExpiry int32) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
|
routingTx, err := newDbRoutingTx(graph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := routingTx.close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error closing db tx: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return findPath(
|
||||||
|
&graphParams{
|
||||||
|
additionalEdges: additionalEdges,
|
||||||
|
bandwidthHints: bandwidthHints,
|
||||||
|
graph: routingTx,
|
||||||
|
},
|
||||||
|
r, cfg, source, target, amt, finalHtlcExpiry,
|
||||||
|
)
|
||||||
|
}
|
||||||
+101
-57
@@ -26,15 +26,18 @@ const (
|
|||||||
// not exist in the graph.
|
// not exist in the graph.
|
||||||
errNoPathFound
|
errNoPathFound
|
||||||
|
|
||||||
// errInsufficientLocalBalance is returned when none of the local
|
|
||||||
// channels have enough balance for the payment.
|
|
||||||
errInsufficientBalance
|
|
||||||
|
|
||||||
// errEmptyPaySession is returned when the empty payment session is
|
// errEmptyPaySession is returned when the empty payment session is
|
||||||
// queried for a route.
|
// queried for a route.
|
||||||
errEmptyPaySession
|
errEmptyPaySession
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultShardMinAmt is the default amount beyond which we won't try to
|
||||||
|
// further split the payment if no route is found. It is the minimum
|
||||||
|
// amount that we use as the shard size when splitting.
|
||||||
|
DefaultShardMinAmt = lnwire.NewMSatFromSatoshis(10000)
|
||||||
|
)
|
||||||
|
|
||||||
// Error returns the string representation of the noRouteError
|
// Error returns the string representation of the noRouteError
|
||||||
func (e noRouteError) Error() string {
|
func (e noRouteError) Error() string {
|
||||||
switch e {
|
switch e {
|
||||||
@@ -50,9 +53,6 @@ func (e noRouteError) Error() string {
|
|||||||
case errEmptyPaySession:
|
case errEmptyPaySession:
|
||||||
return "empty payment session"
|
return "empty payment session"
|
||||||
|
|
||||||
case errInsufficientBalance:
|
|
||||||
return "insufficient local balance"
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "unknown no-route error"
|
return "unknown no-route error"
|
||||||
}
|
}
|
||||||
@@ -69,9 +69,6 @@ func (e noRouteError) FailureReason() channeldb.FailureReason {
|
|||||||
|
|
||||||
return channeldb.FailureReasonNoRoute
|
return channeldb.FailureReasonNoRoute
|
||||||
|
|
||||||
case errInsufficientBalance:
|
|
||||||
return channeldb.FailureReasonInsufficientBalance
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return channeldb.FailureReasonError
|
return channeldb.FailureReasonError
|
||||||
}
|
}
|
||||||
@@ -108,13 +105,25 @@ type paymentSession struct {
|
|||||||
|
|
||||||
getBandwidthHints func() (map[uint64]lnwire.MilliSatoshi, error)
|
getBandwidthHints func() (map[uint64]lnwire.MilliSatoshi, error)
|
||||||
|
|
||||||
sessionSource *SessionSource
|
|
||||||
|
|
||||||
payment *LightningPayment
|
payment *LightningPayment
|
||||||
|
|
||||||
empty bool
|
empty bool
|
||||||
|
|
||||||
pathFinder pathFinder
|
pathFinder pathFinder
|
||||||
|
|
||||||
|
getRoutingGraph func() (routingGraph, func(), error)
|
||||||
|
|
||||||
|
// pathFindingConfig defines global parameters that control the
|
||||||
|
// trade-off in path finding between fees and probabiity.
|
||||||
|
pathFindingConfig PathFindingConfig
|
||||||
|
|
||||||
|
missionControl MissionController
|
||||||
|
|
||||||
|
// minShardAmt is the amount beyond which we won't try to further split
|
||||||
|
// the payment if no route is found. If the maximum number of htlcs
|
||||||
|
// specified in the payment is one, under no circumstances splitting
|
||||||
|
// will happen and this value remains unused.
|
||||||
|
minShardAmt lnwire.MilliSatoshi
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestRoute returns a route which is likely to be capable for successfully
|
// RequestRoute returns a route which is likely to be capable for successfully
|
||||||
@@ -148,10 +157,8 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
|
|||||||
// Taking into account this prune view, we'll attempt to locate a path
|
// Taking into account this prune view, we'll attempt to locate a path
|
||||||
// to our destination, respecting the recommendations from
|
// to our destination, respecting the recommendations from
|
||||||
// MissionControl.
|
// MissionControl.
|
||||||
ss := p.sessionSource
|
|
||||||
|
|
||||||
restrictions := &RestrictParams{
|
restrictions := &RestrictParams{
|
||||||
ProbabilitySource: ss.MissionControl.GetProbability,
|
ProbabilitySource: p.missionControl.GetProbability,
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
OutgoingChannelID: p.payment.OutgoingChannelID,
|
OutgoingChannelID: p.payment.OutgoingChannelID,
|
||||||
LastHop: p.payment.LastHop,
|
LastHop: p.payment.LastHop,
|
||||||
@@ -161,50 +168,87 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
|
|||||||
PaymentAddr: p.payment.PaymentAddr,
|
PaymentAddr: p.payment.PaymentAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll also obtain a set of bandwidthHints from the lower layer for
|
|
||||||
// each of our outbound channels. This will allow the path finding to
|
|
||||||
// skip any links that aren't active or just don't have enough bandwidth
|
|
||||||
// to carry the payment. New bandwidth hints are queried for every new
|
|
||||||
// path finding attempt, because concurrent payments may change
|
|
||||||
// balances.
|
|
||||||
bandwidthHints, err := p.getBandwidthHints()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
|
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
|
||||||
|
|
||||||
path, err := p.pathFinder(
|
for {
|
||||||
&graphParams{
|
// We'll also obtain a set of bandwidthHints from the lower
|
||||||
graph: ss.Graph,
|
// layer for each of our outbound channels. This will allow the
|
||||||
additionalEdges: p.additionalEdges,
|
// path finding to skip any links that aren't active or just
|
||||||
bandwidthHints: bandwidthHints,
|
// don't have enough bandwidth to carry the payment. New
|
||||||
},
|
// bandwidth hints are queried for every new path finding
|
||||||
restrictions, &ss.PathFindingConfig,
|
// attempt, because concurrent payments may change balances.
|
||||||
ss.SelfNode.PubKeyBytes, p.payment.Target,
|
bandwidthHints, err := p.getBandwidthHints()
|
||||||
maxAmt, finalHtlcExpiry,
|
if err != nil {
|
||||||
)
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the next candidate path found, we'll attempt to turn this into
|
log.Debugf("PaymentSession for %x: trying pathfinding with %v",
|
||||||
// a route by applying the time-lock and fee requirements.
|
p.payment.PaymentHash, maxAmt)
|
||||||
sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes)
|
|
||||||
route, err := newRoute(
|
|
||||||
sourceVertex, path, height,
|
|
||||||
finalHopParams{
|
|
||||||
amt: maxAmt,
|
|
||||||
cltvDelta: finalCltvDelta,
|
|
||||||
records: p.payment.DestCustomRecords,
|
|
||||||
paymentAddr: p.payment.PaymentAddr,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
// TODO(roasbeef): return which edge/vertex didn't work
|
|
||||||
// out
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return route, err
|
// Get a routing graph.
|
||||||
|
routingGraph, cleanup, err := p.getRoutingGraph()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceVertex := routingGraph.sourceNode()
|
||||||
|
|
||||||
|
// Find a route for the current amount.
|
||||||
|
path, err := p.pathFinder(
|
||||||
|
&graphParams{
|
||||||
|
additionalEdges: p.additionalEdges,
|
||||||
|
bandwidthHints: bandwidthHints,
|
||||||
|
graph: routingGraph,
|
||||||
|
},
|
||||||
|
restrictions, &p.pathFindingConfig,
|
||||||
|
sourceVertex, p.payment.Target,
|
||||||
|
maxAmt, finalHtlcExpiry,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close routing graph.
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == errNoPathFound:
|
||||||
|
// No splitting if this is the last shard.
|
||||||
|
isLastShard := activeShards+1 >= p.payment.MaxHtlcs
|
||||||
|
if isLastShard {
|
||||||
|
return nil, errNoPathFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is where the magic happens. If we can't find a
|
||||||
|
// route, try it for half the amount.
|
||||||
|
maxAmt /= 2
|
||||||
|
|
||||||
|
// Put a lower bound on the minimum shard size.
|
||||||
|
if maxAmt < p.minShardAmt {
|
||||||
|
return nil, errNoPathFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go pathfinding.
|
||||||
|
continue
|
||||||
|
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the next candidate path found, we'll attempt to turn
|
||||||
|
// this into a route by applying the time-lock and fee
|
||||||
|
// requirements.
|
||||||
|
route, err := newRoute(
|
||||||
|
sourceVertex, path, height,
|
||||||
|
finalHopParams{
|
||||||
|
amt: maxAmt,
|
||||||
|
totalAmt: p.payment.Amount,
|
||||||
|
cltvDelta: finalCltvDelta,
|
||||||
|
records: p.payment.DestCustomRecords,
|
||||||
|
paymentAddr: p.payment.PaymentAddr,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return route, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,6 @@ type SessionSource struct {
|
|||||||
// the available bandwidth of the link should be returned.
|
// the available bandwidth of the link should be returned.
|
||||||
QueryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
|
QueryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
|
||||||
|
|
||||||
// SelfNode is our own node.
|
|
||||||
SelfNode *channeldb.LightningNode
|
|
||||||
|
|
||||||
// MissionControl is a shared memory of sorts that executions of payment
|
// MissionControl is a shared memory of sorts that executions of payment
|
||||||
// path finding use in order to remember which vertexes/edges were
|
// path finding use in order to remember which vertexes/edges were
|
||||||
// pruned from prior attempts. During payment execution, errors sent by
|
// pruned from prior attempts. During payment execution, errors sent by
|
||||||
@@ -43,6 +40,21 @@ type SessionSource struct {
|
|||||||
PathFindingConfig PathFindingConfig
|
PathFindingConfig PathFindingConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRoutingGraph returns a routing graph and a clean-up function for
|
||||||
|
// pathfinding.
|
||||||
|
func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) {
|
||||||
|
routingTx, err := newDbRoutingTx(m.Graph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return routingTx, func() {
|
||||||
|
err := routingTx.close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error closing db tx: %v", err)
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewPaymentSession creates a new payment session backed by the latest prune
|
// NewPaymentSession creates a new payment session backed by the latest prune
|
||||||
// view from Mission Control. An optional set of routing hints can be provided
|
// view from Mission Control. An optional set of routing hints can be provided
|
||||||
// in order to populate additional edges to explore when finding a path to the
|
// in order to populate additional edges to explore when finding a path to the
|
||||||
@@ -69,9 +81,12 @@ func (m *SessionSource) NewPaymentSession(p *LightningPayment) (
|
|||||||
return &paymentSession{
|
return &paymentSession{
|
||||||
additionalEdges: edges,
|
additionalEdges: edges,
|
||||||
getBandwidthHints: getBandwidthHints,
|
getBandwidthHints: getBandwidthHints,
|
||||||
sessionSource: m,
|
|
||||||
payment: p,
|
payment: p,
|
||||||
pathFinder: findPath,
|
pathFinder: findPath,
|
||||||
|
getRoutingGraph: m.getRoutingGraph,
|
||||||
|
pathFindingConfig: m.PathFindingConfig,
|
||||||
|
missionControl: m.MissionControl,
|
||||||
|
minShardAmt: DefaultShardMinAmt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +95,7 @@ func (m *SessionSource) NewPaymentSession(p *LightningPayment) (
|
|||||||
// missioncontrol for resumed payment we don't want to make more attempts for.
|
// missioncontrol for resumed payment we don't want to make more attempts for.
|
||||||
func (m *SessionSource) NewPaymentSessionEmpty() PaymentSession {
|
func (m *SessionSource) NewPaymentSessionEmpty() PaymentSession {
|
||||||
return &paymentSession{
|
return &paymentSession{
|
||||||
sessionSource: m,
|
empty: true,
|
||||||
empty: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ func TestRequestRoute(t *testing.T) {
|
|||||||
height = 10
|
height = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
findPath := func(g *graphParams, r *RestrictParams,
|
findPath := func(
|
||||||
cfg *PathFindingConfig, source, target route.Vertex,
|
g *graphParams,
|
||||||
amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
|
r *RestrictParams, cfg *PathFindingConfig,
|
||||||
[]*channeldb.ChannelEdgePolicy, error) {
|
source, target route.Vertex, amt lnwire.MilliSatoshi,
|
||||||
|
finalHtlcExpiry int32) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
// We expect find path to receive a cltv limit excluding the
|
// We expect find path to receive a cltv limit excluding the
|
||||||
// final cltv delta (including the block padding).
|
// final cltv delta (including the block padding).
|
||||||
@@ -37,13 +38,6 @@ func TestRequestRoute(t *testing.T) {
|
|||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionSource := &SessionSource{
|
|
||||||
SelfNode: &channeldb.LightningNode{},
|
|
||||||
MissionControl: &MissionControl{
|
|
||||||
cfg: &MissionControlConfig{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cltvLimit := uint32(30)
|
cltvLimit := uint32(30)
|
||||||
finalCltvDelta := uint16(8)
|
finalCltvDelta := uint16(8)
|
||||||
|
|
||||||
@@ -60,9 +54,14 @@ func TestRequestRoute(t *testing.T) {
|
|||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
},
|
},
|
||||||
sessionSource: sessionSource,
|
payment: payment,
|
||||||
payment: payment,
|
pathFinder: findPath,
|
||||||
pathFinder: findPath,
|
missionControl: &MissionControl{
|
||||||
|
cfg: &MissionControlConfig{},
|
||||||
|
},
|
||||||
|
getRoutingGraph: func() (routingGraph, func(), error) {
|
||||||
|
return &sessionGraph{}, func() {}, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
route, err := session.RequestRoute(
|
route, err := session.RequestRoute(
|
||||||
@@ -79,3 +78,11 @@ func TestRequestRoute(t *testing.T) {
|
|||||||
route.TotalTimeLock)
|
route.TotalTimeLock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sessionGraph struct {
|
||||||
|
routingGraph
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *sessionGraph) sourceNode() route.Vertex {
|
||||||
|
return route.Vertex{}
|
||||||
|
}
|
||||||
@@ -215,20 +215,9 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
|
|||||||
i.finalFailureReason = &reasonIncorrectDetails
|
i.finalFailureReason = &reasonIncorrectDetails
|
||||||
|
|
||||||
case *lnwire.FailMPPTimeout:
|
case *lnwire.FailMPPTimeout:
|
||||||
// TODO(carla): decide how to penalize mpp timeout. In the
|
// Assign all pairs a success result, as the payment reached the
|
||||||
// meantime, attribute success to the hops along the route and
|
// destination correctly. Continue the payment process.
|
||||||
// do not penalize the final node.
|
i.successPairRange(route, 0, n-1)
|
||||||
|
|
||||||
i.finalFailureReason = &reasonError
|
|
||||||
|
|
||||||
// If this is a direct payment, take no action.
|
|
||||||
if n == 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign all pairs a success result except the final hop, as
|
|
||||||
// the payment reached the destination correctly.
|
|
||||||
i.successPairRange(route, 0, n-2)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// All other errors are considered terminal if coming from the
|
// All other errors are considered terminal if coming from the
|
||||||
|
|||||||
@@ -325,8 +325,10 @@ var resultTestCases = []resultTestCase{
|
|||||||
failure: &lnwire.FailMPPTimeout{},
|
failure: &lnwire.FailMPPTimeout{},
|
||||||
|
|
||||||
expectedResult: &interpretedResult{
|
expectedResult: &interpretedResult{
|
||||||
finalFailureReason: &reasonError,
|
pairResults: map[DirectedNodePair]pairResult{
|
||||||
nodeFailure: nil,
|
getTestPair(0, 1): successPairResult(100),
|
||||||
|
},
|
||||||
|
nodeFailure: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -342,9 +344,9 @@ var resultTestCases = []resultTestCase{
|
|||||||
expectedResult: &interpretedResult{
|
expectedResult: &interpretedResult{
|
||||||
pairResults: map[DirectedNodePair]pairResult{
|
pairResults: map[DirectedNodePair]pairResult{
|
||||||
getTestPair(0, 1): successPairResult(100),
|
getTestPair(0, 1): successPairResult(100),
|
||||||
|
getTestPair(1, 2): successPairResult(99),
|
||||||
},
|
},
|
||||||
finalFailureReason: &reasonError,
|
nodeFailure: nil,
|
||||||
nodeFailure: nil,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-3
@@ -1426,13 +1426,25 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
|
|||||||
// execute our path finding algorithm.
|
// execute our path finding algorithm.
|
||||||
finalHtlcExpiry := currentHeight + int32(finalExpiry)
|
finalHtlcExpiry := currentHeight + int32(finalExpiry)
|
||||||
|
|
||||||
|
routingTx, err := newDbRoutingTx(r.cfg.Graph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := routingTx.close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error closing db tx: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
&graphParams{
|
&graphParams{
|
||||||
graph: r.cfg.Graph,
|
|
||||||
bandwidthHints: bandwidthHints,
|
|
||||||
additionalEdges: routeHints,
|
additionalEdges: routeHints,
|
||||||
|
bandwidthHints: bandwidthHints,
|
||||||
|
graph: routingTx,
|
||||||
},
|
},
|
||||||
restrictions, &r.cfg.PathFindingConfig,
|
restrictions,
|
||||||
|
&r.cfg.PathFindingConfig,
|
||||||
source, target, amt, finalHtlcExpiry,
|
source, target, amt, finalHtlcExpiry,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1444,6 +1456,7 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
|
|||||||
source, path, uint32(currentHeight),
|
source, path, uint32(currentHeight),
|
||||||
finalHopParams{
|
finalHopParams{
|
||||||
amt: amt,
|
amt: amt,
|
||||||
|
totalAmt: amt,
|
||||||
cltvDelta: finalExpiry,
|
cltvDelta: finalExpiry,
|
||||||
records: destCustomRecords,
|
records: destCustomRecords,
|
||||||
},
|
},
|
||||||
@@ -1611,6 +1624,10 @@ type LightningPayment struct {
|
|||||||
// understand this new onion payload format, then the payment will
|
// understand this new onion payload format, then the payment will
|
||||||
// fail.
|
// fail.
|
||||||
DestCustomRecords record.CustomSet
|
DestCustomRecords record.CustomSet
|
||||||
|
|
||||||
|
// MaxHtlcs is the maximum number of partial payments that may be use to
|
||||||
|
// complete the full amount.
|
||||||
|
MaxHtlcs uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendPayment attempts to send a payment as described within the passed
|
// SendPayment attempts to send a payment as described within the passed
|
||||||
@@ -2501,6 +2518,7 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
source, pathEdges, uint32(height),
|
source, pathEdges, uint32(height),
|
||||||
finalHopParams{
|
finalHopParams{
|
||||||
amt: receiverAmt,
|
amt: receiverAmt,
|
||||||
|
totalAmt: receiverAmt,
|
||||||
cltvDelta: uint16(finalCltvDelta),
|
cltvDelta: uint16(finalCltvDelta),
|
||||||
records: nil,
|
records: nil,
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-11
@@ -79,11 +79,6 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
|
|||||||
chain := newMockChain(startingHeight)
|
chain := newMockChain(startingHeight)
|
||||||
chainView := newMockChainView(chain)
|
chainView := newMockChainView(chain)
|
||||||
|
|
||||||
selfNode, err := graphInstance.graph.SourceNode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pathFindingConfig := PathFindingConfig{
|
pathFindingConfig := PathFindingConfig{
|
||||||
MinProbability: 0.01,
|
MinProbability: 0.01,
|
||||||
PaymentAttemptPenalty: 100,
|
PaymentAttemptPenalty: 100,
|
||||||
@@ -104,8 +99,7 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
|
|||||||
}
|
}
|
||||||
|
|
||||||
sessionSource := &SessionSource{
|
sessionSource := &SessionSource{
|
||||||
Graph: graphInstance.graph,
|
Graph: graphInstance.graph,
|
||||||
SelfNode: selfNode,
|
|
||||||
QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
||||||
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
||||||
},
|
},
|
||||||
@@ -2188,10 +2182,8 @@ func TestFindPathFeeWeighting(t *testing.T) {
|
|||||||
// We'll now attempt a path finding attempt using this set up. Due to
|
// We'll now attempt a path finding attempt using this set up. Due to
|
||||||
// the edge weighting, we should select the direct path over the 2 hop
|
// the edge weighting, we should select the direct path over the 2 hop
|
||||||
// path even though the direct path has a higher potential time lock.
|
// path even though the direct path has a higher potential time lock.
|
||||||
path, err := findPath(
|
path, err := dbFindPath(
|
||||||
&graphParams{
|
ctx.graph, nil, nil,
|
||||||
graph: ctx.graph,
|
|
||||||
},
|
|
||||||
noRestrictions,
|
noRestrictions,
|
||||||
testPathFindingConfig,
|
testPathFindingConfig,
|
||||||
sourceNode.PubKeyBytes, target, amt, 0,
|
sourceNode.PubKeyBytes, target, amt, 0,
|
||||||
|
|||||||
@@ -3962,6 +3962,10 @@ func (r *rpcServer) dispatchPaymentIntent(
|
|||||||
DestCustomRecords: payIntent.destCustomRecords,
|
DestCustomRecords: payIntent.destCustomRecords,
|
||||||
DestFeatures: payIntent.destFeatures,
|
DestFeatures: payIntent.destFeatures,
|
||||||
PaymentAddr: payIntent.paymentAddr,
|
PaymentAddr: payIntent.paymentAddr,
|
||||||
|
|
||||||
|
// Don't enable multi-part payments on the main rpc.
|
||||||
|
// Users need to use routerrpc for that.
|
||||||
|
MaxHtlcs: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
preImage, route, routerErr = r.server.chanRouter.SendPayment(
|
preImage, route, routerErr = r.server.chanRouter.SendPayment(
|
||||||
|
|||||||
@@ -731,7 +731,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
|||||||
Graph: chanGraph,
|
Graph: chanGraph,
|
||||||
MissionControl: s.missionControl,
|
MissionControl: s.missionControl,
|
||||||
QueryBandwidth: queryBandwidth,
|
QueryBandwidth: queryBandwidth,
|
||||||
SelfNode: selfNode,
|
|
||||||
PathFindingConfig: pathFindingConfig,
|
PathFindingConfig: pathFindingConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user