contractcourt: add methods to checkpoint states

This commit adds checkpoint methods in `htlcTimeoutResolver`, which are
similar to those used in `htlcSuccessResolver`.
This commit is contained in:
yyforyongyu 2024-07-16 08:44:53 +08:00
parent bfc95b8b2c
commit 7083302fa0
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868

@ -7,6 +7,7 @@ import (
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
@ -451,26 +452,6 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
return nil, h.claimCleanUp(commitSpend)
}
// At this point, the second-level transaction is sufficiently
// confirmed, or a transaction directly spending the output is.
// Therefore, we can now send back our clean up message, failing the
// HTLC on the incoming link.
//
// NOTE: This can be called twice if the outgoing resolver restarts
// before the second-stage timeout transaction is confirmed.
log.Infof("%T(%v): resolving htlc with incoming fail msg, "+
"fully confirmed", h, h.htlcResolution.ClaimOutpoint)
failureMsg := &lnwire.FailPermanentChannelFailure{}
err = h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID,
HtlcIndex: h.htlc.HtlcIndex,
Failure: failureMsg,
})
if err != nil {
return nil, err
}
// Depending on whether this was a local or remote commit, we must
// handle the spending transaction accordingly.
return h.handleCommitSpend(commitSpend)
@ -680,30 +661,9 @@ func (h *htlcTimeoutResolver) waitForConfirmedSpend(op *wire.OutPoint,
return nil, err
}
// Once confirmed, persist the state on disk.
if err := h.checkPointSecondLevelTx(); err != nil {
return nil, err
}
return spend, err
}
// checkPointSecondLevelTx persists the state of a second level HTLC tx to disk
// if it's published by the sweeper.
func (h *htlcTimeoutResolver) checkPointSecondLevelTx() error {
// If this was the second level transaction published by the sweeper,
// we can checkpoint the resolver now that it's confirmed.
if h.htlcResolution.SignDetails != nil && !h.outputIncubating {
h.outputIncubating = true
if err := h.Checkpoint(h); err != nil {
log.Errorf("unable to Checkpoint: %v", err)
return err
}
}
return nil
}
// handleCommitSpend handles the spend of the HTLC output on the commitment
// transaction. If this was our local commitment, the spend will be he
// confirmed second-level timeout transaction, and we'll sweep that into our
@ -727,7 +687,8 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
// accordingly.
spendTxID = commitSpend.SpenderTxHash
reports []*channeldb.ResolverReport
sweepTx *chainntnfs.SpendDetail
err error
)
switch {
@ -756,7 +717,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
"delayed output", h, claimOutpoint)
sweepTx, err := waitForSpend(
sweepTx, err = waitForSpend(
&claimOutpoint,
h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
@ -770,38 +731,16 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
// Once our sweep of the timeout tx has confirmed, we add a
// resolution for our timeoutTx tx first stage transaction.
timeoutTx := commitSpend.SpendingTx
index := commitSpend.SpenderInputIndex
spendHash := commitSpend.SpenderTxHash
reports = append(reports, &channeldb.ResolverReport{
OutPoint: timeoutTx.TxIn[index].PreviousOutPoint,
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: spendHash,
})
err = h.checkpointStageOne(*spendTxID)
if err != nil {
return nil, err
}
}
// With the clean up message sent, we'll now mark the contract
// resolved, update the recovered balance, record the timeout and the
// sweep txid on disk, and wait.
h.resolved = true
h.reportLock.Lock()
h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
h.currentReport.LimboBalance = 0
h.reportLock.Unlock()
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports = append(reports, &channeldb.ResolverReport{
OutPoint: claimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
SpendTxID: spendTxID,
})
return nil, h.Checkpoint(h, reports...)
return nil, h.checkpointClaim(sweepTx)
}
// Stop signals the resolver to cancel any current resolution processes, and
@ -1050,12 +989,6 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
// Create a result chan to hold the results.
result := &spendResult{}
// hasMempoolSpend is a flag that indicates whether we have found a
// preimage spend from the mempool. This is used to determine whether
// to checkpoint the resolver or not when later we found the
// corresponding block spend.
hasMempoolSpent := false
// Wait for a spend event to arrive.
for {
select {
@ -1083,23 +1016,6 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
// Once confirmed, persist the state on disk if
// we haven't seen the output's spending tx in
// mempool before.
//
// NOTE: we don't checkpoint the resolver if
// it's spending tx has already been found in
// mempool - the resolver will take care of the
// checkpoint in its `claimCleanUp`. If we do
// checkpoint here, however, we'd create a new
// record in db for the same htlc resolver
// which won't be cleaned up later, resulting
// the channel to stay in unresolved state.
//
// TODO(yy): when fee bumper is implemented, we
// need to further check whether this is a
// preimage spend. Also need to refactor here
// to save us some indentation.
if !hasMempoolSpent {
result.err = h.checkPointSecondLevelTx()
}
}
// Send the result and exit the loop.
@ -1146,10 +1062,6 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
result.spend = spendDetail
resultChan <- result
// Set the hasMempoolSpent flag to true so we won't
// checkpoint the resolver again in db.
hasMempoolSpent = true
continue
// If the resolver exits, we exit the goroutine.
@ -1317,3 +1229,65 @@ func (h *htlcTimeoutResolver) sweepTimeoutTxOutput() error {
return err
}
// checkpointStageOne creates a checkpoint for the first stage of the htlc
// timeout transaction. This is used to ensure that the resolver can resume
// watching for the second stage spend in case of a restart.
func (h *htlcTimeoutResolver) checkpointStageOne(
spendTxid chainhash.Hash) error {
h.log.Debugf("checkpoint stage one spend of HTLC output %v, spent "+
"in tx %v", h.outpoint(), spendTxid)
// Now that the second-level transaction has confirmed, we checkpoint
// the state so we'll go to the next stage in case of restarts.
h.outputIncubating = true
// Create stage-one report.
report := &channeldb.ResolverReport{
OutPoint: h.outpoint(),
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendTxid,
}
// At this point, the second-level transaction is sufficiently
// confirmed. We can now send back our clean up message, failing the
// HTLC on the incoming link.
failureMsg := &lnwire.FailPermanentChannelFailure{}
err := h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID,
HtlcIndex: h.htlc.HtlcIndex,
Failure: failureMsg,
})
if err != nil {
return err
}
return h.Checkpoint(h, report)
}
// checkpointClaim checkpoints the timeout resolver with the reports it needs.
func (h *htlcTimeoutResolver) checkpointClaim(
spendDetail *chainntnfs.SpendDetail) error {
h.log.Infof("resolving htlc with incoming fail msg, output=%v "+
"confirmed in tx=%v", spendDetail.SpentOutPoint,
spendDetail.SpenderTxHash)
// Create a resolver report for the claiming of the HTLC.
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
report := &channeldb.ResolverReport{
OutPoint: *spendDetail.SpentOutPoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
SpendTxID: spendDetail.SpenderTxHash,
}
// Finally, we checkpoint the resolver with our report(s).
h.resolved = true
return h.Checkpoint(h, report)
}