Browse Source
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.master
Olaoluwa Osuntokun
5 years ago
2 changed files with 296 additions and 0 deletions
@ -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 |
||||
} |
@ -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") |
||||
} |
||||
} |
Loading…
Reference in new issue