diff --git a/docs/release-notes/release-notes-0.8.0.md b/docs/release-notes/release-notes-0.8.0.md index d2c790a27..2c54eccbb 100644 --- a/docs/release-notes/release-notes-0.8.0.md +++ b/docs/release-notes/release-notes-0.8.0.md @@ -21,6 +21,11 @@ # Bug Fixes +* [PR #1920](https://github.com/lightninglabs/taproot-assets/pull/1920) fixes + a bug in which Neutrino-backed nodes could fail to import transfer + proofs for remote-initiated force close transactions if they were not + online to see them broadcast. + # New Features ## Functional Enhancements @@ -36,7 +41,7 @@ - [Garbage collection of orphaned UTXOs](https://github.com/lightninglabs/taproot-assets/pull/1832) by sweeping tombstones and burn outputs when executing onchain transactions. Garbage collection will be executed on every burn, transfer or call to - `AnchorVirtualPsbts`. A new configuration is available to control the sweeping + `AnchorVirtualPsbts`. A new configuration is available to control the sweeping via the flag `wallet.sweep-orphan-utxos`. - [PR](https://github.com/lightninglabs/taproot-assets/pull/1899) tapd now treats HTLC interceptor setup failures as fatal during RFQ subsystem startup. @@ -81,7 +86,7 @@ ## Config Changes - [PR#1870](https://github.com/lightninglabs/taproot-assets/pull/1870) - The `proofs-per-universe` configuration option is removed. New option + The `proofs-per-universe` configuration option is removed. New option `max-proof-cache-size` sets the proof cache limit in bytes and accepts human-readable values such as `64MB`. diff --git a/rpcserver.go b/rpcserver.go index f02b3137a..eaf4799c4 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3244,6 +3244,7 @@ func (r *rpcServer) PublishAndLogTransfer(ctx context.Context, tapfreighter.NewPreAnchoredParcel( activePackets, passivePackets, anchorTx, req.SkipAnchorTxBroadcast, parcelLabel, + fn.None[uint32](), ), ) if err != nil { diff --git a/tapchannel/aux_closer.go b/tapchannel/aux_closer.go index 34a9c8085..d86f5fd7b 100644 --- a/tapchannel/aux_closer.go +++ b/tapchannel/aux_closer.go @@ -643,12 +643,13 @@ func (a *AuxChanCloser) ShutdownBlob( // shipChannelTxn takes a channel transaction, an output commitment, and the // set of vPackets used to make the output commitment and ships a complete -// pre-singed package off to the porter. This'll insert a transfer for the +// pre-signed package off to the porter. This'll insert a transfer for the // channel, send the final transaction to the network, and update any // transition proofs once a confirmation occurs. func shipChannelTxn(txSender tapfreighter.Porter, chanTx *wire.MsgTx, outputCommitments tappsbt.OutputCommitments, - vPkts []*tappsbt.VPacket, closeFee int64) error { + vPkts []*tappsbt.VPacket, closeFee int64, + anchorTxHeightHint fn.Option[uint32]) error { chanTxPsbt, err := tapsend.PrepareAnchoringTemplate(vPkts) if err != nil { @@ -678,6 +679,7 @@ func shipChannelTxn(txSender tapfreighter.Porter, chanTx *wire.MsgTx, parcelLabel := fmt.Sprintf("channel-tx-%s", chanTx.TxHash().String()) preSignedParcel := tapfreighter.NewPreAnchoredParcel( vPkts, nil, closeAnchor, false, parcelLabel, + anchorTxHeightHint, ) _, err = txSender.RequestShipment(preSignedParcel) if err != nil { @@ -787,9 +789,10 @@ func (a *AuxChanCloser) FinalizeClose(desc chancloser.AuxCloseDesc, // With the proofs finalized above, we'll now ship the transaction off // to the porter so it can insert a record on disk, and deliver the - // relevant set of proofs. + // relevant set of proofs. For co-op close, no height hint is needed + // as the transaction is being broadcast now. return shipChannelTxn( a.cfg.TxSender, closeTx, closeInfo.outputCommitments, - closeInfo.vPackets, closeInfo.closeFee, + closeInfo.vPackets, closeInfo.closeFee, fn.None[uint32](), ) } diff --git a/tapchannel/aux_funding_controller.go b/tapchannel/aux_funding_controller.go index c7c487469..f76b9b6c1 100644 --- a/tapchannel/aux_funding_controller.go +++ b/tapchannel/aux_funding_controller.go @@ -1461,6 +1461,7 @@ func (f *FundingController) completeChannelFunding(ctx context.Context, ) preSignedParcel := tapfreighter.NewPreAnchoredParcel( activePkts, passivePkts, anchorTx, false, parcelLabel, + fn.None[uint32](), ) _, err = f.cfg.TxSender.RequestShipment(preSignedParcel) if err != nil { diff --git a/tapchannel/aux_sweeper.go b/tapchannel/aux_sweeper.go index b0d1e8a89..22ae76677 100644 --- a/tapchannel/aux_sweeper.go +++ b/tapchannel/aux_sweeper.go @@ -1720,10 +1720,18 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq, // TODO(roasbeef): import proof for receiver instead? // With all the vPKts created, we can now ship the transaction off to - // the porter for final delivery. + // the porter for final delivery. We use the commitment tx's block + // height as the height hint so that the chain notifier can find the + // confirmation even if the transaction was confirmed while we were + // offline. + heightHint := fn.None[uint32]() + if req.CommitTxBlockHeight > 0 { + heightHint = fn.Some(req.CommitTxBlockHeight) + } + return shipChannelTxn( a.cfg.TxSender, req.CommitTx, outCommitments, vPackets, - int64(req.CommitFee), + int64(req.CommitFee), heightHint, ) } @@ -2566,11 +2574,9 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest, // With the output commitments re-created, we have all we need to log // and ship the transaction. - // - // We pass false for the last arg as we already updated our suffix - // proofs here. return shipChannelTxn( a.cfg.TxSender, sweepTx, outCommitments, allVpkts, int64(fee), + fn.None[uint32](), ) } diff --git a/tapfreighter/chain_porter.go b/tapfreighter/chain_porter.go index 3b412d306..507c30d64 100644 --- a/tapfreighter/chain_porter.go +++ b/tapfreighter/chain_porter.go @@ -1768,15 +1768,25 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) { case SendStateStorePreBroadcast: // We won't broadcast in this state, but in preparation for // broadcasting, we will find out the current height to use as - // a height hint. + // a height hint. If the parcel provides its own height hint, + // we'll use that instead. ctx, cancel := p.WithCtxQuit() defer cancel() - currentHeight, err := p.cfg.ChainBridge.CurrentHeight(ctx) + + parcelHint := currentPkg.Parcel.HeightHint() + parcelHint.WhenSome(func(h uint32) { + log.Infof("Using parcel-provided height hint: %d", h) + }) + heightHint, err := parcelHint.UnwrapOrFuncErr( + func() (uint32, error) { + return p.cfg.ChainBridge.CurrentHeight(ctx) + }, + ) if err != nil { p.unlockInputs(ctx, ¤tPkg) - return nil, fmt.Errorf("unable to get current height: "+ - "%w", err) + return nil, fmt.Errorf("unable to get current "+ + "height: %w", err) } // We now need to find out if this is a transfer to ourselves @@ -1822,7 +1832,7 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) { // We need to prepare the parcel for storage. parcel, err := ConvertToTransfer( - currentHeight, currentPkg.VirtualPackets, + heightHint, currentPkg.VirtualPackets, currentPkg.AnchorTx, currentPkg.PassiveAssets, currentPkg.ZeroValueInputs, isLocalKey, currentPkg.Label, currentPkg.SkipAnchorTxBroadcast, diff --git a/tapfreighter/parcel.go b/tapfreighter/parcel.go index bc1aafb09..e7d335e24 100644 --- a/tapfreighter/parcel.go +++ b/tapfreighter/parcel.go @@ -127,6 +127,11 @@ type Parcel interface { // necessary fields being present in order for the porter not to panic. // Any business logic validation is assumed to already have happened. Validate() error + + // HeightHint returns an optional height hint for the anchor tx + // confirmation. If set, this should be preferred to the current + // chain height when registering for confirmation notifications. + HeightHint() fn.Option[uint32] } // parcelKit is a struct that contains the channels that are used to deliver @@ -234,6 +239,13 @@ func (p *AddressParcel) Validate() error { return nil } +// HeightHint returns an optional height hint for the anchor tx confirmation. +// For AddressParcel, this is always None as the transaction hasn't been +// broadcast yet. +func (p *AddressParcel) HeightHint() fn.Option[uint32] { + return fn.None[uint32]() +} + // PendingParcel is a parcel that has not yet completed delivery. type PendingParcel struct { *parcelKit @@ -277,6 +289,15 @@ func (p *PendingParcel) Validate() error { return nil } +// HeightHint returns an optional height hint for the anchor tx confirmation. +// For PendingParcel, we use the height hint from the stored outbound package. +func (p *PendingParcel) HeightHint() fn.Option[uint32] { + if p.outboundPkg != nil && p.outboundPkg.AnchorTxHeightHint > 0 { + return fn.Some(p.outboundPkg.AnchorTxHeightHint) + } + return fn.None[uint32]() +} + // PreSignedParcel is a request to issue an asset transfer of a pre-signed // parcel. This packages a virtual transaction, the input commitment, and also // the response context. @@ -384,6 +405,13 @@ func (p *PreSignedParcel) Validate() error { return nil } +// HeightHint returns an optional height hint for the anchor tx confirmation. +// For PreSignedParcel, this is always None as the transaction hasn't been +// broadcast yet. +func (p *PreSignedParcel) HeightHint() fn.Option[uint32] { + return fn.None[uint32]() +} + // PreAnchoredParcel is a request to log and publish an asset transfer of a // pre-anchored parcel. All virtual PSBTs and the on-chain BTC level anchor // transaction must be fully signed and ready to be broadcast. @@ -404,6 +432,10 @@ type PreAnchoredParcel struct { // label is an optional user provided transfer label. label string + + // anchorTxHeightHint is an optional height hint for the anchor + // transaction. + anchorTxHeightHint fn.Option[uint32] } // A compile-time assertion to ensure PreAnchoredParcel implements the Parcel @@ -413,7 +445,8 @@ var _ Parcel = (*PreAnchoredParcel)(nil) // NewPreAnchoredParcel creates a new PreAnchoredParcel. func NewPreAnchoredParcel(vPackets []*tappsbt.VPacket, passiveAssets []*tappsbt.VPacket, anchorTx *tapsend.AnchorTransaction, - skipAnchorTxBroadcast bool, label string) *PreAnchoredParcel { + skipAnchorTxBroadcast bool, label string, + anchorTxHeightHint fn.Option[uint32]) *PreAnchoredParcel { return &PreAnchoredParcel{ parcelKit: &parcelKit{ @@ -425,6 +458,7 @@ func NewPreAnchoredParcel(vPackets []*tappsbt.VPacket, anchorTx: anchorTx, skipAnchorTxBroadcast: skipAnchorTxBroadcast, label: label, + anchorTxHeightHint: anchorTxHeightHint, } } @@ -487,6 +521,13 @@ func (p *PreAnchoredParcel) Validate() error { return nil } +// HeightHint returns an optional height hint for the anchor tx confirmation. +// For PreAnchoredParcel, this returns the configured height hint which may be +// set for historical transactions that have already confirmed. +func (p *PreAnchoredParcel) HeightHint() fn.Option[uint32] { + return p.anchorTxHeightHint +} + // sendPackage houses the information we need to complete a package transfer. type sendPackage struct { // SendState is the current send state of this parcel.