150 lines
4.6 KiB
Nim
150 lines
4.6 KiB
Nim
import std/options
|
|
import pkg/questionable/results
|
|
import ../../clock
|
|
import ../../logutils
|
|
import ../../utils/exceptions
|
|
import ../statemachine
|
|
import ../salesagent
|
|
import ../salescontext
|
|
import ./errorhandling
|
|
import ./cancelled
|
|
import ./failed
|
|
import ./errored
|
|
import ./payout
|
|
|
|
logScope:
|
|
topics = "marketplace sales proving"
|
|
|
|
type
|
|
SlotNotFilledError* = object of CatchableError
|
|
SaleProving* = ref object of ErrorHandlingState
|
|
loop: Future[void]
|
|
|
|
method prove*(
|
|
state: SaleProving,
|
|
slot: Slot,
|
|
challenge: ProofChallenge,
|
|
onProve: OnProve,
|
|
market: Market,
|
|
currentPeriod: Period
|
|
) {.base, async.} =
|
|
try:
|
|
without proof =? (await onProve(slot, challenge)), err:
|
|
error "Failed to generate proof", error = err.msg
|
|
# In this state, there's nothing we can do except try again next time.
|
|
return
|
|
debug "Submitting proof", currentPeriod = currentPeriod, slotId = slot.id
|
|
await market.submitProof(slot.id, proof)
|
|
except CancelledError as error:
|
|
trace "Submitting proof cancelled"
|
|
raise error
|
|
except CatchableError as e:
|
|
error "Submitting proof failed", msg = e.msgDetail
|
|
|
|
proc proveLoop(
|
|
state: SaleProving,
|
|
market: Market,
|
|
clock: Clock,
|
|
request: StorageRequest,
|
|
slotIndex: UInt256,
|
|
onProve: OnProve
|
|
) {.async.} =
|
|
|
|
let slot = Slot(request: request, slotIndex: slotIndex)
|
|
let slotId = slot.id
|
|
|
|
logScope:
|
|
period = currentPeriod
|
|
requestId = request.id
|
|
slotIndex
|
|
slotId = slot.id
|
|
|
|
proc getCurrentPeriod(): Future[Period] {.async.} =
|
|
let periodicity = await market.periodicity()
|
|
return periodicity.periodOf(clock.now().u256)
|
|
|
|
proc waitUntilPeriod(period: Period) {.async.} =
|
|
let periodicity = await market.periodicity()
|
|
# Ensure that we're past the period boundary by waiting an additional second
|
|
await clock.waitUntil(periodicity.periodStart(period).truncate(int64) + 1)
|
|
|
|
while true:
|
|
let currentPeriod = await getCurrentPeriod()
|
|
let slotState = await market.slotState(slot.id)
|
|
|
|
case slotState
|
|
of SlotState.Filled:
|
|
debug "Proving for new period", period = currentPeriod
|
|
if (await market.isProofRequired(slotId)) or (await market.willProofBeRequired(slotId)):
|
|
let challenge = await market.getChallenge(slotId)
|
|
debug "Proof is required", period = currentPeriod, challenge = challenge
|
|
await state.prove(slot, challenge, onProve, market, currentPeriod)
|
|
of SlotState.Cancelled:
|
|
debug "Slot reached cancelled state"
|
|
# do nothing, let onCancelled callback take care of it
|
|
of SlotState.Failed:
|
|
debug "Slot reached failed state"
|
|
# do nothing, let onFailed callback take care of it
|
|
of SlotState.Finished:
|
|
debug "Slot reached finished state", period = currentPeriod
|
|
return # exit the loop
|
|
else:
|
|
let message = "Slot is not in Filled state, but in state: " & $slotState
|
|
raise newException(SlotNotFilledError, message)
|
|
|
|
debug "waiting until next period"
|
|
await waitUntilPeriod(currentPeriod + 1)
|
|
|
|
method `$`*(state: SaleProving): string = "SaleProving"
|
|
|
|
method onCancelled*(state: SaleProving, request: StorageRequest): ?State =
|
|
# state.loop cancellation happens automatically when run is cancelled due to
|
|
# state change
|
|
return some State(SaleCancelled())
|
|
|
|
method onFailed*(state: SaleProving, request: StorageRequest): ?State =
|
|
# state.loop cancellation happens automatically when run is cancelled due to
|
|
# state change
|
|
return some State(SaleFailed())
|
|
|
|
method run*(state: SaleProving, machine: Machine): Future[?State] {.async.} =
|
|
let data = SalesAgent(machine).data
|
|
let context = SalesAgent(machine).context
|
|
|
|
without request =? data.request:
|
|
raiseAssert "no sale request"
|
|
|
|
without onProve =? context.onProve:
|
|
raiseAssert "onProve callback not set"
|
|
|
|
without market =? context.market:
|
|
raiseAssert("market not set")
|
|
|
|
without clock =? context.clock:
|
|
raiseAssert("clock not set")
|
|
|
|
debug "Start proving", requestId = data.requestId, slotIndex = data.slotIndex
|
|
try:
|
|
let loop = state.proveLoop(market, clock, request, data.slotIndex, onProve)
|
|
state.loop = loop
|
|
await loop
|
|
except CancelledError:
|
|
discard
|
|
except CatchableError as e:
|
|
error "Proving failed", msg = e.msg
|
|
return some State(SaleErrored(error: e))
|
|
finally:
|
|
# Cleanup of the proving loop
|
|
debug "Stopping proving.", requestId = data.requestId, slotIndex = data.slotIndex
|
|
|
|
if not state.loop.isNil:
|
|
if not state.loop.finished:
|
|
try:
|
|
await state.loop.cancelAndWait()
|
|
except CatchableError as e:
|
|
error "Error during cancellation of proving loop", msg = e.msg
|
|
|
|
state.loop = nil
|
|
|
|
return some State(SalePayout())
|