Skip to content
This repository was archived by the owner on Oct 28, 2021. It is now read-only.

Commit 2102a41

Browse files
authored
Merge pull request #5537 from ethereum/enr
ENR record for host
2 parents dbbfd5b + b517084 commit 2102a41

File tree

10 files changed

+470
-19
lines changed

10 files changed

+470
-19
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [1.7.0] - Unreleased
4+
5+
- Added: [#5537](https://github.com/ethereum/aleth/pull/5537) Creating Ethereum Node Record (ENR) at program start.
6+
7+
[1.6.0]: https://github.com/ethereum/aleth/compare/v1.6.0-alpha.1...master
8+
39
## [1.6.0] - Unreleased
410

511
- Added: [#5485](https://github.com/ethereum/aleth/pull/5485) aleth-bootnode now by default connects to official Ethereum bootnodes. This can be disabled with `--no-bootstrap` flag.
@@ -17,4 +23,5 @@
1723
- Fixed: [#5539](https://github.com/ethereum/aleth/pull/5539) Fix logic for determining if dao hard fork block header should be requested.
1824
- Fixed: [#5547](https://github.com/ethereum/aleth/pull/5547) Fix unnecessary slow-down of eth_flush RPC method.
1925

20-
[1.6.0]: https://github.com/ethereum/aleth/compare/v1.6.0-alpha.1...master
26+
[1.6.0]: https://github.com/ethereum/aleth/compare/v1.6.0-alpha.1...release/1.6
27+
[1.7.0]: https://github.com/ethereum/aleth/compare/release/1.6...master

libdevcore/CommonData.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,10 @@ bool contains(std::set<V> const& _set, V const& _v)
343343
{
344344
return _set.find(_v) != _set.end();
345345
}
346+
347+
template <class K, class V>
348+
bool contains(std::map<K, V> const& _map, K const& _k)
349+
{
350+
return _map.find(_k) != _map.end();
351+
}
346352
}

libp2p/ENR.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Aleth: Ethereum C++ client, tools and libraries.
2+
// Copyright 2019 Aleth Authors.
3+
// Licensed under the GNU General Public License, Version 3.
4+
5+
#include "ENR.h"
6+
#include <libdevcore/SHA3.h>
7+
8+
namespace dev
9+
{
10+
namespace p2p
11+
{
12+
namespace
13+
{
14+
constexpr char c_keyID[] = "id";
15+
constexpr char c_keySec256k1[] = "secp256k1";
16+
constexpr char c_keyIP[] = "ip";
17+
constexpr char c_keyTCP[] = "tcp";
18+
constexpr char c_keyUDP[] = "udp";
19+
constexpr char c_IDV4[] = "v4";
20+
constexpr size_t c_ENRMaxSizeBytes = 300;
21+
22+
23+
// Address can be either boost::asio::ip::address_v4 or boost::asio::ip::address_v6
24+
template <class Address>
25+
bytes addressToBytes(Address const& _address)
26+
{
27+
auto const addressBytes = _address.to_bytes();
28+
return bytes(addressBytes.begin(), addressBytes.end());
29+
}
30+
} // namespace
31+
32+
ENR::ENR(RLP _rlp, VerifyFunction const& _verifyFunction)
33+
{
34+
if (_rlp.data().size() > c_ENRMaxSizeBytes)
35+
BOOST_THROW_EXCEPTION(ENRIsTooBig());
36+
37+
m_signature = _rlp[0].toBytes(RLP::VeryStrict);
38+
39+
m_seq = _rlp[1].toInt<uint64_t>(RLP::VeryStrict);
40+
41+
// read key-values into vector first, to check the order
42+
std::vector<std::pair<std::string const, bytes>> keyValuePairs;
43+
for (size_t i = 2; i < _rlp.itemCount(); i += 2)
44+
{
45+
auto const key = _rlp[i].toString(RLP::VeryStrict);
46+
auto const value = _rlp[i + 1].data().toBytes();
47+
keyValuePairs.push_back({key, value});
48+
}
49+
50+
// transfer to map, this will order them
51+
m_map.insert(keyValuePairs.begin(), keyValuePairs.end());
52+
53+
if (!std::equal(keyValuePairs.begin(), keyValuePairs.end(), m_map.begin()))
54+
BOOST_THROW_EXCEPTION(ENRKeysAreNotUniqueSorted());
55+
56+
if (!_verifyFunction(m_map, dev::ref(m_signature), dev::ref(content())))
57+
BOOST_THROW_EXCEPTION(ENRSignatureIsInvalid());
58+
}
59+
60+
ENR::ENR(uint64_t _seq, std::map<std::string, bytes> const& _keyValuePairs,
61+
SignFunction const& _signFunction)
62+
: m_seq{_seq}, m_map{_keyValuePairs}, m_signature{_signFunction(dev::ref(content()))}
63+
{
64+
}
65+
66+
bytes ENR::content() const
67+
{
68+
RLPStream stream{contentRlpListItemCount()};
69+
streamContent(stream);
70+
return stream.out();
71+
}
72+
73+
74+
void ENR::streamRLP(RLPStream& _s) const
75+
{
76+
_s.appendList(contentRlpListItemCount() + 1);
77+
_s << m_signature;
78+
streamContent(_s);
79+
}
80+
81+
void ENR::streamContent(RLPStream& _s) const
82+
{
83+
_s << m_seq;
84+
for (auto const& keyValue : m_map)
85+
{
86+
_s << keyValue.first;
87+
_s.appendRaw(keyValue.second);
88+
}
89+
}
90+
91+
ENR createV4ENR(Secret const& _secret, boost::asio::ip::address const& _ip, uint16_t _tcpPort, uint16_t _udpPort)
92+
{
93+
ENR::SignFunction signFunction = [&_secret](bytesConstRef _data) {
94+
// dev::sign returns 65 bytes signature containing r,s,v values
95+
Signature s = dev::sign(_secret, sha3(_data));
96+
// The resulting 64-byte signature is encoded as the concatenation of the r and s signature values.
97+
return bytes(&s[0], &s[64]);
98+
};
99+
100+
PublicCompressed const publicKey = toPublicCompressed(_secret);
101+
102+
auto const address = _ip.is_v4() ? addressToBytes(_ip.to_v4()) : addressToBytes(_ip.to_v6());
103+
104+
// Values are of different types (string, bytes, uint16_t),
105+
// so we store them as RLP representation
106+
std::map<std::string, bytes> const keyValuePairs = {{c_keyID, rlp(c_IDV4)},
107+
{c_keySec256k1, rlp(publicKey.asBytes())}, {c_keyIP, rlp(address)},
108+
{c_keyTCP, rlp(_tcpPort)}, {c_keyUDP, rlp(_udpPort)}};
109+
110+
return ENR{0 /* sequence number */, keyValuePairs, signFunction};
111+
}
112+
113+
ENR parseV4ENR(RLP _rlp)
114+
{
115+
ENR::VerifyFunction verifyFunction = [](std::map<std::string, bytes> const& _keyValuePairs,
116+
bytesConstRef _signature, bytesConstRef _data) {
117+
auto itID = _keyValuePairs.find(c_keyID);
118+
if (itID == _keyValuePairs.end())
119+
return false;
120+
auto const id = RLP(itID->second).toString(RLP::VeryStrict);
121+
if (id != c_IDV4)
122+
return false;
123+
124+
auto itKey = _keyValuePairs.find(c_keySec256k1);
125+
if (itKey == _keyValuePairs.end())
126+
return false;
127+
128+
auto const key = RLP(itKey->second).toHash<PublicCompressed>(RLP::VeryStrict);
129+
h512 const signature{_signature};
130+
131+
return dev::verify(key, signature, sha3(_data));
132+
};
133+
134+
return ENR{_rlp, verifyFunction};
135+
}
136+
137+
std::ostream& operator<<(std::ostream& _out, ENR const& _enr)
138+
{
139+
_out << "[ " << toHexPrefixed(_enr.signature()) << " seq=" << _enr.sequenceNumber() << " ";
140+
for (auto const& keyValue : _enr.keyValuePairs())
141+
{
142+
_out << keyValue.first << "=";
143+
_out << toHexPrefixed(RLP{keyValue.second}.toBytes()) << " ";
144+
}
145+
_out << "]";
146+
return _out;
147+
}
148+
149+
} // namespace p2p
150+
} // namespace dev

libp2p/ENR.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Aleth: Ethereum C++ client, tools and libraries.
2+
// Copyright 2019 Aleth Authors.
3+
// Licensed under the GNU General Public License, Version 3.
4+
5+
#pragma once
6+
7+
#include "Common.h"
8+
9+
namespace dev
10+
{
11+
namespace p2p
12+
{
13+
DEV_SIMPLE_EXCEPTION(ENRIsTooBig);
14+
DEV_SIMPLE_EXCEPTION(ENRSignatureIsInvalid);
15+
DEV_SIMPLE_EXCEPTION(ENRKeysAreNotUniqueSorted);
16+
17+
/// Class representing Ethereum Node Record - see EIP-778
18+
class ENR
19+
{
20+
public:
21+
// ENR class implementation is independent of Identity Scheme.
22+
// Identity Scheme specifics are passed to ENR as functions.
23+
24+
// Sign function gets serialized ENR contents and signs it according to some Identity Scheme
25+
using SignFunction = std::function<bytes(bytesConstRef)>;
26+
// Verify function gets ENR key-value pairs, signature, and serialized content and validates the
27+
// signature according to some Identity Scheme
28+
using VerifyFunction =
29+
std::function<bool(std::map<std::string, bytes> const&, bytesConstRef, bytesConstRef)>;
30+
31+
// Parse from RLP with given signature verification function
32+
ENR(RLP _rlp, VerifyFunction const& _verifyFunction);
33+
// Create with given sign function
34+
ENR(uint64_t _seq, std::map<std::string, bytes> const& _keyValues,
35+
SignFunction const& _signFunction);
36+
37+
uint64_t sequenceNumber() const { return m_seq; }
38+
std::map<std::string, bytes> const& keyValuePairs() const { return m_map; }
39+
bytes const& signature() const { return m_signature; }
40+
41+
// Serialize to given RLP stream
42+
void streamRLP(RLPStream& _s) const;
43+
44+
private:
45+
uint64_t m_seq = 0;
46+
std::map<std::string, bytes> m_map;
47+
bytes m_signature;
48+
49+
bytes content() const;
50+
size_t contentRlpListItemCount() const { return m_map.size() * 2 + 1; }
51+
void streamContent(RLPStream& _s) const;
52+
};
53+
54+
55+
ENR createV4ENR(Secret const& _secret, boost::asio::ip::address const& _ip, uint16_t _tcpPort, uint16_t _udpPort);
56+
57+
ENR parseV4ENR(RLP _rlp);
58+
59+
std::ostream& operator<<(std::ostream& _out, ENR const& _enr);
60+
61+
}
62+
}

libp2p/Host.cpp

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ bytes ReputationManager::data(SessionFace const& _s, string const& _sub) const
8282
return bytes();
8383
}
8484

85-
Host::Host(string const& _clientVersion, KeyPair const& _alias, NetworkConfig const& _n)
85+
Host::Host(
86+
string const& _clientVersion, pair<Secret, ENR> const& _secretAndENR, NetworkConfig const& _n)
8687
: Worker("p2p", 0),
8788
m_clientVersion(_clientVersion),
8889
m_netConfig(_n),
@@ -91,15 +92,17 @@ Host::Host(string const& _clientVersion, KeyPair const& _alias, NetworkConfig co
9192
// simultaneously
9293
m_tcp4Acceptor(m_ioService),
9394
m_runTimer(m_ioService),
94-
m_alias(_alias),
95+
m_alias{_secretAndENR.first},
96+
m_enr{_secretAndENR.second},
9597
m_lastPing(chrono::steady_clock::time_point::min()),
9698
m_capabilityHost(createCapabilityHost(*this))
9799
{
98100
cnetnote << "Id: " << id();
101+
cnetnote << "ENR: " << m_enr;
99102
}
100103

101-
Host::Host(string const& _clientVersion, NetworkConfig const& _n, bytesConstRef _restoreNetwork):
102-
Host(_clientVersion, networkAlias(_restoreNetwork), _n)
104+
Host::Host(string const& _clientVersion, NetworkConfig const& _n, bytesConstRef _restoreNetwork)
105+
: Host(_clientVersion, restoreENR(_restoreNetwork, _n), _n)
103106
{
104107
m_restoreNetwork = _restoreNetwork.toBytes();
105108
}
@@ -919,7 +922,12 @@ bytes Host::saveNetwork() const
919922
}
920923

921924
RLPStream ret(3);
922-
ret << dev::p2p::c_protocolVersion << m_alias.secret().ref();
925+
ret << dev::p2p::c_protocolVersion;
926+
927+
ret.appendList(2);
928+
ret << m_alias.secret().ref();
929+
m_enr.streamRLP(ret);
930+
923931
ret.appendList(count);
924932
if (!!count)
925933
ret.appendRaw(network.out(), count);
@@ -989,13 +997,36 @@ bool Host::peerSlotsAvailable(Host::PeerSlotType _type /*= Ingress*/)
989997
return peerCount() + m_pendingPeerConns.size() < peerSlots(_type);
990998
}
991999

992-
KeyPair Host::networkAlias(bytesConstRef _b)
1000+
std::pair<Secret, ENR> Host::restoreENR(bytesConstRef _b, NetworkConfig const& _netConfig)
9931001
{
9941002
RLP r(_b);
1003+
Secret secret;
9951004
if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt<unsigned>() >= 3)
996-
return KeyPair(Secret(r[1].toBytes()));
1005+
{
1006+
if (r[1].isList())
1007+
{
1008+
secret = Secret{r[1][0].toBytes()};
1009+
auto enrRlp = r[1][1];
1010+
1011+
return make_pair(secret, parseV4ENR(enrRlp));
1012+
}
1013+
1014+
// Support for older format without ENR
1015+
secret = Secret{r[1].toBytes()};
1016+
}
9971017
else
998-
return KeyPair::create();
1018+
{
1019+
// no private key found, create new one
1020+
secret = KeyPair::create().secret();
1021+
}
1022+
1023+
// TODO(gumb0): update ENR in case new address given in config
1024+
// https://github.com/ethereum/aleth/issues/5551
1025+
auto const address = _netConfig.publicIPAddress.empty() ?
1026+
bi::address{} :
1027+
bi::address::from_string(_netConfig.publicIPAddress);
1028+
return make_pair(
1029+
secret, createV4ENR(secret, address, _netConfig.listenPort, _netConfig.listenPort));
9991030
}
10001031

