diff --git a/contractcourt/breacharbiter.go b/contractcourt/breacharbiter.go index b5f7583cc..c6fc6c94e 100644 --- a/contractcourt/breacharbiter.go +++ b/contractcourt/breacharbiter.go @@ -1347,9 +1347,9 @@ func (b *BreachArbiter) createSweepTx(inputs []input.Input) (*wire.MsgTx, spendableOutputs = make([]input.Input, 0, len(inputs)) // The justice transaction we construct will be a segwit transaction - // that pays to a p2wkh output. Components such as the version, + // that pays to a p2tr output. Components such as the version, // nLockTime, and output are already included in the TxWeightEstimator. - weightEstimate.AddP2WKHOutput() + weightEstimate.AddP2TROutput() // Next, we iterate over the breached outputs contained in the // retribution info. For each, we switch over the witness type such diff --git a/docs/release-notes/release-notes-0.15.1.md b/docs/release-notes/release-notes-0.15.1.md index 15dfef0f4..66da8a210 100644 --- a/docs/release-notes/release-notes-0.15.1.md +++ b/docs/release-notes/release-notes-0.15.1.md @@ -21,12 +21,15 @@ ## Taproot -[`lnd` will now refuse to start if it detects the full node backned does not +[`lnd` will now refuse to start if it detects the full node backend does not support Tapoot](https://github.com/lightningnetwork/lnd/pull/6798). [`lnd` will now use taproot addresses for co-op closes if the remote peer supports the feature.](https://github.com/lightningnetwork/lnd/pull/6633) +The [wallet also creates P2TR change addresses by +default](https://github.com/lightningnetwork/lnd/pull/6810) in most cases. + ## `lncli` * [Add `payment_addr` flag to diff --git a/lnd.go b/lnd.go index eb57995d2..88ec75be9 100644 --- a/lnd.go +++ b/lnd.go @@ -458,7 +458,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, Net: cfg.net, NewAddress: func() (btcutil.Address, error) { return activeChainControl.Wallet.NewAddress( - lnwallet.WitnessPubKey, false, + lnwallet.TaprootPubkey, false, lnwallet.DefaultAccountName, ) }, diff --git a/lnwallet/chanfunding/coin_select.go b/lnwallet/chanfunding/coin_select.go index 761f7c032..049d94cdd 100644 --- a/lnwallet/chanfunding/coin_select.go +++ b/lnwallet/chanfunding/coin_select.go @@ -98,8 +98,8 @@ func calculateFees(utxos []Coin, feeRate chainfee.SatPerKWeight) (btcutil.Amount requiredFeeNoChange := feeRate.FeeForWeight(totalWeight) // Estimate the fee required for a transaction with a change output. - // Assume that change output is a P2WKH output. - weightEstimate.AddP2WKHOutput() + // Assume that change output is a P2TR output. + weightEstimate.AddP2TROutput() // Now that we have added the change output, redo the fee // estimate. @@ -209,7 +209,8 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt, // Obtain fee estimates both with and without using a change // output. requiredFeeNoChange, requiredFeeWithChange, err := calculateFees( - selectedUtxos, feeRate) + selectedUtxos, feeRate, + ) if err != nil { return nil, 0, 0, err } diff --git a/lnwallet/chanfunding/coin_select_test.go b/lnwallet/chanfunding/coin_select_test.go index 0f3d4ef4b..d243cd358 100644 --- a/lnwallet/chanfunding/coin_select_test.go +++ b/lnwallet/chanfunding/coin_select_test.go @@ -44,7 +44,7 @@ func fundingFee(feeRate chainfee.SatPerKWeight, numInput int, // nolint:unparam // Optionally count a change output. if change { - weightEstimate.AddP2WKHOutput() + weightEstimate.AddP2TROutput() } totalWeight := int64(weightEstimate.Weight()) @@ -81,7 +81,7 @@ func TestCalculateFees(t *testing.T) { }, expectedFeeNoChange: 487, - expectedFeeWithChange: 611, + expectedFeeWithChange: 659, expectedErr: nil, }, @@ -97,7 +97,7 @@ func TestCalculateFees(t *testing.T) { }, expectedFeeNoChange: 579, - expectedFeeWithChange: 703, + expectedFeeWithChange: 751, expectedErr: nil, }, diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index 376360901..6d9c1974e 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -21,7 +21,7 @@ import ( // // Steps to final channel provisioning: // 1. Call BindKeys to notify the intent which keys to use when constructing -// the multi-sig output. +// the multi-sig output. // 2. Call CompileFundingTx afterwards to obtain the funding transaction. // // If either of these steps fail, then the Cancel method MUST be called. diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index dd741fd4c..ebaf91e8d 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -2708,6 +2708,14 @@ var walletTests = []walletTestCase{ name: "change output spend confirmation", test: testChangeOutputSpendConfirmation, }, + { + // TODO(guggero): this test should remain second until dual + // funding can properly exchange full UTXO information and we + // can use P2TR change outputs as the funding inputs for a dual + // funded channel. + name: "dual funder workflow", + test: testDualFundingReservationWorkflow, + }, { name: "spend unconfirmed outputs", test: testSpendUnconfirmed, @@ -2744,10 +2752,6 @@ var walletTests = []walletTestCase{ name: "single funding workflow external funding tx", test: testSingleFunderExternalFundingTx, }, - { - name: "dual funder workflow", - test: testDualFundingReservationWorkflow, - }, { name: "output locking", test: testFundingTransactionLockedOutputs, diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 8ee8cabc6..49c6410be 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -775,7 +775,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg FeeRate: req.FundingFeePerKw, ChangeAddr: func() (btcutil.Address, error) { return l.NewAddress( - WitnessPubKey, true, DefaultAccountName, + TaprootPubkey, true, DefaultAccountName, ) }, } diff --git a/rpcserver.go b/rpcserver.go index 01c9c9e15..a0358cb17 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1330,7 +1330,7 @@ func (r *rpcServer) SendCoins(ctx context.Context, // ensures this is an address the wallet knows about, // allowing us to pass the reserved value check. changeAddr, err := r.server.cc.Wallet.NewAddress( - lnwallet.WitnessPubKey, true, + lnwallet.TaprootPubkey, true, lnwallet.DefaultAccountName, ) if err != nil { diff --git a/server.go b/server.go index 0d2adc7b8..cf8e8a52b 100644 --- a/server.go +++ b/server.go @@ -4534,7 +4534,8 @@ func newSweepPkScriptGen( return func() ([]byte, error) { sweepAddr, err := wallet.NewAddress( - lnwallet.WitnessPubKey, false, lnwallet.DefaultAccountName, + lnwallet.TaprootPubkey, false, + lnwallet.DefaultAccountName, ) if err != nil { return nil, err diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index 9dc48901c..576471032 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -139,7 +139,7 @@ func newTxInputSet(wallet Wallet, feePerKW chainfee.SatPerKWeight, func (t *txInputSet) enoughInput() bool { // If we have a change output above dust, then we certainly have enough // inputs to the transaction. - if t.changeOutput >= lnwallet.DustLimitForSize(input.P2WPKHSize) { + if t.changeOutput >= lnwallet.DustLimitForSize(input.P2TRSize) { return true } diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 3b07f2d64..20e80a00c 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -111,8 +111,8 @@ func generateInputPartitionings(sweepableInputs []txInput, // continuing with the remaining inputs will only lead to sets // with an even lower output value. if !txInputs.enoughInput() { - // The change output is always a p2wpkh here. - dl := lnwallet.DustLimitForSize(input.P2WPKHSize) + // The change output is always a p2tr here. + dl := lnwallet.DustLimitForSize(input.P2TRSize) log.Debugf("Set value %v (r=%v, c=%v) below dust "+ "limit of %v", txInputs.totalOutput(), txInputs.requiredOutput, txInputs.changeOutput, diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index dac18b048..a474d5ccb 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" @@ -228,8 +229,8 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, i := inputIndex[inp.outPoint] justiceTxn.TxIn[i].Witness = inp.witness - // Validate the reconstructed witnesses to ensure they are valid - // for the breached inputs. + // Validate the reconstructed witnesses to ensure they are + // valid for the breached inputs. vm, err := txscript.NewEngine( inp.txOut.PkScript, justiceTxn, i, txscript.StandardVerifyFlags, @@ -239,7 +240,9 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, return nil, err } if err := vm.Execute(); err != nil { - return nil, err + log.Debugf("Failed to validate justice transaction: %s", + spew.Sdump(justiceTxn)) + return nil, fmt.Errorf("error validating TX: %v", err) } } @@ -300,6 +303,9 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { sweepInputs = append(sweepInputs, toLocalInput) + log.Debugf("Found to local witness output=%#v, stack=%v", + toLocalInput.txOut, toLocalInput.witness) + // If the justice kit specifies that we have to sweep the to-remote // output, we'll also try to assemble the output and add it to weight // estimate if successful. @@ -310,6 +316,9 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { } sweepInputs = append(sweepInputs, toRemoteInput) + log.Debugf("Found to remote witness output=%#v, stack=%v", + toRemoteInput.txOut, toRemoteInput.witness) + if p.JusticeKit.BlobType.IsAnchorChannel() { weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize) } else { diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index 5ac179c0a..a72689303 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" @@ -151,6 +152,42 @@ func (t *backupTask) inputs() map[wire.OutPoint]input.Input { return inputs } +// addrType returns the type of an address after parsing it and matching it to +// the set of known script templates. +func addrType(pkScript []byte) txscript.ScriptClass { + // We pass in a set of dummy chain params here as they're only needed + // to make the address struct, which we're ignoring anyway (scripts are + // always the same, it's addresses that change across chains). + scriptClass, _, _, _ := txscript.ExtractPkScriptAddrs( + pkScript, &chaincfg.MainNetParams, + ) + + return scriptClass +} + +// addScriptWeight parses the passed pkScript and adds the computed weight cost +// were the script to be added to the justice transaction. +func addScriptWeight(weightEstimate *input.TxWeightEstimator, + pkScript []byte) error { + + switch addrType(pkScript) { //nolint: whitespace + + case txscript.WitnessV0PubKeyHashTy: + weightEstimate.AddP2WKHOutput() + + case txscript.WitnessV0ScriptHashTy: + weightEstimate.AddP2WSHOutput() + + case txscript.WitnessV1TaprootTy: + weightEstimate.AddP2TROutput() + + default: + return fmt.Errorf("invalid addr type: %v", addrType(pkScript)) + } + + return nil +} + // bindSession determines if the backupTask is compatible with the passed // SessionInfo's policy. If no error is returned, the task has been bound to the // session and can be queued to upload to the tower. Otherwise, the bind failed @@ -192,13 +229,19 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error { } } - // All justice transactions have a p2wkh output paying to the victim. - weightEstimate.AddP2WKHOutput() + // All justice transactions will either use segwit v0 (p2wkh + p2wsh) + // or segwit v1 (p2tr). + if err := addScriptWeight(&weightEstimate, t.sweepPkScript); err != nil { + return err + } // If the justice transaction has a reward output, add the output's // contribution to the weight estimate. if session.Policy.BlobType.Has(blob.FlagReward) { - weightEstimate.AddP2WKHOutput() + err := addScriptWeight(&weightEstimate, session.RewardPkScript) + if err != nil { + return err + } } if t.chanType.HasAnchors() != session.Policy.IsAnchorChannel() { diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index 9c0a3e20e..7d3178f3e 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -247,7 +247,7 @@ func genTaskTest( RewardPkScript: rewardScript, }, bindErr: bindErr, - expSweepScript: makeAddrSlice(22), + expSweepScript: sweepAddr, signer: signer, chanType: chanType, } @@ -259,10 +259,18 @@ var ( blobTypeCommitReward = (blob.FlagCommitOutputs | blob.FlagReward).Type() addr, _ = btcutil.DecodeAddress( - "mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", &chaincfg.TestNet3Params, + "tb1pw8gzj8clt3v5lxykpgacpju5n8xteskt7gxhmudu6pa70nwfhe6s3unsyk", + &chaincfg.TestNet3Params, ) addrScript, _ = txscript.PayToAddrScript(addr) + + sweepAddrScript, _ = btcutil.DecodeAddress( + "tb1qs3jyc9sf5kak3x0w99cav9u605aeu3t600xxx0", + &chaincfg.TestNet3Params, + ) + + sweepAddr, _ = txscript.PayToAddrScript(sweepAddrScript) ) // TestBackupTaskBind tests the initialization and binding of a backupTask to a @@ -297,9 +305,9 @@ func TestBackupTask(t *testing.T) { expSweepCommitNoRewardBoth int64 = 299241 expSweepCommitNoRewardLocal int64 = 199514 expSweepCommitNoRewardRemote int64 = 99561 - expSweepCommitRewardBoth int64 = 296117 - expSweepCommitRewardLocal int64 = 197390 - expSweepCommitRewardRemote int64 = 98437 + expSweepCommitRewardBoth int64 = 296069 + expSweepCommitRewardLocal int64 = 197342 + expSweepCommitRewardRemote int64 = 98389 sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500 sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175350 ) @@ -307,9 +315,9 @@ func TestBackupTask(t *testing.T) { expSweepCommitNoRewardBoth = 299236 expSweepCommitNoRewardLocal = 199513 expSweepCommitNoRewardRemote = 99557 - expSweepCommitRewardBoth = 296112 - expSweepCommitRewardLocal = 197389 - expSweepCommitRewardRemote = 98433 + expSweepCommitRewardBoth = 296064 + expSweepCommitRewardLocal = 197341 + expSweepCommitRewardRemote = 98385 sweepFeeRateNoRewardRemoteDust = 225400 sweepFeeRateRewardRemoteDust = 174100 } @@ -436,7 +444,7 @@ func TestBackupTask(t *testing.T) { "commit reward, to-remote output only, creates dust", 1, // stateNum 0, // toLocalAmt - 100000, // toRemoteAmt + 108221, // toRemoteAmt blobTypeCommitReward, // blobType sweepFeeRateRewardRemoteDust, // sweepFeeRate addrScript, // rewardScript diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index 83bb27ff7..bba12895c 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -58,7 +58,8 @@ var ( // addr is the server's reward address given to watchtower clients. addr, _ = btcutil.DecodeAddress( - "mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", &chaincfg.TestNet3Params, + "tb1pw8gzj8clt3v5lxykpgacpju5n8xteskt7gxhmudu6pa70nwfhe6s3unsyk", + &chaincfg.TestNet3Params, ) addrScript, _ = txscript.PayToAddrScript(addr) diff --git a/watchtower/wtpolicy/policy.go b/watchtower/wtpolicy/policy.go index 6eb88657b..429f1ffb6 100644 --- a/watchtower/wtpolicy/policy.go +++ b/watchtower/wtpolicy/policy.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/watchtower/blob" @@ -156,7 +155,7 @@ func (p Policy) Validate() error { // of the justice transaction and subtracting an amount that satisfies the // policy's fee rate. func (p *Policy) ComputeAltruistOutput(totalAmt btcutil.Amount, - txWeight int64) (btcutil.Amount, error) { + txWeight int64, sweepScript []byte) (btcutil.Amount, error) { txFee := p.SweepFeeRate.FeeForWeight(txWeight) if txFee > totalAmt { @@ -165,10 +164,9 @@ func (p *Policy) ComputeAltruistOutput(totalAmt btcutil.Amount, sweepAmt := totalAmt - txFee - // TODO(conner): replace w/ configurable dust limit - // Check that the created outputs won't be dusty. The sweep pkscript is - // currently a p2wpkh, so we'll use that script's dust limit. - if sweepAmt < lnwallet.DustLimitForSize(input.P2WPKHSize) { + // Check that the created outputs won't be dusty. We'll base the dust + // computation on the type of the script itself. + if sweepAmt < lnwallet.DustLimitForSize(len(sweepScript)) { return 0, ErrCreatesDust } @@ -180,7 +178,8 @@ func (p *Policy) ComputeAltruistOutput(totalAmt btcutil.Amount, // and reward rate. The reward to he tower is subtracted first, before // splitting the remaining balance amongst the victim and fees. func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount, - txWeight int64) (btcutil.Amount, btcutil.Amount, error) { + txWeight int64, + rewardScript []byte) (btcutil.Amount, btcutil.Amount, error) { txFee := p.SweepFeeRate.FeeForWeight(txWeight) if txFee > totalAmt { @@ -198,10 +197,9 @@ func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount, // input value. sweepAmt := totalAmt - rewardAmt - txFee - // TODO(conner): replace w/ configurable dust limit - // Check that the created outputs won't be dusty. The sweep pkscript is - // currently a p2wpkh, so we'll use that script's dust limit. - if sweepAmt < lnwallet.DustLimitForSize(input.P2WPKHSize) { + // Check that the created outputs won't be dusty. We'll base the dust + // computation on the type of the script itself. + if sweepAmt < lnwallet.DustLimitForSize(len(rewardScript)) { return 0, 0, ErrCreatesDust } @@ -231,15 +229,16 @@ func ComputeRewardAmount(total btcutil.Amount, base, rate uint32) btcutil.Amount return rewardBase + proportional } -// ComputeJusticeTxOuts constructs the justice transaction outputs for the given -// policy. If the policy specifies a reward for the tower, there will be two -// outputs paying to the victim and the tower. Otherwise there will be a single -// output sweeping funds back to the victim. The totalAmt should be the sum of -// any inputs used in the transaction. The passed txWeight should include the -// weight of the outputs for the justice transaction, which is dependent on -// whether the justice transaction has a reward. The sweepPkScript should be the -// pkScript of the victim to which funds will be recovered. The rewardPkScript -// is the pkScript of the tower where its reward will be deposited, and will be +// ComputeJusticeTxOuts constructs the justice transaction outputs for the +// given policy. If the policy specifies a reward for the tower, there will be +// two outputs paying to the victim and the tower. Otherwise there will be a +// single output sweeping funds back to the victim. The totalAmt should be the +// sum of any inputs used in the transaction. The passed txWeight should +// include the weight of the outputs for the justice transaction, which is +// dependent on whether the justice transaction has a reward. The sweepPkScript +// should be the pkScript of the victim to which funds will be recovered. The +// rewardPkScript is the pkScript of the tower where its reward will be +// deposited, and will be // ignored if the blob type does not specify a reward. func (p *Policy) ComputeJusticeTxOuts(totalAmt btcutil.Amount, txWeight int64, sweepPkScript, rewardPkScript []byte) ([]*wire.TxOut, error) { @@ -247,19 +246,19 @@ func (p *Policy) ComputeJusticeTxOuts(totalAmt btcutil.Amount, txWeight int64, var outputs []*wire.TxOut // If the policy specifies a reward for the tower, compute a split of - // the funds based on the policy's parameters. Otherwise, we will use an - // the altruist output computation and sweep as much of the funds back - // to the victim as possible. + // the funds based on the policy's parameters. Otherwise, we will use + // the altruist output computation and sweep as much of the funds + // back to the victim as possible. if p.BlobType.Has(blob.FlagReward) { // Using the total input amount and the transaction's weight, - // compute the sweep and reward amounts. This corresponds to the - // amount returned to the victim and the amount paid to the + // compute the sweep and reward amounts. This corresponds to + // the amount returned to the victim and the amount paid to the // tower, respectively. To do so, the required transaction fee // is subtracted from the total, and the remaining amount is - // divided according to the prenegotiated reward rate from the + // divided according to the pre negotiated reward rate from the // client's session info. sweepAmt, rewardAmt, err := p.ComputeRewardOutputs( - totalAmt, txWeight, + totalAmt, txWeight, rewardPkScript, ) if err != nil { return nil, err @@ -280,7 +279,7 @@ func (p *Policy) ComputeJusticeTxOuts(totalAmt btcutil.Amount, txWeight int64, // returned to the victim. To do so, the required transaction // fee is subtracted from the total input amount. sweepAmt, err := p.ComputeAltruistOutput( - totalAmt, txWeight, + totalAmt, txWeight, sweepPkScript, ) if err != nil { return nil, err diff --git a/watchtower/wtserver/server_test.go b/watchtower/wtserver/server_test.go index be94e9bee..9260e4640 100644 --- a/watchtower/wtserver/server_test.go +++ b/watchtower/wtserver/server_test.go @@ -22,7 +22,8 @@ import ( var ( // addr is the server's reward address given to watchtower clients. addr, _ = btcutil.DecodeAddress( - "mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", &chaincfg.TestNet3Params, + "tb1pw8gzj8clt3v5lxykpgacpju5n8xteskt7gxhmudu6pa70nwfhe6s3unsyk", + &chaincfg.TestNet3Params, ) addrScript, _ = txscript.PayToAddrScript(addr)