fix(slot-reservations): Avoid slot filled cancellations (#963)

* Avoid cancelling states when slot is filled

* improve logging

Improves logging for situations where a Sale should be ignored instead of being considered an error, including when reservation is not allowed and when a slot was filled by another host.

* remove onSlotFilled unit tests from states
This commit is contained in:
Eric 2024-10-24 16:56:12 +11:00 committed by GitHub
parent 3a2d0926f1
commit 0157ca4c57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 39 additions and 24 deletions

View File

@ -165,8 +165,14 @@ method fillSlot(market: OnChainMarket,
proof: Groth16Proof, proof: Groth16Proof,
collateral: UInt256) {.async.} = collateral: UInt256) {.async.} =
convertEthersError: convertEthersError:
logScope:
requestId
slotIndex
await market.approveFunds(collateral) await market.approveFunds(collateral)
trace "calling fillSlot on contract"
discard await market.contract.fillSlot(requestId, slotIndex, proof).confirm(0) discard await market.contract.fillSlot(requestId, slotIndex, proof).confirm(0)
trace "fillSlot transaction completed"
method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} = method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
convertEthersError: convertEthersError:
@ -253,7 +259,12 @@ method reserveSlot*(
slotIndex: UInt256) {.async.} = slotIndex: UInt256) {.async.} =
convertEthersError: convertEthersError:
discard await market.contract.reserveSlot(requestId, slotIndex).confirm(0) discard await market.contract.reserveSlot(
requestId,
slotIndex,
# reserveSlot runs out of gas for unknown reason, but 100k gas covers it
TransactionOverrides(gasLimit: some 100000.u256)
).confirm(0)
method canReserveSlot*( method canReserveSlot*(
market: OnChainMarket, market: OnChainMarket,

View File

@ -6,6 +6,8 @@ import ./errorhandling
import ./filled import ./filled
import ./cancelled import ./cancelled
import ./failed import ./failed
import ./ignored
import ./errored
logScope: logScope:
topics = "marketplace sales filling" topics = "marketplace sales filling"
@ -22,16 +24,25 @@ method onCancelled*(state: SaleFilling, request: StorageRequest): ?State =
method onFailed*(state: SaleFilling, request: StorageRequest): ?State = method onFailed*(state: SaleFilling, request: StorageRequest): ?State =
return some State(SaleFailed()) return some State(SaleFailed())
method onSlotFilled*(state: SaleFilling, requestId: RequestId,
slotIndex: UInt256): ?State =
return some State(SaleFilled())
method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} = method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} =
let data = SalesAgent(machine).data let data = SalesAgent(machine).data
let market = SalesAgent(machine).context.market let market = SalesAgent(machine).context.market
without (collateral =? data.request.?ask.?collateral): without (collateral =? data.request.?ask.?collateral):
raiseAssert "Request not set" raiseAssert "Request not set"
debug "Filling slot", requestId = data.requestId, slotIndex = data.slotIndex logScope:
requestId = data.requestId
slotIndex = data.slotIndex
debug "Filling slot"
try:
await market.fillSlot(data.requestId, data.slotIndex, state.proof, collateral) await market.fillSlot(data.requestId, data.slotIndex, state.proof, collateral)
debug "Waiting for slot filled event...", requestId = $data.requestId, slotIndex = $data.slotIndex except MarketError as e:
if e.msg.contains "Slot is not free":
debug "Slot is already filled, ignoring slot"
return some State( SaleIgnored(reprocessSlot: false, returnBytes: true) )
else:
return some State( SaleErrored(error: e) )
# other CatchableErrors are handled "automatically" by the ErrorHandlingState
return some State(SaleFilled())

View File

