github.com/ethersphere/bee/v2@v2.2.0/pkg/api/api_test.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package api_test 6 7 import ( 8 "bytes" 9 "context" 10 "crypto/ecdsa" 11 "crypto/rand" 12 "encoding/hex" 13 "encoding/json" 14 "errors" 15 "io" 16 "math/big" 17 "net" 18 "net/http" 19 "net/http/httptest" 20 "net/url" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethersphere/bee/v2/pkg/accesscontrol" 27 mockac "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" 28 accountingmock "github.com/ethersphere/bee/v2/pkg/accounting/mock" 29 "github.com/ethersphere/bee/v2/pkg/api" 30 "github.com/ethersphere/bee/v2/pkg/crypto" 31 "github.com/ethersphere/bee/v2/pkg/feeds" 32 "github.com/ethersphere/bee/v2/pkg/file/pipeline" 33 "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" 34 "github.com/ethersphere/bee/v2/pkg/file/redundancy" 35 "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest" 36 "github.com/ethersphere/bee/v2/pkg/log" 37 p2pmock "github.com/ethersphere/bee/v2/pkg/p2p/mock" 38 "github.com/ethersphere/bee/v2/pkg/pingpong" 39 "github.com/ethersphere/bee/v2/pkg/postage" 40 mockbatchstore "github.com/ethersphere/bee/v2/pkg/postage/batchstore/mock" 41 mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock" 42 "github.com/ethersphere/bee/v2/pkg/postage/postagecontract" 43 contractMock "github.com/ethersphere/bee/v2/pkg/postage/postagecontract/mock" 44 "github.com/ethersphere/bee/v2/pkg/pss" 45 "github.com/ethersphere/bee/v2/pkg/pusher" 46 "github.com/ethersphere/bee/v2/pkg/resolver" 47 resolverMock "github.com/ethersphere/bee/v2/pkg/resolver/mock" 48 "github.com/ethersphere/bee/v2/pkg/settlement/pseudosettle" 49 chequebookmock "github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook/mock" 50 "github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20" 51 erc20mock "github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20/mock" 52 swapmock "github.com/ethersphere/bee/v2/pkg/settlement/swap/mock" 53 "github.com/ethersphere/bee/v2/pkg/spinlock" 54 statestore "github.com/ethersphere/bee/v2/pkg/statestore/mock" 55 "github.com/ethersphere/bee/v2/pkg/status" 56 "github.com/ethersphere/bee/v2/pkg/steward" 57 "github.com/ethersphere/bee/v2/pkg/storage" 58 "github.com/ethersphere/bee/v2/pkg/storage/inmemstore" 59 testingc "github.com/ethersphere/bee/v2/pkg/storage/testing" 60 "github.com/ethersphere/bee/v2/pkg/storageincentives" 61 "github.com/ethersphere/bee/v2/pkg/storageincentives/redistribution" 62 "github.com/ethersphere/bee/v2/pkg/storageincentives/staking" 63 mock2 "github.com/ethersphere/bee/v2/pkg/storageincentives/staking/mock" 64 mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" 65 "github.com/ethersphere/bee/v2/pkg/swarm" 66 "github.com/ethersphere/bee/v2/pkg/topology/lightnode" 67 topologymock "github.com/ethersphere/bee/v2/pkg/topology/mock" 68 "github.com/ethersphere/bee/v2/pkg/tracing" 69 "github.com/ethersphere/bee/v2/pkg/transaction" 70 "github.com/ethersphere/bee/v2/pkg/transaction/backendmock" 71 transactionmock "github.com/ethersphere/bee/v2/pkg/transaction/mock" 72 "github.com/ethersphere/bee/v2/pkg/util/testutil" 73 "github.com/gorilla/websocket" 74 "resenje.org/web" 75 ) 76 77 var ( 78 batchInvalid = []byte{0} 79 batchOk = make([]byte, 32) 80 batchOkStr string 81 batchEmpty = []byte{} 82 ) 83 84 // nolint:gochecknoinits 85 func init() { 86 _, _ = rand.Read(batchOk) 87 88 batchOkStr = hex.EncodeToString(batchOk) 89 } 90 91 type testServerOptions struct { 92 Storer api.Storer 93 StateStorer storage.StateStorer 94 Resolver resolver.Interface 95 Pss pss.Interface 96 WsPath string 97 WsPingPeriod time.Duration 98 Logger log.Logger 99 PreventRedirect bool 100 Feeds feeds.Factory 101 CORSAllowedOrigins []string 102 PostageContract postagecontract.Interface 103 StakingContract staking.Contract 104 Post postage.Service 105 AccessControl accesscontrol.Controller 106 Steward steward.Interface 107 WsHeaders http.Header 108 DirectUpload bool 109 Probe *api.Probe 110 111 Overlay swarm.Address 112 PublicKey ecdsa.PublicKey 113 PSSPublicKey ecdsa.PublicKey 114 EthereumAddress common.Address 115 BlockTime time.Duration 116 P2P *p2pmock.Service 117 Pingpong pingpong.Interface 118 TopologyOpts []topologymock.Option 119 AccountingOpts []accountingmock.Option 120 ChequebookOpts []chequebookmock.Option 121 SwapOpts []swapmock.Option 122 TransactionOpts []transactionmock.Option 123 124 BatchStore postage.Storer 125 SyncStatus func() (bool, error) 126 127 BackendOpts []backendmock.Option 128 Erc20Opts []erc20mock.Option 129 BeeMode api.BeeNodeMode 130 RedistributionAgent *storageincentives.Agent 131 NodeStatus *status.Service 132 PinIntegrity api.PinIntegrity 133 WhitelistedAddr string 134 } 135 136 func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.Conn, string, *chanStorer) { 137 t.Helper() 138 pk, _ := crypto.GenerateSecp256k1Key() 139 signer := crypto.NewDefaultSigner(pk) 140 141 if o.Logger == nil { 142 o.Logger = log.Noop 143 } 144 if o.Resolver == nil { 145 o.Resolver = resolverMock.NewResolver() 146 } 147 if o.WsPingPeriod == 0 { 148 o.WsPingPeriod = 60 * time.Second 149 } 150 if o.Post == nil { 151 o.Post = mockpost.New() 152 } 153 if o.AccessControl == nil { 154 o.AccessControl = mockac.New() 155 } 156 if o.BatchStore == nil { 157 o.BatchStore = mockbatchstore.New(mockbatchstore.WithAcceptAllExistsFunc()) // default is with accept-all Exists() func 158 } 159 if o.SyncStatus == nil { 160 o.SyncStatus = func() (bool, error) { return true, nil } 161 } 162 163 var chanStore *chanStorer 164 165 topologyDriver := topologymock.NewTopologyDriver(o.TopologyOpts...) 166 acc := accountingmock.NewAccounting(o.AccountingOpts...) 167 settlement := swapmock.New(o.SwapOpts...) 168 chequebook := chequebookmock.NewChequebook(o.ChequebookOpts...) 169 ln := lightnode.NewContainer(o.Overlay) 170 171 transaction := transactionmock.New(o.TransactionOpts...) 172 173 storeRecipient := statestore.NewStateStore() 174 recipient := pseudosettle.New(nil, o.Logger, storeRecipient, nil, big.NewInt(10000), big.NewInt(10000), o.P2P) 175 176 if o.StateStorer == nil { 177 o.StateStorer = storeRecipient 178 } 179 erc20 := erc20mock.New(o.Erc20Opts...) 180 backend := backendmock.New(o.BackendOpts...) 181 182 var extraOpts = api.ExtraOptions{ 183 TopologyDriver: topologyDriver, 184 Accounting: acc, 185 Pseudosettle: recipient, 186 LightNodes: ln, 187 Swap: settlement, 188 Chequebook: chequebook, 189 Pingpong: o.Pingpong, 190 BlockTime: o.BlockTime, 191 Storer: o.Storer, 192 Resolver: o.Resolver, 193 Pss: o.Pss, 194 FeedFactory: o.Feeds, 195 Post: o.Post, 196 AccessControl: o.AccessControl, 197 PostageContract: o.PostageContract, 198 Steward: o.Steward, 199 SyncStatus: o.SyncStatus, 200 Staking: o.StakingContract, 201 NodeStatus: o.NodeStatus, 202 PinIntegrity: o.PinIntegrity, 203 } 204 205 // By default bee mode is set to full mode. 206 if o.BeeMode == api.UnknownMode { 207 o.BeeMode = api.FullMode 208 } 209 210 s := api.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, []string{o.WhitelistedAddr}, o.Logger, transaction, o.BatchStore, o.BeeMode, true, true, backend, o.CORSAllowedOrigins, inmemstore.New()) 211 testutil.CleanupCloser(t, s) 212 213 s.SetP2P(o.P2P) 214 215 if o.RedistributionAgent == nil { 216 o.RedistributionAgent, _ = createRedistributionAgentService(t, o.Overlay, o.StateStorer, erc20, transaction, backend, o.BatchStore) 217 s.SetRedistributionAgent(o.RedistributionAgent) 218 } 219 testutil.CleanupCloser(t, o.RedistributionAgent) 220 221 s.SetSwarmAddress(&o.Overlay) 222 s.SetProbe(o.Probe) 223 224 noOpTracer, tracerCloser, _ := tracing.NewTracer(&tracing.Options{ 225 Enabled: false, 226 }) 227 testutil.CleanupCloser(t, tracerCloser) 228 229 s.Configure(signer, noOpTracer, api.Options{ 230 CORSAllowedOrigins: o.CORSAllowedOrigins, 231 WsPingPeriod: o.WsPingPeriod, 232 }, extraOpts, 1, erc20) 233 234 s.MountTechnicalDebug() 235 s.MountDebug() 236 s.MountAPI() 237 238 if o.DirectUpload { 239 chanStore = newChanStore(o.Storer.PusherFeed()) 240 t.Cleanup(chanStore.stop) 241 } 242 243 ts := httptest.NewServer(s) 244 t.Cleanup(ts.Close) 245 246 var ( 247 httpClient = &http.Client{ 248 Transport: web.RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 249 requestURL := r.URL.String() 250 if r.URL.Scheme != "http" { 251 requestURL = ts.URL + r.URL.String() 252 } 253 u, err := url.Parse(requestURL) 254 if err != nil { 255 return nil, err 256 } 257 r.URL = u 258 259 transport := ts.Client().Transport.(*http.Transport) 260 transport = transport.Clone() 261 // always dial to the server address, regardless of the url host and port 262 transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { 263 return net.Dial(network, ts.Listener.Addr().String()) 264 } 265 return transport.RoundTrip(r) 266 }), 267 } 268 conn *websocket.Conn 269 err error 270 ) 271 272 if o.WsPath != "" { 273 u := url.URL{Scheme: "ws", Host: ts.Listener.Addr().String(), Path: o.WsPath} 274 conn, _, err = websocket.DefaultDialer.Dial(u.String(), o.WsHeaders) 275 if err != nil { 276 t.Fatalf("dial: %v. url %v", err, u.String()) 277 } 278 testutil.CleanupCloser(t, conn) 279 } 280 281 if o.PreventRedirect { 282 httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { 283 return http.ErrUseLastResponse 284 } 285 } 286 287 return httpClient, conn, ts.Listener.Addr().String(), chanStore 288 } 289 290 func request(t *testing.T, client *http.Client, method, resource string, body io.Reader, responseCode int) *http.Response { 291 t.Helper() 292 293 req, err := http.NewRequestWithContext(context.TODO(), method, resource, body) 294 if err != nil { 295 t.Fatal(err) 296 } 297 resp, err := client.Do(req) 298 if err != nil { 299 t.Fatal(err) 300 } 301 if resp.StatusCode != responseCode { 302 t.Fatalf("got response status %s, want %v %s", resp.Status, responseCode, http.StatusText(responseCode)) 303 } 304 return resp 305 } 306 307 func pipelineFactory(s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { 308 return func() pipeline.Interface { 309 return builder.NewPipelineBuilder(context.Background(), s, encrypt, rLevel) 310 } 311 } 312 313 func TestParseName(t *testing.T) { 314 t.Parallel() 315 316 const bzzHash = "89c17d0d8018a19057314aa035e61c9d23c47581a61dd3a79a7839692c617e4d" 317 log := log.Noop 318 319 var errInvalidNameOrAddress = errors.New("invalid name or bzz address") 320 321 testCases := []struct { 322 desc string 323 name string 324 res resolver.Interface 325 noResolver bool 326 wantAdr swarm.Address 327 wantErr error 328 }{ 329 { 330 desc: "empty name", 331 name: "", 332 wantAdr: swarm.ZeroAddress, 333 }, 334 { 335 desc: "bzz hash", 336 name: bzzHash, 337 wantAdr: swarm.MustParseHexAddress(bzzHash), 338 }, 339 { 340 desc: "no resolver connected with bzz hash", 341 name: bzzHash, 342 noResolver: true, 343 wantAdr: swarm.MustParseHexAddress(bzzHash), 344 }, 345 { 346 desc: "no resolver connected with name", 347 name: "itdoesntmatter.eth", 348 noResolver: true, 349 wantErr: api.ErrNoResolver, 350 }, 351 { 352 desc: "name not resolved", 353 name: "not.good", 354 res: resolverMock.NewResolver( 355 resolverMock.WithResolveFunc(func(string) (swarm.Address, error) { 356 return swarm.ZeroAddress, errInvalidNameOrAddress 357 }), 358 ), 359 wantErr: errInvalidNameOrAddress, 360 }, 361 { 362 desc: "name resolved", 363 name: "everything.okay", 364 wantAdr: swarm.MustParseHexAddress("89c17d0d8018a19057314aa035e61c9d23c47581a61dd3a79a7839692c617e4d"), 365 }, 366 } 367 for _, tC := range testCases { 368 if tC.res == nil && !tC.noResolver { 369 tC.res = resolverMock.NewResolver( 370 resolverMock.WithResolveFunc(func(string) (swarm.Address, error) { 371 return tC.wantAdr, nil 372 })) 373 } 374 375 pk, _ := crypto.GenerateSecp256k1Key() 376 signer := crypto.NewDefaultSigner(pk) 377 378 s := api.New(pk.PublicKey, pk.PublicKey, common.Address{}, nil, log, nil, nil, 1, false, false, nil, []string{"*"}, inmemstore.New()) 379 s.Configure(signer, nil, api.Options{}, api.ExtraOptions{Resolver: tC.res}, 1, nil) 380 s.MountAPI() 381 382 tC := tC 383 t.Run(tC.desc, func(t *testing.T) { 384 t.Parallel() 385 386 got, err := s.ResolveNameOrAddress(tC.name) 387 if tC.wantErr != nil && !errors.Is(err, tC.wantErr) { 388 t.Fatalf("bad error: %v", err) 389 } 390 if !got.Equal(tC.wantAdr) { 391 t.Errorf("got %s, want %s", got, tC.wantAdr) 392 } 393 }) 394 } 395 } 396 397 // TestCalculateNumberOfChunks is a unit test for 398 // the chunk-number-according-to-content-length calculation. 399 func TestCalculateNumberOfChunks(t *testing.T) { 400 t.Parallel() 401 402 for _, tc := range []struct{ len, chunks int64 }{ 403 {len: 1000, chunks: 1}, 404 {len: 5000, chunks: 3}, 405 {len: 10000, chunks: 4}, 406 {len: 100000, chunks: 26}, 407 {len: 1000000, chunks: 248}, 408 {len: 325839339210, chunks: 79550620 + 621490 + 4856 + 38 + 1}, 409 } { 410 res := api.CalculateNumberOfChunks(tc.len, false) 411 if res != tc.chunks { 412 t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res) 413 } 414 } 415 } 416 417 // TestCalculateNumberOfChunksEncrypted is a unit test for 418 // the chunk-number-according-to-content-length calculation with encryption 419 // (branching factor=64) 420 func TestCalculateNumberOfChunksEncrypted(t *testing.T) { 421 t.Parallel() 422 423 for _, tc := range []struct{ len, chunks int64 }{ 424 {len: 1000, chunks: 1}, 425 {len: 5000, chunks: 3}, 426 {len: 10000, chunks: 4}, 427 {len: 100000, chunks: 26}, 428 {len: 1000000, chunks: 245 + 4 + 1}, 429 {len: 325839339210, chunks: 79550620 + 1242979 + 19422 + 304 + 5 + 1}, 430 } { 431 res := api.CalculateNumberOfChunks(tc.len, true) 432 if res != tc.chunks { 433 t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res) 434 } 435 } 436 } 437 438 // TestPostageHeaderError tests that incorrect postage batch ids 439 // provided to the api correct the appropriate error code. 440 func TestPostageHeaderError(t *testing.T) { 441 t.Parallel() 442 443 var ( 444 mockStorer = mockstorer.New() 445 endpoints = []string{ 446 "bytes", "bzz", "chunks", 447 } 448 ) 449 content := []byte{7: 0} // 8 zeros 450 for _, endpoint := range endpoints { 451 endpoint := endpoint 452 t.Run(endpoint+": empty batch", func(t *testing.T) { 453 t.Parallel() 454 455 client, _, _, _ := newTestServer(t, testServerOptions{ 456 Storer: mockStorer, 457 Post: newTestPostService(), 458 DirectUpload: true, 459 }) 460 hexbatch := hex.EncodeToString(batchEmpty) 461 expCode := http.StatusBadRequest 462 jsonhttptest.Request(t, client, http.MethodPost, "/"+endpoint, expCode, 463 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch), 464 jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "application/octet-stream"), 465 jsonhttptest.WithRequestBody(bytes.NewReader(content)), 466 ) 467 }) 468 t.Run(endpoint+": ok batch", func(t *testing.T) { 469 t.Parallel() 470 client, _, _, _ := newTestServer(t, testServerOptions{ 471 Storer: mockStorer, 472 Post: newTestPostService(), 473 DirectUpload: true, 474 }) 475 hexbatch := hex.EncodeToString(batchOk) 476 expCode := http.StatusCreated 477 jsonhttptest.Request(t, client, http.MethodPost, "/"+endpoint, expCode, 478 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch), 479 jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "application/octet-stream"), 480 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), 481 jsonhttptest.WithRequestBody(bytes.NewReader(content)), 482 ) 483 }) 484 t.Run(endpoint+": bad batch", func(t *testing.T) { 485 t.Parallel() 486 client, _, _, _ := newTestServer(t, testServerOptions{ 487 Storer: mockStorer, 488 Post: newTestPostService(), 489 DirectUpload: true, 490 }) 491 hexbatch := hex.EncodeToString(batchInvalid) 492 expCode := http.StatusNotFound 493 jsonhttptest.Request(t, client, http.MethodPost, "/"+endpoint, expCode, 494 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch), 495 jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "application/octet-stream"), 496 jsonhttptest.WithRequestBody(bytes.NewReader(content)), 497 ) 498 }) 499 } 500 } 501 502 // TestOptions check whether endpoint compatible with option method 503 func TestOptions(t *testing.T) { 504 t.Parallel() 505 506 var ( 507 client, _, _, _ = newTestServer(t, testServerOptions{}) 508 ) 509 for _, tc := range []struct { 510 endpoint string 511 expectedMethods string // expectedMethods contains HTTP methods like GET, POST, HEAD, PATCH, DELETE, OPTIONS. These are in alphabetical sorted order 512 }{ 513 { 514 endpoint: "tags", 515 expectedMethods: "GET, POST", 516 }, 517 { 518 endpoint: "bzz", 519 expectedMethods: "POST", 520 }, 521 { 522 endpoint: "chunks", 523 expectedMethods: "POST", 524 }, 525 { 526 endpoint: "chunks/123213", 527 expectedMethods: "GET, HEAD", 528 }, 529 { 530 endpoint: "bytes", 531 expectedMethods: "POST", 532 }, 533 { 534 endpoint: "bytes/0121012", 535 expectedMethods: "GET, HEAD", 536 }, 537 } { 538 tc := tc 539 t.Run(tc.endpoint+" options test", func(t *testing.T) { 540 t.Parallel() 541 542 jsonhttptest.Request(t, client, http.MethodOptions, "/"+tc.endpoint, http.StatusNoContent, 543 jsonhttptest.WithExpectedResponseHeader("Allow", tc.expectedMethods), 544 ) 545 }) 546 } 547 } 548 549 // TestPostageDirectAndDeferred tests that incorrect postage batch ids 550 // provided to the api correct the appropriate error code. 551 func TestPostageDirectAndDeferred(t *testing.T) { 552 t.Parallel() 553 554 for _, endpoint := range []string{"bytes", "bzz", "chunks"} { 555 endpoint := endpoint 556 557 if endpoint != "chunks" { 558 t.Run(endpoint+" deferred", func(t *testing.T) { 559 t.Parallel() 560 561 mockStorer := mockstorer.New() 562 client, _, _, chanStorer := newTestServer(t, testServerOptions{ 563 Storer: mockStorer, 564 Post: newTestPostService(), 565 DirectUpload: true, 566 }) 567 hexbatch := hex.EncodeToString(batchOk) 568 chunk := testingc.GenerateTestRandomChunk() 569 var responseBytes []byte 570 jsonhttptest.Request(t, client, http.MethodPost, "/"+endpoint, http.StatusCreated, 571 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch), 572 jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "application/octet-stream"), 573 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), 574 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 575 jsonhttptest.WithPutResponseBody(&responseBytes), 576 ) 577 var body struct { 578 Reference swarm.Address `json:"reference"` 579 } 580 if err := json.Unmarshal(responseBytes, &body); err != nil { 581 t.Fatal("unmarshal response body:", err) 582 } 583 if found, _ := mockStorer.ChunkStore().Has(context.Background(), body.Reference); !found { 584 t.Fatal("chunk not found in the store") 585 } 586 // sleep to allow chanStorer to drain if any to ensure we haven't seen the chunk 587 time.Sleep(100 * time.Millisecond) 588 if chanStorer.Has(body.Reference) { 589 t.Fatal("chunk was not expected to be present in direct channel") 590 } 591 }) 592 } 593 594 t.Run(endpoint+" direct upload", func(t *testing.T) { 595 t.Parallel() 596 597 mockStorer := mockstorer.New() 598 client, _, _, chanStorer := newTestServer(t, testServerOptions{ 599 Storer: mockStorer, 600 Post: newTestPostService(), 601 DirectUpload: true, 602 }) 603 hexbatch := hex.EncodeToString(batchOk) 604 chunk := testingc.GenerateTestRandomChunk() 605 var responseBytes []byte 606 jsonhttptest.Request(t, client, http.MethodPost, "/"+endpoint, http.StatusCreated, 607 jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, hexbatch), 608 jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "application/octet-stream"), 609 jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "false"), 610 jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), 611 jsonhttptest.WithPutResponseBody(&responseBytes), 612 ) 613 614 var body struct { 615 Reference swarm.Address `json:"reference"` 616 } 617 if err := json.Unmarshal(responseBytes, &body); err != nil { 618 t.Fatal("unmarshal response body:", err) 619 } 620 err := spinlock.Wait(time.Second, func() bool { return chanStorer.Has(body.Reference) }) 621 if err != nil { 622 t.Fatal("chunk not found in direct channel") 623 } 624 if found, _ := mockStorer.ChunkStore().Has(context.Background(), body.Reference); found { 625 t.Fatal("chunk was not expected to be present in store") 626 } 627 }) 628 } 629 } 630 631 type chanStorer struct { 632 lock sync.Mutex 633 chunks map[string]struct{} 634 quit chan struct{} 635 } 636 637 func newChanStore(cc <-chan *pusher.Op) *chanStorer { 638 c := &chanStorer{ 639 chunks: make(map[string]struct{}), 640 quit: make(chan struct{}), 641 } 642 go c.drain(cc) 643 return c 644 } 645 646 func (c *chanStorer) drain(cc <-chan *pusher.Op) { 647 for { 648 select { 649 case op := <-cc: 650 c.lock.Lock() 651 c.chunks[op.Chunk.Address().ByteString()] = struct{}{} 652 c.lock.Unlock() 653 op.Err <- nil 654 case <-c.quit: 655 return 656 } 657 } 658 } 659 660 func (c *chanStorer) stop() { 661 close(c.quit) 662 } 663 664 func (c *chanStorer) Has(addr swarm.Address) bool { 665 c.lock.Lock() 666 _, ok := c.chunks[addr.ByteString()] 667 c.lock.Unlock() 668 669 return ok 670 } 671 672 func createRedistributionAgentService( 673 t *testing.T, 674 addr swarm.Address, 675 storer storage.StateStorer, 676 erc20Service erc20.Service, 677 tranService transaction.Service, 678 backend storageincentives.ChainBackend, 679 chainStateGetter postage.ChainStateGetter, 680 ) (*storageincentives.Agent, error) { 681 t.Helper() 682 683 const blocksPerRound uint64 = 12 684 const blocksPerPhase uint64 = 4 685 postageContract := contractMock.New(contractMock.WithExpiresBatchesFunc(func(context.Context) error { 686 return nil 687 }), 688 ) 689 stakingContract := mock2.New(mock2.WithIsFrozen(func(context.Context, uint64) (bool, error) { 690 return true, nil 691 })) 692 contract := &mockContract{} 693 694 return storageincentives.New( 695 addr, 696 common.Address{}, 697 backend, 698 contract, 699 postageContract, 700 stakingContract, 701 mockstorer.NewReserve(), 702 func() bool { return true }, 703 time.Millisecond*10, 704 blocksPerRound, 705 blocksPerPhase, 706 storer, 707 chainStateGetter, 708 erc20Service, 709 tranService, 710 &mockHealth{}, 711 log.Noop, 712 ) 713 } 714 715 type contractCall int 716 717 func (c contractCall) String() string { 718 switch c { 719 case isWinnerCall: 720 return "isWinnerCall" 721 case revealCall: 722 return "revealCall" 723 case commitCall: 724 return "commitCall" 725 case claimCall: 726 return "claimCall" 727 } 728 return "unknown" 729 } 730 731 const ( 732 isWinnerCall contractCall = iota 733 revealCall 734 commitCall 735 claimCall 736 ) 737 738 type mockContract struct { 739 callsList []contractCall 740 mtx sync.Mutex 741 } 742 743 func (m *mockContract) Fee(ctx context.Context, txHash common.Hash) *big.Int { 744 return big.NewInt(1000) 745 } 746 747 func (m *mockContract) ReserveSalt(context.Context) ([]byte, error) { 748 return nil, nil 749 } 750 751 func (m *mockContract) IsPlaying(context.Context, uint8) (bool, error) { 752 return true, nil 753 } 754 755 func (m *mockContract) IsWinner(context.Context) (bool, error) { 756 m.mtx.Lock() 757 defer m.mtx.Unlock() 758 m.callsList = append(m.callsList, isWinnerCall) 759 return false, nil 760 } 761 762 func (m *mockContract) Claim(context.Context, redistribution.ChunkInclusionProofs) (common.Hash, error) { 763 m.mtx.Lock() 764 defer m.mtx.Unlock() 765 m.callsList = append(m.callsList, claimCall) 766 return common.Hash{}, nil 767 } 768 769 func (m *mockContract) Commit(context.Context, []byte, uint64) (common.Hash, error) { 770 m.mtx.Lock() 771 defer m.mtx.Unlock() 772 m.callsList = append(m.callsList, commitCall) 773 return common.Hash{}, nil 774 } 775 776 func (m *mockContract) Reveal(context.Context, uint8, []byte, []byte) (common.Hash, error) { 777 m.mtx.Lock() 778 defer m.mtx.Unlock() 779 m.callsList = append(m.callsList, revealCall) 780 return common.Hash{}, nil 781 } 782 783 type mockHealth struct{} 784 785 func (m *mockHealth) IsHealthy() bool { return true } 786 787 func newTestPostService() postage.Service { 788 return mockpost.New( 789 mockpost.WithIssuer(postage.NewStampIssuer( 790 "", 791 "", 792 batchOk, 793 big.NewInt(3), 794 11, 795 10, 796 1000, 797 true, 798 )), 799 ) 800 }