package itest

import (
	"bytes"
	"encoding/hex"
	"testing"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
	"github.com/btcsuite/btcd/btcec/v2/schnorr"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/btcutil/hdkeychain"
	"github.com/btcsuite/btcd/btcutil/psbt"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightningnetwork/lnd/funding"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/keychain"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnrpc/signrpc"
	"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/lightningnetwork/lnd/lntest/node"
	"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
	"github.com/stretchr/testify/require"
)

// testPsbtChanFunding makes sure a channel can be opened between carol and dave
// by using a Partially Signed Bitcoin Transaction that funds the channel
// multisig funding output.
func testPsbtChanFunding(ht *lntest.HarnessTest) {
	const (
		burnAddr = "bcrt1qxsnqpdc842lu8c0xlllgvejt6rhy49u6fmpgyz"
	)

	testCases := []struct {
		name           string
		commitmentType lnrpc.CommitmentType
		private        bool
	}{
		{
			name:           "anchors",
			commitmentType: lnrpc.CommitmentType_ANCHORS,
			private:        false,
		},
		{
			name:           "simple taproot",
			commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,

			// Set this to true once simple taproot channels can be
			// announced to the network.
			private: true,
		},
	}

	for _, tc := range testCases {
		tc := tc

		success := ht.T.Run(tc.name, func(tt *testing.T) {
			st := ht.Subtest(tt)

			args := lntest.NodeArgsForCommitType(tc.commitmentType)

			// First, we'll create two new nodes that we'll use to
			// open channels between for this test. Dave gets some
			// coins that will be used to fund the PSBT, just to
			// make sure that Carol has an empty wallet.
			carol := st.NewNode("carol", args)
			dave := st.NewNode("dave", args)

			// We just send enough funds to satisfy the anchor
			// channel reserve for 5 channels (50k sats).
			st.FundCoins(50_000, carol)
			st.FundCoins(50_000, dave)

			st.RunTestCase(&lntest.TestCase{
				Name: tc.name,
				TestFunc: func(sst *lntest.HarnessTest) {
					runPsbtChanFunding(
						sst, carol, dave, tc.private,
						tc.commitmentType,
					)
				},
			})

			// Empty out the wallets so there aren't any lingering
			// coins.
			sendAllCoinsConfirm(st, carol, burnAddr)
			sendAllCoinsConfirm(st, dave, burnAddr)

			// Now we test the second scenario. Again, we just send
			// enough funds to satisfy the anchor channel reserve
			// for 5 channels (50k sats).
			st.FundCoins(50_000, carol)
			st.FundCoins(50_000, dave)

			st.RunTestCase(&lntest.TestCase{
				Name: tc.name,
				TestFunc: func(sst *lntest.HarnessTest) {
					runPsbtChanFundingExternal(
						sst, carol, dave, tc.private,
						tc.commitmentType,
					)
				},
			})

			// Empty out the wallets a last time, so there aren't
			// any lingering coins.
			sendAllCoinsConfirm(st, carol, burnAddr)
			sendAllCoinsConfirm(st, dave, burnAddr)

			// The last test case tests the anchor channel reserve
			// itself, so we need empty wallets.
			st.RunTestCase(&lntest.TestCase{
				Name: tc.name,
				TestFunc: func(sst *lntest.HarnessTest) {
					runPsbtChanFundingSingleStep(
						sst, carol, dave, tc.private,
						tc.commitmentType,
					)
				},
			})
		})
		if !success {
			// Log failure time to help relate the lnd logs to the
			// failure.
			ht.Logf("Failure time: %v", time.Now().Format(
				"2006-01-02 15:04:05.000",
			))

			break
		}
	}
}

// runPsbtChanFunding makes sure a channel can be opened between carol and dave
// by using a Partially Signed Bitcoin Transaction that funds the channel
// multisig funding output.
func runPsbtChanFunding(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
	private bool, commitType lnrpc.CommitmentType) {

	const chanSize = funding.MaxBtcFundingAmount
	ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)

	// Before we start the test, we'll ensure both sides are connected so
	// the funding flow can be properly executed.
	alice := ht.Alice
	ht.EnsureConnected(carol, dave)
	ht.EnsureConnected(carol, alice)

	// At this point, we can begin our PSBT channel funding workflow. We'll
	// start by generating a pending channel ID externally that will be used
	// to track this new funding type.
	pendingChanID := ht.Random32Bytes()

	// We'll also test batch funding of two channels so we need another ID.
	pendingChanID2 := ht.Random32Bytes()

	// Now that we have the pending channel ID, Carol will open the channel
	// by specifying a PSBT shim. We use the NoPublish flag here to avoid
	// publishing the whole batch TX too early.
	chanUpdates, tempPsbt := ht.OpenChannelPsbt(
		carol, dave, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID,
						NoPublish:     true,
					},
				},
			},
			Private:        private,
			CommitmentType: commitType,
		},
	)

	// Let's add a second channel to the batch. This time between Carol and
	// Alice. We will publish the batch TX once this channel funding is
	// complete.
	chanUpdates2, psbtBytes2 := ht.OpenChannelPsbt(
		carol, alice, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID2,
						NoPublish:     false,
						BasePsbt:      tempPsbt,
					},
				},
			},
			// We haven't started Alice with the explicit params to
			// support the current commit type, so we'll just use
			// the default for this channel. That also allows us to
			// test batches of different channel types.
		},
	)

	// We'll now ask Dave's wallet to fund the PSBT for us. This will return
	// a packet with inputs and outputs set but without any witness data.
	// This is exactly what we need for the next step.
	fundReq := &walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_Psbt{
			Psbt: psbtBytes2,
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 2,
		},
	}
	fundResp := dave.RPC.FundPsbt(fundReq)

	// We have a PSBT that has no witness data yet, which is exactly what we
	// need for the next step: Verify the PSBT with the funding intents.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID,
				FundedPsbt:    fundResp.FundedPsbt,
			},
		},
	})
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID2,
				FundedPsbt:    fundResp.FundedPsbt,
			},
		},
	})

	// Now we'll ask Dave's wallet to sign the PSBT so we can finish the
	// funding flow.
	finalizeReq := &walletrpc.FinalizePsbtRequest{
		FundedPsbt: fundResp.FundedPsbt,
	}
	finalizeRes := dave.RPC.FinalizePsbt(finalizeReq)

	// We've signed our PSBT now, let's pass it to the intent again.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
			PsbtFinalize: &lnrpc.FundingPsbtFinalize{
				PendingChanId: pendingChanID,
				SignedPsbt:    finalizeRes.SignedPsbt,
			},
		},
	})

	// Consume the "channel pending" update. This waits until the funding
	// transaction was fully compiled.
	updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates)
	upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	chanPoint := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd.ChanPending.Txid,
		},
		OutputIndex: upd.ChanPending.OutputIndex,
	}

	// No transaction should have been published yet.
	ht.Miner.AssertNumTxsInMempool(0)

	// Let's progress the second channel now. This time we'll use the raw
	// wire format transaction directly.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
			PsbtFinalize: &lnrpc.FundingPsbtFinalize{
				PendingChanId: pendingChanID2,
				FinalRawTx:    finalizeRes.RawFinalTx,
			},
		},
	})

	// Consume the "channel pending" update for the second channel. This
	// waits until the funding transaction was fully compiled and in this
	// case published.
	updateResp2 := ht.ReceiveOpenChannelUpdate(chanUpdates2)
	upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	chanPoint2 := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd2.ChanPending.Txid,
		},
		OutputIndex: upd2.ChanPending.OutputIndex,
	}

	// Great, now we can mine a block to get the transaction confirmed, then
	// wait for the new channel to be propagated through the network.
	var finalTx wire.MsgTx
	err := finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx))
	require.NoError(ht, err)

	txHash := finalTx.TxHash()
	block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
	ht.Miner.AssertTxInBlock(block, &txHash)
	ht.AssertTopologyChannelOpen(carol, chanPoint)
	ht.AssertTopologyChannelOpen(carol, chanPoint2)

	// With the channel open, ensure that it is counted towards Carol's
	// total channel balance.
	balRes := carol.RPC.ChannelBalance()
	require.NotZero(ht, balRes.LocalBalance.Sat)

	// Next, to make sure the channel functions as normal, we'll make some
	// payments within the channel.
	payAmt := btcutil.Amount(100000)
	invoice := &lnrpc.Invoice{
		Memo:  "new chans",
		Value: int64(payAmt),
	}
	resp := dave.RPC.AddInvoice(invoice)
	ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})

	// To conclude, we'll close the newly created channel between Carol and
	// Dave. This function will also block until the channel is closed and
	// will additionally assert the relevant channel closing post
	// conditions.
	ht.CloseChannel(carol, chanPoint)
	ht.CloseChannel(carol, chanPoint2)
}