@ -28,10 +28,6 @@ method onCancelled*(state: SaleSlotReserving, request: StorageRequest): ?State =
method onFailed*(state: SaleSlotReserving, request: StorageRequest): ?State = method onFailed*(state: SaleSlotReserving, request: StorageRequest): ?State =
return some State(SaleFailed()) return some State(SaleFailed())
method onSlotFilled*(state: SaleSlotReserving, requestId: RequestId,
slotIndex: UInt256): ?State =
return some State(SaleFilled())
method run*(state: SaleSlotReserving, machine: Machine): Future[?State] {.async.} = method run*(state: SaleSlotReserving, machine: Machine): Future[?State] {.async.} =
let agent = SalesAgent(machine) let agent = SalesAgent(machine)
let data = agent.data let data = agent.data
@ -48,7 +44,12 @@ method run*(state: SaleSlotReserving, machine: Machine): Future[?State] {.async.
trace "Reserving slot" trace "Reserving slot"
await market.reserveSlot(data.requestId, data.slotIndex) await market.reserveSlot(data.requestId, data.slotIndex)
except MarketError as e: except MarketError as e:
if e.msg.contains "Reservation not allowed":
debug "Slot cannot be reserved, ignoring", error = e.msg
return some State( SaleIgnored(reprocessSlot: false, returnBytes: true) )
else:
return some State( SaleErrored(error: e) ) return some State( SaleErrored(error: e) )
# other CatchableErrors are handled "automatically" by the ErrorHandlingState
trace "Slot successfully reserved" trace "Slot successfully reserved"
return some State( SaleDownloading() ) return some State( SaleDownloading() )

View File

@ -75,7 +75,7 @@ proc scheduler(machine: Machine) {.async.} =
await running.cancelAndWait() await running.cancelAndWait()
let fromState = if machine.state.isNil: "<none>" else: $machine.state let fromState = if machine.state.isNil: "<none>" else: $machine.state
machine.state = next machine.state = next
debug "enter state", state = machine.state, fromState debug "enter state", state = fromState & " => " & $machine.state
running = machine.run(machine.state) running = machine.run(machine.state)
running running
.track(machine) .track(machine)

View File

@ -24,7 +24,3 @@ checksuite "sales state 'filling'":
test "switches to failed state when request fails": test "switches to failed state when request fails":
let next = state.onFailed(request) let next = state.onFailed(request)
check !next of SaleFailed check !next of SaleFailed
test "switches to filled state when slot is filled":
let next = state.onSlotFilled(request.id, slotIndex)
check !next of SaleFilled

View File

@ -51,10 +51,6 @@ asyncchecksuite "sales state 'SlotReserving'":
let next = state.onFailed(request) let next = state.onFailed(request)
check !next of SaleFailed check !next of SaleFailed
test "switches to filled state when slot is filled":
let next = state.onSlotFilled(request.id, slotIndex)
check !next of SaleFilled
test "run switches to downloading when slot successfully reserved": test "run switches to downloading when slot successfully reserved":
let next = await state.run(agent) let next = await state.run(agent)
check !next of SaleDownloading check !next of SaleDownloading

View File

@ -115,7 +115,7 @@ method onOutputLineCaptured(node: HardhatProcess, line: string) =
return return
if error =? logFile.writeFile(line & "\n").errorOption: if error =? logFile.writeFile(line & "\n").errorOption:
error "failed to write to hardhat file", errorCode = error error "failed to write to hardhat file", errorCode = $error
discard logFile.closeFile() discard logFile.closeFile()
node.logFile = none IoHandle node.logFile = none IoHandle

View File

@ -128,7 +128,7 @@ method stop*(node: NodeProcess) {.base, async.} =
try: try:
trace "terminating node process..." trace "terminating node process..."
if errCode =? node.process.terminate().errorOption: if errCode =? node.process.terminate().errorOption:
error "failed to terminate process", errCode error "failed to terminate process", errCode = $errCode
trace "waiting for node process to exit" trace "waiting for node process to exit"
let exitCode = await node.process.waitForExit(3.seconds) let exitCode = await node.process.waitForExit(3.seconds)