1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! A dynamic routing provider for the Internet Computer (IC) Agent that enables resilient, adaptive request routing through API boundary nodes.
//!
//! The `DynamicRouteProvider` is an implementation of the [`RouteProvider`](super::RouteProvider) trait. It dynamically discovers and monitors API boundary nodes, filters out unhealthy nodes, and routes API calls across healthy nodes using configurable strategies such as round-robin or latency-based routing.
//! This ensures robust and performant interactions with the IC network by adapting to changes in node availability and topology.
//!
//! # Overview
//! The IC Agent is capable of dispatching API calls to destination endpoints exposing an [HTTPS interface](https://internetcomputer.org/docs/references/ic-interface-spec#http-interface). These endpoints can be:
//! 1. **Replica nodes**: part of the ICP.
//! 2. **API boundary nodes**: part of the ICP.
//! 3. **HTTP Gateways**: Third-party services that proxy requests to API boundary nodes, e.g., gateways hosted on the `ic0.app` domain.
//!
//! The Agent uses the [`RouteProvider`](super::RouteProvider) trait, namely its [`route()`](super::RouteProvider::route()) method to determine the destination endpoint for each call.
//! The `DynamicRouteProvider` is an implementation which is intended to be used only for option (2), it provides:
//! - **Automatic API Node Discovery**: periodically fetches the latest API boundary node topology.
//! - **Health Monitoring**: Continuously checks health of all nodes in the topology.
//! - **Latency-Based Routing**: Directs requests to healthy nodes by prioritizing low-latency nodes via weighted selection, with optional penalties if nodes are unavailable within a sliding time window.
//! - **Customizability**: Supports custom node fetchers, health checkers, and routing logic.
//! # Usage
//! The `DynamicRouteProvider` can be used standalone or injected into the agent to enable dynamic routing. There are several ways to instantiate it:
//! 1. **Via high-Level Agent API**: Initializes the agent with built-in dynamic routing. This method is user-friendly but provides limited customization options.
//! 2. **Via [`DynamicRouteProviderBuilder`](dynamic_route_provider::DynamicRouteProviderBuilder)**: Creates a customized `DynamicRouteProvider` with a specific routing strategy and parameters.
//!
//! This instance can be used standalone or integrated into the agent via [`AgentBuilder::with_route_provider()`](super::super::AgentBuilder::with_route_provider).
//! ## Example: High-Level Agent API
//! ```rust
//! use anyhow::Result;
//! use ic_agent::Agent;
//! use url::Url;
//!
//! #[tokio::main]
//! async fn main() -> Result<()> {
//! // Use the URL of an IC HTTP Gateway or even better - API boundary node as the initial seed
//! let seed_url = Url::parse("https://ic0.app")?;
//!
//! // The agent starts with the seed node and discovers healthy API nodes dynamically
//! // Until then, requests go through the seed, but only if it's healthy.
//! let agent = Agent::builder()
//! .with_url(seed_url)
//! .with_background_dynamic_routing()
//! .build()?;
//!
//! // ... use the agent for API calls
//!
//! Ok(())
//! }
//! ```
//! **Note**: In the example above, `ic0.app` is used as a seed for initial topology discovery. However, it is not a true seed, as it is not an API boundary node in the ICP topology.
//! It will be discarded after the first successful discovery.
//! ## Example: Customized instantiation
//! use std::sync::Arc;
//! use std::time::Duration;
//!
//! use anyhow::Result;
//! use ic_agent::{
//! agent::route_provider::{
//! dynamic_routing::{
//! dynamic_route_provider::{DynamicRouteProvider, DynamicRouteProviderBuilder},
//! node::Node,
//! snapshot::latency_based_routing::LatencyRoutingSnapshot,
//! },
//! RouteProvider,
//! },
//! Agent,
//! };
//!
//! #[tokio::main]
//! async fn main() -> Result<()> {
//! // The provider uses latency-based routing by default
//! // You can configure it using `DynamicRouteProviderBuilder` methods
//!
//! // Seed nodes for initial topology discovery
//! let seed_nodes = vec![
//! Node::new("ic0.app")?,
//! // Optional: add known API boundary nodes to improve resilience
//! // Node::new("<api-boundary-node-domain>")?,
//! ];
//!
//! // Build dynamic route provider with HTTP client
//! let http_client = Arc::new(reqwest::Client::new());
//! let route_provider = DynamicRouteProviderBuilder::new(seed_nodes, http_client, None)
//! // Set how often to fetch the latest API boundary node topology
//! .with_fetch_period(Duration::from_secs(10))
//! // Set how often to perform health checks on the API boundary nodes
//! .with_check_period(Duration::from_secs(2))
//! // Or optionally provide a custom node health checker implementation
//! // .with_checker(custom_checker)
//! // Or optionally provide a custom topology fetcher implementation
//! // .with_fetcher(custom_fetcher)
//! .build();
//!
//! // Start background tasks and wait for initial health checks
//! route_provider.start().await;
//!
//! // Advanced: Provide custom fetcher and checker implementations
//! // let route_provider = DynamicRouteProviderBuilder::from_components(
//! // seed_nodes,
//! // Arc::new(custom_fetcher),
//! // Arc::new(custom_checker),
//! // None, // or Some(k) to limit routing to top k nodes
//! // )
//! // .build();
//!
//! // Example: generate routing URLs
//! let url_1 = route_provider.route().expect("failed to get routing URL");
//! eprintln!("Generated URL: {url_1}");
//!
//! let url_2 = route_provider.route().expect("failed to get routing URL");
//! eprintln!("Generated URL: {url_2}");
//!
//! // Or inject route_provider into the agent for dynamic routing
//! let agent = Agent::builder()
//! .with_route_provider(route_provider)
//! .build()?;
//!
//! // ... use the agent for API calls
//!
//! Ok(())
//! }
//! ```
//! # Implementation Details
//! The `DynamicRouteProvider` spawns two background services:
//! 1. `NodesFetchActor`: Periodically fetches the latest API boundary node topology and sends updates to the `HealthManagerActor`.
//! 2. `HealthManagerActor`: Manages health checks for nodes, starts and stops `HealthCheckActor`s and updates the routing table (routing snapshot) with health information.
//!
//! These background services ensure the routing table remains up-to-date.
//! # Configuration
//! The [`DynamicRouteProviderBuilder`](dynamic_route_provider::DynamicRouteProviderBuilder) allows customized instantiation of `DynamicRouteProvider`:
//! - **Fetch Period**: How often to fetch node topology (default: 5 seconds).
//! - **Health Check Period**: How often to check node health (default: 1 second).
//! - **Nodes Fetcher**: Custom implementation of the [`Fetch`](nodes_fetch::Fetch) trait for node discovery.
//! - **Health Checker**: Custom implementation of the [`HealthCheck`](health_check::HealthCheck) trait for health monitoring.
//!
//! The provider uses latency-based routing by default.
//!
//! # Error Handling
//! Errors during node fetching or health checking are encapsulated in the [`DynamicRouteProviderError`](dynamic_route_provider::DynamicRouteProviderError) enum:
//! - `NodesFetchError`: Occurs when fetching the topology fails.
//! - `HealthCheckError`: Occurs when node health checks fail.
//!
//! These errors are not propagated to the caller. Instead, they are logged internally using the `tracing` crate. To capture these errors, configure a `tracing` subscriber in your application.
//! If no healthy nodes are available, the [`route()`](super::RouteProvider::route()) method returns an [`AgentError::RouteProviderError`](super::super::agent_error::AgentError::RouteProviderError).
//! # Testing
//! The module includes comprehensive tests covering:
//! - Mainnet integration with dynamic node discovery.
//! - Routing behavior with topology and health updates.
//! - Edge cases like initially unhealthy seeds, no healthy nodes, and empty topology fetches.
//!
//! These tests ensure the `DynamicRouteProvider` behaves correctly in various scenarios.
/// Health check implementation.
/// Messages used in dynamic routing.
pub
/// Node implementation.
/// Nodes fetch implementation.
/// Routing snapshot implementation.
pub
/// Type aliases used in dynamic routing.
pub