ic-query 0.25.4

Internet Computer query library for NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
//! Module: sns::report::live::fetch::list
//!
//! Responsibility: fetch deployed SNS inventory and explicitly targeted metadata.
//! Does not own: report assembly, command parsing, cache IO, or rendering.
//! Boundary: keeps the SNS-W inventory query separate from Governance metadata queries.

use super::block_on_sns;
use crate::sns::report::live::{
    convert::{
        mainnet_sns_canisters_from_deployed_sns, mainnet_sns_metadata_from_response,
        metadata_error_summary,
    },
    query::{principal_from_text, query_canister, sns_agent},
    types::{
        GetMetadataRequest, GetMetadataResponse, ListDeployedSnsesRequest,
        ListDeployedSnsesResponse,
    },
};
use crate::sns::report::{
    MAINNET_SNS_WASM_CANISTER_ID, SNS_METADATA_CONCURRENCY, SnsHostError, SnsSourceRequest,
    enforce_mainnet_network,
    source::{MainnetSnsCanisters, MainnetSnsInventory, MainnetSnsMetadata},
};
use crate::subnet_catalog::MAINNET_NETWORK;
use candid::Principal;
use futures::{StreamExt, stream};
use ic_agent::Agent;

/// Fetch the current unenriched deployed-SNS inventory from mainnet SNS-W.
pub(in crate::sns::report::live) fn fetch_mainnet_sns_inventory(
    request: &SnsSourceRequest,
) -> Result<MainnetSnsInventory, SnsHostError> {
    block_on_sns(fetch_mainnet_sns_inventory_async(request))
}

async fn fetch_mainnet_sns_inventory_async(
    request: &SnsSourceRequest,
) -> Result<MainnetSnsInventory, SnsHostError> {
    let agent = sns_agent(request)?;
    let sns_wasm_canister =
        principal_from_text(MAINNET_SNS_WASM_CANISTER_ID, "sns_wasm_canister_id")?;
    let response: ListDeployedSnsesResponse = query_canister(
        &agent,
        &sns_wasm_canister,
        "list_deployed_snses",
        "ListDeployedSnsesRequest",
        "ListDeployedSnsesResponse",
        &ListDeployedSnsesRequest {},
    )
    .await?;
    mainnet_sns_inventory_from_response(request, response)
}

fn mainnet_sns_inventory_from_response(
    request: &SnsSourceRequest,
    response: ListDeployedSnsesResponse,
) -> Result<MainnetSnsInventory, SnsHostError> {
    let sns_instances = response
        .instances
        .into_iter()
        .map(mainnet_sns_canisters_from_deployed_sns)
        .collect::<Result<Vec<MainnetSnsCanisters>, _>>()?;
    Ok(MainnetSnsInventory {
        network: MAINNET_NETWORK.to_string(),
        sns_wasm_canister_id: MAINNET_SNS_WASM_CANISTER_ID.to_string(),
        fetched_at: request.fetched_at.clone(),
        fetched_by: request.fetched_by.clone(),
        source_endpoint: request.endpoint.clone(),
        sns_instances,
    })
}

/// Fetch Governance metadata for exactly the requested deployed-SNS targets.
pub(in crate::sns::report::live) fn fetch_mainnet_sns_metadata(
    request: &SnsSourceRequest,
    targets: &[MainnetSnsCanisters],
) -> Result<Vec<MainnetSnsMetadata>, SnsHostError> {
    enforce_mainnet_network(&request.network)?;
    if targets.is_empty() {
        return Ok(Vec::new());
    }
    block_on_sns(fetch_mainnet_sns_metadata_async(request, targets))
}

async fn fetch_mainnet_sns_metadata_async(
    request: &SnsSourceRequest,
    targets: &[MainnetSnsCanisters],
) -> Result<Vec<MainnetSnsMetadata>, SnsHostError> {
    let agent = sns_agent(request)?;
    let fetched = stream::iter(
        targets
            .iter()
            .cloned()
            .map(|sns| fetch_mainnet_sns_metadata_row(&agent, sns)),
    )
    .buffered(SNS_METADATA_CONCURRENCY)
    .collect::<Vec<_>>()
    .await;
    let mut metadata = Vec::with_capacity(fetched.len());
    for row in fetched {
        metadata.push(row?);
    }
    Ok(metadata)
}

async fn fetch_mainnet_sns_metadata_row(
    agent: &Agent,
    sns: MainnetSnsCanisters,
) -> Result<MainnetSnsMetadata, SnsHostError> {
    let governance_canister =
        principal_from_text(&sns.governance_canister_id, "governance_canister_id")?;
    let (metadata, metadata_error) =
        match fetch_governance_metadata(agent, &governance_canister).await {
            Ok(metadata) => (metadata, None),
            Err(err) => match metadata_error_summary(&err) {
                Some(summary) => (GetMetadataResponse::default(), Some(summary)),
                None => return Err(err),
            },
        };
    Ok(mainnet_sns_metadata_from_response(
        sns.root_canister_id,
        metadata,
        metadata_error,
    ))
}

async fn fetch_governance_metadata(
    agent: &Agent,
    governance_canister: &Principal,
) -> Result<GetMetadataResponse, SnsHostError> {
    query_canister(
        agent,
        governance_canister,
        "get_metadata",
        "GetMetadataRequest",
        "GetMetadataResponse",
        &GetMetadataRequest {},
    )
    .await
}