Skip to content

discv5/discv5_client.hpp

Namespaces

Name
discv5

Classes

Name
class discv5::discv5_client
Discovery v5 protocol client.

Types

Name
using asio::ip::udp udp
using std::shared_ptr< spdlog::logger > Logger

Functions

Name
std::shared_ptr< spdlog::logger > createLogger(const std::string & tag, const std::string & basepath ="")
Create a logger instance.

Types Documentation

using udp

using discv5::udp = asio::ip::udp;

using Logger

using rlp::base::Logger = std::shared_ptr<spdlog::logger>;

Functions Documentation

function createLogger

std::shared_ptr< spdlog::logger > createLogger(
    const std::string & tag,
    const std::string & basepath =""
)

Create a logger instance.

Parameters:

  • tag Tagging name for identifying logger.
  • basepath Optional base path for log output (platform dependent).

Return: Logger object.

Source code

// Copyright 2025 GeniusVentures
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <array>
#include <atomic>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>

#include <base/rlp-logger.hpp>
#include <discv5/discv5_crawler.hpp>
#include <discv5/discv5_error.hpp>
#include <discv5/discv5_types.hpp>

namespace discv5
{

using rlp::base::Logger;
using rlp::base::createLogger;

namespace asio = boost::asio;
using udp = asio::ip::udp;

// ---------------------------------------------------------------------------
// discv5_client
// ---------------------------------------------------------------------------

class discv5_client
{
public:
    explicit discv5_client(asio::io_context& io_context, const discv5Config& config);

    ~discv5_client();

    // Non-copyable, non-movable.
    discv5_client(const discv5_client&)            = delete;
    discv5_client& operator=(const discv5_client&) = delete;
    discv5_client(discv5_client&&)                 = delete;
    discv5_client& operator=(discv5_client&&)      = delete;

    // -----------------------------------------------------------------------
    // Configuration
    // -----------------------------------------------------------------------

    void add_bootnode(const std::string& enr_uri) noexcept;

    void set_peer_discovered_callback(PeerDiscoveredCallback callback) noexcept;

    void set_error_callback(ErrorCallback callback) noexcept;

    // -----------------------------------------------------------------------
    // Lifecycle
    // -----------------------------------------------------------------------

    VoidResult start() noexcept;

    void stop() noexcept;

    // -----------------------------------------------------------------------
    // Accessors
    // -----------------------------------------------------------------------

    [[nodiscard]] CrawlerStats stats() const noexcept;

    [[nodiscard]] const NodeId& local_node_id() const noexcept;

    [[nodiscard]] bool is_running() const noexcept;

    [[nodiscard]] uint16_t bound_port() const noexcept;

    [[nodiscard]] size_t received_packet_count() const noexcept;

    [[nodiscard]] size_t dropped_undersized_packet_count() const noexcept;

    [[nodiscard]] size_t send_findnode_failure_count() const noexcept;

    [[nodiscard]] size_t whoareyou_packet_count() const noexcept;

    [[nodiscard]] size_t handshake_packet_count() const noexcept;

    [[nodiscard]] size_t outbound_handshake_attempt_count() const noexcept;

    [[nodiscard]] size_t outbound_handshake_failure_count() const noexcept;

    [[nodiscard]] size_t inbound_handshake_reject_auth_count() const noexcept;

    [[nodiscard]] size_t inbound_handshake_reject_challenge_count() const noexcept;

    [[nodiscard]] size_t inbound_handshake_reject_record_count() const noexcept;

    [[nodiscard]] size_t inbound_handshake_reject_crypto_count() const noexcept;

    [[nodiscard]] size_t inbound_handshake_reject_decrypt_count() const noexcept;

    [[nodiscard]] size_t inbound_handshake_seen_count() const noexcept;

    [[nodiscard]] size_t inbound_message_seen_count() const noexcept;

    [[nodiscard]] size_t inbound_message_decrypt_fail_count() const noexcept;

