1use std::time::Duration;
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5
6use crate::abi::EventSelector;
7use crate::error::Error;
8use crate::rpc::RpcTransport;
9use crate::tx::{TxHash, TxReceipt, TxStatus};
10use crate::types::{AztecAddress, ContractInstanceWithAddress, Fr};
11
12fn block_param_json(block_number: u64) -> serde_json::Value {
13 if block_number == 0 {
14 serde_json::Value::String("latest".to_owned())
15 } else {
16 serde_json::json!(block_number)
17 }
18}
19
20fn block_hash_param_json(block_hash: &Fr) -> serde_json::Value {
21 serde_json::Value::String(block_hash.to_string())
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct NodeInfo {
32 pub node_version: String,
34 pub l1_chain_id: u64,
36 pub rollup_version: u64,
38 #[serde(default)]
40 pub enr: Option<String>,
41 #[serde(default)]
43 pub l1_contract_addresses: serde_json::Value,
44 #[serde(default)]
46 pub protocol_contract_addresses: serde_json::Value,
47 pub real_proofs: bool,
49 #[serde(default)]
51 pub l2_circuits_vk_tree_root: Option<String>,
52 #[serde(default)]
54 pub l2_protocol_contracts_hash: Option<String>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct LogId {
61 pub block_number: u64,
63 pub log_index: u64,
65}
66
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct PublicLogFilter {
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub tx_hash: Option<TxHash>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub from_block: Option<u64>,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub to_block: Option<u64>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub contract_address: Option<AztecAddress>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub selector: Option<EventSelector>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub after_log: Option<LogId>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct PublicLogId {
95 pub block_number: u64,
97 #[serde(default)]
99 pub block_hash: Option<String>,
100 #[serde(default)]
102 pub tx_hash: Option<TxHash>,
103 #[serde(default)]
105 pub tx_index: Option<u64>,
106 pub log_index: u64,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct PublicLogBody {
114 pub contract_address: AztecAddress,
116 pub fields: Vec<Fr>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct PublicLogEntry {
126 pub id: PublicLogId,
128 pub log: PublicLogBody,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct PublicLog {
136 pub contract_address: AztecAddress,
138 pub data: Vec<Fr>,
140 #[serde(default)]
142 pub tx_hash: Option<TxHash>,
143 pub block_number: u64,
145 pub log_index: u64,
147}
148
149impl From<PublicLogEntry> for PublicLog {
150 fn from(entry: PublicLogEntry) -> Self {
151 Self {
152 contract_address: entry.log.contract_address,
153 data: entry.log.fields,
154 tx_hash: entry.id.tx_hash,
155 block_number: entry.id.block_number,
156 log_index: entry.id.log_index,
157 }
158 }
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164struct RawPublicLogsResponse {
165 logs: Vec<PublicLogEntry>,
166 max_logs_hit: bool,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct PublicLogsResponse {
173 pub logs: Vec<PublicLog>,
175 pub max_logs_hit: bool,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181#[serde(tag = "result", rename_all = "lowercase")]
182pub enum TxValidationResult {
183 Valid,
185 Invalid { reason: Vec<String> },
187 Skipped { reason: Vec<String> },
189}
190
191#[derive(Debug, Clone)]
193pub struct WaitOpts {
194 pub timeout: Duration,
196 pub interval: Duration,
198 pub wait_for_status: TxStatus,
200 pub dont_throw_on_revert: bool,
202 pub ignore_dropped_receipts_for: Duration,
205}
206
207impl Default for WaitOpts {
208 fn default() -> Self {
209 Self {
210 timeout: Duration::from_secs(300),
211 interval: Duration::from_secs(1),
212 wait_for_status: TxStatus::Checkpointed,
213 dont_throw_on_revert: false,
214 ignore_dropped_receipts_for: Duration::from_secs(5),
215 }
216 }
217}
218
219#[async_trait]
225pub trait AztecNode: Send + Sync {
226 async fn get_node_info(&self) -> Result<NodeInfo, Error>;
228 async fn get_block_number(&self) -> Result<u64, Error>;
230 async fn get_proven_block_number(&self) -> Result<u64, Error>;
232 async fn get_tx_receipt(&self, tx_hash: &TxHash) -> Result<TxReceipt, Error>;
234 async fn get_public_logs(&self, filter: PublicLogFilter) -> Result<PublicLogsResponse, Error>;
236 async fn send_tx(&self, tx: &serde_json::Value) -> Result<(), Error>;
238 async fn get_contract(
240 &self,
241 address: &AztecAddress,
242 ) -> Result<Option<ContractInstanceWithAddress>, Error>;
243 async fn get_contract_class(&self, id: &Fr) -> Result<Option<serde_json::Value>, Error>;
245
246 async fn get_block_header(&self, block_number: u64) -> Result<serde_json::Value, Error>;
250
251 async fn get_block(&self, block_number: u64) -> Result<Option<serde_json::Value>, Error>;
253
254 async fn get_note_hash_membership_witness(
256 &self,
257 block_number: u64,
258 note_hash: &Fr,
259 ) -> Result<Option<serde_json::Value>, Error>;
260
261 async fn get_nullifier_membership_witness(
263 &self,
264 block_number: u64,
265 nullifier: &Fr,
266 ) -> Result<Option<serde_json::Value>, Error>;
267
268 async fn get_low_nullifier_membership_witness(
270 &self,
271 block_number: u64,
272 nullifier: &Fr,
273 ) -> Result<Option<serde_json::Value>, Error>;
274
275 async fn get_public_storage_at(
277 &self,
278 block_number: u64,
279 contract: &AztecAddress,
280 slot: &Fr,
281 ) -> Result<Fr, Error>;
282
283 async fn get_public_storage_at_by_hash(
285 &self,
286 block_hash: &Fr,
287 contract: &AztecAddress,
288 slot: &Fr,
289 ) -> Result<Fr, Error> {
290 let _ = block_hash;
291 self.get_public_storage_at(0, contract, slot).await
292 }
293
294 async fn get_public_data_witness(
296 &self,
297 block_number: u64,
298 leaf_slot: &Fr,
299 ) -> Result<Option<serde_json::Value>, Error>;
300
301 async fn get_public_data_witness_by_hash(
303 &self,
304 block_hash: &Fr,
305 leaf_slot: &Fr,
306 ) -> Result<Option<serde_json::Value>, Error> {
307 let _ = block_hash;
308 self.get_public_data_witness(0, leaf_slot).await
309 }
310
311 async fn get_l1_to_l2_message_membership_witness(
313 &self,
314 block_number: u64,
315 entry_key: &Fr,
316 ) -> Result<Option<serde_json::Value>, Error>;
317
318 async fn get_l1_to_l2_message_checkpoint(
322 &self,
323 l1_to_l2_message: &Fr,
324 ) -> Result<Option<u64>, Error> {
325 let _ = l1_to_l2_message;
327 Ok(None)
328 }
329
330 async fn simulate_public_calls(
332 &self,
333 tx: &serde_json::Value,
334 skip_fee_enforcement: bool,
335 ) -> Result<serde_json::Value, Error>;
336
337 async fn is_valid_tx(&self, tx: &serde_json::Value) -> Result<TxValidationResult, Error>;
339
340 async fn get_private_logs_by_tags(&self, tags: &[Fr]) -> Result<serde_json::Value, Error>;
342
343 async fn get_public_logs_by_tags_from_contract(
345 &self,
346 contract: &AztecAddress,
347 tags: &[Fr],
348 ) -> Result<serde_json::Value, Error>;
349
350 async fn register_contract_function_signatures(
352 &self,
353 signatures: &[String],
354 ) -> Result<(), Error>;
355
356 async fn get_tx_effect(&self, tx_hash: &TxHash) -> Result<Option<serde_json::Value>, Error>;
361
362 async fn get_block_hash_membership_witness(
366 &self,
367 block_number: u64,
368 block_hash: &Fr,
369 ) -> Result<Option<serde_json::Value>, Error>;
370
371 async fn find_leaves_indexes(
373 &self,
374 block_number: u64,
375 tree_id: &str,
376 leaves: &[Fr],
377 ) -> Result<Vec<Option<u64>>, Error>;
378
379 async fn get_tx_by_hash(&self, tx_hash: &TxHash) -> Result<Option<serde_json::Value>, Error>;
384}
385
386pub struct HttpNodeClient {
392 transport: RpcTransport,
393}
394
395impl HttpNodeClient {
396 fn new(url: String, timeout: Duration) -> Self {
397 Self {
398 transport: RpcTransport::new(url, timeout),
399 }
400 }
401}
402
403impl Clone for HttpNodeClient {
404 fn clone(&self) -> Self {
405 Self::new(self.transport.url().to_owned(), self.transport.timeout())
406 }
407}
408
409#[async_trait]
410impl AztecNode for HttpNodeClient {
411 async fn get_node_info(&self) -> Result<NodeInfo, Error> {
412 self.transport
413 .call("node_getNodeInfo", serde_json::json!([]))
414 .await
415 }
416
417 async fn get_block_number(&self) -> Result<u64, Error> {
418 self.transport
419 .call("node_getBlockNumber", serde_json::json!([]))
420 .await
421 }
422
423 async fn get_proven_block_number(&self) -> Result<u64, Error> {
424 self.transport
425 .call("node_getProvenBlockNumber", serde_json::json!([]))
426 .await
427 }
428
429 async fn get_tx_receipt(&self, tx_hash: &TxHash) -> Result<TxReceipt, Error> {
430 self.transport
431 .call("node_getTxReceipt", serde_json::json!([tx_hash]))
432 .await
433 }
434
435 async fn get_public_logs(&self, filter: PublicLogFilter) -> Result<PublicLogsResponse, Error> {
436 let raw: RawPublicLogsResponse = self
437 .transport
438 .call("node_getPublicLogs", serde_json::json!([filter]))
439 .await?;
440 Ok(PublicLogsResponse {
441 logs: raw.logs.into_iter().map(PublicLog::from).collect(),
442 max_logs_hit: raw.max_logs_hit,
443 })
444 }
445
446 async fn send_tx(&self, tx: &serde_json::Value) -> Result<(), Error> {
447 self.transport
448 .call_void("node_sendTx", serde_json::json!([tx]))
449 .await
450 }
451
452 async fn get_contract(
453 &self,
454 address: &AztecAddress,
455 ) -> Result<Option<ContractInstanceWithAddress>, Error> {
456 self.transport
457 .call_optional("node_getContract", serde_json::json!([address]))
458 .await
459 }
460
461 async fn get_contract_class(&self, id: &Fr) -> Result<Option<serde_json::Value>, Error> {
462 self.transport
463 .call_optional("node_getContractClass", serde_json::json!([id]))
464 .await
465 }
466
467 async fn get_block_header(&self, block_number: u64) -> Result<serde_json::Value, Error> {
468 self.transport
469 .call(
470 "node_getBlockHeader",
471 serde_json::json!([block_param_json(block_number)]),
472 )
473 .await
474 }
475
476 async fn get_block(&self, block_number: u64) -> Result<Option<serde_json::Value>, Error> {
477 self.transport
478 .call_optional(
479 "node_getBlock",
480 serde_json::json!([block_param_json(block_number)]),
481 )
482 .await
483 }
484
485 async fn get_note_hash_membership_witness(
486 &self,
487 block_number: u64,
488 note_hash: &Fr,
489 ) -> Result<Option<serde_json::Value>, Error> {
490 self.transport
491 .call_optional(
492 "node_getNoteHashMembershipWitness",
493 serde_json::json!([block_param_json(block_number), note_hash]),
494 )
495 .await
496 }
497
498 async fn get_nullifier_membership_witness(
499 &self,
500 block_number: u64,
501 nullifier: &Fr,
502 ) -> Result<Option<serde_json::Value>, Error> {
503 self.transport
504 .call_optional(
505 "node_getNullifierMembershipWitness",
506 serde_json::json!([block_param_json(block_number), nullifier]),
507 )
508 .await
509 }
510
511 async fn get_low_nullifier_membership_witness(
512 &self,
513 block_number: u64,
514 nullifier: &Fr,
515 ) -> Result<Option<serde_json::Value>, Error> {
516 self.transport
517 .call_optional(
518 "node_getLowNullifierMembershipWitness",
519 serde_json::json!([block_param_json(block_number), nullifier]),
520 )
521 .await
522 }
523
524 async fn get_public_storage_at(
525 &self,
526 block_number: u64,
527 contract: &AztecAddress,
528 slot: &Fr,
529 ) -> Result<Fr, Error> {
530 self.transport
531 .call(
532 "node_getPublicStorageAt",
533 serde_json::json!([block_param_json(block_number), contract, slot]),
534 )
535 .await
536 }
537
538 async fn get_public_storage_at_by_hash(
539 &self,
540 block_hash: &Fr,
541 contract: &AztecAddress,
542 slot: &Fr,
543 ) -> Result<Fr, Error> {
544 self.transport
545 .call(
546 "node_getPublicStorageAt",
547 serde_json::json!([block_hash_param_json(block_hash), contract, slot]),
548 )
549 .await
550 }
551
552 async fn get_public_data_witness(
553 &self,
554 block_number: u64,
555 leaf_slot: &Fr,
556 ) -> Result<Option<serde_json::Value>, Error> {
557 self.transport
558 .call_optional(
559 "node_getPublicDataWitness",
560 serde_json::json!([block_param_json(block_number), leaf_slot]),
561 )
562 .await
563 }
564
565 async fn get_public_data_witness_by_hash(
566 &self,
567 block_hash: &Fr,
568 leaf_slot: &Fr,
569 ) -> Result<Option<serde_json::Value>, Error> {
570 self.transport
571 .call_optional(
572 "node_getPublicDataWitness",
573 serde_json::json!([block_hash_param_json(block_hash), leaf_slot]),
574 )
575 .await
576 }
577
578 async fn get_l1_to_l2_message_membership_witness(
579 &self,
580 block_number: u64,
581 entry_key: &Fr,
582 ) -> Result<Option<serde_json::Value>, Error> {
583 self.transport
584 .call_optional(
585 "node_getL1ToL2MessageMembershipWitness",
586 serde_json::json!([block_param_json(block_number), entry_key]),
587 )
588 .await
589 }
590
591 async fn get_l1_to_l2_message_checkpoint(
592 &self,
593 l1_to_l2_message: &Fr,
594 ) -> Result<Option<u64>, Error> {
595 let result: Option<serde_json::Value> = self
596 .transport
597 .call_optional(
598 "node_getL1ToL2MessageCheckpoint",
599 serde_json::json!([l1_to_l2_message]),
600 )
601 .await?;
602 Ok(result.and_then(|v| v.as_u64()))
603 }
604
605 async fn simulate_public_calls(
606 &self,
607 tx: &serde_json::Value,
608 skip_fee_enforcement: bool,
609 ) -> Result<serde_json::Value, Error> {
610 self.transport
611 .call(
612 "node_simulatePublicCalls",
613 serde_json::json!([tx, skip_fee_enforcement]),
614 )
615 .await
616 }
617
618 async fn is_valid_tx(&self, tx: &serde_json::Value) -> Result<TxValidationResult, Error> {
619 self.transport
620 .call("node_isValidTx", serde_json::json!([tx]))
621 .await
622 }
623
624 async fn get_private_logs_by_tags(&self, tags: &[Fr]) -> Result<serde_json::Value, Error> {
625 self.transport
626 .call("node_getPrivateLogsByTags", serde_json::json!([tags]))
627 .await
628 }
629
630 async fn get_public_logs_by_tags_from_contract(
631 &self,
632 contract: &AztecAddress,
633 tags: &[Fr],
634 ) -> Result<serde_json::Value, Error> {
635 self.transport
636 .call(
637 "node_getPublicLogsByTagsFromContract",
638 serde_json::json!([contract, tags]),
639 )
640 .await
641 }
642
643 async fn register_contract_function_signatures(
644 &self,
645 signatures: &[String],
646 ) -> Result<(), Error> {
647 self.transport
648 .call_void(
649 "node_registerContractFunctionSignatures",
650 serde_json::json!([signatures]),
651 )
652 .await
653 }
654
655 async fn get_tx_effect(&self, tx_hash: &TxHash) -> Result<Option<serde_json::Value>, Error> {
656 self.transport
657 .call_optional("node_getTxEffect", serde_json::json!([tx_hash]))
658 .await
659 }
660
661 async fn get_block_hash_membership_witness(
662 &self,
663 block_number: u64,
664 block_hash: &Fr,
665 ) -> Result<Option<serde_json::Value>, Error> {
666 self.transport
667 .call_optional(
668 "node_getBlockHashMembershipWitness",
669 serde_json::json!([block_param_json(block_number), block_hash]),
670 )
671 .await
672 }
673
674 async fn find_leaves_indexes(
675 &self,
676 block_number: u64,
677 tree_id: &str,
678 leaves: &[Fr],
679 ) -> Result<Vec<Option<u64>>, Error> {
680 let raw: Vec<Option<serde_json::Value>> = self
683 .transport
684 .call(
685 "node_findLeavesIndexes",
686 serde_json::json!([
687 block_param_json(block_number),
688 tree_id.parse::<u8>().unwrap_or(0),
689 leaves
690 ]),
691 )
692 .await?;
693 Ok(raw
694 .into_iter()
695 .map(|v| {
696 v.and_then(|val| {
697 let extract = |v: &serde_json::Value| {
700 v.as_u64()
701 .or_else(|| v.as_str().and_then(|s| s.parse::<u64>().ok()))
702 };
703 val.get("data").and_then(extract).or_else(|| extract(&val))
704 })
705 })
706 .collect())
707 }
708
709 async fn get_tx_by_hash(&self, tx_hash: &TxHash) -> Result<Option<serde_json::Value>, Error> {
710 self.transport
711 .call_optional("node_getTxByHash", serde_json::json!([tx_hash]))
712 .await
713 }
714}
715
716pub fn create_aztec_node_client(url: impl Into<String>) -> HttpNodeClient {
718 HttpNodeClient::new(url.into(), Duration::from_secs(30))
719}
720
721pub async fn wait_for_node(node: &(impl AztecNode + ?Sized)) -> Result<NodeInfo, Error> {
730 wait_for_node_opts(node, Duration::from_secs(120), Duration::from_secs(1)).await
731}
732
733async fn wait_for_node_opts(
734 node: &(impl AztecNode + ?Sized),
735 timeout: Duration,
736 interval: Duration,
737) -> Result<NodeInfo, Error> {
738 let deadline = tokio::time::Instant::now() + timeout;
739 loop {
740 match node.get_node_info().await {
741 Ok(info) => return Ok(info),
742 Err(_) if tokio::time::Instant::now() + interval < deadline => {
743 tokio::time::sleep(interval).await;
744 }
745 Err(e) => {
746 return Err(Error::Timeout(format!(
747 "node not ready after {timeout:?}: {e}"
748 )));
749 }
750 }
751 }
752}
753
754const fn status_reached(status: TxStatus, target: TxStatus) -> bool {
756 status_ordinal(status) >= status_ordinal(target)
757}
758
759const fn status_ordinal(s: TxStatus) -> u8 {
760 match s {
761 TxStatus::Dropped => 0,
762 TxStatus::Pending => 1,
763 TxStatus::Proposed => 2,
764 TxStatus::Checkpointed => 3,
765 TxStatus::Proven => 4,
766 TxStatus::Finalized => 5,
767 }
768}
769
770pub async fn wait_for_tx(
777 node: &(impl AztecNode + ?Sized),
778 tx_hash: &TxHash,
779 opts: WaitOpts,
780) -> Result<TxReceipt, Error> {
781 let start = tokio::time::Instant::now();
782 let deadline = start + opts.timeout;
783 let target = opts.wait_for_status;
784
785 loop {
786 match node.get_tx_receipt(tx_hash).await {
787 Ok(receipt) => {
788 if receipt.is_dropped() {
789 let elapsed = start.elapsed();
790 if elapsed >= opts.ignore_dropped_receipts_for {
791 return Err(Error::Reverted(format!(
792 "tx {tx_hash} was dropped: {}",
793 receipt.error.as_deref().unwrap_or("unknown reason")
794 )));
795 }
796 } else {
798 if receipt.has_execution_reverted() && !opts.dont_throw_on_revert {
799 return Err(Error::Reverted(format!(
800 "tx {tx_hash} execution reverted: {}",
801 receipt.error.as_deref().unwrap_or("unknown reason")
802 )));
803 }
804 if status_reached(receipt.status, target) {
805 return Ok(receipt);
806 }
807 }
808 }
809 Err(e) => {
810 if tokio::time::Instant::now() + opts.interval >= deadline {
811 return Err(Error::Timeout(format!(
812 "timed out waiting for tx {tx_hash}: {e}"
813 )));
814 }
815 }
816 }
817
818 if tokio::time::Instant::now() + opts.interval >= deadline {
819 return Err(Error::Timeout(format!(
820 "tx {tx_hash} did not reach {target:?} within {:?}",
821 opts.timeout
822 )));
823 }
824 tokio::time::sleep(opts.interval).await;
825 }
826}
827
828#[derive(Debug, Clone)]
830pub struct WaitForProvenOpts {
831 pub proven_timeout: Duration,
833 pub interval: Duration,
835}
836
837impl Default for WaitForProvenOpts {
838 fn default() -> Self {
839 Self {
840 proven_timeout: Duration::from_secs(600),
841 interval: Duration::from_secs(1),
842 }
843 }
844}
845
846pub async fn wait_for_proven(
852 node: &(impl AztecNode + ?Sized),
853 receipt: &TxReceipt,
854 opts: WaitForProvenOpts,
855) -> Result<u64, Error> {
856 let receipt_block = receipt.block_number.ok_or_else(|| {
857 Error::InvalidData("receipt has no block_number — cannot wait for proven".into())
858 })?;
859
860 let deadline = tokio::time::Instant::now() + opts.proven_timeout;
861
862 loop {
863 match node.get_proven_block_number().await {
864 Ok(proven_block) if proven_block >= receipt_block => {
865 return Ok(proven_block);
866 }
867 Ok(_) => {}
868 Err(e) => {
869 if tokio::time::Instant::now() + opts.interval >= deadline {
870 return Err(Error::Timeout(format!(
871 "timed out waiting for block {receipt_block} to be proven: {e}"
872 )));
873 }
874 }
875 }
876
877 if tokio::time::Instant::now() + opts.interval >= deadline {
878 return Err(Error::Timeout(format!(
879 "block {receipt_block} was not proven within {:?}",
880 opts.proven_timeout
881 )));
882 }
883 tokio::time::sleep(opts.interval).await;
884 }
885}
886
887#[cfg(test)]
892#[allow(clippy::unwrap_used, clippy::expect_used)]
893mod tests {
894 use super::*;
895 use crate::tx::TxExecutionResult;
896 use std::sync::atomic::{AtomicUsize, Ordering};
897 use std::sync::Mutex;
898
899 #[test]
902 fn node_info_deserializes() {
903 let json = r#"{
904 "nodeVersion": "0.42.0",
905 "l1ChainId": 31337,
906 "rollupVersion": 1,
907 "enr": "enr:-abc123",
908 "l1ContractAddresses": {"rollup": "0x1234"},
909 "protocolContractAddresses": {"classRegisterer": "0xabcd"},
910 "realProofs": false
911 }"#;
912
913 let info: NodeInfo = serde_json::from_str(json).unwrap();
914 assert_eq!(info.node_version, "0.42.0");
915 assert_eq!(info.l1_chain_id, 31337);
916 assert_eq!(info.rollup_version, 1);
917 assert_eq!(info.enr.as_deref(), Some("enr:-abc123"));
918 assert!(!info.real_proofs);
919 }
920
921 #[test]
922 fn node_info_roundtrip() {
923 let info = NodeInfo {
924 node_version: "1.0.0".into(),
925 l1_chain_id: 1,
926 rollup_version: 2,
927 enr: None,
928 l1_contract_addresses: serde_json::json!({}),
929 protocol_contract_addresses: serde_json::json!({}),
930 l2_circuits_vk_tree_root: None,
931 l2_protocol_contracts_hash: None,
932 real_proofs: true,
933 };
934 let json = serde_json::to_string(&info).unwrap();
935 let decoded: NodeInfo = serde_json::from_str(&json).unwrap();
936 assert_eq!(decoded.node_version, "1.0.0");
937 assert_eq!(decoded.l1_chain_id, 1);
938 assert_eq!(decoded.rollup_version, 2);
939 assert!(decoded.real_proofs);
940 }
941
942 #[test]
943 fn node_info_minimal_json() {
944 let json = r#"{
945 "nodeVersion": "0.1.0",
946 "l1ChainId": 1,
947 "rollupVersion": 1,
948 "realProofs": false
949 }"#;
950 let info: NodeInfo = serde_json::from_str(json).unwrap();
951 assert!(info.enr.is_none());
952 assert_eq!(info.rollup_version, 1);
953 }
954
955 #[test]
958 fn public_log_filter_default_serializes_empty() {
959 let filter = PublicLogFilter::default();
960 let json = serde_json::to_value(&filter).unwrap();
961 assert_eq!(json, serde_json::json!({}));
962 }
963
964 #[test]
965 fn public_log_filter_with_fields() {
966 let tx_hash =
967 TxHash::from_hex("0x0000000000000000000000000000000000000000000000000000000000000001")
968 .unwrap();
969 let filter = PublicLogFilter {
970 tx_hash: Some(tx_hash),
971 from_block: Some(10),
972 to_block: Some(20),
973 ..Default::default()
974 };
975 let json = serde_json::to_value(&filter).unwrap();
976 assert_eq!(json["txHash"], tx_hash.to_string());
977 assert_eq!(json["fromBlock"], 10);
978 assert_eq!(json["toBlock"], 20);
979 assert!(json.get("contractAddress").is_none());
980 }
981
982 #[test]
985 fn public_logs_response_roundtrip() {
986 let resp = PublicLogsResponse {
987 logs: vec![],
988 max_logs_hit: false,
989 };
990 let json = serde_json::to_string(&resp).unwrap();
991 let decoded: PublicLogsResponse = serde_json::from_str(&json).unwrap();
992 assert!(!decoded.max_logs_hit);
993 assert!(decoded.logs.is_empty());
994 }
995
996 #[test]
999 fn wait_opts_defaults() {
1000 let opts = WaitOpts::default();
1001 assert_eq!(opts.timeout, Duration::from_secs(300));
1002 assert_eq!(opts.interval, Duration::from_secs(1));
1003 assert_eq!(opts.wait_for_status, TxStatus::Checkpointed);
1004 assert!(!opts.dont_throw_on_revert);
1005 assert_eq!(opts.ignore_dropped_receipts_for, Duration::from_secs(5));
1006 }
1007
1008 #[test]
1011 fn status_ordering() {
1012 assert!(status_reached(TxStatus::Finalized, TxStatus::Checkpointed));
1013 assert!(status_reached(TxStatus::Proven, TxStatus::Checkpointed));
1014 assert!(status_reached(
1015 TxStatus::Checkpointed,
1016 TxStatus::Checkpointed
1017 ));
1018 assert!(!status_reached(TxStatus::Proposed, TxStatus::Checkpointed));
1019 assert!(!status_reached(TxStatus::Pending, TxStatus::Checkpointed));
1020 assert!(!status_reached(TxStatus::Dropped, TxStatus::Checkpointed));
1021
1022 assert!(status_reached(TxStatus::Finalized, TxStatus::Proven));
1023 assert!(status_reached(TxStatus::Proven, TxStatus::Proven));
1024 assert!(!status_reached(TxStatus::Checkpointed, TxStatus::Proven));
1025 }
1026
1027 struct MockNode {
1030 info_result: Mutex<Vec<Result<NodeInfo, Error>>>,
1031 block_number: u64,
1032 proven_block_results: Mutex<Vec<Result<u64, Error>>>,
1033 receipt_results: Mutex<Vec<Result<TxReceipt, Error>>>,
1034 call_count: AtomicUsize,
1035 }
1036
1037 impl MockNode {
1038 fn new_ready(info: NodeInfo) -> Self {
1039 Self {
1040 info_result: Mutex::new(vec![Ok(info)]),
1041 block_number: 0,
1042 proven_block_results: Mutex::new(vec![]),
1043 receipt_results: Mutex::new(vec![]),
1044 call_count: AtomicUsize::new(0),
1045 }
1046 }
1047
1048 fn new_with_info_sequence(results: Vec<Result<NodeInfo, Error>>) -> Self {
1049 Self {
1050 info_result: Mutex::new(results),
1051 block_number: 0,
1052 proven_block_results: Mutex::new(vec![]),
1053 receipt_results: Mutex::new(vec![]),
1054 call_count: AtomicUsize::new(0),
1055 }
1056 }
1057
1058 fn new_with_receipt_sequence(results: Vec<Result<TxReceipt, Error>>) -> Self {
1059 Self {
1060 info_result: Mutex::new(vec![]),
1061 block_number: 0,
1062 proven_block_results: Mutex::new(vec![]),
1063 receipt_results: Mutex::new(results),
1064 call_count: AtomicUsize::new(0),
1065 }
1066 }
1067
1068 fn new_with_proven_block_sequence(results: Vec<Result<u64, Error>>) -> Self {
1069 Self {
1070 info_result: Mutex::new(vec![]),
1071 block_number: 0,
1072 proven_block_results: Mutex::new(results),
1073 receipt_results: Mutex::new(vec![]),
1074 call_count: AtomicUsize::new(0),
1075 }
1076 }
1077
1078 fn sample_info() -> NodeInfo {
1079 NodeInfo {
1080 node_version: "test-0.1.0".into(),
1081 l1_chain_id: 31337,
1082 rollup_version: 1,
1083 enr: None,
1084 l1_contract_addresses: serde_json::json!({}),
1085 protocol_contract_addresses: serde_json::json!({}),
1086 l2_circuits_vk_tree_root: None,
1087 l2_protocol_contracts_hash: None,
1088 real_proofs: false,
1089 }
1090 }
1091
1092 fn make_receipt(status: TxStatus, exec: Option<TxExecutionResult>) -> TxReceipt {
1093 TxReceipt {
1094 tx_hash: TxHash::zero(),
1095 status,
1096 execution_result: exec,
1097 error: None,
1098 transaction_fee: None,
1099 block_hash: None,
1100 block_number: None,
1101 epoch_number: None,
1102 }
1103 }
1104 }
1105
1106 #[async_trait]
1107 impl AztecNode for MockNode {
1108 async fn get_node_info(&self) -> Result<NodeInfo, Error> {
1109 let idx = self.call_count.fetch_add(1, Ordering::Relaxed);
1110 let results = self.info_result.lock().unwrap();
1111 if idx < results.len() {
1112 match &results[idx] {
1113 Ok(info) => Ok(info.clone()),
1114 Err(e) => Err(Error::Transport(e.to_string())),
1115 }
1116 } else if let Some(last) = results.last() {
1117 match last {
1118 Ok(info) => Ok(info.clone()),
1119 Err(e) => Err(Error::Transport(e.to_string())),
1120 }
1121 } else {
1122 Err(Error::Transport("no mock results configured".into()))
1123 }
1124 }
1125
1126 async fn get_block_number(&self) -> Result<u64, Error> {
1127 Ok(self.block_number)
1128 }
1129
1130 async fn get_proven_block_number(&self) -> Result<u64, Error> {
1131 let idx = self.call_count.fetch_add(1, Ordering::Relaxed);
1132 let results = self.proven_block_results.lock().unwrap();
1133 if idx < results.len() {
1134 match &results[idx] {
1135 Ok(n) => Ok(*n),
1136 Err(e) => Err(Error::Transport(e.to_string())),
1137 }
1138 } else if let Some(last) = results.last() {
1139 match last {
1140 Ok(n) => Ok(*n),
1141 Err(e) => Err(Error::Transport(e.to_string())),
1142 }
1143 } else {
1144 Ok(0)
1145 }
1146 }
1147
1148 async fn get_tx_receipt(&self, _tx_hash: &TxHash) -> Result<TxReceipt, Error> {
1149 let idx = self.call_count.fetch_add(1, Ordering::Relaxed);
1150 let results = self.receipt_results.lock().unwrap();
1151 if idx < results.len() {
1152 match &results[idx] {
1153 Ok(r) => Ok(r.clone()),
1154 Err(e) => Err(Error::Transport(e.to_string())),
1155 }
1156 } else if let Some(last) = results.last() {
1157 match last {
1158 Ok(r) => Ok(r.clone()),
1159 Err(e) => Err(Error::Transport(e.to_string())),
1160 }
1161 } else {
1162 Err(Error::Transport("no mock results configured".into()))
1163 }
1164 }
1165
1166 async fn get_public_logs(
1167 &self,
1168 _filter: PublicLogFilter,
1169 ) -> Result<PublicLogsResponse, Error> {
1170 Ok(PublicLogsResponse {
1171 logs: vec![],
1172 max_logs_hit: false,
1173 })
1174 }
1175
1176 async fn send_tx(&self, _tx: &serde_json::Value) -> Result<(), Error> {
1177 Ok(())
1178 }
1179
1180 async fn get_contract(
1181 &self,
1182 _address: &AztecAddress,
1183 ) -> Result<Option<ContractInstanceWithAddress>, Error> {
1184 Ok(None)
1185 }
1186
1187 async fn get_contract_class(&self, _id: &Fr) -> Result<Option<serde_json::Value>, Error> {
1188 Ok(None)
1189 }
1190
1191 async fn get_block_header(&self, _block_number: u64) -> Result<serde_json::Value, Error> {
1192 Ok(serde_json::json!({"blockNumber": 1}))
1193 }
1194 async fn get_block(&self, _block_number: u64) -> Result<Option<serde_json::Value>, Error> {
1195 Ok(None)
1196 }
1197 async fn get_tx_effect(
1198 &self,
1199 _tx_hash: &TxHash,
1200 ) -> Result<Option<serde_json::Value>, Error> {
1201 Ok(None)
1202 }
1203 async fn get_note_hash_membership_witness(
1204 &self,
1205 _block_number: u64,
1206 _note_hash: &Fr,
1207 ) -> Result<Option<serde_json::Value>, Error> {
1208 Ok(None)
1209 }
1210 async fn get_nullifier_membership_witness(
1211 &self,
1212 _block_number: u64,
1213 _nullifier: &Fr,
1214 ) -> Result<Option<serde_json::Value>, Error> {
1215 Ok(None)
1216 }
1217 async fn get_low_nullifier_membership_witness(
1218 &self,
1219 _block_number: u64,
1220 _nullifier: &Fr,
1221 ) -> Result<Option<serde_json::Value>, Error> {
1222 Ok(None)
1223 }
1224 async fn get_public_storage_at(
1225 &self,
1226 _block_number: u64,
1227 _contract: &AztecAddress,
1228 _slot: &Fr,
1229 ) -> Result<Fr, Error> {
1230 Ok(Fr::zero())
1231 }
1232 async fn get_public_data_witness(
1233 &self,
1234 _block_number: u64,
1235 _leaf_slot: &Fr,
1236 ) -> Result<Option<serde_json::Value>, Error> {
1237 Ok(None)
1238 }
1239 async fn get_l1_to_l2_message_membership_witness(
1240 &self,
1241 _block_number: u64,
1242 _entry_key: &Fr,
1243 ) -> Result<Option<serde_json::Value>, Error> {
1244 Ok(None)
1245 }
1246 async fn simulate_public_calls(
1247 &self,
1248 _tx: &serde_json::Value,
1249 _skip_fee_enforcement: bool,
1250 ) -> Result<serde_json::Value, Error> {
1251 Ok(serde_json::Value::Null)
1252 }
1253 async fn is_valid_tx(&self, _tx: &serde_json::Value) -> Result<TxValidationResult, Error> {
1254 Ok(TxValidationResult::Valid)
1255 }
1256 async fn get_private_logs_by_tags(&self, _tags: &[Fr]) -> Result<serde_json::Value, Error> {
1257 Ok(serde_json::json!([]))
1258 }
1259 async fn get_public_logs_by_tags_from_contract(
1260 &self,
1261 _contract: &AztecAddress,
1262 _tags: &[Fr],
1263 ) -> Result<serde_json::Value, Error> {
1264 Ok(serde_json::json!([]))
1265 }
1266 async fn register_contract_function_signatures(
1267 &self,
1268 _signatures: &[String],
1269 ) -> Result<(), Error> {
1270 Ok(())
1271 }
1272 async fn get_block_hash_membership_witness(
1273 &self,
1274 _block_number: u64,
1275 _block_hash: &Fr,
1276 ) -> Result<Option<serde_json::Value>, Error> {
1277 Ok(None)
1278 }
1279 async fn find_leaves_indexes(
1280 &self,
1281 _block_number: u64,
1282 _tree_id: &str,
1283 _leaves: &[Fr],
1284 ) -> Result<Vec<Option<u64>>, Error> {
1285 Ok(vec![])
1286 }
1287 async fn get_tx_by_hash(
1288 &self,
1289 _tx_hash: &TxHash,
1290 ) -> Result<Option<serde_json::Value>, Error> {
1291 Ok(None)
1292 }
1293 }
1294
1295 #[tokio::test]
1298 async fn mock_get_node_info() {
1299 let node = MockNode::new_ready(MockNode::sample_info());
1300 let info = node.get_node_info().await.unwrap();
1301 assert_eq!(info.node_version, "test-0.1.0");
1302 assert_eq!(info.l1_chain_id, 31337);
1303 }
1304
1305 #[tokio::test]
1306 async fn mock_get_block_number() {
1307 let node = MockNode {
1308 block_number: 42,
1309 ..MockNode::new_ready(MockNode::sample_info())
1310 };
1311 let bn = node.get_block_number().await.unwrap();
1312 assert_eq!(bn, 42);
1313 }
1314
1315 #[tokio::test]
1316 async fn mock_get_tx_receipt() {
1317 let receipt =
1318 MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success));
1319 let node = MockNode::new_with_receipt_sequence(vec![Ok(receipt.clone())]);
1320 let result = node.get_tx_receipt(&TxHash::zero()).await.unwrap();
1321 assert_eq!(result.status, TxStatus::Checkpointed);
1322 assert!(result.has_execution_succeeded());
1323 }
1324
1325 #[tokio::test]
1326 async fn mock_get_public_logs() {
1327 let node = MockNode::new_ready(MockNode::sample_info());
1328 let resp = node
1329 .get_public_logs(PublicLogFilter::default())
1330 .await
1331 .unwrap();
1332 assert!(resp.logs.is_empty());
1333 assert!(!resp.max_logs_hit);
1334 }
1335
1336 #[tokio::test]
1339 async fn wait_for_node_immediate_success() {
1340 let node = MockNode::new_ready(MockNode::sample_info());
1341 let info = wait_for_node_opts(&node, Duration::from_secs(5), Duration::from_millis(10))
1342 .await
1343 .unwrap();
1344 assert_eq!(info.node_version, "test-0.1.0");
1345 }
1346
1347 #[tokio::test]
1348 async fn wait_for_node_delayed_success() {
1349 let node = MockNode::new_with_info_sequence(vec![
1350 Err(Error::Transport("not ready".into())),
1351 Err(Error::Transport("not ready".into())),
1352 Ok(MockNode::sample_info()),
1353 ]);
1354 let info = wait_for_node_opts(&node, Duration::from_secs(5), Duration::from_millis(10))
1355 .await
1356 .unwrap();
1357 assert_eq!(info.node_version, "test-0.1.0");
1358 }
1359
1360 #[tokio::test]
1361 async fn wait_for_node_timeout() {
1362 let node =
1363 MockNode::new_with_info_sequence(vec![Err(Error::Transport("not ready".into()))]);
1364 let result =
1365 wait_for_node_opts(&node, Duration::from_millis(50), Duration::from_millis(100)).await;
1366 assert!(result.is_err());
1367 let err = result.unwrap_err();
1368 assert!(matches!(err, Error::Timeout(_)));
1369 }
1370
1371 #[tokio::test]
1374 async fn wait_for_tx_immediate_success() {
1375 let receipt =
1376 MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success));
1377 let node = MockNode::new_with_receipt_sequence(vec![Ok(receipt)]);
1378 let opts = WaitOpts {
1379 timeout: Duration::from_secs(5),
1380 interval: Duration::from_millis(10),
1381 ..WaitOpts::default()
1382 };
1383 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1384 assert_eq!(result.status, TxStatus::Checkpointed);
1385 }
1386
1387 #[tokio::test]
1388 async fn wait_for_tx_delayed_success() {
1389 let pending = MockNode::make_receipt(TxStatus::Pending, None);
1390 let proposed = MockNode::make_receipt(TxStatus::Proposed, Some(TxExecutionResult::Success));
1391 let checkpointed =
1392 MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success));
1393
1394 let node =
1395 MockNode::new_with_receipt_sequence(vec![Ok(pending), Ok(proposed), Ok(checkpointed)]);
1396 let opts = WaitOpts {
1397 timeout: Duration::from_secs(5),
1398 interval: Duration::from_millis(10),
1399 ..WaitOpts::default()
1400 };
1401 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1402 assert_eq!(result.status, TxStatus::Checkpointed);
1403 }
1404
1405 #[tokio::test]
1406 async fn wait_for_tx_proven() {
1407 let checkpointed =
1408 MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success));
1409 let proven = MockNode::make_receipt(TxStatus::Proven, Some(TxExecutionResult::Success));
1410
1411 let node = MockNode::new_with_receipt_sequence(vec![Ok(checkpointed), Ok(proven)]);
1412 let opts = WaitOpts {
1413 timeout: Duration::from_secs(5),
1414 interval: Duration::from_millis(10),
1415 wait_for_status: TxStatus::Proven,
1416 ..WaitOpts::default()
1417 };
1418 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1419 assert_eq!(result.status, TxStatus::Proven);
1420 }
1421
1422 #[tokio::test]
1423 async fn wait_for_tx_dropped() {
1424 let receipt = MockNode::make_receipt(TxStatus::Dropped, None);
1425 let node = MockNode::new_with_receipt_sequence(vec![Ok(receipt)]);
1426 let opts = WaitOpts {
1427 timeout: Duration::from_secs(5),
1428 interval: Duration::from_millis(10),
1429 ignore_dropped_receipts_for: Duration::ZERO,
1430 ..WaitOpts::default()
1431 };
1432 let result = wait_for_tx(&node, &TxHash::zero(), opts).await;
1433 assert!(result.is_err());
1434 assert!(matches!(result.unwrap_err(), Error::Reverted(_)));
1435 }
1436
1437 #[tokio::test]
1438 async fn wait_for_tx_reverted() {
1439 let receipt = MockNode::make_receipt(
1440 TxStatus::Checkpointed,
1441 Some(TxExecutionResult::AppLogicReverted),
1442 );
1443 let node = MockNode::new_with_receipt_sequence(vec![Ok(receipt)]);
1444 let opts = WaitOpts {
1445 timeout: Duration::from_secs(5),
1446 interval: Duration::from_millis(10),
1447 ..WaitOpts::default()
1448 };
1449 let result = wait_for_tx(&node, &TxHash::zero(), opts).await;
1450 assert!(result.is_err());
1451 assert!(matches!(result.unwrap_err(), Error::Reverted(_)));
1452 }
1453
1454 #[tokio::test]
1455 async fn wait_for_tx_timeout() {
1456 let pending = MockNode::make_receipt(TxStatus::Pending, None);
1457 let node = MockNode::new_with_receipt_sequence(vec![Ok(pending)]);
1458 let opts = WaitOpts {
1459 timeout: Duration::from_millis(50),
1460 interval: Duration::from_millis(100),
1461 ..WaitOpts::default()
1462 };
1463 let result = wait_for_tx(&node, &TxHash::zero(), opts).await;
1464 assert!(result.is_err());
1465 assert!(matches!(result.unwrap_err(), Error::Timeout(_)));
1466 }
1467
1468 #[tokio::test]
1471 async fn wait_for_tx_finalized_exceeds_checkpointed() {
1472 let receipt = MockNode::make_receipt(TxStatus::Finalized, Some(TxExecutionResult::Success));
1473 let node = MockNode::new_with_receipt_sequence(vec![Ok(receipt)]);
1474 let opts = WaitOpts {
1475 timeout: Duration::from_secs(5),
1476 interval: Duration::from_millis(10),
1477 ..WaitOpts::default()
1478 };
1479 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1480 assert_eq!(result.status, TxStatus::Finalized);
1481 }
1482
1483 #[tokio::test]
1484 async fn wait_for_tx_respects_wait_for_status() {
1485 let proposed = MockNode::make_receipt(TxStatus::Proposed, Some(TxExecutionResult::Success));
1486 let proven = MockNode::make_receipt(TxStatus::Proven, Some(TxExecutionResult::Success));
1487 let node = MockNode::new_with_receipt_sequence(vec![Ok(proposed), Ok(proven)]);
1488 let opts = WaitOpts {
1489 timeout: Duration::from_secs(5),
1490 interval: Duration::from_millis(10),
1491 wait_for_status: TxStatus::Proven,
1492 ..WaitOpts::default()
1493 };
1494 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1495 assert_eq!(result.status, TxStatus::Proven);
1496 }
1497
1498 #[tokio::test]
1499 async fn wait_for_tx_dont_throw_on_revert() {
1500 let receipt = MockNode::make_receipt(
1501 TxStatus::Checkpointed,
1502 Some(TxExecutionResult::AppLogicReverted),
1503 );
1504 let node = MockNode::new_with_receipt_sequence(vec![Ok(receipt)]);
1505 let opts = WaitOpts {
1506 timeout: Duration::from_secs(5),
1507 interval: Duration::from_millis(10),
1508 dont_throw_on_revert: true,
1509 ..WaitOpts::default()
1510 };
1511 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1512 assert_eq!(result.status, TxStatus::Checkpointed);
1513 assert!(result.has_execution_reverted());
1514 }
1515
1516 #[tokio::test]
1517 async fn wait_for_tx_ignores_dropped_within_grace_period() {
1518 let dropped = MockNode::make_receipt(TxStatus::Dropped, None);
1519 let checkpointed =
1520 MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success));
1521 let node = MockNode::new_with_receipt_sequence(vec![Ok(dropped), Ok(checkpointed)]);
1522 let opts = WaitOpts {
1523 timeout: Duration::from_secs(5),
1524 interval: Duration::from_millis(10),
1525 ignore_dropped_receipts_for: Duration::from_secs(60),
1526 ..WaitOpts::default()
1527 };
1528 let result = wait_for_tx(&node, &TxHash::zero(), opts).await.unwrap();
1529 assert_eq!(result.status, TxStatus::Checkpointed);
1530 }
1531
1532 #[tokio::test]
1533 async fn wait_for_tx_fails_dropped_after_grace_period() {
1534 let dropped = MockNode::make_receipt(TxStatus::Dropped, None);
1535 let node = MockNode::new_with_receipt_sequence(vec![Ok(dropped)]);
1536 let opts = WaitOpts {
1537 timeout: Duration::from_secs(5),
1538 interval: Duration::from_millis(10),
1539 ignore_dropped_receipts_for: Duration::ZERO,
1540 ..WaitOpts::default()
1541 };
1542 let result = wait_for_tx(&node, &TxHash::zero(), opts).await;
1543 assert!(result.is_err());
1544 assert!(matches!(result.unwrap_err(), Error::Reverted(_)));
1545 }
1546
1547 #[tokio::test]
1550 async fn wait_for_proven_returns_when_proven() {
1551 let node = MockNode::new_with_proven_block_sequence(vec![Ok(5), Ok(10)]);
1552 let receipt = TxReceipt {
1553 block_number: Some(8),
1554 ..MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success))
1555 };
1556 let opts = WaitForProvenOpts {
1557 proven_timeout: Duration::from_secs(5),
1558 interval: Duration::from_millis(10),
1559 };
1560 let result = wait_for_proven(&node, &receipt, opts).await.unwrap();
1561 assert!(result >= 8);
1562 }
1563
1564 #[tokio::test]
1565 async fn wait_for_proven_times_out() {
1566 let node = MockNode::new_with_proven_block_sequence(vec![Ok(1)]);
1567 let receipt = TxReceipt {
1568 block_number: Some(100),
1569 ..MockNode::make_receipt(TxStatus::Checkpointed, Some(TxExecutionResult::Success))
1570 };
1571 let opts = WaitForProvenOpts {
1572 proven_timeout: Duration::from_millis(50),
1573 interval: Duration::from_millis(100),
1574 };
1575 let result = wait_for_proven(&node, &receipt, opts).await;
1576 assert!(result.is_err());
1577 assert!(matches!(result.unwrap_err(), Error::Timeout(_)));
1578 }
1579
1580 #[tokio::test]
1581 async fn wait_for_proven_rejects_receipt_without_block() {
1582 let node = MockNode::new_with_proven_block_sequence(vec![Ok(10)]);
1583 let receipt = MockNode::make_receipt(TxStatus::Pending, None);
1584 let opts = WaitForProvenOpts::default();
1585 let result = wait_for_proven(&node, &receipt, opts).await;
1586 assert!(result.is_err());
1587 assert!(
1588 result.unwrap_err().to_string().contains("no block_number"),
1589 "should mention missing block_number"
1590 );
1591 }
1592
1593 #[test]
1596 fn create_client_does_not_panic() {
1597 let _client = create_aztec_node_client("http://localhost:8080");
1598 }
1599
1600 #[test]
1603 fn aztec_node_is_object_safe() {
1604 fn _assert_object_safe(_: &dyn AztecNode) {}
1605 }
1606}