From 05d0d028bcd5cbdac226b4f5175dd94c230234c1 Mon Sep 17 00:00:00 2001 From: ccdle12 Date: Mon, 19 Nov 2018 17:37:50 +0000 Subject: [PATCH] htlcswitch+config+server: Adds a rejecthtlc flag that disables forwarded htlcs config: Adding RejectHTLC field in config struct This commit adds a RejectHTLC field in the config struct in config.go. This allows the user to run lnd as a node that does not accept onward HTLCs. htlcswitch/switch: Adding a field RejectHTLC to the switch config This commit adds a field RejectHTLC to the switch config. When the switch receives an HTLC it will check this flag and if the HTLC is not from the source hop, the HTLC will be rejected. htlcswitch/switch: adding check for RejectHTLC flag and incomingChanID This commit adds a check when receiving UpdateAddHTLC. The check looks for the RejectHTLC flag set and whether the HTLC is from the sourceHop (the local switch). If the HTLC is not from the sourceHop, then we reject the HTLC and return a FailChannelDisabled error. server: adding RejectHTLC field to initialization of switch lnd_test: adding test for RejectHTLC This commit adds a test which tests that a node with the --rejecthtlc flag will reject any onward HTLCs but still can receive direct HTLCs and can send HTLCs. --- config.go | 2 + htlcswitch/switch.go | 13 +++ lntest/itest/lnd_test.go | 167 +++++++++++++++++++++++++++++++++++++++ server.go | 1 + 4 files changed, 183 insertions(+) diff --git a/config.go b/config.go index b22cbf2f..d58ec571 100644 --- a/config.go +++ b/config.go @@ -311,6 +311,8 @@ type config struct { RejectPush bool `long:"rejectpush" description:"If true, lnd will not accept channel opening requests with non-zero push amounts. This should prevent accidental pushes to merchant nodes."` + RejectHTLC bool `long:"rejecthtlc" description:"If true, lnd will not forward any HTLCs that are meant as onward payments. This option will still allow lnd to send HTLCs and receive HTLCs but lnd won't be used as a hop."` + StaggerInitialReconnect bool `long:"stagger-initial-reconnect" description:"If true, will apply a randomized staggering between 0s and 30s when reconnecting to persistent peers on startup. The first 10 reconnections will be attempted instantly, regardless of the flag's value"` MaxOutgoingCltvExpiry uint32 `long:"max-cltv-expiry" description:"The maximum number of blocks funds could be locked up for when forwarding payments."` diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 82cdc979..bdc50fe5 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -171,6 +171,10 @@ type Config struct { // the ChannelNotifier when channels become active and inactive. NotifyActiveChannel func(wire.OutPoint) NotifyInactiveChannel func(wire.OutPoint) + + // RejectHTLC is a flag that instructs the htlcswitch to reject any + // HTLCs that are not from the source hop. + RejectHTLC bool } // Switch is the central messaging bus for all incoming/outgoing HTLCs. @@ -1025,6 +1029,15 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { // payment circuit within our internal state so we can properly forward // the ultimate settle message back latter. case *lnwire.UpdateAddHTLC: + // Check if the node is set to reject all onward HTLCs and also make + // sure that HTLC is not from the source node. + if s.cfg.RejectHTLC && packet.incomingChanID != sourceHop { + failure := &lnwire.FailChannelDisabled{} + addErr := fmt.Errorf("unable to forward any htlcs") + + return s.failAddPacket(packet, failure, addErr) + } + if packet.incomingChanID == sourceHop { // A blank incomingChanID indicates that this is // a pending user-initiated payment. diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index ae5ce147..4be3b528 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -8842,6 +8842,169 @@ out: cleanupForceClose(t, net, net.Bob, chanPointBob) } +// testRejectHTLC tests that a node can be created with the flag --rejecthtlc. +// This means that the node will reject all forwarded HTLCs but can still +// accept direct HTLCs as well as send HTLCs. +func testRejectHTLC(net *lntest.NetworkHarness, t *harnessTest) { + // RejectHTLC + // Alice ------> Carol ------> Bob + // + const chanAmt = btcutil.Amount(1000000) + ctxb := context.Background() + timeout := time.Duration(time.Second * 5) + + // Create Carol with reject htlc flag. + carol, err := net.NewNode("Carol", []string{"--rejecthtlc"}) + if err != nil { + t.Fatalf("unable to create new node: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + // Connect Alice to Carol. + if err := net.ConnectNodes(ctxb, net.Alice, carol); err != nil { + t.Fatalf("unable to connect alice to carol: %v", err) + } + + // Connect Carol to Bob. + if err := net.ConnectNodes(ctxb, carol, net.Bob); err != nil { + t.Fatalf("unable to conenct carol to net.Bob: %v", err) + } + + // Send coins to Carol. + err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + + // Send coins to Alice. + err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcent, net.Alice) + if err != nil { + t.Fatalf("unable to send coins to alice: %v", err) + } + + // Open a channel between Alice and Carol. + ctxt, _ := context.WithTimeout(ctxb, timeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Open a channel between Carol and Bob. + ctxt, _ = context.WithTimeout(ctxb, timeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Channel should be ready for payments. + const payAmt = 100 + + // Helper closure to generate a random pre image. + genPreImage := func() []byte { + preimage := make([]byte, 32) + + _, err = rand.Read(preimage) + if err != nil { + t.Fatalf("unable to generate preimage: %v", err) + } + + return preimage + } + + // Create an invoice from Carol of 100 satoshis. + // We expect Alice to be able to pay this invoice. + preimage := genPreImage() + + carolInvoice := &lnrpc.Invoice{ + Memo: "testing - alice should pay carol", + RPreimage: preimage, + Value: payAmt, + } + + // Carol adds the invoice to her database. + resp, err := carol.AddInvoice(ctxb, carolInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Alice pays Carols invoice. + ctxt, _ = context.WithTimeout(ctxb, timeout) + err = completePaymentRequests( + ctxt, net.Alice, []string{resp.PaymentRequest}, true, + ) + if err != nil { + t.Fatalf("unable to send payments from alice to carol: %v", err) + } + + // Create an invoice from Bob of 100 satoshis. + // We expect Carol to be able to pay this invoice. + preimage = genPreImage() + + bobInvoice := &lnrpc.Invoice{ + Memo: "testing - carol should pay bob", + RPreimage: preimage, + Value: payAmt, + } + + // Bob adds the invoice to his database. + resp, err = net.Bob.AddInvoice(ctxb, bobInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Carol pays Bobs invoice. + ctxt, _ = context.WithTimeout(ctxb, timeout) + err = completePaymentRequests( + ctxt, carol, []string{resp.PaymentRequest}, true, + ) + if err != nil { + t.Fatalf("unable to send payments from carol to bob: %v", err) + } + + // Create an invoice from Bob of 100 satoshis. + // Alice attempts to pay Bob but this should fail, since we are + // using Carol as a hop and her node will reject onward HTLCs. + preimage = genPreImage() + + bobInvoice = &lnrpc.Invoice{ + Memo: "testing - alice tries to pay bob", + RPreimage: preimage, + Value: payAmt, + } + + // Bob adds the invoice to his database. + resp, err = net.Bob.AddInvoice(ctxb, bobInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Alice attempts to pay Bobs invoice. This payment should be rejected since + // we are using Carol as an intermediary hop, Carol is running lnd with + // --rejecthtlc. + ctxt, _ = context.WithTimeout(ctxb, timeout) + err = completePaymentRequests( + ctxt, net.Alice, []string{resp.PaymentRequest}, true, + ) + if err == nil { + t.Fatalf( + "should have been rejected, carol will not accept forwarded htlcs", + ) + } + if !strings.Contains(err.Error(), lnwire.CodeChannelDisabled.String()) { + t.Fatalf("error returned should have been Channel Disabled") + } + + // Close all channels. + ctxt, _ = context.WithTimeout(ctxb, timeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, timeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + // graphSubscription houses the proxied update and error chans for a node's // graph subscriptions. type graphSubscription struct { @@ -14047,6 +14210,10 @@ var testsCases = []*testCase{ name: "multi-hop htlc error propagation", test: testHtlcErrorPropagation, }, + { + name: "reject onward htlc", + test: testRejectHTLC, + }, // TODO(roasbeef): multi-path integration test { name: "node announcement", diff --git a/server.go b/server.go index 7687d095..c979cdae 100644 --- a/server.go +++ b/server.go @@ -435,6 +435,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, AckEventTicker: ticker.New(htlcswitch.DefaultAckInterval), NotifyActiveChannel: s.channelNotifier.NotifyActiveChannelEvent, NotifyInactiveChannel: s.channelNotifier.NotifyInactiveChannelEvent, + RejectHTLC: cfg.RejectHTLC, }, uint32(currentHeight)) if err != nil { return nil, err