ic-query 0.30.2

Internet Computer query library for NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
use super::source::nns_certified_registry_delta_limits;
use super::{
    NnsCertifiedRegistryDeltaBatchReport, NnsCertifiedRegistryDeltaBatchRequest,
    NnsCertifiedRegistryDeltaSource, NnsCertifiedRegistryDeltaSourceFuture,
    NnsCertifiedRegistryDeltaVersion, NnsCertifiedRegistryMutation,
    NnsCertifiedRegistryMutationKind, NnsCertifiedRegistryValueEncoding, NnsRegistryCertification,
    NnsRegistryHostError, NnsRegistrySource, NnsRegistryVersionData, NnsRegistryVersionReport,
    NnsRegistryVersionRequest, build_nns_registry_version_report_with_source,
    fetch_nns_certified_registry_delta_batch_with_source_async, nns_registry_version_report_text,
    validate_nns_certified_registry_delta_batch,
};
use crate::nns::{LiveNnsSource, NnsSourceRequest};
use crate::subnet_catalog::{MAINNET_NETWORK, MAINNET_REGISTRY_CANISTER_ID};

#[test]
fn live_registry_source_rejects_non_mainnet_before_agent_construction() {
    let request = NnsSourceRequest::new(
        "local",
        "not a valid replica endpoint",
        "2026-07-29T00:00:00Z",
        "test",
    );

    let error = LiveNnsSource
        .fetch_registry_version(&request)
        .expect_err("unsupported network");

    assert!(matches!(
        error,
        NnsRegistryHostError::UnsupportedNetwork { network } if network == "local"
    ));
}

#[test]
fn live_certified_delta_source_rejects_non_mainnet_before_agent_construction() {
    let request = NnsCertifiedRegistryDeltaBatchRequest::new(
        "local",
        "not a valid replica endpoint",
        41,
        1_780_531_200,
    );

    let error = crate::runtime::block_on_current_thread(
        LiveNnsSource.fetch_certified_registry_delta_batch(&request),
    )
    .expect("test runtime")
    .expect_err("unsupported network");

    assert!(matches!(
        error,
        NnsRegistryHostError::UnsupportedNetwork { network } if network == "local"
    ));
}

#[test]
fn certified_delta_public_builder_validates_custom_source_evidence() {
    let request = certified_delta_request();

    let report = crate::runtime::block_on_current_thread(
        fetch_nns_certified_registry_delta_batch_with_source_async(
            &request,
            &FixtureCertifiedDeltaSource,
        ),
    )
    .expect("test runtime")
    .expect("certified delta report");

    assert_eq!(report.requested_version, 41);
    assert_eq!(report.certified_latest_version, 43);
    assert_eq!(report.first_version, Some(42));
    assert_eq!(report.last_version, Some(42));
    assert!(report.more_available);
    assert_eq!(
        report.versions[0].mutations[0].mutation_kind,
        NnsCertifiedRegistryMutationKind::Upsert
    );
}

#[test]
fn certified_delta_pure_validator_rejects_sequence_and_derived_field_tampering() {
    let request = certified_delta_request();
    let mut report = certified_delta_report(&request);
    report.versions[0].version = 43;
    report.first_version = Some(43);
    report.last_version = Some(43);
    let error = validate_nns_certified_registry_delta_batch(&request, &report)
        .expect_err("wrong first version");
    assert!(matches!(
        error,
        NnsRegistryHostError::InvalidSourceData { reason }
            if reason.contains("version sequence expected 42")
    ));

    let mut report = certified_delta_report(&request);
    report.mutation_count = 2;
    let error = validate_nns_certified_registry_delta_batch(&request, &report)
        .expect_err("derived count mismatch");
    assert!(matches!(
        error,
        NnsRegistryHostError::InvalidSourceData { reason }
            if reason.contains("mutation_count mismatch")
    ));

    let mut report = certified_delta_report(&request);
    report.versions[0].mutations[0].mutation_type = 1;
    let error = validate_nns_certified_registry_delta_batch(&request, &report)
        .expect_err("raw and typed mutation mismatch");
    assert!(matches!(
        error,
        NnsRegistryHostError::InvalidSourceData { reason }
            if reason.contains("does not match kind")
    ));
}

