You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
13 KiB
461 lines
13 KiB
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.SingleFunderBit |
|
if rand.Int63()%2 == 0 { |
|
chanType = channeldb.SingleFunderTweaklessBit |
|
} |
|
|
|
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, |
|
}, |
|
|
|
// The new anchor version, should pack/unpack with no |
|
// problem. |
|
{ |
|
version: AnchorsCommitVersion, |
|
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") |
|
} |
|
} |
|
|
|
// TestSingleUnconfirmedChannel tests that unconfirmed channels get serialized |
|
// correctly by encoding the funding broadcast height as block height of the |
|
// short channel ID. |
|
func TestSingleUnconfirmedChannel(t *testing.T) { |
|
t.Parallel() |
|
|
|
var fundingBroadcastHeight = uint32(1234) |
|
|
|
// Let's create an open channel shell that contains all the information |
|
// we need to create a static channel backup but simulate an |
|
// unconfirmed channel by setting the block height to 0. |
|
channel, err := genRandomOpenChannelShell() |
|
if err != nil { |
|
t.Fatalf("unable to gen open channel: %v", err) |
|
} |
|
channel.ShortChannelID.BlockHeight = 0 |
|
channel.FundingBroadcastHeight = fundingBroadcastHeight |
|
|
|
singleChanBackup := NewSingle(channel, []net.Addr{addr1, addr2}) |
|
keyRing := &mockKeyRing{} |
|
|
|
// Pack it and then unpack it again to make sure everything is written |
|
// correctly, then check that the block height of the unpacked |
|
// is the funding broadcast height we set before. |
|
var b bytes.Buffer |
|
if err := singleChanBackup.PackToWriter(&b, keyRing); err != nil { |
|
t.Fatalf("unable to pack single: %v", err) |
|
} |
|
var unpackedSingle Single |
|
err = unpackedSingle.UnpackFromReader(&b, keyRing) |
|
if err != nil { |
|
t.Fatalf("unable to unpack single: %v", err) |
|
} |
|
if unpackedSingle.ShortChannelID.BlockHeight != fundingBroadcastHeight { |
|
t.Fatalf("invalid block height. got %d expected %d.", |
|
unpackedSingle.ShortChannelID.BlockHeight, |
|
fundingBroadcastHeight) |
|
} |
|
} |
|
|
|
// TODO(roasbsef): fuzz parsing
|
|
|