Add light client bridge (#1386)

* Add light client bridge binary
This commit is contained in:
KonradStaniec 2022-12-27 15:25:20 +01:00 committed by GitHub
parent 5134bb5e04
commit 270ce41d5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 415 additions and 0 deletions

View File

@ -203,6 +203,10 @@ fluffy: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim fluffy $(NIM_PARAMS) nimbus.nims
lc-bridge: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim lc_bridge $(NIM_PARAMS) nimbus.nims
# primitive reproducibility test
fluffy-test-reproducibility:
+ [ -e build/fluffy ] || $(MAKE) V=0 fluffy; \

View File

@ -0,0 +1,230 @@
# Nimbus
# 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.
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.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: [Defect].}
# 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()

View File

@ -0,0 +1,165 @@
# beacon light client bridge
# 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.
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import
std/os,
json_serialization/std/net,
beacon_chain/light_client,
beacon_chain/conf
export net, conf
proc defaultBridgeDataDir*(): string =
let dataDir = when defined(windows):
"AppData" / "Roaming" / "BeaconLightClientBridge"
elif defined(macosx):
"Library" / "Application Support" / "BeaconLightClientBridge"
else:
".cache" / "beacon-ligh-client-bridge"
getHomeDir() / dataDir
const
defaultBridgeDataDirDesc* = defaultBridgeDataDir()
type BridgeConf* = object
# Config
configFile* {.
desc: "Loads the configuration from a TOML file"
name: "config-file" .}: Option[InputFile]
# Logging
logLevel* {.
desc: "Sets the log level"
defaultValue: "INFO"
name: "log-level" .}: string
logStdout* {.
hidden
desc: "Specifies what kind of logs should be written to stdout (auto, colors, nocolors, json)"
defaultValueDesc: "auto"
defaultValue: StdoutLogKind.Auto
name: "log-format" .}: StdoutLogKind
# Storage
dataDir* {.
desc: "The directory where beacon light client bridge will store all data"
defaultValue: defaultBridgeDataDir()
defaultValueDesc: $defaultBridgeDataDirDesc
abbr: "d"
name: "data-dir" .}: OutDir
# Consensus light sync
# No default - Needs to be provided by the user
trustedBlockRoot* {.
desc: "Recent trusted finalized block root to initialize the consensus light client from"
name: "trusted-block-root" .}: Eth2Digest
# Libp2p
bootstrapNodes* {.
desc: "Specifies one or more bootstrap nodes to use when connecting to the network"
abbr: "b"
name: "bootstrap-node" .}: seq[string]
bootstrapNodesFile* {.
desc: "Specifies a line-delimited file of bootstrap Ethereum network addresses"
defaultValue: ""
name: "bootstrap-file" .}: InputFile
listenAddress* {.
desc: "Listening address for the Ethereum LibP2P and Discovery v5 traffic"
defaultValue: defaultListenAddress
defaultValueDesc: $defaultListenAddressDesc
name: "listen-address" .}: ValidIpAddress
tcpPort* {.
desc: "Listening TCP port for Ethereum LibP2P traffic"
defaultValue: defaultEth2TcpPort
defaultValueDesc: $defaultEth2TcpPortDesc
name: "tcp-port" .}: Port
udpPort* {.
desc: "Listening UDP port for node discovery"
defaultValue: defaultEth2TcpPort
defaultValueDesc: $defaultEth2TcpPortDesc
name: "udp-port" .}: Port
# TODO: Select a lower amount of peers.
maxPeers* {.
desc: "The target number of peers to connect to"
defaultValue: 160 # 5 (fanout) * 64 (subnets) / 2 (subs) for a healthy mesh
name: "max-peers" .}: int
hardMaxPeers* {.
desc: "The maximum number of peers to connect to. Defaults to maxPeers * 1.5"
name: "hard-max-peers" .}: Option[int]
nat* {.
desc: "Specify method to use for determining public address. " &
"Must be one of: any, none, upnp, pmp, extip:<IP>"
defaultValue: NatConfig(hasExtIp: false, nat: NatAny)
defaultValueDesc: "any"
name: "nat" .}: NatConfig
enrAutoUpdate* {.
desc: "Discovery can automatically update its ENR with the IP address " &
"and UDP port as seen by other nodes it communicates with. " &
"This option allows to enable/disable this functionality"
defaultValue: false
name: "enr-auto-update" .}: bool
agentString* {.
defaultValue: "nimbus",
desc: "Node agent string which is used as identifier in the LibP2P network"
name: "agent-string" .}: string
discv5Enabled* {.
desc: "Enable Discovery v5"
defaultValue: true
name: "discv5" .}: bool
directPeers* {.
desc: "The list of priviledged, secure and known peers to connect and" &
"maintain the connection to, this requires a not random netkey-file." &
"In the complete multiaddress format like:" &
"/ip4/<address>/tcp/<port>/p2p/<peerId-public-key>." &
"Peering agreements are established out of band and must be reciprocal"
name: "direct-peer" .}: seq[string]
func asLightClientConf*(pc: BridgeConf): LightClientConf =
return LightClientConf(
configFile: pc.configFile,
logLevel: pc.logLevel,
logStdout: pc.logStdout,
logFile: none(OutFile),
dataDir: pc.dataDir,
# Portal networks are defined only over mainnet therefore bridging makes
# sense only for mainnet
eth2Network: some("mainnet"),
bootstrapNodes: pc.bootstrapNodes,
bootstrapNodesFile: pc.bootstrapNodesFile,
listenAddress: pc.listenAddress,
tcpPort: pc.tcpPort,
udpPort: pc.udpPort,
maxPeers: pc.maxPeers,
hardMaxPeers: pc.hardMaxPeers,
nat: pc.nat,
enrAutoUpdate: pc.enrAutoUpdate,
agentString: pc.agentString,
discv5Enabled: pc.discv5Enabled,
directPeers: pc.directPeers,
trustedBlockRoot: pc.trustedBlockRoot,
web3Urls: @[],
jwtSecret: none(string),
stopAtEpoch: 0
)

View File

@ -226,6 +226,19 @@ proc decodeLightClientUpdatesForked*(
return ok(updates)
func bootstrapContentKey*(bh: Digest): ContentKey =
ContentKey(
contentType: lightClientBootstrap,
lightClientBootstrapKey: LightClientBootstrapKey(blockHash: bh)
)
func updateContentKey*(startPeriod: uint64, count: uint64): ContentKey =
ContentKey(
contentType: lightClientUpdate,
lightClientUpdateKey: LightClientUpdateKey(startPeriod: startPeriod, count: count)
)
func finalityUpdateContentKey*(finalSlot: uint64, optimisticSlot: uint64): ContentKey =
ContentKey(
contentType: lightClientFinalityUpdate,

View File

@ -74,6 +74,9 @@ task test_evm, "Run EVM tests":
task fluffy, "Build fluffy":
buildBinary "fluffy", "fluffy/", "-d:chronicles_log_level=TRACE -d:chronosStrictException -d:PREFER_BLST_SHA256=false"
task lc_bridge, "Build light client bridge":
buildBinary "beacon_light_client_bridge", "fluffy/", "-d:chronicles_log_level=TRACE -d:chronosStrictException -d:PREFER_BLST_SHA256=false -d:libp2p_pki_schemes=secp256k1"
task fluffy_test, "Run fluffy tests":
# Need the nimbus_db_backend in state network tests as we need a Hexary to
# start from, even though it only uses the MemoryDb.