#[test]
fn certified_delta_validator_accepts_bounded_chunk_evidence_and_recomputes_accounting() {
    let request = certified_delta_request();
    let mut report = certified_delta_report(&request);
    let hash = "ab".repeat(32);
    let mutation = &mut report.versions[0].mutations[0];
    mutation.value_encoding = NnsCertifiedRegistryValueEncoding::Chunked;
    mutation.chunk_sha256_hexes = vec![hash.clone(), hash];
    mutation.value_hex = Some("6263".to_string());
    report.inline_value_bytes = 0;
    report.chunk_value_bytes = 2;
    report.value_bytes = 2;
    report.chunk_reference_count = 2;
    report.chunk_query_call_count = 1;
    report.query_call_count = 2;
    report.chunk_response_bytes = 32;
    report.response_bytes = 96;

    validate_nns_certified_registry_delta_batch(&request, &report).expect("bounded chunk evidence");

    let mut invalid_hash = report.clone();
    invalid_hash.versions[0].mutations[0].chunk_sha256_hexes[0] = "AB".repeat(32);
    let error = validate_nns_certified_registry_delta_batch(&request, &invalid_hash)
        .expect_err("noncanonical chunk hash");
    assert!(matches!(
        error,
        NnsRegistryHostError::InvalidSourceData { reason }
            if reason.contains("chunk SHA-256")
    ));

    let mut hidden_query = report;
    hidden_query.chunk_query_call_count = 2;
    hidden_query.query_call_count = 3;
    let error = validate_nns_certified_registry_delta_batch(&request, &hidden_query)
        .expect_err("query count must equal unique chunk hashes");
    assert!(matches!(
        error,
        NnsRegistryHostError::InvalidSourceData { reason }
            if reason.contains("chunk_query_call_count mismatch")
    ));
}

#[test]
fn registry_version_report_uses_live_source_shape() {
    let request = NnsRegistryVersionRequest {
        network: MAINNET_NETWORK.to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        now_unix_secs: 1_780_531_200,
    };

    let report = build_nns_registry_version_report_with_source(&request, &FixtureNnsRegistrySource)
        .expect("registry version report");

    assert_eq!(report.schema_version, 2);
    assert_eq!(report.network, MAINNET_NETWORK);
    assert_eq!(report.registry_canister_id, MAINNET_REGISTRY_CANISTER_ID);
    assert_eq!(report.registry_version, 42);
    assert_eq!(report.fetched_at, "2026-06-04T00:00:00Z");
    assert_eq!(report.source_endpoint, "https://icp-api.io");
    assert_eq!(report.fetched_by, "ic-query");
    assert!(report.certification.certificate_verified);
    assert_eq!(report.certification.certificate_time, report.fetched_at);
}

#[test]
fn registry_version_report_rejects_custom_source_provenance_mismatch() {
    let request = NnsRegistryVersionRequest {
        network: MAINNET_NETWORK.to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        now_unix_secs: 1_780_531_200,
    };

    let error =
        build_nns_registry_version_report_with_source(&request, &MismatchedNnsRegistrySource)
            .expect_err("source provenance mismatch must fail");

    assert!(matches!(
        error,
        NnsRegistryHostError::SourceMismatch {
            field: "source_endpoint",
            expected,
            actual,
        } if expected == "https://icp-api.io" && actual == "https://wrong.example"
    ));
}

#[test]
fn registry_version_report_rejects_unverified_custom_source_evidence() {
    let request =
        NnsRegistryVersionRequest::new(MAINNET_NETWORK, "https://icp-api.io", 1_780_531_200);

    let error =
        build_nns_registry_version_report_with_source(&request, &UnverifiedNnsRegistrySource)
            .expect_err("unverified custom evidence must fail");

    assert!(matches!(
        error,
        NnsRegistryHostError::InvalidSourceData { reason }
            if reason.contains("certificate_verified")
    ));
}

#[test]
fn registry_version_text_is_key_value_output() {
    let report = NnsRegistryVersionReport {
        schema_version: 2,
        network: MAINNET_NETWORK.to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        registry_version: 42,
        fetched_at: "2026-06-04T00:00:00Z".to_string(),
        source_endpoint: "https://icp-api.io".to_string(),
        fetched_by: "test".to_string(),
        certification: certification(1_780_531_200),
    };

    let text = nns_registry_version_report_text(&report);

    assert!(text.contains("network: ic"));
    assert!(text.contains("registry_canister_id: rwlgt-iiaaa-aaaaa-aaaaa-cai"));
    assert!(text.contains("registry_version: 42"));
    assert!(text.contains("fetched_at: 2026-06-04T00:00:00Z"));
    assert!(text.contains("assurance: certified"));
    assert!(text.contains("certificate_verified: true"));
}

struct FixtureNnsRegistrySource;

impl NnsRegistrySource for FixtureNnsRegistrySource {
    fn fetch_registry_version(
        &self,
        request: &NnsSourceRequest,
    ) -> Result<NnsRegistryVersionData, NnsRegistryHostError> {
        Ok(NnsRegistryVersionData {
            network: MAINNET_NETWORK.to_string(),
            registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
            registry_version: 42,
            fetched_at: request.fetched_at.clone(),
            fetched_by: request.fetched_by.clone(),
            source_endpoint: request.endpoint.clone(),
            certification: certification(1_780_531_200),
        })
    }
}