// runPsbtChanFundingExternal makes sure a channel can be opened between carol
// and dave by using a Partially Signed Bitcoin Transaction that funds the
// channel multisig funding output and is fully funded by an external third
// party.
func runPsbtChanFundingExternal(ht *lntest.HarnessTest, carol,
	dave *node.HarnessNode, private bool, commitType lnrpc.CommitmentType) {

	const chanSize = funding.MaxBtcFundingAmount

	// Before we start the test, we'll ensure both sides are connected so
	// the funding flow can be properly executed.
	alice := ht.Alice
	ht.EnsureConnected(carol, dave)
	ht.EnsureConnected(carol, alice)

	// At this point, we can begin our PSBT channel funding workflow. We'll
	// start by generating a pending channel ID externally that will be used
	// to track this new funding type.
	pendingChanID := ht.Random32Bytes()

	// We'll also test batch funding of two channels so we need another ID.
	pendingChanID2 := ht.Random32Bytes()

	// Now that we have the pending channel ID, Carol will open the channel
	// by specifying a PSBT shim. We use the NoPublish flag here to avoid
	// publishing the whole batch TX too early.
	chanUpdates, tempPsbt := ht.OpenChannelPsbt(
		carol, dave, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID,
						NoPublish:     true,
					},
				},
			},
			Private:        private,
			CommitmentType: commitType,
		},
	)

	// Let's add a second channel to the batch. This time between Carol and
	// Alice. We will publish the batch TX once this channel funding is
	// complete.
	chanUpdates2, psbtBytes2 := ht.OpenChannelPsbt(
		carol, alice, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID2,
						NoPublish:     true,
						BasePsbt:      tempPsbt,
					},
				},
			},
			// We haven't started Alice with the explicit params to
			// support the current commit type, so we'll just use
			// the default for this channel. That also allows us to
			// test batches of different channel types.
		},
	)

	// We'll now ask Alice's wallet to fund the PSBT for us. This will
	// return a packet with inputs and outputs set but without any witness
	// data. This is exactly what we need for the next step.
	fundReq := &walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_Psbt{
			Psbt: psbtBytes2,
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 2,
		},
	}
	fundResp := alice.RPC.FundPsbt(fundReq)

	// We have a PSBT that has no witness data yet, which is exactly what we
	// need for the next step: Verify the PSBT with the funding intents.
	// We tell the PSBT intent to skip the finalize step because we know the
	// final transaction will not be broadcast by Carol herself but by
	// Alice. And we assume that Alice is a third party that is not in
	// direct communication with Carol and won't send the signed TX to her
	// before broadcasting it. So we cannot call the finalize step but
	// instead just tell lnd to wait for a TX to be published/confirmed.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID,
				FundedPsbt:    fundResp.FundedPsbt,
				SkipFinalize:  true,
			},
		},
	})
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID2,
				FundedPsbt:    fundResp.FundedPsbt,
				SkipFinalize:  true,
			},
		},
	})

	// Consume the "channel pending" update. This waits until the funding
	// transaction was fully compiled for both channels.
	updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates)
	upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	chanPoint := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd.ChanPending.Txid,
		},
		OutputIndex: upd.ChanPending.OutputIndex,
	}
	updateResp2 := ht.ReceiveOpenChannelUpdate(chanUpdates2)
	upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	chanPoint2 := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd2.ChanPending.Txid,
		},
		OutputIndex: upd2.ChanPending.OutputIndex,
	}
	ht.AssertNumPendingOpenChannels(carol, 2)

	// Now we'll ask Alice's wallet to sign the PSBT so we can finish the
	// funding flow.
	finalizeReq := &walletrpc.FinalizePsbtRequest{
		FundedPsbt: fundResp.FundedPsbt,
	}
	finalizeRes := alice.RPC.FinalizePsbt(finalizeReq)

	// No transaction should have been published yet.
	ht.Miner.AssertNumTxsInMempool(0)

	// Great, now let's publish the final raw transaction.
	var finalTx wire.MsgTx
	err := finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx))
	require.NoError(ht, err)

	txHash := finalTx.TxHash()
	_, err = ht.Miner.Client.SendRawTransaction(&finalTx, false)
	require.NoError(ht, err)

	// Now we can mine a block to get the transaction confirmed, then wait
	// for the new channel to be propagated through the network.
	block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
	ht.Miner.AssertTxInBlock(block, &txHash)
	ht.AssertTopologyChannelOpen(carol, chanPoint)
	ht.AssertTopologyChannelOpen(carol, chanPoint2)

	// With the channel open, ensure that it is counted towards Carol's
	// total channel balance.
	balRes := carol.RPC.ChannelBalance()
	require.NotZero(ht, balRes.LocalBalance.Sat)

	// Next, to make sure the channel functions as normal, we'll make some
	// payments within the channel.
	payAmt := btcutil.Amount(100000)
	invoice := &lnrpc.Invoice{
		Memo:  "new chans",
		Value: int64(payAmt),
	}
	resp := dave.RPC.AddInvoice(invoice)
	ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})

	// To conclude, we'll close the newly created channel between Carol and
	// Dave. This function will also block until the channels are closed and
	// will additionally assert the relevant channel closing post
	// conditions.
	ht.CloseChannel(carol, chanPoint)
	ht.CloseChannel(carol, chanPoint2)
}

