Skip to content

Commit

Permalink
adds validation tests for historical state restoration
Browse files Browse the repository at this point in the history
  • Loading branch information
marcinczenko committed Oct 11, 2024
1 parent 32e31c6 commit e801178
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 34 deletions.
11 changes: 6 additions & 5 deletions codex/validation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ proc run(validation: Validation) {.async.} =
except CatchableError as e:
error "Validation failed", msg = e.msg

proc epochForDurationBackFromNow(duration: times.Duration): int64 =
let now = getTime().toUnix
return now - duration.inSeconds
proc epochForDurationBackFromNow(validation: Validation,
duration: times.Duration): SecondsSince1970 =
return validation.clock.now - duration.inSeconds

proc restoreHistoricalState(validation: Validation) {.async} =
trace "Restoring historical state..."
let startTimeEpoch = epochForDurationBackFromNow(MaxStorageRequestDuration)
let startTimeEpoch = validation.epochForDurationBackFromNow(MaxStorageRequestDuration)
let slotFilledEvents = await validation.market.queryPastSlotFilledEvents(
fromTime = startTimeEpoch)
trace "Found slot filled events", numberOfSlots = slotFilledEvents.len
Expand All @@ -150,7 +150,8 @@ proc start*(validation: Validation) {.async.} =
validation.running = validation.run()

proc stop*(validation: Validation) {.async.} =
await validation.running.cancelAndWait()
if not isNil(validation.running):
await validation.running.cancelAndWait()
while validation.subscriptions.len > 0:
let subscription = validation.subscriptions.pop()
await subscription.unsubscribe()
29 changes: 24 additions & 5 deletions tests/codex/helpers/mockmarket.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ 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
Expand Down Expand Up @@ -43,6 +47,7 @@ type
config*: MarketplaceConfig
canReserveSlot*: bool
reserveSlotThrowError*: ?(ref MarketError)
clock: ?Clock
Fulfillment* = object
requestId*: RequestId
proof*: Groth16Proof
Expand All @@ -52,6 +57,7 @@ type
host*: Address
slotIndex*: UInt256
proof*: Groth16Proof
timestamp: ?SecondsSince1970
Subscriptions = object
onRequest: seq[RequestSubscription]
onFulfillment: seq[FulfillmentSubscription]
Expand Down Expand Up @@ -97,7 +103,7 @@ proc hash*(address: Address): Hash =
proc hash*(requestId: RequestId): Hash =
hash(requestId.toArray)

proc new*(_: type MockMarket): MockMarket =
proc new*(_: type MockMarket, clock: ?Clock = Clock.none): MockMarket =
## Create a new mocked Market instance
##
let config = MarketplaceConfig(
Expand All @@ -114,7 +120,8 @@ proc new*(_: type MockMarket): MockMarket =
downtimeProduct: 67.uint8
)
)
MockMarket(signer: Address.example, config: config, canReserveSlot: true)
MockMarket(signer: Address.example, config: config,
canReserveSlot: true, clock: clock)

method getSigner*(market: MockMarket): Future[Address] {.async.} =
return market.signer
Expand Down Expand Up @@ -248,7 +255,8 @@ proc fillSlot*(market: MockMarket,
requestId: requestId,
slotIndex: slotIndex,
proof: proof,
host: host
host: host,
timestamp: market.clock.?now
)
market.filled.add(slot)
market.slotState[slotId(slot.requestId, slot.slotIndex)] = SlotState.Filled
Expand Down Expand Up @@ -506,8 +514,19 @@ method queryPastSlotFilledEvents*(

method queryPastSlotFilledEvents*(
market: MockMarket,
fromTime: int64): Future[seq[SlotFilled]] {.async.} =
return market.filled.map(slot =>
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)
)

Expand Down
120 changes: 96 additions & 24 deletions tests/codex/testvalidation.nim
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import pkg/chronos
import std/strformat
import std/random
import std/times

import codex/validation
import codex/periods
import codex/clock

import ../asynctest
import ./helpers/mockmarket
import ./helpers/mockclock
import ./examples
import ./helpers

logScope:
topics = "testValidation"

asyncchecksuite "validation":
let period = 10
let timeout = 5
Expand All @@ -20,10 +24,10 @@ asyncchecksuite "validation":
let proof = Groth16Proof.example
let collateral = slot.request.ask.collateral

var validation: Validation
var market: MockMarket
var clock: MockClock
var groupIndex: uint16
var validation: Validation

proc initValidationConfig(maxSlots: MaxSlots,
validationGroups: ?ValidationGroups,
Expand All @@ -32,19 +36,27 @@ asyncchecksuite "validation":
maxSlots, groups=validationGroups, groupIndex), error:
raiseAssert fmt"Creating ValidationConfig failed! Error msg: {error.msg}"
validationConfig

proc newValidation(clock: Clock,
market: Market,
maxSlots: MaxSlots,
validationGroups: ?ValidationGroups,
groupIndex: uint16 = 0): Validation =
let validationConfig = initValidationConfig(
maxSlots, validationGroups, groupIndex)
Validation.new(clock, market, validationConfig)

setup:
groupIndex = groupIndexForSlotId(slot.id, !validationGroups)
market = MockMarket.new()
clock = MockClock.new()
let validationConfig = initValidationConfig(
maxSlots, validationGroups, groupIndex)
validation = Validation.new(clock, market, validationConfig)
market = MockMarket.new(clock = Clock(clock).some)
market.config.proofs.period = period.u256
market.config.proofs.timeout = timeout.u256
await validation.start()
validation = newValidation(
clock, market, maxSlots, validationGroups, groupIndex)

