introduce light client processor (#3509)
Adds `LightClientProcessor` as the pendant to `BlockProcessor` while operating in light client mode. Note that a similar mechanism based on async futures is used for interoperability with existing infrastructure, despite light client object validation being done synchronously.
This commit is contained in:
parent
9f8894fb43
commit
12dc427535
|
@ -254,6 +254,16 @@ OK: 9/9 Fail: 0/9 Skip: 0/9
|
|||
+ Pre-Altair OK
|
||||
```
|
||||
OK: 2/3 Fail: 0/3 Skip: 1/3
|
||||
## Light client processor [Preset: mainnet]
|
||||
```diff
|
||||
+ Duplicate bootstrap [Preset: mainnet] OK
|
||||
+ Forced update [Preset: mainnet] OK
|
||||
+ Invalid bootstrap [Preset: mainnet] OK
|
||||
+ Missing bootstrap (optimistic update) [Preset: mainnet] OK
|
||||
+ Missing bootstrap (update) [Preset: mainnet] OK
|
||||
+ Standard sync [Preset: mainnet] OK
|
||||
```
|
||||
OK: 6/6 Fail: 0/6 Skip: 0/6
|
||||
## ListKeys requests [Preset: mainnet]
|
||||
```diff
|
||||
+ Correct token provided [Preset: mainnet] OK
|
||||
|
@ -519,4 +529,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
|
|||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||
|
||||
---TOTAL---
|
||||
OK: 287/293 Fail: 0/293 Skip: 6/293
|
||||
OK: 293/299 Fail: 0/299 Skip: 6/299
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2022 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
stew/objects,
|
||||
chronos, metrics,
|
||||
../spec/datatypes/altair,
|
||||
../spec/light_client_sync,
|
||||
../consensus_object_pools/block_pools_types,
|
||||
".."/[beacon_clock],
|
||||
../sszdump
|
||||
|
||||
export sszdump
|
||||
|
||||
# Light Client Processor
|
||||
# ------------------------------------------------------------------------------
|
||||
# The light client processor handles received light client objects
|
||||
|
||||
declareHistogram light_client_store_object_duration_seconds,
|
||||
"storeObject() duration", buckets = [0.25, 0.5, 1, 2, 4, 8, Inf]
|
||||
|
||||
type
|
||||
DidInitializeStoreCallback* =
|
||||
proc() {.gcsafe, raises: [Defect].}
|
||||
|
||||
LightClientProcessor* = object
|
||||
## This manages the processing of received light client objects
|
||||
##
|
||||
## from:
|
||||
## - Gossip (`OptimisticLightClientUpdate`)
|
||||
## - SyncManager (`BestLightClientUpdatesByRange`)
|
||||
## - LightClientManager (`GetLatestLightClientUpdate`,
|
||||
## `GetOptimisticLightClientUpdate`, `GetLightClientBootstrap`)
|
||||
##
|
||||
## are then verified and added to:
|
||||
## - `LightClientStore`
|
||||
##
|
||||
## The processor will also attempt to force-update the light client state
|
||||
## if no update seems to be available on the network, that is both signed by
|
||||
## a supermajority of sync committee members and also has a finality proof.
|
||||
## This logic is triggered if there is no progress for an extended period
|
||||
## of time, and there are repeated messages indicating that this is the best
|
||||
## available data on the network during that time period.
|
||||
|
||||
# Config
|
||||
# ----------------------------------------------------------------
|
||||
dumpEnabled: bool
|
||||
dumpDirInvalid: string
|
||||
dumpDirIncoming: string
|
||||
|
||||
# Consumer
|
||||
# ----------------------------------------------------------------
|
||||
store: ref Option[LightClientStore]
|
||||
getBeaconTime*: GetBeaconTimeFn
|
||||
didInitializeStoreCallback: DidInitializeStoreCallback
|
||||
|
||||
cfg: RuntimeConfig
|
||||
genesisValidatorsRoot: Eth2Digest
|
||||
trustedBlockRoot: Eth2Digest
|
||||
|
||||
lastProgressTick: BeaconTime # Moment when last update made progress
|
||||
lastDuplicateTick: BeaconTime # Moment when last duplicate update received
|
||||
numDuplicatesSinceProgress: int # Number of duplicates since last progress
|
||||
|
||||
const
|
||||
# These constants have been chosen empirically and are not backed by spec
|
||||
duplicateRateLimit = chronos.seconds(5) # Rate limit for counting duplicates
|
||||
duplicateCountDelay = chronos.minutes(15) # Delay to start counting duplicates
|
||||
minForceUpdateDelay = chronos.minutes(30) # Minimum delay until forced-update
|
||||
minForceUpdateDuplicates = 100 # Minimum duplicates until forced-update
|
||||
|
||||
# Initialization
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc new*(
|
||||
T: type LightClientProcessor,
|
||||
dumpEnabled: bool,
|
||||
dumpDirInvalid, dumpDirIncoming: string,
|
||||
cfg: RuntimeConfig,
|
||||
genesisValidatorsRoot, trustedBlockRoot: Eth2Digest,
|
||||
store: ref Option[LightClientStore],
|
||||
getBeaconTime: GetBeaconTimeFn,
|
||||
didInitializeStoreCallback: DidInitializeStoreCallback = nil
|
||||
): ref LightClientProcessor =
|
||||
(ref LightClientProcessor)(
|
||||
dumpEnabled: dumpEnabled,
|
||||
dumpDirInvalid: dumpDirInvalid,
|
||||
dumpDirIncoming: dumpDirIncoming,
|
||||
store: store,
|
||||
getBeaconTime: getBeaconTime,
|
||||
didInitializeStoreCallback: didInitializeStoreCallback,
|
||||
cfg: cfg,
|
||||
genesisValidatorsRoot: genesisValidatorsRoot,
|
||||
trustedBlockRoot: trustedBlockRoot
|
||||
)
|
||||
|
||||
# Storage
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc dumpInvalidObject(
|
||||
self: LightClientProcessor,
|
||||
obj: SomeLightClientObject) =
|
||||
if self.dumpEnabled:
|
||||
dump(self.dumpDirInvalid, obj)
|
||||
|
||||
proc dumpObject[T](
|
||||
self: LightClientProcessor,
|
||||
obj: SomeLightClientObject,
|
||||
res: Result[T, BlockError]) =
|
||||
if self.dumpEnabled and res.isErr:
|
||||
case res.error
|
||||
of BlockError.Invalid:
|
||||
self.dumpInvalidObject(obj)
|
||||
of BlockError.MissingParent:
|
||||
dump(self.dumpDirIncoming, obj)
|
||||
else:
|
||||
discard
|
||||
|
||||
proc tryForceUpdate(
|
||||
self: var LightClientProcessor,
|
||||
wallTime: BeaconTime) =
|
||||
## Try to force-update to the next sync committee period.
|
||||
let
|
||||
wallSlot = wallTime.slotOrZero()
|
||||
store = self.store
|
||||
|
||||
if store[].isSome:
|
||||
case store[].get.process_slot_for_light_client_store(wallSlot)
|
||||
of NoUpdate:
|
||||
discard
|
||||
of UpdatedWithoutSupermajority:
|
||||
warn "Light client force-update without supermajority",
|
||||
localHeadSlot = store[].get.optimistic_header.slot,
|
||||
finalized = store[].get.finalized_header
|
||||
of UpdatedWithoutFinalityProof:
|
||||
warn "Light client force-update without finality proof",
|
||||
localHeadSlot = store[].get.optimistic_header.slot,
|
||||
finalized = store[].get.finalized_header
|
||||
|
||||
proc storeObject*(
|
||||
self: var LightClientProcessor,
|
||||
src: MsgSource, wallTime: BeaconTime,
|
||||
obj: SomeLightClientObject): Result[void, BlockError] =
|
||||
## storeObject is the main entry point for unvalidated light client objects -
|
||||
## all untrusted objects pass through here. When storing an object, we will
|
||||
## update the `LightClientStore` accordingly
|
||||
let
|
||||
startTick = Moment.now()
|
||||
wallSlot = wallTime.slotOrZero()
|
||||
store = self.store
|
||||
|
||||
res =
|
||||
when obj is altair.LightClientBootstrap:
|
||||
if store[].isSome:
|
||||
err(BlockError.Duplicate)
|
||||
else:
|
||||
let initRes = initialize_light_client_store(
|
||||
self.trustedBlockRoot, obj)
|
||||
if initRes.isErr:
|
||||
err(initRes.error)
|
||||
else:
|
||||
store[] = some(initRes.get)
|
||||
ok()
|
||||
elif obj is altair.LightClientUpdate:
|
||||
if store[].isNone:
|
||||
err(BlockError.MissingParent)
|
||||
else:
|
||||
store[].get.process_light_client_update(
|
||||
obj, wallSlot, self.cfg, self.genesisValidatorsRoot,
|
||||
allowForceUpdate = false)
|
||||
elif obj is altair.OptimisticLightClientUpdate:
|
||||
if store[].isNone:
|
||||
err(BlockError.MissingParent)
|
||||
else:
|
||||
store[].get.process_optimistic_light_client_update(
|
||||
obj, wallSlot, self.cfg, self.genesisValidatorsRoot)
|
||||
|
||||
self.dumpObject(obj, res)
|
||||
|
||||
if res.isErr:
|
||||
when obj is altair.LightClientUpdate:
|
||||
if store[].isSome and store[].get.best_valid_update.isSome:
|
||||
# `best_valid_update` gets set when no supermajority / finality proof
|
||||
# is available. In that case, we will wait for a better update.
|
||||
# If none is made available within reasonable time, the light client
|
||||
# is force-updated using the best known data to ensure sync progress.
|
||||
case res.error
|
||||
of BlockError.Duplicate:
|
||||
if wallTime >= self.lastDuplicateTick + duplicateRateLimit:
|
||||
if self.numDuplicatesSinceProgress < minForceUpdateDuplicates:
|
||||
let
|
||||
finalized_period =
|
||||
store[].get.finalized_header.slot.sync_committee_period
|
||||
update_period =
|
||||
obj.get_active_header().slot.sync_committee_period
|
||||
is_next_sync_committee_known =
|
||||
not store[].get.next_sync_committee.isZeroMemory
|
||||
update_can_advance_period =
|
||||
if is_next_sync_committee_known:
|
||||
update_period == finalized_period + 1
|
||||
else:
|
||||
update_period == finalized_period
|
||||
if update_can_advance_period:
|
||||
self.lastDuplicateTick = wallTime
|
||||
inc self.numDuplicatesSinceProgress
|
||||
if self.numDuplicatesSinceProgress >= minForceUpdateDuplicates and
|
||||
wallTime >= self.lastProgressTick + minForceUpdateDelay:
|
||||
self.tryForceUpdate(wallTime)
|
||||
self.lastProgressTick = wallTime
|
||||
self.lastDuplicateTick = wallTime + duplicateCountDelay
|
||||
self.numDuplicatesSinceProgress = 0
|
||||
else: discard
|
||||
|
||||
return res
|
||||
|
||||
when obj is altair.LightClientBootstrap | altair.LightClientUpdate:
|
||||
self.lastProgressTick = wallTime
|
||||
self.lastDuplicateTick = wallTime + duplicateCountDelay
|
||||
self.numDuplicatesSinceProgress = 0
|
||||
|
||||
let
|
||||
storeObjectTick = Moment.now()
|
||||
storeObjectDur = storeObjectTick - startTick
|
||||
|
||||
light_client_store_object_duration_seconds.observe(
|
||||
storeObjectDur.toFloatSeconds())
|
||||
|
||||
let objSlot =
|
||||
when obj is altair.LightClientBootstrap:
|
||||
obj.header.slot
|
||||
else:
|
||||
obj.attested_header.slot
|
||||
debug "Light client object processed", kind = typeof(obj).name,
|
||||
localHeadSlot = store[].get.optimistic_header.slot,
|
||||
objectSlot = objSlot,
|
||||
storeObjectDur
|
||||
|
||||
when obj is altair.LightClientBootstrap:
|
||||
if self.didInitializeStoreCallback != nil:
|
||||
self.didInitializeStoreCallback()
|
||||
self.didInitializeStoreCallback = nil
|
||||
|
||||
res
|
||||
|
||||
# Enqueue
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc addObject*(
|
||||
self: var LightClientProcessor,
|
||||
src: MsgSource,
|
||||
obj: SomeLightClientObject,
|
||||
resfut: Future[Result[void, BlockError]] = nil) =
|
||||
## Enqueue a Gossip-validated light client object for verification
|
||||
# Backpressure:
|
||||
# Only one object is validated at any time -
|
||||
# Light client objects are always "fast" to process
|
||||
# Producers:
|
||||
# - Gossip (`OptimisticLightClientUpdate`)
|
||||
# - SyncManager (`BestLightClientUpdatesByRange`)
|
||||
# - LightClientManager (`GetLatestLightClientUpdate`,
|
||||
# `GetOptimisticLightClientUpdate`, `GetLightClientBootstrap`)
|
||||
|
||||
let
|
||||
wallTime = self.getBeaconTime()
|
||||
(afterGenesis, wallSlot) = wallTime.toSlot()
|
||||
|
||||
if not afterGenesis:
|
||||
error "Processing light client object before genesis, clock turned back?"
|
||||
quit 1
|
||||
|
||||
let res = self.storeObject(src, wallTime, obj)
|
||||
|
||||
if resFut != nil:
|
||||
resFut.complete(res)
|
|
@ -207,6 +207,11 @@ type
|
|||
is_signed_by_next_sync_committee*: bool ##\
|
||||
## Whether the signature was produced by `attested_header`'s next sync committee
|
||||
|
||||
SomeLightClientObject* =
|
||||
LightClientBootstrap |
|
||||
LightClientUpdate |
|
||||
OptimisticLightClientUpdate
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#lightclientstore
|
||||
LightClientStore* = object
|
||||
finalized_header*: BeaconBlockHeader ##\
|
||||
|
|
|
@ -45,11 +45,11 @@ func period_contains_fork_version(
|
|||
false
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#get_active_header
|
||||
func is_finality_update(update: altair.LightClientUpdate): bool =
|
||||
func is_finality_update*(update: altair.LightClientUpdate): bool =
|
||||
not update.finalized_header.isZeroMemory
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/sync-protocol.md#get_active_header
|
||||
func get_active_header(update: altair.LightClientUpdate): BeaconBlockHeader =
|
||||
func get_active_header*(update: altair.LightClientUpdate): BeaconBlockHeader =
|
||||
# The "active header" is the header that the update is trying to convince
|
||||
# us to accept. If a finalized header is present, it's the finalized
|
||||
# header, otherwise it's the attested header
|
||||
|
|
|
@ -13,6 +13,8 @@ import
|
|||
./spec/[beaconstate, eth2_ssz_serialization, eth2_merkleization, forks],
|
||||
./spec/datatypes/[phase0, altair]
|
||||
|
||||
from spec/light_client_sync import is_finality_update
|
||||
|
||||
export
|
||||
beaconstate, eth2_ssz_serialization, eth2_merkleization, forks
|
||||
|
||||
|
@ -46,3 +48,38 @@ proc dump*(dir: string, v: ForkyHashedBeaconState) =
|
|||
proc dump*(dir: string, v: SyncCommitteeMessage, validator: ValidatorPubKey) =
|
||||
logErrors:
|
||||
SSZ.saveFile(dir / &"sync-committee-msg-{v.slot}-{shortLog(validator)}.ssz", v)
|
||||
|
||||
proc dump*(dir: string, v: altair.LightClientBootstrap) =
|
||||
logErrors:
|
||||
let
|
||||
prefix = "bootstrap"
|
||||
slot = v.header.slot
|
||||
blck = shortLog(v.header.hash_tree_root())
|
||||
root = shortLog(v.hash_tree_root())
|
||||
SSZ.saveFile(
|
||||
dir / &"{prefix}-{slot}-{blck}-{root}.ssz", v)
|
||||
|
||||
proc dump*(dir: string, v: altair.LightClientUpdate) =
|
||||
logErrors:
|
||||
let
|
||||
prefix = "update"
|
||||
attestedSlot = v.attested_header.slot
|
||||
attestedBlck = shortLog(v.attested_header.hash_tree_root())
|
||||
suffix =
|
||||
if v.is_finality_update:
|
||||
"f"
|
||||
else:
|
||||
"o"
|
||||
root = shortLog(v.hash_tree_root())
|
||||
SSZ.saveFile(
|
||||
dir / &"{prefix}-{attestedSlot}-{attestedBlck}-{suffix}-{root}.ssz", v)
|
||||
|
||||
proc dump*(dir: string, v: OptimisticLightClientUpdate) =
|
||||
logErrors:
|
||||
let
|
||||
prefix = "optimistic-update"
|
||||
attestedSlot = v.attested_header.slot
|
||||
attestedBlck = shortLog(v.attested_header.hash_tree_root())
|
||||
root = shortLog(v.hash_tree_root())
|
||||
SSZ.saveFile(
|
||||
dir / &"{prefix}-{attestedSlot}-{attestedBlck}-{root}.ssz", v)
|
||||
|
|
|
@ -32,6 +32,7 @@ import # Unit test
|
|||
./test_honest_validator,
|
||||
./test_interop,
|
||||
./test_light_client,
|
||||
./test_light_client_processor,
|
||||
./test_message_signatures,
|
||||
./test_peer_pool,
|
||||
./test_spec,
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2022 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.used.}
|
||||
|
||||
import
|
||||
# Status libraries
|
||||
chronos, eth/keys,
|
||||
# Beacon chain internals
|
||||
../beacon_chain/consensus_object_pools/
|
||||
[block_clearance, block_quarantine, blockchain_dag],
|
||||
../beacon_chain/gossip_processing/light_client_processor,
|
||||
../beacon_chain/spec/[beacon_time, light_client_sync, state_transition],
|
||||
# Test utilities
|
||||
./testutil, ./testdbutil
|
||||
|
||||
suite "Light client processor" & preset():
|
||||
let
|
||||
cfg = block:
|
||||
var res = defaultRuntimeConfig
|
||||
res.ALTAIR_FORK_EPOCH = GENESIS_EPOCH + 1
|
||||
res
|
||||
|
||||
const numValidators = SLOTS_PER_EPOCH
|
||||
let
|
||||
validatorMonitor = newClone(ValidatorMonitor.init())
|
||||
dag = ChainDAGRef.init(
|
||||
cfg, makeTestDB(numValidators), validatorMonitor, {},
|
||||
serveLightClientData = true,
|
||||
importLightClientData = ImportLightClientData.OnlyNew)
|
||||
quarantine = newClone(Quarantine.init())
|
||||
taskpool = TaskPool.new()
|
||||
var verifier = BatchVerifier(rng: keys.newRng(), taskpool: taskpool)
|
||||
|
||||
var cache: StateCache
|
||||
proc addBlocks(blocks: uint64, syncCommitteeRatio: float) =
|
||||
for blck in makeTestBlocks(dag.headState, cache, blocks.int,
|
||||
attested = true, syncCommitteeRatio, cfg):
|
||||
let added =
|
||||
case blck.kind
|
||||
of BeaconBlockFork.Phase0:
|
||||
const nilCallback = OnPhase0BlockAdded(nil)
|
||||
dag.addHeadBlock(verifier, blck.phase0Data, nilCallback)
|
||||
of BeaconBlockFork.Altair:
|
||||
const nilCallback = OnAltairBlockAdded(nil)
|
||||
dag.addHeadBlock(verifier, blck.altairData, nilCallback)
|
||||
of BeaconBlockFork.Bellatrix:
|
||||
const nilCallback = OnBellatrixBlockAdded(nil)
|
||||
dag.addHeadBlock(verifier, blck.bellatrixData, nilCallback)
|
||||
doAssert added.isOk()
|
||||
dag.updateHead(added[], quarantine[])
|
||||
|
||||
addBlocks(SLOTS_PER_EPOCH, 0.75)
|
||||
let
|
||||
genesisValidatorsRoot = dag.genesisValidatorsRoot
|
||||
trustedBlockRoot = dag.head.root
|
||||
|
||||
const
|
||||
lowPeriod = 0.SyncCommitteePeriod
|
||||
lastPeriodWithSupermajority = 3.SyncCommitteePeriod
|
||||
highPeriod = 5.SyncCommitteePeriod
|
||||
for period in lowPeriod .. highPeriod:
|
||||
const numFilledEpochsPerPeriod = 3
|
||||
let slot = ((period + 1).start_epoch - numFilledEpochsPerPeriod).start_slot
|
||||
var info: ForkedEpochInfo
|
||||
doAssert process_slots(cfg, dag.headState, slot,
|
||||
cache, info, flags = {}).isOk()
|
||||
let syncCommitteeRatio =
|
||||
if period > lastPeriodWithSupermajority:
|
||||
0.25
|
||||
else:
|
||||
0.75
|
||||
addBlocks(numFilledEpochsPerPeriod * SLOTS_PER_EPOCH, syncCommitteeRatio)
|
||||
|
||||
setup:
|
||||
var time = chronos.seconds(0)
|
||||
proc getBeaconTime(): BeaconTime =
|
||||
BeaconTime(ns_since_genesis: time.nanoseconds)
|
||||
func setTimeToSlot(slot: Slot) =
|
||||
time = chronos.seconds((slot * SECONDS_PER_SLOT).int64)
|
||||
|
||||
var numDidInitializeStoreCalls = 0
|
||||
proc didInitializeStore() = inc numDidInitializeStoreCalls
|
||||
|
||||
let store = (ref Option[LightClientStore])()
|
||||
var
|
||||
processor = LightClientProcessor.new(
|
||||
false, "", "", cfg, genesisValidatorsRoot, trustedBlockRoot,
|
||||
store, getBeaconTime, didInitializeStore)
|
||||
res: Result[void, BlockError]
|
||||
|
||||
test "Standard sync" & preset():
|
||||
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||
check bootstrap.isSome
|
||||
setTimeToSlot(bootstrap.get.header.slot)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||
check:
|
||||
res.isOk
|
||||
numDidInitializeStoreCalls == 1
|
||||
|
||||
for period in lowPeriod .. lastPeriodWithSupermajority:
|
||||
let update = dag.getBestLightClientUpdateForPeriod(period)
|
||||
check update.isSome
|
||||
setTimeToSlot(update.get.attested_header.slot + 1)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isOk
|
||||
store[].isSome
|
||||
store[].get.finalized_header == update.get.finalized_header
|
||||
store[].get.optimistic_header == update.get.attested_header
|
||||
|
||||
test "Forced update" & preset():
|
||||
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||
check bootstrap.isSome
|
||||
setTimeToSlot(bootstrap.get.header.slot)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||
check:
|
||||
res.isOk
|
||||
numDidInitializeStoreCalls == 1
|
||||
|
||||
for period in lowPeriod .. lastPeriodWithSupermajority:
|
||||
let update = dag.getBestLightClientUpdateForPeriod(period)
|
||||
check update.isSome
|
||||
setTimeToSlot(update.get.attested_header.slot + 1)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isOk
|
||||
store[].isSome
|
||||
store[].get.finalized_header == update.get.finalized_header
|
||||
store[].get.optimistic_header == update.get.attested_header
|
||||
|
||||
for period in lastPeriodWithSupermajority + 1 .. highPeriod:
|
||||
let update = dag.getBestLightClientUpdateForPeriod(period)
|
||||
check update.isSome
|
||||
setTimeToSlot(update.get.attested_header.slot + 1)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isOk
|
||||
store[].isSome
|
||||
store[].get.best_valid_update.isSome
|
||||
store[].get.best_valid_update.get == update.get
|
||||
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.Duplicate
|
||||
store[].isSome
|
||||
store[].get.best_valid_update.isSome
|
||||
store[].get.best_valid_update.get == update.get
|
||||
time += chronos.minutes(15)
|
||||
|
||||
for _ in 0 ..< 150:
|
||||
time += chronos.seconds(5)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.Duplicate
|
||||
store[].isSome
|
||||
store[].get.best_valid_update.isSome
|
||||
store[].get.best_valid_update.get == update.get
|
||||
|
||||
time += chronos.minutes(15)
|
||||
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.Duplicate
|
||||
store[].isSome
|
||||
store[].get.best_valid_update.isNone
|
||||
store[].get.finalized_header == update.get.finalized_header
|
||||
|
||||
let optimisticUpdate = dag.getOptimisticLightClientUpdate()
|
||||
check optimisticUpdate.isSome
|
||||
setTimeToSlot(optimisticUpdate.get.attested_header.slot + 1)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), optimisticUpdate.get)
|
||||
if res.isOk:
|
||||
check:
|
||||
store[].isSome
|
||||
store[].get.optimistic_header == optimisticUpdate.get.attested_header
|
||||
else:
|
||||
check res.error == BlockError.Duplicate
|
||||
check numDidInitializeStoreCalls == 1
|
||||
|
||||
test "Invalid bootstrap" & preset():
|
||||
var bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||
check bootstrap.isSome
|
||||
bootstrap.get.header.slot.inc()
|
||||
setTimeToSlot(bootstrap.get.header.slot)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.Invalid
|
||||
numDidInitializeStoreCalls == 0
|
||||
|
||||
test "Duplicate bootstrap" & preset():
|
||||
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||
check bootstrap.isSome
|
||||
setTimeToSlot(bootstrap.get.header.slot)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||
check:
|
||||
res.isOk
|
||||
numDidInitializeStoreCalls == 1
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.Duplicate
|
||||
numDidInitializeStoreCalls == 1
|
||||
|
||||
test "Missing bootstrap (update)" & preset():
|
||||
let update = dag.getBestLightClientUpdateForPeriod(lowPeriod)
|
||||
check update.isSome
|
||||
setTimeToSlot(update.get.attested_header.slot + 1)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), update.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.MissingParent
|
||||
numDidInitializeStoreCalls == 0
|
||||
|
||||
test "Missing bootstrap (optimistic update)" & preset():
|
||||
let optimisticUpdate = dag.getOptimisticLightClientUpdate()
|
||||
check optimisticUpdate.isSome
|
||||
setTimeToSlot(optimisticUpdate.get.attested_header.slot + 1)
|
||||
res = processor[].storeObject(
|
||||
MsgSource.gossip, getBeaconTime(), optimisticUpdate.get)
|
||||
check:
|
||||
res.isErr
|
||||
res.error == BlockError.MissingParent
|
||||
numDidInitializeStoreCalls == 0
|
Loading…
Reference in New Issue