diff --git a/channeldb/channel.go b/channeldb/channel.go index b11e09e88..a16fa1c16 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/coreos/bbolt" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" @@ -810,6 +811,85 @@ func (c *OpenChannel) MarkBorked() error { return c.putChanStatus(ChanStatusBorked) } +// ChanSyncMsg returns the ChannelReestablish message that should be sent upon +// reconnection with the remote peer that we're maintaining this channel with. +// The information contained within this message is necessary to re-sync our +// commitment chains in the case of a last or only partially processed message. +// When the remote party receiver this message one of three things may happen: +// +// 1. We're fully synced and no messages need to be sent. +// 2. We didn't get the last CommitSig message they sent, to they'll re-send +// it. +// 3. We didn't get the last RevokeAndAck message they sent, so they'll +// re-send it. +// +// The isRestoredChan bool indicates if we need to craft a chan sync message +// for a channel that's been restored. If this is a restored channel, then +// we'll modify our typical chan sync message to ensure they force close even +// if we're on the very first state. +func (c *OpenChannel) ChanSyncMsg( + isRestoredChan bool) (*lnwire.ChannelReestablish, error) { + + c.Lock() + defer c.Unlock() + + // The remote commitment height that we'll send in the + // ChannelReestablish message is our current commitment height plus + // one. If the receiver thinks that our commitment height is actually + // *equal* to this value, then they'll re-send the last commitment that + // they sent but we never fully processed. + localHeight := c.LocalCommitment.CommitHeight + nextLocalCommitHeight := localHeight + 1 + + // The second value we'll send is the height of the remote commitment + // from our PoV. If the receiver thinks that their height is actually + // *one plus* this value, then they'll re-send their last revocation. + remoteChainTipHeight := c.RemoteCommitment.CommitHeight + + // If this channel has undergone a commitment update, then in order to + // prove to the remote party our knowledge of their prior commitment + // state, we'll also send over the last commitment secret that the + // remote party sent. + var lastCommitSecret [32]byte + if remoteChainTipHeight != 0 { + remoteSecret, err := c.RevocationStore.LookUp( + remoteChainTipHeight - 1, + ) + if err != nil { + return nil, err + } + lastCommitSecret = [32]byte(*remoteSecret) + } + + // Additionally, we'll send over the current unrevoked commitment on + // our local commitment transaction. + currentCommitSecret, err := c.RevocationProducer.AtIndex( + localHeight, + ) + if err != nil { + return nil, err + } + + // If we've restored this channel, then we'll purposefully give them an + // invalid LocalUnrevokedCommitPoint so they'll force close the channel + // allowing us to sweep our funds. + if isRestoredChan { + currentCommitSecret[0] ^= 1 + } + + return &lnwire.ChannelReestablish{ + ChanID: lnwire.NewChanIDFromOutPoint( + &c.FundingOutpoint, + ), + NextLocalCommitHeight: nextLocalCommitHeight, + RemoteCommitTailHeight: remoteChainTipHeight, + LastRemoteCommitSecret: lastCommitSecret, + LocalUnrevokedCommitPoint: input.ComputeCommitmentPoint( + currentCommitSecret[:], + ), + }, nil +} + // isBorked returns true if the channel has been marked as borked in the // database. This requires an existing database transaction to already be // active. diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 8b17e75a3..c7ba2323e 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -731,8 +731,7 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet } // Attempt to add a channel sync message to the close summary. - chanSync, err := lnwallet.ChanSyncMsg( - c.cfg.chanState, + chanSync, err := c.cfg.chanState.ChanSyncMsg( c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored), ) if err != nil { @@ -811,8 +810,7 @@ func (c *chainWatcher) dispatchLocalForceClose( } // Attempt to add a channel sync message to the close summary. - chanSync, err := lnwallet.ChanSyncMsg( - c.cfg.chanState, + chanSync, err := c.cfg.chanState.ChanSyncMsg( c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored), ) if err != nil { @@ -998,8 +996,7 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail } // Attempt to add a channel sync message to the close summary. - chanSync, err := lnwallet.ChanSyncMsg( - c.cfg.chanState, + chanSync, err := c.cfg.chanState.ChanSyncMsg( c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored), ) if err != nil { diff --git a/htlcswitch/link.go b/htlcswitch/link.go index c4603d4b7..c7ef3800b 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -609,8 +609,7 @@ func (l *channelLink) syncChanStates() error { // side. Based on this message, the remote party will decide if they // need to retransmit any data or not. chanState := l.channel.State() - localChanSyncMsg, err := lnwallet.ChanSyncMsg( - chanState, + localChanSyncMsg, err := chanState.ChanSyncMsg( chanState.HasChanStatus(channeldb.ChanStatusRestored), ) if err != nil { diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 99f3e6409..3e6ef764c 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3527,85 +3527,6 @@ func (lc *LightningChannel) ProcessChanSyncMsg( return updates, openedCircuits, closedCircuits, nil } -// ChanSyncMsg returns the ChannelReestablish message that should be sent upon -// reconnection with the remote peer that we're maintaining this channel with. -// The information contained within this message is necessary to re-sync our -// commitment chains in the case of a last or only partially processed message. -// When the remote party receiver this message one of three things may happen: -// -// 1. We're fully synced and no messages need to be sent. -// 2. We didn't get the last CommitSig message they sent, to they'll re-send -// it. -// 3. We didn't get the last RevokeAndAck message they sent, so they'll -// re-send it. -// -// The isRestoredChan bool indicates if we need to craft a chan sync message -// for a channel that's been restored. If this is a restored channel, then -// we'll modify our typical chan sync message to ensure they force close even -// if we're on the very first state. -func ChanSyncMsg(c *channeldb.OpenChannel, - isRestoredChan bool) (*lnwire.ChannelReestablish, error) { - - c.Lock() - defer c.Unlock() - - // The remote commitment height that we'll send in the - // ChannelReestablish message is our current commitment height plus - // one. If the receiver thinks that our commitment height is actually - // *equal* to this value, then they'll re-send the last commitment that - // they sent but we never fully processed. - localHeight := c.LocalCommitment.CommitHeight - nextLocalCommitHeight := localHeight + 1 - - // The second value we'll send is the height of the remote commitment - // from our PoV. If the receiver thinks that their height is actually - // *one plus* this value, then they'll re-send their last revocation. - remoteChainTipHeight := c.RemoteCommitment.CommitHeight - - // If this channel has undergone a commitment update, then in order to - // prove to the remote party our knowledge of their prior commitment - // state, we'll also send over the last commitment secret that the - // remote party sent. - var lastCommitSecret [32]byte - if remoteChainTipHeight != 0 { - remoteSecret, err := c.RevocationStore.LookUp( - remoteChainTipHeight - 1, - ) - if err != nil { - return nil, err - } - lastCommitSecret = [32]byte(*remoteSecret) - } - - // Additionally, we'll send over the current unrevoked commitment on - // our local commitment transaction. - currentCommitSecret, err := c.RevocationProducer.AtIndex( - localHeight, - ) - if err != nil { - return nil, err - } - - // If we've restored this channel, then we'll purposefully give them an - // invalid LocalUnrevokedCommitPoint so they'll force close the channel - // allowing us to sweep our funds. - if isRestoredChan { - currentCommitSecret[0] ^= 1 - } - - return &lnwire.ChannelReestablish{ - ChanID: lnwire.NewChanIDFromOutPoint( - &c.FundingOutpoint, - ), - NextLocalCommitHeight: nextLocalCommitHeight, - RemoteCommitTailHeight: remoteChainTipHeight, - LastRemoteCommitSecret: lastCommitSecret, - LocalUnrevokedCommitPoint: input.ComputeCommitmentPoint( - currentCommitSecret[:], - ), - }, nil -} - // computeView takes the given htlcView, and calculates the balances, filtered // view (settling unsettled HTLCs), commitment weight and feePerKw, after // applying the HTLCs to the latest commitment. The returned balances are the @@ -5187,8 +5108,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si } // Attempt to add a channel sync message to the close summary. - chanSync, err := ChanSyncMsg( - chanState, + chanSync, err := chanState.ChanSyncMsg( chanState.HasChanStatus(channeldb.ChanStatusRestored), ) if err != nil { diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 697020913..81077862b 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -2530,7 +2530,7 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel, _, _, line, _ := runtime.Caller(1) - aliceChanSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceChanSyncMsg, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("line #%v: unable to produce chan sync msg: %v", line, err) @@ -2545,7 +2545,7 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel, "instead wants to send: %v", line, spew.Sdump(bobMsgsToSend)) } - bobChanSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobChanSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("line #%v: unable to produce chan sync msg: %v", line, err) @@ -2778,11 +2778,11 @@ func TestChanSyncOweCommitment(t *testing.T) { // Bob doesn't get this message so upon reconnection, they need to // synchronize. Alice should conclude that she owes Bob a commitment, // while Bob should think he's properly synchronized. - aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceSyncMsg, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } - bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3092,11 +3092,11 @@ func TestChanSyncOweRevocation(t *testing.T) { // If we fetch the channel sync messages at this state, then Alice // should report that she owes Bob a revocation message, while Bob // thinks they're fully in sync. - aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceSyncMsg, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } - bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3261,11 +3261,11 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // If we now attempt to resync, then Alice should conclude that she // doesn't need any further updates, while Bob concludes that he needs // to re-send both his revocation and commit sig message. - aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceSyncMsg, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } - bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3472,11 +3472,11 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // Now if we attempt to synchronize states at this point, Alice should // detect that she owes nothing, while Bob should re-send both his // RevokeAndAck as well as his commitment message. - aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceSyncMsg, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } - bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3677,11 +3677,11 @@ func TestChanSyncFailure(t *testing.T) { assertLocalDataLoss := func(aliceOld *LightningChannel) { t.Helper() - aliceSyncMsg, err := ChanSyncMsg(aliceOld.channelState, false) + aliceSyncMsg, err := aliceOld.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } - bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3755,7 +3755,7 @@ func TestChanSyncFailure(t *testing.T) { // 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 := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3769,7 +3769,7 @@ func TestChanSyncFailure(t *testing.T) { // 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 = ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3782,7 +3782,7 @@ func TestChanSyncFailure(t *testing.T) { // If Bob's NextLocalCommitHeight is lower than what Alice expects, Bob // probably lost state. - bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3795,7 +3795,7 @@ func TestChanSyncFailure(t *testing.T) { // If Alice and Bob's states are in sync, but Bob is sending the wrong // LocalUnrevokedCommitPoint, Alice should detect this. - bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3824,7 +3824,7 @@ func TestChanSyncFailure(t *testing.T) { // when there's a pending remote commit. halfAdvance() - bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -3912,11 +3912,11 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // Bob doesn't get this message so upon reconnection, they need to // synchronize. Alice should conclude that she owes Bob a commitment, // while Bob should think he's properly synchronized. - aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceSyncMsg, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } - bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false) + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } @@ -4361,11 +4361,11 @@ func TestChanSyncInvalidLastSecret(t *testing.T) { } // Next, we'll produce the ChanSync messages for both parties. - aliceChanSync, err := ChanSyncMsg(aliceChannel.channelState, false) + aliceChanSync, err := aliceChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to generate chan sync msg: %v", err) } - bobChanSync, err := ChanSyncMsg(bobChannel.channelState, false) + bobChanSync, err := bobChannel.channelState.ChanSyncMsg(false) if err != nil { t.Fatalf("unable to generate chan sync msg: %v", err) }