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

feat: add fetch_api_boundary_nodes API to ic-agent #509

28 changes: 28 additions & 0 deletions ic-agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ use std::{
time::Duration,
};

use crate::agent::response_authentication::lookup_api_boundary_nodes;

const IC_STATE_ROOT_DOMAIN_SEPARATOR: &[u8; 14] = b"\x0Dic-state-root";

const IC_ROOT_KEY: &[u8; 133] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00\x81\x4c\x0e\x6e\xc7\x1f\xab\x58\x3b\x08\xbd\x81\x37\x3c\x25\x5c\x3c\x37\x1b\x2e\x84\x86\x3c\x98\xa4\xf1\xe0\x8b\x74\x23\x5d\x14\xfb\x5d\x9c\x0c\xd5\x46\xd9\x68\x5f\x91\x3a\x0c\x0b\x2c\xc5\x34\x15\x83\xbf\x4b\x43\x92\xe4\x67\xdb\x96\xd6\x5b\x9b\xb4\xcb\x71\x71\x12\xf8\x47\x2e\x0d\x5a\x4d\x14\x50\x5f\xfd\x74\x84\xb0\x12\x91\x09\x1c\x5f\x87\xb9\x88\x83\x46\x3f\x98\x09\x1a\x0b\xaa\xae";
Expand Down Expand Up @@ -1019,6 +1021,21 @@ impl Agent {
}
}
}

/// Retrieve all existing API Boundary Nodes from the State Tree.
pub async fn fetch_api_boundary_nodes(
&self,
effective_canister_id: Principal,
) -> Result<Arc<Vec<ApiBoundaryNode>>, AgentError> {
nikolay-komarevskiy marked this conversation as resolved.
Show resolved Hide resolved
let certificate = self
.read_state_raw(
vec![vec!["api_boundary_nodes".into()]],
effective_canister_id,
)
.await?;
let api_boundary_nodes = lookup_api_boundary_nodes(certificate)?;
Ok(Arc::new(api_boundary_nodes))
}
}

const DEFAULT_INGRESS_EXPIRY: Duration = Duration::from_secs(240);
Expand Down Expand Up @@ -1328,6 +1345,17 @@ pub(crate) struct Subnet {
canister_ranges: RangeInclusiveSet<Principal, PrincipalStep>,
}

/// API Boundary Node, which routes /api calls to IC replica nodes.
#[derive(Debug)]
pub struct ApiBoundaryNode {
/// Domain name
_domain: String,
nikolay-komarevskiy marked this conversation as resolved.
Show resolved Hide resolved
/// IPv6 address in the hexadecimal notation with colons.
_ipv6_address: String,
/// IPv4 address in the dotted-decimal notation.
_ipv4_address: Option<String>,
}

/// A Query Request Builder.
///
/// This makes it easier to do query calls without actually passing all arguments.
Expand Down
48 changes: 47 additions & 1 deletion ic-agent/src/agent/response_authentication.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::agent::{RejectCode, RejectResponse, RequestStatusResponse};
use crate::agent::{ApiBoundaryNode, RejectCode, RejectResponse, RequestStatusResponse};
use crate::{export::Principal, AgentError, RequestId};
use ic_certification::hash_tree::{HashTree, SubtreeLookupResult};
use ic_certification::{certificate::Certificate, hash_tree::Label, LookupResult};
Expand Down Expand Up @@ -206,6 +206,52 @@ pub(crate) fn lookup_subnet<Storage: AsRef<[u8]> + Clone>(
Ok((subnet_id, subnet))
}

pub(crate) fn lookup_api_boundary_nodes<Storage: AsRef<[u8]> + Clone>(
certificate: Certificate<Storage>,
) -> Result<Vec<ApiBoundaryNode>, AgentError> {
// API Boundary Node paths in the State Tree, as defined in the spec (https://github.com/dfinity/interface-spec/pull/248 to be merged soon).
let api_bn_path = "api_boundary_nodes".as_bytes();
let domain_path = "domain".as_bytes();
let ipv4_path = "ipv4_address".as_bytes();
let ipv6_path = "ipv6_address".as_bytes();

let api_bn_tree = lookup_tree(&certificate.tree, [api_bn_path])?;

let mut api_bns = Vec::<ApiBoundaryNode>::new();

for path in api_bn_tree.list_paths() {
let node_id = Principal::from_slice(path[0].as_bytes());
let node_id = node_id.as_slice();

let domain =
String::from_utf8(lookup_value(&api_bn_tree, [node_id, domain_path])?.to_vec())
.map_err(|err| AgentError::Utf8ReadError(err.utf8_error()))?;

let ipv6_address =
String::from_utf8(lookup_value(&api_bn_tree, [node_id, ipv6_path])?.to_vec())
.map_err(|err| AgentError::Utf8ReadError(err.utf8_error()))?;

let ipv4_address = match lookup_value(&api_bn_tree, [node_id, ipv4_path]) {
Ok(ipv4) => Some(
String::from_utf8(ipv4.to_vec())
.map_err(|err| AgentError::Utf8ReadError(err.utf8_error()))?,
),
// By convention an absent path `/api_boundary_nodes/<node_id>/ipv4_address` in the State Tree signifies that ipv4 is None.
Err(AgentError::LookupPathAbsent(_)) => None,
Err(err) => return Err(err),
};

let api_bn = ApiBoundaryNode {
_domain: domain,
_ipv6_address: ipv6_address,
_ipv4_address: ipv4_address,
};

api_bns.push(api_bn);
}
Ok(api_bns)
}

/// The path to [`lookup_value`]
pub trait LookupPath {
type Item: AsRef<[u8]>;
Expand Down
Loading