556 lines
19 KiB
Nim
556 lines
19 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 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.} =
|
|
debug "queryPastSlotFilledEvents:market.filled",
|
|
numOfFilledSlots = market.filled.len
|
|
let filtered = market.filled.filter(
|
|
proc (slot: MockSlot): bool =
|
|
debug "queryPastSlotFilledEvents:fromTime", timestamp = slot.timestamp,
|
|
fromTime = fromTime
|
|
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)
|