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  }