Skip to content

eth/rpc_manager.hpp

Namespaces

Name
eth
eth::rpc

Classes

Name
struct eth::rpc::RpcEndpointError
struct eth::rpc::RpcEndpoint
struct eth::rpc::RpcEndpointGroup
class eth::rpc::RpcEndpointPool
class eth::rpc::RpcManager
struct eth::rpc::RpcReceiptSourceHandle

Types

Name
enum class RpcEndpointState
enum class RpcEndpointErrorCode
template <typename T >
using outcome::result< T, RpcEndpointError, outcome::policy::all_narrow >
RpcResult
using std::function< std::optional< std::string >(std::string_view)> RpcEnvLookup

Functions

Name
const char * to_string(RpcEndpointErrorCode code)
const char * to_string(RpcEndpointState state)
RpcResult< std::string > render_rpc_endpoint_url(const RpcEndpointConfig & config, RpcEnvLookup env_lookup ={})
Resolve a URL template into a concrete endpoint URL.
RpcResult< RpcEndpoint > build_rpc_endpoint(const RpcEndpointConfig & config, RpcEnvLookup env_lookup)
Materialize a runtime endpoint from configuration.
std::vector< RpcEndpointGroup > group_rpc_endpoints(const RpcManagerConfig & config, RpcEnvLookup env_lookup)
Group endpoints by chain name and chain id with deterministic ordering.
std::optional< RpcReceiptSourceHandle > make_receipt_source(RpcManager & manager, std::string chain_name, uint64_t chain_id, FinalityPolicy finality_policy)

Types Documentation

enum RpcEndpointState

Enumerator Value Description
kAvailable
kTemporarilyFailed
kDisabled

enum RpcEndpointErrorCode

Enumerator Value Description
kMissingApiKey
kInvalidTemplate

using RpcResult

template <typename T >
using eth::rpc::RpcResult = outcome::result<T, RpcEndpointError, outcome::policy::all_narrow>;

using RpcEnvLookup

using eth::rpc::RpcEnvLookup = std::function<std::optional<std::string>(std::string_view)>;

Functions Documentation

function to_string

const char * to_string(
    RpcEndpointErrorCode code
)

function to_string

const char * to_string(
    RpcEndpointState state
)

function render_rpc_endpoint_url

RpcResult< std::string > render_rpc_endpoint_url(
    const RpcEndpointConfig & config,
    RpcEnvLookup env_lookup ={}
)

Resolve a URL template into a concrete endpoint URL.

The only supported placeholder is {key}.

function build_rpc_endpoint

RpcResult< RpcEndpoint > build_rpc_endpoint(
    const RpcEndpointConfig & config,
    RpcEnvLookup env_lookup
)

Materialize a runtime endpoint from configuration.

function group_rpc_endpoints

std::vector< RpcEndpointGroup > group_rpc_endpoints(
    const RpcManagerConfig & config,
    RpcEnvLookup env_lookup
)

Group endpoints by chain name and chain id with deterministic ordering.

function make_receipt_source

std::optional< RpcReceiptSourceHandle > make_receipt_source(
    RpcManager & manager,
    std::string chain_name,
    uint64_t chain_id,
    FinalityPolicy finality_policy
)

Source code

// Copyright 2026 Genius Ventures, Inc.
// SPDX-License-Identifier: MIT

#ifndef EVMRELAY_INCLUDE_ETH_RPC_MANAGER_HPP
#define EVMRELAY_INCLUDE_ETH_RPC_MANAGER_HPP

#include <eth/finality_policy.hpp>
#include <eth/rpc_manager_config.hpp>
#include <eth/rpc_receipt_source.hpp>
#include <eth/rpc_http_transport.hpp>
#include <boost/outcome/result.hpp>

#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace eth::rpc {

namespace outcome = BOOST_OUTCOME_V2_NAMESPACE;

enum class RpcEndpointState
{
    kAvailable,
    kTemporarilyFailed,
    kDisabled
};

enum class RpcEndpointErrorCode
{
    kMissingApiKey,
    kInvalidTemplate
};

struct RpcEndpointError
{
    RpcEndpointErrorCode code = RpcEndpointErrorCode::kInvalidTemplate;
    std::string          detail;
};

template <typename T>
using RpcResult = outcome::result<T, RpcEndpointError, outcome::policy::all_narrow>;

