You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
14 KiB
564 lines
14 KiB
package chanfitness |
|
|
|
import ( |
|
"testing" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/lightningnetwork/lnd/clock" |
|
"github.com/stretchr/testify/require" |
|
) |
|
|
|
// TestPeerLog tests the functionality of the peer log struct. |
|
func TestPeerLog(t *testing.T) { |
|
clock := clock.NewTestClock(testNow) |
|
peerLog := newPeerLog(clock, 0, nil) |
|
|
|
// 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. 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, 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. 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) |
|
require.NoError(t, err) |
|
|
|
// Bump our test clock's time so that our current time is different to |
|
// channel open time. |
|
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. |
|
lifetime, uptime, err := peerLog.channelUptime(chan1) |
|
require.NoError(t, err) |
|
require.Equal(t, time.Hour, lifetime) |
|
require.Equal(t, time.Hour, uptime) |
|
|
|
// 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. |
|
chan2 := wire.OutPoint{ |
|
Index: 2, |
|
} |
|
require.NoError(t, peerLog.addChannel(chan2)) |
|
require.Equal(t, 2, peerLog.channelCount()) |
|
|
|
// Progress our time again, so that our peer has now been offline for |
|
// two hours. |
|
now := lastFlap.Add(time.Hour * 2) |
|
clock.SetTime(now) |
|
|
|
// Our first channel should report as having been monitored for three |
|
// hours, but only online for one of those hours. |
|
lifetime, uptime, err = peerLog.channelUptime(chan1) |
|
require.NoError(t, err) |
|
require.Equal(t, time.Hour*3, lifetime) |
|
require.Equal(t, time.Hour, uptime) |
|
|
|
// Remove our first channel and check that we can still correctly query |
|
// uptime for the second channel. |
|
require.NoError(t, peerLog.removeChannel(chan1)) |
|
require.Equal(t, 1, peerLog.channelCount()) |
|
|
|
// Our second channel, which was created when our peer was offline, |
|
// should report as having been monitored for two hours, but have zero |
|
// uptime. |
|
lifetime, uptime, err = peerLog.channelUptime(chan2) |
|
require.NoError(t, err) |
|
require.Equal(t, time.Hour*2, lifetime) |
|
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 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) |
|
|
|
require.Len(t, peerLog.listEvents(), 0) |
|
require.Nil(t, peerLog.stagedEvent) |
|
} |
|
|
|
// TestRateLimitAdd tests the addition of events to the event log with rate |
|
// limiting in place. |
|
func TestRateLimitAdd(t *testing.T) { |
|
// Create a mock clock specifically for this test so that we can |
|
// progress time without affecting the other tests. |
|
mockedClock := clock.NewTestClock(testNow) |
|
|
|
// Create a new peer log. |
|
peerLog := newPeerLog(mockedClock, 0, nil) |
|
require.Nil(t, peerLog.stagedEvent) |
|
|
|
// Create a channel for our peer log, otherwise it will not track online |
|
// events. |
|
require.NoError(t, peerLog.addChannel(wire.OutPoint{})) |
|
|
|
// First, we add an event to the event log. Since we have no previous |
|
// events, we expect this event to staged immediately. |
|
peerEvent := &event{ |
|
timestamp: testNow, |
|
eventType: peerOfflineEvent, |
|
} |
|
|
|
peerLog.onlineEvent(false) |
|
require.Equal(t, peerEvent, peerLog.stagedEvent) |
|
|
|
// We immediately add another event to our event log. We expect our |
|
// staged event to be replaced with this new event, because insufficient |
|
// time has passed since our last event. |
|
peerEvent = &event{ |
|
timestamp: testNow, |
|
eventType: peerOnlineEvent, |
|
} |
|
|
|
peerLog.onlineEvent(true) |
|
require.Equal(t, peerEvent, peerLog.stagedEvent) |
|
|
|
// We get the amount of time that we need to pass before we record an |
|
// event from our rate limiting tiers. We then progress our test clock |
|
// to just after this point. |
|
delta := getRateLimit(peerLog.flapCount) |
|
newNow := testNow.Add(delta + 1) |
|
mockedClock.SetTime(newNow) |
|
|
|
// Now, when we add an event, we expect our staged event to be added |
|
// to our events list and for our new event to be staged. |
|
newEvent := &event{ |
|
timestamp: newNow, |
|
eventType: peerOfflineEvent, |
|
} |
|
peerLog.onlineEvent(false) |
|
|
|
require.Equal(t, []*event{peerEvent}, peerLog.onlineEvents) |
|
require.Equal(t, newEvent, peerLog.stagedEvent) |
|
|
|
// Now, we test the case where we add many events to our log. We expect |
|
// our set of events to be untouched, but for our staged event to be |
|
// updated. |
|
nextEvent := &event{ |
|
timestamp: newNow, |
|
eventType: peerOnlineEvent, |
|
} |
|
|
|
for i := 0; i < 5; i++ { |
|
// We flip the kind of event for each type so that we can check |
|
// that our staged event is definitely changing each time. |
|
if i%2 == 0 { |
|
nextEvent.eventType = peerOfflineEvent |
|
} else { |
|
nextEvent.eventType = peerOnlineEvent |
|
} |
|
|
|
online := nextEvent.eventType == peerOnlineEvent |
|
|
|
peerLog.onlineEvent(online) |
|
require.Equal(t, []*event{peerEvent}, peerLog.onlineEvents) |
|
require.Equal(t, nextEvent, peerLog.stagedEvent) |
|
} |
|
|
|
// Now, we test the case where a peer's flap count is cooled down |
|
// because it has not flapped for a while. Set our peer's flap count so |
|
// that we fall within our second rate limiting tier and assert that we |
|
// are at this level. |
|
peerLog.flapCount = rateLimitScale + 1 |
|
rateLimit := getRateLimit(peerLog.flapCount) |
|
require.Equal(t, rateLimits[1], rateLimit) |
|
|
|
// Progress our clock to the point where we will have our flap count |
|
// cooled. |
|
newNow = mockedClock.Now().Add(flapCountCooldownPeriod) |
|
mockedClock.SetTime(newNow) |
|
|
|
// Add an online event, and expect it to be staged. |
|
onlineEvent := &event{ |
|
timestamp: newNow, |
|
eventType: peerOnlineEvent, |
|
} |
|
peerLog.onlineEvent(true) |
|
require.Equal(t, onlineEvent, peerLog.stagedEvent) |
|
|
|
// Progress our clock by the rate limit level that we will be on if |
|
// our flap rate is cooled down to a lower level. |
|
newNow = mockedClock.Now().Add(rateLimits[0] + 1) |
|
mockedClock.SetTime(newNow) |
|
|
|
// Add another event. We expect this event to be staged and our previous |
|
// event to be flushed to the event log (because our cooldown has been |
|
// applied). |
|
offlineEvent := &event{ |
|
timestamp: newNow, |
|
eventType: peerOfflineEvent, |
|
} |
|
peerLog.onlineEvent(false) |
|
require.Equal(t, offlineEvent, peerLog.stagedEvent) |
|
|
|
flushedEventIdx := len(peerLog.onlineEvents) - 1 |
|
require.Equal( |
|
t, onlineEvent, peerLog.onlineEvents[flushedEventIdx], |
|
) |
|
} |
|
|
|
// TestGetOnlinePeriod tests the getOnlinePeriod function. It tests the case |
|
// 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) { |
|
fourHoursAgo := testNow.Add(time.Hour * -4) |
|
threeHoursAgo := testNow.Add(time.Hour * -3) |
|
twoHoursAgo := testNow.Add(time.Hour * -2) |
|
|
|
tests := []struct { |
|
name string |
|
events []*event |
|
expectedOnline []*onlinePeriod |
|
}{ |
|
{ |
|
name: "no events", |
|
}, |
|
{ |
|
name: "start on online period", |
|
events: []*event{ |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: twoHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
}, |
|
expectedOnline: []*onlinePeriod{ |
|
{ |
|
start: threeHoursAgo, |
|
end: twoHoursAgo, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "start on offline period", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "end on an online period", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
expectedOnline: []*onlinePeriod{ |
|
{ |
|
start: fourHoursAgo, |
|
end: testNow, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "duplicate online events", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
expectedOnline: []*onlinePeriod{ |
|
{ |
|
start: fourHoursAgo, |
|
end: testNow, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "duplicate offline events", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
}, |
|
expectedOnline: nil, |
|
}, |
|
{ |
|
name: "duplicate online then offline", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: twoHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
}, |
|
expectedOnline: []*onlinePeriod{ |
|
{ |
|
start: fourHoursAgo, |
|
end: twoHoursAgo, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "duplicate offline then online", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
{ |
|
timestamp: twoHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
expectedOnline: []*onlinePeriod{ |
|
{ |
|
start: twoHoursAgo, |
|
end: testNow, |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
for _, test := range tests { |
|
test := test |
|
|
|
t.Run(test.name, func(t *testing.T) { |
|
t.Parallel() |
|
|
|
score := &peerLog{ |
|
onlineEvents: test.events, |
|
clock: clock.NewTestClock(testNow), |
|
} |
|
|
|
online := score.getOnlinePeriods() |
|
|
|
require.Equal(t, test.expectedOnline, online) |
|
}) |
|
|
|
} |
|
} |
|
|
|
// TestUptime tests channel uptime calculation based on its event log. |
|
func TestUptime(t *testing.T) { |
|
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 |
|
|
|
// events is the set of event log that we are calculating uptime |
|
// for. |
|
events []*event |
|
|
|
// startTime is the beginning of the period that we are |
|
// calculating uptime for, it cannot have a zero value. |
|
startTime time.Time |
|
|
|
// endTime is the end of the period that we are calculating |
|
// uptime for, it cannot have a zero value. |
|
endTime time.Time |
|
|
|
// expectedUptime is the amount of uptime we expect to be |
|
// calculated over the period specified by startTime and |
|
// endTime. |
|
expectedUptime time.Duration |
|
|
|
// expectErr is set to true if we expect an error to be returned |
|
// when calling the uptime function. |
|
expectErr bool |
|
}{ |
|
{ |
|
name: "End before start", |
|
endTime: threeHoursAgo, |
|
startTime: testNow, |
|
expectErr: true, |
|
}, |
|
{ |
|
name: "Zero end time", |
|
expectErr: true, |
|
}, |
|
{ |
|
name: "online event and no offline", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
startTime: fourHoursAgo, |
|
endTime: testNow, |
|
expectedUptime: time.Hour * 4, |
|
}, |
|
{ |
|
name: "online then offline event", |
|
events: []*event{ |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: twoHoursAgo, |
|
eventType: peerOfflineEvent, |
|
}, |
|
}, |
|
startTime: fourHoursAgo, |
|
endTime: testNow, |
|
expectedUptime: time.Hour, |
|
}, |
|
{ |
|
name: "online event before uptime period", |
|
events: []*event{ |
|
{ |
|
timestamp: threeHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
startTime: twoHoursAgo, |
|
endTime: testNow, |
|
expectedUptime: time.Hour * 2, |
|
}, |
|
{ |
|
name: "offline event after uptime period", |
|
events: []*event{ |
|
{ |
|
timestamp: fourHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: testNow.Add(time.Hour), |
|
eventType: peerOfflineEvent, |
|
}, |
|
}, |
|
startTime: twoHoursAgo, |
|
endTime: testNow, |
|
expectedUptime: time.Hour * 2, |
|
}, |
|
{ |
|
name: "all events within period", |
|
events: []*event{ |
|
{ |
|
timestamp: twoHoursAgo, |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
startTime: threeHoursAgo, |
|
endTime: oneHourAgo, |
|
expectedUptime: time.Hour, |
|
}, |
|
{ |
|
name: "multiple online and offline", |
|
events: []*event{ |
|
{ |
|
timestamp: testNow.Add(time.Hour * -7), |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: testNow.Add(time.Hour * -6), |
|
eventType: peerOfflineEvent, |
|
}, |
|
{ |
|
timestamp: testNow.Add(time.Hour * -5), |
|
eventType: peerOnlineEvent, |
|
}, |
|
{ |
|
timestamp: testNow.Add(time.Hour * -4), |
|
eventType: peerOfflineEvent, |
|
}, |
|
{ |
|
timestamp: testNow.Add(time.Hour * -3), |
|
eventType: peerOnlineEvent, |
|
}, |
|
}, |
|
startTime: testNow.Add(time.Hour * -8), |
|
endTime: oneHourAgo, |
|
expectedUptime: time.Hour * 4, |
|
}, |
|
} |
|
|
|
for _, test := range tests { |
|
test := test |
|
|
|
t.Run(test.name, func(t *testing.T) { |
|
score := &peerLog{ |
|
onlineEvents: test.events, |
|
clock: clock.NewTestClock(testNow), |
|
} |
|
|
|
uptime, err := score.uptime( |
|
test.startTime, test.endTime, |
|
) |
|
require.Equal(t, test.expectErr, err != nil) |
|
require.Equal(t, test.expectedUptime, uptime) |
|
}) |
|
} |
|
}
|
|
|