From 2b3b70d40bae9eaad1b98bbd564e781f7806787f Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Wed, 11 Aug 2021 17:36:45 +0200 Subject: [PATCH] test: don't enforce reserved value in PSBT midstep This adds an integration test that makes sure channel can be funded from empty wallet using PSBT if the funding transaction contains an output belonging to the wallet, satisfying the reserve. --- lntest/itest/lnd_psbt_test.go | 185 ++++++++++++++++++++++++++ lntest/itest/lnd_test_list_on_test.go | 4 + 2 files changed, 189 insertions(+) diff --git a/lntest/itest/lnd_psbt_test.go b/lntest/itest/lnd_psbt_test.go index 22ab54a5d..5dfdac776 100644 --- a/lntest/itest/lnd_psbt_test.go +++ b/lntest/itest/lnd_psbt_test.go @@ -6,8 +6,10 @@ import ( "crypto/rand" "fmt" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/psbt" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -466,6 +468,189 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(t, net, carol, chanPoint2, false) } +// testPsbtChanFundingSingleStep 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 testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + const chanSize = funding.MaxBtcFundingAmount + + args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) + + // First, we'll create two new nodes that we'll use to open channels + // between for this test. But in this case both nodes have an empty + // wallet. + carol := net.NewNode(t.t, "carol", args) + defer shutdownAndAssert(net, t, carol) + + dave := net.NewNode(t.t, "dave", args) + defer shutdownAndAssert(net, t, dave) + + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) + + // Get new address for anchor reserve. + reserveAddrReq := &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + } + addrResp, err := carol.NewAddress(ctxb, reserveAddrReq) + require.NoError(t.t, err) + reserveAddr, err := btcutil.DecodeAddress(addrResp.Address, harnessNetParams) + require.NoError(t.t, err) + reserveAddrScript, err := txscript.PayToAddrScript(reserveAddr) + require.NoError(t.t, err) + + // Before we start the test, we'll ensure both sides are connected so + // the funding flow can be properly executed. + net.EnsureConnected(t.t, 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. + var pendingChanID [32]byte + _, err = rand.Read(pendingChanID[:]) + require.NoError(t.t, err) + + // Now that we have the pending channel ID, Carol will open the channel + // by specifying a PSBT shim. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + chanUpdates, tempPsbt, err := openChannelPsbt( + ctxt, carol, dave, lntest.OpenChannelParams{ + Amt: chanSize, + FundingShim: &lnrpc.FundingShim{ + Shim: &lnrpc.FundingShim_PsbtShim{ + PsbtShim: &lnrpc.PsbtShim{ + PendingChanId: pendingChanID[:], + NoPublish: false, + }, + }, + }, + }, + ) + require.NoError(t.t, err) + + decodedPsbt, err := psbt.NewFromRawBytes(bytes.NewReader(tempPsbt), false) + require.NoError(t.t, 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(t.t, err) + + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + fundReq := &walletrpc.FundPsbtRequest{ + Template: &walletrpc.FundPsbtRequest_Psbt{ + Psbt: psbtBytes.Bytes(), + }, + Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{ + SatPerVbyte: 2, + }, + } + fundResp, err := net.Alice.WalletKitClient.FundPsbt(ctxt, fundReq) + require.NoError(t.t, err) + + // Make sure the wallets are actually empty + unspentCarol, err := carol.ListUnspent(ctxb, &lnrpc.ListUnspentRequest{}) + require.NoError(t.t, err) + require.Len(t.t, unspentCarol.Utxos, 0) + + unspentDave, err := dave.ListUnspent(ctxb, &lnrpc.ListUnspentRequest{}) + require.NoError(t.t, err) + require.Len(t.t, unspentDave.Utxos, 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. + _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{ + PsbtVerify: &lnrpc.FundingPsbtVerify{ + PendingChanId: pendingChanID[:], + FundedPsbt: fundResp.FundedPsbt, + }, + }, + }) + require.NoError(t.t, err) + + // Now we'll ask Alice's wallet to sign the PSBT so we can finish the + // funding flow. + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + finalizeReq := &walletrpc.FinalizePsbtRequest{ + FundedPsbt: fundResp.FundedPsbt, + } + finalizeRes, err := net.Alice.WalletKitClient.FinalizePsbt(ctxt, finalizeReq) + require.NoError(t.t, err) + + // We've signed our PSBT now, let's pass it to the intent again. + _, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{ + Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{ + PsbtFinalize: &lnrpc.FundingPsbtFinalize{ + PendingChanId: pendingChanID[:], + SignedPsbt: finalizeRes.SignedPsbt, + }, + }, + }) + require.NoError(t.t, err) + + // Consume the "channel pending" update. This waits until the funding + // transaction was fully compiled. + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + updateResp, err := receiveChanUpdate(ctxt, chanUpdates) + require.NoError(t.t, err) + upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) + require.True(t.t, 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(t.t, err) + + txHash := finalTx.TxHash() + block := mineBlocks(t, net, 6, 1)[0] + assertTxInBlock(t, block, &txHash) + err = carol.WaitForNetworkChannelOpen(chanPoint) + require.NoError(t.t, err) + + // 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), + } + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + resp, err := dave.AddInvoice(ctxt, invoice) + require.NoError(t.t, err) + err = completePaymentRequests( + carol, carol.RouterClient, []string{resp.PaymentRequest}, + true, + ) + require.NoError(t.t, err) + + // 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. + closeChannelAndAssert(t, net, carol, chanPoint, false) +} + // openChannelPsbt attempts to open a channel between srcNode and destNode with // the passed channel funding parameters. If the passed context has a timeout, // then if the timeout is reached before the channel pending notification is diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 5fa6b9592..21a80fdc5 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -287,6 +287,10 @@ var allTestCases = []*testCase{ name: "batch channel funding", test: testBatchChanFunding, }, + { + name: "psbt channel funding single step", + test: testPsbtChanFundingSingleStep, + }, { name: "sendtoroute multi path payment", test: testSendToRouteMultiPath,