gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/update.go (about)

     1  package host
     2  
     3  // TODO: Need to check that 'RevisionConfirmed' is sensitive to whether or not
     4  // it was the *most recent* revision that got confirmed.
     5  
     6  import (
     7  	"encoding/binary"
     8  	"encoding/json"
     9  
    10  	bolt "github.com/coreos/bbolt"
    11  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    12  	"gitlab.com/SiaPrime/SiaPrime/modules"
    13  	"gitlab.com/SiaPrime/SiaPrime/types"
    14  )
    15  
    16  // initRescan is a helper function of initConsensusSubscribe, and is called when
    17  // the host and the consensus set have become desynchronized. Desynchronization
    18  // typically happens if the user is replacing or altering the persistent files
    19  // in the consensus set or the host.
    20  func (h *Host) initRescan() error {
    21  	// Reset all of the variables that have relevance to the consensus set.
    22  	var allObligations []storageObligation
    23  	// Reset all of the consensus-relevant variables in the host.
    24  	h.blockHeight = 0
    25  
    26  	// Reset all of the storage obligations.
    27  	err := h.db.Update(func(tx *bolt.Tx) error {
    28  		bsu := tx.Bucket(bucketStorageObligations)
    29  		c := bsu.Cursor()
    30  		for k, soBytes := c.First(); soBytes != nil; k, soBytes = c.Next() {
    31  			var so storageObligation
    32  			err := json.Unmarshal(soBytes, &so)
    33  			if err != nil {
    34  				return err
    35  			}
    36  			so.OriginConfirmed = false
    37  			so.RevisionConfirmed = false
    38  			so.ProofConfirmed = false
    39  			allObligations = append(allObligations, so)
    40  			soBytes, err = json.Marshal(so)
    41  			if err != nil {
    42  				return err
    43  			}
    44  			err = bsu.Put(k, soBytes)
    45  			if err != nil {
    46  				return err
    47  			}
    48  		}
    49  		return nil
    50  	})
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	// Subscribe to the consensus set. This is a blocking call that will not
    56  	// return until the host has fully caught up to the current block.
    57  	//
    58  	// Convention dictates that the host should not make external calls while
    59  	// under lock, but this function happens at startup while blocking. Because
    60  	// it happens while blocking, and because there is no actual host lock held
    61  	// at this time, none of the host external functions are exposed, so it is
    62  	// save to make the exported call.
    63  	err = h.cs.ConsensusSetSubscribe(h, modules.ConsensusChangeBeginning, h.tg.StopChan())
    64  	if err != nil {
    65  		return err
    66  	}
    67  	h.tg.OnStop(func() {
    68  		h.cs.Unsubscribe(h)
    69  	})
    70  
    71  	// Re-queue all of the action items for the storage obligations.
    72  	for i, so := range allObligations {
    73  		soid := so.id()
    74  		err1 := h.queueActionItem(h.blockHeight+resubmissionTimeout, soid)
    75  		err2 := h.queueActionItem(so.expiration()-revisionSubmissionBuffer, soid)
    76  		err3 := h.queueActionItem(so.expiration()+resubmissionTimeout, soid)
    77  		err = composeErrors(err1, err2, err3)
    78  		if err != nil {
    79  			h.log.Println("dropping storage obligation during rescan, id", so.id())
    80  		}
    81  
    82  		// AcceptTransactionSet needs to be called in a goroutine to avoid a
    83  		// deadlock.
    84  		go func(i int) {
    85  			err := h.tpool.AcceptTransactionSet(allObligations[i].OriginTransactionSet)
    86  			if err != nil {
    87  				h.log.Println("Unable to submit contract transaction set after rescan:", soid)
    88  			}
    89  		}(i)
    90  	}
    91  	return nil
    92  }
    93  
    94  // initConsensusSubscription subscribes the host to the consensus set.
    95  func (h *Host) initConsensusSubscription() error {
    96  	// Convention dictates that the host should not make external calls while
    97  	// under lock, but this function happens at startup while blocking. Because
    98  	// it happens while blocking, and because there is no actual host lock held
    99  	// at this time, none of the host external functions are exposed, so it is
   100  	// save to make the exported call.
   101  	err := h.cs.ConsensusSetSubscribe(h, h.recentChange, h.tg.StopChan())
   102  	if err == modules.ErrInvalidConsensusChangeID {
   103  		// Perform a rescan of the consensus set if the change id that the host
   104  		// has is unrecognized by the consensus set. This will typically only
   105  		// happen if the user has been replacing files inside the Sia folder
   106  		// structure.
   107  		return h.initRescan()
   108  	}
   109  	if err != nil {
   110  		return err
   111  	}
   112  	h.tg.OnStop(func() {
   113  		h.cs.Unsubscribe(h)
   114  	})
   115  	return nil
   116  }
   117  
   118  // ProcessConsensusChange will be called by the consensus set every time there
   119  // is a change to the blockchain.
   120  func (h *Host) ProcessConsensusChange(cc modules.ConsensusChange) {
   121  	// Add is called at the beginning of the function, but Done cannot be
   122  	// called until all of the threads spawned by this function have also
   123  	// terminated. This function should not block while these threads wait to
   124  	// terminate.
   125  	h.mu.Lock()
   126  	defer h.mu.Unlock()
   127  
   128  	// Wrap the whole parsing into a single large database tx to keep things
   129  	// efficient.
   130  	var actionItems []types.FileContractID
   131  	err := h.db.Update(func(tx *bolt.Tx) error {
   132  		for _, block := range cc.RevertedBlocks {
   133  			// Look for transactions relevant to open storage obligations.
   134  			for _, txn := range block.Transactions {
   135  				// Check for file contracts.
   136  				if len(txn.FileContracts) > 0 {
   137  					for j := range txn.FileContracts {
   138  						fcid := txn.FileContractID(uint64(j))
   139  						so, err := getStorageObligation(tx, fcid)
   140  						if err != nil {
   141  							// The storage folder may not exist, or the disk
   142  							// may be having trouble. Either way, we ignore the
   143  							// problem. If the disk is having trouble, the user
   144  							// will have to perform a rescan.
   145  							continue
   146  						}
   147  						so.OriginConfirmed = false
   148  						err = putStorageObligation(tx, so)
   149  						if err != nil {
   150  							continue
   151  						}
   152  					}
   153  				}
   154  
   155  				// Check for file contract revisions.
   156  				if len(txn.FileContractRevisions) > 0 {
   157  					for _, fcr := range txn.FileContractRevisions {
   158  						so, err := getStorageObligation(tx, fcr.ParentID)
   159  						if err != nil {
   160  							// The storage folder may not exist, or the disk
   161  							// may be having trouble. Either way, we ignore the
   162  							// problem. If the disk is having trouble, the user
   163  							// will have to perform a rescan.
   164  							continue
   165  						}
   166  						so.RevisionConfirmed = false
   167  						err = putStorageObligation(tx, so)
   168  						if err != nil {
   169  							continue
   170  						}
   171  					}
   172  				}
   173  
   174  				// Check for storage proofs.
   175  				if len(txn.StorageProofs) > 0 {
   176  					for _, sp := range txn.StorageProofs {
   177  						// Check database for relevant storage proofs.
   178  						so, err := getStorageObligation(tx, sp.ParentID)
   179  						if err != nil {
   180  							// The storage folder may not exist, or the disk
   181  							// may be having trouble. Either way, we ignore the
   182  							// problem. If the disk is having trouble, the user
   183  							// will have to perform a rescan.
   184  							continue
   185  						}
   186  						so.ProofConfirmed = false
   187  						err = putStorageObligation(tx, so)
   188  						if err != nil {
   189  							continue
   190  						}
   191  					}
   192  				}
   193  			}
   194  
   195  			// Height is not adjusted when dealing with the genesis block because
   196  			// the default height is 0 and the genesis block height is 0. If
   197  			// removing the genesis block, height will already be at height 0 and
   198  			// should not update, lest an underflow occur.
   199  			if block.ID() != types.GenesisID {
   200  				h.blockHeight--
   201  			}
   202  		}
   203  		for _, block := range cc.AppliedBlocks {
   204  			// Look for transactions relevant to open storage obligations.
   205  			for _, txn := range block.Transactions {
   206  				// Check for file contracts.
   207  				if len(txn.FileContracts) > 0 {
   208  					for i := range txn.FileContracts {
   209  						fcid := txn.FileContractID(uint64(i))
   210  						so, err := getStorageObligation(tx, fcid)
   211  						if err != nil {
   212  							// The storage folder may not exist, or the disk
   213  							// may be having trouble. Either way, we ignore the
   214  							// problem. If the disk is having trouble, the user
   215  							// will have to perform a rescan.
   216  							continue
   217  						}
   218  						so.OriginConfirmed = true
   219  						err = putStorageObligation(tx, so)
   220  						if err != nil {
   221  							continue
   222  						}
   223  					}
   224  				}
   225  
   226  				// Check for file contract revisions.
   227  				if len(txn.FileContractRevisions) > 0 {
   228  					for _, fcr := range txn.FileContractRevisions {
   229  						so, err := getStorageObligation(tx, fcr.ParentID)
   230  						if err != nil {
   231  							// The storage folder may not exist, or the disk
   232  							// may be having trouble. Either way, we ignore the
   233  							// problem. If the disk is having trouble, the user
   234  							// will have to perform a rescan.
   235  							continue
   236  						}
   237  						so.RevisionConfirmed = true
   238  						err = putStorageObligation(tx, so)
   239  						if err != nil {
   240  							continue
   241  						}
   242  					}
   243  				}
   244  
   245  				// Check for storage proofs.
   246  				if len(txn.StorageProofs) > 0 {
   247  					for _, sp := range txn.StorageProofs {
   248  						so, err := getStorageObligation(tx, sp.ParentID)
   249  						if err != nil {
   250  							// The storage folder may not exist, or the disk
   251  							// may be having trouble. Either way, we ignore the
   252  							// problem. If the disk is having trouble, the user
   253  							// will have to perform a rescan.
   254  							continue
   255  						}
   256  						so.ProofConfirmed = true
   257  						err = putStorageObligation(tx, so)
   258  						if err != nil {
   259  							continue
   260  						}
   261  					}
   262  				}
   263  			}
   264  
   265  			// Height is not adjusted when dealing with the genesis block because
   266  			// the default height is 0 and the genesis block height is 0. If adding
   267  			// the genesis block, height will already be at height 0 and should not
   268  			// update.
   269  			if block.ID() != types.GenesisID {
   270  				h.blockHeight++
   271  			}
   272  
   273  			// Handle any action items relevant to the current height.
   274  			bai := tx.Bucket(bucketActionItems)
   275  			heightBytes := make([]byte, 8)
   276  			binary.BigEndian.PutUint64(heightBytes, uint64(h.blockHeight)) // BigEndian used so bolt will keep things sorted automatically.
   277  			existingItems := bai.Get(heightBytes)
   278  
   279  			// From the existing items, pull out a storage obligation.
   280  			knownActionItems := make(map[types.FileContractID]struct{})
   281  			obligationIDs := make([]types.FileContractID, len(existingItems)/crypto.HashSize)
   282  			for i := 0; i < len(existingItems); i += crypto.HashSize {
   283  				copy(obligationIDs[i/crypto.HashSize][:], existingItems[i:i+crypto.HashSize])
   284  			}
   285  			for _, soid := range obligationIDs {
   286  				_, exists := knownActionItems[soid]
   287  				if !exists {
   288  					actionItems = append(actionItems, soid)
   289  					knownActionItems[soid] = struct{}{}
   290  				}
   291  			}
   292  		}
   293  		return nil
   294  	})
   295  	if err != nil {
   296  		h.log.Println(err)
   297  	}
   298  	for i := range actionItems {
   299  		go h.threadedHandleActionItem(actionItems[i])
   300  	}
   301  
   302  	// Update the host's recent change pointer to point to the most recent
   303  	// change.
   304  	h.recentChange = cc.ID
   305  
   306  	// Save the host.
   307  	err = h.saveSync()
   308  	if err != nil {
   309  		h.log.Println("ERROR: could not save during ProcessConsensusChange:", err)
   310  	}
   311  }