github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/accept_reorg_test.go (about) 1 package consensus 2 3 import ( 4 "testing" 5 6 "github.com/NebulousLabs/Sia/build" 7 "github.com/NebulousLabs/Sia/modules" 8 "github.com/NebulousLabs/Sia/types" 9 ) 10 11 // reorgSets contains multiple consensus sets that share a genesis block, which 12 // can be manipulated to cause full integration blockchain reorgs. 13 // 14 // cstBackup is a holding place for cstMain - the blocks originally in cstMain get moved 15 // to cstBackup so that cstMain can be reorganized without that history being lost. 16 // Extending cstBackup will allow cstMain to be reorg'd back to its original blocks. 17 type reorgSets struct { 18 cstMain *consensusSetTester 19 cstAlt *consensusSetTester 20 cstBackup *consensusSetTester 21 } 22 23 // Close will close all of the testers in the reorgSets. Because we don't yet 24 // have a good way to check errors on a deferred statement, a panic will be 25 // thrown if there are any problems closing the reorgSets. 26 func (rs *reorgSets) Close() error { 27 err := rs.cstMain.Close() 28 if err != nil { 29 panic(err) 30 } 31 err = rs.cstAlt.Close() 32 if err != nil { 33 panic(err) 34 } 35 err = rs.cstBackup.Close() 36 if err != nil { 37 panic(err) 38 } 39 return nil 40 } 41 42 // createReorgSets creates a reorg set that is ready to be manipulated. 43 func createReorgSets(name string) *reorgSets { 44 cstMain, err := createConsensusSetTester(name + " - 1") 45 if err != nil { 46 panic(err) 47 } 48 cstAlt, err := createConsensusSetTester(name + " - 2") 49 if err != nil { 50 panic(err) 51 } 52 cstBackup, err := createConsensusSetTester(name + " - 3") 53 if err != nil { 54 panic(err) 55 } 56 57 return &reorgSets{ 58 cstMain: cstMain, 59 cstAlt: cstAlt, 60 cstBackup: cstBackup, 61 } 62 } 63 64 // save takes all of the blocks in cstMain and moves them to cstBackup. 65 func (rs *reorgSets) save() { 66 mainHeight := rs.cstMain.cs.dbBlockHeight() 67 for i := types.BlockHeight(1); i <= mainHeight; i++ { 68 id, err := rs.cstMain.cs.dbGetPath(i) 69 if err != nil { 70 panic(err) 71 } 72 pb, err := rs.cstMain.cs.dbGetBlockMap(id) 73 if err != nil { 74 panic(err) 75 } 76 77 // err is not checked - block may already be in cstBackup. 78 _ = rs.cstBackup.cs.AcceptBlock(pb.Block) 79 } 80 81 // Check that cstMain and cstBackup are even. 82 if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstBackup.cs.dbCurrentProcessedBlock().Block.ID() { 83 panic("could not save cstMain into cstBackup") 84 } 85 if rs.cstMain.cs.dbConsensusChecksum() != rs.cstBackup.cs.dbConsensusChecksum() { 86 panic("reorg checksums do not match after saving") 87 } 88 } 89 90 // extend adds blocks to cstAlt until cstAlt has more weight than cstMain. Then 91 // cstMain is caught up, causing cstMain to perform a reorg that extends all 92 // the way to the genesis block. 93 func (rs *reorgSets) extend() { 94 for rs.cstMain.cs.dbBlockHeight() >= rs.cstAlt.cs.dbBlockHeight() { 95 _, err := rs.cstAlt.miner.AddBlock() 96 if err != nil { 97 panic(err) 98 } 99 } 100 for i := types.BlockHeight(1); i <= rs.cstAlt.cs.dbBlockHeight(); i++ { 101 id, err := rs.cstAlt.cs.dbGetPath(i) 102 if err != nil { 103 panic(err) 104 } 105 pb, err := rs.cstAlt.cs.dbGetBlockMap(id) 106 if err != nil { 107 panic(err) 108 } 109 _ = rs.cstMain.cs.AcceptBlock(pb.Block) 110 } 111 112 // Check that cstMain and cstAlt are even. 113 if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstAlt.cs.dbCurrentProcessedBlock().Block.ID() { 114 panic("could not save cstMain into cstAlt") 115 } 116 if rs.cstMain.cs.dbConsensusChecksum() != rs.cstAlt.cs.dbConsensusChecksum() { 117 panic("reorg checksums do not match after extending") 118 } 119 } 120 121 // restore extends cstBackup until it is ahead of cstMain, and then adds all of 122 // the blocks from cstBackup to cstMain, causing cstMain to reorg to the state 123 // of cstBackup. 124 func (rs *reorgSets) restore() { 125 for rs.cstMain.cs.dbBlockHeight() >= rs.cstBackup.cs.dbBlockHeight() { 126 _, err := rs.cstBackup.miner.AddBlock() 127 if err != nil { 128 panic(err) 129 } 130 } 131 for i := types.BlockHeight(1); i <= rs.cstBackup.cs.dbBlockHeight(); i++ { 132 id, err := rs.cstBackup.cs.dbGetPath(i) 133 if err != nil { 134 panic(err) 135 } 136 pb, err := rs.cstBackup.cs.dbGetBlockMap(id) 137 if err != nil { 138 panic(err) 139 } 140 _ = rs.cstMain.cs.AcceptBlock(pb.Block) 141 } 142 143 // Check that cstMain and cstBackup are even. 144 if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstBackup.cs.dbCurrentProcessedBlock().Block.ID() { 145 panic("could not save cstMain into cstBackup") 146 } 147 if rs.cstMain.cs.dbConsensusChecksum() != rs.cstBackup.cs.dbConsensusChecksum() { 148 panic("reorg checksums do not match after restoring") 149 } 150 } 151 152 // fullReorg saves all of the blocks from cstMain into cstBackup, then extends 153 // cstAlt until cstMain joins cstAlt in structure. Then cstBackup is extended 154 // and cstMain is reorg'd back to have all of the original blocks. 155 func (rs *reorgSets) fullReorg() { 156 rs.save() 157 rs.extend() 158 rs.restore() 159 } 160 161 // TestIntegrationSimpleReorg tries to reorganize a simple block out of, and 162 // then back into, the consensus set. 163 func TestIntegrationSimpleReorg(t *testing.T) { 164 if testing.Short() { 165 t.SkipNow() 166 } 167 t.Parallel() 168 rs := createReorgSets(t.Name()) 169 defer rs.Close() 170 171 // Give a simple block to cstMain. 172 rs.cstMain.testSimpleBlock() 173 174 // Try to trigger consensus inconsistencies by doing a full reorg on the 175 // simple block. 176 rs.fullReorg() 177 } 178 179 // TestIntegrationSiacoinReorg tries to reorganize a siacoin output block out 180 // of, and then back into, the consensus set. 181 func TestIntegrationSiacoinReorg(t *testing.T) { 182 if testing.Short() { 183 t.SkipNow() 184 } 185 t.Parallel() 186 rs := createReorgSets(t.Name()) 187 defer rs.Close() 188 189 // Give a siacoin block to cstMain. 190 rs.cstMain.testSpendSiacoinsBlock() 191 192 // Try to trigger consensus inconsistencies by doing a full reorg on the 193 // simple block. 194 rs.fullReorg() 195 } 196 197 // TestIntegrationValidStorageProofReorg tries to reorganize a valid storage 198 // proof block out of, and then back into, the consensus set. 199 func TestIntegrationValidStorageProofReorg(t *testing.T) { 200 if testing.Short() { 201 t.SkipNow() 202 } 203 t.Parallel() 204 rs := createReorgSets(t.Name()) 205 defer rs.Close() 206 207 // Give a series of blocks containing a file contract and a valid storage 208 // proof to cstMain. 209 rs.cstMain.testValidStorageProofBlocks() 210 211 // Try to trigger consensus inconsistencies by doing a full reorg on the 212 // simple block. 213 rs.fullReorg() 214 } 215 216 // TestIntegrationMissedStorageProofReorg tries to reorganize a valid storage 217 // proof block out of, and then back into, the consensus set. 218 func TestIntegrationMissedStorageProofReorg(t *testing.T) { 219 if testing.Short() { 220 t.SkipNow() 221 } 222 t.Parallel() 223 rs := createReorgSets(t.Name()) 224 defer rs.Close() 225 226 // Give a series of blocks containing a file contract and a valid storage 227 // proof to cstMain. 228 rs.cstMain.testMissedStorageProofBlocks() 229 230 // Try to trigger consensus inconsistencies by doing a full reorg on the 231 // simple block. 232 rs.fullReorg() 233 } 234 235 // TestIntegrationFileContractRevisionReorg tries to reorganize a valid storage 236 // proof block out of, and then back into, the consensus set. 237 func TestIntegrationFileContractRevisionReorg(t *testing.T) { 238 if testing.Short() { 239 t.SkipNow() 240 } 241 t.Parallel() 242 rs := createReorgSets(t.Name()) 243 defer rs.Close() 244 245 // Give a series of blocks containing a file contract and a valid storage 246 // proof to cstMain. 247 rs.cstMain.testFileContractRevision() 248 249 // Try to trigger consensus inconsistencies by doing a full reorg on the 250 // simple block. 251 rs.fullReorg() 252 } 253 254 // TestIntegrationComplexReorg stacks up blocks of all types into a single 255 // blockchain that undergoes a massive reorg as a stress test to the codebase. 256 func TestIntegrationComplexReorg(t *testing.T) { 257 if testing.Short() || !build.VLONG { 258 t.SkipNow() 259 } 260 t.Parallel() 261 rs := createReorgSets(t.Name()) 262 defer rs.Close() 263 264 // Give a wide variety of block types to cstMain. 265 for i := 0; i < 3; i++ { 266 rs.cstMain.testBlockSuite() 267 } 268 // Give fewer blocks to cstAlt, while still using the same variety. 269 for i := 0; i < 2; i++ { 270 rs.cstAlt.testBlockSuite() 271 } 272 273 // Try to trigger consensus inconsistencies by doing a full reorg on the 274 // simple block. 275 rs.fullReorg() 276 } 277 278 /// All functions below this point are deprecated. /// 279 280 // TestBuriedBadFork creates a block with an invalid transaction that's not on 281 // the longest fork. The consensus set will not validate that block. Then valid 282 // blocks are added on top of it to make it the longest fork. When it becomes 283 // the longest fork, all the blocks should be fully validated and thrown out 284 // because a parent is invalid. 285 func TestBuriedBadFork(t *testing.T) { 286 if testing.Short() { 287 t.SkipNow() 288 } 289 t.Parallel() 290 cst, err := createConsensusSetTester(t.Name()) 291 if err != nil { 292 t.Fatal(err) 293 } 294 defer cst.Close() 295 pb := cst.cs.dbCurrentProcessedBlock() 296 297 // Create a bad block that builds on a parent, so that it is part of not 298 // the longest fork. 299 badBlock := types.Block{ 300 ParentID: pb.Block.ParentID, 301 Timestamp: types.CurrentTimestamp(), 302 MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height)}}, 303 Transactions: []types.Transaction{{ 304 SiacoinInputs: []types.SiacoinInput{{}}, // Will trigger an error on full verification but not partial verification. 305 }}, 306 } 307 parent, err := cst.cs.dbGetBlockMap(pb.Block.ParentID) 308 if err != nil { 309 t.Fatal(err) 310 } 311 badBlock, _ = cst.miner.SolveBlock(badBlock, parent.ChildTarget) 312 err = cst.cs.AcceptBlock(badBlock) 313 if err != modules.ErrNonExtendingBlock { 314 t.Fatal(err) 315 } 316 317 // Build another bock on top of the bad block that is fully valid, this 318 // will cause a fork and full validation of the bad block, both the bad 319 // block and this block should be thrown away. 320 block := types.Block{ 321 ParentID: badBlock.ID(), 322 Timestamp: types.CurrentTimestamp(), 323 MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height + 1)}}, 324 } 325 block, _ = cst.miner.SolveBlock(block, parent.ChildTarget) // okay because the target will not change 326 err = cst.cs.AcceptBlock(block) 327 if err == nil { 328 t.Fatal("a bad block failed to cause an error") 329 } 330 }