From dc6c4637b6f52e58afae6291c4400bbdf34ca7b3 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 13 Dec 2019 11:14:22 +0100 Subject: [PATCH] lnwallet+channeldb: add anchor resolutions Co-authored-by: Joost Jager --- lnwallet/channel.go | 139 +++++++++++++++++++++++++++++++++++++++ lnwallet/channel_test.go | 63 +++++++++++++++++- 2 files changed, 199 insertions(+), 3 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index aca93b99..6f64d34d 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5115,6 +5115,11 @@ type UnilateralCloseSummary struct { // RemoteCommit is the exact commitment state that the remote party // broadcast. RemoteCommit channeldb.ChannelCommitment + + // AnchorResolution contains the data required to sweep our anchor + // output. If the channel type doesn't include anchors, the value of + // this field will be nil. + AnchorResolution *AnchorResolution } // NewUnilateralCloseSummary creates a new summary that provides the caller @@ -5229,12 +5234,20 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si closeSummary.LastChanSyncMsg = chanSync } + anchorResolution, err := NewAnchorResolution( + chanState, commitTxBroadcast, + ) + if err != nil { + return nil, err + } + return &UnilateralCloseSummary{ SpendDetail: commitSpend, ChannelCloseSummary: closeSummary, CommitResolution: commitResolution, HtlcResolutions: htlcResolutions, RemoteCommit: remoteCommit, + AnchorResolution: anchorResolution, }, nil } @@ -5685,6 +5698,16 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, }, nil } +// AnchorResolution holds the information necessary to spend our commitment tx +// anchor. +type AnchorResolution struct { + // AnchorSignDescriptor is the sign descriptor for our anchor. + AnchorSignDescriptor input.SignDescriptor + + // CommitAnchor is the anchor outpoint on the commit tx. + CommitAnchor wire.OutPoint +} + // LocalForceCloseSummary describes the final commitment state before the // channel is locked-down to initiate a force closure by broadcasting the // latest state on-chain. If we intend to broadcast this this state, the @@ -5717,6 +5740,11 @@ type LocalForceCloseSummary struct { // ChanSnapshot is a snapshot of the final state of the channel at the // time the summary was created. ChanSnapshot channeldb.ChannelSnapshot + + // AnchorResolution contains the data required to sweep the anchor + // output. If the channel type doesn't include anchors, the value of + // this field will be nil. + AnchorResolution *AnchorResolution } // ForceClose executes a unilateral closure of the transaction at the current @@ -5855,12 +5883,20 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si return nil, err } + anchorResolution, err := NewAnchorResolution( + chanState, commitTx, + ) + if err != nil { + return nil, err + } + return &LocalForceCloseSummary{ ChanPoint: chanState.FundingOutpoint, CloseTx: commitTx, CommitResolution: commitResolution, HtlcResolutions: htlcResolutions, ChanSnapshot: *chanState.Snapshot(), + AnchorResolution: anchorResolution, }, nil } @@ -6019,6 +6055,109 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte, return closeTx, ourBalance, nil } +// NewAnchorResolutions returns the anchor resolutions for all currently valid +// commitment transactions. Because we have no view on the mempool, we can only +// blindly anchor all of these txes down. +func (lc *LightningChannel) NewAnchorResolutions() ([]*AnchorResolution, + error) { + + lc.Lock() + defer lc.Unlock() + + var resolutions []*AnchorResolution + + // Add anchor for local commitment tx, if any. + localRes, err := NewAnchorResolution( + lc.channelState, lc.channelState.LocalCommitment.CommitTx, + ) + if err != nil { + return nil, err + } + if localRes != nil { + resolutions = append(resolutions, localRes) + } + + // Add anchor for remote commitment tx, if any. + remoteRes, err := NewAnchorResolution( + lc.channelState, lc.channelState.RemoteCommitment.CommitTx, + ) + if err != nil { + return nil, err + } + if remoteRes != nil { + resolutions = append(resolutions, remoteRes) + } + + // Add anchor for remote pending commitment tx, if any. + remotePendingCommit, err := lc.channelState.RemoteCommitChainTip() + if err != nil && err != channeldb.ErrNoPendingCommit { + return nil, err + } + + if remotePendingCommit != nil { + remotePendingRes, err := NewAnchorResolution( + lc.channelState, + remotePendingCommit.Commitment.CommitTx, + ) + if err != nil { + return nil, err + } + + if remotePendingRes != nil { + resolutions = append(resolutions, remotePendingRes) + } + } + + return resolutions, nil +} + +// NewAnchorResolution returns the information that is required to sweep the +// local anchor. +func NewAnchorResolution(chanState *channeldb.OpenChannel, + commitTx *wire.MsgTx) (*AnchorResolution, error) { + + // Return nil resolution if the channel has no anchors. + if !chanState.ChanType.HasAnchors() { + return nil, nil + } + + // Derive our local anchor script. + localAnchor, _, err := CommitScriptAnchors( + &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + ) + if err != nil { + return nil, err + } + + // Look up the script on the commitment transaction. It may not be + // present if there is no output paying to us. + found, index := input.FindScriptOutputIndex(commitTx, localAnchor.PkScript) + if !found { + return nil, nil + } + + outPoint := &wire.OutPoint{ + Hash: commitTx.TxHash(), + Index: index, + } + + // Instantiate the sign descriptor that allows sweeping of the anchor. + signDesc := &input.SignDescriptor{ + KeyDesc: chanState.LocalChanCfg.MultiSigKey, + WitnessScript: localAnchor.WitnessScript, + Output: &wire.TxOut{ + PkScript: localAnchor.PkScript, + Value: int64(anchorSize), + }, + HashType: txscript.SigHashAll, + } + + return &AnchorResolution{ + CommitAnchor: *outPoint, + AnchorSignDescriptor: *signDesc, + }, nil +} + // AvailableBalance returns the current balance available for sending within // the channel. By available balance, we mean that if at this very instance a // new commitment were to be created which evals all the log entries, what diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 1b1ac942..47355f57 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -526,13 +526,36 @@ func TestCooperativeChannelClosure(t *testing.T) { // force close generates HTLC resolutions that are capable of sweeping both // incoming and outgoing HTLC's. func TestForceClose(t *testing.T) { + t.Run("tweakless", func(t *testing.T) { + testForceClose(t, &forceCloseTestCase{ + chanType: channeldb.SingleFunderTweaklessBit, + expectedCommitWeight: input.CommitWeight, + }) + }) + t.Run("anchors", func(t *testing.T) { + testForceClose(t, &forceCloseTestCase{ + chanType: channeldb.SingleFunderTweaklessBit | + channeldb.AnchorOutputsBit, + expectedCommitWeight: input.AnchorCommitWeight, + anchorAmt: anchorSize * 2, + }) + }) +} + +type forceCloseTestCase struct { + chanType channeldb.ChannelType + expectedCommitWeight int64 + anchorAmt btcutil.Amount +} + +func testForceClose(t *testing.T, testCase *forceCloseTestCase) { 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( - channeldb.SingleFunderTweaklessBit, + testCase.chanType, ) if err != nil { t.Fatalf("unable to create test channels: %v", err) @@ -594,6 +617,36 @@ func TestForceClose(t *testing.T) { 1, len(closeSummary.HtlcResolutions.IncomingHTLCs)) } + // Verify the anchor resolutions for the anchor commitment format. + if testCase.chanType.HasAnchors() { + // Check the close summary resolution. + anchorRes := closeSummary.AnchorResolution + if anchorRes == nil { + t.Fatal("expected anchor resolution") + } + if anchorRes.CommitAnchor.Hash != closeSummary.CloseTx.TxHash() { + t.Fatal("commit tx not referenced by anchor res") + } + if anchorRes.AnchorSignDescriptor.Output.Value != + int64(anchorSize) { + + t.Fatal("unexpected anchor size") + } + if anchorRes.AnchorSignDescriptor.WitnessScript == nil { + t.Fatal("expected anchor witness script") + } + + // Check the pre-confirmation resolutions. + resList, err := aliceChannel.NewAnchorResolutions() + if err != nil { + t.Fatalf("pre-confirmation resolution error: %v", err) + } + + if len(resList) != 2 { + t.Fatal("expected two resolutions") + } + } + // The SelfOutputSignDesc should be non-nil since the output to-self is // non-dust. aliceCommitResolution := closeSummary.CommitResolution @@ -612,12 +665,16 @@ func TestForceClose(t *testing.T) { // Factoring in the fee rate, Alice's amount should properly reflect // that we've added two additional HTLC to the commitment transaction. - totalCommitWeight := int64(input.CommitWeight + (input.HTLCWeight * 2)) + totalCommitWeight := testCase.expectedCommitWeight + + (input.HTLCWeight * 2) feePerKw := chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) commitFee := feePerKw.FeeForWeight(totalCommitWeight) - expectedAmount := (aliceChannel.Capacity / 2) - htlcAmount.ToSatoshis() - commitFee + + expectedAmount := (aliceChannel.Capacity / 2) - + htlcAmount.ToSatoshis() - commitFee - testCase.anchorAmt + if aliceCommitResolution.SelfOutputSignDesc.Output.Value != int64(expectedAmount) { t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+ "expected %v, got %v", int64(expectedAmount),