struct MismatchedNnsRegistrySource;

impl NnsRegistrySource for MismatchedNnsRegistrySource {
    fn fetch_registry_version(
        &self,
        request: &NnsSourceRequest,
    ) -> Result<NnsRegistryVersionData, NnsRegistryHostError> {
        Ok(NnsRegistryVersionData {
            network: request.network.clone(),
            registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
            registry_version: 42,
            fetched_at: request.fetched_at.clone(),
            fetched_by: request.fetched_by.clone(),
            source_endpoint: "https://wrong.example".to_string(),
            certification: certification(1_780_531_200),
        })
    }
}

struct UnverifiedNnsRegistrySource;

impl NnsRegistrySource for UnverifiedNnsRegistrySource {
    fn fetch_registry_version(
        &self,
        request: &NnsSourceRequest,
    ) -> Result<NnsRegistryVersionData, NnsRegistryHostError> {
        let mut evidence = certification(1_780_531_200);
        evidence.certificate_verified = false;
        Ok(NnsRegistryVersionData {
            network: request.network.clone(),
            registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
            registry_version: 42,
            fetched_at: request.fetched_at.clone(),
            fetched_by: request.fetched_by.clone(),
            source_endpoint: request.endpoint.clone(),
            certification: evidence,
        })
    }
}

struct FixtureCertifiedDeltaSource;

impl NnsCertifiedRegistryDeltaSource for FixtureCertifiedDeltaSource {
    fn fetch_certified_registry_delta_batch<'a>(
        &'a self,
        request: &'a NnsCertifiedRegistryDeltaBatchRequest,
    ) -> NnsCertifiedRegistryDeltaSourceFuture<'a> {
        Box::pin(async move { Ok(certified_delta_report(request)) })
    }
}

fn certified_delta_request() -> NnsCertifiedRegistryDeltaBatchRequest {
    NnsCertifiedRegistryDeltaBatchRequest::new(
        MAINNET_NETWORK,
        "https://icp-api.io",
        41,
        1_780_531_200,
    )
}

fn certified_delta_report(
    request: &NnsCertifiedRegistryDeltaBatchRequest,
) -> NnsCertifiedRegistryDeltaBatchReport {
    NnsCertifiedRegistryDeltaBatchReport {
        schema_version: 2,
        network: MAINNET_NETWORK.to_string(),
        registry_canister_id: MAINNET_REGISTRY_CANISTER_ID.to_string(),
        requested_version: request.requested_version,
        certified_latest_version: 43,
        first_version: Some(42),
        last_version: Some(42),
        version_count: 1,
        mutation_count: 1,
        precondition_count: 0,
        inline_value_bytes: 1,
        chunk_value_bytes: 0,
        value_bytes: 1,
        chunk_reference_count: 0,
        more_available: true,
        fetched_at: "2026-06-04T00:00:00Z".to_string(),
        source_endpoint: request.source_endpoint.clone(),
        fetched_by: "ic-query".to_string(),
        query_call_count: 1,
        chunk_query_call_count: 0,
        certified_response_bytes: 64,
        chunk_response_bytes: 0,
        response_bytes: 64,
        limits: nns_certified_registry_delta_limits(),
        versions: vec![NnsCertifiedRegistryDeltaVersion {
            version: 42,
            timestamp_nanoseconds: 1_780_531_199_000_000_000,
            mutations: vec![NnsCertifiedRegistryMutation {
                mutation_type: 4,
                mutation_kind: NnsCertifiedRegistryMutationKind::Upsert,
                key_hex: "61".to_string(),
                value_encoding: NnsCertifiedRegistryValueEncoding::Inline,
                chunk_sha256_hexes: Vec::new(),
                value_hex: Some("62".to_string()),
            }],
            preconditions: Vec::new(),
        }],
        certification: certification(1_780_531_200),
    }
}

fn certification(timestamp_seconds: u64) -> NnsRegistryCertification {
    NnsRegistryCertification {
        certificate_verified: true,
        certificate_time_nanos: timestamp_seconds * 1_000_000_000,
        certificate_time: crate::subnet_catalog::format_utc_timestamp_secs(timestamp_seconds),
        root_key_digest: "ab".repeat(32),
        certificate_hex: "cd".repeat(8),
        certificate_bytes: 8,
        hash_tree_hex: "ef".repeat(4),
        hash_tree_bytes: 4,
    }
}