diff --git a/Makefile b/Makefile
index 645cbb31b5f..b8968c3a584 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,9 @@ REACHABILITY_OVERRIDE_PUBLIC ?= false
BATCHFACTOR_OVERRIDE_PUBLIC ?= 5
BEE_IMAGE ?= ethersphere/bee:latest
PLATFORM ?= linux/amd64
+# go test defaults to 10m per package binary, which the pkg/api suite exceeds
+# under the race detector on the slower CI runners.
+TEST_TIMEOUT ?= 30m
BEE_API_VERSION ?= "$(shell grep '^ version:' openapi/Swarm.yaml | awk '{print $$2}')"
@@ -99,9 +102,9 @@ check-whitespace:
.PHONY: test-race
test-race:
ifdef cover
- $(GO) test -race -failfast -coverprofile=cover.out -v ./...
+ $(GO) test -race -failfast -timeout $(TEST_TIMEOUT) -coverprofile=cover.out -v ./...
else
- $(GO) test -race -failfast -v ./...
+ $(GO) test -race -failfast -timeout $(TEST_TIMEOUT) -v ./...
endif
.PHONY: test-integration
@@ -127,9 +130,9 @@ endif
.PHONY: test-ci-race
test-ci-race:
ifdef cover
- $(GO) test -race -coverprofile=cover.out ./...
+ $(GO) test -race -timeout $(TEST_TIMEOUT) -coverprofile=cover.out ./...
else
- $(GO) test -race ./...
+ $(GO) test -race -timeout $(TEST_TIMEOUT) ./...
endif
.PHONY: build
diff --git a/openapi/Swarm.yaml b/openapi/Swarm.yaml
index c1c13f2c784..cdabd8635cc 100644
--- a/openapi/Swarm.yaml
+++ b/openapi/Swarm.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
- version: 8.1.1
+ version: 8.2.0
title: Bee API
description: "API endpoints for interacting with the Swarm network, supporting file operations, messaging, and node management"
@@ -967,9 +967,23 @@ paths:
$ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress"
required: true
description: "Single Owner Chunk address (which may have multiple payloads)"
- responses:
- "200":
- description: Establishes a WebSocket subscription for incoming messages on the Single Owner Chunk address
+ - $ref: "SwarmCommon.yaml#/components/parameters/SwarmSocFieldsParameter"
+ - $ref: "SwarmCommon.yaml#/components/parameters/SwarmCacheWrappedChunkParameter"
+ - $ref: "SwarmCommon.yaml#/components/parameters/SwarmSocFieldsQueryParameter"
+ - $ref: "SwarmCommon.yaml#/components/parameters/SwarmCacheWrappedChunkQueryParameter"
+ responses:
+ "200":
+ description: >
+ Establishes a WebSocket subscription for incoming messages on the
+ Single Owner Chunk address. Each message is the binary serialization
+ of the Single Owner Chunk fields requested through the
+ swarm-soc-fields header or query parameter (defaults to the wrapped
+ chunk payload).
+ Pending messages are buffered per subscription up to a fixed limit;
+ a client that does not keep up with the incoming rate misses the
+ oldest of them, as they are discarded in favor of the newest ones.
+ "400":
+ $ref: "SwarmCommon.yaml#/components/responses/400"
"500":
$ref: "SwarmCommon.yaml#/components/responses/500"
default:
diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml
index 0634846b2a5..39f08775ac6 100644
--- a/openapi/SwarmCommon.yaml
+++ b/openapi/SwarmCommon.yaml
@@ -1180,6 +1180,54 @@ components:
required: false
description: Associate upload with an existing Tag UID
+ SwarmSocFieldsParameter:
+ in: header
+ name: swarm-soc-fields
+ schema:
+ type: string
+ default: "payload"
+ required: false
+ description: >
+ Comma separated list of Single Owner Chunk fields to be serialized and
+ channeled on every incoming GSOC message, in the given order. Allowed
+ values are: address, recoveredPubKey, identifier, signature,
+ wrappedAddress, span, payload. When omitted it defaults to "payload".
+ In order to have random access on the response bytes define payload
+ as the last field in the list since it has variable length.
+
+ SwarmCacheWrappedChunkParameter:
+ in: header
+ name: swarm-cache-wrapped-chunk
+ schema:
+ type: boolean
+ required: false
+ description: >
+ Indicates whether the wrapped chunk of every incoming GSOC message should
+ be cached locally so that it can be resolved through the bytes endpoint
+ (useful when the single owner chunk wraps a root chunk larger than 4KB).
+
+ SwarmSocFieldsQueryParameter:
+ in: query
+ name: swarm-soc-fields
+ schema:
+ type: string
+ required: false
+ description: >
+ Same as the swarm-soc-fields header, for clients that cannot set request
+ headers (e.g. browser WebSocket). Takes precedence over the header when
+ both are given.
+
+ SwarmCacheWrappedChunkQueryParameter:
+ in: query
+ name: swarm-cache-wrapped-chunk
+ schema:
+ type: boolean
+ required: false
+ description: >
+ Same as the swarm-cache-wrapped-chunk header, for clients that cannot set
+ request headers (e.g. browser WebSocket). Takes precedence over the header
+ when both are given.
+
SwarmPinParameter:
in: header
name: swarm-pin
diff --git a/pkg/api/accesscontrol_test.go b/pkg/api/accesscontrol_test.go
index c6c889f58c2..946c732dceb 100644
--- a/pkg/api/accesscontrol_test.go
+++ b/pkg/api/accesscontrol_test.go
@@ -153,7 +153,7 @@ func TestAccessLogicEachEndpointWithAct(t *testing.T) {
upTestOpts = append(upTestOpts, jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"))
}
t.Run(v.name, func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -213,7 +213,7 @@ func TestAccessLogicWithoutAct(t *testing.T) {
)
t.Run("upload-w/-act-then-download-w/o-act", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -246,7 +246,7 @@ func TestAccessLogicWithoutAct(t *testing.T) {
})
t.Run("upload-w/o-act-then-download-w/-act", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -310,7 +310,7 @@ func TestAccessLogicInvalidPath(t *testing.T) {
)
t.Run("invalid-path-params", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -361,7 +361,7 @@ func TestAccessLogicHistory(t *testing.T) {
)
t.Run("empty-history-upload-then-download-and-check-data", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -397,7 +397,7 @@ func TestAccessLogicHistory(t *testing.T) {
})
t.Run("with-history-upload-then-download-and-check-data", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -443,7 +443,7 @@ func TestAccessLogicHistory(t *testing.T) {
})
t.Run("upload-then-download-wrong-history", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -479,7 +479,7 @@ func TestAccessLogicHistory(t *testing.T) {
})
t.Run("upload-wrong-history", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -502,7 +502,7 @@ func TestAccessLogicHistory(t *testing.T) {
})
t.Run("download-w/o-history", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -538,7 +538,7 @@ func TestAccessLogicTimestamp(t *testing.T) {
fileName = "sample.html"
)
t.Run("upload-then-download-with-timestamp-and-check-data", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -586,7 +586,7 @@ func TestAccessLogicTimestamp(t *testing.T) {
t.Run("download-w/o-timestamp", func(t *testing.T) {
encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade"
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -601,7 +601,7 @@ func TestAccessLogicTimestamp(t *testing.T) {
)
})
t.Run("download-w/-invalid-timestamp", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -649,7 +649,7 @@ func TestAccessLogicPublisher(t *testing.T) {
)
t.Run("upload-then-download-w/-publisher-and-check-data", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -695,7 +695,7 @@ func TestAccessLogicPublisher(t *testing.T) {
})
t.Run("upload-then-download-invalid-publickey", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -753,7 +753,7 @@ func TestAccessLogicPublisher(t *testing.T) {
downloader = "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2"
encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade"
)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -778,7 +778,7 @@ func TestAccessLogicPublisher(t *testing.T) {
downloader = "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2"
testfile = "testfile1"
)
- downloaderClient, _, _, _ := newTestServer(t, testServerOptions{
+ downloaderClient, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -800,7 +800,7 @@ func TestAccessLogicPublisher(t *testing.T) {
t.Run("download-w/o-publisher", func(t *testing.T) {
encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade"
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -820,13 +820,13 @@ func TestAccessLogicPublisher(t *testing.T) {
func TestAccessLogicGrantees(t *testing.T) {
t.Parallel()
var (
- spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa")
- pk, _ = crypto.DecodeSecp256k1PrivateKey(spk)
- storerMock = mockstorer.New()
- h, fixtureHref = prepareHistoryFixture(storerMock)
- logger = log.Noop
- addr = swarm.RandAddress(t)
- client, _, _, _ = newTestServer(t, testServerOptions{
+ spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa")
+ pk, _ = crypto.DecodeSecp256k1PrivateKey(spk)
+ storerMock = mockstorer.New()
+ h, fixtureHref = prepareHistoryFixture(storerMock)
+ logger = log.Noop
+ addr = swarm.RandAddress(t)
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -839,7 +839,7 @@ func TestAccessLogicGrantees(t *testing.T) {
publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey)
publisher = hex.EncodeToString(publicKeyBytes)
)
- clientwihtpublisher, _, _, _ := newTestServer(t, testServerOptions{
+ clientwihtpublisher, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
diff --git a/pkg/api/accounting_test.go b/pkg/api/accounting_test.go
index 901ca8f2883..5067bfb516b 100644
--- a/pkg/api/accounting_test.go
+++ b/pkg/api/accounting_test.go
@@ -55,7 +55,7 @@ func TestAccountingInfo(t *testing.T) {
return ret, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithPeerAccountingFunc(accountingFunc)},
})
@@ -109,7 +109,7 @@ func TestAccountingInfoError(t *testing.T) {
accountingFunc := func() (map[string]accounting.PeerInfo, error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithPeerAccountingFunc(accountingFunc)},
})
diff --git a/pkg/api/api.go b/pkg/api/api.go
index a6b74837ef4..c9e0d19b969 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -96,6 +96,8 @@ const (
SwarmActTimestampHeader = "Swarm-Act-Timestamp"
SwarmActPublisherHeader = "Swarm-Act-Publisher"
SwarmActHistoryAddressHeader = "Swarm-Act-History-Address"
+ SwarmSocFieldsHeader = "Swarm-Soc-Fields"
+ SwarmCacheWrappedChunkHeader = "Swarm-Cache-Wrapped-Chunk"
ImmutableHeader = "Immutable"
GasPriceHeader = "Gas-Price"
@@ -180,6 +182,17 @@ type Service struct {
wsWg sync.WaitGroup // wait for all websockets to close on exit
quit chan struct{}
+ // bgCtx is the context form of quit: it is canceled by Close and bounds
+ // node-local work that a handler starts and that has to outlive the
+ // request or connection which triggered it.
+ bgCtx context.Context
+ bgCancel context.CancelFunc
+
+ // gsocCacheSubs holds the caching subscriptions shared by the GSOC
+ // websocket subscribers, keyed by GSOC address, see gsoc.go.
+ gsocCacheMu sync.Mutex
+ gsocCacheSubs map[string]*gsocCacheSub
+
overlay *swarm.Address
publicKey ecdsa.PublicKey
pssPublicKey ecdsa.PublicKey
@@ -340,6 +353,8 @@ func (s *Service) Configure(signer crypto.Signer, tracer *tracing.Tracer, o Opti
s.metrics = newMetrics()
s.quit = make(chan struct{})
+ s.bgCtx, s.bgCancel = context.WithCancel(context.Background())
+ s.gsocCacheSubs = make(map[string]*gsocCacheSub)
s.storer = e.Storer
s.resolver = e.Resolver
@@ -400,6 +415,7 @@ func (s *Service) SetIsWarmingUp(v bool) {
func (s *Service) Close() error {
s.logger.Info("api shutting down")
close(s.quit)
+ s.bgCancel()
done := make(chan struct{})
go func() {
@@ -607,6 +623,7 @@ func (s *Service) corsHandler(h http.Handler) http.Handler {
SwarmRedundancyStrategyHeader, SwarmRedundancyFallbackModeHeader, SwarmChunkRetrievalTimeoutHeader, SwarmLookAheadBufferSizeHeader,
SwarmFeedIndexHeader, SwarmFeedIndexNextHeader, SwarmSocSignatureHeader, SwarmOnlyRootChunk, GasPriceHeader, GasLimitHeader, ImmutableHeader,
SwarmActHeader, SwarmActTimestampHeader, SwarmActPublisherHeader, SwarmActHistoryAddressHeader,
+ SwarmSocFieldsHeader, SwarmCacheWrappedChunkHeader,
}
allowedHeadersStr := strings.Join(allowedHeaders, ", ")
diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go
index e6be53ea4e4..02de8ce6d68 100644
--- a/pkg/api/api_test.go
+++ b/pkg/api/api_test.go
@@ -108,6 +108,7 @@ type testServerOptions struct {
AccessControl accesscontrol.Controller
Steward steward.Interface
WsHeaders http.Header
+ WsQuery url.Values
DirectUpload bool
Probe *api.Probe
@@ -141,7 +142,12 @@ type testServerOptions struct {
Erc20ServiceNil bool
}
-func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.Conn, string, *chanStorer) {
+// newTestServer returns an http client and, when o.WsPath is set, a websocket
+// connection, both wired to an httptest.Server serving the api service, the
+// address that server listens on, the chan storer set up by o.DirectUpload and
+// the api service itself, for tests that need to drive it directly (e.g. over
+// a custom net.Listener) instead of through the httptest.Server.
+func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.Conn, string, *chanStorer, *api.Service) {
t.Helper()
pk, _ := crypto.GenerateSecp256k1Key()
signer := crypto.NewDefaultSigner(pk)
@@ -290,7 +296,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
)
if o.WsPath != "" {
- u := url.URL{Scheme: "ws", Host: ts.Listener.Addr().String(), Path: o.WsPath}
+ u := url.URL{Scheme: "ws", Host: ts.Listener.Addr().String(), Path: o.WsPath, RawQuery: o.WsQuery.Encode()}
conn, _, err = websocket.DefaultDialer.Dial(u.String(), o.WsHeaders)
if err != nil {
t.Fatalf("dial: %v. url %v", err, u.String())
@@ -304,7 +310,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
}
}
- return httpClient, conn, ts.Listener.Addr().String(), chanStore
+ return httpClient, conn, ts.Listener.Addr().String(), chanStore, s
}
func pipelineFactory(s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface {
@@ -454,7 +460,7 @@ func TestPostageHeaderError(t *testing.T) {
t.Run(endpoint+": empty batch", func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -469,7 +475,7 @@ func TestPostageHeaderError(t *testing.T) {
})
t.Run(endpoint+": ok batch", func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -485,7 +491,7 @@ func TestPostageHeaderError(t *testing.T) {
})
t.Run(endpoint+": bad batch", func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -505,7 +511,7 @@ func TestPostageHeaderError(t *testing.T) {
func TestOptions(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
for _, tc := range []struct {
endpoint string
expectedMethods string // expectedMethods contains HTTP methods like GET, POST, HEAD, PATCH, DELETE, OPTIONS. These are in alphabetical sorted order
@@ -556,7 +562,7 @@ func TestPostageDirectAndDeferred(t *testing.T) {
t.Parallel()
mockStorer := mockstorer.New()
- client, _, _, chanStorer := newTestServer(t, testServerOptions{
+ client, _, _, chanStorer, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -592,7 +598,7 @@ func TestPostageDirectAndDeferred(t *testing.T) {
t.Parallel()
mockStorer := mockstorer.New()
- client, _, _, chanStorer := newTestServer(t, testServerOptions{
+ client, _, _, chanStorer, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -798,7 +804,11 @@ func newTestPostService() postage.Service {
"",
batchOk,
big.NewInt(3),
- 11,
+ // batch depth over bucket depth: uploads here are a handful of
+ // chunks with random addresses, and an immutable batch fails the
+ // whole request once one bucket is full, so leave enough room per
+ // bucket for several of them to collide.
+ 16,
10,
1000,
true,
diff --git a/pkg/api/balances_test.go b/pkg/api/balances_test.go
index fc6f6a25b3a..27ffaae1ecf 100644
--- a/pkg/api/balances_test.go
+++ b/pkg/api/balances_test.go
@@ -31,7 +31,7 @@ func TestBalances(t *testing.T) {
return ret, err
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithCompensatedBalancesFunc(compensatedBalancesFunc)},
})
@@ -70,7 +70,7 @@ func TestBalancesError(t *testing.T) {
compensatedBalancesFunc := func() (ret map[string]*big.Int, err error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithCompensatedBalancesFunc(compensatedBalancesFunc)},
})
@@ -89,7 +89,7 @@ func TestBalancesPeers(t *testing.T) {
compensatedBalanceFunc := func(swarm.Address) (*big.Int, error) {
return big.NewInt(100000000000000000), nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithCompensatedBalanceFunc(compensatedBalanceFunc)},
})
@@ -109,7 +109,7 @@ func TestBalancesPeersError(t *testing.T) {
compensatedBalanceFunc := func(swarm.Address) (*big.Int, error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithCompensatedBalanceFunc(compensatedBalanceFunc)},
})
@@ -128,7 +128,7 @@ func TestBalancesPeersNoBalance(t *testing.T) {
compensatedBalanceFunc := func(swarm.Address) (*big.Int, error) {
return nil, accounting.ErrPeerNoBalance
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithCompensatedBalanceFunc(compensatedBalanceFunc)},
})
@@ -180,7 +180,7 @@ func TestConsumedBalances(t *testing.T) {
ret["PARTY"] = big.NewInt(0)
return ret, err
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithBalancesFunc(balancesFunc)},
})
@@ -219,7 +219,7 @@ func TestConsumedError(t *testing.T) {
balancesFunc := func() (ret map[string]*big.Int, err error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithBalancesFunc(balancesFunc)},
})
@@ -238,7 +238,7 @@ func TestConsumedPeers(t *testing.T) {
balanceFunc := func(swarm.Address) (*big.Int, error) {
return big.NewInt(1000000000000000000), nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithBalanceFunc(balanceFunc)},
})
@@ -258,7 +258,7 @@ func TestConsumedPeersError(t *testing.T) {
balanceFunc := func(swarm.Address) (*big.Int, error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithBalanceFunc(balanceFunc)},
})
@@ -277,7 +277,7 @@ func TestConsumedPeersNoBalance(t *testing.T) {
balanceFunc := func(swarm.Address) (*big.Int, error) {
return nil, accounting.ErrPeerNoBalance
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
AccountingOpts: []mock.Option{mock.WithBalanceFunc(balanceFunc)},
})
@@ -292,7 +292,7 @@ func TestConsumedPeersNoBalance(t *testing.T) {
func Test_peerBalanceHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -340,7 +340,7 @@ func Test_peerBalanceHandler_invalidInputs(t *testing.T) {
func Test_compensatedPeerBalanceHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/bytes_test.go b/pkg/api/bytes_test.go
index 576e263983d..b2f59547395 100644
--- a/pkg/api/bytes_test.go
+++ b/pkg/api/bytes_test.go
@@ -37,9 +37,9 @@ func TestBytes(t *testing.T) {
)
var (
- storerMock = mockstorer.New()
- logger = log.Noop
- client, _, _, _ = newTestServer(t, testServerOptions{
+ storerMock = mockstorer.New()
+ logger = log.Noop
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -174,7 +174,7 @@ func TestBytesInvalidStamp(t *testing.T) {
}
t.Run("upload batch not found", func(t *testing.T) {
- clientBatchNotExists, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchNotExists, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(),
@@ -209,7 +209,7 @@ func TestBytesInvalidStamp(t *testing.T) {
retErr = errors.New("err happened")
t.Run("upload batch exists error", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -233,7 +233,7 @@ func TestBytesInvalidStamp(t *testing.T) {
})
t.Run("upload batch unusable", func(t *testing.T) {
- clientBatchUnusable, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchUnusable, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -248,7 +248,7 @@ func TestBytesInvalidStamp(t *testing.T) {
})
t.Run("upload invalid tag", func(t *testing.T) {
- clientInvalidTag, _, _, _ := newTestServer(t, testServerOptions{
+ clientInvalidTag, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -263,7 +263,7 @@ func TestBytesInvalidStamp(t *testing.T) {
})
t.Run("upload tag not found", func(t *testing.T) {
- clientTagExists, _, _, _ := newTestServer(t, testServerOptions{
+ clientTagExists, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -281,7 +281,7 @@ func TestBytesInvalidStamp(t *testing.T) {
func TestBytesUploadHandlerInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
})
@@ -332,7 +332,7 @@ func TestBytesUploadHandlerInvalidInputs(t *testing.T) {
func TestBytesGetHandlerInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -380,7 +380,7 @@ func TestBytesGetHandlerInvalidInputs(t *testing.T) {
func TestBytesRedundancyLevel(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -458,7 +458,7 @@ func TestBytesHead(t *testing.T) {
t.Run(fmt.Sprintf("level %d encrypt %v", level, encrypt), func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -513,7 +513,7 @@ func TestBytesHeadRangeAndConditional(t *testing.T) {
t.Fatal(err)
}
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -599,7 +599,7 @@ func TestBytesHeadRangeAndConditional(t *testing.T) {
func TestBytesHeadErrorsMatchGet(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Post: mockpost.New(mockpost.WithAcceptAll()),
})
diff --git a/pkg/api/bzz_test.go b/pkg/api/bzz_test.go
index c3c3cfcd75a..3068447d925 100644
--- a/pkg/api/bzz_test.go
+++ b/pkg/api/bzz_test.go
@@ -77,7 +77,7 @@ func TestBzzUploadDownloadWithRedundancy(t *testing.T) {
}
store := mockstorer.NewForgettingStore(inmemchunkstore.New())
storerMock := mockstorer.NewWithChunkStore(store)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: log.Noop,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -248,7 +248,7 @@ func TestBzzFiles(t *testing.T) {
simpleData = []byte("this is a simple text")
storerMock = mockstorer.New()
logger = log.Noop
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -694,7 +694,7 @@ func TestBzzFilesRangeRequests(t *testing.T) {
t.Parallel()
logger := log.Noop
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -830,11 +830,11 @@ func TestFeedIndirection(t *testing.T) {
// first, "upload" some content for the update
var (
- updateData = []byte("
Swarm Feeds Hello World!
")
- logger = log.Noop
- storer = mockstorer.New()
- ctx = context.Background()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ updateData = []byte("Swarm Feeds Hello World!
")
+ logger = log.Noop
+ storer = mockstorer.New()
+ ctx = context.Background()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -910,7 +910,7 @@ func TestFeedIndirection(t *testing.T) {
look = newMockLookup(-1, 0, feedUpdate, nil, &id{}, nil)
factory = newMockFactory(look)
)
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
Logger: logger,
Feeds: factory,
@@ -939,7 +939,7 @@ func TestFeedIndirection(t *testing.T) {
look = newMockLookup(-1, 0, socRootCh, nil, &id{}, nil)
factory = newMockFactory(look)
)
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
Logger: logger,
Feeds: factory,
@@ -960,7 +960,7 @@ func TestFeedIndirection(t *testing.T) {
func Test_bzzDownloadHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -1030,7 +1030,7 @@ func TestInvalidBzzParams(t *testing.T) {
},
},
})
- clientBatchUnusable, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchUnusable, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -1057,7 +1057,7 @@ func TestInvalidBzzParams(t *testing.T) {
},
},
})
- clientBatchExists, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchExists, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -1084,7 +1084,7 @@ func TestInvalidBzzParams(t *testing.T) {
},
},
})
- clientBatchExists, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchExists, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(),
@@ -1110,7 +1110,7 @@ func TestInvalidBzzParams(t *testing.T) {
},
},
})
- clientInvalidTag, _, _, _ := newTestServer(t, testServerOptions{
+ clientInvalidTag, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -1137,7 +1137,7 @@ func TestInvalidBzzParams(t *testing.T) {
},
},
})
- clientTagExists, _, _, _ := newTestServer(t, testServerOptions{
+ clientTagExists, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -1154,7 +1154,7 @@ func TestInvalidBzzParams(t *testing.T) {
t.Run("address not found", func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -1168,10 +1168,10 @@ func TestInvalidBzzParams(t *testing.T) {
func TestBzzDownloadHeaders(t *testing.T) {
t.Parallel()
var (
- data = []byte("Swarm Hello World!
")
- logger = log.Noop
- storer = mockstorer.New()
- testServer, _, _, _ = newTestServer(t, testServerOptions{
+ data = []byte("Swarm Hello World!
")
+ logger = log.Noop
+ storer = mockstorer.New()
+ testServer, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -1218,7 +1218,7 @@ func TestBzzDownloadHeaders(t *testing.T) {
func TestBzzUploadRedundancyLevel(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -1284,7 +1284,7 @@ func TestBzzUploadRedundancyLevel(t *testing.T) {
func TestBzzDownloadRedundancyLevel(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
Post: mockpost.New(mockpost.WithAcceptAll()),
})
diff --git a/pkg/api/chequebook_test.go b/pkg/api/chequebook_test.go
index 86e918a1bad..3a64a36399e 100644
--- a/pkg/api/chequebook_test.go
+++ b/pkg/api/chequebook_test.go
@@ -39,7 +39,7 @@ func TestChequebookBalance(t *testing.T) {
return returnedAvailableBalance, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{
mock.WithChequebookBalanceFunc(chequebookBalanceFunc),
mock.WithChequebookAvailableBalanceFunc(chequebookAvailableBalanceFunc),
@@ -69,7 +69,7 @@ func TestChequebookBalanceError(t *testing.T) {
return big.NewInt(0), wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{mock.WithChequebookBalanceFunc(chequebookBalanceFunc)},
})
@@ -92,7 +92,7 @@ func TestChequebookAvailableBalanceError(t *testing.T) {
return nil, errors.New("New errors")
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{
mock.WithChequebookBalanceFunc(chequebookBalanceFunc),
mock.WithChequebookAvailableBalanceFunc(chequebookAvailableBalanceFunc),
@@ -114,7 +114,7 @@ func TestChequebookAddress(t *testing.T) {
return common.HexToAddress("0xfffff")
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{mock.WithChequebookAddressFunc(chequebookAddressFunc)},
})
@@ -149,7 +149,7 @@ func TestChequebookWithdraw(t *testing.T) {
return common.Hash{}, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{mock.WithChequebookWithdrawFunc(chequebookWithdrawFunc)},
})
@@ -178,7 +178,7 @@ func TestChequebookWithdraw(t *testing.T) {
return common.Hash{}, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{mock.WithChequebookWithdrawFunc(chequebookWithdrawFunc)},
})
@@ -211,7 +211,7 @@ func TestChequebookDeposit(t *testing.T) {
return common.Hash{}, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{mock.WithChequebookDepositFunc(chequebookDepositFunc)},
})
@@ -241,7 +241,7 @@ func TestChequebookDeposit(t *testing.T) {
return common.Hash{}, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookOpts: []mock.Option{mock.WithChequebookDepositFunc(chequebookDepositFunc)},
})
@@ -349,7 +349,7 @@ func TestChequebookLastCheques(t *testing.T) {
return lastReceivedCheques, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithLastReceivedChequesFunc(lastReceivedChequesFunc), swapmock.WithLastSentChequesFunc(lastSentChequesFunc)},
})
@@ -459,7 +459,7 @@ func TestChequebookLastChequesPeer(t *testing.T) {
return lastReceivedCheque, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithLastReceivedChequeFunc(lastReceivedChequeFunc), swapmock.WithLastSentChequeFunc(lastSentChequeFunc)},
})
@@ -497,7 +497,7 @@ func TestChequebookCashout(t *testing.T) {
return deployCashingHash, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashChequeFunc(cashChequeFunc)},
})
@@ -527,7 +527,7 @@ func TestChequebookCashout_CustomGas(t *testing.T) {
return deployCashingHash, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashChequeFunc(cashChequeFunc)},
})
@@ -602,7 +602,7 @@ func TestChequebookCashoutStatus(t *testing.T) {
return status, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)},
})
@@ -649,7 +649,7 @@ func TestChequebookCashoutStatus(t *testing.T) {
return status, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)},
})
@@ -687,7 +687,7 @@ func TestChequebookCashoutStatus(t *testing.T) {
return status, nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []swapmock.Option{swapmock.WithCashoutStatusFunc(cashoutStatusFunc)},
})
@@ -714,7 +714,7 @@ func TestChequebookCashoutStatus(t *testing.T) {
func Test_chequebookLastPeerHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/chunk_stream_test.go b/pkg/api/chunk_stream_test.go
index add7165fa89..0ae950134b9 100644
--- a/pkg/api/chunk_stream_test.go
+++ b/pkg/api/chunk_stream_test.go
@@ -30,8 +30,8 @@ func TestChunkUploadStream(t *testing.T) {
wsHeaders.Set(api.SwarmPostageBatchIdHeader, batchOkStr)
var (
- storerMock = mockstorer.New()
- _, wsConn, _, chanStorer = newTestServer(t, testServerOptions{
+ storerMock = mockstorer.New()
+ _, wsConn, _, chanStorer, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Post: mockpost.New(mockpost.WithAcceptAll()),
WsPath: "/chunks/stream",
@@ -149,8 +149,8 @@ func TestChunkUploadStreamWithStamp(t *testing.T) {
wsHeaders.Set(api.ContentTypeHeader, "application/octet-stream")
var (
- storerMock = mockstorer.New()
- _, wsConn, _, chanStorer = newTestServer(t, testServerOptions{
+ storerMock = mockstorer.New()
+ _, wsConn, _, chanStorer, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Post: mockpost.New(mockpost.WithAcceptAll()),
BatchStore: batchStore,
@@ -206,8 +206,8 @@ func TestChunkUploadStreamInvalidStamp(t *testing.T) {
wsHeaders.Set(api.ContentTypeHeader, "application/octet-stream")
var (
- storerMock = mockstorer.New()
- _, wsConn, _, _ = newTestServer(t, testServerOptions{
+ storerMock = mockstorer.New()
+ _, wsConn, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Post: mockpost.New(mockpost.WithAcceptAll()),
WsPath: "/chunks/stream",
diff --git a/pkg/api/chunk_test.go b/pkg/api/chunk_test.go
index b2512aa2ae8..f730119fcc3 100644
--- a/pkg/api/chunk_test.go
+++ b/pkg/api/chunk_test.go
@@ -37,11 +37,11 @@ func TestChunkUploadDownload(t *testing.T) {
t.Parallel()
var (
- chunksEndpoint = "/chunks"
- chunksResource = func(a swarm.Address) string { return "/chunks/" + a.String() }
- chunk = testingc.GenerateTestRandomChunk()
- storerMock = mockstorer.New()
- client, _, _, chanStorer = newTestServer(t, testServerOptions{
+ chunksEndpoint = "/chunks"
+ chunksResource = func(a swarm.Address) string { return "/chunks/" + a.String() }
+ chunk = testingc.GenerateTestRandomChunk()
+ storerMock = mockstorer.New()
+ client, _, _, chanStorer, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Post: mockpost.New(mockpost.WithAcceptAll()),
DirectUpload: true,
@@ -104,7 +104,7 @@ func TestChunkUploadDownload(t *testing.T) {
// nolint:paralleltest,tparallel
func TestChunkHasHandler(t *testing.T) {
mockStorer := mockstorer.New()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
})
@@ -141,7 +141,7 @@ func TestChunkHasHandler(t *testing.T) {
func TestChunkHandlersInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -203,7 +203,7 @@ func TestChunkInvalidParams(t *testing.T) {
t.Run("batch unusable", func(t *testing.T) {
t.Parallel()
- clientBatchUnusable, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchUnusable, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -219,7 +219,7 @@ func TestChunkInvalidParams(t *testing.T) {
t.Run("batch exists", func(t *testing.T) {
t.Parallel()
- clientBatchExists, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchExists, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -235,7 +235,7 @@ func TestChunkInvalidParams(t *testing.T) {
t.Run("batch not found", func(t *testing.T) {
t.Parallel()
- clientBatchNotFound, _, _, _ := newTestServer(t, testServerOptions{
+ clientBatchNotFound, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: logger,
Post: mockpost.New(),
@@ -253,11 +253,11 @@ func TestPreSignedUpload(t *testing.T) {
t.Parallel()
var (
- chunksEndpoint = "/chunks"
- chunk = testingc.GenerateTestRandomChunk()
- storerMock = mockstorer.New()
- batchStore = mockbatchstore.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ chunksEndpoint = "/chunks"
+ chunk = testingc.GenerateTestRandomChunk()
+ storerMock = mockstorer.New()
+ batchStore = mockbatchstore.New()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
BatchStore: batchStore,
})
diff --git a/pkg/api/cors_test.go b/pkg/api/cors_test.go
index 715987edf42..cbeb8a9afdd 100644
--- a/pkg/api/cors_test.go
+++ b/pkg/api/cors_test.go
@@ -85,7 +85,7 @@ func TestCORSHeaders(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
CORSAllowedOrigins: tc.allowedOrigins,
})
@@ -162,7 +162,7 @@ func TestCors(t *testing.T) {
t.Run(tc.endpoint, func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
CORSAllowedOrigins: []string{origin},
})
@@ -222,7 +222,7 @@ func TestCorsStatus(t *testing.T) {
t.Run(tc.endpoint, func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
CORSAllowedOrigins: []string{origin},
})
diff --git a/pkg/api/debugstorage_test.go b/pkg/api/debugstorage_test.go
index dc13f8d81bf..92fb8aa1aac 100644
--- a/pkg/api/debugstorage_test.go
+++ b/pkg/api/debugstorage_test.go
@@ -34,7 +34,7 @@ func TestDebugStorage(t *testing.T) {
},
}
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.NewWithDebugInfo(want),
})
diff --git a/pkg/api/dirs_test.go b/pkg/api/dirs_test.go
index 3f7ac099ba5..4c301173120 100644
--- a/pkg/api/dirs_test.go
+++ b/pkg/api/dirs_test.go
@@ -35,7 +35,7 @@ func TestDirs(t *testing.T) {
bzzDownloadResource = func(addr, path string) string { return "/bzz/" + addr + "/" + path }
ctx = context.Background()
storer = mockstorer.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
PreventRedirect: true,
Post: mockpost.New(mockpost.WithAcceptAll()),
@@ -501,9 +501,9 @@ func TestDirsEmtpyDir(t *testing.T) {
t.Parallel()
var (
- dirUploadResource = "/bzz"
- storer = mockstorer.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ dirUploadResource = "/bzz"
+ storer = mockstorer.New()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
PreventRedirect: true,
Post: mockpost.New(mockpost.WithAcceptAll()),
diff --git a/pkg/api/envelope_test.go b/pkg/api/envelope_test.go
index ede77e63875..3e523b39420 100644
--- a/pkg/api/envelope_test.go
+++ b/pkg/api/envelope_test.go
@@ -21,7 +21,7 @@ func TestPostEnvelope(t *testing.T) {
zeroHex := "0000000000000000000000000000000000000000000000000000000000000000"
envelopeEndpoint := func(chunkAddress string) string { return fmt.Sprintf("/envelope/%s", chunkAddress) }
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -43,7 +43,7 @@ func TestPostEnvelope(t *testing.T) {
t.Run("postage does not exist", func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, client, http.MethodPost, envelopeEndpoint(zeroHex), http.StatusNotFound,
jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, zeroHex),
@@ -53,7 +53,7 @@ func TestPostEnvelope(t *testing.T) {
t.Run("batch unusable", func(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Post: mockpost.New(mockpost.WithAcceptAll()),
BatchStore: mockbatchstore.New(),
})
diff --git a/pkg/api/export_test.go b/pkg/api/export_test.go
index 0eb164a8cbe..dde50ea7b2a 100644
--- a/pkg/api/export_test.go
+++ b/pkg/api/export_test.go
@@ -45,6 +45,20 @@ var (
SuccessWsMsg = successWsMsg
)
+const GsocQueueCapacity = gsocQueueCapacity
+
+type GsocQueue = gsocQueue
+
+func NewGsocQueue() *GsocQueue { return newGsocQueue() }
+
+func (q *GsocQueue) Push(b []byte) { q.push(b) }
+func (q *GsocQueue) Pop() ([]byte, bool) { return q.pop() }
+func (q *GsocQueue) Release() { q.release() }
+
+func (s *Service) CacheGsocWrappedChunks(address swarm.Address) func() {
+ return s.cacheGsocWrappedChunks(address)
+}
+
var (
FileSizeBucketsKBytes = fileSizeBucketsKBytes
ToFileSizeBucket = toFileSizeBucket
diff --git a/pkg/api/feed_test.go b/pkg/api/feed_test.go
index 4429d1dc7c2..a5645349881 100644
--- a/pkg/api/feed_test.go
+++ b/pkg/api/feed_test.go
@@ -71,12 +71,12 @@ func TestFeed_Get(t *testing.T) {
t.Parallel()
var (
- timestamp = int64(12121212)
- ch = toChunk(t, uint64(timestamp), mockWrappedCh.Address().Bytes())
- look = newMockLookup(12, 0, ch, nil, &id{}, &id{})
- factory = newMockFactory(look)
- idBytes, _ = (&id{}).MarshalBinary()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ timestamp = int64(12121212)
+ ch = toChunk(t, uint64(timestamp), mockWrappedCh.Address().Bytes())
+ look = newMockLookup(12, 0, ch, nil, &id{}, &id{})
+ factory = newMockFactory(look)
+ idBytes, _ = (&id{}).MarshalBinary()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Feeds: factory,
})
@@ -104,7 +104,7 @@ func TestFeed_Get(t *testing.T) {
factory = newMockFactory(look)
idBytes, _ = (&id{}).MarshalBinary()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Feeds: factory,
})
@@ -134,7 +134,7 @@ func TestFeed_Get(t *testing.T) {
factory = newMockFactory(look)
idBytes, _ = (&id{}).MarshalBinary()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Feeds: factory,
})
@@ -171,7 +171,7 @@ func TestFeed_Get(t *testing.T) {
look = newMockLookup(-1, 2, ch, nil, &id{}, &id{})
factory = newMockFactory(look)
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Feeds: factory,
})
@@ -184,9 +184,9 @@ func TestFeed_Get(t *testing.T) {
t.Parallel()
var (
- look = newMockLookup(1, 0, nil, errors.New("dummy"), &id{}, &id{})
- factory = newMockFactory(look)
- client, _, _, _ = newTestServer(t, testServerOptions{
+ look = newMockLookup(1, 0, nil, errors.New("dummy"), &id{}, &id{})
+ factory = newMockFactory(look)
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Feeds: factory,
})
@@ -218,7 +218,7 @@ func TestFeed_Get(t *testing.T) {
factory = newMockFactory(look)
idBytes, _ = (&id{}).MarshalBinary()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Feeds: factory,
})
@@ -260,11 +260,11 @@ func TestFeed_Post(t *testing.T) {
// get the reference from the store, unmarshal to a
// manifest entry and make sure all metadata correct
var (
- logger = log.Noop
- topic = "aabbcc"
- mp = mockpost.New(mockpost.WithIssuer(postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)))
- mockStorer = mockstorer.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ logger = log.Noop
+ topic = "aabbcc"
+ mp = mockpost.New(mockpost.WithIssuer(postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)))
+ mockStorer = mockstorer.New()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Logger: logger,
Post: mp,
diff --git a/pkg/api/gsoc.go b/pkg/api/gsoc.go
index 60d048ffdc0..55361938e22 100644
--- a/pkg/api/gsoc.go
+++ b/pkg/api/gsoc.go
@@ -5,15 +5,126 @@
package api
import (
+ "bytes"
+ "fmt"
"net/http"
+ "strings"
+ "sync"
"time"
"github.com/ethersphere/bee/v2/pkg/jsonhttp"
+ "github.com/ethersphere/bee/v2/pkg/safe"
+ "github.com/ethersphere/bee/v2/pkg/soc"
"github.com/ethersphere/bee/v2/pkg/swarm"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
+// SOC field identifiers that can be requested through the SwarmSocFieldsHeader
+// to be serialized and channeled on every incoming GSOC chunk.
+const (
+ socFieldAddress = "address"
+ socFieldRecoveredPubKey = "recoveredpubkey"
+ socFieldIdentifier = "identifier"
+ socFieldSignature = "signature"
+ socFieldWrappedAddress = "wrappedaddress"
+ socFieldSpan = "span"
+ socFieldPayload = "payload"
+)
+
+// gsocQueueCapacity is the maximum number of pending outgoing messages held
+// per GSOC websocket subscription. Since a serialized message is at most
+// maxSocFieldsSize bytes, this caps a single subscription's backlog at a bit
+// over 1 MB, while still leaving enough room to absorb the bursts a client
+// that keeps reading can be expected to work through.
+const gsocQueueCapacity = 256
+
+// socFieldSizes is the single source of truth for the valid SOC fields: it maps
+// every field identifier to the maximum number of bytes its serialized form
+// occupies. A field added here is accepted by parseSocFields and accounted for
+// in maxSocFieldsSize without any further change.
+var socFieldSizes = map[string]int{
+ socFieldAddress: swarm.HashSize,
+ socFieldRecoveredPubKey: soc.OwnerPubKeySize,
+ socFieldIdentifier: swarm.HashSize,
+ socFieldSignature: swarm.SocSignatureSize,
+ socFieldWrappedAddress: swarm.HashSize,
+ socFieldSpan: swarm.SpanSize,
+ socFieldPayload: swarm.ChunkSize,
+}
+
+// maxSocFieldsSize is the maximum size of a serialized SOC fields message when
+// every field is requested. It is derived from socFieldSizes so that it stays
+// correct when fields are added or removed.
+var maxSocFieldsSize = func() (size int) {
+ for _, s := range socFieldSizes {
+ size += s
+ }
+ return size
+}()
+
+// parseSocFields parses the SwarmSocFieldsHeader value into a list of SOC field
+// identifiers. When the header is empty it defaults to the payload field only,
+// which preserves backward compatibility. Duplicate fields are dropped, keeping
+// the first occurrence, so the returned slice never exceeds len(socFieldSizes)
+// entries regardless of how many times a field is repeated in the header.
+func parseSocFields(header string) ([]string, error) {
+ if strings.TrimSpace(header) == "" {
+ return []string{socFieldPayload}, nil
+ }
+
+ seen := make(map[string]bool, len(socFieldSizes))
+ parts := strings.Split(header, ",")
+ fields := make([]string, 0, len(socFieldSizes))
+ for _, p := range parts {
+ f := strings.ToLower(strings.TrimSpace(p))
+ if f == "" {
+ continue
+ }
+ if _, ok := socFieldSizes[f]; !ok {
+ return nil, fmt.Errorf("unknown soc field: %q", p)
+ }
+ if seen[f] {
+ continue
+ }
+ seen[f] = true
+ fields = append(fields, f)
+ }
+ if len(fields) == 0 {
+ return []string{socFieldPayload}, nil
+ }
+ return fields, nil
+}
+
+// socFieldsBytes serializes the requested SOC fields in the same order as they
+// were provided in the header.
+func socFieldsBytes(c *soc.SOC, fields []string) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ for _, f := range fields {
+ switch f {
+ case socFieldAddress:
+ addr, err := c.Address()
+ if err != nil {
+ return nil, fmt.Errorf("soc address: %w", err)
+ }
+ buf.Write(addr.Bytes())
+ case socFieldRecoveredPubKey:
+ buf.Write(c.OwnerPubKey())
+ case socFieldIdentifier:
+ buf.Write(c.ID())
+ case socFieldSignature:
+ buf.Write(c.Signature())
+ case socFieldWrappedAddress:
+ buf.Write(c.WrappedChunk().Address().Bytes())
+ case socFieldSpan:
+ buf.Write(c.WrappedChunk().Data()[:swarm.SpanSize])
+ case socFieldPayload:
+ buf.Write(c.WrappedChunk().Data()[swarm.SpanSize:])
+ }
+ }
+ return buf.Bytes(), nil
+}
+
func (s *Service) gsocWsHandler(w http.ResponseWriter, r *http.Request) {
logger := s.logger.WithName("gsoc_subscribe").Build()
@@ -26,9 +137,49 @@ func (s *Service) gsocWsHandler(w http.ResponseWriter, r *http.Request) {
return
}
+ headers := struct {
+ SocFields string `map:"Swarm-Soc-Fields"`
+ CacheWrappedChunk *bool `map:"Swarm-Cache-Wrapped-Chunk"`
+ }{}
+ if response := s.mapStructure(r.Header, &headers); response != nil {
+ response("invalid header params", logger, w)
+ return
+ }
+
+ // Browser WebSocket clients cannot set request headers, so the same
+ // options are also accepted as query parameters. Query parameters take
+ // precedence over headers.
+ queries := struct {
+ SocFields string `map:"swarm-soc-fields"`
+ CacheWrappedChunk *bool `map:"swarm-cache-wrapped-chunk"`
+ }{}
+ if response := s.mapStructure(r.URL.Query(), &queries); response != nil {
+ response("invalid query params", logger, w)
+ return
+ }
+ if queries.SocFields != "" {
+ headers.SocFields = queries.SocFields
+ }
+ if queries.CacheWrappedChunk != nil {
+ headers.CacheWrappedChunk = queries.CacheWrappedChunk
+ }
+
+ fields, err := parseSocFields(headers.SocFields)
+ if err != nil {
+ logger.Debug("invalid soc fields", "error", err)
+ logger.Error(nil, "invalid soc fields")
+ jsonhttp.BadRequest(w, "invalid soc fields")
+ return
+ }
+
upgrader := websocket.Upgrader{
- ReadBufferSize: swarm.ChunkSize,
- WriteBufferSize: swarm.ChunkSize,
+ ReadBufferSize: swarm.SocMaxChunkSize,
+ // WriteBufferSize is only an I/O buffer hint; it does not cap the
+ // message size. The serialized output can be the whole single owner
+ // chunk plus the derived metadata fields (soc address, recovered public
+ // key, wrapped chunk address), so size it to that maximum to avoid split
+ // writes.
+ WriteBufferSize: maxSocFieldsSize,
CheckOrigin: s.checkOrigin,
}
@@ -40,15 +191,200 @@ func (s *Service) gsocWsHandler(w http.ResponseWriter, r *http.Request) {
return
}
+ // Subscribe synchronously, before handing the connection off to its own
+ // goroutine: Upgrade already flushed the 101 response, so the client can
+ // start sending GSOC-triggering activity immediately. Subscribing here
+ // instead of inside the spawned goroutine closes the window in which an
+ // update could arrive before the handler is registered and be silently
+ // missed.
+ //
+ // Caching the wrapped chunks is a subscription of its own, shared by every
+ // subscriber of this address that asked for it. It is registered first, so
+ // that a chunk arriving in between is cached rather than announced to a
+ // client that cannot resolve it yet.
+ releaseCache := func() {}
+ if headers.CacheWrappedChunk != nil && *headers.CacheWrappedChunk {
+ releaseCache = s.cacheGsocWrappedChunks(paths.Address)
+ }
+
+ queue := newGsocQueue()
+ wake := make(chan struct{}, 1)
+ cleanup := s.gsoc.Subscribe(paths.Address, func(c *soc.SOC) {
+ b, err := socFieldsBytes(c, fields)
+ if err != nil {
+ s.logger.Warning("gsoc ws: serialize soc fields failed", "error", err)
+ return
+ }
+
+ queue.push(b)
+
+ // Non-blocking: the writer only needs to know there is something to
+ // drain, not one notification per message, so a full wake channel
+ // means it is already going to pick this up.
+ select {
+ case wake <- struct{}{}:
+ default:
+ }
+ })
+
s.wsWg.Add(1)
- go s.gsocListeningWs(conn, paths.Address)
+ go s.gsocListeningWs(conn, func() {
+ cleanup()
+ releaseCache()
+ }, queue, wake)
}
-func (s *Service) gsocListeningWs(conn *websocket.Conn, socAddress swarm.Address) {
+// gsocCacheSub is the subscription that caches the wrapped chunks delivered on
+// one GSOC address, together with the number of websocket subscribers of that
+// address currently interested in it. Caching is a node-local side effect, so
+// it is done once per chunk no matter how many subscribers asked for it: done
+// per subscriber instead, the very same chunk would be written to the store as
+// many times as there are subscribers.
+type gsocCacheSub struct {
+ refs int
+ cleanup func()
+}
+
+// cacheGsocWrappedChunks subscribes to the given GSOC address to cache the
+// wrapped chunk of every message delivered on it, unless that subscription is
+// already in place, and returns the release of this caller's interest in it.
+// The subscription ends with the release of the last interest in it.
+func (s *Service) cacheGsocWrappedChunks(address swarm.Address) (release func()) {
+ key := address.ByteString()
+
+ s.gsocCacheMu.Lock()
+ defer s.gsocCacheMu.Unlock()
+
+ sub, ok := s.gsocCacheSubs[key]
+ if !ok {
+ sub = &gsocCacheSub{}
+ sub.cleanup = s.gsoc.Subscribe(address, func(c *soc.SOC) {
+ // This callback runs inline on the push and pull sync stream
+ // goroutines, so the store write is handed to its own goroutine:
+ // done here it would hold up the chunk's own storage and, on the
+ // push sync path, the receipt the sending peer is waiting for.
+ // Caching is also independent of any single subscriber's
+ // connection, so it must not be aborted just because a websocket
+ // closes mid-write; it is bound to the node's lifetime instead.
+ wrapped := c.WrappedChunk()
+ safe.Go(s.logger, "gsoc-cache-wrapped-chunk", func() {
+ if err := s.storer.Cache().Put(s.bgCtx, wrapped); err != nil {
+ s.logger.Debug("gsoc ws: cache wrapped chunk failed", "error", err)
+ }
+ })
+ })
+ s.gsocCacheSubs[key] = sub
+ }
+ sub.refs++
+
+ var once sync.Once
+ return func() {
+ once.Do(func() {
+ s.gsocCacheMu.Lock()
+ defer s.gsocCacheMu.Unlock()
+
+ sub.refs--
+ if sub.refs > 0 {
+ return
+ }
+ delete(s.gsocCacheSubs, key)
+ sub.cleanup()
+ })
+ }
+}
+
+// gsocQueue is a bounded FIFO ring buffer of pending outgoing GSOC messages.
+//
+// The producer (the GSOC subscription callback) runs on the node's chunk
+// handling path, so it must never block on the websocket writer; with an
+// unbounded queue that would let anyone spamming a subscribed GSOC address
+// grow the backlog without limit and exhaust the node's memory whenever the
+// client does not keep up. Once the queue is full the oldest pending message
+// is therefore evicted to make room for the newest one: for a real-time
+// subscription a fresh update is worth more than a stale one.
+type gsocQueue struct {
+ mu sync.Mutex
+ items [][]byte // ring buffer, fixed length gsocQueueCapacity
+ head int // index of the oldest queued message
+ size int // number of queued messages
+ dropped uint64 // messages evicted since the last droppedCount call
+ released bool // set once the writer is gone, see release
+}
+
+func newGsocQueue() *gsocQueue {
+ return &gsocQueue{items: make([][]byte, gsocQueueCapacity)}
+}
+
+// push queues a message, evicting the oldest one if the queue is full. It is a
+// no-op once the queue has been released.
+func (q *gsocQueue) push(b []byte) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ if q.released {
+ return
+ }
+ if q.size == len(q.items) {
+ q.items[q.head] = nil
+ q.head = (q.head + 1) % len(q.items)
+ q.size--
+ q.dropped++
+ }
+ q.items[(q.head+q.size)%len(q.items)] = b
+ q.size++
+}
+
+// pop returns the oldest queued message, or ok=false if the queue is empty.
+func (q *gsocQueue) pop() (b []byte, ok bool) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ if q.size == 0 {
+ return nil, false
+ }
+ b = q.items[q.head]
+ q.items[q.head] = nil // drop the reference so the message can be collected
+ q.head = (q.head + 1) % len(q.items)
+ q.size--
+ return b, true
+}
+
+// droppedCount returns how many messages were evicted since the previous call
+// and resets the counter, so that a slow subscriber is reported once per
+// reporting period instead of once per lost message.
+func (q *gsocQueue) droppedCount() uint64 {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ dropped := q.dropped
+ q.dropped = 0
+ return dropped
+}
+
+// release discards the undelivered backlog and stops the queue from accepting
+// further messages. Nothing drains the queue once its writer is gone, so the
+// pending messages are dead weight from that point on; dropping them here
+// frees them right away instead of keeping them alive for as long as a
+// producer that is still mid-callback can reach the queue.
+func (q *gsocQueue) release() {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ clear(q.items)
+ q.head = 0
+ q.size = 0
+ q.released = true
+}
+
+func (s *Service) gsocListeningWs(conn *websocket.Conn, cleanup func(), queue *gsocQueue, wake chan struct{}) {
defer s.wsWg.Done()
+ // Defers run in reverse order: unsubscribe first, so that no producer can
+ // queue anything new, and only then drop whatever backlog this connection
+ // never got to write out.
+ defer queue.release()
+ defer cleanup()
var (
- dataC = make(chan []byte)
gone = make(chan struct{})
ticker = time.NewTicker(s.WsPingPeriod)
err error
@@ -57,17 +393,6 @@ func (s *Service) gsocListeningWs(conn *websocket.Conn, socAddress swarm.Address
ticker.Stop()
_ = conn.Close()
}()
- cleanup := s.gsoc.Subscribe(socAddress, func(m []byte) {
- select {
- case dataC <- m:
- case <-gone:
- return
- case <-s.quit:
- return
- }
- })
-
- defer cleanup()
conn.SetCloseHandler(func(code int, text string) error {
s.logger.Debug("gsoc ws: client gone", "code", code, "message", text)
@@ -77,7 +402,21 @@ func (s *Service) gsocListeningWs(conn *websocket.Conn, socAddress swarm.Address
for {
select {
- case b := <-dataC:
+ case <-wake:
+ // Write a single message per iteration and re-arm wake, instead
+ // of draining the whole queue here: the outer select then keeps
+ // serving the keepalive ping, the shutdown and the client-gone
+ // signals between messages, however long the backlog stays
+ // non-empty. A spurious wake just finds the queue empty.
+ b, ok := queue.pop()
+ if !ok {
+ continue
+ }
+ select {
+ case wake <- struct{}{}:
+ default:
+ }
+
err = conn.SetWriteDeadline(time.Now().Add(writeDeadline))
if err != nil {
s.logger.Debug("gsoc ws: set write deadline failed", "error", err)
@@ -92,20 +431,19 @@ func (s *Service) gsocListeningWs(conn *websocket.Conn, socAddress swarm.Address
case <-s.quit:
// shutdown
- err = conn.SetWriteDeadline(time.Now().Add(writeDeadline))
- if err != nil {
- s.logger.Debug("gsoc ws: set write deadline failed", "error", err)
- return
- }
- err = conn.WriteMessage(websocket.CloseMessage, []byte{})
- if err != nil {
- s.logger.Debug("gsoc ws: write close message failed", "error", err)
- }
+ s.gsocWsNotifyClose(conn)
return
case <-gone:
// client gone
return
case <-ticker.C:
+ // Report evictions once per ping period rather than per message,
+ // so that a subscriber that stays behind is reported periodically
+ // without flooding the operator log.
+ if dropped := queue.droppedCount(); dropped > 0 {
+ s.logger.Warning("gsoc ws: subscriber too slow, messages dropped", "count", dropped)
+ }
+
err = conn.SetWriteDeadline(time.Now().Add(writeDeadline))
if err != nil {
s.logger.Debug("gsoc ws: set write deadline failed", "error", err)
@@ -118,3 +456,15 @@ func (s *Service) gsocListeningWs(conn *websocket.Conn, socAddress swarm.Address
}
}
}
+
+// gsocWsNotifyClose tells the subscriber that the node is going away. It is
+// best effort: the connection is closed either way once the writer returns.
+func (s *Service) gsocWsNotifyClose(conn *websocket.Conn) {
+ if err := conn.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil {
+ s.logger.Debug("gsoc ws: set write deadline failed", "error", err)
+ return
+ }
+ if err := conn.WriteMessage(websocket.CloseMessage, []byte{}); err != nil {
+ s.logger.Debug("gsoc ws: write close message failed", "error", err)
+ }
+}
diff --git a/pkg/api/gsoc_test.go b/pkg/api/gsoc_test.go
index cf3161a9f03..3bbcd566ca5 100644
--- a/pkg/api/gsoc_test.go
+++ b/pkg/api/gsoc_test.go
@@ -5,19 +5,32 @@
package api_test
import (
+ "bytes"
+ "context"
"encoding/hex"
"fmt"
+ "net"
+ "net/http"
"net/url"
+ "slices"
"strings"
+ "sync"
+ "sync/atomic"
"testing"
"time"
+ "github.com/ethersphere/bee/v2/pkg/api"
"github.com/ethersphere/bee/v2/pkg/cac"
"github.com/ethersphere/bee/v2/pkg/crypto"
"github.com/ethersphere/bee/v2/pkg/gsoc"
+ "github.com/ethersphere/bee/v2/pkg/jsonhttp"
+ "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/v2/pkg/log"
mockbatchstore "github.com/ethersphere/bee/v2/pkg/postage/batchstore/mock"
"github.com/ethersphere/bee/v2/pkg/soc"
+ "github.com/ethersphere/bee/v2/pkg/spinlock"
+ "github.com/ethersphere/bee/v2/pkg/storage"
+ "github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore"
mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock"
"github.com/ethersphere/bee/v2/pkg/swarm"
"github.com/ethersphere/bee/v2/pkg/util/testutil"
@@ -134,7 +147,878 @@ func TestGsocPong(t *testing.T) {
}
}
+// TestGsocWebsocketWrappedChunkData verifies that the Swarm-Soc-Fields header
+// allows requesting the whole wrapped chunk data (span + payload).
+func TestGsocWebsocketWrappedChunkData(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ headers = http.Header{api.SwarmSocFieldsHeader: []string{"span,payload"}}
+ g, cl, signer, _, _ = newGsocTestWithOpts(t, id, 0, headers)
+ respC = make(chan error, 1)
+ payload = []byte("The most dangerous phrase in the language is: ‘We've always done it this way.’")
+ )
+
+ err := cl.SetReadDeadline(time.Now().Add(longTimeout))
+ if err != nil {
+ t.Fatal(err)
+ }
+ cl.SetReadLimit(swarm.ChunkSize)
+
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ g.Handle(socCh)
+
+ // span (8 bytes) + payload == full wrapped chunk data
+ go expectMessage(t, cl, respC, ch.Data())
+ if err := <-respC; err != nil {
+ t.Fatal(err)
+ }
+}
+
+// TestGsocWebsocketSocFields verifies that every SOC field can be requested
+// through the Swarm-Soc-Fields header, that field names are case insensitive
+// and that the fields are serialized in the order they are listed in the
+// header rather than in any order internal to the node. Every expected field
+// is derived from the signer and the wrapped chunk, so the recovered public
+// key in particular is checked against an independently compressed key rather
+// than against whatever the SOC happens to carry.
+func TestGsocWebsocketSocFields(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ headers = http.Header{api.SwarmSocFieldsHeader: []string{"payload,address,recoveredPubKey,identifier,signature,wrappedAddress,span"}}
+ g, cl, signer, _, _ = newGsocTestWithOpts(t, id, 0, headers)
+ respC = make(chan error, 1)
+ payload = []byte("The future is already here — it's just not evenly distributed.")
+ )
+
+ err := cl.SetReadDeadline(time.Now().Add(longTimeout))
+ if err != nil {
+ t.Fatal(err)
+ }
+ cl.SetReadLimit(swarm.ChunkSize)
+
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ g.Handle(socCh)
+
+ pubKey, err := signer.PublicKey()
+ if err != nil {
+ t.Fatal(err)
+ }
+ owner, err := signer.EthereumAddress()
+ if err != nil {
+ t.Fatal(err)
+ }
+ socAddr, err := soc.CreateAddress(id, owner.Bytes())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := slices.Concat(
+ payload,
+ socAddr.Bytes(),
+ crypto.EncodeSecp256k1PublicKey(pubKey),
+ id,
+ // the signature is what follows the identifier in the signed chunk
+ signedCh.Data()[swarm.HashSize:swarm.HashSize+swarm.SocSignatureSize],
+ ch.Address().Bytes(),
+ ch.Data()[:swarm.SpanSize],
+ )
+
+ go expectMessage(t, cl, respC, expected)
+ if err := <-respC; err != nil {
+ t.Fatal(err)
+ }
+}
+
+// TestGsocWebsocketSocFieldsDeduplication verifies that repeated field names in
+// the Swarm-Soc-Fields header are de-duplicated, keeping only the first
+// occurrence, instead of serializing the same field multiple times.
+func TestGsocWebsocketSocFieldsDeduplication(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ headers = http.Header{api.SwarmSocFieldsHeader: []string{"payload,payload,identifier,payload,identifier"}}
+ g, cl, signer, _, _ = newGsocTestWithOpts(t, id, 0, headers)
+ respC = make(chan error, 1)
+ payload = []byte("Simplicity is the ultimate sophistication.")
+ )
+
+ err := cl.SetReadDeadline(time.Now().Add(longTimeout))
+ if err != nil {
+ t.Fatal(err)
+ }
+ cl.SetReadLimit(swarm.ChunkSize)
+
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ g.Handle(socCh)
+
+ // each requested field must appear exactly once, in first-occurrence order
+ expected := make([]byte, 0, len(payload)+len(id))
+ expected = append(expected, payload...)
+ expected = append(expected, id...)
+
+ go expectMessage(t, cl, respC, expected)
+ if err := <-respC; err != nil {
+ t.Fatal(err)
+ }
+}
+
+// TestGsocWebsocketInvalidFieldsHeader verifies that an unknown field name in
+// the Swarm-Soc-Fields header is rejected with a 400 Bad Request before the
+// websocket upgrade is attempted.
+func TestGsocWebsocketInvalidFieldsHeader(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ gsocSvc = gsoc.New(log.Noop)
+ addrHex = hex.EncodeToString(id)
+ batchStore = mockbatchstore.New()
+ storer = mockstorer.New()
+ )
+ testutil.CleanupCloser(t, gsocSvc)
+
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
+ Gsoc: gsocSvc,
+ Storer: storer,
+ BatchStore: batchStore,
+ Logger: log.Noop,
+ })
+
+ jsonhttptest.Request(t, client, http.MethodGet, "/gsoc/subscribe/"+addrHex, http.StatusBadRequest,
+ jsonhttptest.WithRequestHeader(api.SwarmSocFieldsHeader, "bogusfield"),
+ jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
+ Message: "invalid soc fields",
+ Code: http.StatusBadRequest,
+ }),
+ )
+}
+
+// TestGsocWebsocketQueryParams verifies that the subscription options can also
+// be given as query parameters, since browser WebSocket clients cannot set
+// request headers, and that query parameters take precedence over headers.
+func TestGsocWebsocketQueryParams(t *testing.T) {
+ t.Parallel()
+
+ payload := []byte("Any sufficiently advanced technology is indistinguishable from magic.")
+
+ for _, tc := range []struct {
+ name string
+ headers http.Header
+ query url.Values
+ fields func(id []byte, ch swarm.Chunk) []byte
+ cached bool
+ }{
+ {
+ name: "query only",
+ query: url.Values{"swarm-soc-fields": {"identifier,payload"}, "swarm-cache-wrapped-chunk": {"true"}},
+ fields: func(id []byte, ch swarm.Chunk) []byte { return slices.Concat(id, payload) },
+ cached: true,
+ },
+ {
+ name: "query precedence",
+ headers: http.Header{
+ api.SwarmSocFieldsHeader: []string{"identifier,payload"},
+ api.SwarmCacheWrappedChunkHeader: []string{"true"},
+ },
+ query: url.Values{"swarm-soc-fields": {"span,payload"}, "swarm-cache-wrapped-chunk": {"false"}},
+ fields: func(id []byte, ch swarm.Chunk) []byte { return ch.Data() },
+ cached: false,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ g, cl, signer, _, storer = newGsocTestWithQuery(t, id, 0, tc.headers, tc.query)
+ respC = make(chan error, 1)
+ )
+
+ err := cl.SetReadDeadline(time.Now().Add(longTimeout))
+ if err != nil {
+ t.Fatal(err)
+ }
+ cl.SetReadLimit(swarm.ChunkSize)
+
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ g.Handle(socCh)
+
+ go expectMessage(t, cl, respC, tc.fields(id, ch))
+ if err := <-respC; err != nil {
+ t.Fatal(err)
+ }
+
+ if tc.cached {
+ if err := spinlock.Wait(longTimeout, func() bool {
+ _, err := storer.ChunkStore().Get(context.Background(), ch.Address())
+ return err == nil
+ }); err != nil {
+ t.Fatalf("wrapped chunk not cached: %v", err)
+ }
+ } else if _, err := storer.ChunkStore().Get(context.Background(), ch.Address()); err == nil {
+ t.Fatal("wrapped chunk cached despite query precedence")
+ }
+ })
+ }
+}
+
+// TestGsocWebsocketSlowConsumer verifies that a subscriber that falls behind
+// incoming GSOC messages is not dropped or disconnected: the server queues the
+// messages and delivers the backlog, in order, once the consumer catches up,
+// instead of racing on the underlying websocket connection or blocking the
+// (synchronous) GSOC handler indefinitely.
+func TestGsocWebsocketSlowConsumer(t *testing.T) {
+ t.Parallel()
+
+ const messageCount = 10
+
+ id := make([]byte, 32)
+ gsocSvc, cl, signer := newGsocPipeTest(t, id, 0)
+
+ // Build the chunks before publishing any of them: from the first publish
+ // until the consumer starts reading again the server's writer is blocked
+ // on the pipe against its write deadline, so only the publishing loop
+ // belongs inside that window.
+ payloads := make([][]byte, messageCount)
+ socs := make([]*soc.SOC, messageCount)
+ for i := range socs {
+ payloads[i] = []byte{byte(i)}
+ ch, _ := cac.New(payloads[i])
+ signedCh, _ := soc.New(id, ch).Sign(signer)
+ socs[i], _ = soc.FromChunk(signedCh)
+ }
+
+ // never read from cl while queuing every message: the first message
+ // blocks the single writer goroutine (nothing reads the pipe yet), and
+ // the rest pile up behind it in the queue. messageCount is well below
+ // api.GsocQueueCapacity, so none of them is evicted.
+ for _, socCh := range socs {
+ gsocSvc.Handle(socCh)
+ }
+
+ // the whole backlog must arrive, in order, once the consumer starts
+ // reading again.
+ for i, want := range payloads {
+ _, got, err := cl.ReadMessage()
+ if err != nil {
+ t.Fatalf("message %d: %v", i, err)
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatalf("message %d: got %q, want %q", i, got, want)
+ }
+ }
+}
+
+// TestGsocWebsocketQueueBound verifies that a subscriber which stops reading
+// cannot make the server side queue grow without limit: once the queue is full
+// the oldest pending messages are evicted, while the most recent ones are still
+// delivered, in order.
+func TestGsocWebsocketQueueBound(t *testing.T) {
+ t.Parallel()
+
+ const messageCount = api.GsocQueueCapacity + 16
+
+ id := make([]byte, 32)
+ gsocSvc, cl, signer := newGsocPipeTest(t, id, 0)
+
+ // Build every chunk before publishing any of them. Signing and recovering
+ // a few hundred single owner chunks is the expensive part of this test,
+ // and from the first publish until the consumer starts reading again the
+ // server's writer sits blocked on the pipe, against a write deadline that
+ // closes the connection if it is not drained in time. Only the publishing
+ // loop belongs inside that window.
+ socs := make([]*soc.SOC, messageCount)
+ for i := range socs {
+ ch, _ := cac.New([]byte{byte(i >> 8), byte(i)})
+ signedCh, _ := soc.New(id, ch).Sign(signer)
+ socs[i], _ = soc.FromChunk(signedCh)
+ }
+
+ // never read from cl while queuing every message: the first message
+ // blocks the single writer goroutine (nothing reads the pipe yet), and
+ // the rest pile up behind it until the queue is full and starts dropping
+ // its oldest entries.
+ for _, socCh := range socs {
+ gsocSvc.Handle(socCh)
+ }
+
+ // the newest message is never evicted, so reading until it arrives
+ // terminates. At most one message can have left the queue before the
+ // writer blocked on the pipe, which puts the whole backlog the consumer
+ // can still see at api.GsocQueueCapacity+1 messages.
+ var (
+ received int
+ last = -1
+ )
+ for {
+ _, got, err := cl.ReadMessage()
+ if err != nil {
+ t.Fatalf("message %d: %v", received, err)
+ }
+ if len(got) != 2 {
+ t.Fatalf("message %d: got payload %q, want 2 bytes", received, got)
+ }
+ received++
+
+ index := int(got[0])<<8 | int(got[1])
+ if index <= last {
+ t.Fatalf("message %d: got index %d after %d, want increasing order", received, index, last)
+ }
+ last = index
+
+ if index == messageCount-1 {
+ break
+ }
+ }
+
+ if received > api.GsocQueueCapacity+1 {
+ t.Fatalf("got %d messages, want at most %d: the queue is not bounded", received, api.GsocQueueCapacity+1)
+ }
+}
+
+// TestGsocWebsocketPingUnderBacklog verifies that the keepalive ping keeps
+// flowing while the subscriber lags behind a continuous stream of messages, so
+// that a backlog that never drains cannot starve the ping and make the client
+// drop the connection on its own pong timeout. The connection runs over an
+// unbuffered pipe, so every write blocks until the client reads it and the
+// queue stays non-empty for the whole test.
+func TestGsocWebsocketPingUnderBacklog(t *testing.T) {
+ t.Parallel()
+
+ const (
+ pingPeriod = 50 * time.Millisecond
+ duration = time.Second
+ readDelay = 5 * time.Millisecond
+ minPings = 5
+ )
+
+ var (
+ id = make([]byte, 32)
+ gsocSvc, cl, signer = newGsocPipeTest(t, id, pingPeriod)
+ pings atomic.Int64
+ )
+
+ ch, _ := cac.New([]byte("keep the writer busy"))
+ signedCh, _ := soc.New(id, ch).Sign(signer)
+ socCh, _ := soc.FromChunk(signedCh)
+
+ cl.SetPingHandler(func(string) error {
+ pings.Add(1)
+ return nil
+ })
+
+ stop := make(chan struct{})
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ for {
+ select {
+ case <-stop:
+ return
+ default:
+ gsocSvc.Handle(socCh)
+ time.Sleep(time.Millisecond)
+ }
+ }
+ }()
+ defer func() {
+ close(stop)
+ <-done
+ }()
+
+ for end := time.Now().Add(duration); time.Now().Before(end); {
+ if _, _, err := cl.ReadMessage(); err != nil {
+ t.Fatalf("read: %v", err)
+ }
+ time.Sleep(readDelay)
+ }
+
+ if got := pings.Load(); got < minPings {
+ t.Fatalf("got %d pings in %v at a %v period, want at least %d", got, duration, pingPeriod, minPings)
+ }
+}
+
+// TestGsocWebsocketStalledConsumer verifies the failure mode of a subscriber
+// that never reads: the writer cannot hand its message over, so the write
+// deadline fires and it gives up on the connection. On its way out it closes
+// the connection, unsubscribes from the GSOC address so that nothing can queue
+// further messages, and releases the backlog nothing will ever drain (see
+// TestGsocQueueRelease for what release itself drops). The test therefore runs
+// for as long as the write deadline.
+func TestGsocWebsocketStalledConsumer(t *testing.T) {
+ t.Parallel()
+
+ id := make([]byte, 32)
+ gsocSvc, cl, signer := newGsocPipeTest(t, id, 0)
+
+ // a single message suffices: the pipe is unbuffered, so this one write
+ // pins the writer until its deadline expires.
+ ch, _ := cac.New([]byte("nobody is going to read this"))
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ gsocSvc.Handle(socCh)
+
+ select {
+ case <-gsocSvc.unsubscribed:
+ case <-time.After(longTimeout):
+ t.Fatal("the writer did not give up on a consumer that never reads")
+ }
+
+ if _, _, err := cl.ReadMessage(); err == nil {
+ t.Fatal("read a message off a connection the writer gave up on, want it closed")
+ }
+}
+
+// TestGsocQueueRelease verifies that the backlog of a subscription whose
+// writer is gone is discarded, rather than kept around by a producer that is
+// still mid-callback when the connection is torn down.
+func TestGsocQueueRelease(t *testing.T) {
+ t.Parallel()
+
+ q := api.NewGsocQueue()
+ q.Push([]byte("queued before release"))
+
+ q.Release()
+
+ if b, ok := q.Pop(); ok {
+ t.Fatalf("got %q after release, want the backlog to be discarded", b)
+ }
+
+ q.Push([]byte("queued after release"))
+ if b, ok := q.Pop(); ok {
+ t.Fatalf("got %q after release, want a late push to be discarded", b)
+ }
+}
+
+// newGsocPipeTest subscribes to the GSOC address of socID over an in-memory
+// net.Pipe instead of a real socket, so that a test fully controls when the
+// client reads: a pipe is unbuffered, so a write only completes once the other
+// side reads it, which pins the server side writer on the first message and
+// makes the queue behind it observable. A real socket would absorb the whole
+// backlog in its kernel buffers instead. It returns once the subscription is
+// registered, handing back the listener to publish through, the client end of
+// the subscription and the signer owning the subscribed address.
+func newGsocPipeTest(t *testing.T, socID []byte, pingPeriod time.Duration) (*subscribedListener, *websocket.Conn, crypto.Signer) {
+ t.Helper()
+
+ var (
+ batchStore = mockbatchstore.New()
+ storer = mockstorer.New()
+ gsocSvc = newSubscribedListener(gsoc.New(log.Noop))
+ )
+ testutil.CleanupCloser(t, gsocSvc)
+
+ _, _, _, _, svc := newTestServer(t, testServerOptions{
+ Gsoc: gsocSvc,
+ Storer: storer,
+ BatchStore: batchStore,
+ Logger: log.Noop,
+ WsPingPeriod: pingPeriod,
+ })
+
+ privKey, err := crypto.GenerateSecp256k1Key()
+ if err != nil {
+ t.Fatal(err)
+ }
+ signer := crypto.NewDefaultSigner(privKey)
+ owner, err := signer.EthereumAddress()
+ if err != nil {
+ t.Fatal(err)
+ }
+ chunkAddr, _ := soc.CreateAddress(socID, owner.Bytes())
+
+ ln := newPipeListener()
+ srv := &http.Server{Handler: svc}
+ testutil.CleanupCloser(t, srv)
+ go func() { _ = srv.Serve(ln) }()
+
+ clientConn, serverConn := net.Pipe()
+ ln.offer(serverConn)
+
+ u := url.URL{Scheme: "ws", Host: "pipe", Path: "/gsoc/subscribe/" + hex.EncodeToString(chunkAddr.Bytes())}
+ dialer := websocket.Dialer{
+ NetDial: func(_, _ string) (net.Conn, error) { return clientConn, nil },
+ }
+ cl, _, err := dialer.Dial(u.String(), nil)
+ if err != nil {
+ t.Fatalf("client handshake: %v", err)
+ }
+ testutil.CleanupCloser(t, cl)
+
+ // Dial returning only means the handshake bytes were exchanged over the
+ // pipe; it says nothing about how far the server side handler has got.
+ // Publishing before it reaches Subscribe would lose the message for good
+ // — there is no subscriber to queue it — so wait for the registration
+ // itself.
+ select {
+ case <-gsocSvc.subscribed:
+ case <-time.After(longTimeout):
+ t.Fatal("timed out waiting for the gsoc subscription")
+ }
+
+ if err := cl.SetReadDeadline(time.Now().Add(longTimeout)); err != nil {
+ t.Fatal(err)
+ }
+
+ return gsocSvc, cl, signer
+}
+
+// subscribedListener reports the completion of the first Subscribe call, and
+// of the cleanup that ends it, on channels. The GSOC listener offers no
+// readiness signal of its own, and the websocket handshake is not one either:
+// a test that published as soon as Dial returned would race the server
+// goroutine's continuation into Subscribe. The cleanup signal is the other end
+// of that: it is how a test observes the writer giving up on a connection.
+type subscribedListener struct {
+ gsoc.Listener
+ subscribed chan struct{}
+ unsubscribed chan struct{}
+ subOnce sync.Once
+ unsubOnce sync.Once
+}
+
+func newSubscribedListener(l gsoc.Listener) *subscribedListener {
+ return &subscribedListener{
+ Listener: l,
+ subscribed: make(chan struct{}),
+ unsubscribed: make(chan struct{}),
+ }
+}
+
+func (l *subscribedListener) Subscribe(address swarm.Address, handler gsoc.Handler) func() {
+ cleanup := l.Listener.Subscribe(address, handler)
+ l.subOnce.Do(func() { close(l.subscribed) })
+ return func() {
+ cleanup()
+ l.unsubOnce.Do(func() { close(l.unsubscribed) })
+ }
+}
+
+// pipeListener is a net.Listener that hands out pre-established net.Conn
+// pairs, so an http.Server can be driven over an in-memory net.Pipe instead
+// of a real OS socket.
+type pipeListener struct {
+ connCh chan net.Conn
+ closed chan struct{}
+ once sync.Once
+}
+
+func newPipeListener() *pipeListener {
+ return &pipeListener{
+ connCh: make(chan net.Conn, 1),
+ closed: make(chan struct{}),
+ }
+}
+
+func (l *pipeListener) offer(conn net.Conn) { l.connCh <- conn }
+
+func (l *pipeListener) Accept() (net.Conn, error) {
+ select {
+ case c := <-l.connCh:
+ return c, nil
+ case <-l.closed:
+ return nil, net.ErrClosed
+ }
+}
+
+func (l *pipeListener) Close() error {
+ l.once.Do(func() { close(l.closed) })
+ return nil
+}
+
+func (l *pipeListener) Addr() net.Addr { return pipeAddr{} }
+
+type pipeAddr struct{}
+
+func (pipeAddr) Network() string { return "pipe" }
+func (pipeAddr) String() string { return "pipe" }
+
+// TestGsocWebsocketMessageOrdering verifies that sequential Handle calls for
+// the same GSOC address are delivered to the subscriber in the same order.
+func TestGsocWebsocketMessageOrdering(t *testing.T) {
+ t.Parallel()
+
+ const messageCount = 10
+
+ var (
+ id = make([]byte, 32)
+ g, cl, signer, _ = newGsocTest(t, id, 0)
+ )
+
+ err := cl.SetReadDeadline(time.Now().Add(longTimeout))
+ if err != nil {
+ t.Fatal(err)
+ }
+ cl.SetReadLimit(swarm.ChunkSize)
+
+ payloads := make([][]byte, messageCount)
+ for i := range payloads {
+ payloads[i] = fmt.Appendf(nil, "message-%d", i)
+ }
+
+ for _, payload := range payloads {
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ g.Handle(socCh)
+ }
+
+ for i, want := range payloads {
+ _, got, err := cl.ReadMessage()
+ if err != nil {
+ t.Fatalf("message %d: %v", i, err)
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatalf("message %d: got %q, want %q", i, got, want)
+ }
+ }
+}
+
+// TestGsocWebsocketCacheWrappedChunk verifies that the Swarm-Cache-Wrapped-Chunk
+// header causes the wrapped chunk to be stored in the cache so that it can be
+// resolved through the bytes endpoint.
+func TestGsocWebsocketCacheWrappedChunk(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ headers = http.Header{api.SwarmCacheWrappedChunkHeader: []string{"true"}}
+ g, cl, signer, _, storer = newGsocTestWithOpts(t, id, 0, headers)
+ respC = make(chan error, 1)
+ payload = []byte("If you don't like change, you're going to like irrelevance even less.")
+ )
+
+ err := cl.SetReadDeadline(time.Now().Add(longTimeout))
+ if err != nil {
+ t.Fatal(err)
+ }
+ cl.SetReadLimit(swarm.ChunkSize)
+
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ g.Handle(socCh)
+
+ go expectMessage(t, cl, respC, payload)
+ if err := <-respC; err != nil {
+ t.Fatal(err)
+ }
+
+ // The chunk is cached on its own goroutine, so it may not be in the store
+ // yet by the time the message reaches the client.
+ var got swarm.Chunk
+ if err := spinlock.Wait(longTimeout, func() bool {
+ c, err := storer.ChunkStore().Get(context.Background(), ch.Address())
+ if err != nil {
+ return false
+ }
+ got = c
+ return true
+ }); err != nil {
+ t.Fatalf("wrapped chunk not cached: %v", err)
+ }
+ if !bytes.Equal(got.Data(), ch.Data()) {
+ t.Fatal("cached wrapped chunk data mismatch")
+ }
+}
+
+// TestGsocWebsocketCacheWrappedChunkOnce verifies that the wrapped chunk of a
+// message is written to the cache once, no matter how many subscribers of the
+// address asked for it: the store write is a node-local side effect done on
+// behalf of the node, not of a connection, and repeating it per subscriber
+// would multiply the work the sync stream goroutines pay for.
+func TestGsocWebsocketCacheWrappedChunkOnce(t *testing.T) {
+ t.Parallel()
+
+ var (
+ id = make([]byte, 32)
+ headers = http.Header{api.SwarmCacheWrappedChunkHeader: []string{"true"}}
+ chunkStore = &countingChunkStore{ChunkStore: inmemchunkstore.New()}
+ gsocSvc = &countingListener{Listener: gsoc.New(log.Noop)}
+ payloads = [][]byte{[]byte("the first message"), []byte("the second message")}
+ )
+ testutil.CleanupCloser(t, gsocSvc)
+
+ privKey, err := crypto.GenerateSecp256k1Key()
+ if err != nil {
+ t.Fatal(err)
+ }
+ signer := crypto.NewDefaultSigner(privKey)
+ owner, err := signer.EthereumAddress()
+ if err != nil {
+ t.Fatal(err)
+ }
+ chunkAddr, _ := soc.CreateAddress(id, owner.Bytes())
+ wsPath := "/gsoc/subscribe/" + hex.EncodeToString(chunkAddr.Bytes())
+
+ _, cl, listener, _, _ := newTestServer(t, testServerOptions{
+ Gsoc: gsocSvc,
+ WsPath: wsPath,
+ WsHeaders: headers,
+ Storer: mockstorer.NewWithChunkStore(chunkStore),
+ BatchStore: mockbatchstore.New(),
+ Logger: log.Noop,
+ WsPingPeriod: 10 * time.Second,
+ })
+
+ u := url.URL{Scheme: "ws", Host: listener, Path: wsPath}
+ cl2, _, err := websocket.DefaultDialer.Dial(u.String(), headers)
+ if err != nil {
+ t.Fatalf("dial: %v. url %v", err, u.String())
+ }
+ testutil.CleanupCloser(t, cl2)
+
+ for _, c := range []*websocket.Conn{cl, cl2} {
+ if err := c.SetReadDeadline(time.Now().Add(longTimeout)); err != nil {
+ t.Fatal(err)
+ }
+ c.SetReadLimit(swarm.ChunkSize)
+ }
+
+ // Neither Dial returning nor the websocket handshake says that the
+ // subscription behind the connection is registered, so wait for the
+ // subscriptions themselves: one per connection, plus the single caching
+ // one the two of them share.
+ if err := spinlock.Wait(longTimeout, func() bool { return gsocSvc.subs.Load() >= 3 }); err != nil {
+ t.Fatalf("subscriptions not registered: %v", err)
+ }
+
+ for _, payload := range payloads {
+ ch, _ := cac.New(payload)
+ socCh := soc.New(id, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+ gsocSvc.Handle(socCh)
+
+ respC := make(chan error, 2)
+ go expectMessage(t, cl, respC, payload)
+ go expectMessage(t, cl2, respC, payload)
+ for range 2 {
+ if err := <-respC; err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // The chunk is cached on its own goroutine, so it may not be in the
+ // store yet by the time the messages reach the clients.
+ if err := spinlock.Wait(longTimeout, func() bool {
+ _, err := chunkStore.Get(context.Background(), ch.Address())
+ return err == nil
+ }); err != nil {
+ t.Fatalf("wrapped chunk not cached: %v", err)
+ }
+ }
+
+ // Both messages are cached by now, so a write per subscriber rather than
+ // per message would already have happened.
+ if puts, want := chunkStore.puts.Load(), int32(len(payloads)); puts != want {
+ t.Fatalf("got %d cache writes, want %d: one per message, not one per subscriber", puts, want)
+ }
+}
+
+// TestGsocCacheSubscriptionShared verifies the bookkeeping behind that single
+// cache write: the subscribers of one address share a single caching
+// subscription, which is registered with the first of them and dropped only
+// once the last one is gone.
+func TestGsocCacheSubscriptionShared(t *testing.T) {
+ t.Parallel()
+
+ var (
+ address = swarm.RandAddress(t)
+ gsocSvc = &countingListener{Listener: gsoc.New(log.Noop)}
+ )
+ testutil.CleanupCloser(t, gsocSvc)
+
+ _, _, _, _, svc := newTestServer(t, testServerOptions{
+ Gsoc: gsocSvc,
+ Storer: mockstorer.New(),
+ BatchStore: mockbatchstore.New(),
+ Logger: log.Noop,
+ })
+
+ first := svc.CacheGsocWrappedChunks(address)
+ second := svc.CacheGsocWrappedChunks(address)
+ if subs := gsocSvc.subs.Load(); subs != 1 {
+ t.Fatalf("got %d subscriptions for two subscribers, want 1", subs)
+ }
+
+ first()
+ if subs := gsocSvc.subs.Load(); subs != 1 {
+ t.Fatalf("got %d subscriptions after one subscriber left, want the other one to keep it", subs)
+ }
+
+ second()
+ if subs := gsocSvc.subs.Load(); subs != 0 {
+ t.Fatalf("got %d subscriptions after the last subscriber left, want 0", subs)
+ }
+
+ // A subscriber arriving after the address was dropped subscribes anew.
+ third := svc.CacheGsocWrappedChunks(address)
+ if subs := gsocSvc.subs.Load(); subs != 1 {
+ t.Fatalf("got %d subscriptions for a new subscriber, want 1", subs)
+ }
+ third()
+}
+
+// countingChunkStore counts the chunks written to the store it wraps.
+type countingChunkStore struct {
+ storage.ChunkStore
+ puts atomic.Int32
+}
+
+func (s *countingChunkStore) Put(ctx context.Context, ch swarm.Chunk) error {
+ s.puts.Add(1)
+ return s.ChunkStore.Put(ctx, ch)
+}
+
+// countingListener counts the subscriptions registered on the GSOC listener it
+// wraps, so that a test can wait for the api to have registered all of them.
+type countingListener struct {
+ gsoc.Listener
+ subs atomic.Int32
+}
+
+func (l *countingListener) Subscribe(address swarm.Address, handler gsoc.Handler) func() {
+ cleanup := l.Listener.Subscribe(address, handler)
+ l.subs.Add(1)
+ return func() {
+ cleanup()
+ l.subs.Add(-1)
+ }
+}
+
func newGsocTest(t *testing.T, socId []byte, pingPeriod time.Duration) (gsoc.Listener, *websocket.Conn, crypto.Signer, string) {
+ t.Helper()
+ g, cl, signer, listener, _ := newGsocTestWithOpts(t, socId, pingPeriod, nil)
+ return g, cl, signer, listener
+}
+
+func newGsocTestWithOpts(t *testing.T, socId []byte, pingPeriod time.Duration, headers http.Header) (gsoc.Listener, *websocket.Conn, crypto.Signer, string, api.Storer) {
+ t.Helper()
+ return newGsocTestWithQuery(t, socId, pingPeriod, headers, nil)
+}
+
+func newGsocTestWithQuery(t *testing.T, socId []byte, pingPeriod time.Duration, headers http.Header, query url.Values) (gsoc.Listener, *websocket.Conn, crypto.Signer, string, api.Storer) {
t.Helper()
if pingPeriod == 0 {
pingPeriod = 10 * time.Second
@@ -158,14 +1042,16 @@ func newGsocTest(t *testing.T, socId []byte, pingPeriod time.Duration) (gsoc.Lis
gsoc := gsoc.New(log.NewLogger("test"))
testutil.CleanupCloser(t, gsoc)
- _, cl, listener, _ := newTestServer(t, testServerOptions{
+ _, cl, listener, _, _ := newTestServer(t, testServerOptions{
Gsoc: gsoc,
WsPath: fmt.Sprintf("/gsoc/subscribe/%s", hex.EncodeToString(chunkAddr.Bytes())),
+ WsHeaders: headers,
+ WsQuery: query,
Storer: storer,
BatchStore: batchStore,
Logger: log.Noop,
WsPingPeriod: pingPeriod,
})
- return gsoc, cl, signer, listener
+ return gsoc, cl, signer, listener, storer
}
diff --git a/pkg/api/health_test.go b/pkg/api/health_test.go
index 11c79f5366c..e0191f45602 100644
--- a/pkg/api/health_test.go
+++ b/pkg/api/health_test.go
@@ -19,7 +19,7 @@ func TestHealth(t *testing.T) {
t.Run("probe not set", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{})
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{})
// When probe is not set health endpoint should indicate that node is not healthy
jsonhttptest.Request(t, testServer, http.MethodGet, "/health", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(api.HealthStatusResponse{
@@ -33,7 +33,7 @@ func TestHealth(t *testing.T) {
t.Parallel()
probe := api.NewProbe()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
Probe: probe,
})
diff --git a/pkg/api/logger_test.go b/pkg/api/logger_test.go
index 78caf7078f3..8c7529d40e8 100644
--- a/pkg/api/logger_test.go
+++ b/pkg/api/logger_test.go
@@ -72,7 +72,7 @@ func TestGetLoggers(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, client, http.MethodGet, "/loggers", http.StatusOK,
jsonhttptest.WithUnmarshalJSONResponse(&have),
)
@@ -88,7 +88,7 @@ func TestSetLoggerVerbosity(t *testing.T) {
api.ReplaceLogSetVerbosityByExp(fn)
}(api.LogSetVerbosityByExp)
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
type data struct {
exp string
@@ -143,7 +143,7 @@ func TestSetLoggerVerbosity(t *testing.T) {
func Test_loggerGetHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -191,7 +191,7 @@ func Test_loggerGetHandler_invalidInputs(t *testing.T) {
func Test_loggerSetVerbosityHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/p2p_test.go b/pkg/api/p2p_test.go
index b4e60973ee9..68b9124b07d 100644
--- a/pkg/api/p2p_test.go
+++ b/pkg/api/p2p_test.go
@@ -39,7 +39,7 @@ func TestAddresses(t *testing.T) {
ethereumAddress := common.HexToAddress("abcd")
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
PublicKey: privateKey.PublicKey,
PSSPublicKey: pssPrivateKey.PublicKey,
Overlay: overlay,
@@ -78,7 +78,7 @@ func TestAddresses_error(t *testing.T) {
testErr := errors.New("test error")
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithAddressesFunc(func() ([]multiaddr.Multiaddr, error) {
return nil, testErr
})),
diff --git a/pkg/api/peer_test.go b/pkg/api/peer_test.go
index 31544edc55b..bd0ee54e1a4 100644
--- a/pkg/api/peer_test.go
+++ b/pkg/api/peer_test.go
@@ -50,7 +50,7 @@ func TestConnect(t *testing.T) {
t.Fatal(err)
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithConnectFunc(func(ctx context.Context, addrs []ma.Multiaddr) (*bzz.Address, error) {
for _, addr := range addrs {
if addr.String() == errorUnderlay {
@@ -79,7 +79,7 @@ func TestConnect(t *testing.T) {
})
t.Run("error - add peer", func(t *testing.T) {
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithConnectFunc(func(ctx context.Context, addrs []ma.Multiaddr) (*bzz.Address, error) {
for _, addr := range addrs {
if addr.String() == errorUnderlay {
@@ -107,7 +107,7 @@ func TestDisconnect(t *testing.T) {
errorAddress := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59a")
testErr := errors.New("test error")
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithDisconnectFunc(func(addr swarm.Address, reason string) error {
if reason != "user requested disconnect" {
return testErr
@@ -163,7 +163,7 @@ func TestPeer(t *testing.T) {
t.Parallel()
overlay := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59c")
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithPeersFunc(func() []p2p.Peer {
return []p2p.Peer{{Address: overlay}}
})),
@@ -184,7 +184,7 @@ func TestBlocklistedPeers(t *testing.T) {
t.Parallel()
overlay := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59c")
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithBlocklistedPeersFunc(func() ([]p2p.BlockListedPeer, error) {
return []p2p.BlockListedPeer{{Peer: p2p.Peer{Address: overlay}}}, nil
})),
@@ -201,7 +201,7 @@ func TestBlocklistedPeersErr(t *testing.T) {
t.Parallel()
overlay := swarm.MustParseHexAddress("ca1e9f3938cc1425c6061b96ad9eb93e134dfe8734ad490164ef20af9d1cf59c")
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithBlocklistedPeersFunc(func() ([]p2p.BlockListedPeer, error) {
return []p2p.BlockListedPeer{{Peer: p2p.Peer{Address: overlay}}}, errors.New("some error")
})),
@@ -219,7 +219,7 @@ func TestBlocklistedPeersErr(t *testing.T) {
func Test_peerConnectHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -254,7 +254,7 @@ func Test_peerConnectHandler_invalidInputs(t *testing.T) {
func Test_peerDisconnectHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/pin_test.go b/pkg/api/pin_test.go
index 009cae49afb..59b60bdc074 100644
--- a/pkg/api/pin_test.go
+++ b/pkg/api/pin_test.go
@@ -80,8 +80,8 @@ func checkPinHandlers(t *testing.T, client *http.Client, rootHash string, create
// nolint:paralleltest
func TestPinHandlers(t *testing.T) {
var (
- storerMock = mockstorer.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ storerMock = mockstorer.New()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -151,7 +151,7 @@ func TestPinHandlers(t *testing.T) {
func TestPinHandlersInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -205,7 +205,7 @@ func TestIntegrityHandler(t *testing.T) {
t.Run("ok", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
PinIntegrity: &mockPinIntegrity{
Store: inmemstore.New(),
tester: t,
@@ -220,7 +220,7 @@ func TestIntegrityHandler(t *testing.T) {
t.Run("wrong hash format", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
PinIntegrity: &mockPinIntegrity{
Store: inmemstore.New(),
tester: t,
diff --git a/pkg/api/pingpong_test.go b/pkg/api/pingpong_test.go
index 20f8af6a06a..8872f655fab 100644
--- a/pkg/api/pingpong_test.go
+++ b/pkg/api/pingpong_test.go
@@ -38,7 +38,7 @@ func TestPingpong(t *testing.T) {
return rtt, nil
})
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
Pingpong: pingpongService,
})
@@ -78,7 +78,7 @@ func TestPingpong(t *testing.T) {
func Test_pingpongHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/postage_test.go b/pkg/api/postage_test.go
index fd5db2cf666..86dc2c2e66a 100644
--- a/pkg/api/postage_test.go
+++ b/pkg/api/postage_test.go
@@ -64,7 +64,7 @@ func TestPostageCreateStamp(t *testing.T) {
return txHash, batchID, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -88,7 +88,7 @@ func TestPostageCreateStamp(t *testing.T) {
return common.Hash{}, nil, errors.New("err")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -108,7 +108,7 @@ func TestPostageCreateStamp(t *testing.T) {
return common.Hash{}, nil, postagecontract.ErrInsufficientFunds
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -128,7 +128,7 @@ func TestPostageCreateStamp(t *testing.T) {
return common.Hash{}, nil, postagecontract.ErrInvalidDepth
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -156,7 +156,7 @@ func TestPostageCreateStamp(t *testing.T) {
return txHash, batchID, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -176,7 +176,7 @@ func TestPostageCreateStamp(t *testing.T) {
t.Run("syncing in progress", func(t *testing.T) {
t.Parallel()
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
SyncStatus: func() (bool, error) { return false, nil },
})
@@ -190,7 +190,7 @@ func TestPostageCreateStamp(t *testing.T) {
t.Run("syncing failed", func(t *testing.T) {
t.Parallel()
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
SyncStatus: func() (bool, error) { return true, errors.New("oops") },
})
@@ -216,7 +216,7 @@ func TestPostageGetStamps(t *testing.T) {
t.Parallel()
bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b))
- ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second})
jsonhttptest.Request(t, ts, http.MethodGet, "/stamps", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.PostageStampsResponse{
@@ -253,7 +253,7 @@ func TestPostageGetStamps(t *testing.T) {
}
ecs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(15), CurrentPrice: big.NewInt(12)}
ebs := mock.New(mock.WithChainState(ecs))
- ts, _, _, _ := newTestServer(t, testServerOptions{Post: emp, BatchStore: ebs, BlockTime: 2 * time.Second})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{Post: emp, BatchStore: ebs, BlockTime: 2 * time.Second})
jsonhttptest.Request(t, ts, http.MethodGet, "/stamps/"+hex.EncodeToString(eb.ID), http.StatusNotFound,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
@@ -275,7 +275,7 @@ func TestGetAllBatches(t *testing.T) {
mp := mockpost.New(mockpost.WithIssuer(si))
cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)}
bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b))
- ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second})
oneBatch := struct {
Batches []api.PostageBatchResponse `json:"batches"`
@@ -312,7 +312,7 @@ func TestGetBatch(t *testing.T) {
b.Value = big.NewInt(20)
cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)}
bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b))
- ts, _, _, _ := newTestServer(t, testServerOptions{BatchStore: bs, BlockTime: 2 * time.Second})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{BatchStore: bs, BlockTime: 2 * time.Second})
want := api.PostageBatchResponse{
BatchID: b.ID,
@@ -367,7 +367,7 @@ func TestPostageGetStamp(t *testing.T) {
mp := mockpost.New(mockpost.WithIssuer(si))
cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)}
bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b))
- ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs, BlockTime: 2 * time.Second})
t.Run("ok", func(t *testing.T) {
t.Parallel()
@@ -396,7 +396,7 @@ func TestPostageGetBuckets(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si))
- ts, _, _, _ := newTestServer(t, testServerOptions{Post: mp})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{Post: mp})
buckets := make([]api.BucketData, 1024)
for i := range buckets {
buckets[i] = api.BucketData{BucketID: uint32(i)}
@@ -419,7 +419,7 @@ func TestPostageGetBuckets(t *testing.T) {
t.Parallel()
mpNotFound := mockpost.New()
- tsNotFound, _, _, _ := newTestServer(t, testServerOptions{Post: mpNotFound})
+ tsNotFound, _, _, _, _ := newTestServer(t, testServerOptions{Post: mpNotFound})
jsonhttptest.Request(t, tsNotFound, http.MethodGet, "/stamps/"+batchOkStr+"/buckets", http.StatusNotFound)
})
@@ -434,7 +434,7 @@ func TestReserveState(t *testing.T) {
s := mockstorer.New()
s.SetStorageRadius(3)
s.SetCommittedDepth(5)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
BatchStore: mock.New(mock.WithRadius(5)),
Storer: s,
})
@@ -449,7 +449,7 @@ func TestReserveState(t *testing.T) {
t.Run("empty", func(t *testing.T) {
t.Parallel()
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
BatchStore: mock.New(),
Storer: mockstorer.New(),
})
@@ -475,7 +475,7 @@ func TestChainState(t *testing.T) {
return 17280, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
BatchStore: mock.New(mock.WithChainState(cs)),
BackendOpts: []backendmock.Option{backendmock.WithBlockNumberFunc(func(ctx context.Context) (uint64, error) {
return 1, nil
@@ -505,7 +505,7 @@ func TestChainState(t *testing.T) {
TotalAmount: big.NewInt(50),
CurrentPrice: big.NewInt(5),
}
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
BatchStore: mock.New(mock.WithChainState(cs)),
BackendOpts: []backendmock.Option{backendmock.WithBlockNumberFunc(func(ctx context.Context) (uint64, error) {
return 1, nil
@@ -547,7 +547,7 @@ func TestPostageTopUpStamp(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -576,7 +576,7 @@ func TestPostageTopUpStamp(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -597,7 +597,7 @@ func TestPostageTopUpStamp(t *testing.T) {
return common.Hash{}, errors.New("err")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -617,7 +617,7 @@ func TestPostageTopUpStamp(t *testing.T) {
return common.Hash{}, postagecontract.ErrInsufficientFunds
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -640,7 +640,7 @@ func TestPostageTopUpStamp(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -677,7 +677,7 @@ func TestPostageDiluteStamp(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -706,7 +706,7 @@ func TestPostageDiluteStamp(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -727,7 +727,7 @@ func TestPostageDiluteStamp(t *testing.T) {
return common.Hash{}, errors.New("err")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -747,7 +747,7 @@ func TestPostageDiluteStamp(t *testing.T) {
return common.Hash{}, postagecontract.ErrInvalidDepth
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -770,7 +770,7 @@ func TestPostageDiluteStamp(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -886,7 +886,7 @@ func TestPostageAccessHandler(t *testing.T) {
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
@@ -911,7 +911,7 @@ func TestPostageAccessHandler(t *testing.T) {
func Test_postageCreateHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -961,7 +961,7 @@ func Test_postageCreateHandler_invalidInputs(t *testing.T) {
func Test_postageGetStampBucketsHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -1022,7 +1022,7 @@ func Test_postageGetStampBucketsHandler_invalidInputs(t *testing.T) {
func Test_postageGetStampHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -1084,7 +1084,7 @@ func Test_postageGetStampHandler_invalidInputs(t *testing.T) {
func Test_postageTopUpHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -1163,7 +1163,7 @@ func Test_postageTopUpHandler_invalidInputs(t *testing.T) {
func Test_postageDiluteHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -1250,7 +1250,7 @@ func TestPostageUpdateLabelStamp(t *testing.T) {
si := postage.NewStampIssuer("original", "test identity", batchID, big.NewInt(3), 24, 6, 1000, false)
mp := mockpost.New(mockpost.WithIssuer(si))
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
Post: mp,
})
@@ -1265,7 +1265,7 @@ func TestPostageUpdateLabelStamp(t *testing.T) {
t.Parallel()
mp := mockpost.New()
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
Post: mp,
})
@@ -1281,7 +1281,7 @@ func TestPostageUpdateLabelStamp(t *testing.T) {
si := postage.NewStampIssuer("original", "test identity", batchID, big.NewInt(3), 24, 6, 1000, false)
mp := mockpost.New(mockpost.WithIssuer(si))
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
Post: mp,
})
@@ -1299,7 +1299,7 @@ func TestPostageUpdateLabelStamp(t *testing.T) {
// must be rejected rather than silently blanking the issuer label.
si := postage.NewStampIssuer("original", "test identity", batchID, big.NewInt(3), 24, 6, 1000, false)
mp := mockpost.New(mockpost.WithIssuer(si))
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
Post: mp,
})
@@ -1317,7 +1317,7 @@ func TestPostageUpdateLabelStamp(t *testing.T) {
func Test_postageUpdateLabelHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/pss_test.go b/pkg/api/pss_test.go
index d78f8ea668f..6bd048aa9d3 100644
--- a/pkg/api/pss_test.go
+++ b/pkg/api/pss_test.go
@@ -181,9 +181,9 @@ func TestPssSend(t *testing.T) {
mtx.Unlock()
return err
}
- mp = mockpost.New(mockpost.WithIssuer(postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)))
- p = newMockPss(sendFn)
- client, _, _, _ = newTestServer(t, testServerOptions{
+ mp = mockpost.New(mockpost.WithIssuer(postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)))
+ p = newMockPss(sendFn)
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Pss: p,
Storer: mockstorer.New(),
Post: mp,
@@ -382,7 +382,7 @@ func newPssTest(t *testing.T, o opts) (pss.Interface, *ecdsa.PublicKey, *websock
if o.pingPeriod == 0 {
o.pingPeriod = 10 * time.Second
}
- _, cl, listener, _ := newTestServer(t, testServerOptions{
+ _, cl, listener, _, _ := newTestServer(t, testServerOptions{
Pss: pss,
WsPath: "/pss/subscribe/testtopic",
Storer: mockstorer.New(),
@@ -396,7 +396,7 @@ func newPssTest(t *testing.T, o opts) (pss.Interface, *ecdsa.PublicKey, *websock
func TestPssPostHandlerInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/readiness_test.go b/pkg/api/readiness_test.go
index 0454f541c2f..7aa84a27733 100644
--- a/pkg/api/readiness_test.go
+++ b/pkg/api/readiness_test.go
@@ -18,7 +18,7 @@ func TestReadiness(t *testing.T) {
t.Run("probe not set", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{})
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{})
// When probe is not set readiness endpoint should indicate that API is not ready
jsonhttptest.Request(t, testServer, http.MethodGet, "/readiness", http.StatusBadRequest)
@@ -28,7 +28,7 @@ func TestReadiness(t *testing.T) {
t.Parallel()
probe := api.NewProbe()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
Probe: probe,
})
diff --git a/pkg/api/redistribution_test.go b/pkg/api/redistribution_test.go
index 879657c9e1d..cccc2383769 100644
--- a/pkg/api/redistribution_test.go
+++ b/pkg/api/redistribution_test.go
@@ -36,7 +36,7 @@ func TestRedistributionStatus(t *testing.T) {
if err != nil {
t.Errorf("redistribution put state: %v", err)
}
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
StateStorer: store,
TransactionOpts: []mock.Option{
mock.WithTransactionFeeFunc(func(ctx context.Context, txHash common.Hash) (*big.Int, error) {
@@ -86,7 +86,7 @@ func TestRedistributionStatus(t *testing.T) {
if err != nil {
t.Errorf("redistribution put state: %v", err)
}
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
StateStorer: store,
TransactionOpts: []mock.Option{
mock.WithTransactionFeeFunc(func(ctx context.Context, txHash common.Hash) (*big.Int, error) {
@@ -129,7 +129,7 @@ func TestRedistributionStatus(t *testing.T) {
if err != nil {
t.Errorf("redistribution put state: %v", err)
}
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
StateStorer: store,
TransactionOpts: []mock.Option{
mock.WithTransactionFeeFunc(func(ctx context.Context, txHash common.Hash) (*big.Int, error) {
@@ -160,7 +160,7 @@ func TestRedistributionStatus(t *testing.T) {
t.Run("bad request", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
BeeMode: api.LightMode,
StateStorer: statestore.NewStateStore(),
TransactionOpts: []mock.Option{
@@ -214,7 +214,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("patch false then true", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, redistributionTestOpts(t))
+ srv, _, _, _, _ := newTestServer(t, redistributionTestOpts(t))
jsonhttptest.Request(t, srv, http.MethodPatch, "/redistributionstate", http.StatusOK,
jsonhttptest.WithJSONRequestBody(map[string]any{"enabled": false}),
@@ -245,7 +245,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("missing enabled", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, redistributionTestOpts(t))
+ srv, _, _, _, _ := newTestServer(t, redistributionTestOpts(t))
jsonhttptest.Request(t, srv, http.MethodPatch, "/redistributionstate", http.StatusBadRequest,
jsonhttptest.WithJSONRequestBody(map[string]any{}),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
@@ -258,7 +258,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("null enabled", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, redistributionTestOpts(t))
+ srv, _, _, _, _ := newTestServer(t, redistributionTestOpts(t))
jsonhttptest.Request(t, srv, http.MethodPatch, "/redistributionstate", http.StatusBadRequest,
jsonhttptest.WithJSONRequestBody(map[string]any{"enabled": nil}),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
@@ -271,7 +271,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("malformed json", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, redistributionTestOpts(t))
+ srv, _, _, _, _ := newTestServer(t, redistributionTestOpts(t))
jsonhttptest.Request(t, srv, http.MethodPatch, "/redistributionstate", http.StatusBadRequest,
jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "application/json"),
jsonhttptest.WithRequestBody(bytes.NewReader([]byte("{invalid"))),
@@ -285,7 +285,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("light mode", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
BeeMode: api.LightMode,
StateStorer: statestore.NewStateStore(),
})
@@ -301,7 +301,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("forbidden when agent missing", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
RedistributionAgentDisabled: true,
})
jsonhttptest.Request(t, srv, http.MethodPatch, "/redistributionstate", http.StatusForbidden,
@@ -316,7 +316,7 @@ func TestRedistributionToggle(t *testing.T) {
t.Run("unavailable when full api disabled", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
FullAPIDisabled: true,
})
jsonhttptest.Request(t, srv, http.MethodPatch, "/redistributionstate", http.StatusServiceUnavailable,
diff --git a/pkg/api/router_test.go b/pkg/api/router_test.go
index 4aaf1388aa2..f7a49dc27ae 100644
--- a/pkg/api/router_test.go
+++ b/pkg/api/router_test.go
@@ -411,7 +411,7 @@ func TestEndpointOptions(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, tc.serverOptions)
+ testServer, _, _, _, _ := newTestServer(t, tc.serverOptions)
routeToName := func(route string) string {
if route == "/" {
diff --git a/pkg/api/settlements_test.go b/pkg/api/settlements_test.go
index ae1f73439f8..d9964846fc6 100644
--- a/pkg/api/settlements_test.go
+++ b/pkg/api/settlements_test.go
@@ -38,7 +38,7 @@ func TestSettlements(t *testing.T) {
return ret, err
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []mock.Option{mock.WithSettlementsSentFunc(settlementsSentFunc), mock.WithSettlementsRecvFunc(settlementsRecvFunc)},
})
@@ -87,7 +87,7 @@ func TestSettlementsError(t *testing.T) {
settlementsSentFunc := func() (map[string]*big.Int, error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []mock.Option{mock.WithSettlementsSentFunc(settlementsSentFunc)},
})
@@ -106,7 +106,7 @@ func TestSettlementsPeers(t *testing.T) {
settlementSentFunc := func(swarm.Address) (*big.Int, error) {
return big.NewInt(1000000000000000000), nil
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []mock.Option{mock.WithSettlementSentFunc(settlementSentFunc)},
})
@@ -133,7 +133,7 @@ func TestSettlementsPeersNoSettlements(t *testing.T) {
t.Run("no sent", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []mock.Option{
mock.WithSettlementSentFunc(errFunc),
mock.WithSettlementRecvFunc(noErrFunc),
@@ -152,7 +152,7 @@ func TestSettlementsPeersNoSettlements(t *testing.T) {
t.Run("no received", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []mock.Option{
mock.WithSettlementSentFunc(noErrFunc),
mock.WithSettlementRecvFunc(errFunc),
@@ -172,7 +172,7 @@ func TestSettlementsPeersNoSettlements(t *testing.T) {
func Test_peerSettlementsHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -225,7 +225,7 @@ func TestSettlementsPeersError(t *testing.T) {
settlementSentFunc := func(swarm.Address) (*big.Int, error) {
return nil, wantErr
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
SwapOpts: []mock.Option{mock.WithSettlementSentFunc(settlementSentFunc)},
})
diff --git a/pkg/api/soc_test.go b/pkg/api/soc_test.go
index 30b0295a9aa..21f5ff386d1 100644
--- a/pkg/api/soc_test.go
+++ b/pkg/api/soc_test.go
@@ -34,7 +34,7 @@ func TestSOC(t *testing.T) {
mockStorer = mockstorer.New()
)
t.Run("empty data", func(t *testing.T) {
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -57,7 +57,7 @@ func TestSOC(t *testing.T) {
sig[12] = 0x98
sig[10] = 0x12
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -74,7 +74,7 @@ func TestSOC(t *testing.T) {
t.Run("ok", func(t *testing.T) {
s := testingsoc.GenerateMockSOC(t, testData)
- client, _, _, chanStore := newTestServer(t, testServerOptions{
+ client, _, _, chanStore, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -127,7 +127,7 @@ func TestSOC(t *testing.T) {
s := testingsoc.GenerateMockSOC(t, testData)
t.Run("err - bad batch", func(t *testing.T) {
hexbatch := "abcdefgg"
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -150,7 +150,7 @@ func TestSOC(t *testing.T) {
t.Run("ok batch", func(t *testing.T) {
s := testingsoc.GenerateMockSOC(t, testData)
hexbatch := hex.EncodeToString(batchOk)
- client, _, _, chanStorer := newTestServer(t, testServerOptions{
+ client, _, _, chanStorer, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
@@ -169,7 +169,7 @@ func TestSOC(t *testing.T) {
s := testingsoc.GenerateMockSOC(t, testData)
hexbatch := hex.EncodeToString(batchOk)
storer := mockstorer.New()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storer,
Post: newTestPostService(),
DirectUpload: true,
@@ -193,7 +193,7 @@ func TestSOC(t *testing.T) {
s := testingsoc.GenerateMockSOC(t, testData)
hexbatch := hex.EncodeToString(batchOk)
storer := mockstorer.New()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storer,
Post: newTestPostService(),
DirectUpload: true,
@@ -222,10 +222,10 @@ func TestSOC(t *testing.T) {
t.Parallel()
var (
- s = testingsoc.GenerateMockSOC(t, testData)
- storerMock = mockstorer.New()
- batchStore = mockbatchstore.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ s = testingsoc.GenerateMockSOC(t, testData)
+ storerMock = mockstorer.New()
+ batchStore = mockbatchstore.New()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
BatchStore: batchStore,
})
@@ -254,7 +254,7 @@ func TestSOC(t *testing.T) {
t.Run("err - batch empty", func(t *testing.T) {
s := testingsoc.GenerateMockSOC(t, testData)
hexbatch := hex.EncodeToString(batchEmpty)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockStorer,
Post: newTestPostService(),
DirectUpload: true,
diff --git a/pkg/api/staking_test.go b/pkg/api/staking_test.go
index febd9823bba..006a84d5eb3 100644
--- a/pkg/api/staking_test.go
+++ b/pkg/api/staking_test.go
@@ -39,7 +39,7 @@ func TestDepositStake(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusOK)
})
@@ -52,7 +52,7 @@ func TestDepositStake(t *testing.T) {
return common.Hash{}, &staking.MinDepositError{Minimum: minDeposit}
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodPost, depositStake("1"), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&api.StakeDepositErrorResponse{
Code: http.StatusBadRequest,
@@ -69,7 +69,7 @@ func TestDepositStake(t *testing.T) {
return common.Hash{}, staking.ErrInsufficientFunds
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusBadRequest)
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "out of funds"})
})
@@ -82,7 +82,7 @@ func TestDepositStake(t *testing.T) {
return common.Hash{}, fmt.Errorf("some error")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusInternalServerError)
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot stake"})
})
@@ -98,7 +98,7 @@ func TestDepositStake(t *testing.T) {
return common.Hash{}, false, fmt.Errorf("update height failed")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.StakeTransactionReponse{TxHash: txHash.String()}))
})
@@ -115,7 +115,7 @@ func TestDepositStake(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
StakingContract: contract,
})
@@ -136,7 +136,7 @@ func TestGetStakeCommitted(t *testing.T) {
return big.NewInt(1), nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.GetStakeResponse{
StakedAmount: bigint.Wrap(big.NewInt(1)),
@@ -152,7 +152,7 @@ func TestGetStakeCommitted(t *testing.T) {
return big.NewInt(0), fmt.Errorf("get stake failed")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError})
jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusInternalServerError,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "get staked amount failed"}))
})
@@ -168,7 +168,7 @@ func TestGetStakeCommitted(t *testing.T) {
return nil, fmt.Errorf("get minimum deposit failed")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError})
jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusInternalServerError,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "get minimum deposit failed"}))
})
@@ -185,7 +185,7 @@ func TestGetStakeWithdrawable(t *testing.T) {
return big.NewInt(1), nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodGet, "/stake/withdrawable", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.GetWithdrawableResponse{WithdrawableAmount: bigint.Wrap(big.NewInt(1))}))
})
@@ -198,7 +198,7 @@ func TestGetStakeWithdrawable(t *testing.T) {
return big.NewInt(0), fmt.Errorf("get stake failed")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError})
jsonhttptest.Request(t, ts, http.MethodGet, "/stake/withdrawable", http.StatusInternalServerError,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "get staked amount failed"}))
})
@@ -207,7 +207,7 @@ func TestGetStakeWithdrawable(t *testing.T) {
func Test_stakingDepositHandler_invalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
@@ -252,7 +252,7 @@ func TestWithdrawStake(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(
&api.StakeTransactionReponse{TxHash: txHash.String()}))
})
@@ -265,7 +265,7 @@ func TestWithdrawStake(t *testing.T) {
return common.Hash{}, staking.ErrInsufficientStake
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake to withdraw"}))
})
@@ -278,7 +278,7 @@ func TestWithdrawStake(t *testing.T) {
return common.Hash{}, fmt.Errorf("some error")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusInternalServerError)
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot withdraw stake"})
})
@@ -295,7 +295,7 @@ func TestWithdrawStake(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
StakingContract: contract,
})
@@ -318,7 +318,7 @@ func TestMigrateStake(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(
&api.StakeTransactionReponse{TxHash: txHash.String()}))
})
@@ -331,7 +331,7 @@ func TestMigrateStake(t *testing.T) {
return common.Hash{}, staking.ErrInsufficientStake
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake to migrate"}))
})
@@ -344,7 +344,7 @@ func TestMigrateStake(t *testing.T) {
return common.Hash{}, fmt.Errorf("some error")
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract})
jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusInternalServerError)
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot withdraw stake"})
})
@@ -361,7 +361,7 @@ func TestMigrateStake(t *testing.T) {
return txHash, nil
}),
)
- ts, _, _, _ := newTestServer(t, testServerOptions{
+ ts, _, _, _, _ := newTestServer(t, testServerOptions{
StakingContract: contract,
})
diff --git a/pkg/api/status_test.go b/pkg/api/status_test.go
index 9da5797caf8..5844e86547e 100644
--- a/pkg/api/status_test.go
+++ b/pkg/api/status_test.go
@@ -65,7 +65,7 @@ func TestGetStatus(t *testing.T) {
statusSvc.SetSync(ssMock)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
BeeMode: mode,
NodeStatus: statusSvc,
})
@@ -93,7 +93,7 @@ func TestGetStatusPeersIncludesBootnodes(t *testing.T) {
statusSvc := status.NewService(log.Noop, nil, new(topologyPeersIterNoopMock), api.FullMode.String(), nil, nil, nil)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
BeeMode: api.FullMode,
NodeStatus: statusSvc,
TopologyOpts: topoOpts,
diff --git a/pkg/api/stewardship_test.go b/pkg/api/stewardship_test.go
index 9b8831dd60b..6d2a1d93305 100644
--- a/pkg/api/stewardship_test.go
+++ b/pkg/api/stewardship_test.go
@@ -36,7 +36,7 @@ func TestStewardship(t *testing.T) {
storer = mockstorer.New()
addr = swarm.NewAddress([]byte{31: 128})
)
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storer,
Logger: logger,
Steward: stewardMock,
@@ -85,10 +85,10 @@ func TestStewardshipWithRedundancy(t *testing.T) {
t.Parallel()
var (
- storerMock = mockstorer.New()
- localRetrieval = &localRetriever{getter: storerMock.ChunkStore()}
- s = steward.New(storerMock, localRetrieval, storerMock.Cache())
- client, _, _, _ = newTestServer(t, testServerOptions{
+ storerMock = mockstorer.New()
+ localRetrieval = &localRetriever{getter: storerMock.ChunkStore()}
+ s = steward.New(storerMock, localRetrieval, storerMock.Cache())
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Logger: log.Noop,
Steward: s,
@@ -124,7 +124,7 @@ func TestStewardshipWithRedundancy(t *testing.T) {
func TestStewardshipInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: mockstorer.New(),
})
diff --git a/pkg/api/subdomain_test.go b/pkg/api/subdomain_test.go
index c1f7fbed142..76f142b4ad4 100644
--- a/pkg/api/subdomain_test.go
+++ b/pkg/api/subdomain_test.go
@@ -92,10 +92,10 @@ func TestSubdomains(t *testing.T) {
t.Parallel()
var (
- dirUploadResource = "/bzz"
- storer = mockstorer.New()
- logger = log.Noop
- client, _, _, _ = newTestServer(t, testServerOptions{
+ dirUploadResource = "/bzz"
+ storer = mockstorer.New()
+ logger = log.Noop
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
Logger: logger,
PreventRedirect: true,
diff --git a/pkg/api/tag_test.go b/pkg/api/tag_test.go
index 3a7697ddae8..834bd7a74e4 100644
--- a/pkg/api/tag_test.go
+++ b/pkg/api/tag_test.go
@@ -26,9 +26,9 @@ func tagsWithIdResource(id uint64) string { return fmt.Sprintf("/tags/%d", id) }
// nolint:paralleltest
func TestTags(t *testing.T) {
var (
- tagsResource = "/tags"
- storerMock = mockstorer.New()
- client, _, _, _ = newTestServer(t, testServerOptions{
+ tagsResource = "/tags"
+ storerMock = mockstorer.New()
+ client, _, _, _, _ = newTestServer(t, testServerOptions{
Storer: storerMock,
Post: mockpost.New(mockpost.WithAcceptAll()),
})
@@ -199,7 +199,7 @@ func TestTags(t *testing.T) {
func TestTagsHandlersInvalidInputs(t *testing.T) {
t.Parallel()
- client, _, _, _ := newTestServer(t, testServerOptions{})
+ client, _, _, _, _ := newTestServer(t, testServerOptions{})
tests := []struct {
name string
diff --git a/pkg/api/topology_test.go b/pkg/api/topology_test.go
index 11dd6703791..2e2b34e4da9 100644
--- a/pkg/api/topology_test.go
+++ b/pkg/api/topology_test.go
@@ -14,7 +14,7 @@ import (
func TestTopologyOK(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{})
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{})
var body []byte
opts := jsonhttptest.WithPutResponseBody(&body)
diff --git a/pkg/api/tracing_test.go b/pkg/api/tracing_test.go
index f07fb5565c7..34430163320 100644
--- a/pkg/api/tracing_test.go
+++ b/pkg/api/tracing_test.go
@@ -44,7 +44,7 @@ func TestTracingHTTPSpan(t *testing.T) {
t.Cleanup(func() { _ = tp.Shutdown(context.Background()) })
storerMock := mockstorer.New()
- client, _, _, _ := newTestServer(t, testServerOptions{
+ client, _, _, _, _ := newTestServer(t, testServerOptions{
Storer: storerMock,
Tracer: tracing.NewTracerFromProvider(tp),
Logger: log.Noop,
diff --git a/pkg/api/transaction_test.go b/pkg/api/transaction_test.go
index c23cc9cf3f5..d069ebb34d4 100644
--- a/pkg/api/transaction_test.go
+++ b/pkg/api/transaction_test.go
@@ -41,7 +41,7 @@ func TestTransactionStoredTransaction(t *testing.T) {
t.Run("found", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) {
return &transaction.StoredTransaction{
@@ -82,7 +82,7 @@ func TestTransactionStoredTransaction(t *testing.T) {
t.Run("not found", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) {
return nil, transaction.ErrUnknownTransaction
@@ -100,7 +100,7 @@ func TestTransactionStoredTransaction(t *testing.T) {
t.Run("other errors", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithStoredTransactionFunc(func(txHash common.Hash) (*transaction.StoredTransaction, error) {
return nil, errors.New("err")
@@ -151,7 +151,7 @@ func TestTransactionList(t *testing.T) {
},
}
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithPendingTransactionsFunc(func() ([]common.Hash, error) {
return []common.Hash{txHash1, txHash2}, nil
@@ -205,7 +205,7 @@ func TestTransactionListError(t *testing.T) {
t.Run("pending transactions error", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithPendingTransactionsFunc(func() ([]common.Hash, error) {
return nil, errors.New("err")
@@ -227,7 +227,7 @@ func TestTransactionListError(t *testing.T) {
t.Run("pending transactions error", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithPendingTransactionsFunc(func() ([]common.Hash, error) {
return []common.Hash{txHash1}, nil
@@ -254,7 +254,7 @@ func TestTransactionResend(t *testing.T) {
t.Run("ok", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error {
return nil
@@ -272,7 +272,7 @@ func TestTransactionResend(t *testing.T) {
t.Run("unknown transaction", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error {
return transaction.ErrUnknownTransaction
@@ -291,7 +291,7 @@ func TestTransactionResend(t *testing.T) {
t.Run("already imported", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error {
return transaction.ErrAlreadyImported
@@ -310,7 +310,7 @@ func TestTransactionResend(t *testing.T) {
t.Run("other error", func(t *testing.T) {
t.Parallel()
- testServer, _, _, _ := newTestServer(t, testServerOptions{
+ testServer, _, _, _, _ := newTestServer(t, testServerOptions{
TransactionOpts: []mock.Option{
mock.WithResendTransactionFunc(func(ctx context.Context, txHash common.Hash) error {
return errors.New("err")
diff --git a/pkg/api/wallet_test.go b/pkg/api/wallet_test.go
index c8bf92b1c01..7dfe45e7c7e 100644
--- a/pkg/api/wallet_test.go
+++ b/pkg/api/wallet_test.go
@@ -27,7 +27,7 @@ func TestWallet(t *testing.T) {
t.Run("Okay", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
return big.NewInt(10000000000000000), nil
@@ -52,7 +52,7 @@ func TestWallet(t *testing.T) {
t.Run("500 - erc20 error", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
BackendOpts: []backendmock.Option{
backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) {
return new(big.Int), nil
@@ -70,7 +70,7 @@ func TestWallet(t *testing.T) {
t.Run("500 - chain backend error", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
return new(big.Int), nil
@@ -88,7 +88,7 @@ func TestWallet(t *testing.T) {
t.Run("swap disabled", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
SwapDisabled: true,
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
@@ -114,7 +114,7 @@ func TestWallet(t *testing.T) {
t.Run("erc20 service unavailable", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
SwapDisabled: true,
Erc20ServiceNil: true,
BackendOpts: []backendmock.Option{
@@ -136,7 +136,7 @@ func TestWallet(t *testing.T) {
t.Run("chequebook disabled", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
ChequebookDisabled: true,
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
@@ -166,7 +166,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("address not whitelisted", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{})
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, srv, http.MethodPost, "/wallet/withdraw/BZZ?address=0xaf&amount=99999999", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
@@ -178,7 +178,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("invalid coin type", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{})
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, srv, http.MethodPost, "/wallet/withdraw/BTC?address=0xaf&amount=99999999", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
@@ -190,7 +190,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("BZZ erc20 balance error", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
})
@@ -204,7 +204,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("BZZ erc20 balance insufficient", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
@@ -223,7 +223,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("BZZ erc20 transfer error", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
@@ -244,7 +244,7 @@ func TestWalletWithdraw(t *testing.T) {
txHash := common.HexToHash("0x00f")
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
Erc20Opts: []erc20mock.Option{
erc20mock.WithBalanceOfFunc(func(ctx context.Context, address common.Address) (*big.Int, error) {
@@ -271,7 +271,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("native balance error", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
})
@@ -285,7 +285,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("native insufficient balance", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
BackendOpts: []backendmock.Option{
backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) {
@@ -304,7 +304,7 @@ func TestWalletWithdraw(t *testing.T) {
t.Run("native backend send error", func(t *testing.T) {
t.Parallel()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
BackendOpts: []backendmock.Option{
backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) {
@@ -325,7 +325,7 @@ func TestWalletWithdraw(t *testing.T) {
txHash := common.HexToHash("0x00f")
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
WhitelistedAddr: "0xaf",
BackendOpts: []backendmock.Option{
backendmock.WithBalanceAt(func(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) {
diff --git a/pkg/api/welcome_message_test.go b/pkg/api/welcome_message_test.go
index 4127f8e3e43..92a5ad1a3c3 100644
--- a/pkg/api/welcome_message_test.go
+++ b/pkg/api/welcome_message_test.go
@@ -22,7 +22,7 @@ func TestGetWelcomeMessage(t *testing.T) {
const DefaultTestWelcomeMessage = "Hello World!"
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithGetWelcomeMessageFunc(func() string {
return DefaultTestWelcomeMessage
})),
@@ -74,7 +74,7 @@ func TestSetWelcomeMessage(t *testing.T) {
t.Run(tC.desc, func(t *testing.T) {
mockP2P := mock.New()
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mockP2P,
})
@@ -110,7 +110,7 @@ func TestSetWelcomeMessageInternalServerError(t *testing.T) {
testError := errors.New("Could not set value")
testURL := "/welcome-message"
- srv, _, _, _ := newTestServer(t, testServerOptions{
+ srv, _, _, _, _ := newTestServer(t, testServerOptions{
P2P: mock.New(mock.WithSetWelcomeMessageFunc(func(string) error {
return testError
})),
diff --git a/pkg/gsoc/gsoc.go b/pkg/gsoc/gsoc.go
index 41e4f54ac2c..bf9bd09e013 100644
--- a/pkg/gsoc/gsoc.go
+++ b/pkg/gsoc/gsoc.go
@@ -5,6 +5,7 @@
package gsoc
import (
+ "slices"
"sync"
"github.com/ethersphere/bee/v2/pkg/log"
@@ -13,8 +14,9 @@ import (
)
// Handler defines code to be executed upon reception of a GSOC sub message.
-// it is used as a parameter definition.
-type Handler func([]byte)
+// it is used as a parameter definition. It receives the recovered single owner
+// chunk so the consumer has access to all of its properties.
+type Handler func(*soc.SOC)
type Listener interface {
Subscribe(address swarm.Address, handler Handler) (cleanup func())
@@ -39,20 +41,35 @@ func New(logger log.Logger) Listener {
}
// Subscribe allows the definition of a Handler func on a specific GSOC address.
+//
+// Handle iterates the handlers of an address without holding handlersMu, so a
+// slice that has been handed out must never be written to again. Subscribing
+// and unsubscribing therefore publish a new slice instead of appending to, or
+// shifting elements within, the backing array a concurrent Handle may be
+// reading.
func (l *listener) Subscribe(address swarm.Address, handler Handler) (cleanup func()) {
+ key := address.ByteString()
+
l.handlersMu.Lock()
defer l.handlersMu.Unlock()
- l.handlers[address.ByteString()] = append(l.handlers[address.ByteString()], &handler)
+ l.handlers[key] = append(slices.Clone(l.handlers[key]), &handler)
return func() {
l.handlersMu.Lock()
defer l.handlersMu.Unlock()
- h := l.handlers[address.ByteString()]
+ h := l.handlers[key]
for i := range h {
if h[i] == &handler {
- l.handlers[address.ByteString()] = append(h[:i], h[i+1:]...)
+ if len(h) == 1 {
+ // drop the entry with its last subscriber, so that
+ // addresses subscribed to briefly do not accumulate in
+ // the map for the lifetime of the node.
+ delete(l.handlers, key)
+ } else {
+ l.handlers[key] = slices.Delete(slices.Clone(h), i, i+1)
+ }
return
}
}
@@ -66,18 +83,19 @@ func (l *listener) Handle(c *soc.SOC) {
return // no handler
}
h := l.getHandlers(addr)
- if h == nil {
+ if len(h) == 0 {
return // no handler
}
l.logger.Debug("new incoming GSOC message", "GSOC Address", addr, "wrapped chunk address", c.WrappedChunk().Address())
for _, hh := range h {
- go func(hh Handler) {
- hh(c.WrappedChunk().Data()[swarm.SpanSize:])
- }(*hh)
+ (*hh)(c)
}
}
+// getHandlers returns the handlers currently subscribed to address. The
+// returned slice is shared with the subscription bookkeeping and must only be
+// read, see Subscribe.
func (p *listener) getHandlers(address swarm.Address) []*Handler {
p.handlersMu.Lock()
defer p.handlersMu.Unlock()
diff --git a/pkg/gsoc/gsoc_test.go b/pkg/gsoc/gsoc_test.go
index dc49b0809a8..8ad2a8df410 100644
--- a/pkg/gsoc/gsoc_test.go
+++ b/pkg/gsoc/gsoc_test.go
@@ -5,8 +5,8 @@
package gsoc_test
import (
+ "sync"
"testing"
- "time"
"github.com/ethersphere/bee/v2/pkg/cac"
"github.com/ethersphere/bee/v2/pkg/crypto"
@@ -25,7 +25,6 @@ func TestRegister(t *testing.T) {
h1Calls = 0
h2Calls = 0
h3Calls = 0
- msgChan = make(chan struct{})
payload1 = []byte("Hello there!")
payload2 = []byte("General Kenobi. You are a bold one. Kill him!")
@@ -37,20 +36,9 @@ func TestRegister(t *testing.T) {
address1, _ = soc.CreateAddress(socId1, owner.Bytes())
address2, _ = soc.CreateAddress(socId2, owner.Bytes())
- h1 = func(m []byte) {
- h1Calls++
- msgChan <- struct{}{}
- }
-
- h2 = func(m []byte) {
- h2Calls++
- msgChan <- struct{}{}
- }
-
- h3 = func(m []byte) {
- h3Calls++
- msgChan <- struct{}{}
- }
+ h1 = func(*soc.SOC) { h1Calls++ }
+ h2 = func(*soc.SOC) { h2Calls++ }
+ h3 = func(*soc.SOC) { h3Calls++ }
)
_ = g.Subscribe(address1, h1)
_ = g.Subscribe(address2, h2)
@@ -68,8 +56,6 @@ func TestRegister(t *testing.T) {
// trigger soc upload on address1, check that only h1 is called
g.Handle(socCh1)
- waitHandlerCallback(t, &msgChan, 1)
-
ensureCalls(t, &h1Calls, 1)
ensureCalls(t, &h2Calls, 0)
@@ -78,8 +64,6 @@ func TestRegister(t *testing.T) {
g.Handle(socCh1)
- waitHandlerCallback(t, &msgChan, 2)
-
ensureCalls(t, &h1Calls, 2)
ensureCalls(t, &h2Calls, 0)
ensureCalls(t, &h3Calls, 1)
@@ -88,16 +72,12 @@ func TestRegister(t *testing.T) {
g.Handle(socCh1)
- waitHandlerCallback(t, &msgChan, 1)
-
ensureCalls(t, &h1Calls, 3)
ensureCalls(t, &h2Calls, 0)
ensureCalls(t, &h3Calls, 1)
g.Handle(socCh2)
- waitHandlerCallback(t, &msgChan, 1)
-
ensureCalls(t, &h1Calls, 3)
ensureCalls(t, &h2Calls, 1)
ensureCalls(t, &h3Calls, 1)
@@ -111,14 +91,55 @@ func ensureCalls(t *testing.T, calls *int, exp int) {
}
}
-func waitHandlerCallback(t *testing.T, msgChan *chan struct{}, count int) {
- t.Helper()
+// TestConcurrentSubscribeHandle verifies that subscriptions coming and going
+// while messages are handled is safe: Handle iterates the handlers of an
+// address without holding the lock, so a subscription ending concurrently must
+// not mutate the slice it is iterating. Run with -race to be meaningful.
+func TestConcurrentSubscribeHandle(t *testing.T) {
+ t.Parallel()
+
+ const (
+ goroutines = 4
+ iterations = 200
+ )
+
+ var (
+ g = gsoc.New(log.Noop)
+ socID = testutil.RandBytes(t, 32)
+ privKey, _ = crypto.GenerateSecp256k1Key()
+ signer = crypto.NewDefaultSigner(privKey)
+ owner, _ = signer.EthereumAddress()
+ address, _ = soc.CreateAddress(socID, owner.Bytes())
+ noop = func(*soc.SOC) {}
+ )
- for range count {
- select {
- case <-*msgChan:
- case <-time.After(1 * time.Second):
- t.Fatal("reached timeout while waiting for handler message")
- }
+ ch, _ := cac.New([]byte("Hello there!"))
+ socCh := soc.New(socID, ch)
+ signedCh, _ := socCh.Sign(signer)
+ socCh, _ = soc.FromChunk(signedCh)
+
+ // a subscription that outlives the test, so that Handle always has
+ // handlers to iterate over.
+ cleanup := g.Subscribe(address, noop)
+ defer cleanup()
+
+ var wg sync.WaitGroup
+ for range goroutines {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for range iterations {
+ g.Subscribe(address, noop)()
+ }
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for range iterations {
+ g.Handle(socCh)
+ }
+ }()
}
+ wg.Wait()
}
diff --git a/pkg/soc/soc.go b/pkg/soc/soc.go
index 28ade83eeb8..09a6cd81c2e 100644
--- a/pkg/soc/soc.go
+++ b/pkg/soc/soc.go
@@ -20,6 +20,10 @@ var (
errWrongChunkSize = errors.New("soc: chunk length is less than minimum")
)
+// OwnerPubKeySize is the byte length of a compressed secp256k1 public key,
+// as returned by crypto.EncodeSecp256k1PublicKey.
+const OwnerPubKeySize = 33
+
// ID is a SOC identifier
type ID []byte