diff --git a/fluffy/common/common_utils.nim b/fluffy/common/common_utils.nim new file mode 100644 index 000000000..a01334ee2 --- /dev/null +++ b/fluffy/common/common_utils.nim @@ -0,0 +1,45 @@ +# Nimbus +# Copyright (c) 2021 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: [Defect].} + +import + std/[os, strutils], + chronicles, + eth/p2p/discoveryv5/enr + +iterator strippedLines(filename: string): string {.raises: [ref IOError].} = + for line in lines(filename): + let stripped = strip(line) + if stripped.startsWith('#'): # Comments + continue + + if stripped.len > 0: + yield stripped + +proc addBootstrapNode(bootstrapAddr: string, + bootstrapEnrs: var seq[Record]) = + var enrRec: enr.Record + if enrRec.fromURI(bootstrapAddr): + bootstrapEnrs.add enrRec + else: + warn "Ignoring invalid bootstrap ENR", bootstrapAddr + +proc loadBootstrapFile*(bootstrapFile: string, + bootstrapEnrs: var seq[Record]) = + if bootstrapFile.len == 0: return + let ext = splitFile(bootstrapFile).ext + if cmpIgnoreCase(ext, ".txt") == 0 or cmpIgnoreCase(ext, ".enr") == 0 : + try: + for ln in strippedLines(bootstrapFile): + addBootstrapNode(ln, bootstrapEnrs) + except IOError as e: + fatal "Could not read bootstrap file", msg = e.msg + quit 1 + else: + fatal "Unknown bootstrap file format", ext + quit 1 diff --git a/fluffy/conf.nim b/fluffy/conf.nim index 9e0b845aa..759e132b1 100644 --- a/fluffy/conf.nim +++ b/fluffy/conf.nim @@ -56,9 +56,14 @@ type desc: "Listening address for the Discovery v5 traffic" name: "listen-address" }: ValidIpAddress - bootnodes* {. - desc: "ENR URI of node to bootstrap Discovery v5 with. Argument may be repeated" - name: "bootnode" .}: seq[Record] + bootstrapNodes* {. + desc: "ENR URI of node to bootstrap Discovery v5 from. Argument may be repeated" + name: "bootstrap-node" .}: seq[Record] + + bootstrapNodesFile* {. + desc: "Specifies a line-delimited file of ENR URIs to bootstrap Discovery v5 from" + defaultValue: "" + name: "bootstrap-file" }: InputFile nat* {. desc: "Specify method to use for determining public address. " & @@ -88,9 +93,14 @@ type # Note: This will add bootstrap nodes for each enabled Portal network. # No distinction is being made on bootstrap nodes for a specific network. - portalBootnodes* {. - desc: "ENR URI of node to bootstrap the Portal protocols with. Argument may be repeated" - name: "portal-bootnode" .}: seq[Record] + portalBootstrapNodes* {. + desc: "ENR URI of node to bootstrap the Portal networks from. Argument may be repeated" + name: "portal-bootstrap-node" .}: seq[Record] + + portalBootstrapNodesFile* {. + desc: "Specifies a line-delimited file of ENR URIs to bootstrap the Portal networks from" + defaultValue: "" + name: "portal-bootstrap-file" }: InputFile metricsEnabled* {. defaultValue: false diff --git a/fluffy/fluffy.nim b/fluffy/fluffy.nim index 6c77cfb74..4759a9fbf 100644 --- a/fluffy/fluffy.nim +++ b/fluffy/fluffy.nim @@ -11,10 +11,11 @@ import std/os, confutils, confutils/std/net, chronicles, chronicles/topics_registry, chronos, metrics, metrics/chronos_httpserver, json_rpc/clients/httpclient, - json_rpc/rpcproxy, stew/byteutils, + json_rpc/rpcproxy, stew/[byteutils, io2], eth/keys, eth/net/nat, eth/p2p/discoveryv5/protocol as discv5_protocol, - ./conf, ./rpc/[rpc_eth_api, bridge_client, rpc_discovery_api, rpc_portal_api], + ./conf, ./common/common_utils, + ./rpc/[rpc_eth_api, bridge_client, rpc_discovery_api, rpc_portal_api], ./network/state/[state_network, state_content], ./network/history/[history_network, history_content], ./content_db @@ -47,9 +48,13 @@ proc run(config: PortalConf) {.raises: [CatchableError, Defect].} = # TODO: Ideally we don't have the Exception here except Exception as exc: raiseAssert exc.msg + var bootstrapRecords: seq[Record] + loadBootstrapFile(string config.bootstrapNodesFile, bootstrapRecords) + bootstrapRecords.add(config.bootstrapNodes) + let d = newProtocol(config.nodeKey, extIp, none(Port), extUdpPort, - bootstrapRecords = config.bootnodes, + bootstrapRecords = bootstrapRecords, bindIp = bindIp, bindPort = udpPort, enrAutoUpdate = config.enrAutoUpdate, rng = rng) @@ -63,12 +68,23 @@ proc run(config: PortalConf) {.raises: [CatchableError, Defect].} = ContentDB.new(config.dataDir / "db" / "contentdb_" & d.localNode.id.toByteArrayBE().toOpenArray(0, 8).toHex()) + var portalBootstrapRecords: seq[Record] + loadBootstrapFile(string config.portalBootstrapNodesFile, portalBootstrapRecords) + portalBootstrapRecords.add(config.portalBootstrapNodes) + let stateNetwork = StateNetwork.new(d, db, - bootstrapRecords = config.portalBootnodes) + bootstrapRecords = portalBootstrapRecords) historyNetwork = HistoryNetwork.new(d, db, - bootstrapRecords = config.portalBootnodes) + bootstrapRecords = portalBootstrapRecords) + # TODO: If no new network key is generated then we should first check if an + # enr file exists, and in the case it does read out the seqNum from it and + # reuse that. + let enrFile = config.dataDir / "fluffy_node.enr" + if io2.writeFile(enrFile, d.localNode.record.toURI()).isErr: + fatal "Failed to write the enr file", file = enrFile + quit 1 if config.metricsEnabled: let diff --git a/fluffy/scripts/launch_local_testnet.sh b/fluffy/scripts/launch_local_testnet.sh index 07df40ef2..f9eb77c5a 100755 --- a/fluffy/scripts/launch_local_testnet.sh +++ b/fluffy/scripts/launch_local_testnet.sh @@ -217,7 +217,6 @@ if [[ "${TIMEOUT_DURATION}" != "0" ]]; then fi PIDS="" -BOOTSTRAP_TIMEOUT=30 # in seconds NUM_JOBS=${NUM_NODES} dump_logs() { @@ -230,11 +229,8 @@ dump_logs() { } BOOTSTRAP_NODE=0 -# TODO: -# For now we just hardcode a network key and the resulting ENR until fluffy -# stores network keys and enrs locally in files. -NETWORK_KEY="0x29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169" -HARDCODED_BOOTSTRAP_ENR="enr:-IS4QDBoE7JdB3W9Jqc3Yoatk3Zw3PgkAcnhDKNFszdiPfm3IGNlvPl5CKiJLn9u5Kk-2QaieAGYvtMgR-EBqIWIqe0BgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQNStnoFzDBxNc8-0t6xLnFpoJbovjIq_QeEHCVcfOKck4N1ZHCCIyg" +BOOTSTRAP_TIMEOUT=5 # in seconds +BOOTSTRAP_ENR_FILE="${DATA_DIR}/node${BOOTSTRAP_NODE}/fluffy_node.enr" for NUM_NODE in $(seq 0 $(( NUM_NODES - 1 ))); do NODE_DATA_DIR="${DATA_DIR}/node${NUM_NODE}" @@ -245,10 +241,21 @@ done echo "Starting ${NUM_NODES} nodes." for NUM_NODE in $(seq 0 $(( NUM_NODES - 1 ))); do NODE_DATA_DIR="${DATA_DIR}/node${NUM_NODE}" - if [[ ${NUM_NODE} == ${BOOTSTRAP_NODE} ]]; then - BOOTSTRAP_ARG="--nodekey:${NETWORK_KEY}" - else - BOOTSTRAP_ARG="--bootnode=${HARDCODED_BOOTSTRAP_ENR} --portal-bootnode=${HARDCODED_BOOTSTRAP_ENR}" + + if [[ ${NUM_NODE} != ${BOOTSTRAP_NODE} ]]; then + BOOTSTRAP_ARG="--bootstrap-file=${BOOTSTRAP_ENR_FILE} --portal-bootstrap-file=${BOOTSTRAP_ENR_FILE}" + + # Wait for the bootstrap node to write out its enr file + START_TIMESTAMP=$(date +%s) + while [[ ! -f "${BOOTSTRAP_ENR_FILE}" ]]; do + sleep 0.1 + NOW_TIMESTAMP=$(date +%s) + if [[ "$(( NOW_TIMESTAMP - START_TIMESTAMP - GENESIS_OFFSET ))" -ge "$BOOTSTRAP_TIMEOUT" ]]; then + echo "Bootstrap node failed to start in ${BOOTSTRAP_TIMEOUT} seconds. Aborting." + dump_logs + exit 1 + fi + done fi # Increasing the loopback address here with NUM_NODE as listen address to