1use super::inner::node_executor::NodeExecutorHandle;
3use super::inner::InMemoryNodeInner;
4use super::vm::AnvilVM;
5use crate::delegate_vm;
6use crate::deps::InMemoryStorage;
7use crate::filters::EthFilters;
8use crate::node::fee_model::TestNodeFeeInputProvider;
9use crate::node::impersonate::{ImpersonationManager, ImpersonationState};
10use crate::node::inner::blockchain::ReadBlockchain;
11use crate::node::inner::storage::ReadStorageDyn;
12use crate::node::inner::time::ReadTime;
13use crate::node::sealer::BlockSealerState;
14use crate::node::state::VersionedState;
15use crate::node::state_override::apply_state_override;
16use crate::node::traces::call_error::CallErrorTracer;
17use crate::node::traces::decoder::CallTraceDecoderBuilder;
18use crate::node::{BlockSealer, BlockSealerMode, NodeExecutor, TxBatch, TxPool};
19use crate::observability::Observability;
20use crate::system_contracts::SystemContracts;
21use anvil_zksync_common::cache::CacheConfig;
22use anvil_zksync_common::sh_println;
23use anvil_zksync_common::shell::get_shell;
24use anvil_zksync_config::constants::{NON_FORK_FIRST_BLOCK_TIMESTAMP, TEST_NODE_NETWORK_ID};
25use anvil_zksync_config::types::Genesis;
26use anvil_zksync_config::TestNodeConfig;
27use anvil_zksync_traces::{
28 build_call_trace_arena, decode_trace_arena, filter_call_trace_arena,
29 identifier::SignaturesIdentifier, render_trace_arena_inner,
30};
31use anvil_zksync_types::{
32 traces::CallTraceArena, LogLevel, ShowGasDetails, ShowStorageLogs, ShowVMDetails,
33};
34use flate2::read::GzDecoder;
35use flate2::write::GzEncoder;
36use flate2::Compression;
37use indexmap::IndexMap;
38use once_cell::sync::OnceCell;
39use serde::{Deserialize, Serialize};
40use std::collections::{HashMap, HashSet};
41use std::io::{Read, Write};
42use std::sync::Arc;
43use tokio::sync::RwLock;
44use zksync_contracts::{BaseSystemContracts, BaseSystemContractsHashes};
45use zksync_error::anvil_zksync::node::{
46 generic_error, to_generic, AnvilNodeError, AnvilNodeResult,
47};
48use zksync_error::anvil_zksync::state::{StateLoaderError, StateLoaderResult};
49use zksync_multivm::interface::storage::{
50 ReadStorage, StoragePtr, StorageView, StorageWithOverrides,
51};
52use zksync_multivm::interface::VmFactory;
53use zksync_multivm::interface::{
54 ExecutionResult, InspectExecutionMode, L1BatchEnv, L2BlockEnv, TxExecutionMode, VmInterface,
55};
56use zksync_multivm::tracers::CallTracer;
57use zksync_multivm::utils::{get_batch_base_fee, get_max_batch_gas_limit};
58use zksync_multivm::vm_latest::Vm;
59use zksync_types::api::state_override::StateOverride;
60
61use crate::node::fork::{ForkClient, ForkSource};
62use crate::node::keys::StorageKeyLayout;
63use zksync_multivm::vm_latest::{HistoryDisabled, ToTracerPointer};
64use zksync_multivm::VmVersion;
65use zksync_types::api::{Block, DebugCall, TransactionReceipt, TransactionVariant};
66use zksync_types::block::{unpack_block_info, L1BatchHeader, L2BlockHasher};
67use zksync_types::fee_model::BatchFeeInput;
68use zksync_types::l2::L2Tx;
69use zksync_types::storage::{
70 EMPTY_UNCLES_HASH, SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
71};
72use zksync_types::web3::Bytes;
73use zksync_types::{
74 h256_to_u256, AccountTreeId, Address, Bloom, L1BatchNumber, L2BlockNumber, L2ChainId,
75 PackedEthSignature, ProtocolVersionId, StorageKey, StorageValue, Transaction, H160, H256, H64,
76 U256, U64,
77};
78
79pub const MAX_TX_SIZE: usize = 1_200_000;
83pub const ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION: u64 = 1_000;
85pub const MAX_PREVIOUS_STATES: u16 = 128;
87pub const PROTOCOL_VERSION: &str = "zks/1";
89
90pub fn compute_hash<'a>(
91 protocol_version: ProtocolVersionId,
92 number: L2BlockNumber,
93 timestamp: u64,
94 prev_l2_block_hash: H256,
95 tx_hashes: impl IntoIterator<Item = &'a H256>,
96) -> H256 {
97 let mut block_hasher = L2BlockHasher::new(number, timestamp, prev_l2_block_hash);
98 for tx_hash in tx_hashes.into_iter() {
99 block_hasher.push_tx_hash(*tx_hash);
100 }
101 block_hasher.finalize(protocol_version)
102}
103
104pub fn create_genesis_from_json(
105 protocol_version: ProtocolVersionId,
106 genesis: &Genesis,
107 timestamp: Option<u64>,
108) -> (Block<TransactionVariant>, L1BatchHeader) {
109 let hash = L2BlockHasher::legacy_hash(L2BlockNumber(0));
110 let timestamp = timestamp
111 .or(genesis.timestamp)
112 .unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP);
113
114 let l1_batch_env = genesis.l1_batch_env.clone().unwrap_or_else(|| L1BatchEnv {
115 previous_batch_hash: None,
116 number: L1BatchNumber(0),
117 timestamp,
118 fee_input: BatchFeeInput::pubdata_independent(0, 0, 0),
119 fee_account: Address::zero(),
120 enforced_base_fee: None,
121 first_l2_block: L2BlockEnv {
122 number: 0,
123 timestamp,
124 prev_block_hash: H256::zero(),
125 max_virtual_blocks_to_create: 0,
126 },
127 });
128
129 let genesis_block = create_block(
130 &l1_batch_env,
131 hash,
132 genesis.parent_hash.unwrap_or_else(H256::zero),
133 genesis.block_number.unwrap_or(0),
134 timestamp,
135 genesis.transactions.clone().unwrap_or_default(),
136 genesis.gas_used.unwrap_or_else(U256::zero),
137 genesis.logs_bloom.unwrap_or_else(Bloom::zero),
138 );
139 let genesis_batch_header = L1BatchHeader::new(
140 L1BatchNumber(0),
141 timestamp,
142 BaseSystemContractsHashes::default(),
143 protocol_version,
144 );
145
146 (genesis_block, genesis_batch_header)
147}
148
149pub fn create_genesis<TX>(
150 protocol_version: ProtocolVersionId,
151 timestamp: Option<u64>,
152) -> (Block<TX>, L1BatchHeader) {
153 let hash = L2BlockHasher::legacy_hash(L2BlockNumber(0));
154 let timestamp = timestamp.unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP);
155 let batch_env = L1BatchEnv {
156 previous_batch_hash: None,
157 number: L1BatchNumber(0),
158 timestamp,
159 fee_input: BatchFeeInput::pubdata_independent(0, 0, 0),
160 fee_account: Default::default(),
161 enforced_base_fee: None,
162 first_l2_block: L2BlockEnv {
163 number: 0,
164 timestamp,
165 prev_block_hash: Default::default(),
166 max_virtual_blocks_to_create: 0,
167 },
168 };
169 let genesis_block = create_block(
170 &batch_env,
171 hash,
172 H256::zero(),
173 0,
174 timestamp,
175 vec![],
176 U256::zero(),
177 Bloom::zero(),
178 );
179 let genesis_batch_header = L1BatchHeader::new(
180 L1BatchNumber(0),
181 timestamp,
182 BaseSystemContractsHashes::default(),
183 protocol_version,
184 );
185
186 (genesis_block, genesis_batch_header)
187}
188
189#[allow(clippy::too_many_arguments)]
190pub fn create_block<TX>(
191 batch_env: &L1BatchEnv,
192 hash: H256,
193 parent_hash: H256,
194 number: u64,
195 timestamp: u64,
196 transactions: Vec<TX>,
197 gas_used: U256,
198 logs_bloom: Bloom,
199) -> Block<TX> {
200 Block {
201 hash,
202 parent_hash,
203 uncles_hash: EMPTY_UNCLES_HASH, number: U64::from(number),
205 l1_batch_number: Some(U64::from(batch_env.number.0)),
206 base_fee_per_gas: U256::from(get_batch_base_fee(batch_env, VmVersion::latest())),
207 timestamp: U256::from(timestamp),
208 l1_batch_timestamp: Some(U256::from(batch_env.timestamp)),
209 transactions,
210 gas_used,
211 gas_limit: U256::from(get_max_batch_gas_limit(VmVersion::latest())),
212 logs_bloom,
213 author: Address::default(), state_root: H256::default(), transactions_root: H256::default(), receipts_root: H256::default(), extra_data: Bytes::default(), difficulty: U256::default(), total_difficulty: U256::default(), seal_fields: vec![], uncles: vec![], size: U256::default(), mix_hash: H256::default(), nonce: H64::default(), }
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct TxExecutionInfo {
231 pub tx: Transaction,
232 pub batch_number: u32,
234 pub miniblock_number: u64,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct TransactionResult {
239 pub info: TxExecutionInfo,
240 pub new_bytecodes: Vec<(H256, Vec<u8>)>,
241 pub receipt: TransactionReceipt,
242 pub debug: DebugCall,
243}
244
245impl TransactionResult {
246 pub fn debug_info(&self, only_top: bool) -> DebugCall {
249 let calls = if only_top {
250 vec![]
251 } else {
252 self.debug.calls.clone()
253 };
254 DebugCall {
255 calls,
256 ..self.debug.clone()
257 }
258 }
259}
260
261#[derive(Debug, Clone, Default)]
264pub struct Snapshot {
265 pub(crate) current_batch: L1BatchNumber,
266 pub(crate) current_block: L2BlockNumber,
267 pub(crate) current_block_hash: H256,
268 pub(crate) fee_input_provider: TestNodeFeeInputProvider,
271 pub(crate) tx_results: HashMap<H256, TransactionResult>,
272 pub(crate) blocks: HashMap<H256, Block<TransactionVariant>>,
273 pub(crate) hashes: HashMap<L2BlockNumber, H256>,
274 pub(crate) filters: EthFilters,
275 pub(crate) impersonation_state: ImpersonationState,
276 pub(crate) rich_accounts: HashSet<H160>,
277 pub(crate) previous_states: IndexMap<H256, HashMap<StorageKey, StorageValue>>,
278 pub(crate) raw_storage: InMemoryStorage,
279 pub(crate) value_read_cache: HashMap<StorageKey, H256>,
280 pub(crate) factory_dep_cache: HashMap<H256, Option<Vec<u8>>>,
281}
282
283#[derive(Clone)]
287pub struct InMemoryNode {
288 pub(crate) inner: Arc<RwLock<InMemoryNodeInner>>,
290 pub(crate) blockchain: Box<dyn ReadBlockchain>,
291 pub(crate) storage: Box<dyn ReadStorageDyn>,
292 pub(crate) fork: Box<dyn ForkSource>,
293 pub node_handle: NodeExecutorHandle,
294 pub(crate) snapshots: Arc<RwLock<Vec<Snapshot>>>,
296 pub(crate) time: Box<dyn ReadTime>,
297 pub(crate) impersonation: ImpersonationManager,
298 pub(crate) observability: Option<Observability>,
300 pub(crate) pool: TxPool,
301 pub(crate) sealer_state: BlockSealerState,
302 pub(crate) system_contracts: SystemContracts,
303 pub(crate) storage_key_layout: StorageKeyLayout,
304}
305
306impl InMemoryNode {
307 #[allow(clippy::too_many_arguments)]
308 pub fn new(
309 inner: Arc<RwLock<InMemoryNodeInner>>,
310 blockchain: Box<dyn ReadBlockchain>,
311 storage: Box<dyn ReadStorageDyn>,
312 fork: Box<dyn ForkSource>,
313 node_handle: NodeExecutorHandle,
314 observability: Option<Observability>,
315 time: Box<dyn ReadTime>,
316 impersonation: ImpersonationManager,
317 pool: TxPool,
318 sealer_state: BlockSealerState,
319 system_contracts: SystemContracts,
320 storage_key_layout: StorageKeyLayout,
321 ) -> Self {
322 InMemoryNode {
323 inner,
324 blockchain,
325 storage,
326 fork,
327 node_handle,
328 snapshots: Default::default(),
329 time,
330 impersonation,
331 observability,
332 pool,
333 sealer_state,
334 system_contracts,
335 storage_key_layout,
336 }
337 }
338
339 pub async fn replay_txs(&self, txs: Vec<Transaction>) -> AnvilNodeResult<()> {
342 let tx_batch = TxBatch {
343 impersonating: false,
344 txs,
345 };
346 let expected_tx_hashes = tx_batch
347 .txs
348 .iter()
349 .map(|tx| tx.hash())
350 .collect::<HashSet<_>>();
351 let block_number = self.node_handle.seal_block_sync(tx_batch).await?;
352 let block = self
354 .blockchain
355 .get_block_by_number(block_number)
356 .await
357 .expect("freshly sealed block could not be found in storage");
358
359 let actual_tx_hashes = block
361 .transactions
362 .iter()
363 .map(|tx| match tx {
364 TransactionVariant::Full(tx) => tx.hash,
365 TransactionVariant::Hash(tx_hash) => *tx_hash,
366 })
367 .collect::<HashSet<_>>();
368
369 let diff_tx_hashes = expected_tx_hashes
373 .difference(&actual_tx_hashes)
374 .collect::<Vec<_>>();
375 if !diff_tx_hashes.is_empty() {
376 return Err(generic_error!(
377 "Failed to replay transactions: {diff_tx_hashes:?}. Please report this."
378 ));
379 }
380
381 Ok(())
382 }
383
384 pub async fn set_rich_account(&self, address: H160, balance: U256) {
386 self.inner.write().await.set_rich_account(address, balance)
387 }
388
389 pub async fn run_l2_call(
391 &self,
392 mut l2_tx: L2Tx,
393 base_contracts: BaseSystemContracts,
394 state_override: Option<StateOverride>,
395 ) -> AnvilNodeResult<ExecutionResult> {
396 let execution_mode = TxExecutionMode::EthCall;
397
398 let inner = self.inner.read().await;
399
400 let (batch_env, _) = inner.create_l1_batch_env().await;
403 let system_env = inner.create_system_env(base_contracts, execution_mode);
404
405 let storage_override = if let Some(state_override) = state_override {
406 apply_state_override(inner.read_storage(), state_override)
407 } else {
408 StorageWithOverrides::new(inner.read_storage())
410 };
411
412 let storage = StorageView::new(storage_override).to_rc_ptr();
413
414 let mut vm = if self.system_contracts.boojum.use_boojum {
415 AnvilVM::BoojumOs(super::boojumos::BoojumOsVM::<_, HistoryDisabled>::new(
416 batch_env,
417 system_env,
418 storage,
419 &inner.fork_storage.inner.read().unwrap().raw_storage,
421 &self.system_contracts.boojum,
422 ))
423 } else {
424 AnvilVM::ZKSync(Vm::new(batch_env, system_env, storage))
425 };
426
427 if l2_tx.common_data.signature.is_empty() {
429 l2_tx.common_data.signature = PackedEthSignature::default().serialize_packed().into();
430 }
431
432 let tx: Transaction = l2_tx.into();
433 delegate_vm!(vm, push_transaction(tx.clone()));
434
435 let call_tracer_result = Arc::new(OnceCell::default());
436 let error_flags_result = Arc::new(OnceCell::new());
437
438 let tracers = vec![
439 CallErrorTracer::new(error_flags_result.clone()).into_tracer_pointer(),
440 CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(),
441 ];
442 let tx_result = delegate_vm!(
443 vm,
444 inspect(&mut tracers.into(), InspectExecutionMode::OneTx)
445 );
446
447 let call_traces = Arc::try_unwrap(call_tracer_result)
448 .unwrap()
449 .take()
450 .unwrap_or_default();
451
452 let verbosity = get_shell().verbosity;
453 if !call_traces.is_empty() && verbosity >= 2 {
454 let tx_result_for_arena = tx_result.clone();
455 let mut builder = CallTraceDecoderBuilder::default();
456 builder = builder.with_signature_identifier(SignaturesIdentifier::global());
457
458 let decoder = builder.build();
459 let arena: CallTraceArena = futures::executor::block_on(async {
460 let blocking_result = tokio::task::spawn_blocking(move || {
461 let mut arena = build_call_trace_arena(&call_traces, &tx_result_for_arena);
462 let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
463 rt.block_on(async {
464 decode_trace_arena(&mut arena, &decoder).await;
465 Ok(arena)
466 })
467 })
468 .await;
469
470 let inner_result: Result<CallTraceArena, AnvilNodeError> =
471 blocking_result.expect("spawn_blocking failed");
472 inner_result
473 })?;
474
475 let filtered_arena = filter_call_trace_arena(&arena, verbosity);
476 let trace_output = render_trace_arena_inner(&filtered_arena, false);
477 sh_println!("\nTraces:\n{}", trace_output);
478 }
479
480 Ok(tx_result.result)
481 }
482
483 pub async fn override_bytecode(
485 &self,
486 address: Address,
487 bytecode: Vec<u8>,
488 ) -> AnvilNodeResult<()> {
489 self.node_handle.set_code_sync(address, bytecode).await
490 }
491
492 pub async fn dump_state(&self, preserve_historical_states: bool) -> AnvilNodeResult<Bytes> {
493 let state = self
494 .inner
495 .read()
496 .await
497 .dump_state(preserve_historical_states)
498 .await?;
499 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
500 encoder
501 .write_all(&serde_json::to_vec(&state).map_err(to_generic)?)
502 .map_err(to_generic)?;
503 Ok(encoder.finish().map_err(to_generic)?.into())
504 }
505
506 pub async fn load_state(&self, buf: Bytes) -> StateLoaderResult<bool> {
507 let orig_buf = &buf.0[..];
508 let mut decoder = GzDecoder::new(orig_buf);
509 let mut decoded_data = Vec::new();
510
511 let decoded = if decoder.header().is_some() {
513 tracing::trace!(bytes = buf.0.len(), "decompressing state");
514 decoder.read_to_end(decoded_data.as_mut()).map_err(|e| {
515 StateLoaderError::StateDecompression {
516 details: e.to_string(),
517 }
518 })?;
519 &decoded_data
520 } else {
521 &buf.0
522 };
523 tracing::trace!(bytes = decoded.len(), "deserializing state");
524 let state: VersionedState = serde_json::from_slice(decoded).map_err(|e| {
525 StateLoaderError::StateDeserialization {
526 details: e.to_string(),
527 }
528 })?;
529
530 self.inner.write().await.load_state(state).await
531 }
532
533 pub async fn get_chain_id(&self) -> AnvilNodeResult<u32> {
534 Ok(self
535 .inner
536 .read()
537 .await
538 .config
539 .chain_id
540 .unwrap_or(TEST_NODE_NETWORK_ID))
541 }
542
543 pub fn get_current_timestamp(&self) -> AnvilNodeResult<u64> {
544 Ok(self.time.current_timestamp())
545 }
546
547 pub async fn set_show_storage_logs(
548 &self,
549 show_storage_logs: ShowStorageLogs,
550 ) -> AnvilNodeResult<String> {
551 self.inner.write().await.config.show_storage_logs = show_storage_logs;
552 Ok(show_storage_logs.to_string())
553 }
554
555 pub async fn set_show_vm_details(
556 &self,
557 show_vm_details: ShowVMDetails,
558 ) -> AnvilNodeResult<String> {
559 self.inner.write().await.config.show_vm_details = show_vm_details;
560 Ok(show_vm_details.to_string())
561 }
562
563 pub async fn set_show_gas_details(
564 &self,
565 show_gas_details: ShowGasDetails,
566 ) -> AnvilNodeResult<String> {
567 self.inner.write().await.config.show_gas_details = show_gas_details;
568 Ok(show_gas_details.to_string())
569 }
570
571 pub async fn set_show_node_config(&self, value: bool) -> AnvilNodeResult<bool> {
572 self.inner.write().await.config.show_node_config = value;
573 Ok(value)
574 }
575
576 pub fn set_log_level(&self, level: LogLevel) -> AnvilNodeResult<bool> {
577 let Some(observability) = &self.observability else {
578 return Err(generic_error!("Node's logging is not set up."));
579 };
580 tracing::debug!("setting log level to '{}'", level);
581 observability.set_log_level(level)?;
582 Ok(true)
583 }
584
585 pub fn set_logging(&self, directive: String) -> AnvilNodeResult<bool> {
586 let Some(observability) = &self.observability else {
587 return Err(generic_error!("Node's logging is not set up."));
588 };
589 tracing::debug!("setting logging to '{}'", directive);
590 observability.set_logging(directive)?;
591 Ok(true)
592 }
593
594 pub async fn chain_id(&self) -> L2ChainId {
595 self.inner.read().await.chain_id()
596 }
597}
598
599pub fn load_last_l1_batch<S: ReadStorage>(storage: StoragePtr<S>) -> Option<(u64, u64)> {
600 let current_l1_batch_info_key = StorageKey::new(
602 AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS),
603 SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
604 );
605 let mut storage_ptr = storage.borrow_mut();
606 let current_l1_batch_info = storage_ptr.read_value(¤t_l1_batch_info_key);
607 let (batch_number, batch_timestamp) = unpack_block_info(h256_to_u256(current_l1_batch_info));
608 let block_number = batch_number as u32;
609 if block_number == 0 {
610 return None;
612 }
613 Some((batch_number, batch_timestamp))
614}
615
616impl InMemoryNode {
621 pub fn test_config(fork_client_opt: Option<ForkClient>, config: TestNodeConfig) -> Self {
622 let fee_provider = TestNodeFeeInputProvider::from_fork(
623 fork_client_opt.as_ref().map(|client| &client.details),
624 &config.base_token_config,
625 );
626 let impersonation = ImpersonationManager::default();
627 let system_contracts = SystemContracts::from_options(
628 config.system_contracts_options,
629 config.system_contracts_path.clone(),
630 ProtocolVersionId::latest(),
631 config.use_evm_interpreter,
632 config.boojum.clone(),
633 );
634 let storage_key_layout = if config.boojum.use_boojum {
635 StorageKeyLayout::BoojumOs
636 } else {
637 StorageKeyLayout::ZkEra
638 };
639 let (inner, storage, blockchain, time, fork, vm_runner) = InMemoryNodeInner::init(
640 fork_client_opt,
641 fee_provider,
642 Arc::new(RwLock::new(Default::default())),
643 config,
644 impersonation.clone(),
645 system_contracts.clone(),
646 storage_key_layout,
647 false,
648 );
649 let (node_executor, node_handle) =
650 NodeExecutor::new(inner.clone(), vm_runner, storage_key_layout);
651 let pool = TxPool::new(
652 impersonation.clone(),
653 anvil_zksync_types::TransactionOrder::Fifo,
654 );
655 let tx_listener = pool.add_tx_listener();
656 let (block_sealer, block_sealer_state) = BlockSealer::new(
657 BlockSealerMode::immediate(1000, tx_listener),
658 pool.clone(),
659 node_handle.clone(),
660 );
661 tokio::spawn(node_executor.run());
662 tokio::spawn(block_sealer.run());
663 Self::new(
664 inner,
665 blockchain,
666 storage,
667 fork,
668 node_handle,
669 None,
670 time,
671 impersonation,
672 pool,
673 block_sealer_state,
674 system_contracts,
675 storage_key_layout,
676 )
677 }
678
679 pub fn test(fork_client_opt: Option<ForkClient>) -> Self {
680 let config = TestNodeConfig {
681 cache_config: CacheConfig::None,
682 ..Default::default()
683 };
684 Self::test_config(fork_client_opt, config)
685 }
686}
687
688#[cfg(test)]
689impl InMemoryNode {
690 pub async fn apply_txs(
691 &self,
692 txs: impl IntoIterator<Item = Transaction>,
693 ) -> AnvilNodeResult<Vec<TransactionReceipt>> {
694 use backon::{ConstantBuilder, Retryable};
695 use std::time::Duration;
696
697 let txs = Vec::from_iter(txs);
698 let expected_tx_hashes = txs.iter().map(|tx| tx.hash()).collect::<Vec<_>>();
699 self.pool.add_txs(txs);
700
701 let mut receipts = Vec::with_capacity(expected_tx_hashes.len());
702 for tx_hash in expected_tx_hashes {
703 let receipt = (|| async {
704 self.blockchain
705 .get_tx_receipt(&tx_hash)
706 .await
707 .ok_or(generic_error!("missing tx receipt"))
708 })
709 .retry(
710 ConstantBuilder::default()
711 .with_delay(Duration::from_millis(200))
712 .with_max_times(5),
713 )
714 .await?;
715 receipts.push(receipt);
716 }
717 Ok(receipts)
718 }
719}