// runPsbtChanFundingSingleStep checks whether PSBT funding works also when
// the wallet of both nodes are empty and one of them uses PSBT and an external
// wallet to fund the channel while creating reserve output in the same
// transaction.
func runPsbtChanFundingSingleStep(ht *lntest.HarnessTest, carol,
	dave *node.HarnessNode, private bool, commitType lnrpc.CommitmentType) {

	const chanSize = funding.MaxBtcFundingAmount

	alice := ht.Alice
	ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)

	// Get new address for anchor reserve.
	req := &lnrpc.NewAddressRequest{
		Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	}
	addrResp := carol.RPC.NewAddress(req)
	reserveAddr, err := btcutil.DecodeAddress(
		addrResp.Address, harnessNetParams,
	)
	require.NoError(ht, err)
	reserveAddrScript, err := txscript.PayToAddrScript(reserveAddr)
	require.NoError(ht, err)

	// Before we start the test, we'll ensure both sides are connected so
	// the funding flow can be properly executed.
	ht.EnsureConnected(carol, dave)

	// At this point, we can begin our PSBT channel funding workflow. We'll
	// start by generating a pending channel ID externally that will be used
	// to track this new funding type.
	pendingChanID := ht.Random32Bytes()

	// Now that we have the pending channel ID, Carol will open the channel
	// by specifying a PSBT shim.
	chanUpdates, tempPsbt := ht.OpenChannelPsbt(
		carol, dave, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID,
						NoPublish:     false,
					},
				},
			},
			Private:        private,
			CommitmentType: commitType,
		},
	)

	decodedPsbt, err := psbt.NewFromRawBytes(
		bytes.NewReader(tempPsbt), false,
	)
	require.NoError(ht, err)

	reserveTxOut := wire.TxOut{
		Value:    10000,
		PkScript: reserveAddrScript,
	}

	decodedPsbt.UnsignedTx.TxOut = append(
		decodedPsbt.UnsignedTx.TxOut, &reserveTxOut,
	)
	decodedPsbt.Outputs = append(decodedPsbt.Outputs, psbt.POutput{})

	var psbtBytes bytes.Buffer
	err = decodedPsbt.Serialize(&psbtBytes)
	require.NoError(ht, err)

	fundReq := &walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_Psbt{
			Psbt: psbtBytes.Bytes(),
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 2,
		},
	}
	fundResp := alice.RPC.FundPsbt(fundReq)

	// Make sure the wallets are actually empty
	ht.AssertNumUTXOsUnconfirmed(alice, 0)
	ht.AssertNumUTXOsUnconfirmed(dave, 0)

	// We have a PSBT that has no witness data yet, which is exactly what we
	// need for the next step: Verify the PSBT with the funding intents.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID,
				FundedPsbt:    fundResp.FundedPsbt,
			},
		},
	})

	// Now we'll ask Alice's wallet to sign the PSBT so we can finish the
	// funding flow.
	finalizeReq := &walletrpc.FinalizePsbtRequest{
		FundedPsbt: fundResp.FundedPsbt,
	}
	finalizeRes := alice.RPC.FinalizePsbt(finalizeReq)

	// We've signed our PSBT now, let's pass it to the intent again.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
			PsbtFinalize: &lnrpc.FundingPsbtFinalize{
				PendingChanId: pendingChanID,
				SignedPsbt:    finalizeRes.SignedPsbt,
			},
		},
	})

	// Consume the "channel pending" update. This waits until the funding
	// transaction was fully compiled.
	updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates)
	upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	chanPoint := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd.ChanPending.Txid,
		},
		OutputIndex: upd.ChanPending.OutputIndex,
	}

	var finalTx wire.MsgTx
	err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx))
	require.NoError(ht, err)

	txHash := finalTx.TxHash()
	block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
	ht.Miner.AssertTxInBlock(block, &txHash)
	ht.AssertTopologyChannelOpen(carol, chanPoint)

	// Next, to make sure the channel functions as normal, we'll make some
	// payments within the channel.
	payAmt := btcutil.Amount(100000)
	invoice := &lnrpc.Invoice{
		Memo:  "new chans",
		Value: int64(payAmt),
	}
	resp := dave.RPC.AddInvoice(invoice)
	ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})

	// To conclude, we'll close the newly created channel between Carol and
	// Dave. This function will also block until the channel is closed and
	// will additionally assert the relevant channel closing post
	// conditions.
	ht.CloseChannel(carol, chanPoint)
}

// testSignPsbt tests that the SignPsbt RPC works correctly.
func testSignPsbt(ht *lntest.HarnessTest) {
	psbtTestRunners := []struct {
		name   string
		runner func(*lntest.HarnessTest, *node.HarnessNode)
	}{
		{
			name:   "sign psbt segwit v0 P2WPKH",
			runner: runSignPsbtSegWitV0P2WKH,
		},
		{
			name:   "sign psbt segwit v0 P2WSH",
			runner: runSignPsbtSegWitV0NP2WKH,
		},
		{
			name:   "sign psbt segwit v1 key spend bip86",
			runner: runSignPsbtSegWitV1KeySpendBip86,
		},
		{
			name:   "sign psbt segwit v1 key spend root hash",
			runner: runSignPsbtSegWitV1KeySpendRootHash,
		},
		{
			name:   "sign psbt segwit v1 script spend",
			runner: runSignPsbtSegWitV1ScriptSpend,
		},
		{
			// The above tests all make sure we can sign for keys
			// that aren't in the wallet. But we also want to make
			// sure we can fund and then sign PSBTs from our
			// wallet.
			name:   "fund and sign psbt",
			runner: runFundAndSignPsbt,
		},
	}

	for _, tc := range psbtTestRunners {
		succeed := ht.Run(tc.name, func(t *testing.T) {
			st := ht.Subtest(t)
			tc.runner(st, st.Alice)
		})

		// Abort the test if failed.
		if !succeed {
			return
		}
	}
}

// runSignPsbtSegWitV0P2WKH tests that the SignPsbt RPC works correctly for a
// SegWit v0 p2wkh input.
func runSignPsbtSegWitV0P2WKH(ht *lntest.HarnessTest, alice *node.HarnessNode) {
	// We test that we can sign a PSBT that spends funds from an input that
	// the wallet doesn't know about. To set up that test case, we first
	// derive an address manually that the wallet won't be watching on
	// chain. We can do that by exporting the account xpub of lnd's main
	// account.
	accounts := alice.RPC.ListAccounts(&walletrpc.ListAccountsRequest{})
	require.NotEmpty(ht, accounts.Accounts)

	// We also need to parse the accounts, so we have easy access to the
	// parsed derivation paths.
	parsedAccounts, err := walletrpc.AccountsToWatchOnly(accounts.Accounts)
	require.NoError(ht, err)

	account := parsedAccounts[0]
	xpub, err := hdkeychain.NewKeyFromString(account.Xpub)
	require.NoError(ht, err)

	const (
		changeIndex = 1
		addrIndex   = 1337
	)
	fullDerivationPath := []uint32{
		hdkeychain.HardenedKeyStart + account.Purpose,
		hdkeychain.HardenedKeyStart + account.CoinType,
		hdkeychain.HardenedKeyStart + account.Account,
		changeIndex,
		addrIndex,
	}

	// Let's simulate a change address.
	change, err := xpub.DeriveNonStandard(changeIndex)
	require.NoError(ht, err)

	// At an index that we are certainly not watching in the wallet.
	addrKey, err := change.DeriveNonStandard(addrIndex)
	require.NoError(ht, err)

	addrPubKey, err := addrKey.ECPubKey()
	require.NoError(ht, err)
	pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed())
	witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
		pubKeyHash, harnessNetParams,
	)
	require.NoError(ht, err)

	pkScript, err := txscript.PayToAddrScript(witnessAddr)
	require.NoError(ht, err)

	// Send some funds to the output and then try to get a signature through
	// the SignPsbt RPC to spend that output again.
	assertPsbtSpend(
		ht, alice, pkScript,
		func(packet *psbt.Packet) {
			in := &packet.Inputs[0]
			in.Bip32Derivation = []*psbt.Bip32Derivation{{
				PubKey:    addrPubKey.SerializeCompressed(),
				Bip32Path: fullDerivationPath,
			}}
			in.SighashType = txscript.SigHashAll
		},
		func(packet *psbt.Packet) {
			require.Len(ht, packet.Inputs, 1)
			require.Len(ht, packet.Inputs[0].PartialSigs, 1)

			partialSig := packet.Inputs[0].PartialSigs[0]
			require.Equal(
				ht, partialSig.PubKey,
				addrPubKey.SerializeCompressed(),
			)
			require.Greater(
				ht, len(partialSig.Signature), ecdsa.MinSigLen,
			)
		},
	)
}

