github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/swarm/network_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 swarm 18 19 import ( 20 "context" 21 "flag" 22 "fmt" 23 "io/ioutil" 24 "math/rand" 25 "os" 26 "sync" 27 "sync/atomic" 28 "testing" 29 "time" 30 31 "github.com/ethereum/go-ethereum/swarm/testutil" 32 33 "github.com/ethereum/go-ethereum/crypto" 34 "github.com/ethereum/go-ethereum/log" 35 "github.com/ethereum/go-ethereum/node" 36 "github.com/ethereum/go-ethereum/p2p/enode" 37 "github.com/ethereum/go-ethereum/p2p/simulations/adapters" 38 "github.com/ethereum/go-ethereum/swarm/api" 39 "github.com/ethereum/go-ethereum/swarm/network/simulation" 40 "github.com/ethereum/go-ethereum/swarm/storage" 41 "github.com/mattn/go-colorable" 42 ) 43 44 var ( 45 loglevel = flag.Int("loglevel", 2, "verbosity of logs") 46 longrunning = flag.Bool("longrunning", false, "do run long-running tests") 47 waitKademlia = flag.Bool("waitkademlia", false, "wait for healthy kademlia before checking files availability") 48 ) 49 50 func init() { 51 rand.Seed(time.Now().UnixNano()) 52 53 flag.Parse() 54 55 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) 56 } 57 58 // TestSwarmNetwork runs a series of test simulations with 59 // static and dynamic Swarm nodes in network simulation, by 60 // uploading files to every node and retrieving them. 61 func TestSwarmNetwork(t *testing.T) { 62 var tests = []testSwarmNetworkCase{ 63 { 64 name: "10_nodes", 65 steps: []testSwarmNetworkStep{ 66 { 67 nodeCount: 10, 68 }, 69 }, 70 options: &testSwarmNetworkOptions{ 71 Timeout: 45 * time.Second, 72 }, 73 }, 74 { 75 name: "10_nodes_skip_check", 76 steps: []testSwarmNetworkStep{ 77 { 78 nodeCount: 10, 79 }, 80 }, 81 options: &testSwarmNetworkOptions{ 82 Timeout: 45 * time.Second, 83 SkipCheck: true, 84 }, 85 }, 86 { 87 name: "dec_inc_node_count", 88 steps: []testSwarmNetworkStep{ 89 { 90 nodeCount: 3, 91 }, 92 { 93 nodeCount: 1, 94 }, 95 { 96 nodeCount: 5, 97 }, 98 }, 99 options: &testSwarmNetworkOptions{ 100 Timeout: 90 * time.Second, 101 }, 102 }, 103 } 104 105 if *longrunning { 106 tests = append(tests, longRunningCases()...) 107 } else if testutil.RaceEnabled { 108 tests = shortCaseForRace() 109 110 } 111 112 for _, tc := range tests { 113 t.Run(tc.name, func(t *testing.T) { 114 testSwarmNetwork(t, tc.options, tc.steps...) 115 }) 116 } 117 } 118 119 type testSwarmNetworkCase struct { 120 name string 121 steps []testSwarmNetworkStep 122 options *testSwarmNetworkOptions 123 } 124 125 // testSwarmNetworkStep is the configuration 126 // for the state of the simulation network. 127 type testSwarmNetworkStep struct { 128 // number of swarm nodes that must be in the Up state 129 nodeCount int 130 } 131 132 // testSwarmNetworkOptions contains optional parameters for running 133 // testSwarmNetwork. 134 type testSwarmNetworkOptions struct { 135 Timeout time.Duration 136 SkipCheck bool 137 } 138 139 func longRunningCases() []testSwarmNetworkCase { 140 return []testSwarmNetworkCase{ 141 { 142 name: "50_nodes", 143 steps: []testSwarmNetworkStep{ 144 { 145 nodeCount: 50, 146 }, 147 }, 148 options: &testSwarmNetworkOptions{ 149 Timeout: 3 * time.Minute, 150 }, 151 }, 152 { 153 name: "50_nodes_skip_check", 154 steps: []testSwarmNetworkStep{ 155 { 156 nodeCount: 50, 157 }, 158 }, 159 options: &testSwarmNetworkOptions{ 160 Timeout: 3 * time.Minute, 161 SkipCheck: true, 162 }, 163 }, 164 { 165 name: "inc_node_count", 166 steps: []testSwarmNetworkStep{ 167 { 168 nodeCount: 2, 169 }, 170 { 171 nodeCount: 5, 172 }, 173 { 174 nodeCount: 10, 175 }, 176 }, 177 options: &testSwarmNetworkOptions{ 178 Timeout: 90 * time.Second, 179 }, 180 }, 181 { 182 name: "dec_node_count", 183 steps: []testSwarmNetworkStep{ 184 { 185 nodeCount: 10, 186 }, 187 { 188 nodeCount: 6, 189 }, 190 { 191 nodeCount: 3, 192 }, 193 }, 194 options: &testSwarmNetworkOptions{ 195 Timeout: 90 * time.Second, 196 }, 197 }, 198 { 199 name: "inc_dec_node_count", 200 steps: []testSwarmNetworkStep{ 201 { 202 nodeCount: 3, 203 }, 204 { 205 nodeCount: 5, 206 }, 207 { 208 nodeCount: 25, 209 }, 210 { 211 nodeCount: 10, 212 }, 213 { 214 nodeCount: 4, 215 }, 216 }, 217 options: &testSwarmNetworkOptions{ 218 Timeout: 5 * time.Minute, 219 }, 220 }, 221 { 222 name: "inc_dec_node_count_skip_check", 223 steps: []testSwarmNetworkStep{ 224 { 225 nodeCount: 3, 226 }, 227 { 228 nodeCount: 5, 229 }, 230 { 231 nodeCount: 25, 232 }, 233 { 234 nodeCount: 10, 235 }, 236 { 237 nodeCount: 4, 238 }, 239 }, 240 options: &testSwarmNetworkOptions{ 241 Timeout: 5 * time.Minute, 242 SkipCheck: true, 243 }, 244 }, 245 } 246 } 247 248 func shortCaseForRace() []testSwarmNetworkCase { 249 // As for now, Travis with -race can only run 8 nodes 250 return []testSwarmNetworkCase{ 251 { 252 name: "8_nodes", 253 steps: []testSwarmNetworkStep{ 254 { 255 nodeCount: 8, 256 }, 257 }, 258 options: &testSwarmNetworkOptions{ 259 Timeout: 1 * time.Minute, 260 }, 261 }, 262 } 263 } 264 265 // file represents the file uploaded on a particular node. 266 type file struct { 267 addr storage.Address 268 data string 269 nodeID enode.ID 270 } 271 272 // check represents a reference to a file that is retrieved 273 // from a particular node. 274 type check struct { 275 key string 276 nodeID enode.ID 277 } 278 279 // testSwarmNetwork is a helper function used for testing different 280 // static and dynamic Swarm network simulations. 281 // It is responsible for: 282 // - Setting up a Swarm network simulation, and updates the number of nodes within the network on every step according to steps. 283 // - Uploading a unique file to every node on every step. 284 // - May wait for Kademlia on every node to be healthy. 285 // - Checking if a file is retrievable from all nodes. 286 func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwarmNetworkStep) { 287 t.Helper() 288 289 if o == nil { 290 o = new(testSwarmNetworkOptions) 291 } 292 293 sim := simulation.New(map[string]simulation.ServiceFunc{ 294 "swarm": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 295 config := api.NewConfig() 296 297 dir, err := ioutil.TempDir("", "swarm-network-test-node") 298 if err != nil { 299 return nil, nil, err 300 } 301 cleanup = func() { 302 err := os.RemoveAll(dir) 303 if err != nil { 304 log.Error("cleaning up swarm temp dir", "err", err) 305 } 306 } 307 308 config.Path = dir 309 310 privkey, err := crypto.GenerateKey() 311 if err != nil { 312 return nil, cleanup, err 313 } 314 315 config.Init(privkey) 316 config.DeliverySkipCheck = o.SkipCheck 317 config.Port = "" 318 319 swarm, err := NewSwarm(config, nil) 320 if err != nil { 321 return nil, cleanup, err 322 } 323 bucket.Store(simulation.BucketKeyKademlia, swarm.bzz.Hive.Kademlia) 324 log.Info("new swarm", "bzzKey", config.BzzKey, "baseAddr", fmt.Sprintf("%x", swarm.bzz.BaseAddr())) 325 return swarm, cleanup, nil 326 }, 327 }) 328 defer sim.Close() 329 330 ctx := context.Background() 331 if o.Timeout > 0 { 332 var cancel context.CancelFunc 333 ctx, cancel = context.WithTimeout(ctx, o.Timeout) 334 defer cancel() 335 } 336 337 files := make([]file, 0) 338 339 for i, step := range steps { 340 log.Debug("test sync step", "n", i+1, "nodes", step.nodeCount) 341 342 change := step.nodeCount - len(sim.UpNodeIDs()) 343 344 if change > 0 { 345 _, err := sim.AddNodesAndConnectChain(change) 346 if err != nil { 347 t.Fatal(err) 348 } 349 } else if change < 0 { 350 _, err := sim.StopRandomNodes(-change) 351 if err != nil { 352 t.Fatal(err) 353 } 354 } else { 355 t.Logf("step %v: no change in nodes", i) 356 continue 357 } 358 359 var checkStatusM sync.Map 360 var nodeStatusM sync.Map 361 var totalFoundCount uint64 362 363 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { 364 nodeIDs := sim.UpNodeIDs() 365 rand.Shuffle(len(nodeIDs), func(i, j int) { 366 nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i] 367 }) 368 for _, id := range nodeIDs { 369 key, data, err := uploadFile(sim.Service("swarm", id).(*Swarm)) 370 if err != nil { 371 return err 372 } 373 log.Trace("file uploaded", "node", id, "key", key.String()) 374 files = append(files, file{ 375 addr: key, 376 data: data, 377 nodeID: id, 378 }) 379 } 380 381 if *waitKademlia { 382 if _, err := sim.WaitTillHealthy(ctx); err != nil { 383 return err 384 } 385 } 386 387 // File retrieval check is repeated until all uploaded files are retrieved from all nodes 388 // or until the timeout is reached. 389 for { 390 if retrieve(sim, files, &checkStatusM, &nodeStatusM, &totalFoundCount) == 0 { 391 return nil 392 } 393 } 394 }) 395 396 if result.Error != nil { 397 t.Fatal(result.Error) 398 } 399 log.Debug("done: test sync step", "n", i+1, "nodes", step.nodeCount) 400 } 401 } 402 403 // uploadFile, uploads a short file to the swarm instance 404 // using the api.Put method. 405 func uploadFile(swarm *Swarm) (storage.Address, string, error) { 406 b := make([]byte, 8) 407 _, err := rand.Read(b) 408 if err != nil { 409 return nil, "", err 410 } 411 // File data is very short, but it is ensured that its 412 // uniqueness is very certain. 413 data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b) 414 ctx := context.TODO() 415 k, wait, err := swarm.api.Put(ctx, data, "text/plain", false) 416 if err != nil { 417 return nil, "", err 418 } 419 if wait != nil { 420 err = wait(ctx) 421 } 422 return k, data, err 423 } 424 425 // retrieve is the function that is used for checking the availability of 426 // uploaded files in testSwarmNetwork test helper function. 427 func retrieve( 428 sim *simulation.Simulation, 429 files []file, 430 checkStatusM *sync.Map, 431 nodeStatusM *sync.Map, 432 totalFoundCount *uint64, 433 ) (missing uint64) { 434 rand.Shuffle(len(files), func(i, j int) { 435 files[i], files[j] = files[j], files[i] 436 }) 437 438 var totalWg sync.WaitGroup 439 errc := make(chan error) 440 441 nodeIDs := sim.UpNodeIDs() 442 443 totalCheckCount := len(nodeIDs) * len(files) 444 445 for _, id := range nodeIDs { 446 if _, ok := nodeStatusM.Load(id); ok { 447 continue 448 } 449 start := time.Now() 450 var checkCount uint64 451 var foundCount uint64 452 453 totalWg.Add(1) 454 455 var wg sync.WaitGroup 456 457 swarm := sim.Service("swarm", id).(*Swarm) 458 for _, f := range files { 459 460 checkKey := check{ 461 key: f.addr.String(), 462 nodeID: id, 463 } 464 if n, ok := checkStatusM.Load(checkKey); ok && n.(int) == 0 { 465 continue 466 } 467 468 checkCount++ 469 wg.Add(1) 470 go func(f file, id enode.ID) { 471 defer wg.Done() 472 473 log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount)) 474 475 r, _, _, _, err := swarm.api.Get(context.TODO(), api.NOOPDecrypt, f.addr, "/") 476 if err != nil { 477 errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) 478 return 479 } 480 d, err := ioutil.ReadAll(r) 481 if err != nil { 482 errc <- fmt.Errorf("api get: read response: node %s, key %s: kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) 483 return 484 } 485 data := string(d) 486 if data != f.data { 487 errc <- fmt.Errorf("file contend missmatch: node %s, key %s, expected %q, got %q", id, f.addr, f.data, data) 488 return 489 } 490 checkStatusM.Store(checkKey, 0) 491 atomic.AddUint64(&foundCount, 1) 492 log.Info("api get: file found", "node", id.String(), "key", f.addr.String(), "content", data, "files found", atomic.LoadUint64(&foundCount)) 493 }(f, id) 494 } 495 496 go func(id enode.ID) { 497 defer totalWg.Done() 498 wg.Wait() 499 500 atomic.AddUint64(totalFoundCount, foundCount) 501 502 if foundCount == checkCount { 503 log.Info("all files are found for node", "id", id.String(), "duration", time.Since(start)) 504 nodeStatusM.Store(id, 0) 505 return 506 } 507 log.Debug("files missing for node", "id", id.String(), "check", checkCount, "found", foundCount) 508 }(id) 509 510 } 511 512 go func() { 513 totalWg.Wait() 514 close(errc) 515 }() 516 517 var errCount int 518 for err := range errc { 519 if err != nil { 520 errCount++ 521 } 522 log.Warn(err.Error()) 523 } 524 525 log.Info("check stats", "total check count", totalCheckCount, "total files found", atomic.LoadUint64(totalFoundCount), "total errors", errCount) 526 527 return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount) 528 }