Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tapchannel: use defer to ensure lease unlocked if funding failure #1033

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 50 additions & 24 deletions tapchannel/aux_funding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@ func (p *pendingAssetFunding) unlockInputs(ctx context.Context,
func (p *pendingAssetFunding) unlockAssetInputs(ctx context.Context,
coinSelect tapfreighter.CoinSelector) error {

log.Debugf("unlocking asset inputs: %v",
spew.Sdump(p.lockedAssetInputs))

err := coinSelect.ReleaseCoins(ctx, p.lockedAssetInputs...)
if err != nil {
return fmt.Errorf("unable to unlock asset outpoints %v: %w",
Expand Down Expand Up @@ -1028,13 +1031,6 @@ func (f *FundingController) completeChannelFunding(ctx context.Context,
return nil, err
}

fundingState.lockedAssetInputs = fn.Map(
Copy link
Member Author

@Roasbeef Roasbeef Jul 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this down (called earlier) so we track the locked inputs as soon as the initial vPSBT funding finishes.

fundedVpkt.VPacket.Inputs,
func(in *tappsbt.VInput) wire.OutPoint {
return in.PrevID.OutPoint
},
)

// Now that we have the initial skeleton for our funding PSBT, we'll
// modify the output value to match the channel amt asked for, which
// lnd will expect.
Expand Down Expand Up @@ -1356,6 +1352,40 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
return fmt.Errorf("unable to fund vPacket: %w", err)
}

// Now that we've funded the vPk, keep track of the set of inputs we
// locked to ensure we unlock them later.
fundingState.lockedAssetInputs = fn.Map(
fundingVpkt.VPacket.Inputs,
func(in *tappsbt.VInput) wire.OutPoint {
return in.PrevID.OutPoint
},
)

// We'll use this closure to ensure that we'll always unlock the inputs
// if we encounter an error below.
unlockLeases := func() {
err := fundingState.unlockInputs(fundReq.ctx, f.cfg.ChainWallet)
if err != nil {
log.Errorf("unable to unlock inputs: %v", err)
}

err = fundingState.unlockAssetInputs(
fundReq.ctx, f.cfg.CoinSelector,
)
if err != nil {
log.Errorf("Unable to unlock asset inputs: %v", err)
}
}

// Register a defer to execute if none of the set up below succeeds.
// This ensure we always unlock the UTXO.
var setupSuccess bool
defer func() {
if !setupSuccess {
unlockLeases()
}
}()

// Now that we know the final funding asset root along with the splits,
// we can derive the tapscript root that'll be used alongside the
// internal key (which we'll only learn from lnd later as we finalize
Expand Down Expand Up @@ -1394,12 +1424,23 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
"proofs: %w", err)
}

setupSuccess = true

// With the ownership proof sent, we'll now spawn a goroutine to take
// care of the final funding steps.
f.Wg.Add(1)
go func() {
defer f.Wg.Done()

// If we've failed, then we'll unlock any of the locked
// UTXOs, so they're free again.
var completeSuccess bool
defer func() {
if !completeSuccess {
unlockLeases()
}
}()

log.Infof("Waiting for funding ack...")

// Before we proceed with the channel funding, we'll wait to
Expand All @@ -1426,23 +1467,6 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
fundReq.ctx, fundingState, fundingVpkt,
)
if err != nil {
// If we've failed, then we'll unlock any of the locked
// UTXOs, so they're free again.
uErr := fundingState.unlockInputs(
fundReq.ctx, f.cfg.ChainWallet,
)
if uErr != nil {
log.Errorf("unable to unlock inputs: %v", uErr)
}

uErr = fundingState.unlockAssetInputs(
fundReq.ctx, f.cfg.CoinSelector,
)
if uErr != nil {
log.Errorf("Unable to unlock asset inputs: %v",
uErr)
}

// If anything went wrong during the funding process,
// the remote side might have an in-memory state and
// wouldn't allow us to try again within the next 10
Expand All @@ -1460,6 +1484,8 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
return
}

completeSuccess = true

fundReq.respChan <- chanPoint
}()

Expand Down
Loading