github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/synchronize_ibd_test.go (about) 1 package consensus 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net" 8 "path/filepath" 9 "strconv" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/NebulousLabs/Sia/build" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/modules/gateway" 17 ) 18 19 // TestSimpleInitialBlockchainDownload tests that 20 // threadedInitialBlockchainDownload synchronizes with peers in the simple case 21 // where there are 8 outbound peers with the same blockchain. 22 func TestSimpleInitialBlockchainDownload(t *testing.T) { 23 if testing.Short() { 24 t.SkipNow() 25 } 26 27 // Create 8 remote peers. 28 remoteCSTs := make([]*consensusSetTester, 8) 29 for i := range remoteCSTs { 30 cst, err := blankConsensusSetTester(fmt.Sprintf("TestSimpleInitialBlockchainDownload - %v", i)) 31 if err != nil { 32 t.Fatal(err) 33 } 34 defer cst.Close() 35 remoteCSTs[i] = cst 36 } 37 // Create the "local" peer. 38 localCST, err := blankConsensusSetTester("TestSimpleInitialBlockchainDownload - local") 39 if err != nil { 40 t.Fatal(err) 41 } 42 defer localCST.Close() 43 for _, cst := range remoteCSTs { 44 err = localCST.cs.gateway.Connect(cst.cs.gateway.Address()) 45 if err != nil { 46 t.Fatal(err) 47 } 48 } 49 // Give the OnConnectRPCs time to finish. 50 time.Sleep(1 * time.Second) 51 52 // Test IBD when all peers have only the genesis block. 53 doneChan := make(chan struct{}) 54 go func() { 55 localCST.cs.threadedInitialBlockchainDownload() 56 doneChan <- struct{}{} 57 }() 58 select { 59 case <-doneChan: 60 case <-time.After(5 * time.Second): 61 t.Fatal("initialBlockchainDownload never completed") 62 } 63 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 64 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 65 } 66 67 // Test IBD when all remote peers have the same longest chain. 68 for i := 0; i < 20; i++ { 69 b, err := remoteCSTs[0].miner.FindBlock() 70 if err != nil { 71 t.Fatal(err) 72 } 73 for _, cst := range remoteCSTs { 74 err = cst.cs.managedAcceptBlock(b) 75 if err != nil { 76 t.Fatal(err) 77 } 78 } 79 } 80 go func() { 81 localCST.cs.threadedInitialBlockchainDownload() 82 doneChan <- struct{}{} 83 }() 84 select { 85 case <-doneChan: 86 case <-time.After(5 * time.Second): 87 t.Fatal("initialBlockchainDownload never completed") 88 } 89 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 90 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 91 } 92 93 // Test IBD when not starting from the genesis block. 94 for i := 0; i < 4; i++ { 95 b, err := remoteCSTs[0].miner.FindBlock() 96 if err != nil { 97 t.Fatal(err) 98 } 99 for _, cst := range remoteCSTs { 100 err = cst.cs.managedAcceptBlock(b) 101 if err != nil { 102 t.Fatal(err) 103 } 104 } 105 } 106 go func() { 107 localCST.cs.threadedInitialBlockchainDownload() 108 doneChan <- struct{}{} 109 }() 110 select { 111 case <-doneChan: 112 case <-time.After(5 * time.Second): 113 t.Fatal("initialBlockchainDownload never completed") 114 } 115 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 116 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 117 } 118 119 // Test IBD when the remote peers are on a longer fork. 120 for i := 0; i < 5; i++ { 121 b, err := localCST.miner.FindBlock() 122 if err != nil { 123 t.Fatal(err) 124 } 125 err = localCST.cs.managedAcceptBlock(b) 126 if err != nil { 127 t.Fatal(err) 128 } 129 } 130 for i := 0; i < 10; i++ { 131 b, err := remoteCSTs[0].miner.FindBlock() 132 if err != nil { 133 t.Fatal(err) 134 } 135 for _, cst := range remoteCSTs { 136 err = cst.cs.managedAcceptBlock(b) 137 if err != nil { 138 t.Fatal(err) 139 } 140 } 141 } 142 go func() { 143 localCST.cs.threadedInitialBlockchainDownload() 144 doneChan <- struct{}{} 145 }() 146 select { 147 case <-doneChan: 148 case <-time.After(5 * time.Second): 149 t.Fatal("initialBlockchainDownload never completed") 150 } 151 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 152 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 153 } 154 155 // Test IBD when the remote peers are on a shorter fork. 156 for i := 0; i < 10; i++ { 157 b, err := localCST.miner.FindBlock() 158 if err != nil { 159 t.Fatal(err) 160 } 161 err = localCST.cs.managedAcceptBlock(b) 162 if err != nil { 163 t.Fatal(err) 164 } 165 } 166 for i := 0; i < 5; i++ { 167 b, err := remoteCSTs[0].miner.FindBlock() 168 if err != nil { 169 t.Fatal(err) 170 } 171 for _, cst := range remoteCSTs { 172 err = cst.cs.managedAcceptBlock(b) 173 if err != nil { 174 t.Fatal(err) 175 } 176 } 177 } 178 localCurrentBlock := localCST.cs.CurrentBlock() 179 go func() { 180 localCST.cs.threadedInitialBlockchainDownload() 181 doneChan <- struct{}{} 182 }() 183 select { 184 case <-doneChan: 185 case <-time.After(5 * time.Second): 186 t.Fatal("initialBlockchainDownload never completed") 187 } 188 if localCST.cs.CurrentBlock().ID() != localCurrentBlock.ID() { 189 t.Fatalf("local was on a longer fork and should not have reorged") 190 } 191 if localCST.cs.CurrentBlock().ID() == remoteCSTs[0].cs.CurrentBlock().ID() { 192 t.Fatalf("ibd syncing is one way, and a longer fork on the local cs should not cause a reorg on the remote cs's") 193 } 194 } 195 196 type mockGatewayRPCError struct { 197 modules.Gateway 198 rpcErrs map[modules.NetAddress]error 199 mu sync.Mutex 200 } 201 202 func (g *mockGatewayRPCError) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { 203 g.mu.Lock() 204 defer g.mu.Unlock() 205 return g.rpcErrs[addr] 206 } 207 208 // TestInitialBlockChainDownloadDisconnects tests that 209 // threadedInitialBlockchainDownload only disconnects from peers that error 210 // with anything but a timeout. 211 func TestInitialBlockchainDownloadDisconnects(t *testing.T) { 212 if testing.Short() { 213 t.SkipNow() 214 } 215 216 testdir := build.TempDir(modules.ConsensusDir, "TestInitialBlockchainDownloadDisconnects") 217 g, err := gateway.New("localhost:0", filepath.Join(testdir, "local", modules.GatewayDir)) 218 if err != nil { 219 t.Fatal(err) 220 } 221 defer g.Close() 222 mg := mockGatewayRPCError{ 223 Gateway: g, 224 rpcErrs: make(map[modules.NetAddress]error), 225 } 226 localCS, err := New(&mg, filepath.Join(testdir, "local", modules.ConsensusDir)) 227 if err != nil { 228 t.Fatal(err) 229 } 230 defer localCS.Close() 231 232 rpcErrs := []error{ 233 // rpcErrs that should cause a a disconnect. 234 io.EOF, 235 errors.New("random error"), 236 errSendBlocksStalled, 237 // rpcErrs that should not cause a disconnect. 238 mockNetError{ 239 error: errors.New("Read timeout"), 240 timeout: true, 241 }, 242 // Need at least minNumOutbound peers that return nil for 243 // threadedInitialBlockchainDownload to mark IBD done. 244 nil, nil, nil, nil, nil, 245 } 246 for i, rpcErr := range rpcErrs { 247 g, err := gateway.New("localhost:0", filepath.Join(testdir, "remote - "+strconv.Itoa(i), modules.GatewayDir)) 248 if err != nil { 249 t.Fatal(err) 250 } 251 defer g.Close() 252 err = localCS.gateway.Connect(g.Address()) 253 if err != nil { 254 t.Fatal(err) 255 } 256 mg.rpcErrs[g.Address()] = rpcErr 257 } 258 // Sleep to to give the OnConnectRPCs time to finish. 259 time.Sleep(500 * time.Millisecond) 260 // Do IBD. 261 localCS.threadedInitialBlockchainDownload() 262 // Check that localCS disconnected from peers that errored but did not time out during SendBlocks. 263 for _, p := range localCS.gateway.Peers() { 264 err = mg.rpcErrs[p.NetAddress] 265 if err == nil { 266 continue 267 } 268 if netErr, ok := err.(net.Error); ok && netErr.Timeout() { 269 continue 270 } 271 t.Fatalf("threadedInitialBlockchainDownload didn't disconnect from a peer that returned '%v'", err) 272 } 273 if len(localCS.gateway.Peers()) != 6 { 274 t.Error("threadedInitialBlockchainDownload disconnected from peers that timedout or didn't error") 275 } 276 } 277 278 // TestInitialBlockchainDownloadDoneRules tests that 279 // threadedInitialBlockchainDownload only terminates under the appropriate 280 // conditions. Appropriate conditions are: 281 // - at least minNumOutbound synced outbound peers 282 // - or at least 1 synced outbound peer and minIBDWaitTime has passed since beginning IBD. 283 func TestInitialBlockchainDownloadDoneRules(t *testing.T) { 284 if testing.Short() { 285 t.SkipNow() 286 } 287 288 // Set minIBDWaitTime to 1s for just this test because no blocks are 289 // transferred between peers so the wait time can be very short. 290 actualMinIBDWaitTime := minIBDWaitTime 291 defer func() { 292 minIBDWaitTime = actualMinIBDWaitTime 293 }() 294 minIBDWaitTime = 1 * time.Second 295 296 testdir := build.TempDir(modules.ConsensusDir, "TestInitialBlockchainDownloadDoneRules") 297 g, err := gateway.New("localhost:0", filepath.Join(testdir, "local", modules.GatewayDir)) 298 if err != nil { 299 t.Fatal(err) 300 } 301 defer g.Close() 302 mg := mockGatewayRPCError{ 303 Gateway: g, 304 rpcErrs: make(map[modules.NetAddress]error), 305 } 306 cs, err := New(&mg, filepath.Join(testdir, "local", modules.ConsensusDir)) 307 if err != nil { 308 t.Fatal(err) 309 } 310 defer cs.Close() 311 312 doneChan := make(chan struct{}) 313 314 // Test when there are 0 peers. 315 go func() { 316 cs.threadedInitialBlockchainDownload() 317 doneChan <- struct{}{} 318 }() 319 select { 320 case <-doneChan: 321 t.Error("threadedInitialBlockchainDownload finished with 0 synced peers") 322 case <-time.After(minIBDWaitTime + ibdLoopDelay): 323 } 324 325 // Test when there are only inbound peers. 326 inboundCSTs := make([]*consensusSetTester, 8) 327 for i := 0; i < len(inboundCSTs); i++ { 328 inboundCST, err := blankConsensusSetTester(filepath.Join("TestInitialBlockchainDownloadDoneRules", fmt.Sprintf("remote - inbound %v", i))) 329 if err != nil { 330 t.Fatal(err) 331 } 332 defer inboundCST.Close() 333 334 inboundCST.cs.gateway.Connect(cs.gateway.Address()) 335 } 336 select { 337 case <-doneChan: 338 t.Error("threadedInitialBlockchainDownload finished with only inbound peers") 339 case <-time.After(minIBDWaitTime + ibdLoopDelay): 340 } 341 342 // Test when there is 1 peer that isn't synced. 343 gatewayTimesout, err := gateway.New("localhost:0", filepath.Join(testdir, "remote - timesout", modules.GatewayDir)) 344 if err != nil { 345 t.Fatal(err) 346 } 347 defer gatewayTimesout.Close() 348 mg.mu.Lock() 349 mg.rpcErrs[gatewayTimesout.Address()] = mockNetError{ 350 error: errors.New("Read timeout"), 351 timeout: true, 352 } 353 mg.mu.Unlock() 354 err = cs.gateway.Connect(gatewayTimesout.Address()) 355 if err != nil { 356 t.Fatal(err) 357 } 358 select { 359 case <-doneChan: 360 t.Error("threadedInitialBlockchainDownload finished with 0 synced peers") 361 case <-time.After(minIBDWaitTime + ibdLoopDelay): 362 } 363 364 // Test when there is 1 peer that is synced and one that is not synced. 365 gatewayNoTimeout, err := gateway.New("localhost:0", filepath.Join(testdir, "remote - no timeout", modules.GatewayDir)) 366 if err != nil { 367 t.Fatal(err) 368 } 369 defer gatewayNoTimeout.Close() 370 mg.mu.Lock() 371 mg.rpcErrs[gatewayNoTimeout.Address()] = nil 372 mg.mu.Unlock() 373 err = cs.gateway.Connect(gatewayNoTimeout.Address()) 374 if err != nil { 375 t.Fatal(err) 376 } 377 select { 378 case <-doneChan: 379 case <-time.After(minIBDWaitTime + ibdLoopDelay): 380 t.Fatal("threadedInitialBlockchainDownload never finished with 1 synced peer") 381 } 382 383 // Test when there are >= minNumOutbound peers, but < minNumOutbound peers are synced. 384 gatewayTimesouts := make([]modules.Gateway, minNumOutbound-1) 385 for i := 0; i < len(gatewayTimesouts); i++ { 386 tmpG, err := gateway.New("localhost:0", filepath.Join(testdir, fmt.Sprintf("remote - timesout %v", i), modules.GatewayDir)) 387 if err != nil { 388 t.Fatal(err) 389 } 390 defer tmpG.Close() 391 mg.mu.Lock() 392 mg.rpcErrs[tmpG.Address()] = mockNetError{ 393 error: errors.New("Write timeout"), 394 timeout: true, 395 } 396 mg.mu.Unlock() 397 gatewayTimesouts[i] = tmpG 398 err = cs.gateway.Connect(gatewayTimesouts[i].Address()) 399 if err != nil { 400 t.Fatal(err) 401 } 402 } 403 go func() { 404 cs.threadedInitialBlockchainDownload() 405 doneChan <- struct{}{} 406 }() 407 select { 408 case <-doneChan: 409 t.Fatal("threadedInitialBlockchainDownload finished before minIBDWaitTime") 410 case <-time.After(minIBDWaitTime): 411 } 412 select { 413 case <-doneChan: 414 case <-time.After(minIBDWaitTime + ibdLoopDelay): 415 t.Fatal("threadedInitialBlockchainDownload didn't finish after minIBDWaitTime") 416 } 417 418 // Test when there are >= minNumOutbound peers and >= minNumOutbound peers are synced. 419 gatewayNoTimeouts := make([]modules.Gateway, minNumOutbound-1) 420 for i := 0; i < len(gatewayNoTimeouts); i++ { 421 tmpG, err := gateway.New("localhost:0", filepath.Join(testdir, fmt.Sprintf("remote - no timeout %v", i), modules.GatewayDir)) 422 if err != nil { 423 t.Fatal(err) 424 } 425 defer tmpG.Close() 426 mg.mu.Lock() 427 mg.rpcErrs[tmpG.Address()] = nil 428 mg.mu.Unlock() 429 gatewayNoTimeouts[i] = tmpG 430 err = cs.gateway.Connect(gatewayNoTimeouts[i].Address()) 431 if err != nil { 432 t.Fatal(err) 433 } 434 } 435 go func() { 436 cs.threadedInitialBlockchainDownload() 437 doneChan <- struct{}{} 438 }() 439 select { 440 case <-doneChan: 441 case <-time.After(minIBDWaitTime): 442 t.Fatal("threadedInitialBlockchainDownload didn't finish in less than minIBDWaitTime") 443 } 444 }