github.com/FUSIONFoundation/efsn@v3.6.2-0.20200916075423-dbb5dd5d2cc7+incompatible/swarm/network/simulations/discovery/discovery_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discovery 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "math/rand" 27 "os" 28 "path" 29 "strings" 30 "sync" 31 "testing" 32 "time" 33 34 "github.com/FusionFoundation/efsn/common" 35 "github.com/FusionFoundation/efsn/log" 36 "github.com/FusionFoundation/efsn/node" 37 "github.com/FusionFoundation/efsn/p2p" 38 "github.com/FusionFoundation/efsn/p2p/discover" 39 "github.com/FusionFoundation/efsn/p2p/simulations" 40 "github.com/FusionFoundation/efsn/p2p/simulations/adapters" 41 "github.com/FusionFoundation/efsn/swarm/network" 42 "github.com/FusionFoundation/efsn/swarm/state" 43 colorable "github.com/mattn/go-colorable" 44 ) 45 46 // serviceName is used with the exec adapter so the exec'd binary knows which 47 // service to execute 48 const serviceName = "discovery" 49 const testMinProxBinSize = 2 50 const discoveryPersistenceDatadir = "discovery_persistence_test_store" 51 52 var discoveryPersistencePath = path.Join(os.TempDir(), discoveryPersistenceDatadir) 53 var discoveryEnabled = true 54 var persistenceEnabled = false 55 56 var services = adapters.Services{ 57 serviceName: newService, 58 } 59 60 func cleanDbStores() error { 61 entries, err := ioutil.ReadDir(os.TempDir()) 62 if err != nil { 63 return err 64 } 65 66 for _, f := range entries { 67 if strings.HasPrefix(f.Name(), discoveryPersistenceDatadir) { 68 os.RemoveAll(path.Join(os.TempDir(), f.Name())) 69 } 70 } 71 return nil 72 73 } 74 75 func getDbStore(nodeID string) (*state.DBStore, error) { 76 if _, err := os.Stat(discoveryPersistencePath + "_" + nodeID); os.IsNotExist(err) { 77 log.Info(fmt.Sprintf("directory for nodeID %s does not exist. creating...", nodeID)) 78 ioutil.TempDir("", discoveryPersistencePath+"_"+nodeID) 79 } 80 log.Info(fmt.Sprintf("opening storage directory for nodeID %s", nodeID)) 81 store, err := state.NewDBStore(discoveryPersistencePath + "_" + nodeID) 82 if err != nil { 83 return nil, err 84 } 85 return store, nil 86 } 87 88 var ( 89 nodeCount = flag.Int("nodes", 10, "number of nodes to create (default 10)") 90 initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)") 91 snapshotFile = flag.String("snapshot", "", "create snapshot") 92 loglevel = flag.Int("loglevel", 3, "verbosity of logs") 93 rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs") 94 ) 95 96 func init() { 97 flag.Parse() 98 // register the discovery service which will run as a devp2p 99 // protocol when using the exec adapter 100 adapters.RegisterServices(services) 101 102 log.PrintOrigins(true) 103 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog)))) 104 } 105 106 // Benchmarks to test the average time it takes for an N-node ring 107 // to full a healthy kademlia topology 108 func BenchmarkDiscovery_8_1(b *testing.B) { benchmarkDiscovery(b, 8, 1) } 109 func BenchmarkDiscovery_16_1(b *testing.B) { benchmarkDiscovery(b, 16, 1) } 110 func BenchmarkDiscovery_32_1(b *testing.B) { benchmarkDiscovery(b, 32, 1) } 111 func BenchmarkDiscovery_64_1(b *testing.B) { benchmarkDiscovery(b, 64, 1) } 112 func BenchmarkDiscovery_128_1(b *testing.B) { benchmarkDiscovery(b, 128, 1) } 113 func BenchmarkDiscovery_256_1(b *testing.B) { benchmarkDiscovery(b, 256, 1) } 114 115 func BenchmarkDiscovery_8_2(b *testing.B) { benchmarkDiscovery(b, 8, 2) } 116 func BenchmarkDiscovery_16_2(b *testing.B) { benchmarkDiscovery(b, 16, 2) } 117 func BenchmarkDiscovery_32_2(b *testing.B) { benchmarkDiscovery(b, 32, 2) } 118 func BenchmarkDiscovery_64_2(b *testing.B) { benchmarkDiscovery(b, 64, 2) } 119 func BenchmarkDiscovery_128_2(b *testing.B) { benchmarkDiscovery(b, 128, 2) } 120 func BenchmarkDiscovery_256_2(b *testing.B) { benchmarkDiscovery(b, 256, 2) } 121 122 func BenchmarkDiscovery_8_4(b *testing.B) { benchmarkDiscovery(b, 8, 4) } 123 func BenchmarkDiscovery_16_4(b *testing.B) { benchmarkDiscovery(b, 16, 4) } 124 func BenchmarkDiscovery_32_4(b *testing.B) { benchmarkDiscovery(b, 32, 4) } 125 func BenchmarkDiscovery_64_4(b *testing.B) { benchmarkDiscovery(b, 64, 4) } 126 func BenchmarkDiscovery_128_4(b *testing.B) { benchmarkDiscovery(b, 128, 4) } 127 func BenchmarkDiscovery_256_4(b *testing.B) { benchmarkDiscovery(b, 256, 4) } 128 129 func TestDiscoverySimulationDockerAdapter(t *testing.T) { 130 testDiscoverySimulationDockerAdapter(t, *nodeCount, *initCount) 131 } 132 133 func testDiscoverySimulationDockerAdapter(t *testing.T, nodes, conns int) { 134 adapter, err := adapters.NewDockerAdapter() 135 if err != nil { 136 if err == adapters.ErrLinuxOnly { 137 t.Skip(err) 138 } else { 139 t.Fatal(err) 140 } 141 } 142 testDiscoverySimulation(t, nodes, conns, adapter) 143 } 144 145 func TestDiscoverySimulationExecAdapter(t *testing.T) { 146 testDiscoverySimulationExecAdapter(t, *nodeCount, *initCount) 147 } 148 149 func testDiscoverySimulationExecAdapter(t *testing.T, nodes, conns int) { 150 baseDir, err := ioutil.TempDir("", "swarm-test") 151 if err != nil { 152 t.Fatal(err) 153 } 154 defer os.RemoveAll(baseDir) 155 testDiscoverySimulation(t, nodes, conns, adapters.NewExecAdapter(baseDir)) 156 } 157 158 func TestDiscoverySimulationSimAdapter(t *testing.T) { 159 testDiscoverySimulationSimAdapter(t, *nodeCount, *initCount) 160 } 161 162 func TestDiscoveryPersistenceSimulationSimAdapter(t *testing.T) { 163 testDiscoveryPersistenceSimulationSimAdapter(t, *nodeCount, *initCount) 164 } 165 166 func testDiscoveryPersistenceSimulationSimAdapter(t *testing.T, nodes, conns int) { 167 testDiscoveryPersistenceSimulation(t, nodes, conns, adapters.NewSimAdapter(services)) 168 } 169 170 func testDiscoverySimulationSimAdapter(t *testing.T, nodes, conns int) { 171 testDiscoverySimulation(t, nodes, conns, adapters.NewSimAdapter(services)) 172 } 173 174 func testDiscoverySimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) { 175 startedAt := time.Now() 176 result, err := discoverySimulation(nodes, conns, adapter) 177 if err != nil { 178 t.Fatalf("Setting up simulation failed: %v", err) 179 } 180 if result.Error != nil { 181 t.Fatalf("Simulation failed: %s", result.Error) 182 } 183 t.Logf("Simulation with %d nodes passed in %s", nodes, result.FinishedAt.Sub(result.StartedAt)) 184 var min, max time.Duration 185 var sum int 186 for _, pass := range result.Passes { 187 duration := pass.Sub(result.StartedAt) 188 if sum == 0 || duration < min { 189 min = duration 190 } 191 if duration > max { 192 max = duration 193 } 194 sum += int(duration.Nanoseconds()) 195 } 196 t.Logf("Min: %s, Max: %s, Average: %s", min, max, time.Duration(sum/len(result.Passes))*time.Nanosecond) 197 finishedAt := time.Now() 198 t.Logf("Setup: %s, shutdown: %s", result.StartedAt.Sub(startedAt), finishedAt.Sub(result.FinishedAt)) 199 } 200 201 func testDiscoveryPersistenceSimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) map[int][]byte { 202 persistenceEnabled = true 203 discoveryEnabled = true 204 205 result, err := discoveryPersistenceSimulation(nodes, conns, adapter) 206 207 if err != nil { 208 t.Fatalf("Setting up simulation failed: %v", err) 209 } 210 if result.Error != nil { 211 t.Fatalf("Simulation failed: %s", result.Error) 212 } 213 t.Logf("Simulation with %d nodes passed in %s", nodes, result.FinishedAt.Sub(result.StartedAt)) 214 // set the discovery and persistence flags again to default so other 215 // tests will not be affected 216 discoveryEnabled = true 217 persistenceEnabled = false 218 return nil 219 } 220 221 func benchmarkDiscovery(b *testing.B, nodes, conns int) { 222 for i := 0; i < b.N; i++ { 223 result, err := discoverySimulation(nodes, conns, adapters.NewSimAdapter(services)) 224 if err != nil { 225 b.Fatalf("setting up simulation failed: %v", err) 226 } 227 if result.Error != nil { 228 b.Logf("simulation failed: %s", result.Error) 229 } 230 } 231 } 232 233 func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simulations.StepResult, error) { 234 // create network 235 net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ 236 ID: "0", 237 DefaultService: serviceName, 238 }) 239 defer net.Shutdown() 240 trigger := make(chan discover.NodeID) 241 ids := make([]discover.NodeID, nodes) 242 for i := 0; i < nodes; i++ { 243 conf := adapters.RandomNodeConfig() 244 node, err := net.NewNodeWithConfig(conf) 245 if err != nil { 246 return nil, fmt.Errorf("error starting node: %s", err) 247 } 248 if err := net.Start(node.ID()); err != nil { 249 return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err) 250 } 251 if err := triggerChecks(trigger, net, node.ID()); err != nil { 252 return nil, fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err) 253 } 254 ids[i] = node.ID() 255 } 256 257 // run a simulation which connects the 10 nodes in a ring and waits 258 // for full peer discovery 259 var addrs [][]byte 260 action := func(ctx context.Context) error { 261 return nil 262 } 263 wg := sync.WaitGroup{} 264 for i := range ids { 265 // collect the overlay addresses, to 266 addrs = append(addrs, network.ToOverlayAddr(ids[i].Bytes())) 267 for j := 0; j < conns; j++ { 268 var k int 269 if j == 0 { 270 k = (i + 1) % len(ids) 271 } else { 272 k = rand.Intn(len(ids)) 273 } 274 wg.Add(1) 275 go func(i, k int) { 276 defer wg.Done() 277 net.Connect(ids[i], ids[k]) 278 }(i, k) 279 } 280 } 281 wg.Wait() 282 log.Debug(fmt.Sprintf("nodes: %v", len(addrs))) 283 // construct the peer pot, so that kademlia health can be checked 284 ppmap := network.NewPeerPotMap(testMinProxBinSize, addrs) 285 check := func(ctx context.Context, id discover.NodeID) (bool, error) { 286 select { 287 case <-ctx.Done(): 288 return false, ctx.Err() 289 default: 290 } 291 292 node := net.GetNode(id) 293 if node == nil { 294 return false, fmt.Errorf("unknown node: %s", id) 295 } 296 client, err := node.Client() 297 if err != nil { 298 return false, fmt.Errorf("error getting node client: %s", err) 299 } 300 healthy := &network.Health{} 301 addr := common.Bytes2Hex(network.ToOverlayAddr(id.Bytes())) 302 if err := client.Call(&healthy, "hive_healthy", ppmap[addr]); err != nil { 303 return false, fmt.Errorf("error getting node health: %s", err) 304 } 305 log.Debug(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v, saturated: %v\n%v", id, healthy.GotNN, healthy.KnowNN, healthy.Full, healthy.Hive)) 306 return healthy.KnowNN && healthy.GotNN && healthy.Full, nil 307 } 308 309 // 64 nodes ~ 1min 310 // 128 nodes ~ 311 timeout := 300 * time.Second 312 ctx, cancel := context.WithTimeout(context.Background(), timeout) 313 defer cancel() 314 result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ 315 Action: action, 316 Trigger: trigger, 317 Expect: &simulations.Expectation{ 318 Nodes: ids, 319 Check: check, 320 }, 321 }) 322 if result.Error != nil { 323 return result, nil 324 } 325 326 if *snapshotFile != "" { 327 snap, err := net.Snapshot() 328 if err != nil { 329 return nil, errors.New("no shapshot dude") 330 } 331 jsonsnapshot, err := json.Marshal(snap) 332 if err != nil { 333 return nil, fmt.Errorf("corrupt json snapshot: %v", err) 334 } 335 log.Info("writing snapshot", "file", *snapshotFile) 336 err = ioutil.WriteFile(*snapshotFile, jsonsnapshot, 0755) 337 if err != nil { 338 return nil, err 339 } 340 } 341 return result, nil 342 } 343 344 func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simulations.StepResult, error) { 345 cleanDbStores() 346 defer cleanDbStores() 347 348 // create network 349 net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ 350 ID: "0", 351 DefaultService: serviceName, 352 }) 353 defer net.Shutdown() 354 trigger := make(chan discover.NodeID) 355 ids := make([]discover.NodeID, nodes) 356 var addrs [][]byte 357 358 for i := 0; i < nodes; i++ { 359 conf := adapters.RandomNodeConfig() 360 node, err := net.NewNodeWithConfig(conf) 361 if err != nil { 362 panic(err) 363 } 364 if err != nil { 365 return nil, fmt.Errorf("error starting node: %s", err) 366 } 367 if err := net.Start(node.ID()); err != nil { 368 return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err) 369 } 370 if err := triggerChecks(trigger, net, node.ID()); err != nil { 371 return nil, fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err) 372 } 373 ids[i] = node.ID() 374 a := network.ToOverlayAddr(ids[i].Bytes()) 375 376 addrs = append(addrs, a) 377 } 378 379 // run a simulation which connects the 10 nodes in a ring and waits 380 // for full peer discovery 381 ppmap := network.NewPeerPotMap(testMinProxBinSize, addrs) 382 383 var restartTime time.Time 384 385 action := func(ctx context.Context) error { 386 ticker := time.NewTicker(500 * time.Millisecond) 387 388 for range ticker.C { 389 isHealthy := true 390 for _, id := range ids { 391 //call Healthy RPC 392 node := net.GetNode(id) 393 if node == nil { 394 return fmt.Errorf("unknown node: %s", id) 395 } 396 client, err := node.Client() 397 if err != nil { 398 return fmt.Errorf("error getting node client: %s", err) 399 } 400 healthy := &network.Health{} 401 addr := common.Bytes2Hex(network.ToOverlayAddr(id.Bytes())) 402 if err := client.Call(&healthy, "hive_healthy", ppmap[addr]); err != nil { 403 return fmt.Errorf("error getting node health: %s", err) 404 } 405 406 log.Info(fmt.Sprintf("NODE: %s, IS HEALTHY: %t", id.String(), healthy.GotNN && healthy.KnowNN && healthy.Full)) 407 if !healthy.GotNN || !healthy.Full { 408 isHealthy = false 409 break 410 } 411 } 412 if isHealthy { 413 break 414 } 415 } 416 ticker.Stop() 417 418 log.Info("reached healthy kademlia. starting to shutdown nodes.") 419 shutdownStarted := time.Now() 420 // stop all ids, then start them again 421 for _, id := range ids { 422 node := net.GetNode(id) 423 424 if err := net.Stop(node.ID()); err != nil { 425 return fmt.Errorf("error stopping node %s: %s", node.ID().TerminalString(), err) 426 } 427 } 428 log.Info(fmt.Sprintf("shutting down nodes took: %s", time.Since(shutdownStarted))) 429 persistenceEnabled = true 430 discoveryEnabled = false 431 restartTime = time.Now() 432 for _, id := range ids { 433 node := net.GetNode(id) 434 if err := net.Start(node.ID()); err != nil { 435 return fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err) 436 } 437 if err := triggerChecks(trigger, net, node.ID()); err != nil { 438 return fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err) 439 } 440 } 441 442 log.Info(fmt.Sprintf("restarting nodes took: %s", time.Since(restartTime))) 443 444 return nil 445 } 446 //connects in a chain 447 wg := sync.WaitGroup{} 448 //connects in a ring 449 for i := range ids { 450 for j := 1; j <= conns; j++ { 451 k := (i + j) % len(ids) 452 if k == i { 453 k = (k + 1) % len(ids) 454 } 455 wg.Add(1) 456 go func(i, k int) { 457 defer wg.Done() 458 net.Connect(ids[i], ids[k]) 459 }(i, k) 460 } 461 } 462 wg.Wait() 463 log.Debug(fmt.Sprintf("nodes: %v", len(addrs))) 464 // construct the peer pot, so that kademlia health can be checked 465 check := func(ctx context.Context, id discover.NodeID) (bool, error) { 466 select { 467 case <-ctx.Done(): 468 return false, ctx.Err() 469 default: 470 } 471 472 node := net.GetNode(id) 473 if node == nil { 474 return false, fmt.Errorf("unknown node: %s", id) 475 } 476 client, err := node.Client() 477 if err != nil { 478 return false, fmt.Errorf("error getting node client: %s", err) 479 } 480 healthy := &network.Health{} 481 addr := common.Bytes2Hex(network.ToOverlayAddr(id.Bytes())) 482 if err := client.Call(&healthy, "hive_healthy", ppmap[addr]); err != nil { 483 return false, fmt.Errorf("error getting node health: %s", err) 484 } 485 log.Info(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v, saturated: %v", id, healthy.GotNN, healthy.KnowNN, healthy.Full)) 486 487 return healthy.KnowNN && healthy.GotNN && healthy.Full, nil 488 } 489 490 // 64 nodes ~ 1min 491 // 128 nodes ~ 492 timeout := 300 * time.Second 493 ctx, cancel := context.WithTimeout(context.Background(), timeout) 494 defer cancel() 495 result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ 496 Action: action, 497 Trigger: trigger, 498 Expect: &simulations.Expectation{ 499 Nodes: ids, 500 Check: check, 501 }, 502 }) 503 if result.Error != nil { 504 return result, nil 505 } 506 507 return result, nil 508 } 509 510 // triggerChecks triggers a simulation step check whenever a peer is added or 511 // removed from the given node, and also every second to avoid a race between 512 // peer events and kademlia becoming healthy 513 func triggerChecks(trigger chan discover.NodeID, net *simulations.Network, id discover.NodeID) error { 514 node := net.GetNode(id) 515 if node == nil { 516 return fmt.Errorf("unknown node: %s", id) 517 } 518 client, err := node.Client() 519 if err != nil { 520 return err 521 } 522 events := make(chan *p2p.PeerEvent) 523 sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") 524 if err != nil { 525 return fmt.Errorf("error getting peer events for node %v: %s", id, err) 526 } 527 go func() { 528 defer sub.Unsubscribe() 529 530 tick := time.NewTicker(time.Second) 531 defer tick.Stop() 532 533 for { 534 select { 535 case <-events: 536 trigger <- id 537 case <-tick.C: 538 trigger <- id 539 case err := <-sub.Err(): 540 if err != nil { 541 log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err) 542 } 543 return 544 } 545 } 546 }() 547 return nil 548 } 549 550 func newService(ctx *adapters.ServiceContext) (node.Service, error) { 551 host := adapters.ExternalIP() 552 553 addr := network.NewAddrFromNodeIDAndPort(ctx.Config.ID, host, ctx.Config.Port) 554 555 kp := network.NewKadParams() 556 kp.MinProxBinSize = testMinProxBinSize 557 558 if ctx.Config.Reachable != nil { 559 kp.Reachable = func(o *network.BzzAddr) bool { 560 return ctx.Config.Reachable(o.ID()) 561 } 562 } 563 kad := network.NewKademlia(addr.Over(), kp) 564 hp := network.NewHiveParams() 565 hp.KeepAliveInterval = time.Duration(200) * time.Millisecond 566 hp.Discovery = discoveryEnabled 567 568 log.Info(fmt.Sprintf("discovery for nodeID %s is %t", ctx.Config.ID.String(), hp.Discovery)) 569 570 config := &network.BzzConfig{ 571 OverlayAddr: addr.Over(), 572 UnderlayAddr: addr.Under(), 573 HiveParams: hp, 574 } 575 576 if persistenceEnabled { 577 log.Info(fmt.Sprintf("persistence enabled for nodeID %s", ctx.Config.ID.String())) 578 store, err := getDbStore(ctx.Config.ID.String()) 579 if err != nil { 580 return nil, err 581 } 582 return network.NewBzz(config, kad, store, nil, nil), nil 583 } 584 585 return network.NewBzz(config, kad, nil, nil, nil), nil 586 }