github.com/decred/dcrlnd@v0.7.6/contractcourt/htlc_outgoing_contest_resolver.go (about)

     1  package contractcourt
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/decred/dcrd/dcrutil/v4"
     8  	"github.com/decred/dcrlnd/channeldb"
     9  	"github.com/decred/dcrlnd/lnwallet"
    10  )
    11  
    12  // htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
    13  // outgoing HTLC that is still contested. An HTLC is still contested, if at the
    14  // time that we broadcast the commitment transaction, it isn't able to be fully
    15  // resolved. In this case, we'll either wait for the HTLC to timeout, or for us
    16  // to learn of the preimage.
    17  type htlcOutgoingContestResolver struct {
    18  	// htlcTimeoutResolver is the inner solver that this resolver may turn
    19  	// into. This only happens if the HTLC expires on-chain.
    20  	*htlcTimeoutResolver
    21  }
    22  
    23  // newOutgoingContestResolver instantiates a new outgoing contested htlc
    24  // resolver.
    25  func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
    26  	broadcastHeight uint32, htlc channeldb.HTLC,
    27  	resCfg ResolverConfig) *htlcOutgoingContestResolver {
    28  
    29  	timeout := newTimeoutResolver(
    30  		res, broadcastHeight, htlc, resCfg,
    31  	)
    32  
    33  	return &htlcOutgoingContestResolver{
    34  		htlcTimeoutResolver: timeout,
    35  	}
    36  }
    37  
    38  // Resolve commences the resolution of this contract. As this contract hasn't
    39  // yet timed out, we'll wait for one of two things to happen
    40  //
    41  //  1. The HTLC expires. In this case, we'll sweep the funds and send a clean
    42  //     up cancel message to outside sub-systems.
    43  //
    44  //  2. The remote party sweeps this HTLC on-chain, in which case we'll add the
    45  //     pre-image to our global cache, then send a clean up settle message
    46  //     backwards.
    47  //
    48  // When either of these two things happens, we'll create a new resolver which
    49  // is able to handle the final resolution of the contract. We're only the pivot
    50  // point.
    51  func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
    52  	// If we're already full resolved, then we don't have anything further
    53  	// to do.
    54  	if h.resolved {
    55  		return nil, nil
    56  	}
    57  
    58  	// Otherwise, we'll watch for two external signals to decide if we'll
    59  	// morph into another resolver, or fully resolve the contract.
    60  	//
    61  	// The output we'll be watching for is the *direct* spend from the HTLC
    62  	// output. If this isn't our commitment transaction, it'll be right on
    63  	// the resolution. Otherwise, we fetch this pointer from the input of
    64  	// the time out transaction.
    65  	outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	// First, we'll register for a spend notification for this output. If
    71  	// the remote party sweeps with the pre-image, we'll be notified.
    72  	spendNtfn, err := h.Notifier.RegisterSpendNtfn(
    73  		outPointToWatch, scriptToWatch, h.broadcastHeight,
    74  	)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	// We'll quickly check to see if the output has already been spent.
    80  	select {
    81  	// If the output has already been spent, then we can stop early and
    82  	// sweep the pre-image from the output.
    83  	case commitSpend, ok := <-spendNtfn.Spend:
    84  		if !ok {
    85  			return nil, errResolverShuttingDown
    86  		}
    87  
    88  		// TODO(roasbeef): Checkpoint?
    89  		return h.claimCleanUp(commitSpend)
    90  
    91  	// If it hasn't, then we'll watch for both the expiration, and the
    92  	// sweeping out this output.
    93  	default:
    94  	}
    95  
    96  	// If we reach this point, then we can't fully act yet, so we'll await
    97  	// either of our signals triggering: the HTLC expires, or we learn of
    98  	// the preimage.
    99  	blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	defer blockEpochs.Cancel()
   104  
   105  	for {
   106  		select {
   107  
   108  		// A new block has arrived, we'll check to see if this leads to
   109  		// HTLC expiration.
   110  		case newBlock, ok := <-blockEpochs.Epochs:
   111  			if !ok {
   112  				return nil, errResolverShuttingDown
   113  			}
   114  
   115  			// If the current height is >= expiry-1, then a timeout
   116  			// path spend will be valid to be included in the next
   117  			// block, and we can immediately return the resolver.
   118  			//
   119  			// TODO(joostjager): Statement above may not be valid.
   120  			// For CLTV locks, the expiry value is the last
   121  			// _invalid_ block. The likely reason that this does not
   122  			// create a problem, is that utxonursery is checking the
   123  			// expiry again (in the proper way).
   124  			//
   125  			// Source:
   126  			// https://github.com/decred/dcrd/blob/991d32e72fe84d5fbf9c47cd604d793a0cd3a072/blockchain/validate.go#L154
   127  			newHeight := uint32(newBlock.Height)
   128  			if newHeight >= h.htlcResolution.Expiry-1 {
   129  				log.Infof("%T(%v): HTLC has expired "+
   130  					"(height=%v, expiry=%v), transforming "+
   131  					"into timeout resolver", h,
   132  					h.htlcResolution.ClaimOutpoint,
   133  					newHeight, h.htlcResolution.Expiry)
   134  				return h.htlcTimeoutResolver, nil
   135  			}
   136  
   137  		// The output has been spent! This means the preimage has been
   138  		// revealed on-chain.
   139  		case commitSpend, ok := <-spendNtfn.Spend:
   140  			if !ok {
   141  				return nil, errResolverShuttingDown
   142  			}
   143  
   144  			// The only way this output can be spent by the remote
   145  			// party is by revealing the preimage. So we'll perform
   146  			// our duties to clean up the contract once it has been
   147  			// claimed.
   148  			return h.claimCleanUp(commitSpend)
   149  
   150  		case <-h.quit:
   151  			return nil, fmt.Errorf("resolver canceled")
   152  		}
   153  	}
   154  }
   155  
   156  // report returns a report on the resolution state of the contract.
   157  func (h *htlcOutgoingContestResolver) report() *ContractReport {
   158  	// No locking needed as these values are read-only.
   159  
   160  	finalAmt := h.htlc.Amt.ToAtoms()
   161  	if h.htlcResolution.SignedTimeoutTx != nil {
   162  		finalAmt = dcrutil.Amount(
   163  			h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
   164  		)
   165  	}
   166  
   167  	return &ContractReport{
   168  		Outpoint:       h.htlcResolution.ClaimOutpoint,
   169  		Type:           ReportOutputOutgoingHtlc,
   170  		Amount:         finalAmt,
   171  		MaturityHeight: h.htlcResolution.Expiry,
   172  		LimboBalance:   finalAmt,
   173  		Stage:          1,
   174  	}
   175  }
   176  
   177  // Stop signals the resolver to cancel any current resolution processes, and
   178  // suspend.
   179  //
   180  // NOTE: Part of the ContractResolver interface.
   181  func (h *htlcOutgoingContestResolver) Stop() {
   182  	close(h.quit)
   183  }
   184  
   185  // IsResolved returns true if the stored state in the resolve is fully
   186  // resolved. In this case the target output can be forgotten.
   187  //
   188  // NOTE: Part of the ContractResolver interface.
   189  func (h *htlcOutgoingContestResolver) IsResolved() bool {
   190  	return h.resolved
   191  }
   192  
   193  // SupplementState allows the user of a ContractResolver to supplement it with
   194  // state required for the proper resolution of a contract.
   195  //
   196  // NOTE: Part of the ContractResolver interface.
   197  func (h *htlcOutgoingContestResolver) SupplementState(state *channeldb.OpenChannel) {
   198  	h.htlcTimeoutResolver.SupplementState(state)
   199  }
   200  
   201  // Encode writes an encoded version of the ContractResolver into the passed
   202  // Writer.
   203  //
   204  // NOTE: Part of the ContractResolver interface.
   205  func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
   206  	return h.htlcTimeoutResolver.Encode(w)
   207  }
   208  
   209  // newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
   210  // from the passed Reader instance, returning an active ContractResolver
   211  // instance.
   212  func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
   213  	*htlcOutgoingContestResolver, error) {
   214  
   215  	h := &htlcOutgoingContestResolver{}
   216  	timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	h.htlcTimeoutResolver = timeoutResolver
   221  	return h, nil
   222  }
   223  
   224  // A compile time assertion to ensure htlcOutgoingContestResolver meets the
   225  // ContractResolver interface.
   226  var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)