package chanfitness import ( "errors" "math/big" "testing" "time" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" "github.com/stretchr/testify/require" ) // testNow is the current time tests will use. var testNow = time.Unix(1592465134, 0) // TestStartStoreError tests the starting of the store in cases where the setup // functions fail. It does not test the mechanics of consuming events because // these are covered in a separate set of tests. func TestStartStoreError(t *testing.T) { // Ok and erroring subscribe functions are defined here to de-clutter // tests. okSubscribeFunc := func() (subscribe.Subscription, error) { return newMockSubscription(t), nil } errSubscribeFunc := func() (subscribe.Subscription, error) { return nil, errors.New("intentional test err") } tests := []struct { name string ChannelEvents func() (subscribe.Subscription, error) PeerEvents func() (subscribe.Subscription, error) GetChannels func() ([]*channeldb.OpenChannel, error) }{ { name: "Channel events fail", ChannelEvents: errSubscribeFunc, }, { name: "Peer events fail", ChannelEvents: okSubscribeFunc, PeerEvents: errSubscribeFunc, }, { name: "Get open channels fails", ChannelEvents: okSubscribeFunc, PeerEvents: okSubscribeFunc, GetChannels: func() ([]*channeldb.OpenChannel, error) { return nil, errors.New("intentional test err") }, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { clock := clock.NewTestClock(testNow) store := NewChannelEventStore(&Config{ SubscribeChannelEvents: test.ChannelEvents, SubscribePeerEvents: test.PeerEvents, GetOpenChannels: test.GetChannels, Clock: clock, }) err := store.Start() // Check that we receive an error, because the test only // checks for error cases. if err == nil { t.Fatalf("Expected error on startup, got: nil") } }) } } // TestMonitorChannelEvents tests the store's handling of channel and peer // events. It tests for the unexpected cases where we receive a channel open for // an already known channel and but does not test for closing an unknown channel // because it would require custom logic in the test to prevent iterating // through an eventLog which does not exist. This test does not test handling // of uptime and lifespan requests, as they are tested in their own tests. func TestMonitorChannelEvents(t *testing.T) { var ( pubKey = &btcec.PublicKey{ X: big.NewInt(0), Y: big.NewInt(1), Curve: btcec.S256(), } chan1 = wire.OutPoint{Index: 1} chan2 = wire.OutPoint{Index: 2} ) peer1, err := route.NewVertexFromBytes(pubKey.SerializeCompressed()) require.NoError(t, err) t.Run("peer comes online after channel open", func(t *testing.T) { gen := func(ctx *chanEventStoreTestCtx) { ctx.sendChannelOpenedUpdate(pubKey, chan1) ctx.peerEvent(peer1, true) } testEventStore(t, gen, 1, map[route.Vertex]bool{ peer1: true, }) }) t.Run("duplicate channel open events", func(t *testing.T) { gen := func(ctx *chanEventStoreTestCtx) { ctx.sendChannelOpenedUpdate(pubKey, chan1) ctx.sendChannelOpenedUpdate(pubKey, chan1) ctx.peerEvent(peer1, true) } testEventStore(t, gen, 1, map[route.Vertex]bool{ peer1: true, }) }) t.Run("peer online before channel created", func(t *testing.T) { gen := func(ctx *chanEventStoreTestCtx) { ctx.peerEvent(peer1, true) ctx.sendChannelOpenedUpdate(pubKey, chan1) } testEventStore(t, gen, 1, map[route.Vertex]bool{ peer1: true, }) }) t.Run("multiple channels for peer", func(t *testing.T) { gen := func(ctx *chanEventStoreTestCtx) { ctx.peerEvent(peer1, true) ctx.sendChannelOpenedUpdate(pubKey, chan1) ctx.peerEvent(peer1, false) ctx.sendChannelOpenedUpdate(pubKey, chan2) } testEventStore(t, gen, 2, map[route.Vertex]bool{ peer1: false, }) }) t.Run("multiple channels for peer, one closed", func(t *testing.T) { gen := func(ctx *chanEventStoreTestCtx) { ctx.peerEvent(peer1, true) ctx.sendChannelOpenedUpdate(pubKey, chan1) ctx.peerEvent(peer1, false) ctx.sendChannelOpenedUpdate(pubKey, chan2) ctx.closeChannel(chan1, pubKey) ctx.peerEvent(peer1, true) } testEventStore(t, gen, 2, map[route.Vertex]bool{ peer1: true, }) }) } // testEventStore creates a new test contexts, generates a set of events for it // and tests that it has the number of channels and online state for peers that // we expect. func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), expectedChannels int, expectedPeers map[route.Vertex]bool) { testCtx := newChanEventStoreTestCtx(t) testCtx.start() generateEvents(testCtx) // Shutdown the store so that we can safely access the maps in our event // store. testCtx.stop() require.Len(t, testCtx.store.channels, expectedChannels) require.Equal(t, expectedPeers, testCtx.store.peers) } // TestGetLifetime tests the GetLifetime function for the cases where a channel // is known and unknown to the store. func TestGetLifetime(t *testing.T) { tests := []struct { name string channelFound bool channelPoint wire.OutPoint opened time.Time closed time.Time expectedError error }{ { name: "Channel found", channelFound: true, opened: testNow, closed: testNow.Add(time.Hour * -1), expectedError: nil, }, { name: "Channel not found", expectedError: ErrChannelNotFound, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { ctx := newChanEventStoreTestCtx(t) // Add channel to eventStore if the test indicates that // it should be present. if test.channelFound { ctx.store.channels[test.channelPoint] = &chanEventLog{ openedAt: test.opened, closedAt: test.closed, } } ctx.start() open, close, err := ctx.store.GetLifespan(test.channelPoint) require.Equal(t, test.expectedError, err) require.Equal(t, test.opened, open) require.Equal(t, test.closed, close) ctx.stop() }) } } // TestGetUptime tests the getUptime call for channels known to the event store. // It does not test the trivial case where a channel is unknown to the store, // because this is simply a zero return if an item is not found in a map. It // tests the unexpected edge cases where a tracked channel does not have any // events recorded, and when a zero time is specified for the uptime range. func TestGetUptime(t *testing.T) { twoHoursAgo := testNow.Add(time.Hour * -2) fourHoursAgo := testNow.Add(time.Hour * -4) tests := []struct { name string channelPoint wire.OutPoint // events is the set of events we expect to find in the channel // store. events []*channelEvent // openedAt is the time the channel is recorded as open by the // store. openedAt time.Time // closedAt is the time the channel is recorded as closed by the // store. If the channel is still open, this value is zero. closedAt time.Time // channelFound is true if we expect to find the channel in the // store. channelFound bool // startTime specifies the beginning of the uptime range we want // to calculate. startTime time.Time // endTime specified the end of the uptime range we want to // calculate. endTime time.Time expectedUptime time.Duration expectedError error }{ { name: "No events", startTime: twoHoursAgo, endTime: testNow, channelFound: true, expectedError: nil, }, { name: "50% Uptime", events: []*channelEvent{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, }, { timestamp: twoHoursAgo, eventType: peerOfflineEvent, }, }, openedAt: fourHoursAgo, expectedUptime: time.Hour * 2, startTime: fourHoursAgo, endTime: testNow, channelFound: true, expectedError: nil, }, { name: "Zero start time", events: []*channelEvent{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, }, }, openedAt: fourHoursAgo, expectedUptime: time.Hour * 4, endTime: testNow, channelFound: true, expectedError: nil, }, { name: "Channel not found", startTime: twoHoursAgo, endTime: testNow, channelFound: false, expectedError: ErrChannelNotFound, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { ctx := newChanEventStoreTestCtx(t) // If we're supposed to find the channel for this test, // add events for it to the store. if test.channelFound { eventLog := &chanEventLog{ events: test.events, clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } ctx.store.channels[test.channelPoint] = eventLog } ctx.start() uptime, err := ctx.store.GetUptime( test.channelPoint, test.startTime, test.endTime, ) require.Equal(t, test.expectedError, err) require.Equal(t, test.expectedUptime, uptime) ctx.stop() }) } } // TestAddChannel tests that channels are added to the event store with // appropriate timestamps. This test addresses a bug where offline channels // did not have an opened time set, and checks that an online event is set for // peers that are online at the time that a channel is opened. func TestAddChannel(t *testing.T) { ctx := newChanEventStoreTestCtx(t) ctx.start() // Create a channel for a peer that is not online yet. _, _, channel1 := ctx.createChannel() // Get a set of values for another channel, but do not create it yet. // peer2, pubkey2, channel2 := ctx.newChannel() ctx.peerEvent(peer2, true) ctx.sendChannelOpenedUpdate(pubkey2, channel2) ctx.stop() // Assert that our peer that was offline on connection has no events // and our peer that was online on connection has one. require.Len(t, ctx.store.channels[channel1].events, 0) chan2Events := ctx.store.channels[channel2].events require.Len(t, chan2Events, 1) require.Equal(t, peerOnlineEvent, chan2Events[0].eventType) }