forked from cskiraly/fast-ethereum-crawler
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevp2p_probe.nim
More file actions
104 lines (91 loc) · 3.26 KB
/
Copy pathdevp2p_probe.nim
File metadata and controls
104 lines (91 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# Copyright (c) 2020-2026 dcrawl contributors
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
## Minimal devp2p (RLPx) probe used to capture a remote peer's
## `clientId` from its Hello message, for EL client identification.
##
## Wraps `rlpxConnect(helloOnly = true)` with a singleton EthereumNode
## so the caller can reuse it across thousands of probes.
{.push raises: [].}
import
std/[options, net],
chronos, chronicles,
eth/keys,
eth/p2p,
eth/p2p/[enode, kademlia],
eth/p2p/discoveryv5/[node as v5node, enr]
logScope:
topics = "devp2p"
type
DevP2pProbe* = ref object
ethNode: EthereumNode
proc createDevP2pProbe*(
ourKeys: KeyPair, ourClientId: string,
rng: ref HmacDrbgContext): DevP2pProbe {.raises: [CatchableError].} =
## Build a singleton EthereumNode for use as the dialer in all
## subsequent `tryDevp2pHello` calls. Discovery v4 / peer pool /
## capability protocols are created but never started — we only use
## `rlpxConnect`.
let ethNode = newEthereumNode(
keys = ourKeys,
address = enode.Address(
ip: parseIpAddress("0.0.0.0"),
udpPort: Port(0),
tcpPort: Port(0)),
networkId = 1.NetworkId,
clientId = ourClientId,
addAllCapabilities = false,
bindUdpPort = Port(0),
bindTcpPort = Port(0),
rng = rng)
DevP2pProbe(ethNode: ethNode)
type
DevP2pHelloResult* = object
clientId*: string
disconnectReason*: Option[uint8]
proc tryDevp2pHello*(
probe: DevP2pProbe, n: v5node.Node, timeoutS: int):
Future[Option[DevP2pHelloResult]] {.async: (raises: [CancelledError]).} =
## Returns Some(result) if the remote peer completes the RLPx
## handshake and sends a Hello, None otherwise. The Hello-only path
## also waits briefly for an incoming devp2p Disconnect to capture
## the reason code (peers usually send one because we declare no
## shared subprotocols). Closes the TCP connection on the way out.
let tcpPortOpt = n.record.tryGet("tcp", int)
if tcpPortOpt.isNone: return none(DevP2pHelloResult)
if n.address.isNone: return none(DevP2pHelloResult)
let kAddress = enode.Address(
ip: n.address.get().ip,
udpPort: Port(0),
tcpPort: Port(tcpPortOpt.get()))
let kEnode = ENode(pubkey: n.pubkey, address: kAddress)
let kNode = newNode(kEnode)
var peer: Peer = nil
try:
let res = await rlpxConnect(probe.ethNode, kNode, helloOnly = true)
.wait(timeoutS.seconds)
if res.isErr:
debug "devp2p handshake error",
id = n.id.toHex, error = res.error
return none(DevP2pHelloResult)
peer = res.value
except CancelledError as e:
raise e
except CatchableError as e:
debug "devp2p connect failed", id = n.id.toHex, error = e.msg
return none(DevP2pHelloResult)
let clientId = peer.clientId
let dr =
if peer.disconnectReason.isSome: some(peer.disconnectReason.get())
else: none(uint8)
try:
if not peer.transport.isNil and not peer.transport.closed:
peer.transport.close()
except CatchableError:
discard
return some(DevP2pHelloResult(clientId: clientId, disconnectReason: dr))
{.pop.}