lnd/chanbackup/recover_test.go
Olaoluwa Osuntokun 7fcab83bb4
chanbackup: add functions to allow recovery of existing channel backups
In this commit, we add a series of functions that will allow users to
recover existing channel backups. We do this using two primary
interfaces: the ChannelRestorer, and the PeerConnector. The first
interfaces allows us to abstract away the details w.r.t exactly how a
channel is restored. Instead, we simply expect that the channel backup
will be inserted as a sort of "channel shell" which contains only the
data required to initiate the data loss protection protocol. The second
interface is how we instruct the Lightning node to connect out to the
channel peer given its known addresses.
2019-01-23 18:11:29 -08:00

233 lines
5.6 KiB
Go

package chanbackup
import (
"bytes"
"fmt"
"net"
"testing"
"github.com/btcsuite/btcd/btcec"
)
type mockChannelRestorer struct {
fail bool
callCount int
}
func (m *mockChannelRestorer) RestoreChansFromSingles(...Single) error {
if m.fail {
return fmt.Errorf("fail")
}
m.callCount++
return nil
}
type mockPeerConnector struct {
fail bool
callCount int
}
func (m *mockPeerConnector) ConnectPeer(node *btcec.PublicKey,
addrs []net.Addr) error {
if m.fail {
return fmt.Errorf("fail")
}
m.callCount++
return nil
}
// TestUnpackAndRecoverSingles tests that we're able to properly unpack and
// recover a set of packed singles.
func TestUnpackAndRecoverSingles(t *testing.T) {
t.Parallel()
keyRing := &mockKeyRing{}
// First, we'll create a number of single chan backups that we'll
// shortly back to so we can begin our recovery attempt.
numSingles := 10
backups := make([]Single, 0, numSingles)
var packedBackups PackedSingles
for i := 0; i < numSingles; i++ {
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable make channel: %v", err)
}
single := NewSingle(channel, nil)
var b bytes.Buffer
if err := single.PackToWriter(&b, keyRing); err != nil {
t.Fatalf("unable to pack single: %v", err)
}
backups = append(backups, single)
packedBackups = append(packedBackups, b.Bytes())
}
chanRestorer := mockChannelRestorer{}
peerConnector := mockPeerConnector{}
// Now that we have our backups (packed and unpacked), we'll attempt to
// restore them all in a single batch.
// If we make the channel restore fail, then the entire method should
// as well
chanRestorer.fail = true
err := UnpackAndRecoverSingles(
packedBackups, keyRing, &chanRestorer, &peerConnector,
)
if err == nil {
t.Fatalf("restoration should have failed")
}
chanRestorer.fail = false
// If we make the peer connector fail, then the entire method should as
// well
peerConnector.fail = true
err = UnpackAndRecoverSingles(
packedBackups, keyRing, &chanRestorer, &peerConnector,
)
if err == nil {
t.Fatalf("restoration should have failed")
}
chanRestorer.callCount--
peerConnector.fail = false
// Next, we'll ensure that if all the interfaces function as expected,
// then the channels will properly be unpacked and restored.
err = UnpackAndRecoverSingles(
packedBackups, keyRing, &chanRestorer, &peerConnector,
)
if err != nil {
t.Fatalf("unable to recover chans: %v", err)
}
// Both the restorer, and connector should have been called 10 times,
// once for each backup.
if chanRestorer.callCount != numSingles {
t.Fatalf("expected %v calls, instead got %v",
numSingles, chanRestorer.callCount)
}
if peerConnector.callCount != numSingles {
t.Fatalf("expected %v calls, instead got %v",
numSingles, peerConnector.callCount)
}
// If we modify the keyRing, then unpacking should fail.
keyRing.fail = true
err = UnpackAndRecoverSingles(
packedBackups, keyRing, &chanRestorer, &peerConnector,
)
if err == nil {
t.Fatalf("unpacking should have failed")
}
// TODO(roasbeef): verify proper call args
}
// TestUnpackAndRecoverMulti tests that we're able to properly unpack and
// recover a packed multi.
func TestUnpackAndRecoverMulti(t *testing.T) {
t.Parallel()
keyRing := &mockKeyRing{}
// First, we'll create a number of single chan backups that we'll
// shortly back to so we can begin our recovery attempt.
numSingles := 10
backups := make([]Single, 0, numSingles)
for i := 0; i < numSingles; i++ {
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable make channel: %v", err)
}
single := NewSingle(channel, nil)
backups = append(backups, single)
}
multi := Multi{
StaticBackups: backups,
}
var b bytes.Buffer
if err := multi.PackToWriter(&b, keyRing); err != nil {
t.Fatalf("unable to pack multi: %v", err)
}
// Next, we'll pack the set of singles into a packed multi, and also
// create the set of interfaces we need to carry out the remainder of
// the test.
packedMulti := PackedMulti(b.Bytes())
chanRestorer := mockChannelRestorer{}
peerConnector := mockPeerConnector{}
// If we make the channel restore fail, then the entire method should
// as well
chanRestorer.fail = true
err := UnpackAndRecoverMulti(
packedMulti, keyRing, &chanRestorer, &peerConnector,
)
if err == nil {
t.Fatalf("restoration should have failed")
}
chanRestorer.fail = false
// If we make the peer connector fail, then the entire method should as
// well
peerConnector.fail = true
err = UnpackAndRecoverMulti(
packedMulti, keyRing, &chanRestorer, &peerConnector,
)
if err == nil {
t.Fatalf("restoration should have failed")
}
chanRestorer.callCount--
peerConnector.fail = false
// Next, we'll ensure that if all the interfaces function as expected,
// then the channels will properly be unpacked and restored.
err = UnpackAndRecoverMulti(
packedMulti, keyRing, &chanRestorer, &peerConnector,
)
if err != nil {
t.Fatalf("unable to recover chans: %v", err)
}
// Both the restorer, and connector should have been called 10 times,
// once for each backup.
if chanRestorer.callCount != numSingles {
t.Fatalf("expected %v calls, instead got %v",
numSingles, chanRestorer.callCount)
}
if peerConnector.callCount != numSingles {
t.Fatalf("expected %v calls, instead got %v",
numSingles, peerConnector.callCount)
}
// If we modify the keyRing, then unpacking should fail.
keyRing.fail = true
err = UnpackAndRecoverMulti(
packedMulti, keyRing, &chanRestorer, &peerConnector,
)
if err == nil {
t.Fatalf("unpacking should have failed")
}
// TODO(roasbeef): verify proper call args
}