Skip to content

Commit 75e8264

Browse files
committed
tapfreighter: release asset UTXOs on error
Previously we only released/unlocked the BTC level outputs we leased from the lnd wallet when an error occurred. But if we're still in a state where nothing has been written to disk and the user can try again, we can safely release/unlock the asset level UTXOs as well to avoid them being locked for 10 minutes.
1 parent 59841f0 commit 75e8264

File tree

1 file changed

+50
-1
lines changed

1 file changed

+50
-1
lines changed

tapfreighter/chain_porter.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,9 +1075,14 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
10751075
// At this point, we have everything we need to sign our _virtual_
10761076
// transaction on the Taproot Asset layer.
10771077
case SendStateVirtualSign:
1078+
ctx, cancel := p.WithCtxQuitNoTimeout()
1079+
defer cancel()
1080+
10781081
vPackets := currentPkg.VirtualPackets
10791082
err := tapsend.ValidateVPacketVersions(vPackets)
10801083
if err != nil {
1084+
p.unlockInputs(ctx, &currentPkg)
1085+
10811086
return nil, err
10821087
}
10831088

@@ -1091,6 +1096,8 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
10911096

10921097
_, err := p.cfg.AssetWallet.SignVirtualPacket(vPkt)
10931098
if err != nil {
1099+
p.unlockInputs(ctx, &currentPkg)
1100+
10941101
return nil, fmt.Errorf("unable to sign and "+
10951102
"commit virtual packet: %w", err)
10961103
}
@@ -1125,6 +1132,8 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
11251132
ctx, tapsend.SendConfTarget,
11261133
)
11271134
if err != nil {
1135+
p.unlockInputs(ctx, &currentPkg)
1136+
11281137
return nil, fmt.Errorf("unable to estimate "+
11291138
"fee: %w", err)
11301139
}
@@ -1148,6 +1157,8 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
11481157
currentPkg.InputCommitments,
11491158
)
11501159
if err != nil {
1160+
p.unlockInputs(ctx, &currentPkg)
1161+
11511162
return nil, fmt.Errorf("unable to create passive "+
11521163
"assets: %w", err)
11531164
}
@@ -1156,6 +1167,8 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
11561167
len(currentPkg.PassiveAssets))
11571168
err = wallet.SignPassiveAssets(currentPkg.PassiveAssets)
11581169
if err != nil {
1170+
p.unlockInputs(ctx, &currentPkg)
1171+
11591172
return nil, fmt.Errorf("unable to sign passive "+
11601173
"assets: %w", err)
11611174
}
@@ -1168,6 +1181,8 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
11681181
},
11691182
)
11701183
if err != nil {
1184+
p.unlockInputs(ctx, &currentPkg)
1185+
11711186
return nil, fmt.Errorf("unable to anchor virtual "+
11721187
"transactions: %w", err)
11731188
}
@@ -1382,10 +1397,44 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
13821397

13831398
// unlockInputs unlocks the inputs that were locked for the given package.
13841399
func (p *ChainPorter) unlockInputs(ctx context.Context, pkg *sendPackage) {
1385-
if pkg == nil || pkg.AnchorTx == nil || pkg.AnchorTx.FundedPsbt == nil {
1400+
// Impossible state, but catch it anyway.
1401+
if pkg == nil {
1402+
return
1403+
}
1404+
1405+
// If we haven't even attempted to broadcast yet, we're still in a state
1406+
// where we give feedback to the user synchronously, as we haven't
1407+
// created an on-chain transaction that we need to await confirmation.
1408+
// We also haven't written the transfer to disk yet, so we can just
1409+
// release/unlock the _asset_ level UTXOs so the user can try again. We
1410+
// sanity-check that we have known input commitments to unlock, since
1411+
// that might not always be the case (for example if another party
1412+
// contributes inputs).
1413+
if pkg.SendState < SendStateStorePreBroadcast &&
1414+
len(pkg.InputCommitments) > 0 {
1415+
1416+
for prevID := range pkg.InputCommitments {
1417+
log.Debugf("Unlocking input %v", prevID.OutPoint)
1418+
1419+
err := p.cfg.AssetWallet.ReleaseCoins(
1420+
ctx, prevID.OutPoint,
1421+
)
1422+
if err != nil {
1423+
log.Warnf("Unable to unlock input %v: %v",
1424+
prevID.OutPoint, err)
1425+
}
1426+
}
1427+
}
1428+
1429+
// If we're in another state, the anchor transaction has been created,
1430+
// and we can't simply unlock the asset level inputs. This will likely
1431+
// require manual intervention.
1432+
if pkg.AnchorTx == nil || pkg.AnchorTx.FundedPsbt == nil {
13861433
return
13871434
}
13881435

1436+
// We need to unlock any _BTC_ level inputs we locked for the anchor
1437+
// transaction.
13891438
for _, op := range pkg.AnchorTx.FundedPsbt.LockedUTXOs {
13901439
err := p.cfg.Wallet.UnlockInput(ctx, op)
13911440
if err != nil {

0 commit comments

Comments
 (0)