All tests that use the Reservation module updated

- add requestId and slotIndex to Reservation (hopefully these will prove to be useful when we persist Reservations until request are completed, to add back bytes to Availability)
- add querying of all reservations, with accompanying tests
- change from find to findAvailabilities
- move onCleanUp from SalesContext to SalesAgent as it was getting overwritten for each slot processed
- remove sales agent AFTER deleting reservation, as this was causing some SIGSEGVs
- retrofit testsales and testslotqueue to match updated Reservations module API
This commit is contained in:
Eric 2023-08-25 14:58:27 +10:00
parent 0e751fe27d
commit 0d6b3f862b
No known key found for this signature in database
15 changed files with 399 additions and 281 deletions

View File

@ -340,7 +340,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
without contracts =? node.contracts.host: without contracts =? node.contracts.host:
return RestApiResponse.error(Http503, "Sales unavailable") return RestApiResponse.error(Http503, "Sales unavailable")
without unused =? (await contracts.sales.context.reservations.allAvailabilities), err: without unused =? (await contracts.sales.context.reservations.all(Availability)), err:
return RestApiResponse.error(Http500, err.msg) return RestApiResponse.error(Http500, err.msg)
let json = %unused let json = %unused
@ -378,8 +378,8 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
), error: ), error:
return RestApiResponse.error(Http500, error.msg) return RestApiResponse.error(Http500, error.msg)
let json = %availability return RestApiResponse.response(availability.toJson,
return RestApiResponse.response($json, contentType="application/json") contentType="application/json")
router.api( router.api(
MethodGet, MethodGet,

View File

@ -105,19 +105,31 @@ proc remove(sales: Sales, agent: SalesAgent) {.async.} =
proc cleanUp(sales: Sales, proc cleanUp(sales: Sales,
agent: SalesAgent, agent: SalesAgent,
processing: Future[void]) {.async.} = processing: Future[void]) {.async.} =
await sales.remove(agent)
let data = agent.data
trace "cleaning up sales agent",
requestId = data.requestId,
slotIndex = data.slotIndex,
reservationId = data.reservation.?id |? ReservationId.default,
availabilityId = data.reservation.?availabilityId |? AvailabilityId.default
# delete reservation and return reservation bytes back to the availability
if reservation =? agent.data.reservation and if reservation =? agent.data.reservation and
deleteErr =? (await sales.context.reservations.deleteReservation( deleteErr =? (await sales.context.reservations.deleteReservation(
reservation.id, reservation.id,
reservation.availabilityId reservation.availabilityId
)).errorOption: )).errorOption:
error "failure deleting reservation", error "failure deleting reservation",
error = deleteErr.msg,
reservationId = reservation.id, reservationId = reservation.id,
availabilityId = reservation.availabilityId availabilityId = reservation.availabilityId
await sales.remove(agent)
proc filled(sales: Sales, proc filled(sales: Sales,
processing: Future[void]) = processing: Future[void]) =
# signal back to the slot queue to cycle a worker # signal back to the slot queue to cycle a worker
if not processing.isNil and not processing.finished(): if not processing.isNil and not processing.finished():
processing.complete() processing.complete()
@ -133,8 +145,8 @@ proc processSlot(sales: Sales, item: SlotQueueItem, done: Future[void]) =
none StorageRequest none StorageRequest
) )
agent.context.onCleanUp = proc {.async.} = agent.onCleanUp = proc {.async.} =
await sales.remove(agent) await sales.cleanUp(agent, done)
agent.context.onFilled = some proc(request: StorageRequest, slotIndex: UInt256) = agent.context.onFilled = some proc(request: StorageRequest, slotIndex: UInt256) =
sales.filled(done) sales.filled(done)
@ -157,6 +169,8 @@ proc mySlots*(sales: Sales): Future[seq[Slot]] {.async.} =
proc load*(sales: Sales) {.async.} = proc load*(sales: Sales) {.async.} =
let slots = await sales.mySlots() let slots = await sales.mySlots()
# TODO: add slots to slotqueue, as workers need to be dispatched
for slot in slots: for slot in slots:
let agent = newSalesAgent( let agent = newSalesAgent(
sales.context, sales.context,
@ -164,7 +178,7 @@ proc load*(sales: Sales) {.async.} =
slot.slotIndex, slot.slotIndex,
some slot.request) some slot.request)
agent.context.onCleanUp = proc {.async.} = await sales.remove(agent) agent.onCleanUp = proc {.async.} = await sales.remove(agent)
agent.start(SaleUnknown()) agent.start(SaleUnknown())
sales.agents.add agent sales.agents.add agent

View File

