lnwallet/channel test: add TestChanSyncFailure
This commit is contained in:
1 file changed
+227
-4
+227
-4
@@ -10,13 +10,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"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/blockchain"
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"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
|
// forceStateTransition executes the necessary interaction between the two
|
||||||
@@ -2562,7 +2563,7 @@ func TestChanSyncFullySynced(t *testing.T) {
|
|||||||
assertNoChanSyncNeeded(t, aliceChannelNew, bobChannelNew)
|
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
|
// initialized instance. This simulates one party restarting and losing their
|
||||||
// in memory state.
|
// in memory state.
|
||||||
func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
|
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
|
// TestFeeUpdateRejectInsaneFee tests that if the initiator tries to attach a
|
||||||
// fee that would put them below their current reserve, then it's rejected by
|
// fee that would put them below their current reserve, then it's rejected by
|
||||||
// the state machine.
|
// the state machine.
|
||||||
|
|||||||
Reference in new issue
Block a user