lnwallet/channel test: add TestChanSyncFailure

This commit is contained in:
Johan T. Halseth 2018-07-12 11:02:53 +02:00
parent 78a4a15bb4
commit 410b730778
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26

@ -10,13 +10,14 @@ import (
"runtime"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnwire"
)
// forceStateTransition executes the necessary interaction between the two
@ -2562,7 +2563,7 @@ func TestChanSyncFullySynced(t *testing.T) {
assertNoChanSyncNeeded(t, aliceChannelNew, bobChannelNew)
}
// restartChannel reads the passe channel from disk, and returns a newly
// restartChannel reads the passed channel from disk, and returns a newly
// initialized instance. This simulates one party restarting and losing their
// in memory state.
func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
@ -3486,6 +3487,228 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) {
}
}
// TestChanSyncFailure tests the various scenarios during channel sync where we
// should be able to detect that the channels cannot be synced because of
// invalid state.
func TestChanSyncFailure(t *testing.T) {
t.Parallel()
// Create a test channel which will be used for the duration of this
// unittest. The channel will be funded evenly with Alice having 5 BTC,
// and Bob having 5 BTC.
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
index := byte(0)
// advanceState is a helper method to fully advance the channel state
// by one.
advanceState := func() {
// We'll kick off the test by having Bob send Alice an HTLC,
// then lock it in with a state transition.
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa - index}, 32))
rHash := sha256.Sum256(bobPreimage[:])
bobHtlc := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
ID: uint64(index),
}
index++
_, err := bobChannel.AddHTLC(bobHtlc, nil)
if err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
_, err = aliceChannel.ReceiveHTLC(bobHtlc)
if err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
err = forceStateTransition(bobChannel, aliceChannel)
if err != nil {
t.Fatalf("unable to complete bob's state "+
"transition: %v", err)
}
}
// halfAdvance is a helper method that sends a new commitment signature
// from Alice to Bob, but doesn't make Bob revoke his current state.
halfAdvance := func() {
// We'll kick off the test by having Bob send Alice an HTLC,
// then lock it in with a state transition.
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa - index}, 32))
rHash := sha256.Sum256(bobPreimage[:])
bobHtlc := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
ID: uint64(index),
}
index++
_, err := bobChannel.AddHTLC(bobHtlc, nil)
if err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
_, err = aliceChannel.ReceiveHTLC(bobHtlc)
if err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("unable to sign next commit: %v", err)
}
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("unable to receive commit sig: %v", err)
}
}
// assertLocalDataLoss checks that aliceOld and bobChannel detects that
// Alice has lost state during sync.
assertLocalDataLoss := func(aliceOld *LightningChannel) {
aliceSyncMsg, err := aliceOld.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := bobChannel.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
// Alice should detect from Bob's message that she lost state.
_, _, _, err = aliceOld.ProcessChanSyncMsg(bobSyncMsg)
if err != ErrCommitSyncLocalDataLoss {
t.Fatalf("wrong error, expected "+
"ErrCommitSyncLocalDataLoss instead got: %v",
err)
}
// Bob should detect that Alice probably lost state.
_, _, _, err = bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
if err != ErrCommitSyncRemoteDataLoss {
t.Fatalf("wrong error, expected "+
"ErrCommitSyncRemoteDataLoss instead got: %v",
err)
}
}
// Start by advancing the state.
advanceState()
// They should be in sync.
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
// Make a copy of Alice's state from the database at this point.
aliceOld, err := restartChannel(aliceChannel)
if err != nil {
t.Fatalf("unable to restart channel: %v", err)
}
// Advance the states.
advanceState()
// Trying to sync up the old version of Alice's channel should detect
// that we are out of sync.
assertLocalDataLoss(aliceOld)
// Make sure the up-to-date channels still are in sync.
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
// Advance the state again, and do the same check.
advanceState()
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
assertLocalDataLoss(aliceOld)
// If we remove the recovery options from Bob's message, Alice cannot
// tell if she lost state, since Bob might be lying. She still should
// be able to detect that chains cannot be synced.
bobSyncMsg, err := bobChannel.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg.LocalUnrevokedCommitPoint = nil
_, _, _, err = aliceOld.ProcessChanSyncMsg(bobSyncMsg)
if err != ErrCannotSyncCommitChains {
t.Fatalf("wrong error, expected ErrCannotSyncCommitChains "+
"instead got: %v", err)
}
// If Bob lies about the NextLocalCommitHeight, making it greater than
// what Alice expect, she cannot tell for sure whether she lost state,
// but should detect the desync.
bobSyncMsg, err = bobChannel.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg.NextLocalCommitHeight++
_, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != ErrCannotSyncCommitChains {
t.Fatalf("wrong error, expected ErrCannotSyncCommitChains "+
"instead got: %v", err)
}
// If Bob's NextLocalCommitHeight is lower than what Alice expects, Bob
// probably lost state.
bobSyncMsg, err = bobChannel.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg.NextLocalCommitHeight--
_, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != ErrCommitSyncRemoteDataLoss {
t.Fatalf("wrong error, expected ErrCommitSyncRemoteDataLoss "+
"instead got: %v", err)
}
// If Alice and Bob's states are in sync, but Bob is sending the wrong
// LocalUnrevokedCommitPoint, Alice should detect this.
bobSyncMsg, err = bobChannel.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
p := bobSyncMsg.LocalUnrevokedCommitPoint.SerializeCompressed()
p[4] ^= 0x01
modCommitPoint, err := btcec.ParsePubKey(p, btcec.S256())
if err != nil {
t.Fatalf("unable to parse pubkey: %v", err)
}
bobSyncMsg.LocalUnrevokedCommitPoint = modCommitPoint
_, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != ErrInvalidLocalUnrevokedCommitPoint {
t.Fatalf("wrong error, expected "+
"ErrInvalidLocalUnrevokedCommitPoint instead got: %v",
err)
}
// Make sure the up-to-date channels still are good.
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
// Finally check that Alice is also able to detect a wrong commit point
// when there's a pending remote commit.
halfAdvance()
bobSyncMsg, err = bobChannel.ChanSyncMsg()
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg.LocalUnrevokedCommitPoint = modCommitPoint
_, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != ErrInvalidLocalUnrevokedCommitPoint {
t.Fatalf("wrong error, expected "+
"ErrInvalidLocalUnrevokedCommitPoint instead got: %v",
err)
}
}
// TestFeeUpdateRejectInsaneFee tests that if the initiator tries to attach a
// fee that would put them below their current reserve, then it's rejected by
// the state machine.