    [[nodiscard]] size_t nodes_packet_count() const noexcept;

private:
    // -----------------------------------------------------------------------
    // Internal coroutines
    // -----------------------------------------------------------------------

    void receive_loop(asio::yield_context yield);

    void crawler_loop(asio::yield_context yield);

    void handle_packet(
        const uint8_t*     data,
        size_t             length,
        const udp::endpoint& sender) noexcept;

    VoidResult send_findnode(const ValidatedPeer& peer, asio::yield_context yield);

    VoidResult send_packet(
        const std::vector<uint8_t>& packet,
        const ValidatedPeer& peer,
        asio::yield_context yield);

    VoidResult send_whoareyou(
        const udp::endpoint& sender,
        const std::array<uint8_t, kKeccak256Bytes>& remote_node_addr,
        const std::array<uint8_t, kGcmNonceBytes>& request_nonce,
        asio::yield_context yield);

    VoidResult handle_findnode_request(
        const std::vector<uint8_t>& req_id,
        const udp::endpoint& sender,
        asio::yield_context yield);

    Result<std::vector<uint8_t>> build_local_enr() noexcept;

    // -----------------------------------------------------------------------
    // Members
    // -----------------------------------------------------------------------

    struct SessionState
    {
        std::array<uint8_t, 16U> write_key{};
        std::array<uint8_t, 16U> read_key{};
        std::array<uint8_t, kKeccak256Bytes> remote_node_addr{};
        NodeId remote_node_id{};
        std::vector<uint8_t> last_req_id{};
    };

    struct PendingRequest
    {
        ValidatedPeer peer{};
        std::vector<uint8_t> req_id{};
        std::array<uint8_t, kGcmNonceBytes> request_nonce{};
        std::vector<uint8_t> challenge_data{};
        std::array<uint8_t, kWhoareyouIdNonceBytes> id_nonce{};
        uint64_t record_seq{};
        bool have_challenge{false};
    };

    struct ChallengeState
    {
        std::array<uint8_t, kKeccak256Bytes> remote_node_addr{};
        std::vector<uint8_t> challenge_data{};
        std::array<uint8_t, kGcmNonceBytes> request_nonce{};
        std::array<uint8_t, kWhoareyouIdNonceBytes> id_nonce{};
        uint64_t record_seq{};
    };

    asio::io_context& io_context_;
    discv5Config      config_;
    udp::socket       socket_;
    discv5_crawler    crawler_;
    Logger            logger_ = createLogger("discv5");

    std::unordered_map<std::string, SessionState> sessions_;
    std::unordered_map<std::string, PendingRequest> pending_requests_;
    std::unordered_map<std::string, ChallengeState> sent_challenges_;

    std::atomic<bool>   running_{false};
    std::atomic<size_t> received_packets_{0U};
    std::atomic<size_t> dropped_undersized_packets_{0U};
    std::atomic<size_t> send_findnode_failures_{0U};
    std::atomic<size_t> whoareyou_packets_{0U};
    std::atomic<size_t> handshake_packets_{0U};
    std::atomic<size_t> outbound_handshake_attempts_{0U};
    std::atomic<size_t> outbound_handshake_failures_{0U};
    std::atomic<size_t> inbound_handshake_reject_auth_{0U};
    std::atomic<size_t> inbound_handshake_reject_challenge_{0U};
    std::atomic<size_t> inbound_handshake_reject_record_{0U};
    std::atomic<size_t> inbound_handshake_reject_crypto_{0U};
    std::atomic<size_t> inbound_handshake_reject_decrypt_{0U};
    std::atomic<size_t> inbound_handshake_seen_{0U};
    std::atomic<size_t> inbound_message_seen_{0U};
    std::atomic<size_t> inbound_message_decrypt_fail_{0U};
    std::atomic<size_t> nodes_packets_{0U};
};

} // namespace discv5

Updated on 2026-04-13 at 23:22:46 -0700