10011032
bool Host::nodeTableHasNode(Public const& _id) const

libp2p/Host.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include "Common.h"
8+
#include "ENR.h"
89
#include "Network.h"
910
#include "NodeTable.h"
1011
#include "Peer.h"
@@ -123,11 +124,8 @@ class Host: public Worker
123124

124125
/// Alternative constructor that allows providing the node key directly
125126
/// without restoring the network.
126-
Host(
127-
std::string const& _clientVersion,
128-
KeyPair const& _alias,
129-
NetworkConfig const& _n = NetworkConfig{}
130-
);
127+
Host(std::string const& _clientVersion, std::pair<Secret, ENR> const& _secretAndENR,
128+
NetworkConfig const& _n = NetworkConfig{});
131129

132130
/// Will block on network process events.
133131
virtual ~Host();
@@ -227,6 +225,9 @@ class Host: public Worker
227225
/// Get the node information.
228226
p2p::NodeInfo nodeInfo() const { return NodeInfo(id(), (networkConfig().publicIPAddress.empty() ? m_tcpPublic.address().to_string() : networkConfig().publicIPAddress), m_tcpPublic.port(), m_clientVersion); }
229227

228+
/// Get Ethereum Node Record of the host
229+
ENR enr() const { return m_enr; }
230+
230231
/// Apply function to each session
231232
void forEachPeer(
232233
std::string const& _capabilityName, std::function<bool(NodeID const&)> _f) const;
@@ -286,8 +287,8 @@ class Host: public Worker
286287
/// Shutdown network. Not thread-safe; to be called only by worker.
287288
virtual void doneWorking();
288289

