Merge pull request #2434 from valentinewallace/fwding-policy-max-htlc
Set max HTLC in forwarding policies.
This commit is contained in:
10 files changed
+699
-564
No files matched your search
@@ -71,6 +71,9 @@ type ForwardingPolicy struct {
|
|||||||
// lifetime of the channel.
|
// lifetime of the channel.
|
||||||
MinHTLC lnwire.MilliSatoshi
|
MinHTLC lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// MaxHTLC is the largest HTLC that is to be forwarded.
|
||||||
|
MaxHTLC lnwire.MilliSatoshi
|
||||||
|
|
||||||
// BaseFee is the base fee, expressed in milli-satoshi that must be
|
// BaseFee is the base fee, expressed in milli-satoshi that must be
|
||||||
// paid for each incoming HTLC. This field, combined with FeeRate is
|
// paid for each incoming HTLC. This field, combined with FeeRate is
|
||||||
// used to compute the required fee for a given HTLC.
|
// used to compute the required fee for a given HTLC.
|
||||||
@@ -1964,6 +1967,25 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte,
|
|||||||
return failure
|
return failure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next, ensure that the passed HTLC isn't too large. If so, we'll cancel
|
||||||
|
// the HTLC directly.
|
||||||
|
if policy.MaxHTLC != 0 && amtToForward > policy.MaxHTLC {
|
||||||
|
l.errorf("outgoing htlc(%x) is too large: max_htlc=%v, "+
|
||||||
|
"htlc_value=%v", payHash[:], policy.MaxHTLC, amtToForward)
|
||||||
|
|
||||||
|
// As part of the returned error, we'll send our latest routing policy
|
||||||
|
// so the sending node obtains the most up-to-date data.
|
||||||
|
var failure lnwire.FailureMessage
|
||||||
|
update, err := l.cfg.FetchLastChannelUpdate(l.ShortChanID())
|
||||||
|
if err != nil {
|
||||||
|
failure = &lnwire.FailTemporaryNodeFailure{}
|
||||||
|
} else {
|
||||||
|
failure = lnwire.NewTemporaryChannelFailure(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
return failure
|
||||||
|
}
|
||||||
|
|
||||||
// Next, using the amount of the incoming HTLC, we'll calculate the
|
// Next, using the amount of the incoming HTLC, we'll calculate the
|
||||||
// expected fee this incoming HTLC must carry in order to satisfy the
|
// expected fee this incoming HTLC must carry in order to satisfy the
|
||||||
// constraints of the outgoing link.
|
// constraints of the outgoing link.
|
||||||
|
|||||||
@@ -772,6 +772,73 @@ func TestLinkForwardMinHTLCPolicyMismatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLinkForwardMaxHTLCPolicyMismatch tests that if a node is an intermediate
|
||||||
|
// node and receives an HTLC which is _above_ its max HTLC policy then the
|
||||||
|
// HTLC will be rejected.
|
||||||
|
func TestLinkForwardMaxHTLCPolicyMismatch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
channels, cleanUp, _, err := createClusterChannels(
|
||||||
|
btcutil.SatoshiPerBitcoin*5, 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.Fatal(err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
// In order to trigger this failure mode, we'll update our policy to have
|
||||||
|
// a new max HTLC of 10 satoshis.
|
||||||
|
maxHtlc := lnwire.NewMSatFromSatoshis(10)
|
||||||
|
|
||||||
|
// First we'll generate a route over 2 hops within the network that
|
||||||
|
// attempts to pay out an amount greater than the max HTLC we're about to
|
||||||
|
// set.
|
||||||
|
amountNoFee := maxHtlc + 1
|
||||||
|
htlcAmt, htlcExpiry, hops := generateHops(
|
||||||
|
amountNoFee, testStartingHeight, n.firstBobChannelLink,
|
||||||
|
n.carolChannelLink,
|
||||||
|
)
|
||||||
|
|
||||||
|
// We'll now update Bob's policy to set the max HTLC we chose earlier.
|
||||||
|
n.secondBobChannelLink.cfg.FwrdingPolicy.MaxHTLC = maxHtlc
|
||||||
|
|
||||||
|
// Finally, we'll make the payment which'll send an HTLC with our
|
||||||
|
// specified parameters.
|
||||||
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||||
|
_, err = makePayment(
|
||||||
|
n.aliceServer, n.carolServer, firstHop, hops, amountNoFee,
|
||||||
|
htlcAmt, htlcExpiry,
|
||||||
|
).Wait(30 * time.Second)
|
||||||
|
|
||||||
|
// We should get an error indicating a temporary channel failure, The
|
||||||
|
// failure is temporary because this payment would be allowed if Bob
|
||||||
|
// updated his policy to increase the max HTLC.
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("payment should have failed but didn't")
|
||||||
|
}
|
||||||
|
|
||||||
|
ferr, ok := err.(*ForwardingError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected a ForwardingError, instead got: %T", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ferr.FailureMessage.(type) {
|
||||||
|
case *lnwire.FailTemporaryChannelFailure:
|
||||||
|
default:
|
||||||
|
t.Fatalf("incorrect error, expected temporary channel failure, "+
|
||||||
|
"instead have: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestUpdateForwardingPolicy tests that the forwarding policy for a link is
|
// TestUpdateForwardingPolicy tests that the forwarding policy for a link is
|
||||||
// able to be updated properly. We'll first create an HTLC that meets the
|
// able to be updated properly. We'll first create an HTLC that meets the
|
||||||
// specified policy, assert that it succeeds, update the policy (to invalidate
|
// specified policy, assert that it succeeds, update the policy (to invalidate
|
||||||
@@ -1528,6 +1595,7 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
|
|||||||
}
|
}
|
||||||
globalPolicy = ForwardingPolicy{
|
globalPolicy = ForwardingPolicy{
|
||||||
MinHTLC: lnwire.NewMSatFromSatoshis(5),
|
MinHTLC: lnwire.NewMSatFromSatoshis(5),
|
||||||
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanAmt),
|
||||||
BaseFee: lnwire.NewMSatFromSatoshis(1),
|
BaseFee: lnwire.NewMSatFromSatoshis(1),
|
||||||
TimeLockDelta: 6,
|
TimeLockDelta: 6,
|
||||||
}
|
}
|
||||||
@@ -5400,6 +5468,7 @@ func TestHtlcSatisfyPolicy(t *testing.T) {
|
|||||||
FwrdingPolicy: ForwardingPolicy{
|
FwrdingPolicy: ForwardingPolicy{
|
||||||
TimeLockDelta: 20,
|
TimeLockDelta: 20,
|
||||||
MinHTLC: 500,
|
MinHTLC: 500,
|
||||||
|
MaxHTLC: 1000,
|
||||||
BaseFee: 10,
|
BaseFee: 10,
|
||||||
},
|
},
|
||||||
FetchLastChannelUpdate: fetchLastChannelUpdate,
|
FetchLastChannelUpdate: fetchLastChannelUpdate,
|
||||||
@@ -5424,6 +5493,14 @@ func TestHtlcSatisfyPolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("above maxhtlc", func(t *testing.T) {
|
||||||
|
result := link.HtlcSatifiesPolicy(hash, 1500, 1200,
|
||||||
|
200, 150, 0)
|
||||||
|
if _, ok := result.(*lnwire.FailTemporaryChannelFailure); !ok {
|
||||||
|
t.Fatalf("expected FailTemporaryChannelFailure failure code")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("insufficient fee", func(t *testing.T) {
|
t.Run("insufficient fee", func(t *testing.T) {
|
||||||
result := link.HtlcSatifiesPolicy(hash, 1005, 1000,
|
result := link.HtlcSatifiesPolicy(hash, 1005, 1000,
|
||||||
200, 150, 0)
|
200, 150, 0)
|
||||||
|
|||||||
+568
-559
File diff suppressed because it is too large.
Load diff
@@ -1597,6 +1597,7 @@ message RoutingPolicy {
|
|||||||
int64 fee_base_msat = 3 [json_name = "fee_base_msat"];
|
int64 fee_base_msat = 3 [json_name = "fee_base_msat"];
|
||||||
int64 fee_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"];
|
int64 fee_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"];
|
||||||
bool disabled = 5 [json_name = "disabled"];
|
bool disabled = 5 [json_name = "disabled"];
|
||||||
|
uint64 max_htlc = 6 [json_name = "max_htlc"];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2815,6 +2815,10 @@
|
|||||||
"disabled": {
|
"disabled": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"format": "boolean"
|
"format": "boolean"
|
||||||
|
},
|
||||||
|
"max_htlc": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6336,3 +6336,9 @@ func (lc *LightningChannel) RemoteCommitHeight() uint64 {
|
|||||||
func (lc *LightningChannel) FwdMinHtlc() lnwire.MilliSatoshi {
|
func (lc *LightningChannel) FwdMinHtlc() lnwire.MilliSatoshi {
|
||||||
return lc.localChanCfg.MinHTLC
|
return lc.localChanCfg.MinHTLC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxPendingAmount returns the maximum HTLC value that can be pending at
|
||||||
|
// any time over this channel.
|
||||||
|
func (lc *LightningChannel) MaxPendingAmount() lnwire.MilliSatoshi {
|
||||||
|
return lc.localChanCfg.MaxPendingAmount
|
||||||
|
}
|
||||||
@@ -488,6 +488,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
|
|||||||
if selfPolicy != nil {
|
if selfPolicy != nil {
|
||||||
forwardingPolicy = &htlcswitch.ForwardingPolicy{
|
forwardingPolicy = &htlcswitch.ForwardingPolicy{
|
||||||
MinHTLC: selfPolicy.MinHTLC,
|
MinHTLC: selfPolicy.MinHTLC,
|
||||||
|
MaxHTLC: selfPolicy.MaxHTLC,
|
||||||
BaseFee: selfPolicy.FeeBaseMSat,
|
BaseFee: selfPolicy.FeeBaseMSat,
|
||||||
FeeRate: selfPolicy.FeeProportionalMillionths,
|
FeeRate: selfPolicy.FeeProportionalMillionths,
|
||||||
TimeLockDelta: uint32(selfPolicy.TimeLockDelta),
|
TimeLockDelta: uint32(selfPolicy.TimeLockDelta),
|
||||||
@@ -1699,15 +1700,17 @@ out:
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll query the localChanCfg of the new channel to
|
// We'll query the localChanCfg of the new channel to determine the
|
||||||
// determine the minimum HTLC value that can be
|
// minimum HTLC value that can be forwarded. For the maximum HTLC
|
||||||
// forwarded. For fees we'll use the default values, as
|
// value that can be forwarded and fees we'll use the default
|
||||||
// they currently are always set to the default values
|
// values, as they currently are always set to the default values
|
||||||
// at initial channel creation.
|
// at initial channel creation. Note that the maximum HTLC value
|
||||||
|
// defaults to the cap on the total value of outstanding HTLCs.
|
||||||
fwdMinHtlc := lnChan.FwdMinHtlc()
|
fwdMinHtlc := lnChan.FwdMinHtlc()
|
||||||
defaultPolicy := p.server.cc.routingPolicy
|
defaultPolicy := p.server.cc.routingPolicy
|
||||||
forwardingPolicy := &htlcswitch.ForwardingPolicy{
|
forwardingPolicy := &htlcswitch.ForwardingPolicy{
|
||||||
MinHTLC: fwdMinHtlc,
|
MinHTLC: fwdMinHtlc,
|
||||||
|
MaxHTLC: lnChan.MaxPendingAmount(),
|
||||||
BaseFee: defaultPolicy.BaseFee,
|
BaseFee: defaultPolicy.BaseFee,
|
||||||
FeeRate: defaultPolicy.FeeRate,
|
FeeRate: defaultPolicy.FeeRate,
|
||||||
TimeLockDelta: defaultPolicy.TimeLockDelta,
|
TimeLockDelta: defaultPolicy.TimeLockDelta,
|
||||||
|
|||||||
@@ -266,6 +266,9 @@ type ChannelEdgeUpdate struct {
|
|||||||
// MinHTLC is the minimum HTLC amount that this channel will forward.
|
// MinHTLC is the minimum HTLC amount that this channel will forward.
|
||||||
MinHTLC lnwire.MilliSatoshi
|
MinHTLC lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// MaxHTLC is the maximum HTLC amount that this channel will forward.
|
||||||
|
MaxHTLC lnwire.MilliSatoshi
|
||||||
|
|
||||||
// BaseFee is the base fee that will charged for all HTLC's forwarded
|
// BaseFee is the base fee that will charged for all HTLC's forwarded
|
||||||
// across the this channel direction.
|
// across the this channel direction.
|
||||||
BaseFee lnwire.MilliSatoshi
|
BaseFee lnwire.MilliSatoshi
|
||||||
@@ -359,6 +362,7 @@ func addToTopologyChange(graph *channeldb.ChannelGraph, update *TopologyChange,
|
|||||||
TimeLockDelta: m.TimeLockDelta,
|
TimeLockDelta: m.TimeLockDelta,
|
||||||
Capacity: edgeInfo.Capacity,
|
Capacity: edgeInfo.Capacity,
|
||||||
MinHTLC: m.MinHTLC,
|
MinHTLC: m.MinHTLC,
|
||||||
|
MaxHTLC: m.MaxHTLC,
|
||||||
BaseFee: m.FeeBaseMSat,
|
BaseFee: m.FeeBaseMSat,
|
||||||
FeeRate: m.FeeProportionalMillionths,
|
FeeRate: m.FeeProportionalMillionths,
|
||||||
AdvertisingNode: aNode,
|
AdvertisingNode: aNode,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ func randEdgePolicy(chanID *lnwire.ShortChannelID,
|
|||||||
LastUpdate: time.Unix(int64(prand.Int31()), 0),
|
LastUpdate: time.Unix(int64(prand.Int31()), 0),
|
||||||
TimeLockDelta: uint16(prand.Int63()),
|
TimeLockDelta: uint16(prand.Int63()),
|
||||||
MinHTLC: lnwire.MilliSatoshi(prand.Int31()),
|
MinHTLC: lnwire.MilliSatoshi(prand.Int31()),
|
||||||
|
MaxHTLC: lnwire.MilliSatoshi(prand.Int31()),
|
||||||
FeeBaseMSat: lnwire.MilliSatoshi(prand.Int31()),
|
FeeBaseMSat: lnwire.MilliSatoshi(prand.Int31()),
|
||||||
FeeProportionalMillionths: lnwire.MilliSatoshi(prand.Int31()),
|
FeeProportionalMillionths: lnwire.MilliSatoshi(prand.Int31()),
|
||||||
Node: node,
|
Node: node,
|
||||||
@@ -435,6 +436,11 @@ func TestEdgeUpdateNotification(t *testing.T) {
|
|||||||
"expected %v, got %v", edgeAnn.MinHTLC,
|
"expected %v, got %v", edgeAnn.MinHTLC,
|
||||||
edgeUpdate.MinHTLC)
|
edgeUpdate.MinHTLC)
|
||||||
}
|
}
|
||||||
|
if edgeUpdate.MaxHTLC != edgeAnn.MaxHTLC {
|
||||||
|
t.Fatalf("max HTLC of edge doesn't match: "+
|
||||||
|
"expected %v, got %v", edgeAnn.MaxHTLC,
|
||||||
|
edgeUpdate.MaxHTLC)
|
||||||
|
}
|
||||||
if edgeUpdate.BaseFee != edgeAnn.FeeBaseMSat {
|
if edgeUpdate.BaseFee != edgeAnn.FeeBaseMSat {
|
||||||
t.Fatalf("base fee of edge doesn't match: "+
|
t.Fatalf("base fee of edge doesn't match: "+
|
||||||
"expected %v, got %v", edgeAnn.FeeBaseMSat,
|
"expected %v, got %v", edgeAnn.FeeBaseMSat,
|
||||||
|
|||||||
@@ -3850,6 +3850,7 @@ func marshalDbEdge(edgeInfo *channeldb.ChannelEdgeInfo,
|
|||||||
edge.Node1Policy = &lnrpc.RoutingPolicy{
|
edge.Node1Policy = &lnrpc.RoutingPolicy{
|
||||||
TimeLockDelta: uint32(c1.TimeLockDelta),
|
TimeLockDelta: uint32(c1.TimeLockDelta),
|
||||||
MinHtlc: int64(c1.MinHTLC),
|
MinHtlc: int64(c1.MinHTLC),
|
||||||
|
MaxHtlc: uint64(c1.MaxHTLC),
|
||||||
FeeBaseMsat: int64(c1.FeeBaseMSat),
|
FeeBaseMsat: int64(c1.FeeBaseMSat),
|
||||||
FeeRateMilliMsat: int64(c1.FeeProportionalMillionths),
|
FeeRateMilliMsat: int64(c1.FeeProportionalMillionths),
|
||||||
Disabled: c1.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
|
Disabled: c1.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
|
||||||
@@ -3860,6 +3861,7 @@ func marshalDbEdge(edgeInfo *channeldb.ChannelEdgeInfo,
|
|||||||
edge.Node2Policy = &lnrpc.RoutingPolicy{
|
edge.Node2Policy = &lnrpc.RoutingPolicy{
|
||||||
TimeLockDelta: uint32(c2.TimeLockDelta),
|
TimeLockDelta: uint32(c2.TimeLockDelta),
|
||||||
MinHtlc: int64(c2.MinHTLC),
|
MinHtlc: int64(c2.MinHTLC),
|
||||||
|
MaxHtlc: uint64(c2.MaxHTLC),
|
||||||
FeeBaseMsat: int64(c2.FeeBaseMSat),
|
FeeBaseMsat: int64(c2.FeeBaseMSat),
|
||||||
FeeRateMilliMsat: int64(c2.FeeProportionalMillionths),
|
FeeRateMilliMsat: int64(c2.FeeProportionalMillionths),
|
||||||
Disabled: c2.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
|
Disabled: c2.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
|
||||||
@@ -4408,6 +4410,7 @@ func marshallTopologyChange(topChange *routing.TopologyChange) *lnrpc.GraphTopol
|
|||||||
RoutingPolicy: &lnrpc.RoutingPolicy{
|
RoutingPolicy: &lnrpc.RoutingPolicy{
|
||||||
TimeLockDelta: uint32(channelUpdate.TimeLockDelta),
|
TimeLockDelta: uint32(channelUpdate.TimeLockDelta),
|
||||||
MinHtlc: int64(channelUpdate.MinHTLC),
|
MinHtlc: int64(channelUpdate.MinHTLC),
|
||||||
|
MaxHtlc: uint64(channelUpdate.MaxHTLC),
|
||||||
FeeBaseMsat: int64(channelUpdate.BaseFee),
|
FeeBaseMsat: int64(channelUpdate.BaseFee),
|
||||||
FeeRateMilliMsat: int64(channelUpdate.FeeRate),
|
FeeRateMilliMsat: int64(channelUpdate.FeeRate),
|
||||||
Disabled: channelUpdate.Disabled,
|
Disabled: channelUpdate.Disabled,
|
||||||
|
|||||||
Reference in new issue
Block a user