add strict mode to light client processor (#3894)
The light client sync protocol employs heuristics to ensure it does not become stuck during non-finality or low sync committee participation. These can enable use cases that prefer availability of recent data over security. For our syncing use case, though, security is preferred. An option is added to light client processor to configure this tradeoff.
This commit is contained in:
parent
24d8401054
commit
735c1df62f
|
@ -287,14 +287,20 @@ OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
OK: 3/3 Fail: 0/3 Skip: 0/3
|
OK: 3/3 Fail: 0/3 Skip: 0/3
|
||||||
## Light client processor [Preset: mainnet]
|
## Light client processor [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Duplicate bootstrap [Preset: mainnet] OK
|
+ Duplicate bootstrap (Optimistic) [Preset: mainnet] OK
|
||||||
+ Invalid bootstrap [Preset: mainnet] OK
|
+ Duplicate bootstrap (Strict) [Preset: mainnet] OK
|
||||||
+ Missing bootstrap (finality update) [Preset: mainnet] OK
|
+ Invalid bootstrap (Optimistic) [Preset: mainnet] OK
|
||||||
+ Missing bootstrap (optimistic update) [Preset: mainnet] OK
|
+ Invalid bootstrap (Strict) [Preset: mainnet] OK
|
||||||
+ Missing bootstrap (update) [Preset: mainnet] OK
|
+ Missing bootstrap (finality update) (Optimistic) [Preset: mainnet] OK
|
||||||
+ Sync [Preset: mainnet] OK
|
+ Missing bootstrap (finality update) (Strict) [Preset: mainnet] OK
|
||||||
|
+ Missing bootstrap (optimistic update) (Optimistic) [Preset: mainnet] OK
|
||||||
|
+ Missing bootstrap (optimistic update) (Strict) [Preset: mainnet] OK
|
||||||
|
+ Missing bootstrap (update) (Optimistic) [Preset: mainnet] OK
|
||||||
|
+ Missing bootstrap (update) (Strict) [Preset: mainnet] OK
|
||||||
|
+ Sync (Optimistic) [Preset: mainnet] OK
|
||||||
|
+ Sync (Strict) [Preset: mainnet] OK
|
||||||
```
|
```
|
||||||
OK: 6/6 Fail: 0/6 Skip: 0/6
|
OK: 12/12 Fail: 0/12 Skip: 0/12
|
||||||
## ListKeys requests [Preset: mainnet]
|
## ListKeys requests [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Correct token provided [Preset: mainnet] OK
|
+ Correct token provided [Preset: mainnet] OK
|
||||||
|
@ -580,4 +586,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 321/326 Fail: 0/326 Skip: 5/326
|
OK: 327/332 Fail: 0/332 Skip: 5/332
|
||||||
|
|
|
@ -42,8 +42,8 @@ proc initLightClient*(
|
||||||
config.safeSlotsToImportOptimistically)
|
config.safeSlotsToImportOptimistically)
|
||||||
|
|
||||||
lightClient = createLightClient(
|
lightClient = createLightClient(
|
||||||
node.network, rng, config, cfg,
|
node.network, rng, config, cfg, forkDigests, getBeaconTime,
|
||||||
forkDigests, getBeaconTime, genesis_validators_root)
|
genesis_validators_root, LightClientFinalizationMode.Strict)
|
||||||
|
|
||||||
if config.lightClientEnable.get:
|
if config.lightClientEnable.get:
|
||||||
proc shouldSyncOptimistically(slot: Slot): bool =
|
proc shouldSyncOptimistically(slot: Slot): bool =
|
||||||
|
|
|
@ -33,6 +33,33 @@ type
|
||||||
VoidCallback* =
|
VoidCallback* =
|
||||||
proc() {.gcsafe, raises: [Defect].}
|
proc() {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
|
LightClientFinalizationMode* {.pure.} = enum
|
||||||
|
Strict
|
||||||
|
## Only finalize light client data that:
|
||||||
|
## - has been signed by a supermajority (2/3) of the sync committee
|
||||||
|
## - has a valid finality proof
|
||||||
|
##
|
||||||
|
## Optimizes for security, but may become stuck if there is any of:
|
||||||
|
## - non-finality for an entire sync committee period
|
||||||
|
## - low sync committee participation for an entire sync committee period
|
||||||
|
## Such periods need to be covered by an out-of-band syncing mechanism.
|
||||||
|
##
|
||||||
|
## Note that a compromised supermajority of the sync committee is able to
|
||||||
|
## sign arbitrary light client data, even after being slashed. The light
|
||||||
|
## client cannot validate the slashing status of sync committee members.
|
||||||
|
## Likewise, voluntarily exited validators may sign bad light client data
|
||||||
|
## for the sync committee periods in which they used to be selected.
|
||||||
|
|
||||||
|
Optimistic
|
||||||
|
## Attempt to finalize light client data not satisfying strict conditions
|
||||||
|
## if there is no progress for an extended period of time and if there are
|
||||||
|
## repeated messages indicating that it is the best available data on the
|
||||||
|
## network for the affected time period.
|
||||||
|
##
|
||||||
|
## Optimizes for availability of recent data, but may end up on incorrect
|
||||||
|
## forks if run in a hostile network environment (no honest peers), or if
|
||||||
|
## the low sync committee participation is being exploited by bad actors.
|
||||||
|
|
||||||
LightClientProcessor* = object
|
LightClientProcessor* = object
|
||||||
## This manages the processing of received light client objects
|
## This manages the processing of received light client objects
|
||||||
##
|
##
|
||||||
|
@ -48,13 +75,6 @@ type
|
||||||
##
|
##
|
||||||
## are then verified and added to:
|
## are then verified and added to:
|
||||||
## - `LightClientStore`
|
## - `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 improves finality.
|
|
||||||
## 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
|
# Config
|
||||||
# ----------------------------------------------------------------
|
# ----------------------------------------------------------------
|
||||||
|
@ -72,9 +92,13 @@ type
|
||||||
cfg: RuntimeConfig
|
cfg: RuntimeConfig
|
||||||
genesis_validators_root: Eth2Digest
|
genesis_validators_root: Eth2Digest
|
||||||
|
|
||||||
lastProgressTick: BeaconTime # Moment when last update made progress
|
case finalizationMode: LightClientFinalizationMode
|
||||||
lastDuplicateTick: BeaconTime # Moment when last duplicate update received
|
of LightClientFinalizationMode.Strict:
|
||||||
numDuplicatesSinceProgress: int # Number of duplicates since last progress
|
discard
|
||||||
|
of LightClientFinalizationMode.Optimistic:
|
||||||
|
lastProgressTick: BeaconTime # Moment when last update made progress
|
||||||
|
lastDuplicateTick: BeaconTime # Moment when last duplicate update received
|
||||||
|
numDuplicatesSinceProgress: int # Number of duplicates since last progress
|
||||||
|
|
||||||
latestFinalityUpdate: altair.LightClientOptimisticUpdate
|
latestFinalityUpdate: altair.LightClientOptimisticUpdate
|
||||||
|
|
||||||
|
@ -94,6 +118,7 @@ proc new*(
|
||||||
dumpDirInvalid, dumpDirIncoming: string,
|
dumpDirInvalid, dumpDirIncoming: string,
|
||||||
cfg: RuntimeConfig,
|
cfg: RuntimeConfig,
|
||||||
genesis_validators_root: Eth2Digest,
|
genesis_validators_root: Eth2Digest,
|
||||||
|
finalizationMode: LightClientFinalizationMode,
|
||||||
store: ref Option[LightClientStore],
|
store: ref Option[LightClientStore],
|
||||||
getBeaconTime: GetBeaconTimeFn,
|
getBeaconTime: GetBeaconTimeFn,
|
||||||
getTrustedBlockRoot: GetTrustedBlockRootCallback,
|
getTrustedBlockRoot: GetTrustedBlockRootCallback,
|
||||||
|
@ -112,8 +137,8 @@ proc new*(
|
||||||
onFinalizedHeader: onFinalizedHeader,
|
onFinalizedHeader: onFinalizedHeader,
|
||||||
onOptimisticHeader: onOptimisticHeader,
|
onOptimisticHeader: onOptimisticHeader,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
genesis_validators_root: genesis_validators_root
|
genesis_validators_root: genesis_validators_root,
|
||||||
)
|
finalizationMode: finalizationMode)
|
||||||
|
|
||||||
# Storage
|
# Storage
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -146,6 +171,7 @@ proc tryForceUpdate(
|
||||||
store = self.store
|
store = self.store
|
||||||
|
|
||||||
if store[].isSome:
|
if store[].isSome:
|
||||||
|
doAssert self.finalizationMode == LightClientFinalizationMode.Optimistic
|
||||||
case store[].get.try_light_client_store_force_update(wallSlot)
|
case store[].get.try_light_client_store_force_update(wallSlot)
|
||||||
of NoUpdate:
|
of NoUpdate:
|
||||||
discard
|
discard
|
||||||
|
@ -192,11 +218,12 @@ proc processObject(
|
||||||
|
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
when obj is altair.LightClientUpdate:
|
when obj is altair.LightClientUpdate:
|
||||||
if store[].isSome and store[].get.best_valid_update.isSome:
|
if self.finalizationMode == LightClientFinalizationMode.Optimistic and
|
||||||
# `best_valid_update` gets set when no supermajority / improved finality
|
store[].isSome and store[].get.best_valid_update.isSome:
|
||||||
# is available. In that case, we will wait for a better update that once
|
# `best_valid_update` gets set when no supermajority / finality proof
|
||||||
# again fulfills those conditions. If none is received within reasonable
|
# is available. In that case, we will wait for a better update.
|
||||||
# time, the light client store is force-updated to `best_valid_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
|
case res.error
|
||||||
of BlockError.Duplicate:
|
of BlockError.Duplicate:
|
||||||
if wallTime >= self.lastDuplicateTick + duplicateRateLimit:
|
if wallTime >= self.lastDuplicateTick + duplicateRateLimit:
|
||||||
|
@ -215,9 +242,10 @@ proc processObject(
|
||||||
return res
|
return res
|
||||||
|
|
||||||
when obj is altair.LightClientBootstrap | altair.LightClientUpdate:
|
when obj is altair.LightClientBootstrap | altair.LightClientUpdate:
|
||||||
self.lastProgressTick = wallTime
|
if self.finalizationMode == LightClientFinalizationMode.Optimistic:
|
||||||
self.lastDuplicateTick = wallTime + duplicateCountDelay
|
self.lastProgressTick = wallTime
|
||||||
self.numDuplicatesSinceProgress = 0
|
self.lastDuplicateTick = wallTime + duplicateCountDelay
|
||||||
|
self.numDuplicatesSinceProgress = 0
|
||||||
|
|
||||||
res
|
res
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import
|
||||||
./sync/light_client_manager,
|
./sync/light_client_manager,
|
||||||
"."/[beacon_clock, conf_light_client]
|
"."/[beacon_clock, conf_light_client]
|
||||||
|
|
||||||
export eth2_network, conf_light_client
|
export LightClientFinalizationMode, eth2_network, conf_light_client
|
||||||
|
|
||||||
logScope: topics = "lightcl"
|
logScope: topics = "lightcl"
|
||||||
|
|
||||||
|
@ -60,7 +60,8 @@ proc createLightClient(
|
||||||
cfg: RuntimeConfig,
|
cfg: RuntimeConfig,
|
||||||
forkDigests: ref ForkDigests,
|
forkDigests: ref ForkDigests,
|
||||||
getBeaconTime: GetBeaconTimeFn,
|
getBeaconTime: GetBeaconTimeFn,
|
||||||
genesis_validators_root: Eth2Digest
|
genesis_validators_root: Eth2Digest,
|
||||||
|
finalizationMode: LightClientFinalizationMode
|
||||||
): LightClient =
|
): LightClient =
|
||||||
let lightClient = LightClient(
|
let lightClient = LightClient(
|
||||||
network: network,
|
network: network,
|
||||||
|
@ -85,7 +86,7 @@ proc createLightClient(
|
||||||
|
|
||||||
lightClient.processor = LightClientProcessor.new(
|
lightClient.processor = LightClientProcessor.new(
|
||||||
dumpEnabled, dumpDirInvalid, dumpDirIncoming,
|
dumpEnabled, dumpDirInvalid, dumpDirIncoming,
|
||||||
cfg, genesis_validators_root,
|
cfg, genesis_validators_root, finalizationMode,
|
||||||
lightClient.store, getBeaconTime, getTrustedBlockRoot,
|
lightClient.store, getBeaconTime, getTrustedBlockRoot,
|
||||||
onStoreInitialized, onFinalizedHeader, onOptimisticHeader)
|
onStoreInitialized, onFinalizedHeader, onOptimisticHeader)
|
||||||
|
|
||||||
|
@ -141,12 +142,13 @@ proc createLightClient*(
|
||||||
cfg: RuntimeConfig,
|
cfg: RuntimeConfig,
|
||||||
forkDigests: ref ForkDigests,
|
forkDigests: ref ForkDigests,
|
||||||
getBeaconTime: GetBeaconTimeFn,
|
getBeaconTime: GetBeaconTimeFn,
|
||||||
genesis_validators_root: Eth2Digest
|
genesis_validators_root: Eth2Digest,
|
||||||
|
finalizationMode: LightClientFinalizationMode
|
||||||
): LightClient =
|
): LightClient =
|
||||||
createLightClient(
|
createLightClient(
|
||||||
network, rng,
|
network, rng,
|
||||||
config.dumpEnabled, config.dumpDirInvalid, config.dumpDirIncoming,
|
config.dumpEnabled, config.dumpDirInvalid, config.dumpDirIncoming,
|
||||||
cfg, forkDigests, getBeaconTime, genesis_validators_root)
|
cfg, forkDigests, getBeaconTime, genesis_validators_root, finalizationMode)
|
||||||
|
|
||||||
proc createLightClient*(
|
proc createLightClient*(
|
||||||
network: Eth2Node,
|
network: Eth2Node,
|
||||||
|
@ -155,12 +157,13 @@ proc createLightClient*(
|
||||||
cfg: RuntimeConfig,
|
cfg: RuntimeConfig,
|
||||||
forkDigests: ref ForkDigests,
|
forkDigests: ref ForkDigests,
|
||||||
getBeaconTime: GetBeaconTimeFn,
|
getBeaconTime: GetBeaconTimeFn,
|
||||||
genesis_validators_root: Eth2Digest
|
genesis_validators_root: Eth2Digest,
|
||||||
|
finalizationMode: LightClientFinalizationMode
|
||||||
): LightClient =
|
): LightClient =
|
||||||
createLightClient(
|
createLightClient(
|
||||||
network, rng,
|
network, rng,
|
||||||
dumpEnabled = false, dumpDirInvalid = ".", dumpDirIncoming = ".",
|
dumpEnabled = false, dumpDirInvalid = ".", dumpDirIncoming = ".",
|
||||||
cfg, forkDigests, getBeaconTime, genesis_validators_root)
|
cfg, forkDigests, getBeaconTime, genesis_validators_root, finalizationMode)
|
||||||
|
|
||||||
proc start*(lightClient: LightClient) =
|
proc start*(lightClient: LightClient) =
|
||||||
notice "Starting light client",
|
notice "Starting light client",
|
||||||
|
|
|
@ -95,8 +95,8 @@ programMain:
|
||||||
config.safeSlotsToImportOptimistically)
|
config.safeSlotsToImportOptimistically)
|
||||||
|
|
||||||
lightClient = createLightClient(
|
lightClient = createLightClient(
|
||||||
network, rng, config, cfg,
|
network, rng, config, cfg, forkDigests, getBeaconTime,
|
||||||
forkDigests, getBeaconTime, genesis_validators_root)
|
genesis_validators_root, LightClientFinalizationMode.Optimistic)
|
||||||
|
|
||||||
info "Listening to incoming network requests"
|
info "Listening to incoming network requests"
|
||||||
network.initBeaconSync(cfg, forkDigests, genesisBlockRoot, getBeaconTime)
|
network.initBeaconSync(cfg, forkDigests, genesisBlockRoot, getBeaconTime)
|
||||||
|
|
|
@ -79,166 +79,225 @@ suite "Light client processor" & preset():
|
||||||
0.82
|
0.82
|
||||||
addBlocks(numFilledEpochsPerPeriod * SLOTS_PER_EPOCH, syncCommitteeRatio)
|
addBlocks(numFilledEpochsPerPeriod * SLOTS_PER_EPOCH, syncCommitteeRatio)
|
||||||
|
|
||||||
setup:
|
for finalizationMode in LightClientFinalizationMode:
|
||||||
var time = chronos.seconds(0)
|
let testNameSuffix = " (" & $finalizationMode & ")" & preset()
|
||||||
proc getBeaconTime(): BeaconTime =
|
|
||||||
BeaconTime(ns_since_genesis: time.nanoseconds)
|
|
||||||
func setTimeToSlot(slot: Slot) =
|
|
||||||
time = chronos.seconds((slot * SECONDS_PER_SLOT).int64)
|
|
||||||
|
|
||||||
var numOnStoreInitializedCalls = 0
|
setup:
|
||||||
proc onStoreInitialized() = inc numOnStoreInitializedCalls
|
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)
|
||||||
|
|
||||||
let store = (ref Option[LightClientStore])()
|
var numOnStoreInitializedCalls = 0
|
||||||
var
|
func onStoreInitialized() = inc numOnStoreInitializedCalls
|
||||||
processor = LightClientProcessor.new(
|
|
||||||
false, "", "", cfg, genesis_validators_root,
|
|
||||||
store, getBeaconTime, getTrustedBlockRoot, onStoreInitialized)
|
|
||||||
res: Result[bool, BlockError]
|
|
||||||
|
|
||||||
test "Sync" & preset():
|
let store = (ref Option[LightClientStore])()
|
||||||
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
var
|
||||||
check bootstrap.isOk
|
processor = LightClientProcessor.new(
|
||||||
setTimeToSlot(bootstrap.get.header.slot)
|
false, "", "", cfg, genesis_validators_root, finalizationMode,
|
||||||
res = processor[].storeObject(
|
store, getBeaconTime, getTrustedBlockRoot, onStoreInitialized)
|
||||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
res: Result[bool, BlockError]
|
||||||
check:
|
|
||||||
res.isOk
|
|
||||||
numOnStoreInitializedCalls == 1
|
|
||||||
|
|
||||||
for period in lowPeriod .. lastPeriodWithSupermajority:
|
test "Sync" & testNameSuffix:
|
||||||
let update = dag.getLightClientUpdateForPeriod(period)
|
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||||
check update.isSome
|
check bootstrap.isOk
|
||||||
setTimeToSlot(update.get.signature_slot)
|
setTimeToSlot(bootstrap.get.header.slot)
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), update.get)
|
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||||
check:
|
check:
|
||||||
res.isOk
|
res.isOk
|
||||||
store[].isSome
|
numOnStoreInitializedCalls == 1
|
||||||
if update.get.finalized_header.slot > bootstrap.get.header.slot:
|
|
||||||
store[].get.finalized_header == update.get.finalized_header
|
|
||||||
else:
|
|
||||||
store[].get.finalized_header == bootstrap.get.header
|
|
||||||
store[].get.optimistic_header == update.get.attested_header
|
|
||||||
|
|
||||||
for period in lastPeriodWithSupermajority + 1 .. highPeriod:
|
# Reduce stack size by making this a `proc`
|
||||||
let update = dag.getLightClientUpdateForPeriod(period)
|
proc applyPeriodWithSupermajority(period: SyncCommitteePeriod) =
|
||||||
check update.isSome
|
let update = dag.getLightClientUpdateForPeriod(period)
|
||||||
setTimeToSlot(update.get.signature_slot)
|
check update.isSome
|
||||||
|
setTimeToSlot(update.get.signature_slot)
|
||||||
for i in 0 ..< 2:
|
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), update.get)
|
MsgSource.gossip, getBeaconTime(), update.get)
|
||||||
check:
|
check:
|
||||||
res.isOk
|
res.isOk
|
||||||
store[].isSome
|
store[].isSome
|
||||||
store[].get.best_valid_update.isSome
|
if update.get.finalized_header.slot > bootstrap.get.header.slot:
|
||||||
store[].get.best_valid_update.get == update.get
|
store[].get.finalized_header == update.get.finalized_header
|
||||||
|
else:
|
||||||
|
store[].get.finalized_header == bootstrap.get.header
|
||||||
|
store[].get.optimistic_header == update.get.attested_header
|
||||||
|
|
||||||
proc applyDuplicate() = # Reduce stack size by making this a `proc`
|
for period in lowPeriod .. lastPeriodWithSupermajority:
|
||||||
|
applyPeriodWithSupermajority(period)
|
||||||
|
|
||||||
|
# Reduce stack size by making this a `proc`
|
||||||
|
proc applyPeriodWithoutSupermajority(period: SyncCommitteePeriod) =
|
||||||
|
let update = dag.getLightClientUpdateForPeriod(period)
|
||||||
|
check update.isSome
|
||||||
|
setTimeToSlot(update.get.signature_slot)
|
||||||
|
|
||||||
|
for i in 0 ..< 2:
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), update.get)
|
MsgSource.gossip, getBeaconTime(), update.get)
|
||||||
check:
|
if finalizationMode == LightClientFinalizationMode.Optimistic or
|
||||||
res.isErr
|
period == lastPeriodWithSupermajority + 1:
|
||||||
res.error == BlockError.Duplicate
|
if finalizationMode == LightClientFinalizationMode.Optimistic or
|
||||||
store[].isSome
|
i == 0:
|
||||||
store[].get.best_valid_update.isSome
|
check:
|
||||||
store[].get.best_valid_update.get == update.get
|
res.isOk
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get == update.get
|
||||||
|
else:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.Duplicate
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get == update.get
|
||||||
|
else:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.MissingParent
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get != update.get
|
||||||
|
|
||||||
|
proc applyDuplicate() = # Reduce stack size by making this a `proc`
|
||||||
|
res = processor[].storeObject(
|
||||||
|
MsgSource.gossip, getBeaconTime(), update.get)
|
||||||
|
if finalizationMode == LightClientFinalizationMode.Optimistic or
|
||||||
|
period == lastPeriodWithSupermajority + 1:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.Duplicate
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get == update.get
|
||||||
|
else:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.MissingParent
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get != update.get
|
||||||
|
|
||||||
applyDuplicate()
|
|
||||||
time += chronos.minutes(15)
|
|
||||||
for _ in 0 ..< 150:
|
|
||||||
applyDuplicate()
|
applyDuplicate()
|
||||||
time += chronos.seconds(5)
|
time += chronos.minutes(15)
|
||||||
time += chronos.minutes(15)
|
for _ in 0 ..< 150:
|
||||||
|
applyDuplicate()
|
||||||
|
time += chronos.seconds(5)
|
||||||
|
time += chronos.minutes(15)
|
||||||
|
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), update.get)
|
MsgSource.gossip, getBeaconTime(), update.get)
|
||||||
|
if finalizationMode == LightClientFinalizationMode.Optimistic:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.Duplicate
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isNone
|
||||||
|
if store[].get.finalized_header == update.get.attested_header:
|
||||||
|
break
|
||||||
|
check store[].get.finalized_header == update.get.finalized_header
|
||||||
|
elif period == lastPeriodWithSupermajority + 1:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.Duplicate
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get == update.get
|
||||||
|
else:
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.MissingParent
|
||||||
|
store[].isSome
|
||||||
|
store[].get.best_valid_update.isSome
|
||||||
|
store[].get.best_valid_update.get != update.get
|
||||||
|
if finalizationMode == LightClientFinalizationMode.Optimistic:
|
||||||
|
check store[].get.finalized_header == update.get.attested_header
|
||||||
|
else:
|
||||||
|
check store[].get.finalized_header != update.get.attested_header
|
||||||
|
|
||||||
|
for period in lastPeriodWithSupermajority + 1 .. highPeriod:
|
||||||
|
applyPeriodWithoutSupermajority(period)
|
||||||
|
|
||||||
|
let
|
||||||
|
previousFinalized = store[].get.finalized_header
|
||||||
|
finalityUpdate = dag.getLightClientFinalityUpdate()
|
||||||
|
check finalityUpdate.isSome
|
||||||
|
setTimeToSlot(finalityUpdate.get.signature_slot)
|
||||||
|
res = processor[].storeObject(
|
||||||
|
MsgSource.gossip, getBeaconTime(), finalityUpdate.get)
|
||||||
|
if res.isOk:
|
||||||
check:
|
check:
|
||||||
res.isErr
|
finalizationMode == LightClientFinalizationMode.Optimistic
|
||||||
res.error == BlockError.Duplicate
|
|
||||||
store[].isSome
|
store[].isSome
|
||||||
store[].get.best_valid_update.isNone
|
store[].get.finalized_header == previousFinalized
|
||||||
if store[].get.finalized_header == update.get.attested_header:
|
store[].get.best_valid_update.isSome
|
||||||
break
|
store[].get.best_valid_update.get.matches(finalityUpdate.get)
|
||||||
check store[].get.finalized_header == update.get.finalized_header
|
store[].get.optimistic_header == finalityUpdate.get.attested_header
|
||||||
check store[].get.finalized_header == update.get.attested_header
|
elif finalizationMode == LightClientFinalizationMode.Optimistic:
|
||||||
|
check res.error == BlockError.Duplicate
|
||||||
|
else:
|
||||||
|
check res.error == BlockError.MissingParent
|
||||||
|
check numOnStoreInitializedCalls == 1
|
||||||
|
|
||||||
let
|
test "Invalid bootstrap" & testNameSuffix:
|
||||||
previousFinalized = store[].get.finalized_header
|
var bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||||
finalityUpdate = dag.getLightClientFinalityUpdate()
|
check bootstrap.isOk
|
||||||
check finalityUpdate.isSome
|
bootstrap.get.header.slot.inc()
|
||||||
setTimeToSlot(finalityUpdate.get.signature_slot)
|
setTimeToSlot(bootstrap.get.header.slot)
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), finalityUpdate.get)
|
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||||
if res.isOk:
|
|
||||||
check:
|
check:
|
||||||
store[].isSome
|
res.isErr
|
||||||
store[].get.finalized_header == previousFinalized
|
res.error == BlockError.Invalid
|
||||||
store[].get.best_valid_update.isSome
|
numOnStoreInitializedCalls == 0
|
||||||
store[].get.best_valid_update.get.matches(finalityUpdate.get)
|
|
||||||
store[].get.optimistic_header == finalityUpdate.get.attested_header
|
|
||||||
else:
|
|
||||||
check res.error == BlockError.Duplicate
|
|
||||||
check numOnStoreInitializedCalls == 1
|
|
||||||
|
|
||||||
test "Invalid bootstrap" & preset():
|
test "Duplicate bootstrap" & testNameSuffix:
|
||||||
var bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
||||||
check bootstrap.isOk
|
check bootstrap.isOk
|
||||||
bootstrap.get.header.slot.inc()
|
setTimeToSlot(bootstrap.get.header.slot)
|
||||||
setTimeToSlot(bootstrap.get.header.slot)
|
res = processor[].storeObject(
|
||||||
res = processor[].storeObject(
|
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
check:
|
||||||
check:
|
res.isOk
|
||||||
res.isErr
|
numOnStoreInitializedCalls == 1
|
||||||
res.error == BlockError.Invalid
|
res = processor[].storeObject(
|
||||||
numOnStoreInitializedCalls == 0
|
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
||||||
|
check:
|
||||||
|
res.isErr
|
||||||
|
res.error == BlockError.Duplicate
|
||||||
|
numOnStoreInitializedCalls == 1
|
||||||
|
|
||||||
test "Duplicate bootstrap" & preset():
|
test "Missing bootstrap (update)" & testNameSuffix:
|
||||||
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
|
let update = dag.getLightClientUpdateForPeriod(lowPeriod)
|
||||||
check bootstrap.isOk
|
check update.isSome
|
||||||
setTimeToSlot(bootstrap.get.header.slot)
|
setTimeToSlot(update.get.signature_slot)
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
MsgSource.gossip, getBeaconTime(), update.get)
|
||||||
check:
|
check:
|
||||||
res.isOk
|
res.isErr
|
||||||
numOnStoreInitializedCalls == 1
|
res.error == BlockError.MissingParent
|
||||||
res = processor[].storeObject(
|
numOnStoreInitializedCalls == 0
|
||||||
MsgSource.gossip, getBeaconTime(), bootstrap.get)
|
|
||||||
check:
|
|
||||||
res.isErr
|
|
||||||
res.error == BlockError.Duplicate
|
|
||||||
numOnStoreInitializedCalls == 1
|
|
||||||
|
|
||||||
test "Missing bootstrap (update)" & preset():
|
test "Missing bootstrap (finality update)" & testNameSuffix:
|
||||||
let update = dag.getLightClientUpdateForPeriod(lowPeriod)
|
let finalityUpdate = dag.getLightClientFinalityUpdate()
|
||||||
check update.isSome
|
check finalityUpdate.isSome
|
||||||
setTimeToSlot(update.get.signature_slot)
|
setTimeToSlot(finalityUpdate.get.signature_slot)
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), update.get)
|
MsgSource.gossip, getBeaconTime(), finalityUpdate.get)
|
||||||
check:
|
check:
|
||||||
res.isErr
|
res.isErr
|
||||||
res.error == BlockError.MissingParent
|
res.error == BlockError.MissingParent
|
||||||
numOnStoreInitializedCalls == 0
|
numOnStoreInitializedCalls == 0
|
||||||
|
|
||||||
test "Missing bootstrap (finality update)" & preset():
|
test "Missing bootstrap (optimistic update)" & testNameSuffix:
|
||||||
let finalityUpdate = dag.getLightClientFinalityUpdate()
|
let optimisticUpdate = dag.getLightClientOptimisticUpdate()
|
||||||
check finalityUpdate.isSome
|
check optimisticUpdate.isSome
|
||||||
setTimeToSlot(finalityUpdate.get.signature_slot)
|
setTimeToSlot(optimisticUpdate.get.signature_slot)
|
||||||
res = processor[].storeObject(
|
res = processor[].storeObject(
|
||||||
MsgSource.gossip, getBeaconTime(), finalityUpdate.get)
|
MsgSource.gossip, getBeaconTime(), optimisticUpdate.get)
|
||||||
check:
|
check:
|
||||||
res.isErr
|
res.isErr
|
||||||
res.error == BlockError.MissingParent
|
res.error == BlockError.MissingParent
|
||||||
numOnStoreInitializedCalls == 0
|
numOnStoreInitializedCalls == 0
|
||||||
|
|
||||||
test "Missing bootstrap (optimistic update)" & preset():
|
|
||||||
let optimisticUpdate = dag.getLightClientOptimisticUpdate()
|
|
||||||
check optimisticUpdate.isSome
|
|
||||||
setTimeToSlot(optimisticUpdate.get.signature_slot)
|
|
||||||
res = processor[].storeObject(
|
|
||||||
MsgSource.gossip, getBeaconTime(), optimisticUpdate.get)
|
|
||||||
check:
|
|
||||||
res.isErr
|
|
||||||
res.error == BlockError.MissingParent
|
|
||||||
numOnStoreInitializedCalls == 0
|
|
||||||
|
|
Loading…
Reference in New Issue