mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-25 16:23:49 +02:00
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:
parent
bfc95b8b2c
commit
7083302fa0
@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
@ -451,26 +452,6 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
|||||||
return nil, h.claimCleanUp(commitSpend)
|
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
|
// Depending on whether this was a local or remote commit, we must
|
||||||
// handle the spending transaction accordingly.
|
// handle the spending transaction accordingly.
|
||||||
return h.handleCommitSpend(commitSpend)
|
return h.handleCommitSpend(commitSpend)
|
||||||
@ -680,30 +661,9 @@ func (h *htlcTimeoutResolver) waitForConfirmedSpend(op *wire.OutPoint,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once confirmed, persist the state on disk.
|
|
||||||
if err := h.checkPointSecondLevelTx(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return spend, 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
|
// handleCommitSpend handles the spend of the HTLC output on the commitment
|
||||||
// transaction. If this was our local commitment, the spend will be he
|
// transaction. If this was our local commitment, the spend will be he
|
||||||
// confirmed second-level timeout transaction, and we'll sweep that into our
|
// confirmed second-level timeout transaction, and we'll sweep that into our
|
||||||
@ -727,7 +687,8 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
|
|||||||
// accordingly.
|
// accordingly.
|
||||||
spendTxID = commitSpend.SpenderTxHash
|
spendTxID = commitSpend.SpenderTxHash
|
||||||
|
|
||||||
reports []*channeldb.ResolverReport
|
sweepTx *chainntnfs.SpendDetail
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@ -756,7 +717,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
|
|||||||
log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
|
log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
|
||||||
"delayed output", h, claimOutpoint)
|
"delayed output", h, claimOutpoint)
|
||||||
|
|
||||||
sweepTx, err := waitForSpend(
|
sweepTx, err = waitForSpend(
|
||||||
&claimOutpoint,
|
&claimOutpoint,
|
||||||
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
||||||
h.broadcastHeight, h.Notifier, h.quit,
|
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
|
// Once our sweep of the timeout tx has confirmed, we add a
|
||||||
// resolution for our timeoutTx tx first stage transaction.
|
// resolution for our timeoutTx tx first stage transaction.
|
||||||
timeoutTx := commitSpend.SpendingTx
|
err = h.checkpointStageOne(*spendTxID)
|
||||||
index := commitSpend.SpenderInputIndex
|
if err != nil {
|
||||||
spendHash := commitSpend.SpenderTxHash
|
return nil, err
|
||||||
|
}
|
||||||
reports = append(reports, &channeldb.ResolverReport{
|
|
||||||
OutPoint: timeoutTx.TxIn[index].PreviousOutPoint,
|
|
||||||
Amount: h.htlc.Amt.ToSatoshis(),
|
|
||||||
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
|
||||||
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
|
|
||||||
SpendTxID: spendHash,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the clean up message sent, we'll now mark the contract
|
// With the clean up message sent, we'll now mark the contract
|
||||||
// resolved, update the recovered balance, record the timeout and the
|
// resolved, update the recovered balance, record the timeout and the
|
||||||
// sweep txid on disk, and wait.
|
// sweep txid on disk, and wait.
|
||||||
h.resolved = true
|
return nil, h.checkpointClaim(sweepTx)
|
||||||
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...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop signals the resolver to cancel any current resolution processes, and
|
// 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.
|
// Create a result chan to hold the results.
|
||||||
result := &spendResult{}
|
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.
|
// Wait for a spend event to arrive.
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -1083,23 +1016,6 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
|
|||||||
// Once confirmed, persist the state on disk if
|
// Once confirmed, persist the state on disk if
|
||||||
// we haven't seen the output's spending tx in
|
// we haven't seen the output's spending tx in
|
||||||
// mempool before.
|
// 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.
|
// Send the result and exit the loop.
|
||||||
@ -1146,10 +1062,6 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
|
|||||||
result.spend = spendDetail
|
result.spend = spendDetail
|
||||||
resultChan <- result
|
resultChan <- result
|
||||||
|
|
||||||
// Set the hasMempoolSpent flag to true so we won't
|
|
||||||
// checkpoint the resolver again in db.
|
|
||||||
hasMempoolSpent = true
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
// If the resolver exits, we exit the goroutine.
|
// If the resolver exits, we exit the goroutine.
|
||||||
@ -1317,3 +1229,65 @@ func (h *htlcTimeoutResolver) sweepTimeoutTxOutput() error {
|
|||||||
|
|
||||||
return err
|
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)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user