Skip to main content

ic_transport_types/
lib.rs

1//! Types related to the [HTTP transport](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-interface)
2//! for the [Internet Computer](https://internetcomputer.org). Primarily used through [`ic-agent`](https://docs.rs/ic-agent).
3
4#![warn(missing_docs, missing_debug_implementations)]
5#![deny(elided_lifetimes_in_paths)]
6
7use std::borrow::Cow;
8
9use candid::Principal;
10use ic_certification::Label;
11pub use request_id::{to_request_id, RequestId, RequestIdError};
12use serde::{Deserialize, Serialize};
13use serde_repr::{Deserialize_repr, Serialize_repr};
14use thiserror::Error;
15
16mod request_id;
17pub mod signed;
18
19/// The authentication envelope, containing the contents and their signature. This struct can be passed to `Agent`'s
20/// `*_signed` methods via [`encode_bytes`](Envelope::encode_bytes).
21#[derive(Debug, Clone, Deserialize, Serialize)]
22#[serde(rename_all = "snake_case")]
23pub struct Envelope<'a> {
24    /// The data that is signed by the caller.
25    pub content: Cow<'a, EnvelopeContent>,
26    /// The public key of the self-signing principal this request is from.
27    #[serde(default, skip_serializing_if = "Option::is_none", with = "serde_bytes")]
28    pub sender_pubkey: Option<Vec<u8>>,
29    /// A cryptographic signature authorizing the request. Not necessarily made by `sender_pubkey`; when delegations are involved,
30    /// `sender_sig` is the tail of the delegation chain, and `sender_pubkey` is the head.
31    #[serde(default, skip_serializing_if = "Option::is_none", with = "serde_bytes")]
32    pub sender_sig: Option<Vec<u8>>,
33    /// The chain of delegations connecting `sender_pubkey` to `sender_sig`, and in that order.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub sender_delegation: Option<Vec<SignedDelegation>>,
36}
37
38impl Envelope<'_> {
39    /// Convert the authentication envelope to the format expected by the IC HTTP interface. The result can be passed to `Agent`'s `*_signed` methods.
40    pub fn encode_bytes(&self) -> Vec<u8> {
41        let mut serializer = serde_cbor::Serializer::new(Vec::new());
42        serializer.self_describe().unwrap();
43        self.serialize(&mut serializer)
44            .expect("infallible Envelope::serialize");
45        serializer.into_inner()
46    }
47}
48
49/// The content of an IC ingress message, not including any signature information.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(tag = "request_type", rename_all = "snake_case")]
52pub enum EnvelopeContent {
53    /// A replicated call to a canister method, whether update or query.
54    Call {
55        /// A random series of bytes to uniquely identify this message.
56        #[serde(default, skip_serializing_if = "Option::is_none", with = "serde_bytes")]
57        nonce: Option<Vec<u8>>,
58        /// A nanosecond timestamp after which this request is no longer valid.
59        ingress_expiry: u64,
60        /// The principal that is sending this request.
61        sender: Principal,
62        /// The ID of the canister to be called.
63        canister_id: Principal,
64        /// The name of the canister method to be called.
65        method_name: String,
66        /// The argument to pass to the canister method.
67        #[serde(with = "serde_bytes")]
68        arg: Vec<u8>,
69    },
70    /// A request for information from the [IC state tree](https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree).
71    ReadState {
72        /// A nanosecond timestamp after which this request is no longer valid.
73        ingress_expiry: u64,
74        /// The principal that is sending this request.
75        sender: Principal,
76        /// A list of paths within the state tree to fetch.
77        paths: Vec<Vec<Label>>,
78    },
79    /// An unreplicated call to a canister query method.
80    Query {
81        /// A nanosecond timestamp after which this request is no longer valid.
82        ingress_expiry: u64,
83        /// The principal that is sending this request.
84        sender: Principal,
85        /// The ID of the canister to be called.
86        canister_id: Principal,
87        /// The name of the canister method to be called.
88        method_name: String,
89        /// The argument to pass to the canister method.
90        #[serde(with = "serde_bytes")]
91        arg: Vec<u8>,
92        /// A random series of bytes to uniquely identify this message.
93        #[serde(default, skip_serializing_if = "Option::is_none", with = "serde_bytes")]
94        nonce: Option<Vec<u8>>,
95    },
96}
97
98impl EnvelopeContent {
99    /// Returns the `ingress_expiry` field common to all variants.
100    pub fn ingress_expiry(&self) -> u64 {
101        let (Self::Call { ingress_expiry, .. }
102        | Self::Query { ingress_expiry, .. }
103        | Self::ReadState { ingress_expiry, .. }) = self;
104        *ingress_expiry
105    }
106    /// Returns the `sender` field common to all variants.
107    pub fn sender(&self) -> &Principal {
108        let (Self::Call { sender, .. }
109        | Self::Query { sender, .. }
110        | Self::ReadState { sender, .. }) = self;
111        sender
112    }
113    /// Converts the envelope content to a request ID.
114    ///
115    /// Equivalent to calling [`to_request_id`], but infallible.
116    pub fn to_request_id(&self) -> RequestId {
117        to_request_id(self)
118            .expect("to_request_id::<EnvelopeContent> should always succeed but did not")
119    }
120}
121
122/// The response from a request to the `read_state` endpoint.
123#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
124pub struct ReadStateResponse {
125    /// A [certificate](https://internetcomputer.org/docs/current/references/ic-interface-spec#certificate), containing
126    /// part of the system state tree as well as a signature to verify its authenticity.
127    /// Use the [`ic-certification`](https://docs.rs/ic-certification) crate to process it.
128    #[serde(with = "serde_bytes")]
129    pub certificate: Vec<u8>,
130}
131
132/// The parsed response from a request to the v3 `call` endpoint. A request to the `call` endpoint.
133#[derive(Debug, Serialize, Deserialize)]
134#[serde(tag = "status", rename_all = "snake_case")]
135pub enum TransportCallResponse {
136    /// The IC responded with a certified response.
137    Replied {
138        /// The CBOR serialized certificate for the call response.
139        #[serde(with = "serde_bytes")]
140        certificate: Vec<u8>,
141    },
142
143    /// The replica responded with a non replicated rejection.
144    NonReplicatedRejection(RejectResponse),
145
146    /// The replica timed out the sync request, but forwarded the ingress message
147    /// to the canister. The request id should be used to poll for the response
148    /// The status of the request must be polled.
149    Accepted,
150}
151
152/// The response from a request to the `call` endpoint.
153#[derive(Debug, PartialEq, Eq, Clone, Hash)]
154pub enum CallResponse<Out> {
155    /// The call completed, and the response is available.
156    Response(Out),
157    /// The replica timed out the update call, and the request id should be used to poll for the response
158    /// using the `Agent::wait` method.
159    Poll(RequestId),
160}
161
162impl<Out> CallResponse<Out> {
163    /// Maps the inner value, if this is `Response`.
164    #[inline]
165    pub fn map<Out2>(self, f: impl FnOnce(Out) -> Out2) -> CallResponse<Out2> {
166        match self {
167            Self::Poll(p) => CallResponse::Poll(p),
168            Self::Response(r) => CallResponse::Response(f(r)),
169        }
170    }
171}
172
173impl<T, E> CallResponse<Result<T, E>> {
174    /// Extracts an inner `Result`, if this is `Response`.
175    #[inline]
176    pub fn transpose(self) -> Result<CallResponse<T>, E> {
177        match self {
178            Self::Poll(p) => Ok(CallResponse::Poll(p)),
179            Self::Response(r) => r.map(CallResponse::Response),
180        }
181    }
182}
183
184impl<T> CallResponse<Option<T>> {
185    /// Extracts an inner `Option`, if this is `Response`.
186    #[inline]
187    pub fn transpose(self) -> Option<CallResponse<T>> {
188        match self {
189            Self::Poll(p) => Some(CallResponse::Poll(p)),
190            Self::Response(r) => r.map(CallResponse::Response),
191        }
192    }
193}
194
195impl<T> CallResponse<(T,)> {
196    /// Extracts the inner value of a 1-tuple, if this is `Response`.
197    #[inline]
198    pub fn detuple(self) -> CallResponse<T> {
199        match self {
200            Self::Poll(p) => CallResponse::Poll(p),
201            Self::Response(r) => CallResponse::Response(r.0),
202        }
203    }
204}
205
206/// Possible responses to a query call.
207#[derive(Debug, Clone, Deserialize, Serialize)]
208#[serde(tag = "status", rename_all = "snake_case")]
209pub enum QueryResponse {
210    /// The request was successfully replied to.
211    Replied {
212        /// The reply from the canister.
213        reply: ReplyResponse,
214
215        /// The list of node signatures.
216        #[serde(default, skip_serializing_if = "Vec::is_empty")]
217        signatures: Vec<NodeSignature>,
218    },
219    /// The request was rejected.
220    Rejected {
221        /// The rejection from the canister.
222        #[serde(flatten)]
223        reject: RejectResponse,
224
225        /// The list of node signatures.
226        #[serde(default, skip_serializing_if = "Vec::is_empty")]
227        signatures: Vec<NodeSignature>,
228    },
229}
230
231impl QueryResponse {
232    /// Returns the signable form of the query response, as described in
233    /// [the spec](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-query).
234    /// This is what is signed in the `signatures` fields.
235    pub fn signable(&self, request_id: RequestId, timestamp: u64) -> Vec<u8> {
236        #[derive(Serialize)]
237        #[serde(tag = "status", rename_all = "snake_case")]
238        enum QueryResponseSignable<'a> {
239            Replied {
240                reply: &'a ReplyResponse, // polyfill until hash_of_map is figured out
241                request_id: RequestId,
242                timestamp: u64,
243            },
244            Rejected {
245                reject_code: RejectCode,
246                reject_message: &'a String,
247                #[serde(default)]
248                error_code: Option<&'a String>,
249                request_id: RequestId,
250                timestamp: u64,
251            },
252        }
253        let response = match self {
254            Self::Replied { reply, .. } => QueryResponseSignable::Replied {
255                reply,
256                request_id,
257                timestamp,
258            },
259            Self::Rejected { reject, .. } => QueryResponseSignable::Rejected {
260                error_code: reject.error_code.as_ref(),
261                reject_code: reject.reject_code,
262                reject_message: &reject.reject_message,
263                request_id,
264                timestamp,
265            },
266        };
267        let mut signable = Vec::with_capacity(44);
268        signable.extend_from_slice(b"\x0Bic-response");
269        signable.extend_from_slice(to_request_id(&response).unwrap().as_slice());
270        signable
271    }
272
273    /// Helper function to get the signatures field present in both variants.
274    pub fn signatures(&self) -> &[NodeSignature] {
275        let (Self::Rejected { signatures, .. } | Self::Replied { signatures, .. }) = self;
276        signatures
277    }
278}
279
280/// An IC execution error received from the replica.
281#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
282pub struct RejectResponse {
283    /// The [reject code](https://internetcomputer.org/docs/current/references/ic-interface-spec#reject-codes) returned by the replica.
284    pub reject_code: RejectCode,
285    /// The rejection message.
286    pub reject_message: String,
287    /// The optional [error code](https://internetcomputer.org/docs/current/references/ic-interface-spec#error-codes) returned by the replica.
288    #[serde(default)]
289    pub error_code: Option<String>,
290}
291
292/// See the [interface spec](https://internetcomputer.org/docs/current/references/ic-interface-spec#reject-codes).
293#[derive(
294    Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr, Ord, PartialOrd,
295)]
296#[repr(u8)]
297pub enum RejectCode {
298    /// Fatal system error, retry unlikely to be useful
299    SysFatal = 1,
300    /// Transient system error, retry might be possible.
301    SysTransient = 2,
302    /// Invalid destination (e.g. canister/account does not exist)
303    DestinationInvalid = 3,
304    /// Explicit reject by the canister.
305    CanisterReject = 4,
306    /// Canister error (e.g., trap, no response)
307    CanisterError = 5,
308}
309
310impl TryFrom<u64> for RejectCode {
311    type Error = InvalidRejectCodeError;
312
313    fn try_from(value: u64) -> Result<Self, InvalidRejectCodeError> {
314        match value {
315            1 => Ok(RejectCode::SysFatal),
316            2 => Ok(RejectCode::SysTransient),
317            3 => Ok(RejectCode::DestinationInvalid),
318            4 => Ok(RejectCode::CanisterReject),
319            5 => Ok(RejectCode::CanisterError),
320            _ => Err(InvalidRejectCodeError(value)),
321        }
322    }
323}
324
325/// Error returned from `RejectCode::try_from`.
326#[derive(Debug, Error)]
327#[error("Invalid reject code {0}")]
328pub struct InvalidRejectCodeError(pub u64);
329
330/// The response of `/api/v3/canister/<effective_canister_id>/read_state` with `request_status` request type.
331///
332/// See [the HTTP interface specification](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-call-overview) for more details.
333#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
334pub enum RequestStatusResponse {
335    /// The status of the request is unknown.
336    Unknown,
337    /// The request has been received, and will probably get processed.
338    Received,
339    /// The request is currently being processed.
340    Processing,
341    /// The request has been successfully replied to.
342    Replied(ReplyResponse),
343    /// The request has been rejected.
344    Rejected(RejectResponse),
345    /// The call has been completed, and it has been long enough that the reply/reject data has been purged, but the call has not expired yet.
346    Done,
347}
348
349/// A successful reply to a canister call.
350#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Serialize, Deserialize)]
351pub struct ReplyResponse {
352    /// The reply message, likely Candid-encoded.
353    #[serde(with = "serde_bytes")]
354    pub arg: Vec<u8>,
355}
356
357/// A delegation from one key to another.
358///
359/// If key A signs a delegation containing key B, then key B may be used to
360/// authenticate as key A's corresponding principal(s).
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct Delegation {
363    /// The delegated-to key.
364    #[serde(with = "serde_bytes")]
365    pub pubkey: Vec<u8>,
366    /// A nanosecond timestamp after which this delegation is no longer valid.
367    pub expiration: u64,
368    /// If present, this delegation only applies to requests sent to one of these canisters.
369    #[serde(default, skip_serializing_if = "Option::is_none")]
370    pub targets: Option<Vec<Principal>>,
371}
372
373const IC_REQUEST_DELEGATION_DOMAIN_SEPARATOR: &[u8] = b"\x1Aic-request-auth-delegation";
374
375impl Delegation {
376    /// Returns the signable form of the delegation, by running it through [`to_request_id`]
377    /// and prepending `\x1Aic-request-auth-delegation` to the result.
378    pub fn signable(&self) -> Vec<u8> {
379        let hash = to_request_id(self).unwrap();
380        let mut bytes = Vec::with_capacity(59);
381        bytes.extend_from_slice(IC_REQUEST_DELEGATION_DOMAIN_SEPARATOR);
382        bytes.extend_from_slice(hash.as_slice());
383        bytes
384    }
385}
386
387/// A [`Delegation`] that has been signed by an [`Identity`](https://docs.rs/ic-agent/latest/ic_agent/trait.Identity.html).
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct SignedDelegation {
390    /// The signed delegation.
391    pub delegation: Delegation,
392    /// The signature for the delegation.
393    #[serde(with = "serde_bytes")]
394    pub signature: Vec<u8>,
395}
396
397/// A response signature from an individual node.
398#[derive(Debug, Clone, Serialize, Deserialize, Eq, Ord, PartialEq, PartialOrd)]
399pub struct NodeSignature {
400    /// The timestamp that the signature was created at.
401    pub timestamp: u64,
402    /// The signature.
403    #[serde(with = "serde_bytes")]
404    pub signature: Vec<u8>,
405    /// The ID of the  node.
406    pub identity: Principal,
407}
408
409/// A list of subnet metrics.
410#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
411pub struct SubnetMetrics {
412    /// The number of canisters on this subnet.
413    pub num_canisters: u64,
414    /// The total size of the state in bytes taken by canisters on this subnet since this subnet was created.
415    pub canister_state_bytes: u64,
416    /// The total number of cycles consumed by all current and deleted canisters on this subnet.
417    #[serde(with = "map_u128")]
418    pub consumed_cycles_total: u128,
419    /// The total number of transactions processed on this subnet since this subnet was created.
420    pub update_transactions_total: u64,
421}
422
423mod map_u128 {
424    use serde::{
425        de::{Error, IgnoredAny, MapAccess, Visitor},
426        ser::SerializeMap,
427        Deserializer, Serializer,
428    };
429    use std::fmt;
430
431    pub fn serialize<S: Serializer>(val: &u128, s: S) -> Result<S::Ok, S::Error> {
432        let low = *val & u64::MAX as u128;
433        let high = *val >> 64;
434        let mut map = s.serialize_map(Some(2))?;
435        map.serialize_entry(&0, &low)?;
436        map.serialize_entry(&1, &(high != 0).then_some(high))?;
437        map.end()
438    }
439
440    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<u128, D::Error> {
441        d.deserialize_map(MapU128Visitor)
442    }
443
444    struct MapU128Visitor;
445
446    impl<'de> Visitor<'de> for MapU128Visitor {
447        type Value = u128;
448
449        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
450            formatter.write_str("a map of low and high")
451        }
452
453        fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
454            let (_, low): (IgnoredAny, u64) = map
455                .next_entry()?
456                .ok_or_else(|| A::Error::missing_field("0"))?;
457            let opt: Option<(IgnoredAny, Option<u64>)> = map.next_entry()?;
458            let high = opt.and_then(|x| x.1).unwrap_or(0);
459            Ok(((high as u128) << 64) | low as u128)
460        }
461    }
462}