Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: documented network config #35

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
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
62 changes: 55 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ use near_jsonrpc_client::JsonRpcClient;
use crate::errors::RetryError;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
/// Using this struct to configure RPC endpoints.
/// This is primary way to configure retry logic.
/// Configuration for a [NEAR RPC](https://docs.near.org/api/rpc/providers) endpoint with retry and backoff settings.
pub struct RPCEndpoint {
/// The URL of the RPC endpoint
pub url: url::Url,
/// Optional API key for authenticated requests
pub api_key: Option<crate::types::ApiKey>,
/// Number of consecutive failures to move on to the next endpoint.
pub retries: u8,
/// Whether to use exponential backoff between retries
///
/// The formula is `d = initial_sleep * factor^retry`
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's left unspecified what backoff behaviour will look like if exponential_backoff is false.
it can be defined more accurately with a doc comment or
by using an enum like the following instead of exponential_backoff: bool:

pub retry_algorithm: RetryAlgorithm,
....
pub enum RetryAlgorithm {
    ExponentialBackoff {
        initial_sleep: std::time::Duration,
        factor: u8,
    },
    SomeOtherWay { ... },
}

pub exponential_backoff: bool,
/// Multiplier for exponential backoff calculation
pub factor: u8,
Copy link
Collaborator

Choose a reason for hiding this comment

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

the backoff multiplier is somewhat simplejack instead of e.g. an f32 (where a factor can be 1.2f32),
but this is not necessarily a bad thing

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Don't love floats, to be honest

/// Base delay duration between retries
pub initial_sleep: std::time::Duration,
}

