Skip to content

Commit ea6d681

Browse files
committed
Merge branch 'master' into feature/ceremony-files
# Conflicts: # vendor/codex-contracts-eth
2 parents 616b237 + 1a0d2d4 commit ea6d681

20 files changed

+102
-111
lines changed

codex/contracts/market.nim

+5
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ method getRequestEnd*(market: OnChainMarket,
126126
convertEthersError:
127127
return await market.contract.requestEnd(id)
128128

129+
method requestExpiresAt*(market: OnChainMarket,
130+
id: RequestId): Future[SecondsSince1970] {.async.} =
131+
convertEthersError:
132+
return await market.contract.requestExpiry(id)
133+
129134
method getHost(market: OnChainMarket,
130135
requestId: RequestId,
131136
slotIndex: UInt256): Future[?Address] {.async.} =

codex/contracts/marketplace.nim

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ proc mySlots*(marketplace: Marketplace): seq[SlotId] {.contract, view.}
5555
proc requestState*(marketplace: Marketplace, requestId: RequestId): RequestState {.contract, view.}
5656
proc slotState*(marketplace: Marketplace, slotId: SlotId): SlotState {.contract, view.}
5757
proc requestEnd*(marketplace: Marketplace, requestId: RequestId): SecondsSince1970 {.contract, view.}
58+
proc requestExpiry*(marketplace: Marketplace, requestId: RequestId): SecondsSince1970 {.contract, view.}
5859

5960
proc proofTimeout*(marketplace: Marketplace): UInt256 {.contract, view.}
6061

codex/market.nim

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ method getRequestEnd*(market: Market,
8484
id: RequestId): Future[SecondsSince1970] {.base, async.} =
8585
raiseAssert("not implemented")
8686

87+
method requestExpiresAt*(market: Market,
88+
id: RequestId): Future[SecondsSince1970] {.base, async.} =
89+
raiseAssert("not implemented")
90+
8791
method getHost*(market: Market,
8892
requestId: RequestId,
8993
slotIndex: UInt256): Future[?Address] {.base, async.} =

codex/purchasing.nim

-5
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,15 @@ type
1818
clock: Clock
1919
purchases: Table[PurchaseId, Purchase]
2020
proofProbability*: UInt256
21-
requestExpiryInterval*: UInt256
2221
PurchaseTimeout* = Timeout
2322

2423
const DefaultProofProbability = 100.u256
25-
const DefaultRequestExpiryInterval = (10 * 60).u256
2624

2725
proc new*(_: type Purchasing, market: Market, clock: Clock): Purchasing =
2826
Purchasing(
2927
market: market,
3028
clock: clock,
3129
proofProbability: DefaultProofProbability,
32-
requestExpiryInterval: DefaultRequestExpiryInterval,
3330
)
3431

3532
proc load*(purchasing: Purchasing) {.async.} =
@@ -52,8 +49,6 @@ proc populate*(purchasing: Purchasing,
5249
result = request
5350
if result.ask.proofProbability == 0.u256:
5451
result.ask.proofProbability = purchasing.proofProbability
55-
if result.expiry == 0.u256:
56-
result.expiry = (purchasing.clock.now().u256 + purchasing.requestExpiryInterval)
5752
if result.nonce == Nonce.default:
5853
var id = result.nonce.toArray
5954
doAssert randomBytes(id) == 32

codex/purchasing/states/submitted.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ method run*(state: PurchaseSubmitted, machine: Machine): Future[?State] {.async.
3434
await subscription.unsubscribe()
3535

3636
proc withTimeout(future: Future[void]) {.async.} =
37-
let expiry = request.expiry.truncate(int64) + 1
37+
let expiry = (await market.requestExpiresAt(request.id)) + 1
3838
trace "waiting for request fulfillment or expiry", expiry
3939
await future.withTimeout(clock, expiry)
4040

codex/rest/api.nim

+4-11
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
399399
## duration - the duration of the request in seconds
400400
## proofProbability - how often storage proofs are required
401401
## reward - the maximum amount of tokens paid per second per slot to hosts the client is willing to pay
402-
## expiry - timestamp, in seconds, when the request expires if the Request does not find requested amount of nodes to host the data
402+
## 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
403403
## nodes - number of nodes the content should be stored on
404404
## tolerance - allowed number of nodes that can be lost before content is lost
405405
## colateral - requested collateral from hosts when they fill slot
@@ -425,15 +425,8 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
425425
without expiry =? params.expiry:
426426
return RestApiResponse.error(Http400, "Expiry required")
427427

428-
if node.clock.isNil:
429-
return RestApiResponse.error(Http500)
430-
431-
if expiry <= node.clock.now.u256:
432-
return RestApiResponse.error(Http400, "Expiry needs to be in future. Now: " & $node.clock.now)
433-
434-
let expiryLimit = node.clock.now.u256 + params.duration
435-
if expiry > expiryLimit:
436-
return RestApiResponse.error(Http400, "Expiry has to be before the request's end (now + duration). Limit: " & $expiryLimit)
428+
if expiry <= 0 or expiry >= params.duration:
429+
return RestApiResponse.error(Http400, "Expiry needs value bigger then zero and smaller then the request's duration")
437430

438431
without purchaseId =? await node.requestStorage(
439432
cid,
@@ -494,7 +487,7 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
494487

495488
proc initNodeApi(node: CodexNodeRef, conf: CodexConf, router: var RestRouter) =
496489
## various node management api's
497-
##
490+
##
498491
router.api(
499492
MethodGet,
500493
"/api/codex/v1/spr") do () -> RestApiResponse:

codex/sales/salesagent.nim

+4-1
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,11 @@ proc subscribeCancellation(agent: SalesAgent) {.async.} =
7272
without request =? data.request:
7373
return
7474

75+
let market = agent.context.market
76+
let expiry = await market.requestExpiresAt(data.requestId)
77+
7578
while true:
76-
let deadline = max(clock.now, request.expiry.truncate(int64)) + 1
79+
let deadline = max(clock.now, expiry) + 1
7780
trace "Waiting for request to be cancelled", now=clock.now, expiry=deadline
7881
await clock.waitUntil(deadline)
7982

codex/validation.nim

+10-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ proc new*(
3434
proc slots*(validation: Validation): seq[SlotId] =
3535
validation.slots.toSeq
3636

37+
proc iterateSlots(validation: Validation, action: proc(s: SlotId): Future[void] {.async.}) {.async.} =
38+
# Copy of hashSet, for iteration.
39+
let slots = validation.slots
40+
for slotId in slots:
41+
await action(slotId)
42+
3743
proc getCurrentPeriod(validation: Validation): UInt256 =
3844
return validation.periodicity.periodOf(validation.clock.now().u256)
3945

@@ -55,11 +61,12 @@ proc subscribeSlotFilled(validation: Validation) {.async.} =
5561

5662
proc removeSlotsThatHaveEnded(validation: Validation) {.async.} =
5763
var ended: HashSet[SlotId]
58-
for slotId in validation.slots:
64+
proc onSlot(slotId: SlotId) {.async.} =
5965
let state = await validation.market.slotState(slotId)
6066
if state != SlotState.Filled:
6167
trace "Removing slot", slotId
6268
ended.incl(slotId)
69+
await validation.iterateSlots(onSlot)
6370
validation.slots.excl(ended)
6471

6572
proc markProofAsMissing(validation: Validation,
@@ -81,9 +88,10 @@ proc markProofAsMissing(validation: Validation,
8188
error "Marking proof as missing failed", msg = e.msg
8289

8390
proc markProofsAsMissing(validation: Validation) {.async.} =
84-
for slotId in validation.slots:
91+
proc onSlot(slotId: SlotId) {.async.} =
8592
let previousPeriod = validation.getCurrentPeriod() - 1
8693
await validation.markProofAsMissing(slotId, previousPeriod)
94+
await validation.iterateSlots(onSlot)
8795

8896
proc run(validation: Validation) {.async.} =
8997
trace "Validation started"

openapi.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,7 @@ components:
213213
description: Number as decimal string that represents how much collateral is asked from hosts that wants to fill a slots
214214
expiry:
215215
type: string
216-
description: Number as decimal string that represents expiry time of the request (in unix timestamp)
217-
216+
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.
218217
StorageAsk:
219218
type: object
220219
required:

tests/codex/helpers/mockmarket.nim

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type
2020
activeSlots*: Table[Address, seq[SlotId]]
2121
requested*: seq[StorageRequest]
2222
requestEnds*: Table[RequestId, SecondsSince1970]
23+
requestExpiry*: Table[RequestId, SecondsSince1970]
2324
requestState*: Table[RequestId, RequestState]
2425
slotState*: Table[SlotId, SlotState]
2526
fulfilled*: seq[Fulfillment]
@@ -165,6 +166,10 @@ method getRequestEnd*(market: MockMarket,
165166
id: RequestId): Future[SecondsSince1970] {.async.} =
166167
return market.requestEnds[id]
167168

169+
method requestExpiresAt*(market: MockMarket,
170+
id: RequestId): Future[SecondsSince1970] {.async.} =
171+
return market.requestExpiry[id]
172+
168173
method getHost*(market: MockMarket,
169174
requestId: RequestId,
170175
slotIndex: UInt256): Future[?Address] {.async.} =

tests/codex/sales/testsales.nim

+8-2
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,9 @@ asyncchecksuite "Sales":
473473
check eventually (await reservations.all(Availability)).get == @[availability]
474474

475475
test "makes storage available again when request expires":
476+
let expiry = getTime().toUnix() + 10
477+
market.requestExpiry[request.id] = expiry
478+
476479
let origSize = availability.freeSize
477480
sales.onStore = proc(request: StorageRequest,
478481
slot: UInt256,
@@ -486,11 +489,14 @@ asyncchecksuite "Sales":
486489
# would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation.
487490
await sleepAsync(chronos.milliseconds(100))
488491
market.requestState[request.id]=RequestState.Cancelled
489-
clock.set(request.expiry.truncate(int64)+1)
492+
clock.set(expiry + 1)
490493
check eventually (await reservations.all(Availability)).get == @[availability]
491494
check getAvailability().freeSize == origSize
492495

493496
test "verifies that request is indeed expired from onchain before firing onCancelled":
497+
let expiry = getTime().toUnix() + 10
498+
market.requestExpiry[request.id] = expiry
499+
494500
let origSize = availability.freeSize
495501
sales.onStore = proc(request: StorageRequest,
496502
slot: UInt256,
@@ -504,7 +510,7 @@ asyncchecksuite "Sales":
504510
# If we would not await, then the `clock.set` would run "too fast" as the `subscribeCancellation()`
505511
# would otherwise not set the timeout early enough as it uses `clock.now` in the deadline calculation.
506512
await sleepAsync(chronos.milliseconds(100))
507-
clock.set(request.expiry.truncate(int64)+1)
513+
clock.set(expiry + 1)
508514
check getAvailability().freeSize == 0
509515

510516
market.requestState[request.id]=RequestState.Cancelled # Now "on-chain" is also expired

tests/codex/sales/testsalesagent.nim

+5-16
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,7 @@ method run*(state: MockErrorState, machine: Machine): Future[?State] {.async.} =
4141
raise newException(ValueError, "failure")
4242

4343
asyncchecksuite "Sales agent":
44-
var request = StorageRequest(
45-
ask: StorageAsk(
46-
slots: 4,
47-
slotSize: 100.u256,
48-
duration: 60.u256,
49-
reward: 10.u256,
50-
),
51-
content: StorageContent(
52-
cid: "some cid"
53-
),
54-
expiry: (getTime() + initDuration(hours=1)).toUnix.u256
55-
)
56-
44+
let request = StorageRequest.example
5745
var agent: SalesAgent
5846
var context: SalesContext
5947
var slotIndex: UInt256
@@ -62,6 +50,7 @@ asyncchecksuite "Sales agent":
6250

6351
setup:
6452
market = MockMarket.new()
53+
market.requestExpiry[request.id] = getTime().toUnix() + request.expiry.truncate(int64)
6554
clock = MockClock.new()
6655
context = SalesContext(market: market, clock: clock)
6756
slotIndex = 0.u256
@@ -109,15 +98,15 @@ asyncchecksuite "Sales agent":
10998
agent.start(MockState.new())
11099
await agent.subscribe()
111100
market.requestState[request.id] = RequestState.Cancelled
112-
clock.set(request.expiry.truncate(int64) + 1)
101+
clock.set(market.requestExpiry[request.id] + 1)
113102
check eventually onCancelCalled
114103

115104
for requestState in {RequestState.New, Started, Finished, Failed}:
116105
test "onCancelled is not called when request state is " & $requestState:
117106
agent.start(MockState.new())
118107
await agent.subscribe()
119108
market.requestState[request.id] = requestState
120-
clock.set(request.expiry.truncate(int64) + 1)
109+
clock.set(market.requestExpiry[request.id] + 1)
121110
await sleepAsync(100.millis)
122111
check not onCancelCalled
123112

@@ -126,7 +115,7 @@ asyncchecksuite "Sales agent":
126115
agent.start(MockState.new())
127116
await agent.subscribe()
128117
market.requestState[request.id] = requestState
129-
clock.set(request.expiry.truncate(int64) + 1)
118+
clock.set(market.requestExpiry[request.id] + 1)
130119
check eventually agent.data.cancelled.finished
131120

132121
test "cancelled future is finished (cancelled) when onFulfilled called":

tests/codex/testpurchasing.nim

+20-23
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ asyncchecksuite "Purchasing":
1919
var purchasing: Purchasing
2020
var market: MockMarket
2121
var clock: MockClock
22-
var request: StorageRequest
22+
var request, populatedRequest: StorageRequest
2323

2424
setup:
2525
market = MockMarket.new()
@@ -34,6 +34,12 @@ asyncchecksuite "Purchasing":
3434
)
3535
)
3636

37+
# We need request which has stable ID during the whole Purchasing pipeline
38+
# for some tests (related to expiry). Because of Purchasing.populate() we need
39+
# to do the steps bellow.
40+
populatedRequest = StorageRequest.example
41+
populatedRequest.client = await market.getSigner()
42+
3743
test "submits a storage request when asked":
3844
discard await purchasing.purchase(request)
3945
check eventually market.requested.len > 0
@@ -63,23 +69,6 @@ asyncchecksuite "Purchasing":
6369
check eventually market.requested.len > 0
6470
check market.requested[0].ask.proofProbability == 42.u256
6571

66-
test "has a default value for request expiration interval":
67-
check purchasing.requestExpiryInterval != 0.u256
68-
69-
test "can change default value for request expiration interval":
70-
purchasing.requestExpiryInterval = 42.u256
71-
let start = getTime().toUnix()
72-
discard await purchasing.purchase(request)
73-
check eventually market.requested.len > 0
74-
check market.requested[0].expiry == (start + 42).u256
75-
76-
test "can override expiry time per request":
77-
let expiry = (getTime().toUnix() + 42).u256
78-
request.expiry = expiry
79-
discard await purchasing.purchase(request)
80-
check eventually market.requested.len > 0
81-
check market.requested[0].expiry == expiry
82-
8372
test "includes a random nonce in every storage request":
8473
discard await purchasing.purchase(request)
8574
discard await purchasing.purchase(request)
@@ -92,29 +81,37 @@ asyncchecksuite "Purchasing":
9281
check market.requested[0].client == await market.getSigner()
9382

9483
test "succeeds when request is finished":
95-
let purchase = await purchasing.purchase(request)
84+
market.requestExpiry[populatedRequest.id] = getTime().toUnix() + 10
85+
let purchase = await purchasing.purchase(populatedRequest)
86+
9687
check eventually market.requested.len > 0
9788
let request = market.requested[0]
9889
let requestEnd = getTime().toUnix() + 42
9990
market.requestEnds[request.id] = requestEnd
91+
10092
market.emitRequestFulfilled(request.id)
10193
clock.set(requestEnd + 1)
10294
await purchase.wait()
10395
check purchase.error.isNone
10496

10597
test "fails when request times out":
106-
let purchase = await purchasing.purchase(request)
98+
let expiry = getTime().toUnix() + 10
99+
market.requestExpiry[populatedRequest.id] = expiry
100+
let purchase = await purchasing.purchase(populatedRequest)
107101
check eventually market.requested.len > 0
108102
let request = market.requested[0]
109-
clock.set(request.expiry.truncate(int64) + 1)
103+
104+
clock.set(expiry + 1)
110105
expect PurchaseTimeout:
111106
await purchase.wait()
112107

113108
test "checks that funds were withdrawn when purchase times out":
114-
let purchase = await purchasing.purchase(request)
109+
let expiry = getTime().toUnix() + 10
110+
market.requestExpiry[populatedRequest.id] = expiry
111+
let purchase = await purchasing.purchase(populatedRequest)
115112
check eventually market.requested.len > 0
116113
let request = market.requested[0]
117-
clock.set(request.expiry.truncate(int64) + 1)
114+
clock.set(expiry + 1)
118115
expect PurchaseTimeout:
119116
await purchase.wait()
120117
check market.withdrawn == @[request.id]

tests/contracts/testContracts.nim

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ ethersuite "Marketplace contracts":
8585
check endBalance == (startBalance + request.ask.duration * request.ask.reward + request.ask.collateral)
8686

8787
test "cannot mark proofs missing for cancelled request":
88-
await ethProvider.advanceTimeTo(request.expiry + 1)
88+
let expiry = await marketplace.requestExpiry(request.id)
89+
await ethProvider.advanceTimeTo((expiry + 1).u256)
8990
switchAccount(client)
9091
let missingPeriod = periodicity.periodOf(await ethProvider.currentTime())
9192
await ethProvider.advanceTime(periodicity.seconds)

0 commit comments

Comments
 (0)