179 lines
6.3 KiB
Nim
179 lines
6.3 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:
|
|
let res = Eth1Monitor.init(
|
|
cfg, db = nil, getBeaconTime, config.web3Urls,
|
|
none(DepositContractSnapshot), metadata.eth1Network,
|
|
forcePolling = false,
|
|
rng[].loadJwtSecret(config, allowCreate = false))
|
|
waitFor res.ensureDataProvider()
|
|
res
|
|
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()
|