From 43f6a9ec0d0d1dcb94e0cc17792ff16ad130a5f1 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 31 Jan 2025 17:17:28 +0100 Subject: [PATCH] Use configSync to avoid async pragma in onStorageRequested --- codex/contracts/market.nim | 59 ++++++++++++++++--------- codex/market.nim | 12 ++--- codex/sales.nim | 70 +++++++++++++++++++----------- codex/sales/slotqueue.nim | 4 +- codex/sales/states/filling.nim | 4 +- tests/codex/helpers/mockmarket.nim | 46 +++++++++++++------- tests/contracts/testMarket.nim | 10 ++++- 7 files changed, 132 insertions(+), 73 deletions(-) diff --git a/codex/contracts/market.nim b/codex/contracts/market.nim index 2d5e0cd1..7c52fa36 100644 --- a/codex/contracts/market.nim +++ b/codex/contracts/market.nim @@ -51,6 +51,9 @@ proc config(market: OnChainMarket): Future[MarketplaceConfig] {.async.} = return resolvedConfig +proc configSync(market: OnChainMarket): ?MarketplaceConfig = + return market.configuration + proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} = debug "Approving tokens", amount convertEthersError: @@ -79,7 +82,7 @@ method proofTimeout*(market: OnChainMarket): Future[UInt256] {.async.} = method repairRewardPercentage*(market: OnChainMarket): Future[uint8] {.async.} = convertEthersError: - let config = await market.contract.configuration() + let config = await market.config() return config.collateral.repairRewardPercentage method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} = @@ -111,12 +114,14 @@ method requestStorage(market: OnChainMarket, request: StorageRequest) {.async.} method getRequest*( market: OnChainMarket, id: RequestId -): Future[?StorageRequest] {.async.} = - convertEthersError: - try: - return some await market.contract.getRequest(id) - except Marketplace_UnknownRequest: - return none StorageRequest +): Future[?StorageRequest] {.async: (raises: []).} = + try: + return some await market.contract.getRequest(id) + except Marketplace_UnknownRequest: + return none StorageRequest + except CatchableError as err: + warn "Cannot retrieve the request", error = err.msg + return none StorageRequest method requestState*( market: OnChainMarket, requestId: RequestId @@ -128,7 +133,9 @@ method requestState*( except Marketplace_UnknownRequest: return none RequestState -method slotState*(market: OnChainMarket, slotId: SlotId): Future[SlotState] {.async.} = +method slotState*( + market: OnChainMarket, slotId: SlotId +): Future[SlotState] {.async: (raises: [CatchableError]).} = convertEthersError: let overrides = CallOverrides(blockTag: some BlockTag.pending) return await market.contract.slotState(slotId, overrides) @@ -486,22 +493,32 @@ method queryPastStorageRequestedEvents*( method slotCollateral*( market: OnChainMarket, requestId: RequestId, slotIndex: UInt256 -): Future[UInt256] {.async.} = +): Future[?UInt256] {.async: (raises: []).} = let slotid = slotId(requestId, slotIndex) - let slotState = await market.slotState(slotid) - return await market.slotCollateral(requestId, slotState) + try: + let slotState = await market.slotState(slotid) + + without request =? await market.getRequest(requestId): + return UInt256.none + + return market.slotCollateral(request.ask.collateralPerSlot, slotState) + except CatchableError as err: + error "Cannot retrieve the slot state", error = err.msg + return UInt256.none method slotCollateral*( - market: OnChainMarket, requestId: RequestId, slotState: SlotState -): Future[UInt256] {.async: (raises: [CancelledError, MarketError]).} = - without request =? await market.getRequest(requestId): - raiseMarketError("Cannot retrieve the request.") - + market: OnChainMarket, collateralPerSlot: UInt256, slotState: SlotState +): ?UInt256 {.raises: [].} = if slotState == SlotState.Repair: - let repairRewardPercentage = (await market.repairRewardPercentage).u256 - return - request.ask.collateralPerSlot - - (request.ask.collateralPerSlot * repairRewardPercentage).div(100.u256) + without repairRewardPercentage =? + market.configSync .? collateral .? repairRewardPercentage: + return UInt256.none - return request.ask.collateralPerSlot + return ( + collateralPerSlot - (collateralPerSlot * repairRewardPercentage.u256).div( + 100.u256 + ) + ).some + + return collateralPerSlot.some diff --git a/codex/market.nim b/codex/market.nim index 1368512a..6029c81c 100644 --- a/codex/market.nim +++ b/codex/market.nim @@ -100,7 +100,7 @@ method mySlots*(market: Market): Future[seq[SlotId]] {.base, async.} = method getRequest*( market: Market, id: RequestId -): Future[?StorageRequest] {.base, async.} = +): Future[?StorageRequest] {.base, async: (raises: []).} = raiseAssert("not implemented") method requestState*( @@ -108,7 +108,9 @@ method requestState*( ): Future[?RequestState] {.base, async.} = raiseAssert("not implemented") -method slotState*(market: Market, slotId: SlotId): Future[SlotState] {.base, async.} = +method slotState*( + market: Market, slotId: SlotId +): Future[SlotState] {.base, async: (raises: [CatchableError]).} = raiseAssert("not implemented") method getRequestEnd*( @@ -271,10 +273,10 @@ method queryPastStorageRequestedEvents*( method slotCollateral*( market: Market, requestId: RequestId, slotIndex: UInt256 -): Future[UInt256] {.base, async.} = +): Future[?UInt256] {.base, async: (raises: []).} = raiseAssert("not implemented") method slotCollateral*( - market: Market, requestId: RequestId, slotState: SlotState -): Future[UInt256] {.base, async: (raises: [CancelledError, MarketError]).} = + market: Market, collateralPerSlot: UInt256, slotState: SlotState +): ?UInt256 {.base, gcsafe, raises: [].} = raiseAssert("not implemented") diff --git a/codex/sales.nim b/codex/sales.nim index 12dcc762..3acd0a3d 100644 --- a/codex/sales.nim +++ b/codex/sales.nim @@ -153,8 +153,10 @@ proc cleanUp( # Re-add items back into the queue to prevent small availabilities from # draining the queue. Seen items will be ordered last. if reprocessSlot and request =? data.request: - let collateral = - await sales.context.market.slotCollateral(data.requestId, data.slotIndex) + without collateral =? + await sales.context.market.slotCollateral(data.requestId, data.slotIndex): + error "Unable to calculate collateral; configuration data may not be retrievable." + return let queue = sales.context.slotQueue var seenItem = SlotQueueItem.init( @@ -288,7 +290,7 @@ proc onAvailabilityAdded(sales: Sales, availability: Availability) {.async.} = proc onStorageRequested( sales: Sales, requestId: RequestId, ask: StorageAsk, expiry: UInt256 -) {.async.} = +) {.raises: [].} = logScope: topics = "marketplace sales onStorageRequested" requestId @@ -300,7 +302,10 @@ proc onStorageRequested( trace "storage requested, adding slots to queue" let market = sales.context.market - let collateral = await market.slotCollateral(requestId, SlotState.Free) + + without collateral =? market.slotCollateral(ask.collateralPerSlot, SlotState.Free): + error "Unable to calculate collateral; configuration data may not be retrievable." + return without items =? SlotQueueItem.init(requestId, ask, expiry, collateral).catch, err: if err of SlotsOutOfRangeError: @@ -331,31 +336,39 @@ proc onSlotFreed(sales: Sales, requestId: RequestId, slotIndex: UInt256) = let context = sales.context let market = context.market let queue = context.slotQueue - var slotQueueItem: SlotQueueItem - try: - without request =? await market.getRequest(requestId): - error "unknown request in contract" - return + without request =? (await market.getRequest(requestId)), err: + error "unknown request in contract", error = err.msgDetail + return - # Take the repairing state into consideration to calculate the collateral. - # This is particularly needed because it will affect the priority in the queue - # and we want to give the user the ability to tweak the parameters. - # Adding the repairing state directly in the queue priority calculation - # would not allow this flexibility. - let collateral = await market.slotCollateral(request.id, SlotState.Repair) + # Take the repairing state into consideration to calculate the collateral. + # This is particularly needed because it will affect the priority in the queue + # and we want to give the user the ability to tweak the parameters. + # Adding the repairing state directly in the queue priority calculation + # would not allow this flexibility. + without collateral =? + market.slotCollateral(request.ask.collateralPerSlot, SlotState.Repair): + error "Unable to calculate collateral; configuration data may not be retrievable." + return - slotQueueItem = - SlotQueueItem.init(request, slotIndex.truncate(uint16), collateral = collateral) + without slotQueueItem =? + SlotQueueItem.init(request, slotIndex.truncate(uint16), collateral = collateral).catch, + err: + warn "Too many slots, cannot add to queue", error = err.msgDetail + return - if err =? queue.push(slotQueueItem).errorOption: - error "failed to push slot items to queue", error = err.msgDetail - except CancelledError: - discard # do not propagate as addSlotToQueue was asyncSpawned - except CatchableError as e: - error "failed to get request from contract and add slots to queue", - error = e.msgDetail + if err =? queue.push(slotQueueItem).errorOption: + if err of SlotQueueItemExistsError: + error "Failed to push item to queue becaue it already exists", + error = err.msgDetail + elif err of QueueNotRunningError: + warn "Failed to push item to queue becaue queue is not running", + error = err.msgDetail + # We could get rid of this by adding the storage ask in the SlotFreed event, + # so we would need to call getRequest to get the collateralPerSlot. + # Or when the request cache is merged, we could assume that the request will be + # in the cache. let fut = addSlotToQueue() sales.trackedFutures.track(fut) asyncSpawn fut @@ -364,8 +377,13 @@ proc subscribeRequested(sales: Sales) {.async.} = let context = sales.context let market = context.market - proc onStorageRequested(requestId: RequestId, ask: StorageAsk, expiry: UInt256) = - discard sales.onStorageRequested(requestId, ask, expiry) + proc onStorageRequested( + requestId: RequestId, ask: StorageAsk, expiry: UInt256 + ) {.raises: [].} = + sales.onStorageRequested(requestId, ask, expiry) + + # Ensure that the config is loaded and repairRewardPercentage is available + discard await market.repairRewardPercentage() try: let sub = await market.subscribeRequests(onStorageRequested) diff --git a/codex/sales/slotqueue.nim b/codex/sales/slotqueue.nim index d448dd0b..31f1191f 100644 --- a/codex/sales/slotqueue.nim +++ b/codex/sales/slotqueue.nim @@ -161,7 +161,7 @@ proc init*( ask: StorageAsk, expiry: UInt256, collateral: UInt256, -): seq[SlotQueueItem] = +): seq[SlotQueueItem] {.raises: [SlotsOutOfRangeError].} = if not ask.slots.inRange: raise newException(SlotsOutOfRangeError, "Too many slots") @@ -240,7 +240,7 @@ proc unpause*(self: SlotQueue) = # set unpaused flag to true -- unblocks coroutines waiting on unpaused.wait() self.unpaused.fire() -proc push*(self: SlotQueue, item: SlotQueueItem): ?!void = +proc push*(self: SlotQueue, item: SlotQueueItem): ?!void {.raises: [].} = logScope: requestId = item.requestId slotIndex = item.slotIndex diff --git a/codex/sales/states/filling.nim b/codex/sales/states/filling.nim index a06915bc..4184ffeb 100644 --- a/codex/sales/states/filling.nim +++ b/codex/sales/states/filling.nim @@ -35,7 +35,9 @@ method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} = requestId = data.requestId slotIndex = data.slotIndex - let collateral = await market.slotCollateral(data.requestId, data.slotIndex) + without collateral =? await market.slotCollateral(data.requestId, data.slotIndex): + error "Unable to calculate collateral; configuration data may not be retrievable." + return debug "Filling slot" try: diff --git a/tests/codex/helpers/mockmarket.nim b/tests/codex/helpers/mockmarket.nim index 8776bc7d..76079cfa 100644 --- a/tests/codex/helpers/mockmarket.nim +++ b/tests/codex/helpers/mockmarket.nim @@ -165,7 +165,7 @@ method mySlots*(market: MockMarket): Future[seq[SlotId]] {.async.} = method getRequest*( market: MockMarket, id: RequestId -): Future[?StorageRequest] {.async.} = +): Future[?StorageRequest] {.async: (raises: []).} = for request in market.requested: if request.id == id: return some request @@ -183,10 +183,18 @@ method requestState*( ): Future[?RequestState] {.async.} = return market.requestState .? [requestId] -method slotState*(market: MockMarket, slotId: SlotId): Future[SlotState] {.async.} = - if not market.slotState.hasKey(slotId): +method slotState*( + market: MockMarket, slotId: SlotId +): Future[SlotState] {.async: (raises: [CatchableError]).} = + if slotId notin market.slotState: return SlotState.Free - return market.slotState[slotId] + + try: + return market.slotState[slotId] + except ref KeyError: + # Should never reach that case. + # Just returning a random slot state. + return SlotState.Repair method getRequestEnd*( market: MockMarket, id: RequestId @@ -529,21 +537,27 @@ method unsubscribe*(subscription: SlotReservationsFullSubscription) {.async.} = method slotCollateral*( market: MockMarket, requestId: RequestId, slotIndex: UInt256 -): Future[UInt256] {.async.} = +): Future[?UInt256] {.async: (raises: []).} = let slotid = slotId(requestId, slotIndex) - let state = await slotState(market, slotid) - return await market.slotCollateral(requestId, state) + + try: + let state = await slotState(market, slotid) + + without request =? await market.getRequest(requestId): + return UInt256.none + + return market.slotCollateral(request.ask.collateralPerSlot, state) + except CatchableError: + return UInt256.none method slotCollateral*( - market: MockMarket, requestId: RequestId, slotState: SlotState -): Future[UInt256] {.async: (raises: [CancelledError, MarketError]).} = - without request =? await market.getRequest(requestId): - raiseMarketError("Cannot retrieve the request.") - + market: MockMarket, collateralPerSlot: UInt256, slotState: SlotState +): ?UInt256 {.raises: [].} = if slotState == SlotState.Repair: let repairRewardPercentage = market.config.collateral.repairRewardPercentage.u256 - return - request.ask.collateralPerSlot - - (request.ask.collateralPerSlot * repairRewardPercentage).div(100.u256) - return request.ask.collateralPerSlot + return ( + collateralPerSlot - (collateralPerSlot * repairRewardPercentage).div(100.u256) + ).some + + return collateralPerSlot.some diff --git a/tests/contracts/testMarket.nim b/tests/contracts/testMarket.nim index 5b41c75d..2c6c9acb 100644 --- a/tests/contracts/testMarket.nim +++ b/tests/contracts/testMarket.nim @@ -598,18 +598,24 @@ ethersuite "On-Chain Market": await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) let slotId = request.slotId(0.u256) - let collateral = await market.slotCollateral(request.id, 0.u256) + without collateral =? await market.slotCollateral(request.id, 0.u256): + fail() check collateral == request.ask.collateralPerSlot test "calculates correctly the collateral when the slot is being repaired": + # Ensure that the config is loaded and repairRewardPercentage is available + discard await market.repairRewardPercentage() + await market.requestStorage(request) await market.reserveSlot(request.id, 0.u256) await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) await market.freeSlot(slotId(request.id, 0.u256)) let slotId = request.slotId(0.u256) - let collateral = await market.slotCollateral(request.id, 0.u256) + + without collateral =? await market.slotCollateral(request.id, 0.u256): + fail() # slotCollateral # repairRewardPercentage = 10