use alloc::boxed::Box;
use alloc::sync::Arc;
use miden_protocol::assembly::{DefaultSourceManager, SourceManagerSync};
use miden_protocol::block::BlockNumber;
use miden_protocol::crypto::rand::RandomCoin;
use miden_protocol::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
use miden_tx::{ExecutionOptions, LocalTransactionProver};
use rand::Rng;
#[cfg(any(feature = "tonic", feature = "std"))]
use crate::alloc::string::ToString;
#[cfg(feature = "std")]
use crate::keystore::FilesystemKeyStore;
use crate::keystore::Keystore;
use crate::note_transport::NoteTransportClient;
use crate::rpc::{Endpoint, NodeRpcClient};
use crate::store::{Store, StoreError};
use crate::transaction::TransactionProver;
use crate::{Client, ClientError, ClientRng, ClientRngBox, DebugMode, grpc_support};
const TX_DISCARD_DELTA: u32 = 20;
pub use grpc_support::*;
pub enum StoreBuilder {
Store(Arc<dyn Store>),
Factory(Box<dyn StoreFactory>),
}
#[async_trait::async_trait]
pub trait StoreFactory {
async fn build(&self) -> Result<Arc<dyn Store>, StoreError>;
}
pub struct ClientBuilder<AUTH> {
rpc_api: Option<Arc<dyn NodeRpcClient>>,
pub store: Option<StoreBuilder>,
rng: Option<ClientRngBox>,
authenticator: Option<Arc<AUTH>>,
in_debug_mode: DebugMode,
tx_discard_delta: Option<u32>,
max_block_number_delta: Option<u32>,
note_transport_api: Option<Arc<dyn NoteTransportClient>>,
#[allow(unused)]
note_transport_config: Option<NoteTransportConfig>,
tx_prover: Option<Arc<dyn TransactionProver + Send + Sync>>,
endpoint: Option<Endpoint>,
source_manager: Option<Arc<dyn SourceManagerSync>>,
}
impl<AUTH> Default for ClientBuilder<AUTH> {
fn default() -> Self {
Self {
rpc_api: None,
store: None,
rng: None,
authenticator: None,
in_debug_mode: DebugMode::Disabled,
tx_discard_delta: Some(TX_DISCARD_DELTA),
max_block_number_delta: None,
note_transport_api: None,
note_transport_config: None,
tx_prover: None,
endpoint: None,
source_manager: None,
}
}
}
#[cfg(feature = "tonic")]
impl<AUTH> ClientBuilder<AUTH>
where
AUTH: BuilderAuthenticator,
{
#[must_use]
pub fn for_testnet() -> Self {
let endpoint = Endpoint::testnet();
Self {
rpc_api: Some(Arc::new(crate::rpc::GrpcClient::new(
&endpoint,
DEFAULT_GRPC_TIMEOUT_MS,
))),
tx_prover: Some(Arc::new(RemoteTransactionProver::new(
TESTNET_PROVER_ENDPOINT.to_string(),
))),
note_transport_config: Some(NoteTransportConfig {
endpoint: crate::note_transport::NOTE_TRANSPORT_TESTNET_ENDPOINT.to_string(),
timeout_ms: DEFAULT_GRPC_TIMEOUT_MS,
}),
endpoint: Some(endpoint),
..Self::default()
}
}
#[must_use]
pub fn for_devnet() -> Self {
let endpoint = Endpoint::devnet();
Self {
rpc_api: Some(Arc::new(crate::rpc::GrpcClient::new(
&endpoint,
DEFAULT_GRPC_TIMEOUT_MS,
))),
tx_prover: Some(Arc::new(RemoteTransactionProver::new(
DEVNET_PROVER_ENDPOINT.to_string(),
))),
note_transport_config: Some(NoteTransportConfig {
endpoint: crate::note_transport::NOTE_TRANSPORT_DEVNET_ENDPOINT.to_string(),
timeout_ms: DEFAULT_GRPC_TIMEOUT_MS,
}),
endpoint: Some(endpoint),
..Self::default()
}
}
#[must_use]
pub fn for_localhost() -> Self {
let endpoint = Endpoint::localhost();
Self {
rpc_api: Some(Arc::new(crate::rpc::GrpcClient::new(
&endpoint,
DEFAULT_GRPC_TIMEOUT_MS,
))),
endpoint: Some(endpoint),
..Self::default()
}
}
}
impl<AUTH> ClientBuilder<AUTH>
where
AUTH: BuilderAuthenticator,
{
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn in_debug_mode(mut self, debug: DebugMode) -> Self {
self.in_debug_mode = debug;
self
}
#[must_use]
pub fn rpc(mut self, client: Arc<dyn NodeRpcClient>) -> Self {
self.rpc_api = Some(client);
self
}
#[must_use]
#[cfg(feature = "tonic")]
pub fn grpc_client(mut self, endpoint: &crate::rpc::Endpoint, timeout_ms: Option<u64>) -> Self {
self.rpc_api = Some(Arc::new(crate::rpc::GrpcClient::new(
endpoint,
timeout_ms.unwrap_or(DEFAULT_GRPC_TIMEOUT_MS),
)));
self
}
#[must_use]
pub fn store(mut self, store: Arc<dyn Store>) -> Self {
self.store = Some(StoreBuilder::Store(store));
self
}
#[must_use]
pub fn rng(mut self, rng: ClientRngBox) -> Self {
self.rng = Some(rng);
self
}
#[must_use]
pub fn authenticator(mut self, authenticator: Arc<AUTH>) -> Self {
self.authenticator = Some(authenticator);
self
}
#[must_use]
pub fn source_manager(mut self, sm: Arc<dyn SourceManagerSync>) -> Self {
self.source_manager = Some(sm);
self
}
#[must_use]
pub fn max_block_number_delta(mut self, delta: u32) -> Self {
self.max_block_number_delta = Some(delta);
self
}
#[must_use]
pub fn tx_discard_delta(mut self, delta: Option<u32>) -> Self {
self.tx_discard_delta = delta;
self
}
#[deprecated(since = "0.10.0", note = "Use `tx_discard_delta` instead")]
#[must_use]
pub fn tx_graceful_blocks(mut self, delta: Option<u32>) -> Self {
self.tx_discard_delta = delta;
self
}
#[must_use]
pub fn note_transport(mut self, client: Arc<dyn NoteTransportClient>) -> Self {
self.note_transport_api = Some(client);
self
}
#[must_use]
pub fn prover(mut self, prover: Arc<dyn TransactionProver + Send + Sync>) -> Self {
self.tx_prover = Some(prover);
self
}
#[must_use]
pub fn endpoint(&self) -> Option<&Endpoint> {
self.endpoint.as_ref()
}
#[allow(clippy::unused_async, unused_mut)]
pub async fn build(mut self) -> Result<Client<AUTH>, ClientError> {
let rpc_api: Arc<dyn NodeRpcClient> = if let Some(client) = self.rpc_api {
client
} else {
return Err(ClientError::ClientInitializationError(
"RPC client is required. Call `.rpc(...)` or `.grpc_client(...)`.".into(),
));
};
let store = if let Some(store_builder) = self.store {
match store_builder {
StoreBuilder::Store(store) => store,
StoreBuilder::Factory(factory) => factory.build().await?,
}
} else {
return Err(ClientError::ClientInitializationError(
"Store must be specified. Call `.store(...)`.".into(),
));
};
let rng = if let Some(user_rng) = self.rng {
user_rng
} else {
let mut seed_rng = rand::rng();
let coin_seed: [u64; 4] = seed_rng.random();
Box::new(RandomCoin::new(coin_seed.map(Felt::new).into()))
};
let tx_prover: Arc<dyn TransactionProver + Send + Sync> =
self.tx_prover.unwrap_or_else(|| Arc::new(LocalTransactionProver::default()));
let source_manager: Arc<dyn SourceManagerSync> =
self.source_manager.unwrap_or_else(|| Arc::new(DefaultSourceManager::default()));
if let Some((genesis, _)) = store.get_block_header_by_num(BlockNumber::GENESIS).await? {
rpc_api.set_genesis_commitment(genesis.commitment()).await?;
}
if let Some(limits) = store.get_rpc_limits().await? {
rpc_api.set_rpc_limits(limits).await;
}
#[cfg(feature = "tonic")]
if self.note_transport_api.is_none()
&& let Some(config) = self.note_transport_config
{
let transport = crate::note_transport::grpc::GrpcNoteTransportClient::new(
config.endpoint,
config.timeout_ms,
);
self.note_transport_api = Some(Arc::new(transport) as Arc<dyn NoteTransportClient>);
}
Ok(Client {
store,
rng: ClientRng::new(rng),
rpc_api,
tx_prover,
authenticator: self.authenticator,
source_manager,
exec_options: ExecutionOptions::new(
Some(MAX_TX_EXECUTION_CYCLES),
MIN_TX_EXECUTION_CYCLES,
ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
false,
self.in_debug_mode.into(),
)
.expect("Default executor's options should always be valid"),
tx_discard_delta: self.tx_discard_delta,
max_block_number_delta: self.max_block_number_delta,
note_transport_api: self.note_transport_api.clone(),
})
}
}
#[cfg(feature = "std")]
pub trait BuilderAuthenticator: Keystore + From<FilesystemKeyStore> + 'static {}
#[cfg(feature = "std")]
impl<T> BuilderAuthenticator for T where T: Keystore + From<FilesystemKeyStore> + 'static {}
#[cfg(not(feature = "std"))]
pub trait BuilderAuthenticator: Keystore + 'static {}
#[cfg(not(feature = "std"))]
impl<T> BuilderAuthenticator for T where T: Keystore + 'static {}
#[cfg(feature = "std")]
impl ClientBuilder<FilesystemKeyStore> {
pub fn filesystem_keystore(
self,
keystore_path: impl Into<std::path::PathBuf>,
) -> Result<Self, ClientError> {
let keystore = FilesystemKeyStore::new(keystore_path.into())
.map_err(|e| ClientError::ClientInitializationError(e.to_string()))?;
Ok(self.authenticator(Arc::new(keystore)))
}
}