github.com/NebulousLabs/Sia@v1.3.7/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/coreos/bbolt"
    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 err != nil || currentPathID != pb.Block.ID() {
    53  		if build.DEBUG {
    54  			panic(errExternalRevert) // needs to be panic for TestRevertToNode
    55  		} else {
    56  			build.Critical(errExternalRevert)
    57  		}
    58  	}
    59  
    60  	// Rewind blocks until 'pb' is the current block.
    61  	for currentBlockID(tx) != pb.Block.ID() {
    62  		block := currentProcessedBlock(tx)
    63  		commitDiffSet(tx, block, modules.DiffRevert)
    64  		revertedBlocks = append(revertedBlocks, block)
    65  
    66  		// Sanity check - after removing a block, check that the consensus set
    67  		// has maintained consistency.
    68  		if build.Release == "testing" {
    69  			cs.checkConsistency(tx)
    70  		} else {
    71  			cs.maybeCheckConsistency(tx)
    72  		}
    73  	}
    74  	return revertedBlocks
    75  }
    76  
    77  // applyUntilBlock will successively apply the blocks between the consensus
    78  // set's current path and 'pb'.
    79  func (cs *ConsensusSet) applyUntilBlock(tx *bolt.Tx, pb *processedBlock) (appliedBlocks []*processedBlock, err error) {
    80  	// Backtrack to the common parent of 'bn' and current path and then apply the new blocks.
    81  	newPath := backtrackToCurrentPath(tx, pb)
    82  	for _, block := range newPath[1:] {
    83  		// If the diffs for this block have already been generated, apply diffs
    84  		// directly instead of generating them. This is much faster.
    85  		if block.DiffsGenerated {
    86  			commitDiffSet(tx, block, modules.DiffApply)
    87  		} else {
    88  			err := generateAndApplyDiff(tx, block)
    89  			if err != nil {
    90  				// Mark the block as invalid.
    91  				cs.dosBlocks[block.Block.ID()] = struct{}{}
    92  				return nil, err
    93  			}
    94  		}
    95  		appliedBlocks = append(appliedBlocks, block)
    96  
    97  		// Sanity check - after applying a block, check that the consensus set
    98  		// has maintained consistency.
    99  		if build.Release == "testing" {
   100  			cs.checkConsistency(tx)
   101  		} else {
   102  			cs.maybeCheckConsistency(tx)
   103  		}
   104  	}
   105  	return appliedBlocks, nil
   106  }
   107  
   108  // forkBlockchain will move the consensus set onto the 'newBlock' fork. An
   109  // error will be returned if any of the blocks applied in the transition are
   110  // found to be invalid. forkBlockchain is atomic; the ConsensusSet is only
   111  // updated if the function returns nil.
   112  func (cs *ConsensusSet) forkBlockchain(tx *bolt.Tx, newBlock *processedBlock) (revertedBlocks, appliedBlocks []*processedBlock, err error) {
   113  	commonParent := backtrackToCurrentPath(tx, newBlock)[0]
   114  	revertedBlocks = cs.revertToBlock(tx, commonParent)
   115  	appliedBlocks, err = cs.applyUntilBlock(tx, newBlock)
   116  	if err != nil {
   117  		return nil, nil, err
   118  	}
   119  	return revertedBlocks, appliedBlocks, nil
   120  }