[marketplace] sales state machine: use slotState

Use `slotState` instead of `requestState` for sales state machine.

[marketplace] clean up
This commit is contained in:
Eric Mastro 2023-02-07 17:26:34 +11:00 committed by Eric Mastro
parent 03a87636e7
commit 47da5c1625
No known key found for this signature in database
GPG Key ID: AD065ECE27A873B9
6 changed files with 115 additions and 93 deletions

View File

@ -1,7 +1,10 @@
import ../statemachine
import ./errored
type SaleCancelled* = ref object of SaleState
type
SaleCancelled* = ref object of SaleState
SaleCancelledError* = object of CatchableError
SaleTimeoutError* = object of SaleCancelledError
method `$`*(state: SaleCancelled): string = "SaleCancelled"
@ -9,5 +12,5 @@ method enterAsync*(state: SaleCancelled) {.async.} =
without agent =? (state.context as SalesAgent):
raiseAssert "invalid state"
let error = newException(Timeout, "Sale cancelled due to timeout")
let error = newException(SaleTimeoutError, "Sale cancelled due to timeout")
await state.switchAsync(SaleErrored(error: error))

View File

@ -8,6 +8,7 @@ import ../statemachine
type
SaleFilled* = ref object of SaleState
SaleFilledError* = object of CatchableError
HostMismatchError* = object of SaleFilledError
method onCancelled*(state: SaleFilled, request: StorageRequest) {.async.} =
await state.switchAsync(SaleCancelled())
@ -29,7 +30,7 @@ method enterAsync(state: SaleFilled) {.async.} =
if host == me.some:
await state.switchAsync(SaleFinished())
else:
let error = newException(SaleFilledError, "Slot filled by other host")
let error = newException(HostMismatchError, "Slot filled by other host")
await state.switchAsync(SaleErrored(error: error))
except CancelledError:

View File

@ -8,6 +8,7 @@ import ./cancelled
type
SaleUnknown* = ref object of SaleState
SaleUnknownError* = object of CatchableError
UnexpectedSlotError* = object of SaleUnknownError
method `$`*(state: SaleUnknown): string = "SaleUnknown"
@ -24,18 +25,22 @@ method enterAsync(state: SaleUnknown) {.async.} =
let market = agent.sales.market
try:
without requestState =? await market.requestState(agent.requestId):
let error = newException(SaleUnknownError, "cannot retrieve request state")
let slotId = slotId(agent.requestId, agent.slotIndex)
without slotState =? await market.slotState(slotId):
let error = newException(SaleUnknownError, "cannot retrieve slot state")
await state.switchAsync(SaleErrored(error: error))
case requestState
of RequestState.New, RequestState.Started:
case slotState
of SlotState.Free:
let error = newException(UnexpectedSlotError,
"slot state on chain should not be 'free'")
await state.switchAsync(SaleErrored(error: error))
of SlotState.Filled:
await state.switchAsync(SaleFilled())
of RequestState.Finished:
of SlotState.Finished, SlotState.Paid:
await state.switchAsync(SaleFinished())
of RequestState.Cancelled:
await state.switchAsync(SaleCancelled())
of RequestState.Failed:
of SlotState.Failed:
await state.switchAsync(SaleFailed())
except CancelledError:

View File

