chanbackup: add functionality to allow external callers to backup channels

In this commit, we introduce a series of interfaces and methods that
will allow external callers to backup either all channels, or a specific
channel identified by its channel point. In order to abstract away the
details w.r.t _how_ we obtain the set of open channels, or their storage
mechanisms, we introduce a new LiveChannelSource interfaces. This
interfaces allows us to fetch all channels, a channel by its channel
point, and also all the known addresses for a node as we'll need this in
order to connect out to the node in the case of a recovery attempt.
This commit is contained in:
Olaoluwa Osuntokun 2018-12-09 18:51:58 -08:00
parent 71df4b0545
commit 1348c6b35b
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
2 changed files with 296 additions and 0 deletions

99
chanbackup/backup.go Normal file

@ -0,0 +1,99 @@
package chanbackup
import (
"fmt"
"net"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
)
// LiveChannelSource is an interface that allows us to query for the set of
// live channels. A live channel is one that is open, and has not had a
// commitment transaction broadcast.
type LiveChannelSource interface {
// FetchAllChannels returns all known live channels.
FetchAllChannels() ([]*channeldb.OpenChannel, error)
// FetchChannel attempts to locate a live channel identified by the
// passed chanPoint.
FetchChannel(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error)
// AddrsForNode returns all known addresses for the target node public
// key.
AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr, error)
}
// assembleChanBackup attempts to assemble a static channel backup for the
// passed open channel. The backup includes all information required to restore
// the channel, as well as addressing information so we can find the peer and
// reconnect to them to initiate the protocol.
func assembleChanBackup(chanSource LiveChannelSource,
openChan *channeldb.OpenChannel) (*Single, error) {
log.Debugf("Crafting backup for ChannelPoint(%v)",
openChan.FundingOutpoint)
// First, we'll query the channel source to obtain all the addresses
// that are are associated with the peer for this channel.
nodeAddrs, err := chanSource.AddrsForNode(openChan.IdentityPub)
if err != nil {
return nil, err
}
single := NewSingle(openChan, nodeAddrs)
return &single, nil
}
// FetchBackupForChan attempts to create a plaintext static channel backup for
// the target channel identified by its channel point. If we're unable to find
// the target channel, then an error will be returned.
func FetchBackupForChan(chanPoint wire.OutPoint,
chanSource LiveChannelSource) (*Single, error) {
// First, we'll query the channel source to see if the channel is known
// and open within the database.
targetChan, err := chanSource.FetchChannel(chanPoint)
if err != nil {
// If we can't find the channel, then we return with an error,
// as we have nothing to backup.
return nil, fmt.Errorf("unable to find target channel")
}
// Once we have the target channel, we can assemble the backup using
// the source to obtain any extra information that we may need.
staticChanBackup, err := assembleChanBackup(chanSource, targetChan)
if err != nil {
return nil, fmt.Errorf("unable to create chan backup: %v", err)
}
return staticChanBackup, nil
}
// FetchStaticChanBackups will return a plaintext static channel back up for
// all known active/open channels within the passed channel source.
func FetchStaticChanBackups(chanSource LiveChannelSource) ([]Single, error) {
// First, we'll query the backup source for information concerning all
// currently open and available channels.
openChans, err := chanSource.FetchAllChannels()
if err != nil {
return nil, err
}
// Now that we have all the channels, we'll use the chanSource to
// obtain any auxiliary information we need to craft a backup for each
// channel.
staticChanBackups := make([]Single, len(openChans))
for i, openChan := range openChans {
chanBackup, err := assembleChanBackup(chanSource, openChan)
if err != nil {
return nil, err
}
staticChanBackups[i] = *chanBackup
}
return staticChanBackups, nil
}

197
chanbackup/backup_test.go Normal file

