openzeppelin_relayer/jobs/handlers/
transaction_status_handler.rs

1//! Transaction status monitoring handler.
2//!
3//! Monitors the status of submitted transactions by:
4//! - Checking transaction status on the network
5//! - Updating transaction status in storage
6//! - Triggering notifications on status changes
7use actix_web::web::ThinData;
8use apalis::prelude::{Attempt, Data, *};
9
10use eyre::Result;
11use log::info;
12
13use crate::{
14    constants::WORKER_DEFAULT_MAXIMUM_RETRIES,
15    domain::{get_relayer_transaction, get_transaction_by_id, Transaction},
16    jobs::{handle_result, Job, TransactionStatusCheck},
17    models::DefaultAppState,
18};
19
20pub async fn transaction_status_handler(
21    job: Job<TransactionStatusCheck>,
22    state: Data<ThinData<DefaultAppState>>,
23    attempt: Attempt,
24) -> Result<(), Error> {
25    info!("Handling transaction status job: {:?}", job.data);
26
27    let result = handle_request(job.data, state).await;
28
29    handle_result(
30        result,
31        attempt,
32        "Transaction Status",
33        WORKER_DEFAULT_MAXIMUM_RETRIES,
34    )
35}
36
37async fn handle_request(
38    status_request: TransactionStatusCheck,
39    state: Data<ThinData<DefaultAppState>>,
40) -> Result<()> {
41    let relayer_transaction =
42        get_relayer_transaction(status_request.relayer_id.clone(), &state).await?;
43
44    let transaction = get_transaction_by_id(status_request.transaction_id, &state).await?;
45
46    relayer_transaction
47        .handle_transaction_status(transaction)
48        .await?;
49
50    info!("Status check handled successfully");
51
52    Ok(())
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use apalis::prelude::Attempt;
59    use std::collections::HashMap;
60
61    #[tokio::test]
62    async fn test_status_check_job_validation() {
63        // Create a basic status check job
64        let check_job = TransactionStatusCheck::new("tx123", "relayer-1");
65        let job = Job::new(crate::jobs::JobType::TransactionStatusCheck, check_job);
66
67        // Validate the job data
68        assert_eq!(job.data.transaction_id, "tx123");
69        assert_eq!(job.data.relayer_id, "relayer-1");
70        assert!(job.data.metadata.is_none());
71    }
72
73    #[tokio::test]
74    async fn test_status_check_with_metadata() {
75        // Create a job with retry metadata
76        let mut metadata = HashMap::new();
77        metadata.insert("retry_count".to_string(), "2".to_string());
78        metadata.insert("last_status".to_string(), "pending".to_string());
79
80        let check_job =
81            TransactionStatusCheck::new("tx123", "relayer-1").with_metadata(metadata.clone());
82
83        // Validate the metadata
84        assert!(check_job.metadata.is_some());
85        let job_metadata = check_job.metadata.unwrap();
86        assert_eq!(job_metadata.get("retry_count").unwrap(), "2");
87        assert_eq!(job_metadata.get("last_status").unwrap(), "pending");
88    }
89
90    #[tokio::test]
91    async fn test_status_handler_attempt_tracking() {
92        // Create attempts with different retry counts
93        let first_attempt = Attempt::default();
94        assert_eq!(first_attempt.current(), 0);
95
96        let second_attempt = Attempt::default();
97        second_attempt.increment();
98        assert_eq!(second_attempt.current(), 1);
99
100        let final_attempt = Attempt::default();
101        for _ in 0..WORKER_DEFAULT_MAXIMUM_RETRIES {
102            final_attempt.increment();
103        }
104        assert_eq!(final_attempt.current(), WORKER_DEFAULT_MAXIMUM_RETRIES);
105    }
106}