openzeppelin_relayer/jobs/handlers/
transaction_submission_handler.rs1use actix_web::web::ThinData;
9use apalis::prelude::{Attempt, Data, TaskId, *};
10use eyre::Result;
11use tracing::{debug, info, instrument};
12
13use crate::{
14 constants::{
15 WORKER_TRANSACTION_CANCEL_RETRIES, WORKER_TRANSACTION_RESEND_RETRIES,
16 WORKER_TRANSACTION_RESUBMIT_RETRIES, WORKER_TRANSACTION_SUBMIT_RETRIES,
17 },
18 domain::{get_relayer_transaction, get_transaction_by_id, Transaction},
19 jobs::{handle_result, Job, TransactionCommand, TransactionSend},
20 models::DefaultAppState,
21 observability::request_id::set_request_id,
22};
23
24#[instrument(
25 level = "info",
26 skip(job, state),
27 fields(
28 request_id = ?job.request_id,
29 job_id = %job.message_id,
30 job_type = %job.job_type.to_string(),
31 attempt = %attempt.current(),
32 tx_id = %job.data.transaction_id,
33 relayer_id = %job.data.relayer_id,
34 task_id = %task_id.to_string(),
35 command = ?job.data.command,
36 )
37)]
38pub async fn transaction_submission_handler(
39 job: Job<TransactionSend>,
40 state: Data<ThinData<DefaultAppState>>,
41 attempt: Attempt,
42 task_id: TaskId,
43) -> Result<(), Error> {
44 if let Some(request_id) = job.request_id.clone() {
45 set_request_id(request_id);
46 }
47
48 debug!(
49 tx_id = %job.data.transaction_id,
50 relayer_id = %job.data.relayer_id,
51 "handling transaction submission"
52 );
53
54 let command = job.data.command.clone();
55 let result = handle_request(job.data, state.clone()).await;
56
57 handle_result(
59 result,
60 attempt,
61 "Transaction Submission",
62 get_max_retries(&command),
63 )
64}
65
66fn get_max_retries(command: &TransactionCommand) -> usize {
68 match command {
69 TransactionCommand::Submit => WORKER_TRANSACTION_SUBMIT_RETRIES,
70 TransactionCommand::Resubmit => WORKER_TRANSACTION_RESUBMIT_RETRIES,
71 TransactionCommand::Cancel { .. } => WORKER_TRANSACTION_CANCEL_RETRIES,
72 TransactionCommand::Resend => WORKER_TRANSACTION_RESEND_RETRIES,
73 }
74}
75
76async fn handle_request(
77 status_request: TransactionSend,
78 state: Data<ThinData<DefaultAppState>>,
79) -> Result<()> {
80 let relayer_transaction =
81 get_relayer_transaction(status_request.relayer_id.clone(), &state).await?;
82
83 let transaction = get_transaction_by_id(status_request.transaction_id, &state).await?;
84
85 let tx_id = transaction.id.clone();
87 let relayer_id = transaction.relayer_id.clone();
88 let command = status_request.command.clone();
89
90 debug!(
91 tx_id = %transaction.id,
92 relayer_id = %transaction.relayer_id,
93 status = ?transaction.status,
94 "loaded transaction for submission"
95 );
96
97 match status_request.command {
98 TransactionCommand::Submit => {
99 relayer_transaction.submit_transaction(transaction).await?;
100 }
101 TransactionCommand::Cancel { reason } => {
102 info!(
103 tx_id = %transaction.id,
104 relayer_id = %transaction.relayer_id,
105 status = ?transaction.status,
106 reason = %reason,
107 "cancelling transaction"
108 );
109 relayer_transaction.submit_transaction(transaction).await?;
110 }
111 TransactionCommand::Resubmit => {
112 debug!(
113 tx_id = %transaction.id,
114 relayer_id = %transaction.relayer_id,
115 status = ?transaction.status,
116 "resubmitting transaction with updated parameters"
117 );
118 relayer_transaction
119 .resubmit_transaction(transaction)
120 .await?;
121 }
122 TransactionCommand::Resend => {
123 debug!(
124 tx_id = %transaction.id,
125 relayer_id = %transaction.relayer_id,
126 status = ?transaction.status,
127 "resending transaction"
128 );
129 relayer_transaction.submit_transaction(transaction).await?;
130 }
131 };
132
133 debug!(
134 tx_id = %tx_id,
135 relayer_id = %relayer_id,
136 command = ?command,
137 "transaction submission completed"
138 );
139
140 Ok(())
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use std::collections::HashMap;
147
148 #[tokio::test]
149 async fn test_submission_handler_job_validation() {
150 let submit_job = TransactionSend::submit("tx123", "relayer-1");
152 let job = Job::new(crate::jobs::JobType::TransactionSend, submit_job);
153
154 match job.data.command {
156 TransactionCommand::Submit => {}
157 _ => panic!("Expected Submit command"),
158 }
159 assert_eq!(job.data.transaction_id, "tx123");
160 assert_eq!(job.data.relayer_id, "relayer-1");
161 assert!(job.data.metadata.is_none());
162
163 let cancel_job = TransactionSend::cancel("tx123", "relayer-1", "user requested");
165 let job = Job::new(crate::jobs::JobType::TransactionSend, cancel_job);
166
167 match job.data.command {
169 TransactionCommand::Cancel { reason } => {
170 assert_eq!(reason, "user requested");
171 }
172 _ => panic!("Expected Cancel command"),
173 }
174 }
175
176 #[tokio::test]
177 async fn test_submission_job_with_metadata() {
178 let mut metadata = HashMap::new();
180 metadata.insert("gas_price".to_string(), "20000000000".to_string());
181
182 let submit_job =
183 TransactionSend::submit("tx123", "relayer-1").with_metadata(metadata.clone());
184
185 assert!(submit_job.metadata.is_some());
187 let job_metadata = submit_job.metadata.unwrap();
188 assert_eq!(job_metadata.get("gas_price").unwrap(), "20000000000");
189 }
190
191 mod get_max_retries_tests {
192 use super::*;
193
194 #[test]
195 fn test_submit_command_retries() {
196 let command = TransactionCommand::Submit;
197 let retries = get_max_retries(&command);
198
199 assert_eq!(
200 retries, WORKER_TRANSACTION_SUBMIT_RETRIES,
201 "Submit command should use WORKER_TRANSACTION_SUBMIT_RETRIES"
202 );
203 }
204
205 #[test]
206 fn test_resubmit_command_retries() {
207 let command = TransactionCommand::Resubmit;
208 let retries = get_max_retries(&command);
209
210 assert_eq!(
211 retries, WORKER_TRANSACTION_RESUBMIT_RETRIES,
212 "Resubmit command should use WORKER_TRANSACTION_RESUBMIT_RETRIES"
213 );
214 }
215
216 #[test]
217 fn test_cancel_command_retries() {
218 let command = TransactionCommand::Cancel {
219 reason: "test cancel".to_string(),
220 };
221 let retries = get_max_retries(&command);
222
223 assert_eq!(
224 retries, WORKER_TRANSACTION_CANCEL_RETRIES,
225 "Cancel command should use WORKER_TRANSACTION_CANCEL_RETRIES"
226 );
227 }
228
229 #[test]
230 fn test_resend_command_retries() {
231 let command = TransactionCommand::Resend;
232 let retries = get_max_retries(&command);
233
234 assert_eq!(
235 retries, WORKER_TRANSACTION_RESEND_RETRIES,
236 "Resend command should use WORKER_TRANSACTION_RESEND_RETRIES"
237 );
238 }
239 }
240}