Merge branch 'master' into feature/ceremony-files

# Conflicts:
#	vendor/codex-contracts-eth
This commit is contained in:
Ben 2024-05-07 09:41:25 +02:00
commit ea6d681bbf
No known key found for this signature in database
GPG Key ID: 541B9D8C9F1426A1
20 changed files with 102 additions and 111 deletions

View File

@ -126,6 +126,11 @@ method getRequestEnd*(market: OnChainMarket,
convertEthersError: convertEthersError:
return await market.contract.requestEnd(id) return await market.contract.requestEnd(id)
method requestExpiresAt*(market: OnChainMarket,
id: RequestId): Future[SecondsSince1970] {.async.} =
convertEthersError:
return await market.contract.requestExpiry(id)
method getHost(market: OnChainMarket, method getHost(market: OnChainMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256): Future[?Address] {.async.} = slotIndex: UInt256): Future[?Address] {.async.} =

View File

@ -55,6 +55,7 @@ proc mySlots*(marketplace: Marketplace): seq[SlotId] {.contract, view.}
proc requestState*(marketplace: Marketplace, requestId: RequestId): RequestState {.contract, view.} proc requestState*(marketplace: Marketplace, requestId: RequestId): RequestState {.contract, view.}
proc slotState*(marketplace: Marketplace, slotId: SlotId): SlotState {.contract, view.} proc slotState*(marketplace: Marketplace, slotId: SlotId): SlotState {.contract, view.}
proc requestEnd*(marketplace: Marketplace, requestId: RequestId): SecondsSince1970 {.contract, view.} proc requestEnd*(marketplace: Marketplace, requestId: RequestId): SecondsSince1970 {.contract, view.}
proc requestExpiry*(marketplace: Marketplace, requestId: RequestId): SecondsSince1970 {.contract, view.}
proc proofTimeout*(marketplace: Marketplace): UInt256 {.contract, view.} proc proofTimeout*(marketplace: Marketplace): UInt256 {.contract, view.}

View File

@ -84,6 +84,10 @@ method getRequestEnd*(market: Market,
id: RequestId): Future[SecondsSince1970] {.base, async.} = id: RequestId): Future[SecondsSince1970] {.base, async.} =
raiseAssert("not implemented") raiseAssert("not implemented")
method requestExpiresAt*(market: Market,
id: RequestId): Future[SecondsSince1970] {.base, async.} =
raiseAssert("not implemented")
method getHost*(market: Market, method getHost*(market: Market,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256): Future[?Address] {.base, async.} = slotIndex: UInt256): Future[?Address] {.base, async.} =

View File

