github.com/alexdevranger/node-1.8.27@v0.0.0-20221128213301-aa5841e41d2d/swarm/network/stream/delivery_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-dubxcoin library. 3 // 4 // The go-dubxcoin 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-dubxcoin 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-dubxcoin library. If not, see <http://www.gnu.org/licenses/>. 16 17 package stream 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/alexdevranger/node-1.8.27/node" 29 "github.com/alexdevranger/node-1.8.27/p2p" 30 "github.com/alexdevranger/node-1.8.27/p2p/enode" 31 "github.com/alexdevranger/node-1.8.27/p2p/protocols" 32 "github.com/alexdevranger/node-1.8.27/p2p/simulations/adapters" 33 p2ptest "github.com/alexdevranger/node-1.8.27/p2p/testing" 34 "github.com/alexdevranger/node-1.8.27/swarm/log" 35 "github.com/alexdevranger/node-1.8.27/swarm/network" 36 pq "github.com/alexdevranger/node-1.8.27/swarm/network/priorityqueue" 37 "github.com/alexdevranger/node-1.8.27/swarm/network/simulation" 38 "github.com/alexdevranger/node-1.8.27/swarm/state" 39 "github.com/alexdevranger/node-1.8.27/swarm/storage" 40 "github.com/alexdevranger/node-1.8.27/swarm/testutil" 41 ) 42 43 //Tests initializing a retrieve request 44 func TestStreamerRetrieveRequest(t *testing.T) { 45 regOpts := &RegistryOptions{ 46 Retrieval: RetrievalClientOnly, 47 Syncing: SyncingDisabled, 48 } 49 tester, streamer, _, teardown, err := newStreamerTester(regOpts) 50 if err != nil { 51 t.Fatal(err) 52 } 53 defer teardown() 54 55 node := tester.Nodes[0] 56 57 ctx := context.Background() 58 req := network.NewRequest( 59 storage.Address(hash0[:]), 60 true, 61 &sync.Map{}, 62 ) 63 streamer.delivery.RequestFromPeers(ctx, req) 64 65 stream := NewStream(swarmChunkServerStreamName, "", true) 66 67 err = tester.TestExchanges(p2ptest.Exchange{ 68 Label: "RetrieveRequestMsg", 69 Expects: []p2ptest.Expect{ 70 { //start expecting a subscription for RETRIEVE_REQUEST due to `RetrievalClientOnly` 71 Code: 4, 72 Msg: &SubscribeMsg{ 73 Stream: stream, 74 History: nil, 75 Priority: Top, 76 }, 77 Peer: node.ID(), 78 }, 79 { //expect a retrieve request message for the given hash 80 Code: 5, 81 Msg: &RetrieveRequestMsg{ 82 Addr: hash0[:], 83 SkipCheck: true, 84 }, 85 Peer: node.ID(), 86 }, 87 }, 88 }) 89 90 if err != nil { 91 t.Fatalf("Expected no error, got %v", err) 92 } 93 } 94 95 //Test requesting a chunk from a peer then issuing a "empty" OfferedHashesMsg (no hashes available yet) 96 //Should time out as the peer does not have the chunk (no syncing happened previously) 97 func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) { 98 tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{ 99 Retrieval: RetrievalEnabled, 100 Syncing: SyncingDisabled, //do no syncing 101 }) 102 if err != nil { 103 t.Fatal(err) 104 } 105 defer teardown() 106 107 node := tester.Nodes[0] 108 109 chunk := storage.NewChunk(storage.Address(hash0[:]), nil) 110 111 peer := streamer.getPeer(node.ID()) 112 113 stream := NewStream(swarmChunkServerStreamName, "", true) 114 //simulate pre-subscription to RETRIEVE_REQUEST stream on peer 115 peer.handleSubscribeMsg(context.TODO(), &SubscribeMsg{ 116 Stream: stream, 117 History: nil, 118 Priority: Top, 119 }) 120 121 //test the exchange 122 err = tester.TestExchanges(p2ptest.Exchange{ 123 Expects: []p2ptest.Expect{ 124 { //first expect a subscription to the RETRIEVE_REQUEST stream 125 Code: 4, 126 Msg: &SubscribeMsg{ 127 Stream: stream, 128 History: nil, 129 Priority: Top, 130 }, 131 Peer: node.ID(), 132 }, 133 }, 134 }, p2ptest.Exchange{ 135 Label: "RetrieveRequestMsg", 136 Triggers: []p2ptest.Trigger{ 137 { //then the actual RETRIEVE_REQUEST.... 138 Code: 5, 139 Msg: &RetrieveRequestMsg{ 140 Addr: chunk.Address()[:], 141 }, 142 Peer: node.ID(), 143 }, 144 }, 145 Expects: []p2ptest.Expect{ 146 { //to which the peer responds with offered hashes 147 Code: 1, 148 Msg: &OfferedHashesMsg{ 149 HandoverProof: nil, 150 Hashes: nil, 151 From: 0, 152 To: 0, 153 }, 154 Peer: node.ID(), 155 }, 156 }, 157 }) 158 159 //should fail with a timeout as the peer we are requesting 160 //the chunk from does not have the chunk 161 expectedError := `exchange #1 "RetrieveRequestMsg": timed out` 162 if err == nil || err.Error() != expectedError { 163 t.Fatalf("Expected error %v, got %v", expectedError, err) 164 } 165 } 166 167 // upstream request server receives a retrieve Request and responds with 168 // offered hashes or delivery if skipHash is set to true 169 func TestStreamerUpstreamRetrieveRequestMsgExchange(t *testing.T) { 170 tester, streamer, localStore, teardown, err := newStreamerTester(&RegistryOptions{ 171 Retrieval: RetrievalEnabled, 172 Syncing: SyncingDisabled, 173 }) 174 if err != nil { 175 t.Fatal(err) 176 } 177 defer teardown() 178 179 node := tester.Nodes[0] 180 181 peer := streamer.getPeer(node.ID()) 182 183 stream := NewStream(swarmChunkServerStreamName, "", true) 184 185 peer.handleSubscribeMsg(context.TODO(), &SubscribeMsg{ 186 Stream: stream, 187 History: nil, 188 Priority: Top, 189 }) 190 191 hash := storage.Address(hash0[:]) 192 chunk := storage.NewChunk(hash, hash) 193 err = localStore.Put(context.TODO(), chunk) 194 if err != nil { 195 t.Fatalf("Expected no err got %v", err) 196 } 197 198 err = tester.TestExchanges(p2ptest.Exchange{ 199 Expects: []p2ptest.Expect{ 200 { 201 Code: 4, 202 Msg: &SubscribeMsg{ 203 Stream: stream, 204 History: nil, 205 Priority: Top, 206 }, 207 Peer: node.ID(), 208 }, 209 }, 210 }, p2ptest.Exchange{ 211 Label: "RetrieveRequestMsg", 212 Triggers: []p2ptest.Trigger{ 213 { 214 Code: 5, 215 Msg: &RetrieveRequestMsg{ 216 Addr: hash, 217 }, 218 Peer: node.ID(), 219 }, 220 }, 221 Expects: []p2ptest.Expect{ 222 { 223 Code: 1, 224 Msg: &OfferedHashesMsg{ 225 HandoverProof: &HandoverProof{ 226 Handover: &Handover{}, 227 }, 228 Hashes: hash, 229 From: 0, 230 // TODO: why is this 32??? 231 To: 32, 232 Stream: stream, 233 }, 234 Peer: node.ID(), 235 }, 236 }, 237 }) 238 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 hash = storage.Address(hash1[:]) 244 chunk = storage.NewChunk(hash, hash1[:]) 245 err = localStore.Put(context.TODO(), chunk) 246 if err != nil { 247 t.Fatalf("Expected no err got %v", err) 248 } 249 250 err = tester.TestExchanges(p2ptest.Exchange{ 251 Label: "RetrieveRequestMsg", 252 Triggers: []p2ptest.Trigger{ 253 { 254 Code: 5, 255 Msg: &RetrieveRequestMsg{ 256 Addr: hash, 257 SkipCheck: true, 258 }, 259 Peer: node.ID(), 260 }, 261 }, 262 Expects: []p2ptest.Expect{ 263 { 264 Code: 6, 265 Msg: &ChunkDeliveryMsg{ 266 Addr: hash, 267 SData: hash, 268 }, 269 Peer: node.ID(), 270 }, 271 }, 272 }) 273 274 if err != nil { 275 t.Fatal(err) 276 } 277 } 278 279 // if there is one peer in the Kademlia, RequestFromPeers should return it 280 func TestRequestFromPeers(t *testing.T) { 281 dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") 282 283 addr := network.RandomAddr() 284 to := network.NewKademlia(addr.OAddr, network.NewKadParams()) 285 delivery := NewDelivery(to, nil) 286 protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil) 287 peer := network.NewPeer(&network.BzzPeer{ 288 BzzAddr: network.RandomAddr(), 289 LightNode: false, 290 Peer: protocolsPeer, 291 }, to) 292 to.On(peer) 293 r := NewRegistry(addr.ID(), delivery, nil, nil, nil, nil) 294 295 // an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished 296 sp := &Peer{ 297 Peer: protocolsPeer, 298 pq: pq.New(int(PriorityQueue), PriorityQueueCap), 299 streamer: r, 300 } 301 r.setPeer(sp) 302 req := network.NewRequest( 303 storage.Address(hash0[:]), 304 true, 305 &sync.Map{}, 306 ) 307 ctx := context.Background() 308 id, _, err := delivery.RequestFromPeers(ctx, req) 309 310 if err != nil { 311 t.Fatal(err) 312 } 313 if *id != dummyPeerID { 314 t.Fatalf("Expected an id, got %v", id) 315 } 316 } 317 318 // RequestFromPeers should not return light nodes 319 func TestRequestFromPeersWithLightNode(t *testing.T) { 320 dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") 321 322 addr := network.RandomAddr() 323 to := network.NewKademlia(addr.OAddr, network.NewKadParams()) 324 delivery := NewDelivery(to, nil) 325 326 protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil) 327 // setting up a lightnode 328 peer := network.NewPeer(&network.BzzPeer{ 329 BzzAddr: network.RandomAddr(), 330 LightNode: true, 331 Peer: protocolsPeer, 332 }, to) 333 to.On(peer) 334 r := NewRegistry(addr.ID(), delivery, nil, nil, nil, nil) 335 // an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished 336 sp := &Peer{ 337 Peer: protocolsPeer, 338 pq: pq.New(int(PriorityQueue), PriorityQueueCap), 339 streamer: r, 340 } 341 r.setPeer(sp) 342 343 req := network.NewRequest( 344 storage.Address(hash0[:]), 345 true, 346 &sync.Map{}, 347 ) 348 349 ctx := context.Background() 350 // making a request which should return with "no peer found" 351 _, _, err := delivery.RequestFromPeers(ctx, req) 352 353 expectedError := "no peer found" 354 if err.Error() != expectedError { 355 t.Fatalf("expected '%v', got %v", expectedError, err) 356 } 357 } 358 359 func TestStreamerDownstreamChunkDeliveryMsgExchange(t *testing.T) { 360 tester, streamer, localStore, teardown, err := newStreamerTester(&RegistryOptions{ 361 Retrieval: RetrievalDisabled, 362 Syncing: SyncingDisabled, 363 }) 364 if err != nil { 365 t.Fatal(err) 366 } 367 defer teardown() 368 369 streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { 370 return &testClient{ 371 t: t, 372 }, nil 373 }) 374 375 node := tester.Nodes[0] 376 377 //subscribe to custom stream 378 stream := NewStream("foo", "", true) 379 err = streamer.Subscribe(node.ID(), stream, NewRange(5, 8), Top) 380 if err != nil { 381 t.Fatalf("Expected no error, got %v", err) 382 } 383 384 chunkKey := hash0[:] 385 chunkData := hash1[:] 386 387 err = tester.TestExchanges(p2ptest.Exchange{ 388 Label: "Subscribe message", 389 Expects: []p2ptest.Expect{ 390 { //first expect subscription to the custom stream... 391 Code: 4, 392 Msg: &SubscribeMsg{ 393 Stream: stream, 394 History: NewRange(5, 8), 395 Priority: Top, 396 }, 397 Peer: node.ID(), 398 }, 399 }, 400 }, 401 p2ptest.Exchange{ 402 Label: "ChunkDelivery message", 403 Triggers: []p2ptest.Trigger{ 404 { //...then trigger a chunk delivery for the given chunk from peer in order for 405 //local node to get the chunk delivered 406 Code: 6, 407 Msg: &ChunkDeliveryMsg{ 408 Addr: chunkKey, 409 SData: chunkData, 410 }, 411 Peer: node.ID(), 412 }, 413 }, 414 }) 415 416 if err != nil { 417 t.Fatalf("Expected no error, got %v", err) 418 } 419 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 420 defer cancel() 421 422 // wait for the chunk to get stored 423 storedChunk, err := localStore.Get(ctx, chunkKey) 424 for err != nil { 425 select { 426 case <-ctx.Done(): 427 t.Fatalf("Chunk is not in localstore after timeout, err: %v", err) 428 default: 429 } 430 storedChunk, err = localStore.Get(ctx, chunkKey) 431 time.Sleep(50 * time.Millisecond) 432 } 433 434 if err != nil { 435 t.Fatalf("Expected no error, got %v", err) 436 } 437 438 if !bytes.Equal(storedChunk.Data(), chunkData) { 439 t.Fatal("Retrieved chunk has different data than original") 440 } 441 442 } 443 444 func TestDeliveryFromNodes(t *testing.T) { 445 testDeliveryFromNodes(t, 2, dataChunkCount, true) 446 testDeliveryFromNodes(t, 2, dataChunkCount, false) 447 testDeliveryFromNodes(t, 4, dataChunkCount, true) 448 testDeliveryFromNodes(t, 4, dataChunkCount, false) 449 testDeliveryFromNodes(t, 8, dataChunkCount, true) 450 testDeliveryFromNodes(t, 8, dataChunkCount, false) 451 testDeliveryFromNodes(t, 16, dataChunkCount, true) 452 testDeliveryFromNodes(t, 16, dataChunkCount, false) 453 } 454 455 func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool) { 456 t.Helper() 457 t.Run(fmt.Sprintf("testDeliveryFromNodes_%d_%d_skipCheck_%t", nodes, chunkCount, skipCheck), func(t *testing.T) { 458 sim := simulation.New(map[string]simulation.ServiceFunc{ 459 "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 460 addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) 461 if err != nil { 462 return nil, nil, err 463 } 464 465 r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ 466 SkipCheck: skipCheck, 467 Syncing: SyncingDisabled, 468 Retrieval: RetrievalEnabled, 469 }, nil) 470 bucket.Store(bucketKeyRegistry, r) 471 472 cleanup = func() { 473 r.Close() 474 clean() 475 } 476 477 return r, cleanup, nil 478 }, 479 }) 480 defer sim.Close() 481 482 log.Info("Adding nodes to simulation") 483 _, err := sim.AddNodesAndConnectChain(nodes) 484 if err != nil { 485 t.Fatal(err) 486 } 487 488 log.Info("Starting simulation") 489 ctx, cancel := context.WithCancel(context.Background()) 490 defer cancel() 491 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { 492 nodeIDs := sim.UpNodeIDs() 493 //determine the pivot node to be the first node of the simulation 494 pivot := nodeIDs[0] 495 496 //distribute chunks of a random file into Stores of nodes 1 to nodes 497 //we will do this by creating a file store with an underlying round-robin store: 498 //the file store will create a hash for the uploaded file, but every chunk will be 499 //distributed to different nodes via round-robin scheduling 500 log.Debug("Writing file to round-robin file store") 501 //to do this, we create an array for chunkstores (length minus one, the pivot node) 502 stores := make([]storage.ChunkStore, len(nodeIDs)-1) 503 //we then need to get all stores from the sim.... 504 lStores := sim.NodesItems(bucketKeyStore) 505 i := 0 506 //...iterate the buckets... 507 for id, bucketVal := range lStores { 508 //...and remove the one which is the pivot node 509 if id == pivot { 510 continue 511 } 512 //the other ones are added to the array... 513 stores[i] = bucketVal.(storage.ChunkStore) 514 i++ 515 } 516 //...which then gets passed to the round-robin file store 517 roundRobinFileStore := storage.NewFileStore(newRoundRobinStore(stores...), storage.NewFileStoreParams()) 518 //now we can actually upload a (random) file to the round-robin store 519 size := chunkCount * chunkSize 520 log.Debug("Storing data to file store") 521 fileHash, wait, err := roundRobinFileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false) 522 // wait until all chunks stored 523 if err != nil { 524 return err 525 } 526 err = wait(ctx) 527 if err != nil { 528 return err 529 } 530 531 log.Debug("Waiting for kademlia") 532 // TODO this does not seem to be correct usage of the function, as the simulation may have no kademlias 533 if _, err := sim.WaitTillHealthy(ctx); err != nil { 534 return err 535 } 536 537 //get the pivot node's filestore 538 item, ok := sim.NodeItem(pivot, bucketKeyFileStore) 539 if !ok { 540 return fmt.Errorf("No filestore") 541 } 542 pivotFileStore := item.(*storage.FileStore) 543 log.Debug("Starting retrieval routine") 544 retErrC := make(chan error) 545 go func() { 546 // start the retrieval on the pivot node - this will spawn retrieve requests for missing chunks 547 // we must wait for the peer connections to have started before requesting 548 n, err := readAll(pivotFileStore, fileHash) 549 log.Info(fmt.Sprintf("retrieved %v", fileHash), "read", n, "err", err) 550 retErrC <- err 551 }() 552 553 disconnected := watchDisconnections(ctx, sim) 554 defer func() { 555 if err != nil && disconnected.bool() { 556 err = errors.New("disconnect events received") 557 } 558 }() 559 560 //finally check that the pivot node gets all chunks via the root hash 561 log.Debug("Check retrieval") 562 success := true 563 var total int64 564 total, err = readAll(pivotFileStore, fileHash) 565 if err != nil { 566 return err 567 } 568 log.Info(fmt.Sprintf("check if %08x is available locally: number of bytes read %v/%v (error: %v)", fileHash, total, size, err)) 569 if err != nil || total != int64(size) { 570 success = false 571 } 572 573 if !success { 574 return fmt.Errorf("Test failed, chunks not available on all nodes") 575 } 576 if err := <-retErrC; err != nil { 577 return fmt.Errorf("requesting chunks: %v", err) 578 } 579 log.Debug("Test terminated successfully") 580 return nil 581 }) 582 if result.Error != nil { 583 t.Fatal(result.Error) 584 } 585 }) 586 } 587 588 func BenchmarkDeliveryFromNodesWithoutCheck(b *testing.B) { 589 for chunks := 32; chunks <= 128; chunks *= 2 { 590 for i := 2; i < 32; i *= 2 { 591 b.Run( 592 fmt.Sprintf("nodes=%v,chunks=%v", i, chunks), 593 func(b *testing.B) { 594 benchmarkDeliveryFromNodes(b, i, chunks, true) 595 }, 596 ) 597 } 598 } 599 } 600 601 func BenchmarkDeliveryFromNodesWithCheck(b *testing.B) { 602 for chunks := 32; chunks <= 128; chunks *= 2 { 603 for i := 2; i < 32; i *= 2 { 604 b.Run( 605 fmt.Sprintf("nodes=%v,chunks=%v", i, chunks), 606 func(b *testing.B) { 607 benchmarkDeliveryFromNodes(b, i, chunks, false) 608 }, 609 ) 610 } 611 } 612 } 613 614 func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck bool) { 615 sim := simulation.New(map[string]simulation.ServiceFunc{ 616 "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 617 addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) 618 if err != nil { 619 return nil, nil, err 620 } 621 622 r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ 623 SkipCheck: skipCheck, 624 Syncing: SyncingDisabled, 625 Retrieval: RetrievalDisabled, 626 SyncUpdateDelay: 0, 627 }, nil) 628 bucket.Store(bucketKeyRegistry, r) 629 630 cleanup = func() { 631 r.Close() 632 clean() 633 } 634 635 return r, cleanup, nil 636 }, 637 }) 638 defer sim.Close() 639 640 log.Info("Initializing test config") 641 _, err := sim.AddNodesAndConnectChain(nodes) 642 if err != nil { 643 b.Fatal(err) 644 } 645 646 ctx, cancel := context.WithCancel(context.Background()) 647 defer cancel() 648 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { 649 nodeIDs := sim.UpNodeIDs() 650 node := nodeIDs[len(nodeIDs)-1] 651 652 item, ok := sim.NodeItem(node, bucketKeyFileStore) 653 if !ok { 654 return errors.New("No filestore") 655 } 656 remoteFileStore := item.(*storage.FileStore) 657 658 pivotNode := nodeIDs[0] 659 item, ok = sim.NodeItem(pivotNode, bucketKeyNetStore) 660 if !ok { 661 return errors.New("No filestore") 662 } 663 netStore := item.(*storage.NetStore) 664 665 if _, err := sim.WaitTillHealthy(ctx); err != nil { 666 return err 667 } 668 669 disconnected := watchDisconnections(ctx, sim) 670 defer func() { 671 if err != nil && disconnected.bool() { 672 err = errors.New("disconnect events received") 673 } 674 }() 675 // benchmark loop 676 b.ResetTimer() 677 b.StopTimer() 678 Loop: 679 for i := 0; i < b.N; i++ { 680 // uploading chunkCount random chunks to the last node 681 hashes := make([]storage.Address, chunkCount) 682 for i := 0; i < chunkCount; i++ { 683 // create actual size real chunks 684 ctx := context.TODO() 685 hash, wait, err := remoteFileStore.Store(ctx, testutil.RandomReader(i, chunkSize), int64(chunkSize), false) 686 if err != nil { 687 return fmt.Errorf("store: %v", err) 688 } 689 // wait until all chunks stored 690 err = wait(ctx) 691 if err != nil { 692 return fmt.Errorf("wait store: %v", err) 693 } 694 // collect the hashes 695 hashes[i] = hash 696 } 697 // now benchmark the actual retrieval 698 // netstore.Get is called for each hash in a go routine and errors are collected 699 b.StartTimer() 700 errs := make(chan error) 701 for _, hash := range hashes { 702 go func(h storage.Address) { 703 _, err := netStore.Get(ctx, h) 704 log.Warn("test check netstore get", "hash", h, "err", err) 705 errs <- err 706 }(hash) 707 } 708 // count and report retrieval errors 709 // if there are misses then chunk timeout is too low for the distance and volume (?) 710 var total, misses int 711 for err := range errs { 712 if err != nil { 713 log.Warn(err.Error()) 714 misses++ 715 } 716 total++ 717 if total == chunkCount { 718 break 719 } 720 } 721 b.StopTimer() 722 723 if misses > 0 { 724 err = fmt.Errorf("%v chunk not found out of %v", misses, total) 725 break Loop 726 } 727 } 728 return err 729 }) 730 if result.Error != nil { 731 b.Fatal(result.Error) 732 } 733 734 }