github.com/johnathanhowell/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 }