adds validation tests for historical state restoration
This commit is contained in:
parent
57e2a97d20
commit
3ddad7bbbd
|
@ -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
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
@ -43,6 +47,7 @@ type
|
|||
config*: MarketplaceConfig
|
||||
canReserveSlot*: bool
|
||||
reserveSlotThrowError*: ?(ref MarketError)
|
||||
clock: ?Clock
|
||||
Fulfillment* = object
|
||||
requestId*: RequestId
|
||||
proof*: Groth16Proof
|
||||
|
@ -52,6 +57,7 @@ type
|
|||
host*: Address
|
||||
slotIndex*: UInt256
|
||||
proof*: Groth16Proof
|
||||
timestamp: ?SecondsSince1970
|
||||
Subscriptions = object
|
||||
onRequest: seq[RequestSubscription]
|
||||
onFulfillment: seq[FulfillmentSubscription]
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
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
|
||||
|
@ -11,6 +12,9 @@ import ./helpers/mockclock
|
|||
import ./examples
|
||||
import ./helpers
|
||||
|
||||
logScope:
|
||||
topics = "testValidation"
|
||||
|
||||
asyncchecksuite "validation":
|
||||
let period = 10
|
||||
let timeout = 5
|
||||
|
@ -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,
|
||||
|
@ -33,18 +37,26 @@ asyncchecksuite "validation":
|
|||
raiseAssert fmt"Creating ValidationConfig failed! Error msg: {error.msg}"
|
||||
validationConfig
|
||||
|
||||
setup:
|
||||
groupIndex = groupIndexForSlotId(slot.id, !validationGroups)
|
||||
market = MockMarket.new()
|
||||
clock = MockClock.new()
|
||||
proc newValidation(clock: Clock,
|
||||
market: Market,
|
||||
maxSlots: MaxSlots,
|
||||
validationGroups: ?ValidationGroups,
|
||||
groupIndex: uint16 = 0): Validation =
|
||||
let validationConfig = initValidationConfig(
|
||||
maxSlots, validationGroups, groupIndex)
|
||||
validation = Validation.new(clock, market, validationConfig)
|
||||
Validation.new(clock, market, validationConfig)
|
||||
|
||||
setup:
|
||||
groupIndex = groupIndexForSlotId(slot.id, !validationGroups)
|
||||
clock = MockClock.new()
|
||||
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 =
|
||||
|
@ -79,6 +91,7 @@ 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
|
||||
|
@ -86,45 +99,41 @@ asyncchecksuite "validation":
|
|||
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()
|
||||
|
@ -132,6 +141,7 @@ asyncchecksuite "validation":
|
|||
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()
|
||||
|
@ -139,13 +149,75 @@ asyncchecksuite "validation":
|
|||
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]
|
||||
|
|
Loading…
Reference in New Issue