diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c88026f8..0aa20772 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1967,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. diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index 9bbdab45..6c5f10ba 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -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 @@ -5426,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)