@ -0,0 +1,197 @@
package chanbackup
import (
"fmt"
"net"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
)
type mockChannelSource struct {
chans map[wire.OutPoint]*channeldb.OpenChannel
failQuery bool
addrs map[[33]byte][]net.Addr
}
func newMockChannelSource() *mockChannelSource {
return &mockChannelSource{
chans: make(map[wire.OutPoint]*channeldb.OpenChannel),
addrs: make(map[[33]byte][]net.Addr),
}
}
func (m *mockChannelSource) FetchAllChannels() ([]*channeldb.OpenChannel, error) {
if m.failQuery {
return nil, fmt.Errorf("fail")
}
chans := make([]*channeldb.OpenChannel, 0, len(m.chans))
for _, channel := range m.chans {
chans = append(chans, channel)
}
return chans, nil
}
func (m *mockChannelSource) FetchChannel(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
if m.failQuery {
return nil, fmt.Errorf("fail")
}
channel, ok := m.chans[chanPoint]
if !ok {
return nil, fmt.Errorf("can't find chan")
}
return channel, nil
}
func (m *mockChannelSource) addAddrsForNode(nodePub *btcec.PublicKey, addrs []net.Addr) {
var nodeKey [33]byte
copy(nodeKey[:], nodePub.SerializeCompressed())
m.addrs[nodeKey] = addrs
}
func (m *mockChannelSource) AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr, error) {
if m.failQuery {
return nil, fmt.Errorf("fail")
}
var nodeKey [33]byte
copy(nodeKey[:], nodePub.SerializeCompressed())
addrs, ok := m.addrs[nodeKey]
if !ok {
return nil, fmt.Errorf("can't find addr")
}
return addrs, nil
}
// TestFetchBackupForChan tests that we're able to construct a single channel
// backup for channels that are known, unknown, and also channels in which we
// can find addresses for and otherwise.
func TestFetchBackupForChan(t *testing.T) {
t.Parallel()
// First, we'll make two channels, only one of them will have all the
// information we need to construct set of backups for them.
randomChan1, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to generate chan: %v", err)
}
randomChan2, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to generate chan: %v", err)
}
chanSource := newMockChannelSource()
chanSource.chans[randomChan1.FundingOutpoint] = randomChan1
chanSource.chans[randomChan2.FundingOutpoint] = randomChan2
chanSource.addAddrsForNode(randomChan1.IdentityPub, []net.Addr{addr1})
testCases := []struct {
chanPoint wire.OutPoint
pass bool
}{
// Able to find channel, and addresses, should pass.
{
chanPoint: randomChan1.FundingOutpoint,
pass: true,
},
// Able to find channel, not able to find addrs, should fail.
{
chanPoint: randomChan2.FundingOutpoint,
pass: false,
},
// Not able to find channel, should fail.
{
chanPoint: op,
pass: false,
},
}
for i, testCase := range testCases {
_, err := FetchBackupForChan(testCase.chanPoint, chanSource)
switch {
// If this is a valid test case, and we failed, then we'll
// return an error.
case err != nil && testCase.pass:
t.Fatalf("#%v, unable to make chan backup: %v", i, err)
// If this is an invalid test case, and we passed it, then
// we'll return an error.
case err == nil && !testCase.pass:
t.Fatalf("#%v got nil error for invalid req: %v",
i, err)
}
}
}
// TestFetchStaticChanBackups tests that we're able to properly query the
// channel source for all channels and construct a Single for each channel.
func TestFetchStaticChanBackups(t *testing.T) {
t.Parallel()
// First, we'll make the set of channels that we want to seed the
// channel source with. Both channels will be fully populated in the
// channel source.
const numChans = 2
randomChan1, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to generate chan: %v", err)
}
randomChan2, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to generate chan: %v", err)
}
chanSource := newMockChannelSource()
chanSource.chans[randomChan1.FundingOutpoint] = randomChan1
chanSource.chans[randomChan2.FundingOutpoint] = randomChan2
chanSource.addAddrsForNode(randomChan1.IdentityPub, []net.Addr{addr1})
chanSource.addAddrsForNode(randomChan2.IdentityPub, []net.Addr{addr2})
// With the channel source populated, we'll now attempt to create a set
// of backups for all the channels. This should succeed, as all items
// are populated within the channel source.
backups, err := FetchStaticChanBackups(chanSource)
if err != nil {
t.Fatalf("unable to create chan back ups: %v", err)
}
if len(backups) != numChans {
t.Fatalf("expected %v chans, instead got %v", numChans,
len(backups))
}
// We'll attempt to create a set up backups again, but this time the
// second channel will have missing information, which should cause the
// query to fail.
var n [33]byte
copy(n[:], randomChan2.IdentityPub.SerializeCompressed())
delete(chanSource.addrs, n)
_, err = FetchStaticChanBackups(chanSource)
if err == nil {
t.Fatalf("query with incomplete information should fail")
}
// To wrap up, we'll ensure that if we're unable to query the channel
// source at all, then we'll fail as well.
chanSource = newMockChannelSource()
chanSource.failQuery = true
_, err = FetchStaticChanBackups(chanSource)
if err == nil {
t.Fatalf("query should fail")
}
}