icrc1_test_replica/
lib.rs1use 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}