Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/configurator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ namespace lean::app {
std::cout << "Lean-node version " << buildVersion() << '\n';
std::cout << cli_options_ << '\n';
std::println(std::cout, "Other commands:");
std::println(std::cout, " qlean key generate-node-key");
std::println(std::cout, " {} key generate-node-key", argv_[0]);
std::println(std::cout, " {} generate-genesis", argv_[0]);
return true;
}

Expand Down
104 changes: 104 additions & 0 deletions src/commands/generate_genesis.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <filesystem>
#include <fstream>
#include <print>

#include <yaml-cpp/yaml.h>

#include "utils/sample_peer.hpp"

inline int cmdGenerateGenesis(auto &&getArg) {
auto cmd = [](std::filesystem::path genesis_directory,
size_t validator_count,
bool shadow) {
auto build_yaml = [](std::filesystem::path path, auto &&build) {
std::ofstream file{path};
YAML::Node yaml;
build(yaml);
file << yaml << "\n";
file.close();
};

auto now = shadow
? std::chrono::seconds{946684800}
: std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
uint64_t genesis_time = (now + std::chrono::seconds{3}).count();

std::filesystem::create_directories(genesis_directory);

build_yaml(genesis_directory / "config.yaml", [&](YAML::Node &yaml) {
yaml["GENESIS_TIME"] = genesis_time;
yaml["VALIDATOR_COUNT"] = validator_count;
});

auto node_id = [](size_t index) { return std::format("node_{}", index); };
std::vector<lean::SamplePeer> peers;
for (size_t index = 0; index < validator_count; ++index) {
peers.emplace_back(index, shadow);
}

for (auto &peer : peers) {
std::ofstream node_key{genesis_directory
/ std::format("{}.key", node_id(peer.index))};
std::println(node_key,
"{}",
qtils::ByteView{peer.keypair.privateKey.data}.toHex());
node_key.close();
}

build_yaml(genesis_directory / "nodes.yaml", [&](YAML::Node &yaml) {
for (auto &peer : peers) {
yaml.push_back(peer.enr);
}
});

build_yaml(genesis_directory / "validators.yaml", [&](YAML::Node &yaml) {
for (auto &peer : peers) {
yaml[node_id(peer.index)].push_back(peer.index);
}
});

build_yaml(genesis_directory / "validator-config.yaml",
[&](YAML::Node &yaml) {
yaml["shuffle"] = "roundrobin";
for (auto &peer : peers) {
YAML::Node yaml_peer;
yaml_peer["name"] = node_id(peer.index);
yaml_peer["privkey"] =
qtils::ByteView{peer.keypair.privateKey.data}.toHex();
auto yaml_enr = yaml_peer["enrFields"];
yaml_enr["ip"] = lean::enr::toString(peer.enr_ip);
yaml_enr["quic"] = peer.port;
yaml_peer["count"] = 1;
yaml["validators"].push_back(yaml_peer);
}
});
};
if (auto arg_2 = getArg(2)) {
std::filesystem::path genesis_directory{*arg_2};
if (auto arg_3 = getArg(3)) {
size_t validator_count = std::stoul(std::string{*arg_3});
if (validator_count != 0) {
auto arg_4 = getArg(4);
auto shadow = arg_4 == "shadow";
if (not arg_4 or shadow) {
cmd(genesis_directory, validator_count, shadow);
return EXIT_SUCCESS;
}
}
}
}
std::println(std::cerr,
"Usage: {} generate-genesis (genesis_directory) "
"(validator_count) (shadow?)",
getArg(0).value());
return EXIT_FAILURE;
}
17 changes: 8 additions & 9 deletions src/executable/lean_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
#include "app/application.hpp"
#include "app/configuration.hpp"
#include "app/configurator.hpp"
#include "executable/cmd_key_generate_node_key.hpp"
#include "commands/generate_genesis.hpp"
#include "commands/key_generate_node_key.hpp"
#include "injector/node_injector.hpp"
#include "loaders/loader.hpp"
#include "log/logger.hpp"
Expand Down Expand Up @@ -130,14 +131,12 @@ int main(int argc, const char **argv, const char **env) {
return EXIT_FAILURE;
}

