mirror of
https://github.com/codex-storage/nim-codex.git
synced 2025-02-16 23:06:55 +00:00
[marketplace] sales state machine: use slotState
Use `slotState` instead of `requestState` for sales state machine. [marketplace] clean up
This commit is contained in:
parent
03a87636e7
commit
47da5c1625
@ -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))
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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.} =
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user