diff --git a/codex/contracts/market.nim b/codex/contracts/market.nim index 75118aba..35557050 100644 --- a/codex/contracts/market.nim +++ b/codex/contracts/market.nim @@ -106,7 +106,7 @@ method mySlots*(market: OnChainMarket): Future[seq[SlotId]] {.async.} = method requestStorage(market: OnChainMarket, request: StorageRequest) {.async.} = convertEthersError: debug "Requesting storage" - await market.approveFunds(request.price()) + await market.approveFunds(request.totalPrice()) discard await market.contract.requestStorage(request).confirm(1) method getRequest*( @@ -156,6 +156,12 @@ method getHost( else: return none Address +method currentCollateral*( + market: OnChainMarket, slotId: SlotId +): Future[UInt256] {.async.} = + convertEthersError: + return await market.contract.currentCollateral(slotId) + method getActiveSlot*(market: OnChainMarket, slotId: SlotId): Future[?Slot] {.async.} = convertEthersError: try: diff --git a/codex/contracts/marketplace.nim b/codex/contracts/marketplace.nim index e7ed0e96..cdcb4238 100644 --- a/codex/contracts/marketplace.nim +++ b/codex/contracts/marketplace.nim @@ -48,6 +48,10 @@ type proc configuration*(marketplace: Marketplace): MarketplaceConfig {.contract, view.} proc token*(marketplace: Marketplace): Address {.contract, view.} +proc currentCollateral*( + marketplace: Marketplace, id: SlotId +): UInt256 {.contract, view.} + proc slashMisses*(marketplace: Marketplace): UInt256 {.contract, view.} proc slashPercentage*(marketplace: Marketplace): UInt256 {.contract, view.} proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view.} diff --git a/codex/contracts/requests.nim b/codex/contracts/requests.nim index 840f785e..48947602 100644 --- a/codex/contracts/requests.nim +++ b/codex/contracts/requests.nim @@ -24,8 +24,8 @@ type slotSize* {.serialize.}: UInt256 duration* {.serialize.}: UInt256 proofProbability* {.serialize.}: UInt256 - reward* {.serialize.}: UInt256 - collateral* {.serialize.}: UInt256 + pricePerBytePerSecond* {.serialize.}: UInt256 + collateralPerByte* {.serialize.}: UInt256 maxSlotLoss* {.serialize.}: uint64 StorageContent* = object @@ -112,8 +112,8 @@ func fromTuple(_: type StorageAsk, tupl: tuple): StorageAsk = slotSize: tupl[1], duration: tupl[2], proofProbability: tupl[3], - reward: tupl[4], - collateral: tupl[5], + pricePerBytePerSecond: tupl[4], + collateralPerByte: tupl[5], maxSlotLoss: tupl[6], ) @@ -174,14 +174,20 @@ func slotId*(request: StorageRequest, slotIndex: UInt256): SlotId = func id*(slot: Slot): SlotId = slotId(slot.request, slot.slotIndex) -func pricePerSlot*(ask: StorageAsk): UInt256 = - ask.duration * ask.reward +func pricePerSlotPerSecond*(ask: StorageAsk): UInt256 = + ask.pricePerBytePerSecond * ask.slotSize -func price*(ask: StorageAsk): UInt256 = +func pricePerSlot*(ask: StorageAsk): UInt256 = + ask.duration * ask.pricePerSlotPerSecond + +func totalPrice*(ask: StorageAsk): UInt256 = ask.slots.u256 * ask.pricePerSlot -func price*(request: StorageRequest): UInt256 = - request.ask.price +func totalPrice*(request: StorageRequest): UInt256 = + request.ask.totalPrice + +func collateralPerSlot*(ask: StorageAsk): UInt256 = + ask.collateralPerByte * ask.slotSize func size*(ask: StorageAsk): UInt256 = ask.slots.u256 * ask.slotSize diff --git a/codex/market.nim b/codex/market.nim index 4c8e459c..bc325cd9 100644 --- a/codex/market.nim +++ b/codex/market.nim @@ -126,6 +126,11 @@ method getHost*( ): Future[?Address] {.base, async.} = raiseAssert("not implemented") +method currentCollateral*( + market: Market, slotId: SlotId +): Future[UInt256] {.base, async.} = + raiseAssert("not implemented") + method getActiveSlot*(market: Market, slotId: SlotId): Future[?Slot] {.base, async.} = raiseAssert("not implemented") diff --git a/codex/node.nim b/codex/node.nim index 543c1c1a..ee2a2b46 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -373,8 +373,8 @@ proc setupRequest( proofProbability: UInt256, nodes: uint, tolerance: uint, - reward: UInt256, - collateral: UInt256, + pricePerBytePerSecond: UInt256, + collateralPerByte: UInt256, expiry: UInt256, ): Future[?!StorageRequest] {.async.} = ## Setup slots for a given dataset @@ -389,9 +389,9 @@ proc setupRequest( duration = duration nodes = nodes tolerance = tolerance - reward = reward + pricePerBytePerSecond = pricePerBytePerSecond proofProbability = proofProbability - collateral = collateral + collateralPerByte = collateralPerByte expiry = expiry ecK = ecK ecM = ecM @@ -435,8 +435,8 @@ proc setupRequest( slotSize: builder.slotBytes.uint.u256, duration: duration, proofProbability: proofProbability, - reward: reward, - collateral: collateral, + pricePerBytePerSecond: pricePerBytePerSecond, + collateralPerByte: collateralPerByte, maxSlotLoss: tolerance, ), content: StorageContent( @@ -456,8 +456,8 @@ proc requestStorage*( proofProbability: UInt256, nodes: uint, tolerance: uint, - reward: UInt256, - collateral: UInt256, + pricePerBytePerSecond: UInt256, + collateralPerByte: UInt256, expiry: UInt256, ): Future[?!PurchaseId] {.async.} = ## Initiate a request for storage sequence, this might @@ -469,9 +469,9 @@ proc requestStorage*( duration = duration nodes = nodes tolerance = tolerance - reward = reward + pricePerBytePerSecond = pricePerBytePerSecond proofProbability = proofProbability - collateral = collateral + collateralPerByte = collateralPerByte expiry = expiry.truncate(int64) now = self.clock.now @@ -483,7 +483,8 @@ proc requestStorage*( without request =? ( await self.setupRequest( - cid, duration, proofProbability, nodes, tolerance, reward, collateral, expiry + cid, duration, proofProbability, nodes, tolerance, pricePerBytePerSecond, + collateralPerByte, expiry, ) ), err: trace "Unable to setup request" diff --git a/codex/rest/api.nim b/codex/rest/api.nim index 9566df92..134aa8d2 100644 --- a/codex/rest/api.nim +++ b/codex/rest/api.nim @@ -403,12 +403,15 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) = router.rawApi(MethodPost, "/api/codex/v1/sales/availability") do() -> RestApiResponse: ## Add available storage to sell. - ## Every time Availability's offer finishes, its capacity is returned to the availability. + ## Every time Availability's offer finishes, its capacity is + ## returned to the availability. ## - ## totalSize - size of available storage in bytes - ## duration - maximum time the storage should be sold for (in seconds) - ## minPrice - minimal price paid (in amount of tokens) for the whole hosted request's slot for the request's duration - ## maxCollateral - maximum collateral user is willing to pay per filled Slot (in amount of tokens) + ## totalSize - size of available storage in bytes + ## duration - maximum time the storage should be sold for (in seconds) + ## minPricePerBytePerSecond - minimal price per byte paid (in amount of + ## tokens) to be matched against the request's pricePerBytePerSecond + ## totalCollateral - total collateral (in amount of + ## tokens) that can be distributed among matching requests var headers = buildCorsHeaders("POST", allowedOrigin) @@ -436,7 +439,8 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) = without availability =? ( await reservations.createAvailability( - restAv.totalSize, restAv.duration, restAv.minPrice, restAv.maxCollateral + restAv.totalSize, restAv.duration, restAv.minPricePerBytePerSecond, + restAv.totalCollateral, ) ), error: return RestApiResponse.error(Http500, error.msg, headers = headers) @@ -467,10 +471,14 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) = ## The new parameters will be only considered for new requests. ## Existing Requests linked to this Availability will continue as is. ## - ## totalSize - size of available storage in bytes. When decreasing the size, then lower limit is the currently `totalSize - freeSize`. - ## duration - maximum time the storage should be sold for (in seconds) - ## minPrice - minimum price to be paid (in amount of tokens) - ## maxCollateral - maximum collateral user is willing to pay per filled Slot (in amount of tokens) + ## totalSize - size of available storage in bytes. + ## When decreasing the size, then lower limit is + ## the currently `totalSize - freeSize`. + ## duration - maximum time the storage should be sold for (in seconds) + ## minPricePerBytePerSecond - minimal price per byte paid (in amount of + ## tokens) to be matched against the request's pricePerBytePerSecond + ## totalCollateral - total collateral (in amount of + ## tokens) that can be distributed among matching requests try: without contracts =? node.contracts.host: return RestApiResponse.error(Http503, "Persistence is not enabled") @@ -512,11 +520,11 @@ proc initSalesApi(node: CodexNodeRef, router: var RestRouter) = if duration =? restAv.duration: availability.duration = duration - if minPrice =? restAv.minPrice: - availability.minPrice = minPrice + if minPricePerBytePerSecond =? restAv.minPricePerBytePerSecond: + availability.minPricePerBytePerSecond = minPricePerBytePerSecond - if maxCollateral =? restAv.maxCollateral: - availability.maxCollateral = maxCollateral + if totalCollateral =? restAv.totalCollateral: + availability.totalCollateral = totalCollateral if err =? (await reservations.update(availability)).errorOption: return RestApiResponse.error(Http500, err.msg) @@ -580,11 +588,11 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) = ## cid - the cid of a previously uploaded dataset ## duration - the duration of the request in seconds ## proofProbability - how often storage proofs are required - ## reward - the maximum amount of tokens paid per second per slot to hosts the client is willing to pay + ## pricePerBytePerSecond - the amount of tokens paid per byte per second to hosts the client is willing to pay ## expiry - specifies threshold in seconds from now when the request expires if the Request does not find requested amount of nodes to host the data ## nodes - number of nodes the content should be stored on ## tolerance - allowed number of nodes that can be lost before content is lost - ## colateral - requested collateral from hosts when they fill slot + ## colateralPerByte - requested collateral per byte from hosts when they fill slot try: without contracts =? node.contracts.client: return RestApiResponse.error( @@ -639,7 +647,7 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) = without purchaseId =? await node.requestStorage( cid, params.duration, params.proofProbability, nodes, tolerance, - params.reward, params.collateral, expiry, + params.pricePerBytePerSecond, params.collateralPerByte, expiry, ), error: if error of InsufficientBlocksError: return RestApiResponse.error( diff --git a/codex/rest/json.nim b/codex/rest/json.nim index 021cb196..9bc7664e 100644 --- a/codex/rest/json.nim +++ b/codex/rest/json.nim @@ -15,8 +15,8 @@ type StorageRequestParams* = object duration* {.serialize.}: UInt256 proofProbability* {.serialize.}: UInt256 - reward* {.serialize.}: UInt256 - collateral* {.serialize.}: UInt256 + pricePerBytePerSecond* {.serialize.}: UInt256 + collateralPerByte* {.serialize.}: UInt256 expiry* {.serialize.}: ?UInt256 nodes* {.serialize.}: ?uint tolerance* {.serialize.}: ?uint @@ -30,8 +30,8 @@ type RestAvailability* = object totalSize* {.serialize.}: UInt256 duration* {.serialize.}: UInt256 - minPrice* {.serialize.}: UInt256 - maxCollateral* {.serialize.}: UInt256 + minPricePerBytePerSecond* {.serialize.}: UInt256 + totalCollateral* {.serialize.}: UInt256 freeSize* {.serialize.}: ?UInt256 RestSalesAgent* = object diff --git a/codex/sales.nim b/codex/sales.nim index 8b113bca..4bf2d13c 100644 --- a/codex/sales.nim +++ b/codex/sales.nim @@ -115,6 +115,7 @@ proc cleanUp( agent: SalesAgent, returnBytes: bool, reprocessSlot: bool, + returnedCollateral: ?UInt256, processing: Future[void], ) {.async.} = let data = agent.data @@ -144,7 +145,7 @@ proc cleanUp( if reservation =? data.reservation and deleteErr =? ( await sales.context.reservations.deleteReservation( - reservation.id, reservation.availabilityId + reservation.id, reservation.availabilityId, returnedCollateral ) ).errorOption: error "failure deleting reservation", error = deleteErr.msg @@ -187,8 +188,10 @@ proc processSlot(sales: Sales, item: SlotQueueItem, done: Future[void]) = sales.context, item.requestId, item.slotIndex.u256, none StorageRequest ) - agent.onCleanUp = proc(returnBytes = false, reprocessSlot = false) {.async.} = - await sales.cleanUp(agent, returnBytes, reprocessSlot, done) + agent.onCleanUp = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ) {.async.} = + await sales.cleanUp(agent, returnBytes, reprocessSlot, returnedCollateral, done) agent.onFilled = some proc(request: StorageRequest, slotIndex: UInt256) = sales.filled(request, slotIndex, done) @@ -253,11 +256,13 @@ proc load*(sales: Sales) {.async.} = let agent = newSalesAgent(sales.context, slot.request.id, slot.slotIndex, some slot.request) - agent.onCleanUp = proc(returnBytes = false, reprocessSlot = false) {.async.} = + agent.onCleanUp = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ) {.async.} = # since workers are not being dispatched, this future has not been created # by a worker. Create a dummy one here so we can call sales.cleanUp let done: Future[void] = nil - await sales.cleanUp(agent, returnBytes, reprocessSlot, done) + await sales.cleanUp(agent, returnBytes, reprocessSlot, returnedCollateral, done) # There is no need to assign agent.onFilled as slots loaded from `mySlots` # are inherently already filled and so assigning agent.onFilled would be diff --git a/codex/sales/reservations.nim b/codex/sales/reservations.nim index 6325a3b2..4f48e057 100644 --- a/codex/sales/reservations.nim +++ b/codex/sales/reservations.nim @@ -7,23 +7,25 @@ ## This file may not be copied, modified, or distributed except according to ## those terms. ## -## +--------------------------------------+ -## | RESERVATION | -## +----------------------------------------+ |--------------------------------------| -## | AVAILABILITY | | ReservationId | id | PK | -## |----------------------------------------| |--------------------------------------| -## | AvailabilityId | id | PK |<-||-------o<-| AvailabilityId | availabilityId | FK | -## |----------------------------------------| |--------------------------------------| -## | UInt256 | totalSize | | | UInt256 | size | | -## |----------------------------------------| |--------------------------------------| -## | UInt256 | freeSize | | | UInt256 | slotIndex | | -## |----------------------------------------| +--------------------------------------+ -## | UInt256 | duration | | -## |----------------------------------------| -## | UInt256 | minPrice | | -## |----------------------------------------| -## | UInt256 | maxCollateral | | -## +----------------------------------------+ +## +--------------------------------------+ +## | RESERVATION | +## +---------------------------------------------------+ |--------------------------------------| +## | AVAILABILITY | | ReservationId | id | PK | +## |---------------------------------------------------| |--------------------------------------| +## | AvailabilityId | id | PK |<-||-------o<-| AvailabilityId | availabilityId | FK | +## |---------------------------------------------------| |--------------------------------------| +## | UInt256 | totalSize | | | UInt256 | size | | +## |---------------------------------------------------| |--------------------------------------| +## | UInt256 | freeSize | | | UInt256 | slotIndex | | +## |---------------------------------------------------| +--------------------------------------+ +## | UInt256 | duration | | +## |---------------------------------------------------| +## | UInt256 | minPricePerBytePerSecond | | +## |---------------------------------------------------| +## | UInt256 | totalCollateral | | +## |---------------------------------------------------| +## | UInt256 | totalRemainingCollateral | | +## +---------------------------------------------------+ import pkg/upraises push: @@ -65,9 +67,9 @@ type totalSize* {.serialize.}: UInt256 freeSize* {.serialize.}: UInt256 duration* {.serialize.}: UInt256 - minPrice* {.serialize.}: UInt256 - # minimal price paid for the whole hosted slot for the request's duration - maxCollateral* {.serialize.}: UInt256 + minPricePerBytePerSecond* {.serialize.}: UInt256 + totalCollateral {.serialize.}: UInt256 + totalRemainingCollateral* {.serialize.}: UInt256 Reservation* = ref object id* {.serialize.}: ReservationId @@ -124,8 +126,8 @@ proc init*( totalSize: UInt256, freeSize: UInt256, duration: UInt256, - minPrice: UInt256, - maxCollateral: UInt256, + minPricePerBytePerSecond: UInt256, + totalCollateral: UInt256, ): Availability = var id: array[32, byte] doAssert randomBytes(id) == 32 @@ -134,10 +136,18 @@ proc init*( totalSize: totalSize, freeSize: freeSize, duration: duration, - minPrice: minPrice, - maxCollateral: maxCollateral, + minPricePerBytePerSecond: minPricePerBytePerSecond, + totalCollateral: totalCollateral, + totalRemainingCollateral: totalCollateral, ) +func totalCollateral*(self: Availability): UInt256 {.inline.} = + return self.totalCollateral + +proc `totalCollateral=`*(self: Availability, value: UInt256) {.inline.} = + self.totalCollateral = value + self.totalRemainingCollateral = value + proc init*( _: type Reservation, availabilityId: AvailabilityId, @@ -195,6 +205,9 @@ func key*(reservationId: ReservationId, availabilityId: AvailabilityId): ?!Key = func key*(availability: Availability): ?!Key = return availability.id.key +func maxCollateralPerByte*(availability: Availability): UInt256 = + return availability.totalRemainingCollateral div availability.freeSize + func key*(reservation: Reservation): ?!Key = return key(reservation.id, reservation.availabilityId) @@ -328,7 +341,10 @@ proc delete(self: Reservations, key: Key): Future[?!void] {.async.} = return success() proc deleteReservation*( - self: Reservations, reservationId: ReservationId, availabilityId: AvailabilityId + self: Reservations, + reservationId: ReservationId, + availabilityId: AvailabilityId, + returnedCollateral: ?UInt256 = UInt256.none, ): Future[?!void] {.async.} = logScope: reservationId @@ -357,6 +373,9 @@ proc deleteReservation*( availability.freeSize += reservation.size + if collateral =? returnedCollateral: + availability.totalRemainingCollateral += collateral + if updateErr =? (await self.updateAvailability(availability)).errorOption: return failure(updateErr) @@ -372,12 +391,14 @@ proc createAvailability*( self: Reservations, size: UInt256, duration: UInt256, - minPrice: UInt256, - maxCollateral: UInt256, + minPricePerBytePerSecond: UInt256, + totalCollateral: UInt256, ): Future[?!Availability] {.async.} = - trace "creating availability", size, duration, minPrice, maxCollateral + trace "creating availability", + size, duration, minPricePerBytePerSecond, totalCollateral - let availability = Availability.init(size, size, duration, minPrice, maxCollateral) + let availability = + Availability.init(size, size, duration, minPricePerBytePerSecond, totalCollateral) let bytes = availability.freeSize.truncate(uint) if reserveErr =? (await self.repo.reserve(bytes.NBytes)).errorOption: @@ -400,6 +421,7 @@ method createReservation*( slotSize: UInt256, requestId: RequestId, slotIndex: UInt256, + collateralPerByte: UInt256, ): Future[?!Reservation] {.async, base.} = withLock(self.availabilityLock): without availabilityKey =? availabilityId.key, error: @@ -412,7 +434,7 @@ method createReservation*( if availability.freeSize < slotSize: let error = newException( BytesOutOfBoundsError, - "trying to reserve an amount of bytes that is greater than the total size of the Availability", + "trying to reserve an amount of bytes that is greater than the free size of the Availability", ) return failure(error) @@ -427,6 +449,9 @@ method createReservation*( # the newly created Reservation availability.freeSize -= slotSize + # adjust the remaining totalRemainingCollateral + availability.totalRemainingCollateral -= slotSize * collateralPerByte + # update availability with reduced size trace "Updating availability with reduced size" if updateErr =? (await self.updateAvailability(availability)).errorOption: @@ -617,7 +642,8 @@ proc all*( return await self.allImpl(T, key) proc findAvailability*( - self: Reservations, size, duration, minPrice, collateral: UInt256 + self: Reservations, + size, duration, pricePerBytePerSecond, collateralPerByte: UInt256, ): Future[?Availability] {.async.} = without storables =? (await self.storables(Availability)), e: error "failed to get all storables", error = e.msg @@ -626,17 +652,18 @@ 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 - collateral <= availability.maxCollateral and minPrice >= availability.minPrice: + collateralPerByte <= availability.maxCollateralPerByte and + pricePerBytePerSecond >= availability.minPricePerBytePerSecond: trace "availability matched", id = availability.id, size, availFreeSize = availability.freeSize, duration, availDuration = availability.duration, - minPrice, - availMinPrice = availability.minPrice, - collateral, - availMaxCollateral = availability.maxCollateral + pricePerBytePerSecond, + availMinPricePerBytePerSecond = availability.minPricePerBytePerSecond, + collateralPerByte, + availMaxCollateralPerByte = availability.maxCollateralPerByte # TODO: As soon as we're on ARC-ORC, we can use destructors # to automatically dispose our iterators when they fall out of scope. @@ -652,7 +679,7 @@ proc findAvailability*( availFreeSize = availability.freeSize, duration, availDuration = availability.duration, - minPrice, - availMinPrice = availability.minPrice, - collateral, - availMaxCollateral = availability.maxCollateral + pricePerBytePerSecond, + availMinPricePerBytePerSecond = availability.minPricePerBytePerSecond, + collateralPerByte, + availMaxCollateralPerByte = availability.maxCollateralPerByte diff --git a/codex/sales/salesagent.nim b/codex/sales/salesagent.nim index 8b8c44ea..8a8e5dc0 100644 --- a/codex/sales/salesagent.nim +++ b/codex/sales/salesagent.nim @@ -25,9 +25,9 @@ type onCleanUp*: OnCleanUp onFilled*: ?OnFilled - OnCleanUp* = proc(returnBytes = false, reprocessSlot = false): Future[void] {. - gcsafe, upraises: [] - .} + OnCleanUp* = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ): Future[void] {.gcsafe, upraises: [].} OnFilled* = proc(request: StorageRequest, slotIndex: UInt256) {.gcsafe, upraises: [].} SalesAgentError = object of CodexError diff --git a/codex/sales/slotqueue.nim b/codex/sales/slotqueue.nim index bfa1491a..332ec9e0 100644 --- a/codex/sales/slotqueue.nim +++ b/codex/sales/slotqueue.nim @@ -32,8 +32,8 @@ type slotIndex: uint16 slotSize: UInt256 duration: UInt256 - reward: UInt256 - collateral: UInt256 + pricePerBytePerSecond: UInt256 + collateralPerByte: UInt256 expiry: UInt256 seen: bool @@ -70,12 +70,14 @@ const DefaultMaxSize = 128'u16 proc profitability(item: SlotQueueItem): UInt256 = StorageAsk( - collateral: item.collateral, duration: item.duration, - reward: item.reward, + pricePerBytePerSecond: item.pricePerBytePerSecond, 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. @@ -92,15 +94,12 @@ proc `<`*(a, b: SlotQueueItem): bool = scoreA.addIf(a.profitability > b.profitability, 3) scoreB.addIf(a.profitability < b.profitability, 3) - scoreA.addIf(a.collateral < b.collateral, 2) - scoreB.addIf(a.collateral > b.collateral, 2) + scoreA.addIf(a.collateralPerSlot < b.collateralPerSlot, 2) + scoreB.addIf(a.collateralPerSlot > b.collateralPerSlot, 2) scoreA.addIf(a.expiry > b.expiry, 1) scoreB.addIf(a.expiry < b.expiry, 1) - scoreA.addIf(a.slotSize < b.slotSize, 0) - scoreB.addIf(a.slotSize > b.slotSize, 0) - return scoreA > scoreB proc `==`*(a, b: SlotQueueItem): bool = @@ -144,8 +143,8 @@ proc init*( slotIndex: slotIndex, slotSize: ask.slotSize, duration: ask.duration, - reward: ask.reward, - collateral: ask.collateral, + pricePerBytePerSecond: ask.pricePerBytePerSecond, + collateralPerByte: ask.collateralPerByte, expiry: expiry, seen: seen, ) @@ -189,11 +188,11 @@ proc slotSize*(self: SlotQueueItem): UInt256 = proc duration*(self: SlotQueueItem): UInt256 = self.duration -proc reward*(self: SlotQueueItem): UInt256 = - self.reward +proc pricePerBytePerSecond*(self: SlotQueueItem): UInt256 = + self.pricePerBytePerSecond -proc collateral*(self: SlotQueueItem): UInt256 = - self.collateral +proc collateralPerByte*(self: SlotQueueItem): UInt256 = + self.collateralPerByte proc seen*(self: SlotQueueItem): bool = self.seen @@ -246,8 +245,8 @@ proc populateItem*( slotIndex: slotIndex, slotSize: item.slotSize, duration: item.duration, - reward: item.reward, - collateral: item.collateral, + pricePerBytePerSecond: item.pricePerBytePerSecond, + collateralPerByte: item.collateralPerByte, expiry: item.expiry, ) return none SlotQueueItem diff --git a/codex/sales/states/cancelled.nim b/codex/sales/states/cancelled.nim index 1f1026a3..3bb92a2c 100644 --- a/codex/sales/states/cancelled.nim +++ b/codex/sales/states/cancelled.nim @@ -22,13 +22,18 @@ method run*(state: SaleCancelled, machine: Machine): Future[?State] {.async.} = let slot = Slot(request: request, slotIndex: data.slotIndex) debug "Collecting collateral and partial payout", requestId = data.requestId, slotIndex = data.slotIndex + let currentCollateral = await market.currentCollateral(slot.id) await market.freeSlot(slot.id) if onClear =? agent.context.onClear and request =? data.request: onClear(request, data.slotIndex) if onCleanUp =? agent.onCleanUp: - await onCleanUp(returnBytes = true, reprocessSlot = false) + await onCleanUp( + returnBytes = true, + reprocessSlot = false, + returnedCollateral = some currentCollateral, + ) warn "Sale cancelled due to timeout", requestId = data.requestId, slotIndex = data.slotIndex diff --git a/codex/sales/states/filling.nim b/codex/sales/states/filling.nim index a49dbb43..1934fc12 100644 --- a/codex/sales/states/filling.nim +++ b/codex/sales/states/filling.nim @@ -28,7 +28,7 @@ method onFailed*(state: SaleFilling, request: StorageRequest): ?State = method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} = let data = SalesAgent(machine).data let market = SalesAgent(machine).context.market - without (fullCollateral =? data.request .? ask .? collateral): + without (request =? data.request): raiseAssert "Request not set" logScope: @@ -36,15 +36,17 @@ method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} = slotIndex = data.slotIndex 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 = - fullCollateral - ((fullCollateral * repairRewardPercentage)).div(100.u256) + requestedCollateral - + ((requestedCollateral * repairRewardPercentage)).div(100.u256) else: - collateral = fullCollateral + collateral = requestedCollateral debug "Filling slot" try: diff --git a/codex/sales/states/finished.nim b/codex/sales/states/finished.nim index 6fcd0cc0..151300d0 100644 --- a/codex/sales/states/finished.nim +++ b/codex/sales/states/finished.nim @@ -11,6 +11,7 @@ logScope: topics = "marketplace sales finished" type SaleFinished* = ref object of ErrorHandlingState + returnedCollateral*: ?UInt256 method `$`*(state: SaleFinished): string = "SaleFinished" @@ -32,4 +33,4 @@ method run*(state: SaleFinished, machine: Machine): Future[?State] {.async.} = requestId = data.requestId, slotIndex = data.slotIndex if onCleanUp =? agent.onCleanUp: - await onCleanUp() + await onCleanUp(returnedCollateral = state.returnedCollateral) diff --git a/codex/sales/states/payout.nim b/codex/sales/states/payout.nim index f8312a4f..9ce36613 100644 --- a/codex/sales/states/payout.nim +++ b/codex/sales/states/payout.nim @@ -21,7 +21,7 @@ method onCancelled*(state: SalePayout, request: StorageRequest): ?State = method onFailed*(state: SalePayout, request: StorageRequest): ?State = return some State(SaleFailed()) -method run(state: SalePayout, machine: Machine): Future[?State] {.async.} = +method run*(state: SalePayout, machine: Machine): Future[?State] {.async.} = let data = SalesAgent(machine).data let market = SalesAgent(machine).context.market @@ -31,6 +31,7 @@ method run(state: SalePayout, machine: Machine): Future[?State] {.async.} = let slot = Slot(request: request, slotIndex: data.slotIndex) debug "Collecting finished slot's reward", requestId = data.requestId, slotIndex = data.slotIndex + let currentCollateral = await market.currentCollateral(slot.id) await market.freeSlot(slot.id) - return some State(SaleFinished()) + return some State(SaleFinished(returnedCollateral: some currentCollateral)) diff --git a/codex/sales/states/preparing.nim b/codex/sales/states/preparing.nim index 0b34f640..bdde1249 100644 --- a/codex/sales/states/preparing.nim +++ b/codex/sales/states/preparing.nim @@ -62,15 +62,13 @@ method run*(state: SalePreparing, machine: Machine): Future[?State] {.async.} = slotIndex = data.slotIndex slotSize = request.ask.slotSize duration = request.ask.duration - pricePerSlot = request.ask.pricePerSlot + pricePerBytePerSecond = request.ask.pricePerBytePerSecond + collateralPerByte = request.ask.collateralPerByte - # 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 - # changed since being added (other slots may have been processed in that time) without availability =? await reservations.findAvailability( - request.ask.slotSize, request.ask.duration, request.ask.pricePerSlot, - request.ask.collateral, + request.ask.slotSize, request.ask.duration, request.ask.pricePerBytePerSecond, + request.ask.collateralPerByte, ): debug "No availability found for request, ignoring" @@ -80,7 +78,8 @@ method run*(state: SalePreparing, machine: Machine): Future[?State] {.async.} = without reservation =? await reservations.createReservation( - availability.id, request.ask.slotSize, request.id, data.slotIndex + availability.id, request.ask.slotSize, request.id, data.slotIndex, + request.ask.collateralPerByte, ), error: trace "Creation of reservation failed" # Race condition: diff --git a/openapi.yaml b/openapi.yaml index c38c916f..9d401e8f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6,7 +6,7 @@ info: description: "List of endpoints and interfaces available to Codex API users" security: - - { } + - {} components: schemas: @@ -50,9 +50,9 @@ components: type: string description: Address of Ethereum address - Reward: + PricePerBytePerSecond: type: string - description: The maximum amount of tokens paid per second per slot to hosts the client is willing to pay + description: The amount of tokens paid per byte per second per slot to hosts the client is willing to pay Duration: type: string @@ -157,12 +157,12 @@ components: description: Total size of availability's storage in bytes as decimal string duration: $ref: "#/components/schemas/Duration" - minPrice: + minPricePerBytePerSecond: type: string - description: Minimal price paid (in amount of tokens) for the whole hosted request's slot for the request's duration as decimal string - maxCollateral: + description: Minimal price per byte per second paid (in amount of tokens) for the hosted request's slot for the request's duration as decimal string + totalCollateral: type: string - description: Maximum collateral user is willing to pay per filled Slot (in amount of tokens) as decimal string + description: Total collateral (in amount of tokens) that can be used for matching requests SalesAvailabilityREAD: allOf: @@ -178,8 +178,8 @@ components: - $ref: "#/components/schemas/SalesAvailability" - required: - totalSize - - minPrice - - maxCollateral + - minPricePerBytePerSecond + - totalCollateral - duration Slot: @@ -243,16 +243,16 @@ components: StorageRequestCreation: type: object required: - - reward + - pricePerBytePerSecond - duration - proofProbability - - collateral + - collateralPerByte - expiry properties: duration: $ref: "#/components/schemas/Duration" - reward: - $ref: "#/components/schemas/Reward" + pricePerBytePerSecond: + $ref: "#/components/schemas/PricePerBytePerSecond" proofProbability: $ref: "#/components/schemas/ProofProbability" nodes: @@ -263,16 +263,16 @@ components: description: Additional number of nodes on top of the `nodes` property that can be lost before pronouncing the content lost type: integer default: 0 - collateral: + collateralPerByte: type: string - description: Number as decimal string that represents how much collateral is asked from hosts that wants to fill a slots + description: Number as decimal string that represents how much collateral per byte is asked from hosts that wants to fill a slots expiry: type: string description: Number as decimal string that represents expiry threshold in seconds from when the Request is submitted. When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided. The number of seconds can not be higher then the Request's duration itself. StorageAsk: type: object required: - - reward + - pricePerBytePerSecond properties: slots: description: Number of slots (eq. hosts) that the Request want to have the content spread over @@ -284,8 +284,8 @@ components: $ref: "#/components/schemas/Duration" proofProbability: $ref: "#/components/schemas/ProofProbability" - reward: - $ref: "#/components/schemas/Reward" + pricePerBytePerSecond: + $ref: "#/components/schemas/PricePerBytePerSecond" maxSlotLoss: type: integer description: Max slots that can be lost without data considered to be lost @@ -418,14 +418,14 @@ paths: description: | If `addrs` param is supplied, it will be used to dial the peer, otherwise the `peerId` is used to invoke peer discovery, if it succeeds the returned addresses will be used to dial. - tags: [ Node ] + tags: [Node] operationId: connectPeer parameters: - in: path name: peerId required: true schema: - $ref: "#/components/schemas/PeerId" + $ref: "#/components/schemas/PeerId" description: Peer that should be dialed. - in: query name: addrs @@ -448,7 +448,7 @@ paths: "/data": get: summary: "Lists manifest CIDs stored locally in node." - tags: [ Data ] + tags: [Data] operationId: listData responses: "200": @@ -468,7 +468,7 @@ paths: description: Well it was bad-bad post: summary: "Upload a file in a streaming manner. Once finished, the file is stored in the node and can be retrieved by any node in the network using the returned CID." - tags: [ Data ] + tags: [Data] operationId: upload parameters: - name: content-type @@ -484,7 +484,7 @@ paths: description: The content disposition used to send the filename. schema: type: string - example: "attachment; filename=\"codex.png\"" + example: 'attachment; filename="codex.png"' requestBody: content: application/octet-stream: @@ -504,14 +504,14 @@ paths: "/data/{cid}": get: summary: "Download a file from the local node in a streaming manner. If the file is not available locally, a 404 is returned." - tags: [ Data ] + tags: [Data] operationId: downloadLocal parameters: - in: path name: cid required: true schema: - $ref: "#/components/schemas/Cid" + $ref: "#/components/schemas/Cid" description: File to be downloaded. responses: @@ -532,14 +532,14 @@ paths: "/data/{cid}/network": post: summary: "Download a file from the network to the local node if it's not available locally. Note: Download is performed async. Call can return before download is completed." - tags: [ Data ] + tags: [Data] operationId: downloadNetwork parameters: - in: path name: cid required: true schema: - $ref: "#/components/schemas/Cid" + $ref: "#/components/schemas/Cid" description: "File to be downloaded." responses: "200": @@ -558,14 +558,14 @@ paths: "/data/{cid}/network/stream": get: summary: "Download a file from the network in a streaming manner. If the file is not available locally, it will be retrieved from other nodes in the network if able." - tags: [ Data ] + tags: [Data] operationId: downloadNetworkStream parameters: - in: path name: cid required: true schema: - $ref: "#/components/schemas/Cid" + $ref: "#/components/schemas/Cid" description: "File to be downloaded." responses: "200": @@ -585,14 +585,14 @@ paths: "/data/{cid}/network/manifest": get: summary: "Download only the dataset manifest from the network to the local node if it's not available locally." - tags: [ Data ] + tags: [Data] operationId: downloadNetworkManifest parameters: - in: path name: cid required: true schema: - $ref: "#/components/schemas/Cid" + $ref: "#/components/schemas/Cid" description: "File for which the manifest is to be downloaded." responses: "200": @@ -611,7 +611,7 @@ paths: "/space": get: summary: "Gets a summary of the storage space allocation of the node." - tags: [ Data ] + tags: [Data] operationId: space responses: "200": @@ -627,7 +627,7 @@ paths: "/sales/slots": get: summary: "Returns active slots" - tags: [ Marketplace ] + tags: [Marketplace] operationId: getActiveSlots responses: "200": @@ -645,7 +645,7 @@ paths: "/sales/slots/{slotId}": get: summary: "Returns active slot with id {slotId} for the host" - tags: [ Marketplace ] + tags: [Marketplace] operationId: getActiveSlotById parameters: - in: path @@ -674,7 +674,7 @@ paths: "/sales/availability": get: summary: "Returns storage that is for sale" - tags: [ Marketplace ] + tags: [Marketplace] operationId: getAvailabilities responses: "200": @@ -693,7 +693,7 @@ paths: post: summary: "Offers storage for sale" operationId: offerStorage - tags: [ Marketplace ] + tags: [Marketplace] requestBody: content: application/json: @@ -721,7 +721,7 @@ paths: The new parameters will be only considered for new requests. Existing Requests linked to this Availability will continue as is. operationId: updateOfferedStorage - tags: [ Marketplace ] + tags: [Marketplace] parameters: - in: path name: id @@ -753,7 +753,7 @@ paths: summary: "Get availability's reservations" description: Return's list of Reservations for ongoing Storage Requests that the node hosts. operationId: getReservations - tags: [ Marketplace ] + tags: [Marketplace] parameters: - in: path name: id @@ -782,7 +782,7 @@ paths: "/storage/request/{cid}": post: summary: "Creates a new Request for storage" - tags: [ Marketplace ] + tags: [Marketplace] operationId: createStorageRequest parameters: - in: path @@ -813,7 +813,7 @@ paths: "/storage/purchases": get: summary: "Returns list of purchase IDs" - tags: [ Marketplace ] + tags: [Marketplace] operationId: getPurchases responses: "200": @@ -830,7 +830,7 @@ paths: "/storage/purchases/{id}": get: summary: "Returns purchase details" - tags: [ Marketplace ] + tags: [Marketplace] operationId: getPurchase parameters: - in: path @@ -857,7 +857,7 @@ paths: get: summary: "Get Node's SPR" operationId: getSPR - tags: [ Node ] + tags: [Node] responses: "200": description: Node's SPR @@ -875,7 +875,7 @@ paths: get: summary: "Get Node's PeerID" operationId: getPeerId - tags: [ Node ] + tags: [Node] responses: "200": description: Node's Peer ID @@ -890,7 +890,7 @@ paths: "/debug/chronicles/loglevel": post: summary: "Set log level at run time" - tags: [ Debug ] + tags: [Debug] operationId: setDebugLogLevel parameters: @@ -912,7 +912,7 @@ paths: get: summary: "Gets node information" operationId: getDebugInfo - tags: [ Debug ] + tags: [Debug] responses: "200": description: Node's information diff --git a/tests/codex/examples.nim b/tests/codex/examples.nim index c97c7a8a..69a85db8 100644 --- a/tests/codex/examples.nim +++ b/tests/codex/examples.nim @@ -55,13 +55,16 @@ proc example*(_: type MultiHash, mcodec = Sha256HashCodec): MultiHash = let bytes = newSeqWith(256, rand(uint8)) MultiHash.digest($mcodec, bytes).tryGet() -proc example*(_: type Availability): Availability = +proc example*( + _: type Availability, collateralPerByte = uint8.example.u256 +): Availability = + let totalSize = uint16.example.u256 Availability.init( - totalSize = uint16.example.u256, + totalSize = totalSize, freeSize = uint16.example.u256, duration = uint16.example.u256, - minPrice = uint64.example.u256, - maxCollateral = uint16.example.u256, + minPricePerBytePerSecond = uint8.example.u256, + totalCollateral = totalSize * collateralPerByte, ) proc example*(_: type Reservation): Reservation = diff --git a/tests/codex/helpers/mockmarket.nim b/tests/codex/helpers/mockmarket.nim index bb8c0180..bb0eaaa2 100644 --- a/tests/codex/helpers/mockmarket.nim +++ b/tests/codex/helpers/mockmarket.nim @@ -60,6 +60,7 @@ type slotIndex*: UInt256 proof*: Groth16Proof timestamp: ?SecondsSince1970 + collateral*: UInt256 Subscriptions = object onRequest: seq[RequestSubscription] @@ -205,6 +206,14 @@ method getHost*( return some slot.host return none Address +method currentCollateral*( + market: MockMarket, slotId: SlotId +): Future[UInt256] {.async.} = + for slot in market.filled: + if slotId == slotId(slot.requestId, slot.slotIndex): + return slot.collateral + return 0.u256 + proc emitSlotFilled*(market: MockMarket, requestId: RequestId, slotIndex: UInt256) = var subscriptions = market.subscriptions.onSlotFilled for subscription in subscriptions: @@ -251,6 +260,7 @@ proc fillSlot*( slotIndex: UInt256, proof: Groth16Proof, host: Address, + collateral = 0.u256, ) = let slot = MockSlot( requestId: requestId, @@ -258,6 +268,7 @@ proc fillSlot*( proof: proof, host: host, timestamp: market.clock .? now, + collateral: collateral, ) market.filled.add(slot) market.slotState[slotId(slot.requestId, slot.slotIndex)] = SlotState.Filled @@ -270,7 +281,7 @@ method fillSlot*( proof: Groth16Proof, collateral: UInt256, ) {.async.} = - market.fillSlot(requestId, slotIndex, proof, market.signer) + market.fillSlot(requestId, slotIndex, proof, market.signer, collateral) method freeSlot*(market: MockMarket, slotId: SlotId) {.async.} = market.freed.add(slotId) diff --git a/tests/codex/helpers/mockreservations.nim b/tests/codex/helpers/mockreservations.nim index 2e9ad2e0..060790a8 100644 --- a/tests/codex/helpers/mockreservations.nim +++ b/tests/codex/helpers/mockreservations.nim @@ -27,6 +27,7 @@ method createReservation*( slotSize: UInt256, requestId: RequestId, slotIndex: UInt256, + collateralPerByte: UInt256, ): Future[?!Reservation] {.async.} = if self.createReservationThrowBytesOutOfBoundsError: let error = newException( @@ -38,5 +39,10 @@ method createReservation*( return failure(error) return await procCall createReservation( - Reservations(self), availabilityId, slotSize, requestId, slotIndex + Reservations(self), + availabilityId, + slotSize, + requestId, + slotIndex, + collateralPerByte, ) diff --git a/tests/codex/helpers/mockslotqueueitem.nim b/tests/codex/helpers/mockslotqueueitem.nim index 709dbc13..bc0c1047 100644 --- a/tests/codex/helpers/mockslotqueueitem.nim +++ b/tests/codex/helpers/mockslotqueueitem.nim @@ -6,8 +6,8 @@ type MockSlotQueueItem* = object slotIndex*: uint16 slotSize*: UInt256 duration*: UInt256 - reward*: UInt256 - collateral*: UInt256 + pricePerBytePerSecond*: UInt256 + collateralPerByte*: UInt256 expiry*: UInt256 seen*: bool @@ -18,8 +18,8 @@ proc toSlotQueueItem*(item: MockSlotQueueItem): SlotQueueItem = ask = StorageAsk( slotSize: item.slotSize, duration: item.duration, - reward: item.reward, - collateral: item.collateral, + pricePerBytePerSecond: item.pricePerBytePerSecond, + collateralPerByte: item.collateralPerByte, ), expiry = item.expiry, seen = item.seen, diff --git a/tests/codex/node/testnode.nim b/tests/codex/node/testnode.nim index 9574a4ae..e4a9d1f4 100644 --- a/tests/codex/node/testnode.nim +++ b/tests/codex/node/testnode.nim @@ -155,10 +155,10 @@ asyncchecksuite "Test Node - Basic": nodes = 5, tolerance = 2, duration = 100.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, expiry = 200.u256, - collateral = 200.u256, + collateralPerByte = 1.u256, ) ).tryGet diff --git a/tests/codex/sales/states/testcancelled.nim b/tests/codex/sales/states/testcancelled.nim index 16f694d3..d2568b98 100644 --- a/tests/codex/sales/states/testcancelled.nim +++ b/tests/codex/sales/states/testcancelled.nim @@ -15,25 +15,40 @@ import ../../helpers/mockclock asyncchecksuite "sales state 'cancelled'": let request = StorageRequest.example let slotIndex = (request.ask.slots div 2).u256 - let market = MockMarket.new() let clock = MockClock.new() + let currentCollateral = UInt256.example + + var market: MockMarket var state: SaleCancelled var agent: SalesAgent - var returnBytesWas = false - var reprocessSlotWas = false + var returnBytesWas = bool.none + var reprocessSlotWas = bool.none + var returnedCollateralValue = UInt256.none setup: - let onCleanUp = proc(returnBytes = false, reprocessSlot = false) {.async.} = - returnBytesWas = returnBytes - reprocessSlotWas = reprocessSlot + market = MockMarket.new() + let onCleanUp = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ) {.async.} = + returnBytesWas = some returnBytes + reprocessSlotWas = some reprocessSlot + returnedCollateralValue = returnedCollateral let context = SalesContext(market: market, clock: clock) agent = newSalesAgent(context, request.id, slotIndex, request.some) agent.onCleanUp = onCleanUp state = SaleCancelled.new() - test "calls onCleanUp with returnBytes = false and reprocessSlot = true": + test "calls onCleanUp with returnBytes = false, reprocessSlot = true, and returnedCollateral = currentCollateral": + market.fillSlot( + requestId = request.id, + slotIndex = slotIndex, + proof = Groth16Proof.default, + host = Address.example, + collateral = currentCollateral, + ) discard await state.run(agent) - check eventually returnBytesWas == true - check eventually reprocessSlotWas == false + check eventually returnBytesWas == some true + check eventually reprocessSlotWas == some false + check eventually returnedCollateralValue == some currentCollateral diff --git a/tests/codex/sales/states/testerrored.nim b/tests/codex/sales/states/testerrored.nim index f3d48669..9c8ee17a 100644 --- a/tests/codex/sales/states/testerrored.nim +++ b/tests/codex/sales/states/testerrored.nim @@ -24,7 +24,9 @@ asyncchecksuite "sales state 'errored'": var reprocessSlotWas = false setup: - let onCleanUp = proc(returnBytes = false, reprocessSlot = false) {.async.} = + let onCleanUp = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ) {.async.} = returnBytesWas = returnBytes reprocessSlotWas = reprocessSlot diff --git a/tests/codex/sales/states/testfinished.nim b/tests/codex/sales/states/testfinished.nim index c6bf5aba..4b353014 100644 --- a/tests/codex/sales/states/testfinished.nim +++ b/tests/codex/sales/states/testfinished.nim @@ -1,18 +1,45 @@ -import std/unittest import pkg/questionable import pkg/codex/contracts/requests import pkg/codex/sales/states/finished import pkg/codex/sales/states/cancelled import pkg/codex/sales/states/failed +import pkg/codex/sales/salesagent +import pkg/codex/sales/salescontext +import pkg/codex/market + +import ../../../asynctest import ../../examples import ../../helpers +import ../../helpers/mockmarket +import ../../helpers/mockclock -checksuite "sales state 'finished'": +asyncchecksuite "sales state 'finished'": let request = StorageRequest.example + let slotIndex = (request.ask.slots div 2).u256 + let clock = MockClock.new() + + let currentCollateral = UInt256.example + + var market: MockMarket var state: SaleFinished + var agent: SalesAgent + var returnBytesWas = bool.none + var reprocessSlotWas = bool.none + var returnedCollateralValue = UInt256.none setup: - state = SaleFinished.new() + market = MockMarket.new() + let onCleanUp = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ) {.async.} = + returnBytesWas = some returnBytes + reprocessSlotWas = some reprocessSlot + returnedCollateralValue = returnedCollateral + + let context = SalesContext(market: market, clock: clock) + agent = newSalesAgent(context, request.id, slotIndex, request.some) + agent.onCleanUp = onCleanUp + state = SaleFinished(returnedCollateral: some currentCollateral) test "switches to cancelled state when request expires": let next = state.onCancelled(request) @@ -21,3 +48,9 @@ checksuite "sales state 'finished'": test "switches to failed state when request fails": let next = state.onFailed(request) check !next of SaleFailed + + test "calls onCleanUp with returnBytes = false, reprocessSlot = true, and returnedCollateral = currentCollateral": + discard await state.run(agent) + check eventually returnBytesWas == some false + check eventually reprocessSlotWas == some false + check eventually returnedCollateralValue == some currentCollateral diff --git a/tests/codex/sales/states/testignored.nim b/tests/codex/sales/states/testignored.nim index 787ff627..1c808e8b 100644 --- a/tests/codex/sales/states/testignored.nim +++ b/tests/codex/sales/states/testignored.nim @@ -24,7 +24,9 @@ asyncchecksuite "sales state 'ignored'": var reprocessSlotWas = false setup: - let onCleanUp = proc(returnBytes = false, reprocessSlot = false) {.async.} = + let onCleanUp = proc( + returnBytes = false, reprocessSlot = false, returnedCollateral = UInt256.none + ) {.async.} = returnBytesWas = returnBytes reprocessSlotWas = reprocessSlot diff --git a/tests/codex/sales/states/testpayout.nim b/tests/codex/sales/states/testpayout.nim new file mode 100644 index 00000000..b1748b45 --- /dev/null +++ b/tests/codex/sales/states/testpayout.nim @@ -0,0 +1,44 @@ +import pkg/questionable +import pkg/chronos +import pkg/codex/contracts/requests +import pkg/codex/sales/states/payout +import pkg/codex/sales/states/finished +import pkg/codex/sales/salesagent +import pkg/codex/sales/salescontext +import pkg/codex/market + +import ../../../asynctest +import ../../examples +import ../../helpers +import ../../helpers/mockmarket +import ../../helpers/mockclock + +asyncchecksuite "sales state 'payout'": + let request = StorageRequest.example + let slotIndex = (request.ask.slots div 2).u256 + let clock = MockClock.new() + + let currentCollateral = UInt256.example + + var market: MockMarket + var state: SalePayout + var agent: SalesAgent + + setup: + market = MockMarket.new() + + let context = SalesContext(market: market, clock: clock) + agent = newSalesAgent(context, request.id, slotIndex, request.some) + state = SalePayout.new() + + test "switches to 'finished' state and provides returnedCollateral": + market.fillSlot( + requestId = request.id, + slotIndex = slotIndex, + proof = Groth16Proof.default, + host = Address.example, + collateral = currentCollateral, + ) + let next = await state.run(agent) + check !next of SaleFinished + check SaleFinished(!next).returnedCollateral == some currentCollateral diff --git a/tests/codex/sales/states/testpreparing.nim b/tests/codex/sales/states/testpreparing.nim index 8ad5e7d6..e78ee25e 100644 --- a/tests/codex/sales/states/testpreparing.nim +++ b/tests/codex/sales/states/testpreparing.nim @@ -33,12 +33,12 @@ asyncchecksuite "sales state 'preparing'": var reservations: MockReservations setup: - availability = Availability( - totalSize: request.ask.slotSize + 100.u256, - freeSize: request.ask.slotSize + 100.u256, - duration: request.ask.duration + 60.u256, - minPrice: request.ask.pricePerSlot - 10.u256, - maxCollateral: request.ask.collateral + 400.u256, + availability = Availability.init( + totalSize = request.ask.slotSize + 100.u256, + freeSize = request.ask.slotSize + 100.u256, + duration = request.ask.duration + 60.u256, + minPricePerBytePerSecond = request.ask.pricePerBytePerSecond, + totalCollateral = request.ask.collateralPerSlot * request.ask.slots.u256, ) let repoDs = SQLiteDatastore.new(Memory).tryGet() let metaDs = SQLiteDatastore.new(Memory).tryGet() @@ -69,8 +69,8 @@ asyncchecksuite "sales state 'preparing'": proc createAvailability() {.async.} = let a = await reservations.createAvailability( - availability.totalSize, availability.duration, availability.minPrice, - availability.maxCollateral, + availability.totalSize, availability.duration, + availability.minPricePerBytePerSecond, availability.totalCollateral, ) availability = a.get diff --git a/tests/codex/sales/testreservations.nim b/tests/codex/sales/testreservations.nim index 7ce78582..a1c7d1a5 100644 --- a/tests/codex/sales/testreservations.nim +++ b/tests/codex/sales/testreservations.nim @@ -22,6 +22,7 @@ asyncchecksuite "Reservations module": repoDs: Datastore metaDs: Datastore reservations: Reservations + collateralPerByte: UInt256 let repoTmp = TempLevelDb.new() metaTmp = TempLevelDb.new() @@ -32,23 +33,25 @@ asyncchecksuite "Reservations module": metaDs = metaTmp.newDb() repo = RepoStore.new(repoDs, metaDs) reservations = Reservations.new(repo) + collateralPerByte = uint8.example.u256 teardown: await repoTmp.destroyDb() await metaTmp.destroyDb() proc createAvailability(): Availability = - let example = Availability.example - let totalSize = rand(100000 .. 200000) + let example = Availability.example(collateralPerByte) + let totalSize = rand(100000 .. 200000).u256 + let totalCollateral = totalSize * collateralPerByte let availability = waitFor reservations.createAvailability( - totalSize.u256, example.duration, example.minPrice, example.maxCollateral + totalSize, example.duration, example.minPricePerBytePerSecond, totalCollateral ) return availability.get proc createReservation(availability: Availability): Reservation = let size = rand(1 ..< availability.freeSize.truncate(int)) let reservation = waitFor reservations.createReservation( - availability.id, size.u256, RequestId.example, UInt256.example + availability.id, size.u256, RequestId.example, UInt256.example, 1.u256 ) return reservation.get @@ -126,7 +129,7 @@ asyncchecksuite "Reservations module": 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 + availability.id, UInt256.example, RequestId.example, UInt256.example, 1.u256 ) check created.isErr check created.error of NotExistsError @@ -134,7 +137,11 @@ asyncchecksuite "Reservations module": test "cannot create reservation larger than availability size": let availability = createAvailability() let created = await reservations.createReservation( - availability.id, availability.totalSize + 1, RequestId.example, UInt256.example + availability.id, + availability.totalSize + 1, + RequestId.example, + UInt256.example, + UInt256.example, ) check created.isErr check created.error of BytesOutOfBoundsError @@ -143,11 +150,16 @@ asyncchecksuite "Reservations module": proc concurrencyTest(): Future[void] {.async.} = let availability = createAvailability() let one = reservations.createReservation( - availability.id, availability.totalSize - 1, RequestId.example, UInt256.example + availability.id, + availability.totalSize - 1, + RequestId.example, + UInt256.example, + UInt256.example, ) let two = reservations.createReservation( - availability.id, availability.totalSize, RequestId.example, UInt256.example + availability.id, availability.totalSize, RequestId.example, UInt256.example, + UInt256.example, ) let oneResult = await one @@ -304,8 +316,8 @@ asyncchecksuite "Reservations module": let availability = createAvailability() let found = await reservations.findAvailability( - availability.freeSize, availability.duration, availability.minPrice, - availability.maxCollateral, + availability.freeSize, availability.duration, + availability.minPricePerBytePerSecond, collateralPerByte, ) check found.isSome @@ -317,23 +329,22 @@ asyncchecksuite "Reservations module": let found = await reservations.findAvailability( availability.freeSize + 1, availability.duration, - availability.minPrice, - availability.maxCollateral, + availability.minPricePerBytePerSecond, + collateralPerByte, ) check found.isNone - test "non-existant availability cannot be found": + test "non-existent availability cannot be found": let availability = Availability.example - let found = ( - await reservations.findAvailability( - availability.freeSize, availability.duration, availability.minPrice, - availability.maxCollateral, - ) + let found = await reservations.findAvailability( + availability.freeSize, availability.duration, + availability.minPricePerBytePerSecond, collateralPerByte, ) + check found.isNone - test "non-existant availability cannot be retrieved": + test "non-existent availability cannot be retrieved": let key = AvailabilityId.example.key.get let got = await reservations.get(key, Availability) check got.error of NotExistsError diff --git a/tests/codex/sales/testsales.nim b/tests/codex/sales/testsales.nim index 3c3f0c63..0d441f34 100644 --- a/tests/codex/sales/testsales.nim +++ b/tests/codex/sales/testsales.nim @@ -43,8 +43,8 @@ asyncchecksuite "Sales - start": slots: 4, slotSize: 100.u256, duration: 60.u256, - reward: 10.u256, - collateral: 200.u256, + pricePerBytePerSecond: 1.u256, + collateralPerByte: 1.u256, ), content: StorageContent(cid: "some cid"), expiry: (getTime() + initDuration(hours = 1)).toUnix.u256, @@ -124,6 +124,10 @@ asyncchecksuite "Sales": repoTmp = TempLevelDb.new() metaTmp = TempLevelDb.new() + var totalAvailabilitySize: UInt256 + var minPricePerBytePerSecond: UInt256 + var requestedCollateralPerByte: UInt256 + var totalCollateral: UInt256 var availability: Availability var request: StorageRequest var sales: Sales @@ -135,20 +139,24 @@ asyncchecksuite "Sales": var itemsProcessed: seq[SlotQueueItem] setup: - availability = Availability( - totalSize: 100.u256, - freeSize: 100.u256, - duration: 60.u256, - minPrice: 600.u256, - maxCollateral: 400.u256, + totalAvailabilitySize = 100.u256 + minPricePerBytePerSecond = 1.u256 + requestedCollateralPerByte = 1.u256 + totalCollateral = requestedCollateralPerByte * totalAvailabilitySize + availability = Availability.init( + totalSize = totalAvailabilitySize, + freeSize = totalAvailabilitySize, + duration = 60.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, ) request = StorageRequest( ask: StorageAsk( slots: 4, slotSize: 100.u256, duration: 60.u256, - reward: 10.u256, - collateral: 200.u256, + pricePerBytePerSecond: minPricePerBytePerSecond, + collateralPerByte: 1.u256, ), content: StorageContent(cid: "some cid"), expiry: (getTime() + initDuration(hours = 1)).toUnix.u256, @@ -210,8 +218,8 @@ asyncchecksuite "Sales": proc createAvailability() = let a = waitFor reservations.createAvailability( - availability.totalSize, availability.duration, availability.minPrice, - availability.maxCollateral, + availability.totalSize, availability.duration, + availability.minPricePerBytePerSecond, availability.totalCollateral, ) availability = a.get # update id @@ -229,7 +237,7 @@ asyncchecksuite "Sales": done.complete() var request1 = StorageRequest.example - request1.ask.collateral = request.ask.collateral + 1 + request1.ask.collateralPerByte = request.ask.collateralPerByte + 1 createAvailability() # saturate queue while queue.len < queue.size - 1: @@ -383,14 +391,14 @@ asyncchecksuite "Sales": check wasIgnored() test "ignores request when reward is too low": - availability.minPrice = request.ask.pricePerSlot + 1 + availability.minPricePerBytePerSecond = request.ask.pricePerBytePerSecond + 1 createAvailability() await market.requestStorage(request) check wasIgnored() test "ignores request when asked collateral is too high": var tooBigCollateral = request - tooBigCollateral.ask.collateral = availability.maxCollateral + 1 + tooBigCollateral.ask.collateralPerByte = requestedCollateralPerByte + 1 createAvailability() await market.requestStorage(tooBigCollateral) check wasIgnored() @@ -606,7 +614,7 @@ asyncchecksuite "Sales": test "deletes inactive reservations on load": createAvailability() discard await reservations.createReservation( - availability.id, 100.u256, RequestId.example, UInt256.example + availability.id, 100.u256, RequestId.example, UInt256.example, UInt256.example ) check (await reservations.all(Reservation)).get.len == 1 await sales.load() diff --git a/tests/codex/sales/testslotqueue.nim b/tests/codex/sales/testslotqueue.nim index 5d331bc3..2e0759ee 100644 --- a/tests/codex/sales/testslotqueue.nim +++ b/tests/codex/sales/testslotqueue.nim @@ -24,12 +24,15 @@ suite "Slot queue start/stop": test "starts out not running": check not queue.running + test "queue starts paused": + check queue.paused + test "can call start multiple times, and when already running": queue.start() queue.start() check queue.running - test "can call stop when alrady stopped": + test "can call stop when already stopped": await queue.stop() check not queue.running @@ -144,16 +147,16 @@ suite "Slot queue": test "correctly compares SlotQueueItems": var requestA = StorageRequest.example requestA.ask.duration = 1.u256 - requestA.ask.reward = 1.u256 - check requestA.ask.pricePerSlot == 1.u256 - requestA.ask.collateral = 100000.u256 + requestA.ask.pricePerBytePerSecond = 1.u256 + check requestA.ask.pricePerSlot == 1.u256 * requestA.ask.slotSize + requestA.ask.collateralPerByte = 100000.u256 requestA.expiry = 1001.u256 var requestB = StorageRequest.example requestB.ask.duration = 100.u256 - requestB.ask.reward = 1000.u256 - check requestB.ask.pricePerSlot == 100000.u256 - requestB.ask.collateral = 1.u256 + requestB.ask.pricePerBytePerSecond = 1000.u256 + check requestB.ask.pricePerSlot == 100000.u256 * requestB.ask.slotSize + requestB.ask.collateralPerByte = 1.u256 requestB.expiry = 1000.u256 let itemA = SlotQueueItem.init(requestA, 0) @@ -168,8 +171,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, duration: 1.u256, - reward: 2.u256, # profitability is higher (good) - collateral: 1.u256, + pricePerBytePerSecond: 2.u256, # profitability is higher (good) + collateralPerByte: 1.u256, expiry: 1.u256, seen: true, # seen (bad), more weight than profitability ) @@ -178,8 +181,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, duration: 1.u256, - reward: 1.u256, # profitability is lower (bad) - collateral: 1.u256, + pricePerBytePerSecond: 1.u256, # profitability is lower (bad) + collateralPerByte: 1.u256, expiry: 1.u256, seen: false, # not seen (good) ) @@ -193,8 +196,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, duration: 1.u256, - reward: 1.u256, # reward is lower (bad) - collateral: 1.u256, # collateral is lower (good) + pricePerBytePerSecond: 1.u256, # reward is lower (bad) + collateralPerByte: 1.u256, # collateral is lower (good) expiry: 1.u256, seen: false, ) @@ -203,8 +206,9 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, duration: 1.u256, - reward: 2.u256, # reward is higher (good), more weight than collateral - collateral: 2.u256, # collateral is higher (bad) + pricePerBytePerSecond: 2.u256, + # reward is higher (good), more weight than collateral + collateralPerByte: 2.u256, # collateral is higher (bad) expiry: 1.u256, seen: false, ) @@ -218,8 +222,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, duration: 1.u256, - reward: 1.u256, - collateral: 2.u256, # collateral is higher (bad) + pricePerBytePerSecond: 1.u256, + collateralPerByte: 2.u256, # collateral is higher (bad) expiry: 2.u256, # expiry is longer (good) seen: false, ) @@ -228,8 +232,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, duration: 1.u256, - reward: 1.u256, - collateral: 1.u256, # collateral is lower (good), more weight than expiry + pricePerBytePerSecond: 1.u256, + collateralPerByte: 1.u256, # collateral is lower (good), more weight than expiry expiry: 1.u256, # expiry is shorter (bad) seen: false, ) @@ -243,8 +247,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, # slotSize is smaller (good) duration: 1.u256, - reward: 1.u256, - collateral: 1.u256, + pricePerBytePerSecond: 1.u256, + collateralPerByte: 1.u256, expiry: 1.u256, # expiry is shorter (bad) seen: false, ) @@ -253,8 +257,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 2.u256, # slotSize is larger (bad) duration: 1.u256, - reward: 1.u256, - collateral: 1.u256, + pricePerBytePerSecond: 1.u256, + collateralPerByte: 1.u256, expiry: 2.u256, # expiry is longer (good), more weight than slotSize seen: false, ) @@ -268,8 +272,8 @@ suite "Slot queue": slotIndex: 0, slotSize: 2.u256, # slotSize is larger (bad) duration: 1.u256, - reward: 1.u256, - collateral: 1.u256, + pricePerBytePerSecond: 1.u256, + collateralPerByte: 1.u256, expiry: 1.u256, # expiry is shorter (bad) seen: false, ) @@ -278,13 +282,13 @@ suite "Slot queue": slotIndex: 0, slotSize: 1.u256, # slotSize is smaller (good) duration: 1.u256, - reward: 1.u256, - collateral: 1.u256, + pricePerBytePerSecond: 1.u256, + collateralPerByte: 1.u256, expiry: 1.u256, seen: false, ) - check itemB.toSlotQueueItem < itemA.toSlotQueueItem # < indicates higher priority + check itemA.toSlotQueueItem < itemB.toSlotQueueItem # < indicates higher priority test "expands available all possible slot indices on init": let request = StorageRequest.example @@ -322,7 +326,7 @@ suite "Slot queue": newSlotQueue(maxSize = 8, maxWorkers = 1, processSlotDelay = 10.millis) let request0 = StorageRequest.example var request1 = StorageRequest.example - request1.ask.collateral += 1.u256 + request1.ask.collateralPerByte += 1.u256 let items0 = SlotQueueItem.init(request0) let items1 = SlotQueueItem.init(request1) check queue.push(items0).isOk @@ -332,8 +336,8 @@ suite "Slot queue": check populated.slotIndex == 12'u16 check populated.slotSize == request1.ask.slotSize check populated.duration == request1.ask.duration - check populated.reward == request1.ask.reward - check populated.collateral == request1.ask.collateral + check populated.pricePerBytePerSecond == request1.ask.pricePerBytePerSecond + check populated.collateralPerByte == request1.ask.collateralPerByte test "does not find exisiting request metadata": newSlotQueue(maxSize = 2, maxWorkers = 2) @@ -394,7 +398,7 @@ suite "Slot queue": newSlotQueue(maxSize = 8, maxWorkers = 1, processSlotDelay = 10.millis) let request0 = StorageRequest.example var request1 = StorageRequest.example - request1.ask.collateral += 1.u256 + request1.ask.collateralPerByte += 1.u256 let items0 = SlotQueueItem.init(request0) let items1 = SlotQueueItem.init(request1) check queue.push(items0).isOk @@ -408,7 +412,7 @@ suite "Slot queue": newSlotQueue(maxSize = 8, maxWorkers = 1, processSlotDelay = 10.millis) let request0 = StorageRequest.example var request1 = StorageRequest.example - request1.ask.collateral += 1.u256 + request1.ask.collateralPerByte += 1.u256 let items0 = SlotQueueItem.init(request0) let items1 = SlotQueueItem.init(request1) check queue.push(items0).isOk @@ -424,11 +428,11 @@ suite "Slot queue": var request3 = StorageRequest.example var request4 = StorageRequest.example var request5 = StorageRequest.example - request1.ask.collateral = request0.ask.collateral + 1 - request2.ask.collateral = request1.ask.collateral + 1 - request3.ask.collateral = request2.ask.collateral + 1 - request4.ask.collateral = request3.ask.collateral + 1 - request5.ask.collateral = request4.ask.collateral + 1 + request1.ask.collateralPerByte = request0.ask.collateralPerByte + 1 + request2.ask.collateralPerByte = request1.ask.collateralPerByte + 1 + 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) @@ -439,19 +443,19 @@ suite "Slot queue": check queue.push(@[item0, item1, item2, item3, item4, item5]).isOk check queue.contains(item5) - test "sorts items by profitability ascending (higher pricePerSlot = higher priority)": + 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) - request.ask.reward += 1.u256 + request.ask.pricePerBytePerSecond += 1.u256 let item1 = SlotQueueItem.init(request, 1) check item1 < item0 - test "sorts items by collateral ascending (less required collateral = higher priority)": + test "sorts items by collateral ascending (higher required collateralPerByte = lower priority == comes later in the list)": var request = StorageRequest.example let item0 = SlotQueueItem.init(request, 0) - request.ask.collateral -= 1.u256 + request.ask.collateralPerByte += 1.u256 let item1 = SlotQueueItem.init(request, 1) - check item1 < item0 + check item1 > item0 test "sorts items by expiry descending (longer expiry = higher priority)": var request = StorageRequest.example @@ -460,10 +464,10 @@ suite "Slot queue": let item1 = SlotQueueItem.init(request, 1) check item1 < item0 - test "sorts items by slot size ascending (smaller dataset = higher priority)": + test "sorts items by slot size descending (bigger dataset = higher profitability = higher priority)": var request = StorageRequest.example let item0 = SlotQueueItem.init(request, 0) - request.ask.slotSize -= 1.u256 + request.ask.slotSize += 1.u256 let item1 = SlotQueueItem.init(request, 1) check item1 < item0 @@ -480,17 +484,17 @@ suite "Slot queue": check queue.push(item).isOk check eventually onProcessSlotCalledWith == @[(item.requestId, item.slotIndex)] - test "should process items in correct order": + test "processes items in order of addition when only one item is added at a time": newSlotQueue(maxSize = 2, maxWorkers = 2) # 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) - request.ask.reward += 1.u256 + request.ask.pricePerBytePerSecond += 1.u256 let item1 = SlotQueueItem.init(request, 1) - request.ask.reward += 1.u256 + request.ask.pricePerBytePerSecond += 1.u256 let item2 = SlotQueueItem.init(request, 2) - request.ask.reward += 1.u256 + request.ask.pricePerBytePerSecond += 1.u256 let item3 = SlotQueueItem.init(request, 3) check queue.push(item0).isOk @@ -511,9 +515,35 @@ suite "Slot queue": ] ) - test "queue starts paused": - newSlotQueue(maxSize = 4, maxWorkers = 4) - check queue.paused + test "should process items in correct order according to the queue invariant when more than one item is added at a time": + newSlotQueue(maxSize = 4, maxWorkers = 2) + # 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) + request.ask.pricePerBytePerSecond += 1.u256 + let item1 = SlotQueueItem.init(request, 1) + request.ask.pricePerBytePerSecond += 1.u256 + let item2 = SlotQueueItem.init(request, 2) + request.ask.pricePerBytePerSecond += 1.u256 + let item3 = SlotQueueItem.init(request, 3) + + check queue.push(item0).isOk + check queue.push(item1).isOk + check queue.push(item2).isOk + check queue.push(item3).isOk + + await sleepAsync(1.millis) + + check eventually ( + onProcessSlotCalledWith == + @[ + (item3.requestId, item3.slotIndex), + (item2.requestId, item2.slotIndex), + (item1.requestId, item1.slotIndex), + (item0.requestId, item0.slotIndex), + ] + ) test "pushing items to queue unpauses queue": newSlotQueue(maxSize = 4, maxWorkers = 4) diff --git a/tests/codex/sales/teststates.nim b/tests/codex/sales/teststates.nim index 4346afb5..fd918ccc 100644 --- a/tests/codex/sales/teststates.nim +++ b/tests/codex/sales/teststates.nim @@ -1,6 +1,7 @@ import ./states/testunknown import ./states/testdownloading import ./states/testfilling +import ./states/testpayout import ./states/testfinished import ./states/testinitialproving import ./states/testfilled diff --git a/tests/codex/testpurchasing.nim b/tests/codex/testpurchasing.nim index 462f6986..bbab4197 100644 --- a/tests/codex/testpurchasing.nim +++ b/tests/codex/testpurchasing.nim @@ -30,7 +30,7 @@ asyncchecksuite "Purchasing": slots: uint8.example.uint64, slotSize: uint32.example.u256, duration: uint16.example.u256, - reward: uint8.example.u256, + pricePerBytePerSecond: uint8.example.u256, ) ) @@ -46,7 +46,8 @@ asyncchecksuite "Purchasing": check market.requested[0].ask.slots == request.ask.slots check market.requested[0].ask.slotSize == request.ask.slotSize check market.requested[0].ask.duration == request.ask.duration - check market.requested[0].ask.reward == request.ask.reward + check market.requested[0].ask.pricePerBytePerSecond == + request.ask.pricePerBytePerSecond test "remembers purchases": let purchase1 = await purchasing.purchase(request) @@ -131,7 +132,7 @@ checksuite "Purchasing state machine": slots: uint8.example.uint64, slotSize: uint32.example.u256, duration: uint16.example.u256, - reward: uint8.example.u256, + pricePerBytePerSecond: uint8.example.u256, ) ) diff --git a/tests/codex/testvalidation.nim b/tests/codex/testvalidation.nim index b8eaef65..95d913c3 100644 --- a/tests/codex/testvalidation.nim +++ b/tests/codex/testvalidation.nim @@ -22,7 +22,7 @@ asyncchecksuite "validation": let validationGroups = ValidationGroups(8).some let slot = Slot.example let proof = Groth16Proof.example - let collateral = slot.request.ask.collateral + let collateral = slot.request.ask.collateralPerSlot var market: MockMarket var clock: MockClock diff --git a/tests/contracts/testContracts.nim b/tests/contracts/testContracts.nim index acdb9875..3af63ac1 100644 --- a/tests/contracts/testContracts.nim +++ b/tests/contracts/testContracts.nim @@ -19,7 +19,7 @@ ethersuite "Marketplace contracts": var filledAt: UInt256 proc expectedPayout(endTimestamp: UInt256): UInt256 = - return (endTimestamp - filledAt) * request.ask.reward + return (endTimestamp - filledAt) * request.ask.pricePerSlotPerSecond() proc switchAccount(account: Signer) = marketplace = marketplace.connect(account) @@ -44,10 +44,11 @@ ethersuite "Marketplace contracts": request.client = await client.getAddress() switchAccount(client) - discard await token.approve(marketplace.address, request.price).confirm(1) + discard await token.approve(marketplace.address, request.totalPrice).confirm(1) discard await marketplace.requestStorage(request).confirm(1) switchAccount(host) - discard await token.approve(marketplace.address, request.ask.collateral).confirm(1) + discard + await token.approve(marketplace.address, request.ask.collateralPerSlot).confirm(1) discard await marketplace.reserveSlot(request.id, 0.u256).confirm(1) let receipt = await marketplace.fillSlot(request.id, 0.u256, proof).confirm(1) filledAt = await ethProvider.blockTime(BlockTag.init(!receipt.blockNumber)) @@ -65,8 +66,9 @@ ethersuite "Marketplace contracts": proc startContract() {.async.} = for slotIndex in 1 ..< request.ask.slots: - discard - await token.approve(marketplace.address, request.ask.collateral).confirm(1) + discard await token + .approve(marketplace.address, request.ask.collateralPerSlot) + .confirm(1) discard await marketplace.reserveSlot(request.id, slotIndex.u256).confirm(1) discard await marketplace.fillSlot(request.id, slotIndex.u256, proof).confirm(1) @@ -94,7 +96,7 @@ ethersuite "Marketplace contracts": discard await marketplace.freeSlot(slotId).confirm(1) let endBalance = await token.balanceOf(address) check endBalance == - (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateral) + (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateralPerSlot) test "can be paid out at the end, specifying reward and collateral recipient": switchAccount(host) @@ -114,7 +116,8 @@ ethersuite "Marketplace contracts": check endBalanceHost == startBalanceHost check endBalanceReward == (startBalanceReward + expectedPayout(requestEnd.u256)) - check endBalanceCollateral == (startBalanceCollateral + request.ask.collateral) + check endBalanceCollateral == + (startBalanceCollateral + request.ask.collateralPerSlot) test "cannot mark proofs missing for cancelled request": let expiry = await marketplace.requestExpiry(request.id) diff --git a/tests/contracts/testMarket.nim b/tests/contracts/testMarket.nim index 5e6a632a..a77c2aaa 100644 --- a/tests/contracts/testMarket.nim +++ b/tests/contracts/testMarket.nim @@ -32,7 +32,7 @@ ethersuite "On-Chain Market": proc expectedPayout( r: StorageRequest, startTimestamp: UInt256, endTimestamp: UInt256 ): UInt256 = - return (endTimestamp - startTimestamp) * r.ask.reward + return (endTimestamp - startTimestamp) * r.ask.pricePerSlotPerSecond proc switchAccount(account: Signer) = marketplace = marketplace.connect(account) @@ -118,7 +118,7 @@ ethersuite "On-Chain Market": let endBalanceClient = await token.balanceOf(clientAddress) - check endBalanceClient == (startBalanceClient + request.price) + check endBalanceClient == (startBalanceClient + request.totalPrice) test "supports request subscriptions": var receivedIds: seq[RequestId] @@ -135,19 +135,19 @@ ethersuite "On-Chain Market": test "supports filling of slots": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) test "can retrieve host that filled slot": await market.requestStorage(request) check (await market.getHost(request.id, slotIndex)) == none Address await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) check (await market.getHost(request.id, slotIndex)) == some accounts[0] test "supports freeing a slot": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) await market.freeSlot(slotId(request.id, slotIndex)) check (await market.getHost(request.id, slotIndex)) == none Address @@ -160,7 +160,7 @@ ethersuite "On-Chain Market": test "submits proofs": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) await advanceToNextPeriod() await market.submitProof(slotId(request.id, slotIndex), proof) @@ -168,7 +168,7 @@ ethersuite "On-Chain Market": let slotId = slotId(request, slotIndex) await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) await waitUntilProofRequired(slotId) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) await advanceToNextPeriod() @@ -179,7 +179,7 @@ ethersuite "On-Chain Market": let slotId = slotId(request, slotIndex) await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) await waitUntilProofRequired(slotId) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) await advanceToNextPeriod() @@ -195,7 +195,7 @@ ethersuite "On-Chain Market": let subscription = await market.subscribeSlotFilled(onSlotFilled) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) check eventually receivedIds == @[request.id] and receivedSlotIndices == @[ slotIndex ] @@ -211,16 +211,16 @@ ethersuite "On-Chain Market": let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled) await market.reserveSlot(request.id, otherSlot) - await market.fillSlot(request.id, otherSlot, proof, request.ask.collateral) + await market.fillSlot(request.id, otherSlot, proof, request.ask.collateralPerSlot) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) check eventually receivedSlotIndices == @[slotIndex] await subscription.unsubscribe() test "supports slot freed subscriptions": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) var receivedRequestIds: seq[RequestId] = @[] var receivedIdxs: seq[UInt256] = @[] proc onSlotFreed(requestId: RequestId, idx: UInt256) = @@ -269,7 +269,9 @@ ethersuite "On-Chain Market": let subscription = await market.subscribeFulfillment(request.id, onFulfillment) for slotIndex in 0 ..< request.ask.slots: await market.reserveSlot(request.id, slotIndex.u256) - await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex.u256, proof, request.ask.collateralPerSlot + ) check eventually receivedIds == @[request.id] await subscription.unsubscribe() @@ -288,11 +290,13 @@ ethersuite "On-Chain Market": for slotIndex in 0 ..< request.ask.slots: await market.reserveSlot(request.id, slotIndex.u256) - await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex.u256, proof, request.ask.collateralPerSlot + ) for slotIndex in 0 ..< otherRequest.ask.slots: await market.reserveSlot(otherRequest.id, slotIndex.u256) await market.fillSlot( - otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateral + otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateralPerSlot ) check eventually receivedIds == @[request.id] @@ -325,7 +329,9 @@ ethersuite "On-Chain Market": for slotIndex in 0 ..< request.ask.slots: await market.reserveSlot(request.id, slotIndex.u256) - await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex.u256, proof, request.ask.collateralPerSlot + ) for slotIndex in 0 .. request.ask.maxSlotLoss: let slotId = request.slotId(slotIndex.u256) while true: @@ -360,7 +366,7 @@ ethersuite "On-Chain Market": test "supports proof submission subscriptions": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) await advanceToNextPeriod() var receivedIds: seq[SlotId] proc onProofSubmission(id: SlotId) = @@ -388,15 +394,19 @@ ethersuite "On-Chain Market": await market.requestStorage(request) for slotIndex in 0 ..< request.ask.slots: await market.reserveSlot(request.id, slotIndex.u256) - await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex.u256, proof, request.ask.collateralPerSlot + ) check (await market.requestState(request.id)) == some RequestState.Started test "can retrieve active slots": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex - 1) - await market.fillSlot(request.id, slotIndex - 1, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex - 1, proof, request.ask.collateralPerSlot + ) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) let slotId1 = request.slotId(slotIndex - 1) let slotId2 = request.slotId(slotIndex) check (await market.mySlots()) == @[slotId1, slotId2] @@ -409,7 +419,7 @@ ethersuite "On-Chain Market": test "can retrieve request details from slot id": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) let slotId = request.slotId(slotIndex) let expected = Slot(request: request, slotIndex: slotIndex) check (await market.getActiveSlot(slotId)) == some expected @@ -421,7 +431,7 @@ ethersuite "On-Chain Market": test "retrieves correct slot state once filled": await market.requestStorage(request) await market.reserveSlot(request.id, slotIndex) - await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) + await market.fillSlot(request.id, slotIndex, proof, request.ask.collateralPerSlot) let slotId = request.slotId(slotIndex) check (await market.slotState(slotId)) == SlotState.Filled @@ -451,9 +461,9 @@ ethersuite "On-Chain Market": await market.reserveSlot(request.id, 0.u256) await market.reserveSlot(request.id, 1.u256) await market.reserveSlot(request.id, 2.u256) - await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral) - await market.fillSlot(request.id, 1.u256, proof, request.ask.collateral) - await market.fillSlot(request.id, 2.u256, proof, request.ask.collateral) + await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) + await market.fillSlot(request.id, 1.u256, proof, request.ask.collateralPerSlot) + await market.fillSlot(request.id, 2.u256, proof, request.ask.collateralPerSlot) let slotId = request.slotId(slotIndex) # `market.fill` executes an `approve` tx before the `fillSlot` tx, so that's @@ -471,7 +481,7 @@ ethersuite "On-Chain Market": test "can query past SlotFilled events since given timestamp": await market.requestStorage(request) await market.reserveSlot(request.id, 0.u256) - await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral) + await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) # The SlotFilled event will be included in the same block as # the fillSlot transaction. If we want to ignore the SlotFilled event @@ -484,8 +494,8 @@ ethersuite "On-Chain Market": await market.reserveSlot(request.id, 1.u256) await market.reserveSlot(request.id, 2.u256) - await market.fillSlot(request.id, 1.u256, proof, request.ask.collateral) - await market.fillSlot(request.id, 2.u256, proof, request.ask.collateral) + await market.fillSlot(request.id, 1.u256, proof, request.ask.collateralPerSlot) + await market.fillSlot(request.id, 2.u256, proof, request.ask.collateralPerSlot) let events = await market.queryPastSlotFilledEvents( fromTime = fromTime.truncate(SecondsSince1970) @@ -503,9 +513,9 @@ ethersuite "On-Chain Market": await market.reserveSlot(request.id, 0.u256) await market.reserveSlot(request.id, 1.u256) await market.reserveSlot(request.id, 2.u256) - await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral) - await market.fillSlot(request.id, 1.u256, proof, request.ask.collateral) - await market.fillSlot(request.id, 2.u256, proof, request.ask.collateral) + await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) + await market.fillSlot(request.id, 1.u256, proof, request.ask.collateralPerSlot) + await market.fillSlot(request.id, 2.u256, proof, request.ask.collateralPerSlot) await ethProvider.advanceTime(10.u256) @@ -531,12 +541,14 @@ ethersuite "On-Chain Market": let address = await host.getAddress() switchAccount(host) await market.reserveSlot(request.id, 0.u256) - await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral) + await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) let filledAt = (await ethProvider.currentTime()) - 1.u256 for slotIndex in 1 ..< request.ask.slots: await market.reserveSlot(request.id, slotIndex.u256) - await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex.u256, proof, request.ask.collateralPerSlot + ) let requestEnd = await market.getRequestEnd(request.id) await ethProvider.advanceTimeTo(requestEnd.u256 + 1) @@ -546,7 +558,7 @@ ethersuite "On-Chain Market": let endBalance = await token.balanceOf(address) let expectedPayout = request.expectedPayout(filledAt, requestEnd.u256) - check endBalance == (startBalance + expectedPayout + request.ask.collateral) + check endBalance == (startBalance + expectedPayout + request.ask.collateralPerSlot) test "pays rewards to reward recipient, collateral to host": market = OnChainMarket.new(marketplace, hostRewardRecipient.some) @@ -556,12 +568,14 @@ ethersuite "On-Chain Market": switchAccount(host) await market.reserveSlot(request.id, 0.u256) - await market.fillSlot(request.id, 0.u256, proof, request.ask.collateral) + await market.fillSlot(request.id, 0.u256, proof, request.ask.collateralPerSlot) let filledAt = (await ethProvider.currentTime()) - 1.u256 for slotIndex in 1 ..< request.ask.slots: await market.reserveSlot(request.id, slotIndex.u256) - await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) + await market.fillSlot( + request.id, slotIndex.u256, proof, request.ask.collateralPerSlot + ) let requestEnd = await market.getRequestEnd(request.id) await ethProvider.advanceTimeTo(requestEnd.u256 + 1) @@ -575,5 +589,5 @@ ethersuite "On-Chain Market": let endBalanceReward = await token.balanceOf(hostRewardRecipient) let expectedPayout = request.expectedPayout(filledAt, requestEnd.u256) - check endBalanceHost == (startBalanceHost + request.ask.collateral) + check endBalanceHost == (startBalanceHost + request.ask.collateralPerSlot) check endBalanceReward == (startBalanceReward + expectedPayout) diff --git a/tests/examples.nim b/tests/examples.nim index ea49b4ea..bfb34cff 100644 --- a/tests/examples.nim +++ b/tests/examples.nim @@ -51,9 +51,9 @@ proc example*(_: type StorageRequest): StorageRequest = slots: 4, slotSize: (1 * 1024 * 1024 * 1024).u256, # 1 Gigabyte duration: (10 * 60 * 60).u256, # 10 hours - collateral: 200.u256, + collateralPerByte: 1.u256, proofProbability: 4.u256, # require a proof roughly once every 4 periods - reward: 84.u256, + pricePerBytePerSecond: 1.u256, maxSlotLoss: 2, # 2 slots can be freed without data considered to be lost ), content: StorageContent( diff --git a/tests/integration/codexclient.nim b/tests/integration/codexclient.nim index 1bf3f211..7826b151 100644 --- a/tests/integration/codexclient.nim +++ b/tests/integration/codexclient.nim @@ -105,9 +105,9 @@ proc requestStorageRaw*( client: CodexClient, cid: Cid, duration: UInt256, - reward: UInt256, + pricePerBytePerSecond: UInt256, proofProbability: UInt256, - collateral: UInt256, + collateralPerByte: UInt256, expiry: uint = 0, nodes: uint = 3, tolerance: uint = 1, @@ -118,9 +118,9 @@ proc requestStorageRaw*( let json = %*{ "duration": duration, - "reward": reward, + "pricePerBytePerSecond": pricePerBytePerSecond, "proofProbability": proofProbability, - "collateral": collateral, + "collateralPerByte": collateralPerByte, "nodes": nodes, "tolerance": tolerance, } @@ -134,17 +134,18 @@ proc requestStorage*( client: CodexClient, cid: Cid, duration: UInt256, - reward: UInt256, + pricePerBytePerSecond: UInt256, proofProbability: UInt256, expiry: uint, - collateral: UInt256, + collateralPerByte: UInt256, nodes: uint = 3, tolerance: uint = 1, ): ?!PurchaseId = ## Call request storage REST endpoint ## let response = client.requestStorageRaw( - cid, duration, reward, proofProbability, collateral, expiry, nodes, tolerance + cid, duration, pricePerBytePerSecond, proofProbability, collateralPerByte, expiry, + nodes, tolerance, ) if response.status != "200 OK": doAssert(false, response.body) @@ -172,7 +173,8 @@ proc getSlots*(client: CodexClient): ?!seq[Slot] = seq[Slot].fromJson(body) proc postAvailability*( - client: CodexClient, totalSize, duration, minPrice, maxCollateral: UInt256 + client: CodexClient, + totalSize, duration, minPricePerBytePerSecond, totalCollateral: UInt256, ): ?!Availability = ## Post sales availability endpoint ## @@ -181,8 +183,8 @@ proc postAvailability*( %*{ "totalSize": totalSize, "duration": duration, - "minPrice": minPrice, - "maxCollateral": maxCollateral, + "minPricePerBytePerSecond": minPricePerBytePerSecond, + "totalCollateral": totalCollateral, } let response = client.http.post(url, $json) doAssert response.status == "201 Created", @@ -192,7 +194,8 @@ proc postAvailability*( proc patchAvailabilityRaw*( client: CodexClient, availabilityId: AvailabilityId, - totalSize, freeSize, duration, minPrice, maxCollateral: ?UInt256 = UInt256.none, + totalSize, freeSize, duration, minPricePerBytePerSecond, totalCollateral: ?UInt256 = + UInt256.none, ): Response = ## Updates availability ## @@ -210,25 +213,26 @@ proc patchAvailabilityRaw*( if duration =? duration: json["duration"] = %duration - if minPrice =? minPrice: - json["minPrice"] = %minPrice + if minPricePerBytePerSecond =? minPricePerBytePerSecond: + json["minPricePerBytePerSecond"] = %minPricePerBytePerSecond - if maxCollateral =? maxCollateral: - json["maxCollateral"] = %maxCollateral + if totalCollateral =? totalCollateral: + json["totalCollateral"] = %totalCollateral client.http.patch(url, $json) proc patchAvailability*( client: CodexClient, availabilityId: AvailabilityId, - totalSize, duration, minPrice, maxCollateral: ?UInt256 = UInt256.none, + totalSize, duration, minPricePerBytePerSecond, totalCollateral: ?UInt256 = + UInt256.none, ): void = let response = client.patchAvailabilityRaw( availabilityId, totalSize = totalSize, duration = duration, - minPrice = minPrice, - maxCollateral = maxCollateral, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, ) doAssert response.status == "200 OK", "expected 200 OK, got " & response.status diff --git a/tests/integration/marketplacesuite.nim b/tests/integration/marketplacesuite.nim index bc2f9cfb..4d155186 100644 --- a/tests/integration/marketplacesuite.nim +++ b/tests/integration/marketplacesuite.nim @@ -45,16 +45,28 @@ template marketplacesuite*(name: string, body: untyped) = proc periods(p: int): uint64 = p.uint64 * period - proc createAvailabilities(datasetSize: int, duration: uint64) = + proc slotSize(blocks: int): UInt256 = + (DefaultBlockSize * blocks.NBytes).Natural.u256 + + proc datasetSize(blocks, nodes, tolerance: int): UInt256 = + (nodes + tolerance).u256 * slotSize(blocks) + + proc createAvailabilities( + datasetSize: UInt256, + duration: uint64, + collateralPerByte: UInt256, + minPricePerBytePerSecond: UInt256, + ) = + let totalCollateral = datasetSize * collateralPerByte # post availability to each provider for i in 0 ..< providers().len: let provider = providers()[i].client discard provider.postAvailability( - totalSize = datasetSize.u256, # should match 1 slot only + totalSize = datasetSize, duration = duration.u256, - minPrice = 300.u256, - maxCollateral = 200.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, ) proc requestStorage( @@ -62,8 +74,8 @@ template marketplacesuite*(name: string, body: untyped) = cid: Cid, proofProbability = 1, duration: uint64 = 12.periods, - reward = 400.u256, - collateral = 100.u256, + pricePerBytePerSecond = 1.u256, + collateralPerByte = 1.u256, expiry: uint64 = 4.periods, nodes = providers().len, tolerance = 0, @@ -73,8 +85,8 @@ template marketplacesuite*(name: string, body: untyped) = expiry = expiry.uint, duration = duration.u256, proofProbability = proofProbability.u256, - collateral = collateral, - reward = reward, + collateralPerByte = collateralPerByte, + pricePerBytePerSecond = pricePerBytePerSecond, nodes = nodes.uint, tolerance = tolerance.uint, ).get diff --git a/tests/integration/testecbug.nim b/tests/integration/testecbug.nim index fde5eb34..e7604de7 100644 --- a/tests/integration/testecbug.nim +++ b/tests/integration/testecbug.nim @@ -19,9 +19,9 @@ marketplacesuite "Bug #821 - node crashes during erasure coding": # .withLogTopics("node", "marketplace", "sales", "reservations", "node", "proving", "clock") .some, ): - let reward = 400.u256 + let pricePerBytePerSecond = 1.u256 let duration = 20.periods - let collateral = 200.u256 + let collateralPerByte = 1.u256 let expiry = 10.periods let data = await RandomChunker.example(blocks = 8) let client = clients()[0] @@ -40,9 +40,9 @@ marketplacesuite "Bug #821 - node crashes during erasure coding": let id = await clientApi.requestStorage( cid, duration = duration, - reward = reward, + pricePerBytePerSecond = pricePerBytePerSecond, expiry = expiry, - collateral = collateral, + collateralPerByte = collateralPerByte, nodes = 3, tolerance = 1, ) diff --git a/tests/integration/testmarketplace.nim b/tests/integration/testmarketplace.nim index b45c3cb8..bc030a1d 100644 --- a/tests/integration/testmarketplace.nim +++ b/tests/integration/testmarketplace.nim @@ -16,6 +16,12 @@ marketplacesuite "Marketplace": var client: CodexClient var clientAccount: Address + const minPricePerBytePerSecond = 1.u256 + const collateralPerByte = 1.u256 + const blocks = 8 + const ecNodes = 3 + const ecTolerance = 1 + setup: host = providers()[0].client hostAccount = providers()[0].ethAccount @@ -29,13 +35,13 @@ marketplacesuite "Marketplace": test "nodes negotiate contracts on the marketplace", marketplaceConfig: let size = 0xFFFFFF.u256 - let data = await RandomChunker.example(blocks = 8) + let data = await RandomChunker.example(blocks = blocks) # host makes storage available let availability = host.postAvailability( totalSize = size, duration = 20 * 60.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = size * minPricePerBytePerSecond, ).get # client requests storage @@ -43,12 +49,12 @@ marketplacesuite "Marketplace": let id = client.requestStorage( cid, duration = 20 * 60.u256, - reward = 400.u256, + pricePerBytePerSecond = minPricePerBytePerSecond, proofProbability = 3.u256, expiry = 10 * 60, - collateral = 200.u256, - nodes = 3, - tolerance = 1, + collateralPerByte = collateralPerByte, + nodes = ecNodes, + tolerance = ecTolerance, ).get check eventually(client.purchaseStateIs(id, "started"), timeout = 10 * 60 * 1000) @@ -66,21 +72,19 @@ marketplacesuite "Marketplace": test "node slots gets paid out and rest of tokens are returned to client", marketplaceConfig: let size = 0xFFFFFF.u256 - let data = await RandomChunker.example(blocks = 8) + let data = await RandomChunker.example(blocks = blocks) let marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) let tokenAddress = await marketplace.token() let token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) - let reward = 400.u256 let duration = 20 * 60.u256 - let nodes = 3'u # host makes storage available let startBalanceHost = await token.balanceOf(hostAccount) discard host.postAvailability( totalSize = size, duration = 20 * 60.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = size * minPricePerBytePerSecond, ).get # client requests storage @@ -88,12 +92,12 @@ marketplacesuite "Marketplace": let id = client.requestStorage( cid, duration = duration, - reward = reward, + pricePerBytePerSecond = minPricePerBytePerSecond, proofProbability = 3.u256, expiry = 10 * 60, - collateral = 200.u256, - nodes = nodes, - tolerance = 1, + collateralPerByte = collateralPerByte, + nodes = ecNodes, + tolerance = ecTolerance, ).get check eventually(client.purchaseStateIs(id, "started"), timeout = 10 * 60 * 1000) @@ -108,8 +112,10 @@ marketplacesuite "Marketplace": await ethProvider.advanceTime(duration) # Checking that the hosting node received reward for at least the time between + let slotSize = slotSize(blocks) + let pricePerSlotPerSecond = minPricePerBytePerSecond * slotSize check eventually (await token.balanceOf(hostAccount)) - startBalanceHost >= - (duration - 5 * 60) * reward * nodes.u256 + (duration - 5 * 60) * pricePerSlotPerSecond * ecNodes.u256 # Checking that client node receives some funds back that were not used for the host nodes check eventually( @@ -118,6 +124,12 @@ marketplacesuite "Marketplace": ) marketplacesuite "Marketplace payouts": + const minPricePerBytePerSecond = 1.u256 + const collateralPerByte = 1.u256 + const blocks = 8 + const ecNodes = 3 + const ecTolerance = 1 + test "expired request partially pays out for stored time", NodeConfigs( # Uncomment to start Hardhat automatically, typically so logs can be inspected locally @@ -133,11 +145,9 @@ marketplacesuite "Marketplace payouts": # .withLogTopics("node", "marketplace", "sales", "reservations", "node", "proving", "clock") .some, ): - let reward = 400.u256 let duration = 20.periods - let collateral = 200.u256 let expiry = 10.periods - let data = await RandomChunker.example(blocks = 8) + let data = await RandomChunker.example(blocks = blocks) let client = clients()[0] let provider = providers()[0] let clientApi = client.client @@ -146,13 +156,15 @@ marketplacesuite "Marketplace payouts": let startBalanceClient = await token.balanceOf(client.ethAccount) # provider makes storage available + let datasetSize = datasetSize(blocks, ecNodes, ecTolerance) + let totalAvailabilitySize = datasetSize div 2 discard providerApi.postAvailability( # make availability size small enough that we can't fill all the slots, # thus causing a cancellation - totalSize = (data.len div 2).u256, + totalSize = totalAvailabilitySize, duration = duration.u256, - minPrice = reward, - maxCollateral = collateral, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = collateralPerByte * totalAvailabilitySize, ) let cid = clientApi.upload(data).get @@ -168,11 +180,11 @@ marketplacesuite "Marketplace payouts": let id = await clientApi.requestStorage( cid, duration = duration, - reward = reward, + pricePerBytePerSecond = minPricePerBytePerSecond, expiry = expiry, - collateral = collateral, - nodes = 3, - tolerance = 1, + collateralPerByte = collateralPerByte, + nodes = ecNodes, + tolerance = ecTolerance, ) # wait until one slot is filled @@ -185,10 +197,13 @@ marketplacesuite "Marketplace payouts": await advanceToNextPeriod() + let slotSize = slotSize(blocks) + let pricePerSlotPerSecond = minPricePerBytePerSecond * slotSize + check eventually ( let endBalanceProvider = (await token.balanceOf(provider.ethAccount)) endBalanceProvider > startBalanceProvider and - endBalanceProvider < startBalanceProvider + expiry.u256 * reward + endBalanceProvider < startBalanceProvider + expiry.u256 * pricePerSlotPerSecond ) check eventually( ( diff --git a/tests/integration/testproofs.nim b/tests/integration/testproofs.nim index c03c3d07..b25643ad 100644 --- a/tests/integration/testproofs.nim +++ b/tests/integration/testproofs.nim @@ -15,6 +15,12 @@ logScope: topics = "integration test proofs" marketplacesuite "Hosts submit regular proofs": + const minPricePerBytePerSecond = 1.u256 + const collateralPerByte = 1.u256 + const blocks = 8 + const ecNodes = 3 + const ecTolerance = 1 + test "hosts submit periodic proofs for slots they fill", NodeConfigs( # Uncomment to start Hardhat automatically, typically so logs can be inspected locally @@ -34,14 +40,29 @@ marketplacesuite "Hosts submit regular proofs": let expiry = 10.periods let duration = expiry + 5.periods - let data = await RandomChunker.example(blocks = 8) - createAvailabilities(data.len * 2, duration) # TODO: better value for data.len + let data = await RandomChunker.example(blocks = blocks) + let datasetSize = + datasetSize(blocks = blocks, nodes = ecNodes, tolerance = ecTolerance) + createAvailabilities( + datasetSize, duration, collateralPerByte, minPricePerBytePerSecond + ) let cid = client0.upload(data).get let purchaseId = await client0.requestStorage( - cid, expiry = expiry, duration = duration, nodes = 3, tolerance = 1 + cid, + expiry = expiry, + duration = duration, + nodes = ecNodes, + tolerance = ecTolerance, ) + + let purchase = client0.getPurchase(purchaseId).get + check purchase.error == none string + + let request = purchase.request.get + let slotSize = request.ask.slotSize + check eventually( client0.purchaseStateIs(purchaseId, "started"), timeout = expiry.int * 1000 ) @@ -62,6 +83,12 @@ marketplacesuite "Simulate invalid proofs": # tightened so that they are showing, as an integration test, that specific # proofs are being marked as missed by the validator. + const minPricePerBytePerSecond = 1.u256 + const collateralPerByte = 1.u256 + const blocks = 8 + const ecNodes = 3 + const ecTolerance = 1 + test "slot is freed after too many invalid proofs submitted", NodeConfigs( # Uncomment to start Hardhat automatically, typically so logs can be inspected locally @@ -88,8 +115,12 @@ marketplacesuite "Simulate invalid proofs": let expiry = 10.periods let duration = expiry + 10.periods - let data = await RandomChunker.example(blocks = 8) - createAvailabilities(data.len * 2, duration) # TODO: better value for data.len + let data = await RandomChunker.example(blocks = blocks) + let datasetSize = + datasetSize(blocks = blocks, nodes = ecNodes, tolerance = ecTolerance) + createAvailabilities( + datasetSize, duration, collateralPerByte, minPricePerBytePerSecond + ) let cid = client0.upload(data).get @@ -97,8 +128,8 @@ marketplacesuite "Simulate invalid proofs": cid, expiry = expiry, duration = duration, - nodes = 3, - tolerance = 1, + nodes = ecNodes, + tolerance = ecTolerance, proofProbability = 1, ) let requestId = client0.requestId(purchaseId).get @@ -144,8 +175,12 @@ marketplacesuite "Simulate invalid proofs": let expiry = 10.periods let duration = expiry + 10.periods - let data = await RandomChunker.example(blocks = 8) - createAvailabilities(data.len * 2, duration) # TODO: better value for data.len + let data = await RandomChunker.example(blocks = blocks) + let datasetSize = + datasetSize(blocks = blocks, nodes = ecNodes, tolerance = ecTolerance) + createAvailabilities( + datasetSize, duration, collateralPerByte, minPricePerBytePerSecond + ) let cid = client0.upload(data).get @@ -153,8 +188,8 @@ marketplacesuite "Simulate invalid proofs": cid, expiry = expiry, duration = duration, - nodes = 3, - tolerance = 1, + nodes = ecNodes, + tolerance = ecTolerance, proofProbability = 1, ) let requestId = client0.requestId(purchaseId).get @@ -187,6 +222,9 @@ marketplacesuite "Simulate invalid proofs": await freedSubscription.unsubscribe() # TODO: uncomment once fixed + # WARNING: in the meantime minPrice has changed to minPricePerBytePerSecond + # and maxCollateral has changed to totalCollateral - double check if + # it is set correctly below # test "host that submits invalid proofs is paid out less", NodeConfigs( # # Uncomment to start Hardhat automatically, typically so logs can be inspected locally # # hardhat: HardhatConfig().withLogFile(), @@ -227,8 +265,8 @@ marketplacesuite "Simulate invalid proofs": # discard provider0.client.postAvailability( # totalSize=slotSize, # should match 1 slot only # duration=totalPeriods.periods.u256, - # minPrice=300.u256, - # maxCollateral=200.u256 + # minPricePerBytePerSecond=minPricePerBytePerSecond, + # totalCollateral=slotSize * minPricePerBytePerSecond # ) # let cid = client0.upload(data).get @@ -260,8 +298,8 @@ marketplacesuite "Simulate invalid proofs": # discard provider1.client.postAvailability( # totalSize=slotSize, # should match 1 slot only # duration=totalPeriods.periods.u256, - # minPrice=300.u256, - # maxCollateral=200.u256 + # minPricePerBytePerSecond=minPricePerBytePerSecond, + # totalCollateral=slotSize * minPricePerBytePerSecond # ) # check eventually filledSlotIds.len > 1 @@ -269,8 +307,8 @@ marketplacesuite "Simulate invalid proofs": # discard provider2.client.postAvailability( # totalSize=slotSize, # should match 1 slot only # duration=totalPeriods.periods.u256, - # minPrice=300.u256, - # maxCollateral=200.u256 + # minPricePerBytePerSecond=minPricePerBytePerSecond, + # totalCollateral=slotSize * minPricePerBytePerSecond # ) # check eventually filledSlotIds.len > 2 diff --git a/tests/integration/testpurchasing.nim b/tests/integration/testpurchasing.nim index a78ea125..4e08e7a8 100644 --- a/tests/integration/testpurchasing.nim +++ b/tests/integration/testpurchasing.nim @@ -12,18 +12,18 @@ twonodessuite "Purchasing": let id1 = client1.requestStorage( cid, duration = 100.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, expiry = 10, - collateral = 200.u256, + collateralPerByte = 1.u256, ).get let id2 = client1.requestStorage( cid, duration = 400.u256, - reward = 5.u256, + pricePerBytePerSecond = 2.u256, proofProbability = 6.u256, expiry = 10, - collateral = 201.u256, + collateralPerByte = 2.u256, ).get check id1 != id2 @@ -38,33 +38,36 @@ twonodessuite "Purchasing": let id = client1.requestStorage( cid, duration = 100.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, expiry = 30, - collateral = 200.u256, + collateralPerByte = 1.u256, nodes = 3, tolerance = 1, ).get let request = client1.getPurchase(id).get.request.get check request.ask.duration == 100.u256 - check request.ask.reward == 2.u256 + check request.ask.pricePerBytePerSecond == 1.u256 check request.ask.proofProbability == 3.u256 check request.expiry == 30 - check request.ask.collateral == 200.u256 + check request.ask.collateralPerByte == 1.u256 check request.ask.slots == 3'u64 check request.ask.maxSlotLoss == 1'u64 # TODO: We currently do not support encoding single chunks # test "node retrieves purchase status with 1 chunk", twoNodesConfig: # let cid = client1.upload("some file contents").get - # let id = client1.requestStorage(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, expiry=30, collateral=200.u256, nodes=2, tolerance=1).get + # let id = client1.requestStorage( + # cid, duration=1.u256, pricePerBytePerSecond=1.u256, + # proofProbability=3.u256, expiry=30, collateralPerByte=1.u256, + # nodes=2, tolerance=1).get # let request = client1.getPurchase(id).get.request.get # check request.ask.duration == 1.u256 - # check request.ask.reward == 2.u256 + # check request.ask.pricePerBytePerSecond == 1.u256 # check request.ask.proofProbability == 3.u256 # check request.expiry == 30 - # check request.ask.collateral == 200.u256 + # check request.ask.collateralPerByte == 1.u256 # check request.ask.slots == 3'u64 # check request.ask.maxSlotLoss == 1'u64 @@ -74,10 +77,10 @@ twonodessuite "Purchasing": let id = client1.requestStorage( cid, duration = 10 * 60.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, expiry = 5 * 60, - collateral = 200.u256, + collateralPerByte = 1.u256, nodes = 3.uint, tolerance = 1.uint, ).get @@ -89,10 +92,10 @@ twonodessuite "Purchasing": check eventually(client1.purchaseStateIs(id, "submitted"), timeout = 3 * 60 * 1000) let request = client1.getPurchase(id).get.request.get check request.ask.duration == (10 * 60).u256 - check request.ask.reward == 2.u256 + check request.ask.pricePerBytePerSecond == 1.u256 check request.ask.proofProbability == 3.u256 check request.expiry == (5 * 60).u256 - check request.ask.collateral == 200.u256 + check request.ask.collateralPerByte == 1.u256 check request.ask.slots == 3'u64 check request.ask.maxSlotLoss == 1'u64 @@ -103,9 +106,9 @@ twonodessuite "Purchasing": let responseMissing = client1.requestStorageRaw( cid, duration = 1.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, - collateral = 200.u256, + collateralPerByte = 1.u256, ) check responseMissing.status == "400 Bad Request" check responseMissing.body == "Expiry required" @@ -113,9 +116,9 @@ twonodessuite "Purchasing": let responseBefore = client1.requestStorageRaw( cid, duration = 10.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, - collateral = 200.u256, + collateralPerByte = 1.u256, expiry = 10, ) check responseBefore.status == "400 Bad Request" diff --git a/tests/integration/testrestapi.nim b/tests/integration/testrestapi.nim index 9c1ec633..52b722d6 100644 --- a/tests/integration/testrestapi.nim +++ b/tests/integration/testrestapi.nim @@ -21,8 +21,14 @@ twonodessuite "REST API": test "node shows used and available space", twoNodesConfig: discard client1.upload("some file contents").get + let totalSize = 12.u256 + let minPricePerBytePerSecond = 1.u256 + let totalCollateral = totalSize * minPricePerBytePerSecond discard client1.postAvailability( - totalSize = 12.u256, duration = 2.u256, minPrice = 3.u256, maxCollateral = 4.u256 + totalSize = totalSize, + duration = 2.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, ).get let space = client1.space().tryGet() check: @@ -47,9 +53,9 @@ twonodessuite "REST API": let response = client1.requestStorageRaw( cid, duration = 10.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, - collateral = 200.u256, + collateralPerByte = 1.u256, expiry = 9, ) @@ -65,9 +71,9 @@ twonodessuite "REST API": let response = client1.requestStorageRaw( cid, duration = 10.u256, - reward = 2.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, - collateral = 200.u256, + collateralPerByte = 1.u256, expiry = 9, ) @@ -78,16 +84,16 @@ twonodessuite "REST API": let data = await RandomChunker.example(blocks = 2) let cid = client1.upload(data).get let duration = 100.u256 - let reward = 2.u256 + let pricePerBytePerSecond = 1.u256 let proofProbability = 3.u256 let expiry = 30.uint - let collateral = 200.u256 + let collateralPerByte = 1.u256 let nodes = 3 let tolerance = 0 var responseBefore = client1.requestStorageRaw( - cid, duration, reward, proofProbability, collateral, expiry, nodes.uint, - tolerance.uint, + cid, duration, pricePerBytePerSecond, proofProbability, collateralPerByte, expiry, + nodes.uint, tolerance.uint, ) check responseBefore.status == "400 Bad Request" @@ -97,18 +103,18 @@ twonodessuite "REST API": let data = await RandomChunker.example(blocks = 2) let cid = client1.upload(data).get let duration = 100.u256 - let reward = 2.u256 + let pricePerBytePerSecond = 1.u256 let proofProbability = 3.u256 let expiry = 30.uint - let collateral = 200.u256 + let collateralPerByte = 1.u256 let ecParams = @[(1, 1), (2, 1), (3, 2), (3, 3)] for ecParam in ecParams: let (nodes, tolerance) = ecParam var responseBefore = client1.requestStorageRaw( - cid, duration, reward, proofProbability, collateral, expiry, nodes.uint, - tolerance.uint, + cid, duration, pricePerBytePerSecond, proofProbability, collateralPerByte, + expiry, nodes.uint, tolerance.uint, ) check responseBefore.status == "400 Bad Request" @@ -120,18 +126,18 @@ twonodessuite "REST API": let data = await RandomChunker.example(blocks = 2) let cid = client1.upload(data).get let duration = 100.u256 - let reward = 2.u256 + let pricePerBytePerSecond = 1.u256 let proofProbability = 3.u256 let expiry = 30.uint - let collateral = 200.u256 + let collateralPerByte = 1.u256 let ecParams = @[(0, 1), (1, 2), (2, 3)] for ecParam in ecParams: let (nodes, tolerance) = ecParam var responseBefore = client1.requestStorageRaw( - cid, duration, reward, proofProbability, collateral, expiry, nodes.uint, - tolerance.uint, + cid, duration, pricePerBytePerSecond, proofProbability, collateralPerByte, + expiry, nodes.uint, tolerance.uint, ) check responseBefore.status == "400 Bad Request" @@ -142,18 +148,18 @@ twonodessuite "REST API": let data = await RandomChunker.example(blocks = 2) let cid = client1.upload(data).get let duration = 100.u256 - let reward = 2.u256 + let pricePerBytePerSecond = 1.u256 let proofProbability = 3.u256 let expiry = 30.uint - let collateral = 200.u256 + let collateralPerByte = 1.u256 let ecParams = @[(3, 1), (5, 2)] for ecParam in ecParams: let (nodes, tolerance) = ecParam var responseBefore = client1.requestStorageRaw( - cid, duration, reward, proofProbability, collateral, expiry, nodes.uint, - tolerance.uint, + cid, duration, pricePerBytePerSecond, proofProbability, collateralPerByte, + expiry, nodes.uint, tolerance.uint, ) check responseBefore.status == "200 OK" diff --git a/tests/integration/testsales.nim b/tests/integration/testsales.nim index 3cf8bd73..a77e5649 100644 --- a/tests/integration/testsales.nim +++ b/tests/integration/testsales.nim @@ -20,6 +20,8 @@ multinodesuite "Sales": providers: CodexConfigs.init(nodes = 1).some, ) + let minPricePerBytePerSecond = 1.u256 + var host: CodexClient var client: CodexClient @@ -29,16 +31,25 @@ multinodesuite "Sales": test "node handles new storage availability", salesConfig: let availability1 = host.postAvailability( - totalSize = 1.u256, duration = 2.u256, minPrice = 3.u256, maxCollateral = 4.u256 + totalSize = 1.u256, + duration = 2.u256, + minPricePerBytePerSecond = 3.u256, + totalCollateral = 4.u256, ).get let availability2 = host.postAvailability( - totalSize = 4.u256, duration = 5.u256, minPrice = 6.u256, maxCollateral = 7.u256 + totalSize = 4.u256, + duration = 5.u256, + minPricePerBytePerSecond = 6.u256, + totalCollateral = 7.u256, ).get check availability1 != availability2 test "node lists storage that is for sale", salesConfig: let availability = host.postAvailability( - totalSize = 1.u256, duration = 2.u256, minPrice = 3.u256, maxCollateral = 4.u256 + totalSize = 1.u256, + duration = 2.u256, + minPricePerBytePerSecond = 3.u256, + totalCollateral = 4.u256, ).get check availability in host.getAvailabilities().get @@ -46,8 +57,8 @@ multinodesuite "Sales": let nonExistingResponse = host.patchAvailabilityRaw( AvailabilityId.example, duration = 100.u256.some, - minPrice = 200.u256.some, - maxCollateral = 200.u256.some, + minPricePerBytePerSecond = 2.u256.some, + totalCollateral = 200.u256.some, ) check nonExistingResponse.status == "404 Not Found" @@ -55,21 +66,21 @@ multinodesuite "Sales": let availability = host.postAvailability( totalSize = 140000.u256, duration = 200.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = 3.u256, + totalCollateral = 300.u256, ).get host.patchAvailability( availability.id, duration = 100.u256.some, - minPrice = 200.u256.some, - maxCollateral = 200.u256.some, + minPricePerBytePerSecond = 2.u256.some, + totalCollateral = 200.u256.some, ) let updatedAvailability = (host.getAvailabilities().get).findItem(availability).get check updatedAvailability.duration == 100 - check updatedAvailability.minPrice == 200 - check updatedAvailability.maxCollateral == 200 + check updatedAvailability.minPricePerBytePerSecond == 2 + check updatedAvailability.totalCollateral == 200 check updatedAvailability.totalSize == 140000 check updatedAvailability.freeSize == 140000 @@ -77,8 +88,8 @@ multinodesuite "Sales": let availability = host.postAvailability( totalSize = 140000.u256, duration = 200.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = 3.u256, + totalCollateral = 300.u256, ).get let freeSizeResponse = host.patchAvailabilityRaw(availability.id, freeSize = 110000.u256.some) @@ -89,8 +100,8 @@ multinodesuite "Sales": let availability = host.postAvailability( totalSize = 140000.u256, duration = 200.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = 3.u256, + totalCollateral = 300.u256, ).get host.patchAvailability(availability.id, totalSize = 100000.u256.some) let updatedAvailability = (host.getAvailabilities().get).findItem(availability).get @@ -101,11 +112,14 @@ multinodesuite "Sales": salesConfig: let originalSize = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks = 8) + let minPricePerBytePerSecond = 3.u256 + let collateralPerByte = 1.u256 + let totalCollateral = originalSize * collateralPerByte let availability = host.postAvailability( totalSize = originalSize, duration = 20 * 60.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, ).get # Lets create storage request that will utilize some of the availability's space @@ -113,10 +127,10 @@ multinodesuite "Sales": let id = client.requestStorage( cid, duration = 20 * 60.u256, - reward = 400.u256, + pricePerBytePerSecond = minPricePerBytePerSecond, proofProbability = 3.u256, expiry = 10 * 60, - collateral = 200.u256, + collateralPerByte = collateralPerByte, nodes = 3, tolerance = 1, ).get diff --git a/tests/integration/testvalidator.nim b/tests/integration/testvalidator.nim index 9b93c0e7..8b7fbc5b 100644 --- a/tests/integration/testvalidator.nim +++ b/tests/integration/testvalidator.nim @@ -34,9 +34,13 @@ template eventuallyS( await eventuallyS() marketplacesuite "Validation": - let nodes = 3 - let tolerance = 1 - let proofProbability = 1 + const blocks = 8 + const ecNodes = 3 + const ecTolerance = 1 + const proofProbability = 1 + + const collateralPerByte = 1.u256 + const minPricePerBytePerSecond = 1.u256 proc waitForRequestToFail( marketplace: Marketplace, requestId: RequestId, timeout = 10, step = 5 @@ -92,19 +96,20 @@ marketplacesuite "Validation": var currentTime = await ethProvider.currentTime() let requestEndTime = currentTime.truncate(uint64) + duration - let data = await RandomChunker.example(blocks = 8) - - # TODO: better value for data.len below. This TODO is also present in - # testproofs.nim - we may want to address it or remove the comment. - createAvailabilities(data.len * 2, duration) + let data = await RandomChunker.example(blocks = blocks) + let datasetSize = + datasetSize(blocks = blocks, nodes = ecNodes, tolerance = ecTolerance) + createAvailabilities( + datasetSize, duration, collateralPerByte, minPricePerBytePerSecond + ) let cid = client0.upload(data).get let purchaseId = await client0.requestStorage( cid, expiry = expiry, duration = duration, - nodes = nodes, - tolerance = tolerance, + nodes = ecNodes, + tolerance = ecTolerance, proofProbability = proofProbability, ) let requestId = client0.requestId(purchaseId).get @@ -158,19 +163,20 @@ marketplacesuite "Validation": var currentTime = await ethProvider.currentTime() let requestEndTime = currentTime.truncate(uint64) + duration - let data = await RandomChunker.example(blocks = 8) - - # TODO: better value for data.len below. This TODO is also present in - # testproofs.nim - we may want to address it or remove the comment. - createAvailabilities(data.len * 2, duration) + let data = await RandomChunker.example(blocks = blocks) + let datasetSize = + datasetSize(blocks = blocks, nodes = ecNodes, tolerance = ecTolerance) + createAvailabilities( + datasetSize, duration, collateralPerByte, minPricePerBytePerSecond + ) let cid = client0.upload(data).get let purchaseId = await client0.requestStorage( cid, expiry = expiry, duration = duration, - nodes = nodes, - tolerance = tolerance, + nodes = ecNodes, + tolerance = ecTolerance, proofProbability = proofProbability, ) let requestId = client0.requestId(purchaseId).get diff --git a/tests/testTaiko.nim b/tests/testTaiko.nim index e86d2bd4..8036e8a3 100644 --- a/tests/testTaiko.nim +++ b/tests/testTaiko.nim @@ -46,11 +46,14 @@ suite "Taiko L2 Integration Tests": node2.removeDataDir() test "node 1 buys storage from node 2": + let size = 0xFFFFF.u256 + let minPricePerBytePerSecond = 1.u256 + let totalCollateral = size * minPricePerBytePerSecond discard node2.client.postAvailability( - size = 0xFFFFF.u256, + size = size, duration = 200.u256, - minPrice = 300.u256, - maxCollateral = 300.u256, + minPricePerBytePerSecond = minPricePerBytePerSecond, + totalCollateral = totalCollateral, ) let cid = !node1.client.upload("some file contents") @@ -60,9 +63,9 @@ suite "Taiko L2 Integration Tests": !node1.client.requestStorage( cid, duration = 30.u256, - reward = 400.u256, + pricePerBytePerSecond = 1.u256, proofProbability = 3.u256, - collateral = 200.u256, + collateralPerByte = 1.u256, expiry = expiry.u256, ) diff --git a/vendor/codex-contracts-eth b/vendor/codex-contracts-eth index 02e3b8d2..e74d3397 160000 --- a/vendor/codex-contracts-eth +++ b/vendor/codex-contracts-eth @@ -1 +1 @@ -Subproject commit 02e3b8d22b15975fd450ccdad30e32c0be7e593f +Subproject commit e74d3397a133eaf1eb95d9ce59f56747a7c8c30b