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 }