Skip to content

Commit

Permalink
Merge pull request #5539 from Kixunil/no-psbt-midstep-enforce-new-res…
Browse files Browse the repository at this point in the history
…erved-value

Do not enforce new reserved value in PSBT midstep
  • Loading branch information
guggero authored Dec 10, 2021
2 parents f022e55 + 333fe39 commit 1ccd037
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 6 deletions.
10 changes: 10 additions & 0 deletions docs/release-notes/release-notes-0.14.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
* [Return the nearest known fee rate when a given conf target cannot be found
from Web API fee estimator.](https://github.com/lightningnetwork/lnd/pull/6062)

## Wallet

* A bug that prevented opening anchor-based channels from external wallets when
the internal wallet was empty even though the transaction contained a
sufficiently large output belonging to the internal wallet
[was fixed](https://github.com/lightningnetwork/lnd/pull/5539)
In other words, freshly-installed LND can now be initailized with multiple
channels from an external (e.g. hardware) wallet *in a single transaction*.

## Build System

* [Clean up Makefile by using go
Expand Down Expand Up @@ -35,6 +44,7 @@

* Andras Banki-Horvath
* Harsha Goli
* Martin Habovštiak
* Naveen Srinivasan
* Oliver Gugger
* Yong Yu
185 changes: 185 additions & 0 deletions lntest/itest/lnd_psbt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lntest/itest/lnd_test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 17 additions & 6 deletions lnwallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
return
}

// We need to avoid enforcing reserved value in the middle of PSBT
// funding because some of the following steps may add UTXOs funding
// the on-chain wallet.
// The enforcement still happens at the last step - in PsbtFundingVerify
enforceNewReservedValue := true

// If no chanFunder was provided, then we'll assume the default
// assembler, which is backed by the wallet's internal coin selection.
if req.ChanFunder == nil {
Expand All @@ -720,6 +726,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
DustLimit: DustLimitForSize(input.P2WSHSize),
}
req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
} else {
_, isPsbtFunder := req.ChanFunder.(*chanfunding.PsbtAssembler)
enforceNewReservedValue = !isPsbtFunder
}

localFundingAmt := req.LocalFundingAmt
Expand Down Expand Up @@ -819,13 +828,15 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
// when the PSBT has been verified.
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
hasAnchors := req.CommitType.HasAnchors()
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
if err != nil {
fundingIntent.Cancel()
if enforceNewReservedValue {
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
if err != nil {
fundingIntent.Cancel()

req.err <- err
req.resp <- nil
return
req.err <- err
req.resp <- nil
return
}
}

// The total channel capacity will be the size of the funding output we
Expand Down

0 comments on commit 1ccd037

Please sign in to comment.