From 0aa7590c963e458ac70393760a4741d72c3631bd Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 22 Feb 2024 07:20:40 +0100 Subject: [PATCH] sales: calculate initial proof at start of period reason: this ensures that the period (and therefore the challenge) doesn't change while we're calculating the proof --- codex/sales/states/initialproving.nim | 8 ++++++ tests/codex/sales/helpers/periods.nim | 8 ++++++ .../codex/sales/states/testinitialproving.nim | 28 ++++++++++++++++--- tests/codex/sales/testsales.nim | 20 ++++++++++++- 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 tests/codex/sales/helpers/periods.nim diff --git a/codex/sales/states/initialproving.nim b/codex/sales/states/initialproving.nim index 8f3a7287..9e1ed073 100644 --- a/codex/sales/states/initialproving.nim +++ b/codex/sales/states/initialproving.nim @@ -1,4 +1,5 @@ import pkg/questionable/results +import ../../clock import ../../logutils import ../statemachine import ../salesagent @@ -25,6 +26,8 @@ method onFailed*(state: SaleInitialProving, request: StorageRequest): ?State = method run*(state: SaleInitialProving, machine: Machine): Future[?State] {.async.} = let data = SalesAgent(machine).data let context = SalesAgent(machine).context + let market = context.market + let clock = context.clock without request =? data.request: raiseAssert "no sale request" @@ -32,6 +35,11 @@ method run*(state: SaleInitialProving, machine: Machine): Future[?State] {.async without onProve =? context.onProve: raiseAssert "onProve callback not set" + debug "Waiting until next period" + let periodicity = await market.periodicity() + let period = periodicity.periodOf(clock.now().u256) + await clock.waitUntil(periodicity.periodEnd(period).truncate(int64) + 1) + debug "Generating initial proof", requestId = data.requestId let slot = Slot(request: request, slotIndex: data.slotIndex) diff --git a/tests/codex/sales/helpers/periods.nim b/tests/codex/sales/helpers/periods.nim new file mode 100644 index 00000000..ba1793c2 --- /dev/null +++ b/tests/codex/sales/helpers/periods.nim @@ -0,0 +1,8 @@ +import pkg/codex/market +import ../../helpers/mockclock + +proc advanceToNextPeriod*(clock: MockClock, market: Market) {.async.} = + let periodicity = await market.periodicity() + let period = periodicity.periodOf(clock.now().u256) + let periodEnd = periodicity.periodEnd(period) + clock.set((periodEnd + 1).truncate(int)) diff --git a/tests/codex/sales/states/testinitialproving.nim b/tests/codex/sales/states/testinitialproving.nim index 0659edd3..e1d64d29 100644 --- a/tests/codex/sales/states/testinitialproving.nim +++ b/tests/codex/sales/states/testinitialproving.nim @@ -14,12 +14,15 @@ import ../../../asynctest import ../../examples import ../../helpers import ../../helpers/mockmarket +import ../../helpers/mockclock +import ../helpers/periods asyncchecksuite "sales state 'initialproving'": let proof = Groth16Proof.example let request = StorageRequest.example let slotIndex = (request.ask.slots div 2).u256 let market = MockMarket.new() + let clock = MockClock.new() var state: SaleInitialProving var agent: SalesAgent @@ -31,7 +34,8 @@ asyncchecksuite "sales state 'initialproving'": return success(proof) let context = SalesContext( onProve: onProve.some, - market: market + market: market, + clock: clock ) agent = newSalesAgent(context, request.id, @@ -39,6 +43,12 @@ asyncchecksuite "sales state 'initialproving'": request.some) state = SaleInitialProving.new() + proc allowProofToStart {.async.} = + # wait until we're in initialproving state + await sleepAsync(10.millis) + # it won't start proving until the next period + await clock.advanceToNextPeriod(market) + test "switches to cancelled state when request expires": let next = state.onCancelled(request) check !next of SaleCancelled @@ -48,7 +58,10 @@ asyncchecksuite "sales state 'initialproving'": check !next of SaleFailed test "switches to filling state when initial proving is complete": - let next = await state.run(agent) + let future = state.run(agent) + await allowProofToStart() + let next = await future + check !next of SaleFilling check SaleFilling(!next).proof == proof @@ -56,6 +69,9 @@ asyncchecksuite "sales state 'initialproving'": market.proofChallenge = ProofChallenge.example let future = state.run(agent) + await allowProofToStart() + + discard await future check receivedChallenge == market.proofChallenge @@ -65,12 +81,16 @@ asyncchecksuite "sales state 'initialproving'": let proofFailedContext = SalesContext( onProve: onProveFailed.some, - market: market + market: market, + clock: clock ) agent = newSalesAgent(proofFailedContext, request.id, slotIndex, request.some) - let next = await state.run(agent) + let future = state.run(agent) + await allowProofToStart() + let next = await future + check !next of SaleErrored diff --git a/tests/codex/sales/testsales.nim b/tests/codex/sales/testsales.nim index 921ece83..b5fe4821 100644 --- a/tests/codex/sales/testsales.nim +++ b/tests/codex/sales/testsales.nim @@ -19,6 +19,7 @@ import ../helpers/mockmarket import ../helpers/mockclock import ../helpers/always import ../examples +import ./helpers/periods asyncchecksuite "Sales - start": let proof = Groth16Proof.example @@ -176,6 +177,12 @@ asyncchecksuite "Sales": await sales.stop() await repo.stop() + proc allowRequestToStart {.async.} = + # wait until we're in initialproving state + await sleepAsync(10.millis) + # it won't start proving until the next period + await clock.advanceToNextPeriod(market) + proc getAvailability: Availability = let key = availability.id.key.get (waitFor reservations.get(key, Availability)).get @@ -311,7 +318,8 @@ asyncchecksuite "Sales": createAvailability() let origSize = availability.size await market.requestStorage(request) - await sold # allow proving to start + await allowRequestToStart() + await sold # complete request market.slotState[request.slotId(slotIndex)] = SlotState.Finished @@ -379,6 +387,8 @@ asyncchecksuite "Sales": saleFailed = true createAvailability() await market.requestStorage(request) + await allowRequestToStart() + check eventually saleFailed test "makes storage available again when data retrieval fails": @@ -400,12 +410,16 @@ asyncchecksuite "Sales": return success(Groth16Proof.example) createAvailability() await market.requestStorage(request) + await allowRequestToStart() + check eventually provingRequest == request check provingSlot < request.ask.slots.u256 test "fills a slot": createAvailability() await market.requestStorage(request) + await allowRequestToStart() + check eventually market.filled.len > 0 check market.filled[0].requestId == request.id check market.filled[0].slotIndex < request.ask.slots.u256 @@ -421,6 +435,8 @@ asyncchecksuite "Sales": soldSlotIndex = slotIndex createAvailability() await market.requestStorage(request) + await allowRequestToStart() + check eventually soldRequest == request check soldSlotIndex < request.ask.slots.u256 @@ -437,6 +453,8 @@ asyncchecksuite "Sales": clearedSlotIndex = slotIndex createAvailability() await market.requestStorage(request) + await allowRequestToStart() + check eventually clearedRequest == request check clearedSlotIndex < request.ask.slots.u256