Skip to content

Commit 1907aa6

Browse files
bitdivinenikolay-komarevskiy
authored andcommitted
feat: Add serialization traits to Status (dfinity#498)
1 parent 981706c commit 1907aa6

File tree

5 files changed

+131
-5
lines changed

5 files changed

+131
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
* Changed the return type of `stored_chunks` to a struct.
1212
* Added a prime256v1-based `Identity` impl to complement the ed25519 and secp256k1 `Identity` impls.
13+
* Added serde and candid serialization traits to the `Status` type.
1314

1415
## [0.32.0] - 2024-01-18
1516

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ic-agent/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ simple_asn1 = "0.6.1"
4343
thiserror = { workspace = true }
4444
time = { workspace = true }
4545
url = "2.1.0"
46+
lazy_static = "1.4"
4647

4748
[dependencies.hyper]
4849
version = "0.14"
@@ -76,6 +77,7 @@ web-sys = { version = "0.3", features = ["Window"], optional = true }
7677

7778
[dev-dependencies]
7879
serde_json = "1.0.79"
80+
candid = { workspace = true, features = ["value"]}
7981

8082
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
8183
tokio = { version = "1.24.2", features = ["full"] }

ic-agent/src/agent/http_transport/reqwest_transport.rs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
//! A [`Transport`] that connects using a [`reqwest`] client.
22
#![cfg(feature = "reqwest")]
33

4+
use futures_util::StreamExt;
45
use ic_transport_types::RejectResponse;
6+
use lazy_static::lazy_static;
57
pub use reqwest;
6-
7-
use futures_util::StreamExt;
88
use reqwest::{
99
header::{HeaderMap, CONTENT_TYPE},
1010
Body, Client, Method, Request, StatusCode,
1111
};
12+
use serde_cbor::Value;
13+
use std::fs::{File, OpenOptions};
14+
use std::io::{self, prelude::*};
15+
use std::sync::Arc;
16+
use std::{collections::BTreeMap, sync::Mutex};
1217

1318
use crate::{
1419
agent::{
@@ -20,6 +25,30 @@ use crate::{
2025
AgentError, RequestId,
2126
};
2227

28+
struct GlobalFile {
29+
file: Mutex<File>,
30+
}
31+
32+
// Create a lazy static instance of the global file
33+
lazy_static! {
34+
static ref GLOBAL_FILE: Arc<GlobalFile> = {
35+
let file = OpenOptions::new()
36+
.create(true)
37+
.write(true)
38+
.append(true)
39+
.open("results.txt")
40+
.unwrap();
41+
42+
Arc::new(GlobalFile {
43+
file: Mutex::new(file),
44+
})
45+
};
46+
}
47+
48+
fn write_to_global_file(tree: BTreeMap<&&str, i32>) {
49+
let mut file = GLOBAL_FILE.file.lock().unwrap();
50+
writeln!(file, "{:?}", tree).expect("Write failed");
51+
}
2352
/// A [`Transport`] using [`reqwest`] to make HTTP calls to the Internet Computer.
2453
#[derive(Debug)]
2554
pub struct ReqwestTransport {
@@ -139,6 +168,26 @@ impl ReqwestTransport {
139168
let headers = request_result.1;
140169
let body = request_result.2;
141170

171+
if let Some(timestamps) = headers.get("timestamps") {
172+
let mut all_timestamps: Vec<_> = timestamps
173+
.to_str()
174+
.unwrap()
175+
.split(", ")
176+
.into_iter()
177+
.collect();
178+
let decoded_data: Value =
179+
serde_cbor::from_reader(std::io::Cursor::new(body.clone().to_vec()))
180+
.expect("Failed to decode CBOR");
181+
let own_timestamp = extract_timestamp(&decoded_data);
182+
all_timestamps.push(own_timestamp.as_str());
183+
let mut btree_map: BTreeMap<&&str, i32> = BTreeMap::new();
184+
for value in all_timestamps.iter() {
185+
let entry = btree_map.entry(value).or_insert(0);
186+
*entry += 1;
187+
}
188+
write_to_global_file(btree_map);
189+
}
190+
142191
// status == OK means we have an error message for call requests
143192
// see https://internetcomputer.org/docs/current/references/ic-interface-spec#http-call
144193
if status == StatusCode::OK && endpoint.ends_with("call") {
@@ -166,6 +215,34 @@ impl ReqwestTransport {
166215
}
167216
}
168217

218+
fn extract_timestamp(value: &Value) -> String {
219+
match value {
220+
Value::Map(map) => {
221+
let signatures = map
222+
.get(&Value::Text("signatures".into()))
223+
.expect("unexpected cbor body");
224+
match signatures {
225+
Value::Array(array) => match &array[0] {
226+
Value::Map(map) => {
227+
let value = map
228+
.get(&Value::Text("timestamp".to_string()))
229+
.expect("unexpected cbor body");
230+
match value {
231+
Value::Integer(integer_value) => {
232+
return integer_value.to_string();
233+
}
234+
_ => panic!("unexpected cbor body"),
235+
}
236+
}
237+
_ => panic!("unexpected cbor body"),
238+
},
239+
_ => panic!("unexpected cbor body"),
240+
}
241+
}
242+
_ => panic!("unexpected cbor body"),
243+
}
244+
}
245+
169246
impl Transport for ReqwestTransport {
170247
fn call(
171248
&self,
@@ -201,7 +278,7 @@ impl Transport for ReqwestTransport {
201278

202279
fn query(&self, effective_canister_id: Principal, envelope: Vec<u8>) -> AgentFuture<Vec<u8>> {
203280
Box::pin(async move {
204-
let endpoint = format!("canister/{effective_canister_id}/query");
281+
let endpoint = format!("canister/{effective_canister_id}/certified_query");
205282
self.execute(Method::POST, &endpoint, Some(envelope)).await
206283
})
207284
}

ic-agent/src/agent/status.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
//! Types for interacting with the status endpoint of a replica. See [`Status`] for details.
22
3+
use candid::{CandidType, Deserialize};
4+
use serde::Serialize;
35
use std::{collections::BTreeMap, fmt::Debug};
46

57
/// Value returned by the status endpoint of a replica. This is a loose mapping to CBOR values.
68
/// Because the agent should not return [`serde_cbor::Value`] directly across API boundaries,
79
/// we reimplement it as [`Value`] here.
8-
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash)]
10+
#[derive(
11+
Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, CandidType, Serialize, Deserialize,
12+
)]
913
pub enum Value {
1014
/// See [`Null`](serde_cbor::Value::Null).
1115
Null,
@@ -40,7 +44,7 @@ impl std::fmt::Display for Value {
4044

4145
/// The structure returned by [`super::Agent::status`], containing the information returned
4246
/// by the status endpoint of a replica.
43-
#[derive(Debug, Ord, PartialOrd, PartialEq, Eq)]
47+
#[derive(Debug, Ord, PartialOrd, PartialEq, Eq, CandidType, Deserialize, Serialize)]
4448
pub struct Status {
4549
/// Optional. The precise git revision of the Internet Computer Protocol implementation.
4650
pub impl_version: Option<String>,
@@ -55,6 +59,47 @@ pub struct Status {
5559
pub values: BTreeMap<String, Box<Value>>,
5660
}
5761

62+
#[test]
63+
fn can_serilaize_status_as_json() {
64+
let status = Status {
65+
impl_version: None,
66+
replica_health_status: None,
67+
root_key: None,
68+
values: BTreeMap::new(),
69+
};
70+
let expected_json =
71+
r#"{"impl_version":null,"replica_health_status":null,"root_key":null,"values":{}}"#;
72+
let actual_json = serde_json::to_string(&status).expect("Failed to serialize as JSON");
73+
assert_eq!(expected_json, actual_json);
74+
}
75+
#[test]
76+
fn can_serialize_status_as_idl() {
77+
use candid::types::value::IDLValue;
78+
use candid::{Encode, IDLArgs, Result as CandidResult, TypeEnv};
79+
let status = Status {
80+
impl_version: Some("Foo".to_string()),
81+
replica_health_status: None,
82+
root_key: None,
83+
values: BTreeMap::new(),
84+
};
85+
// Expresses data as IDLValue. Then use .to_string() to convert to text-form candid.
86+
// TODO: This function has been added to the Candid library and will be available in the next
87+
// release. Then, this definition here can be deleted.
88+
pub fn try_from_candid_type<T>(data: &T) -> CandidResult<IDLValue>
89+
where
90+
T: CandidType,
91+
{
92+
let blob = Encode!(data)?;
93+
let args = IDLArgs::from_bytes_with_types(&blob, &TypeEnv::default(), &[T::ty()])?;
94+
Ok(args.args[0].clone())
95+
}
96+
let expected_idl = "record {\n values = vec {};\n replica_health_status = null;\n impl_version = opt \"Foo\";\n root_key = null;\n}";
97+
let actual_idl = try_from_candid_type(&status)
98+
.expect("Failed to convert to idl")
99+
.to_string();
100+
assert_eq!(expected_idl, actual_idl);
101+
}
102+
58103
impl std::fmt::Display for Status {
59104
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60105
f.write_str("{\n")?;

0 commit comments

Comments
 (0)