lnd.xprv/chanfitness/chaneventstore_test.go

248 lines
7.2 KiB
Go
Raw Normal View History

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, peer1, 1)
})
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, peer1, 1)
})
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, peer1, 1)
})
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, peer1, 2)
})
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, peer1, 1)
})
}
// testEventStore creates a new test contexts, generates a set of events for it
// and tests that it has the number of channels we expect.
func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx),
peer route.Vertex, expectedChannels int) {
testCtx := newChanEventStoreTestCtx(t)
testCtx.start()
generateEvents(testCtx)
// Shutdown the store so that we can safely access the maps in our event
// store.
testCtx.stop()
// Get our peer and check that it has the channels we expect.
monitor, ok := testCtx.store.peers[peer]
require.True(t, ok)
require.Equal(t, expectedChannels, monitor.channelCount())
}
// TestGetChanInfo tests the GetChanInfo function for the cases where a channel
// is known and unknown to the store.
func TestGetChanInfo(t *testing.T) {
ctx := newChanEventStoreTestCtx(t)
ctx.start()
// Make a note of the time that our mocked clock starts on.
now := ctx.clock.Now()
// Create mock vars for a channel but do not add them to our store yet.
peer, pk, channel := ctx.newChannel()
// Send an online event for our peer, although we do not yet have an
// open channel.
ctx.peerEvent(peer, true)
// Try to get info for a channel that has not been opened yet, we
// expect to get an error.
_, err := ctx.store.GetChanInfo(channel, peer)
require.Equal(t, ErrChannelNotFound, err)
// Now we send our store a notification that a channel has been opened.
ctx.sendChannelOpenedUpdate(pk, channel)
// Wait for our channel to be recognized by our store. We need to wait
// for the channel to be created so that we do not update our time
// before the channel open is processed.
require.Eventually(t, func() bool {
_, err = ctx.store.GetChanInfo(channel, peer)
return err == nil
}, timeout, time.Millisecond*20)
// Increment our test clock by an hour.
now = now.Add(time.Hour)
ctx.clock.SetTime(now)
// At this stage our channel has been open and online for an hour.
info, err := ctx.store.GetChanInfo(channel, peer)
require.NoError(t, err)
require.Equal(t, time.Hour, info.Lifetime)
require.Equal(t, time.Hour, info.Uptime)
// Now we send a peer offline event for our channel.
ctx.peerEvent(peer, false)
// Since we have not bumped our mocked time, our uptime calculations
// should be the same, even though we've just processed an offline
// event.
info, err = ctx.store.GetChanInfo(channel, peer)
require.NoError(t, err)
require.Equal(t, time.Hour, info.Lifetime)
require.Equal(t, time.Hour, info.Uptime)
// Progress our time again. This time, our peer is currently tracked as
// being offline, so we expect our channel info to reflect that the peer
// has been offline for this period.
now = now.Add(time.Hour)
ctx.clock.SetTime(now)
info, err = ctx.store.GetChanInfo(channel, peer)
require.NoError(t, err)
require.Equal(t, time.Hour*2, info.Lifetime)
require.Equal(t, time.Hour, info.Uptime)
ctx.stop()
}