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