From e80117899d6b32db5a49462fc9a1706f0ff7c8e7 Mon Sep 17 00:00:00 2001 From: Marcin Czenko Date: Thu, 10 Oct 2024 06:26:32 +0200 Subject: [PATCH] adds validation tests for historical state restoration --- codex/validation.nim | 11 +-- tests/codex/helpers/mockmarket.nim | 29 +++++-- tests/codex/testvalidation.nim | 120 +++++++++++++++++++++++------ 3 files changed, 126 insertions(+), 34 deletions(-) diff --git a/codex/validation.nim b/codex/validation.nim index b245ebc28..6468e6a83 100644 --- a/codex/validation.nim +++ b/codex/validation.nim @@ -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() diff --git a/tests/codex/helpers/mockmarket.nim b/tests/codex/helpers/mockmarket.nim index 1d554102d..02191b789 100644 --- a/tests/codex/helpers/mockmarket.nim +++ b/tests/codex/helpers/mockmarket.nim @@ -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) ) diff --git a/tests/codex/testvalidation.nim b/tests/codex/testvalidation.nim index e67172f77..6694ce7e6 100644 --- a/tests/codex/testvalidation.nim +++ b/tests/codex/testvalidation.nim @@ -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, @@ -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 = @@ -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..