1- //! Module that deals with requests to /api/v2 /canister/.../query
1+ //! Module that deals with requests to /api/v{2,3} /canister/.../query and /api/v3/subnet /.../query
22
33use crate :: {
44 ReplicaHealthStatus ,
@@ -29,7 +29,7 @@ use ic_logger::{ReplicaLogger, error};
2929use ic_nns_delegation_manager:: { CanisterRangesFilter , NNSDelegationReader } ;
3030use ic_registry_client_helpers:: crypto:: root_of_trust:: RegistryRootOfTrustProvider ;
3131use ic_types:: {
32- CanisterId , NodeId ,
32+ CanisterId , NodeId , PrincipalId , SubnetId ,
3333 crypto:: threshold_sig:: IcRootOfTrust ,
3434 ingress:: WasmResult ,
3535 malicious_flags:: MaliciousFlags ,
@@ -53,6 +53,8 @@ pub enum Version {
5353 V2 ,
5454 // Endpoint with the NNS delegation using the tree format of the canister ranges.
5555 V3 ,
56+ // Subnet endpoint with no canister ranges in the NNS delegation.
57+ SubnetV3 ,
5658}
5759
5860#[ derive( Clone ) ]
@@ -67,6 +69,7 @@ pub struct QueryService {
6769 registry_client : Arc < dyn RegistryClient > ,
6870 additional_root_of_trust : Option < IcRootOfTrust > ,
6971 query_execution_service : Arc < Mutex < QueryExecutionService > > ,
72+ subnet_id : SubnetId ,
7073 version : Version ,
7174}
7275
@@ -82,6 +85,7 @@ pub struct QueryServiceBuilder {
8285 registry_client : Arc < dyn RegistryClient > ,
8386 additional_root_of_trust : Option < IcRootOfTrust > ,
8487 query_execution_service : QueryExecutionService ,
88+ subnet_id : SubnetId ,
8589 version : Version ,
8690}
8791
@@ -90,6 +94,7 @@ impl QueryService {
9094 match version {
9195 Version :: V2 => "/api/v2/canister/{effective_canister_id}/query" ,
9296 Version :: V3 => "/api/v3/canister/{effective_canister_id}/query" ,
97+ Version :: SubnetV3 => "/api/v3/subnet/{effective_subnet_id}/query" ,
9398 }
9499 }
95100}
@@ -103,6 +108,7 @@ impl QueryServiceBuilder {
103108 ingress_verifier : Arc < dyn IngressSigVerifier > ,
104109 nns_delegation_reader : NNSDelegationReader ,
105110 query_execution_service : QueryExecutionService ,
111+ subnet_id : SubnetId ,
106112 version : Version ,
107113 ) -> Self {
108114 Self {
@@ -117,6 +123,7 @@ impl QueryServiceBuilder {
117123 registry_client,
118124 additional_root_of_trust : None ,
119125 query_execution_service,
126+ subnet_id,
120127 version,
121128 }
122129 }
@@ -162,6 +169,7 @@ impl QueryServiceBuilder {
162169 registry_client : self . registry_client ,
163170 additional_root_of_trust : self . additional_root_of_trust ,
164171 query_execution_service : Arc :: new ( Mutex :: new ( self . query_execution_service ) ) ,
172+ subnet_id : self . subnet_id ,
165173 version : self . version ,
166174 } ;
167175 Router :: new ( ) . route_service (
@@ -179,7 +187,7 @@ impl QueryServiceBuilder {
179187}
180188
181189pub ( crate ) async fn query (
182- axum:: extract:: Path ( effective_canister_id ) : axum:: extract:: Path < CanisterId > ,
190+ axum:: extract:: Path ( id ) : axum:: extract:: Path < PrincipalId > ,
183191 State ( QueryService {
184192 log,
185193 node_id,
@@ -191,6 +199,7 @@ pub(crate) async fn query(
191199 nns_delegation_reader,
192200 additional_root_of_trust,
193201 query_execution_service,
202+ subnet_id,
194203 version,
195204 } ) : State < QueryService > ,
196205 WithTimeout ( Cbor ( request) ) : WithTimeout < Cbor < HttpRequestEnvelope < HttpQueryContent > > > ,
@@ -217,12 +226,41 @@ pub(crate) async fn query(
217226 }
218227 } ;
219228 let canister_id = request. content ( ) . canister_id ( ) ;
220- if canister_id != CanisterId :: ic_00 ( ) && canister_id != effective_canister_id {
221- let status = StatusCode :: BAD_REQUEST ;
222- let text = format ! (
223- "Specified CanisterId {canister_id} does not match effective canister id in URL {effective_canister_id}"
224- ) ;
225- return ( status, text) . into_response ( ) ;
229+
230+ // Validate effective destination.
231+ match version {
232+ Version :: V2 | Version :: V3 => {
233+ let effective_canister_id = CanisterId :: unchecked_from_principal ( id) ;
234+ if canister_id != CanisterId :: ic_00 ( ) && canister_id != effective_canister_id {
235+ let status = StatusCode :: BAD_REQUEST ;
236+ let text = format ! (
237+ "Specified canister ID {canister_id} does not match effective canister ID in URL {effective_canister_id}"
238+ ) ;
239+ return ( status, text) . into_response ( ) ;
240+ }
241+ }
242+ Version :: SubnetV3 => {
243+ let effective_subnet_id = SubnetId :: from ( id) ;
244+ if effective_subnet_id != subnet_id {
245+ let status = StatusCode :: BAD_REQUEST ;
246+ let text = format ! (
247+ "Specified subnet ID {effective_subnet_id} does not match the subnet ID of this node {subnet_id}"
248+ ) ;
249+ return ( status, text) . into_response ( ) ;
250+ }
251+ if canister_id != CanisterId :: ic_00 ( )
252+ || request. content ( ) . method_name != "list_canisters"
253+ {
254+ let status = StatusCode :: BAD_REQUEST ;
255+ let text = format ! (
256+ "Subnet query endpoint only accepts queries to the management canister ({}) 'list_canisters' method, got canister_id={} method_name='{}'" ,
257+ CanisterId :: ic_00( ) ,
258+ canister_id,
259+ request. content( ) . method_name
260+ ) ;
261+ return ( status, text) . into_response ( ) ;
262+ }
263+ }
226264 }
227265
228266 let root_of_trust_provider = if let Some ( additional_root_of_trust) = additional_root_of_trust {
@@ -263,8 +301,12 @@ pub(crate) async fn query(
263301 Version :: V2 => {
264302 nns_delegation_reader. get_delegation_with_metadata ( CanisterRangesFilter :: Flat )
265303 }
266- Version :: V3 => nns_delegation_reader
267- . get_delegation_with_metadata ( CanisterRangesFilter :: Tree ( effective_canister_id) ) ,
304+ Version :: V3 => nns_delegation_reader. get_delegation_with_metadata (
305+ CanisterRangesFilter :: Tree ( CanisterId :: unchecked_from_principal ( id) ) ,
306+ ) ,
307+ Version :: SubnetV3 => {
308+ nns_delegation_reader. get_delegation_with_metadata ( CanisterRangesFilter :: None )
309+ }
268310 } ;
269311 let query_execution_input = QueryExecutionInput {
270312 query : user_query. clone ( ) ,
0 commit comments