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.
This commit is contained in:
ccdle12 2018-11-19 17:37:50 +00:00
parent 701244012a
commit 05d0d028bc
No known key found for this signature in database
GPG Key ID: D28DF4293D698C5A
4 changed files with 183 additions and 0 deletions

View File

@ -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."`

View File

@ -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.

View File

@ -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",

View File

@ -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