@@ -1610,24 +1610,28 @@ impl ProposalAction for ProposeToUpdateCanisterSettingsCmd {
16101610#[ derive( Parser , ProposalMetadata ) ]
16111611struct ProposeToBlessAlternativeGuestOsVersionCmd {
16121612 /// Subnet to query for chip IDs and launch measurements.
1613- /// If specified, chip_ids and base_launch_measurements are automatically fetched from the registry.
1613+ /// If specified, base_launch_measurements are automatically fetched from the registry.
1614+ /// If --node-ids or --chip-ids are also provided, they act as filters (must be part of the subnet).
1615+ /// Otherwise, chip IDs for all subnet members are used.
16141616 /// Either --subnet or (--chip-ids/--node-ids and --base-launch-measurements) must be provided.
16151617 #[ clap(
16161618 long,
1617- conflicts_with_all = & [ "chip_ids" , " base_launch_measurements", "node_ids" ] ,
1619+ conflicts_with = " base_launch_measurements",
16181620 required_unless_present_any = & [ "chip_ids" , "node_ids" ]
16191621 ) ]
16201622 subnet : Option < SubnetDescriptor > ,
16211623
16221624 /// Node IDs of nodes to bless the alternative Guest OS version on.
1623- /// Either --subnet or (--chip-ids/--node-ids and --base-launch-measurements) must be provided.
1624- #[ clap( long, value_parser = parse_node_id) ]
1625+ /// If --subnet is provided, all the node IDs in this list must be part of that subnet.
1626+ /// If --subnet is not provided, --base-launch-measurements must be specified.
1627+ #[ clap( long, num_args( 1 ..) , value_parser = parse_node_id, conflicts_with = "chip_ids" ) ]
16251628 node_ids : Vec < NodeId > ,
16261629
16271630 /// Hex-encoded chip IDs (AMD Secure Processor chip IDs). Each must be exactly 64 bytes.
16281631 /// Multiple values can be passed.
1629- /// Either --subnet or (--chip-ids/--node-ids and --base-launch-measurements) must be provided.
1630- #[ clap( long, requires = "base_launch_measurements" , conflicts_with = "subnet" ) ]
1632+ /// If --subnet is provided, all the chip IDs must belong to nodes in that subnet.
1633+ /// If --subnet is not provided, --base-launch-measurements must be specified.
1634+ #[ clap( long, num_args( 1 ..) , conflicts_with = "node_ids" ) ]
16311635 chip_ids : Vec < String > ,
16321636
16331637 /// Hexadecimal fingerprint of the Recovery-GuestOS rootfs that is allowed to run on the nodes
@@ -1650,38 +1654,81 @@ impl ProposalTitle for ProposeToBlessAlternativeGuestOsVersionCmd {
16501654 None => {
16511655 if let Some ( subnet) = self . subnet {
16521656 format ! (
1653- "Bless alternative Guest OS for with rootfs_hash= {} on subnet {}" ,
1657+ "Bless alternative Guest OS with rootfs hash {} on subnet {}" ,
16541658 self . rootfs_hash,
16551659 shortened_subnet_string( & subnet)
16561660 )
16571661 } else {
1658- format ! ( "Bless alternative Guest OS for hash {}" , self . rootfs_hash)
1662+ format ! (
1663+ "Bless alternative Guest OS with rootfs hash {} for {} nodes" ,
1664+ self . rootfs_hash,
1665+ self . node_ids. len( ) + self . chip_ids. len( )
1666+ )
16591667 }
16601668 }
16611669 }
16621670 }
16631671}
16641672
16651673impl ProposeToBlessAlternativeGuestOsVersionCmd {
1666- /// Fetches chip IDs and launch measurements from the registry for a given subnet.
1667- async fn get_chip_ids_and_measurements_from_subnet (
1668- subnet : & SubnetDescriptor ,
1669- agent : & Agent ,
1670- ) -> ( Vec < Vec < u8 > > , ic_nns_governance_api:: GuestLaunchMeasurements ) {
1671- let registry_canister = RegistryCanister :: new_with_agent ( agent. clone ( ) ) ;
1674+ /// Returns chip IDs for a subnet, optionally filtered by node IDs or chip IDs.
1675+ ///
1676+ /// `select_node_ids` and `select_chip_ids` are mutually exclusive and must refer to a subset
1677+ /// of the subnet's nodes.
1678+ async fn get_chip_ids_from_subnet (
1679+ subnet_descriptor : & SubnetDescriptor ,
1680+ subnet_record : & SubnetRecord ,
1681+ select_node_ids : & [ NodeId ] ,
1682+ select_chip_ids : & [ String ] ,
1683+ registry_canister : & RegistryCanister ,
1684+ ) -> Vec < Vec < u8 > > {
1685+ assert ! (
1686+ select_node_ids. is_empty( ) || select_chip_ids. is_empty( ) ,
1687+ "--node-ids and --chip-ids are mutually exclusive"
1688+ ) ;
16721689
1673- let subnet_record = Self :: fetch_subnet_record ( & registry_canister, subnet) . await ;
1674- let node_ids: Vec < NodeId > = Self :: get_subnet_member_node_ids ( & subnet_record) ;
1675- let chip_ids = Self :: get_chip_ids_from_node_ids ( & registry_canister, & node_ids, false ) . await ;
1690+ let subnet_member_node_ids = Self :: get_subnet_member_node_ids ( subnet_record) ;
16761691
1677- // Query the ReplicaVersionRecord to get guest_launch_measurements
1678- let guest_launch_measurements = Self :: get_guest_launch_measurements_from_replica_version (
1679- & registry_canister,
1680- & subnet_record. replica_version_id ,
1681- )
1682- . await ;
1692+ // No selection: use all nodes in the subnet.
1693+ if select_node_ids. is_empty ( ) && select_chip_ids. is_empty ( ) {
1694+ return Self :: get_chip_ids_from_node_ids (
1695+ registry_canister,
1696+ & subnet_member_node_ids,
1697+ false ,
1698+ )
1699+ . await ;
1700+ }
1701+
1702+ // Select node IDs: validate membership, then fetch their chip IDs.
1703+ if !select_node_ids. is_empty ( ) {
1704+ let subnet_node_id_set: HashSet < & NodeId > = subnet_member_node_ids. iter ( ) . collect ( ) ;
1705+ for node_id in select_node_ids {
1706+ assert ! (
1707+ subnet_node_id_set. contains( node_id) ,
1708+ "Node ID {node_id} is not a member of subnet {}" ,
1709+ shortened_subnet_string( subnet_descriptor) ,
1710+ ) ;
1711+ }
1712+ return Self :: get_chip_ids_from_node_ids ( registry_canister, select_node_ids, true )
1713+ . await ;
1714+ }
16831715
1684- ( chip_ids, guest_launch_measurements)
1716+ // Select chip IDs: validate against the subnet's actual chip IDs.
1717+ let all_subnet_chip_ids: HashSet < Vec < u8 > > =
1718+ Self :: get_chip_ids_from_node_ids ( registry_canister, & subnet_member_node_ids, false )
1719+ . await
1720+ . into_iter ( )
1721+ . collect ( ) ;
1722+ let decoded = Self :: decode_chip_ids ( select_chip_ids) ;
1723+ for chip_id in & decoded {
1724+ assert ! (
1725+ all_subnet_chip_ids. contains( chip_id) ,
1726+ "Chip ID {} does not belong to any node in subnet {}" ,
1727+ hex:: encode( chip_id) ,
1728+ shortened_subnet_string( subnet_descriptor) ,
1729+ ) ;
1730+ }
1731+ decoded
16851732 }
16861733
16871734 fn get_subnet_member_node_ids ( subnet_record : & SubnetRecord ) -> Vec < NodeId > {
@@ -1786,14 +1833,11 @@ impl ProposeToBlessAlternativeGuestOsVersionCmd {
17861833 } ) ;
17871834
17881835 // Execute all futures in parallel and collect results
1789- let chip_ids = futures:: future:: join_all ( futures) . await ;
1790- let chip_ids: Vec < _ > = chip_ids. into_iter ( ) . flatten ( ) . collect ( ) ;
1791-
1792- if chip_ids. is_empty ( ) {
1793- panic ! ( "No chip IDs found for subnet" ) ;
1794- }
1795-
1796- chip_ids
1836+ futures:: future:: join_all ( futures)
1837+ . await
1838+ . into_iter ( )
1839+ . flatten ( )
1840+ . collect ( )
17971841 }
17981842
17991843 /// Queries the ReplicaVersionRecord and extracts GuestLaunchMeasurements.
@@ -1837,36 +1881,49 @@ impl ProposeToBlessAlternativeGuestOsVersionCmd {
18371881#[ async_trait]
18381882impl ProposalAction for ProposeToBlessAlternativeGuestOsVersionCmd {
18391883 async fn action ( & self , agent : & Agent ) -> ProposalActionRequest {
1840- let ( chip_ids, measurements) = if let Some ( subnet) = & self . subnet {
1841- Self :: get_chip_ids_and_measurements_from_subnet ( subnet, agent) . await
1842- } else if let ( chip_ids, node_ids, Some ( base_launch_measurements) ) = (
1843- & self . chip_ids ,
1844- & self . node_ids ,
1845- & self . base_launch_measurements ,
1846- ) && chip_ids. len ( ) + node_ids. len ( ) > 0
1847- {
1848- let mut chip_ids = Self :: decode_chip_ids ( chip_ids) ;
1849- if !node_ids. is_empty ( ) {
1850- let registry_canister = RegistryCanister :: new_with_agent ( agent. clone ( ) ) ;
1851- let chip_ids_from_node_ids =
1852- Self :: get_chip_ids_from_node_ids ( & registry_canister, node_ids, true ) . await ;
1853- if chip_ids_from_node_ids. len ( ) != node_ids. len ( ) {
1854- panic ! ( "Not all node IDs provided have a chip ID in the registry." ) ;
1855- }
1856- chip_ids. extend ( chip_ids_from_node_ids) ;
1857- }
1884+ let chip_ids: Vec < Vec < u8 > > ;
1885+ let measurements: ic_nns_governance_api:: GuestLaunchMeasurements ;
1886+ let registry_canister = RegistryCanister :: new_with_agent ( agent. clone ( ) ) ;
18581887
1859- (
1860- chip_ids,
1861- read_from_json_file :: < ic_nns_governance_api:: GuestLaunchMeasurements > (
1862- base_launch_measurements,
1863- ) ,
1888+ if let Some ( subnet) = & self . subnet {
1889+ let subnet_record = Self :: fetch_subnet_record ( & registry_canister, subnet) . await ;
1890+
1891+ measurements = Self :: get_guest_launch_measurements_from_replica_version (
1892+ & registry_canister,
1893+ & subnet_record. replica_version_id ,
18641894 )
1895+ . await ;
1896+ chip_ids = Self :: get_chip_ids_from_subnet (
1897+ subnet,
1898+ & subnet_record,
1899+ & self . node_ids ,
1900+ & self . chip_ids ,
1901+ & registry_canister,
1902+ )
1903+ . await ;
1904+
1905+ assert ! (
1906+ !chip_ids. is_empty( ) ,
1907+ "No SEV nodes with chip IDs found in subnet {}" ,
1908+ shortened_subnet_string( subnet)
1909+ ) ;
18651910 } else {
1866- panic ! (
1867- "Either --subnet or (--chip-ids/--node-ids and --base-launch-measurements) must be provided"
1911+ measurements = read_from_json_file :: < ic_nns_governance_api:: GuestLaunchMeasurements > (
1912+ self . base_launch_measurements . as_ref ( ) . expect (
1913+ "--base-launch-measurements must be provided if --subnet is not provided" ,
1914+ ) ,
18681915 ) ;
1869- } ;
1916+
1917+ if !self . chip_ids . is_empty ( ) && self . node_ids . is_empty ( ) {
1918+ chip_ids = Self :: decode_chip_ids ( & self . chip_ids ) ;
1919+ } else if !self . node_ids . is_empty ( ) && self . chip_ids . is_empty ( ) {
1920+ chip_ids =
1921+ Self :: get_chip_ids_from_node_ids ( & registry_canister, & self . node_ids , true )
1922+ . await ;
1923+ } else {
1924+ panic ! ( "Exactly one of --chip-ids or --node-ids must be provided" ) ;
1925+ }
1926+ }
18701927
18711928 ProposalActionRequest :: BlessAlternativeGuestOsVersion (
18721929 ic_nns_governance_api:: BlessAlternativeGuestOsVersion {
0 commit comments