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

ENR record for host #5537

Merged
merged 13 commits into from
Apr 10, 2019
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [1.7.0] - Unreleased

- Added: [#5537](https://github.com/ethereum/aleth/pull/5537) Creating Ethereum Node Record (ENR) at program start.

[1.6.0]: https://github.com/ethereum/aleth/compare/v1.6.0-alpha.1...master

## [1.6.0] - Unreleased

- 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.
Expand All @@ -17,4 +23,5 @@
- Fixed: [#5539](https://github.com/ethereum/aleth/pull/5539) Fix logic for determining if dao hard fork block header should be requested.
- Fixed: [#5547](https://github.com/ethereum/aleth/pull/5547) Fix unnecessary slow-down of eth_flush RPC method.

[1.6.0]: https://github.com/ethereum/aleth/compare/v1.6.0-alpha.1...master
[1.6.0]: https://github.com/ethereum/aleth/compare/v1.6.0-alpha.1...release/1.6
[1.7.0]: https://github.com/ethereum/aleth/compare/release/1.6...master
6 changes: 6 additions & 0 deletions libdevcore/CommonData.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,10 @@ bool contains(std::set<V> const& _set, V const& _v)
{
return _set.find(_v) != _set.end();
}

template <class K, class V>
bool contains(std::map<K, V> const& _map, K const& _k)
{
return _map.find(_k) != _map.end();
}
}
150 changes: 150 additions & 0 deletions libp2p/ENR.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Aleth: Ethereum C++ client, tools and libraries.
// Copyright 2019 Aleth Authors.
// Licensed under the GNU General Public License, Version 3.

#include "ENR.h"
#include <libdevcore/SHA3.h>

namespace dev
{
namespace p2p
{
namespace
{
constexpr char c_keyID[] = "id";
constexpr char c_keySec256k1[] = "secp256k1";
constexpr char c_keyIP[] = "ip";
constexpr char c_keyTCP[] = "tcp";
constexpr char c_keyUDP[] = "udp";
constexpr char c_IDV4[] = "v4";
constexpr size_t c_ENRMaxSizeBytes = 300;


// Address can be either boost::asio::ip::address_v4 or boost::asio::ip::address_v6
template <class Address>
bytes addressToBytes(Address const& _address)
{
auto const addressBytes = _address.to_bytes();
return bytes(addressBytes.begin(), addressBytes.end());
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
}
} // namespace

ENR::ENR(RLP _rlp, VerifyFunction const& _verifyFunction)
{
if (_rlp.data().size() > c_ENRMaxSizeBytes)
BOOST_THROW_EXCEPTION(ENRIsTooBig());

m_signature = _rlp[0].toBytes(RLP::VeryStrict);

m_seq = _rlp[1].toInt<uint64_t>(RLP::VeryStrict);

// read key-values into vector first, to check the order
std::vector<std::pair<std::string const, bytes>> keyValuePairs;
for (size_t i = 2; i < _rlp.itemCount(); i += 2)
{
auto const key = _rlp[i].toString(RLP::VeryStrict);
auto const value = _rlp[i + 1].data().toBytes();
keyValuePairs.push_back({key, value});
}

// transfer to map, this will order them
m_map.insert(keyValuePairs.begin(), keyValuePairs.end());

if (!std::equal(keyValuePairs.begin(), keyValuePairs.end(), m_map.begin()))
BOOST_THROW_EXCEPTION(ENRKeysAreNotUniqueSorted());

if (!_verifyFunction(m_map, dev::ref(m_signature), dev::ref(content())))
BOOST_THROW_EXCEPTION(ENRSignatureIsInvalid());
}

ENR::ENR(uint64_t _seq, std::map<std::string, bytes> const& _keyValuePairs,
SignFunction const& _signFunction)
: m_seq{_seq}, m_map{_keyValuePairs}, m_signature{_signFunction(dev::ref(content()))}
{
}

bytes ENR::content() const
{
RLPStream stream{contentRlpListItemCount()};
streamContent(stream);
return stream.out();
}


void ENR::streamRLP(RLPStream& _s) const
{
_s.appendList(contentRlpListItemCount() + 1);
_s << m_signature;
streamContent(_s);
}

void ENR::streamContent(RLPStream& _s) const
{
_s << m_seq;
for (auto const& keyValue : m_map)
{
_s << keyValue.first;
_s.appendRaw(keyValue.second);
}
}

ENR createV4ENR(Secret const& _secret, boost::asio::ip::address const& _ip, uint16_t _tcpPort, uint16_t _udpPort)
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
{
ENR::SignFunction signFunction = [&_secret](bytesConstRef _data) {
// dev::sign returns 65 bytes signature containing r,s,v values
Signature s = dev::sign(_secret, sha3(_data));
// The resulting 64-byte signature is encoded as the concatenation of the r and s signature values.
return bytes(&s[0], &s[64]);
};

PublicCompressed const publicKey = toPublicCompressed(_secret);

auto const address = _ip.is_v4() ? addressToBytes(_ip.to_v4()) : addressToBytes(_ip.to_v6());

// Values are of different types (string, bytes, uint16_t),
// so we store them as RLP representation
std::map<std::string, bytes> const keyValuePairs = {{c_keyID, rlp(c_IDV4)},
{c_keySec256k1, rlp(publicKey.asBytes())}, {c_keyIP, rlp(address)},
{c_keyTCP, rlp(_tcpPort)}, {c_keyUDP, rlp(_udpPort)}};

return ENR{0 /* sequence number */, keyValuePairs, signFunction};
}

ENR parseV4ENR(RLP _rlp)
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
{
ENR::VerifyFunction verifyFunction = [](std::map<std::string, bytes> const& _keyValuePairs,
bytesConstRef _signature, bytesConstRef _data) {
auto itID = _keyValuePairs.find(c_keyID);
if (itID == _keyValuePairs.end())
return false;
auto const id = RLP(itID->second).toString(RLP::VeryStrict);
if (id != c_IDV4)
return false;

auto itKey = _keyValuePairs.find(c_keySec256k1);
if (itKey == _keyValuePairs.end())
return false;

auto const key = RLP(itKey->second).toHash<PublicCompressed>(RLP::VeryStrict);
h512 const signature{_signature};

return dev::verify(key, signature, sha3(_data));
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
};

return ENR{_rlp, verifyFunction};
}

std::ostream& operator<<(std::ostream& _out, ENR const& _enr)
{
_out << "[ " << toHexPrefixed(_enr.signature()) << " seq=" << _enr.sequenceNumber() << " ";
for (auto const& keyValue : _enr.keyValuePairs())
{
_out << keyValue.first << "=";
_out << toHexPrefixed(RLP{keyValue.second}.toBytes()) << " ";
}
_out << "]";
return _out;
}

} // namespace p2p
} // namespace dev
62 changes: 62 additions & 0 deletions libp2p/ENR.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Aleth: Ethereum C++ client, tools and libraries.
// Copyright 2019 Aleth Authors.
// Licensed under the GNU General Public License, Version 3.

#pragma once

#include "Common.h"

namespace dev
{
namespace p2p
{
DEV_SIMPLE_EXCEPTION(ENRIsTooBig);
DEV_SIMPLE_EXCEPTION(ENRSignatureIsInvalid);
DEV_SIMPLE_EXCEPTION(ENRKeysAreNotUniqueSorted);

/// Class representing Ethereum Node Record - see EIP-778
class ENR
{
public:
// ENR class implementation is independent of Identity Scheme.
// Identity Scheme specifics are passed to ENR as functions.

// Sign function gets serialized ENR contents and signs it according to some Identity Scheme
using SignFunction = std::function<bytes(bytesConstRef)>;
// Verify function gets ENR key-value pairs, signature, and serialized content and validates the
// signature according to some Identity Scheme
using VerifyFunction =
std::function<bool(std::map<std::string, bytes> const&, bytesConstRef, bytesConstRef)>;

// Parse from RLP with given signature verification function
ENR(RLP _rlp, VerifyFunction const& _verifyFunction);
// Create with given sign function
ENR(uint64_t _seq, std::map<std::string, bytes> const& _keyValues,
SignFunction const& _signFunction);

uint64_t sequenceNumber() const { return m_seq; }
std::map<std::string, bytes> const& keyValuePairs() const { return m_map; }
bytes const& signature() const { return m_signature; }

// Serialize to given RLP stream
void streamRLP(RLPStream& _s) const;

private:
uint64_t m_seq = 0;
std::map<std::string, bytes> m_map;
bytes m_signature;

bytes content() const;
size_t contentRlpListItemCount() const { return m_map.size() * 2 + 1; }
void streamContent(RLPStream& _s) const;
};


ENR createV4ENR(Secret const& _secret, boost::asio::ip::address const& _ip, uint16_t _tcpPort, uint16_t _udpPort);

ENR parseV4ENR(RLP _rlp);

std::ostream& operator<<(std::ostream& _out, ENR const& _enr);

}
}
47 changes: 39 additions & 8 deletions libp2p/Host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ bytes ReputationManager::data(SessionFace const& _s, string const& _sub) const
return bytes();
}

Host::Host(string const& _clientVersion, KeyPair const& _alias, NetworkConfig const& _n)
Host::Host(
string const& _clientVersion, pair<Secret, ENR> const& _secretAndENR, NetworkConfig const& _n)
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
: Worker("p2p", 0),
m_clientVersion(_clientVersion),
m_netConfig(_n),
Expand All @@ -91,15 +92,17 @@ Host::Host(string const& _clientVersion, KeyPair const& _alias, NetworkConfig co
// simultaneously
m_tcp4Acceptor(m_ioService),
m_runTimer(m_ioService),
m_alias(_alias),
m_alias{_secretAndENR.first},
m_enr{_secretAndENR.second},
m_lastPing(chrono::steady_clock::time_point::min()),
m_capabilityHost(createCapabilityHost(*this))
{
cnetnote << "Id: " << id();
cnetnote << "ENR: " << m_enr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the interest of consistency should we call enr() here? (since we call id() rather than m_alias.pub())

}

Host::Host(string const& _clientVersion, NetworkConfig const& _n, bytesConstRef _restoreNetwork):
Host(_clientVersion, networkAlias(_restoreNetwork), _n)
Host::Host(string const& _clientVersion, NetworkConfig const& _n, bytesConstRef _restoreNetwork)
: Host(_clientVersion, restoreENR(_restoreNetwork, _n), _n)
{
m_restoreNetwork = _restoreNetwork.toBytes();
}
Expand Down Expand Up @@ -919,7 +922,12 @@ bytes Host::saveNetwork() const
}

RLPStream ret(3);
ret << dev::p2p::c_protocolVersion << m_alias.secret().ref();
ret << dev::p2p::c_protocolVersion;

ret.appendList(2);
ret << m_alias.secret().ref();
m_enr.streamRLP(ret);

ret.appendList(count);
if (!!count)
ret.appendRaw(network.out(), count);
Expand Down Expand Up @@ -989,13 +997,36 @@ bool Host::peerSlotsAvailable(Host::PeerSlotType _type /*= Ingress*/)
return peerCount() + m_pendingPeerConns.size() < peerSlots(_type);
}

