From 4eb07e82886ec8de807516e9dedfbca170d27b75 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:09:39 -0700 Subject: [PATCH 1/8] htlcswitch: add new TestForwardingAsymmetricTimeLockPolicies test In this commit, we add a new test to the switch: TestForwardingAsymmetricTimeLockPolicies. This test ensures that a link has two channels, one of which has a greater CLTV delta than the latter, that a payment will successfully be routed across the channels. Atm, the test fails (including the fix to hop payload generation included in the next commit). Atm, due to the way that we check forwarding policies, we'll reject this payment as we're attempting to enforce the policy of the incoming link (cltv delta of 7), instead of that of the outgoing link (cltv delta of 6). As a result, atm, the incoming link checks if (incoming_timeout - delta < outgoing_timeout). For the values in the test case: 112 - 7 < 106 -> 105 < 106, this check fails. The payload is proper, but the check itself should be applied at the outgoing hop. --- htlcswitch/link_test.go | 59 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index c6b2819f..726b46f5 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -618,6 +618,7 @@ func TestLinkForwardTimelockPolicyMismatch(t *testing.T) { _, err = n.makePayment(n.aliceServer, n.carolServer, n.bobServer.PubKey(), hops, amount, htlcAmt, htlcExpiry).Wait(30 * time.Second) + // We should get an error, and that error should indicate that the HTLC // should be rejected due to a policy violation. if err == nil { @@ -637,9 +638,9 @@ func TestLinkForwardTimelockPolicyMismatch(t *testing.T) { } } -// TestLinkForwardTimelockPolicyMismatch tests that if a node is an -// intermediate node in a multi-hop payment and receives an HTLC that violates -// its current fee policy, then the HTLC is rejected with the proper error. +// TestLinkForwardFeePolicyMismatch tests that if a node is an intermediate +// node in a multi-hop payment and receives an HTLC that violates its current +// fee policy, then the HTLC is rejected with the proper error. func TestLinkForwardFeePolicyMismatch(t *testing.T) { t.Parallel() @@ -4527,3 +4528,55 @@ func TestExpectedFee(t *testing.T) { } } } + +// TestForwardingAsymmetricTimeLockPolicies tests that each link is able to +// properly handle forwarding HTLCs when their outgoing channels have +// asymmetric policies w.r.t what they require for time locks. +func TestForwardingAsymmetricTimeLockPolicies(t *testing.T) { + t.Parallel() + + // First, we'll create our traditional three hop network. Bob + // interacting with and asserting the state of two of the end points + // for this test. + channels, cleanUp, _, err := createClusterChannels( + btcutil.SatoshiPerBitcoin*3, + btcutil.SatoshiPerBitcoin*5, + ) + if err != nil { + t.Fatalf("unable to create channel: %v", err) + } + defer cleanUp() + + n := newThreeHopNetwork( + t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, + channels.carolToBob, testStartingHeight, + ) + if err := n.start(); err != nil { + t.Fatalf("unable to start three hop network: %v", err) + } + defer n.stop() + + // Now that each of the links are up, we'll modify the link from Alice + // -> Bob to have a greater time lock delta than that of the link of + // Bob -> Carol. + n.firstBobChannelLink.UpdateForwardingPolicy(ForwardingPolicy{ + TimeLockDelta: 7, + }) + + // Now that the Alice -> Bob link has been updated, we'll craft and + // send a payment from Alice -> Carol. This should succeed as normal, + // even though Bob has asymmetric time lock policies. + amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) + htlcAmt, totalTimelock, hops := generateHops( + amount, testStartingHeight, n.firstBobChannelLink, + n.carolChannelLink, + ) + + _, err = n.makePayment( + n.aliceServer, n.carolServer, n.bobServer.PubKey(), hops, + amount, htlcAmt, totalTimelock, + ).Wait(30 * time.Second) + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } +} From 6e051a80ffb1534ef883b32025bbcdc975e4ad0f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:12:07 -0700 Subject: [PATCH 2/8] htlcswitch: fix bug in generateHops, use CLTV delta of prior hop to compute payload In this commit, we fix a bug in the generateHops helper function. Before this commit, it erroneously used the CLTV delta of the current hop, rather than that of the prior hop when computing the payload. This was incorrect, as when computing the timelock for the incoming hop, we need to factor in the CTLV delta of the outgoing lock, not the incoming lock. --- htlcswitch/test_utils.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 505b87c7..8da96ece 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -601,15 +601,17 @@ func generateHops(payAmt lnwire.MilliSatoshi, startingHeight uint32, nextHop = path[i+1].channel.ShortChanID() } + var timeLock uint32 // If this is the last, hop, then the time lock will be their // specified delta policy plus our starting height. - totalTimelock += lastHop.cfg.FwrdingPolicy.TimeLockDelta - timeLock := totalTimelock - - // Otherwise, the outgoing time lock should be the incoming - // timelock minus their specified delta. - if i != len(path)-1 { - delta := path[i].cfg.FwrdingPolicy.TimeLockDelta + if i == len(path)-1 { + totalTimelock += lastHop.cfg.FwrdingPolicy.TimeLockDelta + timeLock = totalTimelock + } else { + // Otherwise, the outgoing time lock should be the + // incoming timelock minus their specified delta. + delta := path[i+1].cfg.FwrdingPolicy.TimeLockDelta + totalTimelock += delta timeLock = totalTimelock - delta } From 74ec9147b12f426bef680c1f13ce73edaffb58a3 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:15:46 -0700 Subject: [PATCH 3/8] htlcswitch: extend the HtlcSatifiesPolicy to also accept timelock/height info In this commit, we extend the existing HtlcSatifiesPolicy method to also accept timelock and height information. This is required as an upcoming commit will fix an existing bug in the forwarding logic wherein we use the time lock policies of the incoming node rather than that of the outgoing node. --- htlcswitch/interfaces.go | 6 ++++-- htlcswitch/link.go | 4 +++- htlcswitch/mock.go | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index d1afa568..e5cf4cd8 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -81,8 +81,10 @@ type ChannelLink interface { // Otherwise, a valid protocol failure message should be returned in // order to signal to the source of the HTLC, the policy consistency // issue. - HtlcSatifiesPolicy(payHash [32]byte, - incomingAmt, amtToForward lnwire.MilliSatoshi) lnwire.FailureMessage + HtlcSatifiesPolicy(payHash [32]byte, incomingAmt lnwire.MilliSatoshi, + amtToForward lnwire.MilliSatoshi, + incomingTimeout, outgoingTimeout uint32, + heightNow uint32) lnwire.FailureMessage // Bandwidth returns the amount of milli-satoshis which current link // might pass through channel link. The value returned from this method diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 9a933f04..0a5ae904 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1741,7 +1741,9 @@ func (l *channelLink) UpdateForwardingPolicy(newPolicy ForwardingPolicy) { // // NOTE: Part of the ChannelLink interface. func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte, - incomingHtlcAmt, amtToForward lnwire.MilliSatoshi) lnwire.FailureMessage { + incomingHtlcAmt, amtToForward lnwire.MilliSatoshi, + incomingTimeout, outgoingTimeout uint32, + heightNow uint32) lnwire.FailureMessage { l.RLock() policy := l.cfg.FwrdingPolicy diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index dc47730f..b96fa1e4 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -613,7 +613,7 @@ func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) { func (f *mockChannelLink) UpdateForwardingPolicy(_ ForwardingPolicy) { } func (f *mockChannelLink) HtlcSatifiesPolicy([32]byte, lnwire.MilliSatoshi, - lnwire.MilliSatoshi) lnwire.FailureMessage { + lnwire.MilliSatoshi, uint32, uint32, uint32) lnwire.FailureMessage { return nil } From 2bb5931bb7333f4cd79cab382b6623409dbb8459 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:23:10 -0700 Subject: [PATCH 4/8] htlcswitch: move timelock policy verification logic to HtlcSatifiesPolicy In this commit, we extract the time lock policy verification logic from the processRemoteAdds method to the HtlcSatifiesPolicy method. With this change, we fix a lingering bug within the link: we'll no longer verify time lock polices within the incoming link, instead we'll verify it at forwarding time like we should. This is a bug left over from the switch of what the CLTV delta denotes in the channel update message we made within the spec sometime last year. --- htlcswitch/link.go | 133 ++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 80 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 0a5ae904..6884962c 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1784,10 +1784,8 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte, // any case, we'll cancel this HTLC. actualFee := incomingHtlcAmt - amtToForward if incomingHtlcAmt < amtToForward || actualFee < expectedFee { - l.errorf("outgoing htlc(%x) has insufficient "+ - "fee: expected %v, got %v", payHash[:], - int64(expectedFee), - int64(actualFee)) + l.errorf("outgoing htlc(%x) has insufficient fee: expected %v, "+ + "got %v", payHash[:], int64(expectedFee), int64(actualFee)) // As part of the returned error, we'll send our latest routing // policy so the sending node obtains the most up to date data. @@ -1804,6 +1802,54 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte, return failure } + // We want to avoid accepting an HTLC which will expire in the near + // future, so we'll reject an HTLC if its expiration time is too close + // to the current height. + timeDelta := policy.TimeLockDelta + if incomingTimeout-timeDelta <= heightNow { + log.Errorf("htlc(%x) has an expiry that's too soon: "+ + "outgoing_expiry=%v, best_height=%v", payHash[:], + incomingTimeout-timeDelta, heightNow) + + var failure lnwire.FailureMessage + update, err := l.cfg.FetchLastChannelUpdate( + l.ShortChanID(), + ) + if err != nil { + failure = lnwire.NewTemporaryChannelFailure(update) + } else { + failure = lnwire.NewExpiryTooSoon(*update) + } + + return failure + } + + // Finally, we'll ensure that the time-lock on the outgoing HTLC meets + // the following constraint: the incoming time-lock minus our time-lock + // delta should equal the outgoing time lock. Otherwise, whether the + // sender messed up, or an intermediate node tampered with the HTLC. + if incomingTimeout-timeDelta < outgoingTimeout { + log.Errorf("Incoming htlc(%x) has incorrect time-lock value: "+ + "expected at least %v block delta, got %v block delta", + payHash[:], timeDelta, incomingTimeout-outgoingTimeout) + + // Grab the latest routing policy so the sending node is up to + // date with our current policy. + var failure lnwire.FailureMessage + update, err := l.cfg.FetchLastChannelUpdate( + l.ShortChanID(), + ) + if err != nil { + failure = lnwire.NewTemporaryChannelFailure(update) + } else { + failure = lnwire.NewIncorrectCltvExpiry( + incomingTimeout, *update, + ) + } + + return failure + } + return nil } @@ -2285,8 +2331,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, needUpdate = true // There are additional channels left within this route. So - // we'll verify that our forwarding constraints have been - // properly met by this incoming HTLC. + // we'll simply do some forwarding package book-keeping. default: // If hodl.AddIncoming is requested, we will not // validate the forwarded ADD, nor will we send the @@ -2344,80 +2389,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, continue } - // We'll consult the forwarding policy for this link - // when checking time locked related constraints. - hopPolicy := l.cfg.FwrdingPolicy - - // We want to avoid forwarding an HTLC which will - // expire in the near future, so we'll reject an HTLC - // if its expiration time is too close to the current - // height. - timeDelta := hopPolicy.TimeLockDelta - if pd.Timeout-timeDelta <= heightNow { - log.Errorf("htlc(%x) has an expiry "+ - "that's too soon: outgoing_expiry=%v, "+ - "best_height=%v", pd.RHash[:], - pd.Timeout-timeDelta, heightNow) - - var failure lnwire.FailureMessage - update, err := l.cfg.FetchLastChannelUpdate( - l.ShortChanID(), - ) - if err != nil { - failure = lnwire.NewTemporaryChannelFailure( - update, - ) - } else { - failure = lnwire.NewExpiryTooSoon(*update) - } - - l.sendHTLCError( - pd.HtlcIndex, failure, obfuscator, - pd.SourceRef, - ) - needUpdate = true - continue - } - - // Finally, we'll ensure that the time-lock on the - // outgoing HTLC meets the following constraint: the - // incoming time-lock minus our time-lock delta should - // equal the outgoing time lock. Otherwise, whether the - // sender messed up, or an intermediate node tampered - // with the HTLC. - if pd.Timeout-timeDelta < fwdInfo.OutgoingCTLV { - log.Errorf("Incoming htlc(%x) has incorrect "+ - "time-lock value: expected at least "+ - "%v block delta, got %v block delta", - pd.RHash[:], timeDelta, - pd.Timeout-fwdInfo.OutgoingCTLV) - - // Grab the latest routing policy so the - // sending node is up to date with our current - // policy. - update, err := l.cfg.FetchLastChannelUpdate( - l.ShortChanID(), - ) - if err != nil { - l.fail(LinkFailureError{ - code: ErrInternalError}, - "unable to create channel "+ - "update while handling "+ - "the error: %v", err) - return false - } - - failure := lnwire.NewIncorrectCltvExpiry( - pd.Timeout, *update) - l.sendHTLCError( - pd.HtlcIndex, failure, obfuscator, pd.SourceRef, - ) - - needUpdate = true - continue - } - - // TODO(roasbeef): also add max timeout value + // TODO(roasbeef): ensure don't accept outrageous + // timeout for htlc // With all our forwarding constraints met, we'll // create the outgoing HTLC using the parameters as From 7b4c150983bc47f65080db0b6a9ca7c6451cc980 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:25:21 -0700 Subject: [PATCH 5/8] htlcswitch: add new incoming+outgoing timeout fields to htlcPacket --- htlcswitch/link.go | 36 ++++++++++++++++++++---------------- htlcswitch/packet.go | 9 +++++++++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 6884962c..9f801b99 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2373,14 +2373,16 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, chanIterator.EncodeNextHop(buf) updatePacket := &htlcPacket{ - incomingChanID: l.ShortChanID(), - incomingHTLCID: pd.HtlcIndex, - outgoingChanID: fwdInfo.NextHop, - sourceRef: pd.SourceRef, - incomingAmount: pd.Amount, - amount: addMsg.Amount, - htlc: addMsg, - obfuscator: obfuscator, + incomingChanID: l.ShortChanID(), + incomingHTLCID: pd.HtlcIndex, + outgoingChanID: fwdInfo.NextHop, + sourceRef: pd.SourceRef, + incomingAmount: pd.Amount, + amount: addMsg.Amount, + htlc: addMsg, + obfuscator: obfuscator, + incomingTimeout: pd.Timeout, + outgoingTimeout: fwdInfo.OutgoingCTLV, } switchPackets = append( switchPackets, updatePacket, @@ -2439,14 +2441,16 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // section. if fwdPkg.State == channeldb.FwdStateLockedIn { updatePacket := &htlcPacket{ - incomingChanID: l.ShortChanID(), - incomingHTLCID: pd.HtlcIndex, - outgoingChanID: fwdInfo.NextHop, - sourceRef: pd.SourceRef, - incomingAmount: pd.Amount, - amount: addMsg.Amount, - htlc: addMsg, - obfuscator: obfuscator, + incomingChanID: l.ShortChanID(), + incomingHTLCID: pd.HtlcIndex, + outgoingChanID: fwdInfo.NextHop, + sourceRef: pd.SourceRef, + incomingAmount: pd.Amount, + amount: addMsg.Amount, + htlc: addMsg, + obfuscator: obfuscator, + incomingTimeout: pd.Timeout, + outgoingTimeout: fwdInfo.OutgoingCTLV, } fwdPkg.FwdFilter.Set(idx) diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 01e50d27..b6bff471 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -80,6 +80,15 @@ type htlcPacket struct { // circuit holds a reference to an Add's circuit which is persisted in // the switch during successful forwarding. circuit *PaymentCircuit + + // incomingTimeout is the timeout that the incoming HTLC carried. This + // is the timeout of the HTLC applied to the incoming link. + incomingTimeout uint32 + + // outgoingTimeout is the timeout of the proposed outgoing HTLC. This + // will be extraced from the hop payload recevived by the incoming + // link. + outgoingTimeout uint32 } // inKey returns the circuit key used to identify the incoming htlc. From bdecc5bea90de97bab03d56e4ea04af58b8b6bd3 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:25:59 -0700 Subject: [PATCH 6/8] htlcswitch: update forwarding policy verification to use new time lock info --- htlcswitch/link.go | 4 ++-- htlcswitch/switch.go | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 9f801b99..4df5996f 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1807,7 +1807,7 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte, // to the current height. timeDelta := policy.TimeLockDelta if incomingTimeout-timeDelta <= heightNow { - log.Errorf("htlc(%x) has an expiry that's too soon: "+ + l.errorf("htlc(%x) has an expiry that's too soon: "+ "outgoing_expiry=%v, best_height=%v", payHash[:], incomingTimeout-timeDelta, heightNow) @@ -1829,7 +1829,7 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte, // delta should equal the outgoing time lock. Otherwise, whether the // sender messed up, or an intermediate node tampered with the HTLC. if incomingTimeout-timeDelta < outgoingTimeout { - log.Errorf("Incoming htlc(%x) has incorrect time-lock value: "+ + l.errorf("Incoming htlc(%x) has incorrect time-lock value: "+ "expected at least %v block delta, got %v block delta", payHash[:], timeDelta, incomingTimeout-outgoingTimeout) diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 1d649d73..12240238 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -964,9 +964,11 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { // Before we check the link's bandwidth, we'll ensure // that the HTLC satisfies the current forwarding // policy of this target link. + currentHeight := atomic.LoadUint32(&s.bestHeight) err := link.HtlcSatifiesPolicy( htlc.PaymentHash, packet.incomingAmount, - packet.amount, + packet.amount, packet.incomingTimeout, + packet.outgoingTimeout, currentHeight, ) if err != nil { linkErrs[link.ShortChanID()] = err @@ -1404,6 +1406,7 @@ out: } atomic.StoreUint32(&s.bestHeight, uint32(blockEpoch.Height)) + // A local close request has arrived, we'll forward this to the // relevant link (if it exists) so the channel can be // cooperatively closed (if possible). From 6c986864ffb5b3720434b9baa75d5c28d69d1851 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:27:46 -0700 Subject: [PATCH 7/8] routing: fix bug in newRoute, use time lock delta of prior hop, not current In this commit, we fix an existing bug in the newRoute method. Before this commit we would use the time lock delta of the current hop to compute the outgoing time lock for the current hop. This is incorrect as the time lock delta of the _outgoing_ hop should be used, as this is what we're paying for "transit" on. This is a bug left over from when we switched the meaning of the CLTV delta on the ChannelUpdate message sometime last year. The fix is simple: use the CLTV delta of the prior (later in the route) hop. --- routing/pathfind.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index b85b149f..5aa2298b 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -373,14 +373,14 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex, // route such that each hops time lock increases as we // walk backwards in the route, using the delta of the // previous hop. - route.TotalTimeLock += uint32(edge.TimeLockDelta) + delta := uint32(pathEdges[i+1].TimeLockDelta) + route.TotalTimeLock += delta // Otherwise, the value of the outgoing time-lock will // be the value of the time-lock for the _outgoing_ // HTLC, so we factor in their specified grace period // (time lock delta). - nextHop.OutgoingTimeLock = route.TotalTimeLock - - uint32(edge.TimeLockDelta) + nextHop.OutgoingTimeLock = route.TotalTimeLock - delta } route.Hops[i] = nextHop From 6a6c29cd008603fd4f97e2b9ba84c55f2c082560 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 25 Jun 2018 20:29:03 -0700 Subject: [PATCH 8/8] routing: fix incorrect expiry values in spec_example.json In this commit, we fix the incorrect expiry values in the spec_example.json test file. Many of the time locks were incorrect which allowed bugs within the path finding logic related to CLTV deltas to go un-detected. --- routing/testdata/spec_example.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/routing/testdata/spec_example.json b/routing/testdata/spec_example.json index a0cd0ffe..892d99b7 100644 --- a/routing/testdata/spec_example.json +++ b/routing/testdata/spec_example.json @@ -30,7 +30,7 @@ "channel_id": 12345, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 1, - "expiry": 20, + "expiry": 10, "min_htlc": 1, "fee_base_msat": 100, "fee_rate": 1000, @@ -43,7 +43,7 @@ "channel_id": 12345, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 0, - "expiry": 10, + "expiry": 20, "min_htlc": 1, "fee_base_msat": 200, "fee_rate": 2000, @@ -56,7 +56,7 @@ "channel_id": 12345839, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 1, - "expiry": 40, + "expiry": 10, "min_htlc": 1, "fee_base_msat": 100, "fee_rate": 1000, @@ -69,7 +69,7 @@ "channel_id": 12345839, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 0, - "expiry": 10, + "expiry": 40, "min_htlc": 1, "fee_base_msat": 400, "fee_rate": 4000, @@ -95,7 +95,7 @@ "channel_id": 1234583, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 1, - "expiry": 40, + "expiry": 30, "min_htlc": 1, "fee_base_msat": 300, "fee_rate": 3000, @@ -108,7 +108,7 @@ "channel_id": 1234589, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 1, - "expiry": 20, + "expiry": 30, "min_htlc": 1, "fee_base_msat": 300, "fee_rate": 3000, @@ -121,7 +121,7 @@ "channel_id": 1234589, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", "flags": 0, - "expiry": 30, + "expiry": 20, "min_htlc": 1, "fee_base_msat": 200, "fee_rate": 2000,