mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-01 10:11:11 +02:00
contractcourt: add resolver handlers in htlcSuccessResolver
This commit refactors the `Resolve` method by adding two resolver handlers to handle waiting for spending confirmations.
This commit is contained in:
@@ -140,27 +140,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
|||||||
|
|
||||||
// To wrap this up, we'll wait until the second-level transaction has
|
// To wrap this up, we'll wait until the second-level transaction has
|
||||||
// been spent, then fully resolve the contract.
|
// been spent, then fully resolve the contract.
|
||||||
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
|
return nil, h.resolveSuccessTxOutput(*secondLevelOutpoint)
|
||||||
"after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
|
|
||||||
|
|
||||||
spend, err := waitForSpend(
|
|
||||||
secondLevelOutpoint,
|
|
||||||
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
|
||||||
h.broadcastHeight, h.Notifier, h.quit,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.reportLock.Lock()
|
|
||||||
h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
|
|
||||||
h.currentReport.LimboBalance = 0
|
|
||||||
h.reportLock.Unlock()
|
|
||||||
|
|
||||||
h.resolved = true
|
|
||||||
return nil, h.checkpointClaim(
|
|
||||||
spend.SpenderTxHash, channeldb.ResolverOutcomeClaimed,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastSuccessTx handles an HTLC output on our local commitment by
|
// broadcastSuccessTx handles an HTLC output on our local commitment by
|
||||||
@@ -187,40 +167,11 @@ func (h *htlcSuccessResolver) broadcastSuccessTx() (
|
|||||||
|
|
||||||
// We'll now broadcast the second layer transaction so we can kick off
|
// We'll now broadcast the second layer transaction so we can kick off
|
||||||
// the claiming process.
|
// the claiming process.
|
||||||
//
|
err := h.resolveLegacySuccessTx()
|
||||||
// TODO(roasbeef): after changing sighashes send to tx bundler
|
|
||||||
label := labels.MakeLabel(
|
|
||||||
labels.LabelTypeChannelClose, &h.ShortChanID,
|
|
||||||
)
|
|
||||||
err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, this is an output on our commitment transaction. In this
|
|
||||||
// case, we'll send it to the incubator, but only if we haven't already
|
|
||||||
// done so.
|
|
||||||
if !h.outputIncubating {
|
|
||||||
log.Infof("%T(%x): incubating incoming htlc output",
|
|
||||||
h, h.htlc.RHash[:])
|
|
||||||
|
|
||||||
err := h.IncubateOutputs(
|
|
||||||
h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
|
|
||||||
fn.Some(h.htlcResolution),
|
|
||||||
h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.outputIncubating = true
|
|
||||||
|
|
||||||
if err := h.Checkpoint(h); err != nil {
|
|
||||||
log.Errorf("unable to Checkpoint: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &h.htlcResolution.ClaimOutpoint, nil
|
return &h.htlcResolution.ClaimOutpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,33 +193,25 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("%T(%x): waiting for second-level HTLC success "+
|
err = h.resolveSuccessTx()
|
||||||
"transaction to confirm", h, h.htlc.RHash[:])
|
|
||||||
|
|
||||||
// Wait for the second level transaction to confirm.
|
|
||||||
commitSpend, err = waitForSpend(
|
|
||||||
&h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
|
|
||||||
h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
|
|
||||||
h.broadcastHeight, h.Notifier, h.quit,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
if err := h.Checkpoint(h); err != nil {
|
|
||||||
log.Errorf("unable to Checkpoint: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("%T(%x): second-level HTLC success transaction "+
|
|
||||||
"confirmed!", h, h.htlc.RHash[:])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.sweepSuccessTxOutput()
|
// This should be non-blocking as we will only attempt to sweep the
|
||||||
|
// output when the second level tx has already been confirmed. In other
|
||||||
|
// words, waitForSpend will return immediately.
|
||||||
|
commitSpend, err := waitForSpend(
|
||||||
|
&h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
|
||||||
|
h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
|
||||||
|
h.broadcastHeight, h.Notifier, h.quit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.sweepSuccessTxOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -304,23 +247,14 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once the transaction has received a sufficient number of
|
|
||||||
// confirmations, we'll mark ourselves as fully resolved and exit.
|
|
||||||
h.resolved = true
|
|
||||||
|
|
||||||
// Checkpoint the resolver, and write the outcome to disk.
|
// Checkpoint the resolver, and write the outcome to disk.
|
||||||
return nil, h.checkpointClaim(
|
return nil, h.checkpointClaim(sweepTxDetails.SpenderTxHash)
|
||||||
sweepTxDetails.SpenderTxHash,
|
|
||||||
channeldb.ResolverOutcomeClaimed,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkpointClaim checkpoints the success resolver with the reports it needs.
|
// checkpointClaim checkpoints the success resolver with the reports it needs.
|
||||||
// If this htlc was claimed two stages, it will write reports for both stages,
|
// If this htlc was claimed two stages, it will write reports for both stages,
|
||||||
// otherwise it will just write for the single htlc claim.
|
// otherwise it will just write for the single htlc claim.
|
||||||
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
|
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash) error {
|
||||||
outcome channeldb.ResolverOutcome) error {
|
|
||||||
|
|
||||||
// Mark the htlc as final settled.
|
// Mark the htlc as final settled.
|
||||||
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
|
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
|
||||||
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
|
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
|
||||||
@@ -348,7 +282,7 @@ func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
|
|||||||
OutPoint: h.htlcResolution.ClaimOutpoint,
|
OutPoint: h.htlcResolution.ClaimOutpoint,
|
||||||
Amount: amt,
|
Amount: amt,
|
||||||
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
||||||
ResolverOutcome: outcome,
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
|
||||||
SpendTxID: spendTx,
|
SpendTxID: spendTx,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -373,6 +307,7 @@ func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally, we checkpoint the resolver with our report(s).
|
// Finally, we checkpoint the resolver with our report(s).
|
||||||
|
h.resolved = true
|
||||||
return h.Checkpoint(h, reports...)
|
return h.Checkpoint(h, reports...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,3 +683,129 @@ func (h *htlcSuccessResolver) sweepSuccessTxOutput() error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveLegacySuccessTx handles an HTLC output from a pre-anchor type channel
|
||||||
|
// by broadcasting the second-level success transaction.
|
||||||
|
func (h *htlcSuccessResolver) resolveLegacySuccessTx() error {
|
||||||
|
// Otherwise we'll publish the second-level transaction directly and
|
||||||
|
// offer the resolution to the nursery to handle.
|
||||||
|
h.log.Infof("broadcasting legacy second-level success tx: %v",
|
||||||
|
h.htlcResolution.SignedSuccessTx.TxHash())
|
||||||
|
|
||||||
|
// We'll now broadcast the second layer transaction so we can kick off
|
||||||
|
// the claiming process.
|
||||||
|
//
|
||||||
|
// TODO(yy): offer it to the sweeper instead.
|
||||||
|
label := labels.MakeLabel(
|
||||||
|
labels.LabelTypeChannelClose, &h.ShortChanID,
|
||||||
|
)
|
||||||
|
err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast-forward to resolve the output from the success tx if the it has
|
||||||
|
// already been sent to the UtxoNursery.
|
||||||
|
if h.outputIncubating {
|
||||||
|
return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.log.Infof("incubating incoming htlc output")
|
||||||
|
|
||||||
|
// Send the output to the incubator.
|
||||||
|
err = h.IncubateOutputs(
|
||||||
|
h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
|
||||||
|
fn.Some(h.htlcResolution),
|
||||||
|
h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the output as incubating and checkpoint it.
|
||||||
|
h.outputIncubating = true
|
||||||
|
if err := h.Checkpoint(h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to resolve the output.
|
||||||
|
return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveSuccessTx waits for the sweeping tx of the second-level success tx to
|
||||||
|
// confirm and offers the output from the success tx to the sweeper.
|
||||||
|
func (h *htlcSuccessResolver) resolveSuccessTx() error {
|
||||||
|
h.log.Infof("waiting for 2nd-level HTLC success transaction to confirm")
|
||||||
|
|
||||||
|
// Create aliases to make the code more readable.
|
||||||
|
outpoint := h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
|
||||||
|
pkScript := h.htlcResolution.SignDetails.SignDesc.Output.PkScript
|
||||||
|
|
||||||
|
// Wait for the second level transaction to confirm.
|
||||||
|
commitSpend, err := waitForSpend(
|
||||||
|
&outpoint, pkScript, h.broadcastHeight, h.Notifier, h.quit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll use this input index to determine the second-level output
|
||||||
|
// index on the transaction, as the signatures requires the indexes to
|
||||||
|
// be the same. We don't look for the second-level output script
|
||||||
|
// directly, as there might be more than one HTLC output to the same
|
||||||
|
// pkScript.
|
||||||
|
op := wire.OutPoint{
|
||||||
|
Hash: *commitSpend.SpenderTxHash,
|
||||||
|
Index: commitSpend.SpenderInputIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the 2nd-stage sweeping has already been started, we can
|
||||||
|
// fast-forward to start the resolving process for the stage two
|
||||||
|
// output.
|
||||||
|
if h.outputIncubating {
|
||||||
|
return h.resolveSuccessTxOutput(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if err := h.Checkpoint(h); err != nil {
|
||||||
|
log.Errorf("unable to Checkpoint: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.log.Infof("2nd-level HTLC success tx=%v confirmed",
|
||||||
|
commitSpend.SpenderTxHash)
|
||||||
|
|
||||||
|
// Send the sweep request for the output from the success tx.
|
||||||
|
if err := h.sweepSuccessTxOutput(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.resolveSuccessTxOutput(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveSuccessTxOutput waits for the spend of the output from the 2nd-level
|
||||||
|
// success tx.
|
||||||
|
func (h *htlcSuccessResolver) resolveSuccessTxOutput(op wire.OutPoint) error {
|
||||||
|
// To wrap this up, we'll wait until the second-level transaction has
|
||||||
|
// been spent, then fully resolve the contract.
|
||||||
|
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
|
||||||
|
"after csv_delay=%v", h, h.htlc.RHash[:],
|
||||||
|
h.htlcResolution.CsvDelay)
|
||||||
|
|
||||||
|
spend, err := waitForSpend(
|
||||||
|
&op, h.htlcResolution.SweepSignDesc.Output.PkScript,
|
||||||
|
h.broadcastHeight, h.Notifier, h.quit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.reportLock.Lock()
|
||||||
|
h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
|
||||||
|
h.currentReport.LimboBalance = 0
|
||||||
|
h.reportLock.Unlock()
|
||||||
|
|
||||||
|
return h.checkpointClaim(spend.SpenderTxHash)
|
||||||
|
}
|
||||||
|
@@ -548,9 +548,9 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendSecondLevelTxLegacy sends a second level timeout transaction to the utxo
|
// resolveSecondLevelTxLegacy sends a second level timeout transaction to the
|
||||||
// nursery. This transaction uses the legacy SIGHASH_ALL flag.
|
// utxo nursery. This transaction uses the legacy SIGHASH_ALL flag.
|
||||||
func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error {
|
func (h *htlcTimeoutResolver) resolveSecondLevelTxLegacy() error {
|
||||||
log.Debugf("%T(%v): incubating htlc output", h,
|
log.Debugf("%T(%v): incubating htlc output", h,
|
||||||
h.htlcResolution.ClaimOutpoint)
|
h.htlcResolution.ClaimOutpoint)
|
||||||
|
|
||||||
@@ -654,7 +654,7 @@ func (h *htlcTimeoutResolver) spendHtlcOutput() (
|
|||||||
// commitment for a non-anchor channel, so we'll send it to the utxo
|
// commitment for a non-anchor channel, so we'll send it to the utxo
|
||||||
// nursery.
|
// nursery.
|
||||||
case h.isLegacyOutput() && !h.outputIncubating:
|
case h.isLegacyOutput() && !h.outputIncubating:
|
||||||
if err := h.sendSecondLevelTxLegacy(); err != nil {
|
if err := h.resolveSecondLevelTxLegacy(); err != nil {
|
||||||
log.Errorf("Sending timeout tx to nursery: %v", err)
|
log.Errorf("Sending timeout tx to nursery: %v", err)
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Reference in New Issue
Block a user