From af68ff1640125a7e086ae6ac7da858c5b4ae6620 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 6 Mar 2020 16:11:46 +0100 Subject: [PATCH] lnwallet: add anchor commitmenttype With this commitment type, we'll add extra anchor outputs to the commitment transaction if the anchor channel type is active. --- channeldb/channel.go | 6 +- contractcourt/chain_watcher.go | 5 +- lnwallet/channel.go | 22 +++--- lnwallet/commitment.go | 130 +++++++++++++++++++++++++++++---- lnwallet/transactions_test.go | 2 +- lnwallet/wallet.go | 4 +- 6 files changed, 136 insertions(+), 33 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index 1ce43ad4..4b97caa6 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -338,13 +338,15 @@ type ChannelCommitment struct { // LocalBalance is the current available settled balance within the // channel directly spendable by us. // - // NOTE: This is the balance *after* subtracting any commitment fee. + // NOTE: This is the balance *after* subtracting any commitment fee, + // AND anchor output values. LocalBalance lnwire.MilliSatoshi // RemoteBalance is the current available settled balance within the // channel directly spendable by the remote node. // - // NOTE: This is the balance *after* subtracting any commitment fee. + // NOTE: This is the balance *after* subtracting any commitment fee, + // AND anchor output values. RemoteBalance lnwire.MilliSatoshi // CommitFee is the amount calculated to be paid in fees for the diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 3012376b..bbb3cbb1 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -351,9 +351,8 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig, // With the keys derived, we'll construct the remote script that'll be // present if they have a non-dust balance on the commitment. - remoteDelay := uint32(remoteChanCfg.CsvDelay) - remoteScript, err := lnwallet.CommitScriptToRemote( - chanType, remoteDelay, commitKeyRing.ToRemoteKey, + remoteScript, _, err := lnwallet.CommitScriptToRemote( + chanType, commitKeyRing.ToRemoteKey, ) if err != nil { return false, err diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 79722da8..b62985fc 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -503,7 +503,8 @@ type commitment struct { // evaluating all the add/remove/settle log entries before the listed // indexes. // - // NOTE: This is the balance *after* subtracting any commitment fee. + // NOTE: This is the balance *after* subtracting any commitment fee, + // AND anchor output values. ourBalance lnwire.MilliSatoshi theirBalance lnwire.MilliSatoshi @@ -2089,9 +2090,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // Since it is the remote breach we are reconstructing, the output going // to us will be a to-remote script with our local params. - ourDelay := uint32(chanState.LocalChanCfg.CsvDelay) - ourScript, err := CommitScriptToRemote( - chanState.ChanType, ourDelay, keyRing.ToRemoteKey, + ourScript, _, err := CommitScriptToRemote( + chanState.ChanType, keyRing.ToRemoteKey, ) if err != nil { return nil, err @@ -5027,8 +5027,8 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { } // CommitOutputResolution carries the necessary information required to allow -// us to sweep our direct commitment output in the case that either party goes -// to chain. +// us to sweep our commitment output in the case that either party goes to +// chain. type CommitOutputResolution struct { // SelfOutPoint is the full outpoint that points to out pay-to-self // output within the closing commitment transaction. @@ -5040,8 +5040,7 @@ type CommitOutputResolution struct { // MaturityDelay is the relative time-lock, in blocks for all outputs // that pay to the local party within the broadcast commitment - // transaction. This value will be non-zero iff, this output was on our - // commitment transaction. + // transaction. MaturityDelay uint32 } @@ -5122,9 +5121,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // Before we can generate the proper sign descriptor, we'll need to // locate the output index of our non-delayed output on the commitment // transaction. - localDelay := uint32(chanState.LocalChanCfg.CsvDelay) - selfScript, err := CommitScriptToRemote( - chanState.ChanType, localDelay, keyRing.ToRemoteKey, + selfScript, maturityDelay, err := CommitScriptToRemote( + chanState.ChanType, keyRing.ToRemoteKey, ) if err != nil { return nil, fmt.Errorf("unable to create self commit "+ @@ -5165,7 +5163,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si }, HashType: txscript.SigHashAll, }, - MaturityDelay: 0, + MaturityDelay: maturityDelay, } } diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 2f8b93ac..e3d98cb4 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -13,6 +13,9 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) +// anchorSize is the constant anchor output size. +const anchorSize = btcutil.Amount(330) + // CommitmentKeyRing holds all derived keys needed to construct commitment and // HTLC transactions. The keys are derived differently depending whether the // commitment transaction is ours or the remote peer's. Private keys associated @@ -183,13 +186,34 @@ type ScriptInfo struct { // CommitScriptToRemote creates the script that will pay to the non-owner of // the commitment transaction, adding a delay to the script based on the -// channel type. -func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32, - key *btcec.PublicKey) (*ScriptInfo, error) { +// channel type. The second return value is the CSV deleay of the output +// script, what must be satisfied in order to spend the output. +func CommitScriptToRemote(chanType channeldb.ChannelType, + key *btcec.PublicKey) (*ScriptInfo, uint32, error) { + // If this channel type has anchors, we derive the delayed to_remote + // script. + if chanType.HasAnchors() { + script, err := input.CommitScriptToRemoteConfirmed(key) + if err != nil { + return nil, 0, err + } + + p2wsh, err := input.WitnessScriptHash(script) + if err != nil { + return nil, 0, err + } + + return &ScriptInfo{ + PkScript: p2wsh, + WitnessScript: script, + }, 1, nil + } + + // Otherwise the to_remote will be a simple p2wkh. p2wkh, err := input.CommitScriptUnencumbered(key) if err != nil { - return nil, err + return nil, 0, err } // Since this is a regular P2WKH, the WitnessScipt and PkScript should @@ -197,7 +221,47 @@ func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32, return &ScriptInfo{ WitnessScript: p2wkh, PkScript: p2wkh, - }, nil + }, 0, nil +} + +// CommitScriptAnchors return the scripts to use for the local and remote +// anchor. +func CommitScriptAnchors(localChanCfg, + remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, + *ScriptInfo, error) { + + // Helper to create anchor ScriptInfo from key. + anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) { + script, err := input.CommitScriptAnchor(key) + if err != nil { + return nil, err + } + + scriptHash, err := input.WitnessScriptHash(script) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: scriptHash, + WitnessScript: script, + }, nil + } + + // Get the script used for the anchor output spendable by the local + // node. + localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey) + if err != nil { + return nil, nil, err + } + + // And the anchor spemdable by the remote node. + remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey) + if err != nil { + return nil, nil, err + } + + return localAnchor, remoteAnchor, nil } // CommitmentBuilder is a type that wraps the type of channel we are dealing @@ -216,6 +280,11 @@ type CommitmentBuilder struct { // NewCommitmentBuilder creates a new CommitmentBuilder from chanState. func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder { + // The anchor channel type MUST be tweakless. + if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() { + panic("invalid channel type combination") + } + return &CommitmentBuilder{ chanState: chanState, obfuscator: createStateHintObfuscator(chanState), @@ -248,8 +317,9 @@ type unsignedCommitmentTx struct { // fee is the total fee of the commitment transaction. fee btcutil.Amount - // ourBalance|theirBalance is the balances of this commitment. This can - // be different than the balances before creating the commitment + // ourBalance|theirBalance are the balances of this commitment *after* + // subtracting commitment fees and anchor outputs. This can be + // different than the balances before creating the commitment // transaction as one party must pay the commitment fee. ourBalance lnwire.MilliSatoshi theirBalance lnwire.MilliSatoshi @@ -258,7 +328,7 @@ type unsignedCommitmentTx struct { // createUnsignedCommitmentTx generates the unsigned commitment transaction for // a commitment view and returns it as part of the unsignedCommitmentTx. The // passed in balances should be balances *before* subtracting any commitment -// fees. +// fees, but after anchor outputs. func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, theirBalance lnwire.MilliSatoshi, isOurs bool, feePerKw chainfee.SatPerKWeight, height uint64, @@ -333,12 +403,14 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), + numHTLCs, ) } else { commitTx, err = CreateCommitTx( cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), + numHTLCs, ) } if err != nil { @@ -436,7 +508,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, func CreateCommitTx(chanType channeldb.ChannelType, fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) { + amountToLocal, amountToRemote btcutil.Amount, + numHTLCs int64) (*wire.MsgTx, error) { // First, we create the script for the delayed "pay-to-self" output. // This output has 2 main redemption clauses: either we can redeem the @@ -458,8 +531,8 @@ func CreateCommitTx(chanType channeldb.ChannelType, } // Next, we create the script paying to the remote. - toRemoteScript, err := CommitScriptToRemote( - chanType, uint32(remoteChanCfg.CsvDelay), keyRing.ToRemoteKey, + toRemoteScript, _, err := CommitScriptToRemote( + chanType, keyRing.ToRemoteKey, ) if err != nil { return nil, err @@ -472,19 +545,50 @@ func CreateCommitTx(chanType channeldb.ChannelType, commitTx.AddTxIn(&fundingOutput) // Avoid creating dust outputs within the commitment transaction. - if amountToLocal >= localChanCfg.DustLimit { + localOutput := amountToLocal >= localChanCfg.DustLimit + if localOutput { commitTx.AddTxOut(&wire.TxOut{ PkScript: toLocalScriptHash, Value: int64(amountToLocal), }) } - if amountToRemote >= localChanCfg.DustLimit { + + remoteOutput := amountToRemote >= localChanCfg.DustLimit + if remoteOutput { commitTx.AddTxOut(&wire.TxOut{ PkScript: toRemoteScript.PkScript, Value: int64(amountToRemote), }) } + // If this channel type has anchors, we'll also add those. + if chanType.HasAnchors() { + localAnchor, remoteAnchor, err := CommitScriptAnchors( + localChanCfg, remoteChanCfg, + ) + if err != nil { + return nil, err + } + + // Add local anchor output only if we have a commitment output + // or there are HTLCs. + if localOutput || numHTLCs > 0 { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: localAnchor.PkScript, + Value: int64(anchorSize), + }) + } + + // Add anchor output to remote only if they have a commitment + // output or there are HTLCs. + if remoteOutput || numHTLCs > 0 { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: remoteAnchor.PkScript, + Value: int64(anchorSize), + }) + } + } + return commitTx, nil } diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index db5a8dce..7a3f08a2 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -1090,7 +1090,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { } commitmentTx, err := CreateCommitTx( channelType, *fakeFundingTxIn, keyRing, aliceChanCfg, - bobChanCfg, channelBalance, channelBalance, + bobChanCfg, channelBalance, channelBalance, 0, ) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 5143b9f5..5649b652 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -784,7 +784,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourCommitTx, err := CreateCommitTx( chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, - theirChanCfg, localBalance, remoteBalance, + theirChanCfg, localBalance, remoteBalance, 0, ) if err != nil { return nil, nil, err @@ -797,7 +797,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, theirCommitTx, err := CreateCommitTx( chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg, - ourChanCfg, remoteBalance, localBalance, + ourChanCfg, remoteBalance, localBalance, 0, ) if err != nil { return nil, nil, err