mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-04-08 20:28:04 +02:00
Merge pull request #2434 from valentinewallace/fwding-policy-max-htlc
Set max HTLC in forwarding policies.
This commit is contained in:
commit
b13d8cd261
@ -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.
|
||||
|
@ -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)
|
||||
|
1127
lnrpc/rpc.pb.go
1127
lnrpc/rpc.pb.go
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_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"];
|
||||
bool disabled = 5 [json_name = "disabled"];
|
||||
uint64 max_htlc = 6 [json_name = "max_htlc"];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2815,6 +2815,10 @@
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
},
|
||||
"max_htlc": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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
13
peer.go
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user