// runSignPsbtSegWitV0NP2WKH tests that the SignPsbt RPC works correctly for a
// SegWit v0 np2wkh input.
func runSignPsbtSegWitV0NP2WKH(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// We test that we can sign a PSBT that spends funds from an input that
	// the wallet doesn't know about. To set up that test case, we first
	// derive an address manually that the wallet won't be watching on
	// chain. We can do that by exporting the account xpub of lnd's main
	// account.
	accounts := alice.RPC.ListAccounts(&walletrpc.ListAccountsRequest{})
	require.NotEmpty(ht, accounts.Accounts)

	// We also need to parse the accounts, so we have easy access to the
	// parsed derivation paths.
	parsedAccounts, err := walletrpc.AccountsToWatchOnly(accounts.Accounts)
	require.NoError(ht, err)

	account := parsedAccounts[0]
	xpub, err := hdkeychain.NewKeyFromString(account.Xpub)
	require.NoError(ht, err)

	const (
		changeIndex = 1
		addrIndex   = 1337
	)
	fullDerivationPath := []uint32{
		hdkeychain.HardenedKeyStart + account.Purpose,
		hdkeychain.HardenedKeyStart + account.CoinType,
		hdkeychain.HardenedKeyStart + account.Account,
		changeIndex,
		addrIndex,
	}

	// Let's simulate a change address.
	change, err := xpub.DeriveNonStandard(changeIndex)
	require.NoError(ht, err)

	// At an index that we are certainly not watching in the wallet.
	addrKey, err := change.DeriveNonStandard(addrIndex)
	require.NoError(ht, err)

	addrPubKey, err := addrKey.ECPubKey()
	require.NoError(ht, err)
	pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed())
	witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
		pubKeyHash, harnessNetParams,
	)
	require.NoError(ht, err)

	witnessProgram, err := txscript.PayToAddrScript(witnessAddr)
	require.NoError(ht, err)
	np2wkhAddr, err := btcutil.NewAddressScriptHash(
		witnessProgram, harnessNetParams,
	)
	require.NoError(ht, err)

	pkScript, err := txscript.PayToAddrScript(np2wkhAddr)
	require.NoError(ht, err)

	// Send some funds to the output and then try to get a signature through
	// the SignPsbt RPC to spend that output again.
	assertPsbtSpend(
		ht, alice, pkScript,
		func(packet *psbt.Packet) {
			in := &packet.Inputs[0]
			in.RedeemScript = witnessProgram
			in.Bip32Derivation = []*psbt.Bip32Derivation{{
				PubKey:    addrPubKey.SerializeCompressed(),
				Bip32Path: fullDerivationPath,
			}}
			in.SighashType = txscript.SigHashAll
		},
		func(packet *psbt.Packet) {
			require.Len(ht, packet.Inputs, 1)
			require.Len(ht, packet.Inputs[0].PartialSigs, 1)

			partialSig := packet.Inputs[0].PartialSigs[0]
			require.Equal(
				ht, partialSig.PubKey,
				addrPubKey.SerializeCompressed(),
			)
			require.Greater(
				ht, len(partialSig.Signature), ecdsa.MinSigLen,
			)
		},
	)
}

// runSignPsbtSegWitV1KeySpendBip86 tests that the SignPsbt RPC works correctly
// for a SegWit v1 p2tr key spend BIP-0086 input.
func runSignPsbtSegWitV1KeySpendBip86(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// Derive a key we can use for signing.
	keyDesc, internalKey, fullDerivationPath := deriveInternalKey(ht, alice)

	// Our taproot key is a BIP0086 key spend only construction that just
	// commits to the internal key and no root hash.
	taprootKey := txscript.ComputeTaprootKeyNoScript(internalKey)
	tapScriptAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
	require.NoError(ht, err)

	// Send some funds to the output and then try to get a signature through
	// the SignPsbt RPC to spend that output again.
	assertPsbtSpend(
		ht, alice, p2trPkScript,
		func(packet *psbt.Packet) {
			in := &packet.Inputs[0]
			in.Bip32Derivation = []*psbt.Bip32Derivation{{
				PubKey:    keyDesc.RawKeyBytes,
				Bip32Path: fullDerivationPath,
			}}
			p2trDerivation := []*psbt.TaprootBip32Derivation{{
				XOnlyPubKey: keyDesc.RawKeyBytes[1:],
				Bip32Path:   fullDerivationPath,
			}}
			in.TaprootBip32Derivation = p2trDerivation
			in.SighashType = txscript.SigHashDefault
		},
		func(packet *psbt.Packet) {
			require.Len(ht, packet.Inputs, 1)
			require.Len(
				ht, packet.Inputs[0].TaprootKeySpendSig, 64,
			)
		},
	)
}

// runSignPsbtSegWitV1KeySpendRootHash tests that the SignPsbt RPC works
// correctly for a SegWit v1 p2tr key spend that also commits to a script tree
// root hash.
func runSignPsbtSegWitV1KeySpendRootHash(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// Derive a key we can use for signing.
	keyDesc, internalKey, fullDerivationPath := deriveInternalKey(ht, alice)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))

	rootHash := leaf1.TapHash()
	taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
	tapScriptAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
	require.NoError(ht, err)

	// Send some funds to the output and then try to get a signature through
	// the SignPsbt RPC to spend that output again.
	assertPsbtSpend(
		ht, alice, p2trPkScript,
		func(packet *psbt.Packet) {
			in := &packet.Inputs[0]
			in.Bip32Derivation = []*psbt.Bip32Derivation{{
				PubKey:    keyDesc.RawKeyBytes,
				Bip32Path: fullDerivationPath,
			}}
			p2trDerivation := []*psbt.TaprootBip32Derivation{{
				XOnlyPubKey: keyDesc.RawKeyBytes[1:],
				Bip32Path:   fullDerivationPath,
			}}
			in.TaprootBip32Derivation = p2trDerivation
			in.TaprootMerkleRoot = rootHash[:]
			in.SighashType = txscript.SigHashDefault
		},
		func(packet *psbt.Packet) {
			require.Len(ht, packet.Inputs, 1)
			require.Len(
				ht, packet.Inputs[0].TaprootKeySpendSig, 64,
			)
		},
	)
}

// runSignPsbtSegWitV1ScriptSpend tests that the SignPsbt RPC works correctly
// for a SegWit v1 p2tr script spend.
func runSignPsbtSegWitV1ScriptSpend(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// Derive a key we can use for signing.
	keyDesc, internalKey, fullDerivationPath := deriveInternalKey(ht, alice)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptSchnorrSig(ht.T, internalKey)

	rootHash := leaf1.TapHash()
	taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
	tapScriptAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
	require.NoError(ht, err)

	// We need to assemble the control block to be able to spend through the
	// script path.
	tapscript := input.TapscriptPartialReveal(internalKey, leaf1, nil)
	controlBlockBytes, err := tapscript.ControlBlock.ToBytes()
	require.NoError(ht, err)

	// Send some funds to the output and then try to get a signature through
	// the SignPsbt RPC to spend that output again.
	assertPsbtSpend(
		ht, alice, p2trPkScript,
		func(packet *psbt.Packet) {
			in := &packet.Inputs[0]
			in.Bip32Derivation = []*psbt.Bip32Derivation{{
				PubKey:    keyDesc.RawKeyBytes,
				Bip32Path: fullDerivationPath,
			}}
			p2trDerivation := []*psbt.TaprootBip32Derivation{{
				XOnlyPubKey: keyDesc.RawKeyBytes[1:],
				Bip32Path:   fullDerivationPath,
				LeafHashes:  [][]byte{rootHash[:]},
			}}
			in.TaprootBip32Derivation = p2trDerivation
			in.SighashType = txscript.SigHashDefault
			in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{
				ControlBlock: controlBlockBytes,
				Script:       leaf1.Script,
				LeafVersion:  leaf1.LeafVersion,
			}}
		},
		func(packet *psbt.Packet) {
			require.Len(ht, packet.Inputs, 1)
			require.Len(
				ht, packet.Inputs[0].TaprootScriptSpendSig, 1,
			)

			spendSig := packet.Inputs[0].TaprootScriptSpendSig[0]
			require.Len(ht, spendSig.Signature, 64)
		},
	)
}

