From 19af79786e478f338eadc47ac3a9024ff236ed4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Uhl=C3=AD=C5=99?= Date: Thu, 12 Dec 2024 21:19:56 +0100 Subject: [PATCH] feat: repair is rewarded (#1022) * feat: repair is rewarded * chore: update contracts repo * feat: proving loop handles repair case * test: assert repair state * chore: update contracts repo * fix: upon unknown state of repair go to error --- codex/contracts/market.nim | 5 +++++ codex/contracts/requests.nim | 1 + codex/market.nim | 3 +++ codex/sales/states/filling.nim | 13 ++++++++++++- codex/sales/states/preparing.nim | 2 +- codex/sales/states/proving.nim | 5 +++++ codex/sales/states/unknown.nim | 7 ++++++- tests/codex/helpers/mockmarket.nim | 3 +++ tests/contracts/testMarket.nim | 2 +- vendor/codex-contracts-eth | 2 +- 10 files changed, 38 insertions(+), 5 deletions(-) diff --git a/codex/contracts/market.nim b/codex/contracts/market.nim index cc60e27c..049e38bb 100644 --- a/codex/contracts/market.nim +++ b/codex/contracts/market.nim @@ -84,6 +84,11 @@ method proofTimeout*(market: OnChainMarket): Future[UInt256] {.async.} = let config = await market.config() return config.proofs.timeout +method repairRewardPercentage*(market: OnChainMarket): Future[uint8] {.async.} = + convertEthersError: + let config = await market.contract.configuration() + return config.collateral.repairRewardPercentage + method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} = convertEthersError: let config = await market.config() diff --git a/codex/contracts/requests.nim b/codex/contracts/requests.nim index d94baa17..be85e6b5 100644 --- a/codex/contracts/requests.nim +++ b/codex/contracts/requests.nim @@ -49,6 +49,7 @@ type Failed Paid Cancelled + Repair proc `==`*(x, y: Nonce): bool {.borrow.} proc `==`*(x, y: RequestId): bool {.borrow.} diff --git a/codex/market.nim b/codex/market.nim index 6892237c..cb86e0d7 100644 --- a/codex/market.nim +++ b/codex/market.nim @@ -67,6 +67,9 @@ method periodicity*(market: Market): Future[Periodicity] {.base, async.} = method proofTimeout*(market: Market): Future[UInt256] {.base, async.} = raiseAssert("not implemented") +method repairRewardPercentage*(market: Market): Future[uint8] {.base, async.} = + raiseAssert("not implemented") + method proofDowntime*(market: Market): Future[uint8] {.base, async.} = raiseAssert("not implemented") diff --git a/codex/sales/states/filling.nim b/codex/sales/states/filling.nim index 678e2a21..a5531e79 100644 --- a/codex/sales/states/filling.nim +++ b/codex/sales/states/filling.nim @@ -1,3 +1,4 @@ +import pkg/stint import ../../logutils import ../../market import ../statemachine @@ -27,13 +28,23 @@ method onFailed*(state: SaleFilling, request: StorageRequest): ?State = method run(state: SaleFilling, machine: Machine): Future[?State] {.async.} = let data = SalesAgent(machine).data let market = SalesAgent(machine).context.market - without (collateral =? data.request.?ask.?collateral): + without (fullCollateral =? data.request.?ask.?collateral): raiseAssert "Request not set" logScope: requestId = data.requestId slotIndex = data.slotIndex + let slotState = await market.slotState(slotId(data.requestId, data.slotIndex)) + var collateral: Uint256 + + if slotState == SlotState.Repair: + # When repairing the node gets "discount" on the collateral that it needs to + let repairRewardPercentage = (await market.repairRewardPercentage).u256 + collateral = fullCollateral - ((fullCollateral * repairRewardPercentage)).div(100.u256) + else: + collateral = fullCollateral + debug "Filling slot" try: await market.fillSlot(data.requestId, data.slotIndex, state.proof, collateral) diff --git a/codex/sales/states/preparing.nim b/codex/sales/states/preparing.nim index c92ec716..169eb964 100644 --- a/codex/sales/states/preparing.nim +++ b/codex/sales/states/preparing.nim @@ -49,7 +49,7 @@ method run*(state: SalePreparing, machine: Machine): Future[?State] {.async.} = let slotId = slotId(data.requestId, data.slotIndex) let state = await market.slotState(slotId) - if state != SlotState.Free: + if state != SlotState.Free and state != SlotState.Repair: return some State(SaleIgnored(reprocessSlot: false, returnBytes: false)) # TODO: Once implemented, check to ensure the host is allowed to fill the slot, diff --git a/codex/sales/states/proving.nim b/codex/sales/states/proving.nim index dd05ac7f..76180ab2 100644 --- a/codex/sales/states/proving.nim +++ b/codex/sales/states/proving.nim @@ -16,6 +16,7 @@ logScope: topics = "marketplace sales proving" type + SlotFreedError* = object of CatchableError SlotNotFilledError* = object of CatchableError SaleProving* = ref object of ErrorHandlingState loop: Future[void] @@ -82,6 +83,10 @@ proc proveLoop( of SlotState.Cancelled: debug "Slot reached cancelled state" # do nothing, let onCancelled callback take care of it + of SlotState.Repair: + warn "Slot was forcible freed" + let message = "Slot was forcible freed and host was removed from its hosting" + raise newException(SlotFreedError, message) of SlotState.Failed: debug "Slot reached failed state" # do nothing, let onFailed callback take care of it diff --git a/codex/sales/states/unknown.nim b/codex/sales/states/unknown.nim index db00f517..d497cba3 100644 --- a/codex/sales/states/unknown.nim +++ b/codex/sales/states/unknown.nim @@ -5,6 +5,7 @@ import ./filled import ./finished import ./failed import ./errored +import ./proving import ./cancelled import ./payout @@ -38,7 +39,7 @@ method run*(state: SaleUnknown, machine: Machine): Future[?State] {.async.} = case slotState of SlotState.Free: let error = newException(UnexpectedSlotError, - "slot state on chain should not be 'free'") + "Slot state on chain should not be 'free'") return some State(SaleErrored(error: error)) of SlotState.Filled: return some State(SaleFilled()) @@ -50,3 +51,7 @@ method run*(state: SaleUnknown, machine: Machine): Future[?State] {.async.} = return some State(SaleFailed()) of SlotState.Cancelled: return some State(SaleCancelled()) + of SlotState.Repair: + let error = newException(SlotFreedError, + "Slot was forcible freed and host was removed from its hosting") + return some State(SaleErrored(error: error)) diff --git a/tests/codex/helpers/mockmarket.nim b/tests/codex/helpers/mockmarket.nim index d19f0c21..07eeb856 100644 --- a/tests/codex/helpers/mockmarket.nim +++ b/tests/codex/helpers/mockmarket.nim @@ -125,6 +125,9 @@ method proofTimeout*(market: MockMarket): Future[UInt256] {.async.} = method proofDowntime*(market: MockMarket): Future[uint8] {.async.} = return market.config.proofs.downtime +method repairRewardPercentage*(market: MockMarket): Future[uint8] {.async.} = + return market.config.collateral.repairRewardPercentage + method getPointer*(market: MockMarket, slotId: SlotId): Future[uint8] {.async.} = return market.proofPointer diff --git a/tests/contracts/testMarket.nim b/tests/contracts/testMarket.nim index 8e4d013e..a32590d6 100644 --- a/tests/contracts/testMarket.nim +++ b/tests/contracts/testMarket.nim @@ -303,7 +303,7 @@ ethersuite "On-Chain Market": let slotId = request.slotId(slotIndex.u256) while true: let slotState = await marketplace.slotState(slotId) - if slotState == SlotState.Free: + if slotState == SlotState.Repair or slotState == SlotState.Failed: break await waitUntilProofRequired(slotId) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) diff --git a/vendor/codex-contracts-eth b/vendor/codex-contracts-eth index 945f6008..dfab6102 160000 --- a/vendor/codex-contracts-eth +++ b/vendor/codex-contracts-eth @@ -1 +1 @@ -Subproject commit 945f6008c8817abd7ca43a40368d33bb1e014c14 +Subproject commit dfab6102e71d2acaff86af45b87be2536530c624