miden_client_integration_tests/
config.rs1use std::env::temp_dir;
2use std::fmt;
3use std::path::{Path, PathBuf};
4use std::str::FromStr;
5use std::sync::Arc;
6
7use anyhow::{Context, Result};
8use miden_client::RemoteTransactionProver;
9use miden_client::builder::ClientBuilder;
10use miden_client::grpc_support::{DEVNET_PROVER_ENDPOINT, TESTNET_PROVER_ENDPOINT};
11use miden_client::note_transport::grpc::GrpcNoteTransportClient;
12use miden_client::note_transport::{
13 NOTE_TRANSPORT_DEVNET_ENDPOINT,
14 NOTE_TRANSPORT_TESTNET_ENDPOINT,
15};
16use miden_client::rpc::{Endpoint, GrpcClient, VerifyingRpcClient};
17use miden_client::testing::common::{FilesystemKeyStore, TestClient, create_test_store_path};
18use miden_client::testing::fee::FeeFunder;
19use miden_client_sqlite_store::ClientBuilderSqliteExt;
20use uuid::Uuid;
21
22use crate::fee_funding;
23
24const NETWORK_DEVNET: &str = "devnet";
25const NETWORK_TESTNET: &str = "testnet";
26const NETWORK_LOCALHOST: &str = "localhost";
27
28#[derive(Clone, Debug)]
30pub enum NoteTransportEndpoint {
31 Devnet,
32 Testnet,
33 Custom(String),
34}
35
36impl NoteTransportEndpoint {
37 pub fn to_url(&self) -> String {
39 match self {
40 Self::Devnet => NOTE_TRANSPORT_DEVNET_ENDPOINT.to_string(),
41 Self::Testnet => NOTE_TRANSPORT_TESTNET_ENDPOINT.to_string(),
42 Self::Custom(url) => url.clone(),
43 }
44 }
45}
46
47impl FromStr for NoteTransportEndpoint {
48 type Err = std::convert::Infallible;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 Ok(match s.to_lowercase().as_str() {
52 "devnet" => Self::Devnet,
53 "testnet" => Self::Testnet,
54 _ => Self::Custom(s.to_string()),
55 })
56 }
57}
58
59impl fmt::Display for NoteTransportEndpoint {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::Devnet => write!(f, "devnet ({NOTE_TRANSPORT_DEVNET_ENDPOINT})"),
63 Self::Testnet => write!(f, "testnet ({NOTE_TRANSPORT_TESTNET_ENDPOINT})"),
64 Self::Custom(url) => write!(f, "{url}"),
65 }
66 }
67}
68
69#[derive(Clone, Debug)]
70pub struct ClientConfig {
71 pub rpc_endpoint: Endpoint,
72 pub rpc_timeout_ms: u64,
73 pub prover_endpoint: Option<String>,
76 pub note_transport_endpoint: Option<NoteTransportEndpoint>,
79 pub fee_funder: Option<Arc<dyn FeeFunder>>,
83}
84
85impl ClientConfig {
86 pub fn new(rpc_endpoint: Endpoint, rpc_timeout_ms: u64) -> Self {
87 Self {
88 rpc_endpoint,
89 rpc_timeout_ms,
90 prover_endpoint: None,
91 note_transport_endpoint: None,
92 fee_funder: None,
93 }
94 }
95
96 #[allow(clippy::return_self_not_must_use)]
97 pub fn with_prover_endpoint(mut self, prover_endpoint: Option<String>) -> Self {
98 self.prover_endpoint = prover_endpoint;
99 self
100 }
101
102 #[allow(clippy::return_self_not_must_use)]
103 pub fn with_note_transport_endpoint(
104 mut self,
105 note_transport_endpoint: Option<NoteTransportEndpoint>,
106 ) -> Self {
107 self.note_transport_endpoint = note_transport_endpoint;
108 self
109 }
110
111 #[allow(clippy::return_self_not_must_use)]
113 pub fn with_fee_funder(mut self, fee_funder: Option<Arc<dyn FeeFunder>>) -> Self {
114 self.fee_funder = fee_funder;
115 self
116 }
117
118 pub fn with_funders(self, funders: Option<&Path>) -> Result<Self> {
122 let fee_funder = fee_funding::load(&self, funders)?;
123 Ok(self.with_fee_funder(fee_funder))
124 }
125
126 pub async fn flush_funder(&self) -> Result<()> {
128 match &self.fee_funder {
129 Some(funder) => funder.flush().await,
130 None => Ok(()),
131 }
132 }
133
134 pub async fn into_unsynced_client(self) -> Result<TestClient> {
141 let fee_funder = self.fee_funder.clone();
142
143 let store_config = create_test_store_path();
144 let auth_path = create_test_auth_path();
145
146 let keystore = FilesystemKeyStore::new(auth_path.clone()).with_context(|| {
147 format!("failed to create keystore at path: {}", auth_path.to_string_lossy())
148 })?;
149
150 let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
151 &self.rpc_endpoint,
152 self.rpc_timeout_ms,
153 )));
154
155 let mut builder = ClientBuilder::new()
156 .rpc(rpc_client)
157 .sqlite_store(store_config)
158 .authenticator(Arc::new(keystore))
159 .tx_discard_delta(None);
160
161 if let Some(prover_url) = &self.prover_endpoint {
162 builder = builder.prover(Arc::new(RemoteTransactionProver::new(prover_url)));
163 }
164
165 if let Some(transport) = &self.note_transport_endpoint {
166 let transport_url = transport.to_url();
167 let transport_timeout = std::env::var("MIDEN_TEST_TIMEOUT")
168 .ok()
169 .and_then(|s| s.parse::<u64>().ok())
170 .unwrap_or(10_000);
171 let nt_client =
172 Arc::new(GrpcNoteTransportClient::new(transport_url.clone(), transport_timeout));
173 builder = builder.note_transport(nt_client);
174 }
175
176 let client = builder.build().await.with_context(|| "failed to build test client")?;
177
178 Ok(TestClient::from(client).with_fee_funder(fee_funder))
179 }
180
181 pub async fn into_client(self) -> Result<TestClient> {
186 let mut client = self.into_unsynced_client().await?;
187
188 client.sync_state().await.with_context(|| "failed to sync client state")?;
189
190 Ok(client)
191 }
192}
193
194impl Default for ClientConfig {
195 fn default() -> Self {
208 let network = std::env::var("TEST_MIDEN_NETWORK").ok();
209 let network_lower = network.map(|n| n.to_lowercase());
210
211 let endpoint = if let Ok(rpc_url) = std::env::var("TEST_MIDEN_RPC_URL") {
214 Endpoint::try_from(rpc_url.as_str()).unwrap()
215 } else {
216 match network_lower.as_deref() {
217 Some(NETWORK_DEVNET) => Endpoint::devnet(),
218 Some(NETWORK_TESTNET) => Endpoint::testnet(),
219 Some(NETWORK_LOCALHOST) | None => Endpoint::localhost(),
220 Some(custom) => Endpoint::try_from(custom).unwrap(),
221 }
222 };
223
224 let prover_endpoint = if let Ok(url) = std::env::var("TEST_MIDEN_PROVER_URL") {
227 match url.to_lowercase().as_str() {
228 NETWORK_LOCALHOST => None,
229 NETWORK_DEVNET => Some(DEVNET_PROVER_ENDPOINT.to_string()),
230 NETWORK_TESTNET => Some(TESTNET_PROVER_ENDPOINT.to_string()),
231 _ => Some(url),
232 }
233 } else {
234 match network_lower.as_deref() {
236 Some(NETWORK_TESTNET) => Some(TESTNET_PROVER_ENDPOINT.to_string()),
237 Some(NETWORK_DEVNET) => Some(DEVNET_PROVER_ENDPOINT.to_string()),
238 _ => None,
239 }
240 };
241
242 let note_transport_endpoint =
244 if let Ok(url) = std::env::var("TEST_MIDEN_NOTE_TRANSPORT_URL") {
245 Some(url.parse::<NoteTransportEndpoint>().unwrap())
246 } else {
247 match network_lower.as_deref() {
249 Some(NETWORK_TESTNET) => Some(NoteTransportEndpoint::Testnet),
250 Some(NETWORK_DEVNET) => Some(NoteTransportEndpoint::Devnet),
251 _ => None,
252 }
253 };
254
255 let timeout_ms = std::env::var("MIDEN_TEST_TIMEOUT")
256 .ok()
257 .and_then(|s| s.parse::<u64>().ok())
258 .unwrap_or(10_000);
259
260 Self::new(endpoint, timeout_ms)
261 .with_prover_endpoint(prover_endpoint)
262 .with_note_transport_endpoint(note_transport_endpoint)
263 }
264}
265
266pub fn create_test_auth_path() -> PathBuf {
268 let auth_path = temp_dir().join(format!("keystore-{}", Uuid::new_v4()));
269 std::fs::create_dir_all(&auth_path).unwrap();
270 auth_path
271}