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:
|
except CatchableError as e:
|
||||||
error "Validation failed", msg = e.msg
|
error "Validation failed", msg = e.msg
|
||||||
|
|
||||||
proc epochForDurationBackFromNow(duration: times.Duration): int64 =
|
proc epochForDurationBackFromNow(validation: Validation,
|
||||||
let now = getTime().toUnix
|
duration: times.Duration): SecondsSince1970 =
|
||||||
return now - duration.inSeconds
|
return validation.clock.now - duration.inSeconds
|
||||||
|
|
||||||
proc restoreHistoricalState(validation: Validation) {.async} =
|
proc restoreHistoricalState(validation: Validation) {.async} =
|
||||||
trace "Restoring historical state..."
|
trace "Restoring historical state..."
|
||||||
let startTimeEpoch = epochForDurationBackFromNow(MaxStorageRequestDuration)
|
let startTimeEpoch = validation.epochForDurationBackFromNow(MaxStorageRequestDuration)
|
||||||
let slotFilledEvents = await validation.market.queryPastSlotFilledEvents(
|
let slotFilledEvents = await validation.market.queryPastSlotFilledEvents(
|
||||||
fromTime = startTimeEpoch)
|
fromTime = startTimeEpoch)
|
||||||
trace "Found slot filled events", numberOfSlots = slotFilledEvents.len
|
trace "Found slot filled events", numberOfSlots = slotFilledEvents.len
|
||||||
|
@ -150,6 +150,7 @@ proc start*(validation: Validation) {.async.} =
|
||||||
validation.running = validation.run()
|
validation.running = validation.run()
|
||||||
|
|
||||||
proc stop*(validation: Validation) {.async.} =
|
proc stop*(validation: Validation) {.async.} =
|
||||||
|
if not isNil(validation.running):
|
||||||
await validation.running.cancelAndWait()
|
await validation.running.cancelAndWait()
|
||||||
while validation.subscriptions.len > 0:
|
while validation.subscriptions.len > 0:
|
||||||
let subscription = validation.subscriptions.pop()
|
let subscription = validation.subscriptions.pop()
|
||||||
|
|
|
@ -10,12 +10,16 @@ import pkg/codex/contracts/proofs
|
||||||
import pkg/codex/contracts/config
|
import pkg/codex/contracts/config
|
||||||
|
|
||||||
from pkg/ethers import BlockTag
|
from pkg/ethers import BlockTag
|
||||||
|
import codex/clock
|
||||||
|
|
||||||
import ../examples
|
import ../examples
|
||||||
|
|
||||||
export market
|
export market
|
||||||
export tables
|
export tables
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "mockMarket"
|
||||||
|
|
||||||
type
|
type
|
||||||
MockMarket* = ref object of Market
|
MockMarket* = ref object of Market
|
||||||
periodicity: Periodicity
|
periodicity: Periodicity
|
||||||
|
@ -43,6 +47,7 @@ type
|
||||||
config*: MarketplaceConfig
|
config*: MarketplaceConfig
|
||||||
canReserveSlot*: bool
|
canReserveSlot*: bool
|
||||||
reserveSlotThrowError*: ?(ref MarketError)
|
reserveSlotThrowError*: ?(ref MarketError)
|
||||||
|
clock: ?Clock
|
||||||
Fulfillment* = object
|
Fulfillment* = object
|
||||||
requestId*: RequestId
|
requestId*: RequestId
|
||||||
proof*: Groth16Proof
|
proof*: Groth16Proof
|
||||||
|
@ -52,6 +57,7 @@ type
|
||||||
host*: Address
|
host*: Address
|
||||||
slotIndex*: UInt256
|
slotIndex*: UInt256
|
||||||
proof*: Groth16Proof
|
proof*: Groth16Proof
|
||||||
|
timestamp: ?SecondsSince1970
|
||||||
Subscriptions = object
|
Subscriptions = object
|
||||||
onRequest: seq[RequestSubscription]
|
onRequest: seq[RequestSubscription]
|
||||||
onFulfillment: seq[FulfillmentSubscription]
|
onFulfillment: seq[FulfillmentSubscription]
|
||||||
|
@ -97,7 +103,7 @@ proc hash*(address: Address): Hash =
|
||||||
proc hash*(requestId: RequestId): Hash =
|
proc hash*(requestId: RequestId): Hash =
|
||||||
hash(requestId.toArray)
|
hash(requestId.toArray)
|
||||||
|
|
||||||
proc new*(_: type MockMarket): MockMarket =
|
proc new*(_: type MockMarket, clock: ?Clock = Clock.none): MockMarket =
|
||||||
## Create a new mocked Market instance
|
## Create a new mocked Market instance
|
||||||
##
|
##
|
||||||
let config = MarketplaceConfig(
|
let config = MarketplaceConfig(
|
||||||
|
@ -114,7 +120,8 @@ proc new*(_: type MockMarket): MockMarket =
|
||||||
downtimeProduct: 67.uint8
|
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.} =
|
method getSigner*(market: MockMarket): Future[Address] {.async.} =
|
||||||
return market.signer
|
return market.signer
|
||||||
|
@ -248,7 +255,8 @@ proc fillSlot*(market: MockMarket,
|
||||||
requestId: requestId,
|
requestId: requestId,
|
||||||
slotIndex: slotIndex,
|
slotIndex: slotIndex,
|
||||||
proof: proof,
|
proof: proof,
|
||||||
host: host
|
host: host,
|
||||||
|
timestamp: market.clock.?now
|
||||||
)
|
)
|
||||||
market.filled.add(slot)
|
market.filled.add(slot)
|
||||||
market.slotState[slotId(slot.requestId, slot.slotIndex)] = SlotState.Filled
|
market.slotState[slotId(slot.requestId, slot.slotIndex)] = SlotState.Filled
|
||||||
|
@ -506,8 +514,19 @@ method queryPastSlotFilledEvents*(
|
||||||
|
|
||||||
method queryPastSlotFilledEvents*(
|
method queryPastSlotFilledEvents*(
|
||||||
market: MockMarket,
|
market: MockMarket,
|
||||||
fromTime: int64): Future[seq[SlotFilled]] {.async.} =
|
fromTime: SecondsSince1970): Future[seq[SlotFilled]] {.async.} =
|
||||||
return market.filled.map(slot =>
|
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)
|
SlotFilled(requestId: slot.requestId, slotIndex: slot.slotIndex)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/random
|
import std/times
|
||||||
|
|
||||||
import codex/validation
|
import codex/validation
|
||||||
import codex/periods
|
import codex/periods
|
||||||
|
import codex/clock
|
||||||
|
|
||||||
import ../asynctest
|
import ../asynctest
|
||||||
import ./helpers/mockmarket
|
import ./helpers/mockmarket
|
||||||
|
@ -11,6 +12,9 @@ import ./helpers/mockclock
|
||||||
import ./examples
|
import ./examples
|
||||||
import ./helpers
|
import ./helpers
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "testValidation"
|
||||||
|
|
||||||
asyncchecksuite "validation":
|
asyncchecksuite "validation":
|
||||||
let period = 10
|
let period = 10
|
||||||
let timeout = 5
|
let timeout = 5
|
||||||
|
@ -20,10 +24,10 @@ asyncchecksuite "validation":
|
||||||
let proof = Groth16Proof.example
|
let proof = Groth16Proof.example
|
||||||
let collateral = slot.request.ask.collateral
|
let collateral = slot.request.ask.collateral
|
||||||
|
|
||||||
var validation: Validation
|
|
||||||
var market: MockMarket
|
var market: MockMarket
|
||||||
var clock: MockClock
|
var clock: MockClock
|
||||||
var groupIndex: uint16
|
var groupIndex: uint16
|
||||||
|
var validation: Validation
|
||||||
|
|
||||||
proc initValidationConfig(maxSlots: MaxSlots,
|
proc initValidationConfig(maxSlots: MaxSlots,
|
||||||
validationGroups: ?ValidationGroups,
|
validationGroups: ?ValidationGroups,
|
||||||
|
@ -33,18 +37,26 @@ asyncchecksuite "validation":
|
||||||
raiseAssert fmt"Creating ValidationConfig failed! Error msg: {error.msg}"
|
raiseAssert fmt"Creating ValidationConfig failed! Error msg: {error.msg}"
|
||||||
validationConfig
|
validationConfig
|
||||||
|
|
||||||
setup:
|
proc newValidation(clock: Clock,
|
||||||
groupIndex = groupIndexForSlotId(slot.id, !validationGroups)
|
market: Market,
|
||||||
market = MockMarket.new()
|
maxSlots: MaxSlots,
|
||||||
clock = MockClock.new()
|
validationGroups: ?ValidationGroups,
|
||||||
|
groupIndex: uint16 = 0): Validation =
|
||||||
let validationConfig = initValidationConfig(
|
let validationConfig = initValidationConfig(
|
||||||
maxSlots, validationGroups, groupIndex)
|
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.period = period.u256
|
||||||
market.config.proofs.timeout = timeout.u256
|
market.config.proofs.timeout = timeout.u256
|
||||||
await validation.start()
|
validation = newValidation(
|
||||||
|
clock, market, maxSlots, validationGroups, groupIndex)
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
|
# calling stop on validation that did not start is harmless
|
||||||
await validation.stop()
|
await validation.stop()
|
||||||
|
|
||||||
proc advanceToNextPeriod =
|
proc advanceToNextPeriod =
|
||||||
|
@ -79,6 +91,7 @@ asyncchecksuite "validation":
|
||||||
test "initializing ValidationConfig fails when maxSlots is negative " &
|
test "initializing ValidationConfig fails when maxSlots is negative " &
|
||||||
"(validationGroups set)":
|
"(validationGroups set)":
|
||||||
let maxSlots = -1
|
let maxSlots = -1
|
||||||
|
let groupIndex = 0'u16
|
||||||
let validationConfig = ValidationConfig.init(
|
let validationConfig = ValidationConfig.init(
|
||||||
maxSlots = maxSlots, groups = validationGroups, groupIndex)
|
maxSlots = maxSlots, groups = validationGroups, groupIndex)
|
||||||
check validationConfig.isFailure == true
|
check validationConfig.isFailure == true
|
||||||
|
@ -86,45 +99,41 @@ asyncchecksuite "validation":
|
||||||
fmt"be greater than or equal to 0! (got: {maxSlots})"
|
fmt"be greater than or equal to 0! (got: {maxSlots})"
|
||||||
|
|
||||||
test "slot is not observed if it is not in the validation group":
|
test "slot is not observed if it is not in the validation group":
|
||||||
let validationConfig = initValidationConfig(maxSlots, validationGroups,
|
validation = newValidation(clock, market, maxSlots, validationGroups,
|
||||||
(groupIndex + 1) mod uint16(!validationGroups))
|
(groupIndex + 1) mod uint16(!validationGroups))
|
||||||
let validation = Validation.new(clock, market, validationConfig)
|
|
||||||
await validation.start()
|
await validation.start()
|
||||||
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
await validation.stop()
|
|
||||||
check validation.slots.len == 0
|
check validation.slots.len == 0
|
||||||
|
|
||||||
test "when a slot is filled on chain, it is added to the list":
|
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)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
check validation.slots == @[slot.id]
|
check validation.slots == @[slot.id]
|
||||||
|
|
||||||
test "slot should be observed if maxSlots is set to 0":
|
test "slot should be observed if maxSlots is set to 0":
|
||||||
let validationConfig = initValidationConfig(
|
validation = newValidation(clock, market, maxSlots = 0, ValidationGroups.none)
|
||||||
maxSlots = 0, ValidationGroups.none)
|
|
||||||
let validation = Validation.new(clock, market, validationConfig)
|
|
||||||
await validation.start()
|
await validation.start()
|
||||||
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
await validation.stop()
|
|
||||||
check validation.slots == @[slot.id]
|
check validation.slots == @[slot.id]
|
||||||
|
|
||||||
test "slot should be observed if validation group is not set (and " &
|
test "slot should be observed if validation group is not set (and " &
|
||||||
"maxSlots is not 0)":
|
"maxSlots is not 0)":
|
||||||
let validationConfig = initValidationConfig(
|
validation = newValidation(clock, market, maxSlots, ValidationGroups.none)
|
||||||
maxSlots, ValidationGroups.none)
|
|
||||||
let validation = Validation.new(clock, market, validationConfig)
|
|
||||||
await validation.start()
|
await validation.start()
|
||||||
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
await validation.stop()
|
|
||||||
check validation.slots == @[slot.id]
|
check validation.slots == @[slot.id]
|
||||||
|
|
||||||
for state in [SlotState.Finished, SlotState.Failed]:
|
for state in [SlotState.Finished, SlotState.Failed]:
|
||||||
test fmt"when slot state changes to {state}, it is removed from the list":
|
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)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
market.slotState[slot.id] = state
|
market.slotState[slot.id] = state
|
||||||
advanceToNextPeriod()
|
advanceToNextPeriod()
|
||||||
check eventually validation.slots.len == 0
|
check eventually validation.slots.len == 0
|
||||||
|
|
||||||
test "when a proof is missed, it is marked as missing":
|
test "when a proof is missed, it is marked as missing":
|
||||||
|
await validation.start()
|
||||||
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
market.setCanProofBeMarkedAsMissing(slot.id, true)
|
market.setCanProofBeMarkedAsMissing(slot.id, true)
|
||||||
advanceToNextPeriod()
|
advanceToNextPeriod()
|
||||||
|
@ -132,6 +141,7 @@ asyncchecksuite "validation":
|
||||||
check market.markedAsMissingProofs.contains(slot.id)
|
check market.markedAsMissingProofs.contains(slot.id)
|
||||||
|
|
||||||
test "when a proof can not be marked as missing, it will not be marked":
|
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)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
market.setCanProofBeMarkedAsMissing(slot.id, false)
|
market.setCanProofBeMarkedAsMissing(slot.id, false)
|
||||||
advanceToNextPeriod()
|
advanceToNextPeriod()
|
||||||
|
@ -139,13 +149,75 @@ asyncchecksuite "validation":
|
||||||
check market.markedAsMissingProofs.len == 0
|
check market.markedAsMissingProofs.len == 0
|
||||||
|
|
||||||
test "it does not monitor more than the maximum number of slots":
|
test "it does not monitor more than the maximum number of slots":
|
||||||
let validationGroups = ValidationGroups.none
|
validation = newValidation(clock, market, maxSlots, ValidationGroups.none)
|
||||||
let validationConfig = initValidationConfig(
|
|
||||||
maxSlots, validationGroups)
|
|
||||||
let validation = Validation.new(clock, market, validationConfig)
|
|
||||||
await validation.start()
|
await validation.start()
|
||||||
for _ in 0..<maxSlots + 1:
|
for _ in 0..<maxSlots + 1:
|
||||||
let slot = Slot.example
|
let slot = Slot.example
|
||||||
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
|
||||||
await validation.stop()
|
|
||||||
check validation.slots.len == maxSlots
|
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