Skip to main content

icrc1_test_replica/
lib.rs

1use ic_agent::agent::http_transport::reqwest_transport::ReqwestHttpReplicaV2Transport;
2use ic_agent::identity::BasicIdentity;
3use ic_agent::Agent;
4use std::path::Path;
5use std::process::{Child, Command};
6use std::sync::Arc;
7use tokio::time::{sleep, Duration};
8
9struct KillOnDrop(Child);
10
11pub struct ReplicaContext {
12    _proc: KillOnDrop,
13    _state: tempfile::TempDir,
14    port: u16,
15}
16
17impl ReplicaContext {
18    pub fn port(&self) -> u16 {
19        self.port
20    }
21}
22
23impl Drop for KillOnDrop {
24    fn drop(&mut self) {
25        let _ = self.0.kill();
26    }
27}
28
29fn test_identity() -> BasicIdentity {
30    BasicIdentity::from_pem(
31        &b"-----BEGIN PRIVATE KEY-----
32MFMCAQEwBQYDK2VwBCIEIJKDIfd1Ybt48Z23cVEbjL2DGj1P5iDYmthcrptvBO3z
33oSMDIQCJuBJPWt2WWxv0zQmXcXMjY+fP0CJSsB80ztXpOFd2ZQ==
34-----END PRIVATE KEY-----"[..],
35    )
36    .expect("failed to parse identity from PEM")
37}
38
39pub async fn start_replica(
40    replica_bin: &Path,
41    ic_starter_bin: &Path,
42    sandbox_launcher_bin: &Path,
43    canister_sandbox_bin: &Path,
44) -> (Agent, ReplicaContext) {
45    let state = tempfile::TempDir::new().expect("failed to create a temporary directory");
46
47    let port_file = state.path().join("replica.port");
48
49    assert!(
50        ic_starter_bin.exists(),
51        "ic-starter path {} does not exist",
52        ic_starter_bin.display(),
53    );
54    assert!(
55        replica_bin.exists(),
56        "replica path {} does not exist",
57        replica_bin.display(),
58    );
59    assert!(
60        sandbox_launcher_bin.exists(),
61        "sandbox_launcher path {} does not exist",
62        sandbox_launcher_bin.display(),
63    );
64    assert!(
65        canister_sandbox_bin.exists(),
66        "canister_sandbox path {} does not exist",
67        canister_sandbox_bin.display(),
68    );
69
70    let replica_path = format!(
71        "{}:{}{}",
72        sandbox_launcher_bin.parent().unwrap().display(),
73        canister_sandbox_bin.parent().unwrap().display(),
74        std::env::var("PATH").map_or("".into(), |s| format!(":{}", s)),
75    );
76
77    let mut cmd = Command::new(ic_starter_bin);
78    cmd.env("RUST_MIN_STACK", "8192000")
79        .env("PATH", replica_path)
80        .arg("--replica-path")
81        .arg(replica_bin)
82        .arg("--state-dir")
83        .arg(state.path())
84        .arg("--create-funds-whitelist")
85        .arg("*")
86        .arg("--log-level")
87        .arg("critical")
88        .arg("--subnet-type")
89        .arg("system")
90        .arg("--subnet-features")
91        .arg("canister_sandboxing")
92        .arg("--http-port-file")
93        .arg(&port_file)
94        .arg("--initial-notary-delay-millis")
95        .arg("600");
96
97    #[cfg(target_os = "macos")]
98    cmd.args(["--consensus-pool-backend", "rocksdb"]);
99
100    let _proc = KillOnDrop(
101        cmd.stdout(std::process::Stdio::inherit())
102            .stderr(std::process::Stdio::inherit())
103            .spawn()
104            .unwrap_or_else(|e| {
105                panic!(
106                    "Failed to execute ic-starter (path = {}, exists? = {}): {}",
107                    ic_starter_bin.display(),
108                    ic_starter_bin.exists(),
109                    e
110                )
111            }),
112    );
113
114    let mut tries_left = 100;
115    while tries_left > 0 && !port_file.exists() {
116        sleep(Duration::from_millis(100)).await;
117        tries_left -= 1;
118    }
119
120    if !port_file.exists() {
121        panic!("Port file does not exist");
122    }
123
124    let port_bytes = std::fs::read(&port_file).expect("failed to read port file");
125    let port: u16 = String::from_utf8(port_bytes)
126        .unwrap()
127        .parse()
128        .expect("failed to parse port");
129
130    let client = reqwest::ClientBuilder::new()
131        .build()
132        .expect("failed to build an HTTP client");
133
134    let transport = Arc::new(
135        ReqwestHttpReplicaV2Transport::create_with_client(
136            format!("http://localhost:{}", port),
137            client,
138        )
139        .expect("failed to construct replica transport"),
140    );
141
142    let agent = Agent::builder()
143        .with_transport(transport)
144        .with_identity(test_identity())
145        .build()
146        .expect("failed to build agent");
147
148    let mut tries_left = 100;
149    let mut ok = false;
150    let mut last_status = None;
151    while tries_left > 0 && !ok {
152        match agent.status().await {
153            Ok(status) => {
154                ok = status.replica_health_status == Some("healthy".to_string());
155                if let Some(root_key) = status.root_key.as_ref() {
156                    agent.set_root_key(root_key.clone())
157                }
158                last_status = Some(status);
159            }
160            Err(_) => {
161                sleep(Duration::from_millis(500)).await;
162                tries_left -= 1;
163            }
164        }
165    }
166
167    if !ok {
168        panic!(
169            "Replica did not become healthy on port {}, status: {:?}",
170            port, last_status
171        );
172    }
173
174    (
175        agent,
176        ReplicaContext {
177            _proc,
178            _state: state,
179            port,
180        },
181    )
182}