463 lines
12 KiB
Go
463 lines
12 KiB
Go
|
package chanfitness
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/btcsuite/btcd/btcec"
|
||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||
|
"github.com/lightningnetwork/lnd/peernotifier"
|
||
|
"github.com/lightningnetwork/lnd/routing/route"
|
||
|
"github.com/lightningnetwork/lnd/subscribe"
|
||
|
)
|
||
|
|
||
|
// 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.Client, error) {
|
||
|
return &subscribe.Client{
|
||
|
Cancel: func() {},
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
errSubscribeFunc := func() (client *subscribe.Client, e error) {
|
||
|
return nil, errors.New("intentional test err")
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
ChannelEvents func() (*subscribe.Client, error)
|
||
|
PeerEvents func() (*subscribe.Client, 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() (channels []*channeldb.OpenChannel, e error) {
|
||
|
return nil, errors.New("intentional test err")
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
test := test
|
||
|
|
||
|
t.Run(test.name, func(t *testing.T) {
|
||
|
store := NewChannelEventStore(&Config{
|
||
|
SubscribeChannelEvents: test.ChannelEvents,
|
||
|
SubscribePeerEvents: test.PeerEvents,
|
||
|
GetOpenChannels: test.GetChannels,
|
||
|
})
|
||
|
|
||
|
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) {
|
||
|
privKey, err := btcec.NewPrivateKey(btcec.S256())
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error getting pubkey: %v", err)
|
||
|
}
|
||
|
|
||
|
pubKey, err := route.NewVertexFromBytes(
|
||
|
privKey.PubKey().SerializeCompressed(),
|
||
|
)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Could not create vertex: %v", err)
|
||
|
}
|
||
|
|
||
|
shortID := lnwire.ShortChannelID{
|
||
|
BlockHeight: 1234,
|
||
|
TxIndex: 2,
|
||
|
TxPosition: 2,
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
|
||
|
// generateEvents takes channels which represent the updates channels
|
||
|
// for subscription clients and passes events in the desired order.
|
||
|
// This function is intended to be blocking so that the test does not
|
||
|
// have a data race with event consumption, so the channels should not
|
||
|
// be buffered.
|
||
|
generateEvents func(channelEvents, peerEvents chan<- interface{})
|
||
|
|
||
|
// expectedEvents is the expected set of event types in the store.
|
||
|
expectedEvents []eventType
|
||
|
}{
|
||
|
{
|
||
|
name: "Channel opened, peer comes online",
|
||
|
generateEvents: func(channelEvents, peerEvents chan<- interface{}) {
|
||
|
// Add an open channel event
|
||
|
channelEvents <- channelnotifier.OpenChannelEvent{
|
||
|
Channel: &channeldb.OpenChannel{
|
||
|
ShortChannelID: shortID,
|
||
|
IdentityPub: privKey.PubKey(),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Add a peer online event.
|
||
|
peerEvents <- peernotifier.PeerOnlineEvent{PubKey: pubKey}
|
||
|
},
|
||
|
expectedEvents: []eventType{peerOnlineEvent},
|
||
|
},
|
||
|
{
|
||
|
name: "Duplicate channel open events",
|
||
|
generateEvents: func(channelEvents, peerEvents chan<- interface{}) {
|
||
|
// Add an open channel event
|
||
|
channelEvents <- channelnotifier.OpenChannelEvent{
|
||
|
Channel: &channeldb.OpenChannel{
|
||
|
ShortChannelID: shortID,
|
||
|
IdentityPub: privKey.PubKey(),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Add a peer online event.
|
||
|
peerEvents <- peernotifier.PeerOnlineEvent{PubKey: pubKey}
|
||
|
|
||
|
// Add a duplicate channel open event.
|
||
|
channelEvents <- channelnotifier.OpenChannelEvent{
|
||
|
Channel: &channeldb.OpenChannel{
|
||
|
ShortChannelID: shortID,
|
||
|
IdentityPub: privKey.PubKey(),
|
||
|
},
|
||
|
}
|
||
|
},
|
||
|
expectedEvents: []eventType{peerOnlineEvent},
|
||
|
},
|
||
|
{
|
||
|
name: "Channel opened, peer already online",
|
||
|
generateEvents: func(channelEvents, peerEvents chan<- interface{}) {
|
||
|
// Add a peer online event.
|
||
|
peerEvents <- peernotifier.PeerOnlineEvent{PubKey: pubKey}
|
||
|
|
||
|
// Add an open channel event
|
||
|
channelEvents <- channelnotifier.OpenChannelEvent{
|
||
|
Channel: &channeldb.OpenChannel{
|
||
|
ShortChannelID: shortID,
|
||
|
IdentityPub: privKey.PubKey(),
|
||
|
},
|
||
|
}
|
||
|
},
|
||
|
expectedEvents: []eventType{peerOnlineEvent},
|
||
|
},
|
||
|
|
||
|
{
|
||
|
name: "Channel opened, peer offline, closed",
|
||
|
generateEvents: func(channelEvents, peerEvents chan<- interface{}) {
|
||
|
// Add an open channel event
|
||
|
channelEvents <- channelnotifier.OpenChannelEvent{
|
||
|
Channel: &channeldb.OpenChannel{
|
||
|
ShortChannelID: shortID,
|
||
|
IdentityPub: privKey.PubKey(),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Add a peer online event.
|
||
|
peerEvents <- peernotifier.PeerOfflineEvent{PubKey: pubKey}
|
||
|
|
||
|
// Add a close channel event.
|
||
|
channelEvents <- channelnotifier.ClosedChannelEvent{
|
||
|
CloseSummary: &channeldb.ChannelCloseSummary{
|
||
|
ShortChanID: shortID,
|
||
|
},
|
||
|
}
|
||
|
},
|
||
|
expectedEvents: []eventType{peerOfflineEvent},
|
||
|
},
|
||
|
{
|
||
|
name: "Event after channel close not recorded",
|
||
|
generateEvents: func(channelEvents, peerEvents chan<- interface{}) {
|
||
|
// Add an open channel event
|
||
|
channelEvents <- channelnotifier.OpenChannelEvent{
|
||
|
Channel: &channeldb.OpenChannel{
|
||
|
ShortChannelID: shortID,
|
||
|
IdentityPub: privKey.PubKey(),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Add a close channel event.
|
||
|
channelEvents <- channelnotifier.ClosedChannelEvent{
|
||
|
CloseSummary: &channeldb.ChannelCloseSummary{
|
||
|
ShortChanID: shortID,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Add a peer online event.
|
||
|
peerEvents <- peernotifier.PeerOfflineEvent{PubKey: pubKey}
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
test := test
|
||
|
|
||
|
t.Run(test.name, func(t *testing.T) {
|
||
|
// Create a store with the channels and online peers specified
|
||
|
// by the test.
|
||
|
store := NewChannelEventStore(&Config{})
|
||
|
|
||
|
// Create channels which represent the subscriptions we have to peer
|
||
|
// and client events.
|
||
|
channelEvents := make(chan interface{})
|
||
|
peerEvents := make(chan interface{})
|
||
|
|
||
|
store.wg.Add(1)
|
||
|
go store.consume(&subscriptions{
|
||
|
channelUpdates: channelEvents,
|
||
|
peerUpdates: peerEvents,
|
||
|
cancel: func() {},
|
||
|
})
|
||
|
|
||
|
// Add events to the store then kill the goroutine using store.Stop.
|
||
|
test.generateEvents(channelEvents, peerEvents)
|
||
|
store.Stop()
|
||
|
|
||
|
// Retrieve the eventLog for the channel and check that its
|
||
|
// contents are as expected.
|
||
|
eventLog, ok := store.channels[shortID.ToUint64()]
|
||
|
if !ok {
|
||
|
t.Fatalf("Expected to find event store")
|
||
|
}
|
||
|
|
||
|
for i, e := range eventLog.events {
|
||
|
if test.expectedEvents[i] != e.eventType {
|
||
|
t.Fatalf("Expected type: %v, got: %v",
|
||
|
test.expectedEvents[i], e.eventType)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
chanID uint64
|
||
|
opened time.Time
|
||
|
closed time.Time
|
||
|
expectErr bool
|
||
|
}{
|
||
|
{
|
||
|
name: "Channel found",
|
||
|
channelFound: true,
|
||
|
opened: now,
|
||
|
closed: now.Add(time.Hour * -1),
|
||
|
expectErr: false,
|
||
|
},
|
||
|
{
|
||
|
name: "Channel not found",
|
||
|
expectErr: true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
test := test
|
||
|
|
||
|
t.Run(test.name, func(t *testing.T) {
|
||
|
// Create and empty events store for testing.
|
||
|
store := NewChannelEventStore(&Config{})
|
||
|
|
||
|
// Start goroutine which consumes GetLifespan requests.
|
||
|
store.wg.Add(1)
|
||
|
go store.consume(&subscriptions{
|
||
|
channelUpdates: make(chan interface{}),
|
||
|
peerUpdates: make(chan interface{}),
|
||
|
cancel: func() {},
|
||
|
})
|
||
|
|
||
|
// Stop the store's go routine.
|
||
|
defer store.Stop()
|
||
|
|
||
|
// Add channel to eventStore if the test indicates that it should
|
||
|
// be present.
|
||
|
if test.channelFound {
|
||
|
store.channels[test.chanID] = &chanEventLog{
|
||
|
openedAt: test.opened,
|
||
|
closedAt: test.closed,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
open, close, err := store.GetLifespan(test.chanID)
|
||
|
if test.expectErr && err == nil {
|
||
|
t.Fatal("Expected an error, got nil")
|
||
|
}
|
||
|
if !test.expectErr && err != nil {
|
||
|
t.Fatalf("Expected no error, got: %v", err)
|
||
|
}
|
||
|
|
||
|
if open != test.opened {
|
||
|
t.Errorf("Expected: %v, got %v", test.opened, open)
|
||
|
}
|
||
|
|
||
|
if close != test.closed {
|
||
|
t.Errorf("Expected: %v, got %v", test.closed, close)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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) {
|
||
|
// Set time for deterministic unit tests.
|
||
|
now := time.Now()
|
||
|
|
||
|
twoHoursAgo := now.Add(time.Hour * -2)
|
||
|
fourHoursAgo := now.Add(time.Hour * -4)
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
|
||
|
chanID uint64
|
||
|
|
||
|
// 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
|
||
|
expectErr bool
|
||
|
}{
|
||
|
{
|
||
|
name: "No events",
|
||
|
startTime: twoHoursAgo,
|
||
|
endTime: now,
|
||
|
channelFound: true,
|
||
|
},
|
||
|
{
|
||
|
name: "50% Uptime",
|
||
|
events: []*channelEvent{
|
||
|
{
|
||
|
timestamp: fourHoursAgo,
|
||
|
eventType: peerOnlineEvent,
|
||
|
},
|
||
|
{
|
||
|
timestamp: twoHoursAgo,
|
||
|
eventType: peerOfflineEvent,
|
||
|
},
|
||
|
},
|
||
|
openedAt: fourHoursAgo,
|
||
|
expectedUptime: time.Hour * 2,
|
||
|
startTime: fourHoursAgo,
|
||
|
endTime: now,
|
||
|
channelFound: true,
|
||
|
},
|
||
|
{
|
||
|
name: "Zero start time",
|
||
|
events: []*channelEvent{
|
||
|
{
|
||
|
timestamp: fourHoursAgo,
|
||
|
eventType: peerOnlineEvent,
|
||
|
},
|
||
|
},
|
||
|
openedAt: fourHoursAgo,
|
||
|
expectedUptime: time.Hour * 4,
|
||
|
endTime: now,
|
||
|
channelFound: true,
|
||
|
},
|
||
|
{
|
||
|
name: "Channel not found",
|
||
|
startTime: twoHoursAgo,
|
||
|
endTime: now,
|
||
|
channelFound: false,
|
||
|
expectErr: true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, test := range tests {
|
||
|
test := test
|
||
|
|
||
|
t.Run(test.name, func(t *testing.T) {
|
||
|
// Set up event store with the events specified for the test and
|
||
|
// mocked time.
|
||
|
store := NewChannelEventStore(&Config{})
|
||
|
|
||
|
// Start goroutine which consumes GetUptime requests.
|
||
|
store.wg.Add(1)
|
||
|
go store.consume(&subscriptions{
|
||
|
channelUpdates: make(chan interface{}),
|
||
|
peerUpdates: make(chan interface{}),
|
||
|
cancel: func() {},
|
||
|
})
|
||
|
|
||
|
// Stop the store's goroutine.
|
||
|
defer store.Stop()
|
||
|
|
||
|
// Add the channel to the store if it is intended to be found.
|
||
|
if test.channelFound {
|
||
|
store.channels[test.chanID] = &chanEventLog{
|
||
|
events: test.events,
|
||
|
now: func() time.Time { return now },
|
||
|
openedAt: test.openedAt,
|
||
|
closedAt: test.closedAt,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uptime, err := store.GetUptime(test.chanID, test.startTime, test.endTime)
|
||
|
if test.expectErr && err == nil {
|
||
|
t.Fatal("Expected an error, got nil")
|
||
|
}
|
||
|
if !test.expectErr && err != nil {
|
||
|
t.Fatalf("Expcted no error, got: %v", err)
|
||
|
}
|
||
|
|
||
|
if uptime != test.expectedUptime {
|
||
|
t.Fatalf("Expected uptime percentage: %v, got %v",
|
||
|
test.expectedUptime, uptime)
|
||
|
}
|
||
|
|
||
|
})
|
||
|
}
|
||
|
}
|