@ -18,18 +18,15 @@ type
clock: Clock clock: Clock
purchases: Table[PurchaseId, Purchase] purchases: Table[PurchaseId, Purchase]
proofProbability*: UInt256 proofProbability*: UInt256
requestExpiryInterval*: UInt256
PurchaseTimeout* = Timeout PurchaseTimeout* = Timeout
const DefaultProofProbability = 100.u256 const DefaultProofProbability = 100.u256
const DefaultRequestExpiryInterval = (10 * 60).u256
proc new*(_: type Purchasing, market: Market, clock: Clock): Purchasing = proc new*(_: type Purchasing, market: Market, clock: Clock): Purchasing =
Purchasing( Purchasing(
market: market, market: market,
clock: clock, clock: clock,
proofProbability: DefaultProofProbability, proofProbability: DefaultProofProbability,
requestExpiryInterval: DefaultRequestExpiryInterval,
) )
proc load*(purchasing: Purchasing) {.async.} = proc load*(purchasing: Purchasing) {.async.} =
@ -52,8 +49,6 @@ proc populate*(purchasing: Purchasing,
result = request result = request
if result.ask.proofProbability == 0.u256: if result.ask.proofProbability == 0.u256:
result.ask.proofProbability = purchasing.proofProbability result.ask.proofProbability = purchasing.proofProbability
if result.expiry == 0.u256:
result.expiry = (purchasing.clock.now().u256 + purchasing.requestExpiryInterval)
if result.nonce == Nonce.default: if result.nonce == Nonce.default:
var id = result.nonce.toArray var id = result.nonce.toArray
doAssert randomBytes(id) == 32 doAssert randomBytes(id) == 32

View File

@ -34,7 +34,7 @@ method run*(state: PurchaseSubmitted, machine: Machine): Future[?State] {.async.
await subscription.unsubscribe() await subscription.unsubscribe()
proc withTimeout(future: Future[void]) {.async.} = proc withTimeout(future: Future[void]) {.async.} =
let expiry = request.expiry.truncate(int64) + 1 let expiry = (await market.requestExpiresAt(request.id)) + 1
trace "waiting for request fulfillment or expiry", expiry trace "waiting for request fulfillment or expiry", expiry
await future.withTimeout(clock, expiry) await future.withTimeout(clock, expiry)

View File

@ -399,7 +399,7 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
## duration - the duration of the request in seconds ## duration - the duration of the request in seconds
## proofProbability - how often storage proofs are required ## 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 ## reward - the maximum amount of tokens paid per second per slot to hosts the client is willing to pay
## expiry - timestamp, in seconds, when the request expires if the Request does not find requested amount of nodes to host the data ## 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 ## nodes - number of nodes the content should be stored on
## tolerance - allowed number of nodes that can be lost before content is lost ## tolerance - allowed number of nodes that can be lost before content is lost
## colateral - requested collateral from hosts when they fill slot ## colateral - requested collateral from hosts when they fill slot
@ -425,15 +425,8 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
without expiry =? params.expiry: without expiry =? params.expiry:
return RestApiResponse.error(Http400, "Expiry required") return RestApiResponse.error(Http400, "Expiry required")
if node.clock.isNil: if expiry <= 0 or expiry >= params.duration:
return RestApiResponse.error(Http500) return RestApiResponse.error(Http400, "Expiry needs value bigger then zero and smaller then the request's duration")
if expiry <= node.clock.now.u256:
return RestApiResponse.error(Http400, "Expiry needs to be in future. Now: " & $node.clock.now)
let expiryLimit = node.clock.now.u256 + params.duration
if expiry > expiryLimit:
return RestApiResponse.error(Http400, "Expiry has to be before the request's end (now + duration). Limit: " & $expiryLimit)
without purchaseId =? await node.requestStorage( without purchaseId =? await node.requestStorage(
cid, cid,

View File

@ -72,8 +72,11 @@ proc subscribeCancellation(agent: SalesAgent) {.async.} =
without request =? data.request: without request =? data.request:
return return
let market = agent.context.market
let expiry = await market.requestExpiresAt(data.requestId)
while true: while true:
let deadline = max(clock.now, request.expiry.truncate(int64)) + 1 let deadline = max(clock.now, expiry) + 1
trace "Waiting for request to be cancelled", now=clock.now, expiry=deadline trace "Waiting for request to be cancelled", now=clock.now, expiry=deadline
await clock.waitUntil(deadline) await clock.waitUntil(deadline)

View File

@ -34,6 +34,12 @@ proc new*(
proc slots*(validation: Validation): seq[SlotId] = proc slots*(validation: Validation): seq[SlotId] =
validation.slots.toSeq validation.slots.toSeq
proc iterateSlots(validation: Validation, action: proc(s: SlotId): Future[void] {.async.}) {.async.} =
# Copy of hashSet, for iteration.
let slots = validation.slots
for slotId in slots:
await action(slotId)
proc getCurrentPeriod(validation: Validation): UInt256 = proc getCurrentPeriod(validation: Validation): UInt256 =
return validation.periodicity.periodOf(validation.clock.now().u256) return validation.periodicity.periodOf(validation.clock.now().u256)
@ -55,11 +61,12 @@ proc subscribeSlotFilled(validation: Validation) {.async.} =
proc removeSlotsThatHaveEnded(validation: Validation) {.async.} = proc removeSlotsThatHaveEnded(validation: Validation) {.async.} =
var ended: HashSet[SlotId] var ended: HashSet[SlotId]
for slotId in validation.slots: proc onSlot(slotId: SlotId) {.async.} =
let state = await validation.market.slotState(slotId) let state = await validation.market.slotState(slotId)
if state != SlotState.Filled: if state != SlotState.Filled:
trace "Removing slot", slotId trace "Removing slot", slotId
ended.incl(slotId) ended.incl(slotId)
await validation.iterateSlots(onSlot)
validation.slots.excl(ended) validation.slots.excl(ended)
proc markProofAsMissing(validation: Validation, proc markProofAsMissing(validation: Validation,
@ -81,9 +88,10 @@ proc markProofAsMissing(validation: Validation,
error "Marking proof as missing failed", msg = e.msg error "Marking proof as missing failed", msg = e.msg
proc markProofsAsMissing(validation: Validation) {.async.} = proc markProofsAsMissing(validation: Validation) {.async.} =
for slotId in validation.slots: proc onSlot(slotId: SlotId) {.async.} =
let previousPeriod = validation.getCurrentPeriod() - 1 let previousPeriod = validation.getCurrentPeriod() - 1
await validation.markProofAsMissing(slotId, previousPeriod) await validation.markProofAsMissing(slotId, previousPeriod)
await validation.iterateSlots(onSlot)
proc run(validation: Validation) {.async.} = proc run(validation: Validation) {.async.} =
trace "Validation started" trace "Validation started"

View File

@ -213,8 +213,7 @@ components:
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 is asked from hosts that wants to fill a slots
expiry: expiry:
type: string type: string
description: Number as decimal string that represents expiry time of the request (in unix timestamp) 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: StorageAsk:
type: object type: object
required: required:

View File

@ -20,6 +20,7 @@ type
activeSlots*: Table[Address, seq[SlotId]] activeSlots*: Table[Address, seq[SlotId]]
requested*: seq[StorageRequest] requested*: seq[StorageRequest]
requestEnds*: Table[RequestId, SecondsSince1970] requestEnds*: Table[RequestId, SecondsSince1970]
requestExpiry*: Table[RequestId, SecondsSince1970]
requestState*: Table[RequestId, RequestState] requestState*: Table[RequestId, RequestState]
slotState*: Table[SlotId, SlotState] slotState*: Table[SlotId, SlotState]
fulfilled*: seq[Fulfillment] fulfilled*: seq[Fulfillment]
@ -165,6 +166,10 @@ method getRequestEnd*(market: MockMarket,
id: RequestId): Future[SecondsSince1970] {.async.} = id: RequestId): Future[SecondsSince1970] {.async.} =
return market.requestEnds[id] return market.requestEnds[id]
method requestExpiresAt*(market: MockMarket,
id: RequestId): Future[SecondsSince1970] {.async.} =
return market.requestExpiry[id]
method getHost*(market: MockMarket, method getHost*(market: MockMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256): Future[?Address] {.async.} = slotIndex: UInt256): Future[?Address] {.async.} =

View File

@ -473,6 +473,9 @@ asyncchecksuite "Sales":
check eventually (await reservations.all(Availability)).get == @[availability] check eventually (await reservations.all(Availability)).get == @[availability]
test "makes storage available again when request expires": test "makes storage available again when request expires":
let expiry = getTime().toUnix() + 10
market.requestExpiry[request.id] = expiry
let origSize = availability.freeSize let origSize = availability.freeSize
sales.onStore = proc(request: StorageRequest, sales.onStore = proc(request: StorageRequest,
slot: UInt256, slot: UInt256,
@ -486,11 +489,14 @@ asyncchecksuite "Sales":
# would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation. # would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation.
await sleepAsync(chronos.milliseconds(100)) await sleepAsync(chronos.milliseconds(100))
market.requestState[request.id]=RequestState.Cancelled market.requestState[request.id]=RequestState.Cancelled
clock.set(request.expiry.truncate(int64)+1) clock.set(expiry + 1)
check eventually (await reservations.all(Availability)).get == @[availability] check eventually (await reservations.all(Availability)).get == @[availability]
check getAvailability().freeSize == origSize check getAvailability().freeSize == origSize
test "verifies that request is indeed expired from onchain before firing onCancelled": test "verifies that request is indeed expired from onchain before firing onCancelled":
let expiry = getTime().toUnix() + 10
market.requestExpiry[request.id] = expiry
let origSize = availability.freeSize let origSize = availability.freeSize
sales.onStore = proc(request: StorageRequest, sales.onStore = proc(request: StorageRequest,
slot: UInt256, slot: UInt256,
@ -504,7 +510,7 @@ asyncchecksuite "Sales":
# If we would not await, then the `clock.set` would run "too fast" as the `subscribeCancellation()` # If we would not await, then the `clock.set` would run "too fast" as the `subscribeCancellation()`
# would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation. # would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation.
await sleepAsync(chronos.milliseconds(100)) await sleepAsync(chronos.milliseconds(100))
clock.set(request.expiry.truncate(int64)+1) clock.set(expiry + 1)
check getAvailability().freeSize == 0 check getAvailability().freeSize == 0
market.requestState[request.id]=RequestState.Cancelled # Now "on-chain" is also expired market.requestState[request.id]=RequestState.Cancelled # Now "on-chain" is also expired

View File

@ -41,19 +41,7 @@ method run*(state: MockErrorState, machine: Machine): Future[?State] {.async.} =
raise newException(ValueError, "failure") raise newException(ValueError, "failure")
asyncchecksuite "Sales agent": asyncchecksuite "Sales agent":
var request = StorageRequest( let request = StorageRequest.example
ask: StorageAsk(
slots: 4,
slotSize: 100.u256,
duration: 60.u256,
reward: 10.u256,
),
content: StorageContent(
cid: "some cid"
),
expiry: (getTime() + initDuration(hours=1)).toUnix.u256
)
var agent: SalesAgent var agent: SalesAgent
var context: SalesContext var context: SalesContext
var slotIndex: UInt256 var slotIndex: UInt256
@ -62,6 +50,7 @@ asyncchecksuite "Sales agent":
setup: setup:
market = MockMarket.new() market = MockMarket.new()
market.requestExpiry[request.id] = getTime().toUnix() + request.expiry.truncate(int64)
clock = MockClock.new() clock = MockClock.new()
context = SalesContext(market: market, clock: clock) context = SalesContext(market: market, clock: clock)
slotIndex = 0.u256 slotIndex = 0.u256
@ -109,7 +98,7 @@ asyncchecksuite "Sales agent":
agent.start(MockState.new()) agent.start(MockState.new())
await agent.subscribe() await agent.subscribe()
market.requestState[request.id] = RequestState.Cancelled market.requestState[request.id] = RequestState.Cancelled
clock.set(request.expiry.truncate(int64) + 1) clock.set(market.requestExpiry[request.id] + 1)
check eventually onCancelCalled check eventually onCancelCalled
for requestState in {RequestState.New, Started, Finished, Failed}: for requestState in {RequestState.New, Started, Finished, Failed}:
@ -117,7 +106,7 @@ asyncchecksuite "Sales agent":
agent.start(MockState.new()) agent.start(MockState.new())
await agent.subscribe() await agent.subscribe()
market.requestState[request.id] = requestState market.requestState[request.id] = requestState
clock.set(request.expiry.truncate(int64) + 1) clock.set(market.requestExpiry[request.id] + 1)
await sleepAsync(100.millis) await sleepAsync(100.millis)
check not onCancelCalled check not onCancelCalled
@ -126,7 +115,7 @@ asyncchecksuite "Sales agent":
agent.start(MockState.new()) agent.start(MockState.new())
await agent.subscribe() await agent.subscribe()
market.requestState[request.id] = requestState market.requestState[request.id] = requestState
clock.set(request.expiry.truncate(int64) + 1) clock.set(market.requestExpiry[request.id] + 1)
check eventually agent.data.cancelled.finished check eventually agent.data.cancelled.finished
test "cancelled future is finished (cancelled) when onFulfilled called": test "cancelled future is finished (cancelled) when onFulfilled called":

View File

@ -19,7 +19,7 @@ asyncchecksuite "Purchasing":
var purchasing: Purchasing var purchasing: Purchasing
var market: MockMarket var market: MockMarket
var clock: MockClock var clock: MockClock
var request: StorageRequest var request, populatedRequest: StorageRequest
setup: setup:
market = MockMarket.new() market = MockMarket.new()
@ -34,6 +34,12 @@ asyncchecksuite "Purchasing":
) )
) )
# We need request which has stable ID during the whole Purchasing pipeline
# for some tests (related to expiry). Because of Purchasing.populate() we need
# to do the steps bellow.
populatedRequest = StorageRequest.example
populatedRequest.client = await market.getSigner()
test "submits a storage request when asked": test "submits a storage request when asked":
discard await purchasing.purchase(request) discard await purchasing.purchase(request)
check eventually market.requested.len > 0 check eventually market.requested.len > 0
@ -63,23 +69,6 @@ asyncchecksuite "Purchasing":
check eventually market.requested.len > 0 check eventually market.requested.len > 0
check market.requested[0].ask.proofProbability == 42.u256 check market.requested[0].ask.proofProbability == 42.u256
test "has a default value for request expiration interval":
check purchasing.requestExpiryInterval != 0.u256
test "can change default value for request expiration interval":
purchasing.requestExpiryInterval = 42.u256
let start = getTime().toUnix()
discard await purchasing.purchase(request)
check eventually market.requested.len > 0
check market.requested[0].expiry == (start + 42).u256
test "can override expiry time per request":
let expiry = (getTime().toUnix() + 42).u256
request.expiry = expiry
discard await purchasing.purchase(request)
check eventually market.requested.len > 0
check market.requested[0].expiry == expiry
test "includes a random nonce in every storage request": test "includes a random nonce in every storage request":
discard await purchasing.purchase(request) discard await purchasing.purchase(request)
discard await purchasing.purchase(request) discard await purchasing.purchase(request)
@ -92,29 +81,37 @@ asyncchecksuite "Purchasing":
check market.requested[0].client == await market.getSigner() check market.requested[0].client == await market.getSigner()
test "succeeds when request is finished": test "succeeds when request is finished":
let purchase = await purchasing.purchase(request) market.requestExpiry[populatedRequest.id] = getTime().toUnix() + 10
let purchase = await purchasing.purchase(populatedRequest)
check eventually market.requested.len > 0 check eventually market.requested.len > 0
let request = market.requested[0] let request = market.requested[0]
let requestEnd = getTime().toUnix() + 42 let requestEnd = getTime().toUnix() + 42
market.requestEnds[request.id] = requestEnd market.requestEnds[request.id] = requestEnd
market.emitRequestFulfilled(request.id) market.emitRequestFulfilled(request.id)
clock.set(requestEnd + 1) clock.set(requestEnd + 1)
await purchase.wait() await purchase.wait()
check purchase.error.isNone check purchase.error.isNone
test "fails when request times out": test "fails when request times out":
let purchase = await purchasing.purchase(request) let expiry = getTime().toUnix() + 10
market.requestExpiry[populatedRequest.id] = expiry
let purchase = await purchasing.purchase(populatedRequest)
check eventually market.requested.len > 0 check eventually market.requested.len > 0
let request = market.requested[0] let request = market.requested[0]
clock.set(request.expiry.truncate(int64) + 1)
clock.set(expiry + 1)
expect PurchaseTimeout: expect PurchaseTimeout:
await purchase.wait() await purchase.wait()
test "checks that funds were withdrawn when purchase times out": test "checks that funds were withdrawn when purchase times out":
let purchase = await purchasing.purchase(request) let expiry = getTime().toUnix() + 10
market.requestExpiry[populatedRequest.id] = expiry
let purchase = await purchasing.purchase(populatedRequest)
check eventually market.requested.len > 0 check eventually market.requested.len > 0
let request = market.requested[0] let request = market.requested[0]
clock.set(request.expiry.truncate(int64) + 1) clock.set(expiry + 1)
expect PurchaseTimeout: expect PurchaseTimeout:
await purchase.wait() await purchase.wait()
check market.withdrawn == @[request.id] check market.withdrawn == @[request.id]

View File

@ -85,7 +85,8 @@ ethersuite "Marketplace contracts":
check endBalance == (startBalance + request.ask.duration * request.ask.reward + request.ask.collateral) check endBalance == (startBalance + request.ask.duration * request.ask.reward + request.ask.collateral)
test "cannot mark proofs missing for cancelled request": test "cannot mark proofs missing for cancelled request":
await ethProvider.advanceTimeTo(request.expiry + 1) let expiry = await marketplace.requestExpiry(request.id)
await ethProvider.advanceTimeTo((expiry + 1).u256)
switchAccount(client) switchAccount(client)
let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime())
await ethProvider.advanceTime(periodicity.seconds) await ethProvider.advanceTime(periodicity.seconds)

View File

@ -32,6 +32,10 @@ ethersuite "On-Chain Market":
let currentPeriod = periodicity.periodOf(await ethProvider.currentTime()) let currentPeriod = periodicity.periodOf(await ethProvider.currentTime())
await ethProvider.advanceTimeTo(periodicity.periodEnd(currentPeriod) + 1) await ethProvider.advanceTimeTo(periodicity.periodEnd(currentPeriod) + 1)
proc advanceToCancelledRequest(request: StorageRequest) {.async.} =
let expiry = (await market.requestExpiresAt(request.id)) + 1
await ethProvider.advanceTimeTo(expiry.u256)
proc waitUntilProofRequired(slotId: SlotId) {.async.} = proc waitUntilProofRequired(slotId: SlotId) {.async.} =
await advanceToNextPeriod() await advanceToNextPeriod()
while not ( while not (
@ -70,22 +74,19 @@ ethersuite "On-Chain Market":
test "supports withdrawing of funds": test "supports withdrawing of funds":
await market.requestStorage(request) await market.requestStorage(request)
await ethProvider.advanceTimeTo(request.expiry + 1) await advanceToCancelledRequest(request)
await market.withdrawFunds(request.id) await market.withdrawFunds(request.id)
test "supports request subscriptions": test "supports request subscriptions":
var receivedIds: seq[RequestId] var receivedIds: seq[RequestId]
var receivedAsks: seq[StorageAsk] var receivedAsks: seq[StorageAsk]
var receivedExpirys: seq[UInt256]
proc onRequest(id: RequestId, ask: StorageAsk, expiry: UInt256) = proc onRequest(id: RequestId, ask: StorageAsk, expiry: UInt256) =
receivedIds.add(id) receivedIds.add(id)
receivedAsks.add(ask) receivedAsks.add(ask)
receivedExpirys.add(expiry)
let subscription = await market.subscribeRequests(onRequest) let subscription = await market.subscribeRequests(onRequest)
await market.requestStorage(request) await market.requestStorage(request)
check receivedIds == @[request.id] check receivedIds == @[request.id]
check receivedAsks == @[request.ask] check receivedAsks == @[request.ask]
check receivedExpirys == @[request.expiry]
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports filling of slots": test "supports filling of slots":
@ -216,7 +217,7 @@ ethersuite "On-Chain Market":
receivedIds.add(id) receivedIds.add(id)
let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled) let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled)
await ethProvider.advanceTimeTo(request.expiry + 1) await advanceToCancelledRequest(request)
await market.withdrawFunds(request.id) await market.withdrawFunds(request.id)
check receivedIds == @[request.id] check receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
@ -255,7 +256,7 @@ ethersuite "On-Chain Market":
receivedIds.add(requestId) receivedIds.add(requestId)
let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled) let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled)
await ethProvider.advanceTimeTo(request.expiry + 1) # shares expiry with otherRequest advanceToCancelledRequest(otherRequest) # shares expiry with otherRequest
await market.withdrawFunds(otherRequest.id) await market.withdrawFunds(otherRequest.id)
check receivedIds.len == 0 check receivedIds.len == 0
await market.withdrawFunds(request.id) await market.withdrawFunds(request.id)
@ -338,13 +339,12 @@ ethersuite "On-Chain Market":
# 6 blocks, we only need to check 5 "blocks ago". We don't need to check the # 6 blocks, we only need to check 5 "blocks ago". We don't need to check the
# `approve` for the first `requestStorage` call, so that's 1 less again = 4 # `approve` for the first `requestStorage` call, so that's 1 less again = 4
# "blocks ago". # "blocks ago".
check eventually (
(await market.queryPastStorageRequests(5)) == proc getsPastRequest(): Future[bool] {.async.} =
@[ let reqs = await market.queryPastStorageRequests(5)
PastStorageRequest(requestId: request.id, ask: request.ask, expiry: request.expiry), reqs.mapIt(it.requestId) == @[request.id, request1.id, request2.id]
PastStorageRequest(requestId: request1.id, ask: request1.ask, expiry: request1.expiry),
PastStorageRequest(requestId: request2.id, ask: request2.ask, expiry: request2.expiry) check eventually await getsPastRequest()
])
test "past event query can specify negative `blocksAgo` parameter": test "past event query can specify negative `blocksAgo` parameter":
await market.requestStorage(request) await market.requestStorage(request)

View File

@ -59,7 +59,7 @@ proc example*(_: type StorageRequest): StorageRequest =
cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob", cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob",
merkleRoot: array[32, byte].example merkleRoot: array[32, byte].example
), ),
expiry: (getTime() + 1.hours).toUnix.u256, expiry:(60 * 60).u256, # 1 hour ,
nonce: Nonce.example nonce: Nonce.example
) )

