From 473dfd115be3b5a241e2c4c44e6474fb43302ce1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 27 Feb 2018 22:19:21 -0800 Subject: [PATCH] htlcswitch: add set of tests for the forwarding log --- htlcswitch/link.go | 2 + htlcswitch/mock.go | 19 ++-- htlcswitch/switch.go | 2 +- htlcswitch/switch_test.go | 201 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 210 insertions(+), 14 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c0c3e8f3..ae923277 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -683,6 +683,8 @@ out: // carried out by the remote peer. In the case of such an // event, we'll wipe the channel state from the peer, and mark // the contract as fully settled. Afterwards we can exit. + // + // TODO(roasbeef): add force closure? also breach? case <-l.cfg.ChainEvents.UnilateralClosure: log.Warnf("Remote peer has closed ChannelPoint(%v) on-chain", l.channel.ChannelPoint()) diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index b748c668..7b6e5803 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -82,6 +82,7 @@ var _ lnwallet.FeeEstimator = (*mockFeeEstimator)(nil) type mockForwardingLog struct { sync.Mutex + events map[time.Time]channeldb.ForwardingEvent } @@ -124,13 +125,17 @@ func newMockServer(t testing.TB, name string) *mockServer { copy(id[:], h[:]) return &mockServer{ - t: t, - id: id, - name: name, - messages: make(chan lnwire.Message, 3000), - quit: make(chan struct{}), - registry: newMockRegistry(), - htlcSwitch: New(Config{}), + t: t, + id: id, + name: name, + messages: make(chan lnwire.Message, 3000), + quit: make(chan struct{}), + registry: newMockRegistry(), + htlcSwitch: New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }), interceptorFuncs: make([]messageInterceptor, 0), } } diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 029c1515..9086700b 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -754,7 +754,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { } } - // For local HTLC's we'll dispatch the settle event back to the + // For local HTLCs we'll dispatch the settle event back to the // caller, rather than to the peer that sent us the HTLC // originally. localHTLC := packet.incomingChanID == (lnwire.ShortChannelID{}) diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index 6fbd489f..95d95797 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -7,10 +7,13 @@ import ( "time" "github.com/btcsuite/fastsha256" + "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" ) var ( @@ -35,7 +38,11 @@ func TestSwitchForward(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -122,7 +129,11 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -177,7 +188,11 @@ func TestSkipIneligibleLinksLocalForward(t *testing.T) { // to forward form the get go. alicePeer := newMockServer(t, "alice") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -216,7 +231,11 @@ func TestSwitchCancel(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -298,7 +317,11 @@ func TestSwitchAddSamePayment(t *testing.T) { alicePeer := newMockServer(t, "alice") bobPeer := newMockServer(t, "bob") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -422,7 +445,11 @@ func TestSwitchSendPayment(t *testing.T) { alicePeer := newMockServer(t, "alice") - s := New(Config{}) + s := New(Config{ + FwdingLog: &mockForwardingLog{ + events: make(map[time.Time]channeldb.ForwardingEvent), + }, + }) s.Start() aliceChannelLink := newMockChannelLink( @@ -542,3 +569,165 @@ func TestSwitchSendPayment(t *testing.T) { t.Fatal("wrong amount of pending payments") } } + +// TestLocalPaymentNoForwardingEvents tests that if we send a series of locally +// initiated payments, then they aren't reflected in the forwarding log. +func TestLocalPaymentNoForwardingEvents(t *testing.T) { + t.Parallel() + + // First, we'll create our traditional three hop network. We'll only be + // interacting with and asserting the state of the first end point for + // this test. + channels, cleanUp, _, err := createClusterChannels( + btcutil.SatoshiPerBitcoin*3, + 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.Fatalf("unable to start three hop network: %v", err) + } + + // We'll now craft and send a payment from Alice to Bob. + amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) + htlcAmt, totalTimelock, hops := generateHops( + amount, testStartingHeight, n.firstBobChannelLink, + ) + + // With the payment crafted, we'll send it from Alice to Bob. We'll + // wait for Alice to receive the preimage for the payment before + // proceeding. + receiver := n.bobServer + _, err = n.makePayment( + n.aliceServer, receiver, n.bobServer.PubKey(), hops, amount, + htlcAmt, totalTimelock, + ).Wait(30 * time.Second) + if err != nil { + t.Fatalf("unable to make the payment: %v", err) + } + + // At this point, we'll forcibly stop the three hop network. Doing + // this will cause any pending forwarding events to be flushed by the + // various switches in the network. + n.stop() + + // With all the switches stopped, we'll fetch Alice's mock forwarding + // event log. + log, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + + // If we examine the memory of the forwarding log, then it should be + // blank. + if len(log.events) != 0 { + t.Fatalf("log should have no events, instead has: %v", + spew.Sdump(log.events)) + } +} + +// TestMultiHopPaymentForwardingEvents tests that if we send a series of +// multi-hop payments via Alice->Bob->Carol. Then Bob properly logs forwarding +// events, while Alice and Carol don't. +func TestMultiHopPaymentForwardingEvents(t *testing.T) { + t.Parallel() + + // First, we'll create our traditional three hop network. + channels, cleanUp, _, err := createClusterChannels( + btcutil.SatoshiPerBitcoin*3, + 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.Fatalf("unable to start three hop network: %v", err) + } + + // We'll make now 10 payments, of 100k satoshis each from Alice to + // Carol via Bob. + const numPayments = 10 + finalAmt := lnwire.NewMSatFromSatoshis(100000) + htlcAmt, totalTimelock, hops := generateHops( + finalAmt, testStartingHeight, n.firstBobChannelLink, + n.carolChannelLink, + ) + for i := 0; i < numPayments; i++ { + _, err := n.makePayment( + n.aliceServer, n.carolServer, n.bobServer.PubKey(), + hops, finalAmt, htlcAmt, totalTimelock, + ).Wait(30 * time.Second) + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + } + + time.Sleep(time.Millisecond * 200) + + // With all 10 payments sent. We'll now manually stop each of the + // switches so we can examine their end state. + n.stop() + + // Alice and Carol shouldn't have any recorded forwarding events, as + // they were the source and the sink for these payment flows. + aliceLog, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + if len(aliceLog.events) != 0 { + t.Fatalf("log should have no events, instead has: %v", + spew.Sdump(aliceLog.events)) + } + carolLog, ok := n.carolServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + if len(carolLog.events) != 0 { + t.Fatalf("log should have no events, instead has: %v", + spew.Sdump(carolLog.events)) + } + + // Bob on the other hand, should have 10 events. + bobLog, ok := n.bobServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog) + if !ok { + t.Fatalf("mockForwardingLog assertion failed") + } + if len(bobLog.events) != 10 { + t.Fatalf("log should have 10 events, instead has: %v", + spew.Sdump(bobLog.events)) + } + + // Each of the 10 events should have had all fields set properly. + for _, event := range bobLog.events { + // The incoming and outgoing channels should properly be set for + // the event. + if event.IncomingChanID != n.aliceChannelLink.ShortChanID() { + t.Fatalf("chan id mismatch: expected %v, got %v", + event.IncomingChanID, + n.aliceChannelLink.ShortChanID()) + } + if event.OutgoingChanID != n.carolChannelLink.ShortChanID() { + t.Fatalf("chan id mismatch: expected %v, got %v", + event.OutgoingChanID, + n.carolChannelLink.ShortChanID()) + } + + // Additionally, the incoming and outgoing amounts should also + // be properly set. + if event.AmtIn != htlcAmt { + t.Fatalf("incoming amt mismatch: expected %v, got %v", + event.AmtIn, htlcAmt) + } + if event.AmtOut != finalAmt { + t.Fatalf("outgoing amt mismatch: expected %v, got %v", + event.AmtOut, finalAmt) + } + } +}