diff --git a/codex/rest/api.nim b/codex/rest/api.nim index 134aa8d2..2aac641c 100644 --- a/codex/rest/api.nim +++ b/codex/rest/api.nim @@ -439,8 +439,11 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) = without availability =? ( await reservations.createAvailability( - restAv.totalSize, restAv.duration, restAv.minPricePerBytePerSecond, + restAv.totalSize, + restAv.duration, + restAv.minPricePerBytePerSecond, restAv.totalCollateral, + enabled = restAv.enabled |? true, ) ), error: return RestApiResponse.error(Http500, error.msg, headers = headers) @@ -526,6 +529,8 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) = if totalCollateral =? restAv.totalCollateral: availability.totalCollateral = totalCollateral + availability.enabled = restAv.enabled |? true + if err =? (await reservations.update(availability)).errorOption: return RestApiResponse.error(Http500, err.msg) diff --git a/codex/rest/json.nim b/codex/rest/json.nim index 9bc7664e..14124cd0 100644 --- a/codex/rest/json.nim +++ b/codex/rest/json.nim @@ -33,6 +33,7 @@ type minPricePerBytePerSecond* {.serialize.}: UInt256 totalCollateral* {.serialize.}: UInt256 freeSize* {.serialize.}: ?UInt256 + enabled* {.serialize.}: ?bool RestSalesAgent* = object state* {.serialize.}: string diff --git a/codex/sales.nim b/codex/sales.nim index 4bf2d13c..24dadc12 100644 --- a/codex/sales.nim +++ b/codex/sales.nim @@ -271,7 +271,9 @@ proc load*(sales: Sales) {.async.} = agent.start(SaleUnknown()) sales.agents.add agent -proc onAvailabilityAdded(sales: Sales, availability: Availability) {.async.} = +proc onAvailabilityAdded( + sales: Sales, availability: Availability +) {.async: (raises: []).} = ## When availabilities are modified or added, the queue should be unpaused if ## it was paused and any slots in the queue should have their `seen` flag ## cleared. @@ -483,7 +485,7 @@ proc startSlotQueue(sales: Sales) = slotQueue.start() - proc onAvailabilityAdded(availability: Availability) {.async.} = + proc onAvailabilityAdded(availability: Availability) {.async: (raises: []).} = await sales.onAvailabilityAdded(availability) reservations.onAvailabilityAdded = onAvailabilityAdded diff --git a/codex/sales/reservations.nim b/codex/sales/reservations.nim index 4f48e057..fca83dc8 100644 --- a/codex/sales/reservations.nim +++ b/codex/sales/reservations.nim @@ -70,6 +70,10 @@ type minPricePerBytePerSecond* {.serialize.}: UInt256 totalCollateral {.serialize.}: UInt256 totalRemainingCollateral* {.serialize.}: UInt256 + # If false, the availability will not be able to receive new slots. + # If it is turned on and the availability is already hosting slots, + # it will not affect those existing slots. + enabled* {.serialize.}: bool Reservation* = ref object id* {.serialize.}: ReservationId @@ -86,8 +90,9 @@ type GetNext* = proc(): Future[?seq[byte]] {.upraises: [], gcsafe, closure.} IterDispose* = proc(): Future[?!void] {.gcsafe, closure.} - OnAvailabilityAdded* = - proc(availability: Availability): Future[void] {.upraises: [], gcsafe.} + OnAvailabilityAdded* = proc(availability: Availability): Future[void] {. + upraises: [], gcsafe, async: (raises: []) + .} StorableIter* = ref object finished*: bool next*: GetNext @@ -128,6 +133,7 @@ proc init*( duration: UInt256, minPricePerBytePerSecond: UInt256, totalCollateral: UInt256, + enabled: bool, ): Availability = var id: array[32, byte] doAssert randomBytes(id) == 32 @@ -139,6 +145,7 @@ proc init*( minPricePerBytePerSecond: minPricePerBytePerSecond, totalCollateral: totalCollateral, totalRemainingCollateral: totalCollateral, + enabled: enabled, ) func totalCollateral*(self: Availability): UInt256 {.inline.} = @@ -268,18 +275,8 @@ proc updateAvailability( trace "Creating new Availability" let res = await self.updateImpl(obj) # inform subscribers that Availability has been added - if onAvailabilityAdded =? self.onAvailabilityAdded: - # when chronos v4 is implemented, and OnAvailabilityAdded is annotated - # with async:(raises:[]), we can remove this try/catch as we know, with - # certainty, that nothing will be raised - try: - await onAvailabilityAdded(obj) - except CancelledError as e: - raise e - except CatchableError as e: - # we don't have any insight into types of exceptions that - # `onAvailabilityAdded` can raise because it is caller-defined - warn "Unknown error during 'onAvailabilityAdded' callback", error = e.msg + if obj.enabled and onAvailabilityAdded =? self.onAvailabilityAdded: + await onAvailabilityAdded(obj) return res else: return failure(err) @@ -304,22 +301,11 @@ proc updateAvailability( let res = await self.updateImpl(obj) - if oldAvailability.freeSize < obj.freeSize: # availability added + if obj.enabled and oldAvailability.freeSize < obj.freeSize: # availability added # inform subscribers that Availability has been modified (with increased # size) if onAvailabilityAdded =? self.onAvailabilityAdded: - # when chronos v4 is implemented, and OnAvailabilityAdded is annotated - # with async:(raises:[]), we can remove this try/catch as we know, with - # certainty, that nothing will be raised - try: - await onAvailabilityAdded(obj) - except CancelledError as e: - raise e - except CatchableError as e: - # we don't have any insight into types of exceptions that - # `onAvailabilityAdded` can raise because it is caller-defined - warn "Unknown error during 'onAvailabilityAdded' callback", error = e.msg - + await onAvailabilityAdded(obj) return res proc update*(self: Reservations, obj: Reservation): Future[?!void] {.async.} = @@ -393,12 +379,14 @@ proc createAvailability*( duration: UInt256, minPricePerBytePerSecond: UInt256, totalCollateral: UInt256, + enabled: bool, ): Future[?!Availability] {.async.} = trace "creating availability", - size, duration, minPricePerBytePerSecond, totalCollateral + size, duration, minPricePerBytePerSecond, totalCollateral, enabled - let availability = - Availability.init(size, size, duration, minPricePerBytePerSecond, totalCollateral) + let availability = Availability.init( + size, size, duration, minPricePerBytePerSecond, totalCollateral, enabled + ) let bytes = availability.freeSize.truncate(uint) if reserveErr =? (await self.repo.reserve(bytes.NBytes)).errorOption: @@ -651,11 +639,13 @@ proc findAvailability*( for item in storables.items: if bytes =? (await item) and availability =? Availability.fromJson(bytes): - if size <= availability.freeSize and duration <= availability.duration and + if availability.enabled and size <= availability.freeSize and + duration <= availability.duration and collateralPerByte <= availability.maxCollateralPerByte and pricePerBytePerSecond >= availability.minPricePerBytePerSecond: trace "availability matched", id = availability.id, + enabled = availability.enabled, size, availFreeSize = availability.freeSize, duration, @@ -675,6 +665,7 @@ proc findAvailability*( trace "availability did not match", id = availability.id, + enabled = availability.enabled, size, availFreeSize = availability.freeSize, duration, diff --git a/openapi.yaml b/openapi.yaml index 9d401e8f..3b224c4d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -163,6 +163,10 @@ components: totalCollateral: type: string description: Total collateral (in amount of tokens) that can be used for matching requests + enabled: + type: boolean + description: Enable the ability to receive sales on this availability. + default: true SalesAvailabilityREAD: allOf: diff --git a/tests/codex/examples.nim b/tests/codex/examples.nim index 69a85db8..5ea99956 100644 --- a/tests/codex/examples.nim +++ b/tests/codex/examples.nim @@ -65,6 +65,7 @@ proc example*( duration = uint16.example.u256, minPricePerBytePerSecond = uint8.example.u256, totalCollateral = totalSize * collateralPerByte, + enabled = true, ) proc example*(_: type Reservation): Reservation = diff --git a/tests/codex/sales/states/testpreparing.nim b/tests/codex/sales/states/testpreparing.nim index e78ee25e..97c8ea0b 100644 --- a/tests/codex/sales/states/testpreparing.nim +++ b/tests/codex/sales/states/testpreparing.nim @@ -39,6 +39,7 @@ asyncchecksuite "sales state 'preparing'": duration = request.ask.duration + 60.u256, minPricePerBytePerSecond = request.ask.pricePerBytePerSecond, totalCollateral = request.ask.collateralPerSlot * request.ask.slots.u256, + enabled = true, ) let repoDs = SQLiteDatastore.new(Memory).tryGet() let metaDs = SQLiteDatastore.new(Memory).tryGet() @@ -67,10 +68,10 @@ asyncchecksuite "sales state 'preparing'": let next = state.onSlotFilled(request.id, slotIndex) check !next of SaleFilled - proc createAvailability() {.async.} = + proc createAvailability(enabled = true) {.async.} = let a = await reservations.createAvailability( availability.totalSize, availability.duration, - availability.minPricePerBytePerSecond, availability.totalCollateral, + availability.minPricePerBytePerSecond, availability.totalCollateral, enabled, ) availability = a.get @@ -81,6 +82,11 @@ asyncchecksuite "sales state 'preparing'": check ignored.reprocessSlot check ignored.returnBytes == false + test "run switches to ignored when a availability is not enabled": + await createAvailability(enabled = false) + let next = !(await state.run(agent)) + check next of SaleIgnored + test "run switches to slot reserving state after reservation created": await createAvailability() let next = await state.run(agent) diff --git a/tests/codex/sales/testreservations.nim b/tests/codex/sales/testreservations.nim index a1c7d1a5..d8880579 100644 --- a/tests/codex/sales/testreservations.nim +++ b/tests/codex/sales/testreservations.nim @@ -39,12 +39,13 @@ asyncchecksuite "Reservations module": await repoTmp.destroyDb() await metaTmp.destroyDb() - proc createAvailability(): Availability = + proc createAvailability(enabled = true): Availability = let example = Availability.example(collateralPerByte) let totalSize = rand(100000 .. 200000).u256 let totalCollateral = totalSize * collateralPerByte let availability = waitFor reservations.createAvailability( - totalSize, example.duration, example.minPricePerBytePerSecond, totalCollateral + totalSize, example.duration, example.minPricePerBytePerSecond, totalCollateral, + enabled, ) return availability.get @@ -64,8 +65,8 @@ asyncchecksuite "Reservations module": check (await reservations.all(Availability)).get.len == 0 test "generates unique ids for storage availability": - 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, 5.u256) + let availability1 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256, 5.u256, true) + let availability2 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256, 5.u256, true) check availability1.id != availability2.id test "can reserve available storage": @@ -285,7 +286,9 @@ asyncchecksuite "Reservations module": test "onAvailabilityAdded called when availability is created": var added: Availability - reservations.onAvailabilityAdded = proc(a: Availability) {.async.} = + reservations.onAvailabilityAdded = proc( + a: Availability + ) {.gcsafe, async: (raises: []).} = added = a let availability = createAvailability() @@ -295,7 +298,9 @@ asyncchecksuite "Reservations module": test "onAvailabilityAdded called when availability size is increased": var availability = createAvailability() var added: Availability - reservations.onAvailabilityAdded = proc(a: Availability) {.async.} = + reservations.onAvailabilityAdded = proc( + a: Availability + ) {.gcsafe, async: (raises: []).} = added = a availability.freeSize += 1.u256 discard await reservations.update(availability) @@ -305,7 +310,21 @@ asyncchecksuite "Reservations module": test "onAvailabilityAdded is not called when availability size is decreased": var availability = createAvailability() var called = false - reservations.onAvailabilityAdded = proc(a: Availability) {.async.} = + reservations.onAvailabilityAdded = proc( + a: Availability + ) {.gcsafe, async: (raises: []).} = + called = true + availability.freeSize -= 1.u256 + discard await reservations.update(availability) + + check not called + + test "onAvailabilityAdded is not called when enabled is false": + var availability = createAvailability(enabled = false) + var called = false + reservations.onAvailabilityAdded = proc( + a: Availability + ) {.gcsafe, async: (raises: []).} = called = true availability.freeSize -= 1.u256 discard await reservations.update(availability) @@ -323,6 +342,16 @@ asyncchecksuite "Reservations module": check found.isSome check found.get == availability + test "availabilities cannot be found when it is not enabled": + let availability = createAvailability(enabled = false) + + let found = await reservations.findAvailability( + availability.freeSize, availability.duration, + availability.minPricePerBytePerSecond, collateralPerByte, + ) + + check found.isNone + test "non-matching availabilities are not found": let availability = createAvailability() @@ -364,6 +393,7 @@ asyncchecksuite "Reservations module": UInt256.example, UInt256.example, UInt256.example, + enabled = true, ) check created.isErr check created.error of ReserveFailedError diff --git a/tests/codex/sales/testsales.nim b/tests/codex/sales/testsales.nim index 0d441f34..724f0737 100644 --- a/tests/codex/sales/testsales.nim +++ b/tests/codex/sales/testsales.nim @@ -149,6 +149,7 @@ asyncchecksuite "Sales": duration = 60.u256, minPricePerBytePerSecond = minPricePerBytePerSecond, totalCollateral = totalCollateral, + enabled = true, ) request = StorageRequest( ask: StorageAsk( @@ -216,10 +217,10 @@ asyncchecksuite "Sales": let key = availability.id.key.get (waitFor reservations.get(key, Availability)).get - proc createAvailability() = + proc createAvailability(enabled = true) = let a = waitFor reservations.createAvailability( availability.totalSize, availability.duration, - availability.minPricePerBytePerSecond, availability.totalCollateral, + availability.minPricePerBytePerSecond, availability.totalCollateral, enabled, ) availability = a.get # update id @@ -412,6 +413,20 @@ asyncchecksuite "Sales": market.slotState[request.slotId(3.u256)] = SlotState.Filled check wasIgnored() + test "ignores request when availability is not enabled": + createAvailability(enabled = false) + await market.requestStorage(request) + check wasIgnored() + + test "ignores request when availability was disabled after the request storage is created": + createAvailability(enabled = true) + await market.requestStorage(request) + + availability.enabled = false + discard await reservations.update(availability) + + check wasIgnored() + test "retrieves and stores data locally": var storingRequest: StorageRequest var storingSlot: UInt256 diff --git a/tests/integration/codexclient.nim b/tests/integration/codexclient.nim index 7826b151..fbaa8dcb 100644 --- a/tests/integration/codexclient.nim +++ b/tests/integration/codexclient.nim @@ -175,6 +175,7 @@ proc getSlots*(client: CodexClient): ?!seq[Slot] = proc postAvailability*( client: CodexClient, totalSize, duration, minPricePerBytePerSecond, totalCollateral: UInt256, + enabled: ?bool = bool.none, ): ?!Availability = ## Post sales availability endpoint ## @@ -185,6 +186,7 @@ proc postAvailability*( "duration": duration, "minPricePerBytePerSecond": minPricePerBytePerSecond, "totalCollateral": totalCollateral, + "enabled": enabled |? true, } let response = client.http.post(url, $json) doAssert response.status == "201 Created", @@ -196,6 +198,7 @@ proc patchAvailabilityRaw*( availabilityId: AvailabilityId, totalSize, freeSize, duration, minPricePerBytePerSecond, totalCollateral: ?UInt256 = UInt256.none, + enabled: ?bool = bool.none, ): Response = ## Updates availability ## @@ -219,6 +222,9 @@ proc patchAvailabilityRaw*( if totalCollateral =? totalCollateral: json["totalCollateral"] = %totalCollateral + if enabled =? enabled: + json["enabled"] = %enabled + client.http.patch(url, $json) proc patchAvailability*( @@ -226,6 +232,7 @@ proc patchAvailability*( availabilityId: AvailabilityId, totalSize, duration, minPricePerBytePerSecond, totalCollateral: ?UInt256 = UInt256.none, + enabled: ?bool = bool.none, ): void = let response = client.patchAvailabilityRaw( availabilityId, @@ -233,6 +240,7 @@ proc patchAvailability*( duration = duration, minPricePerBytePerSecond = minPricePerBytePerSecond, totalCollateral = totalCollateral, + enabled = enabled, ) doAssert response.status == "200 OK", "expected 200 OK, got " & response.status diff --git a/tests/integration/testproofs.nim b/tests/integration/testproofs.nim index b25643ad..94844df0 100644 --- a/tests/integration/testproofs.nim +++ b/tests/integration/testproofs.nim @@ -266,7 +266,8 @@ marketplacesuite "Simulate invalid proofs": # totalSize=slotSize, # should match 1 slot only # duration=totalPeriods.periods.u256, # minPricePerBytePerSecond=minPricePerBytePerSecond, - # totalCollateral=slotSize * minPricePerBytePerSecond + # totalCollateral=slotSize * minPricePerBytePerSecond, + # enabled = true.some # ) # let cid = client0.upload(data).get diff --git a/tests/integration/testrestapi.nim b/tests/integration/testrestapi.nim index 52b722d6..1019e7f5 100644 --- a/tests/integration/testrestapi.nim +++ b/tests/integration/testrestapi.nim @@ -29,6 +29,7 @@ twonodessuite "REST API": duration = 2.u256, minPricePerBytePerSecond = minPricePerBytePerSecond, totalCollateral = totalCollateral, + enabled = true.some, ).get let space = client1.space().tryGet() check: @@ -37,6 +38,19 @@ twonodessuite "REST API": space.quotaUsedBytes == 65598.NBytes space.quotaReservedBytes == 12.NBytes + test "created correctly an availability when not enabled by default", twoNodesConfig: + let totalSize = 12.u256 + let minPricePerBytePerSecond = 1.u256 + let totalCollateral = totalSize * minPricePerBytePerSecond + let availability = client1.postAvailability( + totalSize = totalSize, + duration = 2.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, + enabled = false.some, + ).get + check availability.enabled == false + test "node lists local files", twoNodesConfig: let content1 = "some file contents" let content2 = "some other contents" diff --git a/tests/integration/testsales.nim b/tests/integration/testsales.nim index a77e5649..6d1f76d4 100644 --- a/tests/integration/testsales.nim +++ b/tests/integration/testsales.nim @@ -108,6 +108,18 @@ multinodesuite "Sales": check updatedAvailability.totalSize == 100000 check updatedAvailability.freeSize == 100000 + test "updating availability - updating enabled", salesConfig: + let availability = host.postAvailability( + totalSize = 140000.u256, + duration = 200.u256, + minPricePerBytePerSecond = 3.u256, + totalCollateral = 300.u256, + enabled = true.some, + ).get + host.patchAvailability(availability.id, enabled = false.some) + let updatedAvailability = (host.getAvailabilities().get).findItem(availability).get + check updatedAvailability.enabled == false + test "updating availability - updating totalSize does not allow bellow utilized", salesConfig: let originalSize = 0xFFFFFF.u256