1pub(crate) mod agent_config;
3pub mod agent_error;
4pub(crate) mod builder;
5#[doc(hidden)]
7#[deprecated(since = "0.38.0", note = "use the AgentBuilder methods")]
8pub mod http_transport;
9pub(crate) mod nonce;
10pub(crate) mod response_authentication;
11pub mod route_provider;
12pub mod status;
13pub mod subnet;
14
15pub use agent_config::AgentConfig;
16pub use agent_error::AgentError;
17use agent_error::{HttpErrorPayload, Operation};
18use async_lock::Semaphore;
19use async_trait::async_trait;
20pub use builder::AgentBuilder;
21use bytes::Bytes;
22use cached::{Cached, TimedCache};
23use http::{header::CONTENT_TYPE, HeaderMap, Method, StatusCode, Uri};
24use ic_ed25519::{PublicKey, SignatureError};
25#[doc(inline)]
26pub use ic_transport_types::{
27 signed, CallResponse, Envelope, EnvelopeContent, RejectCode, RejectResponse, ReplyResponse,
28 RequestStatusResponse,
29};
30pub use nonce::{NonceFactory, NonceGenerator};
31use rangemap::RangeInclusiveMap;
32use reqwest::{Client, Request, Response};
33use route_provider::{
34 dynamic_routing::{dynamic_route_provider::DynamicRouteProviderBuilder, node::Node},
35 RouteProvider, UrlUntilReady,
36};
37pub use subnet::{Subnet, SubnetType};
38use time::OffsetDateTime;
39use tower_service::Service;
40
41#[cfg(test)]
42mod agent_test;
43
44use crate::{
45 agent::response_authentication::{
46 extract_der, lookup_canister_info, lookup_canister_metadata, lookup_canister_ranges,
47 lookup_incomplete_subnet, lookup_request_status, lookup_subnet_and_ranges,
48 lookup_subnet_canister_ranges, lookup_subnet_metrics, lookup_time, lookup_tree,
49 lookup_value,
50 },
51 agent_error::TransportError,
52 export::Principal,
53 identity::Identity,
54 to_request_id, RequestId,
55};
56use backon::{BackoffBuilder, ExponentialBackoff, ExponentialBuilder};
57use ic_certification::{Certificate, Delegation, Label};
58use ic_transport_types::{
59 signed::{SignedQuery, SignedRequestStatus, SignedUpdate},
60 QueryResponse, ReadStateResponse, SubnetMetrics, TransportCallResponse,
61};
62use serde::Serialize;
63use status::Status;
64use std::{
65 borrow::Cow,
66 convert::TryFrom,
67 fmt::{self, Debug},
68 future::{Future, IntoFuture},
69 pin::Pin,
70 str::FromStr,
71 sync::{Arc, Mutex, RwLock},
72 task::{Context, Poll},
73 time::Duration,
74};
75
76use crate::agent::response_authentication::lookup_api_boundary_nodes;
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum EffectiveId {
93 Canister(Principal),
95 Subnet(Principal),
97}
98
99impl EffectiveId {
100 pub fn as_principal(&self) -> Principal {
102 match self {
103 EffectiveId::Canister(p) | EffectiveId::Subnet(p) => *p,
104 }
105 }
106}
107
108impl From<Principal> for EffectiveId {
109 fn from(p: Principal) -> Self {
110 EffectiveId::Canister(p)
111 }
112}
113
114pub(crate) const IC_STATE_ROOT_DOMAIN_SEPARATOR: &[u8; 14] = b"\x0Dic-state-root";
115
116pub(crate) 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";
117
118#[cfg(not(target_family = "wasm"))]
119type AgentFuture<'a, V> = Pin<Box<dyn Future<Output = Result<V, AgentError>> + Send + 'a>>;
120
121#[cfg(target_family = "wasm")]
122type AgentFuture<'a, V> = Pin<Box<dyn Future<Output = Result<V, AgentError>> + 'a>>;
123
124#[cfg_attr(unix, doc = " ```rust")] #[cfg_attr(not(unix), doc = " ```ignore")]
128#[derive(Clone)]
189pub struct Agent {
190 nonce_factory: Arc<dyn NonceGenerator>,
191 identity: Arc<dyn Identity>,
192 ingress_expiry: Duration,
193 root_key: Arc<RwLock<Vec<u8>>>,
194 client: Arc<dyn HttpService>,
195 route_provider: Arc<dyn RouteProvider>,
196 subnet_key_cache: Arc<Mutex<SubnetCache>>,
197 concurrent_requests_semaphore: Arc<Semaphore>,
198 verify_query_signatures: bool,
199 max_response_body_size: Option<usize>,
200 max_polling_time: Duration,
201 #[allow(dead_code)]
202 max_tcp_error_retries: usize,
203}
204
205impl fmt::Debug for Agent {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
207 f.debug_struct("Agent")
208 .field("ingress_expiry", &self.ingress_expiry)
209 .finish_non_exhaustive()
210 }
211}
212
213impl Agent {
214 pub fn builder() -> builder::AgentBuilder {
217 Default::default()
218 }
219
220 pub fn new(config: agent_config::AgentConfig) -> Result<Agent, AgentError> {
222 let client = config.http_service.unwrap_or_else(|| {
223 Arc::new(RetryLogic {
224 client: config.client.unwrap_or_else(|| {
225 #[cfg(not(target_family = "wasm"))]
226 {
227 Client::builder()
228 .use_rustls_tls()
229 .timeout(Duration::from_secs(360))
230 .build()
231 .expect("Could not create HTTP client.")
232 }
233 #[cfg(all(target_family = "wasm", feature = "wasm-bindgen"))]
234 {
235 Client::new()
236 }
237 }),
238 })
239 });
240 Ok(Agent {
241 nonce_factory: config.nonce_factory,
242 identity: config.identity,
243 ingress_expiry: config.ingress_expiry,
244 root_key: Arc::new(RwLock::new(IC_ROOT_KEY.to_vec())),
245 client: client.clone(),
246 route_provider: if let Some(route_provider) = config.route_provider {
247 route_provider
248 } else if let Some(url) = config.url {
249 if config.background_dynamic_routing {
250 assert!(
251 url.scheme() == "https" && url.path() == "/" && url.port().is_none() && url.domain().is_some(),
252 "in dynamic routing mode, URL must be in the exact form https://domain with no path, port, IP, or non-HTTPS scheme"
253 );
254 let seeds = vec![Node::new(url.domain().unwrap()).unwrap()];
255 UrlUntilReady::new(url, async move {
256 let provider =
257 DynamicRouteProviderBuilder::new(seeds, client, None).build();
258 provider.start().await;
259 provider
260 }) as Arc<dyn RouteProvider>
261 } else {
262 Arc::new(url)
263 }
264 } else {
265 panic!("either route_provider or url must be specified");
266 },
267 subnet_key_cache: Arc::new(Mutex::new(SubnetCache::new())),
268 verify_query_signatures: config.verify_query_signatures,
269 concurrent_requests_semaphore: Arc::new(Semaphore::new(config.max_concurrent_requests)),
270 max_response_body_size: config.max_response_body_size,
271 max_tcp_error_retries: config.max_tcp_error_retries,
272 max_polling_time: config.max_polling_time,
273 })
274 }
275
276 pub fn set_identity<I>(&mut self, identity: I)
282 where
283 I: 'static + Identity,
284 {
285 self.identity = Arc::new(identity);
286 }
287 pub fn set_arc_identity(&mut self, identity: Arc<dyn Identity>) {
293 self.identity = identity;
294 }
295
296 pub async fn fetch_root_key(&self) -> Result<(), AgentError> {
305 if self.read_root_key()[..] != IC_ROOT_KEY[..] {
306 return Ok(());
308 }
309 let status = self.status().await?;
310 let Some(root_key) = status.root_key else {
311 return Err(AgentError::NoRootKeyInStatus(status));
312 };
313 self.set_root_key(root_key);
314 Ok(())
315 }
316
317 pub fn set_root_key(&self, root_key: Vec<u8>) {
322 *self.root_key.write().unwrap() = root_key;
323 }
324
325 pub fn read_root_key(&self) -> Vec<u8> {
327 self.root_key.read().unwrap().clone()
328 }
329
330 fn get_expiry_date(&self) -> u64 {
331 let expiry_raw = OffsetDateTime::now_utc() + self.ingress_expiry;
332 let mut rounded = expiry_raw.replace_nanosecond(0).unwrap();
333 if self.ingress_expiry.as_secs() > 90 {
334 rounded = rounded.replace_second(0).unwrap();
335 }
336 rounded.unix_timestamp_nanos().try_into().unwrap()
337 }
338
339 pub fn get_principal(&self) -> Result<Principal, String> {
341 self.identity.sender()
342 }
343
344 async fn query_endpoint<A>(
345 &self,
346 effective_canister_id: Principal,
347 serialized_bytes: Vec<u8>,
348 ) -> Result<A, AgentError>
349 where
350 A: serde::de::DeserializeOwned,
351 {
352 let _permit = self.concurrent_requests_semaphore.acquire().await;
353 let bytes = self
354 .execute(
355 Method::POST,
356 &format!("api/v3/canister/{}/query", effective_canister_id.to_text()),
357 Some(serialized_bytes),
358 )
359 .await?
360 .1;
361 serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)
362 }
363
364 async fn read_state_endpoint<A>(
365 &self,
366 effective_canister_id: Principal,
367 serialized_bytes: Vec<u8>,
368 ) -> Result<A, AgentError>
369 where
370 A: serde::de::DeserializeOwned,
371 {
372 let _permit = self.concurrent_requests_semaphore.acquire().await;
373 let endpoint = format!(
374 "api/v3/canister/{}/read_state",
375 effective_canister_id.to_text()
376 );
377 let bytes = self
378 .execute(Method::POST, &endpoint, Some(serialized_bytes))
379 .await?
380 .1;
381 serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)
382 }
383
384 async fn read_subnet_state_endpoint<A>(
385 &self,
386 subnet_id: Principal,
387 serialized_bytes: Vec<u8>,
388 ) -> Result<A, AgentError>
389 where
390 A: serde::de::DeserializeOwned,
391 {
392 let _permit = self.concurrent_requests_semaphore.acquire().await;
393 let endpoint = format!("api/v3/subnet/{}/read_state", subnet_id.to_text());
394 let bytes = self
395 .execute(Method::POST, &endpoint, Some(serialized_bytes))
396 .await?
397 .1;
398 serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)
399 }
400
401 async fn query_subnet_endpoint<A>(
402 &self,
403 subnet_id: Principal,
404 serialized_bytes: Vec<u8>,
405 ) -> Result<A, AgentError>
406 where
407 A: serde::de::DeserializeOwned,
408 {
409 let _permit = self.concurrent_requests_semaphore.acquire().await;
410 let endpoint = format!("api/v3/subnet/{}/query", subnet_id.to_text());
411 let bytes = self
412 .execute(Method::POST, &endpoint, Some(serialized_bytes))
413 .await?
414 .1;
415 serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)
416 }
417
418 async fn query_endpoint_for<A>(
419 &self,
420 effective_id: EffectiveId,
421 serialized_bytes: Vec<u8>,
422 ) -> Result<A, AgentError>
423 where
424 A: serde::de::DeserializeOwned,
425 {
426 match effective_id {
427 EffectiveId::Canister(p) => self.query_endpoint(p, serialized_bytes).await,
428 EffectiveId::Subnet(p) => self.query_subnet_endpoint(p, serialized_bytes).await,
429 }
430 }
431
432 async fn read_state_endpoint_for<A>(
433 &self,
434 effective_id: EffectiveId,
435 serialized_bytes: Vec<u8>,
436 ) -> Result<A, AgentError>
437 where
438 A: serde::de::DeserializeOwned,
439 {
440 match effective_id {
441 EffectiveId::Canister(p) => self.read_state_endpoint(p, serialized_bytes).await,
442 EffectiveId::Subnet(p) => self.read_subnet_state_endpoint(p, serialized_bytes).await,
443 }
444 }
445
446 async fn call_endpoint_for(
447 &self,
448 effective_id: EffectiveId,
449 serialized_bytes: Vec<u8>,
450 ) -> Result<TransportCallResponse, AgentError> {
451 match effective_id {
452 EffectiveId::Canister(p) => self.call_endpoint(p, serialized_bytes).await,
453 EffectiveId::Subnet(p) => self.call_subnet_endpoint(p, serialized_bytes).await,
454 }
455 }
456
457 async fn get_subnet_for(&self, effective_id: EffectiveId) -> Result<Arc<Subnet>, AgentError> {
458 match effective_id {
459 EffectiveId::Canister(p) => self.get_subnet_by_canister(&p).await,
460 EffectiveId::Subnet(p) => self.get_subnet_by_id(&p).await,
461 }
462 }
463
464 async fn fetch_subnet_for(&self, effective_id: EffectiveId) -> Result<Arc<Subnet>, AgentError> {
465 match effective_id {
466 EffectiveId::Canister(p) => self.fetch_subnet_by_canister(&p).await,
467 EffectiveId::Subnet(p) => self.fetch_subnet_by_id(&p).await,
468 }
469 }
470
471 async fn call_endpoint(
472 &self,
473 effective_canister_id: Principal,
474 serialized_bytes: Vec<u8>,
475 ) -> Result<TransportCallResponse, AgentError> {
476 let _permit = self.concurrent_requests_semaphore.acquire().await;
477 let endpoint = format!("api/v4/canister/{}/call", effective_canister_id.to_text());
478 let (status_code, response_body) = self
479 .execute(Method::POST, &endpoint, Some(serialized_bytes))
480 .await?;
481
482 if status_code == StatusCode::ACCEPTED {
483 return Ok(TransportCallResponse::Accepted);
484 }
485
486 serde_cbor::from_slice(&response_body).map_err(AgentError::InvalidCborData)
487 }
488
489 async fn call_subnet_endpoint(
490 &self,
491 subnet_id: Principal,
492 serialized_bytes: Vec<u8>,
493 ) -> Result<TransportCallResponse, AgentError> {
494 let _permit = self.concurrent_requests_semaphore.acquire().await;
495 let endpoint = format!("api/v4/subnet/{}/call", subnet_id.to_text());
496 let (status_code, response_body) = self
497 .execute(Method::POST, &endpoint, Some(serialized_bytes))
498 .await?;
499
500 if status_code == StatusCode::ACCEPTED {
501 return Ok(TransportCallResponse::Accepted);
502 }
503
504 serde_cbor::from_slice(&response_body).map_err(AgentError::InvalidCborData)
505 }
506
507 #[allow(clippy::too_many_arguments)]
510 async fn query_raw(
511 &self,
512 canister_id: Principal,
513 effective_canister_id: Principal,
514 method_name: String,
515 arg: Vec<u8>,
516 ingress_expiry_datetime: Option<u64>,
517 use_nonce: bool,
518 explicit_verify_query_signatures: Option<bool>,
519 ) -> Result<Vec<u8>, AgentError> {
520 let operation = Operation::Call {
521 canister: canister_id,
522 method: method_name.clone(),
523 };
524 let content = self.query_content(
525 canister_id,
526 method_name,
527 arg,
528 ingress_expiry_datetime,
529 use_nonce,
530 )?;
531 let serialized_bytes = sign_envelope(&content, self.identity.clone())?;
532 self.query_inner(
533 EffectiveId::Canister(effective_canister_id),
534 serialized_bytes,
535 content.to_request_id(),
536 explicit_verify_query_signatures,
537 operation,
538 )
539 .await
540 }
541
542 pub async fn query_signed(
552 &self,
553 effective_id: impl Into<EffectiveId>,
554 signed_query: Vec<u8>,
555 ) -> Result<Vec<u8>, AgentError> {
556 let envelope: Envelope =
557 serde_cbor::from_slice(&signed_query).map_err(AgentError::InvalidCborData)?;
558 let EnvelopeContent::Query {
559 canister_id,
560 method_name,
561 ..
562 } = &*envelope.content
563 else {
564 return Err(AgentError::CallDataMismatch {
565 field: "request_type".to_string(),
566 value_arg: "query".to_string(),
567 value_cbor: if matches!(*envelope.content, EnvelopeContent::Call { .. }) {
568 "update"
569 } else {
570 "read_state"
571 }
572 .to_string(),
573 });
574 };
575 let operation = Operation::Call {
576 canister: *canister_id,
577 method: method_name.clone(),
578 };
579 self.query_inner(
580 effective_id.into(),
581 signed_query,
582 envelope.content.to_request_id(),
583 None,
584 operation,
585 )
586 .await
587 }
588
589 async fn query_inner(
593 &self,
594 effective_id: EffectiveId,
595 signed_query: Vec<u8>,
596 request_id: RequestId,
597 explicit_verify_query_signatures: Option<bool>,
598 operation: Operation,
599 ) -> Result<Vec<u8>, AgentError> {
600 let response = if explicit_verify_query_signatures.unwrap_or(self.verify_query_signatures) {
601 let (response, mut subnet) = futures_util::try_join!(
602 self.query_endpoint_for::<QueryResponse>(effective_id, signed_query),
603 self.get_subnet_for(effective_id)
604 )?;
605 if response.signatures().is_empty() {
606 return Err(AgentError::MissingSignature);
607 } else if response.signatures().len() > subnet.node_keys.len() {
608 return Err(AgentError::TooManySignatures {
609 had: response.signatures().len(),
610 needed: subnet.node_keys.len(),
611 });
612 }
613 for signature in response.signatures() {
614 if OffsetDateTime::now_utc()
615 - OffsetDateTime::from_unix_timestamp_nanos(signature.timestamp.into()).unwrap()
616 > self.ingress_expiry
617 {
618 return Err(AgentError::CertificateOutdated(self.ingress_expiry));
619 }
620 let signable = response.signable(request_id, signature.timestamp);
621 let node_key = if let Some(node_key) = subnet.node_keys.get(&signature.identity) {
622 node_key
623 } else {
624 subnet = self.fetch_subnet_for(effective_id).await?;
625 subnet
626 .node_keys
627 .get(&signature.identity)
628 .ok_or(AgentError::CertificateNotAuthorized())?
629 };
630 if node_key.len() != 44 {
631 return Err(AgentError::DerKeyLengthMismatch {
632 expected: 44,
633 actual: node_key.len(),
634 });
635 }
636 const DER_PREFIX: [u8; 12] = [48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0];
637 if node_key[..12] != DER_PREFIX {
638 return Err(AgentError::DerPrefixMismatch {
639 expected: DER_PREFIX.to_vec(),
640 actual: node_key[..12].to_vec(),
641 });
642 }
643 let pubkey = PublicKey::deserialize_raw(&node_key[12..])
644 .map_err(|_| AgentError::MalformedPublicKey)?;
645
646 match pubkey.verify_signature(&signable, &signature.signature[..]) {
647 Ok(()) => (),
648 Err(SignatureError::InvalidSignature) => {
649 return Err(AgentError::QuerySignatureVerificationFailed)
650 }
651 Err(SignatureError::InvalidLength) => {
652 return Err(AgentError::MalformedSignature)
653 }
654 _ => unreachable!(),
655 }
656 }
657 response
658 } else {
659 self.query_endpoint_for::<QueryResponse>(effective_id, signed_query)
660 .await?
661 };
662
663 match response {
664 QueryResponse::Replied { reply, .. } => Ok(reply.arg),
665 QueryResponse::Rejected { reject, .. } => Err(AgentError::UncertifiedReject {
666 reject,
667 operation: Some(operation),
668 }),
669 }
670 }
671
672 fn query_content(
673 &self,
674 canister_id: Principal,
675 method_name: String,
676 arg: Vec<u8>,
677 ingress_expiry_datetime: Option<u64>,
678 use_nonce: bool,
679 ) -> Result<EnvelopeContent, AgentError> {
680 Ok(EnvelopeContent::Query {
681 sender: self.identity.sender().map_err(AgentError::SigningError)?,
682 canister_id,
683 method_name,
684 arg,
685 ingress_expiry: ingress_expiry_datetime.unwrap_or_else(|| self.get_expiry_date()),
686 nonce: use_nonce.then(|| self.nonce_factory.generate()).flatten(),
687 sender_info: self.identity.sender_info(),
688 })
689 }
690
691 async fn update_raw(
693 &self,
694 canister_id: Principal,
695 effective_canister_id: Principal,
696 method_name: String,
697 arg: Vec<u8>,
698 ingress_expiry_datetime: Option<u64>,
699 ) -> Result<CallResponse<(Vec<u8>, Certificate)>, AgentError> {
700 let nonce = self.nonce_factory.generate();
701 let content = self.update_content(
702 canister_id,
703 method_name.clone(),
704 arg,
705 ingress_expiry_datetime,
706 nonce,
707 )?;
708 let operation = Some(Operation::Call {
709 canister: canister_id,
710 method: method_name,
711 });
712 let request_id = to_request_id(&content)?;
713 let serialized_bytes = sign_envelope(&content, self.identity.clone())?;
714
715 let response_body = self
716 .call_endpoint(effective_canister_id, serialized_bytes)
717 .await?;
718
719 match response_body {
720 TransportCallResponse::Replied { certificate } => {
721 let certificate =
722 serde_cbor::from_slice(&certificate).map_err(AgentError::InvalidCborData)?;
723
724 self.verify(&certificate, effective_canister_id)?;
725 let status = lookup_request_status(&certificate, &request_id)?;
726
727 match status {
728 RequestStatusResponse::Replied(reply) => {
729 Ok(CallResponse::Response((reply.arg, certificate)))
730 }
731 RequestStatusResponse::Rejected(reject_response) => {
732 Err(AgentError::CertifiedReject {
733 reject: reject_response,
734 operation,
735 })?
736 }
737 _ => Ok(CallResponse::Poll(request_id)),
738 }
739 }
740 TransportCallResponse::Accepted => Ok(CallResponse::Poll(request_id)),
741 TransportCallResponse::NonReplicatedRejection(reject_response) => {
742 Err(AgentError::UncertifiedReject {
743 reject: reject_response,
744 operation,
745 })
746 }
747 }
748 }
749
750 pub async fn update_signed(
760 &self,
761 effective_id: impl Into<EffectiveId>,
762 signed_update: Vec<u8>,
763 ) -> Result<CallResponse<Vec<u8>>, AgentError> {
764 let effective_id = effective_id.into();
765 let envelope: Envelope =
766 serde_cbor::from_slice(&signed_update).map_err(AgentError::InvalidCborData)?;
767 let EnvelopeContent::Call {
768 canister_id,
769 method_name,
770 ..
771 } = &*envelope.content
772 else {
773 return Err(AgentError::CallDataMismatch {
774 field: "request_type".to_string(),
775 value_arg: "update".to_string(),
776 value_cbor: if matches!(*envelope.content, EnvelopeContent::Query { .. }) {
777 "query"
778 } else {
779 "read_state"
780 }
781 .to_string(),
782 });
783 };
784 let operation = Some(Operation::Call {
785 canister: *canister_id,
786 method: method_name.clone(),
787 });
788 let request_id = to_request_id(&envelope.content)?;
789
790 let response_body = self.call_endpoint_for(effective_id, signed_update).await?;
791
792 match response_body {
793 TransportCallResponse::Replied { certificate } => {
794 let certificate =
795 serde_cbor::from_slice(&certificate).map_err(AgentError::InvalidCborData)?;
796
797 self.verify(&certificate, effective_id)?;
798 let status = lookup_request_status(&certificate, &request_id)?;
799
800 match status {
801 RequestStatusResponse::Replied(reply) => Ok(CallResponse::Response(reply.arg)),
802 RequestStatusResponse::Rejected(reject_response) => {
803 Err(AgentError::CertifiedReject {
804 reject: reject_response,
805 operation,
806 })?
807 }
808 _ => Ok(CallResponse::Poll(request_id)),
809 }
810 }
811 TransportCallResponse::Accepted => Ok(CallResponse::Poll(request_id)),
812 TransportCallResponse::NonReplicatedRejection(reject_response) => {
813 Err(AgentError::UncertifiedReject {
814 reject: reject_response,
815 operation,
816 })
817 }
818 }
819 }
820
821 fn update_content(
822 &self,
823 canister_id: Principal,
824 method_name: String,
825 arg: Vec<u8>,
826 ingress_expiry_datetime: Option<u64>,
827 nonce: Option<Vec<u8>>,
828 ) -> Result<EnvelopeContent, AgentError> {
829 Ok(EnvelopeContent::Call {
830 canister_id,
831 method_name,
832 arg,
833 nonce,
834 sender: self.identity.sender().map_err(AgentError::SigningError)?,
835 ingress_expiry: ingress_expiry_datetime.unwrap_or_else(|| self.get_expiry_date()),
836 sender_info: self.identity.sender_info(),
837 })
838 }
839
840 fn poll_backoff(&self) -> ExponentialBackoff {
844 ExponentialBuilder::default()
845 .with_min_delay(Duration::from_millis(500))
846 .with_max_delay(Duration::from_secs(1))
847 .with_factor(1.4)
848 .with_jitter()
849 .without_max_times()
850 .with_total_delay(Some(self.max_polling_time))
851 .build()
852 }
853
854 pub async fn wait_signed(
860 &self,
861 request_id: &RequestId,
862 effective_id: impl Into<EffectiveId>,
863 signed_request_status: Vec<u8>,
864 ) -> Result<(Vec<u8>, Certificate), AgentError> {
865 let effective_id = effective_id.into();
866 let mut backoff = self.poll_backoff();
867 let mut slept = Duration::ZERO;
868
869 let mut request_accepted = false;
870 loop {
871 let (resp, cert) = self
872 .request_status_signed(request_id, effective_id, signed_request_status.clone())
873 .await?;
874 match resp {
875 RequestStatusResponse::Unknown => {
876 if slept > Duration::from_secs(5 * 60) {
880 return Err(AgentError::TimeoutWaitingForResponse());
881 }
882 }
883
884 RequestStatusResponse::Received | RequestStatusResponse::Processing => {
885 if !request_accepted {
886 backoff = self.poll_backoff();
887 slept = Duration::ZERO;
888 request_accepted = true;
889 }
890 }
891
892 RequestStatusResponse::Replied(ReplyResponse { arg, .. }) => {
893 return Ok((arg, cert))
894 }
895
896 RequestStatusResponse::Rejected(response) => {
897 return Err(AgentError::CertifiedReject {
898 reject: response,
899 operation: None,
900 })
901 }
902
903 RequestStatusResponse::Done => {
904 return Err(AgentError::RequestStatusDoneNoReply(String::from(
905 *request_id,
906 )))
907 }
908 };
909
910 match backoff.next() {
911 Some(duration) => {
912 slept += duration;
913 crate::util::sleep(duration).await;
914 }
915 None => return Err(AgentError::TimeoutWaitingForResponse()),
916 }
917 }
918 }
919
920 pub async fn wait(
926 &self,
927 request_id: &RequestId,
928 effective_id: impl Into<EffectiveId>,
929 ) -> Result<(Vec<u8>, Certificate), AgentError> {
930 self.wait_inner(request_id, effective_id.into(), None).await
931 }
932
933 async fn wait_inner(
934 &self,
935 request_id: &RequestId,
936 effective_id: EffectiveId,
937 operation: Option<Operation>,
938 ) -> Result<(Vec<u8>, Certificate), AgentError> {
939 let mut backoff = self.poll_backoff();
940 let mut slept = Duration::ZERO;
941
942 let mut request_accepted = false;
943 loop {
944 let (resp, cert) = self.request_status_raw(request_id, effective_id).await?;
945 match resp {
946 RequestStatusResponse::Unknown => {
947 if slept > Duration::from_secs(5 * 60) {
951 return Err(AgentError::TimeoutWaitingForResponse());
952 }
953 }
954
955 RequestStatusResponse::Received | RequestStatusResponse::Processing => {
956 if !request_accepted {
957 backoff = self.poll_backoff();
965 slept = Duration::ZERO;
966 request_accepted = true;
967 }
968 }
969
970 RequestStatusResponse::Replied(ReplyResponse { arg, .. }) => {
971 return Ok((arg, cert))
972 }
973
974 RequestStatusResponse::Rejected(response) => {
975 return Err(AgentError::CertifiedReject {
976 reject: response,
977 operation,
978 })
979 }
980
981 RequestStatusResponse::Done => {
982 return Err(AgentError::RequestStatusDoneNoReply(String::from(
983 *request_id,
984 )))
985 }
986 };
987
988 match backoff.next() {
989 Some(duration) => {
990 slept += duration;
991 crate::util::sleep(duration).await;
992 }
993 None => return Err(AgentError::TimeoutWaitingForResponse()),
994 }
995 }
996 }
997
998 pub async fn read_state_raw(
1005 &self,
1006 paths: Vec<Vec<Label>>,
1007 effective_id: impl Into<EffectiveId>,
1008 ) -> Result<Certificate, AgentError> {
1009 let effective_id = effective_id.into();
1010 let content = self.read_state_content(paths)?;
1011 let serialized_bytes = sign_envelope(&content, self.identity.clone())?;
1012
1013 let read_state_response: ReadStateResponse = self
1014 .read_state_endpoint_for(effective_id, serialized_bytes)
1015 .await?;
1016 let cert: Certificate = serde_cbor::from_slice(&read_state_response.certificate)
1017 .map_err(AgentError::InvalidCborData)?;
1018 self.verify(&cert, effective_id)?;
1019 Ok(cert)
1020 }
1021
1022 pub async fn read_subnet_state_raw(
1027 &self,
1028 paths: Vec<Vec<Label>>,
1029 subnet_id: Principal,
1030 ) -> Result<Certificate, AgentError> {
1031 self.read_state_raw(paths, EffectiveId::Subnet(subnet_id))
1032 .await
1033 }
1034
1035 fn read_state_content(&self, paths: Vec<Vec<Label>>) -> Result<EnvelopeContent, AgentError> {
1036 Ok(EnvelopeContent::ReadState {
1037 sender: self.identity.sender().map_err(AgentError::SigningError)?,
1038 paths,
1039 ingress_expiry: self.get_expiry_date(),
1040 })
1041 }
1042
1043 pub fn verify(
1050 &self,
1051 cert: &Certificate,
1052 effective_id: impl Into<EffectiveId>,
1053 ) -> Result<(), AgentError> {
1054 match effective_id.into() {
1055 EffectiveId::Canister(p) => self.verify_cert(cert, p)?,
1056 EffectiveId::Subnet(p) => self.verify_cert_for_subnet(cert, p)?,
1057 }
1058 self.verify_cert_timestamp(cert)?;
1059 Ok(())
1060 }
1061
1062 fn verify_cert(
1063 &self,
1064 cert: &Certificate,
1065 effective_canister_id: Principal,
1066 ) -> Result<(), AgentError> {
1067 let sig = &cert.signature;
1068
1069 let root_hash = cert.tree.digest();
1070 let mut msg = vec![];
1071 msg.extend_from_slice(IC_STATE_ROOT_DOMAIN_SEPARATOR);
1072 msg.extend_from_slice(&root_hash);
1073
1074 let der_key = self.check_delegation(&cert.delegation, effective_canister_id)?;
1075 let key = extract_der(der_key)?;
1076
1077 ic_verify_bls_signature::verify_bls_signature(sig, &msg, &key)
1078 .map_err(|_| AgentError::CertificateVerificationFailed())?;
1079 Ok(())
1080 }
1081
1082 pub fn verify_for_subnet(
1088 &self,
1089 cert: &Certificate,
1090 subnet_id: Principal,
1091 ) -> Result<(), AgentError> {
1092 self.verify(cert, EffectiveId::Subnet(subnet_id))
1093 }
1094
1095 fn verify_cert_for_subnet(
1096 &self,
1097 cert: &Certificate,
1098 subnet_id: Principal,
1099 ) -> Result<(), AgentError> {
1100 let sig = &cert.signature;
1101
1102 let root_hash = cert.tree.digest();
1103 let mut msg = vec![];
1104 msg.extend_from_slice(IC_STATE_ROOT_DOMAIN_SEPARATOR);
1105 msg.extend_from_slice(&root_hash);
1106
1107 let der_key = self.check_delegation_for_subnet(&cert.delegation, subnet_id)?;
1108 let key = extract_der(der_key)?;
1109
1110 ic_verify_bls_signature::verify_bls_signature(sig, &msg, &key)
1111 .map_err(|_| AgentError::CertificateVerificationFailed())?;
1112 Ok(())
1113 }
1114
1115 fn verify_cert_timestamp(&self, cert: &Certificate) -> Result<(), AgentError> {
1116 let time = lookup_time(cert)?;
1119 if (OffsetDateTime::now_utc()
1120 - OffsetDateTime::from_unix_timestamp_nanos(time.into()).unwrap())
1121 > self.ingress_expiry
1122 {
1123 Err(AgentError::CertificateOutdated(self.ingress_expiry))
1124 } else {
1125 Ok(())
1126 }
1127 }
1128
1129 fn check_delegation(
1130 &self,
1131 delegation: &Option<Delegation>,
1132 effective_canister_id: Principal,
1133 ) -> Result<Vec<u8>, AgentError> {
1134 match delegation {
1135 None => Ok(self.read_root_key()),
1136 Some(delegation) => {
1137 let cert: Certificate = serde_cbor::from_slice(&delegation.certificate)
1138 .map_err(AgentError::InvalidCborData)?;
1139 if cert.delegation.is_some() {
1140 return Err(AgentError::CertificateHasTooManyDelegations);
1141 }
1142 self.verify_cert(&cert, effective_canister_id)?;
1143 let canister_range_shards_lookup =
1144 ["canister_ranges".as_bytes(), delegation.subnet_id.as_ref()];
1145 let ranges: Vec<(Principal, Principal)> =
1146 match lookup_tree(&cert.tree, canister_range_shards_lookup) {
1147 Ok(canister_range_shards) => {
1148 let mut shard_paths = canister_range_shards
1149 .list_paths() .into_iter()
1151 .map(|mut x| {
1152 x.pop() .ok_or_else(AgentError::CertificateVerificationFailed)
1154 })
1155 .collect::<Result<Vec<_>, _>>()?;
1156 if shard_paths.is_empty() {
1157 return Err(AgentError::CertificateNotAuthorized());
1158 }
1159 shard_paths.sort_unstable();
1160 let shard_division = shard_paths.partition_point(|shard| {
1161 shard.as_bytes() <= effective_canister_id.as_slice()
1162 });
1163 if shard_division == 0 {
1164 return Err(AgentError::CertificateNotAuthorized());
1166 }
1167 let max_potential_shard = &shard_paths[shard_division - 1];
1168 let canister_range = lookup_value(
1169 &canister_range_shards,
1170 [max_potential_shard.as_bytes()],
1171 )?;
1172 serde_cbor::from_slice(canister_range)
1173 .map_err(AgentError::InvalidCborData)?
1174 }
1175 Err(AgentError::LookupPathAbsent(_) | AgentError::LookupPathUnknown(_)) => {
1177 let subnet_ranges_path = [
1178 "subnet".as_bytes(),
1179 delegation.subnet_id.as_ref(),
1180 "canister_ranges".as_bytes(),
1181 ];
1182 let canister_range = lookup_value(&cert.tree, subnet_ranges_path)?;
1183 serde_cbor::from_slice(canister_range)
1184 .map_err(AgentError::InvalidCborData)?
1185 }
1186 Err(e) => return Err(e),
1187 };
1188 if !principal_is_within_ranges(&effective_canister_id, &ranges[..]) {
1189 return Err(AgentError::CertificateNotAuthorized());
1191 }
1192
1193 let public_key_path = [
1194 "subnet".as_bytes(),
1195 delegation.subnet_id.as_ref(),
1196 "public_key".as_bytes(),
1197 ];
1198 lookup_value(&cert.tree, public_key_path).map(<[u8]>::to_vec)
1199 }
1200 }
1201 }
1202
1203 fn check_delegation_for_subnet(
1204 &self,
1205 delegation: &Option<Delegation>,
1206 subnet_id: Principal,
1207 ) -> Result<Vec<u8>, AgentError> {
1208 match delegation {
1209 None => Ok(self.read_root_key()),
1210 Some(delegation) => {
1211 let cert: Certificate = serde_cbor::from_slice(&delegation.certificate)
1212 .map_err(AgentError::InvalidCborData)?;
1213 if cert.delegation.is_some() {
1214 return Err(AgentError::CertificateHasTooManyDelegations);
1215 }
1216 self.verify_cert_for_subnet(&cert, subnet_id)?;
1217 let public_key_path = [
1218 "subnet".as_bytes(),
1219 subnet_id.as_ref(),
1220 "public_key".as_bytes(),
1221 ];
1222 let pk = lookup_value(&cert.tree, public_key_path)
1223 .map_err(|_| AgentError::CertificateNotAuthorized())?
1224 .to_vec();
1225 Ok(pk)
1226 }
1227 }
1228 }
1229
1230 pub async fn read_state_canister_info(
1233 &self,
1234 canister_id: Principal,
1235 path: &str,
1236 ) -> Result<Vec<u8>, AgentError> {
1237 let paths: Vec<Vec<Label>> = vec![vec![
1238 "canister".into(),
1239 Label::from_bytes(canister_id.as_slice()),
1240 path.into(),
1241 ]];
1242
1243 let cert = self.read_state_raw(paths, canister_id).await?;
1244
1245 lookup_canister_info(cert, canister_id, path)
1246 }
1247
1248 pub async fn read_state_canister_controllers(
1250 &self,
1251 canister_id: Principal,
1252 ) -> Result<Vec<Principal>, AgentError> {
1253 let blob = self
1254 .read_state_canister_info(canister_id, "controllers")
1255 .await?;
1256 let controllers: Vec<Principal> =
1257 serde_cbor::from_slice(&blob).map_err(AgentError::InvalidCborData)?;
1258 Ok(controllers)
1259 }
1260
1261 pub async fn read_state_canister_module_hash(
1263 &self,
1264 canister_id: Principal,
1265 ) -> Result<Vec<u8>, AgentError> {
1266 self.read_state_canister_info(canister_id, "module_hash")
1267 .await
1268 }
1269
1270 pub async fn read_state_canister_metadata(
1272 &self,
1273 canister_id: Principal,
1274 path: &str,
1275 ) -> Result<Vec<u8>, AgentError> {
1276 let paths: Vec<Vec<Label>> = vec![vec![
1277 "canister".into(),
1278 Label::from_bytes(canister_id.as_slice()),
1279 "metadata".into(),
1280 path.into(),
1281 ]];
1282
1283 let cert = self.read_state_raw(paths, canister_id).await?;
1284
1285 lookup_canister_metadata(cert, canister_id, path)
1286 }
1287
1288 pub async fn read_state_subnet_metrics(
1290 &self,
1291 subnet_id: Principal,
1292 ) -> Result<SubnetMetrics, AgentError> {
1293 let paths = vec![vec![
1294 "subnet".into(),
1295 Label::from_bytes(subnet_id.as_slice()),
1296 "metrics".into(),
1297 ]];
1298 let cert = self.read_subnet_state_raw(paths, subnet_id).await?;
1299 lookup_subnet_metrics(cert, subnet_id)
1300 }
1301
1302 pub async fn read_state_subnet_canister_ranges(
1304 &self,
1305 subnet_id: Principal,
1306 ) -> Result<Vec<(Principal, Principal)>, AgentError> {
1307 let paths = vec![vec![
1308 "subnet".into(),
1309 Label::from_bytes(subnet_id.as_slice()),
1310 "canister_ranges".into(),
1311 ]];
1312 let cert = self.read_subnet_state_raw(paths, subnet_id).await?;
1313 lookup_subnet_canister_ranges(&cert, subnet_id)
1314 }
1315
1316 pub async fn request_status_raw(
1322 &self,
1323 request_id: &RequestId,
1324 effective_id: impl Into<EffectiveId>,
1325 ) -> Result<(RequestStatusResponse, Certificate), AgentError> {
1326 let paths: Vec<Vec<Label>> =
1327 vec![vec!["request_status".into(), request_id.to_vec().into()]];
1328
1329 let cert = self.read_state_raw(paths, effective_id).await?;
1330
1331 Ok((lookup_request_status(&cert, request_id)?, cert))
1332 }
1333
1334 pub async fn request_status_signed(
1342 &self,
1343 request_id: &RequestId,
1344 effective_id: impl Into<EffectiveId>,
1345 signed_request_status: Vec<u8>,
1346 ) -> Result<(RequestStatusResponse, Certificate), AgentError> {
1347 let effective_id = effective_id.into();
1348 let _envelope: Envelope =
1349 serde_cbor::from_slice(&signed_request_status).map_err(AgentError::InvalidCborData)?;
1350 let read_state_response: ReadStateResponse = self
1351 .read_state_endpoint_for(effective_id, signed_request_status)
1352 .await?;
1353
1354 let cert: Certificate = serde_cbor::from_slice(&read_state_response.certificate)
1355 .map_err(AgentError::InvalidCborData)?;
1356 self.verify(&cert, effective_id)?;
1357 Ok((lookup_request_status(&cert, request_id)?, cert))
1358 }
1359
1360 pub fn update<S: Into<String>>(
1363 &self,
1364 canister_id: &Principal,
1365 method_name: S,
1366 ) -> UpdateBuilder<'_> {
1367 UpdateBuilder::new(self, *canister_id, method_name.into())
1368 }
1369
1370 pub async fn status(&self) -> Result<Status, AgentError> {
1372 let endpoint = "api/v2/status";
1373 let bytes = self.execute(Method::GET, endpoint, None).await?.1;
1374
1375 let cbor: serde_cbor::Value =
1376 serde_cbor::from_slice(&bytes).map_err(AgentError::InvalidCborData)?;
1377
1378 Status::try_from(&cbor).map_err(|_| AgentError::InvalidReplicaStatus)
1379 }
1380
1381 pub fn query<S: Into<String>>(
1384 &self,
1385 canister_id: &Principal,
1386 method_name: S,
1387 ) -> QueryBuilder<'_> {
1388 QueryBuilder::new(self, *canister_id, method_name.into())
1389 }
1390
1391 pub fn sign_request_status(
1397 &self,
1398 effective_id: impl Into<EffectiveId>,
1399 request_id: RequestId,
1400 ) -> Result<SignedRequestStatus, AgentError> {
1401 let effective_id = effective_id.into();
1402 let paths: Vec<Vec<Label>> =
1403 vec![vec!["request_status".into(), request_id.to_vec().into()]];
1404 let read_state_content = self.read_state_content(paths)?;
1405 let signed_request_status = sign_envelope(&read_state_content, self.identity.clone())?;
1406 let ingress_expiry = read_state_content.ingress_expiry();
1407 let sender = *read_state_content.sender();
1408 Ok(SignedRequestStatus {
1409 ingress_expiry,
1410 sender,
1411 effective_canister_id: effective_id.as_principal(),
1412 request_id,
1413 signed_request_status,
1414 })
1415 }
1416
1417 pub async fn get_subnet_by_canister(
1420 &self,
1421 canister: &Principal,
1422 ) -> Result<Arc<Subnet>, AgentError> {
1423 let subnet = self
1424 .subnet_key_cache
1425 .lock()
1426 .unwrap()
1427 .get_subnet_by_canister(canister);
1428 if let Some(subnet) = subnet {
1429 Ok(subnet)
1430 } else {
1431 self.fetch_subnet_by_canister(canister).await
1432 }
1433 }
1434
1435 pub async fn get_subnet_by_id(&self, subnet_id: &Principal) -> Result<Arc<Subnet>, AgentError> {
1438 let subnet = self
1439 .subnet_key_cache
1440 .lock()
1441 .unwrap()
1442 .get_subnet_by_id(subnet_id);
1443 if let Some(subnet) = subnet {
1444 Ok(subnet)
1445 } else {
1446 self.fetch_subnet_by_id(subnet_id).await
1447 }
1448 }
1449
1450 pub async fn fetch_api_boundary_nodes_by_canister_id(
1452 &self,
1453 canister_id: Principal,
1454 ) -> Result<Vec<ApiBoundaryNode>, AgentError> {
1455 let paths = vec![vec!["api_boundary_nodes".into()]];
1456 let certificate = self.read_state_raw(paths, canister_id).await?;
1457 let api_boundary_nodes = lookup_api_boundary_nodes(certificate)?;
1458 Ok(api_boundary_nodes)
1459 }
1460
1461 pub async fn fetch_api_boundary_nodes_by_subnet_id(
1463 &self,
1464 subnet_id: Principal,
1465 ) -> Result<Vec<ApiBoundaryNode>, AgentError> {
1466 let paths = vec![vec!["api_boundary_nodes".into()]];
1467 let certificate = self.read_subnet_state_raw(paths, subnet_id).await?;
1468 let api_boundary_nodes = lookup_api_boundary_nodes(certificate)?;
1469 Ok(api_boundary_nodes)
1470 }
1471
1472 pub async fn fetch_subnet_by_canister(
1481 &self,
1482 canister: &Principal,
1483 ) -> Result<Arc<Subnet>, AgentError> {
1484 let canister_cert = self
1485 .read_state_raw(vec![vec!["subnet".into()]], *canister)
1486 .await?;
1487 let subnet_id = if let Some(delegation) = canister_cert.delegation.as_ref() {
1488 Principal::try_from_slice(&delegation.subnet_id)?
1489 } else {
1490 Principal::self_authenticating(&self.root_key.read().unwrap()[..])
1492 };
1493 let mut subnet = lookup_incomplete_subnet(&subnet_id, &canister_cert)?;
1494 let canister_ranges = if let Some(delegation) = canister_cert.delegation.as_ref() {
1495 let delegation_cert: Certificate = serde_cbor::from_slice(&delegation.certificate)?;
1497 lookup_canister_ranges(&subnet_id, &delegation_cert)?
1498 } else {
1499 lookup_canister_ranges(&subnet_id, &canister_cert)?
1500 };
1501 subnet.canister_ranges = canister_ranges;
1502 if !subnet.canister_ranges.contains(canister) {
1503 return Err(AgentError::CertificateNotAuthorized());
1504 }
1505 let subnet = Arc::new(subnet);
1506 self.subnet_key_cache
1507 .lock()
1508 .unwrap()
1509 .insert_subnet(subnet_id, subnet.clone());
1510 Ok(subnet)
1511 }
1512
1513 pub async fn fetch_subnet_by_id(
1524 &self,
1525 subnet_id: &Principal,
1526 ) -> Result<Arc<Subnet>, AgentError> {
1527 let subnet_cert = self
1528 .read_subnet_state_raw(
1529 vec![
1530 vec!["canister_ranges".into(), subnet_id.as_slice().into()],
1531 vec!["subnet".into(), subnet_id.as_slice().into()],
1532 ],
1533 *subnet_id,
1534 )
1535 .await?;
1536 let subnet = lookup_subnet_and_ranges(subnet_id, &subnet_cert)?;
1537 let subnet = Arc::new(subnet);
1538 self.subnet_key_cache
1541 .lock()
1542 .unwrap()
1543 .insert_subnet_keys_only(*subnet_id, subnet.clone());
1544 Ok(subnet)
1545 }
1546
1547 async fn request(
1548 &self,
1549 method: Method,
1550 endpoint: &str,
1551 body: Option<Vec<u8>>,
1552 ) -> Result<(StatusCode, HeaderMap, Vec<u8>), AgentError> {
1553 let body = body.map(Bytes::from);
1554
1555 let create_request_with_generated_url = || -> Result<http::Request<Bytes>, AgentError> {
1556 let url = self.route_provider.route()?.join(endpoint)?;
1557 let uri = Uri::from_str(url.as_str())
1558 .map_err(|e| AgentError::InvalidReplicaUrl(e.to_string()))?;
1559 let body = body.clone().unwrap_or_default();
1560 let request = http::Request::builder()
1561 .method(method.clone())
1562 .uri(uri)
1563 .header(CONTENT_TYPE, "application/cbor")
1564 .body(body)
1565 .map_err(|e| {
1566 AgentError::TransportError(TransportError::Generic(format!(
1567 "unable to create request: {e:#}"
1568 )))
1569 })?;
1570
1571 Ok(request)
1572 };
1573
1574 let response = self
1575 .client
1576 .call(
1577 &create_request_with_generated_url,
1578 self.max_tcp_error_retries,
1579 self.max_response_body_size,
1580 )
1581 .await?;
1582
1583 let (parts, body) = response.into_parts();
1584
1585 Ok((parts.status, parts.headers, body.to_vec()))
1586 }
1587
1588 async fn execute(
1589 &self,
1590 method: Method,
1591 endpoint: &str,
1592 body: Option<Vec<u8>>,
1593 ) -> Result<(StatusCode, Vec<u8>), AgentError> {
1594 let request_result = self.request(method.clone(), endpoint, body.clone()).await?;
1595
1596 let status = request_result.0;
1597 let headers = request_result.1;
1598 let body = request_result.2;
1599
1600 if status.is_client_error() || status.is_server_error() {
1601 Err(AgentError::HttpError(HttpErrorPayload {
1602 status: status.into(),
1603 content_type: headers
1604 .get(CONTENT_TYPE)
1605 .and_then(|value| value.to_str().ok())
1606 .map(str::to_string),
1607 content: body,
1608 }))
1609 } else if !(status == StatusCode::OK || status == StatusCode::ACCEPTED) {
1610 Err(AgentError::InvalidHttpResponse(format!(
1611 "Expected `200`, `202`, 4xx`, or `5xx` HTTP status code. Got: {status}",
1612 )))
1613 } else {
1614 Ok((status, body))
1615 }
1616 }
1617}
1618
1619fn principal_is_within_ranges(principal: &Principal, ranges: &[(Principal, Principal)]) -> bool {
1622 ranges
1623 .iter()
1624 .any(|r| principal >= &r.0 && principal <= &r.1)
1625}
1626
1627fn sign_envelope(
1628 content: &EnvelopeContent,
1629 identity: Arc<dyn Identity>,
1630) -> Result<Vec<u8>, AgentError> {
1631 let signature = identity.sign(content).map_err(AgentError::SigningError)?;
1632
1633 let envelope = Envelope {
1634 content: Cow::Borrowed(content),
1635 sender_pubkey: signature.public_key,
1636 sender_sig: signature.signature,
1637 sender_delegation: signature.delegations,
1638 };
1639
1640 let mut serialized_bytes = Vec::new();
1641 let mut serializer = serde_cbor::Serializer::new(&mut serialized_bytes);
1642 serializer.self_describe()?;
1643 envelope.serialize(&mut serializer)?;
1644
1645 Ok(serialized_bytes)
1646}
1647
1648pub fn signed_query_inspect(
1651 sender: Principal,
1652 canister_id: Principal,
1653 method_name: &str,
1654 arg: &[u8],
1655 ingress_expiry: u64,
1656 signed_query: Vec<u8>,
1657) -> Result<(), AgentError> {
1658 let envelope: Envelope =
1659 serde_cbor::from_slice(&signed_query).map_err(AgentError::InvalidCborData)?;
1660 match envelope.content.as_ref() {
1661 EnvelopeContent::Query {
1662 ingress_expiry: ingress_expiry_cbor,
1663 sender: sender_cbor,
1664 canister_id: canister_id_cbor,
1665 method_name: method_name_cbor,
1666 arg: arg_cbor,
1667 nonce: _nonce,
1668 sender_info: _,
1669 } => {
1670 if ingress_expiry != *ingress_expiry_cbor {
1671 return Err(AgentError::CallDataMismatch {
1672 field: "ingress_expiry".to_string(),
1673 value_arg: ingress_expiry.to_string(),
1674 value_cbor: ingress_expiry_cbor.to_string(),
1675 });
1676 }
1677 if sender != *sender_cbor {
1678 return Err(AgentError::CallDataMismatch {
1679 field: "sender".to_string(),
1680 value_arg: sender.to_string(),
1681 value_cbor: sender_cbor.to_string(),
1682 });
1683 }
1684 if canister_id != *canister_id_cbor {
1685 return Err(AgentError::CallDataMismatch {
1686 field: "canister_id".to_string(),
1687 value_arg: canister_id.to_string(),
1688 value_cbor: canister_id_cbor.to_string(),
1689 });
1690 }
1691 if method_name != *method_name_cbor {
1692 return Err(AgentError::CallDataMismatch {
1693 field: "method_name".to_string(),
1694 value_arg: method_name.to_string(),
1695 value_cbor: method_name_cbor.clone(),
1696 });
1697 }
1698 if arg != *arg_cbor {
1699 return Err(AgentError::CallDataMismatch {
1700 field: "arg".to_string(),
1701 value_arg: format!("{arg:?}"),
1702 value_cbor: format!("{arg_cbor:?}"),
1703 });
1704 }
1705 }
1706 EnvelopeContent::Call { .. } => {
1707 return Err(AgentError::CallDataMismatch {
1708 field: "request_type".to_string(),
1709 value_arg: "query".to_string(),
1710 value_cbor: "call".to_string(),
1711 })
1712 }
1713 EnvelopeContent::ReadState { .. } => {
1714 return Err(AgentError::CallDataMismatch {
1715 field: "request_type".to_string(),
1716 value_arg: "query".to_string(),
1717 value_cbor: "read_state".to_string(),
1718 })
1719 }
1720 }
1721 Ok(())
1722}
1723
1724pub fn signed_update_inspect(
1727 sender: Principal,
1728 canister_id: Principal,
1729 method_name: &str,
1730 arg: &[u8],
1731 ingress_expiry: u64,
1732 signed_update: Vec<u8>,
1733) -> Result<(), AgentError> {
1734 let envelope: Envelope =
1735 serde_cbor::from_slice(&signed_update).map_err(AgentError::InvalidCborData)?;
1736 match envelope.content.as_ref() {
1737 EnvelopeContent::Call {
1738 nonce: _nonce,
1739 ingress_expiry: ingress_expiry_cbor,
1740 sender: sender_cbor,
1741 canister_id: canister_id_cbor,
1742 method_name: method_name_cbor,
1743 arg: arg_cbor,
1744 sender_info: _,
1745 } => {
1746 if ingress_expiry != *ingress_expiry_cbor {
1747 return Err(AgentError::CallDataMismatch {
1748 field: "ingress_expiry".to_string(),
1749 value_arg: ingress_expiry.to_string(),
1750 value_cbor: ingress_expiry_cbor.to_string(),
1751 });
1752 }
1753 if sender != *sender_cbor {
1754 return Err(AgentError::CallDataMismatch {
1755 field: "sender".to_string(),
1756 value_arg: sender.to_string(),
1757 value_cbor: sender_cbor.to_string(),
1758 });
1759 }
1760 if canister_id != *canister_id_cbor {
1761 return Err(AgentError::CallDataMismatch {
1762 field: "canister_id".to_string(),
1763 value_arg: canister_id.to_string(),
1764 value_cbor: canister_id_cbor.to_string(),
1765 });
1766 }
1767 if method_name != *method_name_cbor {
1768 return Err(AgentError::CallDataMismatch {
1769 field: "method_name".to_string(),
1770 value_arg: method_name.to_string(),
1771 value_cbor: method_name_cbor.clone(),
1772 });
1773 }
1774 if arg != *arg_cbor {
1775 return Err(AgentError::CallDataMismatch {
1776 field: "arg".to_string(),
1777 value_arg: format!("{arg:?}"),
1778 value_cbor: format!("{arg_cbor:?}"),
1779 });
1780 }
1781 }
1782 EnvelopeContent::ReadState { .. } => {
1783 return Err(AgentError::CallDataMismatch {
1784 field: "request_type".to_string(),
1785 value_arg: "call".to_string(),
1786 value_cbor: "read_state".to_string(),
1787 })
1788 }
1789 EnvelopeContent::Query { .. } => {
1790 return Err(AgentError::CallDataMismatch {
1791 field: "request_type".to_string(),
1792 value_arg: "call".to_string(),
1793 value_cbor: "query".to_string(),
1794 })
1795 }
1796 }
1797 Ok(())
1798}
1799
1800pub fn signed_request_status_inspect(
1803 sender: Principal,
1804 request_id: &RequestId,
1805 ingress_expiry: u64,
1806 signed_request_status: Vec<u8>,
1807) -> Result<(), AgentError> {
1808 let paths: Vec<Vec<Label>> = vec![vec!["request_status".into(), request_id.to_vec().into()]];
1809 let envelope: Envelope =
1810 serde_cbor::from_slice(&signed_request_status).map_err(AgentError::InvalidCborData)?;
1811 match envelope.content.as_ref() {
1812 EnvelopeContent::ReadState {
1813 ingress_expiry: ingress_expiry_cbor,
1814 sender: sender_cbor,
1815 paths: paths_cbor,
1816 } => {
1817 if ingress_expiry != *ingress_expiry_cbor {
1818 return Err(AgentError::CallDataMismatch {
1819 field: "ingress_expiry".to_string(),
1820 value_arg: ingress_expiry.to_string(),
1821 value_cbor: ingress_expiry_cbor.to_string(),
1822 });
1823 }
1824 if sender != *sender_cbor {
1825 return Err(AgentError::CallDataMismatch {
1826 field: "sender".to_string(),
1827 value_arg: sender.to_string(),
1828 value_cbor: sender_cbor.to_string(),
1829 });
1830 }
1831
1832 if paths != *paths_cbor {
1833 return Err(AgentError::CallDataMismatch {
1834 field: "paths".to_string(),
1835 value_arg: format!("{paths:?}"),
1836 value_cbor: format!("{paths_cbor:?}"),
1837 });
1838 }
1839 }
1840 EnvelopeContent::Query { .. } => {
1841 return Err(AgentError::CallDataMismatch {
1842 field: "request_type".to_string(),
1843 value_arg: "read_state".to_string(),
1844 value_cbor: "query".to_string(),
1845 })
1846 }
1847 EnvelopeContent::Call { .. } => {
1848 return Err(AgentError::CallDataMismatch {
1849 field: "request_type".to_string(),
1850 value_arg: "read_state".to_string(),
1851 value_cbor: "call".to_string(),
1852 })
1853 }
1854 }
1855 Ok(())
1856}
1857
1858#[derive(Clone)]
1864struct CachedSubnet {
1865 subnet: Arc<Subnet>,
1866 ranges_authoritative: bool,
1869}
1870
1871#[derive(Clone)]
1872struct SubnetCache {
1873 subnets: TimedCache<Principal, CachedSubnet>,
1874 canister_index: RangeInclusiveMap<Principal, Principal>,
1875}
1876
1877impl SubnetCache {
1878 fn new() -> Self {
1879 Self {
1880 subnets: TimedCache::with_lifespan(Duration::from_secs(300)),
1881 canister_index: RangeInclusiveMap::new(),
1882 }
1883 }
1884
1885 fn get_subnet_by_canister(&mut self, canister: &Principal) -> Option<Arc<Subnet>> {
1886 self.canister_index
1887 .get(canister)
1888 .and_then(|subnet_id| self.subnets.cache_get(subnet_id).cloned())
1889 .filter(|cached| cached.ranges_authoritative)
1892 .map(|cached| cached.subnet)
1893 .filter(|subnet| subnet.canister_ranges.contains(canister))
1894 }
1895
1896 fn get_subnet_by_id(&mut self, subnet_id: &Principal) -> Option<Arc<Subnet>> {
1897 self.subnets
1898 .cache_get(subnet_id)
1899 .map(|cached| cached.subnet.clone())
1900 }
1901
1902 fn insert_subnet(&mut self, subnet_id: Principal, subnet: Arc<Subnet>) {
1905 for range in subnet.canister_ranges.iter() {
1906 self.canister_index.insert(range.clone(), subnet_id);
1907 }
1908 self.subnets.cache_set(
1909 subnet_id,
1910 CachedSubnet {
1911 subnet,
1912 ranges_authoritative: true,
1913 },
1914 );
1915 }
1916
1917 fn insert_subnet_keys_only(&mut self, subnet_id: Principal, subnet: Arc<Subnet>) {
1926 self.subnets.cache_set(
1927 subnet_id,
1928 CachedSubnet {
1929 subnet,
1930 ranges_authoritative: false,
1931 },
1932 );
1933 }
1934}
1935
1936#[derive(Debug, Clone)]
1938pub struct ApiBoundaryNode {
1939 pub domain: String,
1941 pub ipv6_address: String,
1943 pub ipv4_address: Option<String>,
1945}
1946
1947#[derive(Debug, Clone)]
1951#[non_exhaustive]
1952pub struct QueryBuilder<'agent> {
1953 agent: &'agent Agent,
1954 pub effective_canister_id: Principal,
1956 pub canister_id: Principal,
1958 pub method_name: String,
1960 pub arg: Vec<u8>,
1962 pub ingress_expiry_datetime: Option<u64>,
1964 pub use_nonce: bool,
1966}
1967
1968impl<'agent> QueryBuilder<'agent> {
1969 pub fn new(agent: &'agent Agent, canister_id: Principal, method_name: String) -> Self {
1971 Self {
1972 agent,
1973 effective_canister_id: canister_id,
1974 canister_id,
1975 method_name,
1976 arg: vec![],
1977 ingress_expiry_datetime: None,
1978 use_nonce: false,
1979 }
1980 }
1981
1982 pub fn with_effective_canister_id(mut self, canister_id: Principal) -> Self {
1984 self.effective_canister_id = canister_id;
1985 self
1986 }
1987
1988 pub fn with_arg<A: Into<Vec<u8>>>(mut self, arg: A) -> Self {
1990 self.arg = arg.into();
1991 self
1992 }
1993
1994 pub fn expire_at(mut self, time: impl Into<OffsetDateTime>) -> Self {
1996 self.ingress_expiry_datetime = Some(time.into().unix_timestamp_nanos() as u64);
1997 self
1998 }
1999
2000 pub fn expire_after(mut self, duration: Duration) -> Self {
2002 self.ingress_expiry_datetime = Some(
2003 OffsetDateTime::now_utc()
2004 .saturating_add(duration.try_into().expect("negative duration"))
2005 .unix_timestamp_nanos() as u64,
2006 );
2007 self
2008 }
2009
2010 pub fn with_nonce_generation(mut self) -> Self {
2013 self.use_nonce = true;
2014 self
2015 }
2016
2017 pub async fn call(self) -> Result<Vec<u8>, AgentError> {
2019 self.agent
2020 .query_raw(
2021 self.canister_id,
2022 self.effective_canister_id,
2023 self.method_name,
2024 self.arg,
2025 self.ingress_expiry_datetime,
2026 self.use_nonce,
2027 None,
2028 )
2029 .await
2030 }
2031
2032 pub async fn call_with_verification(self) -> Result<Vec<u8>, AgentError> {
2037 self.agent
2038 .query_raw(
2039 self.canister_id,
2040 self.effective_canister_id,
2041 self.method_name,
2042 self.arg,
2043 self.ingress_expiry_datetime,
2044 self.use_nonce,
2045 Some(true),
2046 )
2047 .await
2048 }
2049
2050 pub async fn call_without_verification(self) -> Result<Vec<u8>, AgentError> {
2055 self.agent
2056 .query_raw(
2057 self.canister_id,
2058 self.effective_canister_id,
2059 self.method_name,
2060 self.arg,
2061 self.ingress_expiry_datetime,
2062 self.use_nonce,
2063 Some(false),
2064 )
2065 .await
2066 }
2067
2068 pub fn sign(self) -> Result<SignedQuery, AgentError> {
2071 let effective_canister_id = self.effective_canister_id;
2072 let identity = self.agent.identity.clone();
2073 let content = self.into_envelope()?;
2074 let signed_query = sign_envelope(&content, identity)?;
2075 let EnvelopeContent::Query {
2076 ingress_expiry,
2077 sender,
2078 canister_id,
2079 method_name,
2080 arg,
2081 nonce,
2082 sender_info,
2083 } = content
2084 else {
2085 unreachable!()
2086 };
2087 Ok(SignedQuery {
2088 ingress_expiry,
2089 sender,
2090 canister_id,
2091 method_name,
2092 arg,
2093 effective_canister_id,
2094 signed_query,
2095 nonce,
2096 sender_info,
2097 })
2098 }
2099
2100 pub fn into_envelope(self) -> Result<EnvelopeContent, AgentError> {
2102 self.agent.query_content(
2103 self.canister_id,
2104 self.method_name,
2105 self.arg,
2106 self.ingress_expiry_datetime,
2107 self.use_nonce,
2108 )
2109 }
2110}
2111
2112impl<'agent> IntoFuture for QueryBuilder<'agent> {
2113 type IntoFuture = AgentFuture<'agent, Vec<u8>>;
2114 type Output = Result<Vec<u8>, AgentError>;
2115 fn into_future(self) -> Self::IntoFuture {
2116 Box::pin(self.call())
2117 }
2118}
2119
2120pub struct UpdateCall<'agent> {
2122 agent: &'agent Agent,
2123 response_future: AgentFuture<'agent, CallResponse<(Vec<u8>, Certificate)>>,
2124 effective_canister_id: Principal,
2125 canister_id: Principal,
2126 method_name: String,
2127}
2128
2129impl fmt::Debug for UpdateCall<'_> {
2130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2131 f.debug_struct("UpdateCall")
2132 .field("agent", &self.agent)
2133 .field("effective_canister_id", &self.effective_canister_id)
2134 .finish_non_exhaustive()
2135 }
2136}
2137
2138impl Future for UpdateCall<'_> {
2139 type Output = Result<CallResponse<(Vec<u8>, Certificate)>, AgentError>;
2140 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2141 self.response_future.as_mut().poll(cx)
2142 }
2143}
2144
2145impl<'a> UpdateCall<'a> {
2146 pub async fn and_wait(self) -> Result<(Vec<u8>, Certificate), AgentError> {
2148 let response = self.response_future.await?;
2149
2150 match response {
2151 CallResponse::Response(response) => Ok(response),
2152 CallResponse::Poll(request_id) => {
2153 self.agent
2154 .wait_inner(
2155 &request_id,
2156 EffectiveId::Canister(self.effective_canister_id),
2157 Some(Operation::Call {
2158 canister: self.canister_id,
2159 method: self.method_name,
2160 }),
2161 )
2162 .await
2163 }
2164 }
2165 }
2166}
2167#[derive(Debug)]
2172pub struct UpdateBuilder<'agent> {
2173 agent: &'agent Agent,
2174 pub effective_canister_id: Principal,
2176 pub canister_id: Principal,
2178 pub method_name: String,
2180 pub arg: Vec<u8>,
2182 pub ingress_expiry_datetime: Option<u64>,
2184}
2185
2186impl<'agent> UpdateBuilder<'agent> {
2187 pub fn new(agent: &'agent Agent, canister_id: Principal, method_name: String) -> Self {
2189 Self {
2190 agent,
2191 effective_canister_id: canister_id,
2192 canister_id,
2193 method_name,
2194 arg: vec![],
2195 ingress_expiry_datetime: None,
2196 }
2197 }
2198
2199 pub fn with_effective_canister_id(mut self, canister_id: Principal) -> Self {
2201 self.effective_canister_id = canister_id;
2202 self
2203 }
2204
2205 pub fn with_arg<A: Into<Vec<u8>>>(mut self, arg: A) -> Self {
2207 self.arg = arg.into();
2208 self
2209 }
2210
2211 pub fn expire_at(mut self, time: impl Into<OffsetDateTime>) -> Self {
2213 self.ingress_expiry_datetime = Some(time.into().unix_timestamp_nanos() as u64);
2214 self
2215 }
2216
2217 pub fn expire_after(mut self, duration: Duration) -> Self {
2219 self.ingress_expiry_datetime = Some(
2220 OffsetDateTime::now_utc()
2221 .saturating_add(duration.try_into().expect("negative duration"))
2222 .unix_timestamp_nanos() as u64,
2223 );
2224 self
2225 }
2226
2227 pub async fn call_and_wait(self) -> Result<Vec<u8>, AgentError> {
2230 self.call().and_wait().await.map(|x| x.0)
2231 }
2232
2233 pub fn call(self) -> UpdateCall<'agent> {
2236 let method_name = self.method_name.clone();
2237 let response_future = async move {
2238 self.agent
2239 .update_raw(
2240 self.canister_id,
2241 self.effective_canister_id,
2242 self.method_name,
2243 self.arg,
2244 self.ingress_expiry_datetime,
2245 )
2246 .await
2247 };
2248 UpdateCall {
2249 agent: self.agent,
2250 response_future: Box::pin(response_future),
2251 effective_canister_id: self.effective_canister_id,
2252 canister_id: self.canister_id,
2253 method_name,
2254 }
2255 }
2256
2257 pub fn sign(self) -> Result<SignedUpdate, AgentError> {
2260 let identity = self.agent.identity.clone();
2261 let effective_canister_id = self.effective_canister_id;
2262 let content = self.into_envelope()?;
2263 let signed_update = sign_envelope(&content, identity)?;
2264 let request_id = to_request_id(&content)?;
2265 let EnvelopeContent::Call {
2266 nonce,
2267 ingress_expiry,
2268 sender,
2269 canister_id,
2270 method_name,
2271 arg,
2272 sender_info,
2273 } = content
2274 else {
2275 unreachable!()
2276 };
2277 Ok(SignedUpdate {
2278 nonce,
2279 ingress_expiry,
2280 sender,
2281 canister_id,
2282 method_name,
2283 arg,
2284 effective_canister_id,
2285 signed_update,
2286 request_id,
2287 sender_info,
2288 })
2289 }
2290
2291 pub fn into_envelope(self) -> Result<EnvelopeContent, AgentError> {
2293 let nonce = self.agent.nonce_factory.generate();
2294 self.agent.update_content(
2295 self.canister_id,
2296 self.method_name,
2297 self.arg,
2298 self.ingress_expiry_datetime,
2299 nonce,
2300 )
2301 }
2302}
2303
2304impl<'agent> IntoFuture for UpdateBuilder<'agent> {
2305 type IntoFuture = AgentFuture<'agent, Vec<u8>>;
2306 type Output = Result<Vec<u8>, AgentError>;
2307 fn into_future(self) -> Self::IntoFuture {
2308 Box::pin(self.call_and_wait())
2309 }
2310}
2311
2312#[cfg_attr(target_family = "wasm", async_trait(?Send))]
2314#[cfg_attr(not(target_family = "wasm"), async_trait)]
2315pub trait HttpService: Send + Sync + Debug {
2316 async fn call<'a>(
2318 &'a self,
2319 req: &'a (dyn Fn() -> Result<http::Request<Bytes>, AgentError> + Send + Sync),
2320 max_retries: usize,
2321 size_limit: Option<usize>,
2322 ) -> Result<http::Response<Bytes>, AgentError>;
2323}
2324
2325fn from_http_request(req: http::Request<Bytes>) -> Result<Request, AgentError> {
2327 let (parts, body) = req.into_parts();
2328 let body = reqwest::Body::from(body);
2329 let request = http::Request::from_parts(parts, body)
2332 .try_into()
2333 .map_err(|e: reqwest::Error| AgentError::InvalidReplicaUrl(e.to_string()))?;
2334
2335 Ok(request)
2336}
2337
2338#[cfg(not(target_family = "wasm"))]
2340async fn to_http_response(
2341 resp: Response,
2342 size_limit: Option<usize>,
2343) -> Result<http::Response<Bytes>, AgentError> {
2344 use http_body_util::{BodyExt, Limited};
2345
2346 let resp: http::Response<reqwest::Body> = resp.into();
2347 let (parts, body) = resp.into_parts();
2348 let body = Limited::new(body, size_limit.unwrap_or(usize::MAX));
2349 let body = body
2350 .collect()
2351 .await
2352 .map_err(|e| {
2353 AgentError::TransportError(TransportError::Generic(format!(
2354 "unable to read response body: {e:#}"
2355 )))
2356 })?
2357 .to_bytes();
2358 let resp = http::Response::from_parts(parts, body);
2359
2360 Ok(resp)
2361}
2362
2363#[cfg(target_family = "wasm")]
2367async fn to_http_response(
2368 resp: Response,
2369 size_limit: Option<usize>,
2370) -> Result<http::Response<Bytes>, AgentError> {
2371 use futures_util::StreamExt;
2372 use http_body::Frame;
2373 use http_body_util::{Limited, StreamBody};
2374
2375 let status = resp.status();
2377 let headers = resp.headers().clone();
2378
2379 let stream = resp.bytes_stream().map(|x| x.map(Frame::data));
2381 let body = StreamBody::new(stream);
2382 let body = Limited::new(body, size_limit.unwrap_or(usize::MAX));
2383 let body = http_body_util::BodyExt::collect(body)
2384 .await
2385 .map_err(|e| {
2386 AgentError::TransportError(TransportError::Generic(format!(
2387 "unable to read response body: {e:#}"
2388 )))
2389 })?
2390 .to_bytes();
2391
2392 let mut resp = http::Response::new(body);
2393 *resp.status_mut() = status;
2394 *resp.headers_mut() = headers;
2395
2396 Ok(resp)
2397}
2398
2399#[cfg(not(target_family = "wasm"))]
2400#[async_trait]
2401impl<T> HttpService for T
2402where
2403 for<'a> &'a T: Service<Request, Response = Response, Error = reqwest::Error>,
2404 for<'a> <&'a Self as Service<Request>>::Future: Send,
2405 T: Send + Sync + Debug + ?Sized,
2406{
2407 #[allow(clippy::needless_arbitrary_self_type)]
2408 async fn call<'a>(
2409 mut self: &'a Self,
2410 req: &'a (dyn Fn() -> Result<http::Request<Bytes>, AgentError> + Send + Sync),
2411 max_retries: usize,
2412 size_limit: Option<usize>,
2413 ) -> Result<http::Response<Bytes>, AgentError> {
2414 let mut retry_count = 0;
2415 loop {
2416 let request = from_http_request(req()?)?;
2417
2418 match Service::call(&mut self, request).await {
2419 Err(err) => {
2420 if err.is_connect() {
2422 if retry_count >= max_retries {
2423 return Err(AgentError::TransportError(TransportError::Reqwest(err)));
2424 }
2425 retry_count += 1;
2426 }
2427 else {
2429 return Err(AgentError::TransportError(TransportError::Reqwest(err)));
2430 }
2431 }
2432
2433 Ok(resp) => {
2434 let resp = to_http_response(resp, size_limit).await?;
2435 return Ok(resp);
2436 }
2437 }
2438 }
2439 }
2440}
2441
2442#[cfg(target_family = "wasm")]
2443#[async_trait(?Send)]
2444impl<T> HttpService for T
2445where
2446 for<'a> &'a T: Service<Request, Response = Response, Error = reqwest::Error>,
2447 T: Send + Sync + Debug + ?Sized,
2448{
2449 #[allow(clippy::needless_arbitrary_self_type)]
2450 async fn call<'a>(
2451 mut self: &'a Self,
2452 req: &'a (dyn Fn() -> Result<http::Request<Bytes>, AgentError> + Send + Sync),
2453 _retries: usize,
2454 _size_limit: Option<usize>,
2455 ) -> Result<http::Response<Bytes>, AgentError> {
2456 let request = from_http_request(req()?)?;
2457 let response = Service::call(&mut self, request)
2458 .await
2459 .map_err(|e| AgentError::TransportError(TransportError::Reqwest(e)))?;
2460
2461 to_http_response(response, _size_limit).await
2462 }
2463}
2464
2465#[derive(Debug)]
2466struct RetryLogic {
2467 client: Client,
2468}
2469
2470#[cfg_attr(target_family = "wasm", async_trait(?Send))]
2471#[cfg_attr(not(target_family = "wasm"), async_trait)]
2472impl HttpService for RetryLogic {
2473 async fn call<'a>(
2474 &'a self,
2475 req: &'a (dyn Fn() -> Result<http::Request<Bytes>, AgentError> + Send + Sync),
2476 _max_tcp_retries: usize,
2477 _size_limit: Option<usize>,
2478 ) -> Result<http::Response<Bytes>, AgentError> {
2479 let mut retries = 0;
2480 loop {
2481 #[cfg(not(target_family = "wasm"))]
2482 let resp = self.client.call(req, _max_tcp_retries, _size_limit).await?;
2483 #[cfg(target_family = "wasm")]
2485 let resp = {
2486 let request = from_http_request(req()?)?;
2487 let resp = self
2488 .client
2489 .execute(request)
2490 .await
2491 .map_err(|e| AgentError::TransportError(TransportError::Reqwest(e)))?;
2492
2493 to_http_response(resp, _size_limit).await?
2494 };
2495
2496 if resp.status() == StatusCode::TOO_MANY_REQUESTS
2497 || resp.status() == StatusCode::SERVICE_UNAVAILABLE
2498 {
2499 if retries == 6 {
2500 break Ok(resp);
2501 } else {
2502 retries += 1;
2503 crate::util::sleep(Duration::from_millis(250)).await;
2504 continue;
2505 }
2506 } else {
2507 break Ok(resp);
2508 }
2509 }
2510 }
2511}
2512
2513#[cfg(all(test, not(target_family = "wasm")))]
2514mod offline_tests {
2515 use super::*;
2516 use tokio::net::TcpListener;
2517 #[test]
2520 fn rounded_expiry() {
2521 let agent = Agent::builder()
2522 .with_url("http://not-a-real-url")
2523 .build()
2524 .unwrap();
2525 let mut prev_expiry = None;
2526 let mut num_timestamps = 0;
2527 for _ in 0..6 {
2528 let update = agent
2529 .update(&Principal::management_canister(), "not_a_method")
2530 .sign()
2531 .unwrap();
2532 if prev_expiry < Some(update.ingress_expiry) {
2533 prev_expiry = Some(update.ingress_expiry);
2534 num_timestamps += 1;
2535 }
2536 }
2537 assert!(num_timestamps <= 2, "num_timestamps:{num_timestamps} > 2");
2539 }
2540
2541 #[tokio::test]
2542 async fn client_ratelimit() {
2543 let mock_server = TcpListener::bind("127.0.0.1:0").await.unwrap();
2544 let count = Arc::new(Mutex::new(0));
2545 let port = mock_server.local_addr().unwrap().port();
2546 tokio::spawn({
2547 let count = count.clone();
2548 async move {
2549 loop {
2550 let (mut conn, _) = mock_server.accept().await.unwrap();
2551 *count.lock().unwrap() += 1;
2552 tokio::spawn(
2553 async move { tokio::io::copy(&mut conn, &mut tokio::io::sink()).await },
2555 );
2556 }
2557 }
2558 });
2559 let agent = Agent::builder()
2560 .with_http_client(Client::builder().http1_only().build().unwrap())
2561 .with_url(format!("http://127.0.0.1:{port}"))
2562 .with_max_concurrent_requests(2)
2563 .build()
2564 .unwrap();
2565 for _ in 0..3 {
2566 let agent = agent.clone();
2567 tokio::spawn(async move {
2568 agent
2569 .query(&"ryjl3-tyaaa-aaaaa-aaaba-cai".parse().unwrap(), "greet")
2570 .call()
2571 .await
2572 });
2573 }
2574 crate::util::sleep(Duration::from_millis(250)).await;
2575 assert_eq!(*count.lock().unwrap(), 2);
2576 }
2577}