nim-codex/tests/codex/helpers/mockmarket.nim
Adam Uhlíř e5df8c50d3
style: nph formatting (#1067)
* style: nph setup

* chore: formates codex/ and tests/ folder with nph 0.6.1
2025-01-21 20:54:46 +00:00

518 lines
17 KiB
Nim

import std/sequtils
import std/tables
import std/hashes
import std/sets
import std/sugar
import pkg/questionable
import pkg/codex/market
import pkg/codex/contracts/requests
import pkg/codex/contracts/proofs
import pkg/codex/contracts/config
from pkg/ethers import BlockTag
import codex/clock
import ../examples
export market
export tables
logScope:
topics = "mockMarket"
type
MockMarket* = ref object of Market
periodicity: Periodicity
activeRequests*: Table[Address, seq[RequestId]]
activeSlots*: Table[Address, seq[SlotId]]
requested*: seq[StorageRequest]
requestEnds*: Table[RequestId, SecondsSince1970]
requestExpiry*: Table[RequestId, SecondsSince1970]
requestState*: Table[RequestId, RequestState]
slotState*: Table[SlotId, SlotState]
fulfilled*: seq[Fulfillment]
filled*: seq[MockSlot]
freed*: seq[SlotId]
submitted*: seq[Groth16Proof]
markedAsMissingProofs*: seq[SlotId]
canBeMarkedAsMissing: HashSet[SlotId]
withdrawn*: seq[RequestId]
proofPointer*: uint8
proofsRequired: HashSet[SlotId]
proofsToBeRequired: HashSet[SlotId]
proofChallenge*: ProofChallenge
proofEnds: Table[SlotId, UInt256]
signer: Address
subscriptions: Subscriptions
config*: MarketplaceConfig
canReserveSlot*: bool
reserveSlotThrowError*: ?(ref MarketError)
clock: ?Clock
Fulfillment* = object
requestId*: RequestId
proof*: Groth16Proof
host*: Address
MockSlot* = object
requestId*: RequestId
host*: Address
slotIndex*: UInt256
proof*: Groth16Proof
timestamp: ?SecondsSince1970
Subscriptions = object
onRequest: seq[RequestSubscription]
onFulfillment: seq[FulfillmentSubscription]
onSlotFilled: seq[SlotFilledSubscription]
onSlotFreed: seq[SlotFreedSubscription]
onSlotReservationsFull: seq[SlotReservationsFullSubscription]
onRequestCancelled: seq[RequestCancelledSubscription]
onRequestFailed: seq[RequestFailedSubscription]
onProofSubmitted: seq[ProofSubmittedSubscription]
RequestSubscription* = ref object of Subscription
market: MockMarket
callback: OnRequest
FulfillmentSubscription* = ref object of Subscription
market: MockMarket
requestId: ?RequestId
callback: OnFulfillment
SlotFilledSubscription* = ref object of Subscription
market: MockMarket
requestId: ?RequestId
slotIndex: ?UInt256
callback: OnSlotFilled
SlotFreedSubscription* = ref object of Subscription
market: MockMarket
callback: OnSlotFreed
SlotReservationsFullSubscription* = ref object of Subscription
market: MockMarket
callback: OnSlotReservationsFull
RequestCancelledSubscription* = ref object of Subscription
market: MockMarket
requestId: ?RequestId
callback: OnRequestCancelled
RequestFailedSubscription* = ref object of Subscription
market: MockMarket
requestId: ?RequestId
callback: OnRequestCancelled
ProofSubmittedSubscription = ref object of Subscription
market: MockMarket
callback: OnProofSubmitted
proc hash*(address: Address): Hash =
hash(address.toArray)
proc hash*(requestId: RequestId): Hash =
hash(requestId.toArray)
proc new*(_: type MockMarket, clock: ?Clock = Clock.none): MockMarket =
## Create a new mocked Market instance
##
let config = MarketplaceConfig(
collateral: CollateralConfig(
repairRewardPercentage: 10,
maxNumberOfSlashes: 5,
slashCriterion: 3,
slashPercentage: 10,
),
proofs: ProofConfig(
period: 10.u256, timeout: 5.u256, downtime: 64.uint8, downtimeProduct: 67.uint8
),
)
MockMarket(
signer: Address.example, config: config, canReserveSlot: true, clock: clock
)
method getSigner*(market: MockMarket): Future[Address] {.async.} =
return market.signer
method periodicity*(mock: MockMarket): Future[Periodicity] {.async.} =
return Periodicity(seconds: mock.config.proofs.period)
method proofTimeout*(market: MockMarket): Future[UInt256] {.async.} =
return market.config.proofs.timeout
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
method requestStorage*(market: MockMarket, request: StorageRequest) {.async.} =
market.requested.add(request)
var subscriptions = market.subscriptions.onRequest
for subscription in subscriptions:
subscription.callback(request.id, request.ask, request.expiry)
method myRequests*(market: MockMarket): Future[seq[RequestId]] {.async.} =
return market.activeRequests[market.signer]
method mySlots*(market: MockMarket): Future[seq[SlotId]] {.async.} =
return market.activeSlots[market.signer]
method getRequest*(
market: MockMarket, id: RequestId
): Future[?StorageRequest] {.async.} =
for request in market.requested:
if request.id == id:
return some request
return none StorageRequest
method getActiveSlot*(market: MockMarket, slotId: SlotId): Future[?Slot] {.async.} =
for slot in market.filled:
if slotId(slot.requestId, slot.slotIndex) == slotId and
request =? await market.getRequest(slot.requestId):
return some Slot(request: request, slotIndex: slot.slotIndex)
return none Slot
method requestState*(
market: MockMarket, requestId: RequestId
): Future[?RequestState] {.async.} =
return market.requestState .? [requestId]
method slotState*(market: MockMarket, slotId: SlotId): Future[SlotState] {.async.} =
if not market.slotState.hasKey(slotId):
return SlotState.Free
return market.slotState[slotId]
method getRequestEnd*(
market: MockMarket, id: RequestId
): Future[SecondsSince1970] {.async.} =
return market.requestEnds[id]
method requestExpiresAt*(
market: MockMarket, id: RequestId
): Future[SecondsSince1970] {.async.} =
return market.requestExpiry[id]
method getHost*(
market: MockMarket, requestId: RequestId, slotIndex: UInt256
): Future[?Address] {.async.} =
for slot in market.filled:
if slot.requestId == requestId and slot.slotIndex == slotIndex:
return some slot.host
return none Address
proc emitSlotFilled*(market: MockMarket, requestId: RequestId, slotIndex: UInt256) =
var subscriptions = market.subscriptions.onSlotFilled
for subscription in subscriptions:
let requestMatches =
subscription.requestId.isNone or subscription.requestId == some requestId
let slotMatches =
subscription.slotIndex.isNone or subscription.slotIndex == some slotIndex
if requestMatches and slotMatches:
subscription.callback(requestId, slotIndex)
proc emitSlotFreed*(market: MockMarket, requestId: RequestId, slotIndex: UInt256) =
var subscriptions = market.subscriptions.onSlotFreed
for subscription in subscriptions:
subscription.callback(requestId, slotIndex)
proc emitSlotReservationsFull*(
market: MockMarket, requestId: RequestId, slotIndex: UInt256
) =
var subscriptions = market.subscriptions.onSlotReservationsFull
for subscription in subscriptions:
subscription.callback(requestId, slotIndex)
proc emitRequestCancelled*(market: MockMarket, requestId: RequestId) =
var subscriptions = market.subscriptions.onRequestCancelled
for subscription in subscriptions:
if subscription.requestId == requestId.some or subscription.requestId.isNone:
subscription.callback(requestId)
proc emitRequestFulfilled*(market: MockMarket, requestId: RequestId) =
var subscriptions = market.subscriptions.onFulfillment
for subscription in subscriptions:
if subscription.requestId == requestId.some or subscription.requestId.isNone:
subscription.callback(requestId)
proc emitRequestFailed*(market: MockMarket, requestId: RequestId) =
var subscriptions = market.subscriptions.onRequestFailed
for subscription in subscriptions:
if subscription.requestId == requestId.some or subscription.requestId.isNone:
subscription.callback(requestId)
proc fillSlot*(
market: MockMarket,
requestId: RequestId,
slotIndex: UInt256,
proof: Groth16Proof,
host: Address,
) =
let slot = MockSlot(
requestId: requestId,
slotIndex: slotIndex,
proof: proof,
host: host,
timestamp: market.clock .? now,
)
market.filled.add(slot)
market.slotState[slotId(slot.requestId, slot.slotIndex)] = SlotState.Filled
market.emitSlotFilled(requestId, slotIndex)
method fillSlot*(
market: MockMarket,
requestId: RequestId,
slotIndex: UInt256,
proof: Groth16Proof,
collateral: UInt256,
) {.async.} =
market.fillSlot(requestId, slotIndex, proof, market.signer)
method freeSlot*(market: MockMarket, slotId: SlotId) {.async.} =
market.freed.add(slotId)
for s in market.filled:
if slotId(s.requestId, s.slotIndex) == slotId:
market.emitSlotFreed(s.requestId, s.slotIndex)
break
market.slotState[slotId] = SlotState.Free
method withdrawFunds*(market: MockMarket, requestId: RequestId) {.async.} =
market.withdrawn.add(requestId)
if state =? market.requestState .? [requestId] and state == RequestState.Cancelled:
market.emitRequestCancelled(requestId)
proc setProofRequired*(mock: MockMarket, id: SlotId, required: bool) =
if required:
mock.proofsRequired.incl(id)
else:
mock.proofsRequired.excl(id)
method isProofRequired*(mock: MockMarket, id: SlotId): Future[bool] {.async.} =
return mock.proofsRequired.contains(id)
proc setProofToBeRequired*(mock: MockMarket, id: SlotId, required: bool) =
if required:
mock.proofsToBeRequired.incl(id)
else:
mock.proofsToBeRequired.excl(id)
method willProofBeRequired*(mock: MockMarket, id: SlotId): Future[bool] {.async.} =
return mock.proofsToBeRequired.contains(id)
method getChallenge*(mock: MockMarket, id: SlotId): Future[ProofChallenge] {.async.} =
return mock.proofChallenge
proc setProofEnd*(mock: MockMarket, id: SlotId, proofEnd: UInt256) =
mock.proofEnds[id] = proofEnd
method submitProof*(mock: MockMarket, id: SlotId, proof: Groth16Proof) {.async.} =
mock.submitted.add(proof)
for subscription in mock.subscriptions.onProofSubmitted:
subscription.callback(id)
method markProofAsMissing*(market: MockMarket, id: SlotId, period: Period) {.async.} =
market.markedAsMissingProofs.add(id)
proc setCanProofBeMarkedAsMissing*(mock: MockMarket, id: SlotId, required: bool) =
if required:
mock.canBeMarkedAsMissing.incl(id)
else:
mock.canBeMarkedAsMissing.excl(id)
method canProofBeMarkedAsMissing*(
market: MockMarket, id: SlotId, period: Period
): Future[bool] {.async.} =
return market.canBeMarkedAsMissing.contains(id)
method reserveSlot*(
market: MockMarket, requestId: RequestId, slotIndex: UInt256
) {.async.} =
if error =? market.reserveSlotThrowError:
raise error
method canReserveSlot*(
market: MockMarket, requestId: RequestId, slotIndex: UInt256
): Future[bool] {.async.} =
return market.canReserveSlot
func setCanReserveSlot*(market: MockMarket, canReserveSlot: bool) =
market.canReserveSlot = canReserveSlot
func setReserveSlotThrowError*(market: MockMarket, error: ?(ref MarketError)) =
market.reserveSlotThrowError = error
method subscribeRequests*(
market: MockMarket, callback: OnRequest
): Future[Subscription] {.async.} =
let subscription = RequestSubscription(market: market, callback: callback)
market.subscriptions.onRequest.add(subscription)
return subscription
method subscribeFulfillment*(
market: MockMarket, callback: OnFulfillment
): Future[Subscription] {.async.} =
let subscription = FulfillmentSubscription(
market: market, requestId: none RequestId, callback: callback
)
market.subscriptions.onFulfillment.add(subscription)
return subscription
method subscribeFulfillment*(
market: MockMarket, requestId: RequestId, callback: OnFulfillment
): Future[Subscription] {.async.} =
let subscription = FulfillmentSubscription(
market: market, requestId: some requestId, callback: callback
)
market.subscriptions.onFulfillment.add(subscription)
return subscription
method subscribeSlotFilled*(
market: MockMarket, callback: OnSlotFilled
): Future[Subscription] {.async.} =
let subscription = SlotFilledSubscription(market: market, callback: callback)
market.subscriptions.onSlotFilled.add(subscription)
return subscription
method subscribeSlotFilled*(
market: MockMarket, requestId: RequestId, slotIndex: UInt256, callback: OnSlotFilled
): Future[Subscription] {.async.} =
let subscription = SlotFilledSubscription(
market: market,
requestId: some requestId,
slotIndex: some slotIndex,
callback: callback,
)
market.subscriptions.onSlotFilled.add(subscription)
return subscription
method subscribeSlotFreed*(
market: MockMarket, callback: OnSlotFreed
): Future[Subscription] {.async.} =
let subscription = SlotFreedSubscription(market: market, callback: callback)
market.subscriptions.onSlotFreed.add(subscription)
return subscription
method subscribeSlotReservationsFull*(
market: MockMarket, callback: OnSlotReservationsFull
): Future[Subscription] {.async.} =
let subscription =
SlotReservationsFullSubscription(market: market, callback: callback)
market.subscriptions.onSlotReservationsFull.add(subscription)
return subscription
method subscribeRequestCancelled*(
market: MockMarket, callback: OnRequestCancelled
): Future[Subscription] {.async.} =
let subscription = RequestCancelledSubscription(
market: market, requestId: none RequestId, callback: callback
)
market.subscriptions.onRequestCancelled.add(subscription)
return subscription
method subscribeRequestCancelled*(
market: MockMarket, requestId: RequestId, callback: OnRequestCancelled
): Future[Subscription] {.async.} =
let subscription = RequestCancelledSubscription(
market: market, requestId: some requestId, callback: callback
)
market.subscriptions.onRequestCancelled.add(subscription)
return subscription
method subscribeRequestFailed*(
market: MockMarket, callback: OnRequestFailed
): Future[Subscription] {.async.} =
let subscription = RequestFailedSubscription(
market: market, requestId: none RequestId, callback: callback
)
market.subscriptions.onRequestFailed.add(subscription)
return subscription
method subscribeRequestFailed*(
market: MockMarket, requestId: RequestId, callback: OnRequestFailed
): Future[Subscription] {.async.} =
let subscription = RequestFailedSubscription(
market: market, requestId: some requestId, callback: callback
)
market.subscriptions.onRequestFailed.add(subscription)
return subscription
method subscribeProofSubmission*(
mock: MockMarket, callback: OnProofSubmitted
): Future[Subscription] {.async.} =
let subscription = ProofSubmittedSubscription(market: mock, callback: callback)
mock.subscriptions.onProofSubmitted.add(subscription)
return subscription
method queryPastStorageRequestedEvents*(
market: MockMarket, fromBlock: BlockTag
): Future[seq[StorageRequested]] {.async.} =
return market.requested.map(
request =>
StorageRequested(requestId: request.id, ask: request.ask, expiry: request.expiry)
)
method queryPastStorageRequestedEvents*(
market: MockMarket, blocksAgo: int
): Future[seq[StorageRequested]] {.async.} =
return market.requested.map(
request =>
StorageRequested(requestId: request.id, ask: request.ask, expiry: request.expiry)
)
method queryPastSlotFilledEvents*(
market: MockMarket, fromBlock: BlockTag
): Future[seq[SlotFilled]] {.async.} =
return market.filled.map(
slot => SlotFilled(requestId: slot.requestId, slotIndex: slot.slotIndex)
)
method queryPastSlotFilledEvents*(
market: MockMarket, blocksAgo: int
): Future[seq[SlotFilled]] {.async.} =
return market.filled.map(
slot => SlotFilled(requestId: slot.requestId, slotIndex: slot.slotIndex)
)
method queryPastSlotFilledEvents*(
market: MockMarket, fromTime: SecondsSince1970
): Future[seq[SlotFilled]] {.async.} =
let filtered = market.filled.filter(
proc(slot: MockSlot): bool =
if timestamp =? slot.timestamp:
return timestamp >= fromTime
else:
true
)
return filtered.map(
slot => SlotFilled(requestId: slot.requestId, slotIndex: slot.slotIndex)
)
method unsubscribe*(subscription: RequestSubscription) {.async.} =
subscription.market.subscriptions.onRequest.keepItIf(it != subscription)
method unsubscribe*(subscription: FulfillmentSubscription) {.async.} =
subscription.market.subscriptions.onFulfillment.keepItIf(it != subscription)
method unsubscribe*(subscription: SlotFilledSubscription) {.async.} =
subscription.market.subscriptions.onSlotFilled.keepItIf(it != subscription)
method unsubscribe*(subscription: SlotFreedSubscription) {.async.} =
subscription.market.subscriptions.onSlotFreed.keepItIf(it != subscription)
method unsubscribe*(subscription: RequestCancelledSubscription) {.async.} =
subscription.market.subscriptions.onRequestCancelled.keepItIf(it != subscription)
method unsubscribe*(subscription: RequestFailedSubscription) {.async.} =
subscription.market.subscriptions.onRequestFailed.keepItIf(it != subscription)
method unsubscribe*(subscription: ProofSubmittedSubscription) {.async.} =
subscription.market.subscriptions.onProofSubmitted.keepItIf(it != subscription)
method unsubscribe*(subscription: SlotReservationsFullSubscription) {.async.} =
subscription.market.subscriptions.onSlotReservationsFull.keepItIf(it != subscription)