// runFundAndSignPsbt makes sure we can sign PSBTs that were funded by our
// internal wallet.
func runFundAndSignPsbt(ht *lntest.HarnessTest, alice *node.HarnessNode) {
	alice.AddToLogf("================ runFundAndSignPsbt ===============")

	// We'll be using a "main" address where we send the funds to and from
	// several times.
	mainAddrResp := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	})

	fundOutputs := map[string]uint64{
		mainAddrResp.Address: 999000,
	}
	spendAddrTypes := []lnrpc.AddressType{
		lnrpc.AddressType_NESTED_PUBKEY_HASH,
		lnrpc.AddressType_WITNESS_PUBKEY_HASH,
		lnrpc.AddressType_TAPROOT_PUBKEY,
	}
	changeAddrTypes := []walletrpc.ChangeAddressType{
		walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
		walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR,
	}

	for _, addrType := range spendAddrTypes {
		for _, changeType := range changeAddrTypes {
			ht.Logf("testing with address type %s and "+
				"change address type %s", addrType, changeType)

			// First, spend all the coins in the wallet to an
			// address of the given type so that UTXO will be picked
			// when funding a PSBT.
			sendAllCoinsToAddrType(ht, alice, addrType)

			// Let's fund a PSBT now where we want to send a few
			// sats to our main address.
			assertPsbtFundSignSpend(
				ht, alice, fundOutputs, changeType, false,
			)

			// Send all coins back to a single address once again.
			sendAllCoinsToAddrType(ht, alice, addrType)

			// And now make sure the alternate way of signing a
			// PSBT, which is calling FinalizePsbt directly, also
			// works for this address type.
			assertPsbtFundSignSpend(
				ht, alice, fundOutputs, changeType, true,
			)
		}
	}
}

// testFundPsbt tests the FundPsbt RPC use cases that aren't covered by the PSBT
// channel funding tests above. These specifically are the use cases of funding
// a PSBT that already specifies an input but where the user still wants the
// wallet to perform coin selection.
func testFundPsbt(ht *lntest.HarnessTest) {
	// We test a pay-join between Alice and Bob. Bob wants to send Alice
	// 5 million Satoshis in a non-obvious way. So Bob selects a UTXO that's
	// bigger than 5 million Satoshis and expects the change minus the send
	// amount back. Alice then funds the PSBT with coins of her own and
	// combines her change with the 5 million Satoshis from Bob. With this
	// Alice ends up paying the fees for a transfer to her.
	const sendAmount = 5_000_000
	aliceAddr := ht.Alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: lnrpc.AddressType_TAPROOT_PUBKEY,
	})
	bobAddr := ht.Bob.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: lnrpc.AddressType_TAPROOT_PUBKEY,
	})

	ht.Alice.UpdateState()
	ht.Bob.UpdateState()
	aliceStartBalance := ht.Alice.State.Wallet.TotalBalance
	bobStartBalance := ht.Bob.State.Wallet.TotalBalance

	var bobUtxo *lnrpc.Utxo
	bobUnspent := ht.Bob.RPC.ListUnspent(&walletrpc.ListUnspentRequest{})
	for _, utxo := range bobUnspent.Utxos {
		if utxo.AmountSat > sendAmount {
			bobUtxo = utxo
			break
		}
	}
	if bobUtxo == nil {
		ht.Fatalf("Bob doesn't have a UTXO of at least %d sats",
			sendAmount)
	}

	bobUtxoTxHash, err := chainhash.NewHash(bobUtxo.Outpoint.TxidBytes)
	require.NoError(ht, err)

	tx := wire.NewMsgTx(2)
	tx.TxIn = append(tx.TxIn, &wire.TxIn{
		PreviousOutPoint: wire.OutPoint{
			Hash:  *bobUtxoTxHash,
			Index: bobUtxo.Outpoint.OutputIndex,
		},
	})
	tx.TxOut = append(tx.TxOut, &wire.TxOut{
		// Change going back to Bob.
		PkScript: addressToPkScript(ht, bobAddr.Address),
		Value:    bobUtxo.AmountSat - sendAmount,
	}, &wire.TxOut{
		// Amount to be sent to Alice, but we'll also send her change
		// here.
		PkScript: addressToPkScript(ht, aliceAddr.Address),
		Value:    sendAmount,
	})

	packet, err := psbt.NewFromUnsignedTx(tx)
	require.NoError(ht, err)

	derivation, trDerivation := getAddressBip32Derivation(
		ht, bobUtxo.Address, ht.Bob,
	)

	bobUtxoPkScript, _ := hex.DecodeString(bobUtxo.PkScript)
	firstInput := &packet.Inputs[0]
	firstInput.WitnessUtxo = &wire.TxOut{
		PkScript: bobUtxoPkScript,
		Value:    bobUtxo.AmountSat,
	}
	firstInput.Bip32Derivation = []*psbt.Bip32Derivation{derivation}
	firstInput.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
		trDerivation,
	}
	if txscript.IsPayToWitnessPubKeyHash(bobUtxoPkScript) {
		packet.Inputs[0].SighashType = txscript.SigHashAll
	}

	// We have the template now. Bob basically funds the 5 million Sats to
	// send to Alice and Alice now only needs to coin select to pay for the
	// fees.
	fundedPacket := fundPsbtCoinSelect(ht, ht.Alice, packet, 1)
	txFee, err := fundedPacket.GetTxFee()
	require.NoError(ht, err)

	// We now let Bob sign the transaction.
	signedPacket := signPacket(ht, ht.Bob, fundedPacket)

	// And then Alice, which should give us a fully signed TX.
	signedPacket = signPacket(ht, ht.Alice, signedPacket)

	// We should be able to finalize the PSBT and extract the final TX now.
	extractPublishAndMine(ht, ht.Alice, signedPacket)

	// Make sure the new wallet balances are reflected correctly.
	ht.AssertActiveNodesSynced()
	ht.Alice.UpdateState()
	ht.Bob.UpdateState()

	require.Equal(
		ht, aliceStartBalance+sendAmount-int64(txFee),
		ht.Alice.State.Wallet.TotalBalance,
	)
	require.Equal(
		ht, bobStartBalance-sendAmount,
		ht.Bob.State.Wallet.TotalBalance,
	)
}

// addressToPkScript parses the given address string and returns the pkScript
// for the regtest environment.
func addressToPkScript(t testing.TB, addr string) []byte {
	parsed, err := btcutil.DecodeAddress(addr, harnessNetParams)
	require.NoError(t, err)

	pkScript, err := txscript.PayToAddrScript(parsed)
	require.NoError(t, err)

	return pkScript
}

// getAddressBip32Derivation returns the PSBT BIP-0032 derivation info of an
// address.
func getAddressBip32Derivation(t testing.TB, addr string,
	node *node.HarnessNode) (*psbt.Bip32Derivation,
	*psbt.TaprootBip32Derivation) {

	// We can't query a single address directly, so we just query all wallet
	// addresses.
	addresses := node.RPC.ListAddresses(
		&walletrpc.ListAddressesRequest{},
	)

	var (
		path        []uint32
		pubKeyBytes []byte
		err         error
	)
	for _, account := range addresses.AccountWithAddresses {
		for _, address := range account.Addresses {
			if address.Address == addr {
				path, err = lntest.ParseDerivationPath(
					address.DerivationPath,
				)
				require.NoError(t, err)

				pubKeyBytes = address.PublicKey
			}
		}
	}

	if len(path) != 5 || len(pubKeyBytes) == 0 {
		t.Fatalf("Derivation path for address %s not found or invalid",
			addr)
	}

	// The actual derivation path in a PSBT needs to be using the hardened
	// uint32 notation for the first three elements.
	path[0] += hdkeychain.HardenedKeyStart
	path[1] += hdkeychain.HardenedKeyStart
	path[2] += hdkeychain.HardenedKeyStart

	return &psbt.Bip32Derivation{
			PubKey:    pubKeyBytes,
			Bip32Path: path,
		}, &psbt.TaprootBip32Derivation{
			XOnlyPubKey: pubKeyBytes[1:],
			Bip32Path:   path,
		}
}

