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