struct RpcEndpoint
{
    std::string chain_name;
    uint64_t    chain_id = 0;
    std::string url;
    uint32_t    priority = 0;
    uint32_t    weight = 0;
    uint32_t    rate_limit_per_second = 0;
    bool        is_paid = false;
    bool        is_public = true;
    bool        verified = false;
    RpcEndpointState state = RpcEndpointState::kAvailable;

    std::chrono::steady_clock::time_point last_failure_time{};
    uint32_t                               failure_count = 0;
    std::chrono::steady_clock::time_point backoff_until{};
};

struct RpcEndpointGroup
{
    std::string            chain_name;
    uint64_t               chain_id = 0;
    std::vector<RpcEndpoint> endpoints;
};

using RpcEnvLookup = std::function<std::optional<std::string>(std::string_view)>;

[[nodiscard]] const char* to_string(RpcEndpointErrorCode code) noexcept;
[[nodiscard]] const char* to_string(RpcEndpointState state) noexcept;

[[nodiscard]] RpcResult<std::string> render_rpc_endpoint_url(
    const RpcEndpointConfig& config,
    RpcEnvLookup             env_lookup = {});

[[nodiscard]] RpcResult<RpcEndpoint> build_rpc_endpoint(
    const RpcEndpointConfig& config,
    RpcEnvLookup             env_lookup = {});

[[nodiscard]] std::vector<RpcEndpointGroup> group_rpc_endpoints(
    const RpcManagerConfig& config,
    RpcEnvLookup            env_lookup = {});

class RpcEndpointPool
{
public:
    static constexpr auto kMaxBackoff = std::chrono::seconds(60);
    static constexpr auto kBaseBackoff = std::chrono::seconds(1);
    static constexpr auto kEscalationWindow = std::chrono::minutes(5);
    static constexpr uint32_t kEscalationThreshold = 3;

    RpcEndpointPool() = default;
    explicit RpcEndpointPool(std::vector<RpcEndpoint> endpoints);

    [[nodiscard]] const std::vector<RpcEndpoint>& endpoints() const noexcept
    {
        return endpoints_;
    }

    [[nodiscard]] std::vector<RpcEndpoint>& endpoints() noexcept
    {
        return endpoints_;
    }

    [[nodiscard]] std::optional<std::reference_wrapper<RpcEndpoint>> next_endpoint();
    [[nodiscard]] std::optional<std::reference_wrapper<const RpcEndpoint>> next_endpoint() const;

    void mark_temporary_failure(std::string_view url);
    void disable(std::string_view url);
    void reset_temporary_failures();

private:
    [[nodiscard]] static bool is_usable(const RpcEndpoint& endpoint) noexcept;

    std::vector<RpcEndpoint> endpoints_;
};

class RpcManager
{
public:
    explicit RpcManager(
        RpcManagerConfig config,
        RpcEnvLookup     env_lookup = {});

    [[nodiscard]] const std::vector<RpcEndpointGroup>& endpoint_groups() const noexcept
    {
        return groups_;
    }

    [[nodiscard]] std::optional<std::reference_wrapper<RpcEndpointPool>> pool(
        std::string_view chain_name,
        uint64_t         chain_id);
    [[nodiscard]] std::optional<std::reference_wrapper<const RpcEndpointPool>> pool(
        std::string_view chain_name,
        uint64_t         chain_id) const;

private:
    struct PoolEntry
    {
        RpcEndpointGroup group;
        RpcEndpointPool  pool;
    };

    [[nodiscard]] static std::string make_chain_key(std::string_view chain_name, uint64_t chain_id);

    std::vector<RpcEndpointGroup> groups_;
    std::vector<PoolEntry>        pools_;
};

struct RpcReceiptSourceHandle
{
    std::unique_ptr<RpcHttpTransport>  transport;
    std::unique_ptr<RpcReceiptSource>  source;
};

[[nodiscard]] std::optional<RpcReceiptSourceHandle> make_receipt_source(
    RpcManager&   manager,
    std::string   chain_name,
    uint64_t      chain_id,
    FinalityPolicy finality_policy = {});

} // namespace eth::rpc

#endif // EVMRELAY_INCLUDE_ETH_RPC_MANAGER_HPP

Updated on 2026-06-05 at 17:22:19 -0700