teardown:
# calling stop on validation that did not start is harmless
await validation.stop()

proc advanceToNextPeriod =
Expand Down Expand Up @@ -79,73 +91,133 @@ asyncchecksuite "validation":
test "initializing ValidationConfig fails when maxSlots is negative " &
"(validationGroups set)":
let maxSlots = -1
let groupIndex = 0'u16
let validationConfig = ValidationConfig.init(
maxSlots = maxSlots, groups = validationGroups, groupIndex)
check validationConfig.isFailure == true
check validationConfig.error.msg == "The value of maxSlots must " &
fmt"be greater than or equal to 0! (got: {maxSlots})"

test "slot is not observed if it is not in the validation group":
let validationConfig = initValidationConfig(maxSlots, validationGroups,
(groupIndex + 1) mod uint16(!validationGroups))
let validation = Validation.new(clock, market, validationConfig)
validation = newValidation(clock, market, maxSlots, validationGroups,
(groupIndex + 1) mod uint16(!validationGroups))
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
await validation.stop()
check validation.slots.len == 0

test "when a slot is filled on chain, it is added to the list":
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
check validation.slots == @[slot.id]

test "slot should be observed if maxSlots is set to 0":
let validationConfig = initValidationConfig(
maxSlots = 0, ValidationGroups.none)
let validation = Validation.new(clock, market, validationConfig)
validation = newValidation(clock, market, maxSlots = 0, ValidationGroups.none)
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
await validation.stop()
check validation.slots == @[slot.id]

test "slot should be observed if validation group is not set (and " &
"maxSlots is not 0)":
let validationConfig = initValidationConfig(
maxSlots, ValidationGroups.none)
let validation = Validation.new(clock, market, validationConfig)
validation = newValidation(clock, market, maxSlots, ValidationGroups.none)
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
await validation.stop()
check validation.slots == @[slot.id]

for state in [SlotState.Finished, SlotState.Failed]:
test fmt"when slot state changes to {state}, it is removed from the list":
validation = newValidation(clock, market, maxSlots, validationGroups)
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.slotState[slot.id] = state
advanceToNextPeriod()
check eventually validation.slots.len == 0

test "when a proof is missed, it is marked as missing":
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, true)
advanceToNextPeriod()
await sleepAsync(1.millis)
check market.markedAsMissingProofs.contains(slot.id)

test "when a proof can not be marked as missing, it will not be marked":
await validation.start()
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, false)
advanceToNextPeriod()
await sleepAsync(1.millis)
check market.markedAsMissingProofs.len == 0

test "it does not monitor more than the maximum number of slots":
let validationGroups = ValidationGroups.none
let validationConfig = initValidationConfig(
maxSlots, validationGroups)
let validation = Validation.new(clock, market, validationConfig)
validation = newValidation(clock, market, maxSlots, ValidationGroups.none)
await validation.start()
for _ in 0..<maxSlots + 1:
let slot = Slot.example
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
await validation.stop()
check validation.slots.len == maxSlots

test "[restoring historical state] it retrieves the historical state " &
"for max 30 days in the past":
let earlySlot = Slot.example
await market.fillSlot(earlySlot.request.id, earlySlot.slotIndex, proof, collateral)
let fromTime = clock.now()
clock.set(fromTime + 1)
let duration: times.Duration = initDuration(days = 30)
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)

clock.set(fromTime + duration.inSeconds + 1)

validation = newValidation(clock, market, maxSlots = 0,
ValidationGroups.none)
await validation.start()

check validation.slots == @[slot.id]

for state in [SlotState.Finished, SlotState.Failed]:
test "[restoring historical state] when restoring historical state, " &
fmt"it excludes slots in {state} state":
let slot1 = Slot.example
let slot2 = Slot.example
await market.fillSlot(slot1.request.id, slot1.slotIndex,
proof, collateral)
await market.fillSlot(slot2.request.id, slot2.slotIndex,
proof, collateral)

market.slotState[slot1.id] = state

validation = newValidation(clock, market, maxSlots = 0,
ValidationGroups.none)
await validation.start()

check validation.slots == @[slot2.id]

test "[restoring historical state] it does not monitor more than the " &
"maximum number of slots ":
for _ in 0..<maxSlots + 1:
let slot = Slot.example
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
validation = newValidation(clock, market, maxSlots, ValidationGroups.none)
await validation.start()
check validation.slots.len == maxSlots

test "[restoring historical state] slot is not observed if it is not " &
"in the validation group":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
validation = newValidation(clock, market, maxSlots, validationGroups,
(groupIndex + 1) mod uint16(!validationGroups))
await validation.start()
check validation.slots.len == 0

test "[restoring historical state] slot should be observed if maxSlots " &
"is set to 0":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
validation = newValidation(clock, market, maxSlots = 0, ValidationGroups.none)
await validation.start()
check validation.slots == @[slot.id]

test "[restoring historical state] slot should be observed if validation " &
"group is not set (and maxSlots is not 0)":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
validation = newValidation(clock, market, maxSlots, ValidationGroups.none)
await validation.start()
check validation.slots == @[slot.id]

0 comments on commit e801178

Please sign in to comment.