KeyPair Host::networkAlias(bytesConstRef _b)
std::pair<Secret, ENR> Host::restoreENR(bytesConstRef _b, NetworkConfig const& _netConfig)
{
RLP r(_b);
Secret secret;
if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt<unsigned>() >= 3)
return KeyPair(Secret(r[1].toBytes()));
{
if (r[1].isList())
{
secret = Secret{r[1][0].toBytes()};
auto enrRlp = r[1][1];

return make_pair(secret, parseV4ENR(enrRlp));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std:: prefix (for make_pair)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has using namespace std, so I follow surrounding code's convention

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I called this out since I saw some use of std:: in the file...but if the std:: namespace is being imported then ultimately it doesn't matter.

}

// Support for older format without ENR
secret = Secret{r[1].toBytes()};
}
else
return KeyPair::create();
{
// no private key found, create new one
secret = KeyPair::create().secret();
}

// TODO(gumb0): update ENR in case new address given in config
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
// https://github.com/ethereum/aleth/issues/5551
auto const address = _netConfig.publicIPAddress.empty() ?
bi::address{} :
bi::address::from_string(_netConfig.publicIPAddress);
return make_pair(
halfalicious marked this conversation as resolved.
Show resolved Hide resolved
secret, createV4ENR(secret, address, _netConfig.listenPort, _netConfig.listenPort));
}

