github.com/letterj/go-ethereum@v1.8.22-0.20190204142846-520024dfd689/p2p/simulations/network_test.go (about) 1 // Copyright 2017 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 simulations 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "strconv" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/ethereum/go-ethereum/log" 29 "github.com/ethereum/go-ethereum/node" 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 "github.com/ethereum/go-ethereum/p2p/simulations/adapters" 32 ) 33 34 // Tests that a created snapshot with a minimal service only contains the expected connections 35 // and that a network when loaded with this snapshot only contains those same connections 36 func TestSnapshot(t *testing.T) { 37 38 // PART I 39 // create snapshot from ring network 40 41 // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting 42 adapter := adapters.NewSimAdapter(adapters.Services{ 43 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 44 return NewNoopService(nil), nil 45 }, 46 }) 47 48 // create network 49 network := NewNetwork(adapter, &NetworkConfig{ 50 DefaultService: "noopwoop", 51 }) 52 // \todo consider making a member of network, set to true threadsafe when shutdown 53 runningOne := true 54 defer func() { 55 if runningOne { 56 network.Shutdown() 57 } 58 }() 59 60 // create and start nodes 61 nodeCount := 20 62 ids := make([]enode.ID, nodeCount) 63 for i := 0; i < nodeCount; i++ { 64 conf := adapters.RandomNodeConfig() 65 node, err := network.NewNodeWithConfig(conf) 66 if err != nil { 67 t.Fatalf("error creating node: %s", err) 68 } 69 if err := network.Start(node.ID()); err != nil { 70 t.Fatalf("error starting node: %s", err) 71 } 72 ids[i] = node.ID() 73 } 74 75 // subscribe to peer events 76 evC := make(chan *Event) 77 sub := network.Events().Subscribe(evC) 78 defer sub.Unsubscribe() 79 80 // connect nodes in a ring 81 // spawn separate thread to avoid deadlock in the event listeners 82 go func() { 83 for i, id := range ids { 84 peerID := ids[(i+1)%len(ids)] 85 if err := network.Connect(id, peerID); err != nil { 86 t.Fatal(err) 87 } 88 } 89 }() 90 91 // collect connection events up to expected number 92 ctx, cancel := context.WithTimeout(context.TODO(), time.Second) 93 defer cancel() 94 checkIds := make(map[enode.ID][]enode.ID) 95 connEventCount := nodeCount 96 OUTER: 97 for { 98 select { 99 case <-ctx.Done(): 100 t.Fatal(ctx.Err()) 101 case ev := <-evC: 102 if ev.Type == EventTypeConn && !ev.Control { 103 104 // fail on any disconnect 105 if !ev.Conn.Up { 106 t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) 107 } 108 checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other) 109 checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One) 110 connEventCount-- 111 log.Debug("ev", "count", connEventCount) 112 if connEventCount == 0 { 113 break OUTER 114 } 115 } 116 } 117 } 118 119 // create snapshot of current network 120 snap, err := network.Snapshot() 121 if err != nil { 122 t.Fatal(err) 123 } 124 j, err := json.Marshal(snap) 125 if err != nil { 126 t.Fatal(err) 127 } 128 log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j)) 129 130 // verify that the snap element numbers check out 131 if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) { 132 t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds)) 133 } 134 135 // shut down sim network 136 runningOne = false 137 sub.Unsubscribe() 138 network.Shutdown() 139 140 // check that we have all the expected connections in the snapshot 141 for nodid, nodConns := range checkIds { 142 for _, nodConn := range nodConns { 143 var match bool 144 for _, snapConn := range snap.Conns { 145 if snapConn.One == nodid && snapConn.Other == nodConn { 146 match = true 147 break 148 } else if snapConn.Other == nodid && snapConn.One == nodConn { 149 match = true 150 break 151 } 152 } 153 if !match { 154 t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn) 155 } 156 } 157 } 158 log.Info("snapshot checked") 159 160 // PART II 161 // load snapshot and verify that exactly same connections are formed 162 163 adapter = adapters.NewSimAdapter(adapters.Services{ 164 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 165 return NewNoopService(nil), nil 166 }, 167 }) 168 network = NewNetwork(adapter, &NetworkConfig{ 169 DefaultService: "noopwoop", 170 }) 171 defer func() { 172 network.Shutdown() 173 }() 174 175 // subscribe to peer events 176 // every node up and conn up event will generate one additional control event 177 // therefore multiply the count by two 178 evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2)) 179 sub = network.Events().Subscribe(evC) 180 defer sub.Unsubscribe() 181 182 // load the snapshot 183 // spawn separate thread to avoid deadlock in the event listeners 184 err = network.Load(snap) 185 if err != nil { 186 t.Fatal(err) 187 } 188 189 // collect connection events up to expected number 190 ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3) 191 defer cancel() 192 193 connEventCount = nodeCount 194 195 OUTER_TWO: 196 for { 197 select { 198 case <-ctx.Done(): 199 t.Fatal(ctx.Err()) 200 case ev := <-evC: 201 if ev.Type == EventTypeConn && !ev.Control { 202 203 // fail on any disconnect 204 if !ev.Conn.Up { 205 t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) 206 } 207 log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other) 208 checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other) 209 checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One) 210 connEventCount-- 211 log.Debug("ev", "count", connEventCount) 212 if connEventCount == 0 { 213 break OUTER_TWO 214 } 215 } 216 } 217 } 218 219 // check that we have all expected connections in the network 220 for _, snapConn := range snap.Conns { 221 var match bool 222 for nodid, nodConns := range checkIds { 223 for _, nodConn := range nodConns { 224 if snapConn.One == nodid && snapConn.Other == nodConn { 225 match = true 226 break 227 } else if snapConn.Other == nodid && snapConn.One == nodConn { 228 match = true 229 break 230 } 231 } 232 } 233 if !match { 234 t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other) 235 } 236 } 237 238 // verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time 239 ctx, cancel = context.WithTimeout(context.TODO(), time.Second) 240 defer cancel() 241 select { 242 case <-ctx.Done(): 243 case ev := <-evC: 244 if ev.Type == EventTypeConn { 245 t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other) 246 } 247 } 248 249 // This test validates if all connections from the snapshot 250 // are created in the network. 251 t.Run("conns after load", func(t *testing.T) { 252 // Create new network. 253 n := NewNetwork( 254 adapters.NewSimAdapter(adapters.Services{ 255 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 256 return NewNoopService(nil), nil 257 }, 258 }), 259 &NetworkConfig{ 260 DefaultService: "noopwoop", 261 }, 262 ) 263 defer n.Shutdown() 264 265 // Load the same snapshot. 266 err := n.Load(snap) 267 if err != nil { 268 t.Fatal(err) 269 } 270 271 // Check every connection from the snapshot 272 // if it is in the network, too. 273 for _, c := range snap.Conns { 274 if n.GetConn(c.One, c.Other) == nil { 275 t.Errorf("missing connection: %s -> %s", c.One, c.Other) 276 } 277 } 278 }) 279 } 280 281 // TestNetworkSimulation creates a multi-node simulation network with each node 282 // connected in a ring topology, checks that all nodes successfully handshake 283 // with each other and that a snapshot fully represents the desired topology 284 func TestNetworkSimulation(t *testing.T) { 285 // create simulation network with 20 testService nodes 286 adapter := adapters.NewSimAdapter(adapters.Services{ 287 "test": newTestService, 288 }) 289 network := NewNetwork(adapter, &NetworkConfig{ 290 DefaultService: "test", 291 }) 292 defer network.Shutdown() 293 nodeCount := 20 294 ids := make([]enode.ID, nodeCount) 295 for i := 0; i < nodeCount; i++ { 296 conf := adapters.RandomNodeConfig() 297 node, err := network.NewNodeWithConfig(conf) 298 if err != nil { 299 t.Fatalf("error creating node: %s", err) 300 } 301 if err := network.Start(node.ID()); err != nil { 302 t.Fatalf("error starting node: %s", err) 303 } 304 ids[i] = node.ID() 305 } 306 307 // perform a check which connects the nodes in a ring (so each node is 308 // connected to exactly two peers) and then checks that all nodes 309 // performed two handshakes by checking their peerCount 310 action := func(_ context.Context) error { 311 for i, id := range ids { 312 peerID := ids[(i+1)%len(ids)] 313 if err := network.Connect(id, peerID); err != nil { 314 return err 315 } 316 } 317 return nil 318 } 319 check := func(ctx context.Context, id enode.ID) (bool, error) { 320 // check we haven't run out of time 321 select { 322 case <-ctx.Done(): 323 return false, ctx.Err() 324 default: 325 } 326 327 // get the node 328 node := network.GetNode(id) 329 if node == nil { 330 return false, fmt.Errorf("unknown node: %s", id) 331 } 332 333 // check it has exactly two peers 334 client, err := node.Client() 335 if err != nil { 336 return false, err 337 } 338 var peerCount int64 339 if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil { 340 return false, err 341 } 342 switch { 343 case peerCount < 2: 344 return false, nil 345 case peerCount == 2: 346 return true, nil 347 default: 348 return false, fmt.Errorf("unexpected peerCount: %d", peerCount) 349 } 350 } 351 352 timeout := 30 * time.Second 353 ctx, cancel := context.WithTimeout(context.Background(), timeout) 354 defer cancel() 355 356 // trigger a check every 100ms 357 trigger := make(chan enode.ID) 358 go triggerChecks(ctx, ids, trigger, 100*time.Millisecond) 359 360 result := NewSimulation(network).Run(ctx, &Step{ 361 Action: action, 362 Trigger: trigger, 363 Expect: &Expectation{ 364 Nodes: ids, 365 Check: check, 366 }, 367 }) 368 if result.Error != nil { 369 t.Fatalf("simulation failed: %s", result.Error) 370 } 371 372 // take a network snapshot and check it contains the correct topology 373 snap, err := network.Snapshot() 374 if err != nil { 375 t.Fatal(err) 376 } 377 if len(snap.Nodes) != nodeCount { 378 t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes)) 379 } 380 if len(snap.Conns) != nodeCount { 381 t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns)) 382 } 383 for i, id := range ids { 384 conn := snap.Conns[i] 385 if conn.One != id { 386 t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One) 387 } 388 peerID := ids[(i+1)%len(ids)] 389 if conn.Other != peerID { 390 t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other) 391 } 392 } 393 } 394 395 func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { 396 tick := time.NewTicker(interval) 397 defer tick.Stop() 398 for { 399 select { 400 case <-tick.C: 401 for _, id := range ids { 402 select { 403 case trigger <- id: 404 case <-ctx.Done(): 405 return 406 } 407 } 408 case <-ctx.Done(): 409 return 410 } 411 } 412 } 413 414 // \todo: refactor to implement shapshots 415 // and connect configuration methods once these are moved from 416 // swarm/network/simulations/connect.go 417 func BenchmarkMinimalService(b *testing.B) { 418 b.Run("ring/32", benchmarkMinimalServiceTmp) 419 } 420 421 func benchmarkMinimalServiceTmp(b *testing.B) { 422 423 // stop timer to discard setup time pollution 424 args := strings.Split(b.Name(), "/") 425 nodeCount, err := strconv.ParseInt(args[2], 10, 16) 426 if err != nil { 427 b.Fatal(err) 428 } 429 430 for i := 0; i < b.N; i++ { 431 // this is a minimal service, whose protocol will close a channel upon run of protocol 432 // making it possible to bench the time it takes for the service to start and protocol actually to be run 433 protoCMap := make(map[enode.ID]map[enode.ID]chan struct{}) 434 adapter := adapters.NewSimAdapter(adapters.Services{ 435 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 436 protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{}) 437 svc := NewNoopService(protoCMap[ctx.Config.ID]) 438 return svc, nil 439 }, 440 }) 441 442 // create network 443 network := NewNetwork(adapter, &NetworkConfig{ 444 DefaultService: "noopwoop", 445 }) 446 defer network.Shutdown() 447 448 // create and start nodes 449 ids := make([]enode.ID, nodeCount) 450 for i := 0; i < int(nodeCount); i++ { 451 conf := adapters.RandomNodeConfig() 452 node, err := network.NewNodeWithConfig(conf) 453 if err != nil { 454 b.Fatalf("error creating node: %s", err) 455 } 456 if err := network.Start(node.ID()); err != nil { 457 b.Fatalf("error starting node: %s", err) 458 } 459 ids[i] = node.ID() 460 } 461 462 // ready, set, go 463 b.ResetTimer() 464 465 // connect nodes in a ring 466 for i, id := range ids { 467 peerID := ids[(i+1)%len(ids)] 468 if err := network.Connect(id, peerID); err != nil { 469 b.Fatal(err) 470 } 471 } 472 473 // wait for all protocols to signal to close down 474 ctx, cancel := context.WithTimeout(context.TODO(), time.Second) 475 defer cancel() 476 for nodid, peers := range protoCMap { 477 for peerid, peerC := range peers { 478 log.Debug("getting ", "node", nodid, "peer", peerid) 479 select { 480 case <-ctx.Done(): 481 b.Fatal(ctx.Err()) 482 case <-peerC: 483 } 484 } 485 } 486 } 487 }