routing: if MaxShardAmt is set, then use that as a ceiling for our splits
In this commit, we thread through the necessary state to allow users to set a max shard amount. If this value is set, then this'll effectively serve as a ceiling for all our split attempts. If we need to split, we'll first try to use `paymentAmt/2`, if that's bigger than `MaxShardAmt, then we'll use the latter instead. Ideally in the future we have a dynamic way to automatically set both the `MaxShardAmt` as well as `MaxParts` for users. Until then exposing these two new fields will allow us to experiment with setting them automatically using the RPC interface, and also give users a bit more control over how we attempt to route payments, akin to coin control for on-chain payments. Fixes #4730
This commit is contained in:
parent
7398e59927
commit
b73a6e2c61
@ -204,6 +204,7 @@ out:
|
|||||||
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
MaxParts: 1,
|
||||||
}
|
}
|
||||||
sendAndAssertFailure(
|
sendAndAssertFailure(
|
||||||
t, net.Alice,
|
t, net.Alice,
|
||||||
@ -240,6 +241,7 @@ out:
|
|||||||
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
MaxParts: 1,
|
||||||
}
|
}
|
||||||
sendAndAssertFailure(
|
sendAndAssertFailure(
|
||||||
t, net.Alice,
|
t, net.Alice,
|
||||||
@ -300,6 +302,7 @@ out:
|
|||||||
PaymentRequest: carolInvoice2.PaymentRequest,
|
PaymentRequest: carolInvoice2.PaymentRequest,
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
MaxParts: 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -332,6 +335,7 @@ out:
|
|||||||
PaymentRequest: carolInvoice3.PaymentRequest,
|
PaymentRequest: carolInvoice3.PaymentRequest,
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
MaxParts: 1,
|
||||||
}
|
}
|
||||||
sendAndAssertFailure(
|
sendAndAssertFailure(
|
||||||
t, net.Alice,
|
t, net.Alice,
|
||||||
@ -381,6 +385,7 @@ out:
|
|||||||
PaymentRequest: carolInvoice.PaymentRequest,
|
PaymentRequest: carolInvoice.PaymentRequest,
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
MaxParts: 1,
|
||||||
},
|
},
|
||||||
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
||||||
)
|
)
|
||||||
|
@ -28,6 +28,7 @@ type integratedRoutingContext struct {
|
|||||||
target *mockNode
|
target *mockNode
|
||||||
|
|
||||||
amt lnwire.MilliSatoshi
|
amt lnwire.MilliSatoshi
|
||||||
|
maxShardAmt *lnwire.MilliSatoshi
|
||||||
finalExpiry int32
|
finalExpiry int32
|
||||||
|
|
||||||
mcCfg MissionControlConfig
|
mcCfg MissionControlConfig
|
||||||
@ -151,6 +152,10 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
|
|||||||
MaxParts: maxParts,
|
MaxParts: maxParts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.maxShardAmt != nil {
|
||||||
|
payment.MaxShardAmt = c.maxShardAmt
|
||||||
|
}
|
||||||
|
|
||||||
session, err := newPaymentSession(
|
session, err := newPaymentSession(
|
||||||
&payment, getBandwidthHints,
|
&payment, getBandwidthHints,
|
||||||
func() (routingGraph, func(), error) {
|
func() (routingGraph, func(), error) {
|
||||||
|
@ -89,6 +89,7 @@ type mppSendTestCase struct {
|
|||||||
graph func(g *mockGraph)
|
graph func(g *mockGraph)
|
||||||
expectedFailure bool
|
expectedFailure bool
|
||||||
maxParts uint32
|
maxParts uint32
|
||||||
|
maxShardSize btcutil.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -208,6 +209,33 @@ var mppTestCases = []mppSendTestCase{
|
|||||||
expectedFailure: true,
|
expectedFailure: true,
|
||||||
maxParts: 10,
|
maxParts: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Test that if maxShardSize is set, then all attempts are below the
|
||||||
|
// max shard size, yet still sum up to the total payment amount. A
|
||||||
|
// payment of 30k satoshis with a max shard size of 10k satoshis should
|
||||||
|
// produce 3 payments of 10k sats each.
|
||||||
|
{
|
||||||
|
name: "max shard size clamping",
|
||||||
|
graph: onePathGraph,
|
||||||
|
amt: 30_000,
|
||||||
|
expectedAttempts: 3,
|
||||||
|
expectedSuccesses: []expectedHtlcSuccess{
|
||||||
|
{
|
||||||
|
amt: 10_000,
|
||||||
|
chans: []uint64{chanSourceIm1, chanIm1Target},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amt: 10_000,
|
||||||
|
chans: []uint64{chanSourceIm1, chanIm1Target},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amt: 10_000,
|
||||||
|
chans: []uint64{chanSourceIm1, chanIm1Target},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxParts: 1000,
|
||||||
|
maxShardSize: 10_000,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMppSend tests that a payment can be completed using multiple shards.
|
// TestMppSend tests that a payment can be completed using multiple shards.
|
||||||
@ -229,6 +257,11 @@ func testMppSend(t *testing.T, testCase *mppSendTestCase) {
|
|||||||
|
|
||||||
ctx.amt = lnwire.NewMSatFromSatoshis(testCase.amt)
|
ctx.amt = lnwire.NewMSatFromSatoshis(testCase.amt)
|
||||||
|
|
||||||
|
if testCase.maxShardSize != 0 {
|
||||||
|
shardAmt := lnwire.NewMSatFromSatoshis(testCase.maxShardSize)
|
||||||
|
ctx.maxShardAmt = &shardAmt
|
||||||
|
}
|
||||||
|
|
||||||
attempts, err := ctx.testPayment(testCase.maxParts)
|
attempts, err := ctx.testPayment(testCase.maxParts)
|
||||||
switch {
|
switch {
|
||||||
case err == nil && testCase.expectedFailure:
|
case err == nil && testCase.expectedFailure:
|
||||||
|
@ -230,6 +230,18 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
|
|||||||
|
|
||||||
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
|
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
|
||||||
|
|
||||||
|
// Before we enter the loop below, we'll make sure to respect the max
|
||||||
|
// payment shard size (if it's set), which is effectively our
|
||||||
|
// client-side MTU that we'll attempt to respect at all times.
|
||||||
|
maxShardActive := p.payment.MaxShardAmt != nil
|
||||||
|
if maxShardActive && maxAmt > *p.payment.MaxShardAmt {
|
||||||
|
p.log.Debug("Clamping payment attempt from %v to %v due to "+
|
||||||
|
"max shard size of %v", maxAmt,
|
||||||
|
*p.payment.MaxShardAmt, maxAmt)
|
||||||
|
|
||||||
|
maxAmt = *p.payment.MaxShardAmt
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// We'll also obtain a set of bandwidthHints from the lower
|
// We'll also obtain a set of bandwidthHints from the lower
|
||||||
// layer for each of our outbound channels. This will allow the
|
// layer for each of our outbound channels. This will allow the
|
||||||
@ -279,7 +291,8 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !p.payment.DestFeatures.HasFeature(lnwire.MPPOptional) {
|
if !p.payment.DestFeatures.HasFeature(lnwire.MPPOptional) {
|
||||||
p.log.Debug("not splitting because destination doesn't declare MPP")
|
p.log.Debug("not splitting because " +
|
||||||
|
"destination doesn't declare MPP")
|
||||||
|
|
||||||
return nil, errNoPathFound
|
return nil, errNoPathFound
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user