bool Host::nodeTableHasNode(Public const& _id) const
Expand Down
19 changes: 11 additions & 8 deletions libp2p/Host.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "Common.h"
#include "ENR.h"
#include "Network.h"
#include "NodeTable.h"
#include "Peer.h"
Expand Down Expand Up @@ -123,11 +124,8 @@ class Host: public Worker

/// Alternative constructor that allows providing the node key directly
/// without restoring the network.
Host(
std::string const& _clientVersion,
KeyPair const& _alias,
NetworkConfig const& _n = NetworkConfig{}
);
Host(std::string const& _clientVersion, std::pair<Secret, ENR> const& _secretAndENR,
NetworkConfig const& _n = NetworkConfig{});

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

/// Get Ethereum Node Record of the host
ENR enr() const { return m_enr; }

/// Apply function to each session
void forEachPeer(
std::string const& _capabilityName, std::function<bool(NodeID const&)> _f) const;
Expand Down Expand Up @@ -286,8 +287,8 @@ class Host: public Worker
/// Shutdown network. Not thread-safe; to be called only by worker.
virtual void doneWorking();

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

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

bi::tcp::endpoint m_tcpPublic; ///< Our public listening endpoint.
KeyPair m_alias; ///< Alias for network communication. Network address is k*G. k is key material. TODO: Replace KeyPair.
/// Alias for network communication.
KeyPair m_alias;
ENR m_enr;
std::shared_ptr<NodeTable> m_nodeTable; ///< Node table (uses kademlia-like discovery).
mutable std::mutex x_nodeTable;
std::shared_ptr<NodeTable> nodeTable() const { Guard l(x_nodeTable); return m_nodeTable; }
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(unittest_sources

unittests/libp2p/capability.cpp
unittests/libp2p/eip-8.cpp
unittests/libp2p/ENRTest.cpp
unittests/libp2p/rlpx.cpp

unittests/libweb3core/memorydb.cpp
Expand Down
Loading