nimbus-eth2/beacon_chain/eth2_discovery.nim

145 lines
4.9 KiB
Nim

import
os, net, strutils, strformat, parseutils,
chronicles, stew/result, eth/keys, eth/trie/db, eth/p2p/enode,
eth/p2p/discoveryv5/[enr, protocol, node, discovery_db, types],
libp2p/[multiaddress, multicodec, peer],
libp2p/crypto/crypto as libp2pCrypto,
conf
type
Eth2DiscoveryProtocol* = protocol.Protocol
Eth2DiscoveryId* = NodeId
export
Eth2DiscoveryProtocol, open, start, close, result
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)
proc toENode*(a: MultiAddress): Result[ENode, cstring] =
if not IPFS.match(a):
return err "Unsupported MultiAddress"
try:
# TODO. This code is quite messy with so much string handling.
# MultiAddress can offer a more type-safe API?
var
peerId = PeerID.init(a[2].protoAddress())
addressFragments = split($a[0], "/")
portFragments = split($a[1], "/")
tcpPort: int
if addressFragments.len != 3 or
addressFragments[1] != "ip4" or
portFragments.len != 3 or
portFragments[1] notin ["tcp", "udp"] or
parseInt(portFragments[2], tcpPort) == 0:
return err "Only IPv4 MultiAddresses are supported"
let
ipAddress = parseIpAddress(addressFragments[2])
# TODO. The multiaddress will have either a TCP or a UDP value, but
# is it reasonable to assume that a client will use the same ports?
# Probably not, but how can we bootstrap then?
udpPort = tcpPort
var pubkey: libp2pCrypto.PublicKey
if peerId.extractPublicKey(pubkey):
if pubkey.scheme == Secp256k1:
return ok ENode(pubkey: pubkey.skkey,
address: Address(ip: ipAddress,
tcpPort: Port tcpPort,
udpPort: Port udpPort))
except CatchableError:
# This will reach the error exit path below
discard
return err "Invalid MultiAddress"
proc toMultiAddressStr*(enode: ENode): string =
var peerId = PeerID.init(libp2pCrypto.PublicKey(scheme: Secp256k1,
skkey: enode.pubkey))
&"/ip4/{enode.address.ip}/tcp/{enode.address.tcpPort}/p2p/{peerId.pretty}"
proc parseBootstrapAddress*(address: TaintedString): Result[ENode, cstring] =
if address.len == 0:
return err "an empty string is not a valid bootstrap node"
logScope:
address = string(address)
if address[0] == '/':
try:
let ma = MultiAddress.init(address)
return toENode(ma)
except CatchableError:
return err "Invalid bootstrap multiaddress"
else:
let lowerCaseAddress = toLowerAscii(string address)
if lowerCaseAddress.startsWith("enr:"):
var enrRec: enr.Record
if enrRec.fromURI(string address):
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)
var pubKey: keys.PublicKey
if not enrRec.get(pubKey):
return err "Failed to read public key from ENR record"
return ok ENode(pubkey: pubkey,
address: Address(ip: ip,
tcpPort: tcpPort,
udpPort: udpPort))
except CatchableError:
# This will continue to the failure path below
discard
return err "Invalid ENR bootstrap record"
elif lowerCaseAddress.startsWith("enode:"):
try:
return ok initEnode(string address)
except CatchableError as err:
return err "Ignoring invalid enode bootstrap address"
else:
return err "Ignoring unrecognized bootstrap address type"
proc addBootstrapNode*(bootNodes: var seq[ENode],
bootstrapAddr: string) =
let enodeRes = parseBootstrapAddress(bootstrapAddr)
if enodeRes.isOk:
bootNodes.add enodeRes.value
else:
warn "Ignoring invalid bootstrap address",
bootstrapAddr, reason = enodeRes.error
proc loadBootstrapFile*(bootNodes: var seq[ENode],
bootstrapFile: string) =
if bootstrapFile.len == 0: return
let ext = splitFile(bootstrapFile).ext
if cmpIgnoreCase(ext, ".txt") == 0:
for ln in lines(bootstrapFile):
bootNodes.addBootstrapNode(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):
bootNodes.addBootstrapNode(string(ln[3..^2]))
else:
error "Unknown bootstrap file format", ext
quit 1