github.com/Ethersocial/go-esn@v0.3.7/swarm/storage/netstore_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 storage 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/rand" 23 "io/ioutil" 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/ethersocial/go-esn/common" 29 "github.com/ethersocial/go-esn/p2p/enode" 30 ch "github.com/ethersocial/go-esn/swarm/chunk" 31 ) 32 33 var sourcePeerID = enode.HexID("99d8594b52298567d2ca3f4c441a5ba0140ee9245e26460d01102a52773c73b9") 34 35 type mockNetFetcher struct { 36 peers *sync.Map 37 sources []*enode.ID 38 peersPerRequest [][]Address 39 requestCalled bool 40 offerCalled bool 41 quit <-chan struct{} 42 ctx context.Context 43 hopCounts []uint8 44 } 45 46 func (m *mockNetFetcher) Offer(ctx context.Context, source *enode.ID) { 47 m.offerCalled = true 48 m.sources = append(m.sources, source) 49 } 50 51 func (m *mockNetFetcher) Request(ctx context.Context, hopCount uint8) { 52 m.requestCalled = true 53 var peers []Address 54 m.peers.Range(func(key interface{}, _ interface{}) bool { 55 peers = append(peers, common.FromHex(key.(string))) 56 return true 57 }) 58 m.peersPerRequest = append(m.peersPerRequest, peers) 59 m.hopCounts = append(m.hopCounts, hopCount) 60 } 61 62 type mockNetFetchFuncFactory struct { 63 fetcher *mockNetFetcher 64 } 65 66 func (m *mockNetFetchFuncFactory) newMockNetFetcher(ctx context.Context, _ Address, peers *sync.Map) NetFetcher { 67 m.fetcher.peers = peers 68 m.fetcher.quit = ctx.Done() 69 m.fetcher.ctx = ctx 70 return m.fetcher 71 } 72 73 func mustNewNetStore(t *testing.T) *NetStore { 74 netStore, _ := mustNewNetStoreWithFetcher(t) 75 return netStore 76 } 77 78 func mustNewNetStoreWithFetcher(t *testing.T) (*NetStore, *mockNetFetcher) { 79 t.Helper() 80 81 datadir, err := ioutil.TempDir("", "netstore") 82 if err != nil { 83 t.Fatal(err) 84 } 85 naddr := make([]byte, 32) 86 params := NewDefaultLocalStoreParams() 87 params.Init(datadir) 88 params.BaseKey = naddr 89 localStore, err := NewTestLocalStoreForAddr(params) 90 if err != nil { 91 t.Fatal(err) 92 } 93 94 fetcher := &mockNetFetcher{} 95 mockNetFetchFuncFactory := &mockNetFetchFuncFactory{ 96 fetcher: fetcher, 97 } 98 netStore, err := NewNetStore(localStore, mockNetFetchFuncFactory.newMockNetFetcher) 99 if err != nil { 100 t.Fatal(err) 101 } 102 return netStore, fetcher 103 } 104 105 // TestNetStoreGetAndPut tests calling NetStore.Get which is blocked until the same chunk is Put. 106 // After the Put there should no active fetchers, and the context created for the fetcher should 107 // be cancelled. 108 func TestNetStoreGetAndPut(t *testing.T) { 109 netStore, fetcher := mustNewNetStoreWithFetcher(t) 110 111 chunk := GenerateRandomChunk(ch.DefaultSize) 112 113 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 114 defer cancel() 115 116 c := make(chan struct{}) // this channel ensures that the gouroutine with the Put does not run earlier than the Get 117 go func() { 118 <-c // wait for the Get to be called 119 time.Sleep(200 * time.Millisecond) // and a little more so it is surely called 120 121 // check if netStore created a fetcher in the Get call for the unavailable chunk 122 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 123 t.Fatal("Expected netStore to use a fetcher for the Get call") 124 } 125 126 err := netStore.Put(ctx, chunk) 127 if err != nil { 128 t.Fatalf("Expected no err got %v", err) 129 } 130 }() 131 132 close(c) 133 recChunk, err := netStore.Get(ctx, chunk.Address()) // this is blocked until the Put above is done 134 if err != nil { 135 t.Fatalf("Expected no err got %v", err) 136 } 137 // the retrieved chunk should be the same as what we Put 138 if !bytes.Equal(recChunk.Address(), chunk.Address()) || !bytes.Equal(recChunk.Data(), chunk.Data()) { 139 t.Fatalf("Different chunk received than what was put") 140 } 141 // the chunk is already available locally, so there should be no active fetchers waiting for it 142 if netStore.fetchers.Len() != 0 { 143 t.Fatal("Expected netStore to remove the fetcher after delivery") 144 } 145 146 // A fetcher was created when the Get was called (and the chunk was not available). The chunk 147 // was delivered with the Put call, so the fetcher should be cancelled now. 148 select { 149 case <-fetcher.ctx.Done(): 150 default: 151 t.Fatal("Expected fetcher context to be cancelled") 152 } 153 154 } 155 156 // TestNetStoreGetAndPut tests calling NetStore.Put and then NetStore.Get. 157 // After the Put the chunk is available locally, so the Get can just retrieve it from LocalStore, 158 // there is no need to create fetchers. 159 func TestNetStoreGetAfterPut(t *testing.T) { 160 netStore, fetcher := mustNewNetStoreWithFetcher(t) 161 162 chunk := GenerateRandomChunk(ch.DefaultSize) 163 164 ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) 165 defer cancel() 166 167 // First we Put the chunk, so the chunk will be available locally 168 err := netStore.Put(ctx, chunk) 169 if err != nil { 170 t.Fatalf("Expected no err got %v", err) 171 } 172 173 // Get should retrieve the chunk from LocalStore, without creating fetcher 174 recChunk, err := netStore.Get(ctx, chunk.Address()) 175 if err != nil { 176 t.Fatalf("Expected no err got %v", err) 177 } 178 // the retrieved chunk should be the same as what we Put 179 if !bytes.Equal(recChunk.Address(), chunk.Address()) || !bytes.Equal(recChunk.Data(), chunk.Data()) { 180 t.Fatalf("Different chunk received than what was put") 181 } 182 // no fetcher offer or request should be created for a locally available chunk 183 if fetcher.offerCalled || fetcher.requestCalled { 184 t.Fatal("NetFetcher.offerCalled or requestCalled not expected to be called") 185 } 186 // no fetchers should be created for a locally available chunk 187 if netStore.fetchers.Len() != 0 { 188 t.Fatal("Expected netStore to not have fetcher") 189 } 190 191 } 192 193 // TestNetStoreGetTimeout tests a Get call for an unavailable chunk and waits for timeout 194 func TestNetStoreGetTimeout(t *testing.T) { 195 netStore, fetcher := mustNewNetStoreWithFetcher(t) 196 197 chunk := GenerateRandomChunk(ch.DefaultSize) 198 199 ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) 200 defer cancel() 201 202 c := make(chan struct{}) // this channel ensures that the gouroutine does not run earlier than the Get 203 go func() { 204 <-c // wait for the Get to be called 205 time.Sleep(200 * time.Millisecond) // and a little more so it is surely called 206 207 // check if netStore created a fetcher in the Get call for the unavailable chunk 208 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 209 t.Fatal("Expected netStore to use a fetcher for the Get call") 210 } 211 }() 212 213 close(c) 214 // We call Get on this chunk, which is not in LocalStore. We don't Put it at all, so there will 215 // be a timeout 216 _, err := netStore.Get(ctx, chunk.Address()) 217 218 // Check if the timeout happened 219 if err != context.DeadlineExceeded { 220 t.Fatalf("Expected context.DeadLineExceeded err got %v", err) 221 } 222 223 // A fetcher was created, check if it has been removed after timeout 224 if netStore.fetchers.Len() != 0 { 225 t.Fatal("Expected netStore to remove the fetcher after timeout") 226 } 227 228 // Check if the fetcher context has been cancelled after the timeout 229 select { 230 case <-fetcher.ctx.Done(): 231 default: 232 t.Fatal("Expected fetcher context to be cancelled") 233 } 234 } 235 236 // TestNetStoreGetCancel tests a Get call for an unavailable chunk, then cancels the context and checks 237 // the errors 238 func TestNetStoreGetCancel(t *testing.T) { 239 netStore, fetcher := mustNewNetStoreWithFetcher(t) 240 241 chunk := GenerateRandomChunk(ch.DefaultSize) 242 243 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 244 245 c := make(chan struct{}) // this channel ensures that the gouroutine with the cancel does not run earlier than the Get 246 go func() { 247 <-c // wait for the Get to be called 248 time.Sleep(200 * time.Millisecond) // and a little more so it is surely called 249 // check if netStore created a fetcher in the Get call for the unavailable chunk 250 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 251 t.Fatal("Expected netStore to use a fetcher for the Get call") 252 } 253 cancel() 254 }() 255 256 close(c) 257 // We call Get with an unavailable chunk, so it will create a fetcher and wait for delivery 258 _, err := netStore.Get(ctx, chunk.Address()) 259 260 // After the context is cancelled above Get should return with an error 261 if err != context.Canceled { 262 t.Fatalf("Expected context.Canceled err got %v", err) 263 } 264 265 // A fetcher was created, check if it has been removed after cancel 266 if netStore.fetchers.Len() != 0 { 267 t.Fatal("Expected netStore to remove the fetcher after cancel") 268 } 269 270 // Check if the fetcher context has been cancelled after the request context cancel 271 select { 272 case <-fetcher.ctx.Done(): 273 default: 274 t.Fatal("Expected fetcher context to be cancelled") 275 } 276 } 277 278 // TestNetStoreMultipleGetAndPut tests four Get calls for the same unavailable chunk. The chunk is 279 // delivered with a Put, we have to make sure all Get calls return, and they use a single fetcher 280 // for the chunk retrieval 281 func TestNetStoreMultipleGetAndPut(t *testing.T) { 282 netStore, fetcher := mustNewNetStoreWithFetcher(t) 283 284 chunk := GenerateRandomChunk(ch.DefaultSize) 285 286 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 287 defer cancel() 288 289 go func() { 290 // sleep to make sure Put is called after all the Get 291 time.Sleep(500 * time.Millisecond) 292 // check if netStore created exactly one fetcher for all Get calls 293 if netStore.fetchers.Len() != 1 { 294 t.Fatal("Expected netStore to use one fetcher for all Get calls") 295 } 296 err := netStore.Put(ctx, chunk) 297 if err != nil { 298 t.Fatalf("Expected no err got %v", err) 299 } 300 }() 301 302 // call Get 4 times for the same unavailable chunk. The calls will be blocked until the Put above. 303 getWG := sync.WaitGroup{} 304 for i := 0; i < 4; i++ { 305 getWG.Add(1) 306 go func() { 307 defer getWG.Done() 308 recChunk, err := netStore.Get(ctx, chunk.Address()) 309 if err != nil { 310 t.Fatalf("Expected no err got %v", err) 311 } 312 if !bytes.Equal(recChunk.Address(), chunk.Address()) || !bytes.Equal(recChunk.Data(), chunk.Data()) { 313 t.Fatalf("Different chunk received than what was put") 314 } 315 }() 316 } 317 318 finishedC := make(chan struct{}) 319 go func() { 320 getWG.Wait() 321 close(finishedC) 322 }() 323 324 // The Get calls should return after Put, so no timeout expected 325 select { 326 case <-finishedC: 327 case <-time.After(1 * time.Second): 328 t.Fatalf("Timeout waiting for Get calls to return") 329 } 330 331 // A fetcher was created, check if it has been removed after cancel 332 if netStore.fetchers.Len() != 0 { 333 t.Fatal("Expected netStore to remove the fetcher after delivery") 334 } 335 336 // A fetcher was created, check if it has been removed after delivery 337 select { 338 case <-fetcher.ctx.Done(): 339 default: 340 t.Fatal("Expected fetcher context to be cancelled") 341 } 342 343 } 344 345 // TestNetStoreFetchFuncTimeout tests a FetchFunc call for an unavailable chunk and waits for timeout 346 func TestNetStoreFetchFuncTimeout(t *testing.T) { 347 netStore, fetcher := mustNewNetStoreWithFetcher(t) 348 349 chunk := GenerateRandomChunk(ch.DefaultSize) 350 351 ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) 352 defer cancel() 353 354 // FetchFunc is called for an unavaible chunk, so the returned wait function should not be nil 355 wait := netStore.FetchFunc(ctx, chunk.Address()) 356 if wait == nil { 357 t.Fatal("Expected wait function to be not nil") 358 } 359 360 // There should an active fetcher for the chunk after the FetchFunc call 361 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 362 t.Fatalf("Expected netStore to have one fetcher for the requested chunk") 363 } 364 365 // wait function should timeout because we don't deliver the chunk with a Put 366 err := wait(ctx) 367 if err != context.DeadlineExceeded { 368 t.Fatalf("Expected context.DeadLineExceeded err got %v", err) 369 } 370 371 // the fetcher should be removed after timeout 372 if netStore.fetchers.Len() != 0 { 373 t.Fatal("Expected netStore to remove the fetcher after timeout") 374 } 375 376 // the fetcher context should be cancelled after timeout 377 select { 378 case <-fetcher.ctx.Done(): 379 default: 380 t.Fatal("Expected fetcher context to be cancelled") 381 } 382 } 383 384 // TestNetStoreFetchFuncAfterPut tests that the FetchFunc should return nil for a locally available chunk 385 func TestNetStoreFetchFuncAfterPut(t *testing.T) { 386 netStore := mustNewNetStore(t) 387 388 chunk := GenerateRandomChunk(ch.DefaultSize) 389 390 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 391 defer cancel() 392 393 // We deliver the created the chunk with a Put 394 err := netStore.Put(ctx, chunk) 395 if err != nil { 396 t.Fatalf("Expected no err got %v", err) 397 } 398 399 // FetchFunc should return nil, because the chunk is available locally, no need to fetch it 400 wait := netStore.FetchFunc(ctx, chunk.Address()) 401 if wait != nil { 402 t.Fatal("Expected wait to be nil") 403 } 404 405 // No fetchers should be created at all 406 if netStore.fetchers.Len() != 0 { 407 t.Fatal("Expected netStore to not have fetcher") 408 } 409 } 410 411 // TestNetStoreGetCallsRequest tests if Get created a request on the NetFetcher for an unavailable chunk 412 func TestNetStoreGetCallsRequest(t *testing.T) { 413 netStore, fetcher := mustNewNetStoreWithFetcher(t) 414 415 chunk := GenerateRandomChunk(ch.DefaultSize) 416 417 ctx := context.WithValue(context.Background(), "hopcount", uint8(5)) 418 ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) 419 defer cancel() 420 421 // We call get for a not available chunk, it will timeout because the chunk is not delivered 422 _, err := netStore.Get(ctx, chunk.Address()) 423 424 if err != context.DeadlineExceeded { 425 t.Fatalf("Expected context.DeadlineExceeded err got %v", err) 426 } 427 428 // NetStore should call NetFetcher.Request and wait for the chunk 429 if !fetcher.requestCalled { 430 t.Fatal("Expected NetFetcher.Request to be called") 431 } 432 433 if fetcher.hopCounts[0] != 5 { 434 t.Fatalf("Expected NetFetcher.Request be called with hopCount 5, got %v", fetcher.hopCounts[0]) 435 } 436 } 437 438 // TestNetStoreGetCallsOffer tests if Get created a request on the NetFetcher for an unavailable chunk 439 // in case of a source peer provided in the context. 440 func TestNetStoreGetCallsOffer(t *testing.T) { 441 netStore, fetcher := mustNewNetStoreWithFetcher(t) 442 443 chunk := GenerateRandomChunk(ch.DefaultSize) 444 445 // If a source peer is added to the context, NetStore will handle it as an offer 446 ctx := context.WithValue(context.Background(), "source", sourcePeerID.String()) 447 ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) 448 defer cancel() 449 450 // We call get for a not available chunk, it will timeout because the chunk is not delivered 451 chunk, err := netStore.Get(ctx, chunk.Address()) 452 453 if err != context.DeadlineExceeded { 454 t.Fatalf("Expect error %v got %v", context.DeadlineExceeded, err) 455 } 456 457 // NetStore should call NetFetcher.Offer with the source peer 458 if !fetcher.offerCalled { 459 t.Fatal("Expected NetFetcher.Request to be called") 460 } 461 462 if len(fetcher.sources) != 1 { 463 t.Fatalf("Expected fetcher sources length 1 got %v", len(fetcher.sources)) 464 } 465 466 if fetcher.sources[0].String() != sourcePeerID.String() { 467 t.Fatalf("Expected fetcher source %v got %v", sourcePeerID, fetcher.sources[0]) 468 } 469 470 } 471 472 // TestNetStoreFetcherCountPeers tests multiple NetStore.Get calls with peer in the context. 473 // There is no Put call, so the Get calls timeout 474 func TestNetStoreFetcherCountPeers(t *testing.T) { 475 476 netStore, fetcher := mustNewNetStoreWithFetcher(t) 477 478 addr := randomAddr() 479 peers := []string{randomAddr().Hex(), randomAddr().Hex(), randomAddr().Hex()} 480 481 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 482 defer cancel() 483 errC := make(chan error) 484 nrGets := 3 485 486 // Call Get 3 times with a peer in context 487 for i := 0; i < nrGets; i++ { 488 peer := peers[i] 489 go func() { 490 ctx := context.WithValue(ctx, "peer", peer) 491 _, err := netStore.Get(ctx, addr) 492 errC <- err 493 }() 494 } 495 496 // All 3 Get calls should timeout 497 for i := 0; i < nrGets; i++ { 498 err := <-errC 499 if err != context.DeadlineExceeded { 500 t.Fatalf("Expected \"%v\" error got \"%v\"", context.DeadlineExceeded, err) 501 } 502 } 503 504 // fetcher should be closed after timeout 505 select { 506 case <-fetcher.quit: 507 case <-time.After(3 * time.Second): 508 t.Fatalf("mockNetFetcher not closed after timeout") 509 } 510 511 // All 3 peers should be given to NetFetcher after the 3 Get calls 512 if len(fetcher.peersPerRequest) != nrGets { 513 t.Fatalf("Expected 3 got %v", len(fetcher.peersPerRequest)) 514 } 515 516 for i, peers := range fetcher.peersPerRequest { 517 if len(peers) < i+1 { 518 t.Fatalf("Expected at least %v got %v", i+1, len(peers)) 519 } 520 } 521 } 522 523 // TestNetStoreFetchFuncCalledMultipleTimes calls the wait function given by FetchFunc three times, 524 // and checks there is still exactly one fetcher for one chunk. Afthe chunk is delivered, it checks 525 // if the fetcher is closed. 526 func TestNetStoreFetchFuncCalledMultipleTimes(t *testing.T) { 527 netStore, fetcher := mustNewNetStoreWithFetcher(t) 528 529 chunk := GenerateRandomChunk(ch.DefaultSize) 530 531 ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) 532 defer cancel() 533 534 // FetchFunc should return a non-nil wait function, because the chunk is not available 535 wait := netStore.FetchFunc(ctx, chunk.Address()) 536 if wait == nil { 537 t.Fatal("Expected wait function to be not nil") 538 } 539 540 // There should be exactly one fetcher for the chunk 541 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 542 t.Fatalf("Expected netStore to have one fetcher for the requested chunk") 543 } 544 545 // Call wait three times parallelly 546 wg := sync.WaitGroup{} 547 for i := 0; i < 3; i++ { 548 wg.Add(1) 549 go func() { 550 err := wait(ctx) 551 if err != nil { 552 t.Fatalf("Expected no err got %v", err) 553 } 554 wg.Done() 555 }() 556 } 557 558 // sleep a little so the wait functions are called above 559 time.Sleep(100 * time.Millisecond) 560 561 // there should be still only one fetcher, because all wait calls are for the same chunk 562 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 563 t.Fatal("Expected netStore to have one fetcher for the requested chunk") 564 } 565 566 // Deliver the chunk with a Put 567 err := netStore.Put(ctx, chunk) 568 if err != nil { 569 t.Fatalf("Expected no err got %v", err) 570 } 571 572 // wait until all wait calls return (because the chunk is delivered) 573 wg.Wait() 574 575 // There should be no more fetchers for the delivered chunk 576 if netStore.fetchers.Len() != 0 { 577 t.Fatal("Expected netStore to remove the fetcher after delivery") 578 } 579 580 // The context for the fetcher should be cancelled after delivery 581 select { 582 case <-fetcher.ctx.Done(): 583 default: 584 t.Fatal("Expected fetcher context to be cancelled") 585 } 586 } 587 588 // TestNetStoreFetcherLifeCycleWithTimeout is similar to TestNetStoreFetchFuncCalledMultipleTimes, 589 // the only difference is that we don't deilver the chunk, just wait for timeout 590 func TestNetStoreFetcherLifeCycleWithTimeout(t *testing.T) { 591 netStore, fetcher := mustNewNetStoreWithFetcher(t) 592 593 chunk := GenerateRandomChunk(ch.DefaultSize) 594 595 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 596 defer cancel() 597 598 // FetchFunc should return a non-nil wait function, because the chunk is not available 599 wait := netStore.FetchFunc(ctx, chunk.Address()) 600 if wait == nil { 601 t.Fatal("Expected wait function to be not nil") 602 } 603 604 // There should be exactly one fetcher for the chunk 605 if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { 606 t.Fatalf("Expected netStore to have one fetcher for the requested chunk") 607 } 608 609 // Call wait three times parallelly 610 wg := sync.WaitGroup{} 611 for i := 0; i < 3; i++ { 612 wg.Add(1) 613 go func() { 614 defer wg.Done() 615 rctx, rcancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 616 defer rcancel() 617 err := wait(rctx) 618 if err != context.DeadlineExceeded { 619 t.Fatalf("Expected err %v got %v", context.DeadlineExceeded, err) 620 } 621 }() 622 } 623 624 // wait until all wait calls timeout 625 wg.Wait() 626 627 // There should be no more fetchers after timeout 628 if netStore.fetchers.Len() != 0 { 629 t.Fatal("Expected netStore to remove the fetcher after delivery") 630 } 631 632 // The context for the fetcher should be cancelled after timeout 633 select { 634 case <-fetcher.ctx.Done(): 635 default: 636 t.Fatal("Expected fetcher context to be cancelled") 637 } 638 } 639 640 func randomAddr() Address { 641 addr := make([]byte, 32) 642 rand.Read(addr) 643 return Address(addr) 644 }