github.com/wzbox/go-ethereum@v1.9.2/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 "reflect" 24 "strconv" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/node" 31 "github.com/ethereum/go-ethereum/p2p/enode" 32 "github.com/ethereum/go-ethereum/p2p/simulations/adapters" 33 ) 34 35 // Tests that a created snapshot with a minimal service only contains the expected connections 36 // and that a network when loaded with this snapshot only contains those same connections 37 func TestSnapshot(t *testing.T) { 38 39 // PART I 40 // create snapshot from ring network 41 42 // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting 43 adapter := adapters.NewSimAdapter(adapters.Services{ 44 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 45 return NewNoopService(nil), nil 46 }, 47 }) 48 49 // create network 50 network := NewNetwork(adapter, &NetworkConfig{ 51 DefaultService: "noopwoop", 52 }) 53 // \todo consider making a member of network, set to true threadsafe when shutdown 54 runningOne := true 55 defer func() { 56 if runningOne { 57 network.Shutdown() 58 } 59 }() 60 61 // create and start nodes 62 nodeCount := 20 63 ids := make([]enode.ID, nodeCount) 64 for i := 0; i < nodeCount; i++ { 65 conf := adapters.RandomNodeConfig() 66 node, err := network.NewNodeWithConfig(conf) 67 if err != nil { 68 t.Fatalf("error creating node: %s", err) 69 } 70 if err := network.Start(node.ID()); err != nil { 71 t.Fatalf("error starting node: %s", err) 72 } 73 ids[i] = node.ID() 74 } 75 76 // subscribe to peer events 77 evC := make(chan *Event) 78 sub := network.Events().Subscribe(evC) 79 defer sub.Unsubscribe() 80 81 // connect nodes in a ring 82 // spawn separate thread to avoid deadlock in the event listeners 83 go func() { 84 for i, id := range ids { 85 peerID := ids[(i+1)%len(ids)] 86 if err := network.Connect(id, peerID); err != nil { 87 t.Fatal(err) 88 } 89 } 90 }() 91 92 // collect connection events up to expected number 93 ctx, cancel := context.WithTimeout(context.TODO(), time.Second) 94 defer cancel() 95 checkIds := make(map[enode.ID][]enode.ID) 96 connEventCount := nodeCount 97 OUTER: 98 for { 99 select { 100 case <-ctx.Done(): 101 t.Fatal(ctx.Err()) 102 case ev := <-evC: 103 if ev.Type == EventTypeConn && !ev.Control { 104 105 // fail on any disconnect 106 if !ev.Conn.Up { 107 t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) 108 } 109 checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other) 110 checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One) 111 connEventCount-- 112 log.Debug("ev", "count", connEventCount) 113 if connEventCount == 0 { 114 break OUTER 115 } 116 } 117 } 118 } 119 120 // create snapshot of current network 121 snap, err := network.Snapshot() 122 if err != nil { 123 t.Fatal(err) 124 } 125 j, err := json.Marshal(snap) 126 if err != nil { 127 t.Fatal(err) 128 } 129 log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j)) 130 131 // verify that the snap element numbers check out 132 if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) { 133 t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds)) 134 } 135 136 // shut down sim network 137 runningOne = false 138 sub.Unsubscribe() 139 network.Shutdown() 140 141 // check that we have all the expected connections in the snapshot 142 for nodid, nodConns := range checkIds { 143 for _, nodConn := range nodConns { 144 var match bool 145 for _, snapConn := range snap.Conns { 146 if snapConn.One == nodid && snapConn.Other == nodConn { 147 match = true 148 break 149 } else if snapConn.Other == nodid && snapConn.One == nodConn { 150 match = true 151 break 152 } 153 } 154 if !match { 155 t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn) 156 } 157 } 158 } 159 log.Info("snapshot checked") 160 161 // PART II 162 // load snapshot and verify that exactly same connections are formed 163 164 adapter = adapters.NewSimAdapter(adapters.Services{ 165 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 166 return NewNoopService(nil), nil 167 }, 168 }) 169 network = NewNetwork(adapter, &NetworkConfig{ 170 DefaultService: "noopwoop", 171 }) 172 defer func() { 173 network.Shutdown() 174 }() 175 176 // subscribe to peer events 177 // every node up and conn up event will generate one additional control event 178 // therefore multiply the count by two 179 evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2)) 180 sub = network.Events().Subscribe(evC) 181 defer sub.Unsubscribe() 182 183 // load the snapshot 184 // spawn separate thread to avoid deadlock in the event listeners 185 err = network.Load(snap) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 // collect connection events up to expected number 191 ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3) 192 defer cancel() 193 194 connEventCount = nodeCount 195 196 OuterTwo: 197 for { 198 select { 199 case <-ctx.Done(): 200 t.Fatal(ctx.Err()) 201 case ev := <-evC: 202 if ev.Type == EventTypeConn && !ev.Control { 203 204 // fail on any disconnect 205 if !ev.Conn.Up { 206 t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) 207 } 208 log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other) 209 checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other) 210 checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One) 211 connEventCount-- 212 log.Debug("ev", "count", connEventCount) 213 if connEventCount == 0 { 214 break OuterTwo 215 } 216 } 217 } 218 } 219 220 // check that we have all expected connections in the network 221 for _, snapConn := range snap.Conns { 222 var match bool 223 for nodid, nodConns := range checkIds { 224 for _, nodConn := range nodConns { 225 if snapConn.One == nodid && snapConn.Other == nodConn { 226 match = true 227 break 228 } else if snapConn.Other == nodid && snapConn.One == nodConn { 229 match = true 230 break 231 } 232 } 233 } 234 if !match { 235 t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other) 236 } 237 } 238 239 // verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time 240 ctx, cancel = context.WithTimeout(context.TODO(), time.Second) 241 defer cancel() 242 select { 243 case <-ctx.Done(): 244 case ev := <-evC: 245 if ev.Type == EventTypeConn { 246 t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other) 247 } 248 } 249 250 // This test validates if all connections from the snapshot 251 // are created in the network. 252 t.Run("conns after load", func(t *testing.T) { 253 // Create new network. 254 n := NewNetwork( 255 adapters.NewSimAdapter(adapters.Services{ 256 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 257 return NewNoopService(nil), nil 258 }, 259 }), 260 &NetworkConfig{ 261 DefaultService: "noopwoop", 262 }, 263 ) 264 defer n.Shutdown() 265 266 // Load the same snapshot. 267 err := n.Load(snap) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 // Check every connection from the snapshot 273 // if it is in the network, too. 274 for _, c := range snap.Conns { 275 if n.GetConn(c.One, c.Other) == nil { 276 t.Errorf("missing connection: %s -> %s", c.One, c.Other) 277 } 278 } 279 }) 280 } 281 282 // TestNetworkSimulation creates a multi-node simulation network with each node 283 // connected in a ring topology, checks that all nodes successfully handshake 284 // with each other and that a snapshot fully represents the desired topology 285 func TestNetworkSimulation(t *testing.T) { 286 // create simulation network with 20 testService nodes 287 adapter := adapters.NewSimAdapter(adapters.Services{ 288 "test": newTestService, 289 }) 290 network := NewNetwork(adapter, &NetworkConfig{ 291 DefaultService: "test", 292 }) 293 defer network.Shutdown() 294 nodeCount := 20 295 ids := make([]enode.ID, nodeCount) 296 for i := 0; i < nodeCount; i++ { 297 conf := adapters.RandomNodeConfig() 298 node, err := network.NewNodeWithConfig(conf) 299 if err != nil { 300 t.Fatalf("error creating node: %s", err) 301 } 302 if err := network.Start(node.ID()); err != nil { 303 t.Fatalf("error starting node: %s", err) 304 } 305 ids[i] = node.ID() 306 } 307 308 // perform a check which connects the nodes in a ring (so each node is 309 // connected to exactly two peers) and then checks that all nodes 310 // performed two handshakes by checking their peerCount 311 action := func(_ context.Context) error { 312 for i, id := range ids { 313 peerID := ids[(i+1)%len(ids)] 314 if err := network.Connect(id, peerID); err != nil { 315 return err 316 } 317 } 318 return nil 319 } 320 check := func(ctx context.Context, id enode.ID) (bool, error) { 321 // check we haven't run out of time 322 select { 323 case <-ctx.Done(): 324 return false, ctx.Err() 325 default: 326 } 327 328 // get the node 329 node := network.GetNode(id) 330 if node == nil { 331 return false, fmt.Errorf("unknown node: %s", id) 332 } 333 334 // check it has exactly two peers 335 client, err := node.Client() 336 if err != nil { 337 return false, err 338 } 339 var peerCount int64 340 if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil { 341 return false, err 342 } 343 switch { 344 case peerCount < 2: 345 return false, nil 346 case peerCount == 2: 347 return true, nil 348 default: 349 return false, fmt.Errorf("unexpected peerCount: %d", peerCount) 350 } 351 } 352 353 timeout := 30 * time.Second 354 ctx, cancel := context.WithTimeout(context.Background(), timeout) 355 defer cancel() 356 357 // trigger a check every 100ms 358 trigger := make(chan enode.ID) 359 go triggerChecks(ctx, ids, trigger, 100*time.Millisecond) 360 361 result := NewSimulation(network).Run(ctx, &Step{ 362 Action: action, 363 Trigger: trigger, 364 Expect: &Expectation{ 365 Nodes: ids, 366 Check: check, 367 }, 368 }) 369 if result.Error != nil { 370 t.Fatalf("simulation failed: %s", result.Error) 371 } 372 373 // take a network snapshot and check it contains the correct topology 374 snap, err := network.Snapshot() 375 if err != nil { 376 t.Fatal(err) 377 } 378 if len(snap.Nodes) != nodeCount { 379 t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes)) 380 } 381 if len(snap.Conns) != nodeCount { 382 t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns)) 383 } 384 for i, id := range ids { 385 conn := snap.Conns[i] 386 if conn.One != id { 387 t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One) 388 } 389 peerID := ids[(i+1)%len(ids)] 390 if conn.Other != peerID { 391 t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other) 392 } 393 } 394 } 395 396 func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { 397 tick := time.NewTicker(interval) 398 defer tick.Stop() 399 for { 400 select { 401 case <-tick.C: 402 for _, id := range ids { 403 select { 404 case trigger <- id: 405 case <-ctx.Done(): 406 return 407 } 408 } 409 case <-ctx.Done(): 410 return 411 } 412 } 413 } 414 415 // \todo: refactor to implement shapshots 416 // and connect configuration methods once these are moved from 417 // swarm/network/simulations/connect.go 418 func BenchmarkMinimalService(b *testing.B) { 419 b.Run("ring/32", benchmarkMinimalServiceTmp) 420 } 421 422 func benchmarkMinimalServiceTmp(b *testing.B) { 423 424 // stop timer to discard setup time pollution 425 args := strings.Split(b.Name(), "/") 426 nodeCount, err := strconv.ParseInt(args[2], 10, 16) 427 if err != nil { 428 b.Fatal(err) 429 } 430 431 for i := 0; i < b.N; i++ { 432 // this is a minimal service, whose protocol will close a channel upon run of protocol 433 // making it possible to bench the time it takes for the service to start and protocol actually to be run 434 protoCMap := make(map[enode.ID]map[enode.ID]chan struct{}) 435 adapter := adapters.NewSimAdapter(adapters.Services{ 436 "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { 437 protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{}) 438 svc := NewNoopService(protoCMap[ctx.Config.ID]) 439 return svc, nil 440 }, 441 }) 442 443 // create network 444 network := NewNetwork(adapter, &NetworkConfig{ 445 DefaultService: "noopwoop", 446 }) 447 defer network.Shutdown() 448 449 // create and start nodes 450 ids := make([]enode.ID, nodeCount) 451 for i := 0; i < int(nodeCount); i++ { 452 conf := adapters.RandomNodeConfig() 453 node, err := network.NewNodeWithConfig(conf) 454 if err != nil { 455 b.Fatalf("error creating node: %s", err) 456 } 457 if err := network.Start(node.ID()); err != nil { 458 b.Fatalf("error starting node: %s", err) 459 } 460 ids[i] = node.ID() 461 } 462 463 // ready, set, go 464 b.ResetTimer() 465 466 // connect nodes in a ring 467 for i, id := range ids { 468 peerID := ids[(i+1)%len(ids)] 469 if err := network.Connect(id, peerID); err != nil { 470 b.Fatal(err) 471 } 472 } 473 474 // wait for all protocols to signal to close down 475 ctx, cancel := context.WithTimeout(context.TODO(), time.Second) 476 defer cancel() 477 for nodid, peers := range protoCMap { 478 for peerid, peerC := range peers { 479 log.Debug("getting ", "node", nodid, "peer", peerid) 480 select { 481 case <-ctx.Done(): 482 b.Fatal(ctx.Err()) 483 case <-peerC: 484 } 485 } 486 } 487 } 488 } 489 490 func TestNode_UnmarshalJSON(t *testing.T) { 491 t.Run( 492 "test unmarshal of Node up field", 493 func(t *testing.T) { 494 runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField()) 495 }, 496 ) 497 t.Run( 498 "test unmarshal of Node Config field", 499 func(t *testing.T) { 500 runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField()) 501 }, 502 ) 503 } 504 505 func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) { 506 t.Helper() 507 for _, tt := range tests { 508 t.Run(tt.name, func(t *testing.T) { 509 var got Node 510 if err := got.UnmarshalJSON([]byte(tt.marshaled)); err != nil { 511 expectErrorMessageToContain(t, err, tt.wantErr) 512 } 513 expectNodeEquality(t, got, tt.want) 514 }) 515 } 516 } 517 518 type nodeUnmarshalTestCase struct { 519 name string 520 marshaled string 521 want Node 522 wantErr string 523 } 524 525 func expectErrorMessageToContain(t *testing.T, got error, want string) { 526 t.Helper() 527 if got == nil && want == "" { 528 return 529 } 530 531 if got == nil && want != "" { 532 t.Errorf("error was expected, got: nil, want: %v", want) 533 return 534 } 535 536 if !strings.Contains(got.Error(), want) { 537 t.Errorf( 538 "unexpected error message, got %v, want: %v", 539 want, 540 got, 541 ) 542 } 543 } 544 545 func expectNodeEquality(t *testing.T, got Node, want Node) { 546 t.Helper() 547 if !reflect.DeepEqual(got, want) { 548 t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want) 549 } 550 } 551 552 func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase { 553 return []nodeUnmarshalTestCase{ 554 { 555 name: "empty json", 556 marshaled: "{}", 557 want: Node{ 558 up: false, 559 }, 560 }, 561 { 562 name: "a stopped node", 563 marshaled: "{\"up\": false}", 564 want: Node{ 565 up: false, 566 }, 567 }, 568 { 569 name: "a running node", 570 marshaled: "{\"up\": true}", 571 want: Node{ 572 up: true, 573 }, 574 }, 575 { 576 name: "invalid JSON value on valid key", 577 marshaled: "{\"up\": foo}", 578 wantErr: "invalid character", 579 }, 580 { 581 name: "invalid JSON key and value", 582 marshaled: "{foo: bar}", 583 wantErr: "invalid character", 584 }, 585 { 586 name: "bool value expected but got something else (string)", 587 marshaled: "{\"up\": \"true\"}", 588 wantErr: "cannot unmarshal string into Go struct", 589 }, 590 } 591 } 592 593 func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase { 594 // Don't do a big fuss around testing, as adapters.NodeConfig should 595 // handle it's own serialization. Just do a sanity check. 596 return []nodeUnmarshalTestCase{ 597 { 598 name: "Config field is omitted", 599 marshaled: "{}", 600 want: Node{ 601 Config: nil, 602 }, 603 }, 604 { 605 name: "Config field is nil", 606 marshaled: "{\"config\": nil}", 607 want: Node{ 608 Config: nil, 609 }, 610 }, 611 { 612 name: "a non default Config field", 613 marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}", 614 want: Node{ 615 Config: &adapters.NodeConfig{ 616 Name: "node_ecdd0", 617 Port: 44665, 618 }, 619 }, 620 }, 621 } 622 }