lnd/chanbackup/pubsub_test.go

281 lines
7.5 KiB
Go
Raw Normal View History

package chanbackup
import (
"fmt"
"testing"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/stretchr/testify/require"
)
type mockSwapper struct {
fail bool
swaps chan PackedMulti
swapState *Multi
keyChain keychain.KeyRing
}
func newMockSwapper(keychain keychain.KeyRing) *mockSwapper {
return &mockSwapper{
swaps: make(chan PackedMulti, 1),
keyChain: keychain,
swapState: &Multi{},
}
}
func (m *mockSwapper) UpdateAndSwap(newBackup PackedMulti) error {
if m.fail {
return fmt.Errorf("fail")
}
swapState, err := newBackup.Unpack(m.keyChain)
if err != nil {
return fmt.Errorf("unable to decode on disk swaps: %v", err)
}
m.swapState = swapState
m.swaps <- newBackup
return nil
}
func (m *mockSwapper) ExtractMulti(keychain keychain.KeyRing) (*Multi, error) {
return m.swapState, nil
}
type mockChannelNotifier struct {
fail bool
chanEvents chan ChannelEvent
}
func newMockChannelNotifier() *mockChannelNotifier {
return &mockChannelNotifier{
chanEvents: make(chan ChannelEvent),
}
}
func (m *mockChannelNotifier) SubscribeChans(chans map[wire.OutPoint]struct{}) (
*ChannelSubscription, error) {
if m.fail {
return nil, fmt.Errorf("fail")
}
return &ChannelSubscription{
ChanUpdates: m.chanEvents,
Cancel: func() {
},
}, nil
}
// TestNewSubSwapperSubscribeFail tests that if we're unable to obtain a
// channel subscription, then the entire sub-swapper will fail to start.
func TestNewSubSwapperSubscribeFail(t *testing.T) {
t.Parallel()
keyRing := &lnencrypt.MockKeyRing{}
var swapper mockSwapper
chanNotifier := mockChannelNotifier{
fail: true,
}
_, err := NewSubSwapper(nil, &chanNotifier, keyRing, &swapper)
if err == nil {
t.Fatalf("expected fail due to lack of subscription")
}
}
func assertExpectedBackupSwap(t *testing.T, swapper *mockSwapper,
subSwapper *SubSwapper, keyRing keychain.KeyRing,
expectedChanSet map[wire.OutPoint]Single) {
t.Helper()
select {
case newPackedMulti := <-swapper.swaps:
// If we unpack the new multi, then we should find all the old
// channels, and also the new channel included and any deleted
// channel omitted.
newMulti, err := newPackedMulti.Unpack(keyRing)
if err != nil {
t.Fatalf("unable to unpack multi: %v", err)
}
// Ensure that once unpacked, the current backup has the
// expected number of Singles.
if len(newMulti.StaticBackups) != len(expectedChanSet) {
t.Fatalf("new backup wasn't included: expected %v "+
"backups have %v", len(expectedChanSet),
len(newMulti.StaticBackups))
}
// We should also find all the old and new channels in this new
// backup.
for _, backup := range newMulti.StaticBackups {
_, ok := expectedChanSet[backup.FundingOutpoint]
if !ok {
t.Fatalf("didn't find backup in original set: %v",
backup.FundingOutpoint)
}
}
// The same applies for our in-memory state, but it's also
// possible for there to be items in the on-disk state that we
// don't know of explicit.
newChans := make(map[wire.OutPoint]Single)
for _, newChan := range newMulti.StaticBackups {
newChans[newChan.FundingOutpoint] = newChan
}
for _, backup := range subSwapper.backupState {
_, ok := newChans[backup.FundingOutpoint]
if !ok {
t.Fatalf("didn't find backup in original set: %v",
backup.FundingOutpoint)
}
}
case <-time.After(time.Second * 5):
t.Fatalf("update swapper didn't swap out multi")
}
}
// TestSubSwapperIdempotentStartStop tests that calling the Start/Stop methods
// multiple time is permitted.
func TestSubSwapperIdempotentStartStop(t *testing.T) {
t.Parallel()
keyRing := &lnencrypt.MockKeyRing{}
var chanNotifier mockChannelNotifier
swapper := newMockSwapper(keyRing)
subSwapper, err := NewSubSwapper(nil, &chanNotifier, keyRing, swapper)
require.NoError(t, err, "unable to init subSwapper")
if err := subSwapper.Start(); err != nil {
t.Fatalf("unable to start swapper: %v", err)
}
// The swapper should write the initial channel state as soon as it's
// active.
backupSet := make(map[wire.OutPoint]Single)
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)
subSwapper.Start()
subSwapper.Stop()
subSwapper.Stop()
}
// TestSubSwapperUpdater tests that the SubSwapper will properly swap out
// new/old channels within the channel set, and notify the swapper to update
// the master multi file backup.
func TestSubSwapperUpdater(t *testing.T) {
t.Parallel()
keyRing := &lnencrypt.MockKeyRing{}
chanNotifier := newMockChannelNotifier()
swapper := newMockSwapper(keyRing)
// First, we'll start out by creating a channels set for the initial
// set of channels known to the sub-swapper.
const numStartingChans = 3
initialChanSet := make([]Single, 0, numStartingChans)
backupSet := make(map[wire.OutPoint]Single)
for i := 0; i < numStartingChans; i++ {
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to make test chan: %v", err)
}
single := NewSingle(channel, nil)
backupSet[channel.FundingOutpoint] = single
initialChanSet = append(initialChanSet, single)
}
// We'll also generate two additional channels which will already be
// present on disk. However, these will at first only be known by the
// on disk backup (the backup set).
const numDiskChans = 2
for i := 0; i < numDiskChans; i++ {
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to make test chan: %v", err)
}
single := NewSingle(channel, nil)
backupSet[channel.FundingOutpoint] = single
swapper.swapState.StaticBackups = append(
swapper.swapState.StaticBackups, single,
)
}
// With our channel set created, we'll make a fresh sub swapper
// instance to begin our test.
subSwapper, err := NewSubSwapper(
initialChanSet, chanNotifier, keyRing, swapper,
)
require.NoError(t, err, "unable to make swapper")
if err := subSwapper.Start(); err != nil {
t.Fatalf("unable to start sub swapper: %v", err)
}
defer subSwapper.Stop()
// The swapper should write the initial channel state as soon as it's
// active.
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)
// Now that the sub-swapper is active, we'll notify to add a brand new
// channel to the channel state.
newChannel, err := genRandomOpenChannelShell()
require.NoError(t, err, "unable to create new chan")
// With the new channel created, we'll send a new update to the main
// goroutine telling it about this new channel.
select {
case chanNotifier.chanEvents <- ChannelEvent{
NewChans: []ChannelWithAddrs{
{
OpenChannel: newChannel,
},
},
}:
case <-time.After(time.Second * 5):
t.Fatalf("update swapper didn't read new channel: %v", err)
}
backupSet[newChannel.FundingOutpoint] = NewSingle(newChannel, nil)
// At this point, the sub-swapper should now have packed a new multi,
// and then sent it to the swapper so the back up can be updated.
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)
// We'll now trigger an update to remove an existing channel.
chanToDelete := initialChanSet[0].FundingOutpoint
select {
case chanNotifier.chanEvents <- ChannelEvent{
ClosedChans: []wire.OutPoint{chanToDelete},
}:
case <-time.After(time.Second * 5):
t.Fatalf("update swapper didn't read new channel: %v", err)
}
delete(backupSet, chanToDelete)
// Verify that the new set of backups, now has one less after the
// sub-swapper switches the new set with the old.
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)
}