// fundPsbtCoinSelect calls the FundPsbt RPC on the given node using the coin
// selection with template PSBT mode.
func fundPsbtCoinSelect(t testing.TB, node *node.HarnessNode,
	packet *psbt.Packet, changeIndex int32) *psbt.Packet {

	var buf bytes.Buffer
	err := packet.Serialize(&buf)
	require.NoError(t, err)

	cs := &walletrpc.PsbtCoinSelect{
		Psbt: buf.Bytes(),
	}
	if changeIndex >= 0 {
		cs.ChangeOutput = &walletrpc.PsbtCoinSelect_ExistingOutputIndex{
			ExistingOutputIndex: 1,
		}
	} else {
		cs.ChangeOutput = &walletrpc.PsbtCoinSelect_Add{
			Add: true,
		}
	}

	fundResp := node.RPC.FundPsbt(&walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_CoinSelect{
			CoinSelect: cs,
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 50,
		},
	})

	fundedPacket, err := psbt.NewFromRawBytes(
		bytes.NewReader(fundResp.FundedPsbt), false,
	)
	require.NoError(t, err)

	return fundedPacket
}

// signPacket calls the SignPsbt RPC on the given node.
func signPacket(t testing.TB, node *node.HarnessNode,
	packet *psbt.Packet) *psbt.Packet {

	var buf bytes.Buffer
	err := packet.Serialize(&buf)
	require.NoError(t, err)

	signResp := node.RPC.SignPsbt(&walletrpc.SignPsbtRequest{
		FundedPsbt: buf.Bytes(),
	})

	// Let's make sure we have a partial signature.
	signedPacket, err := psbt.NewFromRawBytes(
		bytes.NewReader(signResp.SignedPsbt), false,
	)
	require.NoError(t, err)

	return signedPacket
}

// extractAndPublish extracts the final transaction from the packet and
// publishes it with the given node, mines a block and asserts the TX was mined
// successfully.
func extractPublishAndMine(ht *lntest.HarnessTest, node *node.HarnessNode,
	packet *psbt.Packet) *wire.MsgTx {

	err := psbt.MaybeFinalizeAll(packet)
	require.NoError(ht, err)

	finalTx, err := psbt.Extract(packet)
	require.NoError(ht, err)

	var buf bytes.Buffer
	err = finalTx.Serialize(&buf)
	require.NoError(ht, err)

	// Publish the second transaction and then mine both of them.
	node.RPC.PublishTransaction(&walletrpc.Transaction{TxHex: buf.Bytes()})

	// Mine one block which should contain two transactions.
	block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
	txHash := finalTx.TxHash()
	ht.Miner.AssertTxInBlock(block, &txHash)

	return finalTx
}

// assertPsbtSpend creates an output with the given pkScript on chain and then
// attempts to create a sweep transaction that is signed using the SignPsbt RPC
// that spends that output again.
func assertPsbtSpend(ht *lntest.HarnessTest, alice *node.HarnessNode,
	pkScript []byte, decorateUnsigned func(*psbt.Packet),
	verifySigned func(*psbt.Packet)) {

	// Let's send some coins to that address now.
	utxo := &wire.TxOut{
		Value:    600_000,
		PkScript: pkScript,
	}
	req := &walletrpc.SendOutputsRequest{
		Outputs: []*signrpc.TxOut{{
			Value:    utxo.Value,
			PkScript: utxo.PkScript,
		}},
		MinConfs:         0,
		SpendUnconfirmed: true,
		SatPerKw:         2500,
	}
	resp := alice.RPC.SendOutputs(req)

	prevTx := wire.NewMsgTx(2)
	err := prevTx.Deserialize(bytes.NewReader(resp.RawTx))
	require.NoError(ht, err)

	prevOut := -1
	for idx, txOut := range prevTx.TxOut {
		if bytes.Equal(txOut.PkScript, pkScript) {
			prevOut = idx
		}
	}
	require.Greater(ht, prevOut, -1)

	// Okay, we have everything we need to create a PSBT now.
	pendingTx := &wire.MsgTx{
		Version: 2,
		TxIn: []*wire.TxIn{{
			PreviousOutPoint: wire.OutPoint{
				Hash:  prevTx.TxHash(),
				Index: uint32(prevOut),
			},
		}},
		// We send to the same address again, but deduct some fees.
		TxOut: []*wire.TxOut{{
			Value:    utxo.Value - 600,
			PkScript: utxo.PkScript,
		}},
	}
	packet, err := psbt.NewFromUnsignedTx(pendingTx)
	require.NoError(ht, err)

	// We first try to sign the psbt without the necessary input data
	// which should fail with the expected error.
	var buf bytes.Buffer
	err = packet.Serialize(&buf)
	require.NoError(ht, err)

	signReq := &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}
	err = alice.RPC.SignPsbtErr(signReq)
	require.ErrorContains(ht, err, "input (index=0) doesn't specify "+
		"any UTXO info", "error does not match")

	// Now let's add the meta information that we need for signing.
	packet.Inputs[0].WitnessUtxo = utxo
	packet.Inputs[0].NonWitnessUtxo = prevTx
	decorateUnsigned(packet)

	// That's it, we should be able to sign the PSBT now.
	signedPacket := signPacket(ht, alice, packet)

	// Allow the caller to also verify (and potentially move) some of the
	// returned fields.
	verifySigned(signedPacket)

	// We should be able to finalize the PSBT and extract the final TX now.
	err = psbt.MaybeFinalizeAll(signedPacket)
	require.NoError(ht, err)

	finalTx, err := psbt.Extract(signedPacket)
	require.NoError(ht, err)

	// Make sure we can also sign a second time. This makes sure any key
	// tweaking that happened for the signing didn't affect any keys in the
	// cache.
	signedPacket2 := signPacket(ht, alice, packet)
	verifySigned(signedPacket2)

	buf.Reset()
	err = finalTx.Serialize(&buf)
	require.NoError(ht, err)

	// Publish the second transaction and then mine both of them.
	txReq := &walletrpc.Transaction{TxHex: buf.Bytes()}
	alice.RPC.PublishTransaction(txReq)

	// Mine one block which should contain two transactions.
	block := ht.MineBlocksAndAssertNumTxes(1, 2)[0]
	firstTxHash := prevTx.TxHash()
	secondTxHash := finalTx.TxHash()
	ht.Miner.AssertTxInBlock(block, &firstTxHash)
	ht.Miner.AssertTxInBlock(block, &secondTxHash)
}

// assertPsbtFundSignSpend funds a PSBT from the internal wallet and then
// attempts to sign it by using the SignPsbt or FinalizePsbt method.
func assertPsbtFundSignSpend(ht *lntest.HarnessTest, alice *node.HarnessNode,
	fundOutputs map[string]uint64, changeType walletrpc.ChangeAddressType,
	useFinalize bool) {

	fundResp := alice.RPC.FundPsbt(&walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_Raw{
			Raw: &walletrpc.TxTemplate{
				Outputs: fundOutputs,
			},
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 2,
		},
		MinConfs:   1,
		ChangeType: changeType,
	},
	)
	require.GreaterOrEqual(ht, fundResp.ChangeOutputIndex, int32(-1))

	// Make sure our change output has all the meta information required for
	// signing.
	fundedPacket, err := psbt.NewFromRawBytes(
		bytes.NewReader(fundResp.FundedPsbt), false,
	)
	require.NoError(ht, err)

	pOut := fundedPacket.Outputs[fundResp.ChangeOutputIndex]
	require.NotEmpty(ht, pOut.Bip32Derivation)
	derivation := pOut.Bip32Derivation[0]
	_, err = btcec.ParsePubKey(derivation.PubKey)
	require.NoError(ht, err)
	require.Len(ht, derivation.Bip32Path, 5)

	// Ensure we get the change output properly decorated with all the new
	// Taproot related fields, if it is a Taproot output.
	if changeType == walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR {
		require.NotEmpty(ht, pOut.TaprootBip32Derivation)
		require.NotEmpty(ht, pOut.TaprootInternalKey)

		trDerivation := pOut.TaprootBip32Derivation[0]
		require.Equal(
			ht, trDerivation.XOnlyPubKey, pOut.TaprootInternalKey,
		)
		_, err := schnorr.ParsePubKey(pOut.TaprootInternalKey)
		require.NoError(ht, err)
	}

	var signedPsbt []byte
	if useFinalize {
		finalizeResp := alice.RPC.FinalizePsbt(
			&walletrpc.FinalizePsbtRequest{
				FundedPsbt: fundResp.FundedPsbt,
			},
		)

		signedPsbt = finalizeResp.SignedPsbt
	} else {
		signResp := alice.RPC.SignPsbt(
			&walletrpc.SignPsbtRequest{
				FundedPsbt: fundResp.FundedPsbt,
			},
		)

		signedPsbt = signResp.SignedPsbt
	}

	// Let's make sure we have a partial signature.
	signedPacket, err := psbt.NewFromRawBytes(
		bytes.NewReader(signedPsbt), false,
	)
	require.NoError(ht, err)

	// We should be able to finalize the PSBT, extract and publish the final
	// TX now.
	finalTx := extractPublishAndMine(ht, alice, signedPacket)

	// Check type of the change script depending on the change address
	// type we provided in FundPsbt.
	changeScript := finalTx.TxOut[fundResp.ChangeOutputIndex].PkScript
	assertChangeScriptType(ht, changeScript, changeType)
}

