mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-02 13:33:10 +00:00
feat(marketplace): indicate that slot is being repaired when trying to download (#1083)
* Indicate that slot is being repaired when trying to download * Fix tests * Apply nph * Calculate the repair collateral when adding the item into the queue * Add slotCollateral calculation with getRequest cache and remove populationItem function * Update with pricePerByte * Simplify StorageAsk parameter * Minor fixes * Move cache request to another PR * Rename SlotQueueItem collateral and required in init * Use override func to optimise calls when the slot state is known * Remove unused code * Cosmetic change * Use raiseMarketError helper * Add exceptions to async pragma * Cosmetic change * Use raiseMarketError helper * Let slotCollateral determines the slot sate * Use configSync to avoid async pragma in onStorageRequested * Add loadConfig function * Add CatchableError to async pragma * Add missing pragma raises errors * Move loadConfig * Avoid swallow CancelledError * Avoid swallowing CancelledError * Avoid swallowing CancelledError * Update error messages * Except MarketError instead of CatchableError * Fix merge issue * Log fatal when configuration cannot be loaded * Propagate MarketError in slotCollateral * Remove useless configSync * Use result with explicit error * Fix syntax --------- Signed-off-by: Arnaud <arnaud@status.im>
This commit is contained in:
parent
fab5e16afd
commit
7065718e09
@ -134,6 +134,10 @@ proc bootstrapInteractions(s: CodexServer): Future[void] {.async.} =
|
||||
if config.simulateProofFailures > 0:
|
||||
warn "Proof failure simulation is not enabled for this build! Configuration ignored"
|
||||
|
||||
if error =? (await market.loadConfig()).errorOption:
|
||||
fatal "Cannot load market configuration", error = error.msg
|
||||
quit QuitFailure
|
||||
|
||||
let purchasing = Purchasing.new(market, clock)
|
||||
let sales = Sales.new(market, clock, repo, proofFailures)
|
||||
client = some ClientInteractions.new(clock, purchasing)
|
||||
|
||||
@ -55,11 +55,17 @@ template convertEthersError(body) =
|
||||
except EthersError as error:
|
||||
raiseMarketError(error.msgDetail)
|
||||
|
||||
proc config(market: OnChainMarket): Future[MarketplaceConfig] {.async.} =
|
||||
proc config(
|
||||
market: OnChainMarket
|
||||
): Future[MarketplaceConfig] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
without resolvedConfig =? market.configuration:
|
||||
let fetchedConfig = await market.contract.configuration()
|
||||
market.configuration = some fetchedConfig
|
||||
return fetchedConfig
|
||||
if err =? (await market.loadConfig()).errorOption:
|
||||
raiseMarketError(err.msg)
|
||||
|
||||
without config =? market.configuration:
|
||||
raiseMarketError("Failed to access to config from the Marketplace contract")
|
||||
|
||||
return config
|
||||
|
||||
return resolvedConfig
|
||||
|
||||
@ -70,7 +76,26 @@ proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} =
|
||||
let token = Erc20Token.new(tokenAddress, market.signer)
|
||||
discard await token.increaseAllowance(market.contract.address(), amount).confirm(1)
|
||||
|
||||
method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} =
|
||||
method loadConfig*(
|
||||
market: OnChainMarket
|
||||
): Future[?!void] {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
without config =? market.configuration:
|
||||
let fetchedConfig = await market.contract.configuration()
|
||||
|
||||
market.configuration = some fetchedConfig
|
||||
|
||||
return success()
|
||||
except AsyncLockError, EthersError:
|
||||
let err = getCurrentException()
|
||||
return failure newException(
|
||||
MarketError,
|
||||
"Failed to fetch the config from the Marketplace contract: " & err.msg,
|
||||
)
|
||||
|
||||
method getZkeyHash*(
|
||||
market: OnChainMarket
|
||||
): Future[?string] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
let config = await market.config()
|
||||
return some config.proofs.zkeyHash
|
||||
|
||||
@ -78,18 +103,24 @@ method getSigner*(market: OnChainMarket): Future[Address] {.async.} =
|
||||
convertEthersError:
|
||||
return await market.signer.getAddress()
|
||||
|
||||
method periodicity*(market: OnChainMarket): Future[Periodicity] {.async.} =
|
||||
method periodicity*(
|
||||
market: OnChainMarket
|
||||
): Future[Periodicity] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
convertEthersError:
|
||||
let config = await market.config()
|
||||
let period = config.proofs.period
|
||||
return Periodicity(seconds: period)
|
||||
|
||||
method proofTimeout*(market: OnChainMarket): Future[uint64] {.async.} =
|
||||
method proofTimeout*(
|
||||
market: OnChainMarket
|
||||
): Future[uint64] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
convertEthersError:
|
||||
let config = await market.config()
|
||||
return config.proofs.timeout
|
||||
|
||||
method repairRewardPercentage*(market: OnChainMarket): Future[uint8] {.async.} =
|
||||
method repairRewardPercentage*(
|
||||
market: OnChainMarket
|
||||
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
convertEthersError:
|
||||
let config = await market.config()
|
||||
return config.collateral.repairRewardPercentage
|
||||
@ -99,7 +130,9 @@ method requestDurationLimit*(market: OnChainMarket): Future[uint64] {.async.} =
|
||||
let config = await market.config()
|
||||
return config.requestDurationLimit
|
||||
|
||||
method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} =
|
||||
method proofDowntime*(
|
||||
market: OnChainMarket
|
||||
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
convertEthersError:
|
||||
let config = await market.config()
|
||||
return config.proofs.downtime
|
||||
@ -128,19 +161,22 @@ method requestStorage(market: OnChainMarket, request: StorageRequest) {.async.}
|
||||
|
||||
method getRequest*(
|
||||
market: OnChainMarket, id: RequestId
|
||||
): Future[?StorageRequest] {.async.} =
|
||||
let key = $id
|
||||
): Future[?StorageRequest] {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
let key = $id
|
||||
|
||||
if market.requestCache.contains(key):
|
||||
return some market.requestCache[key]
|
||||
if key in market.requestCache:
|
||||
return some market.requestCache[key]
|
||||
|
||||
convertEthersError:
|
||||
try:
|
||||
let request = await market.contract.getRequest(id)
|
||||
market.requestCache[key] = request
|
||||
return some request
|
||||
except Marketplace_UnknownRequest:
|
||||
return none StorageRequest
|
||||
let request = await market.contract.getRequest(id)
|
||||
market.requestCache[key] = request
|
||||
return some request
|
||||
except Marketplace_UnknownRequest, KeyError:
|
||||
warn "Cannot retrieve the request", error = getCurrentExceptionMsg()
|
||||
return none StorageRequest
|
||||
except EthersError, AsyncLockError:
|
||||
error "Cannot retrieve the request", error = getCurrentExceptionMsg()
|
||||
return none StorageRequest
|
||||
|
||||
method requestState*(
|
||||
market: OnChainMarket, requestId: RequestId
|
||||
@ -152,10 +188,17 @@ method requestState*(
|
||||
except Marketplace_UnknownRequest:
|
||||
return none RequestState
|
||||
|
||||
method slotState*(market: OnChainMarket, slotId: SlotId): Future[SlotState] {.async.} =
|
||||
method slotState*(
|
||||
market: OnChainMarket, slotId: SlotId
|
||||
): Future[SlotState] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
convertEthersError:
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.slotState(slotId, overrides)
|
||||
try:
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.slotState(slotId, overrides)
|
||||
except AsyncLockError as err:
|
||||
raiseMarketError(
|
||||
"Failed to fetch the slot state from the Marketplace contract: " & err.msg
|
||||
)
|
||||
|
||||
method getRequestEnd*(
|
||||
market: OnChainMarket, id: RequestId
|
||||
@ -507,3 +550,40 @@ method queryPastStorageRequestedEvents*(
|
||||
let fromBlock = await market.contract.provider.pastBlockTag(blocksAgo)
|
||||
|
||||
return await market.queryPastStorageRequestedEvents(fromBlock)
|
||||
|
||||
method slotCollateral*(
|
||||
market: OnChainMarket, requestId: RequestId, slotIndex: uint64
|
||||
): Future[?!UInt256] {.async: (raises: [CancelledError]).} =
|
||||
let slotid = slotId(requestId, slotIndex)
|
||||
|
||||
try:
|
||||
let slotState = await market.slotState(slotid)
|
||||
|
||||
without request =? await market.getRequest(requestId):
|
||||
return failure newException(
|
||||
MarketError, "Failure calculating the slotCollateral, cannot get the request"
|
||||
)
|
||||
|
||||
return market.slotCollateral(request.ask.collateralPerSlot, slotState)
|
||||
except MarketError as error:
|
||||
error "Error when trying to calculate the slotCollateral", error = error.msg
|
||||
return failure error
|
||||
|
||||
method slotCollateral*(
|
||||
market: OnChainMarket, collateralPerSlot: UInt256, slotState: SlotState
|
||||
): ?!UInt256 {.raises: [].} =
|
||||
if slotState == SlotState.Repair:
|
||||
without repairRewardPercentage =?
|
||||
market.configuration .? collateral .? repairRewardPercentage:
|
||||
return failure newException(
|
||||
MarketError,
|
||||
"Failure calculating the slotCollateral, cannot get the reward percentage",
|
||||
)
|
||||
|
||||
return success (
|
||||
collateralPerSlot - (collateralPerSlot * repairRewardPercentage.u256).div(
|
||||
100.u256
|
||||
)
|
||||
)
|
||||
|
||||
return success(collateralPerSlot)
|
||||
|
||||
@ -62,25 +62,40 @@ type
|
||||
ProofSubmitted* = object of MarketplaceEvent
|
||||
id*: SlotId
|
||||
|
||||
method getZkeyHash*(market: Market): Future[?string] {.base, async.} =
|
||||
method loadConfig*(
|
||||
market: Market
|
||||
): Future[?!void] {.base, async: (raises: [CancelledError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method getZkeyHash*(
|
||||
market: Market
|
||||
): Future[?string] {.base, async: (raises: [CancelledError, MarketError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method getSigner*(market: Market): Future[Address] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method periodicity*(market: Market): Future[Periodicity] {.base, async.} =
|
||||
method periodicity*(
|
||||
market: Market
|
||||
): Future[Periodicity] {.base, async: (raises: [CancelledError, MarketError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method proofTimeout*(market: Market): Future[uint64] {.base, async.} =
|
||||
method proofTimeout*(
|
||||
market: Market
|
||||
): Future[uint64] {.base, async: (raises: [CancelledError, MarketError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method repairRewardPercentage*(market: Market): Future[uint8] {.base, async.} =
|
||||
method repairRewardPercentage*(
|
||||
market: Market
|
||||
): Future[uint8] {.base, async: (raises: [CancelledError, MarketError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method requestDurationLimit*(market: Market): Future[uint64] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method proofDowntime*(market: Market): Future[uint8] {.base, async.} =
|
||||
method proofDowntime*(
|
||||
market: Market
|
||||
): Future[uint8] {.base, async: (raises: [CancelledError, MarketError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method getPointer*(market: Market, slotId: SlotId): Future[uint8] {.base, async.} =
|
||||
@ -102,7 +117,7 @@ method mySlots*(market: Market): Future[seq[SlotId]] {.base, async.} =
|
||||
|
||||
method getRequest*(
|
||||
market: Market, id: RequestId
|
||||
): Future[?StorageRequest] {.base, async.} =
|
||||
): Future[?StorageRequest] {.base, async: (raises: [CancelledError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method requestState*(
|
||||
@ -110,7 +125,9 @@ method requestState*(
|
||||
): Future[?RequestState] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method slotState*(market: Market, slotId: SlotId): Future[SlotState] {.base, async.} =
|
||||
method slotState*(
|
||||
market: Market, slotId: SlotId
|
||||
): Future[SlotState] {.base, async: (raises: [CancelledError, MarketError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method getRequestEnd*(
|
||||
@ -270,3 +287,13 @@ method queryPastStorageRequestedEvents*(
|
||||
market: Market, blocksAgo: int
|
||||
): Future[seq[StorageRequested]] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method slotCollateral*(
|
||||
market: Market, requestId: RequestId, slotIndex: uint64
|
||||
): Future[?!UInt256] {.base, async: (raises: [CancelledError]).} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method slotCollateral*(
|
||||
market: Market, collateralPerSlot: UInt256, slotState: SlotState
|
||||
): ?!UInt256 {.base, gcsafe, raises: [].} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
@ -591,7 +591,11 @@ proc requestStorage*(
|
||||
success purchase.id
|
||||
|
||||
proc onStore(
|
||||
self: CodexNodeRef, request: StorageRequest, slotIdx: uint64, blocksCb: BlocksCb
|
||||
self: CodexNodeRef,
|
||||
request: StorageRequest,
|
||||
slotIdx: uint64,
|
||||
blocksCb: BlocksCb,
|
||||
isRepairing: bool = false,
|
||||
): Future[?!void] {.async.} =
|
||||
## store data in local storage
|
||||
##
|
||||
@ -604,6 +608,10 @@ proc onStore(
|
||||
|
||||
trace "Received a request to store a slot"
|
||||
|
||||
# TODO: Use the isRepairing to manage the slot download.
|
||||
# If isRepairing is true, the slot has to be repaired before
|
||||
# being downloaded.
|
||||
|
||||
without manifest =? (await self.fetchManifest(cid)), err:
|
||||
trace "Unable to fetch manifest for cid", cid, err = err.msg
|
||||
return failure(err)
|
||||
@ -745,9 +753,12 @@ proc start*(self: CodexNodeRef) {.async.} =
|
||||
|
||||
if hostContracts =? self.contracts.host:
|
||||
hostContracts.sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest,
|
||||
slot: uint64,
|
||||
onBatch: BatchProc,
|
||||
isRepairing: bool = false,
|
||||
): Future[?!void] =
|
||||
self.onStore(request, slot, onBatch)
|
||||
self.onStore(request, slot, onBatch, isRepairing)
|
||||
|
||||
hostContracts.sales.onExpiryUpdate = proc(
|
||||
rootCid: Cid, expiry: SecondsSince1970
|
||||
|
||||
@ -157,13 +157,28 @@ proc cleanUp(
|
||||
# Re-add items back into the queue to prevent small availabilities from
|
||||
# draining the queue. Seen items will be ordered last.
|
||||
if reprocessSlot and request =? data.request:
|
||||
let queue = sales.context.slotQueue
|
||||
var seenItem = SlotQueueItem.init(
|
||||
data.requestId, data.slotIndex.uint16, data.ask, request.expiry, seen = true
|
||||
)
|
||||
trace "pushing ignored item to queue, marked as seen"
|
||||
if err =? queue.push(seenItem).errorOption:
|
||||
error "failed to readd slot to queue", errorType = $(type err), error = err.msg
|
||||
try:
|
||||
without collateral =?
|
||||
await sales.context.market.slotCollateral(data.requestId, data.slotIndex), err:
|
||||
error "Failed to re-add item back to the slot queue: unable to calculate collateral",
|
||||
error = err.msg
|
||||
return
|
||||
|
||||
let queue = sales.context.slotQueue
|
||||
var seenItem = SlotQueueItem.init(
|
||||
data.requestId,
|
||||
data.slotIndex.uint16,
|
||||
data.ask,
|
||||
request.expiry,
|
||||
seen = true,
|
||||
collateral = collateral,
|
||||
)
|
||||
trace "pushing ignored item to queue, marked as seen"
|
||||
if err =? queue.push(seenItem).errorOption:
|
||||
error "failed to readd slot to queue", errorType = $(type err), error = err.msg
|
||||
except MarketError as e:
|
||||
error "Failed to re-add item back to the slot queue.", error = e.msg
|
||||
return
|
||||
|
||||
await sales.remove(agent)
|
||||
|
||||
@ -283,7 +298,7 @@ proc onAvailabilityAdded(sales: Sales, availability: Availability) {.async.} =
|
||||
|
||||
proc onStorageRequested(
|
||||
sales: Sales, requestId: RequestId, ask: StorageAsk, expiry: uint64
|
||||
) =
|
||||
) {.raises: [].} =
|
||||
logScope:
|
||||
topics = "marketplace sales onStorageRequested"
|
||||
requestId
|
||||
@ -294,7 +309,14 @@ proc onStorageRequested(
|
||||
|
||||
trace "storage requested, adding slots to queue"
|
||||
|
||||
without items =? SlotQueueItem.init(requestId, ask, expiry).catch, err:
|
||||
let market = sales.context.market
|
||||
|
||||
without collateral =? market.slotCollateral(ask.collateralPerSlot, SlotState.Free),
|
||||
err:
|
||||
error "Request failure, unable to calculate collateral", error = err.msg
|
||||
return
|
||||
|
||||
without items =? SlotQueueItem.init(requestId, ask, expiry, collateral).catch, err:
|
||||
if err of SlotsOutOfRangeError:
|
||||
warn "Too many slots, cannot add to queue"
|
||||
else:
|
||||
@ -319,35 +341,45 @@ proc onSlotFreed(sales: Sales, requestId: RequestId, slotIndex: uint64) =
|
||||
|
||||
trace "slot freed, adding to queue"
|
||||
|
||||
proc addSlotToQueue() {.async: (raises: []).} =
|
||||
proc addSlotToQueue() {.async: (raises: [CancelledError]).} =
|
||||
let context = sales.context
|
||||
let market = context.market
|
||||
let queue = context.slotQueue
|
||||
|
||||
without request =? (await market.getRequest(requestId)), err:
|
||||
error "unknown request in contract", error = err.msgDetail
|
||||
return
|
||||
|
||||
# Take the repairing state into consideration to calculate the collateral.
|
||||
# This is particularly needed because it will affect the priority in the queue
|
||||
# and we want to give the user the ability to tweak the parameters.
|
||||
# Adding the repairing state directly in the queue priority calculation
|
||||
# would not allow this flexibility.
|
||||
without collateral =?
|
||||
market.slotCollateral(request.ask.collateralPerSlot, SlotState.Repair), err:
|
||||
error "Failed to add freed slot to queue: unable to calculate collateral",
|
||||
error = err.msg
|
||||
return
|
||||
|
||||
if slotIndex > uint16.high.uint64:
|
||||
error "Cannot cast slot index to uint16, value = ", slotIndex
|
||||
return
|
||||
|
||||
# first attempt to populate request using existing metadata in queue
|
||||
without var found =? queue.populateItem(requestId, slotIndex.uint16):
|
||||
trace "no existing request metadata, getting request info from contract"
|
||||
# if there's no existing slot for that request, retrieve the request
|
||||
# from the contract.
|
||||
try:
|
||||
without request =? await market.getRequest(requestId):
|
||||
error "unknown request in contract"
|
||||
return
|
||||
without slotQueueItem =?
|
||||
SlotQueueItem.init(request, slotIndex.uint16, collateral = collateral).catch, err:
|
||||
warn "Too many slots, cannot add to queue", error = err.msgDetail
|
||||
return
|
||||
|
||||
found = SlotQueueItem.init(request, slotIndex.uint16)
|
||||
except CancelledError:
|
||||
discard # do not propagate as addSlotToQueue was asyncSpawned
|
||||
except CatchableError as e:
|
||||
error "failed to get request from contract and add slots to queue",
|
||||
error = e.msgDetail
|
||||
|
||||
if err =? queue.push(found).errorOption:
|
||||
error "failed to push slot items to queue", error = err.msgDetail
|
||||
if err =? queue.push(slotQueueItem).errorOption:
|
||||
if err of SlotQueueItemExistsError:
|
||||
error "Failed to push item to queue becaue it already exists",
|
||||
error = err.msgDetail
|
||||
elif err of QueueNotRunningError:
|
||||
warn "Failed to push item to queue becaue queue is not running",
|
||||
error = err.msgDetail
|
||||
|
||||
# We could get rid of this by adding the storage ask in the SlotFreed event,
|
||||
# so we would not need to call getRequest to get the collateralPerSlot.
|
||||
let fut = addSlotToQueue()
|
||||
sales.trackedFutures.track(fut)
|
||||
asyncSpawn fut
|
||||
@ -356,7 +388,9 @@ proc subscribeRequested(sales: Sales) {.async.} =
|
||||
let context = sales.context
|
||||
let market = context.market
|
||||
|
||||
proc onStorageRequested(requestId: RequestId, ask: StorageAsk, expiry: uint64) =
|
||||
proc onStorageRequested(
|
||||
requestId: RequestId, ask: StorageAsk, expiry: uint64
|
||||
) {.raises: [].} =
|
||||
sales.onStorageRequested(requestId, ask, expiry)
|
||||
|
||||
try:
|
||||
|
||||
@ -26,7 +26,7 @@ type
|
||||
|
||||
BlocksCb* = proc(blocks: seq[bt.Block]): Future[?!void] {.gcsafe, raises: [].}
|
||||
OnStore* = proc(
|
||||
request: StorageRequest, slot: uint64, blocksCb: BlocksCb
|
||||
request: StorageRequest, slot: uint64, blocksCb: BlocksCb, isRepairing: bool
|
||||
): Future[?!void] {.gcsafe, upraises: [].}
|
||||
OnProve* = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.
|
||||
gcsafe, upraises: []
|
||||
|
||||
@ -34,7 +34,7 @@ type
|
||||
slotSize: uint64
|
||||
duration: uint64
|
||||
pricePerBytePerSecond: UInt256
|
||||
collateralPerByte: UInt256
|
||||
collateral: UInt256 # Collateral computed
|
||||
expiry: uint64
|
||||
seen: bool
|
||||
|
||||
@ -76,9 +76,6 @@ proc profitability(item: SlotQueueItem): UInt256 =
|
||||
slotSize: item.slotSize,
|
||||
).pricePerSlot
|
||||
|
||||
proc collateralPerSlot(item: SlotQueueItem): UInt256 =
|
||||
StorageAsk(collateralPerByte: item.collateralPerByte, slotSize: item.slotSize).collateralPerSlot
|
||||
|
||||
proc `<`*(a, b: SlotQueueItem): bool =
|
||||
# for A to have a higher priority than B (in a min queue), A must be less than
|
||||
# B.
|
||||
@ -95,8 +92,8 @@ proc `<`*(a, b: SlotQueueItem): bool =
|
||||
scoreA.addIf(a.profitability > b.profitability, 3)
|
||||
scoreB.addIf(a.profitability < b.profitability, 3)
|
||||
|
||||
scoreA.addIf(a.collateralPerSlot < b.collateralPerSlot, 2)
|
||||
scoreB.addIf(a.collateralPerSlot > b.collateralPerSlot, 2)
|
||||
scoreA.addIf(a.collateral < b.collateral, 2)
|
||||
scoreB.addIf(a.collateral > b.collateral, 2)
|
||||
|
||||
scoreA.addIf(a.expiry > b.expiry, 1)
|
||||
scoreB.addIf(a.expiry < b.expiry, 1)
|
||||
@ -137,6 +134,7 @@ proc init*(
|
||||
slotIndex: uint16,
|
||||
ask: StorageAsk,
|
||||
expiry: uint64,
|
||||
collateral: UInt256,
|
||||
seen = false,
|
||||
): SlotQueueItem =
|
||||
SlotQueueItem(
|
||||
@ -145,25 +143,32 @@ proc init*(
|
||||
slotSize: ask.slotSize,
|
||||
duration: ask.duration,
|
||||
pricePerBytePerSecond: ask.pricePerBytePerSecond,
|
||||
collateralPerByte: ask.collateralPerByte,
|
||||
collateral: collateral,
|
||||
expiry: expiry,
|
||||
seen: seen,
|
||||
)
|
||||
|
||||
proc init*(
|
||||
_: type SlotQueueItem, request: StorageRequest, slotIndex: uint16
|
||||
_: type SlotQueueItem,
|
||||
request: StorageRequest,
|
||||
slotIndex: uint16,
|
||||
collateral: UInt256,
|
||||
): SlotQueueItem =
|
||||
SlotQueueItem.init(request.id, slotIndex, request.ask, request.expiry)
|
||||
SlotQueueItem.init(request.id, slotIndex, request.ask, request.expiry, collateral)
|
||||
|
||||
proc init*(
|
||||
_: type SlotQueueItem, requestId: RequestId, ask: StorageAsk, expiry: uint64
|
||||
): seq[SlotQueueItem] =
|
||||
_: type SlotQueueItem,
|
||||
requestId: RequestId,
|
||||
ask: StorageAsk,
|
||||
expiry: uint64,
|
||||
collateral: UInt256,
|
||||
): seq[SlotQueueItem] {.raises: [SlotsOutOfRangeError].} =
|
||||
if not ask.slots.inRange:
|
||||
raise newException(SlotsOutOfRangeError, "Too many slots")
|
||||
|
||||
var i = 0'u16
|
||||
proc initSlotQueueItem(): SlotQueueItem =
|
||||
let item = SlotQueueItem.init(requestId, i, ask, expiry)
|
||||
let item = SlotQueueItem.init(requestId, i, ask, expiry, collateral)
|
||||
inc i
|
||||
return item
|
||||
|
||||
@ -171,8 +176,10 @@ proc init*(
|
||||
Rng.instance.shuffle(items)
|
||||
return items
|
||||
|
||||
proc init*(_: type SlotQueueItem, request: StorageRequest): seq[SlotQueueItem] =
|
||||
return SlotQueueItem.init(request.id, request.ask, request.expiry)
|
||||
proc init*(
|
||||
_: type SlotQueueItem, request: StorageRequest, collateral: UInt256
|
||||
): seq[SlotQueueItem] =
|
||||
return SlotQueueItem.init(request.id, request.ask, request.expiry, collateral)
|
||||
|
||||
proc inRange*(val: SomeUnsignedInt): bool =
|
||||
val.uint16 in SlotQueueSize.low .. SlotQueueSize.high
|
||||
@ -234,25 +241,7 @@ proc unpause*(self: SlotQueue) =
|
||||
# set unpaused flag to true -- unblocks coroutines waiting on unpaused.wait()
|
||||
self.unpaused.fire()
|
||||
|
||||
proc populateItem*(
|
||||
self: SlotQueue, requestId: RequestId, slotIndex: uint16
|
||||
): ?SlotQueueItem =
|
||||
trace "populate item, items in queue", len = self.queue.len
|
||||
for item in self.queue.items:
|
||||
trace "populate item search", itemRequestId = item.requestId, requestId
|
||||
if item.requestId == requestId:
|
||||
return some SlotQueueItem(
|
||||
requestId: requestId,
|
||||
slotIndex: slotIndex,
|
||||
slotSize: item.slotSize,
|
||||
duration: item.duration,
|
||||
pricePerBytePerSecond: item.pricePerBytePerSecond,
|
||||
collateralPerByte: item.collateralPerByte,
|
||||
expiry: item.expiry,
|
||||
)
|
||||
return none SlotQueueItem
|
||||
|
||||
proc push*(self: SlotQueue, item: SlotQueueItem): ?!void =
|
||||
proc push*(self: SlotQueue, item: SlotQueueItem): ?!void {.raises: [].} =
|
||||
logScope:
|
||||
requestId = item.requestId
|
||||
slotIndex = item.slotIndex
|
||||
|
||||
@ -67,8 +67,11 @@ method run*(
|
||||
return await reservations.release(reservation.id, reservation.availabilityId, bytes)
|
||||
|
||||
try:
|
||||
let slotId = slotId(request.id, data.slotIndex)
|
||||
let isRepairing = (await context.market.slotState(slotId)) == SlotState.Repair
|
||||
|
||||
trace "Starting download"
|
||||
if err =? (await onStore(request, data.slotIndex, onBlocks)).errorOption:
|
||||
if err =? (await onStore(request, data.slotIndex, onBlocks, isRepairing)).errorOption:
|
||||
return some State(SaleErrored(error: err, reprocessSlot: false))
|
||||
|
||||
trace "Download complete"
|
||||
|
||||
@ -38,18 +38,11 @@ method run*(
|
||||
slotIndex = data.slotIndex
|
||||
|
||||
try:
|
||||
let slotState = await market.slotState(slotId(data.requestId, data.slotIndex))
|
||||
let requestedCollateral = request.ask.collateralPerSlot
|
||||
var collateral: UInt256
|
||||
|
||||
if slotState == SlotState.Repair:
|
||||
# When repairing the node gets "discount" on the collateral that it needs to
|
||||
let repairRewardPercentage = (await market.repairRewardPercentage).u256
|
||||
collateral =
|
||||
requestedCollateral -
|
||||
((requestedCollateral * repairRewardPercentage)).div(100.u256)
|
||||
else:
|
||||
collateral = requestedCollateral
|
||||
without collateral =? await market.slotCollateral(data.requestId, data.slotIndex),
|
||||
err:
|
||||
error "Failure attempting to fill slot: unable to calculate collateral",
|
||||
error = err.msg
|
||||
return
|
||||
|
||||
debug "Filling slot"
|
||||
try:
|
||||
|
||||
@ -138,22 +138,35 @@ proc new*(_: type MockMarket, clock: ?Clock = Clock.none): MockMarket =
|
||||
signer: Address.example, config: config, canReserveSlot: true, clock: clock
|
||||
)
|
||||
|
||||
method loadConfig*(
|
||||
market: MockMarket
|
||||
): Future[?!void] {.async: (raises: [CancelledError]).} =
|
||||
discard
|
||||
|
||||
method getSigner*(market: MockMarket): Future[Address] {.async.} =
|
||||
return market.signer
|
||||
|
||||
method periodicity*(mock: MockMarket): Future[Periodicity] {.async.} =
|
||||
method periodicity*(
|
||||
mock: MockMarket
|
||||
): Future[Periodicity] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
return Periodicity(seconds: mock.config.proofs.period)
|
||||
|
||||
method proofTimeout*(market: MockMarket): Future[uint64] {.async.} =
|
||||
method proofTimeout*(
|
||||
market: MockMarket
|
||||
): Future[uint64] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
return market.config.proofs.timeout
|
||||
|
||||
method requestDurationLimit*(market: MockMarket): Future[uint64] {.async.} =
|
||||
return market.config.requestDurationLimit
|
||||
|
||||
method proofDowntime*(market: MockMarket): Future[uint8] {.async.} =
|
||||
method proofDowntime*(
|
||||
market: MockMarket
|
||||
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
return market.config.proofs.downtime
|
||||
|
||||
method repairRewardPercentage*(market: MockMarket): Future[uint8] {.async.} =
|
||||
method repairRewardPercentage*(
|
||||
market: MockMarket
|
||||
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
return market.config.collateral.repairRewardPercentage
|
||||
|
||||
method getPointer*(market: MockMarket, slotId: SlotId): Future[uint8] {.async.} =
|
||||
@ -173,7 +186,7 @@ method mySlots*(market: MockMarket): Future[seq[SlotId]] {.async.} =
|
||||
|
||||
method getRequest*(
|
||||
market: MockMarket, id: RequestId
|
||||
): Future[?StorageRequest] {.async.} =
|
||||
): Future[?StorageRequest] {.async: (raises: [CancelledError]).} =
|
||||
for request in market.requested:
|
||||
if request.id == id:
|
||||
return some request
|
||||
@ -191,10 +204,16 @@ method requestState*(
|
||||
): Future[?RequestState] {.async.} =
|
||||
return market.requestState .? [requestId]
|
||||
|
||||
method slotState*(market: MockMarket, slotId: SlotId): Future[SlotState] {.async.} =
|
||||
if not market.slotState.hasKey(slotId):
|
||||
method slotState*(
|
||||
market: MockMarket, slotId: SlotId
|
||||
): Future[SlotState] {.async: (raises: [CancelledError, MarketError]).} =
|
||||
if slotId notin market.slotState:
|
||||
return SlotState.Free
|
||||
return market.slotState[slotId]
|
||||
|
||||
try:
|
||||
return market.slotState[slotId]
|
||||
except KeyError as e:
|
||||
raiseAssert "SlotId not found in known slots (MockMarket.slotState)"
|
||||
|
||||
method getRequestEnd*(
|
||||
market: MockMarket, id: RequestId
|
||||
@ -534,3 +553,33 @@ method unsubscribe*(subscription: ProofSubmittedSubscription) {.async.} =
|
||||
|
||||
method unsubscribe*(subscription: SlotReservationsFullSubscription) {.async.} =
|
||||
subscription.market.subscriptions.onSlotReservationsFull.keepItIf(it != subscription)
|
||||
|
||||
method slotCollateral*(
|
||||
market: MockMarket, requestId: RequestId, slotIndex: uint64
|
||||
): Future[?!UInt256] {.async: (raises: [CancelledError]).} =
|
||||
let slotid = slotId(requestId, slotIndex)
|
||||
|
||||
try:
|
||||
let state = await slotState(market, slotid)
|
||||
|
||||
without request =? await market.getRequest(requestId):
|
||||
return failure newException(
|
||||
MarketError, "Failure calculating the slotCollateral, cannot get the request"
|
||||
)
|
||||
|
||||
return market.slotCollateral(request.ask.collateralPerSlot, state)
|
||||
except MarketError as error:
|
||||
error "Error when trying to calculate the slotCollateral", error = error.msg
|
||||
return failure error
|
||||
|
||||
method slotCollateral*(
|
||||
market: MockMarket, collateralPerSlot: UInt256, slotState: SlotState
|
||||
): ?!UInt256 {.raises: [].} =
|
||||
if slotState == SlotState.Repair:
|
||||
let repairRewardPercentage = market.config.collateral.repairRewardPercentage.u256
|
||||
|
||||
return success (
|
||||
collateralPerSlot - (collateralPerSlot * repairRewardPercentage).div(100.u256)
|
||||
)
|
||||
|
||||
return success collateralPerSlot
|
||||
|
||||
@ -7,7 +7,7 @@ type MockSlotQueueItem* = object
|
||||
slotSize*: uint64
|
||||
duration*: uint64
|
||||
pricePerBytePerSecond*: UInt256
|
||||
collateralPerByte*: UInt256
|
||||
collateral*: UInt256
|
||||
expiry*: uint64
|
||||
seen*: bool
|
||||
|
||||
@ -19,8 +19,8 @@ proc toSlotQueueItem*(item: MockSlotQueueItem): SlotQueueItem =
|
||||
slotSize: item.slotSize,
|
||||
duration: item.duration,
|
||||
pricePerBytePerSecond: item.pricePerBytePerSecond,
|
||||
collateralPerByte: item.collateralPerByte,
|
||||
),
|
||||
expiry = item.expiry,
|
||||
seen = item.seen,
|
||||
collateral = item.collateral,
|
||||
)
|
||||
|
||||
@ -125,7 +125,7 @@ asyncchecksuite "Test Node - Host contracts":
|
||||
fetchedBytes += blk.data.len.uint
|
||||
return success()
|
||||
|
||||
(await onStore(request, 1.uint64, onBlocks)).tryGet()
|
||||
(await onStore(request, 1.uint64, onBlocks, isRepairing = false)).tryGet()
|
||||
check fetchedBytes == 12 * DefaultBlockSize.uint
|
||||
|
||||
let indexer = verifiable.protectedStrategy.init(
|
||||
|
||||
@ -62,7 +62,7 @@ asyncchecksuite "Sales - start":
|
||||
sales = Sales.new(market, clock, repo)
|
||||
reservations = sales.context.reservations
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
return success()
|
||||
|
||||
@ -181,7 +181,7 @@ asyncchecksuite "Sales":
|
||||
sales = Sales.new(market, clock, repo)
|
||||
reservations = sales.context.reservations
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
return success()
|
||||
|
||||
@ -229,7 +229,7 @@ asyncchecksuite "Sales":
|
||||
availability = a.get # update id
|
||||
|
||||
proc notProcessed(itemsProcessed: seq[SlotQueueItem], request: StorageRequest): bool =
|
||||
let items = SlotQueueItem.init(request)
|
||||
let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot)
|
||||
for i in 0 ..< items.len:
|
||||
if itemsProcessed.contains(items[i]):
|
||||
return false
|
||||
@ -266,7 +266,7 @@ asyncchecksuite "Sales":
|
||||
done.complete()
|
||||
createAvailability()
|
||||
await market.requestStorage(request)
|
||||
let items = SlotQueueItem.init(request)
|
||||
let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot)
|
||||
check eventually items.allIt(itemsProcessed.contains(it))
|
||||
|
||||
test "removes slots from slot queue once RequestCancelled emitted":
|
||||
@ -287,13 +287,15 @@ asyncchecksuite "Sales":
|
||||
test "removes slot index from slot queue once SlotFilled emitted":
|
||||
let request1 = await addRequestToSaturatedQueue()
|
||||
market.emitSlotFilled(request1.id, 1.uint64)
|
||||
let expected = SlotQueueItem.init(request1, 1'u16)
|
||||
let expected =
|
||||
SlotQueueItem.init(request1, 1'u16, collateral = request1.ask.collateralPerSlot)
|
||||
check always (not itemsProcessed.contains(expected))
|
||||
|
||||
test "removes slot index from slot queue once SlotReservationsFull emitted":
|
||||
let request1 = await addRequestToSaturatedQueue()
|
||||
market.emitSlotReservationsFull(request1.id, 1.uint64)
|
||||
let expected = SlotQueueItem.init(request1, 1'u16)
|
||||
let expected =
|
||||
SlotQueueItem.init(request1, 1'u16, collateral = request1.ask.collateralPerSlot)
|
||||
check always (not itemsProcessed.contains(expected))
|
||||
|
||||
test "adds slot index to slot queue once SlotFreed emitted":
|
||||
@ -303,14 +305,21 @@ asyncchecksuite "Sales":
|
||||
|
||||
createAvailability()
|
||||
market.requested.add request # "contract" must be able to return request
|
||||
|
||||
market.emitSlotFreed(request.id, 2.uint64)
|
||||
|
||||
let expected = SlotQueueItem.init(request, 2.uint16)
|
||||
without collateralPerSlot =? await market.slotCollateral(request.id, 2.uint64),
|
||||
error:
|
||||
fail()
|
||||
|
||||
let expected =
|
||||
SlotQueueItem.init(request, 2.uint16, collateral = request.ask.collateralPerSlot)
|
||||
|
||||
check eventually itemsProcessed.contains(expected)
|
||||
|
||||
test "items in queue are readded (and marked seen) once ignored":
|
||||
await market.requestStorage(request)
|
||||
let items = SlotQueueItem.init(request)
|
||||
let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot)
|
||||
check eventually queue.len > 0
|
||||
# queue starts paused, allow items to be added to the queue
|
||||
check eventually queue.paused
|
||||
@ -331,7 +340,7 @@ asyncchecksuite "Sales":
|
||||
test "queue is paused once availability is insufficient to service slots in queue":
|
||||
createAvailability() # enough to fill a single slot
|
||||
await market.requestStorage(request)
|
||||
let items = SlotQueueItem.init(request)
|
||||
let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot)
|
||||
check eventually queue.len > 0
|
||||
# queue starts paused, allow items to be added to the queue
|
||||
check eventually queue.paused
|
||||
@ -348,7 +357,7 @@ asyncchecksuite "Sales":
|
||||
|
||||
test "availability size is reduced by request slot size when fully downloaded":
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
let blk = bt.Block.new(@[1.byte]).get
|
||||
await onBatch(blk.repeat(request.ask.slotSize.int))
|
||||
@ -361,7 +370,7 @@ asyncchecksuite "Sales":
|
||||
test "non-downloaded bytes are returned to availability once finished":
|
||||
var slotIndex = 0.uint64
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
slotIndex = slot
|
||||
let blk = bt.Block.new(@[1.byte]).get
|
||||
@ -421,7 +430,7 @@ asyncchecksuite "Sales":
|
||||
var storingRequest: StorageRequest
|
||||
var storingSlot: uint64
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
storingRequest = request
|
||||
storingSlot = slot
|
||||
@ -434,7 +443,7 @@ asyncchecksuite "Sales":
|
||||
test "makes storage available again when data retrieval fails":
|
||||
let error = newException(IOError, "data retrieval failed")
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
return failure(error)
|
||||
createAvailability()
|
||||
@ -503,7 +512,7 @@ asyncchecksuite "Sales":
|
||||
test "makes storage available again when other host fills the slot":
|
||||
let otherHost = Address.example
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
await sleepAsync(chronos.hours(1))
|
||||
return success()
|
||||
@ -519,7 +528,7 @@ asyncchecksuite "Sales":
|
||||
|
||||
let origSize = availability.freeSize
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
await sleepAsync(chronos.hours(1))
|
||||
return success()
|
||||
@ -544,7 +553,7 @@ asyncchecksuite "Sales":
|
||||
|
||||
let origSize = availability.freeSize
|
||||
sales.onStore = proc(
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc
|
||||
request: StorageRequest, slot: uint64, onBatch: BatchProc, isRepairing = false
|
||||
): Future[?!void] {.async.} =
|
||||
await sleepAsync(chronos.hours(1))
|
||||
return success()
|
||||
|
||||
@ -159,8 +159,10 @@ suite "Slot queue":
|
||||
requestB.ask.collateralPerByte = 1.u256
|
||||
requestB.expiry = 1000.uint64
|
||||
|
||||
let itemA = SlotQueueItem.init(requestA, 0)
|
||||
let itemB = SlotQueueItem.init(requestB, 0)
|
||||
let itemA =
|
||||
SlotQueueItem.init(requestA, 0, collateral = requestA.ask.collateralPerSlot)
|
||||
let itemB =
|
||||
SlotQueueItem.init(requestB, 0, collateral = requestB.ask.collateralPerSlot)
|
||||
check itemB < itemA # B higher priority than A
|
||||
check itemA > itemB
|
||||
|
||||
@ -172,7 +174,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64,
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 2.u256, # profitability is higher (good)
|
||||
collateralPerByte: 1.u256,
|
||||
collateral: 1.u256,
|
||||
expiry: 1.uint64,
|
||||
seen: true, # seen (bad), more weight than profitability
|
||||
)
|
||||
@ -182,7 +184,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64,
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256, # profitability is lower (bad)
|
||||
collateralPerByte: 1.u256,
|
||||
collateral: 1.u256,
|
||||
expiry: 1.uint64,
|
||||
seen: false, # not seen (good)
|
||||
)
|
||||
@ -197,7 +199,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64,
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256, # reward is lower (bad)
|
||||
collateralPerByte: 1.u256, # collateral is lower (good)
|
||||
collateral: 1.u256, # collateral is lower (good)
|
||||
expiry: 1.uint64,
|
||||
seen: false,
|
||||
)
|
||||
@ -208,7 +210,7 @@ suite "Slot queue":
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 2.u256,
|
||||
# reward is higher (good), more weight than collateral
|
||||
collateralPerByte: 2.u256, # collateral is higher (bad)
|
||||
collateral: 2.u256, # collateral is higher (bad)
|
||||
expiry: 1.uint64,
|
||||
seen: false,
|
||||
)
|
||||
@ -223,7 +225,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64,
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256,
|
||||
collateralPerByte: 2.u256, # collateral is higher (bad)
|
||||
collateral: 2.u256, # collateral is higher (bad)
|
||||
expiry: 2.uint64, # expiry is longer (good)
|
||||
seen: false,
|
||||
)
|
||||
@ -233,7 +235,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64,
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256,
|
||||
collateralPerByte: 1.u256, # collateral is lower (good), more weight than expiry
|
||||
collateral: 1.u256, # collateral is lower (good), more weight than expiry
|
||||
expiry: 1.uint64, # expiry is shorter (bad)
|
||||
seen: false,
|
||||
)
|
||||
@ -248,7 +250,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64, # slotSize is smaller (good)
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256,
|
||||
collateralPerByte: 1.u256,
|
||||
collateral: 1.u256,
|
||||
expiry: 1.uint64, # expiry is shorter (bad)
|
||||
seen: false,
|
||||
)
|
||||
@ -258,7 +260,7 @@ suite "Slot queue":
|
||||
slotSize: 2.uint64, # slotSize is larger (bad)
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256,
|
||||
collateralPerByte: 1.u256,
|
||||
collateral: 1.u256,
|
||||
expiry: 2.uint64, # expiry is longer (good), more weight than slotSize
|
||||
seen: false,
|
||||
)
|
||||
@ -273,7 +275,7 @@ suite "Slot queue":
|
||||
slotSize: 2.uint64, # slotSize is larger (bad)
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256,
|
||||
collateralPerByte: 1.u256,
|
||||
collateral: 1.u256,
|
||||
expiry: 1.uint64, # expiry is shorter (bad)
|
||||
seen: false,
|
||||
)
|
||||
@ -283,7 +285,7 @@ suite "Slot queue":
|
||||
slotSize: 1.uint64, # slotSize is smaller (good)
|
||||
duration: 1.uint64,
|
||||
pricePerBytePerSecond: 1.u256,
|
||||
collateralPerByte: 1.u256,
|
||||
collateral: 1.u256,
|
||||
expiry: 1.uint64,
|
||||
seen: false,
|
||||
)
|
||||
@ -292,11 +294,16 @@ suite "Slot queue":
|
||||
|
||||
test "expands available all possible slot indices on init":
|
||||
let request = StorageRequest.example
|
||||
let items = SlotQueueItem.init(request)
|
||||
let items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot)
|
||||
check items.len.uint64 == request.ask.slots
|
||||
var checked = 0
|
||||
for slotIndex in 0'u16 ..< request.ask.slots.uint16:
|
||||
check items.anyIt(it == SlotQueueItem.init(request, slotIndex))
|
||||
check items.anyIt(
|
||||
it ==
|
||||
SlotQueueItem.init(
|
||||
request, slotIndex, collateral = request.ask.collateralPerSlot
|
||||
)
|
||||
)
|
||||
inc checked
|
||||
check checked == items.len
|
||||
|
||||
@ -322,34 +329,17 @@ suite "Slot queue":
|
||||
check isOk queue.push(item3)
|
||||
check isOk queue.push(item4)
|
||||
|
||||
test "populates item with exisiting request metadata":
|
||||
newSlotQueue(maxSize = 8, maxWorkers = 1, processSlotDelay = 10.millis)
|
||||
let request0 = StorageRequest.example
|
||||
var request1 = StorageRequest.example
|
||||
request1.ask.collateralPerByte += 1.u256
|
||||
let items0 = SlotQueueItem.init(request0)
|
||||
let items1 = SlotQueueItem.init(request1)
|
||||
check queue.push(items0).isOk
|
||||
check queue.push(items1).isOk
|
||||
let populated = !queue.populateItem(request1.id, 12'u16)
|
||||
check populated.requestId == request1.id
|
||||
check populated.slotIndex == 12'u16
|
||||
check populated.slotSize == request1.ask.slotSize
|
||||
check populated.duration == request1.ask.duration
|
||||
check populated.pricePerBytePerSecond == request1.ask.pricePerBytePerSecond
|
||||
check populated.collateralPerByte == request1.ask.collateralPerByte
|
||||
|
||||
test "does not find exisiting request metadata":
|
||||
newSlotQueue(maxSize = 2, maxWorkers = 2)
|
||||
let item = SlotQueueItem.example
|
||||
check queue.populateItem(item.requestId, 12'u16).isNone
|
||||
|
||||
test "can support uint16.high slots":
|
||||
var request = StorageRequest.example
|
||||
let maxUInt16 = uint16.high
|
||||
let uint64Slots = uint64(maxUInt16)
|
||||
request.ask.slots = uint64Slots
|
||||
let items = SlotQueueItem.init(request.id, request.ask, request.expiry)
|
||||
let items = SlotQueueItem.init(
|
||||
request.id,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
collateral = request.ask.collateralPerSlot,
|
||||
)
|
||||
check items.len.uint16 == maxUInt16
|
||||
|
||||
test "cannot support greater than uint16.high slots":
|
||||
@ -358,7 +348,12 @@ suite "Slot queue":
|
||||
let uint64Slots = uint64(int32Slots)
|
||||
request.ask.slots = uint64Slots
|
||||
expect SlotsOutOfRangeError:
|
||||
discard SlotQueueItem.init(request.id, request.ask, request.expiry)
|
||||
discard SlotQueueItem.init(
|
||||
request.id,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
collateral = request.ask.collateralPerSlot,
|
||||
)
|
||||
|
||||
test "cannot push duplicate items":
|
||||
newSlotQueue(maxSize = 6, maxWorkers = 1, processSlotDelay = 15.millis)
|
||||
@ -399,8 +394,10 @@ suite "Slot queue":
|
||||
let request0 = StorageRequest.example
|
||||
var request1 = StorageRequest.example
|
||||
request1.ask.collateralPerByte += 1.u256
|
||||
let items0 = SlotQueueItem.init(request0)
|
||||
let items1 = SlotQueueItem.init(request1)
|
||||
let items0 =
|
||||
SlotQueueItem.init(request0, collateral = request0.ask.collateralPerSlot)
|
||||
let items1 =
|
||||
SlotQueueItem.init(request1, collateral = request1.ask.collateralPerSlot)
|
||||
check queue.push(items0).isOk
|
||||
check queue.push(items1).isOk
|
||||
let last = items1[items1.high]
|
||||
@ -413,8 +410,10 @@ suite "Slot queue":
|
||||
let request0 = StorageRequest.example
|
||||
var request1 = StorageRequest.example
|
||||
request1.ask.collateralPerByte += 1.u256
|
||||
let items0 = SlotQueueItem.init(request0)
|
||||
let items1 = SlotQueueItem.init(request1)
|
||||
let items0 =
|
||||
SlotQueueItem.init(request0, collateral = request0.ask.collateralPerSlot)
|
||||
let items1 =
|
||||
SlotQueueItem.init(request1, collateral = request1.ask.collateralPerSlot)
|
||||
check queue.push(items0).isOk
|
||||
check queue.push(items1).isOk
|
||||
queue.delete(request1.id)
|
||||
@ -433,42 +432,56 @@ suite "Slot queue":
|
||||
request3.ask.collateralPerByte = request2.ask.collateralPerByte + 1
|
||||
request4.ask.collateralPerByte = request3.ask.collateralPerByte + 1
|
||||
request5.ask.collateralPerByte = request4.ask.collateralPerByte + 1
|
||||
let item0 = SlotQueueItem.init(request0, 0)
|
||||
let item1 = SlotQueueItem.init(request1, 0)
|
||||
let item2 = SlotQueueItem.init(request2, 0)
|
||||
let item3 = SlotQueueItem.init(request3, 0)
|
||||
let item4 = SlotQueueItem.init(request4, 0)
|
||||
let item5 = SlotQueueItem.init(request5, 0)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request0, 0, collateral = request0.ask.collateralPerSlot)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request1, 0, collateral = request1.ask.collateralPerSlot)
|
||||
let item2 =
|
||||
SlotQueueItem.init(request2, 0, collateral = request2.ask.collateralPerSlot)
|
||||
let item3 =
|
||||
SlotQueueItem.init(request3, 0, collateral = request3.ask.collateralPerSlot)
|
||||
let item4 =
|
||||
SlotQueueItem.init(request4, 0, collateral = request4.ask.collateralPerSlot)
|
||||
let item5 =
|
||||
SlotQueueItem.init(request5, 0, collateral = request5.ask.collateralPerSlot)
|
||||
check queue.contains(item5) == false
|
||||
check queue.push(@[item0, item1, item2, item3, item4, item5]).isOk
|
||||
check queue.contains(item5)
|
||||
|
||||
test "sorts items by profitability descending (higher pricePerBytePerSecond == higher priority == goes first in the list)":
|
||||
var request = StorageRequest.example
|
||||
let item0 = SlotQueueItem.init(request, 0)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item1 = SlotQueueItem.init(request, 1)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot)
|
||||
check item1 < item0
|
||||
|
||||
test "sorts items by collateral ascending (higher required collateralPerByte = lower priority == comes later in the list)":
|
||||
test "sorts items by collateral ascending (higher required collateral = lower priority == comes later in the list)":
|
||||
var request = StorageRequest.example
|
||||
let item0 = SlotQueueItem.init(request, 0)
|
||||
request.ask.collateralPerByte += 1.u256
|
||||
let item1 = SlotQueueItem.init(request, 1)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot)
|
||||
let item1 = SlotQueueItem.init(
|
||||
request, 1, collateral = request.ask.collateralPerSlot + 1.u256
|
||||
)
|
||||
check item1 > item0
|
||||
|
||||
test "sorts items by expiry descending (longer expiry = higher priority)":
|
||||
var request = StorageRequest.example
|
||||
let item0 = SlotQueueItem.init(request, 0)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot)
|
||||
request.expiry += 1
|
||||
let item1 = SlotQueueItem.init(request, 1)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot)
|
||||
check item1 < item0
|
||||
|
||||
test "sorts items by slot size descending (bigger dataset = higher profitability = higher priority)":
|
||||
var request = StorageRequest.example
|
||||
let item0 = SlotQueueItem.init(request, 0)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.slotSize += 1
|
||||
let item1 = SlotQueueItem.init(request, 1)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot)
|
||||
check item1 < item0
|
||||
|
||||
test "should call callback once an item is added":
|
||||
@ -489,13 +502,17 @@ suite "Slot queue":
|
||||
# sleeping after push allows the slotqueue loop to iterate,
|
||||
# calling the callback for each pushed/updated item
|
||||
var request = StorageRequest.example
|
||||
let item0 = SlotQueueItem.init(request, 0)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item1 = SlotQueueItem.init(request, 1)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item2 = SlotQueueItem.init(request, 2)
|
||||
let item2 =
|
||||
SlotQueueItem.init(request, 2, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item3 = SlotQueueItem.init(request, 3)
|
||||
let item3 =
|
||||
SlotQueueItem.init(request, 3, collateral = request.ask.collateralPerSlot)
|
||||
|
||||
check queue.push(item0).isOk
|
||||
await sleepAsync(1.millis)
|
||||
@ -520,13 +537,17 @@ suite "Slot queue":
|
||||
# sleeping after push allows the slotqueue loop to iterate,
|
||||
# calling the callback for each pushed/updated item
|
||||
var request = StorageRequest.example
|
||||
let item0 = SlotQueueItem.init(request, 0)
|
||||
let item0 =
|
||||
SlotQueueItem.init(request, 0, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item1 = SlotQueueItem.init(request, 1)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request, 1, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item2 = SlotQueueItem.init(request, 2)
|
||||
let item2 =
|
||||
SlotQueueItem.init(request, 2, collateral = request.ask.collateralPerSlot)
|
||||
request.ask.pricePerBytePerSecond += 1.u256
|
||||
let item3 = SlotQueueItem.init(request, 3)
|
||||
let item3 =
|
||||
SlotQueueItem.init(request, 3, collateral = request.ask.collateralPerSlot)
|
||||
|
||||
check queue.push(item0).isOk
|
||||
check queue.push(item1).isOk
|
||||
@ -550,7 +571,7 @@ suite "Slot queue":
|
||||
queue.pause
|
||||
|
||||
let request = StorageRequest.example
|
||||
var items = SlotQueueItem.init(request)
|
||||
var items = SlotQueueItem.init(request, collateral = request.ask.collateralPerSlot)
|
||||
check queue.push(items).isOk
|
||||
# check all items processed
|
||||
check eventually queue.len == 0
|
||||
@ -558,8 +579,14 @@ suite "Slot queue":
|
||||
test "pushing seen item does not unpause queue":
|
||||
newSlotQueue(maxSize = 4, maxWorkers = 4)
|
||||
let request = StorageRequest.example
|
||||
let item0 =
|
||||
SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = true)
|
||||
let item0 = SlotQueueItem.init(
|
||||
request.id,
|
||||
0'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = true,
|
||||
)
|
||||
check queue.paused
|
||||
check queue.push(item0).isOk
|
||||
check queue.paused
|
||||
@ -567,8 +594,14 @@ suite "Slot queue":
|
||||
test "paused queue waits for unpause before continuing processing":
|
||||
newSlotQueue(maxSize = 4, maxWorkers = 4)
|
||||
let request = StorageRequest.example
|
||||
let item =
|
||||
SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = false)
|
||||
let item = SlotQueueItem.init(
|
||||
request.id,
|
||||
1'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = false,
|
||||
)
|
||||
check queue.paused
|
||||
# push causes unpause
|
||||
check queue.push(item).isOk
|
||||
@ -579,10 +612,22 @@ suite "Slot queue":
|
||||
test "processing a 'seen' item pauses the queue":
|
||||
newSlotQueue(maxSize = 4, maxWorkers = 4)
|
||||
let request = StorageRequest.example
|
||||
let unseen =
|
||||
SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = false)
|
||||
let seen =
|
||||
SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = true)
|
||||
let unseen = SlotQueueItem.init(
|
||||
request.id,
|
||||
0'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = false,
|
||||
)
|
||||
let seen = SlotQueueItem.init(
|
||||
request.id,
|
||||
1'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = true,
|
||||
)
|
||||
# push causes unpause
|
||||
check queue.push(unseen).isSuccess
|
||||
# check all items processed
|
||||
@ -595,10 +640,22 @@ suite "Slot queue":
|
||||
test "processing a 'seen' item does not decrease the number of workers":
|
||||
newSlotQueue(maxSize = 4, maxWorkers = 4)
|
||||
let request = StorageRequest.example
|
||||
let unseen =
|
||||
SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = false)
|
||||
let seen =
|
||||
SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = true)
|
||||
let unseen = SlotQueueItem.init(
|
||||
request.id,
|
||||
0'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = false,
|
||||
)
|
||||
let seen = SlotQueueItem.init(
|
||||
request.id,
|
||||
1'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = true,
|
||||
)
|
||||
# push seen item to ensure that queue is pausing
|
||||
check queue.push(seen).isSuccess
|
||||
# unpause and pause a number of times
|
||||
@ -615,10 +672,22 @@ suite "Slot queue":
|
||||
test "item 'seen' flags can be cleared":
|
||||
newSlotQueue(maxSize = 4, maxWorkers = 1)
|
||||
let request = StorageRequest.example
|
||||
let item0 =
|
||||
SlotQueueItem.init(request.id, 0'u16, request.ask, request.expiry, seen = true)
|
||||
let item1 =
|
||||
SlotQueueItem.init(request.id, 1'u16, request.ask, request.expiry, seen = true)
|
||||
let item0 = SlotQueueItem.init(
|
||||
request.id,
|
||||
0'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = true,
|
||||
)
|
||||
let item1 = SlotQueueItem.init(
|
||||
request.id,
|
||||
1'u16,
|
||||
request.ask,
|
||||
request.expiry,
|
||||
request.ask.collateralPerSlot,
|
||||
seen = true,
|
||||
)
|
||||
check queue.push(item0).isOk
|
||||
check queue.push(item1).isOk
|
||||
check queue[0].seen
|
||||
|
||||
@ -598,6 +598,37 @@ ethersuite "On-Chain Market":
|
||||
check endBalanceHost == (startBalanceHost + request.ask.collateralPerSlot)
|
||||
check endBalanceReward == (startBalanceReward + expectedPayout)
|
||||
|
||||
test "returns the collateral when the slot is not being repaired":
|
||||
await market.requestStorage(request)
|
||||
await market.reserveSlot(request.id, 0.uint64)
|
||||
await market.fillSlot(request.id, 0.uint64, proof, request.ask.collateralPerSlot)
|
||||
|
||||
let slotId = request.slotId(0.uint64)
|
||||
without collateral =? await market.slotCollateral(request.id, 0.uint64), error:
|
||||
fail()
|
||||
|
||||
check collateral == request.ask.collateralPerSlot
|
||||
|
||||
test "calculates correctly the collateral when the slot is being repaired":
|
||||
# Ensure that the config is loaded and repairRewardPercentage is available
|
||||
discard await market.repairRewardPercentage()
|
||||
|
||||
await market.requestStorage(request)
|
||||
await market.reserveSlot(request.id, 0.uint64)
|
||||
await market.fillSlot(request.id, 0.uint64, proof, request.ask.collateralPerSlot)
|
||||
await market.freeSlot(slotId(request.id, 0.uint64))
|
||||
|
||||
let slotId = request.slotId(0.uint64)
|
||||
|
||||
without collateral =? await market.slotCollateral(request.id, 0.uint64), error:
|
||||
fail()
|
||||
|
||||
# slotCollateral
|
||||
# repairRewardPercentage = 10
|
||||
# expected collateral = slotCollateral - slotCollateral * 0.1
|
||||
check collateral ==
|
||||
request.ask.collateralPerSlot - (request.ask.collateralPerSlot * 10).div(100.u256)
|
||||
|
||||
test "the request is added in cache after the fist access":
|
||||
await market.requestStorage(request)
|
||||
|
||||
|
||||
@ -72,7 +72,9 @@ proc example*(_: type Slot): Slot =
|
||||
proc example*(_: type SlotQueueItem): SlotQueueItem =
|
||||
let request = StorageRequest.example
|
||||
let slot = Slot.example
|
||||
SlotQueueItem.init(request, slot.slotIndex.uint16)
|
||||
SlotQueueItem.init(
|
||||
request, slot.slotIndex.uint16, collateral = request.ask.collateralPerSlot
|
||||
)
|
||||
|
||||
proc example(_: type G1Point): G1Point =
|
||||
G1Point(x: UInt256.example, y: UInt256.example)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user