use std::env::temp_dir;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use anyhow::{Context, Result};
use miden_client::builder::ClientBuilder;
use miden_client::crypto::RandomCoin;
use miden_client::grpc_support::{DEVNET_PROVER_ENDPOINT, TESTNET_PROVER_ENDPOINT};
use miden_client::note_transport::grpc::GrpcNoteTransportClient;
use miden_client::note_transport::{
NOTE_TRANSPORT_DEVNET_ENDPOINT,
NOTE_TRANSPORT_TESTNET_ENDPOINT,
};
use miden_client::rpc::{Endpoint, GrpcClient};
use miden_client::testing::common::{FilesystemKeyStore, TestClient, create_test_store_path};
use miden_client::{DebugMode, Felt, RemoteTransactionProver};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use rand::Rng;
use uuid::Uuid;
const NETWORK_DEVNET: &str = "devnet";
const NETWORK_TESTNET: &str = "testnet";
const NETWORK_LOCALHOST: &str = "localhost";
#[derive(Clone, Debug)]
pub enum NoteTransportEndpoint {
Devnet,
Testnet,
Custom(String),
}
impl NoteTransportEndpoint {
pub fn to_url(&self) -> String {
match self {
Self::Devnet => NOTE_TRANSPORT_DEVNET_ENDPOINT.to_string(),
Self::Testnet => NOTE_TRANSPORT_TESTNET_ENDPOINT.to_string(),
Self::Custom(url) => url.clone(),
}
}
}
impl FromStr for NoteTransportEndpoint {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_lowercase().as_str() {
"devnet" => Self::Devnet,
"testnet" => Self::Testnet,
_ => Self::Custom(s.to_string()),
})
}
}
impl fmt::Display for NoteTransportEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Devnet => write!(f, "devnet ({})", NOTE_TRANSPORT_DEVNET_ENDPOINT),
Self::Testnet => write!(f, "testnet ({})", NOTE_TRANSPORT_TESTNET_ENDPOINT),
Self::Custom(url) => write!(f, "{url}"),
}
}
}
#[derive(Clone, Debug)]
pub struct ClientConfig {
pub rpc_endpoint: Endpoint,
pub rpc_timeout_ms: u64,
pub store_config: PathBuf,
pub auth_path: PathBuf,
pub prover_endpoint: Option<String>,
pub note_transport_endpoint: Option<NoteTransportEndpoint>,
}
impl ClientConfig {
pub fn new(rpc_endpoint: Endpoint, rpc_timeout_ms: u64) -> Self {
Self {
rpc_endpoint,
rpc_timeout_ms,
auth_path: create_test_auth_path(),
store_config: create_test_store_path(),
prover_endpoint: None,
note_transport_endpoint: None,
}
}
pub fn as_parts(&self) -> (Endpoint, u64, PathBuf, PathBuf) {
(
self.rpc_endpoint.clone(),
self.rpc_timeout_ms,
self.store_config.clone(),
self.auth_path.clone(),
)
}
#[allow(clippy::return_self_not_must_use)]
pub fn with_prover_endpoint(mut self, prover_endpoint: Option<String>) -> Self {
self.prover_endpoint = prover_endpoint;
self
}
#[allow(clippy::return_self_not_must_use)]
pub fn with_note_transport_endpoint(
mut self,
note_transport_endpoint: Option<NoteTransportEndpoint>,
) -> Self {
self.note_transport_endpoint = note_transport_endpoint;
self
}
#[allow(clippy::return_self_not_must_use)]
pub fn with_rpc_endpoint(mut self, rpc_endpoint: Endpoint) -> Self {
self.rpc_endpoint = rpc_endpoint;
self
}
pub fn rpc_endpoint(&self) -> Endpoint {
self.rpc_endpoint.clone()
}
pub async fn into_client_builder(
self,
) -> Result<(ClientBuilder<FilesystemKeyStore>, FilesystemKeyStore)> {
let (rpc_endpoint, rpc_timeout, store_config, auth_path) = self.as_parts();
let mut rng = rand::rng();
let coin_seed: [u64; 4] = rng.random();
let rng = RandomCoin::new(coin_seed.map(Felt::new_unchecked).into());
let keystore = FilesystemKeyStore::new(auth_path.clone()).with_context(|| {
format!("failed to create keystore at path: {}", auth_path.to_string_lossy())
})?;
let rpc_client = Arc::new(GrpcClient::new(&rpc_endpoint, rpc_timeout));
let mut builder = ClientBuilder::new()
.rpc(rpc_client)
.rng(Box::new(rng))
.sqlite_store(store_config)
.authenticator(Arc::new(keystore.clone()))
.in_debug_mode(DebugMode::Disabled)
.tx_discard_delta(None);
if let Some(prover_url) = &self.prover_endpoint {
builder = builder.prover(Arc::new(RemoteTransactionProver::new(prover_url)));
}
if let Some(transport) = &self.note_transport_endpoint {
let transport_url = transport.to_url();
let transport_timeout = std::env::var("MIDEN_TEST_TIMEOUT")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(10_000);
let nt_client =
Arc::new(GrpcNoteTransportClient::new(transport_url.clone(), transport_timeout));
builder = builder.note_transport(nt_client);
}
Ok((builder, keystore))
}
pub async fn into_client(self) -> Result<(TestClient, FilesystemKeyStore)> {
let (builder, keystore) = self.into_client_builder().await?;
let mut client = builder.build().await.with_context(|| "failed to build test client")?;
client.sync_state().await.with_context(|| "failed to sync client state")?;
Ok((client, keystore))
}
}
impl Default for ClientConfig {
fn default() -> Self {
let network = std::env::var("TEST_MIDEN_NETWORK").ok();
let network_lower = network.map(|n| n.to_lowercase());
let endpoint = if let Ok(rpc_url) = std::env::var("TEST_MIDEN_RPC_URL") {
Endpoint::try_from(rpc_url.as_str()).unwrap()
} else {
match network_lower.as_deref() {
Some(NETWORK_DEVNET) => Endpoint::devnet(),
Some(NETWORK_TESTNET) => Endpoint::testnet(),
Some(NETWORK_LOCALHOST) | None => Endpoint::localhost(),
Some(custom) => Endpoint::try_from(custom).unwrap(),
}
};
let prover_endpoint = if let Ok(url) = std::env::var("TEST_MIDEN_PROVER_URL") {
match url.to_lowercase().as_str() {
NETWORK_LOCALHOST => None,
NETWORK_DEVNET => Some(DEVNET_PROVER_ENDPOINT.to_string()),
NETWORK_TESTNET => Some(TESTNET_PROVER_ENDPOINT.to_string()),
_ => Some(url),
}
} else {
match network_lower.as_deref() {
Some(NETWORK_TESTNET) => Some(TESTNET_PROVER_ENDPOINT.to_string()),
Some(NETWORK_DEVNET) => Some(DEVNET_PROVER_ENDPOINT.to_string()),
_ => None,
}
};
let note_transport_endpoint =
if let Ok(url) = std::env::var("TEST_MIDEN_NOTE_TRANSPORT_URL") {
Some(url.parse::<NoteTransportEndpoint>().unwrap())
} else {
match network_lower.as_deref() {
Some(NETWORK_TESTNET) => Some(NoteTransportEndpoint::Testnet),
Some(NETWORK_DEVNET) => Some(NoteTransportEndpoint::Devnet),
_ => None,
}
};
let timeout_ms = std::env::var("MIDEN_TEST_TIMEOUT")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(10_000);
Self::new(endpoint, timeout_ms)
.with_prover_endpoint(prover_endpoint)
.with_note_transport_endpoint(note_transport_endpoint)
}
}
pub(crate) fn create_test_auth_path() -> PathBuf {
let auth_path = temp_dir().join(format!("keystore-{}", Uuid::new_v4()));
std::fs::create_dir_all(&auth_path).unwrap();
auth_path
}