// assertChangeScriptType checks if the given script has the right type given
// the change address type we used in FundPsbt. By default, the script should
// be a P2WPKH one.
func assertChangeScriptType(ht *lntest.HarnessTest, script []byte,
	fundChangeType walletrpc.ChangeAddressType) {

	switch fundChangeType {
	case walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
		require.True(ht, txscript.IsPayToTaproot(script))

	default:
		require.True(ht, txscript.IsPayToWitnessPubKeyHash(script))
	}
}

// deriveInternalKey derives a signing key and returns its descriptor, full
// derivation path and parsed public key.
func deriveInternalKey(ht *lntest.HarnessTest,
	alice *node.HarnessNode) (*signrpc.KeyDescriptor, *btcec.PublicKey,
	[]uint32) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}
	keyDesc := alice.RPC.DeriveNextKey(req)

	// The DeriveNextKey returns a key from the internal 1017 scope.
	fullDerivationPath := []uint32{
		hdkeychain.HardenedKeyStart + keychain.BIP0043Purpose,
		hdkeychain.HardenedKeyStart + harnessNetParams.HDCoinType,
		hdkeychain.HardenedKeyStart + uint32(keyDesc.KeyLoc.KeyFamily),
		0,
		uint32(keyDesc.KeyLoc.KeyIndex),
	}

	parsedPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes)
	require.NoError(ht, err)

	return keyDesc, parsedPubKey, fullDerivationPath
}

// sendAllCoinsToAddrType sweeps all coins from the wallet and sends them to a
// new address of the given type.
func sendAllCoinsToAddrType(ht *lntest.HarnessTest,
	hn *node.HarnessNode, addrType lnrpc.AddressType) {

	resp := hn.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: addrType,
	})

	hn.RPC.SendCoins(&lnrpc.SendCoinsRequest{
		Addr:             resp.Address,
		SendAll:          true,
		SpendUnconfirmed: true,
		TargetConf:       6,
	})

	ht.MineBlocksAndAssertNumTxes(1, 1)
	ht.WaitForBlockchainSync(hn)
}

// testPsbtChanFundingFailFlow tests the failing of a funding flow by the
// remote peer and that the initiator receives the expected error and aborts
// the channel opening. The psbt funding flow is used to simulate this behavior
// because we can easily let the remote peer run into the timeout.
func testPsbtChanFundingFailFlow(ht *lntest.HarnessTest) {
	const chanSize = funding.MaxBtcFundingAmount

	// Decrease the timeout window for the remote peer to accelerate the
	// funding fail process.
	args := []string{
		"--dev.reservationtimeout=1s",
		"--dev.zombiesweeperinterval=1s",
	}
	ht.RestartNodeWithExtraArgs(ht.Bob, args)

	// Before we start the test, we'll ensure both sides are connected so
	// the funding flow can be properly executed.
	alice := ht.Alice
	bob := ht.Bob
	ht.EnsureConnected(alice, bob)

	// At this point, we can begin our PSBT channel funding workflow. We'll
	// start by generating a pending channel ID externally that will be used
	// to track this new funding type.
	pendingChanID := ht.Random32Bytes()

	// Now that we have the pending channel ID, Alice will open the channel
	// by specifying a PSBT shim.
	chanUpdates, _ := ht.OpenChannelPsbt(
		alice, bob, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID,
					},
				},
			},
		},
	)

	// We received the AcceptChannel msg from our peer but we are not going
	// to fund this channel but instead wait for our peer to fail the
	// funding workflow with an internal error.
	ht.ReceiveOpenChannelError(chanUpdates, chanfunding.ErrRemoteCanceled)
}

