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