@ -14,7 +14,8 @@ type
activeSlots*: Table[Address, seq[SlotId]]
requested*: seq[StorageRequest]
requestEnds*: Table[RequestId, SecondsSince1970]
state*: Table[RequestId, RequestState]
requestState*: Table[RequestId, RequestState]
slotState*: Table[SlotId, SlotState]
fulfilled*: seq[Fulfillment]
filled*: seq[MockSlot]
withdrawn*: seq[RequestId]
@ -110,7 +111,11 @@ method getRequestFromSlotId*(market: MockMarket,
method requestState*(market: MockMarket,
requestId: RequestId): Future[?RequestState] {.async.} =
return market.state.?[requestId]
return market.requestState.?[requestId]
method slotState*(market: MockMarket,
slotId: SlotId): Future[SlotState] {.async.} =
return market.slotState[slotId]
method getRequestEnd*(market: MockMarket,
id: RequestId): Future[SecondsSince1970] {.async.} =

View File

@ -141,11 +141,11 @@ suite "Purchasing state machine":
let request1, request2, request3, request4, request5 = StorageRequest.example
market.requested = @[request1, request2, request3, request4, request5]
market.activeRequests[me] = @[request1.id, request2.id, request3.id, request4.id, request5.id]
market.state[request1.id] = RequestState.New
market.state[request2.id] = RequestState.Started
market.state[request3.id] = RequestState.Cancelled
market.state[request4.id] = RequestState.Finished
market.state[request5.id] = RequestState.Failed
market.requestState[request1.id] = RequestState.New
market.requestState[request2.id] = RequestState.Started
market.requestState[request3.id] = RequestState.Cancelled
market.requestState[request4.id] = RequestState.Finished
market.requestState[request5.id] = RequestState.Failed
# ensure the started state doesn't error, giving a false positive test result
market.requestEnds[request2.id] = clock.now() - 1
@ -162,7 +162,7 @@ suite "Purchasing state machine":
let request = StorageRequest.example
let purchase = Purchase.new(request, market, clock)
market.requested = @[request]
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
purchase.switch(PurchaseUnknown())
check (purchase.state as PurchaseSubmitted).isSome
@ -171,7 +171,7 @@ suite "Purchasing state machine":
let purchase = Purchase.new(request, market, clock)
market.requestEnds[request.id] = clock.now() + request.ask.duration.truncate(int64)
market.requested = @[request]
market.state[request.id] = RequestState.Started
market.requestState[request.id] = RequestState.Started
purchase.switch(PurchaseUnknown())
check (purchase.state as PurchaseStarted).isSome
@ -179,7 +179,7 @@ suite "Purchasing state machine":
let request = StorageRequest.example
let purchase = Purchase.new(request, market, clock)
market.requested = @[request]
market.state[request.id] = RequestState.Cancelled
market.requestState[request.id] = RequestState.Cancelled
purchase.switch(PurchaseUnknown())
check (purchase.state as PurchaseErrored).isSome
check purchase.error.?msg == "Purchase cancelled due to timeout".some
@ -188,7 +188,7 @@ suite "Purchasing state machine":
let request = StorageRequest.example
let purchase = Purchase.new(request, market, clock)
market.requested = @[request]
market.state[request.id] = RequestState.Finished
market.requestState[request.id] = RequestState.Finished
purchase.switch(PurchaseUnknown())
check (purchase.state as PurchaseFinished).isSome
@ -196,7 +196,7 @@ suite "Purchasing state machine":
let request = StorageRequest.example
let purchase = Purchase.new(request, market, clock)
market.requested = @[request]
market.state[request.id] = RequestState.Failed
market.requestState[request.id] = RequestState.Failed
purchase.switch(PurchaseUnknown())
check (purchase.state as PurchaseErrored).isSome
check purchase.error.?msg == "Purchase failed".some
@ -206,7 +206,7 @@ suite "Purchasing state machine":
let request = StorageRequest.example
market.requested = @[request]
market.activeRequests[me] = @[request.id]
market.state[request.id] = RequestState.Started
market.requestState[request.id] = RequestState.Started
market.requestEnds[request.id] = clock.now() + request.ask.duration.truncate(int64)
await purchasing.load()
@ -226,7 +226,7 @@ suite "Purchasing state machine":
let request = StorageRequest.example
market.requested = @[request]
market.activeRequests[me] = @[request.id]
market.state[request.id] = RequestState.Started
market.requestState[request.id] = RequestState.Started
market.requestEnds[request.id] = clock.now() + request.ask.duration.truncate(int64)
await purchasing.load()

View File

@ -243,6 +243,8 @@ suite "Sales state machine":
var market: MockMarket
var clock: MockClock
var proving: Proving
var slotIdx: UInt256
var slotId: SlotId
setup:
market = MockMarket.new()
@ -258,6 +260,8 @@ suite "Sales state machine":
return proof
await sales.start()
request.expiry = (clock.now() + 42).u256
slotIdx = 0.u256
slotId = slotId(request.id, slotIdx)
teardown:
await sales.stop()
@ -276,50 +280,44 @@ suite "Sales state machine":
proof: proof,
host: address)
market.filled.add slot
market.slotState[slotId(request.id, slotIdx)] = SlotState.Filled
test "moves to SaleErrored when SaleFilled errors":
let agent = newSalesAgent()
market.state[request.id] = RequestState.New
market.slotState[slotId] = SlotState.Free
await agent.switchAsync(SaleUnknown())
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Slot filled by other host"
without state =? (agent.state as SaleErrored):
fail()
check state.error of UnexpectedSlotError
check state.error.msg == "slot state on chain should not be 'free'"
test "moves to SaleFinished when request state is New":
test "moves to SaleFilled>SaleFinished when slot state is Filled":
let agent = newSalesAgent()
await fillSlot()
market.state[request.id] = RequestState.New
await agent.switchAsync(SaleUnknown())
check (agent.state as SaleFinished).isSome
test "moves to SaleFinished when request state is Started":
test "moves to SaleFinished when slot state is Finished":
let agent = newSalesAgent()
await fillSlot()
market.state[request.id] = RequestState.Started
market.slotState[slotId] = SlotState.Finished
agent.switch(SaleUnknown())
check (agent.state as SaleFinished).isSome
test "moves to SaleFinished when request state is Finished":
test "moves to SaleFinished when slot state is Paid":
let agent = newSalesAgent()
market.state[request.id] = RequestState.Finished
market.slotState[slotId] = SlotState.Paid
agent.switch(SaleUnknown())
check (agent.state as SaleFinished).isSome
test "moves to SaleErrored when request state is Cancelled":
test "moves to SaleErrored when slot state is Failed":
let agent = newSalesAgent()
market.state[request.id] = RequestState.Cancelled
market.slotState[slotId] = SlotState.Failed
agent.switch(SaleUnknown())
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale cancelled due to timeout"
test "moves to SaleErrored when request state is Failed":
let agent = newSalesAgent()
market.state[request.id] = RequestState.Failed
agent.switch(SaleUnknown())
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale failed"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleFailedError
check state.error.msg == "Sale failed"
test "moves to SaleErrored when Downloading and request expires":
sales.onStore = proc(request: StorageRequest,
@ -330,14 +328,15 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleDownloading())
clock.set(request.expiry.truncate(int64))
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale cancelled due to timeout"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleTimeoutError
check state.error.msg == "Sale cancelled due to timeout"
test "moves to SaleErrored when Downloading and request fails":
sales.onStore = proc(request: StorageRequest,
@ -347,68 +346,73 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleDownloading())
market.emitRequestFailed(request.id)
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale failed"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleFailedError
check state.error.msg == "Sale failed"
test "moves to SaleErrored when Filling and request expires":
request.expiry = (getTime() + initDuration(seconds=2)).toUnix.u256
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleFilling())
clock.set(request.expiry.truncate(int64))
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale cancelled due to timeout"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleTimeoutError
check state.error.msg == "Sale cancelled due to timeout"
test "moves to SaleErrored when Filling and request fails":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleFilling())
market.emitRequestFailed(request.id)
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale failed"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleFailedError
check state.error.msg == "Sale failed"
test "moves to SaleErrored when Finished and request expires":
request.expiry = (getTime() + initDuration(seconds=2)).toUnix.u256
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.Finished
market.requestState[request.id] = RequestState.Finished
await agent.switchAsync(SaleFinished())
clock.set(request.expiry.truncate(int64))
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale cancelled due to timeout"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleTimeoutError
check state.error.msg == "Sale cancelled due to timeout"
test "moves to SaleErrored when Finished and request fails":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.Finished
market.requestState[request.id] = RequestState.Finished
await agent.switchAsync(SaleFinished())
market.emitRequestFailed(request.id)
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale failed"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleFailedError
check state.error.msg == "Sale failed"
test "moves to SaleErrored when Proving and request expires":
sales.onProve = proc(request: StorageRequest,
@ -419,14 +423,15 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleProving())
clock.set(request.expiry.truncate(int64))
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale cancelled due to timeout"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleTimeoutError
check state.error.msg == "Sale cancelled due to timeout"
test "moves to SaleErrored when Proving and request fails":
sales.onProve = proc(request: StorageRequest,
@ -436,14 +441,15 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleProving())
market.emitRequestFailed(request.id)
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Sale failed"
without state =? (agent.state as SaleErrored):
fail()
check state.error of SaleFailedError
check state.error.msg == "Sale failed"
test "moves to SaleErrored when Downloading and slot is filled by another host":
sales.onStore = proc(request: StorageRequest,
@ -453,7 +459,7 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleDownloading())
market.fillSlot(request.id, agent.slotIndex, proof, Address.example)
await sleepAsync chronos.seconds(2)
@ -470,14 +476,15 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await agent.switchAsync(SaleProving())
market.fillSlot(request.id, agent.slotIndex, proof, Address.example)
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Slot filled by other host"
without state =? (agent.state as SaleErrored):
fail()
check state.error of HostMismatchError
check state.error.msg == "Slot filled by other host"
test "moves to SaleErrored when Filling and slot is filled by another host":
sales.onProve = proc(request: StorageRequest,
@ -487,14 +494,15 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
market.fillSlot(request.id, agent.slotIndex, proof, Address.example)
await agent.switchAsync(SaleFilling())
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleErrored)
check state.isSome
check (!state).error.msg == "Slot filled by other host"
without state =? (agent.state as SaleErrored):
fail()
check state.error of HostMismatchError
check state.error.msg == "Slot filled by other host"
test "moves from SaleDownloading to SaleFinished, calling necessary callbacks":
var onProveCalled, onStoreCalled, onClearCalled, onSaleCalled: bool
@ -518,14 +526,14 @@ suite "Sales state machine":
let agent = newSalesAgent()
await agent.start(request.ask.slots)
market.requested.add request
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
await fillSlot(agent.slotIndex)
await agent.switchAsync(SaleDownloading())
market.emitRequestFulfilled(request.id)
await sleepAsync chronos.seconds(2)
let state = (agent.state as SaleFinished)
check state.isSome
without state =? (agent.state as SaleFinished):
fail()
check onProveCalled
check onStoreCalled
check not onClearCalled
@ -536,7 +544,7 @@ suite "Sales state machine":
request.ask.slots = 2
market.requested = @[request]
market.state[request.id] = RequestState.New
market.requestState[request.id] = RequestState.New
let slot0 = MockSlot(requestId: request.id,
slotIndex: 0.u256,