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