diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index c749cf1c..1125f373 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -5,6 +5,7 @@ import ( "time" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/routing/route" ) @@ -46,9 +47,8 @@ type chanEventLog struct { // events is a log of timestamped events observed for the channel. events []*channelEvent - // now is expected to return the current time. It is supplied as an - // external function to enable deterministic unit tests. - now func() time.Time + // clock allows creation of deterministic unit tests. + clock clock.Clock // openedAt tracks the first time this channel was seen. This is not // necessarily the time that it confirmed on chain because channel @@ -62,13 +62,13 @@ type chanEventLog struct { // newEventLog creates an event log for a channel with the openedAt time set. func newEventLog(channelPoint wire.OutPoint, peer route.Vertex, - now func() time.Time) *chanEventLog { + clock clock.Clock) *chanEventLog { eventlog := &chanEventLog{ channelPoint: channelPoint, peer: peer, - now: now, - openedAt: now(), + clock: clock, + openedAt: clock.Now(), } return eventlog @@ -76,7 +76,7 @@ func newEventLog(channelPoint wire.OutPoint, peer route.Vertex, // close sets the closing time for an event log. func (e *chanEventLog) close() { - e.closedAt = e.now() + e.closedAt = e.clock.Now() } // add appends an event with the given type and current time to the event log. @@ -91,7 +91,7 @@ func (e *chanEventLog) add(eventType eventType) { // Add the event to the eventLog with the current timestamp. event := &channelEvent{ - timestamp: e.now(), + timestamp: e.clock.Now(), eventType: eventType, } e.events = append(e.events, event) @@ -165,7 +165,7 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // closure. It it is still open, we calculate it until the present. endTime := e.closedAt if endTime.IsZero() { - endTime = e.now() + endTime = e.clock.Now() } // Add the final online period to the set and return. diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 1fec1d95..457ae80b 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -3,6 +3,8 @@ package chanfitness import ( "testing" "time" + + "github.com/lightningnetwork/lnd/clock" ) // TestAdd tests adding events to an event log. It tests the case where the @@ -18,7 +20,7 @@ func TestAdd(t *testing.T) { { name: "Channel open", eventLog: &chanEventLog{ - now: time.Now, + clock: clock.NewTestClock(testNow), }, event: peerOnlineEvent, expected: []eventType{peerOnlineEvent}, @@ -26,7 +28,7 @@ func TestAdd(t *testing.T) { { name: "Channel closed, event not added", eventLog: &chanEventLog{ - now: time.Now, + clock: clock.NewTestClock(testNow), }, event: peerOnlineEvent, expected: []eventType{}, @@ -55,13 +57,10 @@ func TestAdd(t *testing.T) { // where no events present, and the case where an additional online period // must be added because the event log ends on an online event. func TestGetOnlinePeriod(t *testing.T) { - // Set time for consistent testing. - now := time.Now() - - fourHoursAgo := now.Add(time.Hour * -4) - threeHoursAgo := now.Add(time.Hour * -3) - twoHoursAgo := now.Add(time.Hour * -2) - oneHourAgo := now.Add(time.Hour * -1) + fourHoursAgo := testNow.Add(time.Hour * -4) + threeHoursAgo := testNow.Add(time.Hour * -3) + twoHoursAgo := testNow.Add(time.Hour * -2) + oneHourAgo := testNow.Add(time.Hour * -1) tests := []struct { name string @@ -112,7 +111,7 @@ func TestGetOnlinePeriod(t *testing.T) { expectedOnline: []*onlinePeriod{ { start: fourHoursAgo, - end: now, + end: testNow, }, }, }, @@ -139,10 +138,8 @@ func TestGetOnlinePeriod(t *testing.T) { t.Run(test.name, func(t *testing.T) { score := &chanEventLog{ - events: test.events, - now: func() time.Time { - return now - }, + events: test.events, + clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } @@ -172,13 +169,10 @@ func TestGetOnlinePeriod(t *testing.T) { // TestUptime tests channel uptime calculation based on its event log. func TestUptime(t *testing.T) { - // Set time for consistent testing. - now := time.Now() - - fourHoursAgo := now.Add(time.Hour * -4) - threeHoursAgo := now.Add(time.Hour * -3) - twoHoursAgo := now.Add(time.Hour * -2) - oneHourAgo := now.Add(time.Hour * -1) + fourHoursAgo := testNow.Add(time.Hour * -4) + threeHoursAgo := testNow.Add(time.Hour * -3) + twoHoursAgo := testNow.Add(time.Hour * -2) + oneHourAgo := testNow.Add(time.Hour * -1) tests := []struct { name string @@ -216,7 +210,7 @@ func TestUptime(t *testing.T) { { name: "End before start", endTime: threeHoursAgo, - startTime: now, + startTime: testNow, expectErr: true, }, { @@ -234,7 +228,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour * 3, }, { @@ -247,7 +241,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour * 4, }, { @@ -261,7 +255,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, }, { name: "Online event before close", @@ -274,7 +268,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour, }, { @@ -292,7 +286,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour, }, { @@ -306,7 +300,7 @@ func TestUptime(t *testing.T) { }, }, startTime: twoHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour, }, { @@ -318,12 +312,12 @@ func TestUptime(t *testing.T) { eventType: peerOnlineEvent, }, { - timestamp: now.Add(time.Hour), + timestamp: testNow.Add(time.Hour), eventType: peerOfflineEvent, }, }, startTime: twoHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour * 2, }, { @@ -341,30 +335,30 @@ func TestUptime(t *testing.T) { }, { name: "Multiple online and offline", - openedAt: now.Add(time.Hour * -8), + openedAt: testNow.Add(time.Hour * -8), events: []*channelEvent{ { - timestamp: now.Add(time.Hour * -7), + timestamp: testNow.Add(time.Hour * -7), eventType: peerOnlineEvent, }, { - timestamp: now.Add(time.Hour * -6), + timestamp: testNow.Add(time.Hour * -6), eventType: peerOfflineEvent, }, { - timestamp: now.Add(time.Hour * -5), + timestamp: testNow.Add(time.Hour * -5), eventType: peerOnlineEvent, }, { - timestamp: now.Add(time.Hour * -4), + timestamp: testNow.Add(time.Hour * -4), eventType: peerOfflineEvent, }, { - timestamp: now.Add(time.Hour * -3), + timestamp: testNow.Add(time.Hour * -3), eventType: peerOnlineEvent, }, }, - startTime: now.Add(time.Hour * -8), + startTime: testNow.Add(time.Hour * -8), endTime: oneHourAgo, expectedUptime: time.Hour * 4, }, @@ -375,10 +369,8 @@ func TestUptime(t *testing.T) { t.Run(test.name, func(t *testing.T) { score := &chanEventLog{ - events: test.events, - now: func() time.Time { - return now - }, + events: test.events, + clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index 3bf7c324..6ff73ccf 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -18,6 +18,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" @@ -72,6 +73,10 @@ type Config struct { // used to populate the ChannelEventStore with a set of channels on // startup. GetOpenChannels func() ([]*channeldb.OpenChannel, error) + + // Clock is the time source that the subsystem uses, provided here + // for ease of testing. + Clock clock.Clock } // lifespanRequest contains the channel ID required to query the store for a @@ -212,7 +217,7 @@ func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, } // Create an event log for the channel. - eventLog := newEventLog(channelPoint, peer, time.Now) + eventLog := newEventLog(channelPoint, peer, c.cfg.Clock) // If the peer is already online, add a peer online event to record // the starting state of the peer. diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index df7b34d3..206ec8ca 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -9,11 +9,15 @@ import ( "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. @@ -57,10 +61,13 @@ func TestStartStoreError(t *testing.T) { 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() @@ -182,8 +189,6 @@ func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), // TestGetLifetime tests the GetLifetime function for the cases where a channel // is known and unknown to the store. func TestGetLifetime(t *testing.T) { - now := time.Now() - tests := []struct { name string channelFound bool @@ -195,8 +200,8 @@ func TestGetLifetime(t *testing.T) { { name: "Channel found", channelFound: true, - opened: now, - closed: now.Add(time.Hour * -1), + opened: testNow, + closed: testNow.Add(time.Hour * -1), expectedError: nil, }, { @@ -240,11 +245,8 @@ func TestGetLifetime(t *testing.T) { // 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) { - // Set time for deterministic unit tests. - now := time.Now() - - twoHoursAgo := now.Add(time.Hour * -2) - fourHoursAgo := now.Add(time.Hour * -4) + twoHoursAgo := testNow.Add(time.Hour * -2) + fourHoursAgo := testNow.Add(time.Hour * -4) tests := []struct { name string @@ -282,7 +284,7 @@ func TestGetUptime(t *testing.T) { { name: "No events", startTime: twoHoursAgo, - endTime: now, + endTime: testNow, channelFound: true, expectedError: nil, }, @@ -301,7 +303,7 @@ func TestGetUptime(t *testing.T) { openedAt: fourHoursAgo, expectedUptime: time.Hour * 2, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, channelFound: true, expectedError: nil, }, @@ -315,14 +317,14 @@ func TestGetUptime(t *testing.T) { }, openedAt: fourHoursAgo, expectedUptime: time.Hour * 4, - endTime: now, + endTime: testNow, channelFound: true, expectedError: nil, }, { name: "Channel not found", startTime: twoHoursAgo, - endTime: now, + endTime: testNow, channelFound: false, expectedError: ErrChannelNotFound, }, @@ -339,7 +341,7 @@ func TestGetUptime(t *testing.T) { if test.channelFound { eventLog := &chanEventLog{ events: test.events, - now: func() time.Time { return now }, + clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } diff --git a/chanfitness/chaneventstore_testctx_test.go b/chanfitness/chaneventstore_testctx_test.go index 86ff43a2..6a6b8fd7 100644 --- a/chanfitness/chaneventstore_testctx_test.go +++ b/chanfitness/chaneventstore_testctx_test.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" @@ -34,6 +35,9 @@ type chanEventStoreTestCtx struct { // for a single pubkey + channel combination because its actual value // does not matter. testVarIdx int + + // clock is the clock that our test store will use. + clock *clock.TestClock } // newChanEventStoreTestCtx creates a test context which can be used to test @@ -43,9 +47,11 @@ func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { t: t, channelSubscription: newMockSubscription(t), peerSubscription: newMockSubscription(t), + clock: clock.NewTestClock(testNow), } cfg := &Config{ + Clock: testCtx.clock, SubscribeChannelEvents: func() (subscribe.Subscription, error) { return testCtx.channelSubscription, nil }, diff --git a/server.go b/server.go index 91bbf211..d737a096 100644 --- a/server.go +++ b/server.go @@ -1211,6 +1211,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return s.peerNotifier.SubscribePeerEvents() }, GetOpenChannels: s.remoteChanDB.FetchAllOpenChannels, + Clock: clock.NewDefaultClock(), }) if cfg.WtClient.Active {