Skip to content

tapfreighter/[bug]: universe proof transfer not resumed after restart #1009

Open
@Roasbeef

Description

@Roasbeef

Background

After we confirm a transfer, and update proofs (only for the file based store), we'll attempt to upload any proofs (if needed) to a universe:

// At this point, the transfer transaction is confirmed on-chain, and
// we've stored the sender and receiver proofs in the proof archive.
// We'll now attempt to transfer the receiver proof to the receiver.
case SendStateReceiverProofTransfer:
// We'll set the package state to complete early here so the
// main loop breaks out. We'll continue to attempt proof
// deliver in the background.
currentPkg.SendState = SendStateComplete
p.Wg.Add(1)
go func() {
defer p.Wg.Done()
err := p.transferReceiverProof(&currentPkg)
if err != nil {
log.Errorf("unable to transfer receiver "+
"proof: %v", err)
p.publishSubscriberEvent(newAssetSendErrorEvent(
err, SendStateReceiverProofTransfer,
currentPkg,
))
}
}()
return &currentPkg, nil

The issue here is that this is run in a goroutine, with the state step function returning immediately. Upon restart, we won't attempt to publish the proofs again. Even if we don't have any proofs, this is important, as only once we transfer all the proofs, do we call ConfirmParcelDelivery: https://github.com/lightninglabs/taproot-assets/blob/main/tapfreighter/chain_porter.go#L795-L808.

The function name is slightly overloaded, but ConfirmParcelDelivery is what will actually finalize the transfer by applying the pending outputs (make new assets w/ the relevant details):

// Since we define that a transfer can only move assets
// within the same asset ID, we can take any of the
// inputs as a template for the new asset, since the
// genesis and group key will be the same. We'll
// overwrite all other fields.
templateID := spentAssetIDs[0]
params := ApplyPendingOutput{
ScriptKeyID: out.ScriptKeyID,
AnchorUtxoID: sqlInt64(
out.AnchorUtxoID,
),
Amount: out.Amount,
LockTime: out.LockTime,
RelativeLockTime: out.RelativeLockTime,
//nolint:lll
SplitCommitmentRootHash: out.SplitCommitmentRootHash,
SplitCommitmentRootValue: out.SplitCommitmentRootValue,
SpentAssetID: templateID,
Spent: isTombstone || isBurn,
AssetVersion: out.AssetVersion,
}
newAssetID, err := q.ApplyPendingOutput(ctx, params)
if err != nil {
return fmt.Errorf("unable to apply pending "+
"output: %w", err)
}
.

Note that we do have the transfer log, and can resume from that, but this doesn't allow us to also execute this call back that finalizes the transfer.

One other relevant detail is that transferReceiverProof sets the state to SendStateComplete:

pkg.SendState = SendStateComplete
, but only after it has completed. There's a line before the call that prematurely updates this state:
// We'll set the package state to complete early here so the
// main loop breaks out. We'll continue to attempt proof
// deliver in the background.
currentPkg.SendState = SendStateComplete

Steps to reproduce

Update an itest to initiate a transfer, confirm, but then cause the proof transfer to fail.

Expected behavior

It should continue to both transmit the proofs, but also fix the logic above to call ConfirmParcelDelivery as soon as we get the confirmation notification.

We should also remove the premature update of SendStateComplete. This'll ensure that that case gets re-executed on start up until the proofs are actually sent.

With the above, we also need to read the parcels from disk, to relaunch the state machine for parcels that weren't finalized.

Actual behavior

On restart, lnd won't reattempt proof transfer. However, the proof files on disk are properly updated.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

🔖 Ready

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions