miden_node_ntx_builder/
lib.rs1use std::num::NonZeroUsize;
2use std::path::PathBuf;
3use std::sync::Arc;
4use std::time::Duration;
5
6use actor::AccountActorContext;
7use anyhow::Context;
8use builder::MempoolEventStream;
9use chain_state::SharedChainState;
10use clients::{BlockProducerClient, StoreClient, ValidatorClient};
11use coordinator::Coordinator;
12use db::Db;
13use futures::TryStreamExt;
14use miden_node_utils::ErrorReport;
15use miden_node_utils::lru_cache::LruCache;
16use miden_remote_prover_client::RemoteTransactionProver;
17use tokio::sync::mpsc;
18use url::Url;
19
20pub(crate) type NoteError = Arc<dyn ErrorReport + Send + Sync>;
21
22mod actor;
23mod builder;
24mod chain_state;
25mod clients;
26mod coordinator;
27pub(crate) mod db;
28pub(crate) mod inflight_note;
29pub mod server;
30
31#[cfg(test)]
32pub(crate) mod test_utils;
33
34pub use builder::NetworkTransactionBuilder;
35
36const COMPONENT: &str = "miden-ntx-builder";
40
41const DEFAULT_MAX_NOTES_PER_TX: NonZeroUsize = NonZeroUsize::new(20).expect("literal is non-zero");
43const _: () = assert!(DEFAULT_MAX_NOTES_PER_TX.get() <= miden_tx::MAX_NUM_CHECKER_NOTES);
44
45const DEFAULT_MAX_CONCURRENT_TXS: usize = 4;
50
51const DEFAULT_MAX_BLOCK_COUNT: usize = 4;
53
54const DEFAULT_ACCOUNT_CHANNEL_CAPACITY: usize = 1_000;
56
57const DEFAULT_MAX_NOTE_ATTEMPTS: usize = 30;
59
60const DEFAULT_SCRIPT_CACHE_SIZE: NonZeroUsize =
62 NonZeroUsize::new(1_000).expect("literal is non-zero");
63
64const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
66
67const DEFAULT_MAX_ACCOUNT_CRASHES: usize = 10;
69
70const DEFAULT_REQUEST_BACKOFF_INITIAL: Duration = Duration::from_millis(100);
74
75const DEFAULT_REQUEST_BACKOFF_MAX: Duration = Duration::from_secs(30);
77
78const DEFAULT_MAX_TX_CYCLES: u32 = 1 << 19;
83
84#[derive(Debug, Clone)]
91pub struct NtxBuilderConfig {
92 pub store_url: Url,
94
95 pub block_producer_url: Url,
97
98 pub validator_url: Url,
100
101 pub tx_prover_url: Option<Url>,
103
104 pub script_cache_size: NonZeroUsize,
107
108 pub max_concurrent_txs: usize,
111
112 pub max_notes_per_tx: NonZeroUsize,
114
115 pub max_note_attempts: usize,
118
119 pub max_block_count: usize,
121
122 pub account_channel_capacity: usize,
124
125 pub idle_timeout: Duration,
130
131 pub max_account_crashes: usize,
135
136 pub max_cycles: u32,
141
142 pub request_backoff_initial: Duration,
147
148 pub request_backoff_max: Duration,
150
151 pub database_filepath: PathBuf,
153}
154
155impl NtxBuilderConfig {
156 pub fn new(
157 store_url: Url,
158 block_producer_url: Url,
159 validator_url: Url,
160 database_filepath: PathBuf,
161 ) -> Self {
162 Self {
163 store_url,
164 block_producer_url,
165 validator_url,
166 tx_prover_url: None,
167 script_cache_size: DEFAULT_SCRIPT_CACHE_SIZE,
168 max_concurrent_txs: DEFAULT_MAX_CONCURRENT_TXS,
169 max_notes_per_tx: DEFAULT_MAX_NOTES_PER_TX,
170 max_note_attempts: DEFAULT_MAX_NOTE_ATTEMPTS,
171 max_block_count: DEFAULT_MAX_BLOCK_COUNT,
172 account_channel_capacity: DEFAULT_ACCOUNT_CHANNEL_CAPACITY,
173 idle_timeout: DEFAULT_IDLE_TIMEOUT,
174 max_account_crashes: DEFAULT_MAX_ACCOUNT_CRASHES,
175 max_cycles: DEFAULT_MAX_TX_CYCLES,
176 request_backoff_initial: DEFAULT_REQUEST_BACKOFF_INITIAL,
177 request_backoff_max: DEFAULT_REQUEST_BACKOFF_MAX,
178 database_filepath,
179 }
180 }
181
182 #[must_use]
186 pub fn with_tx_prover_url(mut self, url: Option<Url>) -> Self {
187 self.tx_prover_url = url;
188 self
189 }
190
191 #[must_use]
193 pub fn with_script_cache_size(mut self, size: NonZeroUsize) -> Self {
194 self.script_cache_size = size;
195 self
196 }
197
198 #[must_use]
200 pub fn with_max_concurrent_txs(mut self, max: usize) -> Self {
201 self.max_concurrent_txs = max;
202 self
203 }
204
205 #[must_use]
211 pub fn with_max_notes_per_tx(mut self, max: NonZeroUsize) -> Self {
212 assert!(
213 max.get() <= miden_tx::MAX_NUM_CHECKER_NOTES,
214 "max_notes_per_tx ({}) exceeds MAX_NUM_CHECKER_NOTES ({})",
215 max,
216 miden_tx::MAX_NUM_CHECKER_NOTES
217 );
218 self.max_notes_per_tx = max;
219 self
220 }
221
222 #[must_use]
224 pub fn with_max_note_attempts(mut self, max: usize) -> Self {
225 self.max_note_attempts = max;
226 self
227 }
228
229 #[must_use]
231 pub fn with_max_block_count(mut self, max: usize) -> Self {
232 self.max_block_count = max;
233 self
234 }
235
236 #[must_use]
238 pub fn with_account_channel_capacity(mut self, capacity: usize) -> Self {
239 self.account_channel_capacity = capacity;
240 self
241 }
242
243 #[must_use]
247 pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
248 self.idle_timeout = timeout;
249 self
250 }
251
252 #[must_use]
254 pub fn with_max_account_crashes(mut self, max: usize) -> Self {
255 self.max_account_crashes = max;
256 self
257 }
258
259 #[must_use]
261 pub fn with_max_cycles(mut self, max: u32) -> Self {
262 self.max_cycles = max;
263 self
264 }
265
266 #[must_use]
269 pub fn with_request_backoff(mut self, initial: Duration, max: Duration) -> Self {
270 self.request_backoff_initial = initial;
271 self.request_backoff_max = max;
272 self
273 }
274
275 pub async fn build(self) -> anyhow::Result<NetworkTransactionBuilder> {
287 let db = Db::setup(self.database_filepath.clone()).await?;
289
290 db.purge_inflight().await.context("failed to purge inflight state")?;
292
293 let script_cache = LruCache::new(self.script_cache_size);
294 let coordinator =
295 Coordinator::new(self.max_concurrent_txs, self.max_account_crashes, db.clone());
296
297 let store = StoreClient::new(self.store_url.clone());
298 let block_producer = BlockProducerClient::new(self.block_producer_url.clone());
299 let validator = ValidatorClient::new(self.validator_url.clone());
300 let prover = self.tx_prover_url.clone().map(RemoteTransactionProver::new);
301
302 let subscription = block_producer
305 .subscribe_to_mempool_with_retry()
306 .await
307 .map_err(|err| anyhow::anyhow!(err))
308 .context("failed to subscribe to mempool events")?;
309 let mempool_events: MempoolEventStream = Box::pin(subscription.into_stream());
310
311 let (chain_tip_header, chain_mmr) = store
312 .get_latest_blockchain_data_with_retry()
313 .await?
314 .context("store should contain a latest block")?;
315
316 db.upsert_chain_state(chain_tip_header.block_num(), chain_tip_header.clone())
318 .await
319 .context("failed to upsert chain state")?;
320
321 let chain_state = Arc::new(SharedChainState::new(chain_tip_header, chain_mmr));
322
323 let (request_tx, actor_request_rx) = mpsc::channel(1);
324
325 let actor_context = AccountActorContext {
326 block_producer: block_producer.clone(),
327 validator,
328 prover,
329 chain_state: Arc::clone(&chain_state),
330 store: store.clone(),
331 script_cache,
332 max_notes_per_tx: self.max_notes_per_tx,
333 max_note_attempts: self.max_note_attempts,
334 idle_timeout: self.idle_timeout,
335 db: db.clone(),
336 request_tx,
337 max_cycles: self.max_cycles,
338 request_backoff_initial: self.request_backoff_initial,
339 request_backoff_max: self.request_backoff_max,
340 };
341
342 Ok(NetworkTransactionBuilder::new(
343 self,
344 coordinator,
345 store,
346 db,
347 chain_state,
348 actor_context,
349 mempool_events,
350 actor_request_rx,
351 ))
352 }
353}