mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
ab41f28a8f
With this commit, we update all the resolvers to pass in the new htlc resolution blobs. Along the way, we remove the old blocking guard on this resolution logic for HTLCs with blobs.
740 lines
23 KiB
Go
740 lines
23 KiB
Go
package contractcourt
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"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"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
|
"github.com/lightningnetwork/lnd/fn"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/labels"
|
|
"github.com/lightningnetwork/lnd/lnutils"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/sweep"
|
|
)
|
|
|
|
// htlcSuccessResolver is a resolver that's capable of sweeping an incoming
|
|
// HTLC output on-chain. If this is the remote party's commitment, we'll sweep
|
|
// it directly from the commitment output *immediately*. If this is our
|
|
// commitment, we'll first broadcast the success transaction, then send it to
|
|
// the incubator for sweeping. That's it, no need to send any clean up
|
|
// messages.
|
|
//
|
|
// TODO(roasbeef): don't need to broadcast?
|
|
type htlcSuccessResolver struct {
|
|
// htlcResolution is the incoming HTLC resolution for this HTLC. It
|
|
// contains everything we need to properly resolve this HTLC.
|
|
htlcResolution lnwallet.IncomingHtlcResolution
|
|
|
|
// outputIncubating returns true if we've sent the output to the output
|
|
// incubator (utxo nursery). In case the htlcResolution has non-nil
|
|
// SignDetails, it means we will let the Sweeper handle broadcasting
|
|
// the secondd-level transaction, and sweeping its output. In this case
|
|
// we let this field indicate whether we need to broadcast the
|
|
// second-level tx (false) or if it has confirmed and we must sweep the
|
|
// second-level output (true).
|
|
outputIncubating bool
|
|
|
|
// resolved reflects if the contract has been fully resolved or not.
|
|
resolved bool
|
|
|
|
// broadcastHeight is the height that the original contract was
|
|
// broadcast to the main-chain at. We'll use this value to bound any
|
|
// historical queries to the chain for spends/confirmations.
|
|
broadcastHeight uint32
|
|
|
|
// htlc contains information on the htlc that we are resolving on-chain.
|
|
htlc channeldb.HTLC
|
|
|
|
// currentReport stores the current state of the resolver for reporting
|
|
// over the rpc interface. This should only be reported in case we have
|
|
// a non-nil SignDetails on the htlcResolution, otherwise the nursery
|
|
// will produce reports.
|
|
currentReport ContractReport
|
|
|
|
// reportLock prevents concurrent access to the resolver report.
|
|
reportLock sync.Mutex
|
|
|
|
contractResolverKit
|
|
|
|
htlcLeaseResolver
|
|
}
|
|
|
|
// newSuccessResolver instanties a new htlc success resolver.
|
|
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
|
|
broadcastHeight uint32, htlc channeldb.HTLC,
|
|
resCfg ResolverConfig) *htlcSuccessResolver {
|
|
|
|
h := &htlcSuccessResolver{
|
|
contractResolverKit: *newContractResolverKit(resCfg),
|
|
htlcResolution: res,
|
|
broadcastHeight: broadcastHeight,
|
|
htlc: htlc,
|
|
}
|
|
|
|
h.initReport()
|
|
|
|
return h
|
|
}
|
|
|
|
// ResolverKey returns an identifier which should be globally unique for this
|
|
// particular resolver within the chain the original contract resides within.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) ResolverKey() []byte {
|
|
// The primary key for this resolver will be the outpoint of the HTLC
|
|
// on the commitment transaction itself. If this is our commitment,
|
|
// then the output can be found within the signed success tx,
|
|
// otherwise, it's just the ClaimOutpoint.
|
|
var op wire.OutPoint
|
|
if h.htlcResolution.SignedSuccessTx != nil {
|
|
op = h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
|
|
} else {
|
|
op = h.htlcResolution.ClaimOutpoint
|
|
}
|
|
|
|
key := newResolverID(op)
|
|
return key[:]
|
|
}
|
|
|
|
// Resolve attempts to resolve an unresolved incoming HTLC that we know the
|
|
// preimage to. If the HTLC is on the commitment of the remote party, then we'll
|
|
// simply sweep it directly. Otherwise, we'll hand this off to the utxo nursery
|
|
// to do its duty. There is no need to make a call to the invoice registry
|
|
// anymore. Every HTLC has already passed through the incoming contest resolver
|
|
// and in there the invoice was already marked as settled.
|
|
//
|
|
// TODO(roasbeef): create multi to batch
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) Resolve(
|
|
immediate bool) (ContractResolver, error) {
|
|
|
|
// If we're already resolved, then we can exit early.
|
|
if h.resolved {
|
|
return nil, nil
|
|
}
|
|
|
|
// If we don't have a success transaction, then this means that this is
|
|
// an output on the remote party's commitment transaction.
|
|
if h.htlcResolution.SignedSuccessTx == nil {
|
|
return h.resolveRemoteCommitOutput(immediate)
|
|
}
|
|
|
|
// Otherwise this an output on our own commitment, and we must start by
|
|
// broadcasting the second-level success transaction.
|
|
secondLevelOutpoint, err := h.broadcastSuccessTx(immediate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 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(
|
|
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
|
|
// broadcasting the second-level success transaction. It returns the ultimate
|
|
// outpoint of the second-level tx, that we must wait to be spent for the
|
|
// resolver to be fully resolved.
|
|
func (h *htlcSuccessResolver) broadcastSuccessTx(
|
|
immediate bool) (*wire.OutPoint, error) {
|
|
|
|
// If we have non-nil SignDetails, this means that have a 2nd level
|
|
// HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY
|
|
// (the case for anchor type channels). In this case we can re-sign it
|
|
// and attach fees at will. We let the sweeper handle this job. We use
|
|
// the checkpointed outputIncubating field to determine if we already
|
|
// swept the HTLC output into the second level transaction.
|
|
if h.htlcResolution.SignDetails != nil {
|
|
return h.broadcastReSignedSuccessTx(immediate)
|
|
}
|
|
|
|
// Otherwise we'll publish the second-level transaction directly and
|
|
// offer the resolution to the nursery to handle.
|
|
log.Infof("%T(%x): broadcasting second-layer transition tx: %v",
|
|
h, h.htlc.RHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx))
|
|
|
|
// We'll now broadcast the second layer transaction so we can kick off
|
|
// the claiming process.
|
|
//
|
|
// 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 {
|
|
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
|
|
}
|
|
|
|
// broadcastReSignedSuccessTx handles the case where we have non-nil
|
|
// SignDetails, and offers the second level transaction to the Sweeper, that
|
|
// will re-sign it and attach fees at will.
|
|
//
|
|
//nolint:funlen
|
|
func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) (
|
|
*wire.OutPoint, error) {
|
|
|
|
// Keep track of the tx spending the HTLC output on the commitment, as
|
|
// this will be the confirmed second-level tx we'll ultimately sweep.
|
|
var commitSpend *chainntnfs.SpendDetail
|
|
|
|
// We will have to let the sweeper re-sign the success tx and wait for
|
|
// it to confirm, if we haven't already.
|
|
isTaproot := txscript.IsPayToTaproot(
|
|
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
|
)
|
|
if !h.outputIncubating {
|
|
var secondLevelInput input.HtlcSecondLevelAnchorInput
|
|
if isTaproot {
|
|
//nolint:lll
|
|
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
|
|
h.htlcResolution.SignedSuccessTx,
|
|
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
|
|
h.broadcastHeight,
|
|
input.WithResolutionBlob(
|
|
h.htlcResolution.ResolutionBlob,
|
|
),
|
|
)
|
|
} else {
|
|
//nolint:lll
|
|
secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
|
|
h.htlcResolution.SignedSuccessTx,
|
|
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
|
|
h.broadcastHeight,
|
|
)
|
|
}
|
|
|
|
// Calculate the budget for this sweep.
|
|
value := btcutil.Amount(
|
|
secondLevelInput.SignDesc().Output.Value,
|
|
)
|
|
budget := calculateBudget(
|
|
value, h.Budget.DeadlineHTLCRatio,
|
|
h.Budget.DeadlineHTLC,
|
|
)
|
|
|
|
// The deadline would be the CLTV in this HTLC output. If we
|
|
// are the initiator of this force close, with the default
|
|
// `IncomingBroadcastDelta`, it means we have 10 blocks left
|
|
// when going onchain. Given we need to mine one block to
|
|
// confirm the force close tx, and one more block to trigger
|
|
// the sweep, we have 8 blocks left to sweep the HTLC.
|
|
deadline := fn.Some(int32(h.htlc.RefundTimeout))
|
|
|
|
log.Infof("%T(%x): offering second-level HTLC success tx to "+
|
|
"sweeper with deadline=%v, budget=%v", h,
|
|
h.htlc.RHash[:], h.htlc.RefundTimeout, budget)
|
|
|
|
// We'll now offer the second-level transaction to the sweeper.
|
|
_, err := h.Sweeper.SweepInput(
|
|
&secondLevelInput,
|
|
sweep.Params{
|
|
Budget: budget,
|
|
DeadlineHeight: deadline,
|
|
Immediate: immediate,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("%T(%x): waiting for second-level HTLC success "+
|
|
"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 {
|
|
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[:])
|
|
}
|
|
|
|
// If we ended up here after a restart, we must again get the
|
|
// spend notification.
|
|
if commitSpend == nil {
|
|
var err error
|
|
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
|
|
}
|
|
}
|
|
|
|
// The HTLC success tx has a CSV lock that we must wait for, and if
|
|
// this is a lease enforced channel and we're the imitator, we may need
|
|
// to wait for longer.
|
|
waitHeight := h.deriveWaitHeight(
|
|
h.htlcResolution.CsvDelay, commitSpend,
|
|
)
|
|
|
|
// Now that the sweeper has broadcasted the second-level transaction,
|
|
// it has confirmed, and we have checkpointed our state, we'll sweep
|
|
// the second level output. We report the resolver has moved the next
|
|
// stage.
|
|
h.reportLock.Lock()
|
|
h.currentReport.Stage = 2
|
|
h.currentReport.MaturityHeight = waitHeight
|
|
h.reportLock.Unlock()
|
|
|
|
if h.hasCLTV() {
|
|
log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
|
|
"expire at height %v", h, h.htlc.RHash[:],
|
|
waitHeight)
|
|
} else {
|
|
log.Infof("%T(%x): waiting for CSV lock to expire at "+
|
|
"height %v", h, h.htlc.RHash[:], waitHeight)
|
|
}
|
|
|
|
// Deduct one block so this input is offered to the sweeper one block
|
|
// earlier since the sweeper will wait for one block to trigger the
|
|
// sweeping.
|
|
//
|
|
// TODO(yy): this is done so the outputs can be aggregated
|
|
// properly. Suppose CSV locks of five 2nd-level outputs all
|
|
// expire at height 840000, there is a race in block digestion
|
|
// between contractcourt and sweeper:
|
|
// - G1: block 840000 received in contractcourt, it now offers
|
|
// the outputs to the sweeper.
|
|
// - G2: block 840000 received in sweeper, it now starts to
|
|
// sweep the received outputs - there's no guarantee all
|
|
// fives have been received.
|
|
// To solve this, we either offer the outputs earlier, or
|
|
// implement `blockbeat`, and force contractcourt and sweeper
|
|
// to consume each block sequentially.
|
|
waitHeight--
|
|
|
|
// TODO(yy): let sweeper handles the wait?
|
|
err := waitForHeight(waitHeight, h.Notifier, h.quit)
|
|
if err != nil {
|
|
return nil, 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,
|
|
}
|
|
|
|
// Let the sweeper sweep the second-level output now that the
|
|
// CSV/CLTV locks have expired.
|
|
var witType input.StandardWitnessType
|
|
if isTaproot {
|
|
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
|
|
} else {
|
|
witType = input.HtlcAcceptedSuccessSecondLevel
|
|
}
|
|
inp := h.makeSweepInput(
|
|
op, witType,
|
|
input.LeaseHtlcAcceptedSuccessSecondLevel,
|
|
&h.htlcResolution.SweepSignDesc,
|
|
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
|
|
h.htlc.RHash, h.htlcResolution.ResolutionBlob,
|
|
)
|
|
|
|
// Calculate the budget for this sweep.
|
|
budget := calculateBudget(
|
|
btcutil.Amount(inp.SignDesc().Output.Value),
|
|
h.Budget.NoDeadlineHTLCRatio,
|
|
h.Budget.NoDeadlineHTLC,
|
|
)
|
|
|
|
log.Infof("%T(%x): offering second-level success tx output to sweeper "+
|
|
"with no deadline and budget=%v at height=%v", h,
|
|
h.htlc.RHash[:], budget, waitHeight)
|
|
|
|
// TODO(roasbeef): need to update above for leased types
|
|
_, err = h.Sweeper.SweepInput(
|
|
inp,
|
|
sweep.Params{
|
|
Budget: budget,
|
|
|
|
// For second level success tx, there's no rush to get
|
|
// it confirmed, so we use a nil deadline.
|
|
DeadlineHeight: fn.None[int32](),
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Will return this outpoint, when this is spent the resolver is fully
|
|
// resolved.
|
|
return op, nil
|
|
}
|
|
|
|
// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
|
|
// commitment with the preimage. In this case we can sweep the output directly,
|
|
// and don't have to broadcast a second-level transaction.
|
|
func (h *htlcSuccessResolver) resolveRemoteCommitOutput(immediate bool) (
|
|
ContractResolver, error) {
|
|
|
|
isTaproot := txscript.IsPayToTaproot(
|
|
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
|
)
|
|
|
|
// Before we can craft out sweeping transaction, we need to
|
|
// create an input which contains all the items required to add
|
|
// this input to a sweeping transaction, and generate a
|
|
// witness.
|
|
var inp input.Input
|
|
if isTaproot {
|
|
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
|
|
&h.htlcResolution.ClaimOutpoint,
|
|
&h.htlcResolution.SweepSignDesc,
|
|
h.htlcResolution.Preimage[:],
|
|
h.broadcastHeight,
|
|
h.htlcResolution.CsvDelay,
|
|
input.WithResolutionBlob(
|
|
h.htlcResolution.ResolutionBlob,
|
|
),
|
|
))
|
|
} else {
|
|
inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
|
|
&h.htlcResolution.ClaimOutpoint,
|
|
&h.htlcResolution.SweepSignDesc,
|
|
h.htlcResolution.Preimage[:],
|
|
h.broadcastHeight,
|
|
h.htlcResolution.CsvDelay,
|
|
))
|
|
}
|
|
|
|
// Calculate the budget for this sweep.
|
|
budget := calculateBudget(
|
|
btcutil.Amount(inp.SignDesc().Output.Value),
|
|
h.Budget.DeadlineHTLCRatio,
|
|
h.Budget.DeadlineHTLC,
|
|
)
|
|
|
|
deadline := fn.Some(int32(h.htlc.RefundTimeout))
|
|
|
|
log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
|
|
"with deadline=%v, budget=%v", h, h.htlc.RHash[:],
|
|
h.htlc.RefundTimeout, budget)
|
|
|
|
// We'll now offer the direct preimage HTLC to the sweeper.
|
|
_, err := h.Sweeper.SweepInput(
|
|
inp,
|
|
sweep.Params{
|
|
Budget: budget,
|
|
DeadlineHeight: deadline,
|
|
Immediate: immediate,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Wait for the direct-preimage HTLC sweep tx to confirm.
|
|
sweepTxDetails, err := waitForSpend(
|
|
&h.htlcResolution.ClaimOutpoint,
|
|
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
|
h.broadcastHeight, h.Notifier, h.quit,
|
|
)
|
|
if err != nil {
|
|
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.
|
|
return nil, h.checkpointClaim(
|
|
sweepTxDetails.SpenderTxHash,
|
|
channeldb.ResolverOutcomeClaimed,
|
|
)
|
|
}
|
|
|
|
// checkpointClaim checkpoints the success resolver with the reports it needs.
|
|
// If this htlc was claimed two stages, it will write reports for both stages,
|
|
// otherwise it will just write for the single htlc claim.
|
|
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
|
|
outcome channeldb.ResolverOutcome) error {
|
|
|
|
// Mark the htlc as final settled.
|
|
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
|
|
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Send notification.
|
|
h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
|
|
models.CircuitKey{
|
|
ChanID: h.ShortChanID,
|
|
HtlcID: h.htlc.HtlcIndex,
|
|
},
|
|
channeldb.FinalHtlcInfo{
|
|
Settled: true,
|
|
Offchain: false,
|
|
},
|
|
)
|
|
|
|
// Create a resolver report for claiming of the htlc itself.
|
|
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
|
|
reports := []*channeldb.ResolverReport{
|
|
{
|
|
OutPoint: h.htlcResolution.ClaimOutpoint,
|
|
Amount: amt,
|
|
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
|
ResolverOutcome: outcome,
|
|
SpendTxID: spendTx,
|
|
},
|
|
}
|
|
|
|
// If we have a success tx, we append a report to represent our first
|
|
// stage claim.
|
|
if h.htlcResolution.SignedSuccessTx != nil {
|
|
// If the SignedSuccessTx is not nil, we are claiming the htlc
|
|
// in two stages, so we need to create a report for the first
|
|
// stage transaction as well.
|
|
spendTx := h.htlcResolution.SignedSuccessTx
|
|
spendTxID := spendTx.TxHash()
|
|
|
|
report := &channeldb.ResolverReport{
|
|
OutPoint: spendTx.TxIn[0].PreviousOutPoint,
|
|
Amount: h.htlc.Amt.ToSatoshis(),
|
|
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
|
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
|
|
SpendTxID: &spendTxID,
|
|
}
|
|
reports = append(reports, report)
|
|
}
|
|
|
|
// Finally, we checkpoint the resolver with our report(s).
|
|
return h.Checkpoint(h, reports...)
|
|
}
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
// suspend.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) Stop() {
|
|
close(h.quit)
|
|
}
|
|
|
|
// IsResolved returns true if the stored state in the resolve is fully
|
|
// resolved. In this case the target output can be forgotten.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) IsResolved() bool {
|
|
return h.resolved
|
|
}
|
|
|
|
// report returns a report on the resolution state of the contract.
|
|
func (h *htlcSuccessResolver) report() *ContractReport {
|
|
// If the sign details are nil, the report will be created by handled
|
|
// by the nursery.
|
|
if h.htlcResolution.SignDetails == nil {
|
|
return nil
|
|
}
|
|
|
|
h.reportLock.Lock()
|
|
defer h.reportLock.Unlock()
|
|
cpy := h.currentReport
|
|
return &cpy
|
|
}
|
|
|
|
func (h *htlcSuccessResolver) initReport() {
|
|
// We create the initial report. This will only be reported for
|
|
// resolvers not handled by the nursery.
|
|
finalAmt := h.htlc.Amt.ToSatoshis()
|
|
if h.htlcResolution.SignedSuccessTx != nil {
|
|
finalAmt = btcutil.Amount(
|
|
h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
|
|
)
|
|
}
|
|
|
|
h.currentReport = ContractReport{
|
|
Outpoint: h.htlcResolution.ClaimOutpoint,
|
|
Type: ReportOutputIncomingHtlc,
|
|
Amount: finalAmt,
|
|
MaturityHeight: h.htlcResolution.CsvDelay,
|
|
LimboBalance: finalAmt,
|
|
Stage: 1,
|
|
}
|
|
}
|
|
|
|
// Encode writes an encoded version of the ContractResolver into the passed
|
|
// Writer.
|
|
//
|
|
// NOTE: Part of the ContractResolver interface.
|
|
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
|
|
// First we'll encode our inner HTLC resolution.
|
|
if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Next, we'll write out the fields that are specified to the contract
|
|
// resolver.
|
|
if err := binary.Write(w, endian, h.outputIncubating); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(w, endian, h.resolved); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
|
|
return err
|
|
}
|
|
if _, err := w.Write(h.htlc.RHash[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
// We encode the sign details last for backwards compatibility.
|
|
err := encodeSignDetails(w, h.htlcResolution.SignDetails)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
|
|
// from the passed Reader instance, returning an active ContractResolver
|
|
// instance.
|
|
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
|
|
*htlcSuccessResolver, error) {
|
|
|
|
h := &htlcSuccessResolver{
|
|
contractResolverKit: *newContractResolverKit(resCfg),
|
|
}
|
|
|
|
// First we'll decode our inner HTLC resolution.
|
|
if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Next, we'll read all the fields that are specified to the contract
|
|
// resolver.
|
|
if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Read(r, endian, &h.resolved); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Sign details is a new field that was added to the htlc resolution,
|
|
// so it is serialized last for backwards compatibility. We try to read
|
|
// it, but don't error out if there are not bytes left.
|
|
signDetails, err := decodeSignDetails(r)
|
|
if err == nil {
|
|
h.htlcResolution.SignDetails = signDetails
|
|
} else if err != io.EOF && err != io.ErrUnexpectedEOF {
|
|
return nil, err
|
|
}
|
|
|
|
h.initReport()
|
|
|
|
return h, nil
|
|
}
|
|
|
|
// Supplement adds additional information to the resolver that is required
|
|
// before Resolve() is called.
|
|
//
|
|
// NOTE: Part of the htlcContractResolver interface.
|
|
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
|
|
h.htlc = htlc
|
|
}
|
|
|
|
// HtlcPoint returns the htlc's outpoint on the commitment tx.
|
|
//
|
|
// NOTE: Part of the htlcContractResolver interface.
|
|
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
|
|
return h.htlcResolution.HtlcPoint()
|
|
}
|
|
|
|
// SupplementDeadline does nothing for an incoming htlc resolver.
|
|
//
|
|
// NOTE: Part of the htlcContractResolver interface.
|
|
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
|
|
}
|
|
|
|
// A compile time assertion to ensure htlcSuccessResolver meets the
|
|
// ContractResolver interface.
|
|
var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
|