diff --git a/codex/codex.nim b/codex/codex.nim index b8905205..8a03510c 100644 --- a/codex/codex.nim +++ b/codex/codex.nim @@ -134,6 +134,10 @@ proc bootstrapInteractions(s: CodexServer): Future[void] {.async.} = if config.simulateProofFailures > 0: warn "Proof failure simulation is not enabled for this build! Configuration ignored" + if error =? (await market.loadConfig()).errorOption: + fatal "Cannot load market configuration", error = error.msg + quit QuitFailure + let purchasing = Purchasing.new(market, clock) let sales = Sales.new(market, clock, repo, proofFailures) client = some ClientInteractions.new(clock, purchasing) diff --git a/codex/contracts/market.nim b/codex/contracts/market.nim index 3c016a59..9079ac8a 100644 --- a/codex/contracts/market.nim +++ b/codex/contracts/market.nim @@ -55,11 +55,17 @@ template convertEthersError(body) = except EthersError as error: raiseMarketError(error.msgDetail) -proc config(market: OnChainMarket): Future[MarketplaceConfig] {.async.} = +proc config( + market: OnChainMarket +): Future[MarketplaceConfig] {.async: (raises: [CancelledError, MarketError]).} = without resolvedConfig =? market.configuration: - let fetchedConfig = await market.contract.configuration() - market.configuration = some fetchedConfig - return fetchedConfig + if err =? (await market.loadConfig()).errorOption: + raiseMarketError(err.msg) + + without config =? market.configuration: + raiseMarketError("Failed to access to config from the Marketplace contract") + + return config return resolvedConfig @@ -70,7 +76,26 @@ proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} = let token = Erc20Token.new(tokenAddress, market.signer) discard await token.increaseAllowance(market.contract.address(), amount).confirm(1) -method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} = +method loadConfig*( + market: OnChainMarket +): Future[?!void] {.async: (raises: [CancelledError]).} = + try: + without config =? market.configuration: + let fetchedConfig = await market.contract.configuration() + + market.configuration = some fetchedConfig + + return success() + except AsyncLockError, EthersError: + let err = getCurrentException() + return failure newException( + MarketError, + "Failed to fetch the config from the Marketplace contract: " & err.msg, + ) + +method getZkeyHash*( + market: OnChainMarket +): Future[?string] {.async: (raises: [CancelledError, MarketError]).} = let config = await market.config() return some config.proofs.zkeyHash @@ -78,18 +103,24 @@ method getSigner*(market: OnChainMarket): Future[Address] {.async.} = convertEthersError: return await market.signer.getAddress() -method periodicity*(market: OnChainMarket): Future[Periodicity] {.async.} = +method periodicity*( + market: OnChainMarket +): Future[Periodicity] {.async: (raises: [CancelledError, MarketError]).} = convertEthersError: let config = await market.config() let period = config.proofs.period return Periodicity(seconds: period) -method proofTimeout*(market: OnChainMarket): Future[uint64] {.async.} = +method proofTimeout*( + market: OnChainMarket +): Future[uint64] {.async: (raises: [CancelledError, MarketError]).} = convertEthersError: let config = await market.config() return config.proofs.timeout -method repairRewardPercentage*(market: OnChainMarket): Future[uint8] {.async.} = +method repairRewardPercentage*( + market: OnChainMarket +): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} = convertEthersError: let config = await market.config() return config.collateral.repairRewardPercentage @@ -99,7 +130,9 @@ method requestDurationLimit*(market: OnChainMarket): Future[uint64] {.async.} = let config = await market.config() return config.requestDurationLimit -method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} = +method proofDowntime*( + market: OnChainMarket +): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} = convertEthersError: let config = await market.config() return config.proofs.downtime @@ -128,19 +161,22 @@ method requestStorage(market: OnChainMarket, request: StorageRequest) {.async.} method getRequest*( market: OnChainMarket, id: RequestId -): Future[?StorageRequest] {.async.} = - let key = $id +): Future[?StorageRequest] {.async: (raises: [CancelledError]).} = + try: + let key = $id - if market.requestCache.contains(key): - return some market.requestCache[key] + if key in market.requestCache: + return some market.requestCache[key] - convertEthersError: - try: - let request = await market.contract.getRequest(id) - market.requestCache[key] = request - return some request - except Marketplace_UnknownRequest: - return none StorageRequest + let request = await market.contract.getRequest(id) + market.requestCache[key] = request + return some request + except Marketplace_UnknownRequest, KeyError: + warn "Cannot retrieve the request", error = getCurrentExceptionMsg() + return none StorageRequest + except EthersError, AsyncLockError: + error "Cannot retrieve the request", error = getCurrentExceptionMsg() + return none StorageRequest method requestState*( market: OnChainMarket, requestId: RequestId @@ -152,10 +188,17 @@ 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: [CancelledError, MarketError]).} = convertEthersError: - let overrides = CallOverrides(blockTag: some BlockTag.pending) - return await market.contract.slotState(slotId, overrides) + try: + let overrides = CallOverrides(blockTag: some BlockTag.pending) + return await market.contract.slotState(slotId, overrides) + except AsyncLockError as err: + raiseMarketError( + "Failed to fetch the slot state from the Marketplace contract: " & err.msg + ) method getRequestEnd*( market: OnChainMarket, id: RequestId @@ -507,3 +550,40 @@ method queryPastStorageRequestedEvents*( let fromBlock = await market.contract.provider.pastBlockTag(blocksAgo) return await market.queryPastStorageRequestedEvents(fromBlock) + +method slotCollateral*( + market: OnChainMarket, requestId: RequestId, slotIndex: uint64 +): Future[?!UInt256] {.async: (raises: [CancelledError]).} = + let slotid = slotId(requestId, slotIndex) + + try: + let slotState = await market.slotState(slotid) + + without request =? await market.getRequest(requestId): + return failure newException( + MarketError, "Failure calculating the slotCollateral, cannot get the request" + ) + + return market.slotCollateral(request.ask.collateralPerSlot, slotState) + except MarketError as error: + error "Error when trying to calculate the slotCollateral", error = error.msg + return failure error + +method slotCollateral*( + market: OnChainMarket, collateralPerSlot: UInt256, slotState: SlotState +): ?!UInt256 {.raises: [].} = + if slotState == SlotState.Repair: + without repairRewardPercentage =? + market.configuration .? collateral .? repairRewardPercentage: + return failure newException( + MarketError, + "Failure calculating the slotCollateral, cannot get the reward percentage", + ) + + return success ( + collateralPerSlot - (collateralPerSlot * repairRewardPercentage.u256).div( + 100.u256 + ) + ) + + return success(collateralPerSlot) diff --git a/codex/market.nim b/codex/market.nim index 5417c8e1..c5177aeb 100644 --- a/codex/market.nim +++ b/codex/market.nim @@ -62,25 +62,40 @@ type ProofSubmitted* = object of MarketplaceEvent id*: SlotId -method getZkeyHash*(market: Market): Future[?string] {.base, async.} = +method loadConfig*( + market: Market +): Future[?!void] {.base, async: (raises: [CancelledError]).} = + raiseAssert("not implemented") + +method getZkeyHash*( + market: Market +): Future[?string] {.base, async: (raises: [CancelledError, MarketError]).} = raiseAssert("not implemented") method getSigner*(market: Market): Future[Address] {.base, async.} = raiseAssert("not implemented") -method periodicity*(market: Market): Future[Periodicity] {.base, async.} = +method periodicity*( + market: Market +): Future[Periodicity] {.base, async: (raises: [CancelledError, MarketError]).} = raiseAssert("not implemented") -method proofTimeout*(market: Market): Future[uint64] {.base, async.} = +method proofTimeout*( + market: Market +): Future[uint64] {.base, async: (raises: [CancelledError, MarketError]).} = raiseAssert("not implemented") -method repairRewardPercentage*(market: Market): Future[uint8] {.base, async.} = +method repairRewardPercentage*( + market: Market +): Future[uint8] {.base, async: (raises: [CancelledError, MarketError]).} = raiseAssert("not implemented") method requestDurationLimit*(market: Market): Future[uint64] {.base, async.} = raiseAssert("not implemented") -method proofDowntime*(market: Market): Future[uint8] {.base, async.} = +method proofDowntime*( + market: Market +): Future[uint8] {.base, async: (raises: [CancelledError, MarketError]).} = raiseAssert("not implemented") method getPointer*(market: Market, slotId: SlotId): Future[uint8] {.base, async.} = @@ -102,7 +117,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: [CancelledError]).} = raiseAssert("not implemented") method requestState*( @@ -110,7 +125,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: [CancelledError, MarketError]).} = raiseAssert("not implemented") method getRequestEnd*( @@ -270,3 +287,13 @@ method queryPastStorageRequestedEvents*( market: Market, blocksAgo: int ): Future[seq[StorageRequested]] {.base, async.} = raiseAssert("not implemented") + +method slotCollateral*( + market: Market, requestId: RequestId, slotIndex: uint64 +): Future[?!UInt256] {.base, async: (raises: [CancelledError]).} = + raiseAssert("not implemented") + +method slotCollateral*( + market: Market, collateralPerSlot: UInt256, slotState: SlotState +): ?!UInt256 {.base, gcsafe, raises: [].} = + raiseAssert("not implemented") diff --git a/codex/node.nim b/codex/node.nim index b0f66c90..b248e6df 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -591,7 +591,11 @@ proc requestStorage*( success purchase.id proc onStore( - self: CodexNodeRef, request: StorageRequest, slotIdx: uint64, blocksCb: BlocksCb + self: CodexNodeRef, + request: StorageRequest, + slotIdx: uint64, + blocksCb: BlocksCb, + isRepairing: bool = false, ): Future[?!void] {.async.} = ## store data in local storage ## @@ -604,6 +608,10 @@ proc onStore( trace "Received a request to store a slot" + # TODO: Use the isRepairing to manage the slot download. + # If isRepairing is true, the slot has to be repaired before + # being downloaded. + without manifest =? (await self.fetchManifest(cid)), err: trace "Unable to fetch manifest for cid", cid, err = err.msg return failure(err) @@ -745,9 +753,12 @@ proc start*(self: CodexNodeRef) {.async.} = if hostContracts =? self.contracts.host: hostContracts.sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, + slot: uint64, + onBatch: BatchProc, + isRepairing: bool = false, ): Future[?!void] = - self.onStore(request, slot, onBatch) + self.onStore(request, slot, onBatch, isRepairing) hostContracts.sales.onExpiryUpdate = proc( rootCid: Cid, expiry: SecondsSince1970 diff --git a/codex/sales.nim b/codex/sales.nim index 91d882b8..af594a9a 100644 --- a/codex/sales.nim +++ b/codex/sales.nim @@ -157,13 +157,28 @@ 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 queue = sales.context.slotQueue - var seenItem = SlotQueueItem.init( - data.requestId, data.slotIndex.uint16, data.ask, request.expiry, seen = true - ) - trace "pushing ignored item to queue, marked as seen" - if err =? queue.push(seenItem).errorOption: - error "failed to readd slot to queue", errorType = $(type err), error = err.msg + try: + without collateral =? + await sales.context.market.slotCollateral(data.requestId, data.slotIndex), err: + error "Failed to re-add item back to the slot queue: unable to calculate collateral", + error = err.msg + return + + let queue = sales.context.slotQueue + var seenItem = SlotQueueItem.init( + data.requestId, + data.slotIndex.uint16, + data.ask, + request.expiry, + seen = true, + collateral = collateral, + ) + trace "pushing ignored item to queue, marked as seen" + if err =? queue.push(seenItem).errorOption: + error "failed to readd slot to queue", errorType = $(type err), error = err.msg + except MarketError as e: + error "Failed to re-add item back to the slot queue.", error = e.msg + return await sales.remove(agent) @@ -283,7 +298,7 @@ proc onAvailabilityAdded(sales: Sales, availability: Availability) {.async.} = proc onStorageRequested( sales: Sales, requestId: RequestId, ask: StorageAsk, expiry: uint64 -) = +) {.raises: [].} = logScope: topics = "marketplace sales onStorageRequested" requestId @@ -294,7 +309,14 @@ proc onStorageRequested( trace "storage requested, adding slots to queue" - without items =? SlotQueueItem.init(requestId, ask, expiry).catch, err: + let market = sales.context.market + + without collateral =? market.slotCollateral(ask.collateralPerSlot, SlotState.Free), + err: + error "Request failure, unable to calculate collateral", error = err.msg + return + + without items =? SlotQueueItem.init(requestId, ask, expiry, collateral).catch, err: if err of SlotsOutOfRangeError: warn "Too many slots, cannot add to queue" else: @@ -319,35 +341,45 @@ proc onSlotFreed(sales: Sales, requestId: RequestId, slotIndex: uint64) = trace "slot freed, adding to queue" - proc addSlotToQueue() {.async: (raises: []).} = + proc addSlotToQueue() {.async: (raises: [CancelledError]).} = let context = sales.context let market = context.market let queue = context.slotQueue + 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. + without collateral =? + market.slotCollateral(request.ask.collateralPerSlot, SlotState.Repair), err: + error "Failed to add freed slot to queue: unable to calculate collateral", + error = err.msg + return + if slotIndex > uint16.high.uint64: error "Cannot cast slot index to uint16, value = ", slotIndex return - # first attempt to populate request using existing metadata in queue - without var found =? queue.populateItem(requestId, slotIndex.uint16): - trace "no existing request metadata, getting request info from contract" - # if there's no existing slot for that request, retrieve the request - # from the contract. - try: - without request =? await market.getRequest(requestId): - error "unknown request in contract" - return + without slotQueueItem =? + SlotQueueItem.init(request, slotIndex.uint16, collateral = collateral).catch, err: + warn "Too many slots, cannot add to queue", error = err.msgDetail + return - found = SlotQueueItem.init(request, slotIndex.uint16) - 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(found).errorOption: - error "failed to push slot items to queue", error = err.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 not need to call getRequest to get the collateralPerSlot. let fut = addSlotToQueue() sales.trackedFutures.track(fut) asyncSpawn fut @@ -356,7 +388,9 @@ proc subscribeRequested(sales: Sales) {.async.} = let context = sales.context let market = context.market - proc onStorageRequested(requestId: RequestId, ask: StorageAsk, expiry: uint64) = + proc onStorageRequested( + requestId: RequestId, ask: StorageAsk, expiry: uint64 + ) {.raises: [].} = sales.onStorageRequested(requestId, ask, expiry) try: diff --git a/codex/sales/salescontext.nim b/codex/sales/salescontext.nim index 6e6a3568..af940a4b 100644 --- a/codex/sales/salescontext.nim +++ b/codex/sales/salescontext.nim @@ -26,7 +26,7 @@ type BlocksCb* = proc(blocks: seq[bt.Block]): Future[?!void] {.gcsafe, raises: [].} OnStore* = proc( - request: StorageRequest, slot: uint64, blocksCb: BlocksCb + request: StorageRequest, slot: uint64, blocksCb: BlocksCb, isRepairing: bool ): Future[?!void] {.gcsafe, upraises: [].} OnProve* = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {. gcsafe, upraises: [] diff --git a/codex/sales/slotqueue.nim b/codex/sales/slotqueue.nim index a032d46b..fa57a983 100644 --- a/codex/sales/slotqueue.nim +++ b/codex/sales/slotqueue.nim @@ -34,7 +34,7 @@ type slotSize: uint64 duration: uint64 pricePerBytePerSecond: UInt256 - collateralPerByte: UInt256 + collateral: UInt256 # Collateral computed expiry: uint64 seen: bool @@ -76,9 +76,6 @@ proc profitability(item: SlotQueueItem): UInt256 = slotSize: item.slotSize, ).pricePerSlot -proc collateralPerSlot(item: SlotQueueItem): UInt256 = - StorageAsk(collateralPerByte: item.collateralPerByte, slotSize: item.slotSize).collateralPerSlot - proc `<`*(a, b: SlotQueueItem): bool = # for A to have a higher priority than B (in a min queue), A must be less than # B. @@ -95,8 +92,8 @@ proc `<`*(a, b: SlotQueueItem): bool = scoreA.addIf(a.profitability > b.profitability, 3) scoreB.addIf(a.profitability < b.profitability, 3) - scoreA.addIf(a.collateralPerSlot < b.collateralPerSlot, 2) - scoreB.addIf(a.collateralPerSlot > b.collateralPerSlot, 2) + scoreA.addIf(a.collateral < b.collateral, 2) + scoreB.addIf(a.collateral > b.collateral, 2) scoreA.addIf(a.expiry > b.expiry, 1) scoreB.addIf(a.expiry < b.expiry, 1) @@ -137,6 +134,7 @@ proc init*( slotIndex: uint16, ask: StorageAsk, expiry: uint64, + collateral: UInt256, seen = false, ): SlotQueueItem = SlotQueueItem( @@ -145,25 +143,32 @@ proc init*( slotSize: ask.slotSize, duration: ask.duration, pricePerBytePerSecond: ask.pricePerBytePerSecond, - collateralPerByte: ask.collateralPerByte, + collateral: collateral, expiry: expiry, seen: seen, ) proc init*( - _: type SlotQueueItem, request: StorageRequest, slotIndex: uint16 + _: type SlotQueueItem, + request: StorageRequest, + slotIndex: uint16, + collateral: UInt256, ): SlotQueueItem = - SlotQueueItem.init(request.id, slotIndex, request.ask, request.expiry) + SlotQueueItem.init(request.id, slotIndex, request.ask, request.expiry, collateral) proc init*( - _: type SlotQueueItem, requestId: RequestId, ask: StorageAsk, expiry: uint64 -): seq[SlotQueueItem] = + _: type SlotQueueItem, + requestId: RequestId, + ask: StorageAsk, + expiry: uint64, + collateral: UInt256, +): seq[SlotQueueItem] {.raises: [SlotsOutOfRangeError].} = if not ask.slots.inRange: raise newException(SlotsOutOfRangeError, "Too many slots") var i = 0'u16 proc initSlotQueueItem(): SlotQueueItem = - let item = SlotQueueItem.init(requestId, i, ask, expiry) + let item = SlotQueueItem.init(requestId, i, ask, expiry, collateral) inc i return item @@ -171,8 +176,10 @@ proc init*( Rng.instance.shuffle(items) return items -proc init*(_: type SlotQueueItem, request: StorageRequest): seq[SlotQueueItem] = - return SlotQueueItem.init(request.id, request.ask, request.expiry) +proc init*( + _: type SlotQueueItem, request: StorageRequest, collateral: UInt256 +): seq[SlotQueueItem] = + return SlotQueueItem.init(request.id, request.ask, request.expiry, collateral) proc inRange*(val: SomeUnsignedInt): bool = val.uint16 in SlotQueueSize.low .. SlotQueueSize.high @@ -234,25 +241,7 @@ proc unpause*(self: SlotQueue) = # set unpaused flag to true -- unblocks coroutines waiting on unpaused.wait() self.unpaused.fire() -proc populateItem*( - self: SlotQueue, requestId: RequestId, slotIndex: uint16 -): ?SlotQueueItem = - trace "populate item, items in queue", len = self.queue.len - for item in self.queue.items: - trace "populate item search", itemRequestId = item.requestId, requestId - if item.requestId == requestId: - return some SlotQueueItem( - requestId: requestId, - slotIndex: slotIndex, - slotSize: item.slotSize, - duration: item.duration, - pricePerBytePerSecond: item.pricePerBytePerSecond, - collateralPerByte: item.collateralPerByte, - expiry: item.expiry, - ) - return none SlotQueueItem - -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/downloading.nim b/codex/sales/states/downloading.nim index 39137545..7cf304d3 100644 --- a/codex/sales/states/downloading.nim +++ b/codex/sales/states/downloading.nim @@ -67,8 +67,11 @@ method run*( return await reservations.release(reservation.id, reservation.availabilityId, bytes) try: + let slotId = slotId(request.id, data.slotIndex) + let isRepairing = (await context.market.slotState(slotId)) == SlotState.Repair + trace "Starting download" - if err =? (await onStore(request, data.slotIndex, onBlocks)).errorOption: + if err =? (await onStore(request, data.slotIndex, onBlocks, isRepairing)).errorOption: return some State(SaleErrored(error: err, reprocessSlot: false)) trace "Download complete" diff --git a/codex/sales/states/filling.nim b/codex/sales/states/filling.nim index 0c20a64e..03e2ef2b 100644 --- a/codex/sales/states/filling.nim +++ b/codex/sales/states/filling.nim @@ -38,18 +38,11 @@ method run*( slotIndex = data.slotIndex try: - let slotState = await market.slotState(slotId(data.requestId, data.slotIndex)) - let requestedCollateral = request.ask.collateralPerSlot - var collateral: UInt256 - - if slotState == SlotState.Repair: - # When repairing the node gets "discount" on the collateral that it needs to - let repairRewardPercentage = (await market.repairRewardPercentage).u256 - collateral = - requestedCollateral - - ((requestedCollateral * repairRewardPercentage)).div(100.u256) - else: - collateral = requestedCollateral + without collateral =? await market.slotCollateral(data.requestId, data.slotIndex), + err: + error "Failure attempting to fill slot: unable to calculate collateral", + error = err.msg + return debug "Filling slot" try: diff --git a/tests/codex/helpers/mockmarket.nim b/tests/codex/helpers/mockmarket.nim index 48b20f28..16806cb2 100644 --- a/tests/codex/helpers/mockmarket.nim +++ b/tests/codex/helpers/mockmarket.nim @@ -138,22 +138,35 @@ proc new*(_: type MockMarket, clock: ?Clock = Clock.none): MockMarket = signer: Address.example, config: config, canReserveSlot: true, clock: clock ) +method loadConfig*( + market: MockMarket +): Future[?!void] {.async: (raises: [CancelledError]).} = + discard + method getSigner*(market: MockMarket): Future[Address] {.async.} = return market.signer -method periodicity*(mock: MockMarket): Future[Periodicity] {.async.} = +method periodicity*( + mock: MockMarket +): Future[Periodicity] {.async: (raises: [CancelledError, MarketError]).} = return Periodicity(seconds: mock.config.proofs.period) -method proofTimeout*(market: MockMarket): Future[uint64] {.async.} = +method proofTimeout*( + market: MockMarket +): Future[uint64] {.async: (raises: [CancelledError, MarketError]).} = return market.config.proofs.timeout method requestDurationLimit*(market: MockMarket): Future[uint64] {.async.} = return market.config.requestDurationLimit -method proofDowntime*(market: MockMarket): Future[uint8] {.async.} = +method proofDowntime*( + market: MockMarket +): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} = return market.config.proofs.downtime -method repairRewardPercentage*(market: MockMarket): Future[uint8] {.async.} = +method repairRewardPercentage*( + market: MockMarket +): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} = return market.config.collateral.repairRewardPercentage method getPointer*(market: MockMarket, slotId: SlotId): Future[uint8] {.async.} = @@ -173,7 +186,7 @@ method mySlots*(market: MockMarket): Future[seq[SlotId]] {.async.} = method getRequest*( market: MockMarket, id: RequestId -): Future[?StorageRequest] {.async.} = +): Future[?StorageRequest] {.async: (raises: [CancelledError]).} = for request in market.requested: if request.id == id: return some request @@ -191,10 +204,16 @@ 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: [CancelledError, MarketError]).} = + if slotId notin market.slotState: return SlotState.Free - return market.slotState[slotId] + + try: + return market.slotState[slotId] + except KeyError as e: + raiseAssert "SlotId not found in known slots (MockMarket.slotState)" method getRequestEnd*( market: MockMarket, id: RequestId @@ -534,3 +553,33 @@ method unsubscribe*(subscription: ProofSubmittedSubscription) {.async.} = method unsubscribe*(subscription: SlotReservationsFullSubscription) {.async.} = subscription.market.subscriptions.onSlotReservationsFull.keepItIf(it != subscription) + +method slotCollateral*( + market: MockMarket, requestId: RequestId, slotIndex: uint64 +): Future[?!UInt256] {.async: (raises: [CancelledError]).} = + let slotid = slotId(requestId, slotIndex) + + try: + let state = await slotState(market, slotid) + + without request =? await market.getRequest(requestId): + return failure newException( + MarketError, "Failure calculating the slotCollateral, cannot get the request" + ) + + return market.slotCollateral(request.ask.collateralPerSlot, state) + except MarketError as error: + error "Error when trying to calculate the slotCollateral", error = error.msg + return failure error + +method slotCollateral*( + market: MockMarket, collateralPerSlot: UInt256, slotState: SlotState +): ?!UInt256 {.raises: [].} = + if slotState == SlotState.Repair: + let repairRewardPercentage = market.config.collateral.repairRewardPercentage.u256 + + return success ( + collateralPerSlot - (collateralPerSlot * repairRewardPercentage).div(100.u256) + ) + + return success collateralPerSlot diff --git a/tests/codex/helpers/mockslotqueueitem.nim b/tests/codex/helpers/mockslotqueueitem.nim index 7a1505ec..8657850f 100644 --- a/tests/codex/helpers/mockslotqueueitem.nim +++ b/tests/codex/helpers/mockslotqueueitem.nim @@ -7,7 +7,7 @@ type MockSlotQueueItem* = object slotSize*: uint64 duration*: uint64 pricePerBytePerSecond*: UInt256 - collateralPerByte*: UInt256 + collateral*: UInt256 expiry*: uint64 seen*: bool @@ -19,8 +19,8 @@ proc toSlotQueueItem*(item: MockSlotQueueItem): SlotQueueItem = slotSize: item.slotSize, duration: item.duration, pricePerBytePerSecond: item.pricePerBytePerSecond, - collateralPerByte: item.collateralPerByte, ), expiry = item.expiry, seen = item.seen, + collateral = item.collateral, ) diff --git a/tests/codex/node/testcontracts.nim b/tests/codex/node/testcontracts.nim index 11f4f273..73dd8daf 100644 --- a/tests/codex/node/testcontracts.nim +++ b/tests/codex/node/testcontracts.nim @@ -125,7 +125,7 @@ asyncchecksuite "Test Node - Host contracts": fetchedBytes += blk.data.len.uint return success() - (await onStore(request, 1.uint64, onBlocks)).tryGet() + (await onStore(request, 1.uint64, onBlocks, isRepairing = false)).tryGet() check fetchedBytes == 12 * DefaultBlockSize.uint let indexer = verifiable.protectedStrategy.init( diff --git a/tests/codex/sales/testsales.nim b/tests/codex/sales/testsales.nim index f078cbee..e92f9607 100644 --- a/tests/codex/sales/testsales.nim +++ b/tests/codex/sales/testsales.nim @@ -62,7 +62,7 @@ asyncchecksuite "Sales - start": sales = Sales.new(market, clock, repo) reservations = sales.context.reservations sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = return success() @@ -181,7 +181,7 @@ asyncchecksuite "Sales": sales = Sales.new(market, clock, repo) reservations = sales.context.reservations sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = return success() @@ -229,7 +229,7 @@ asyncchecksuite "Sales": availability = a.get # update id proc notProcessed(itemsProcessed: seq[SlotQueueItem], request: StorageRequest): bool = - let items = SlotQueueItem.init(request) + let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot) for i in 0 ..< items.len: if itemsProcessed.contains(items[i]): return false @@ -266,7 +266,7 @@ asyncchecksuite "Sales": done.complete() createAvailability() await market.requestStorage(request) - let items = SlotQueueItem.init(request) + let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot) check eventually items.allIt(itemsProcessed.contains(it)) test "removes slots from slot queue once RequestCancelled emitted": @@ -287,13 +287,15 @@ asyncchecksuite "Sales": test "removes slot index from slot queue once SlotFilled emitted": let request1 = await addRequestToSaturatedQueue() market.emitSlotFilled(request1.id, 1.uint64) - let expected = SlotQueueItem.init(request1, 1'u16) + let expected = + SlotQueueItem.init(request1, 1'u16, collateral = request1.ask.collateralPerSlot) check always (not itemsProcessed.contains(expected)) test "removes slot index from slot queue once SlotReservationsFull emitted": let request1 = await addRequestToSaturatedQueue() market.emitSlotReservationsFull(request1.id, 1.uint64) - let expected = SlotQueueItem.init(request1, 1'u16) + let expected = + SlotQueueItem.init(request1, 1'u16, collateral = request1.ask.collateralPerSlot) check always (not itemsProcessed.contains(expected)) test "adds slot index to slot queue once SlotFreed emitted": @@ -303,14 +305,21 @@ asyncchecksuite "Sales": createAvailability() market.requested.add request # "contract" must be able to return request + market.emitSlotFreed(request.id, 2.uint64) - let expected = SlotQueueItem.init(request, 2.uint16) + without collateralPerSlot =? await market.slotCollateral(request.id, 2.uint64), + error: + fail() + + let expected = + SlotQueueItem.init(request, 2.uint16, collateral = request.ask.collateralPerSlot) + check eventually itemsProcessed.contains(expected) test "items in queue are readded (and marked seen) once ignored": await market.requestStorage(request) - let items = SlotQueueItem.init(request) + let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot) check eventually queue.len > 0 # queue starts paused, allow items to be added to the queue check eventually queue.paused @@ -331,7 +340,7 @@ asyncchecksuite "Sales": test "queue is paused once availability is insufficient to service slots in queue": createAvailability() # enough to fill a single slot await market.requestStorage(request) - let items = SlotQueueItem.init(request) + let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot) check eventually queue.len > 0 # queue starts paused, allow items to be added to the queue check eventually queue.paused @@ -348,7 +357,7 @@ asyncchecksuite "Sales": test "availability size is reduced by request slot size when fully downloaded": sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = let blk = bt.Block.new(@[1.byte]).get await onBatch(blk.repeat(request.ask.slotSize.int)) @@ -361,7 +370,7 @@ asyncchecksuite "Sales": test "non-downloaded bytes are returned to availability once finished": var slotIndex = 0.uint64 sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = slotIndex = slot let blk = bt.Block.new(@[1.byte]).get @@ -421,7 +430,7 @@ asyncchecksuite "Sales": var storingRequest: StorageRequest var storingSlot: uint64 sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = storingRequest = request storingSlot = slot @@ -434,7 +443,7 @@ asyncchecksuite "Sales": test "makes storage available again when data retrieval fails": let error = newException(IOError, "data retrieval failed") sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = return failure(error) createAvailability() @@ -503,7 +512,7 @@ asyncchecksuite "Sales": test "makes storage available again when other host fills the slot": let otherHost = Address.example sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = await sleepAsync(chronos.hours(1)) return success() @@ -519,7 +528,7 @@ asyncchecksuite "Sales": let origSize = availability.freeSize sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = await sleepAsync(chronos.hours(1)) return success() @@ -544,7 +553,7 @@ asyncchecksuite "Sales": let origSize = availability.freeSize sales.onStore = proc( - request: StorageRequest, slot: uint64, onBatch: BatchProc + request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false ): Future[?!void] {.async.} = await sleepAsync(chronos.hours(1)) return success() diff --git a/tests/codex/sales/testslotqueue.nim b/tests/codex/sales/testslotqueue.nim index 46c35b1c..03c658be 100644 --- a/tests/codex/sales/testslotqueue.nim +++ b/tests/codex/sales/testslotqueue.nim @@ -159,8 +159,10 @@ suite "Slot queue": requestB.ask.collateralPerByte = 1.u256 requestB.expiry = 1000.uint64 - let itemA = SlotQueueItem.init(requestA, 0) - let itemB = SlotQueueItem.init(requestB, 0) + let itemA = + SlotQueueItem.init(requestA, 0, collateral = requestA.ask.collateralPerSlot) + let itemB = + SlotQueueItem.init(requestB, 0, collateral = requestB.ask.collateralPerSlot) check itemB < itemA # B higher priority than A check itemA > itemB @@ -172,7 +174,7 @@ suite "Slot queue": slotSize: 1.uint64, duration: 1.uint64, pricePerBytePerSecond: 2.u256, # profitability is higher (good) - collateralPerByte: 1.u256, + collateral: 1.u256, expiry: 1.uint64, seen: true, # seen (bad), more weight than profitability ) @@ -182,7 +184,7 @@ suite "Slot queue": slotSize: 1.uint64, duration: 1.uint64, pricePerBytePerSecond: 1.u256, # profitability is lower (bad) - collateralPerByte: 1.u256, + collateral: 1.u256, expiry: 1.uint64, seen: false, # not seen (good) ) @@ -197,7 +199,7 @@ suite "Slot queue": slotSize: 1.uint64, duration: 1.uint64, pricePerBytePerSecond: 1.u256, # reward is lower (bad) - collateralPerByte: 1.u256, # collateral is lower (good) + collateral: 1.u256, # collateral is lower (good) expiry: 1.uint64, seen: false, ) @@ -208,7 +210,7 @@ suite "Slot queue": duration: 1.uint64, pricePerBytePerSecond: 2.u256, # reward is higher (good), more weight than collateral - collateralPerByte: 2.u256, # collateral is higher (bad) + collateral: 2.u256, # collateral is higher (bad) expiry: 1.uint64, seen: false, ) @@ -223,7 +225,7 @@ suite "Slot queue": slotSize: 1.uint64, duration: 1.uint64, pricePerBytePerSecond: 1.u256, - collateralPerByte: 2.u256, # collateral is higher (bad) + collateral: 2.u256, # collateral is higher (bad) expiry: 2.uint64, # expiry is longer (good) seen: false, ) @@ -233,7 +235,7 @@ suite "Slot queue": slotSize: 1.uint64, duration: 1.uint64, pricePerBytePerSecond: 1.u256, - collateralPerByte: 1.u256, # collateral is lower (good), more weight than expiry + collateral: 1.u256, # collateral is lower (good), more weight than expiry expiry: 1.uint64, # expiry is shorter (bad) seen: false, ) @@ -248,7 +250,7 @@ suite "Slot queue": slotSize: 1.uint64, # slotSize is smaller (good) duration: 1.uint64, pricePerBytePerSecond: 1.u256, - collateralPerByte: 1.u256, + collateral: 1.u256, expiry: 1.uint64, # expiry is shorter (bad) seen: false, ) @@ -258,7 +260,7 @@ suite "Slot queue": slotSize: 2.uint64, # slotSize is larger (bad) duration: 1.uint64, pricePerBytePerSecond: 1.u256, - collateralPerByte: 1.u256, + collateral: 1.u256, expiry: 2.uint64, # expiry is longer (good), more weight than slotSize seen: false, ) @@ -273,7 +275,7 @@ suite "Slot queue": slotSize: 2.uint64, # slotSize is larger (bad) duration: 1.uint64, pricePerBytePerSecond: 1.u256, - collateralPerByte: 1.u256, + collateral: 1.u256, expiry: 1.uint64, # expiry is shorter (bad) seen: false, ) @@ -283,7 +285,7 @@ suite "Slot queue": slotSize: 1.uint64, # slotSize is smaller (good) duration: 1.uint64, pricePerBytePerSecond: 1.u256, - collateralPerByte: 1.u256, + collateral: 1.u256, expiry: 1.uint64, seen: false, ) @@ -292,11 +294,16 @@ suite "Slot queue": test "expands available all possible slot indices on init": let request = StorageRequest.example - let items = SlotQueueItem.init(request) + let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot) check items.len.uint64 == request.ask.slots var checked = 0 for slotIndex in 0'u16 ..< request.ask.slots.uint16: - check items.anyIt(it == SlotQueueItem.init(request, slotIndex)) + check items.anyIt( + it == + SlotQueueItem.init( + request, slotIndex, collateral = request.ask.collateralPerSlot + ) + ) inc checked check checked == items.len @@ -322,34 +329,17 @@ suite "Slot queue": check isOk queue.push(item3) check isOk queue.push(item4) - test "populates item with exisiting request metadata": - newSlotQueue(maxSize = 8, maxWorkers = 1, processSlotDelay = 10.millis) - let request0 = StorageRequest.example - var request1 = StorageRequest.example - request1.ask.collateralPerByte += 1.u256 - let items0 = SlotQueueItem.init(request0) - let items1 = SlotQueueItem.init(request1) - check queue.push(items0).isOk - check queue.push(items1).isOk - let populated = !queue.populateItem(request1.id, 12'u16) - check populated.requestId == request1.id - check populated.slotIndex == 12'u16 - check populated.slotSize == request1.ask.slotSize - check populated.duration == request1.ask.duration - check populated.pricePerBytePerSecond == request1.ask.pricePerBytePerSecond - check populated.collateralPerByte == request1.ask.collateralPerByte - - test "does not find exisiting request metadata": - newSlotQueue(maxSize = 2, maxWorkers = 2) - let item = SlotQueueItem.example - check queue.populateItem(item.requestId, 12'u16).isNone - test "can support uint16.high slots": var request = StorageRequest.example let maxUInt16 = uint16.high let uint64Slots = uint64(maxUInt16) request.ask.slots = uint64Slots - let items = SlotQueueItem.init(request.id, request.ask, request.expiry) + let items = SlotQueueItem.init( + request.id, + request.ask, + request.expiry, + collateral = request.ask.collateralPerSlot, + ) check items.len.uint16 == maxUInt16 test "cannot support greater than uint16.high slots": @@ -358,7 +348,12 @@ suite "Slot queue": let uint64Slots = uint64(int32Slots) request.ask.slots = uint64Slots expect SlotsOutOfRangeError: - discard SlotQueueItem.init(request.id, request.ask, request.expiry) + discard SlotQueueItem.init( + request.id, + request.ask, + request.expiry, + collateral = request.ask.collateralPerSlot, + ) test "cannot push duplicate items": newSlotQueue(maxSize = 6, maxWorkers = 1, processSlotDelay = 15.millis) @@ -399,8 +394,10 @@ suite "Slot queue": let request0 = StorageRequest.example var request1 = StorageRequest.example request1.ask.collateralPerByte += 1.u256 - let items0 = SlotQueueItem.init(request0) - let items1 = SlotQueueItem.init(request1) + let items0 = + SlotQueueItem.init(request0, collateral = request0.ask.collateralPerSlot) + let items1 = + SlotQueueItem.init(request1, collateral = request1.ask.collateralPerSlot) check queue.push(items0).isOk check queue.push(items1).isOk let last = items1[items1.high] @@ -413,8 +410,10 @@ suite "Slot queue": let request0 = StorageRequest.example var request1 = StorageRequest.example request1.ask.collateralPerByte += 1.u256 - let items0 = SlotQueueItem.init(request0) - let items1 = SlotQueueItem.init(request1) + let items0 = + SlotQueueItem.init(request0, collateral = request0.ask.collateralPerSlot) + let items1 = + SlotQueueItem.init(request1, collateral = request1.ask.collateralPerSlot) check queue.push(items0).isOk check queue.push(items1).isOk queue.delete(request1.id) @@ -433,42 +432,56 @@ suite "Slot queue": request3.ask.collateralPerByte = request2.ask.collateralPerByte + 1 request4.ask.collateralPerByte = request3.ask.collateralPerByte + 1 request5.ask.collateralPerByte = request4.ask.collateralPerByte + 1 - let item0 = SlotQueueItem.init(request0, 0) - let item1 = SlotQueueItem.init(request1, 0) - let item2 = SlotQueueItem.init(request2, 0) - let item3 = SlotQueueItem.init(request3, 0) - let item4 = SlotQueueItem.init(request4, 0) - let item5 = SlotQueueItem.init(request5, 0) + let item0 = + SlotQueueItem.init(request0, 0, collateral = request0.ask.collateralPerSlot) + let item1 = + SlotQueueItem.init(request1, 0, collateral = request1.ask.collateralPerSlot) + let item2 = + SlotQueueItem.init(request2, 0, collateral = request2.ask.collateralPerSlot) + let item3 = + SlotQueueItem.init(request3, 0, collateral = request3.ask.collateralPerSlot) + let item4 = + SlotQueueItem.init(request4, 0, collateral = request4.ask.collateralPerSlot) + let item5 = + SlotQueueItem.init(request5, 0, collateral = request5.ask.collateralPerSlot) check queue.contains(item5) == false check queue.push(@[item0, item1, item2, item3, item4, item5]).isOk check queue.contains(item5) test "sorts items by profitability descending (higher pricePerBytePerSecond == higher priority == goes first in the list)": var request = StorageRequest.example - let item0 = SlotQueueItem.init(request, 0) + let item0 = + SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item1 = SlotQueueItem.init(request, 1) + let item1 = + SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot) check item1 < item0 - test "sorts items by collateral ascending (higher required collateralPerByte = lower priority == comes later in the list)": + test "sorts items by collateral ascending (higher required collateral = lower priority == comes later in the list)": var request = StorageRequest.example - let item0 = SlotQueueItem.init(request, 0) - request.ask.collateralPerByte += 1.u256 - let item1 = SlotQueueItem.init(request, 1) + let item0 = + SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot) + let item1 = SlotQueueItem.init( + request, 1, collateral = request.ask.collateralPerSlot + 1.u256 + ) check item1 > item0 test "sorts items by expiry descending (longer expiry = higher priority)": var request = StorageRequest.example - let item0 = SlotQueueItem.init(request, 0) + let item0 = + SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot) request.expiry += 1 - let item1 = SlotQueueItem.init(request, 1) + let item1 = + SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot) check item1 < item0 test "sorts items by slot size descending (bigger dataset = higher profitability = higher priority)": var request = StorageRequest.example - let item0 = SlotQueueItem.init(request, 0) + let item0 = + SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot) request.ask.slotSize += 1 - let item1 = SlotQueueItem.init(request, 1) + let item1 = + SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot) check item1 < item0 test "should call callback once an item is added": @@ -489,13 +502,17 @@ suite "Slot queue": # sleeping after push allows the slotqueue loop to iterate, # calling the callback for each pushed/updated item var request = StorageRequest.example - let item0 = SlotQueueItem.init(request, 0) + let item0 = + SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item1 = SlotQueueItem.init(request, 1) + let item1 = + SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item2 = SlotQueueItem.init(request, 2) + let item2 = + SlotQueueItem.init(request, 2, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item3 = SlotQueueItem.init(request, 3) + let item3 = + SlotQueueItem.init(request, 3, collateral = request.ask.collateralPerSlot) check queue.push(item0).isOk await sleepAsync(1.millis) @@ -520,13 +537,17 @@ suite "Slot queue": # sleeping after push allows the slotqueue loop to iterate, # calling the callback for each pushed/updated item var request = StorageRequest.example - let item0 = SlotQueueItem.init(request, 0) + let item0 = + SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item1 = SlotQueueItem.init(request, 1) + let item1 = + SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item2 = SlotQueueItem.init(request, 2) + let item2 = + SlotQueueItem.init(request, 2, collateral = request.ask.collateralPerSlot) request.ask.pricePerBytePerSecond += 1.u256 - let item3 = SlotQueueItem.init(request, 3) + let item3 = + SlotQueueItem.init(request, 3, collateral = request.ask.collateralPerSlot) check queue.push(item0).isOk check queue.push(item1).isOk @@ -550,7 +571,7 @@ suite "Slot queue": queue.pause let request = StorageRequest.example - var items = SlotQueueItem.init(request) + var items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot) check queue.push(items).isOk # check all items processed check eventually queue.len == 0 @@ -558,8 +579,14 @@ suite "Slot queue": test "pushing seen item does not unpause queue": newSlotQueue(maxSize = 4, maxWorkers = 4) let request = StorageRequest.example - let item0 = - SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = true) + let item0 = SlotQueueItem.init( + request.id, + 0'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = true, + ) check queue.paused check queue.push(item0).isOk check queue.paused @@ -567,8 +594,14 @@ suite "Slot queue": test "paused queue waits for unpause before continuing processing": newSlotQueue(maxSize = 4, maxWorkers = 4) let request = StorageRequest.example - let item = - SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = false) + let item = SlotQueueItem.init( + request.id, + 1'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = false, + ) check queue.paused # push causes unpause check queue.push(item).isOk @@ -579,10 +612,22 @@ suite "Slot queue": test "processing a 'seen' item pauses the queue": newSlotQueue(maxSize = 4, maxWorkers = 4) let request = StorageRequest.example - let unseen = - SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = false) - let seen = - SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = true) + let unseen = SlotQueueItem.init( + request.id, + 0'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = false, + ) + let seen = SlotQueueItem.init( + request.id, + 1'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = true, + ) # push causes unpause check queue.push(unseen).isSuccess # check all items processed @@ -595,10 +640,22 @@ suite "Slot queue": test "processing a 'seen' item does not decrease the number of workers": newSlotQueue(maxSize = 4, maxWorkers = 4) let request = StorageRequest.example - let unseen = - SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = false) - let seen = - SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = true) + let unseen = SlotQueueItem.init( + request.id, + 0'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = false, + ) + let seen = SlotQueueItem.init( + request.id, + 1'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = true, + ) # push seen item to ensure that queue is pausing check queue.push(seen).isSuccess # unpause and pause a number of times @@ -615,10 +672,22 @@ suite "Slot queue": test "item 'seen' flags can be cleared": newSlotQueue(maxSize = 4, maxWorkers = 1) let request = StorageRequest.example - let item0 = - SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = true) - let item1 = - SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = true) + let item0 = SlotQueueItem.init( + request.id, + 0'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = true, + ) + let item1 = SlotQueueItem.init( + request.id, + 1'u16, + request.ask, + request.expiry, + request.ask.collateralPerSlot, + seen = true, + ) check queue.push(item0).isOk check queue.push(item1).isOk check queue[0].seen diff --git a/tests/contracts/testMarket.nim b/tests/contracts/testMarket.nim index 74d6a65e..068a4d2e 100644 --- a/tests/contracts/testMarket.nim +++ b/tests/contracts/testMarket.nim @@ -598,6 +598,37 @@ ethersuite "On-Chain Market": check endBalanceHost == (startBalanceHost + request.ask.collateralPerSlot) check endBalanceReward == (startBalanceReward + expectedPayout) + test "returns the collateral when the slot is not being repaired": + await market.requestStorage(request) + await market.reserveSlot(request.id, 0.uint64) + await market.fillSlot(request.id, 0.uint64, proof, request.ask.collateralPerSlot) + + let slotId = request.slotId(0.uint64) + without collateral =? await market.slotCollateral(request.id, 0.uint64), error: + 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.uint64) + await market.fillSlot(request.id, 0.uint64, proof, request.ask.collateralPerSlot) + await market.freeSlot(slotId(request.id, 0.uint64)) + + let slotId = request.slotId(0.uint64) + + without collateral =? await market.slotCollateral(request.id, 0.uint64), error: + fail() + + # slotCollateral + # repairRewardPercentage = 10 + # expected collateral = slotCollateral - slotCollateral * 0.1 + check collateral == + request.ask.collateralPerSlot - (request.ask.collateralPerSlot * 10).div(100.u256) + test "the request is added in cache after the fist access": await market.requestStorage(request) diff --git a/tests/examples.nim b/tests/examples.nim index 9b88b4a5..9ef4e292 100644 --- a/tests/examples.nim +++ b/tests/examples.nim @@ -72,7 +72,9 @@ proc example*(_: type Slot): Slot = proc example*(_: type SlotQueueItem): SlotQueueItem = let request = StorageRequest.example let slot = Slot.example - SlotQueueItem.init(request, slot.slotIndex.uint16) + SlotQueueItem.init( + request, slot.slotIndex.uint16, collateral = request.ask.collateralPerSlot + ) proc example(_: type G1Point): G1Point = G1Point(x: UInt256.example, y: UInt256.example)