diff --git a/codex/node.nim b/codex/node.nim index e010b085..1ca471d5 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -897,3 +897,10 @@ proc new*( contracts: contracts, trackedFutures: TrackedFutures(), ) + +proc hasLocalBlock*( + self: CodexNodeRef, cid: Cid +): Future[bool] {.async: (raises: [CancelledError]).} = + ## Returns true if the given Cid is present in the local store + + return await (cid in self.networkStore.localStore) diff --git a/codex/rest/api.nim b/codex/rest/api.nim index e31a0f59..adab821b 100644 --- a/codex/rest/api.nim +++ b/codex/rest/api.nim @@ -365,6 +365,22 @@ proc initDataApi(node: CodexNodeRef, repoStore: RepoStore, router: var RestRoute let json = %formatManifest(cid.get(), manifest) return RestApiResponse.response($json, contentType = "application/json") + router.api(MethodGet, "/api/codex/v1/data/{cid}/exists") do( + cid: Cid, resp: HttpResponseRef + ) -> RestApiResponse: + ## Only test if the give CID is available in the local store + ## + var headers = buildCorsHeaders("GET", allowedOrigin) + + if cid.isErr: + return RestApiResponse.error(Http400, $cid.error(), headers = headers) + + let cid = cid.get() + let hasCid = await node.hasLocalBlock(cid) + + let json = %*{$cid: hasCid} + return RestApiResponse.response($json, contentType = "application/json") + router.api(MethodGet, "/api/codex/v1/space") do() -> RestApiResponse: let json = %RestRepoStore( diff --git a/openapi.yaml b/openapi.yaml index a84842f7..fab3d334 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -727,6 +727,34 @@ paths: "500": description: Well it was bad-bad + "/data/{cid}/exists": + get: + summary: "Check if a block identified by CID exists in the local node." + tags: [Data] + operationId: hasBlock + parameters: + - in: path + name: cid + required: true + schema: + $ref: "#/components/schemas/Cid" + description: "CID of the block to check." + responses: + "200": + description: Block existence information + content: + application/json: + schema: + type: object + properties: + has: + type: boolean + description: Indicates whether the block exists in the local node + "400": + description: Invalid CID is specified + "500": + description: Well it was bad-bad + "/space": get: summary: "Gets a summary of the storage space allocation of the node." diff --git a/tests/codex/node/testnode.nim b/tests/codex/node/testnode.nim index 78298ad7..a3fe7e94 100644 --- a/tests/codex/node/testnode.nim +++ b/tests/codex/node/testnode.nim @@ -229,3 +229,17 @@ asyncchecksuite "Test Node - Basic": check not await manifestCid in localStore for blk in blocks: check not (await blk.cid in localStore) + + test "Should return true when a cid is already in the local store": + let + blocks = await makeRandomBlocks(datasetSize = 1024, blockSize = 256'nb) + manifest = await storeDataGetManifest(localStore, blocks) + manifestBlock = (await store.storeManifest(manifest)).tryGet() + manifestCid = manifestBlock.cid + + check (await node.hasLocalBlock(manifestCid)) == true + + test "Should returns false when a cid is not in the local store": + let randomBlock = bt.Block.new("Random block".toBytes).tryGet() + + check (await node.hasLocalBlock(randomBlock.cid)) == false diff --git a/tests/integration/5_minutes/testrestapi.nim b/tests/integration/5_minutes/testrestapi.nim index 71cf97ef..9a6b9c11 100644 --- a/tests/integration/5_minutes/testrestapi.nim +++ b/tests/integration/5_minutes/testrestapi.nim @@ -220,3 +220,13 @@ twonodessuite "REST API": let response2 = await client1.downloadRaw($cid) check (await response2.body) == contents + + test "should returns true when the block exists", twoNodesConfig: + let cid = (await client2.upload("some file contents")).get + + var response = await client1.hasBlock(cid) + check response.get() == false + + discard (await client1.download(cid)).get + response = await client1.hasBlock(cid) + check response.get() == false diff --git a/tests/integration/5_minutes/testrestapivalidation.nim b/tests/integration/5_minutes/testrestapivalidation.nim index db11da9c..d428402e 100644 --- a/tests/integration/5_minutes/testrestapivalidation.nim +++ b/tests/integration/5_minutes/testrestapivalidation.nim @@ -396,3 +396,10 @@ multinodesuite "Rest API validation": check: response.status == 422 (await response.body) == "totalCollateral must be larger then zero" + + test "has block returns error 400 when the cid is invalid", config: + let response = await client.hasBlockRaw("invalid-cid") + + check: + response.status == 400 + (await response.body) == "Incorrect Cid" diff --git a/tests/integration/codexclient.nim b/tests/integration/codexclient.nim index 17ed6dd4..3fdf564f 100644 --- a/tests/integration/codexclient.nim +++ b/tests/integration/codexclient.nim @@ -16,6 +16,9 @@ type CodexClient* = ref object baseurl: string session: HttpSessionRef +type HasBlockResponse = object + has: bool + proc new*(_: type CodexClient, baseurl: string): CodexClient = CodexClient(session: HttpSessionRef.new(), baseurl: baseurl) @@ -431,3 +434,21 @@ proc getSlots*( let url = client.baseurl & "/sales/slots" let body = await client.getContent(url) seq[Slot].fromJson(body) + +proc hasBlock*( + client: CodexClient, cid: Cid +): Future[?!bool] {.async: (raises: [CancelledError, HttpError]).} = + let url = client.baseurl & "/data/" & $cid & "/exists" + let body = await client.getContent(url) + let response = HasBlockResponse.fromJson(body) + if response.isErr: + return failure "Failed to parse has block response" + return response.get.has.success + +proc hasBlockRaw*( + client: CodexClient, cid: string +): Future[HttpClientResponseRef] {. + async: (raw: true, raises: [CancelledError, HttpError]) +.} = + let url = client.baseurl & "/data/" & cid & "/exists" + return client.get(url)