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