228 lines
7.3 KiB
Nim
228 lines
7.3 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2022-2023 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: [].}
|
|
|
|
import
|
|
std/[os, strutils],
|
|
chronicles, chronicles/chronos_tools, chronos,
|
|
eth/keys,
|
|
beacon_chain/eth1/eth1_monitor,
|
|
beacon_chain/gossip_processing/[optimistic_processor, light_client_processor],
|
|
beacon_chain/spec/beaconstate,
|
|
beacon_chain/spec/datatypes/[phase0, altair, bellatrix],
|
|
beacon_chain/[light_client, nimbus_binary_common, version],
|
|
"."/network/beacon_light_client/[
|
|
light_client_db,
|
|
light_client_network,
|
|
light_client_content,
|
|
beacon_light_client_bridge_conf
|
|
],
|
|
"."/network/wire/[portal_stream, portal_protocol_config, portal_protocol]
|
|
|
|
# TODO Find what can throw exception
|
|
proc run() {.raises: [Exception, Defect].} =
|
|
{.pop.}
|
|
var config = makeBannerAndConfig(
|
|
"Beacon light client bridge " & fullVersionStr, BridgeConf)
|
|
{.push raises: [].}
|
|
|
|
# Required as both Eth2Node and LightClient requires correct config type
|
|
var lcConfig = config.asLightClientConf()
|
|
|
|
setupLogging(config.logLevel, config.logStdout, none(OutFile))
|
|
|
|
notice "Launching Beacon light client bridge",
|
|
version = fullVersionStr, cmdParams = commandLineParams(), config
|
|
|
|
let
|
|
metadata = loadEth2Network(lcConfig.eth2Network)
|
|
|
|
for node in metadata.bootstrapNodes:
|
|
lcConfig.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, lcConfig, netKeys, cfg,
|
|
forkDigests, getBeaconTime, genesis_validators_root
|
|
)
|
|
|
|
streamManager = StreamManager.new(network.discovery)
|
|
|
|
db = LightClientDb.new(lcConfig.dataDir / "db")
|
|
|
|
lcNetwork = LightClientNetwork.new(
|
|
network.discovery,
|
|
db,
|
|
streamManager,
|
|
forkDigests[]
|
|
)
|
|
|
|
lightClient = createLightClient(
|
|
network, rng, lcConfig, 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()
|
|
lcNetwork.start()
|
|
|
|
proc onFinalizedHeader(
|
|
lightClient: LightClient, finalizedHeader: BeaconBlockHeader) =
|
|
info "New LC finalized header",
|
|
finalized_header = shortLog(finalizedHeader)
|
|
|
|
proc onOptimisticHeader(
|
|
lightClient: LightClient, optimisticHeader: BeaconBlockHeader) =
|
|
info "New LC optimistic header",
|
|
optimistic_header = shortLog(optimisticHeader)
|
|
|
|
# TODO Currently the only thing bridge does it to save all lc objects received
|
|
# from libp2p network to portal compatible database format. This way portal
|
|
# nodes can find this content in the network if bridge node is their neighbour.
|
|
# Ultimately bridge node should not only save objects into db, but also actively
|
|
# gossip them into the portal light client network.
|
|
proc onBootstrap(
|
|
lightClient: LightClient,
|
|
bootstrap: altair.LightClientBootstrap) =
|
|
info "New LC boostrap",
|
|
bootstrap, period = bootstrap.header.slot.sync_committee_period
|
|
|
|
let
|
|
bh = hash_tree_root(bootstrap.header)
|
|
contentKey = encode(bootstrapContentKey(bh))
|
|
contentId = toContentId(contentKey)
|
|
content = encodeBootstrapForked(
|
|
network.forkDigests.altair,
|
|
bootstrap
|
|
)
|
|
lcNetwork.portalProtocol.storeContent(
|
|
contentKey,
|
|
contentId,
|
|
content
|
|
)
|
|
|
|
proc onLCUpdate(lightClient: LightClient, update: altair.LightClientUpdate) =
|
|
info "New LC update",
|
|
update, period = update.attested_header.slot.sync_committee_period
|
|
let
|
|
period = update.attested_header.slot.sync_committee_period
|
|
contentKey = encode(updateContentKey(period.uint64, uint64(1)))
|
|
contentId = toContentId(contentKey)
|
|
content = encodeLightClientUpdatesForked(
|
|
network.forkDigests.altair,
|
|
@[update]
|
|
)
|
|
lcNetwork.portalProtocol.storeContent(
|
|
contentKey,
|
|
contentId,
|
|
content
|
|
)
|
|
|
|
proc onOptimisticUpdate(
|
|
lightClient: LightClient,
|
|
optUpdate: altair.LightClientOptimisticUpdate) =
|
|
info "New LC optimistic update",
|
|
optUpdate, period = optUpdate.attested_header.slot.sync_committee_period
|
|
let
|
|
slot = optUpdate.attested_header.slot
|
|
contentKey = encode(optimisticUpdateContentKey(slot.uint64))
|
|
contentId = toContentId(contentKey)
|
|
content = encodeOptimisticUpdateForked(
|
|
network.forkDigests.altair,
|
|
optUpdate
|
|
)
|
|
lcNetwork.portalProtocol.storeContent(
|
|
contentKey,
|
|
contentId,
|
|
content
|
|
)
|
|
|
|
proc onFinalityUpdate(
|
|
lightClient: LightClient,
|
|
finUpdate: altair.LightClientFinalityUpdate) =
|
|
info "New LC finality update",
|
|
finUpdate, period = finUpdate.attested_header.slot.sync_committee_period
|
|
let
|
|
finSlot = finUpdate.finalized_header.slot
|
|
optSlot = finUpdate.attested_header.slot
|
|
contentKey = encode(finalityUpdateContentKey(finSlot.uint64, optSlot.uint64))
|
|
contentId = toContentId(contentKey)
|
|
content = encodeFinalityUpdateForked(
|
|
network.forkDigests.altair,
|
|
finUpdate
|
|
)
|
|
lcNetwork.portalProtocol.storeContent(
|
|
contentKey,
|
|
contentId,
|
|
content
|
|
)
|
|
|
|
lightClient.onFinalizedHeader = onFinalizedHeader
|
|
lightClient.onOptimisticHeader = onOptimisticHeader
|
|
lightClient.trustedBlockRoot = some config.trustedBlockRoot
|
|
lightClient.bootstrapObserver = onBootstrap
|
|
lightClient.updateObserver = onLCUpdate
|
|
lightClient.finalityUpdateObserver = onFinalityUpdate
|
|
lightClient.optimisticUpdateObserver = onOptimisticUpdate
|
|
|
|
proc onSecond(time: Moment) =
|
|
let wallSlot = getBeaconTime().slotOrZero()
|
|
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())
|
|
|
|
lightClient.start()
|
|
|
|
asyncSpawn runOnSecondLoop()
|
|
|
|
while true:
|
|
poll()
|
|
|
|
when isMainModule:
|
|
run()
|