Merge pull request #2434 from valentinewallace/fwding-policy-max-htlc

Set max HTLC in forwarding policies.
This commit is contained in:
Olaoluwa Osuntokun 2019-02-25 12:43:15 -03:00 committed by GitHub
commit b13d8cd261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 699 additions and 564 deletions

View File

@ -71,6 +71,9 @@ type ForwardingPolicy struct {
// lifetime of the channel.
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
// paid for each incoming HTLC. This field, combined with FeeRate is
// used to compute the required fee for a given HTLC.
@ -1964,6 +1967,25 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte,
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
// expected fee this incoming HTLC must carry in order to satisfy the
// constraints of the outgoing link.

View File

@ -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
// 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
@ -1528,6 +1595,7 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
}
globalPolicy = ForwardingPolicy{
MinHTLC: lnwire.NewMSatFromSatoshis(5),
MaxHTLC: lnwire.NewMSatFromSatoshis(chanAmt),
BaseFee: lnwire.NewMSatFromSatoshis(1),
TimeLockDelta: 6,
}
@ -5400,6 +5468,7 @@ func TestHtlcSatisfyPolicy(t *testing.T) {
FwrdingPolicy: ForwardingPolicy{
TimeLockDelta: 20,
MinHTLC: 500,
MaxHTLC: 1000,
BaseFee: 10,
},
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) {
result := link.HtlcSatifiesPolicy(hash, 1005, 1000,
200, 150, 0)

File diff suppressed because it is too large Load Diff

View File

@ -1597,6 +1597,7 @@ message RoutingPolicy {
int64 fee_base_msat = 3 [json_name = "fee_base_msat"];
int64 fee_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"];
bool disabled = 5 [json_name = "disabled"];
uint64 max_htlc = 6 [json_name = "max_htlc"];
}
/**

View File

@ -2815,6 +2815,10 @@
"disabled": {
"type": "boolean",
"format": "boolean"
},
"max_htlc": {
"type": "string",
"format": "uint64"
}
}
},

View File

@ -6336,3 +6336,9 @@ func (lc *LightningChannel) RemoteCommitHeight() uint64 {
func (lc *LightningChannel) FwdMinHtlc() lnwire.MilliSatoshi {
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
}

13
peer.go
View File

@ -488,6 +488,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
if selfPolicy != nil {
forwardingPolicy = &htlcswitch.ForwardingPolicy{
MinHTLC: selfPolicy.MinHTLC,
MaxHTLC: selfPolicy.MaxHTLC,
BaseFee: selfPolicy.FeeBaseMSat,
FeeRate: selfPolicy.FeeProportionalMillionths,
TimeLockDelta: uint32(selfPolicy.TimeLockDelta),
@ -1699,15 +1700,17 @@ out:
continue
}
// We'll query the localChanCfg of the new channel to
// determine the minimum HTLC value that can be
// forwarded. For fees we'll use the default values, as
// they currently are always set to the default values
// at initial channel creation.
// We'll query the localChanCfg of the new channel to determine the
// minimum HTLC value that can be forwarded. For the maximum HTLC
// value that can be forwarded and fees we'll use the default
// values, as they currently are always set to the default values
// at initial channel creation. Note that the maximum HTLC value
// defaults to the cap on the total value of outstanding HTLCs.
fwdMinHtlc := lnChan.FwdMinHtlc()
defaultPolicy := p.server.cc.routingPolicy
forwardingPolicy := &htlcswitch.ForwardingPolicy{
MinHTLC: fwdMinHtlc,
MaxHTLC: lnChan.MaxPendingAmount(),
BaseFee: defaultPolicy.BaseFee,
FeeRate: defaultPolicy.FeeRate,
TimeLockDelta: defaultPolicy.TimeLockDelta,

View File

@ -266,6 +266,9 @@ type ChannelEdgeUpdate struct {
// MinHTLC is the minimum HTLC amount that this channel will forward.
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
// across the this channel direction.
BaseFee lnwire.MilliSatoshi
@ -359,6 +362,7 @@ func addToTopologyChange(graph *channeldb.ChannelGraph, update *TopologyChange,
TimeLockDelta: m.TimeLockDelta,
Capacity: edgeInfo.Capacity,
MinHTLC: m.MinHTLC,
MaxHTLC: m.MaxHTLC,
BaseFee: m.FeeBaseMSat,
FeeRate: m.FeeProportionalMillionths,
AdvertisingNode: aNode,

View File

@ -77,6 +77,7 @@ func randEdgePolicy(chanID *lnwire.ShortChannelID,
LastUpdate: time.Unix(int64(prand.Int31()), 0),
TimeLockDelta: uint16(prand.Int63()),
MinHTLC: lnwire.MilliSatoshi(prand.Int31()),
MaxHTLC: lnwire.MilliSatoshi(prand.Int31()),
FeeBaseMSat: lnwire.MilliSatoshi(prand.Int31()),
FeeProportionalMillionths: lnwire.MilliSatoshi(prand.Int31()),
Node: node,
@ -435,6 +436,11 @@ func TestEdgeUpdateNotification(t *testing.T) {
"expected %v, got %v", edgeAnn.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 {
t.Fatalf("base fee of edge doesn't match: "+
"expected %v, got %v", edgeAnn.FeeBaseMSat,

View File

@ -3850,6 +3850,7 @@ func marshalDbEdge(edgeInfo *channeldb.ChannelEdgeInfo,
edge.Node1Policy = &lnrpc.RoutingPolicy{
TimeLockDelta: uint32(c1.TimeLockDelta),
MinHtlc: int64(c1.MinHTLC),
MaxHtlc: uint64(c1.MaxHTLC),
FeeBaseMsat: int64(c1.FeeBaseMSat),
FeeRateMilliMsat: int64(c1.FeeProportionalMillionths),
Disabled: c1.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
@ -3860,6 +3861,7 @@ func marshalDbEdge(edgeInfo *channeldb.ChannelEdgeInfo,
edge.Node2Policy = &lnrpc.RoutingPolicy{
TimeLockDelta: uint32(c2.TimeLockDelta),
MinHtlc: int64(c2.MinHTLC),
MaxHtlc: uint64(c2.MaxHTLC),
FeeBaseMsat: int64(c2.FeeBaseMSat),
FeeRateMilliMsat: int64(c2.FeeProportionalMillionths),
Disabled: c2.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
@ -4408,6 +4410,7 @@ func marshallTopologyChange(topChange *routing.TopologyChange) *lnrpc.GraphTopol
RoutingPolicy: &lnrpc.RoutingPolicy{
TimeLockDelta: uint32(channelUpdate.TimeLockDelta),
MinHtlc: int64(channelUpdate.MinHTLC),
MaxHtlc: uint64(channelUpdate.MaxHTLC),
FeeBaseMsat: int64(channelUpdate.BaseFee),
FeeRateMilliMsat: int64(channelUpdate.FeeRate),
Disabled: channelUpdate.Disabled,