mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-02 13:33:10 +00:00
Feat: price per byte (#1078)
* openAPI: StorageRequestCreation: reward => pricePerByte, collateral => collateralPerByte * purchasing: reward => pricePerByte, collateral => collateralPerByte * Updates availabilities and reservations to use totalCollateral, minPricePerByte, and maxCollateralPerByte * Uses correct div operator when operating on UInt256 * proposal updating totalCollateral in availability * makes sure that reading currentCollateral happens before freeing slot * Updates naming * fixes tests: unit and contracts * uses feat/price-per-byte branch for codex-contracts-eth * temporarily disables integration tests on CI * introduces high level <<totalCollateral>> property for a cleaner external interface * updates integration tests * Applies review comments * Updates description of totalCollateral in SalesAvailability * updates codex-contracts-eth (price-per-byte)
This commit is contained in:
parent
f6c792de79
commit
962fc1cd95
@ -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:
|
||||
|
||||
@ -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.}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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:
|
||||
|
||||
90
openapi.yaml
90
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
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
44
tests/codex/sales/states/testpayout.nim
Normal file
44
tests/codex/sales/states/testpayout.nim
Normal file
@ -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
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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 <expiry;end>
|
||||
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(
|
||||
(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
|
||||
2
vendor/codex-contracts-eth
vendored
2
vendor/codex-contracts-eth
vendored
@ -1 +1 @@
|
||||
Subproject commit 02e3b8d22b15975fd450ccdad30e32c0be7e593f
|
||||
Subproject commit e74d3397a133eaf1eb95d9ce59f56747a7c8c30b
|
||||
Loading…
x
Reference in New Issue
Block a user