2019-01-16 20:03:59 +01:00
|
|
|
package contractcourt
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
2024-06-20 21:56:52 +08:00
|
|
|
"fmt"
|
2019-01-16 20:03:59 +01:00
|
|
|
"io"
|
2020-12-09 12:24:03 +01:00
|
|
|
"sync"
|
2019-01-16 20:03:59 +01:00
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2020-07-07 19:49:55 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2023-03-01 22:16:42 -08:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2019-01-16 20:03:59 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2019-11-01 12:23:48 +01:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2024-12-03 15:51:05 -07:00
|
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
2024-10-22 12:57:00 +02:00
|
|
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
2019-02-19 17:06:00 -08:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2020-07-29 09:27:22 +02:00
|
|
|
"github.com/lightningnetwork/lnd/labels"
|
2023-03-01 22:16:42 -08:00
|
|
|
"github.com/lightningnetwork/lnd/lnutils"
|
2019-01-16 20:03:59 +01:00
|
|
|
"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
|
2020-12-09 12:24:03 +01:00
|
|
|
// 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).
|
2019-01-16 20:03:59 +01:00
|
|
|
outputIncubating 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
|
|
|
|
|
2019-11-05 14:23:15 +01:00
|
|
|
// htlc contains information on the htlc that we are resolving on-chain.
|
|
|
|
htlc channeldb.HTLC
|
2019-01-22 20:45:32 -08:00
|
|
|
|
2020-12-09 12:24:03 +01:00
|
|
|
// 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
|
|
|
|
|
2019-11-06 13:16:50 +01:00
|
|
|
contractResolverKit
|
2022-05-31 16:53:06 -07:00
|
|
|
|
|
|
|
htlcLeaseResolver
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
2019-11-01 11:04:28 +01:00
|
|
|
// newSuccessResolver instanties a new htlc success resolver.
|
|
|
|
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
|
2019-11-05 14:23:15 +01:00
|
|
|
broadcastHeight uint32, htlc channeldb.HTLC,
|
2019-11-01 11:04:28 +01:00
|
|
|
resCfg ResolverConfig) *htlcSuccessResolver {
|
|
|
|
|
2020-12-09 12:24:03 +01:00
|
|
|
h := &htlcSuccessResolver{
|
2019-11-01 11:04:28 +01:00
|
|
|
contractResolverKit: *newContractResolverKit(resCfg),
|
|
|
|
htlcResolution: res,
|
|
|
|
broadcastHeight: broadcastHeight,
|
2019-11-05 14:23:15 +01:00
|
|
|
htlc: htlc,
|
2019-11-01 11:04:28 +01:00
|
|
|
}
|
2020-12-09 12:24:03 +01:00
|
|
|
|
|
|
|
h.initReport()
|
2024-06-20 21:56:52 +08:00
|
|
|
h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
|
2020-12-09 12:24:03 +01:00
|
|
|
|
|
|
|
return h
|
2019-11-01 11:04:28 +01:00
|
|
|
}
|
|
|
|
|
2024-06-20 21:56:52 +08:00
|
|
|
// outpoint returns the outpoint of the HTLC output we're attempting to sweep.
|
|
|
|
func (h *htlcSuccessResolver) outpoint() wire.OutPoint {
|
2019-01-16 20:03:59 +01:00
|
|
|
// 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.
|
|
|
|
if h.htlcResolution.SignedSuccessTx != nil {
|
2024-06-20 21:56:52 +08:00
|
|
|
return h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
2024-06-20 21:56:52 +08:00
|
|
|
return h.htlcResolution.ClaimOutpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
key := newResolverID(h.outpoint())
|
2019-01-16 20:03:59 +01:00
|
|
|
return key[:]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve attempts to resolve an unresolved incoming HTLC that we know the
|
2019-04-16 12:11:20 +02:00
|
|
|
// 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.
|
2019-01-16 20:03:59 +01:00
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
2024-07-16 07:24:45 +08:00
|
|
|
//
|
|
|
|
// TODO(yy): refactor the interface method to return an error only.
|
2024-06-05 00:56:39 +08:00
|
|
|
func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
2024-07-16 07:24:45 +08:00
|
|
|
var err error
|
2020-12-09 12:24:03 +01:00
|
|
|
|
2024-07-16 07:24:45 +08:00
|
|
|
switch {
|
|
|
|
// If we're already resolved, then we can exit early.
|
2024-07-10 18:08:23 +08:00
|
|
|
case h.IsResolved():
|
2024-07-16 07:24:45 +08:00
|
|
|
h.log.Errorf("already resolved")
|
2020-07-07 19:49:55 +02:00
|
|
|
|
2024-07-16 07:24:45 +08:00
|
|
|
// If this is an output on the remote party's commitment transaction,
|
|
|
|
// use the direct-spend path to sweep the htlc.
|
|
|
|
case h.isRemoteCommitOutput():
|
|
|
|
err = h.resolveRemoteCommitOutput()
|
2020-12-09 12:24:03 +01:00
|
|
|
|
2024-07-16 07:24:45 +08:00
|
|
|
// If this is an output on our commitment transaction using post-anchor
|
|
|
|
// channel type, it will be handled by the sweeper.
|
|
|
|
case h.isZeroFeeOutput():
|
2024-11-14 21:59:55 +08:00
|
|
|
err = h.resolveSuccessTx()
|
2020-12-09 12:24:03 +01:00
|
|
|
|
2024-07-16 07:24:45 +08:00
|
|
|
// If this is an output on our own commitment using pre-anchor channel
|
|
|
|
// type, we will publish the success tx and offer the output to the
|
|
|
|
// nursery.
|
|
|
|
default:
|
|
|
|
err = h.resolveLegacySuccessTx()
|
2022-05-31 16:53:06 -07:00
|
|
|
}
|
2020-12-09 12:24:03 +01:00
|
|
|
|
2024-07-16 07:24:45 +08:00
|
|
|
return nil, err
|
2020-12-09 12:24:03 +01:00
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:02 +01:00
|
|
|
// 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.
|
2024-07-16 07:24:45 +08:00
|
|
|
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() error {
|
|
|
|
h.log.Info("waiting for direct-preimage spend of the htlc to confirm")
|
2023-08-09 22:34:38 +08:00
|
|
|
|
2024-03-19 12:32:01 +08:00
|
|
|
// Wait for the direct-preimage HTLC sweep tx to confirm.
|
2024-07-16 07:24:45 +08:00
|
|
|
//
|
|
|
|
// TODO(yy): use the result chan returned from `SweepInput`.
|
2024-03-19 12:32:01 +08:00
|
|
|
sweepTxDetails, err := waitForSpend(
|
|
|
|
&h.htlcResolution.ClaimOutpoint,
|
|
|
|
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
|
|
|
h.broadcastHeight, h.Notifier, h.quit,
|
2020-12-09 12:24:02 +01:00
|
|
|
)
|
|
|
|
if err != nil {
|
2024-07-16 07:24:45 +08:00
|
|
|
return err
|
2020-12-09 12:24:02 +01:00
|
|
|
}
|
|
|
|
|
2024-07-16 07:24:45 +08:00
|
|
|
// TODO(yy): should also update the `RecoveredBalance` and
|
|
|
|
// `LimboBalance` like other paths?
|
|
|
|
|
2020-12-09 12:24:02 +01:00
|
|
|
// Checkpoint the resolver, and write the outcome to disk.
|
2024-07-16 07:24:45 +08:00
|
|
|
return h.checkpointClaim(sweepTxDetails.SpenderTxHash)
|
2020-12-09 12:24:02 +01:00
|
|
|
}
|
|
|
|
|
2020-07-07 19:49:55 +02:00
|
|
|
// 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.
|
2024-11-14 21:59:55 +08:00
|
|
|
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash) error {
|
2022-05-10 12:33:44 +02:00
|
|
|
// Mark the htlc as final settled.
|
|
|
|
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
|
|
|
|
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-08-29 13:28:17 +02:00
|
|
|
// Send notification.
|
|
|
|
h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
|
2022-11-18 03:15:22 -08:00
|
|
|
models.CircuitKey{
|
2022-08-29 13:28:17 +02:00
|
|
|
ChanID: h.ShortChanID,
|
|
|
|
HtlcID: h.htlc.HtlcIndex,
|
|
|
|
},
|
|
|
|
channeldb.FinalHtlcInfo{
|
|
|
|
Settled: true,
|
|
|
|
Offchain: false,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2020-07-07 19:49:55 +02:00
|
|
|
// 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,
|
2024-11-14 21:59:55 +08:00
|
|
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
|
2020-07-07 19:49:55 +02:00
|
|
|
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).
|
2024-07-10 18:08:23 +08:00
|
|
|
h.markResolved()
|
2020-07-07 19:49:55 +02:00
|
|
|
return h.Checkpoint(h, reports...)
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop signals the resolver to cancel any current resolution processes, and
|
|
|
|
// suspend.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the ContractResolver interface.
|
|
|
|
func (h *htlcSuccessResolver) Stop() {
|
2024-07-16 07:24:45 +08:00
|
|
|
h.log.Debugf("stopping...")
|
|
|
|
defer h.log.Debugf("stopped")
|
|
|
|
|
2019-11-06 13:31:13 +01:00
|
|
|
close(h.quit)
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:03 +01:00
|
|
|
// 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()
|
2022-02-07 13:58:25 +01:00
|
|
|
cpy := h.currentReport
|
|
|
|
return &cpy
|
2020-12-09 12:24:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-16 20:03:59 +01:00
|
|
|
// 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
|
|
|
|
}
|
2024-07-10 18:08:23 +08:00
|
|
|
if err := binary.Write(w, endian, h.IsResolved()); err != nil {
|
2019-01-16 20:03:59 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-05 14:23:15 +01:00
|
|
|
if _, err := w.Write(h.htlc.RHash[:]); err != nil {
|
2019-01-16 20:03:59 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:01 +01:00
|
|
|
// We encode the sign details last for backwards compatibility.
|
|
|
|
err := encodeSignDetails(w, h.htlcResolution.SignDetails)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-16 20:03:59 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-01 10:24:33 +01:00
|
|
|
// 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),
|
|
|
|
}
|
|
|
|
|
2019-01-16 20:03:59 +01:00
|
|
|
// First we'll decode our inner HTLC resolution.
|
|
|
|
if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
|
2019-11-01 10:24:33 +01:00
|
|
|
return nil, err
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Next, we'll read all the fields that are specified to the contract
|
|
|
|
// resolver.
|
|
|
|
if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
|
2019-11-01 10:24:33 +01:00
|
|
|
return nil, err
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
2024-07-10 18:08:23 +08:00
|
|
|
|
|
|
|
var resolved bool
|
|
|
|
if err := binary.Read(r, endian, &resolved); err != nil {
|
2019-11-01 10:24:33 +01:00
|
|
|
return nil, err
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
2024-07-10 18:08:23 +08:00
|
|
|
if resolved {
|
|
|
|
h.markResolved()
|
|
|
|
}
|
|
|
|
|
2019-01-16 20:03:59 +01:00
|
|
|
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
|
2019-11-01 10:24:33 +01:00
|
|
|
return nil, err
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
2019-11-05 14:23:15 +01:00
|
|
|
if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
|
2019-11-01 10:24:33 +01:00
|
|
|
return nil, err
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:01 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:03 +01:00
|
|
|
h.initReport()
|
2024-06-20 21:56:52 +08:00
|
|
|
h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
|
2020-12-09 12:24:03 +01:00
|
|
|
|
2019-11-01 10:24:33 +01:00
|
|
|
return h, nil
|
2019-01-16 20:03:59 +01:00
|
|
|
}
|
|
|
|
|
2019-11-01 12:23:48 +01:00
|
|
|
// 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) {
|
2019-11-05 14:23:15 +01:00
|
|
|
h.htlc = htlc
|
2019-11-01 12:23:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
|
2024-03-19 10:36:00 +08:00
|
|
|
// SupplementDeadline does nothing for an incoming htlc resolver.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the htlcContractResolver interface.
|
|
|
|
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
|
|
|
|
}
|
|
|
|
|
2019-01-16 20:03:59 +01:00
|
|
|
// A compile time assertion to ensure htlcSuccessResolver meets the
|
|
|
|
// ContractResolver interface.
|
2019-11-01 12:23:48 +01:00
|
|
|
var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
|
2024-11-14 02:28:56 +08:00
|
|
|
|
|
|
|
// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
|
|
|
|
// on the remote commitment.
|
|
|
|
func (h *htlcSuccessResolver) isRemoteCommitOutput() bool {
|
|
|
|
// If we don't have a success transaction, then this means that this is
|
|
|
|
// an output on the remote party's commitment transaction.
|
|
|
|
return h.htlcResolution.SignedSuccessTx == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
|
|
|
|
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
|
|
|
|
func (h *htlcSuccessResolver) isZeroFeeOutput() bool {
|
|
|
|
// If we have non-nil SignDetails, this means it has 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.
|
|
|
|
return h.htlcResolution.SignedSuccessTx != nil &&
|
|
|
|
h.htlcResolution.SignDetails != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isTaproot returns true if the resolver is for a taproot output.
|
|
|
|
func (h *htlcSuccessResolver) isTaproot() bool {
|
|
|
|
return txscript.IsPayToTaproot(
|
|
|
|
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
|
|
|
)
|
|
|
|
}
|
2024-11-14 21:59:16 +08:00
|
|
|
|
|
|
|
// sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on
|
|
|
|
// the remote commitment via the direct preimage-spend.
|
|
|
|
func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error {
|
|
|
|
// 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 h.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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// sweepSuccessTx attempts to sweep the second level success tx.
|
|
|
|
func (h *htlcSuccessResolver) sweepSuccessTx() error {
|
|
|
|
var secondLevelInput input.HtlcSecondLevelAnchorInput
|
|
|
|
if h.isTaproot() {
|
|
|
|
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
|
|
|
|
h.htlcResolution.SignedSuccessTx,
|
|
|
|
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
|
|
|
|
h.broadcastHeight, input.WithResolutionBlob(
|
|
|
|
h.htlcResolution.ResolutionBlob,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
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.
|
|
|
|
deadline := fn.Some(int32(h.htlc.RefundTimeout))
|
|
|
|
|
|
|
|
h.log.Infof("offering second-level HTLC success tx to sweeper with "+
|
|
|
|
"deadline=%v, budget=%v", 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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// sweepSuccessTxOutput attempts to sweep the output of the second level
|
|
|
|
// success tx.
|
|
|
|
func (h *htlcSuccessResolver) sweepSuccessTxOutput() error {
|
|
|
|
h.log.Debugf("sweeping output %v from 2nd-level HTLC success tx",
|
|
|
|
h.htlcResolution.ClaimOutpoint)
|
|
|
|
|
|
|
|
// 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 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 h.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(yy): use the result chan returned from SweepInput.
|
|
|
|
_, 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](),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
2024-11-14 21:59:55 +08:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
2024-07-16 07:24:45 +08:00
|
|
|
|
|
|
|
// Launch creates an input based on the details of the incoming htlc resolution
|
|
|
|
// and offers it to the sweeper.
|
|
|
|
func (h *htlcSuccessResolver) Launch() error {
|
2024-07-11 16:19:01 +08:00
|
|
|
if h.isLaunched() {
|
2024-07-16 07:24:45 +08:00
|
|
|
h.log.Tracef("already launched")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
h.log.Debugf("launching resolver...")
|
2024-07-11 16:19:01 +08:00
|
|
|
h.markLaunched()
|
2024-07-16 07:24:45 +08:00
|
|
|
|
|
|
|
switch {
|
|
|
|
// If we're already resolved, then we can exit early.
|
2024-07-10 18:08:23 +08:00
|
|
|
case h.IsResolved():
|
2024-07-16 07:24:45 +08:00
|
|
|
h.log.Errorf("already resolved")
|
|
|
|
return nil
|
|
|
|
|
|
|
|
// If this is an output on the remote party's commitment transaction,
|
|
|
|
// use the direct-spend path.
|
|
|
|
case h.isRemoteCommitOutput():
|
|
|
|
return h.sweepRemoteCommitOutput()
|
|
|
|
|
|
|
|
// If this is an anchor type channel, we now sweep either the
|
|
|
|
// second-level success tx or the output from the second-level success
|
|
|
|
// tx.
|
|
|
|
case h.isZeroFeeOutput():
|
|
|
|
// If the second-level success tx has already been swept, we
|
|
|
|
// can go ahead and sweep its output.
|
|
|
|
if h.outputIncubating {
|
|
|
|
return h.sweepSuccessTxOutput()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, sweep the second level tx.
|
|
|
|
return h.sweepSuccessTx()
|
|
|
|
|
|
|
|
// If this is a legacy channel type, the output is handled by the
|
|
|
|
// nursery via the Resolve so we do nothing here.
|
|
|
|
//
|
|
|
|
// TODO(yy): handle the legacy output by offering it to the sweeper.
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|