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

     1  package contractcourt
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  	"math"
     8  	"sync"
     9  
    10  	"github.com/decred/dcrd/chaincfg/chainhash"
    11  	"github.com/decred/dcrd/dcrutil/v4"
    12  	"github.com/decred/dcrd/txscript/v4"
    13  	"github.com/decred/dcrd/wire"
    14  	"github.com/decred/dcrlnd/chainntnfs"
    15  	"github.com/decred/dcrlnd/channeldb"
    16  	"github.com/decred/dcrlnd/input"
    17  	"github.com/decred/dcrlnd/lnwallet"
    18  	"github.com/decred/dcrlnd/sweep"
    19  )
    20  
    21  const (
    22  	// commitOutputConfTarget is the default confirmation target we'll use
    23  	// for sweeps of commit outputs that belong to us.
    24  	commitOutputConfTarget = 6
    25  )
    26  
    27  // commitSweepResolver is a resolver that will attempt to sweep the commitment
    28  // output paying to us, in the case that the remote party broadcasts their
    29  // version of the commitment transaction. We can sweep this output immediately,
    30  // as it doesn't have a time-lock delay.
    31  type commitSweepResolver struct {
    32  	// commitResolution contains all data required to successfully sweep
    33  	// this HTLC on-chain.
    34  	commitResolution lnwallet.CommitOutputResolution
    35  
    36  	// resolved reflects if the contract has been fully resolved or not.
    37  	resolved bool
    38  
    39  	// broadcastHeight is the height that the original contract was
    40  	// broadcast to the main-chain at. We'll use this value to bound any
    41  	// historical queries to the chain for spends/confirmations.
    42  	broadcastHeight uint32
    43  
    44  	// chanPoint is the channel point of the original contract.
    45  	chanPoint wire.OutPoint
    46  
    47  	// channelInitiator denotes whether the party responsible for resolving
    48  	// the contract initiated the channel.
    49  	channelInitiator bool
    50  
    51  	// leaseExpiry denotes the additional waiting period the contract must
    52  	// hold until it can be resolved. This waiting period is known as the
    53  	// expiration of a script-enforced leased channel and only applies to
    54  	// the channel initiator.
    55  	//
    56  	// NOTE: This value should only be set when the contract belongs to a
    57  	// leased channel.
    58  	leaseExpiry uint32
    59  
    60  	// currentReport stores the current state of the resolver for reporting
    61  	// over the rpc interface.
    62  	currentReport ContractReport
    63  
    64  	// reportLock prevents concurrent access to the resolver report.
    65  	reportLock sync.Mutex
    66  
    67  	contractResolverKit
    68  }
    69  
    70  // newCommitSweepResolver instantiates a new direct commit output resolver.
    71  func newCommitSweepResolver(res lnwallet.CommitOutputResolution,
    72  	broadcastHeight uint32, chanPoint wire.OutPoint,
    73  	resCfg ResolverConfig) *commitSweepResolver {
    74  
    75  	r := &commitSweepResolver{
    76  		contractResolverKit: *newContractResolverKit(resCfg),
    77  		commitResolution:    res,
    78  		broadcastHeight:     broadcastHeight,
    79  		chanPoint:           chanPoint,
    80  	}
    81  
    82  	r.initLogger(r)
    83  	r.initReport()
    84  
    85  	return r
    86  }
    87  
    88  // ResolverKey returns an identifier which should be globally unique for this
    89  // particular resolver within the chain the original contract resides within.
    90  func (c *commitSweepResolver) ResolverKey() []byte {
    91  	key := newResolverID(c.commitResolution.SelfOutPoint)
    92  	return key[:]
    93  }
    94  
    95  // waitForHeight registers for block notifications and waits for the provided
    96  // block height to be reached.
    97  func waitForHeight(waitHeight uint32, notifier chainntnfs.ChainNotifier,
    98  	quit <-chan struct{}) error {
    99  
   100  	// Register for block epochs. After registration, the current height
   101  	// will be sent on the channel immediately.
   102  	blockEpochs, err := notifier.RegisterBlockEpochNtfn(nil)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	defer blockEpochs.Cancel()
   107  
   108  	for {
   109  		select {
   110  		case newBlock, ok := <-blockEpochs.Epochs:
   111  			if !ok {
   112  				return errResolverShuttingDown
   113  			}
   114  			height := newBlock.Height
   115  			if height >= int32(waitHeight) {
   116  				return nil
   117  			}
   118  
   119  		case <-quit:
   120  			return errResolverShuttingDown
   121  		}
   122  	}
   123  }
   124  
   125  // waitForSpend waits for the given outpoint to be spent, and returns the
   126  // details of the spending tx.
   127  func waitForSpend(op *wire.OutPoint, pkScript []byte, heightHint uint32,
   128  	notifier chainntnfs.ChainNotifier, quit <-chan struct{}) (
   129  	*chainntnfs.SpendDetail, error) {
   130  
   131  	spendNtfn, err := notifier.RegisterSpendNtfn(
   132  		op, pkScript, heightHint,
   133  	)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	select {
   139  	case spendDetail, ok := <-spendNtfn.Spend:
   140  		if !ok {
   141  			return nil, errResolverShuttingDown
   142  		}
   143  
   144  		return spendDetail, nil
   145  
   146  	case <-quit:
   147  		return nil, errResolverShuttingDown
   148  	}
   149  }
   150  
   151  // getCommitTxConfHeight waits for confirmation of the commitment tx and returns
   152  // the confirmation height.
   153  func (c *commitSweepResolver) getCommitTxConfHeight() (uint32, error) {
   154  	txID := c.commitResolution.SelfOutPoint.Hash
   155  	signDesc := c.commitResolution.SelfOutputSignDesc
   156  	pkScript := signDesc.Output.PkScript
   157  	const confDepth = 1
   158  	confChan, err := c.Notifier.RegisterConfirmationsNtfn(
   159  		&txID, pkScript, confDepth, c.broadcastHeight,
   160  	)
   161  	if err != nil {
   162  		return 0, err
   163  	}
   164  	defer confChan.Cancel()
   165  
   166  	select {
   167  	case txConfirmation, ok := <-confChan.Confirmed:
   168  		if !ok {
   169  			return 0, fmt.Errorf("cannot get confirmation "+
   170  				"for commit tx %v", txID)
   171  		}
   172  
   173  		return txConfirmation.BlockHeight, nil
   174  
   175  	case <-c.quit:
   176  		return 0, errResolverShuttingDown
   177  	}
   178  }
   179  
   180  // Resolve instructs the contract resolver to resolve the output on-chain. Once
   181  // the output has been *fully* resolved, the function should return immediately
   182  // with a nil ContractResolver value for the first return value.  In the case
   183  // that the contract requires further resolution, then another resolve is
   184  // returned.
   185  //
   186  // NOTE: This function MUST be run as a goroutine.
   187  func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
   188  	// If we're already resolved, then we can exit early.
   189  	if c.resolved {
   190  		return nil, nil
   191  	}
   192  
   193  	confHeight, err := c.getCommitTxConfHeight()
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	// Wait up until the CSV expires, unless we also have a CLTV that
   199  	// expires after.
   200  	unlockHeight := confHeight + c.commitResolution.MaturityDelay
   201  	if c.hasCLTV() {
   202  		unlockHeight = uint32(math.Max(
   203  			float64(unlockHeight), float64(c.leaseExpiry),
   204  		))
   205  	}
   206  
   207  	c.log.Debugf("commit conf_height=%v, unlock_height=%v",
   208  		confHeight, unlockHeight)
   209  
   210  	// Update report now that we learned the confirmation height.
   211  	c.reportLock.Lock()
   212  	c.currentReport.MaturityHeight = unlockHeight
   213  	c.reportLock.Unlock()
   214  
   215  	// If there is a csv/cltv lock, we'll wait for that.
   216  	if c.commitResolution.MaturityDelay > 0 || c.hasCLTV() {
   217  		// Determine what height we should wait until for the locks to
   218  		// expire.
   219  		var waitHeight uint32
   220  		switch {
   221  		// If we have both a csv and cltv lock, we'll need to look at
   222  		// both and see which expires later.
   223  		case c.commitResolution.MaturityDelay > 0 && c.hasCLTV():
   224  			c.log.Debugf("waiting for CSV and CLTV lock to expire "+
   225  				"at height %v", unlockHeight)
   226  			// If the CSV expires after the CLTV, or there is no
   227  			// CLTV, then we can broadcast a sweep a block before.
   228  			// Otherwise, we need to broadcast at our expected
   229  			// unlock height.
   230  			waitHeight = uint32(math.Max(
   231  				float64(unlockHeight-1), float64(c.leaseExpiry),
   232  			))
   233  
   234  		// If we only have a csv lock, wait for the height before the
   235  		// lock expires as the spend path should be unlocked by then.
   236  		case c.commitResolution.MaturityDelay > 0:
   237  			c.log.Debugf("waiting for CSV lock to expire at "+
   238  				"height %v", unlockHeight)
   239  			waitHeight = unlockHeight - 1
   240  		}
   241  
   242  		err := waitForHeight(waitHeight, c.Notifier, c.quit)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  	}
   247  
   248  	// The output is on our local commitment if the script starts with
   249  	// OP_IF for the revocation clause. On the remote commitment it will
   250  	// either be a regular P2WKH or a simple sig spend with a CSV delay.
   251  	isLocalCommitTx := c.commitResolution.SelfOutputSignDesc.WitnessScript[0] == txscript.OP_IF
   252  	isDelayedOutput := c.commitResolution.MaturityDelay != 0
   253  
   254  	c.log.Debugf("isDelayedOutput=%v, isLocalCommitTx=%v", isDelayedOutput,
   255  		isLocalCommitTx)
   256  
   257  	// There're three types of commitments, those that have tweaks
   258  	// for the remote key (us in this case), those that don't, and a third
   259  	// where there is no tweak and the output is delayed. On the local
   260  	// commitment our output will always be delayed. We'll rely on the
   261  	// presence of the commitment tweak to to discern which type of
   262  	// commitment this is.
   263  	var witnessType input.WitnessType
   264  	switch {
   265  
   266  	// Delayed output to us on our local commitment for a channel lease in
   267  	// which we are the initiator.
   268  	case isLocalCommitTx && c.hasCLTV():
   269  		witnessType = input.LeaseCommitmentTimeLock
   270  
   271  	// Delayed output to us on our local commitment.
   272  	case isLocalCommitTx:
   273  		witnessType = input.CommitmentTimeLock
   274  
   275  	// A confirmed output to us on the remote commitment for a channel lease
   276  	// in which we are the initiator.
   277  	case isDelayedOutput && c.hasCLTV():
   278  		witnessType = input.LeaseCommitmentToRemoteConfirmed
   279  
   280  	// A confirmed output to us on the remote commitment.
   281  	case isDelayedOutput:
   282  		witnessType = input.CommitmentToRemoteConfirmed
   283  
   284  	// A non-delayed output on the remote commitment where the key is
   285  	// tweakless.
   286  	case c.commitResolution.SelfOutputSignDesc.SingleTweak == nil:
   287  		witnessType = input.CommitSpendNoDelayTweakless
   288  
   289  	// A non-delayed output on the remote commitment where the key is
   290  	// tweaked.
   291  	default:
   292  		witnessType = input.CommitmentNoDelay
   293  	}
   294  
   295  	c.log.Infof("Sweeping with witness type: %v", witnessType)
   296  
   297  	// We'll craft an input with all the information required for
   298  	// the sweeper to create a fully valid sweeping transaction to
   299  	// recover these coins.
   300  	var inp *input.BaseInput
   301  	if c.hasCLTV() {
   302  		inp = input.NewCsvInputWithCltv(
   303  			&c.commitResolution.SelfOutPoint, witnessType,
   304  			&c.commitResolution.SelfOutputSignDesc,
   305  			c.broadcastHeight, c.commitResolution.MaturityDelay,
   306  			c.leaseExpiry,
   307  		)
   308  	} else {
   309  		inp = input.NewCsvInput(
   310  			&c.commitResolution.SelfOutPoint, witnessType,
   311  			&c.commitResolution.SelfOutputSignDesc,
   312  			c.broadcastHeight, c.commitResolution.MaturityDelay,
   313  		)
   314  	}
   315  
   316  	// With our input constructed, we'll now offer it to the
   317  	// sweeper.
   318  	c.log.Infof("sweeping commit output")
   319  
   320  	feePref := sweep.FeePreference{ConfTarget: commitOutputConfTarget}
   321  	resultChan, err := c.Sweeper.SweepInput(inp, sweep.Params{Fee: feePref})
   322  	if err != nil {
   323  		c.log.Errorf("unable to sweep input: %v", err)
   324  
   325  		return nil, err
   326  	}
   327  
   328  	var sweepTxID chainhash.Hash
   329  
   330  	// Sweeper is going to join this input with other inputs if
   331  	// possible and publish the sweep tx. When the sweep tx
   332  	// confirms, it signals us through the result channel with the
   333  	// outcome. Wait for this to happen.
   334  	outcome := channeldb.ResolverOutcomeClaimed
   335  	select {
   336  	case sweepResult := <-resultChan:
   337  		switch sweepResult.Err {
   338  		case sweep.ErrRemoteSpend:
   339  			// If the remote party was able to sweep this output
   340  			// it's likely what we sent was actually a revoked
   341  			// commitment. Report the error and continue to wrap up
   342  			// the contract.
   343  			c.log.Warnf("local commitment output was swept by "+
   344  				"remote party via %v", sweepResult.Tx.TxHash())
   345  			outcome = channeldb.ResolverOutcomeUnclaimed
   346  		case nil:
   347  			// No errors, therefore continue processing.
   348  			c.log.Infof("commit tx fully resolved by sweep tx: %v",
   349  				sweepResult.Tx.TxHash())
   350  		default:
   351  			// Unknown errors.
   352  			c.log.Errorf("unable to sweep input: %v",
   353  				sweepResult.Err)
   354  
   355  			return nil, sweepResult.Err
   356  		}
   357  
   358  		sweepTxID = sweepResult.Tx.TxHash()
   359  
   360  	case <-c.quit:
   361  		return nil, errResolverShuttingDown
   362  	}
   363  
   364  	// Funds have been swept and balance is no longer in limbo.
   365  	c.reportLock.Lock()
   366  	if outcome == channeldb.ResolverOutcomeClaimed {
   367  		// We only record the balance as recovered if it actually came
   368  		// back to us.
   369  		c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
   370  	}
   371  	c.currentReport.LimboBalance = 0
   372  	c.reportLock.Unlock()
   373  	report := c.currentReport.resolverReport(
   374  		&sweepTxID, channeldb.ResolverTypeCommit, outcome,
   375  	)
   376  	c.resolved = true
   377  
   378  	// Checkpoint the resolver with a closure that will write the outcome
   379  	// of the resolver and its sweep transaction to disk.
   380  	return nil, c.Checkpoint(c, report)
   381  }
   382  
   383  // Stop signals the resolver to cancel any current resolution processes, and
   384  // suspend.
   385  //
   386  // NOTE: Part of the ContractResolver interface.
   387  func (c *commitSweepResolver) Stop() {
   388  	close(c.quit)
   389  }
   390  
   391  // IsResolved returns true if the stored state in the resolve is fully
   392  // resolved. In this case the target output can be forgotten.
   393  //
   394  // NOTE: Part of the ContractResolver interface.
   395  func (c *commitSweepResolver) IsResolved() bool {
   396  	return c.resolved
   397  }
   398  
   399  // SupplementState allows the user of a ContractResolver to supplement it with
   400  // state required for the proper resolution of a contract.
   401  //
   402  // NOTE: Part of the ContractResolver interface.
   403  func (c *commitSweepResolver) SupplementState(state *channeldb.OpenChannel) {
   404  	if state.ChanType.HasLeaseExpiration() {
   405  		c.leaseExpiry = state.ThawHeight
   406  	}
   407  	c.channelInitiator = state.IsInitiator
   408  }
   409  
   410  // hasCLTV denotes whether the resolver must wait for an additional CLTV to
   411  // expire before resolving the contract.
   412  func (c *commitSweepResolver) hasCLTV() bool {
   413  	return c.channelInitiator && c.leaseExpiry > 0
   414  }
   415  
   416  // Encode writes an encoded version of the ContractResolver into the passed
   417  // Writer.
   418  //
   419  // NOTE: Part of the ContractResolver interface.
   420  func (c *commitSweepResolver) Encode(w io.Writer) error {
   421  	if err := encodeCommitResolution(w, &c.commitResolution); err != nil {
   422  		return err
   423  	}
   424  
   425  	if err := binary.Write(w, endian, c.resolved); err != nil {
   426  		return err
   427  	}
   428  	if err := binary.Write(w, endian, c.broadcastHeight); err != nil {
   429  		return err
   430  	}
   431  	if _, err := w.Write(c.chanPoint.Hash[:]); err != nil {
   432  		return err
   433  	}
   434  	err := binary.Write(w, endian, c.chanPoint.Index)
   435  	if err != nil {
   436  		return err
   437  	}
   438  
   439  	// Previously a sweep tx was serialized at this point. Refactoring
   440  	// removed this, but keep in mind that this data may still be present in
   441  	// the database.
   442  
   443  	return nil
   444  }
   445  
   446  // newCommitSweepResolverFromReader attempts to decode an encoded
   447  // ContractResolver from the passed Reader instance, returning an active
   448  // ContractResolver instance.
   449  func newCommitSweepResolverFromReader(r io.Reader, resCfg ResolverConfig) (
   450  	*commitSweepResolver, error) {
   451  
   452  	c := &commitSweepResolver{
   453  		contractResolverKit: *newContractResolverKit(resCfg),
   454  	}
   455  
   456  	if err := decodeCommitResolution(r, &c.commitResolution); err != nil {
   457  		return nil, err
   458  	}
   459  
   460  	if err := binary.Read(r, endian, &c.resolved); err != nil {
   461  		return nil, err
   462  	}
   463  	if err := binary.Read(r, endian, &c.broadcastHeight); err != nil {
   464  		return nil, err
   465  	}
   466  	_, err := io.ReadFull(r, c.chanPoint.Hash[:])
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  	err = binary.Read(r, endian, &c.chanPoint.Index)
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  
   475  	// Previously a sweep tx was deserialized at this point. Refactoring
   476  	// removed this, but keep in mind that this data may still be present in
   477  	// the database.
   478  
   479  	c.initLogger(c)
   480  	c.initReport()
   481  
   482  	return c, nil
   483  }
   484  
   485  // report returns a report on the resolution state of the contract.
   486  func (c *commitSweepResolver) report() *ContractReport {
   487  	c.reportLock.Lock()
   488  	defer c.reportLock.Unlock()
   489  
   490  	copy := c.currentReport
   491  	return &copy
   492  }
   493  
   494  // initReport initializes the pending channels report for this resolver.
   495  func (c *commitSweepResolver) initReport() {
   496  	amt := dcrutil.Amount(
   497  		c.commitResolution.SelfOutputSignDesc.Output.Value,
   498  	)
   499  
   500  	// Set the initial report. All fields are filled in, except for the
   501  	// maturity height which remains 0 until Resolve() is executed.
   502  	//
   503  	// TODO(joostjager): Resolvers only activate after the commit tx
   504  	// confirms. With more refactoring in channel arbitrator, it would be
   505  	// possible to make the confirmation height part of ResolverConfig and
   506  	// populate MaturityHeight here.
   507  	c.currentReport = ContractReport{
   508  		Outpoint:         c.commitResolution.SelfOutPoint,
   509  		Type:             ReportOutputUnencumbered,
   510  		Amount:           amt,
   511  		LimboBalance:     amt,
   512  		RecoveredBalance: 0,
   513  	}
   514  }
   515  
   516  // A compile time assertion to ensure commitSweepResolver meets the
   517  // ContractResolver interface.
   518  var _ reportingContractResolver = (*commitSweepResolver)(nil)