Integrate Discovery V5 and support ENR bootstrap records

This commit is contained in:
Zahary Karadjov 2019-12-13 19:30:39 +02:00 committed by zah
parent 74fc34eef2
commit 52878405b7
9 changed files with 246 additions and 15 deletions

View File

@ -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)

View File

@ -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."

View File

@ -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)

View File

@ -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

View File

@ -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 =

120
beacon_chain/nimquery.nim Normal file
View File

@ -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

View File

@ -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

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit 655fc43751f203acdc525bab688115043f504b87
Subproject commit 3527d47cb5c7911741766a38d56c4b153e06e155

2
vendor/nim-stint vendored

@ -1 +1 @@
Subproject commit 25c2604b4b41d1b13f4a2740486507fe5f63086e
Subproject commit 6f665a4fa2f08a878d1b8e442293eab8218f3fb8