Skip to content

Commit

Permalink
add strict mode to light client processor (#3894)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
etan-status committed Jul 21, 2022
1 parent 24d8401 commit 735c1df
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 185 deletions.
22 changes: 14 additions & 8 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,20 @@ OK: 9/9 Fail: 0/9 Skip: 0/9
OK: 3/3 Fail: 0/3 Skip: 0/3
## Light client processor [Preset: mainnet]
```diff
+ Duplicate bootstrap [Preset: mainnet] OK
+ Invalid bootstrap [Preset: mainnet] OK
+ Missing bootstrap (finality update) [Preset: mainnet] OK
+ Missing bootstrap (optimistic update) [Preset: mainnet] OK
+ Missing bootstrap (update) [Preset: mainnet] OK
+ Sync [Preset: mainnet] OK
+ Duplicate bootstrap (Optimistic) [Preset: mainnet] OK
+ Duplicate bootstrap (Strict) [Preset: mainnet] OK
+ Invalid bootstrap (Optimistic) [Preset: mainnet] OK
+ Invalid bootstrap (Strict) [Preset: mainnet] OK
+ Missing bootstrap (finality update) (Optimistic) [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]
```diff
+ Correct token provided [Preset: mainnet] OK
Expand Down Expand Up @@ -580,4 +586,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
OK: 9/9 Fail: 0/9 Skip: 0/9

---TOTAL---
OK: 321/326 Fail: 0/326 Skip: 5/326
OK: 327/332 Fail: 0/332 Skip: 5/332
4 changes: 2 additions & 2 deletions beacon_chain/beacon_node_light_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ proc initLightClient*(
config.safeSlotsToImportOptimistically)

lightClient = createLightClient(
node.network, rng, config, cfg,
forkDigests, getBeaconTime, genesis_validators_root)
node.network, rng, config, cfg, forkDigests, getBeaconTime,
genesis_validators_root, LightClientFinalizationMode.Strict)

if config.lightClientEnable.get:
proc shouldSyncOptimistically(slot: Slot): bool =
Expand Down
68 changes: 48 additions & 20 deletions beacon_chain/gossip_processing/light_client_processor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,33 @@ type
VoidCallback* =
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
## This manages the processing of received light client objects
##
Expand All @@ -48,13 +75,6 @@ type
##
## 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 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
# ----------------------------------------------------------------
Expand All @@ -72,9 +92,13 @@ type
cfg: RuntimeConfig
genesis_validators_root: 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
case finalizationMode: LightClientFinalizationMode
of LightClientFinalizationMode.Strict:
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

Expand All @@ -94,6 +118,7 @@ proc new*(
dumpDirInvalid, dumpDirIncoming: string,
cfg: RuntimeConfig,
genesis_validators_root: Eth2Digest,
finalizationMode: LightClientFinalizationMode,
store: ref Option[LightClientStore],
getBeaconTime: GetBeaconTimeFn,
getTrustedBlockRoot: GetTrustedBlockRootCallback,
Expand All @@ -112,8 +137,8 @@ proc new*(
onFinalizedHeader: onFinalizedHeader,
onOptimisticHeader: onOptimisticHeader,
cfg: cfg,
genesis_validators_root: genesis_validators_root
)
genesis_validators_root: genesis_validators_root,
finalizationMode: finalizationMode)

# Storage
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -146,6 +171,7 @@ proc tryForceUpdate(
store = self.store

if store[].isSome:
doAssert self.finalizationMode == LightClientFinalizationMode.Optimistic
case store[].get.try_light_client_store_force_update(wallSlot)
of NoUpdate:
discard
Expand Down Expand Up @@ -192,11 +218,12 @@ proc processObject(

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 / improved finality
# is available. In that case, we will wait for a better update that once
# again fulfills those conditions. If none is received within reasonable
# time, the light client store is force-updated to `best_valid_update`.
if self.finalizationMode == LightClientFinalizationMode.Optimistic and
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:
Expand All @@ -215,9 +242,10 @@ proc processObject(
return res

when obj is altair.LightClientBootstrap | altair.LightClientUpdate:
self.lastProgressTick = wallTime
self.lastDuplicateTick = wallTime + duplicateCountDelay
self.numDuplicatesSinceProgress = 0
if self.finalizationMode == LightClientFinalizationMode.Optimistic:
self.lastProgressTick = wallTime
self.lastDuplicateTick = wallTime + duplicateCountDelay
self.numDuplicatesSinceProgress = 0

res

Expand Down
17 changes: 10 additions & 7 deletions beacon_chain/light_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import
./sync/light_client_manager,
"."/[beacon_clock, conf_light_client]

export eth2_network, conf_light_client
export LightClientFinalizationMode, eth2_network, conf_light_client

logScope: topics = "lightcl"

Expand Down Expand Up @@ -60,7 +60,8 @@ proc createLightClient(
cfg: RuntimeConfig,
forkDigests: ref ForkDigests,
getBeaconTime: GetBeaconTimeFn,
genesis_validators_root: Eth2Digest
genesis_validators_root: Eth2Digest,
finalizationMode: LightClientFinalizationMode
): LightClient =
let lightClient = LightClient(
network: network,
Expand All @@ -85,7 +86,7 @@ proc createLightClient(

lightClient.processor = LightClientProcessor.new(
dumpEnabled, dumpDirInvalid, dumpDirIncoming,
cfg, genesis_validators_root,
cfg, genesis_validators_root, finalizationMode,
lightClient.store, getBeaconTime, getTrustedBlockRoot,
onStoreInitialized, onFinalizedHeader, onOptimisticHeader)

Expand Down Expand Up @@ -141,12 +142,13 @@ proc createLightClient*(
cfg: RuntimeConfig,
forkDigests: ref ForkDigests,
getBeaconTime: GetBeaconTimeFn,
genesis_validators_root: Eth2Digest
genesis_validators_root: Eth2Digest,
finalizationMode: LightClientFinalizationMode
): LightClient =
createLightClient(
network, rng,
config.dumpEnabled, config.dumpDirInvalid, config.dumpDirIncoming,
cfg, forkDigests, getBeaconTime, genesis_validators_root)
cfg, forkDigests, getBeaconTime, genesis_validators_root, finalizationMode)

proc createLightClient*(
network: Eth2Node,
Expand All @@ -155,12 +157,13 @@ proc createLightClient*(
cfg: RuntimeConfig,
forkDigests: ref ForkDigests,
getBeaconTime: GetBeaconTimeFn,
genesis_validators_root: Eth2Digest
genesis_validators_root: Eth2Digest,
finalizationMode: LightClientFinalizationMode
): LightClient =
createLightClient(
network, rng,
dumpEnabled = false, dumpDirInvalid = ".", dumpDirIncoming = ".",
cfg, forkDigests, getBeaconTime, genesis_validators_root)
cfg, forkDigests, getBeaconTime, genesis_validators_root, finalizationMode)

proc start*(lightClient: LightClient) =
notice "Starting light client",
Expand Down
4 changes: 2 additions & 2 deletions beacon_chain/nimbus_light_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ programMain:
config.safeSlotsToImportOptimistically)

lightClient = createLightClient(
network, rng, config, cfg,
forkDigests, getBeaconTime, genesis_validators_root)
network, rng, config, cfg, forkDigests, getBeaconTime,
genesis_validators_root, LightClientFinalizationMode.Optimistic)

info "Listening to incoming network requests"
network.initBeaconSync(cfg, forkDigests, genesisBlockRoot, getBeaconTime)
Expand Down
Loading

0 comments on commit 735c1df

Please sign in to comment.