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::ChainState;
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::{RwLock, 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_MAX_TX_CYCLES: u32 = 1 << 19;
75
76#[derive(Debug, Clone)]
83pub struct NtxBuilderConfig {
84 pub store_url: Url,
86
87 pub block_producer_url: Url,
89
90 pub validator_url: Url,
92
93 pub tx_prover_url: Option<Url>,
95
96 pub script_cache_size: NonZeroUsize,
99
100 pub max_concurrent_txs: usize,
103
104 pub max_notes_per_tx: NonZeroUsize,
106
107 pub max_note_attempts: usize,
110
111 pub max_block_count: usize,
113
114 pub account_channel_capacity: usize,
116
117 pub idle_timeout: Duration,
122
123 pub max_account_crashes: usize,
127
128 pub max_cycles: u32,
133
134 pub database_filepath: PathBuf,
136}
137
138impl NtxBuilderConfig {
139 pub fn new(
140 store_url: Url,
141 block_producer_url: Url,
142 validator_url: Url,
143 database_filepath: PathBuf,
144 ) -> Self {
145 Self {
146 store_url,
147 block_producer_url,
148 validator_url,
149 tx_prover_url: None,
150 script_cache_size: DEFAULT_SCRIPT_CACHE_SIZE,
151 max_concurrent_txs: DEFAULT_MAX_CONCURRENT_TXS,
152 max_notes_per_tx: DEFAULT_MAX_NOTES_PER_TX,
153 max_note_attempts: DEFAULT_MAX_NOTE_ATTEMPTS,
154 max_block_count: DEFAULT_MAX_BLOCK_COUNT,
155 account_channel_capacity: DEFAULT_ACCOUNT_CHANNEL_CAPACITY,
156 idle_timeout: DEFAULT_IDLE_TIMEOUT,
157 max_account_crashes: DEFAULT_MAX_ACCOUNT_CRASHES,
158 max_cycles: DEFAULT_MAX_TX_CYCLES,
159 database_filepath,
160 }
161 }
162
163 #[must_use]
167 pub fn with_tx_prover_url(mut self, url: Option<Url>) -> Self {
168 self.tx_prover_url = url;
169 self
170 }
171
172 #[must_use]
174 pub fn with_script_cache_size(mut self, size: NonZeroUsize) -> Self {
175 self.script_cache_size = size;
176 self
177 }
178
179 #[must_use]
181 pub fn with_max_concurrent_txs(mut self, max: usize) -> Self {
182 self.max_concurrent_txs = max;
183 self
184 }
185
186 #[must_use]
192 pub fn with_max_notes_per_tx(mut self, max: NonZeroUsize) -> Self {
193 assert!(
194 max.get() <= miden_tx::MAX_NUM_CHECKER_NOTES,
195 "max_notes_per_tx ({}) exceeds MAX_NUM_CHECKER_NOTES ({})",
196 max,
197 miden_tx::MAX_NUM_CHECKER_NOTES
198 );
199 self.max_notes_per_tx = max;
200 self
201 }
202
203 #[must_use]
205 pub fn with_max_note_attempts(mut self, max: usize) -> Self {
206 self.max_note_attempts = max;
207 self
208 }
209
210 #[must_use]
212 pub fn with_max_block_count(mut self, max: usize) -> Self {
213 self.max_block_count = max;
214 self
215 }
216
217 #[must_use]
219 pub fn with_account_channel_capacity(mut self, capacity: usize) -> Self {
220 self.account_channel_capacity = capacity;
221 self
222 }
223
224 #[must_use]
228 pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
229 self.idle_timeout = timeout;
230 self
231 }
232
233 #[must_use]
235 pub fn with_max_account_crashes(mut self, max: usize) -> Self {
236 self.max_account_crashes = max;
237 self
238 }
239
240 #[must_use]
242 pub fn with_max_cycles(mut self, max: u32) -> Self {
243 self.max_cycles = max;
244 self
245 }
246
247 pub async fn build(self) -> anyhow::Result<NetworkTransactionBuilder> {
259 let db = Db::setup(self.database_filepath.clone()).await?;
261
262 db.purge_inflight().await.context("failed to purge inflight state")?;
264
265 let script_cache = LruCache::new(self.script_cache_size);
266 let coordinator =
267 Coordinator::new(self.max_concurrent_txs, self.max_account_crashes, db.clone());
268
269 let store = StoreClient::new(self.store_url.clone());
270 let block_producer = BlockProducerClient::new(self.block_producer_url.clone());
271 let validator = ValidatorClient::new(self.validator_url.clone());
272 let prover = self.tx_prover_url.clone().map(RemoteTransactionProver::new);
273
274 let subscription = block_producer
277 .subscribe_to_mempool_with_retry()
278 .await
279 .map_err(|err| anyhow::anyhow!(err))
280 .context("failed to subscribe to mempool events")?;
281 let mempool_events: MempoolEventStream = Box::pin(subscription.into_stream());
282
283 let (chain_tip_header, chain_mmr) = store
284 .get_latest_blockchain_data_with_retry()
285 .await?
286 .context("store should contain a latest block")?;
287
288 db.upsert_chain_state(chain_tip_header.block_num(), chain_tip_header.clone())
290 .await
291 .context("failed to upsert chain state")?;
292
293 let chain_state = Arc::new(RwLock::new(ChainState::new(chain_tip_header, chain_mmr)));
294
295 let (request_tx, actor_request_rx) = mpsc::channel(1);
296
297 let actor_context = AccountActorContext {
298 block_producer: block_producer.clone(),
299 validator,
300 prover,
301 chain_state: chain_state.clone(),
302 store: store.clone(),
303 script_cache,
304 max_notes_per_tx: self.max_notes_per_tx,
305 max_note_attempts: self.max_note_attempts,
306 idle_timeout: self.idle_timeout,
307 db: db.clone(),
308 request_tx,
309 max_cycles: self.max_cycles,
310 };
311
312 Ok(NetworkTransactionBuilder::new(
313 self,
314 coordinator,
315 store,
316 db,
317 chain_state,
318 actor_context,
319 mempool_events,
320 actor_request_rx,
321 ))
322 }
323}