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:
Marcin Czenko 2025-01-24 18:18:00 +01:00 committed by GitHub
parent f6c792de79
commit 962fc1cd95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 848 additions and 494 deletions

View File

@ -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:

View File

@ -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.}

View File

@ -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

View File

@ -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")

View File

@ -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"

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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))

View File

@ -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:

View File

@ -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

View File

@ -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 =

View File

@ -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)

View File

@ -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,
)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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,
)
)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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,
)

View File

@ -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(
(

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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,
)

@ -1 +1 @@
Subproject commit 02e3b8d22b15975fd450ccdad30e32c0be7e593f
Subproject commit e74d3397a133eaf1eb95d9ce59f56747a7c8c30b