diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 4fb6b142..30e856ee 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -46,6 +46,14 @@ type peerLog struct { // onlineEvents is a log of timestamped events observed for the peer. onlineEvents []*event + // flapCount is the number of times this peer has been observed as + // going offline. + flapCount int + + // lastFlap is the timestamp of the last flap we recorded for the peer. + // This value will be nil if we have never recorded a flap for the peer. + lastFlap *time.Time + // clock allows creation of deterministic unit tests. clock clock.Clock @@ -76,8 +84,15 @@ func newChannelInfo(openedAt time.Time) *channelInfo { } } -// onlineEvent records a peer online or offline event in the log. +// onlineEvent records a peer online or offline event in the log and increments +// the peer's flap count. func (p *peerLog) onlineEvent(online bool) { + eventTime := p.clock.Now() + + // Record flap count information and online state regardless of whether + // we have any channels open with this peer. + p.flapCount++ + p.lastFlap = &eventTime p.online = online // If we have no channels currently open with the peer, we do not want @@ -87,7 +102,7 @@ func (p *peerLog) onlineEvent(online bool) { return } - p.addEvent(online, p.clock.Now()) + p.addEvent(online, eventTime) } // addEvent records an online or offline event in our event log. @@ -176,6 +191,12 @@ func (p *peerLog) channelUptime(channelPoint wire.OutPoint) (time.Duration, return now.Sub(channel.openedAt), uptime, nil } +// getFlapCount returns the peer's flap count and the timestamp that we last +// recorded a flap. +func (p *peerLog) getFlapCount() (int, *time.Time) { + return p.flapCount, p.lastFlap +} + // onlinePeriod represents a period of time over which a peer was online. type onlinePeriod struct { start, end time.Time diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index c4dd97a8..7d75841d 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -14,29 +14,53 @@ func TestPeerLog(t *testing.T) { clock := clock.NewTestClock(testNow) peerLog := newPeerLog(clock) + // assertFlapCount is a helper that asserts that our peer's flap count + // and timestamp is set to expected values. + assertFlapCount := func(expectedCount int, expectedTs *time.Time) { + flapCount, flapTs := peerLog.getFlapCount() + require.Equal(t, expectedCount, flapCount) + require.Equal(t, expectedTs, flapTs) + } + require.Zero(t, peerLog.channelCount()) require.False(t, peerLog.online) + assertFlapCount(0, nil) // Test that looking up an unknown channel fails. _, _, err := peerLog.channelUptime(wire.OutPoint{Index: 1}) require.Error(t, err) + lastFlap := clock.Now() + // Add an offline event, since we have no channels, we do not expect - // to have any online periods recorded for our peer. + // to have any online periods recorded for our peer. However, we should + // increment our flap count for the peer. peerLog.onlineEvent(false) require.Len(t, peerLog.getOnlinePeriods(), 0) + assertFlapCount(1, &lastFlap) + + // Bump our test clock's time by an hour so that we can create an online + // event with a distinct time. + lastFlap = testNow.Add(time.Hour) + clock.SetTime(lastFlap) // Likewise, if we have an online event, nothing beyond the online state - // of our peer log should change. + // of our peer log should change, but our flap count should change. peerLog.onlineEvent(true) require.Len(t, peerLog.getOnlinePeriods(), 0) + assertFlapCount(2, &lastFlap) - // Add a channel and assert that we have one channel listed. + // Add a channel and assert that we have one channel listed. Since this + // is the first channel we track for the peer, we expect an online + // event to be added, however, our flap count should not change because + // this is not a new online event, we are just copying one into our log + // for our purposes. chan1 := wire.OutPoint{ Index: 1, } require.NoError(t, peerLog.addChannel(chan1)) require.Equal(t, 1, peerLog.channelCount()) + assertFlapCount(2, &lastFlap) // Assert that we can now successfully get our added channel. _, _, err = peerLog.channelUptime(chan1) @@ -44,8 +68,8 @@ func TestPeerLog(t *testing.T) { // Bump our test clock's time so that our current time is different to // channel open time. - now := testNow.Add(time.Hour) - clock.SetTime(now) + lastFlap = clock.Now().Add(time.Hour) + clock.SetTime(lastFlap) // Now that we have added a channel and an hour has passed, we expect // our uptime and lifetime to both equal an hour. @@ -54,8 +78,10 @@ func TestPeerLog(t *testing.T) { require.Equal(t, time.Hour, lifetime) require.Equal(t, time.Hour, uptime) - // Add an offline event for our peer. + // Add an offline event for our peer and assert that our flap count is + // incremented. peerLog.onlineEvent(false) + assertFlapCount(3, &lastFlap) // Now we add another channel to our store and assert that we now report // two channels for this peer. @@ -67,7 +93,7 @@ func TestPeerLog(t *testing.T) { // Progress our time again, so that our peer has now been offline for // two hours. - now = now.Add(time.Hour * 2) + now := lastFlap.Add(time.Hour * 2) clock.SetTime(now) // Our first channel should report as having been monitored for three @@ -91,10 +117,11 @@ func TestPeerLog(t *testing.T) { require.Equal(t, time.Duration(0), uptime) // Finally, remove our second channel and assert that our peer cleans - // up its in memory set of events. + // up its in memory set of events but keeps its flap count record. require.NoError(t, peerLog.removeChannel(chan2)) require.Equal(t, 0, peerLog.channelCount()) require.Len(t, peerLog.onlineEvents, 0) + assertFlapCount(3, &lastFlap) } // TestGetOnlinePeriod tests the getOnlinePeriod function. It tests the case diff --git a/chanfitness/interface.go b/chanfitness/interface.go index 2da9302c..22678d65 100644 --- a/chanfitness/interface.go +++ b/chanfitness/interface.go @@ -26,4 +26,9 @@ type peerMonitor interface { // the channel has been monitored for and its uptime over this period. channelUptime(channelPoint wire.OutPoint) (time.Duration, time.Duration, error) + + // getFlapCount returns the peer's flap count and the timestamp that we + // last recorded a flap, which may be nil if we have never recorded a + // flap for this peer. + getFlapCount() (int, *time.Time) }