if (getArg(1) == "key") {
if (getArg(2) == "generate-node-key") {
cmdKeyGenerateNodeKey();
return EXIT_SUCCESS;
}
std::println(std::cerr, "Expected one of following commands:");
std::println(std::cerr, " qlean key generate-node-key");
return EXIT_FAILURE;
if (getArg(1) == "key" and getArg(2) == "generate-node-key") {
cmdKeyGenerateNodeKey();
return EXIT_SUCCESS;
}
if (getArg(1) == "generate-genesis") {
return cmdGenerateGenesis(getArg);
}

auto app_configurator =
Expand Down
92 changes: 76 additions & 16 deletions src/serde/enr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

#include <bit>

#include <boost/endian/conversion.hpp>
#include <cppcodec/base64_url_unpadded.hpp>
#include <libp2p/crypto/secp256k1_provider/secp256k1_provider_impl.hpp>
#include <libp2p/crypto/sha/keccak.hpp>
#include <libp2p/peer/peer_id_from.hpp>
#include <qtils/bytestr.hpp>

Expand Down Expand Up @@ -252,20 +255,36 @@ namespace lean::rlp {

namespace lean::enr {
enum class Error {
EXPECTED_SECP256K1_KEYPAIR,
INVALID_PREFIX,
INVALID_ID,
SIGNATURE_VERIFICATION_FAILED,
};
Q_ENUM_ERROR_CODE(Error) {
using E = decltype(e);
switch (e) {
case E::EXPECTED_SECP256K1_KEYPAIR:
return "Expected secp256k1 keypair";
case E::INVALID_PREFIX:
return "Invalid ENR prefix";
case E::INVALID_ID:
return "Invalid ENR id";
case E::SIGNATURE_VERIFICATION_FAILED:
return "Signature verification failed";
}
abort();
}

Ip makeIp(uint32_t i) {
Ip ip;
boost::endian::store_big_u32(ip.data(), i);
return ip;
}

std::string toString(const Ip &ip) {
return std::format("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]);
}

libp2p::PeerId Enr::peerId() const {
return libp2p::peerIdFromSecp256k1(public_key);
}
Expand All @@ -279,11 +298,8 @@ namespace lean::enr {
libp2p::Multiaddress Enr::connectAddress() const {
auto &ip = this->ip.value();
return libp2p::Multiaddress::create(
std::format("/ip4/{}.{}.{}.{}/udp/{}/quic-v1/p2p/{}",
ip[0],
ip[1],
ip[2],
ip[3],
std::format("/ip4/{}/udp/{}/quic-v1/p2p/{}",
toString(ip),
port.value(),
peerId().toBase58()))
.value();
Expand All @@ -293,6 +309,24 @@ namespace lean::enr {
return {peerId(), {connectAddress()}};
}

inline void encodeContent(rlp::Encoder &rlp, const Enr &enr) {
rlp.uint(enr.sequence);
rlp.str("id");
rlp.str("v4");
rlp.str("ip");
rlp.bytes(enr.ip.value());
rlp.str("quic");
rlp.uint(enr.port.value());
rlp.str("secp256k1");
rlp.bytes(enr.public_key);
}

qtils::ByteVec Enr::signable() const {
rlp::Encoder rlp;
encodeContent(rlp, *this);
return rlp.list();
}

outcome::result<Enr> decode(std::string_view str) {
constexpr std::string_view s_enr{"enr:"};
if (not str.starts_with(s_enr)) {
Expand Down Expand Up @@ -333,22 +367,48 @@ namespace lean::enr {
BOOST_OUTCOME_TRY(enr.port, kv_quic->second.uint<Port>());
}

libp2p::crypto::secp256k1::Secp256k1ProviderImpl secp256k1{nullptr};
BOOST_OUTCOME_TRY(
auto valid_signature,
secp256k1.verifyCompact(libp2p::Keccak::hash(enr.signable()),
enr.signature,
enr.public_key));
if (not valid_signature) {
return Error::SIGNATURE_VERIFICATION_FAILED;
}

return enr;
}

std::string encode(const Secp256k1PublicKey &public_key, Port port) {
Enr enr{Secp256k1Signature{}, 1, public_key, Ip{127, 0, 0, 1}, port};
outcome::result<std::string> encode(const libp2p::crypto::KeyPair &keypair,
Ip ip,
Port port) {
if (keypair.privateKey.type != libp2p::crypto::Key::Type::Secp256k1) {
return Error::EXPECTED_SECP256K1_KEYPAIR;
}
if (keypair.publicKey.type != libp2p::crypto::Key::Type::Secp256k1) {
return Error::EXPECTED_SECP256K1_KEYPAIR;
}
BOOST_OUTCOME_TRY(auto private_key,
Secp256k1PrivateKey::fromSpan(keypair.privateKey.data));
BOOST_OUTCOME_TRY(auto public_key,
Secp256k1PublicKey::fromSpan(keypair.publicKey.data));

Enr enr{
.sequence = 1,
.public_key = public_key,
.ip = ip,
.port = port,
};

libp2p::crypto::secp256k1::Secp256k1ProviderImpl secp256k1{nullptr};
enr.signature = Secp256k1Signature{
secp256k1.signCompact(libp2p::Keccak::hash(enr.signable()), private_key)
.value()};

rlp::Encoder rlp;
rlp.bytes(enr.signature);
rlp.uint(enr.sequence);
rlp.str("id");
rlp.str("v4");
rlp.str("ip");
rlp.bytes(enr.ip.value());
rlp.str("quic");
rlp.uint(enr.port.value());
rlp.str("secp256k1");
rlp.bytes(enr.public_key);
encodeContent(rlp, enr);
return "enr:" + cppcodec::base64_url_unpadded::encode(rlp.list());
}
} // namespace lean::enr
14 changes: 13 additions & 1 deletion src/serde/enr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@
#include <libp2p/peer/peer_info.hpp>
#include <qtils/bytes.hpp>

namespace libp2p::crypto {
struct KeyPair;
} // namespace libp2p::crypto

namespace lean::enr {
using Secp256k1Signature = qtils::ByteArr<64>;
using Sequence = uint64_t;
using Secp256k1PrivateKey = qtils::ByteArr<32>;
using Secp256k1PublicKey = qtils::ByteArr<33>;
using Ip = qtils::ByteArr<4>;
using Port = uint16_t;

Ip makeIp(uint32_t i);
std::string toString(const Ip &ip);

struct Enr {
Secp256k1Signature signature;
Sequence sequence;
Expand All @@ -30,9 +38,13 @@ namespace lean::enr {
libp2p::Multiaddress listenAddress() const;
libp2p::Multiaddress connectAddress() const;
libp2p::PeerInfo connectInfo() const;

qtils::ByteVec signable() const;
};

outcome::result<Enr> decode(std::string_view str);

std::string encode(const Secp256k1PublicKey &public_key, Port port);
outcome::result<std::string> encode(const libp2p::crypto::KeyPair &keypair,
Ip ip,
Port port);
} // namespace lean::enr
27 changes: 19 additions & 8 deletions src/utils/sample_peer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,25 @@

namespace lean {
struct SamplePeer : libp2p::SamplePeer {
SamplePeer(size_t index) : libp2p::SamplePeer{makeSecp256k1(index)} {}

std::string enr() const {
enr::Secp256k1PublicKey public_key;
assert(keypair.publicKey.data.size() == public_key.size());
memcpy(
public_key.data(), keypair.publicKey.data.data(), public_key.size());
return enr::encode(public_key, port);
static enr::Ip makeIp(size_t index, bool shadow) {
return shadow ? enr::makeIp((10 << 24) + index) : enr::Ip{127, 0, 0, 1};
}

SamplePeer(size_t index, bool shadow)
: libp2p::SamplePeer{
index,
enr::toString(makeIp(index, shadow)),
samplePort(index),
Secp256k1,
},
enr_ip{makeIp(index,shadow)},
enr{enr::encode(
keypair,
enr_ip,
port
).value()} {}

enr::Ip enr_ip;
std::string enr;
};
} // namespace lean
4 changes: 2 additions & 2 deletions vcpkg-overlay/leanp2p/portfile.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO qdrvm/leanp2p
REF refs/tags/v0.0.2
SHA512 72a82d313ca84fe0095c755daa733cfdd09609080a6a9f627587355d57d0bf4a34448884282f29ebd1e23fcc21d8f5181ada63ff3d87772e060e5e9f554406a2
REF 56c1859237f454e6e029a54b0d71f3d6fb420404
SHA512 19fce0c1463e85aff685634f2c3d8da17ff8899c2490e7d9f579818006fedd505b26a4b6b42eb5378fe0c494aceb4a7826a52fee6c29c9f97a6bcedb5a4307a5
)
vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}")
vcpkg_cmake_install()
Expand Down
Loading