feat(api): availabilities patch endpoint (#692)
* feat(api): availabilities patch and reservations endpoints * test: fixing tests and writing * test: test reservations endpoint * chore: feedback implementation * chore: feedback implementation * test: fix integration tests
This commit is contained in:
parent
43f0bf1c1c
commit
de1714ed06
|
@ -35,6 +35,7 @@ import ../contracts
|
||||||
import ../manifest
|
import ../manifest
|
||||||
import ../streams/asyncstreamwrapper
|
import ../streams/asyncstreamwrapper
|
||||||
import ../stores
|
import ../stores
|
||||||
|
import ../utils/options
|
||||||
|
|
||||||
import ./coders
|
import ./coders
|
||||||
import ./json
|
import ./json
|
||||||
|
@ -253,9 +254,10 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) =
|
||||||
router.rawApi(
|
router.rawApi(
|
||||||
MethodPost,
|
MethodPost,
|
||||||
"/api/codex/v1/sales/availability") do () -> RestApiResponse:
|
"/api/codex/v1/sales/availability") do () -> RestApiResponse:
|
||||||
## Add available storage to sell
|
## Add available storage to sell.
|
||||||
|
## Every time Availability's offer finishes, its capacity is returned to the availability.
|
||||||
##
|
##
|
||||||
## size - size of available storage in bytes
|
## totalSize - size of available storage in bytes
|
||||||
## duration - maximum time the storage should be sold for (in seconds)
|
## duration - maximum time the storage should be sold for (in seconds)
|
||||||
## minPrice - minimum price to be paid (in amount of tokens)
|
## minPrice - minimum price to be paid (in amount of tokens)
|
||||||
## maxCollateral - maximum collateral user is willing to pay per filled Slot (in amount of tokens)
|
## maxCollateral - maximum collateral user is willing to pay per filled Slot (in amount of tokens)
|
||||||
|
@ -271,12 +273,15 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) =
|
||||||
|
|
||||||
let reservations = contracts.sales.context.reservations
|
let reservations = contracts.sales.context.reservations
|
||||||
|
|
||||||
if not reservations.hasAvailable(restAv.size.truncate(uint)):
|
if restAv.totalSize == 0:
|
||||||
|
return RestApiResponse.error(Http400, "Total size must be larger then zero")
|
||||||
|
|
||||||
|
if not reservations.hasAvailable(restAv.totalSize.truncate(uint)):
|
||||||
return RestApiResponse.error(Http422, "Not enough storage quota")
|
return RestApiResponse.error(Http422, "Not enough storage quota")
|
||||||
|
|
||||||
without availability =? (
|
without availability =? (
|
||||||
await reservations.createAvailability(
|
await reservations.createAvailability(
|
||||||
restAv.size,
|
restAv.totalSize,
|
||||||
restAv.duration,
|
restAv.duration,
|
||||||
restAv.minPrice,
|
restAv.minPrice,
|
||||||
restAv.maxCollateral)
|
restAv.maxCollateral)
|
||||||
|
@ -284,11 +289,106 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) =
|
||||||
return RestApiResponse.error(Http500, error.msg)
|
return RestApiResponse.error(Http500, error.msg)
|
||||||
|
|
||||||
return RestApiResponse.response(availability.toJson,
|
return RestApiResponse.response(availability.toJson,
|
||||||
|
Http201,
|
||||||
contentType="application/json")
|
contentType="application/json")
|
||||||
except CatchableError as exc:
|
except CatchableError as exc:
|
||||||
trace "Excepting processing request", exc = exc.msg
|
trace "Excepting processing request", exc = exc.msg
|
||||||
return RestApiResponse.error(Http500)
|
return RestApiResponse.error(Http500)
|
||||||
|
|
||||||
|
router.rawApi(
|
||||||
|
MethodPatch,
|
||||||
|
"/api/codex/v1/sales/availability/{id}") do (id: AvailabilityId) -> RestApiResponse:
|
||||||
|
## Updates Availability.
|
||||||
|
## The new parameters will be only considered for new requests.
|
||||||
|
## Existing Requests linked to this Availability will continue as is.
|
||||||
|
##
|
||||||
|
## totalSize - size of available storage in bytes. When decreasing the size, then lower limit is the currently `totalSize - freeSize`.
|
||||||
|
## duration - maximum time the storage should be sold for (in seconds)
|
||||||
|
## minPrice - minimum price to be paid (in amount of tokens)
|
||||||
|
## maxCollateral - maximum collateral user is willing to pay per filled Slot (in amount of tokens)
|
||||||
|
|
||||||
|
try:
|
||||||
|
without contracts =? node.contracts.host:
|
||||||
|
return RestApiResponse.error(Http503, "Sales unavailable")
|
||||||
|
|
||||||
|
without id =? id.tryGet.catch, error:
|
||||||
|
return RestApiResponse.error(Http400, error.msg)
|
||||||
|
without keyId =? id.key.tryGet.catch, error:
|
||||||
|
return RestApiResponse.error(Http400, error.msg)
|
||||||
|
|
||||||
|
let
|
||||||
|
body = await request.getBody()
|
||||||
|
reservations = contracts.sales.context.reservations
|
||||||
|
|
||||||
|
type OptRestAvailability = Optionalize(RestAvailability)
|
||||||
|
without restAv =? OptRestAvailability.fromJson(body), error:
|
||||||
|
return RestApiResponse.error(Http400, error.msg)
|
||||||
|
|
||||||
|
without availability =? (await reservations.get(keyId, Availability)), error:
|
||||||
|
if error of NotExistsError:
|
||||||
|
return RestApiResponse.error(Http404, "Availability not found")
|
||||||
|
|
||||||
|
return RestApiResponse.error(Http500, error.msg)
|
||||||
|
|
||||||
|
if isSome restAv.freeSize:
|
||||||
|
return RestApiResponse.error(Http400, "Updating freeSize is not allowed")
|
||||||
|
|
||||||
|
if size =? restAv.totalSize:
|
||||||
|
# we don't allow lowering the totalSize bellow currently utilized size
|
||||||
|
if size < (availability.totalSize - availability.freeSize):
|
||||||
|
return RestApiResponse.error(Http400, "New totalSize must be larger then current totalSize - freeSize, which is currently: " & $(availability.totalSize - availability.freeSize))
|
||||||
|
|
||||||
|
availability.freeSize += size - availability.totalSize
|
||||||
|
availability.totalSize = size
|
||||||
|
|
||||||
|
if duration =? restAv.duration:
|
||||||
|
availability.duration = duration
|
||||||
|
|
||||||
|
if minPrice =? restAv.minPrice:
|
||||||
|
availability.minPrice = minPrice
|
||||||
|
|
||||||
|
if maxCollateral =? restAv.maxCollateral:
|
||||||
|
availability.maxCollateral = maxCollateral
|
||||||
|
|
||||||
|
if err =? (await reservations.update(availability)).errorOption:
|
||||||
|
return RestApiResponse.error(Http500, err.msg)
|
||||||
|
|
||||||
|
return RestApiResponse.response(Http200)
|
||||||
|
except CatchableError as exc:
|
||||||
|
trace "Excepting processing request", exc = exc.msg
|
||||||
|
return RestApiResponse.error(Http500)
|
||||||
|
|
||||||
|
router.rawApi(
|
||||||
|
MethodGet,
|
||||||
|
"/api/codex/v1/sales/availability/{id}/reservations") do (id: AvailabilityId) -> RestApiResponse:
|
||||||
|
## Gets Availability's reservations.
|
||||||
|
|
||||||
|
try:
|
||||||
|
without contracts =? node.contracts.host:
|
||||||
|
return RestApiResponse.error(Http503, "Sales unavailable")
|
||||||
|
|
||||||
|
without id =? id.tryGet.catch, error:
|
||||||
|
return RestApiResponse.error(Http400, error.msg)
|
||||||
|
without keyId =? id.key.tryGet.catch, error:
|
||||||
|
return RestApiResponse.error(Http400, error.msg)
|
||||||
|
|
||||||
|
let reservations = contracts.sales.context.reservations
|
||||||
|
|
||||||
|
if error =? (await reservations.get(keyId, Availability)).errorOption:
|
||||||
|
if error of NotExistsError:
|
||||||
|
return RestApiResponse.error(Http404, "Availability not found")
|
||||||
|
else:
|
||||||
|
return RestApiResponse.error(Http500, error.msg)
|
||||||
|
|
||||||
|
without availabilitysReservations =? (await reservations.all(Reservation, id)), err:
|
||||||
|
return RestApiResponse.error(Http500, err.msg)
|
||||||
|
|
||||||
|
# TODO: Expand this structure with information about the linked StorageRequest not only RequestID
|
||||||
|
return RestApiResponse.response(availabilitysReservations.toJson, contentType="application/json")
|
||||||
|
except CatchableError as exc:
|
||||||
|
trace "Excepting processing request", exc = exc.msg
|
||||||
|
return RestApiResponse.error(Http500)
|
||||||
|
|
||||||
proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
|
proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
|
||||||
router.rawApi(
|
router.rawApi(
|
||||||
MethodPost,
|
MethodPost,
|
||||||
|
@ -329,10 +429,11 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
|
||||||
return RestApiResponse.error(Http500)
|
return RestApiResponse.error(Http500)
|
||||||
|
|
||||||
if expiry <= node.clock.now.u256:
|
if expiry <= node.clock.now.u256:
|
||||||
return RestApiResponse.error(Http400, "Expiry needs to be in future")
|
return RestApiResponse.error(Http400, "Expiry needs to be in future. Now: " & $node.clock.now)
|
||||||
|
|
||||||
if expiry > node.clock.now.u256 + params.duration:
|
let expiryLimit = node.clock.now.u256 + params.duration
|
||||||
return RestApiResponse.error(Http400, "Expiry has to be before the request's end (now + duration)")
|
if expiry > expiryLimit:
|
||||||
|
return RestApiResponse.error(Http400, "Expiry has to be before the request's end (now + duration). Limit: " & $expiryLimit)
|
||||||
|
|
||||||
without purchaseId =? await node.requestStorage(
|
without purchaseId =? await node.requestStorage(
|
||||||
cid,
|
cid,
|
||||||
|
|
|
@ -84,7 +84,7 @@ proc decodeString*(_: type array[32, byte],
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
err e.msg.cstring
|
err e.msg.cstring
|
||||||
|
|
||||||
proc decodeString*[T: PurchaseId | RequestId | Nonce | SlotId](_: type T,
|
proc decodeString*[T: PurchaseId | RequestId | Nonce | SlotId | AvailabilityId](_: type T,
|
||||||
value: string): Result[T, cstring] =
|
value: string): Result[T, cstring] =
|
||||||
array[32, byte].decodeString(value).map(id => T(id))
|
array[32, byte].decodeString(value).map(id => T(id))
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,11 @@ type
|
||||||
error* {.serialize.}: ?string
|
error* {.serialize.}: ?string
|
||||||
|
|
||||||
RestAvailability* = object
|
RestAvailability* = object
|
||||||
size* {.serialize.}: UInt256
|
totalSize* {.serialize.}: UInt256
|
||||||
duration* {.serialize.}: UInt256
|
duration* {.serialize.}: UInt256
|
||||||
minPrice* {.serialize.}: UInt256
|
minPrice* {.serialize.}: UInt256
|
||||||
maxCollateral* {.serialize.}: UInt256
|
maxCollateral* {.serialize.}: UInt256
|
||||||
|
freeSize* {.serialize.}: ?UInt256
|
||||||
|
|
||||||
RestSalesAgent* = object
|
RestSalesAgent* = object
|
||||||
state* {.serialize.}: string
|
state* {.serialize.}: string
|
||||||
|
|
|
@ -9,24 +9,27 @@
|
||||||
##
|
##
|
||||||
## +--------------------------------------+
|
## +--------------------------------------+
|
||||||
## | RESERVATION |
|
## | RESERVATION |
|
||||||
## +--------------------------------------+ |--------------------------------------|
|
## +----------------------------------------+ |--------------------------------------|
|
||||||
## | AVAILABILITY | | ReservationId | id | PK |
|
## | AVAILABILITY | | ReservationId | id | PK |
|
||||||
## |--------------------------------------| |--------------------------------------|
|
## |----------------------------------------| |--------------------------------------|
|
||||||
## | AvailabilityId | id | PK |<-||-------o<-| AvailabilityId | availabilityId | FK |
|
## | AvailabilityId | id | PK |<-||-------o<-| AvailabilityId | availabilityId | FK |
|
||||||
## |--------------------------------------| |--------------------------------------|
|
## |----------------------------------------| |--------------------------------------|
|
||||||
## | UInt256 | size | | | UInt256 | size | |
|
## | UInt256 | totalSize | | | UInt256 | size | |
|
||||||
## |--------------------------------------| |--------------------------------------|
|
## |----------------------------------------| |--------------------------------------|
|
||||||
## | UInt256 | duration | | | SlotId | slotId | |
|
## | UInt256 | freeSize | | | SlotId | slotId | |
|
||||||
## |--------------------------------------| +--------------------------------------+
|
## |----------------------------------------| +--------------------------------------+
|
||||||
|
## | UInt256 | duration | |
|
||||||
|
## |----------------------------------------|
|
||||||
## | UInt256 | minPrice | |
|
## | UInt256 | minPrice | |
|
||||||
## |--------------------------------------|
|
## |----------------------------------------|
|
||||||
## | UInt256 | maxCollateral | |
|
## | UInt256 | maxCollateral | |
|
||||||
## +--------------------------------------+
|
## +----------------------------------------+
|
||||||
|
|
||||||
import pkg/upraises
|
import pkg/upraises
|
||||||
push: {.upraises: [].}
|
push: {.upraises: [].}
|
||||||
|
|
||||||
import std/typetraits
|
import std/typetraits
|
||||||
|
import std/sequtils
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/datastore
|
import pkg/datastore
|
||||||
import pkg/nimcrypto
|
import pkg/nimcrypto
|
||||||
|
@ -35,7 +38,9 @@ import pkg/questionable/results
|
||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ../logutils
|
import ../logutils
|
||||||
|
import ../clock
|
||||||
import ../stores
|
import ../stores
|
||||||
|
import ../market
|
||||||
import ../contracts/requests
|
import ../contracts/requests
|
||||||
import ../utils/json
|
import ../utils/json
|
||||||
|
|
||||||
|
@ -52,7 +57,8 @@ type
|
||||||
SomeStorableId = AvailabilityId | ReservationId
|
SomeStorableId = AvailabilityId | ReservationId
|
||||||
Availability* = ref object
|
Availability* = ref object
|
||||||
id* {.serialize.}: AvailabilityId
|
id* {.serialize.}: AvailabilityId
|
||||||
size* {.serialize.}: UInt256
|
totalSize* {.serialize.}: UInt256
|
||||||
|
freeSize* {.serialize.}: UInt256
|
||||||
duration* {.serialize.}: UInt256
|
duration* {.serialize.}: UInt256
|
||||||
minPrice* {.serialize.}: UInt256
|
minPrice* {.serialize.}: UInt256
|
||||||
maxCollateral* {.serialize.}: UInt256
|
maxCollateral* {.serialize.}: UInt256
|
||||||
|
@ -91,14 +97,15 @@ proc new*(T: type Reservations,
|
||||||
|
|
||||||
proc init*(
|
proc init*(
|
||||||
_: type Availability,
|
_: type Availability,
|
||||||
size: UInt256,
|
totalSize: UInt256,
|
||||||
|
freeSize: UInt256,
|
||||||
duration: UInt256,
|
duration: UInt256,
|
||||||
minPrice: UInt256,
|
minPrice: UInt256,
|
||||||
maxCollateral: UInt256): Availability =
|
maxCollateral: UInt256): Availability =
|
||||||
|
|
||||||
var id: array[32, byte]
|
var id: array[32, byte]
|
||||||
doAssert randomBytes(id) == 32
|
doAssert randomBytes(id) == 32
|
||||||
Availability(id: AvailabilityId(id), size: size, duration: duration, minPrice: minPrice, maxCollateral: maxCollateral)
|
Availability(id: AvailabilityId(id), totalSize:totalSize, freeSize: freeSize, duration: duration, minPrice: minPrice, maxCollateral: maxCollateral)
|
||||||
|
|
||||||
proc init*(
|
proc init*(
|
||||||
_: type Reservation,
|
_: type Reservation,
|
||||||
|
@ -118,17 +125,9 @@ func toArray(id: SomeStorableId): array[32, byte] =
|
||||||
proc `==`*(x, y: AvailabilityId): bool {.borrow.}
|
proc `==`*(x, y: AvailabilityId): bool {.borrow.}
|
||||||
proc `==`*(x, y: ReservationId): bool {.borrow.}
|
proc `==`*(x, y: ReservationId): bool {.borrow.}
|
||||||
proc `==`*(x, y: Reservation): bool =
|
proc `==`*(x, y: Reservation): bool =
|
||||||
x.id == y.id and
|
x.id == y.id
|
||||||
x.availabilityId == y.availabilityId and
|
|
||||||
x.size == y.size and
|
|
||||||
x.requestId == y.requestId and
|
|
||||||
x.slotIndex == y.slotIndex
|
|
||||||
proc `==`*(x, y: Availability): bool =
|
proc `==`*(x, y: Availability): bool =
|
||||||
x.id == y.id and
|
x.id == y.id
|
||||||
x.size == y.size and
|
|
||||||
x.duration == y.duration and
|
|
||||||
x.maxCollateral == y.maxCollateral and
|
|
||||||
x.minPrice == y.minPrice
|
|
||||||
|
|
||||||
proc `$`*(id: SomeStorableId): string = id.toArray.toHex
|
proc `$`*(id: SomeStorableId): string = id.toArray.toHex
|
||||||
|
|
||||||
|
@ -198,11 +197,11 @@ proc get*(
|
||||||
|
|
||||||
return success obj
|
return success obj
|
||||||
|
|
||||||
proc update(
|
proc updateImpl(
|
||||||
self: Reservations,
|
self: Reservations,
|
||||||
obj: SomeStorableObject): Future[?!void] {.async.} =
|
obj: SomeStorableObject): Future[?!void] {.async.} =
|
||||||
|
|
||||||
trace "updating " & $(obj.type), id = obj.id, size = obj.size
|
trace "updating " & $(obj.type), id = obj.id
|
||||||
|
|
||||||
without key =? obj.key, error:
|
without key =? obj.key, error:
|
||||||
return failure(error)
|
return failure(error)
|
||||||
|
@ -215,6 +214,39 @@ proc update(
|
||||||
|
|
||||||
return success()
|
return success()
|
||||||
|
|
||||||
|
proc update*(
|
||||||
|
self: Reservations,
|
||||||
|
obj: Reservation): Future[?!void] {.async.} =
|
||||||
|
return await self.updateImpl(obj)
|
||||||
|
|
||||||
|
proc update*(
|
||||||
|
self: Reservations,
|
||||||
|
obj: Availability): Future[?!void] {.async.} =
|
||||||
|
|
||||||
|
without key =? obj.key, error:
|
||||||
|
return failure(error)
|
||||||
|
|
||||||
|
let getResult = await self.get(key, Availability)
|
||||||
|
|
||||||
|
if getResult.isOk:
|
||||||
|
let oldAvailability = !getResult
|
||||||
|
|
||||||
|
# Sizing of the availability changed, we need to adjust the repo reservation accordingly
|
||||||
|
if oldAvailability.totalSize != obj.totalSize:
|
||||||
|
if oldAvailability.totalSize < obj.totalSize: # storage added
|
||||||
|
if reserveErr =? (await self.repo.reserve((obj.totalSize - oldAvailability.totalSize).truncate(uint))).errorOption:
|
||||||
|
return failure(reserveErr.toErr(ReserveFailedError))
|
||||||
|
|
||||||
|
elif oldAvailability.totalSize > obj.totalSize: # storage removed
|
||||||
|
if reserveErr =? (await self.repo.release((oldAvailability.totalSize - obj.totalSize).truncate(uint))).errorOption:
|
||||||
|
return failure(reserveErr.toErr(ReleaseFailedError))
|
||||||
|
else:
|
||||||
|
let err = getResult.error()
|
||||||
|
if not (err of NotExistsError):
|
||||||
|
return failure(err)
|
||||||
|
|
||||||
|
return await self.updateImpl(obj)
|
||||||
|
|
||||||
proc delete(
|
proc delete(
|
||||||
self: Reservations,
|
self: Reservations,
|
||||||
key: Key): Future[?!void] {.async.} =
|
key: Key): Future[?!void] {.async.} =
|
||||||
|
@ -258,7 +290,7 @@ proc deleteReservation*(
|
||||||
without var availability =? await self.get(availabilityKey, Availability), error:
|
without var availability =? await self.get(availabilityKey, Availability), error:
|
||||||
return failure(error)
|
return failure(error)
|
||||||
|
|
||||||
availability.size += reservation.size
|
availability.freeSize += reservation.size
|
||||||
|
|
||||||
if updateErr =? (await self.update(availability)).errorOption:
|
if updateErr =? (await self.update(availability)).errorOption:
|
||||||
return failure(updateErr)
|
return failure(updateErr)
|
||||||
|
@ -278,9 +310,9 @@ proc createAvailability*(
|
||||||
trace "creating availability", size, duration, minPrice, maxCollateral
|
trace "creating availability", size, duration, minPrice, maxCollateral
|
||||||
|
|
||||||
let availability = Availability.init(
|
let availability = Availability.init(
|
||||||
size, duration, minPrice, maxCollateral
|
size, size, duration, minPrice, maxCollateral
|
||||||
)
|
)
|
||||||
let bytes = availability.size.truncate(uint)
|
let bytes = availability.freeSize.truncate(uint)
|
||||||
|
|
||||||
if reserveErr =? (await self.repo.reserve(bytes)).errorOption:
|
if reserveErr =? (await self.repo.reserve(bytes)).errorOption:
|
||||||
return failure(reserveErr.toErr(ReserveFailedError))
|
return failure(reserveErr.toErr(ReserveFailedError))
|
||||||
|
@ -324,7 +356,7 @@ proc createReservation*(
|
||||||
without var availability =? await self.get(availabilityKey, Availability), error:
|
without var availability =? await self.get(availabilityKey, Availability), error:
|
||||||
return failure(error)
|
return failure(error)
|
||||||
|
|
||||||
if availability.size < slotSize:
|
if availability.freeSize < slotSize:
|
||||||
let error = newException(
|
let error = newException(
|
||||||
BytesOutOfBoundsError,
|
BytesOutOfBoundsError,
|
||||||
"trying to reserve an amount of bytes that is greater than the total size of the Availability")
|
"trying to reserve an amount of bytes that is greater than the total size of the Availability")
|
||||||
|
@ -333,9 +365,9 @@ proc createReservation*(
|
||||||
if createResErr =? (await self.update(reservation)).errorOption:
|
if createResErr =? (await self.update(reservation)).errorOption:
|
||||||
return failure(createResErr)
|
return failure(createResErr)
|
||||||
|
|
||||||
# reduce availability size by the slot size, which is now accounted for in
|
# reduce availability freeSize by the slot size, which is now accounted for in
|
||||||
# the newly created Reservation
|
# the newly created Reservation
|
||||||
availability.size -= slotSize
|
availability.freeSize -= slotSize
|
||||||
|
|
||||||
# update availability with reduced size
|
# update availability with reduced size
|
||||||
if updateErr =? (await self.update(availability)).errorOption:
|
if updateErr =? (await self.update(availability)).errorOption:
|
||||||
|
@ -393,7 +425,7 @@ proc returnBytesToAvailability*(
|
||||||
without var availability =? await self.get(availabilityKey, Availability), error:
|
without var availability =? await self.get(availabilityKey, Availability), error:
|
||||||
return failure(error)
|
return failure(error)
|
||||||
|
|
||||||
availability.size += bytesToBeReturned
|
availability.freeSize += bytesToBeReturned
|
||||||
|
|
||||||
# Update availability with returned size
|
# Update availability with returned size
|
||||||
if updateErr =? (await self.update(availability)).errorOption:
|
if updateErr =? (await self.update(availability)).errorOption:
|
||||||
|
@ -456,11 +488,12 @@ iterator items(self: StorableIter): Future[?seq[byte]] =
|
||||||
|
|
||||||
proc storables(
|
proc storables(
|
||||||
self: Reservations,
|
self: Reservations,
|
||||||
T: type SomeStorableObject
|
T: type SomeStorableObject,
|
||||||
|
queryKey: Key = ReservationsKey
|
||||||
): Future[?!StorableIter] {.async.} =
|
): Future[?!StorableIter] {.async.} =
|
||||||
|
|
||||||
var iter = StorableIter()
|
var iter = StorableIter()
|
||||||
let query = Query.init(ReservationsKey)
|
let query = Query.init(queryKey)
|
||||||
when T is Availability:
|
when T is Availability:
|
||||||
# should indicate key length of 4, but let the .key logic determine it
|
# should indicate key length of 4, but let the .key logic determine it
|
||||||
without defaultKey =? AvailabilityId.default.key, error:
|
without defaultKey =? AvailabilityId.default.key, error:
|
||||||
|
@ -475,6 +508,7 @@ proc storables(
|
||||||
without results =? await self.repo.metaDs.query(query), error:
|
without results =? await self.repo.metaDs.query(query), error:
|
||||||
return failure(error)
|
return failure(error)
|
||||||
|
|
||||||
|
# /sales/reservations
|
||||||
proc next(): Future[?seq[byte]] {.async.} =
|
proc next(): Future[?seq[byte]] {.async.} =
|
||||||
await idleAsync()
|
await idleAsync()
|
||||||
iter.finished = results.finished
|
iter.finished = results.finished
|
||||||
|
@ -491,14 +525,15 @@ proc storables(
|
||||||
iter.next = next
|
iter.next = next
|
||||||
return success iter
|
return success iter
|
||||||
|
|
||||||
proc all*(
|
proc allImpl(
|
||||||
self: Reservations,
|
self: Reservations,
|
||||||
T: type SomeStorableObject
|
T: type SomeStorableObject,
|
||||||
|
queryKey: Key = ReservationsKey
|
||||||
): Future[?!seq[T]] {.async.} =
|
): Future[?!seq[T]] {.async.} =
|
||||||
|
|
||||||
var ret: seq[T] = @[]
|
var ret: seq[T] = @[]
|
||||||
|
|
||||||
without storables =? (await self.storables(T)), error:
|
without storables =? (await self.storables(T, queryKey)), error:
|
||||||
return failure(error)
|
return failure(error)
|
||||||
|
|
||||||
for storable in storables.items:
|
for storable in storables.items:
|
||||||
|
@ -515,6 +550,22 @@ proc all*(
|
||||||
|
|
||||||
return success(ret)
|
return success(ret)
|
||||||
|
|
||||||
|
proc all*(
|
||||||
|
self: Reservations,
|
||||||
|
T: type SomeStorableObject
|
||||||
|
): Future[?!seq[T]] {.async.} =
|
||||||
|
return await self.allImpl(T)
|
||||||
|
|
||||||
|
proc all*(
|
||||||
|
self: Reservations,
|
||||||
|
T: type SomeStorableObject,
|
||||||
|
availabilityId: AvailabilityId
|
||||||
|
): Future[?!seq[T]] {.async.} =
|
||||||
|
without key =? (ReservationsKey / $availabilityId):
|
||||||
|
return failure("no key")
|
||||||
|
|
||||||
|
return await self.allImpl(T, key)
|
||||||
|
|
||||||
proc findAvailability*(
|
proc findAvailability*(
|
||||||
self: Reservations,
|
self: Reservations,
|
||||||
size, duration, minPrice, collateral: UInt256
|
size, duration, minPrice, collateral: UInt256
|
||||||
|
@ -528,13 +579,13 @@ proc findAvailability*(
|
||||||
if bytes =? (await item) and
|
if bytes =? (await item) and
|
||||||
availability =? Availability.fromJson(bytes):
|
availability =? Availability.fromJson(bytes):
|
||||||
|
|
||||||
if size <= availability.size and
|
if size <= availability.freeSize and
|
||||||
duration <= availability.duration and
|
duration <= availability.duration and
|
||||||
collateral <= availability.maxCollateral and
|
collateral <= availability.maxCollateral and
|
||||||
minPrice >= availability.minPrice:
|
minPrice >= availability.minPrice:
|
||||||
|
|
||||||
trace "availability matched",
|
trace "availability matched",
|
||||||
size, availsize = availability.size,
|
size, availFreeSize = availability.freeSize,
|
||||||
duration, availDuration = availability.duration,
|
duration, availDuration = availability.duration,
|
||||||
minPrice, availMinPrice = availability.minPrice,
|
minPrice, availMinPrice = availability.minPrice,
|
||||||
collateral, availMaxCollateral = availability.maxCollateral
|
collateral, availMaxCollateral = availability.maxCollateral
|
||||||
|
@ -542,7 +593,7 @@ proc findAvailability*(
|
||||||
return some availability
|
return some availability
|
||||||
|
|
||||||
trace "availability did not match",
|
trace "availability did not match",
|
||||||
size, availsize = availability.size,
|
size, availFreeSize = availability.freeSize,
|
||||||
duration, availDuration = availability.duration,
|
duration, availDuration = availability.duration,
|
||||||
minPrice, availMinPrice = availability.minPrice,
|
minPrice, availMinPrice = availability.minPrice,
|
||||||
collateral, availMaxCollateral = availability.maxCollateral
|
collateral, availMaxCollateral = availability.maxCollateral
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import pkg/questionable
|
|
||||||
import pkg/questionable/operators
|
|
||||||
|
|
||||||
export questionable
|
|
||||||
|
|
||||||
proc `as`*[T](value: T, U: type): ?U =
|
|
||||||
## Casts a value to another type, returns an Option.
|
|
||||||
## When the cast succeeds, the option will contain the casted value.
|
|
||||||
## When the cast fails, the option will have no value.
|
|
||||||
when value is U:
|
|
||||||
return some value
|
|
||||||
elif value is ref object:
|
|
||||||
if value of U:
|
|
||||||
return some U(value)
|
|
||||||
|
|
||||||
Option.liftBinary `as`
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import macros
|
||||||
|
import strutils
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/questionable/operators
|
||||||
|
|
||||||
|
export questionable
|
||||||
|
|
||||||
|
proc `as`*[T](value: T, U: type): ?U =
|
||||||
|
## Casts a value to another type, returns an Option.
|
||||||
|
## When the cast succeeds, the option will contain the casted value.
|
||||||
|
## When the cast fails, the option will have no value.
|
||||||
|
when value is U:
|
||||||
|
return some value
|
||||||
|
elif value is ref object:
|
||||||
|
if value of U:
|
||||||
|
return some U(value)
|
||||||
|
|
||||||
|
Option.liftBinary `as`
|
||||||
|
|
||||||
|
# Template that wraps type with `Option[]` only if it is already not `Option` type
|
||||||
|
template WrapOption*(input: untyped): type =
|
||||||
|
when input is Option:
|
||||||
|
input
|
||||||
|
else:
|
||||||
|
Option[input]
|
||||||
|
|
||||||
|
|
||||||
|
macro createType(t: typedesc): untyped =
|
||||||
|
var objectType = getType(t)
|
||||||
|
|
||||||
|
# Work around for https://github.com/nim-lang/Nim/issues/23112
|
||||||
|
while objectType.kind == nnkBracketExpr and objectType[0].eqIdent"typeDesc":
|
||||||
|
objectType = getType(objectType[1])
|
||||||
|
|
||||||
|
expectKind(objectType, NimNodeKind.nnkObjectTy)
|
||||||
|
var fields = nnkRecList.newTree()
|
||||||
|
|
||||||
|
# Generates the list of fields that are wrapped in `Option[T]`.
|
||||||
|
# Technically wrapped with `WrapOption` which is template used to prevent
|
||||||
|
# re-wrapping already filed which is `Option[T]`.
|
||||||
|
for field in objectType[2]:
|
||||||
|
let fieldType = getTypeInst(field)
|
||||||
|
let newFieldNode =
|
||||||
|
nnkIdentDefs.newTree(ident($field), nnkCall.newTree(ident("WrapOption"), fieldType), newEmptyNode())
|
||||||
|
|
||||||
|
fields.add(newFieldNode)
|
||||||
|
|
||||||
|
# Creates new object type T with the fields lists from steps above.
|
||||||
|
let tSym = genSym(nskType, "T")
|
||||||
|
nnkStmtList.newTree(
|
||||||
|
nnkTypeSection.newTree(
|
||||||
|
nnkTypeDef.newTree(tSym, newEmptyNode(), nnkObjectTy.newTree(newEmptyNode(), newEmptyNode(), fields))
|
||||||
|
),
|
||||||
|
tSym
|
||||||
|
)
|
||||||
|
|
||||||
|
template Optionalize*(t: typed): untyped =
|
||||||
|
## Takes object type and wraps all the first level fields into
|
||||||
|
## Option type unless it is already Option type.
|
||||||
|
createType(t)
|
||||||
|
|
128
openapi.yaml
128
openapi.yaml
|
@ -20,6 +20,15 @@ components:
|
||||||
description: Peer Identity reference as specified at https://docs.libp2p.io/concepts/fundamentals/peers/
|
description: Peer Identity reference as specified at https://docs.libp2p.io/concepts/fundamentals/peers/
|
||||||
example: QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
|
example: QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
|
||||||
|
|
||||||
|
Id:
|
||||||
|
type: string
|
||||||
|
description: 32bits identifier encoded in hex-decimal string.
|
||||||
|
example: 0x...
|
||||||
|
|
||||||
|
BigInt:
|
||||||
|
type: string
|
||||||
|
description: Integer represented as decimal string
|
||||||
|
|
||||||
Cid:
|
Cid:
|
||||||
type: string
|
type: string
|
||||||
description: Content Identifier as specified at https://github.com/multiformats/cid
|
description: Content Identifier as specified at https://github.com/multiformats/cid
|
||||||
|
@ -102,16 +111,12 @@ components:
|
||||||
|
|
||||||
SalesAvailability:
|
SalesAvailability:
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- size
|
|
||||||
- minPrice
|
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
|
$ref: "#/components/schemas/Id"
|
||||||
|
totalSize:
|
||||||
type: string
|
type: string
|
||||||
description: Hexadecimal identifier of the availability
|
description: Total size of availability's storage in bytes as decimal string
|
||||||
size:
|
|
||||||
type: string
|
|
||||||
description: Size of available storage in bytes as decimal string
|
|
||||||
duration:
|
duration:
|
||||||
$ref: "#/components/schemas/Duration"
|
$ref: "#/components/schemas/Duration"
|
||||||
minPrice:
|
minPrice:
|
||||||
|
@ -121,6 +126,24 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: Maximum collateral user is willing to pay per filled Slot (in amount of tokens) as decimal string
|
description: Maximum collateral user is willing to pay per filled Slot (in amount of tokens) as decimal string
|
||||||
|
|
||||||
|
SalesAvailabilityREAD:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/SalesAvailability"
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
freeSize:
|
||||||
|
type: string
|
||||||
|
description: Unused size of availability's storage in bytes as decimal string
|
||||||
|
|
||||||
|
SalesAvailabilityCREATE:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/SalesAvailability"
|
||||||
|
- required:
|
||||||
|
- totalSize
|
||||||
|
- minPrice
|
||||||
|
- maxCollateral
|
||||||
|
- duration
|
||||||
|
|
||||||
Slot:
|
Slot:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -132,6 +155,21 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: Slot Index as hexadecimal string
|
description: Slot Index as hexadecimal string
|
||||||
|
|
||||||
|
Reservation:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/Id"
|
||||||
|
availabilityId:
|
||||||
|
$ref: "#/components/schemas/Id"
|
||||||
|
size:
|
||||||
|
$ref: "#/components/schemas/BigInt"
|
||||||
|
requestId:
|
||||||
|
$ref: "#/components/schemas/Id"
|
||||||
|
slotIndex:
|
||||||
|
type: string
|
||||||
|
description: Slot Index as hexadecimal string
|
||||||
|
|
||||||
StorageRequestCreation:
|
StorageRequestCreation:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
@ -493,16 +531,84 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/SalesAvailability"
|
$ref: "#/components/schemas/SalesAvailabilityCREATE"
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"201":
|
||||||
description: Created storage availability
|
description: Created storage availability
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/SalesAvailability"
|
$ref: "#/components/schemas/SalesAvailabilityREAD"
|
||||||
|
"400":
|
||||||
|
description: Invalid data input
|
||||||
|
"422":
|
||||||
|
description: Not enough node's storage quota available
|
||||||
"500":
|
"500":
|
||||||
description: Error reserving availablility
|
description: Error reserving availability
|
||||||
|
"503":
|
||||||
|
description: Sales are unavailable
|
||||||
|
"/sales/availability/{id}":
|
||||||
|
patch:
|
||||||
|
summary: "Updates availability"
|
||||||
|
description: |
|
||||||
|
The new parameters will be only considered for new requests.
|
||||||
|
Existing Requests linked to this Availability will continue as is.
|
||||||
|
operationId: updateOfferedStorage
|
||||||
|
tags: [ Marketplace ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: ID of Availability
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/SalesAvailability"
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: Availability successfully updated
|
||||||
|
"400":
|
||||||
|
description: Invalid data input
|
||||||
|
"404":
|
||||||
|
description: Availability not found
|
||||||
|
"422":
|
||||||
|
description: Not enough node's storage quota available
|
||||||
|
"500":
|
||||||
|
description: Error reserving availability
|
||||||
|
"503":
|
||||||
|
description: Sales are unavailable
|
||||||
|
|
||||||
|
"/sales/availability/{id}/reservations":
|
||||||
|
patch:
|
||||||
|
summary: "Get availability's reservations"
|
||||||
|
description: Return's list of Reservations for ongoing Storage Requests that the node hosts.
|
||||||
|
operationId: getReservations
|
||||||
|
tags: [ Marketplace ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: ID of Availability
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Retrieved storage availabilities of the node
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Reservation"
|
||||||
|
"400":
|
||||||
|
description: Invalid Availability ID
|
||||||
|
"404":
|
||||||
|
description: Availability not found
|
||||||
|
"500":
|
||||||
|
description: Error getting reservations
|
||||||
"503":
|
"503":
|
||||||
description: Sales are unavailable
|
description: Sales are unavailable
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,8 @@ proc example*(_: type MultiHash, mcodec = Sha256HashCodec): MultiHash =
|
||||||
|
|
||||||
proc example*(_: type Availability): Availability =
|
proc example*(_: type Availability): Availability =
|
||||||
Availability.init(
|
Availability.init(
|
||||||
size = uint16.example.u256,
|
totalSize = uint16.example.u256,
|
||||||
|
freeSize = uint16.example.u256,
|
||||||
duration = uint16.example.u256,
|
duration = uint16.example.u256,
|
||||||
minPrice = uint64.example.u256,
|
minPrice = uint64.example.u256,
|
||||||
maxCollateral = uint16.example.u256
|
maxCollateral = uint16.example.u256
|
||||||
|
|
|
@ -29,9 +29,9 @@ asyncchecksuite "Reservations module":
|
||||||
|
|
||||||
proc createAvailability(): Availability =
|
proc createAvailability(): Availability =
|
||||||
let example = Availability.example
|
let example = Availability.example
|
||||||
let size = rand(100000..200000)
|
let totalSize = rand(100000..200000)
|
||||||
let availability = waitFor reservations.createAvailability(
|
let availability = waitFor reservations.createAvailability(
|
||||||
size.u256,
|
totalSize.u256,
|
||||||
example.duration,
|
example.duration,
|
||||||
example.minPrice,
|
example.minPrice,
|
||||||
example.maxCollateral
|
example.maxCollateral
|
||||||
|
@ -39,7 +39,7 @@ asyncchecksuite "Reservations module":
|
||||||
return availability.get
|
return availability.get
|
||||||
|
|
||||||
proc createReservation(availability: Availability): Reservation =
|
proc createReservation(availability: Availability): Reservation =
|
||||||
let size = rand(1..<availability.size.truncate(int))
|
let size = rand(1..<availability.freeSize.truncate(int))
|
||||||
let reservation = waitFor reservations.createReservation(
|
let reservation = waitFor reservations.createReservation(
|
||||||
availability.id,
|
availability.id,
|
||||||
size.u256,
|
size.u256,
|
||||||
|
@ -57,8 +57,8 @@ asyncchecksuite "Reservations module":
|
||||||
check (await reservations.all(Availability)).get.len == 0
|
check (await reservations.all(Availability)).get.len == 0
|
||||||
|
|
||||||
test "generates unique ids for storage availability":
|
test "generates unique ids for storage availability":
|
||||||
let availability1 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256)
|
let availability1 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256, 5.u256)
|
||||||
let availability2 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256)
|
let availability2 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256, 5.u256)
|
||||||
check availability1.id != availability2.id
|
check availability1.id != availability2.id
|
||||||
|
|
||||||
test "can reserve available storage":
|
test "can reserve available storage":
|
||||||
|
@ -68,7 +68,7 @@ asyncchecksuite "Reservations module":
|
||||||
test "creating availability reserves bytes in repo":
|
test "creating availability reserves bytes in repo":
|
||||||
let orig = repo.available
|
let orig = repo.available
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
check repo.available == (orig.u256 - availability.size).truncate(uint)
|
check repo.available == (orig.u256 - availability.freeSize).truncate(uint)
|
||||||
|
|
||||||
test "can get all availabilities":
|
test "can get all availabilities":
|
||||||
let availability1 = createAvailability()
|
let availability1 = createAvailability()
|
||||||
|
@ -106,6 +106,19 @@ asyncchecksuite "Reservations module":
|
||||||
reservations.contains(reservation1)
|
reservations.contains(reservation1)
|
||||||
reservations.contains(reservation2)
|
reservations.contains(reservation2)
|
||||||
|
|
||||||
|
test "can get reservations of specific availability":
|
||||||
|
let availability1 = createAvailability()
|
||||||
|
let availability2 = createAvailability()
|
||||||
|
let reservation1 = createReservation(availability1)
|
||||||
|
let reservation2 = createReservation(availability2)
|
||||||
|
let reservations = !(await reservations.all(Reservation, availability1.id))
|
||||||
|
|
||||||
|
check:
|
||||||
|
# perform unordered checks
|
||||||
|
reservations.len == 1
|
||||||
|
reservations.contains(reservation1)
|
||||||
|
not reservations.contains(reservation2)
|
||||||
|
|
||||||
test "cannot create reservation with non-existant availability":
|
test "cannot create reservation with non-existant availability":
|
||||||
let availability = Availability.example
|
let availability = Availability.example
|
||||||
let created = await reservations.createReservation(
|
let created = await reservations.createReservation(
|
||||||
|
@ -121,7 +134,7 @@ asyncchecksuite "Reservations module":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
let created = await reservations.createReservation(
|
let created = await reservations.createReservation(
|
||||||
availability.id,
|
availability.id,
|
||||||
availability.size + 1,
|
availability.totalSize + 1,
|
||||||
RequestId.example,
|
RequestId.example,
|
||||||
UInt256.example
|
UInt256.example
|
||||||
)
|
)
|
||||||
|
@ -130,11 +143,11 @@ asyncchecksuite "Reservations module":
|
||||||
|
|
||||||
test "creating reservation reduces availability size":
|
test "creating reservation reduces availability size":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
let orig = availability.size
|
let orig = availability.freeSize
|
||||||
let reservation = createReservation(availability)
|
let reservation = createReservation(availability)
|
||||||
let key = availability.id.key.get
|
let key = availability.id.key.get
|
||||||
let updated = (await reservations.get(key, Availability)).get
|
let updated = (await reservations.get(key, Availability)).get
|
||||||
check updated.size == orig - reservation.size
|
check updated.freeSize == orig - reservation.size
|
||||||
|
|
||||||
test "can check if reservation exists":
|
test "can check if reservation exists":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
|
@ -166,19 +179,19 @@ asyncchecksuite "Reservations module":
|
||||||
|
|
||||||
test "deleting reservation returns bytes back to availability":
|
test "deleting reservation returns bytes back to availability":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
let orig = availability.size
|
let orig = availability.freeSize
|
||||||
let reservation = createReservation(availability)
|
let reservation = createReservation(availability)
|
||||||
discard await reservations.deleteReservation(
|
discard await reservations.deleteReservation(
|
||||||
reservation.id, reservation.availabilityId
|
reservation.id, reservation.availabilityId
|
||||||
)
|
)
|
||||||
let key = availability.key.get
|
let key = availability.key.get
|
||||||
let updated = !(await reservations.get(key, Availability))
|
let updated = !(await reservations.get(key, Availability))
|
||||||
check updated.size == orig
|
check updated.freeSize == orig
|
||||||
|
|
||||||
test "calling returnBytesToAvailability returns bytes back to availability":
|
test "calling returnBytesToAvailability returns bytes back to availability":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
let reservation = createReservation(availability)
|
let reservation = createReservation(availability)
|
||||||
let orig = availability.size - reservation.size
|
let orig = availability.freeSize - reservation.size
|
||||||
let origQuota = repo.quotaReservedBytes
|
let origQuota = repo.quotaReservedBytes
|
||||||
let returnedBytes = reservation.size + 200.u256
|
let returnedBytes = reservation.size + 200.u256
|
||||||
|
|
||||||
|
@ -189,10 +202,28 @@ asyncchecksuite "Reservations module":
|
||||||
let key = availability.key.get
|
let key = availability.key.get
|
||||||
let updated = !(await reservations.get(key, Availability))
|
let updated = !(await reservations.get(key, Availability))
|
||||||
|
|
||||||
check updated.size > orig
|
check updated.freeSize > orig
|
||||||
check (updated.size - orig) == 200.u256
|
check (updated.freeSize - orig) == 200.u256
|
||||||
check (repo.quotaReservedBytes - origQuota) == 200
|
check (repo.quotaReservedBytes - origQuota) == 200
|
||||||
|
|
||||||
|
test "update releases quota when lowering size":
|
||||||
|
let
|
||||||
|
availability = createAvailability()
|
||||||
|
origQuota = repo.quotaReservedBytes
|
||||||
|
availability.totalSize = availability.totalSize - 100
|
||||||
|
|
||||||
|
check isOk await reservations.update(availability)
|
||||||
|
check (origQuota - repo.quotaReservedBytes) == 100
|
||||||
|
|
||||||
|
test "update reserves quota when growing size":
|
||||||
|
let
|
||||||
|
availability = createAvailability()
|
||||||
|
origQuota = repo.quotaReservedBytes
|
||||||
|
availability.totalSize = availability.totalSize + 100
|
||||||
|
|
||||||
|
check isOk await reservations.update(availability)
|
||||||
|
check (repo.quotaReservedBytes - origQuota) == 100
|
||||||
|
|
||||||
test "reservation can be partially released":
|
test "reservation can be partially released":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
let reservation = createReservation(availability)
|
let reservation = createReservation(availability)
|
||||||
|
@ -240,7 +271,7 @@ asyncchecksuite "Reservations module":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
|
|
||||||
let found = await reservations.findAvailability(
|
let found = await reservations.findAvailability(
|
||||||
availability.size,
|
availability.freeSize,
|
||||||
availability.duration,
|
availability.duration,
|
||||||
availability.minPrice,
|
availability.minPrice,
|
||||||
availability.maxCollateral)
|
availability.maxCollateral)
|
||||||
|
@ -252,7 +283,7 @@ asyncchecksuite "Reservations module":
|
||||||
let availability = createAvailability()
|
let availability = createAvailability()
|
||||||
|
|
||||||
let found = await reservations.findAvailability(
|
let found = await reservations.findAvailability(
|
||||||
availability.size + 1,
|
availability.freeSize + 1,
|
||||||
availability.duration,
|
availability.duration,
|
||||||
availability.minPrice,
|
availability.minPrice,
|
||||||
availability.maxCollateral)
|
availability.maxCollateral)
|
||||||
|
@ -262,7 +293,7 @@ asyncchecksuite "Reservations module":
|
||||||
test "non-existant availability cannot be found":
|
test "non-existant availability cannot be found":
|
||||||
let availability = Availability.example
|
let availability = Availability.example
|
||||||
let found = (await reservations.findAvailability(
|
let found = (await reservations.findAvailability(
|
||||||
availability.size,
|
availability.freeSize,
|
||||||
availability.duration,
|
availability.duration,
|
||||||
availability.minPrice,
|
availability.minPrice,
|
||||||
availability.maxCollateral
|
availability.maxCollateral
|
||||||
|
|
|
@ -127,7 +127,8 @@ asyncchecksuite "Sales":
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
availability = Availability(
|
availability = Availability(
|
||||||
size: 100.u256,
|
totalSize: 100.u256,
|
||||||
|
freeSize: 100.u256,
|
||||||
duration: 60.u256,
|
duration: 60.u256,
|
||||||
minPrice: 600.u256,
|
minPrice: 600.u256,
|
||||||
maxCollateral: 400.u256
|
maxCollateral: 400.u256
|
||||||
|
@ -189,7 +190,7 @@ asyncchecksuite "Sales":
|
||||||
|
|
||||||
proc createAvailability() =
|
proc createAvailability() =
|
||||||
let a = waitFor reservations.createAvailability(
|
let a = waitFor reservations.createAvailability(
|
||||||
availability.size,
|
availability.totalSize,
|
||||||
availability.duration,
|
availability.duration,
|
||||||
availability.minPrice,
|
availability.minPrice,
|
||||||
availability.maxCollateral
|
availability.maxCollateral
|
||||||
|
@ -224,7 +225,7 @@ asyncchecksuite "Sales":
|
||||||
proc wasIgnored(): bool =
|
proc wasIgnored(): bool =
|
||||||
let run = proc(): Future[bool] {.async.} =
|
let run = proc(): Future[bool] {.async.} =
|
||||||
always (
|
always (
|
||||||
getAvailability().size == availability.size and
|
getAvailability().freeSize == availability.freeSize and
|
||||||
(waitFor reservations.all(Reservation)).get.len == 0
|
(waitFor reservations.all(Reservation)).get.len == 0
|
||||||
)
|
)
|
||||||
waitFor run()
|
waitFor run()
|
||||||
|
@ -300,7 +301,7 @@ asyncchecksuite "Sales":
|
||||||
|
|
||||||
createAvailability()
|
createAvailability()
|
||||||
await market.requestStorage(request)
|
await market.requestStorage(request)
|
||||||
check eventually getAvailability().size == availability.size - request.ask.slotSize
|
check eventually getAvailability().freeSize == availability.freeSize - request.ask.slotSize
|
||||||
|
|
||||||
test "non-downloaded bytes are returned to availability once finished":
|
test "non-downloaded bytes are returned to availability once finished":
|
||||||
var slotIndex = 0.u256
|
var slotIndex = 0.u256
|
||||||
|
@ -316,7 +317,7 @@ asyncchecksuite "Sales":
|
||||||
sold.complete()
|
sold.complete()
|
||||||
|
|
||||||
createAvailability()
|
createAvailability()
|
||||||
let origSize = availability.size
|
let origSize = availability.freeSize
|
||||||
await market.requestStorage(request)
|
await market.requestStorage(request)
|
||||||
await allowRequestToStart()
|
await allowRequestToStart()
|
||||||
await sold
|
await sold
|
||||||
|
@ -325,7 +326,7 @@ asyncchecksuite "Sales":
|
||||||
market.slotState[request.slotId(slotIndex)] = SlotState.Finished
|
market.slotState[request.slotId(slotIndex)] = SlotState.Finished
|
||||||
clock.advance(request.ask.duration.truncate(int64))
|
clock.advance(request.ask.duration.truncate(int64))
|
||||||
|
|
||||||
check eventually getAvailability().size == origSize - 1
|
check eventually getAvailability().freeSize == origSize - 1
|
||||||
|
|
||||||
test "ignores download when duration not long enough":
|
test "ignores download when duration not long enough":
|
||||||
availability.duration = request.ask.duration - 1
|
availability.duration = request.ask.duration - 1
|
||||||
|
@ -334,7 +335,7 @@ asyncchecksuite "Sales":
|
||||||
check wasIgnored()
|
check wasIgnored()
|
||||||
|
|
||||||
test "ignores request when slot size is too small":
|
test "ignores request when slot size is too small":
|
||||||
availability.size = request.ask.slotSize - 1
|
availability.totalSize = request.ask.slotSize - 1
|
||||||
createAvailability()
|
createAvailability()
|
||||||
await market.requestStorage(request)
|
await market.requestStorage(request)
|
||||||
check wasIgnored()
|
check wasIgnored()
|
||||||
|
@ -399,7 +400,7 @@ asyncchecksuite "Sales":
|
||||||
return failure(error)
|
return failure(error)
|
||||||
createAvailability()
|
createAvailability()
|
||||||
await market.requestStorage(request)
|
await market.requestStorage(request)
|
||||||
check getAvailability().size == availability.size
|
check getAvailability().freeSize == availability.freeSize
|
||||||
|
|
||||||
test "generates proof of storage":
|
test "generates proof of storage":
|
||||||
var provingRequest: StorageRequest
|
var provingRequest: StorageRequest
|
||||||
|
@ -472,7 +473,7 @@ asyncchecksuite "Sales":
|
||||||
check eventually (await reservations.all(Availability)).get == @[availability]
|
check eventually (await reservations.all(Availability)).get == @[availability]
|
||||||
|
|
||||||
test "makes storage available again when request expires":
|
test "makes storage available again when request expires":
|
||||||
let origSize = availability.size
|
let origSize = availability.freeSize
|
||||||
sales.onStore = proc(request: StorageRequest,
|
sales.onStore = proc(request: StorageRequest,
|
||||||
slot: UInt256,
|
slot: UInt256,
|
||||||
onBatch: BatchProc): Future[?!void] {.async.} =
|
onBatch: BatchProc): Future[?!void] {.async.} =
|
||||||
|
@ -487,10 +488,10 @@ asyncchecksuite "Sales":
|
||||||
market.requestState[request.id]=RequestState.Cancelled
|
market.requestState[request.id]=RequestState.Cancelled
|
||||||
clock.set(request.expiry.truncate(int64)+1)
|
clock.set(request.expiry.truncate(int64)+1)
|
||||||
check eventually (await reservations.all(Availability)).get == @[availability]
|
check eventually (await reservations.all(Availability)).get == @[availability]
|
||||||
check getAvailability().size == origSize
|
check getAvailability().freeSize == origSize
|
||||||
|
|
||||||
test "verifies that request is indeed expired from onchain before firing onCancelled":
|
test "verifies that request is indeed expired from onchain before firing onCancelled":
|
||||||
let origSize = availability.size
|
let origSize = availability.freeSize
|
||||||
sales.onStore = proc(request: StorageRequest,
|
sales.onStore = proc(request: StorageRequest,
|
||||||
slot: UInt256,
|
slot: UInt256,
|
||||||
onBatch: BatchProc): Future[?!void] {.async.} =
|
onBatch: BatchProc): Future[?!void] {.async.} =
|
||||||
|
@ -504,10 +505,10 @@ asyncchecksuite "Sales":
|
||||||
# would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation.
|
# would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation.
|
||||||
await sleepAsync(chronos.milliseconds(100))
|
await sleepAsync(chronos.milliseconds(100))
|
||||||
clock.set(request.expiry.truncate(int64)+1)
|
clock.set(request.expiry.truncate(int64)+1)
|
||||||
check getAvailability().size == 0
|
check getAvailability().freeSize == 0
|
||||||
|
|
||||||
market.requestState[request.id]=RequestState.Cancelled # Now "on-chain" is also expired
|
market.requestState[request.id]=RequestState.Cancelled # Now "on-chain" is also expired
|
||||||
check eventually getAvailability().size == origSize
|
check eventually getAvailability().freeSize == origSize
|
||||||
|
|
||||||
test "loads active slots from market":
|
test "loads active slots from market":
|
||||||
let me = await market.getSigner()
|
let me = await market.getSigner()
|
||||||
|
@ -556,4 +557,4 @@ asyncchecksuite "Sales":
|
||||||
check (await reservations.all(Reservation)).get.len == 1
|
check (await reservations.all(Reservation)).get.len == 1
|
||||||
await sales.load()
|
await sales.load()
|
||||||
check (await reservations.all(Reservation)).get.len == 0
|
check (await reservations.all(Reservation)).get.len == 0
|
||||||
check getAvailability().size == availability.size # was restored
|
check getAvailability().freeSize == availability.freeSize # was restored
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ./utils/testoptionalcast
|
import ./utils/testoptions
|
||||||
import ./utils/testkeyutils
|
import ./utils/testkeyutils
|
||||||
import ./utils/testasyncstatemachine
|
import ./utils/testasyncstatemachine
|
||||||
import ./utils/testtimer
|
import ./utils/testtimer
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import std/unittest
|
import std/unittest
|
||||||
import codex/utils/optionalcast
|
import codex/utils/options
|
||||||
import ../helpers
|
import ../helpers
|
||||||
|
|
||||||
checksuite "optional casts":
|
checksuite "optional casts":
|
||||||
|
@ -28,3 +28,22 @@ checksuite "optional casts":
|
||||||
check 42.some as int == some 42
|
check 42.some as int == some 42
|
||||||
check 42.some as string == string.none
|
check 42.some as string == string.none
|
||||||
check int.none as int == int.none
|
check int.none as int == int.none
|
||||||
|
|
||||||
|
checksuite "Optionalize":
|
||||||
|
test "does not except non-object types":
|
||||||
|
static:
|
||||||
|
doAssert not compiles(Optionalize(int))
|
||||||
|
|
||||||
|
test "converts object fields to option":
|
||||||
|
type BaseType = object
|
||||||
|
a: int
|
||||||
|
b: bool
|
||||||
|
c: string
|
||||||
|
d: Option[string]
|
||||||
|
|
||||||
|
type OptionalizedType = Optionalize(BaseType)
|
||||||
|
|
||||||
|
check OptionalizedType.a is Option[int]
|
||||||
|
check OptionalizedType.b is Option[bool]
|
||||||
|
check OptionalizedType.c is Option[string]
|
||||||
|
check OptionalizedType.d is Option[string]
|
|
@ -133,27 +133,70 @@ proc getSlots*(client: CodexClient): ?!seq[Slot] =
|
||||||
|
|
||||||
proc postAvailability*(
|
proc postAvailability*(
|
||||||
client: CodexClient,
|
client: CodexClient,
|
||||||
size, duration, minPrice, maxCollateral: UInt256
|
totalSize, duration, minPrice, maxCollateral: UInt256
|
||||||
): ?!Availability =
|
): ?!Availability =
|
||||||
## Post sales availability endpoint
|
## Post sales availability endpoint
|
||||||
##
|
##
|
||||||
let url = client.baseurl & "/sales/availability"
|
let url = client.baseurl & "/sales/availability"
|
||||||
let json = %*{
|
let json = %*{
|
||||||
"size": size,
|
"totalSize": totalSize,
|
||||||
"duration": duration,
|
"duration": duration,
|
||||||
"minPrice": minPrice,
|
"minPrice": minPrice,
|
||||||
"maxCollateral": maxCollateral,
|
"maxCollateral": maxCollateral,
|
||||||
}
|
}
|
||||||
let response = client.http.post(url, $json)
|
let response = client.http.post(url, $json)
|
||||||
doAssert response.status == "200 OK", "expected 200 OK, got " & response.status & ", body: " & response.body
|
doAssert response.status == "201 Created", "expected 201 Created, got " & response.status & ", body: " & response.body
|
||||||
Availability.fromJson(response.body)
|
Availability.fromJson(response.body)
|
||||||
|
|
||||||
|
proc patchAvailabilityRaw*(
|
||||||
|
client: CodexClient,
|
||||||
|
availabilityId: AvailabilityId,
|
||||||
|
totalSize, freeSize, duration, minPrice, maxCollateral: ?UInt256 = UInt256.none
|
||||||
|
): Response =
|
||||||
|
## 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 minPrice =? minPrice:
|
||||||
|
json["minPrice"] = %minPrice
|
||||||
|
|
||||||
|
if maxCollateral =? maxCollateral:
|
||||||
|
json["maxCollateral"] = %maxCollateral
|
||||||
|
|
||||||
|
client.http.patch(url, $json)
|
||||||
|
|
||||||
|
proc patchAvailability*(
|
||||||
|
client: CodexClient,
|
||||||
|
availabilityId: AvailabilityId,
|
||||||
|
totalSize, duration, minPrice, maxCollateral: ?UInt256 = UInt256.none
|
||||||
|
): void =
|
||||||
|
let response = client.patchAvailabilityRaw(availabilityId, totalSize=totalSize, duration=duration, minPrice=minPrice, maxCollateral=maxCollateral)
|
||||||
|
doAssert response.status == "200 OK", "expected 200 OK, got " & response.status
|
||||||
|
|
||||||
proc getAvailabilities*(client: CodexClient): ?!seq[Availability] =
|
proc getAvailabilities*(client: CodexClient): ?!seq[Availability] =
|
||||||
## Call sales availability REST endpoint
|
## Call sales availability REST endpoint
|
||||||
let url = client.baseurl & "/sales/availability"
|
let url = client.baseurl & "/sales/availability"
|
||||||
let body = client.http.getContent(url)
|
let body = client.http.getContent(url)
|
||||||
seq[Availability].fromJson(body)
|
seq[Availability].fromJson(body)
|
||||||
|
|
||||||
|
proc getAvailabilityReservations*(client: CodexClient, availabilityId: AvailabilityId): ?!seq[Reservation] =
|
||||||
|
## Retrieves Availability's Reservations
|
||||||
|
let url = client.baseurl & "/sales/availability/" & $availabilityId & "/reservations"
|
||||||
|
let body = client.http.getContent(url)
|
||||||
|
seq[Reservation].fromJson(body)
|
||||||
|
|
||||||
proc close*(client: CodexClient) =
|
proc close*(client: CodexClient) =
|
||||||
client.http.close()
|
client.http.close()
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ template marketplacesuite*(name: string, body: untyped) =
|
||||||
let provider = providers()[i].client
|
let provider = providers()[i].client
|
||||||
|
|
||||||
discard provider.postAvailability(
|
discard provider.postAvailability(
|
||||||
size=datasetSize.u256, # should match 1 slot only
|
totalSize=datasetSize.u256, # should match 1 slot only
|
||||||
duration=duration.u256,
|
duration=duration.u256,
|
||||||
minPrice=300.u256,
|
minPrice=300.u256,
|
||||||
maxCollateral=200.u256
|
maxCollateral=200.u256
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import std/options
|
import std/options
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
|
import std/strutils
|
||||||
import std/httpclient
|
import std/httpclient
|
||||||
from pkg/libp2p import `==`
|
from pkg/libp2p import `==`
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
|
@ -12,8 +13,16 @@ import ../contracts/time
|
||||||
import ../contracts/deployment
|
import ../contracts/deployment
|
||||||
import ../codex/helpers
|
import ../codex/helpers
|
||||||
import ../examples
|
import ../examples
|
||||||
|
import ../codex/examples
|
||||||
import ./twonodes
|
import ./twonodes
|
||||||
|
|
||||||
|
proc findItem[T](items: seq[T], item: T): ?!T =
|
||||||
|
for tmp in items:
|
||||||
|
if tmp == item:
|
||||||
|
return success tmp
|
||||||
|
|
||||||
|
return failure("Not found")
|
||||||
|
|
||||||
# For debugging you can enable logging output with debugX = true
|
# For debugging you can enable logging output with debugX = true
|
||||||
# You can also pass a string in same format like for the `--log-level` parameter
|
# You can also pass a string in same format like for the `--log-level` parameter
|
||||||
# to enable custom logging levels for specific topics like: debug2 = "INFO; TRACE: marketplace"
|
# to enable custom logging levels for specific topics like: debug2 = "INFO; TRACE: marketplace"
|
||||||
|
@ -38,7 +47,7 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
|
||||||
|
|
||||||
test "node shows used and available space":
|
test "node shows used and available space":
|
||||||
discard client1.upload("some file contents").get
|
discard client1.upload("some file contents").get
|
||||||
discard client1.postAvailability(size=12.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get
|
discard client1.postAvailability(totalSize=12.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get
|
||||||
let space = client1.space().tryGet()
|
let space = client1.space().tryGet()
|
||||||
check:
|
check:
|
||||||
space.totalBlocks == 2.uint
|
space.totalBlocks == 2.uint
|
||||||
|
@ -94,12 +103,12 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
|
||||||
[cid1, cid2].allIt(it in list.mapIt(it.cid))
|
[cid1, cid2].allIt(it in list.mapIt(it.cid))
|
||||||
|
|
||||||
test "node handles new storage availability":
|
test "node handles new storage availability":
|
||||||
let availability1 = client1.postAvailability(size=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get
|
let availability1 = client1.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get
|
||||||
let availability2 = client1.postAvailability(size=4.u256, duration=5.u256, minPrice=6.u256, maxCollateral=7.u256).get
|
let availability2 = client1.postAvailability(totalSize=4.u256, duration=5.u256, minPrice=6.u256, maxCollateral=7.u256).get
|
||||||
check availability1 != availability2
|
check availability1 != availability2
|
||||||
|
|
||||||
test "node lists storage that is for sale":
|
test "node lists storage that is for sale":
|
||||||
let availability = client1.postAvailability(size=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get
|
let availability = client1.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get
|
||||||
check availability in client1.getAvailabilities().get
|
check availability in client1.getAvailabilities().get
|
||||||
|
|
||||||
test "node handles storage request":
|
test "node handles storage request":
|
||||||
|
@ -177,7 +186,7 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
|
||||||
let size = 0xFFFFFF.u256
|
let size = 0xFFFFFF.u256
|
||||||
let data = await RandomChunker.example(blocks=8)
|
let data = await RandomChunker.example(blocks=8)
|
||||||
# client 2 makes storage available
|
# client 2 makes storage available
|
||||||
discard client2.postAvailability(size=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256)
|
let availability = client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
|
||||||
# client 1 requests storage
|
# client 1 requests storage
|
||||||
let expiry = (await ethProvider.currentTime()) + 5*60
|
let expiry = (await ethProvider.currentTime()) + 5*60
|
||||||
|
@ -197,9 +206,13 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
|
||||||
check purchase.error == none string
|
check purchase.error == none string
|
||||||
let availabilities = client2.getAvailabilities().get
|
let availabilities = client2.getAvailabilities().get
|
||||||
check availabilities.len == 1
|
check availabilities.len == 1
|
||||||
let newSize = availabilities[0].size
|
let newSize = availabilities[0].freeSize
|
||||||
check newSize > 0 and newSize < size
|
check newSize > 0 and newSize < size
|
||||||
|
|
||||||
|
let reservations = client2.getAvailabilityReservations(availability.id).get
|
||||||
|
check reservations.len == 5
|
||||||
|
check reservations[0].requestId == purchase.requestId
|
||||||
|
|
||||||
test "node slots gets paid out":
|
test "node slots gets paid out":
|
||||||
let size = 0xFFFFFF.u256
|
let size = 0xFFFFFF.u256
|
||||||
let data = await RandomChunker.example(blocks = 8)
|
let data = await RandomChunker.example(blocks = 8)
|
||||||
|
@ -212,7 +225,7 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
|
||||||
|
|
||||||
# client 2 makes storage available
|
# client 2 makes storage available
|
||||||
let startBalance = await token.balanceOf(account2)
|
let startBalance = await token.balanceOf(account2)
|
||||||
discard client2.postAvailability(size=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
discard client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
|
||||||
# client 1 requests storage
|
# client 1 requests storage
|
||||||
let expiry = (await ethProvider.currentTime()) + 5*60
|
let expiry = (await ethProvider.currentTime()) + 5*60
|
||||||
|
@ -263,8 +276,69 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
|
||||||
|
|
||||||
let responsePast = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime-10)
|
let responsePast = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime-10)
|
||||||
check responsePast.status == "400 Bad Request"
|
check responsePast.status == "400 Bad Request"
|
||||||
check responsePast.body == "Expiry needs to be in future"
|
check "Expiry needs to be in future" in responsePast.body
|
||||||
|
|
||||||
let responseBefore = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime+10)
|
let responseBefore = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime+10)
|
||||||
check responseBefore.status == "400 Bad Request"
|
check responseBefore.status == "400 Bad Request"
|
||||||
check responseBefore.body == "Expiry has to be before the request's end (now + duration)"
|
check "Expiry has to be before the request's end (now + duration)" in responseBefore.body
|
||||||
|
|
||||||
|
test "updating non-existing availability":
|
||||||
|
let nonExistingResponse = client1.patchAvailabilityRaw(AvailabilityId.example, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some)
|
||||||
|
check nonExistingResponse.status == "404 Not Found"
|
||||||
|
|
||||||
|
test "updating availability":
|
||||||
|
let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
|
||||||
|
client1.patchAvailability(availability.id, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some)
|
||||||
|
|
||||||
|
let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get
|
||||||
|
check updatedAvailability.duration == 100
|
||||||
|
check updatedAvailability.minPrice == 200
|
||||||
|
check updatedAvailability.maxCollateral == 200
|
||||||
|
check updatedAvailability.totalSize == 140000
|
||||||
|
check updatedAvailability.freeSize == 140000
|
||||||
|
|
||||||
|
test "updating availability - freeSize is not allowed to be changed":
|
||||||
|
let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
let freeSizeResponse = client1.patchAvailabilityRaw(availability.id, freeSize=110000.u256.some)
|
||||||
|
check freeSizeResponse.status == "400 Bad Request"
|
||||||
|
check "not allowed" in freeSizeResponse.body
|
||||||
|
|
||||||
|
test "updating availability - updating totalSize":
|
||||||
|
let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
client1.patchAvailability(availability.id, totalSize=100000.u256.some)
|
||||||
|
let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get
|
||||||
|
check updatedAvailability.totalSize == 100000
|
||||||
|
check updatedAvailability.freeSize == 100000
|
||||||
|
|
||||||
|
test "updating availability - updating totalSize does not allow bellow utilized":
|
||||||
|
let originalSize = 0xFFFFFF.u256
|
||||||
|
let data = await RandomChunker.example(blocks=8)
|
||||||
|
let availability = client1.postAvailability(totalSize=originalSize, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
|
||||||
|
|
||||||
|
# Lets create storage request that will utilize some of the availability's space
|
||||||
|
let expiry = (await ethProvider.currentTime()) + 5*60
|
||||||
|
let cid = client2.upload(data).get
|
||||||
|
let id = client2.requestStorage(
|
||||||
|
cid,
|
||||||
|
duration=10*60.u256,
|
||||||
|
reward=400.u256,
|
||||||
|
proofProbability=3.u256,
|
||||||
|
expiry=expiry,
|
||||||
|
collateral=200.u256,
|
||||||
|
nodes = 5,
|
||||||
|
tolerance = 2).get
|
||||||
|
|
||||||
|
check eventually(client2.purchaseStateIs(id, "started"), timeout=5*60*1000)
|
||||||
|
let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get
|
||||||
|
check updatedAvailability.totalSize != updatedAvailability.freeSize
|
||||||
|
|
||||||
|
let utilizedSize = updatedAvailability.totalSize - updatedAvailability.freeSize
|
||||||
|
let totalSizeResponse = client1.patchAvailabilityRaw(availability.id, totalSize=(utilizedSize-1.u256).some)
|
||||||
|
check totalSizeResponse.status == "400 Bad Request"
|
||||||
|
check "totalSize must be larger then current totalSize" in totalSizeResponse.body
|
||||||
|
|
||||||
|
client1.patchAvailability(availability.id, totalSize=(originalSize + 20000).some)
|
||||||
|
let newUpdatedAvailability = (client1.getAvailabilities().get).findItem(availability).get
|
||||||
|
check newUpdatedAvailability.totalSize == originalSize + 20000
|
||||||
|
check newUpdatedAvailability.freeSize - updatedAvailability.freeSize == 20000
|
||||||
|
|
|
@ -41,7 +41,7 @@ marketplacesuite "Marketplace payouts":
|
||||||
discard providerApi.postAvailability(
|
discard providerApi.postAvailability(
|
||||||
# make availability size small enough that we can't fill all the slots,
|
# make availability size small enough that we can't fill all the slots,
|
||||||
# thus causing a cancellation
|
# thus causing a cancellation
|
||||||
size=(data.len div 2).u256,
|
totalSize=(data.len div 2).u256,
|
||||||
duration=duration.u256,
|
duration=duration.u256,
|
||||||
minPrice=reward,
|
minPrice=reward,
|
||||||
maxCollateral=collateral)
|
maxCollateral=collateral)
|
||||||
|
|
|
@ -230,7 +230,7 @@ marketplacesuite "Simulate invalid proofs":
|
||||||
# let slotSize = (DefaultBlockSize * 4.NBytes).Natural.u256
|
# let slotSize = (DefaultBlockSize * 4.NBytes).Natural.u256
|
||||||
|
|
||||||
# discard provider0.client.postAvailability(
|
# discard provider0.client.postAvailability(
|
||||||
# size=slotSize, # should match 1 slot only
|
# totalSize=slotSize, # should match 1 slot only
|
||||||
# duration=totalPeriods.periods.u256,
|
# duration=totalPeriods.periods.u256,
|
||||||
# minPrice=300.u256,
|
# minPrice=300.u256,
|
||||||
# maxCollateral=200.u256
|
# maxCollateral=200.u256
|
||||||
|
@ -263,7 +263,7 @@ marketplacesuite "Simulate invalid proofs":
|
||||||
# # now add availability for providers 1 and 2, which should allow them to to
|
# # now add availability for providers 1 and 2, which should allow them to to
|
||||||
# # put the remaining slots in their queues
|
# # put the remaining slots in their queues
|
||||||
# discard provider1.client.postAvailability(
|
# discard provider1.client.postAvailability(
|
||||||
# size=slotSize, # should match 1 slot only
|
# totalSize=slotSize, # should match 1 slot only
|
||||||
# duration=totalPeriods.periods.u256,
|
# duration=totalPeriods.periods.u256,
|
||||||
# minPrice=300.u256,
|
# minPrice=300.u256,
|
||||||
# maxCollateral=200.u256
|
# maxCollateral=200.u256
|
||||||
|
@ -272,7 +272,7 @@ marketplacesuite "Simulate invalid proofs":
|
||||||
# check eventually filledSlotIds.len > 1
|
# check eventually filledSlotIds.len > 1
|
||||||
|
|
||||||
# discard provider2.client.postAvailability(
|
# discard provider2.client.postAvailability(
|
||||||
# size=slotSize, # should match 1 slot only
|
# totalSize=slotSize, # should match 1 slot only
|
||||||
# duration=totalPeriods.periods.u256,
|
# duration=totalPeriods.periods.u256,
|
||||||
# minPrice=300.u256,
|
# minPrice=300.u256,
|
||||||
# maxCollateral=200.u256
|
# maxCollateral=200.u256
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3984431dc0fc829eb668e12e57e90542b041d298
|
Subproject commit c17bfdda2c60cf5fadb043feb22e328b7659c719
|
Loading…
Reference in New Issue