mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-02 13:33:10 +00:00
* Add availability enabled parameter * Return bytes to availability when finished * Add until parameter * Remove debug message * Clean up and fix tests * Update documentations and cleanup * Avoid swallowing CancelledError * Move until validation to reservations module * Call onAvailabilityAdded callabck when the availability is enabled in sales * Remove until validation in restapi when creating an availability * Add openapi documentation * Use results instead of stew/results (#1112) * feat: request duration limit (#1057) * feat: request duration limit * Fix tests and duration type * Add custom error * Remove merge issue * Update codex contracts eth * Update market config and fix test * Fix SlotReservationsConfig syntax * Update dependencies * test: remove doubled test * chore: update contracts repo --------- Co-authored-by: Arnaud <arnaud@status.im> * fix(statemachine): do not raise from state.run (#1115) * fix(statemachine): do not raise from state.run * fix rebase * fix exception handling in SaleProvingSimulated.prove - re-raise CancelledError - don't return State on CatchableError - expect the Proofs_InvalidProof custom error instead of checking a string * asyncSpawn salesagent.onCancelled This was swallowing a KeyError in one of the tests (fixed in the previous commit) * remove error handling states in asyncstatemachine * revert unneeded changes * formatting * PR feedback, logging updates * chore(integration): simplify block expiration integration test (#1100) * chore(integration): simplify block expiration integration test * clean up * fix after rebase * perf: contract storage optimizations (#1094) * perf: contract storage optimizations * Apply optimization changes * Apply optimizing parameters sizing * Update codex-contracts-eth * bump latest changes in contracts branch * Change requestDurationLimit to uint64 * fix tests * fix tests --------- Co-authored-by: Arnaud <arnaud@status.im> Co-authored-by: Eric <5089238+emizzle@users.noreply.github.com> * bump contracts to master (#1122) * Add availability enabled parameter * Return bytes to availability when finished * Add until parameter * Clean up and fix tests * Move until validation to reservations module * Apply suggestion changes: return the reservation module error * Apply suggestion changes for until dates * Apply suggestion changes: reorganize tests * Fix indent * Remove test related to timing issue * Add raises errors to async pragram and remove useless try except * Update open api documentation * Fix wording * Remove the httpClient restart statements * Use market.getRequestEnd to set validUntil * Remove returnBytes * Use clock.now in testing * Move the api validation file to the right file --------- Co-authored-by: Adam Uhlíř <adam@uhlir.dev> Co-authored-by: Eric <5089238+emizzle@users.noreply.github.com>
428 lines
13 KiB
Nim
428 lines
13 KiB
Nim
import std/httpclient
|
|
import std/strutils
|
|
|
|
from pkg/libp2p import Cid, `$`, init
|
|
import pkg/stint
|
|
import pkg/questionable/results
|
|
import pkg/chronos/apps/http/[httpserver, shttpserver, httpclient, httptable]
|
|
import pkg/codex/logutils
|
|
import pkg/codex/rest/json
|
|
import pkg/codex/purchasing
|
|
import pkg/codex/errors
|
|
import pkg/codex/sales/reservations
|
|
|
|
export purchasing, httptable, httpclient
|
|
|
|
type CodexClient* = ref object
|
|
baseurl: string
|
|
session: HttpSessionRef
|
|
|
|
proc new*(_: type CodexClient, baseurl: string): CodexClient =
|
|
CodexClient(session: HttpSessionRef.new(), baseurl: baseurl)
|
|
|
|
proc close*(self: CodexClient): Future[void] {.async: (raises: []).} =
|
|
await self.session.closeWait()
|
|
|
|
proc request(
|
|
self: CodexClient,
|
|
httpMethod: httputils.HttpMethod,
|
|
url: string,
|
|
body: openArray[char] = [],
|
|
headers: openArray[HttpHeaderTuple] = [],
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
HttpClientRequestRef
|
|
.new(
|
|
self.session,
|
|
url,
|
|
httpMethod,
|
|
version = HttpVersion11,
|
|
flags = {},
|
|
maxResponseHeadersSize = HttpMaxHeadersSize,
|
|
headers = headers,
|
|
body = body.toOpenArrayByte(0, len(body) - 1),
|
|
).get
|
|
.send()
|
|
|
|
proc post*(
|
|
self: CodexClient,
|
|
url: string,
|
|
body: string = "",
|
|
headers: seq[HttpHeaderTuple] = @[],
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return self.request(MethodPost, url, headers = headers, body = body)
|
|
|
|
proc get(
|
|
self: CodexClient, url: string, headers: seq[HttpHeaderTuple] = @[]
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return self.request(MethodGet, url, headers = headers)
|
|
|
|
proc delete(
|
|
self: CodexClient, url: string, headers: seq[HttpHeaderTuple] = @[]
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return self.request(MethodDelete, url, headers = headers)
|
|
|
|
proc patch*(
|
|
self: CodexClient,
|
|
url: string,
|
|
body: string = "",
|
|
headers: seq[HttpHeaderTuple] = @[],
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return self.request(MethodPatch, url, headers = headers, body = body)
|
|
|
|
proc body*(
|
|
response: HttpClientResponseRef
|
|
): Future[string] {.async: (raises: [CancelledError, HttpError]).} =
|
|
return bytesToString (await response.getBodyBytes())
|
|
|
|
proc getContent(
|
|
client: CodexClient, url: string, headers: seq[HttpHeaderTuple] = @[]
|
|
): Future[string] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.get(url, headers)
|
|
return await response.body
|
|
|
|
proc info*(
|
|
client: CodexClient
|
|
): Future[?!JsonNode] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.get(client.baseurl & "/debug/info")
|
|
return JsonNode.parse(await response.body)
|
|
|
|
proc setLogLevel*(
|
|
client: CodexClient, level: string
|
|
): Future[void] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let
|
|
url = client.baseurl & "/debug/chronicles/loglevel?level=" & level
|
|
headers = @[("Content-Type", "text/plain")]
|
|
response = await client.post(url, headers = headers, body = "")
|
|
assert response.status == 200
|
|
|
|
proc uploadRaw*(
|
|
client: CodexClient, contents: string, headers: seq[HttpHeaderTuple] = @[]
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return client.post(client.baseurl & "/data", body = contents, headers = headers)
|
|
|
|
proc upload*(
|
|
client: CodexClient, contents: string
|
|
): Future[?!Cid] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.uploadRaw(contents)
|
|
assert response.status == 200
|
|
Cid.init(await response.body).mapFailure
|
|
|
|
proc upload*(
|
|
client: CodexClient, bytes: seq[byte]
|
|
): Future[?!Cid] {.async: (raw: true).} =
|
|
return client.upload(string.fromBytes(bytes))
|
|
|
|
proc downloadRaw*(
|
|
client: CodexClient, cid: string, local = false
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return
|
|
client.get(client.baseurl & "/data/" & cid & (if local: "" else: "/network/stream"))
|
|
|
|
proc downloadBytes*(
|
|
client: CodexClient, cid: Cid, local = false
|
|
): Future[?!seq[byte]] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.downloadRaw($cid, local = local)
|
|
|
|
if response.status != 200:
|
|
return failure($response.status)
|
|
|
|
success await response.getBodyBytes()
|
|
|
|
proc download*(
|
|
client: CodexClient, cid: Cid, local = false
|
|
): Future[?!string] {.async: (raises: [CancelledError, HttpError]).} =
|
|
without response =? await client.downloadBytes(cid, local = local), err:
|
|
return failure(err)
|
|
return success bytesToString(response)
|
|
|
|
proc downloadNoStream*(
|
|
client: CodexClient, cid: Cid
|
|
): Future[?!string] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.post(client.baseurl & "/data/" & $cid & "/network")
|
|
|
|
if response.status != 200:
|
|
return failure($response.status)
|
|
|
|
success await response.body
|
|
|
|
proc downloadManifestOnly*(
|
|
client: CodexClient, cid: Cid
|
|
): Future[?!string] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response =
|
|
await client.get(client.baseurl & "/data/" & $cid & "/network/manifest")
|
|
|
|
if response.status != 200:
|
|
return failure($response.status)
|
|
|
|
success await response.body
|
|
|
|
proc deleteRaw*(
|
|
client: CodexClient, cid: string
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return client.delete(client.baseurl & "/data/" & cid)
|
|
|
|
proc delete*(
|
|
client: CodexClient, cid: Cid
|
|
): Future[?!void] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.deleteRaw($cid)
|
|
|
|
if response.status != 204:
|
|
return failure($response.status)
|
|
|
|
success()
|
|
|
|
proc listRaw*(
|
|
client: CodexClient
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
return client.get(client.baseurl & "/data")
|
|
|
|
proc list*(
|
|
client: CodexClient
|
|
): Future[?!RestContentList] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.listRaw()
|
|
|
|
if response.status != 200:
|
|
return failure($response.status)
|
|
|
|
RestContentList.fromJson(await response.body)
|
|
|
|
proc space*(
|
|
client: CodexClient
|
|
): Future[?!RestRepoStore] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let url = client.baseurl & "/space"
|
|
let response = await client.get(url)
|
|
|
|
if response.status != 200:
|
|
return failure($response.status)
|
|
|
|
RestRepoStore.fromJson(await response.body)
|
|
|
|
proc requestStorageRaw*(
|
|
client: CodexClient,
|
|
cid: Cid,
|
|
duration: uint64,
|
|
pricePerBytePerSecond: UInt256,
|
|
proofProbability: UInt256,
|
|
collateralPerByte: UInt256,
|
|
expiry: uint64 = 0,
|
|
nodes: uint = 3,
|
|
tolerance: uint = 1,
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
## Call request storage REST endpoint
|
|
##
|
|
let url = client.baseurl & "/storage/request/" & $cid
|
|
let json =
|
|
%*{
|
|
"duration": duration,
|
|
"pricePerBytePerSecond": pricePerBytePerSecond,
|
|
"proofProbability": proofProbability,
|
|
"collateralPerByte": collateralPerByte,
|
|
"nodes": nodes,
|
|
"tolerance": tolerance,
|
|
}
|
|
|
|
if expiry != 0:
|
|
json["expiry"] = %($expiry)
|
|
|
|
return client.post(url, $json)
|
|
|
|
proc requestStorage*(
|
|
client: CodexClient,
|
|
cid: Cid,
|
|
duration: uint64,
|
|
pricePerBytePerSecond: UInt256,
|
|
proofProbability: UInt256,
|
|
expiry: uint64,
|
|
collateralPerByte: UInt256,
|
|
nodes: uint = 3,
|
|
tolerance: uint = 1,
|
|
): Future[?!PurchaseId] {.async: (raises: [CancelledError, HttpError]).} =
|
|
## Call request storage REST endpoint
|
|
##
|
|
let
|
|
response = await client.requestStorageRaw(
|
|
cid, duration, pricePerBytePerSecond, proofProbability, collateralPerByte, expiry,
|
|
nodes, tolerance,
|
|
)
|
|
body = await response.body
|
|
|
|
if response.status != 200:
|
|
doAssert(false, body)
|
|
PurchaseId.fromHex(body).catch
|
|
|
|
proc getPurchase*(
|
|
client: CodexClient, purchaseId: PurchaseId
|
|
): Future[?!RestPurchase] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let url = client.baseurl & "/storage/purchases/" & purchaseId.toHex
|
|
try:
|
|
let body = await client.getContent(url)
|
|
return RestPurchase.fromJson(body)
|
|
except CatchableError as e:
|
|
return failure e.msg
|
|
|
|
proc getSalesAgent*(
|
|
client: CodexClient, slotId: SlotId
|
|
): Future[?!RestSalesAgent] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let url = client.baseurl & "/sales/slots/" & slotId.toHex
|
|
try:
|
|
let body = await client.getContent(url)
|
|
return RestSalesAgent.fromJson(body)
|
|
except CatchableError as e:
|
|
return failure e.msg
|
|
|
|
proc postAvailabilityRaw*(
|
|
client: CodexClient,
|
|
totalSize, duration: uint64,
|
|
minPricePerBytePerSecond, totalCollateral: UInt256,
|
|
enabled: ?bool = bool.none,
|
|
until: ?SecondsSince1970 = SecondsSince1970.none,
|
|
): Future[HttpClientResponseRef] {.async: (raises: [CancelledError, HttpError]).} =
|
|
## Post sales availability endpoint
|
|
##
|
|
let url = client.baseurl & "/sales/availability"
|
|
let json =
|
|
%*{
|
|
"totalSize": totalSize,
|
|
"duration": duration,
|
|
"minPricePerBytePerSecond": minPricePerBytePerSecond,
|
|
"totalCollateral": totalCollateral,
|
|
"enabled": enabled,
|
|
"until": until,
|
|
}
|
|
return await client.post(url, $json)
|
|
|
|
proc postAvailability*(
|
|
client: CodexClient,
|
|
totalSize, duration: uint64,
|
|
minPricePerBytePerSecond, totalCollateral: UInt256,
|
|
enabled: ?bool = bool.none,
|
|
until: ?SecondsSince1970 = SecondsSince1970.none,
|
|
): Future[?!Availability] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.postAvailabilityRaw(
|
|
totalSize = totalSize,
|
|
duration = duration,
|
|
minPricePerBytePerSecond = minPricePerBytePerSecond,
|
|
totalCollateral = totalCollateral,
|
|
enabled = enabled,
|
|
until = until,
|
|
)
|
|
|
|
let body = await response.body
|
|
|
|
doAssert response.status == 201,
|
|
"expected 201 Created, got " & $response.status & ", body: " & body
|
|
Availability.fromJson(body)
|
|
|
|
proc patchAvailabilityRaw*(
|
|
client: CodexClient,
|
|
availabilityId: AvailabilityId,
|
|
totalSize, freeSize, duration: ?uint64 = uint64.none,
|
|
minPricePerBytePerSecond, totalCollateral: ?UInt256 = UInt256.none,
|
|
enabled: ?bool = bool.none,
|
|
until: ?SecondsSince1970 = SecondsSince1970.none,
|
|
): Future[HttpClientResponseRef] {.
|
|
async: (raw: true, raises: [CancelledError, HttpError])
|
|
.} =
|
|
## Updates availability
|
|
##
|
|
let url = client.baseurl & "/sales/availability/" & $availabilityId
|
|
|
|
# TODO: Optionalize macro does not keep `serialize` pragmas so we can't use `Optionalize(RestAvailability)` here.
|
|
var json = %*{}
|
|
|
|
if totalSize =? totalSize:
|
|
json["totalSize"] = %totalSize
|
|
|
|
if freeSize =? freeSize:
|
|
json["freeSize"] = %freeSize
|
|
|
|
if duration =? duration:
|
|
json["duration"] = %duration
|
|
|
|
if minPricePerBytePerSecond =? minPricePerBytePerSecond:
|
|
json["minPricePerBytePerSecond"] = %minPricePerBytePerSecond
|
|
|
|
if totalCollateral =? totalCollateral:
|
|
json["totalCollateral"] = %totalCollateral
|
|
|
|
if enabled =? enabled:
|
|
json["enabled"] = %enabled
|
|
|
|
if until =? until:
|
|
json["until"] = %until
|
|
|
|
client.patch(url, $json)
|
|
|
|
proc patchAvailability*(
|
|
client: CodexClient,
|
|
availabilityId: AvailabilityId,
|
|
totalSize, duration: ?uint64 = uint64.none,
|
|
minPricePerBytePerSecond, totalCollateral: ?UInt256 = UInt256.none,
|
|
enabled: ?bool = bool.none,
|
|
until: ?SecondsSince1970 = SecondsSince1970.none,
|
|
): Future[void] {.async: (raises: [CancelledError, HttpError]).} =
|
|
let response = await client.patchAvailabilityRaw(
|
|
availabilityId,
|
|
totalSize = totalSize,
|
|
duration = duration,
|
|
minPricePerBytePerSecond = minPricePerBytePerSecond,
|
|
totalCollateral = totalCollateral,
|
|
enabled = enabled,
|
|
until = until,
|
|
)
|
|
doAssert response.status == 204, "expected No Content, got " & $response.status
|
|
|
|
proc getAvailabilities*(
|
|
client: CodexClient
|
|
): Future[?!seq[Availability]] {.async: (raises: [CancelledError, HttpError]).} =
|
|
## Call sales availability REST endpoint
|
|
let url = client.baseurl & "/sales/availability"
|
|
let body = await client.getContent(url)
|
|
seq[Availability].fromJson(body)
|
|
|
|
proc getAvailabilityReservations*(
|
|
client: CodexClient, availabilityId: AvailabilityId
|
|
): Future[?!seq[Reservation]] {.async: (raises: [CancelledError, HttpError]).} =
|
|
## Retrieves Availability's Reservations
|
|
let url = client.baseurl & "/sales/availability/" & $availabilityId & "/reservations"
|
|
let body = await client.getContent(url)
|
|
seq[Reservation].fromJson(body)
|
|
|
|
proc purchaseStateIs*(
|
|
client: CodexClient, id: PurchaseId, state: string
|
|
): Future[bool] {.async: (raises: [CancelledError, HttpError]).} =
|
|
(await client.getPurchase(id)).option .? state == some state
|
|
|
|
proc saleStateIs*(
|
|
client: CodexClient, id: SlotId, state: string
|
|
): Future[bool] {.async: (raises: [CancelledError, HttpError]).} =
|
|
(await client.getSalesAgent(id)).option .? state == some state
|
|
|
|
proc requestId*(
|
|
client: CodexClient, id: PurchaseId
|
|
): Future[?RequestId] {.async: (raises: [CancelledError, HttpError]).} =
|
|
return (await client.getPurchase(id)).option .? requestId
|
|
|
|
proc buildUrl*(client: CodexClient, path: string): string =
|
|
return client.baseurl & path
|