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

     1  package contractcourt
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"sync"
     7  
     8  	"github.com/decred/dcrd/chaincfg/chainhash"
     9  	"github.com/decred/dcrd/dcrutil/v4"
    10  	"github.com/decred/dcrd/wire"
    11  	"github.com/decred/dcrlnd/channeldb"
    12  	"github.com/decred/dcrlnd/input"
    13  	"github.com/decred/dcrlnd/sweep"
    14  )
    15  
    16  // anchorResolver is a resolver that will attempt to sweep our anchor output.
    17  type anchorResolver struct {
    18  	// anchorSignDescriptor contains the information that is required to
    19  	// sweep the anchor.
    20  	anchorSignDescriptor input.SignDescriptor
    21  
    22  	// anchor is the outpoint on the commitment transaction.
    23  	anchor wire.OutPoint
    24  
    25  	// resolved reflects if the contract has been fully resolved or not.
    26  	resolved bool
    27  
    28  	// broadcastHeight is the height that the original contract was
    29  	// broadcast to the main-chain at. We'll use this value to bound any
    30  	// historical queries to the chain for spends/confirmations.
    31  	broadcastHeight uint32
    32  
    33  	// chanPoint is the channel point of the original contract.
    34  	chanPoint wire.OutPoint
    35  
    36  	// currentReport stores the current state of the resolver for reporting
    37  	// over the rpc interface.
    38  	currentReport ContractReport
    39  
    40  	// reportLock prevents concurrent access to the resolver report.
    41  	reportLock sync.Mutex
    42  
    43  	contractResolverKit
    44  }
    45  
    46  // newAnchorResolver instantiates a new anchor resolver.
    47  func newAnchorResolver(anchorSignDescriptor input.SignDescriptor,
    48  	anchor wire.OutPoint, broadcastHeight uint32,
    49  	chanPoint wire.OutPoint, resCfg ResolverConfig) *anchorResolver {
    50  
    51  	amt := dcrutil.Amount(anchorSignDescriptor.Output.Value)
    52  
    53  	report := ContractReport{
    54  		Outpoint:         anchor,
    55  		Type:             ReportOutputAnchor,
    56  		Amount:           amt,
    57  		LimboBalance:     amt,
    58  		RecoveredBalance: 0,
    59  	}
    60  
    61  	r := &anchorResolver{
    62  		contractResolverKit:  *newContractResolverKit(resCfg),
    63  		anchorSignDescriptor: anchorSignDescriptor,
    64  		anchor:               anchor,
    65  		broadcastHeight:      broadcastHeight,
    66  		chanPoint:            chanPoint,
    67  		currentReport:        report,
    68  	}
    69  
    70  	r.initLogger(r)
    71  
    72  	return r
    73  }
    74  
    75  // ResolverKey returns an identifier which should be globally unique for this
    76  // particular resolver within the chain the original contract resides within.
    77  func (c *anchorResolver) ResolverKey() []byte {
    78  	// The anchor resolver is stateless and doesn't need a database key.
    79  	return nil
    80  }
    81  
    82  // Resolve offers the anchor output to the sweeper and waits for it to be swept.
    83  func (c *anchorResolver) Resolve() (ContractResolver, error) {
    84  	// Attempt to update the sweep parameters to the post-confirmation
    85  	// situation. We don't want to force sweep anymore, because the anchor
    86  	// lost its special purpose to get the commitment confirmed. It is just
    87  	// an output that we want to sweep only if it is economical to do so.
    88  	//
    89  	// An exclusive group is not necessary anymore, because we know that
    90  	// this is the only anchor that can be swept.
    91  	//
    92  	// We also clear the parent tx information for cpfp, because the
    93  	// commitment tx is confirmed.
    94  	//
    95  	// After a restart or when the remote force closes, the sweeper is not
    96  	// yet aware of the anchor. In that case, it will be added as new input
    97  	// to the sweeper.
    98  	relayFeeRate := c.Sweeper.RelayFeePerKB()
    99  
   100  	anchorInput := input.MakeBaseInput(
   101  		&c.anchor,
   102  		input.CommitmentAnchor,
   103  		&c.anchorSignDescriptor,
   104  		c.broadcastHeight,
   105  		nil,
   106  	)
   107  
   108  	resultChan, err := c.Sweeper.SweepInput(
   109  		&anchorInput,
   110  		sweep.Params{
   111  			Fee: sweep.FeePreference{
   112  				FeeRate: relayFeeRate,
   113  			},
   114  		},
   115  	)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var (
   121  		outcome channeldb.ResolverOutcome
   122  		spendTx *chainhash.Hash
   123  	)
   124  
   125  	select {
   126  	case sweepRes := <-resultChan:
   127  		switch sweepRes.Err {
   128  
   129  		// Anchor was swept successfully.
   130  		case nil:
   131  			sweepTxID := sweepRes.Tx.TxHash()
   132  
   133  			spendTx = &sweepTxID
   134  			outcome = channeldb.ResolverOutcomeClaimed
   135  
   136  		// Anchor was swept by someone else. This is possible after the
   137  		// 16 block csv lock.
   138  		case sweep.ErrRemoteSpend:
   139  			c.log.Warnf("our anchor spent by someone else")
   140  			outcome = channeldb.ResolverOutcomeUnclaimed
   141  
   142  		// The sweeper gave up on sweeping the anchor. This happens
   143  		// after the maximum number of sweep attempts has been reached.
   144  		// See sweep.DefaultMaxSweepAttempts. Sweep attempts are
   145  		// interspaced with random delays picked from a range that
   146  		// increases exponentially.
   147  		//
   148  		// We consider the anchor as being lost.
   149  		case sweep.ErrTooManyAttempts:
   150  			c.log.Warnf("anchor sweep abandoned")
   151  			outcome = channeldb.ResolverOutcomeUnclaimed
   152  
   153  		// An unexpected error occurred.
   154  		default:
   155  			c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err)
   156  
   157  			return nil, sweepRes.Err
   158  		}
   159  
   160  	case <-c.quit:
   161  		return nil, errResolverShuttingDown
   162  	}
   163  
   164  	// Update report to reflect that funds are no longer in limbo.
   165  	c.reportLock.Lock()
   166  	if outcome == channeldb.ResolverOutcomeClaimed {
   167  		c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
   168  	}
   169  	c.currentReport.LimboBalance = 0
   170  	report := c.currentReport.resolverReport(
   171  		spendTx, channeldb.ResolverTypeAnchor, outcome,
   172  	)
   173  	c.reportLock.Unlock()
   174  
   175  	c.resolved = true
   176  	return nil, c.PutResolverReport(nil, report)
   177  }
   178  
   179  // Stop signals the resolver to cancel any current resolution processes, and
   180  // suspend.
   181  //
   182  // NOTE: Part of the ContractResolver interface.
   183  func (c *anchorResolver) Stop() {
   184  	close(c.quit)
   185  }
   186  
   187  // IsResolved returns true if the stored state in the resolve is fully
   188  // resolved. In this case the target output can be forgotten.
   189  //
   190  // NOTE: Part of the ContractResolver interface.
   191  func (c *anchorResolver) IsResolved() bool {
   192  	return c.resolved
   193  }
   194  
   195  // SupplementState allows the user of a ContractResolver to supplement it with
   196  // state required for the proper resolution of a contract.
   197  //
   198  // NOTE: Part of the ContractResolver interface.
   199  func (c *anchorResolver) SupplementState(_ *channeldb.OpenChannel) {
   200  }
   201  
   202  // report returns a report on the resolution state of the contract.
   203  func (c *anchorResolver) report() *ContractReport {
   204  	c.reportLock.Lock()
   205  	defer c.reportLock.Unlock()
   206  
   207  	reportCopy := c.currentReport
   208  	return &reportCopy
   209  }
   210  
   211  func (c *anchorResolver) Encode(w io.Writer) error {
   212  	return errors.New("serialization not supported")
   213  }
   214  
   215  // A compile time assertion to ensure anchorResolver meets the
   216  // ContractResolver interface.
   217  var _ ContractResolver = (*anchorResolver)(nil)