// testPsbtChanFundingWithUnstableUtxos tests that channel openings with
// unstable utxos, in this case in particular unconfirmed utxos still in use by
// the sweeper subsystem, are not considered when opening a channel. They bear
// the risk of being RBFed and are therefore not safe to open a channel with.
func testPsbtChanFundingWithUnstableUtxos(ht *lntest.HarnessTest) {
	fundingAmt := btcutil.Amount(2_000_000)

	// First, we'll create two new nodes that we'll use to open channel
	// between for this test.
	carol := ht.NewNode("carol", nil)
	dave := ht.NewNode("dave", nil)
	ht.EnsureConnected(carol, dave)

	// Fund Carol's wallet with a confirmed utxo.
	ht.FundCoins(fundingAmt, carol)

	ht.AssertNumUTXOs(carol, 1)

	// Now spend the coins to create an unconfirmed transaction. This is
	// necessary to test also the neutrino behaviour. For neutrino nodes
	// only unconfirmed transactions originating from this node will be
	// recognized as unconfirmed.
	req := &lnrpc.NewAddressRequest{Type: AddrTypeTaprootPubkey}
	resp := carol.RPC.NewAddress(req)

	sendCoinsResp := carol.RPC.SendCoins(&lnrpc.SendCoinsRequest{
		Addr:        resp.Address,
		SendAll:     true,
		SatPerVbyte: 1,
	})

	walletUtxo := ht.AssertNumUTXOsUnconfirmed(carol, 1)[0]
	require.EqualValues(ht, sendCoinsResp.Txid, walletUtxo.Outpoint.TxidStr)

	chanSize := btcutil.Amount(walletUtxo.AmountSat / 2)

	// We use STATIC_REMOTE_KEY channels to easily generate sweeps without
	// anchor sweeps interfering.
	cType := lnrpc.CommitmentType_STATIC_REMOTE_KEY

	// We open a normal channel so that we can force-close it and produce
	// a sweeper originating utxo.
	update := ht.OpenChannelAssertPending(carol, dave,
		lntest.OpenChannelParams{
			Amt:              chanSize,
			SpendUnconfirmed: true,
		})
	channelPoint := lntest.ChanPointFromPendingUpdate(update)
	ht.MineBlocksAndAssertNumTxes(1, 2)

	// Now force close the channel by dave to generate a utxo which is
	// swept by the sweeper. We have STATIC_REMOTE_KEY Channel Types.
	ht.CloseChannelAssertPending(dave, channelPoint, true)
	ht.MineBlocksAndAssertNumTxes(1, 1)

	// Mine one block to trigger the sweep transaction.
	ht.MineEmptyBlocks(1)

	// We wait for the to_remote sweep tx.
	ht.AssertNumUTXOsUnconfirmed(carol, 1)

	// We need the maximum funding amount to ensure we are opening the next
	// channel with all available utxos.
	carolBalance := carol.RPC.WalletBalance()

	// The max chan size needs to account for the fee opening the channel
	// itself.
	// NOTE: We need to always account for a change here, because their is
	// an inaccurarcy in the backend code.
	chanSize = btcutil.Amount(carolBalance.TotalBalance) -
		fundingFee(2, true)

	// Now open a channel of this amount via a psbt workflow.
	// At this point, we can begin our PSBT channel funding workflow. We'll
	// start by generating a pending channel ID externally that will be used
	// to track this new funding type.
	pendingChanID := ht.Random32Bytes()

	// Now that we have the pending channel ID, Carol will open the channel
	// by specifying a PSBT shim. We expect it to fail because we try to
	// fund a channel with the maximum amount of our wallet, which also
	// includes an unstable utxo originating from the sweeper.
	chanUpdates, tempPsbt := ht.OpenChannelPsbt(
		carol, dave, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID,
					},
				},
			},
			CommitmentType:   cType,
			SpendUnconfirmed: true,
		},
	)

	fundReq := &walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_Psbt{
			Psbt: tempPsbt,
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 50,
		},
		MinConfs:         0,
		SpendUnconfirmed: true,
	}
	carol.RPC.FundPsbtAssertErr(fundReq)

	// We confirm the sweep transaction and make sure we see it as confirmed
	// from the perspective of the underlying wallet.
	ht.MineBlocksAndAssertNumTxes(1, 1)

	// We expect 2 confirmed utxos, the change of the prior successful
	// channel opening and the confirmed to_remote output.
	ht.AssertNumUTXOsConfirmed(carol, 2)

	// We fund the psbt request again and now all utxo are stable and can
	// finally be used to fund the channel.
	fundResp := carol.RPC.FundPsbt(fundReq)

	// We verify the psbt before finalizing it.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID,
				FundedPsbt:    fundResp.FundedPsbt,
			},
		},
	})

	// Now we'll ask Carol's wallet to sign the PSBT so we can finish the
	// funding flow.
	finalizeReq := &walletrpc.FinalizePsbtRequest{
		FundedPsbt: fundResp.FundedPsbt,
	}
	finalizeRes := carol.RPC.FinalizePsbt(finalizeReq)

	// We've signed our PSBT now, let's pass it to the intent again.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
			PsbtFinalize: &lnrpc.FundingPsbtFinalize{
				PendingChanId: pendingChanID,
				SignedPsbt:    finalizeRes.SignedPsbt,
			},
		},
	})

	// Consume the "channel pending" update. This waits until the funding
	// transaction was fully compiled.
	updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates)
	upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	channelPoint2 := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd.ChanPending.Txid,
		},
		OutputIndex: upd.ChanPending.OutputIndex,
	}

	var finalTx wire.MsgTx
	err := finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx))
	require.NoError(ht, err)

	txHash := finalTx.TxHash()
	block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
	ht.Miner.AssertTxInBlock(block, &txHash)

	// Now we do the same but instead use preselected utxos to verify that
	// these utxos respects the utxo restrictions on sweeper unconfirmed
	// inputs as well.

	// Now force close the channel by dave to generate a utxo which is
	// swept by the sweeper. We have STATIC_REMOTE_KEY Channel Types.
	ht.CloseChannelAssertPending(dave, channelPoint2, true)
	ht.MineBlocksAndAssertNumTxes(1, 1)

	// Mine one block to trigger the sweep transaction.
	ht.MineEmptyBlocks(1)

	// We wait for the to_remote sweep tx of channelPoint2.
	utxos := ht.AssertNumUTXOsUnconfirmed(carol, 1)

	// We need the maximum funding amount to ensure we are opening the next
	// channel with all available utxos.
	carolBalance = carol.RPC.WalletBalance()

	// The max chan size needs to account for the fee opening the channel
	// itself.
	// NOTE: We need to always account for a change here, because their is
	// an inaccurarcy in the backend code calculating the fee of a 1 input
	// one output transaction, it always account for a channge in that case
	// as well.
	chanSize = btcutil.Amount(carolBalance.TotalBalance) -
		fundingFee(2, true)

	// Now open a channel of this amount via a psbt workflow.
	// At this point, we can begin our PSBT channel funding workflow. We'll
	// start by generating a pending channel ID externally that will be used
	// to track this new funding type.
	pendingChanID = ht.Random32Bytes()

	// Now that we have the pending channel ID, Carol will open the channel
	// by specifying a PSBT shim. We expect it to fail because we try to
	// fund a channel with the maximum amount of our wallet, which also
	// includes an unstable utxo originating from the sweeper.
	chanUpdates, tempPsbt = ht.OpenChannelPsbt(
		carol, dave, lntest.OpenChannelParams{
			Amt: chanSize,
			FundingShim: &lnrpc.FundingShim{
				Shim: &lnrpc.FundingShim_PsbtShim{
					PsbtShim: &lnrpc.PsbtShim{
						PendingChanId: pendingChanID,
					},
				},
			},
			CommitmentType:   cType,
			SpendUnconfirmed: true,
		},
	)
	// Add selected utxos to the funding intent.
	decodedPsbt, err := psbt.NewFromRawBytes(
		bytes.NewReader(tempPsbt), false,
	)
	require.NoError(ht, err)

	for _, input := range utxos {
		txHash, err := chainhash.NewHashFromStr(input.Outpoint.TxidStr)
		require.NoError(ht, err)
		decodedPsbt.UnsignedTx.TxIn = append(
			decodedPsbt.UnsignedTx.TxIn, &wire.TxIn{
				PreviousOutPoint: wire.OutPoint{
					Hash:  *txHash,
					Index: input.Outpoint.OutputIndex,
				},
			})

		// The inputs we are using to fund the transaction are known to
		// the internal wallet that's why we just append an empty input
		// element so that the parsing of the psbt package succeeds.
		decodedPsbt.Inputs = append(decodedPsbt.Inputs, psbt.PInput{})
	}

	var psbtBytes bytes.Buffer
	err = decodedPsbt.Serialize(&psbtBytes)
	require.NoError(ht, err)

	fundReq = &walletrpc.FundPsbtRequest{
		Template: &walletrpc.FundPsbtRequest_Psbt{
			Psbt: psbtBytes.Bytes(),
		},
		Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
			SatPerVbyte: 50,
		},
		MinConfs:         0,
		SpendUnconfirmed: true,
	}
	carol.RPC.FundPsbtAssertErr(fundReq)

	ht.MineBlocksAndAssertNumTxes(1, 1)

	// We expect 2 confirmed utxos, the change of the last successful
	// channel opening and the confirmed to_remote output of channelPoint2.
	ht.AssertNumUTXOsConfirmed(carol, 2)

	// After the confirmation of the sweep to_remote output the funding
	// will now proceed.
	fundResp = carol.RPC.FundPsbt(fundReq)

	// We verify the funded psbt.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
			PsbtVerify: &lnrpc.FundingPsbtVerify{
				PendingChanId: pendingChanID,
				FundedPsbt:    fundResp.FundedPsbt,
			},
		},
	})

	// Now we'll ask Carol's wallet to sign the PSBT so we can finish the
	// funding flow.
	finalizeReq = &walletrpc.FinalizePsbtRequest{
		FundedPsbt: fundResp.FundedPsbt,
	}
	finalizeRes = carol.RPC.FinalizePsbt(finalizeReq)

	// We've signed our PSBT now, let's pass it to the intent again.
	carol.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
		Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
			PsbtFinalize: &lnrpc.FundingPsbtFinalize{
				PendingChanId: pendingChanID,
				SignedPsbt:    finalizeRes.SignedPsbt,
			},
		},
	})

	// Consume the "channel pending" update. This waits until the funding
	// transaction was fully compiled.
	updateResp = ht.ReceiveOpenChannelUpdate(chanUpdates)
	upd, ok = updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
	require.True(ht, ok)
	channelPoint3 := &lnrpc.ChannelPoint{
		FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
			FundingTxidBytes: upd.ChanPending.Txid,
		},
		OutputIndex: upd.ChanPending.OutputIndex,
	}

	err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx))
	require.NoError(ht, err)

	txHash = finalTx.TxHash()
	block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
	ht.Miner.AssertTxInBlock(block, &txHash)

	ht.CloseChannel(carol, channelPoint3)
}