289-
/// Get or create host identifier (KeyPair).
290-
static KeyPair networkAlias(bytesConstRef _b);
290+
/// Get or create host's Ethereum Node record.
291+
std::pair<Secret, ENR> restoreENR(bytesConstRef _b, NetworkConfig const& _networkConfig);
291292

292293
bool nodeTableHasNode(Public const& _id) const;
293294
Node nodeFromNodeTable(Public const& _id) const;
@@ -334,7 +335,9 @@ class Host: public Worker
334335
std::set<Peer*> m_pendingPeerConns; /// Used only by connect(Peer&) to limit concurrently connecting to same node. See connect(shared_ptr<Peer>const&).
335336

336337
bi::tcp::endpoint m_tcpPublic; ///< Our public listening endpoint.
337-
KeyPair m_alias; ///< Alias for network communication. Network address is k*G. k is key material. TODO: Replace KeyPair.
338+
/// Alias for network communication.
339+
KeyPair m_alias;
340+
ENR m_enr;
338341
std::shared_ptr<NodeTable> m_nodeTable; ///< Node table (uses kademlia-like discovery).
339342
mutable std::mutex x_nodeTable;
340343
std::shared_ptr<NodeTable> nodeTable() const { Guard l(x_nodeTable); return m_nodeTable; }

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ set(unittest_sources
2525

2626
unittests/libp2p/capability.cpp
2727
unittests/libp2p/eip-8.cpp
28+
unittests/libp2p/ENRTest.cpp
2829
unittests/libp2p/rlpx.cpp
2930

3031
unittests/libweb3core/memorydb.cpp

0 commit comments

Comments
 (0)