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