github.com/NebulousLabs/Sia@v1.3.7/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 "github.com/NebulousLabs/Sia/types" 18 ) 19 20 // TestSimpleInitialBlockchainDownload tests that 21 // threadedInitialBlockchainDownload synchronizes with peers in the simple case 22 // where there are 8 outbound peers with the same blockchain. 23 func TestSimpleInitialBlockchainDownload(t *testing.T) { 24 if testing.Short() || !build.VLONG { 25 t.SkipNow() 26 } 27 28 // Create 8 remote peers. 29 remoteCSTs := make([]*consensusSetTester, 8) 30 for i := range remoteCSTs { 31 cst, err := blankConsensusSetTester(t.Name()+strconv.Itoa(i), modules.ProdDependencies) 32 if err != nil { 33 t.Fatal(err) 34 } 35 defer cst.Close() 36 remoteCSTs[i] = cst 37 } 38 // Create the "local" peer. 39 localCST, err := blankConsensusSetTester(t.Name()+"- local", modules.ProdDependencies) 40 if err != nil { 41 t.Fatal(err) 42 } 43 defer localCST.Close() 44 for _, cst := range remoteCSTs { 45 err = localCST.cs.gateway.Connect(cst.cs.gateway.Address()) 46 if err != nil { 47 t.Fatal(err) 48 } 49 } 50 // Give the OnConnectRPCs time to finish. 51 time.Sleep(5 * time.Second) 52 53 // Test IBD when all peers have only the genesis block. 54 doneChan := make(chan struct{}) 55 go func() { 56 localCST.cs.threadedInitialBlockchainDownload() 57 doneChan <- struct{}{} 58 }() 59 select { 60 case <-doneChan: 61 case <-time.After(5 * time.Second): 62 t.Fatal("initialBlockchainDownload never completed") 63 } 64 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 65 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 66 } 67 68 // Test IBD when all remote peers have the same longest chain. 69 for i := 0; i < 20; i++ { 70 b, err := remoteCSTs[0].miner.FindBlock() 71 if err != nil { 72 t.Fatal(err) 73 } 74 for _, cst := range remoteCSTs { 75 _, err = cst.cs.managedAcceptBlocks([]types.Block{b}) 76 if err != nil && err != modules.ErrBlockKnown && err != modules.ErrNonExtendingBlock { 77 t.Fatal(err) 78 } 79 } 80 } 81 go func() { 82 localCST.cs.threadedInitialBlockchainDownload() 83 doneChan <- struct{}{} 84 }() 85 select { 86 case <-doneChan: 87 case <-time.After(5 * time.Second): 88 t.Fatal("initialBlockchainDownload never completed") 89 } 90 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 91 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 92 } 93 94 // Test IBD when not starting from the genesis block. 95 for i := 0; i < 4; i++ { 96 b, err := remoteCSTs[0].miner.FindBlock() 97 if err != nil { 98 t.Fatal(err) 99 } 100 for _, cst := range remoteCSTs { 101 _, err = cst.cs.managedAcceptBlocks([]types.Block{b}) 102 if err != nil && err != modules.ErrBlockKnown { 103 t.Fatal(err) 104 } 105 } 106 } 107 go func() { 108 localCST.cs.threadedInitialBlockchainDownload() 109 doneChan <- struct{}{} 110 }() 111 select { 112 case <-doneChan: 113 case <-time.After(5 * time.Second): 114 t.Fatal("initialBlockchainDownload never completed") 115 } 116 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 117 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 118 } 119 120 // Test IBD when the remote peers are on a longer fork. 121 for i := 0; i < 5; i++ { 122 b, err := localCST.miner.FindBlock() 123 if err != nil { 124 t.Fatal(err) 125 } 126 _, err = localCST.cs.managedAcceptBlocks([]types.Block{b}) 127 if err != nil { 128 t.Fatal(err) 129 } 130 } 131 for i := 0; i < 10; i++ { 132 b, err := remoteCSTs[0].miner.FindBlock() 133 if err != nil { 134 t.Fatal(err) 135 } 136 for _, cst := range remoteCSTs { 137 _, err = cst.cs.managedAcceptBlocks([]types.Block{b}) 138 if err != nil && err != modules.ErrBlockKnown { 139 t.Log(i) 140 t.Fatal(err) 141 } 142 } 143 } 144 go func() { 145 localCST.cs.threadedInitialBlockchainDownload() 146 doneChan <- struct{}{} 147 }() 148 select { 149 case <-doneChan: 150 case <-time.After(5 * time.Second): 151 t.Fatal("initialBlockchainDownload never completed") 152 } 153 if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() { 154 t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID()) 155 } 156 157 // Test IBD when the remote peers are on a shorter fork. 158 for i := 0; i < 10; i++ { 159 b, err := localCST.miner.FindBlock() 160 if err != nil { 161 t.Fatal(err) 162 } 163 _, err = localCST.cs.managedAcceptBlocks([]types.Block{b}) 164 if err != nil { 165 t.Fatal(err) 166 } 167 } 168 for i := 0; i < 5; i++ { 169 b, err := remoteCSTs[0].miner.FindBlock() 170 if err != nil { 171 t.Fatal(err) 172 } 173 for _, cst := range remoteCSTs { 174 _, err = cst.cs.managedAcceptBlocks([]types.Block{b}) 175 if err != nil && err != modules.ErrBlockKnown { 176 t.Log(i) 177 t.Fatal(err) 178 } 179 } 180 } 181 localCurrentBlock := localCST.cs.CurrentBlock() 182 go func() { 183 localCST.cs.threadedInitialBlockchainDownload() 184 doneChan <- struct{}{} 185 }() 186 select { 187 case <-doneChan: 188 case <-time.After(5 * time.Second): 189 t.Fatal("initialBlockchainDownload never completed") 190 } 191 if localCST.cs.CurrentBlock().ID() != localCurrentBlock.ID() { 192 t.Fatalf("local was on a longer fork and should not have reorged") 193 } 194 if localCST.cs.CurrentBlock().ID() == remoteCSTs[0].cs.CurrentBlock().ID() { 195 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") 196 } 197 } 198 199 type mockGatewayRPCError struct { 200 modules.Gateway 201 rpcErrs map[modules.NetAddress]error 202 mu sync.Mutex 203 } 204 205 func (g *mockGatewayRPCError) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { 206 g.mu.Lock() 207 defer g.mu.Unlock() 208 return g.rpcErrs[addr] 209 } 210 211 // TestInitialBlockChainDownloadDisconnects tests that 212 // threadedInitialBlockchainDownload only disconnects from peers that error 213 // with anything but a timeout. 214 func TestInitialBlockchainDownloadDisconnects(t *testing.T) { 215 if testing.Short() { 216 t.SkipNow() 217 } 218 219 testdir := build.TempDir(modules.ConsensusDir, t.Name()) 220 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, "local", modules.GatewayDir)) 221 if err != nil { 222 t.Fatal(err) 223 } 224 defer g.Close() 225 mg := mockGatewayRPCError{ 226 Gateway: g, 227 rpcErrs: make(map[modules.NetAddress]error), 228 } 229 localCS, err := New(&mg, false, filepath.Join(testdir, "local", modules.ConsensusDir)) 230 if err != nil { 231 t.Fatal(err) 232 } 233 defer localCS.Close() 234 235 rpcErrs := []error{ 236 // rpcErrs that should cause a a disconnect. 237 io.EOF, 238 errors.New("random error"), 239 errSendBlocksStalled, 240 // rpcErrs that should not cause a disconnect. 241 mockNetError{ 242 error: errors.New("Read timeout"), 243 timeout: true, 244 }, 245 // Need at least minNumOutbound peers that return nil for 246 // threadedInitialBlockchainDownload to mark IBD done. 247 nil, nil, nil, nil, nil, 248 } 249 for i, rpcErr := range rpcErrs { 250 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, "remote - "+strconv.Itoa(i), modules.GatewayDir)) 251 if err != nil { 252 t.Fatal(err) 253 } 254 defer g.Close() 255 err = localCS.gateway.Connect(g.Address()) 256 if err != nil { 257 t.Fatal(err) 258 } 259 mg.rpcErrs[g.Address()] = rpcErr 260 } 261 // Sleep to to give the OnConnectRPCs time to finish. 262 time.Sleep(500 * time.Millisecond) 263 // Do IBD. 264 localCS.threadedInitialBlockchainDownload() 265 // Check that localCS disconnected from peers that errored but did not time out during SendBlocks. 266 if len(localCS.gateway.Peers()) != 6 { 267 t.Error("threadedInitialBlockchainDownload disconnected from peers that timedout or didn't error", len(localCS.gateway.Peers())) 268 } 269 for _, p := range localCS.gateway.Peers() { 270 err = mg.rpcErrs[p.NetAddress] 271 if err == nil { 272 continue 273 } 274 if netErr, ok := err.(net.Error); ok && netErr.Timeout() { 275 continue 276 } 277 t.Fatalf("threadedInitialBlockchainDownload didn't disconnect from a peer that returned '%v', %v", err, p.NetAddress) 278 } 279 } 280 281 // TestInitialBlockchainDownloadDoneRules tests that 282 // threadedInitialBlockchainDownload only terminates under the appropriate 283 // conditions. Appropriate conditions are: 284 // - at least minNumOutbound synced outbound peers 285 // - or at least 1 synced outbound peer and minIBDWaitTime has passed since beginning IBD. 286 func TestInitialBlockchainDownloadDoneRules(t *testing.T) { 287 if testing.Short() || !build.VLONG { 288 t.SkipNow() 289 } 290 testdir := build.TempDir(modules.ConsensusDir, t.Name()) 291 292 // Create a gateway that can be forced to return errors when its RPC method 293 // is called, then create a consensus set using that gateway. 294 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, "local", modules.GatewayDir)) 295 if err != nil { 296 t.Fatal(err) 297 } 298 defer g.Close() 299 mg := mockGatewayRPCError{ 300 Gateway: g, 301 rpcErrs: make(map[modules.NetAddress]error), 302 } 303 cs, err := New(&mg, false, filepath.Join(testdir, "local", modules.ConsensusDir)) 304 if err != nil { 305 t.Fatal(err) 306 } 307 defer cs.Close() 308 309 // Verify that the consensus set will not signal IBD completion when it has 310 // zero peers. 311 doneChan := make(chan struct{}) 312 go func() { 313 cs.threadedInitialBlockchainDownload() 314 doneChan <- struct{}{} 315 }() 316 select { 317 case <-doneChan: 318 t.Error("threadedInitialBlockchainDownload finished with 0 synced peers") 319 case <-time.After(minIBDWaitTime + ibdLoopDelay): 320 } 321 322 // threadedInitialBlockchainDownload is already running. Feed some inbound 323 // peers to the consensus set. The gateway, through its own process of 324 // trying to find outbound peers, will eventually convert one of the 325 // inbound peers to an outbound peer. IBD should not complete until there 326 // is at least one outbound peer. 327 // 328 // After this function has completed, all of the peers will be shutdown, 329 // leaving the consensus set once again with zero peers. 330 func() { 331 inboundCSTs := make([]*consensusSetTester, 8) 332 for i := 0; i < len(inboundCSTs); i++ { 333 inboundCST, err := blankConsensusSetTester(filepath.Join(t.Name(), " - inbound "+strconv.Itoa(i)), modules.ProdDependencies) 334 if err != nil { 335 t.Fatal(err) 336 } 337 defer inboundCST.Close() 338 inboundCST.cs.gateway.Connect(cs.gateway.Address()) 339 } 340 <-doneChan 341 peers := cs.gateway.Peers() 342 outbound := false 343 for _, p := range peers { 344 if !p.Inbound { 345 outbound = true 346 break 347 } 348 } 349 if !outbound { 350 t.Error("threadedInitialBlockchainDownload finished with only inbound peers") 351 } 352 }() 353 354 // Try another initial blockchain download, this time with an outbound peer 355 // who is not synced. The consensus set should not determine itself to have 356 // completed IBD with only unsynced peers. 357 // 358 // 'NotSynced' is simulated in this peer by having all RPCs return errors. 359 go func() { 360 cs.threadedInitialBlockchainDownload() 361 doneChan <- struct{}{} 362 }() 363 gatewayTimesout, err := gateway.New("localhost:0", false, filepath.Join(testdir, "remote - timesout", modules.GatewayDir)) 364 if err != nil { 365 t.Fatal(err) 366 } 367 defer gatewayTimesout.Close() 368 mg.mu.Lock() 369 mg.rpcErrs[gatewayTimesout.Address()] = mockNetError{ 370 error: errors.New("Read timeout"), 371 timeout: true, 372 } 373 mg.mu.Unlock() 374 err = cs.gateway.Connect(gatewayTimesout.Address()) 375 if err != nil { 376 t.Fatal(err) 377 } 378 select { 379 case <-doneChan: 380 t.Error("threadedInitialBlockchainDownload finished with 0 synced peers") 381 case <-time.After(minIBDWaitTime + ibdLoopDelay): 382 } 383 384 // Add a peer that is synced to the peer that is not synced. IBD should not 385 // be considered completed when there is a tie between synced and 386 // not-synced peers. 387 gatewayNoTimeout, err := gateway.New("localhost:0", false, filepath.Join(testdir, "remote - no timeout1", modules.GatewayDir)) 388 if err != nil { 389 t.Fatal(err) 390 } 391 defer gatewayNoTimeout.Close() 392 mg.mu.Lock() 393 mg.rpcErrs[gatewayNoTimeout.Address()] = nil 394 mg.mu.Unlock() 395 err = cs.gateway.Connect(gatewayNoTimeout.Address()) 396 if err != nil { 397 t.Fatal(err) 398 } 399 select { 400 case <-doneChan: 401 t.Fatal("threadedInitialBlockchainDownload finished with 1 synced peer and 1 non-synced peer") 402 case <-time.After(minIBDWaitTime + ibdLoopDelay): 403 } 404 405 // Test when there is 2 peers that are synced and one that is not synced. 406 // There is now a majority synced peers and the minIBDWaitTime has passed, 407 // so the IBD function should finish. 408 gatewayNoTimeout2, err := gateway.New("localhost:0", false, filepath.Join(testdir, "remote - no timeout2", modules.GatewayDir)) 409 if err != nil { 410 t.Fatal(err) 411 } 412 defer gatewayNoTimeout2.Close() 413 mg.mu.Lock() 414 mg.rpcErrs[gatewayNoTimeout2.Address()] = nil 415 mg.mu.Unlock() 416 err = cs.gateway.Connect(gatewayNoTimeout2.Address()) 417 if err != nil { 418 t.Fatal(err) 419 } 420 select { 421 case <-doneChan: 422 case <-time.After(4 * (minIBDWaitTime + ibdLoopDelay)): 423 t.Fatal("threadedInitialBlockchainDownload never finished with 2 synced peers and 1 non-synced peer") 424 } 425 426 // Test when there are >= minNumOutbound peers and >= minNumOutbound peers are synced. 427 gatewayNoTimeouts := make([]modules.Gateway, minNumOutbound-1) 428 for i := 0; i < len(gatewayNoTimeouts); i++ { 429 tmpG, err := gateway.New("localhost:0", false, filepath.Join(testdir, fmt.Sprintf("remote - no timeout-auto-%v", i+3), modules.GatewayDir)) 430 if err != nil { 431 t.Fatal(err) 432 } 433 defer tmpG.Close() 434 mg.mu.Lock() 435 mg.rpcErrs[tmpG.Address()] = nil 436 mg.mu.Unlock() 437 gatewayNoTimeouts[i] = tmpG 438 err = cs.gateway.Connect(gatewayNoTimeouts[i].Address()) 439 if err != nil { 440 t.Fatal(err) 441 } 442 } 443 go func() { 444 cs.threadedInitialBlockchainDownload() 445 doneChan <- struct{}{} 446 }() 447 select { 448 case <-doneChan: 449 case <-time.After(minIBDWaitTime): 450 t.Fatal("threadedInitialBlockchainDownload didn't finish in less than minIBDWaitTime") 451 } 452 } 453 454 // TestGenesisBlockSync is a regression test that checks what happens when two 455 // consensus sets with only the genesis block are connected. They should 456 // determine that they are sync'd, however previously they would not sync to 457 // eachother as they would report EOF instead of performing correct block 458 // exchange. 459 func TestGenesisBlockSync(t *testing.T) { 460 if testing.Short() || !build.VLONG { 461 t.SkipNow() 462 } 463 464 // Create two consensus sets that have zero blocks each (except for the 465 // genesis block). 466 cst1, err := blankConsensusSetTester(t.Name()+"1", modules.ProdDependencies) 467 if err != nil { 468 t.Fatal(err) 469 } 470 cst2, err := blankConsensusSetTester(t.Name()+"2", modules.ProdDependencies) 471 if err != nil { 472 t.Fatal(err) 473 } 474 475 // Connect them. 476 err = cst1.gateway.Connect(cst2.gateway.Address()) 477 if err != nil { 478 t.Fatal(err) 479 } 480 // Block until both report that they are sync'd. 481 for i := 0; i < 100; i++ { 482 time.Sleep(time.Millisecond * 100) 483 if cst1.cs.Synced() && cst2.cs.Synced() { 484 break 485 } 486 } 487 if !cst1.cs.Synced() || !cst2.cs.Synced() { 488 t.Error("Consensus sets did not synchronize to eachother", cst1.cs.Synced(), cst2.cs.Synced()) 489 } 490 491 time.Sleep(time.Second * 12) 492 if len(cst1.gateway.Peers()) == 0 { 493 t.Error("disconnection occurred!") 494 } 495 }