github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/fork.go (about)

     1  package consensus
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/NebulousLabs/Sia/build"
     7  	"github.com/NebulousLabs/Sia/modules"
     8  
     9  	"github.com/NebulousLabs/bolt"
    10  )
    11  
    12  var (
    13  	errExternalRevert = errors.New("cannot revert to block outside of current path")
    14  )
    15  
    16  // backtrackToCurrentPath traces backwards from 'pb' until it reaches a block
    17  // in the ConsensusSet's current path (the "common parent"). It returns the
    18  // (inclusive) set of blocks between the common parent and 'pb', starting from
    19  // the former.
    20  func backtrackToCurrentPath(tx *bolt.Tx, pb *processedBlock) []*processedBlock {
    21  	path := []*processedBlock{pb}
    22  	for {
    23  		// Error is not checked in production code - an error can only indicate
    24  		// that pb.Height > blockHeight(tx).
    25  		currentPathID, err := getPath(tx, pb.Height)
    26  		if currentPathID == pb.Block.ID() {
    27  			break
    28  		}
    29  		// Sanity check - an error should only indicate that pb.Height >
    30  		// blockHeight(tx).
    31  		if build.DEBUG && err != nil && pb.Height <= blockHeight(tx) {
    32  			panic(err)
    33  		}
    34  
    35  		// Prepend the next block to the list of blocks leading from the
    36  		// current path to the input block.
    37  		pb, err = getBlockMap(tx, pb.Block.ParentID)
    38  		if build.DEBUG && err != nil {
    39  			panic(err)
    40  		}
    41  		path = append([]*processedBlock{pb}, path...)
    42  	}
    43  	return path
    44  }
    45  
    46  // revertToBlock will revert blocks from the ConsensusSet's current path until
    47  // 'pb' is the current block. Blocks are returned in the order that they were
    48  // reverted.  'pb' is not reverted.
    49  func (cs *ConsensusSet) revertToBlock(tx *bolt.Tx, pb *processedBlock) (revertedBlocks []*processedBlock) {
    50  	// Sanity check - make sure that pb is in the current path.
    51  	currentPathID, err := getPath(tx, pb.Height)
    52  	if build.DEBUG && (err != nil || currentPathID != pb.Block.ID()) {
    53  		panic(errExternalRevert)
    54  	}
    55  
    56  	// Rewind blocks until 'pb' is the current block.
    57  	for currentBlockID(tx) != pb.Block.ID() {
    58  		block := currentProcessedBlock(tx)
    59  		commitDiffSet(tx, block, modules.DiffRevert)
    60  		revertedBlocks = append(revertedBlocks, block)
    61  
    62  		// Sanity check - after removing a block, check that the consensus set
    63  		// has maintained consistency.
    64  		if build.Release == "testing" {
    65  			cs.checkConsistency(tx)
    66  		} else {
    67  			cs.maybeCheckConsistency(tx)
    68  		}
    69  	}
    70  	return revertedBlocks
    71  }
    72  
    73  // applyUntilBlock will successively apply the blocks between the consensus
    74  // set's current path and 'pb'.
    75  func (cs *ConsensusSet) applyUntilBlock(tx *bolt.Tx, pb *processedBlock) (appliedBlocks []*processedBlock, err error) {
    76  	// Backtrack to the common parent of 'bn' and current path and then apply the new blocks.
    77  	newPath := backtrackToCurrentPath(tx, pb)
    78  	for _, block := range newPath[1:] {
    79  		// If the diffs for this block have already been generated, apply diffs
    80  		// directly instead of generating them. This is much faster.
    81  		if block.DiffsGenerated {
    82  			commitDiffSet(tx, block, modules.DiffApply)
    83  		} else {
    84  			err := generateAndApplyDiff(tx, block)
    85  			if err != nil {
    86  				// Mark the block as invalid.
    87  				cs.dosBlocks[block.Block.ID()] = struct{}{}
    88  				return nil, err
    89  			}
    90  		}
    91  		appliedBlocks = append(appliedBlocks, block)
    92  
    93  		// Sanity check - after applying a block, check that the consensus set
    94  		// has maintained consistency.
    95  		if build.Release == "testing" {
    96  			cs.checkConsistency(tx)
    97  		} else {
    98  			cs.maybeCheckConsistency(tx)
    99  		}
   100  	}
   101  	return appliedBlocks, nil
   102  }
   103  
   104  // forkBlockchain will move the consensus set onto the 'newBlock' fork. An
   105  // error will be returned if any of the blocks applied in the transition are
   106  // found to be invalid. forkBlockchain is atomic; the ConsensusSet is only
   107  // updated if the function returns nil.
   108  func (cs *ConsensusSet) forkBlockchain(tx *bolt.Tx, newBlock *processedBlock) (revertedBlocks, appliedBlocks []*processedBlock, err error) {
   109  	commonParent := backtrackToCurrentPath(tx, newBlock)[0]
   110  	revertedBlocks = cs.revertToBlock(tx, commonParent)
   111  	appliedBlocks, err = cs.applyUntilBlock(tx, newBlock)
   112  	if err != nil {
   113  		return nil, nil, err
   114  	}
   115  	return revertedBlocks, appliedBlocks, nil
   116  }