gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/watchdog.go (about)

     1  package contractor
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"gitlab.com/NebulousLabs/errors"
     8  
     9  	"gitlab.com/SkynetLabs/skyd/skymodules"
    10  	"go.sia.tech/siad/crypto"
    11  	"go.sia.tech/siad/modules"
    12  	"go.sia.tech/siad/types"
    13  )
    14  
    15  // Key Assumptions:
    16  //
    17  // Contracts are removed from watchdog state after their storage proof window
    18  // has closed. The assumption here is that contracts are generally formed such
    19  // that by the time the window has closed, it is both extremely unlikely that
    20  // the original file contract transaction or any revisions will be re-orged out
    21  // and it is also irrelevant to the renter by that point in time because they
    22  // will already have started treating the contract as expired. We also note that
    23  // the watchdog does *not* check if storage proofs are re-orged out.  If a host
    24  // has ever submitted a valid storage proof, then from the renter's point of
    25  // view they have fulfilled their obligation for the contract.
    26  //
    27  // TODOs:
    28  // - Perform action when storage proof is found and when missing at the end of
    29  //   the window.
    30  //
    31  // - When creating sweep transaction, add parent transactions if the renter's
    32  //   own dependencies are causing this to be triggered.
    33  
    34  type watchdog struct {
    35  	// contracts stores all contract metadata used by the watchdog for any
    36  	// contract it is watching. Any contract that is being watched must have data
    37  	// in here.
    38  	contracts map[types.FileContractID]*fileContractStatus
    39  
    40  	// archivedContracts are contracts that have expired and are stored for
    41  	// archival purposes.
    42  	archivedContracts map[types.FileContractID]skymodules.ContractWatchStatus
    43  
    44  	// outputDependencies maps Siacoin outputs to the file contracts that are
    45  	// dependent on them. When a contract is first submitted to the watchdog to be
    46  	// monitored, the outputDependencies created for that contract are the
    47  	// confirmed Siacoin outputs used to create the file contract transaction set.
    48  	// The inverse mapping can be created on demand for any given file contract
    49  	// using the corresponding (updated)formationTxnSet. This map is used to check
    50  	// for double-spends on inputs used to form a contract.
    51  	outputDependencies map[types.SiacoinOutputID]map[types.FileContractID]struct{}
    52  
    53  	// The watchdog uses the renewWindow to compute the first blockheight (start
    54  	// of storage proof window minus renewWindow) at which the watchdog will
    55  	// broadcast the most recent revision if it hasn't seen it on chain yet.
    56  	renewWindow types.BlockHeight
    57  	blockHeight types.BlockHeight
    58  
    59  	staticContractor *Contractor
    60  	staticDeps       modules.Dependencies
    61  	staticTPool      transactionPool
    62  
    63  	mu sync.Mutex
    64  }
    65  
    66  // fileContractStatus holds all the metadata the watchdog needs to monitor a file
    67  // contract.
    68  type fileContractStatus struct {
    69  	// formationSweepHeight is the blockheight by which the watchdog expects to
    70  	// find the contract on-chain. Up until this height, if contract is not yet
    71  	// found the watchdog will rebbroadcast the formationTxnSet. After this height
    72  	// the watchdog will attempt to sweep its inputs and abandon this contract
    73  	formationSweepHeight types.BlockHeight
    74  	contractFound        bool
    75  	revisionFound        uint64            // store the revision number found
    76  	storageProofFound    types.BlockHeight // store the blockheight at which the proof was found.
    77  
    78  	// While watching for contract formation, the watchdog may periodically
    79  	// rebroadcast the initial file contract transaction and unconfirmed parent
    80  	// transactions. Any transactions in the original txn set that have been found
    81  	// on-chain are removed from this set. If a Siacoin output that this file
    82  	// contract depends on is re-orged out, then the transaction that creates that
    83  	// output is added to the set.
    84  	formationTxnSet []types.Transaction
    85  
    86  	// parentOutputs stores SiacoinOutputID of outputs which this file contract is
    87  	// dependent on, i.e. the parent outputs of the formationTxnSet. It is
    88  	// initialized with the parent outputs from the formationTxnSet but may grow
    89  	// and shrink as transactions are added or pruned from the formationTxnSet.
    90  	parentOutputs map[types.SiacoinOutputID]struct{}
    91  
    92  	// In case its been too long since the contract was supposed to form and the
    93  	// initial set has yet to appear on-chain, the watchdog is also prepared to
    94  	// double spend the inputs used by the contractor to create the contract with
    95  	// a higher fee-rate if necessary. It does so by extending the sweepTxn.
    96  	sweepTxn     types.Transaction
    97  	sweepParents []types.Transaction
    98  
    99  	// Store the storage proof window start and end heights.
   100  	windowStart types.BlockHeight
   101  	windowEnd   types.BlockHeight
   102  }
   103  
   104  // monitorContractArgs defines the arguments passed to callMonitorContract.
   105  type monitorContractArgs struct {
   106  	// recovered indicates that the contract has been recovered, and that it
   107  	// doesn't need to be monitored for formation.
   108  	recovered bool
   109  
   110  	fcID            types.FileContractID
   111  	revisionTxn     types.Transaction
   112  	formationTxnSet []types.Transaction
   113  	sweepTxn        types.Transaction
   114  	sweepParents    []types.Transaction
   115  	blockHeight     types.BlockHeight
   116  }
   117  
   118  // newWatchdog creates a new watchdog.
   119  func newWatchdog(contractor *Contractor) *watchdog {
   120  	renewWindow := contractor.Allowance().RenewWindow
   121  	return &watchdog{
   122  		contracts:          make(map[types.FileContractID]*fileContractStatus),
   123  		archivedContracts:  make(map[types.FileContractID]skymodules.ContractWatchStatus),
   124  		outputDependencies: make(map[types.SiacoinOutputID]map[types.FileContractID]struct{}),
   125  
   126  		renewWindow: renewWindow,
   127  		blockHeight: contractor.blockHeight,
   128  
   129  		staticTPool:      contractor.staticTPool,
   130  		staticDeps:       contractor.staticDeps,
   131  		staticContractor: contractor,
   132  	}
   133  }
   134  
   135  // ContractStatus returns the status of a contract in the watchdog.
   136  func (c *Contractor) ContractStatus(fcID types.FileContractID) (skymodules.ContractWatchStatus, bool) {
   137  	if err := c.staticTG.Add(); err != nil {
   138  		return skymodules.ContractWatchStatus{}, false
   139  	}
   140  	defer c.staticTG.Done()
   141  	return c.staticWatchdog.managedContractStatus(fcID)
   142  }
   143  
   144  // callAllowanceUpdated informs the watchdog of an allowance change.
   145  func (w *watchdog) callAllowanceUpdated(a skymodules.Allowance) {
   146  	w.mu.Lock()
   147  	defer w.mu.Unlock()
   148  
   149  	// Set the new renewWindow.
   150  	w.renewWindow = a.RenewWindow
   151  }
   152  
   153  // callMonitorContract tells the watchdog to monitor the blockchain for data
   154  // relevant to the given contract.
   155  func (w *watchdog) callMonitorContract(args monitorContractArgs) error {
   156  	w.mu.Lock()
   157  	defer w.mu.Unlock()
   158  	w.staticContractor.staticLog.Debugf("callMonitorContract for contract: %v at height %v (Watchdog height: %v)", args.fcID, args.blockHeight, w.blockHeight)
   159  
   160  	if _, ok := w.contracts[args.fcID]; ok {
   161  		w.staticContractor.staticLog.Println("watchdog asked to watch contract it already knowns: ", args.fcID)
   162  		return errAlreadyWatchingContract
   163  	}
   164  
   165  	if len(args.revisionTxn.FileContractRevisions) == 0 {
   166  		w.staticContractor.staticLog.Println("No revisions in revisiontxn", args)
   167  		return errors.New("no revision in monitor contract args")
   168  	}
   169  
   170  	// Sanity check on non-recovery monitoring.
   171  	if !args.recovered {
   172  		saneInputs := len(args.formationTxnSet) != 0
   173  		saneInputs = saneInputs && len(args.sweepTxn.SiacoinInputs) != 0
   174  		saneInputs = saneInputs && args.blockHeight != 0
   175  		if !saneInputs {
   176  			w.staticContractor.staticLog.Critical("Bad args given for contract: ", args)
   177  			return errors.New("bad args for non recovered contract")
   178  		}
   179  	}
   180  
   181  	fileContractStatus := &fileContractStatus{
   182  		// this height is meaningless for a recovered contract, but will be set to
   183  		// something reasonable if the formation transaction is reorged out.
   184  		formationSweepHeight: args.blockHeight + waitTime,
   185  		formationTxnSet:      args.formationTxnSet,
   186  		contractFound:        args.recovered,
   187  		parentOutputs:        make(map[types.SiacoinOutputID]struct{}),
   188  		sweepTxn:             args.sweepTxn,
   189  		sweepParents:         args.sweepParents,
   190  		windowStart:          args.revisionTxn.FileContractRevisions[0].NewWindowStart,
   191  		windowEnd:            args.revisionTxn.FileContractRevisions[0].NewWindowEnd,
   192  	}
   193  	w.contracts[args.fcID] = fileContractStatus
   194  
   195  	// Watch the parent outputs of this set.
   196  	outputDependencies := getParentOutputIDs(args.formationTxnSet)
   197  	for _, oid := range outputDependencies {
   198  		w.addOutputDependency(oid, args.fcID)
   199  	}
   200  
   201  	w.staticContractor.staticLog.Debugln("Monitoring contract: ", args.fcID)
   202  	return nil
   203  }
   204  
   205  // callScanConsensusChange scans applied and reverted blocks, updating the
   206  // watchdog's state with all information relevant to monitored contracts.
   207  func (w *watchdog) callScanConsensusChange(cc modules.ConsensusChange) {
   208  	w.mu.Lock()
   209  	defer w.mu.Unlock()
   210  	for _, block := range cc.RevertedBlocks {
   211  		if block.ID() != types.GenesisID {
   212  			w.blockHeight--
   213  		}
   214  		w.scanRevertedBlock(block)
   215  	}
   216  
   217  	for _, block := range cc.AppliedBlocks {
   218  		if block.ID() != types.GenesisID {
   219  			w.blockHeight++
   220  		}
   221  		w.scanAppliedBlock(block)
   222  	}
   223  }
   224  
   225  // sendTxnSet broadcasts a transaction set and logs errors that are not
   226  // duplicate transaction errors. (This is because the watchdog may be
   227  // overzealous in sending out transactions).
   228  func (w *watchdog) sendTxnSet(txnSet []types.Transaction, reason string) {
   229  	w.staticContractor.staticLog.Debugln("Sending txn set to tpool", reason)
   230  	if w.staticDeps.Disrupt("DisableWatchdogBroadcast") {
   231  		w.staticContractor.staticLog.Debugln("(Watchdog Broadcast Disrupted)")
   232  		return
   233  	}
   234  
   235  	// Send the transaction set in a go-routine to avoid deadlock when this
   236  	// sendTxnSet is called within ProcessConsensusChange.
   237  	go func() {
   238  		err := w.staticContractor.staticTG.Add()
   239  		if err != nil {
   240  			return
   241  		}
   242  		defer w.staticContractor.staticTG.Done()
   243  
   244  		err = w.staticTPool.AcceptTransactionSet(txnSet)
   245  		if err != nil && !errors.Contains(err, modules.ErrDuplicateTransactionSet) {
   246  			w.staticContractor.staticLog.Println("watchdog send transaction error: "+reason, err)
   247  		}
   248  	}()
   249  }
   250  
   251  // archiveContract archives the file contract. Include a non-zero double spend
   252  // height if the reason for archival is that the contract was double-spent.
   253  func (w *watchdog) archiveContract(fcID types.FileContractID, doubleSpendHeight types.BlockHeight) {
   254  	w.staticContractor.staticLog.Println("Archiving contract: ", fcID)
   255  	contractData, ok := w.contracts[fcID]
   256  	if !ok {
   257  		return
   258  	}
   259  	for oid := range contractData.parentOutputs {
   260  		w.removeOutputDependency(oid, fcID)
   261  	}
   262  	w.archivedContracts[fcID] = skymodules.ContractWatchStatus{
   263  		Archived:                  true,
   264  		FormationSweepHeight:      contractData.formationSweepHeight,
   265  		ContractFound:             contractData.contractFound,
   266  		LatestRevisionFound:       contractData.revisionFound,
   267  		StorageProofFoundAtHeight: contractData.storageProofFound,
   268  		DoubleSpendHeight:         doubleSpendHeight,
   269  		WindowStart:               contractData.windowStart,
   270  		WindowEnd:                 contractData.windowEnd,
   271  	}
   272  	delete(w.contracts, fcID)
   273  }
   274  
   275  // addOutputDependency marks the contract with fcID as dependent on this Siacoin
   276  // output.
   277  func (w *watchdog) addOutputDependency(outputID types.SiacoinOutputID, fcID types.FileContractID) {
   278  	dependentFCs, ok := w.outputDependencies[outputID]
   279  	if !ok {
   280  		dependentFCs = make(map[types.FileContractID]struct{})
   281  	}
   282  	dependentFCs[fcID] = struct{}{}
   283  	w.outputDependencies[outputID] = dependentFCs
   284  
   285  	// Add the dependencies into the contract metadata also.
   286  	contractData := w.contracts[fcID]
   287  	contractData.parentOutputs[outputID] = struct{}{}
   288  }
   289  
   290  // removeOutputDependency removes the given SiacoinOutput from the dependencies
   291  // of this file contract.
   292  func (w *watchdog) removeOutputDependency(outputID types.SiacoinOutputID, fcID types.FileContractID) {
   293  	dependentFCs, ok := w.outputDependencies[outputID]
   294  	if !ok {
   295  		w.staticContractor.staticLog.Debugf("unable to remove output dependency: outputID not found in outputDependencies: outputID: %s", crypto.Hash(outputID).String())
   296  		return
   297  	}
   298  
   299  	_, foundContract := dependentFCs[fcID]
   300  	if !foundContract {
   301  		w.staticContractor.staticLog.Debugf("unable to remove output dependency: FileContract not marked in outputDependencies: fcID: %s, outputID: %s", crypto.Hash(fcID).String(), crypto.Hash(outputID).String())
   302  		return
   303  	}
   304  
   305  	if len(dependentFCs) == 1 {
   306  		// If this is the only file contract dependent on that output, delete the
   307  		// whole set.
   308  		delete(w.outputDependencies, outputID)
   309  	} else {
   310  		delete(dependentFCs, fcID)
   311  	}
   312  
   313  	if contractData, ok := w.contracts[fcID]; ok {
   314  		delete(contractData.parentOutputs, outputID)
   315  	}
   316  }
   317  
   318  // getParentOutputIDs returns the IDs of the parent SiacoinOutputs used in the
   319  // transaction set. That is, it returns the SiacoinOutputs that this transaction
   320  // set is dependent on.
   321  func getParentOutputIDs(txnSet []types.Transaction) []types.SiacoinOutputID {
   322  	// Create a map of created and spent outputs. The parent outputs are those
   323  	// that are spent but not created in this set.
   324  	createdOutputs := make(map[types.SiacoinOutputID]bool)
   325  	spentOutputs := make(map[types.SiacoinOutputID]bool)
   326  	for _, txn := range txnSet {
   327  		for _, scInput := range txn.SiacoinInputs {
   328  			spentOutputs[scInput.ParentID] = true
   329  		}
   330  		for i := range txn.SiacoinOutputs {
   331  			createdOutputs[txn.SiacoinOutputID(uint64(i))] = true
   332  		}
   333  	}
   334  
   335  	// Remove all intermediary outputs that were created in the set from the set
   336  	// of spentOutputs.
   337  	parentOutputs := make([]types.SiacoinOutputID, 0)
   338  	for id := range spentOutputs {
   339  		if !createdOutputs[id] {
   340  			parentOutputs = append(parentOutputs, id)
   341  		}
   342  	}
   343  
   344  	return parentOutputs
   345  }
   346  
   347  // removeTxnFromSet is a helper function used to create a standalone-valid
   348  // transaction set by removing confirmed transactions from a transaction set. If
   349  // the transaction meant to be removed is not present in the set and error is
   350  // returned, otherwise a new transaction set is returned that no longer contains
   351  // that transaction.
   352  func removeTxnFromSet(txn types.Transaction, txnSet []types.Transaction) ([]types.Transaction, error) {
   353  	txnID := txn.ID()
   354  
   355  	for i, txnFromSet := range txnSet {
   356  		if txnFromSet.ID() == txnID {
   357  			// Create the new set without the txn.
   358  			newSet := append(txnSet[:i], txnSet[i+1:]...)
   359  			return newSet, nil
   360  		}
   361  	}
   362  
   363  	// Since this function is called when some parent inputs of the txnSet are
   364  	// spent, this error indicates that the txn given double-spends a txn from
   365  	// the set.
   366  	return nil, errTxnNotInSet
   367  }
   368  
   369  // scanAppliedBlock updates the watchdog's state with data from a newly
   370  // connected block. It checks for contracts, revisions, and proofs of monitored
   371  // contracts and also for double-spends of any outputs which monitored contracts
   372  // depend on.
   373  func (w *watchdog) scanAppliedBlock(block types.Block) {
   374  	w.staticContractor.staticLog.Debugln("Watchdog scanning applied block at height: ", w.blockHeight)
   375  
   376  	for _, txn := range block.Transactions {
   377  		for i := range txn.FileContracts {
   378  			fcID := txn.FileContractID(uint64(i))
   379  			if contractData, ok := w.contracts[fcID]; ok {
   380  				contractData.contractFound = true
   381  				w.staticContractor.staticLog.Debugln("Found contract: ", fcID)
   382  			}
   383  		}
   384  
   385  		for _, rev := range txn.FileContractRevisions {
   386  			if contractData, ok := w.contracts[rev.ParentID]; ok {
   387  				contractData.revisionFound = rev.NewRevisionNumber
   388  				w.staticContractor.staticLog.Debugln("Found revision for: ", rev.ParentID, rev.NewRevisionNumber)
   389  			}
   390  		}
   391  
   392  		for _, storageProof := range txn.StorageProofs {
   393  			if contractData, ok := w.contracts[storageProof.ParentID]; ok {
   394  				contractData.storageProofFound = w.blockHeight
   395  				w.staticContractor.staticLog.Debugln("Found storage proof: ", storageProof.ParentID)
   396  			}
   397  		}
   398  
   399  		// Check the transaction for spends of any inputs a monitored file contract
   400  		// depends on.
   401  		w.findDependencySpends(txn)
   402  	}
   403  }
   404  
   405  // findDependencySpends checks the transactions from a newly applied block to
   406  // see if it spends outputs which monitored contracts are dependent on. If so,
   407  // the function prunes the formationTxnSet for that contract and updates its
   408  // dependencies.
   409  func (w *watchdog) findDependencySpends(txn types.Transaction) {
   410  	// This transaction could be double-spending inputs used across multiple
   411  	// file contracts. Keep track of those here.
   412  	inputsSpent := make(map[types.FileContractID]struct{})
   413  	spendsMonitoredOutput := false
   414  	for _, scInput := range txn.SiacoinInputs {
   415  		// Check if the output spent here is a parent of any contract being
   416  		// monitored.
   417  		fcIDs, watching := w.outputDependencies[scInput.ParentID]
   418  		if !watching {
   419  			continue
   420  		}
   421  
   422  		for fcID := range fcIDs {
   423  			// If we found the contract already, then this output must be spent in the
   424  			// formation txn set. Otherwise we must check if this transaction
   425  			// double-spends any inputs for the formation transaction set.
   426  			_, ok := w.contracts[fcID]
   427  			if !ok {
   428  				w.staticContractor.staticLog.Critical("Found dependency on un-monitored formation")
   429  				continue
   430  			}
   431  			spendsMonitoredOutput = true
   432  			inputsSpent[fcID] = struct{}{}
   433  		}
   434  	}
   435  
   436  	if !spendsMonitoredOutput {
   437  		return
   438  	}
   439  
   440  	// Try removing this transaction from the formation transaction set, and
   441  	// check if the pruned formation transaction set is still considered valid.
   442  	for fcID := range inputsSpent {
   443  		// Some transactions from this contract's formation set may have already
   444  		// been pruned. If so, use the most recent set.
   445  		contractData := w.contracts[fcID]
   446  		txnSet := contractData.formationTxnSet
   447  
   448  		// Try removing this transaction from the set.
   449  		prunedFormationTxnSet, err := removeTxnFromSet(txn, txnSet)
   450  		if err != nil {
   451  			w.staticContractor.staticLog.Println("Error removing txn from set, inputs were double-spent:", err, fcID, len(txnSet), txn.ID())
   452  
   453  			//  Signal to the contractor that this contract's inputs were
   454  			//  double-spent and that it should be removed.
   455  			w.archiveContract(fcID, w.blockHeight)
   456  			go w.staticContractor.callNotifyDoubleSpend(fcID, w.blockHeight)
   457  			continue
   458  		}
   459  
   460  		w.staticContractor.staticLog.Debugln("Removed transaction from set for: ", fcID, len(prunedFormationTxnSet), txn.ID())
   461  		contractData.formationTxnSet = prunedFormationTxnSet
   462  
   463  		// Get the new set of parent output IDs.
   464  		newDepOutputs := getParentOutputIDs(prunedFormationTxnSet)
   465  
   466  		// Remove outputs no longer needed as dependencies
   467  		for oid := range contractData.parentOutputs {
   468  			isStillADependency := false
   469  			for _, newDep := range newDepOutputs {
   470  				if oid == newDep {
   471  					isStillADependency = true
   472  					break
   473  				}
   474  			}
   475  			if !isStillADependency {
   476  				w.removeOutputDependency(oid, fcID)
   477  			}
   478  		}
   479  
   480  		// Add any new dependencies.
   481  		for _, oid := range newDepOutputs {
   482  			if _, ok := contractData.parentOutputs[oid]; !ok {
   483  				w.addOutputDependency(oid, fcID)
   484  			}
   485  		}
   486  	}
   487  }
   488  
   489  // scanRevertedBlock updates the watchdog's state with data from a newly
   490  // reverted block. It checks for the removal of contracts, revisions, and proofs
   491  // of monitored contracts and also for the creation of any new dependencies for
   492  // monitored formation transaction sets.
   493  func (w *watchdog) scanRevertedBlock(block types.Block) {
   494  	w.staticContractor.staticLog.Debugln("Watchdog scanning reverted block at height: ", w.blockHeight)
   495  
   496  	outputsCreatedInBlock := make(map[types.SiacoinOutputID]*types.Transaction)
   497  	for i := 0; i < len(block.Transactions); i++ {
   498  		txn := &block.Transactions[i]
   499  		for i := range txn.SiacoinOutputs {
   500  			outputsCreatedInBlock[txn.SiacoinOutputID(uint64(i))] = txn
   501  		}
   502  
   503  		for i := range txn.FileContracts {
   504  			fcID := txn.FileContractID(uint64(i))
   505  			// After a blockchain reorg, it's possible that a contract that used to be on
   506  			// the active chain is no longer in the new active chain. To make sure all
   507  			// active contracts are actually committed to on-chain, the watchdog keeps track
   508  			// of any contracts removed during a reorg. If they have not re-appeared and the
   509  			// contractor is synced then the watchdog must re-broadcast the file contract's
   510  			// transaction.
   511  			contractData, ok := w.contracts[fcID]
   512  			if !ok {
   513  				continue
   514  			}
   515  
   516  			w.staticContractor.staticLog.Println("Contract formation txn reverted: ", fcID)
   517  			contractData.contractFound = false
   518  
   519  			// Set watchheight to max(current watch height,  current height + leeway)
   520  			if contractData.formationSweepHeight < w.blockHeight+reorgLeeway {
   521  				contractData.formationSweepHeight = w.blockHeight + reorgLeeway
   522  			}
   523  
   524  			// Sanity check: if the contract was previously confirmed, it should have
   525  			// been removed from the formationTxnSet.
   526  			if len(contractData.formationTxnSet) != 0 {
   527  				w.staticContractor.staticLog.Critical("found reverted contract with non-empty formationTxnSet in watchdog", fcID)
   528  			}
   529  
   530  			// Re-add the file contract transaction to the formationTxnSet.
   531  			contractData.formationTxnSet = []types.Transaction{*txn}
   532  			outputDependencies := getParentOutputIDs(contractData.formationTxnSet)
   533  			for _, oid := range outputDependencies {
   534  				w.addOutputDependency(oid, fcID)
   535  			}
   536  		}
   537  
   538  		for _, rev := range txn.FileContractRevisions {
   539  			if contractData, ok := w.contracts[rev.ParentID]; ok {
   540  				w.staticContractor.staticLog.Debugln("Revision for monitored contract reverted: ", rev.ParentID, rev.NewRevisionNumber)
   541  				contractData.revisionFound = 0 // There are no zero revisions.
   542  			}
   543  		}
   544  
   545  		for _, storageProof := range txn.StorageProofs {
   546  			if contractData, ok := w.contracts[storageProof.ParentID]; ok {
   547  				w.staticContractor.staticLog.Debugln("Storage proof for monitored contract reverted: ", storageProof.ParentID)
   548  				contractData.storageProofFound = 0
   549  			}
   550  		}
   551  	}
   552  	w.updateDependenciesFromRevertedBlock(outputsCreatedInBlock)
   553  }
   554  
   555  // updateDependenciesFromRevertedBlock checks all created outputs in a reverted
   556  // block to see if any monitored formation transactions are dependent on them.
   557  // If so, the watchdog adds the reverted transaction creating that output as a
   558  // dependency for that file contract.
   559  func (w *watchdog) updateDependenciesFromRevertedBlock(createdOutputs map[types.SiacoinOutputID]*types.Transaction) {
   560  	// Create a queue of outputs to check.
   561  	outputQueue := make([]types.SiacoinOutputID, 0)
   562  	outputsInQueue := make(map[types.SiacoinOutputID]struct{})
   563  
   564  	// Helper function that adds outputs spent in this transaction to the queue,
   565  	// if they are not already in it.
   566  	addParentOutputsToQueue := func(txn *types.Transaction) {
   567  		for _, scInput := range txn.SiacoinInputs {
   568  			_, outputCreatedInBlock := createdOutputs[scInput.ParentID]
   569  			_, inQueue := outputsInQueue[scInput.ParentID]
   570  			if !inQueue && outputCreatedInBlock {
   571  				outputQueue = append(outputQueue, scInput.ParentID)
   572  				outputsInQueue[scInput.ParentID] = struct{}{}
   573  			}
   574  		}
   575  	}
   576  
   577  	// Populate the queue first by checking all outputs once.
   578  	for outputID, txn := range createdOutputs {
   579  		// Check if any file contracts being monitored by the watchdog have this
   580  		// output as a dependency.
   581  		dependentFCs, watching := w.outputDependencies[outputID]
   582  		if !watching {
   583  			continue
   584  		}
   585  		// Add the new dependencies to file contracts dependent on this output.
   586  		for fcID := range dependentFCs {
   587  			w.staticContractor.staticLog.Debugln("Adding dependency to file contract:", fcID, txn.ID())
   588  			w.addDependencyToContractFormationSet(fcID, *txn)
   589  		}
   590  		// Queue up the parent outputs so that we can check if they are adding new
   591  		// dependencies as well.
   592  		addParentOutputsToQueue(txn)
   593  	}
   594  
   595  	// Go through all the new dependencies in the queue.
   596  	var outputID types.SiacoinOutputID
   597  	for len(outputQueue) > 0 {
   598  		// Pop on output ID off the queue.
   599  		outputID, outputQueue = outputQueue[0], outputQueue[1:]
   600  		txn := createdOutputs[outputID]
   601  
   602  		// Check if any file contracts being monitored by the watchdog have this
   603  		// output as a dependency.
   604  		dependentFCs, watching := w.outputDependencies[outputID]
   605  		if !watching {
   606  			continue
   607  		}
   608  		// Add the new dependencies to file contracts dependent on this output.
   609  		for fcID := range dependentFCs {
   610  			w.staticContractor.staticLog.Debugln("Adding dependency to file contract:", fcID, txn.ID())
   611  			w.addDependencyToContractFormationSet(fcID, *txn)
   612  		}
   613  		// Queue up the parent outputs so that we can check if they are adding new
   614  		// dependencies as well.
   615  		addParentOutputsToQueue(txn)
   616  
   617  		// Remove from outputsInQueue map at end of function, in order to not re-add
   618  		// the same output.
   619  		delete(outputsInQueue, outputID)
   620  	}
   621  }
   622  
   623  // addDependencyToContractFormationSet adds a tranasaction to a contract's
   624  // formationTransactionSet, if it is not already in that set. It also adds
   625  // the outputs spent in that transaction as dependencies for this file contract.
   626  func (w *watchdog) addDependencyToContractFormationSet(fcID types.FileContractID, txn types.Transaction) {
   627  	contractData := w.contracts[fcID]
   628  	txnSet := contractData.formationTxnSet
   629  
   630  	txnID := txn.ID()
   631  	for _, existingTxn := range contractData.formationTxnSet {
   632  		// Don't add duplicate transactions.
   633  		if txnID == existingTxn.ID() {
   634  			return
   635  		}
   636  	}
   637  
   638  	// Add this transaction as a new dependency.
   639  	// NOTE: Dependencies must be prepended to maintain correct ordering.
   640  	txnSet = append([]types.Transaction{txn}, txnSet...)
   641  	contractData.formationTxnSet = txnSet
   642  
   643  	// Add outputs as dependencies to this file contract.
   644  	for _, scInput := range txn.SiacoinInputs {
   645  		w.addOutputDependency(scInput.ParentID, fcID)
   646  	}
   647  }
   648  
   649  // callCheckContracts checks if the watchdog needs to take any actions for
   650  // any contracts its watching at this blockHeight.  For newly formed contracts,
   651  // it checks if a contract has been seen on-chain yet, if not the watchdog will
   652  // re-broadcast the initial transaction. If enough time has elapsed the watchdog
   653  // will double-spend the inputs used to create that file contract.
   654  //
   655  // The watchdog also checks if the latest revision for a file contract has been
   656  // posted yet. If not, the watchdog will also re-broadcast that transaction.
   657  //
   658  // Finally, the watchdog checks if hosts' storage proofs made it on chain within
   659  // their expiration window, and notifies the contractor of the storage proof
   660  // status.
   661  func (w *watchdog) callCheckContracts() {
   662  	w.mu.Lock()
   663  	defer w.mu.Unlock()
   664  	w.staticContractor.staticLog.Debugln("Watchdog checking contracts at height:", w.blockHeight)
   665  
   666  	for fcID, contractData := range w.contracts {
   667  		if !contractData.contractFound {
   668  			w.checkUnconfirmedContract(fcID, contractData)
   669  		}
   670  
   671  		if (w.blockHeight >= contractData.windowStart-w.renewWindow) && (contractData.revisionFound != 0) {
   672  			// Check if the most recent revision has appeared on-chain. If not send it
   673  			// ourselves. Called in a go-routine because the contractor may be in
   674  			// maintenance which can cause a deadlock because this function Acquires a
   675  			// lock using the contractset.
   676  			w.staticContractor.staticLog.Debugln("Checking revision for monitored contract: ", fcID)
   677  			go func(fcid types.FileContractID, bh types.BlockHeight) {
   678  				err := w.staticContractor.staticTG.Add()
   679  				if err != nil {
   680  					return
   681  				}
   682  				defer w.staticContractor.staticTG.Done()
   683  				w.threadedCheckMonitorRevision(fcid, bh)
   684  			}(fcID, w.blockHeight)
   685  		}
   686  
   687  		if w.blockHeight >= contractData.windowEnd {
   688  			if contractData.storageProofFound == 0 {
   689  				// TODO: penalize host / send signal back to watchee
   690  				w.staticContractor.staticLog.Debugln("didn't find proof", fcID)
   691  			} else {
   692  				// TODO: ++ host / send signal back to watchee
   693  				w.staticContractor.staticLog.Debugln("did find proof", fcID)
   694  			}
   695  			w.archiveContract(fcID, 0)
   696  		}
   697  	}
   698  }
   699  
   700  // checkUnconfirmedContract re-broadcasts the file contract formation
   701  // transaction or sweeps the inputs used by the renter, depending on whether or
   702  // not the transaction set has too many added dependencies or if the
   703  // formationSweepHeight has been reached.
   704  func (w *watchdog) checkUnconfirmedContract(fcID types.FileContractID, contractData *fileContractStatus) {
   705  	// Check that the size of the formationTxnSet has not gone beyond the
   706  	// standardness limit. If it has, then we won't be able to propagate it
   707  	// anymore.
   708  	var setSize int
   709  	for _, txn := range contractData.formationTxnSet {
   710  		setSize += txn.MarshalSiaSize()
   711  	}
   712  	if setSize > modules.TransactionSetSizeLimit {
   713  		w.staticContractor.staticLog.Println("UpdatedFormationTxnSet beyond set size limit", fcID)
   714  	}
   715  
   716  	if (w.blockHeight >= contractData.formationSweepHeight) || (setSize > modules.TransactionSetSizeLimit) {
   717  		w.staticContractor.staticLog.Println("Sweeping inputs: ", w.blockHeight, contractData.formationSweepHeight)
   718  		// TODO: Add parent transactions if the renter's own dependencies are
   719  		// causing this to be triggered.
   720  		w.sweepContractInputs(fcID, contractData)
   721  	} else {
   722  		// Try to broadcast the transaction set again.
   723  		debugStr := fmt.Sprintf("sending formation txn for contract with id: %s at h=%d wh=%d", fcID.String(), w.blockHeight, contractData.formationSweepHeight)
   724  		w.staticContractor.staticLog.Debugln(debugStr)
   725  		w.sendTxnSet(contractData.formationTxnSet, debugStr)
   726  	}
   727  }
   728  
   729  // threadedCheckMonitorRevision checks if the given FileContract has it latest
   730  // revision posted on-chain. If not, the watchdog broadcasts the latest revision
   731  // transaction itself.
   732  func (w *watchdog) threadedCheckMonitorRevision(fcID types.FileContractID, height types.BlockHeight) {
   733  	// Get the highest revision number seen by the watchdog for this FC.
   734  	var revNumFound uint64
   735  	w.mu.Lock()
   736  	if contractData, ok := w.contracts[fcID]; ok {
   737  		revNumFound = contractData.revisionFound
   738  	}
   739  	w.mu.Unlock()
   740  
   741  	// Get the last revision transaction from the contractset / oldcontracts.
   742  	var lastRevisionTxn types.Transaction
   743  	contract, ok := w.staticContractor.staticContracts.Acquire(fcID)
   744  	if ok {
   745  		lastRevisionTxn = contract.Metadata().Transaction
   746  		w.staticContractor.staticContracts.Return(contract)
   747  	} else {
   748  		w.staticContractor.staticLog.Debugln("Unable to Acquire monitored contract from contractset", fcID)
   749  		// Try old contracts. If the contract was renewed already it won't be in the
   750  		// contractset.
   751  		w.staticContractor.mu.RLock()
   752  		contract, ok := w.staticContractor.oldContracts[fcID]
   753  		if !ok {
   754  			w.staticContractor.staticLog.Debugln("Unable to Acquire monitored contract from oldContracts", fcID)
   755  			w.staticContractor.mu.RUnlock()
   756  			return
   757  		}
   758  		lastRevisionTxn = contract.Transaction
   759  		w.staticContractor.mu.RUnlock()
   760  	}
   761  
   762  	lastRevNum := lastRevisionTxn.FileContractRevisions[0].NewRevisionNumber
   763  	if lastRevNum > revNumFound {
   764  		// NOTE: fee-bumping via CPFP (the watchdog will do this every block
   765  		// until it sees the revision or the window has closed.)
   766  		debugStr := fmt.Sprintf("sending revision txn for contract with id: %s revNum: %d", fcID.String(), lastRevNum)
   767  		w.staticContractor.staticLog.Debugln(debugStr)
   768  		w.mu.Lock()
   769  		w.sendTxnSet([]types.Transaction{lastRevisionTxn}, debugStr)
   770  		w.mu.Unlock()
   771  	}
   772  }
   773  
   774  // sweepContractInputs spends the inputs used initially by the contractor
   775  // for creating a file contract, and sends them to an address owned by
   776  // this wallet.  This is done only if a file contract has not appeared on-chain
   777  // in time.
   778  // TODO: this function fails if the wallet is locked. since an alert is already
   779  // broadcast to the user, it might be useful to retry a sweep once the wallet
   780  // is unlocked.
   781  func (w *watchdog) sweepContractInputs(fcID types.FileContractID, contractData *fileContractStatus) {
   782  	sweepBuilder, err := w.staticContractor.staticWallet.RegisterTransaction(contractData.sweepTxn, contractData.sweepParents)
   783  	if err != nil {
   784  		w.staticContractor.staticLog.Println("Unable to register sweep transaction")
   785  		return
   786  	}
   787  	markedInputs := sweepBuilder.MarkWalletInputs()
   788  	if !markedInputs {
   789  		w.staticContractor.staticLog.Println("sweepBuilder did not mark any owned inputs")
   790  	}
   791  
   792  	// Get the size of the transaction set so far for fee calculation.
   793  	setSize := contractData.sweepTxn.MarshalSiaSize()
   794  	for _, sweepParent := range contractData.sweepParents {
   795  		setSize += sweepParent.MarshalSiaSize()
   796  	}
   797  
   798  	// Estimate a transaction fee and add it to the txn.
   799  	_, maxFee := w.staticTPool.FeeEstimation()
   800  	txnFee := maxFee.Mul64(uint64(setSize)) // Estimated transaction size in bytes
   801  	sweepBuilder.AddMinerFee(txnFee)
   802  
   803  	txn, _ := sweepBuilder.View()
   804  	// There can be refund outputs, but the last output is the one that is used to
   805  	// sweep.
   806  	numOuts := len(txn.SiacoinOutputs)
   807  	if numOuts == 0 {
   808  		w.staticContractor.staticLog.Println("expected at least 1 output in sweepTxn", len(txn.SiacoinOutputs))
   809  		return
   810  	}
   811  	replacementOutput := types.SiacoinOutput{
   812  		Value:      txn.SiacoinOutputs[numOuts-1].Value.Sub(txnFee),
   813  		UnlockHash: txn.SiacoinOutputs[numOuts-1].UnlockHash,
   814  	}
   815  	err = sweepBuilder.ReplaceSiacoinOutput(uint64(numOuts-1), replacementOutput)
   816  	if err != nil {
   817  		w.staticContractor.staticLog.Println("error replacing output in sweep")
   818  		return
   819  	}
   820  
   821  	signedTxnSet, err := sweepBuilder.Sign(true)
   822  	if err != nil {
   823  		w.staticContractor.staticLog.Println("unable to sign sweep txn", fcID)
   824  		return
   825  	}
   826  
   827  	debugStr := fmt.Sprintf("SweepTxn for contract with id: %s", fcID.String())
   828  	w.sendTxnSet(signedTxnSet, debugStr)
   829  }
   830  
   831  // managedContractStatus returns the status of a contract in the watchdog if it
   832  // exists.
   833  func (w *watchdog) managedContractStatus(fcID types.FileContractID) (skymodules.ContractWatchStatus, bool) {
   834  	w.mu.Lock()
   835  	defer w.mu.Unlock()
   836  
   837  	contractStatus, ok := w.archivedContracts[fcID]
   838  	if ok {
   839  		return contractStatus, true
   840  	}
   841  
   842  	contractData, ok := w.contracts[fcID]
   843  	if !ok {
   844  		return skymodules.ContractWatchStatus{}, false
   845  	}
   846  
   847  	return skymodules.ContractWatchStatus{
   848  		Archived:                  false,
   849  		FormationSweepHeight:      contractData.formationSweepHeight,
   850  		ContractFound:             contractData.contractFound,
   851  		LatestRevisionFound:       contractData.revisionFound,
   852  		StorageProofFoundAtHeight: contractData.storageProofFound,
   853  		WindowStart:               contractData.windowStart,
   854  		WindowEnd:                 contractData.windowEnd,
   855  	}, true
   856  }
   857  
   858  // threadedSendMostRecentRevision sends the most recent revision transaction out.
   859  // Should be called whenever a contract is no longer going to be used.
   860  func (w *watchdog) threadedSendMostRecentRevision(metadata skymodules.RenterContract) {
   861  	if err := w.staticContractor.staticTG.Add(); err != nil {
   862  		return
   863  	}
   864  	defer w.staticContractor.staticTG.Done()
   865  	fcID := metadata.ID
   866  	lastRevisionTxn := metadata.Transaction
   867  	lastRevNum := lastRevisionTxn.FileContractRevisions[0].NewRevisionNumber
   868  
   869  	debugStr := fmt.Sprintf("sending most recent revision txn for contract with id: %s revNum: %d", fcID.String(), lastRevNum)
   870  	w.mu.Lock()
   871  	w.sendTxnSet([]types.Transaction{lastRevisionTxn}, debugStr)
   872  	w.mu.Unlock()
   873  }