Skip to content

Commit

Permalink
feat: add fetch_api_boundary_nodes API to ic-agent (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikolay-komarevskiy authored Mar 6, 2024
1 parent d588988 commit a3cc081
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

* Added `Agent::fetch_api_boundary_nodes` for looking up API boundary nodes in the state tree.
* Timestamps are now being checked in `Agent::verify` and `Agent::verify_for_subnet`. If you were using it with old certificates, increase the expiry timeout to continue to verify them.
* Added node metrics, ECDSA, and Bitcoin functions to `MgmtMethod`. Most do not have wrappers in `ManagementCanister` because only canisters can call these functions.
* Added `FetchCanisterLogs` function to `MgmtMethod` and a corresponding wrapper to `ManagementCanister`.
Expand Down
28 changes: 28 additions & 0 deletions ic-agent/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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 @@ -1121,6 +1123,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<Vec<ApiBoundaryNode>, AgentError> {
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(api_boundary_nodes)
}

async fn fetch_subnet_by_canister(
&self,
canister: &Principal,
Expand Down Expand Up @@ -1446,6 +1463,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
pub domain: String,
/// IPv6 address in the hexadecimal notation with colons.
pub ipv6_address: String,
/// IPv4 address in the dotted-decimal notation.
pub ipv4_address: Option<String>,
}

/// A Query Request Builder.
///
/// This makes it easier to do query calls without actually passing all arguments.
Expand Down
50 changes: 48 additions & 2 deletions ic-agent/src/agent/response_authentication.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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};
use ic_transport_types::{ReplyResponse, SubnetMetrics};
use rangemap::RangeInclusiveSet;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::str::from_utf8;

use super::Subnet;
Expand Down Expand Up @@ -213,6 +213,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 nodes paths in the state tree, as defined in the spec (https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree-api-bn).
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();
let paths = api_bn_tree.list_paths();
let node_ids: HashSet<&[u8]> = paths.iter().map(|path| path[0].as_bytes()).collect();

for node_id in node_ids {
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,
ipv6_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

0 comments on commit a3cc081

Please sign in to comment.