lnd.xprv/chanbackup/single_test.go
Olaoluwa Osuntokun c6ee42d3e2
chanbackup: create new Single version for tweakless commitment chans
In this commit, we create a new Single version for channels that use the
tweakless commitment scheme. When recovering from an SCB into an open
channel shell, we'll now check this field and use it to determine the
proper channel type. Otherwise, we may attempt to sweep the on chain
funds using the commitment point, when it goes directly to our key, or
the other way around.
2019-09-25 18:26:20 -07:00

414 lines
11 KiB
Go

package chanbackup
import (
"bytes"
"math"
"math/rand"
"net"
"reflect"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
)
var (
chainHash = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x18, 0xa3, 0xef, 0xb9,
0x64, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
op = wire.OutPoint{
Hash: chainHash,
Index: 4,
}
addr1, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9000")
addr2, _ = net.ResolveTCPAddr("tcp", "10.0.0.3:9000")
)
func assertSingleEqual(t *testing.T, a, b Single) {
t.Helper()
if a.Version != b.Version {
t.Fatalf("versions don't match: %v vs %v", a.Version,
b.Version)
}
if a.IsInitiator != b.IsInitiator {
t.Fatalf("initiators don't match: %v vs %v", a.IsInitiator,
b.IsInitiator)
}
if a.ChainHash != b.ChainHash {
t.Fatalf("chainhash doesn't match: %v vs %v", a.ChainHash,
b.ChainHash)
}
if a.FundingOutpoint != b.FundingOutpoint {
t.Fatalf("chan point doesn't match: %v vs %v",
a.FundingOutpoint, b.FundingOutpoint)
}
if a.ShortChannelID != b.ShortChannelID {
t.Fatalf("chan id doesn't match: %v vs %v",
a.ShortChannelID, b.ShortChannelID)
}
if a.Capacity != b.Capacity {
t.Fatalf("capacity doesn't match: %v vs %v",
a.Capacity, b.Capacity)
}
if !a.RemoteNodePub.IsEqual(b.RemoteNodePub) {
t.Fatalf("node pubs don't match %x vs %x",
a.RemoteNodePub.SerializeCompressed(),
b.RemoteNodePub.SerializeCompressed())
}
if !reflect.DeepEqual(a.LocalChanCfg, b.LocalChanCfg) {
t.Fatalf("local chan config doesn't match: %v vs %v",
spew.Sdump(a.LocalChanCfg),
spew.Sdump(b.LocalChanCfg))
}
if !reflect.DeepEqual(a.RemoteChanCfg, b.RemoteChanCfg) {
t.Fatalf("remote chan config doesn't match: %v vs %v",
spew.Sdump(a.RemoteChanCfg),
spew.Sdump(b.RemoteChanCfg))
}
if !reflect.DeepEqual(a.ShaChainRootDesc, b.ShaChainRootDesc) {
t.Fatalf("sha chain point doesn't match: %v vs %v",
spew.Sdump(a.ShaChainRootDesc),
spew.Sdump(b.ShaChainRootDesc))
}
if len(a.Addresses) != len(b.Addresses) {
t.Fatalf("expected %v addrs got %v", len(a.Addresses),
len(b.Addresses))
}
for i := 0; i < len(a.Addresses); i++ {
if a.Addresses[i].String() != b.Addresses[i].String() {
t.Fatalf("addr mismatch: %v vs %v",
a.Addresses[i], b.Addresses[i])
}
}
}
func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
var testPriv [32]byte
if _, err := rand.Read(testPriv[:]); err != nil {
return nil, err
}
_, pub := btcec.PrivKeyFromBytes(btcec.S256(), testPriv[:])
var chanPoint wire.OutPoint
if _, err := rand.Read(chanPoint.Hash[:]); err != nil {
return nil, err
}
pub.Curve = nil
chanPoint.Index = uint32(rand.Intn(math.MaxUint16))
var shaChainRoot [32]byte
if _, err := rand.Read(shaChainRoot[:]); err != nil {
return nil, err
}
shaChainProducer := shachain.NewRevocationProducer(shaChainRoot)
var isInitiator bool
if rand.Int63()%2 == 0 {
isInitiator = true
}
chanType := channeldb.SingleFunder
if rand.Int63()%2 == 0 {
chanType = channeldb.SingleFunderTweakless
}
return &channeldb.OpenChannel{
ChainHash: chainHash,
ChanType: chanType,
IsInitiator: isInitiator,
FundingOutpoint: chanPoint,
ShortChannelID: lnwire.NewShortChanIDFromInt(
uint64(rand.Int63()),
),
IdentityPub: pub,
LocalChanCfg: channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
CsvDelay: uint16(rand.Int63()),
},
MultiSigKey: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
RevocationBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
PaymentBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
DelayBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
HtlcBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
},
RemoteChanCfg: channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
CsvDelay: uint16(rand.Int63()),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: pub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
},
RevocationProducer: shaChainProducer,
}, nil
}
// TestSinglePackUnpack tests that we're able to unpack a previously packed
// channel backup.
func TestSinglePackUnpack(t *testing.T) {
t.Parallel()
// Given our test pub key, we'll create an open channel shell that
// contains all the information we need to create a static channel
// backup.
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to gen open channel: %v", err)
}
singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2})
singleChanBackup.RemoteNodePub.Curve = nil
keyRing := &mockKeyRing{}
versionTestCases := []struct {
// version is the pack/unpack version that we should use to
// decode/encode the final SCB.
version SingleBackupVersion
// valid tests us if this test case should pass or not.
valid bool
}{
// The default version, should pack/unpack with no problem.
{
version: DefaultSingleVersion,
valid: true,
},
// The new tweakless version, should pack/unpack with no problem.
{
version: TweaklessCommitVersion,
valid: true,
},
// A non-default version, atm this should result in a failure.
{
version: 99,
valid: false,
},
}
for i, versionCase := range versionTestCases {
// First, we'll re-assign SCB version to what was indicated in
// the test case.
singleChanBackup.Version = versionCase.version
var b bytes.Buffer
err := singleChanBackup.PackToWriter(&b, keyRing)
switch {
// If this is a valid test case, and we failed, then we'll
// return an error.
case err != nil && versionCase.valid:
t.Fatalf("#%v, unable to pack single: %v", i, err)
// If this is an invalid test case, and we passed it, then
// we'll return an error.
case err == nil && !versionCase.valid:
t.Fatalf("#%v got nil error for invalid pack: %v",
i, err)
}
// If this is a valid test case, then we'll continue to ensure
// we can unpack it, and also that if we mutate the packed
// version, then we trigger an error.
if versionCase.valid {
var unpackedSingle Single
err = unpackedSingle.UnpackFromReader(&b, keyRing)
if err != nil {
t.Fatalf("#%v unable to unpack single: %v",
i, err)
}
unpackedSingle.RemoteNodePub.Curve = nil
assertSingleEqual(t, singleChanBackup, unpackedSingle)
// If this was a valid packing attempt, then we'll test
// to ensure that if we mutate the version prepended to
// the serialization, then unpacking will fail as well.
var rawSingle bytes.Buffer
err := unpackedSingle.Serialize(&rawSingle)
if err != nil {
t.Fatalf("unable to serialize single: %v", err)
}
rawBytes := rawSingle.Bytes()
rawBytes[0] ^= 5
newReader := bytes.NewReader(rawBytes)
err = unpackedSingle.Deserialize(newReader)
if err == nil {
t.Fatalf("#%v unpack with unknown version "+
"should have failed", i)
}
}
}
}
// TestPackedSinglesUnpack tests that we're able to properly unpack a series of
// packed singles.
func TestPackedSinglesUnpack(t *testing.T) {
t.Parallel()
keyRing := &mockKeyRing{}
// To start, we'll create 10 new singles, and them assemble their
// packed forms into a slice.
numSingles := 10
packedSingles := make([][]byte, 0, numSingles)
unpackedSingles := make([]Single, 0, numSingles)
for i := 0; i < numSingles; i++ {
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to gen 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)
}
packedSingles = append(packedSingles, b.Bytes())
unpackedSingles = append(unpackedSingles, single)
}
// With all singles packed, we'll create the grouped type and attempt
// to Unpack all of them in a single go.
freshSingles, err := PackedSingles(packedSingles).Unpack(keyRing)
if err != nil {
t.Fatalf("unable to unpack singles: %v", err)
}
// The set of freshly unpacked singles should exactly match the initial
// set of singles that we packed before.
for i := 0; i < len(unpackedSingles); i++ {
assertSingleEqual(t, unpackedSingles[i], freshSingles[i])
}
// If we mutate one of the packed singles, then the entire method
// should fail.
packedSingles[0][0] ^= 1
_, err = PackedSingles(packedSingles).Unpack(keyRing)
if err == nil {
t.Fatalf("unpack attempt should fail")
}
}
// TestSinglePackStaticChanBackups tests that we're able to batch pack a set of
// Singles, and then unpack them obtaining the same set of unpacked singles.
func TestSinglePackStaticChanBackups(t *testing.T) {
t.Parallel()
keyRing := &mockKeyRing{}
// First, we'll create a set of random single, and along the way,
// create a map that will let us look up each single by its chan point.
numSingles := 10
singleMap := make(map[wire.OutPoint]Single, numSingles)
unpackedSingles := make([]Single, 0, numSingles)
for i := 0; i < numSingles; i++ {
channel, err := genRandomOpenChannelShell()
if err != nil {
t.Fatalf("unable to gen channel: %v", err)
}
single := NewSingle(channel, nil)
singleMap[channel.FundingOutpoint] = single
unpackedSingles = append(unpackedSingles, single)
}
// Now that we have all of our singles are created, we'll attempt to
// pack them all in a single batch.
packedSingleMap, err := PackStaticChanBackups(unpackedSingles, keyRing)
if err != nil {
t.Fatalf("unable to pack backups: %v", err)
}
// With our packed singles obtained, we'll ensure that each of them
// match their unpacked counterparts after they themselves have been
// unpacked.
for chanPoint, single := range singleMap {
packedSingles, ok := packedSingleMap[chanPoint]
if !ok {
t.Fatalf("unable to find single %v", chanPoint)
}
var freshSingle Single
err := freshSingle.UnpackFromReader(
bytes.NewReader(packedSingles), keyRing,
)
if err != nil {
t.Fatalf("unable to unpack single: %v", err)
}
assertSingleEqual(t, single, freshSingle)
}
// If we attempt to pack again, but force the key ring to fail, then
// the entire method should fail.
_, err = PackStaticChanBackups(
unpackedSingles, &mockKeyRing{true},
)
if err == nil {
t.Fatalf("pack attempt should fail")
}
}
// TODO(roasbsef): fuzz parsing