openchannel: optimize open channel request creation and err handling

This commit is contained in:
Slyghtning 2026-02-23 12:43:16 +01:00
parent cd377f35f8
commit 6cb54edf06
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
2 changed files with 115 additions and 72 deletions

View file

@ -23,6 +23,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"google.golang.org/protobuf/proto"
)
const (
@ -366,64 +367,55 @@ func (m *Manager) OpenChannel(ctx context.Context,
return nil, err
}
openChanRequest := &lnrpc.OpenChannelRequest{
NodePubkey: req.NodePubkey,
LocalFundingAmount: int64(chanFundingAmt),
PushSat: req.PushSat,
Private: req.Private,
MinHtlcMsat: req.MinHtlcMsat,
RemoteCsvDelay: req.RemoteCsvDelay,
MinConfs: defaultUtxoMinConf,
SpendUnconfirmed: false,
CloseAddress: req.CloseAddress,
RemoteMaxValueInFlightMsat: req.RemoteMaxValueInFlightMsat,
RemoteMaxHtlcs: req.RemoteMaxHtlcs,
MaxLocalCsv: req.MaxLocalCsv,
CommitmentType: chanCommitmentType,
ZeroConf: req.ZeroConf,
ScidAlias: req.ScidAlias,
BaseFee: req.BaseFee,
FeeRate: req.FeeRate,
UseBaseFee: req.UseBaseFee,
UseFeeRate: req.UseFeeRate,
RemoteChanReserveSat: req.RemoteChanReserveSat,
Memo: req.Memo,
// Clone the request message and set mandatory parameters.
reqClone := proto.Clone(req).(*lnrpc.OpenChannelRequest)
// Override fields consumed locally or incompatible with PSBT funding.
reqClone.LocalFundingAmount = int64(chanFundingAmt)
// TODO: Once lnd's PSBT channel open flow supports fundmax natively,
// we can pass FundMax through to lnd and remove Loop's local coin
// selection for the fundmax case.
reqClone.FundMax = false
reqClone.MinConfs = defaultUtxoMinConf
reqClone.SpendUnconfirmed = false
// In the lnd PSBT flow, fee estimation params on the request are
// explicitly disallowed.
reqClone.SatPerVbyte = 0
reqClone.CommitmentType = chanCommitmentType
chanOutpoint, err := m.openChannelPsbt(ctx, reqClone, deposits, feeRate)
if err == nil {
return chanOutpoint, nil
}
chanOutpoint, err := m.openChannelPsbt(
ctx, openChanRequest, deposits, feeRate,
)
if err != nil {
log.Infof("error opening channel: %v", err)
log.Infof("error opening channel: %v", err)
// If the PSBT was already finalized and sent to lnd, the
// funding transaction may have been broadcast. In that case
// we must not blindly roll back. Instead, try to recover
// the deposits now so they don't remain stuck in
// OpeningChannel until the next restart.
if errors.Is(err, errPsbtFinalized) {
recoverErr := m.recoverOpeningChannelDeposits(ctx)
if recoverErr != nil {
log.Errorf("failed recovering deposits "+
"after PSBT finalize: %v",
recoverErr)
}
} else {
err2 := m.cfg.DepositManager.TransitionDeposits(
ctx, deposits, fsm.OnError,
deposit.Deposited,
)
if err2 != nil {
log.Errorf("failed transitioning deposits "+
"after failed channel open: %v",
err2)
}
// If the PSBT was already finalized and sent to lnd, the
// funding transaction may have been broadcast. In that case
// we must not blindly roll back. Instead, try to recover
// the deposits now so they don't remain stuck in
// OpeningChannel until the next restart.
if errors.Is(err, errPsbtFinalized) {
recoverErr := m.recoverOpeningChannelDeposits(ctx)
if recoverErr != nil {
log.Errorf("failed recovering deposits "+
"after PSBT finalize: %v",
recoverErr)
}
} else {
err2 := m.cfg.DepositManager.TransitionDeposits(
ctx, deposits, fsm.OnError,
deposit.Deposited,
)
if err2 != nil {
log.Errorf("failed transitioning deposits "+
"after failed channel open: %v",
err2)
}
return nil, err
}
return chanOutpoint, nil
return nil, err
}
// openChannelPsbt starts an interactive channel open protocol that uses a
@ -455,6 +447,7 @@ func (m *Manager) openChannelPsbt(ctx context.Context,
psbtFinalized bool
basePsbtBytes []byte
quit = make(chan struct{})
quitCause error
closeQuitOnce sync.Once
srvMsg = make(chan *lnrpc.OpenStatusUpdate, 1)
srvErr = make(chan error, 1)
@ -594,6 +587,8 @@ func (m *Manager) openChannelPsbt(ctx context.Context,
shimPending = false
shimMu.Unlock()
}
quitCause = err
closeQuit()
case <-quit:
@ -606,6 +601,9 @@ func (m *Manager) openChannelPsbt(ctx context.Context,
case srvResponse = <-srvMsg:
case <-quit:
cancelErr := fmt.Errorf("open channel flow canceled")
if quitCause != nil {
cancelErr = quitCause
}
if psbtFinalized {
return nil, fmt.Errorf("%w: %v",
errPsbtFinalized, cancelErr)
@ -757,6 +755,26 @@ func validateInitialPsbtFlags(req *lnrpc.OpenChannelRequest) error {
"for PSBT funding")
}
if req.TargetConf != 0 {
return fmt.Errorf("TargetConf is not supported for PSBT " +
"funding, use SatPerVbyte to specify fee rate")
}
if req.SatPerByte != 0 { //nolint:staticcheck
return fmt.Errorf("SatPerByte is deprecated and not " +
"supported for PSBT funding, use SatPerVbyte")
}
if req.NodePubkeyString != "" { //nolint:staticcheck
return fmt.Errorf("NodePubkeyString is not supported, " +
"use NodePubkey instead")
}
if req.FundingShim != nil {
return fmt.Errorf("FundingShim is not supported, it is " +
"managed internally for PSBT funding")
}
return nil
}

View file

@ -471,42 +471,67 @@ func TestValidateInitialPsbtFlags(t *testing.T) {
tests := []struct {
name string
minConfs int32
spendUnconfirmed bool
req *lnrpc.OpenChannelRequest
expectedErrSubstr string
}{
{
name: "default min confs accepted",
minConfs: 0,
spendUnconfirmed: false,
name: "default min confs accepted",
req: &lnrpc.OpenChannelRequest{},
},
{
name: "explicit default min confs accepted",
minConfs: defaultUtxoMinConf,
spendUnconfirmed: false,
name: "explicit default min confs accepted",
req: &lnrpc.OpenChannelRequest{
MinConfs: defaultUtxoMinConf,
},
},
{
name: "custom min confs rejected",
minConfs: defaultUtxoMinConf + 1,
spendUnconfirmed: false,
name: "custom min confs rejected",
req: &lnrpc.OpenChannelRequest{
MinConfs: defaultUtxoMinConf + 1,
},
expectedErrSubstr: "custom MinConfs not supported",
},
{
name: "spend unconfirmed rejected",
minConfs: defaultUtxoMinConf,
spendUnconfirmed: true,
name: "spend unconfirmed rejected",
req: &lnrpc.OpenChannelRequest{
MinConfs: defaultUtxoMinConf,
SpendUnconfirmed: true,
},
expectedErrSubstr: "SpendUnconfirmed is not supported",
},
{
name: "target conf rejected",
req: &lnrpc.OpenChannelRequest{
TargetConf: 6,
},
expectedErrSubstr: "TargetConf is not supported",
},
{
name: "sat per byte rejected",
req: &lnrpc.OpenChannelRequest{
SatPerByte: 10, //nolint:staticcheck
},
expectedErrSubstr: "SatPerByte is deprecated",
},
{
name: "node pubkey string rejected",
req: &lnrpc.OpenChannelRequest{
NodePubkeyString: "abc", //nolint:staticcheck
},
expectedErrSubstr: "NodePubkeyString is not supported",
},
{
name: "funding shim rejected",
req: &lnrpc.OpenChannelRequest{
FundingShim: &lnrpc.FundingShim{},
},
expectedErrSubstr: "FundingShim is not supported",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
req := &lnrpc.OpenChannelRequest{
MinConfs: tc.minConfs,
SpendUnconfirmed: tc.spendUnconfirmed,
}
err := validateInitialPsbtFlags(req)
err := validateInitialPsbtFlags(tc.req)
if tc.expectedErrSubstr == "" {
require.NoError(t, err)
return