mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-15 03:51:23 +01:00
Merge pull request #5539 from Kixunil/no-psbt-midstep-enforce-new-reserved-value
Do not enforce new reserved value in PSBT midstep
This commit is contained in:
commit
1ccd03784b
4 changed files with 216 additions and 6 deletions
|
@ -5,6 +5,15 @@
|
||||||
* [Return the nearest known fee rate when a given conf target cannot be found
|
* [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)
|
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
|
## Build System
|
||||||
|
|
||||||
* [Clean up Makefile by using go
|
* [Clean up Makefile by using go
|
||||||
|
@ -35,6 +44,7 @@
|
||||||
|
|
||||||
* Andras Banki-Horvath
|
* Andras Banki-Horvath
|
||||||
* Harsha Goli
|
* Harsha Goli
|
||||||
|
* Martin Habovštiak
|
||||||
* Naveen Srinivasan
|
* Naveen Srinivasan
|
||||||
* Oliver Gugger
|
* Oliver Gugger
|
||||||
* Yong Yu
|
* Yong Yu
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/lightningnetwork/lnd/funding"
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
|
@ -466,6 +468,189 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
closeChannelAndAssert(t, net, carol, chanPoint2, false)
|
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
|
// openChannelPsbt attempts to open a channel between srcNode and destNode with
|
||||||
// the passed channel funding parameters. If the passed context has a timeout,
|
// the passed channel funding parameters. If the passed context has a timeout,
|
||||||
// then if the timeout is reached before the channel pending notification is
|
// then if the timeout is reached before the channel pending notification is
|
||||||
|
|
|
@ -287,6 +287,10 @@ var allTestCases = []*testCase{
|
||||||
name: "batch channel funding",
|
name: "batch channel funding",
|
||||||
test: testBatchChanFunding,
|
test: testBatchChanFunding,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "psbt channel funding single step",
|
||||||
|
test: testPsbtChanFundingSingleStep,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "sendtoroute multi path payment",
|
name: "sendtoroute multi path payment",
|
||||||
test: testSendToRouteMultiPath,
|
test: testSendToRouteMultiPath,
|
||||||
|
|
|
@ -706,6 +706,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||||
return
|
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
|
// If no chanFunder was provided, then we'll assume the default
|
||||||
// assembler, which is backed by the wallet's internal coin selection.
|
// assembler, which is backed by the wallet's internal coin selection.
|
||||||
if req.ChanFunder == nil {
|
if req.ChanFunder == nil {
|
||||||
|
@ -720,6 +726,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||||
DustLimit: DustLimitForSize(input.P2WSHSize),
|
DustLimit: DustLimitForSize(input.P2WSHSize),
|
||||||
}
|
}
|
||||||
req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
|
req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
|
||||||
|
} else {
|
||||||
|
_, isPsbtFunder := req.ChanFunder.(*chanfunding.PsbtAssembler)
|
||||||
|
enforceNewReservedValue = !isPsbtFunder
|
||||||
}
|
}
|
||||||
|
|
||||||
localFundingAmt := req.LocalFundingAmt
|
localFundingAmt := req.LocalFundingAmt
|
||||||
|
@ -819,13 +828,15 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||||
// when the PSBT has been verified.
|
// when the PSBT has been verified.
|
||||||
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
|
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
|
||||||
hasAnchors := req.CommitType.HasAnchors()
|
hasAnchors := req.CommitType.HasAnchors()
|
||||||
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
|
if enforceNewReservedValue {
|
||||||
if err != nil {
|
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
|
||||||
fundingIntent.Cancel()
|
if err != nil {
|
||||||
|
fundingIntent.Cancel()
|
||||||
|
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The total channel capacity will be the size of the funding output we
|
// The total channel capacity will be the size of the funding output we
|
||||||
|
|
Loading…
Add table
Reference in a new issue