Integrate Discovery V5 and support ENR bootstrap records
This commit is contained in:
parent
74fc34eef2
commit
52878405b7
|
@ -6,7 +6,7 @@ import
|
|||
stew/[objects, bitseqs, byteutils],
|
||||
chronos, chronicles, confutils, metrics,
|
||||
json_serialization/std/[options, sets], serialization/errors,
|
||||
kvstore, kvstore_lmdb, eth/async_utils,
|
||||
kvstore, kvstore_lmdb, eth/async_utils, eth/p2p/discoveryv5/enr,
|
||||
|
||||
# Local modules
|
||||
spec/[datatypes, digest, crypto, beaconstate, helpers, validator, network],
|
||||
|
@ -48,6 +48,7 @@ type
|
|||
networkIdentity: Eth2NodeIdentity
|
||||
requestManager: RequestManager
|
||||
bootstrapNodes: seq[BootstrapAddr]
|
||||
bootstrapEnrs: seq[enr.Record]
|
||||
db: BeaconChainDB
|
||||
config: BeaconNodeConf
|
||||
attachedValidators: ValidatorPool
|
||||
|
@ -130,6 +131,36 @@ proc loadBootstrapFile(bootstrapFile: string): seq[BootstrapAddr] =
|
|||
for line in lines(bootstrapFile):
|
||||
result.addBootstrapAddr(line)
|
||||
|
||||
proc addEnrBootstrapNode(node: BeaconNode, enrBase64: string) =
|
||||
info "Adding bootsrap node", enr = enrBase64
|
||||
var enrRec: enr.Record
|
||||
if enrRec.fromBase64(enrBase64):
|
||||
info "Parsed ENR record", value = enrRec
|
||||
try:
|
||||
let
|
||||
ip = IpAddress(family: IpAddressFamily.IPv4,
|
||||
address_v4: cast[array[4, uint8]](enrRec.get("ip", int)))
|
||||
tcpPort = Port enrRec.get("tcp", int)
|
||||
# udpPort = Port enrRec.get("udp", int)
|
||||
node.addBootstrapNode BootstrapAddr.initAddress(ip, tcpPort)
|
||||
node.bootstrapEnrs.add enrRec
|
||||
except CatchableError as err:
|
||||
warn "Invalid ENR record", enrRec
|
||||
|
||||
proc useEnrBootstrapFile(node: BeaconNode, bootstrapFile: string) =
|
||||
let ext = splitFile(bootstrapFile).ext
|
||||
if cmpIgnoreCase(ext, ".txt") == 0:
|
||||
for ln in lines(bootstrapFile):
|
||||
node.addEnrBootstrapNode(string ln)
|
||||
elif cmpIgnoreCase(ext, ".yaml") == 0:
|
||||
# TODO. This is very ugly, but let's try to negotiate the
|
||||
# removal of YAML metadata.
|
||||
for ln in lines(bootstrapFile):
|
||||
node.addEnrBootstrapNode(string(ln[3..^2]))
|
||||
else:
|
||||
error "Unknown bootstrap file format", ext
|
||||
quit 1
|
||||
|
||||
proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async.} =
|
||||
let
|
||||
networkId = getPersistentNetIdentity(conf)
|
||||
|
@ -184,7 +215,15 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
|
|||
blockPool.headState.data.data.eth1_data.block_hash)
|
||||
mainchainMonitor.start()
|
||||
|
||||
var bootNodes: seq[BootstrapAddr]
|
||||
var
|
||||
bootNodes: seq[BootstrapAddr]
|
||||
bootstrapEnrs: seq[enr.Record]
|
||||
|
||||
# TODO: rebase this
|
||||
let enrBootstrapFile = string conf.enrBootstrapNodesFile
|
||||
if enrBootstrapFile.len > 0:
|
||||
result.useEnrBootstrapFile(enrBootstrapFile)
|
||||
|
||||
for node in conf.bootstrapNodes: bootNodes.addBootstrapAddr(node)
|
||||
bootNodes.add(loadBootstrapFile(string conf.bootstrapNodesFile))
|
||||
bootNodes.add(loadBootstrapFile(conf.dataDir / "bootstrap_nodes.txt"))
|
||||
|
@ -192,7 +231,7 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): Future[BeaconNode] {.async
|
|||
bootNodes = filterIt(bootNodes, not it.isSameNode(networkId))
|
||||
|
||||
let
|
||||
network = await createEth2Node(conf, bootNodes)
|
||||
network = await createEth2Node(conf, bootNodes, bootstrapEnrs)
|
||||
|
||||
let addressFile = string(conf.dataDir) / "beacon_node.address"
|
||||
network.saveConnectionAddressFile(addressFile)
|
||||
|
|
|
@ -90,6 +90,11 @@ type
|
|||
desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses."
|
||||
name: "bootstrap-file" }: InputFile
|
||||
|
||||
enrBootstrapNodesFile* {.
|
||||
defaultValue: ""
|
||||
desc: "Specifies a line-delimited file of bootstrap ENR records"
|
||||
name: "enr-bootstrap-file" }: InputFile
|
||||
|
||||
tcpPort* {.
|
||||
defaultValue: defaultPort(config)
|
||||
desc: "TCP listening port."
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import
|
||||
net,
|
||||
eth/keys, eth/trie/db,
|
||||
eth/p2p/discoveryv5/[protocol, node, discovery_db, types],
|
||||
conf
|
||||
|
||||
type
|
||||
Eth2DiscoveryProtocol* = protocol.Protocol
|
||||
Eth2DiscoveryId* = NodeId
|
||||
|
||||
export
|
||||
Eth2DiscoveryProtocol, open, start, close
|
||||
|
||||
proc new*(T: type Eth2DiscoveryProtocol,
|
||||
conf: BeaconNodeConf,
|
||||
rawPrivKeyBytes: openarray[byte]): T =
|
||||
# TODO
|
||||
# Implement more configuration options:
|
||||
# * for setting up a specific key
|
||||
# * for using a persistent database
|
||||
var
|
||||
pk = initPrivateKey(rawPrivKeyBytes)
|
||||
db = DiscoveryDB.init(newMemoryDB())
|
||||
|
||||
newProtocol(pk, db, Port conf.udpPort)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import
|
||||
options, tables,
|
||||
chronos, json_serialization, strutils, chronicles, metrics, eth/net/nat,
|
||||
chronos, json_serialization, strutils, chronicles, metrics,
|
||||
eth/net/nat, eth/p2p/discoveryv5/enr,
|
||||
version, conf
|
||||
|
||||
const
|
||||
|
@ -126,6 +127,10 @@ when networkBackend == rlpx:
|
|||
proc initAddress*(T: type BootstrapAddr, str: string): T =
|
||||
initENode(str)
|
||||
|
||||
proc initAddress*(T: type BootstrapAddr, ip: IpAddress, tcpPort: Port): T =
|
||||
# TODO
|
||||
discard
|
||||
|
||||
func peersCount*(node: Eth2Node): int =
|
||||
node.peerPool.len
|
||||
|
||||
|
@ -178,6 +183,12 @@ else:
|
|||
raise newException(MultiAddressError,
|
||||
"Invalid bootstrap node multi-address")
|
||||
|
||||
template tcpEndPoint(address, port): auto =
|
||||
MultiAddress.init(address, Protocol.IPPROTO_TCP, port)
|
||||
|
||||
proc initAddress*(T: type BootstrapAddr, ip: IpAddress, tcpPort: Port): T =
|
||||
tcpEndPoint(ip, tcpPort)
|
||||
|
||||
proc ensureNetworkIdFile(conf: BeaconNodeConf): string =
|
||||
result = conf.dataDir / networkKeyFilename
|
||||
if not fileExists(result):
|
||||
|
@ -198,15 +209,13 @@ else:
|
|||
|
||||
result = KeyPair(seckey: privKey, pubkey: privKey.getKey())
|
||||
|
||||
template tcpEndPoint(address, port): auto =
|
||||
MultiAddress.init(address, Protocol.IPPROTO_TCP, port)
|
||||
|
||||
proc allMultiAddresses(nodes: seq[BootstrapAddr]): seq[string] =
|
||||
for node in nodes:
|
||||
result.add $node
|
||||
|
||||
proc createEth2Node*(conf: BeaconNodeConf,
|
||||
bootstrapNodes: seq[BootstrapAddr]): Future[Eth2Node] {.async.} =
|
||||
bootstrapNodes: seq[BootstrapAddr],
|
||||
bootstrapEnrs: seq[enr.Record]): Future[Eth2Node] {.async.} =
|
||||
var
|
||||
(extIp, extTcpPort, _) = setupNat(conf)
|
||||
hostAddress = tcpEndPoint(globalListeningAddr, Port conf.tcpPort)
|
||||
|
@ -222,8 +231,11 @@ else:
|
|||
# TODO nim-libp2p still doesn't have support for announcing addresses
|
||||
# that are different from the host address (this is relevant when we
|
||||
# are running behind a NAT).
|
||||
result = Eth2Node.init newStandardSwitch(some keys.seckey, hostAddress,
|
||||
triggerSelf = true, gossip = true)
|
||||
var switch = newStandardSwitch(some keys.seckey, hostAddress,
|
||||
triggerSelf = true, gossip = true)
|
||||
result = Eth2Node.init(conf, switch, keys.seckey)
|
||||
for enr in bootstrapEnrs:
|
||||
result.addKnownPeer(enr)
|
||||
await result.start()
|
||||
else:
|
||||
let keyFile = conf.ensureNetworkIdFile
|
||||
|
|
|
@ -3,6 +3,7 @@ import
|
|||
stew/[varints,base58], stew/shims/[macros, tables], chronos, chronicles,
|
||||
faststreams/output_stream, serialization,
|
||||
json_serialization/std/options, eth/p2p/p2p_protocol_dsl,
|
||||
eth/p2p/discoveryv5/enr,
|
||||
# TODO: create simpler to use libp2p modules that use re-exports
|
||||
libp2p/[switch, multistream, connection,
|
||||
multiaddress, peerinfo, peer,
|
||||
|
@ -11,7 +12,10 @@ import
|
|||
libp2p/protocols/secure/[secure, secio],
|
||||
libp2p/protocols/pubsub/[pubsub, floodsub],
|
||||
libp2p/transports/[transport, tcptransport],
|
||||
libp2p_json_serialization, ssz
|
||||
libp2p_json_serialization, eth2_discovery, conf, ssz
|
||||
|
||||
import
|
||||
eth/p2p/discoveryv5/protocol as discv5_protocol
|
||||
|
||||
export
|
||||
p2pProtocol, libp2p_json_serialization, ssz
|
||||
|
@ -22,7 +26,10 @@ type
|
|||
# TODO Is this really needed?
|
||||
Eth2Node* = ref object of RootObj
|
||||
switch*: Switch
|
||||
discovery*: Eth2DiscoveryProtocol
|
||||
wantedPeers*: int
|
||||
peers*: Table[PeerID, Peer]
|
||||
peersByDiscoveryId*: Table[Eth2DiscoveryId, Peer]
|
||||
protocolStates*: seq[RootRef]
|
||||
libp2pTransportLoops*: seq[Future[void]]
|
||||
|
||||
|
@ -32,6 +39,7 @@ type
|
|||
network*: Eth2Node
|
||||
info*: PeerInfo
|
||||
wasDialed*: bool
|
||||
discoveryId*: Eth2DiscoveryId
|
||||
connectionState*: ConnectionState
|
||||
protocolStates*: seq[RootRef]
|
||||
maxInactivityAllowed*: Duration
|
||||
|
@ -139,10 +147,26 @@ include eth/p2p/p2p_backends_helpers
|
|||
include eth/p2p/p2p_tracing
|
||||
include libp2p_backends_common
|
||||
|
||||
proc init*(T: type Eth2Node, switch: Switch): T =
|
||||
proc dialPeer*(node: Eth2Node, enr: enr.Record) {.async.} =
|
||||
discard
|
||||
|
||||
proc runDiscoveryLoop(node: Eth2Node) {.async.} =
|
||||
while true:
|
||||
if node.peersByDiscoveryId.len < node.wantedPeers:
|
||||
let discoveredPeers = await node.discovery.lookupRandom()
|
||||
for peer in discoveredPeers:
|
||||
if peer.id notin node.peersByDiscoveryId:
|
||||
# TODO do this in parallel
|
||||
await node.dialPeer(peer.record)
|
||||
|
||||
await sleepAsync seconds(1)
|
||||
|
||||
proc init*(T: type Eth2Node, conf: BeaconNodeConf,
|
||||
switch: Switch, privKey: PrivateKey): T =
|
||||
new result
|
||||
result.switch = switch
|
||||
result.peers = initTable[PeerID, Peer]()
|
||||
result.discovery = Eth2DiscoveryProtocol.new(conf, privKey.getBytes)
|
||||
|
||||
newSeq result.protocolStates, allProtocols.len
|
||||
for proto in allProtocols:
|
||||
|
@ -153,7 +177,12 @@ proc init*(T: type Eth2Node, switch: Switch): T =
|
|||
if msg.protocolMounter != nil:
|
||||
msg.protocolMounter result
|
||||
|
||||
proc addKnownPeer*(node: Eth2Node, peerEnr: enr.Record) =
|
||||
node.discovery.addNode peerEnr
|
||||
|
||||
proc start*(node: Eth2Node) {.async.} =
|
||||
node.discovery.open()
|
||||
node.discovery.start()
|
||||
node.libp2pTransportLoops = await node.switch.start()
|
||||
|
||||
proc init*(T: type Peer, network: Eth2Node, info: PeerInfo): Peer =
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
import
|
||||
strutils, strformat, parseutils
|
||||
|
||||
type
|
||||
TokenKind* = enum
|
||||
tIdent = "ident"
|
||||
tNumber = "number"
|
||||
tDot = "dot"
|
||||
tOpenBracket = "["
|
||||
tCloseBracket = "]"
|
||||
tEof = "end of file"
|
||||
tError = "error"
|
||||
|
||||
Token* = object
|
||||
case kind*: TokenKind
|
||||
of tIdent:
|
||||
name*: string
|
||||
of tNumber:
|
||||
val*: uint64
|
||||
of tError:
|
||||
errMsg: string
|
||||
else:
|
||||
discard
|
||||
|
||||
Lexer* = object
|
||||
tok*: Token
|
||||
input: string
|
||||
pos: int
|
||||
|
||||
Parser* = object
|
||||
lexer: Lexer
|
||||
|
||||
NodeKind* = enum
|
||||
Ident
|
||||
Number
|
||||
Dot
|
||||
ArrayAccess
|
||||
Error
|
||||
|
||||
Node* = ref object {.acyclic.}
|
||||
case kind*: NodeKind
|
||||
of Dot:
|
||||
objVal*, field*: Node
|
||||
of ArrayAccess:
|
||||
arrayVal*, index*: Node
|
||||
of Ident:
|
||||
name*: string
|
||||
of Number:
|
||||
numVal*: uint64
|
||||
of Error:
|
||||
errMsg*: string
|
||||
|
||||
func advance(lexer: var Lexer) =
|
||||
if lexer.pos >= lexer.input.len:
|
||||
lexer.tok = Token(kind: tEof)
|
||||
else:
|
||||
let nextChar = lexer.input[lexer.pos]
|
||||
case nextChar
|
||||
of IdentStartChars:
|
||||
lexer.tok = Token(kind: tIdent)
|
||||
lexer.pos = parseIdent(lexer.input, lexer.tok.name, lexer.pos)
|
||||
of Whitespace:
|
||||
lexer.pos = skipWhitespace(lexer.input, lexer.pos)
|
||||
advance lexer
|
||||
of Digits:
|
||||
lexer.tok = Token(kind: tNumber)
|
||||
lexer.pos = parseBiggestUInt(lexer.input, lexer.tok.val, lexer.pos)
|
||||
of '[':
|
||||
lexer.tok = Token(kind: tOpenBracket)
|
||||
inc lexer.pos
|
||||
of ']':
|
||||
lexer.tok = Token(kind: tCloseBracket)
|
||||
inc lexer.pos
|
||||
of '.':
|
||||
lexer.tok = Token(kind: tDot)
|
||||
inc lexer.pos
|
||||
else:
|
||||
lexer.tok = Token(
|
||||
kind: tError,
|
||||
errMsg: &"Unexpected character '{nextChar}' at position {lexer.pos}")
|
||||
|
||||
func init*(T: type Lexer, src: string): Lexer =
|
||||
result.input = src
|
||||
result.pos = 0
|
||||
advance result
|
||||
|
||||
func init*(T: type Parser, src: string): Parser =
|
||||
Parser(lexer: Lexer.init(src))
|
||||
|
||||
func expr(parser: var Parser): Node =
|
||||
template unexpectedToken =
|
||||
return Node(kind: Error, errMsg: &"Unexpected {parser.lexer.tok.kind} token")
|
||||
|
||||
case parser.lexer.tok.kind
|
||||
of tIdent:
|
||||
result = Node(kind: Ident, name: parser.lexer.tok.name)
|
||||
of tNumber:
|
||||
return Node(kind: Number, numVal: parser.lexer.tok.val)
|
||||
else:
|
||||
unexpectedToken
|
||||
|
||||
advance parser.lexer
|
||||
case parser.lexer.tok.kind
|
||||
of tOpenBracket:
|
||||
advance parser.lexer
|
||||
result = Node(kind: ArrayAccess, arrayVal: result, index: parser.expr)
|
||||
if parser.lexer.tok.kind != tCloseBracket:
|
||||
unexpectedToken
|
||||
else:
|
||||
advance parser.lexer
|
||||
of tDot:
|
||||
advance parser.lexer
|
||||
return Node(kind: Dot, objVal: result, field: parser.expr)
|
||||
else:
|
||||
discard
|
||||
|
||||
func parse*(input: string): Node =
|
||||
var p = Parser.init(input)
|
||||
p.expr
|
||||
|
|
@ -5,7 +5,7 @@ type
|
|||
rlpx
|
||||
|
||||
const
|
||||
NETWORK_TYPE {.strdefine.} = "libp2p_daemon"
|
||||
NETWORK_TYPE {.strdefine.} = "libp2p"
|
||||
|
||||
networkBackend* = when NETWORK_TYPE == "rlpx": rlpx
|
||||
elif NETWORK_TYPE == "libp2p": libp2p
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 655fc43751f203acdc525bab688115043f504b87
|
||||
Subproject commit 3527d47cb5c7911741766a38d56c4b153e06e155
|
|
@ -1 +1 @@
|
|||
Subproject commit 25c2604b4b41d1b13f4a2740486507fe5f63086e
|
||||
Subproject commit 6f665a4fa2f08a878d1b8e442293eab8218f3fb8
|
Loading…
Reference in New Issue