View File

@ -70,7 +70,7 @@ proc requestStorageRaw*(
reward: UInt256, reward: UInt256,
proofProbability: UInt256, proofProbability: UInt256,
collateral: UInt256, collateral: UInt256,
expiry: UInt256 = 0.u256, expiry: uint = 0,
nodes: uint = 1, nodes: uint = 1,
tolerance: uint = 0 tolerance: uint = 0
): Response = ): Response =
@ -88,7 +88,7 @@ proc requestStorageRaw*(
} }
if expiry != 0: if expiry != 0:
json["expiry"] = %expiry json["expiry"] = %($expiry)
return client.http.post(url, $json) return client.http.post(url, $json)
@ -98,7 +98,7 @@ proc requestStorage*(
duration: UInt256, duration: UInt256,
reward: UInt256, reward: UInt256,
proofProbability: UInt256, proofProbability: UInt256,
expiry: UInt256, expiry: uint,
collateral: UInt256, collateral: UInt256,
nodes: uint = 1, nodes: uint = 1,
tolerance: uint = 0 tolerance: uint = 0

View File

@ -68,11 +68,9 @@ template marketplacesuite*(name: string, body: untyped) =
expiry: uint64 = 4.periods, expiry: uint64 = 4.periods,
nodes = providers().len, nodes = providers().len,
tolerance = 0): Future[PurchaseId] {.async.} = tolerance = 0): Future[PurchaseId] {.async.} =
let expiry = (await ethProvider.currentTime()) + expiry.u256
let id = client.requestStorage( let id = client.requestStorage(
cid, cid,
expiry=expiry, expiry=expiry.uint,
duration=duration.u256, duration=duration.u256,
proofProbability=proofProbability.u256, proofProbability=proofProbability.u256,
collateral=collateral, collateral=collateral,

View File

@ -112,10 +112,9 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
check availability in client1.getAvailabilities().get check availability in client1.getAvailabilities().get
test "node handles storage request": test "node handles storage request":
let expiry = (await ethProvider.currentTime()) + 10
let cid = client1.upload("some file contents").get let cid = client1.upload("some file contents").get
let id1 = client1.requestStorage(cid, duration=100.u256, reward=2.u256, proofProbability=3.u256, expiry=expiry, collateral=200.u256).get let id1 = client1.requestStorage(cid, duration=100.u256, reward=2.u256, proofProbability=3.u256, expiry=10, collateral=200.u256).get
let id2 = client1.requestStorage(cid, duration=400.u256, reward=5.u256, proofProbability=6.u256, expiry=expiry, collateral=201.u256).get let id2 = client1.requestStorage(cid, duration=400.u256, reward=5.u256, proofProbability=6.u256, expiry=10, collateral=201.u256).get
check id1 != id2 check id1 != id2
test "node retrieves purchase status": test "node retrieves purchase status":
@ -124,13 +123,12 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
let chunker = RandomChunker.new(rng, size = DefaultBlockSize * 2, chunkSize = DefaultBlockSize * 2) let chunker = RandomChunker.new(rng, size = DefaultBlockSize * 2, chunkSize = DefaultBlockSize * 2)
let data = await chunker.getBytes() let data = await chunker.getBytes()
let cid = client1.upload(byteutils.toHex(data)).get let cid = client1.upload(byteutils.toHex(data)).get
let expiry = (await ethProvider.currentTime()) + 30
let id = client1.requestStorage( let id = client1.requestStorage(
cid, cid,
duration=100.u256, duration=100.u256,
reward=2.u256, reward=2.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=expiry, expiry=30,
collateral=200.u256, collateral=200.u256,
nodes=2, nodes=2,
tolerance=1).get tolerance=1).get
@ -139,16 +137,15 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
check request.ask.duration == 100.u256 check request.ask.duration == 100.u256
check request.ask.reward == 2.u256 check request.ask.reward == 2.u256
check request.ask.proofProbability == 3.u256 check request.ask.proofProbability == 3.u256
check request.expiry == expiry check request.expiry == 30
check request.ask.collateral == 200.u256 check request.ask.collateral == 200.u256
check request.ask.slots == 2'u64 check request.ask.slots == 2'u64
check request.ask.maxSlotLoss == 1'u64 check request.ask.maxSlotLoss == 1'u64
# TODO: We currently do not support encoding single chunks # TODO: We currently do not support encoding single chunks
# test "node retrieves purchase status with 1 chunk": # test "node retrieves purchase status with 1 chunk":
# let expiry = (await ethProvider.currentTime()) + 30
# let cid = client1.upload("some file contents").get # let cid = client1.upload("some file contents").get
# let id = client1.requestStorage(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, expiry=expiry, collateral=200.u256, nodes=2, tolerance=1).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 request = client1.getPurchase(id).get.request.get # let request = client1.getPurchase(id).get.request.get
# check request.ask.duration == 1.u256 # check request.ask.duration == 1.u256
# check request.ask.reward == 2.u256 # check request.ask.reward == 2.u256
@ -159,13 +156,12 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
# check request.ask.maxSlotLoss == 1'u64 # check request.ask.maxSlotLoss == 1'u64
test "node remembers purchase status after restart": test "node remembers purchase status after restart":
let expiry = (await ethProvider.currentTime()) + 30
let cid = client1.upload("some file contents").get let cid = client1.upload("some file contents").get
let id = client1.requestStorage(cid, let id = client1.requestStorage(cid,
duration=100.u256, duration=100.u256,
reward=2.u256, reward=2.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=expiry, expiry=30,
collateral=200.u256).get collateral=200.u256).get
check eventually client1.purchaseStateIs(id, "submitted") check eventually client1.purchaseStateIs(id, "submitted")
@ -177,7 +173,7 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
check request.ask.duration == 100.u256 check request.ask.duration == 100.u256
check request.ask.reward == 2.u256 check request.ask.reward == 2.u256
check request.ask.proofProbability == 3.u256 check request.ask.proofProbability == 3.u256
check request.expiry == expiry check request.expiry == 30
check request.ask.collateral == 200.u256 check request.ask.collateral == 200.u256
check request.ask.slots == 1'u64 check request.ask.slots == 1'u64
check request.ask.maxSlotLoss == 0'u64 check request.ask.maxSlotLoss == 0'u64
@ -189,14 +185,13 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
let availability = client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get let availability = client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
# client 1 requests storage # client 1 requests storage
let expiry = (await ethProvider.currentTime()) + 5*60
let cid = client1.upload(data).get let cid = client1.upload(data).get
let id = client1.requestStorage( let id = client1.requestStorage(
cid, cid,
duration=10*60.u256, duration=10*60.u256,
reward=400.u256, reward=400.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=expiry, expiry=5*60,
collateral=200.u256, collateral=200.u256,
nodes = 5, nodes = 5,
tolerance = 2).get tolerance = 2).get
@ -228,14 +223,13 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
discard client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get discard client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
# client 1 requests storage # client 1 requests storage
let expiry = (await ethProvider.currentTime()) + 5*60
let cid = client1.upload(data).get let cid = client1.upload(data).get
let id = client1.requestStorage( let id = client1.requestStorage(
cid, cid,
duration=duration, duration=duration,
reward=reward, reward=reward,
proofProbability=3.u256, proofProbability=3.u256,
expiry=expiry, expiry=5*60,
collateral=200.u256, collateral=200.u256,
nodes = nodes, nodes = nodes,
tolerance = 2).get tolerance = 2).get
@ -253,12 +247,11 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
test "request storage fails if nodes and tolerance aren't correct": test "request storage fails if nodes and tolerance aren't correct":
let cid = client1.upload("some file contents").get let cid = client1.upload("some file contents").get
let expiry = (await ethProvider.currentTime()) + 30
let responseBefore = client1.requestStorageRaw(cid, let responseBefore = client1.requestStorageRaw(cid,
duration=100.u256, duration=100.u256,
reward=2.u256, reward=2.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=expiry, expiry=30,
collateral=200.u256, collateral=200.u256,
nodes=1, nodes=1,
tolerance=1) tolerance=1)
@ -267,20 +260,15 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
check responseBefore.body == "Tolerance cannot be greater or equal than nodes (nodes - tolerance)" check responseBefore.body == "Tolerance cannot be greater or equal than nodes (nodes - tolerance)"
test "node requires expiry and its value to be in future": test "node requires expiry and its value to be in future":
let currentTime = await ethProvider.currentTime()
let cid = client1.upload("some file contents").get let cid = client1.upload("some file contents").get
let responseMissing = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256) let responseMissing = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256)
check responseMissing.status == "400 Bad Request" check responseMissing.status == "400 Bad Request"
check responseMissing.body == "Expiry required" check responseMissing.body == "Expiry required"
let responsePast = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime-10) let responseBefore = client1.requestStorageRaw(cid, duration=10.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=10)
check responsePast.status == "400 Bad Request"
check "Expiry needs to be in future" in responsePast.body
let responseBefore = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime+10)
check responseBefore.status == "400 Bad Request" check responseBefore.status == "400 Bad Request"
check "Expiry has to be before the request's end (now + duration)" in responseBefore.body check "Expiry needs value bigger then zero and smaller then the request's duration" in responseBefore.body
test "updating non-existing availability": test "updating non-existing availability":
let nonExistingResponse = client1.patchAvailabilityRaw(AvailabilityId.example, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) let nonExistingResponse = client1.patchAvailabilityRaw(AvailabilityId.example, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some)
@ -317,14 +305,13 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false:
let availability = client1.postAvailability(totalSize=originalSize, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get let availability = client1.postAvailability(totalSize=originalSize, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get
# Lets create storage request that will utilize some of the availability's space # Lets create storage request that will utilize some of the availability's space
let expiry = (await ethProvider.currentTime()) + 5*60
let cid = client2.upload(data).get let cid = client2.upload(data).get
let id = client2.requestStorage( let id = client2.requestStorage(
cid, cid,
duration=10*60.u256, duration=10*60.u256,
reward=400.u256, reward=400.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=expiry, expiry=5*60,
collateral=200.u256, collateral=200.u256,
nodes = 5, nodes = 5,
tolerance = 2).get tolerance = 2).get

@ -1 +1 @@
Subproject commit c3d7db345649d8a2dafabcede90edf5ff6b0bfc7 Subproject commit 57e8cd5013325f05e16833a5320b575d32a403f3