mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-11 14:54:12 +00:00
735c1df62f
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.
177 lines
6.2 KiB
Nim
177 lines
6.2 KiB
Nim
# 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.
|
|
|
|
# This implements the pre-release proposal of the libp2p based light client sync
|
|
# protocol. See https://github.com/ethereum/consensus-specs/pull/2802
|
|
|
|
import
|
|
std/os,
|
|
chronicles, chronicles/chronos_tools, chronos,
|
|
eth/keys,
|
|
./eth1/eth1_monitor,
|
|
./spec/beaconstate,
|
|
./sync/optimistic_sync_light_client,
|
|
"."/[light_client, nimbus_binary_common, version]
|
|
|
|
from ./consensus_object_pools/consensus_manager import runForkchoiceUpdated
|
|
from ./gossip_processing/block_processor import newExecutionPayload
|
|
|
|
programMain:
|
|
var config = makeBannerAndConfig(
|
|
"Nimbus light client " & fullVersionStr, LightClientConf)
|
|
setupLogging(config.logLevel, config.logStdout, config.logFile)
|
|
|
|
notice "Launching light client",
|
|
version = fullVersionStr, cmdParams = commandLineParams(), config
|
|
|
|
let metadata = loadEth2Network(config.eth2Network)
|
|
for node in metadata.bootstrapNodes:
|
|
config.bootstrapNodes.add node
|
|
template cfg(): auto = metadata.cfg
|
|
|
|
let
|
|
genesisState =
|
|
try:
|
|
template genesisData(): auto = metadata.genesisData
|
|
newClone(readSszForkedHashedBeaconState(
|
|
cfg, genesisData.toOpenArrayByte(genesisData.low, genesisData.high)))
|
|
except CatchableError as err:
|
|
raiseAssert "Invalid baked-in state: " & err.msg
|
|
|
|
beaconClock = BeaconClock.init(
|
|
getStateField(genesisState[], genesis_time))
|
|
getBeaconTime = beaconClock.getBeaconTimeFn()
|
|
|
|
genesis_validators_root =
|
|
getStateField(genesisState[], genesis_validators_root)
|
|
forkDigests = newClone ForkDigests.init(cfg, genesis_validators_root)
|
|
|
|
genesisBlockRoot = get_initial_beacon_block(genesisState[]).root
|
|
|
|
rng = keys.newRng()
|
|
netKeys = getRandomNetKeys(rng[])
|
|
network = createEth2Node(
|
|
rng, config, netKeys, cfg,
|
|
forkDigests, getBeaconTime, genesis_validators_root)
|
|
|
|
eth1Monitor =
|
|
if config.web3Urls.len > 0:
|
|
Eth1Monitor.init(
|
|
cfg, db = nil, getBeaconTime, config.web3Urls,
|
|
none(DepositContractSnapshot), metadata.eth1Network,
|
|
forcePolling = false,
|
|
rng[].loadJwtSecret(config, allowCreate = false))
|
|
else:
|
|
nil
|
|
|
|
optimisticProcessor = proc(signedBlock: ForkedMsgTrustedSignedBeaconBlock):
|
|
Future[void] {.async.} =
|
|
notice "New LC optimistic block",
|
|
opt = signedBlock.toBlockId(),
|
|
wallSlot = getBeaconTime().slotOrZero
|
|
withBlck(signedBlock):
|
|
when stateFork >= BeaconStateFork.Bellatrix:
|
|
if blck.message.is_execution_block:
|
|
template payload(): auto = blck.message.body.execution_payload
|
|
|
|
if eth1Monitor != nil:
|
|
await eth1Monitor.ensureDataProvider()
|
|
|
|
# engine_newPayloadV1
|
|
discard await eth1Monitor.newExecutionPayload(payload)
|
|
|
|
# engine_forkchoiceUpdatedV1
|
|
discard await eth1Monitor.runForkchoiceUpdated(
|
|
headBlockRoot = payload.block_hash,
|
|
finalizedBlockRoot = ZERO_HASH)
|
|
else: discard
|
|
return
|
|
optSync = initLCOptimisticSync(
|
|
network, getBeaconTime, optimisticProcessor,
|
|
config.safeSlotsToImportOptimistically)
|
|
|
|
lightClient = createLightClient(
|
|
network, rng, config, cfg, forkDigests, getBeaconTime,
|
|
genesis_validators_root, LightClientFinalizationMode.Optimistic)
|
|
|
|
info "Listening to incoming network requests"
|
|
network.initBeaconSync(cfg, forkDigests, genesisBlockRoot, getBeaconTime)
|
|
lightClient.installMessageValidators()
|
|
waitFor network.startListening()
|
|
waitFor network.start()
|
|
|
|
proc shouldSyncOptimistically(slot: Slot): bool =
|
|
const
|
|
# Maximum age of light client optimistic header to use optimistic sync
|
|
maxAge = 2 * SLOTS_PER_EPOCH
|
|
|
|
if eth1Monitor == nil:
|
|
false
|
|
elif getBeaconTime().slotOrZero > slot + maxAge:
|
|
false
|
|
else:
|
|
true
|
|
|
|
proc onFinalizedHeader(lightClient: LightClient) =
|
|
info "New LC finalized header",
|
|
finalized_header = shortLog(lightClient.finalizedHeader.get)
|
|
let optimisticHeader = lightClient.optimisticHeader.valueOr:
|
|
return
|
|
if not shouldSyncOptimistically(optimisticHeader.slot):
|
|
return
|
|
let finalizedHeader = lightClient.finalizedHeader.valueOr:
|
|
return
|
|
optSync.setOptimisticHeader(optimisticHeader)
|
|
optSync.setFinalizedHeader(finalizedHeader)
|
|
|
|
proc onOptimisticHeader(lightClient: LightClient) =
|
|
info "New LC optimistic header",
|
|
optimistic_header = shortLog(lightClient.optimisticHeader.get)
|
|
let optimisticHeader = lightClient.optimisticHeader.valueOr:
|
|
return
|
|
if not shouldSyncOptimistically(optimisticHeader.slot):
|
|
return
|
|
optSync.setOptimisticHeader(optimisticHeader)
|
|
|
|
lightClient.onFinalizedHeader = onFinalizedHeader
|
|
lightClient.onOptimisticHeader = onOptimisticHeader
|
|
lightClient.trustedBlockRoot = some config.trustedBlockRoot
|
|
|
|
var nextExchangeTransitionConfTime: Moment
|
|
|
|
proc onSecond(time: Moment) =
|
|
# engine_exchangeTransitionConfigurationV1
|
|
if time > nextExchangeTransitionConfTime and eth1Monitor != nil:
|
|
nextExchangeTransitionConfTime = time + chronos.minutes(1)
|
|
traceAsyncErrors eth1Monitor.exchangeTransitionConfiguration()
|
|
|
|
let wallSlot = getBeaconTime().slotOrZero()
|
|
if checkIfShouldStopAtEpoch(wallSlot, config.stopAtEpoch):
|
|
quit(0)
|
|
|
|
lightClient.updateGossipStatus(wallSlot + 1)
|
|
|
|
proc runOnSecondLoop() {.async.} =
|
|
let sleepTime = chronos.seconds(1)
|
|
while true:
|
|
let start = chronos.now(chronos.Moment)
|
|
await chronos.sleepAsync(sleepTime)
|
|
let afterSleep = chronos.now(chronos.Moment)
|
|
let sleepTime = afterSleep - start
|
|
onSecond(start)
|
|
let finished = chronos.now(chronos.Moment)
|
|
let processingTime = finished - afterSleep
|
|
trace "onSecond task completed", sleepTime, processingTime
|
|
|
|
onSecond(Moment.now())
|
|
optSync.start()
|
|
lightClient.start()
|
|
|
|
asyncSpawn runOnSecondLoop()
|
|
while true:
|
|
poll()
|