@ -49,7 +49,7 @@ type
ReservationId* = distinct array[32, byte] ReservationId* = distinct array[32, byte]
SomeStorableObject = Availability | Reservation SomeStorableObject = Availability | Reservation
SomeStorableId = AvailabilityId | ReservationId SomeStorableId = AvailabilityId | ReservationId
Availability* = object Availability* = ref object
id* {.serialize.}: AvailabilityId id* {.serialize.}: AvailabilityId
size* {.serialize.}: UInt256 size* {.serialize.}: UInt256
duration* {.serialize.}: UInt256 duration* {.serialize.}: UInt256
@ -60,22 +60,24 @@ type
id* {.serialize.}: ReservationId id* {.serialize.}: ReservationId
availabilityId* {.serialize.}: AvailabilityId availabilityId* {.serialize.}: AvailabilityId
size* {.serialize.}: UInt256 size* {.serialize.}: UInt256
slotId* {.serialize.}: SlotId requestId* {.serialize.}: RequestId
slotIndex* {.serialize.}: UInt256
Reservations* = ref object Reservations* = ref object
repo: RepoStore repo: RepoStore
onAvailabilityAdded: ?OnAvailabilityAdded onAvailabilityAdded: ?OnAvailabilityAdded
onMarkUnused: ?OnAvailabilityAdded onMarkUnused: ?OnAvailabilityAdded
GetNext* = proc(): Future[?Availability] {.upraises: [], gcsafe, closure.} GetNext* = proc(): Future[?seq[byte]] {.upraises: [], gcsafe, closure.}
OnAvailabilityAdded* = proc(availability: Availability): Future[void] {.upraises: [], gcsafe.} OnAvailabilityAdded* = proc(availability: Availability): Future[void] {.upraises: [], gcsafe.}
AvailabilityIter* = ref object StorableIter* = ref object
finished*: bool finished*: bool
next*: GetNext next*: GetNext
ReservationsError* = object of CodexError ReservationsError* = object of CodexError
AlreadyExistsError* = object of ReservationsError
ReserveFailedError* = object of ReservationsError ReserveFailedError* = object of ReservationsError
ReleaseFailedError* = object of ReservationsError ReleaseFailedError* = object of ReservationsError
DeleteFailedError* = object of ReservationsError DeleteFailedError* = object of ReservationsError
GetFailedError* = object of ReservationsError GetFailedError* = object of ReservationsError
NotExistsError* = object of ReservationsError
SerializationError* = object of ReservationsError
UpdateFailedError* = object of ReservationsError UpdateFailedError* = object of ReservationsError
BytesOutOfBoundsError* = object of ReservationsError BytesOutOfBoundsError* = object of ReservationsError
@ -103,12 +105,13 @@ proc init*(
_: type Reservation, _: type Reservation,
availabilityId: AvailabilityId, availabilityId: AvailabilityId,
size: UInt256, size: UInt256,
slotId: SlotId requestId: RequestId,
slotIndex: UInt256
): Reservation = ): Reservation =
var id: array[32, byte] var id: array[32, byte]
doAssert randomBytes(id) == 32 doAssert randomBytes(id) == 32
Reservation(id: ReservationId(id), availabilityId: availabilityId, size: size, slotId: slotId) Reservation(id: ReservationId(id), availabilityId: availabilityId, size: size, requestId: requestId, slotIndex: slotIndex)
func toArray(id: SomeStorableId): array[32, byte] = func toArray(id: SomeStorableId): array[32, byte] =
array[32, byte](id) array[32, byte](id)
@ -119,7 +122,8 @@ proc `==`*(x, y: Reservation): bool =
x.id == y.id and x.id == y.id and
x.availabilityId == y.availabilityId and x.availabilityId == y.availabilityId and
x.size == y.size and x.size == y.size and
x.slotId == y.slotId 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 and
x.size == y.size and x.size == y.size and
@ -179,11 +183,11 @@ proc getImpl(
key: Key): Future[?!seq[byte]] {.async.} = key: Key): Future[?!seq[byte]] {.async.} =
if exists =? (await self.exists(key)) and not exists: if exists =? (await self.exists(key)) and not exists:
let err = newException(GetFailedError, "object with key " & $key & " does not exist") let err = newException(NotExistsError, "object with key " & $key & " does not exist")
return failure(err) return failure(err)
without serialized =? await self.repo.metaDs.get(key), err: without serialized =? await self.repo.metaDs.get(key), error:
return failure(err.toErr(GetFailedError)) return failure(error.toErr(GetFailedError))
return success serialized return success serialized
@ -192,11 +196,11 @@ proc get*(
key: Key, key: Key,
T: type SomeStorableObject): Future[?!T] {.async.} = T: type SomeStorableObject): Future[?!T] {.async.} =
without serialized =? await self.getImpl(key), err: without serialized =? await self.getImpl(key), error:
return failure(err) return failure(error)
without obj =? T.fromJson(serialized), err: without obj =? T.fromJson(serialized), error:
return failure(err.toErr(GetFailedError)) return failure(error.toErr(SerializationError))
return success obj return success obj
@ -206,8 +210,8 @@ proc update(
trace "updating " & $(obj.type), id = obj.id, size = obj.size trace "updating " & $(obj.type), id = obj.id, size = obj.size
without key =? obj.key, err: without key =? obj.key, error:
return failure(err) return failure(error)
if err =? (await self.repo.metaDs.put( if err =? (await self.repo.metaDs.put(
key, key,
@ -236,16 +240,24 @@ proc deleteReservation*(
reservationId: ReservationId, reservationId: ReservationId,
availabilityId: AvailabilityId): Future[?!void] {.async.} = availabilityId: AvailabilityId): Future[?!void] {.async.} =
trace "deleting reservation", reservationId, availabilityId logScope:
reservationId
availabilityId
without key =? key(reservationId, availabilityId), err: trace "deleting reservation"
return failure(err) without key =? key(reservationId, availabilityId), error:
without reservation =? (await self.get(key, Reservation)), error:
return failure(error) return failure(error)
without reservation =? (await self.get(key, Reservation)), error:
if error of NotExistsError:
return success()
else:
return failure(error)
if reservation.size > 0.u256: if reservation.size > 0.u256:
# return remaining bytes to availability trace "returning remaining reservation bytes to availability",
size = reservation.size
without availabilityKey =? availabilityId.key, error: without availabilityKey =? availabilityId.key, error:
return failure(error) return failure(error)
@ -269,18 +281,11 @@ proc createAvailability*(
minPrice: UInt256, minPrice: UInt256,
maxCollateral: UInt256): Future[?!Availability] {.async.} = maxCollateral: UInt256): Future[?!Availability] {.async.} =
trace "creating availability", size, duration, minPrice, maxCollateral
let availability = Availability.init( let availability = Availability.init(
size, duration, minPrice, maxCollateral size, duration, minPrice, maxCollateral
) )
without key =? availability.key, err:
return failure(err)
if exists =? (await self.exists(key)) and exists:
let err = newException(AlreadyExistsError,
"Availability already exists")
return failure(err)
let bytes = availability.size.truncate(uint) let bytes = availability.size.truncate(uint)
if reserveErr =? (await self.repo.reserve(bytes)).errorOption: if reserveErr =? (await self.repo.reserve(bytes)).errorOption:
@ -311,18 +316,13 @@ proc createReservation*(
self: Reservations, self: Reservations,
availabilityId: AvailabilityId, availabilityId: AvailabilityId,
slotSize: UInt256, slotSize: UInt256,
slotId: SlotId requestId: RequestId,
slotIndex: UInt256
): Future[?!Reservation] {.async.} = ): Future[?!Reservation] {.async.} =
let reservation = Reservation.init(availabilityId, slotSize, slotId) trace "creating reservation", availabilityId, slotSize, requestId, slotIndex
without key =? reservation.key, error: let reservation = Reservation.init(availabilityId, slotSize, requestId, slotIndex)
return failure(error)
if exists =? (await self.exists(key)) and exists:
let err = newException(AlreadyExistsError,
"Reservation already exists")
return failure(err)
without availabilityKey =? availabilityId.key, error: without availabilityKey =? availabilityId.key, error:
return failure(error) return failure(error)
@ -342,17 +342,8 @@ proc createReservation*(
# the newly created Reservation # the newly created Reservation
availability.size -= slotSize availability.size -= slotSize
# remove availabilities with no reserved bytes remaining # update availability with reduced size
if availability.size == 0.u256: if updateErr =? (await self.update(availability)).errorOption:
without key =? availability.key, error:
return failure(error)
if err =? (await self.delete(key)).errorOption:
# rollbackRelease(err)
return failure(err)
# otherwise, update availability with reduced size
elif updateErr =? (await self.update(availability)).errorOption:
trace "rolling back reservation creation" trace "rolling back reservation creation"
@ -383,11 +374,11 @@ proc release*(
trace "releasing bytes and updating reservation" trace "releasing bytes and updating reservation"
without key =? key(reservationId, availabilityId), err: without key =? key(reservationId, availabilityId), error:
return failure(err) return failure(error)
without var reservation =? (await self.get(key, Reservation)), err: without var reservation =? (await self.get(key, Reservation)), error:
return failure(err) return failure(error)
if reservation.size < bytes.u256: if reservation.size < bytes.u256:
let error = newException(BytesOutOfBoundsError, let error = newException(BytesOutOfBoundsError,
@ -400,8 +391,6 @@ proc release*(
reservation.size -= bytes.u256 reservation.size -= bytes.u256
# TODO: remove used up reservation after sales process is complete
# persist partially used Reservation with updated size # persist partially used Reservation with updated size
if err =? (await self.update(reservation)).errorOption: if err =? (await self.update(reservation)).errorOption:
@ -414,58 +403,75 @@ proc release*(
return success() return success()
iterator items*(self: AvailabilityIter): Future[?Availability] = iterator items(self: StorableIter): Future[?seq[byte]] =
while not self.finished: while not self.finished:
yield self.next() yield self.next()
proc availabilities*( proc storables(
self: Reservations): Future[?!AvailabilityIter] {.async.} = self: Reservations,
T: type SomeStorableObject
): Future[?!StorableIter] {.async.} =
var iter = AvailabilityIter() var iter = StorableIter()
let query = Query.init(ReservationsKey) let query = Query.init(ReservationsKey)
when T is Availability:
# should indicate key length of 4, but let the .key logic determine it
without defaultKey =? AvailabilityId.default.key, error:
return failure(error)
else:
# should indicate key length of 5, but let the .key logic determine it
without defaultKey =? key(ReservationId.default, AvailabilityId.default), error:
return failure(error)
without results =? await self.repo.metaDs.query(query), err: without results =? await self.repo.metaDs.query(query), error:
return failure(err) return failure(error)
proc next(): Future[?Availability] {.async.} = proc next(): Future[?seq[byte]] {.async.} =
await idleAsync() await idleAsync()
iter.finished = results.finished iter.finished = results.finished
if not results.finished and if not results.finished and
r =? (await results.next()) and res =? (await results.next()) and
serialized =? r.data and res.data.len > 0 and
serialized.len > 0: key =? res.key and
key.namespaces.len == defaultKey.namespaces.len:
return Availability.fromJson(serialized).option return some res.data
return none Availability return none seq[byte]
iter.next = next iter.next = next
return success iter return success iter
proc allAvailabilities*(r: Reservations): Future[?!seq[Availability]] {.async.} = proc all*(
var ret: seq[Availability] = @[] self: Reservations,
T: type SomeStorableObject
): Future[?!seq[T]] {.async.} =
without availabilities =? (await r.availabilities), err: var ret: seq[T] = @[]
return failure(err)
for a in availabilities: without storables =? (await self.storables(T)), error:
if availability =? (await a): return failure(error)
ret.add availability
# NOTICE: there is a swallowed deserialization error
for storable in storables.items:
if bytes =? (await storable) and
obj =? T.fromJson(bytes):
ret.add obj
return success(ret) return success(ret)
proc find*( proc findAvailability*(
self: Reservations, self: Reservations,
size, duration, minPrice, collateral: UInt256 size, duration, minPrice, collateral: UInt256
): Future[?Availability] {.async.} = ): Future[?Availability] {.async.} =
without storables =? (await self.storables(Availability)), error:
without availabilities =? (await self.availabilities), err: error "failed to get all storables", error = error.msg
error "failed to get all availabilities", error = err.msg
return none Availability return none Availability
for a in availabilities: for item in storables.items:
if availability =? (await a): if bytes =? (await item) and
availability =? Availability.fromJson(bytes):
if size <= availability.size and if size <= availability.size and
duration <= availability.duration and duration <= availability.duration and

View File

@ -21,6 +21,10 @@ type
context*: SalesContext context*: SalesContext
data*: SalesData data*: SalesData
subscribed: bool subscribed: bool
onCleanUp*: OnCleanUp
OnCleanUp* = proc: Future[void] {.gcsafe, upraises: [].}
SalesAgentError = object of CodexError SalesAgentError = object of CodexError
AllSlotsFilledError* = object of SalesAgentError AllSlotsFilledError* = object of SalesAgentError

View File

@ -16,7 +16,7 @@ type
SaleDownloading* = ref object of ErrorHandlingState SaleDownloading* = ref object of ErrorHandlingState
logScope: logScope:
topics = "marketplace sales downloading" topics = "marketplace sales downloading"
method `$`*(state: SaleDownloading): string = "SaleDownloading" method `$`*(state: SaleDownloading): string = "SaleDownloading"
@ -48,6 +48,12 @@ method run*(state: SaleDownloading, machine: Machine): Future[?State] {.async.}
without reservation =? data.reservation: without reservation =? data.reservation:
raiseAssert("no reservation") raiseAssert("no reservation")
logScope:
requestId = request.id
slotIndex
reservationId = reservation.id
availabilityId = reservation.availabilityId
proc onBatch(blocks: seq[bt.Block]) {.async.} = proc onBatch(blocks: seq[bt.Block]) {.async.} =
# release batches of blocks as they are written to disk and # release batches of blocks as they are written to disk and
# update availability size # update availability size
@ -68,9 +74,7 @@ method run*(state: SaleDownloading, machine: Machine): Future[?State] {.async.}
if err =? (await onStore(request, if err =? (await onStore(request,
slotIndex, slotIndex,
onBatch)).errorOption: onBatch)).errorOption:
return some State(SaleErrored(error: err)) return some State(SaleErrored(error: err))
trace "Download complete" trace "Download complete"
return some State(SaleInitialProving()) return some State(SaleInitialProving())

View File

@ -28,6 +28,6 @@ method run*(state: SaleErrored, machine: Machine): Future[?State] {.async.} =
slotIndex =? data.slotIndex: slotIndex =? data.slotIndex:
onClear(request, slotIndex) onClear(request, slotIndex)
if onCleanUp =? context.onCleanUp: if onCleanUp =? agent.onCleanUp:
await onCleanUp() await onCleanUp()

View File

@ -33,5 +33,5 @@ method run*(state: SaleFinished, machine: Machine): Future[?State] {.async.} =
info "Slot finished and paid out", requestId = $data.requestId, slotIndex info "Slot finished and paid out", requestId = $data.requestId, slotIndex
if onCleanUp =? context.onCleanUp: if onCleanUp =? agent.onCleanUp:
await onCleanUp() await onCleanUp()

View File

@ -16,5 +16,5 @@ method run*(state: SaleIgnored, machine: Machine): Future[?State] {.async.} =
let agent = SalesAgent(machine) let agent = SalesAgent(machine)
let context = agent.context let context = agent.context
if onCleanUp =? context.onCleanUp: if onCleanUp =? agent.onCleanUp:
await onCleanUp() await onCleanUp()

View File

@ -51,25 +51,31 @@ method run*(state: SalePreparing, machine: Machine): Future[?State] {.async.} =
# TODO: Once implemented, check to ensure the host is allowed to fill the slot, # TODO: Once implemented, check to ensure the host is allowed to fill the slot,
# due to the [sliding window mechanism](https://github.com/codex-storage/codex-research/blob/master/design/marketplace.md#dispersal) # due to the [sliding window mechanism](https://github.com/codex-storage/codex-research/blob/master/design/marketplace.md#dispersal)
logScope:
slotIndex = data.slotIndex
slotSize = request.ask.slotSize
duration = request.ask.duration
pricePerSlot = request.ask.pricePerSlot
# availability was checked for this slot when it entered the queue, however # availability was checked for this slot when it entered the queue, however
# check to the ensure that there is still availability as they may have # check to the ensure that there is still availability as they may have
# changed since being added (other slots may have been processed in that time) # changed since being added (other slots may have been processed in that time)
without availability =? await reservations.find( without availability =? await reservations.findAvailability(
request.ask.slotSize, request.ask.slotSize,
request.ask.duration, request.ask.duration,
request.ask.pricePerSlot, request.ask.pricePerSlot,
request.ask.collateral): request.ask.collateral):
info "no availability found for request, ignoring", info "no availability found for request, ignoring"
slotSize = request.ask.slotSize,
duration = request.ask.duration,
pricePerSlot = request.ask.pricePerSlot
return some State(SaleIgnored()) return some State(SaleIgnored())
info "availability found for request, creating reservation"
without reservation =? await reservations.createReservation( without reservation =? await reservations.createReservation(
availability.id, availability.id,
request.ask.slotSize, request.ask.slotSize,
slotId request.id,
data.slotIndex
), error: ), error:
return some State(SaleErrored(error: error)) return some State(SaleErrored(error: error))

View File

@ -103,6 +103,8 @@ proc stop*(machine: Machine) {.async.} =
if not machine.started: if not machine.started:
return return
trace "stopping state machine"
machine.started = false machine.started = false
await machine.trackedFutures.cancelTracked() await machine.trackedFutures.cancelTracked()

View File

@ -16,14 +16,12 @@ proc len*(self: TrackedFutures): int = self.futures.len
proc removeFuture(self: TrackedFutures, future: FutureBase) = proc removeFuture(self: TrackedFutures, future: FutureBase) =
if not self.cancelling and not future.isNil: if not self.cancelling and not future.isNil:
trace "removing tracked future"
self.futures.del(future.id) self.futures.del(future.id)
proc track*[T](self: TrackedFutures, fut: Future[T]): Future[T] = proc track*[T](self: TrackedFutures, fut: Future[T]): Future[T] =
if self.cancelling: if self.cancelling:
return fut return fut
trace "tracking future", id = fut.id
self.futures[fut.id] = FutureBase(fut) self.futures[fut.id] = FutureBase(fut)
fut fut
@ -42,6 +40,8 @@ proc track*[T, U](future: Future[T], self: U): Future[T] =
proc cancelTracked*(self: TrackedFutures) {.async.} = proc cancelTracked*(self: TrackedFutures) {.async.} =
self.cancelling = true self.cancelling = true
trace "cancelling tracked futures"
for future in self.futures.values: for future in self.futures.values:
if not future.isNil and not future.finished: if not future.isNil and not future.finished:
trace "cancelling tracked future", id = future.id trace "cancelling tracked future", id = future.id

View File

@ -60,3 +60,11 @@ proc example*(_: type Availability): Availability =
minPrice = uint64.example.u256, minPrice = uint64.example.u256,
maxCollateral = uint16.example.u256 maxCollateral = uint16.example.u256
) )
proc example*(_: type Reservation): Reservation =
Reservation.init(
availabilityId = AvailabilityId(array[32, byte].example),
size = uint16.example.u256,
slotId = SlotId.example
)

View File

@ -1,3 +1,5 @@
import std/random
import pkg/questionable import pkg/questionable
import pkg/questionable/results import pkg/questionable/results
import pkg/chronos import pkg/chronos
@ -16,33 +18,43 @@ asyncchecksuite "Reservations module":
repo: RepoStore repo: RepoStore
repoDs: Datastore repoDs: Datastore
metaDs: SQLiteDatastore metaDs: SQLiteDatastore
availability: Availability
reservations: Reservations reservations: Reservations
setup: setup:
randomize()
repoDs = SQLiteDatastore.new(Memory).tryGet() repoDs = SQLiteDatastore.new(Memory).tryGet()
metaDs = SQLiteDatastore.new(Memory).tryGet() metaDs = SQLiteDatastore.new(Memory).tryGet()
repo = RepoStore.new(repoDs, metaDs) repo = RepoStore.new(repoDs, metaDs)
reservations = Reservations.new(repo) reservations = Reservations.new(repo)
availability = Availability.example
proc createAvailability(): Availability = proc createAvailability(): Availability =
let example = Availability.example let example = Availability.example
let size = rand(100000..200000)
let availability = waitFor reservations.createAvailability( let availability = waitFor reservations.createAvailability(
example.size, size.u256,
example.duration, example.duration,
example.minPrice, example.minPrice,
example.maxCollateral example.maxCollateral
) )
return availability.get return availability.get
proc createReservation(availability: Availability): Reservation =
let size = rand(1..<availability.size.truncate(int))
let reservation = waitFor reservations.createReservation(
availability.id,
size.u256,
RequestId.example,
UInt256.example
)
return reservation.get
test "availability can be serialised and deserialised": test "availability can be serialised and deserialised":
let availability = Availability.example let availability = Availability.example
let serialised = %availability let serialised = %availability
check Availability.fromJson(serialised).get == availability check Availability.fromJson(serialised).get == availability
test "has no availability initially": test "has no availability initially":
check (await reservations.allAvailabilities()).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)
@ -50,12 +62,18 @@ asyncchecksuite "Reservations module":
check availability1.id != availability2.id check availability1.id != availability2.id
test "can reserve available storage": test "can reserve available storage":
let availability = createAvailability()
check availability.id != AvailabilityId.default
test "creating availability reserves bytes in repo":
let orig = repo.available
let availability = createAvailability()
check repo.available == (orig.u256 - availability.size).truncate(uint)
test "can get all availabilities":
let availability1 = createAvailability() let availability1 = createAvailability()
let availability2 = createAvailability() let availability2 = createAvailability()
check availability1.id != AvailabilityId.default let availabilities = !(await reservations.all(Availability))
check availability2.id != AvailabilityId.default
let availabilities = (await reservations.allAvailabilities()).get
check: check:
# perform unordered checks # perform unordered checks
availabilities.len == 2 availabilities.len == 2
@ -70,144 +88,189 @@ asyncchecksuite "Reservations module":
check exists check exists
test "reserved availability can be partially released": test "reservation can be created":
let size = availability.size.truncate(uint) let availability = createAvailability()
check isOk await reservations.create(availability) let reservation = createReservation(availability)
check isOk await reservations.release(availability.id, size - 1) check reservation.id != ReservationId.default
without a =? await reservations.get(availability.id): test "can get all reservations":
fail() let availability1 = createAvailability()
let availability2 = createAvailability()
let reservation1 = createReservation(availability1)
let reservation2 = createReservation(availability2)
let reservations = !(await reservations.all(Reservation))
check:
# perform unordered checks
reservations.len == 2
reservations.contains(reservation1)
reservations.contains(reservation2)
check a.size == 1 test "cannot create reservation with non-existant availability":
let availability = Availability.example
let created = await reservations.createReservation(
availability.id,
UInt256.example,
RequestId.example,
UInt256.example
)
check created.isErr
check created.error of NotExistsError
test "availability is deleted after being fully released": test "cannot create reservation larger than availability size":
let size = availability.size.truncate(uint) let availability = createAvailability()
check isOk await reservations.create(availability) let created = await reservations.createReservation(
check isOk await reservations.release(availability.id, size) availability.id,
availability.size + 1,
RequestId.example,
UInt256.example
)
check created.isErr
check created.error of BytesOutOfBoundsError
without exists =? await reservations.exists(availability.id): test "creating reservation reduces availability size":
fail() let availability = createAvailability()
let orig = availability.size
let reservation = createReservation(availability)
let key = availability.id.key.get
let updated = (await reservations.get(key, Availability)).get
check updated.size == orig - reservation.size
check not exists test "can check if reservation exists":
let availability = createAvailability()
let reservation = createReservation(availability)
let key = reservation.key.get
check await reservations.exists(key)
test "non-existant availability cannot be released": test "non-existant availability does not exist":
let size = availability.size.truncate(uint) let key = AvailabilityId.example.key.get
let r = await reservations.release(availability.id, size - 1) check not (await reservations.exists(key))
check r.error of AvailabilityGetFailedError
check r.error.msg == "Availability does not exist"
test "added availability is not used initially": test "non-existant reservation does not exist":
check isOk await reservations.create(availability) let key = key(ReservationId.example, AvailabilityId.example).get
check not (await reservations.exists(key))
without available =? await reservations.get(availability.id): test "can check if availability exists":
fail() let availability = createAvailability()
let key = availability.key.get
check await reservations.exists(key)
check not available.used test "can delete reservation":
let availability = createAvailability()
let reservation = createReservation(availability)
check isOk (await reservations.deleteReservation(
reservation.id, reservation.availabilityId)
)
let key = reservation.key.get
check not (await reservations.exists(key))
test "availability can be marked used": test "deleting reservation returns bytes back to availability":
check isOk await reservations.create(availability) let availability = createAvailability()
let orig = availability.size
let reservation = createReservation(availability)
discard await reservations.deleteReservation(
reservation.id, reservation.availabilityId
)
let key = availability.key.get
let updated = !(await reservations.get(key, Availability))
check updated.size == orig
check isOk await reservations.markUsed(availability.id) test "reservation can be partially released":
let availability = createAvailability()
let reservation = createReservation(availability)
check isOk await reservations.release(
reservation.id,
reservation.availabilityId,
1
)
let key = reservation.key.get
let updated = !(await reservations.get(key, Reservation))
check updated.size == reservation.size - 1
without available =? await reservations.get(availability.id): test "cannot release more bytes than size of reservation":
fail() let availability = createAvailability()
let reservation = createReservation(availability)
let updated = await reservations.release(
reservation.id,
reservation.availabilityId,
(reservation.size + 1).truncate(uint)
)
check updated.isErr
check updated.error of BytesOutOfBoundsError
check available.used test "cannot release bytes from non-existant reservation":
let availability = createAvailability()
let reservation = createReservation(availability)
let updated = await reservations.release(
ReservationId.example,
availability.id,
1
)
check updated.isErr
check updated.error of NotExistsError
test "availability can be marked unused": test "onAvailabilityAdded called when availability is reserved":
check isOk await reservations.create(availability)
check isOk await reservations.markUsed(availability.id)
check isOk await reservations.markUnused(availability.id)
without available =? await reservations.get(availability.id):
fail()
check not available.used
test "onMarkedUnused called when availability marked unused":
var markedUnused: Availability
reservations.onMarkUnused = proc(a: Availability) {.async.} =
markedUnused = a
check isOk await reservations.create(availability)
check isOk await reservations.markUnused(availability.id)
check markedUnused == availability
test "onAdded called when availability is reserved":
var added: Availability var added: Availability
reservations.onAdded = proc(a: Availability) {.async.} = reservations.onAvailabilityAdded = proc(a: Availability) {.async.} =
added = a added = a
check isOk await reservations.create(availability) let availability = createAvailability()
check added == availability check added == availability
test "used availability can be found": test "availabilities can be found":
check isOk await reservations.create(availability) let availability = createAvailability()
check isOk await reservations.markUsed(availability.id) let found = await reservations.findAvailability(
availability.size,
availability.duration,
availability.minPrice,
availability.maxCollateral)
without available =? await reservations.find(availability.size, check found.isSome
availability.duration, availability.minPrice, availability.maxCollateral, used = true): check found.get == availability
fail() test "non-matching availabilities are not found":
let availability = createAvailability()
test "unused availability can be found": let found = await reservations.findAvailability(
check isOk await reservations.create(availability) availability.size + 1,
availability.duration,
availability.minPrice,
availability.maxCollateral)
without available =? await reservations.find(availability.size, check found.isNone
availability.duration, availability.minPrice, availability.maxCollateral, used = false):
fail()
test "non-existant availability cannot be found": test "non-existant availability cannot be found":
check isNone (await reservations.find(availability.size, let availability = Availability.example
availability.duration, availability.minPrice, availability.maxCollateral, used = false)) let found = (await reservations.findAvailability(
availability.size,
availability.duration,
availability.minPrice,
availability.maxCollateral
))
check found.isNone
test "non-existant availability cannot be retrieved": test "non-existant availability cannot be retrieved":
let r = await reservations.get(availability.id) let key = AvailabilityId.example.key.get
check r.error of AvailabilityGetFailedError let got = await reservations.get(key, Availability)
check r.error.msg == "Availability does not exist" check got.error of NotExistsError
test "same availability cannot be reserved twice":
check isOk await reservations.create(availability)
let r = await reservations.create(availability)
check r.error of AvailabilityAlreadyExistsError
test "can get available bytes in repo": test "can get available bytes in repo":
check reservations.available == DefaultQuotaBytes check reservations.available == DefaultQuotaBytes
test "reserving availability reduces available bytes":
check isOk await reservations.create(availability)
check reservations.available ==
DefaultQuotaBytes - availability.size.truncate(uint)
test "reports quota available to be reserved": test "reports quota available to be reserved":
check reservations.hasAvailable(availability.size.truncate(uint)) check reservations.hasAvailable(DefaultQuotaBytes - 1)
test "reports quota not available to be reserved": test "reports quota not available to be reserved":
repo = RepoStore.new(repoDs, metaDs, check not reservations.hasAvailable(DefaultQuotaBytes + 1)
quotaMaxBytes = availability.size.truncate(uint) - 1)
reservations = Reservations.new(repo)
check not reservations.hasAvailable(availability.size.truncate(uint))
test "fails to reserve availability with size that is larger than available quota": test "fails to create availability with size that is larger than available quota":
repo = RepoStore.new(repoDs, metaDs, let created = await reservations.createAvailability(
quotaMaxBytes = availability.size.truncate(uint) - 1) (DefaultQuotaBytes + 1).u256,
reservations = Reservations.new(repo) UInt256.example,
let r = await reservations.create(availability) UInt256.example,
check r.error of AvailabilityReserveFailedError UInt256.example
check r.error.parent of QuotaNotEnoughError )
check exists =? (await reservations.exists(availability.id)) and not exists check created.isErr
check created.error of ReserveFailedError
test "fails to release availability size that is larger than available quota": check created.error.parent of QuotaNotEnoughError
let size = availability.size.truncate(uint)
repo = RepoStore.new(repoDs, metaDs,
quotaMaxBytes = size)
reservations = Reservations.new(repo)
discard await reservations.create(availability)
let r = await reservations.release(availability.id, size + 1)
check r.error of AvailabilityReleaseFailedError
check r.error.parent.msg == "Cannot release this many bytes"

View File

@ -15,11 +15,11 @@ import pkg/codex/sales/slotqueue
import pkg/codex/stores/repostore import pkg/codex/stores/repostore
import pkg/codex/blocktype as bt import pkg/codex/blocktype as bt
import pkg/codex/node import pkg/codex/node
import ../helpers
import ../helpers/mockmarket import ../helpers/mockmarket
import ../helpers/mockclock import ../helpers/mockclock
import ../helpers/always import ../helpers/always
import ../examples import ../examples
import ./helpers
asyncchecksuite "Sales - start": asyncchecksuite "Sales - start":
let proof = exampleProof() let proof = exampleProof()
@ -122,11 +122,11 @@ asyncchecksuite "Sales":
var itemsProcessed: seq[SlotQueueItem] var itemsProcessed: seq[SlotQueueItem]
setup: setup:
availability = Availability.init( availability = Availability(
size=100.u256, size: 100.u256,
duration=60.u256, duration: 60.u256,
minPrice=600.u256, minPrice: 600.u256,
maxCollateral=400.u256 maxCollateral: 400.u256
) )
request = StorageRequest( request = StorageRequest(
ask: StorageAsk( ask: StorageAsk(
@ -169,8 +169,18 @@ asyncchecksuite "Sales":
await sales.stop() await sales.stop()
await repo.stop() await repo.stop()
proc getAvailability: ?!Availability = proc getAvailability: Availability =
waitFor reservations.get(availability.id) let key = availability.id.key.get
(waitFor reservations.get(key, Availability)).get
proc createAvailability() =
let a = waitFor reservations.createAvailability(
availability.size,
availability.duration,
availability.minPrice,
availability.maxCollateral
)
availability = a.get # update id
proc notProcessed(itemsProcessed: seq[SlotQueueItem], proc notProcessed(itemsProcessed: seq[SlotQueueItem],
request: StorageRequest): bool = request: StorageRequest): bool =
@ -188,7 +198,7 @@ asyncchecksuite "Sales":
var request1 = StorageRequest.example var request1 = StorageRequest.example
request1.ask.collateral = request.ask.collateral + 1 request1.ask.collateral = request.ask.collateral + 1
discard await reservations.reserve(availability) createAvailability()
# saturate queue # saturate queue
while queue.len < queue.size - 1: while queue.len < queue.size - 1:
await market.requestStorage(StorageRequest.example) await market.requestStorage(StorageRequest.example)
@ -197,11 +207,19 @@ asyncchecksuite "Sales":
await sleepAsync(5.millis) # wait for request slots to be added to queue await sleepAsync(5.millis) # wait for request slots to be added to queue
return request1 return request1
proc wasIgnored(): bool =
let run = proc(): Future[bool] {.async.} =
always (
getAvailability().size == availability.size and
(waitFor reservations.all(Reservation)).get.len == 0
)
waitFor run()
test "processes all request's slots once StorageRequested emitted": test "processes all request's slots once StorageRequested emitted":
queue.onProcessSlot = proc(item: SlotQueueItem, done: Future[void]) {.async.} = queue.onProcessSlot = proc(item: SlotQueueItem, done: Future[void]) {.async.} =
itemsProcessed.add item itemsProcessed.add item
done.complete() done.complete()
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
let items = SlotQueueItem.init(request) let items = SlotQueueItem.init(request)
check eventually items.allIt(itemsProcessed.contains(it)) check eventually items.allIt(itemsProcessed.contains(it))
@ -232,7 +250,7 @@ asyncchecksuite "Sales":
itemsProcessed.add item itemsProcessed.add item
done.complete() done.complete()
check isOk await reservations.create(availability) createAvailability()
market.requested.add request # "contract" must be able to return request market.requested.add request # "contract" must be able to return request
market.emitSlotFreed(request.id, 2.u256) market.emitSlotFreed(request.id, 2.u256)
@ -248,75 +266,69 @@ asyncchecksuite "Sales":
await market.requestStorage(request) await market.requestStorage(request)
# now add matching availability # now add matching availability
check isOk await reservations.reserve(availability) createAvailability()
check eventually itemsProcessed.len == request.ask.slots.int check eventually itemsProcessed.len == request.ask.slots.int
test "makes storage unavailable when downloading a matched request": test "availability size is reduced by request slot size when fully downloaded":
var used = false
sales.onStore = proc(request: StorageRequest, sales.onStore = proc(request: StorageRequest,
slot: UInt256, slot: UInt256,
onBatch: BatchProc): Future[?!void] {.async.} = onBatch: BatchProc): Future[?!void] {.async.} =
without avail =? await reservations.get(availability.id): let blk = bt.Block.new( @[1.byte] ).get
fail() onBatch( blk.repeat(request.ask.slotSize.truncate(int)) )
used = avail.used
return success() return success()
check isOk await reservations.create(availability) createAvailability()
let origSize = availability.size
await market.requestStorage(request) await market.requestStorage(request)
check eventually used check eventuallyCheck getAvailability().size == availability.size - request.ask.slotSize
test "reduces remaining availability size after download": test "non-downloaded bytes are returned to availability once finished":
let blk = bt.Block.example
request.ask.slotSize = blk.data.len.u256
availability.size = request.ask.slotSize + 1
sales.onStore = proc(request: StorageRequest, sales.onStore = proc(request: StorageRequest,
slot: UInt256, slot: UInt256,
onBatch: BatchProc): Future[?!void] {.async.} = onBatch: BatchProc): Future[?!void] {.async.} =
await onBatch(@[blk]) let blk = bt.Block.new( @[1.byte] ).get
onBatch(@[ blk ])
return success() return success()
check isOk await reservations.create(availability)
createAvailability()
let origSize = availability.size
await market.requestStorage(request) await market.requestStorage(request)
check eventually getAvailability().?size == success 1.u256 await sleepAsync(1.millis)
check eventuallyCheck getAvailability().size == 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
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check getAvailability().?size == success availability.size 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.size = request.ask.slotSize - 1
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check getAvailability().?size == success availability.size check wasIgnored()
test "ignores request when reward is too low": test "ignores request when reward is too low":
availability.minPrice = request.ask.pricePerSlot + 1 availability.minPrice = request.ask.pricePerSlot + 1
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check getAvailability().?size == success availability.size check wasIgnored()
test "availability remains unused when request is ignored":
availability.minPrice = request.ask.pricePerSlot + 1
check isOk await reservations.create(availability)
await market.requestStorage(request)
check getAvailability().?used == success false
test "ignores request when asked collateral is too high": test "ignores request when asked collateral is too high":
var tooBigCollateral = request var tooBigCollateral = request
tooBigCollateral.ask.collateral = availability.maxCollateral + 1 tooBigCollateral.ask.collateral = availability.maxCollateral + 1
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(tooBigCollateral) await market.requestStorage(tooBigCollateral)
check getAvailability().?size == success availability.size check wasIgnored()
test "ignores request when slot state is not free": test "ignores request when slot state is not free":
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
market.slotState[request.slotId(0.u256)] = SlotState.Filled market.slotState[request.slotId(0.u256)] = SlotState.Filled
market.slotState[request.slotId(1.u256)] = SlotState.Filled market.slotState[request.slotId(1.u256)] = SlotState.Filled
market.slotState[request.slotId(2.u256)] = SlotState.Filled market.slotState[request.slotId(2.u256)] = SlotState.Filled
market.slotState[request.slotId(3.u256)] = SlotState.Filled market.slotState[request.slotId(3.u256)] = SlotState.Filled
check getAvailability().?size == success availability.size check wasIgnored()
test "retrieves and stores data locally": test "retrieves and stores data locally":
var storingRequest: StorageRequest var storingRequest: StorageRequest
@ -327,7 +339,7 @@ asyncchecksuite "Sales":
storingRequest = request storingRequest = request
storingSlot = slot storingSlot = slot
return success() return success()
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually storingRequest == request check eventually storingRequest == request
check storingSlot < request.ask.slots.u256 check storingSlot < request.ask.slots.u256
@ -342,7 +354,7 @@ asyncchecksuite "Sales":
sales.onClear = proc(request: StorageRequest, sales.onClear = proc(request: StorageRequest,
idx: UInt256) = idx: UInt256) =
saleFailed = true saleFailed = true
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually saleFailed check eventually saleFailed
@ -352,10 +364,9 @@ asyncchecksuite "Sales":
slot: UInt256, slot: UInt256,
onBatch: BatchProc): Future[?!void] {.async.} = onBatch: BatchProc): Future[?!void] {.async.} =
return failure(error) return failure(error)
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually getAvailability().?used == success false check getAvailability().size == availability.size
check getAvailability().?size == success availability.size
test "generates proof of storage": test "generates proof of storage":
var provingRequest: StorageRequest var provingRequest: StorageRequest
@ -363,13 +374,13 @@ asyncchecksuite "Sales":
sales.onProve = proc(slot: Slot): Future[seq[byte]] {.async.} = sales.onProve = proc(slot: Slot): Future[seq[byte]] {.async.} =
provingRequest = slot.request provingRequest = slot.request
provingSlot = slot.slotIndex provingSlot = slot.slotIndex
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually provingRequest == request check eventually provingRequest == request
check provingSlot < request.ask.slots.u256 check provingSlot < request.ask.slots.u256
test "fills a slot": test "fills a slot":
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually market.filled.len > 0 check eventually market.filled.len > 0
check market.filled[0].requestId == request.id check market.filled[0].requestId == request.id
@ -378,19 +389,15 @@ asyncchecksuite "Sales":
check market.filled[0].host == await market.getSigner() check market.filled[0].host == await market.getSigner()
test "calls onFilled when slot is filled": test "calls onFilled when slot is filled":
var soldAvailability: Availability var soldRequest = StorageRequest.default
var soldRequest: StorageRequest var soldSlotIndex = UInt256.high
var soldSlotIndex: UInt256
sales.onSale = proc(request: StorageRequest, sales.onSale = proc(request: StorageRequest,
slotIndex: UInt256) = slotIndex: UInt256) =
if a =? availability:
soldAvailability = a
soldRequest = request soldRequest = request
soldSlotIndex = slotIndex soldSlotIndex = slotIndex
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually soldAvailability == availability check eventuallyCheck soldRequest == request
check soldRequest == request
check soldSlotIndex < request.ask.slots.u256 check soldSlotIndex < request.ask.slots.u256
test "calls onClear when storage becomes available again": test "calls onClear when storage becomes available again":
@ -404,7 +411,7 @@ asyncchecksuite "Sales":
slotIndex: UInt256) = slotIndex: UInt256) =
clearedRequest = request clearedRequest = request
clearedSlotIndex = slotIndex clearedSlotIndex = slotIndex
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually clearedRequest == request check eventually clearedRequest == request
check clearedSlotIndex < request.ask.slots.u256 check clearedSlotIndex < request.ask.slots.u256
@ -416,22 +423,24 @@ asyncchecksuite "Sales":
onBatch: BatchProc): Future[?!void] {.async.} = onBatch: BatchProc): Future[?!void] {.async.} =
await sleepAsync(chronos.hours(1)) await sleepAsync(chronos.hours(1))
return success() return success()
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
for slotIndex in 0..<request.ask.slots: for slotIndex in 0..<request.ask.slots:
market.fillSlot(request.id, slotIndex.u256, proof, otherHost) market.fillSlot(request.id, slotIndex.u256, proof, otherHost)
check eventually (await reservations.allAvailabilities) == @[availability] check eventuallyCheck (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
sales.onStore = proc(request: StorageRequest, sales.onStore = proc(request: StorageRequest,
slot: UInt256, slot: UInt256,
onBatch: BatchProc): Future[?!void] {.async.} = onBatch: BatchProc): Future[?!void] {.async.} =
await sleepAsync(chronos.hours(1)) await sleepAsync(chronos.hours(1))
return success() return success()
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
clock.set(request.expiry.truncate(int64)) clock.set(request.expiry.truncate(int64))
check eventuallyCheck (await reservations.allAvailabilities) == @[availability] check eventuallyCheck (await reservations.all(Availability)).get == @[availability]
check getAvailability().size == origSize
test "adds proving for slot when slot is filled": test "adds proving for slot when slot is filled":
var soldSlotIndex: UInt256 var soldSlotIndex: UInt256
@ -439,7 +448,7 @@ asyncchecksuite "Sales":
slotIndex: UInt256) = slotIndex: UInt256) =
soldSlotIndex = slotIndex soldSlotIndex = slotIndex
check proving.slots.len == 0 check proving.slots.len == 0
check isOk await reservations.create(availability) createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventuallyCheck proving.slots.len == 1 check eventuallyCheck proving.slots.len == 1
check proving.slots.contains(Slot(request: request, slotIndex: soldSlotIndex)) check proving.slots.contains(Slot(request: request, slotIndex: soldSlotIndex))

View File

@ -1,6 +1,7 @@
import std/random import std/random
import std/sequtils import std/sequtils
import std/times import std/times
import std/typetraits
import pkg/codex/contracts/requests import pkg/codex/contracts/requests
import pkg/codex/sales/slotqueue import pkg/codex/sales/slotqueue
import pkg/stint import pkg/stint
@ -19,8 +20,9 @@ proc example*[T](_: type seq[T]): seq[T] =
proc example*(_: type UInt256): UInt256 = proc example*(_: type UInt256): UInt256 =
UInt256.fromBytes(array[32, byte].example) UInt256.fromBytes(array[32, byte].example)
proc example*[T: RequestId | SlotId | Nonce](_: type T): T = proc example*[T: distinct](_: type T): T =
T(array[32, byte].example) type baseType = T.distinctBase
T(baseType.example)
proc example*(_: type StorageRequest): StorageRequest = proc example*(_: type StorageRequest): StorageRequest =
StorageRequest( StorageRequest(