impl RPCEndpoint {
/// Constructs a new RPC endpoint configuration with default settings.
pub const fn new(url: url::Url) -> Self {
Self {
url,
Expand All @@ -28,10 +35,12 @@ impl RPCEndpoint {
}
}

/// Constructs default mainnet configuration.
pub fn mainnet() -> Self {
Self::new("https://archival-rpc.mainnet.near.org".parse().unwrap())
}

/// Constructs default testnet configuration.
pub fn testnet() -> Self {
Self::new("https://archival-rpc.testnet.near.org".parse().unwrap())
}
Expand Down Expand Up @@ -71,20 +80,49 @@ impl RPCEndpoint {
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
/// Configuration for a NEAR network including RPC endpoints and network-specific settings.
///
/// # Multiple RPC endpoints
///
/// This struct is used to configure multiple RPC endpoints for a NEAR network.
/// It allows for failover between endpoints in case of a failure.
///
///
/// ## Example
/// ```rust,no_run
/// use near_api::*;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let config = NetworkConfig {
/// rpc_endpoints: vec![RPCEndpoint::mainnet(), RPCEndpoint::new("https://near.lava.build".parse()?)],
/// ..NetworkConfig::mainnet()
/// };
/// # Ok(())
/// # }
/// ```
pub struct NetworkConfig {
/// Human readable name of the network (e.g. "mainnet", "testnet")
pub network_name: String,
/// List of [RPC endpoints](https://docs.near.org/api/rpc/providers) to use with failover
pub rpc_endpoints: Vec<RPCEndpoint>,
// https://github.com/near/near-cli-rs/issues/116
/// Account ID used for [linkdrop functionality](https://docs.near.org/build/primitives/linkdrop)
pub linkdrop_account_id: Option<near_primitives::types::AccountId>,
// https://docs.near.org/social/contract
/// Account ID of the [NEAR Social contract](https://docs.near.org/social/contract)
Copy link
Collaborator

Choose a reason for hiding this comment

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

link for NEAR Social contract appears to be broken

pub near_social_db_contract_account_id: Option<near_primitives::types::AccountId>,
/// URL of the network's faucet service
pub faucet_url: Option<url::Url>,
/// URL for the [meta transaction relayer](https://docs.near.org/concepts/abstraction/relayers) service
pub meta_transaction_relayer_url: Option<url::Url>,
/// URL for the [fastnear](https://docs.near.org/tools/ecosystem-apis/fastnear-api) service.
///
/// Currently, unused. See [#30](https://github.com/near/near-api-rs/issues/30)
pub fastnear_url: Option<url::Url>,
/// Account ID of the [staking pools factory](https://github.com/near/core-contracts/tree/master/staking-pool-factory)
pub staking_pools_factory_account_id: Option<near_primitives::types::AccountId>,
}

impl NetworkConfig {
/// Constructs default mainnet configuration.
pub fn mainnet() -> Self {
Self {
network_name: "mainnet".to_string(),
Expand All @@ -94,18 +132,19 @@ impl NetworkConfig {
faucet_url: None,
meta_transaction_relayer_url: None,
fastnear_url: Some("https://api.fastnear.com/".parse().unwrap()),
staking_pools_factory_account_id: Some("pool.near".parse().unwrap()),
staking_pools_factory_account_id: Some("poolv1.near".parse().unwrap()),
}
}

/// Constructs default testnet configuration.
pub fn testnet() -> Self {
Self {
network_name: "testnet".to_string(),
rpc_endpoints: vec![RPCEndpoint::testnet()],
linkdrop_account_id: Some("testnet".parse().unwrap()),
near_social_db_contract_account_id: Some("v1.social08.testnet".parse().unwrap()),
faucet_url: Some("https://helper.nearprotocol.com/account".parse().unwrap()),
meta_transaction_relayer_url: Some("http://localhost:3030/relay".parse().unwrap()),
meta_transaction_relayer_url: None,
Copy link
Collaborator

Choose a reason for hiding this comment

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

somewhat unrelated, but bon can be used here instead of ..NetworkConfig::mainnet() syntax,

// $ cargo add bon
// bon = { version= "3.3.1", features = ["experimental-overwritable"]}
use bon::Builder;
use network_builder::{SetName, SetRpcStuff};

#[derive(Builder)]
struct Network {
    name: String,
    #[builder(overwritable)]
    rpc_stuff: String,
}

impl Network {
    pub fn testnet() -> NetworkBuilder<SetRpcStuff<SetName>> {
        Self::builder()
            .name("testnet".into())
            .rpc_stuff("my_fast_near_rpc".to_string())
    }
}

fn main() {
    {
        let network = Network::testnet().build();

        assert_eq!(network.name, "testnet");
        assert_eq!(network.rpc_stuff, "my_fast_near_rpc");
    }
    {
        let network = Network::testnet()
            .rpc_stuff("very_slow_another_rpc".to_owned())
            .build();

        assert_eq!(network.name, "testnet");
        assert_eq!(network.rpc_stuff, "very_slow_another_rpc");
    }
}

referencing elastio/bon#149 , as it links to experimental feature needed.

without experimental-overwritable it kind of not possible to achieve the same with #[builder(default)] on the same Network type as there're different defaults for different networks

Copy link
Collaborator

Choose a reason for hiding this comment

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

but this crate being a lib, it won't be too great to use a dependency with experimental feature, which can break on minor releases,

though pinning it with ~3.3.1 (only patch releases updates allowed) or =3.3.1 (fully pinned) would allow to avoid problems for downstream consumers of the library

Copy link
Collaborator Author

@akorchyn akorchyn Dec 27, 2024

Choose a reason for hiding this comment

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

To be honest, I don't really want ro pull some extra dep for that

Ideally is to remove as much deps as possible, and keep it as slim as possible

Copy link

@Veetaha Veetaha Dec 27, 2024

Choose a reason for hiding this comment

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

If you were to use bon builder for this, you could alternatively do it this way without the overwritable feature:

// A `#[derive(bon::Builder)]` here isn't required, but it's not prohibited!
// If you want to have both `Network::builder()` for regular code and
// `Network::testnet()` for test code, then both can coexist just fine
struct Network {
    name: String,
    rpc_stuff: String,
}

// Define a builder using the method syntax.
// There can be as many builders with as many different defaults as you need
// for different use cases.
#[bon::bon]
impl Network {
    #[builder]
    pub fn testnet(
        #[builder(default = "testnet".into())]
        name: String,
        #[builder(default = "my_fast_near_rpc".into())]
        rpc_stuff: String,
    ) -> Self {
        Self { name, rpc_stuff }
    }
}

// Both of these work as expected
Network::testnet().build();
Network::testnet().rpc_stuff("very_slow_another_rpc".to_owned()).build();

// If you were to add `#[derive(bon::Builder)]`, you'd also have this one.
// Here you'd probably make both `name` and `rpc_stuff` required:
// Network::builder().name("foo".into()).rpc_stuff("bar".into()).build();

cc @dj8yfo

Copy link
Collaborator

@dj8yfo dj8yfo Dec 28, 2024

Choose a reason for hiding this comment

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

@akorchyn well, this dependency has a useful function, it will be pulled in exactly for that and nothing more, it's really convenient to configure options with this stuff, i've tried. Besides Veetaha just showed how to do this with stable features of bon. Anyway, this was just a suggestion how stuff could be improved, not how it must be improved imo.
After having near-*-0.28 in, bon wouldn't be too much of additional weight, cannot compile near-workspaces tests --workspace with default number of builders, only small number -j2 works, othewise it hangs and is later killed with oom.

fastnear_url: None,
staking_pools_factory_account_id: Some("pool.f863973.m0".parse().unwrap()),
}
Expand Down Expand Up @@ -135,17 +174,21 @@ impl<T: near_workspaces::Network> From<near_workspaces::Worker<T>> for NetworkCo
linkdrop_account_id: None,
near_social_db_contract_account_id: None,
faucet_url: None,
meta_transaction_relayer_url: None,
fastnear_url: None,
meta_transaction_relayer_url: None,
staking_pools_factory_account_id: None,
}
}
}

#[derive(Debug)]
/// Represents the possible outcomes of a retryable operation.
pub enum RetryResponse<R, E> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this type doesn't really bubble up to be eventually visible in documentation, does it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For now, I just not really sure if it's needed to be public. But I kept documentation just in case I will decide to make it public

Copy link
Collaborator

Choose a reason for hiding this comment

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

actually , you could try adding

# checked to work on `bubbly_bub_test` crate
[package.metadata.docs.rs]
# Additional `RUSTDOCFLAGS` to set (default: [])
rustdoc-args = ["--document-private-items"]

in order to still have it published to documentation , while not bothering with reexports or otherwise organizing module structure.
Sometimes the comments from original author are very helpful if there's some additional insight in documentation besides self-evident description.

/// Operation succeeded with result R
Ok(R),
/// Operation failed with error E, should be retried
Retry(E),
/// Operation failed with critical error E, should not be retried
Critical(E),
}

Expand All @@ -158,6 +201,11 @@ impl<R, E> From<Result<R, E>> for RetryResponse<R, E> {
}
}

/// Retry a task with exponential backoff and failover.
///
/// # Arguments
/// * `network` - The network configuration to use for the retryable operation.
/// * `task` - The task to retry.
pub async fn retry<R, E, T, F>(network: NetworkConfig, mut task: F) -> Result<R, RetryError<E>>
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here, it doesn't really show up in documentation

where
F: FnMut(JsonRpcClient) -> T + Send,
Expand Down
Loading