1use super::governance_verifier::GovernanceVerifier;
5use crate::crypto::{BridgeAuthorityKeyPair, BridgeAuthoritySignInfo};
6use crate::error::{BridgeError, BridgeResult};
7use crate::eth_client::EthClient;
8use crate::sui_client::{SuiClient, SuiClientInner};
9use crate::types::{BridgeAction, SignedBridgeAction};
10use alloy::primitives::TxHash;
11use async_trait::async_trait;
12use axum::Json;
13use std::str::FromStr;
14use std::sync::Arc;
15use sui_types::digests::TransactionDigest;
16use tap::TapFallible;
17use tracing::info;
18
19#[async_trait]
20pub trait BridgeRequestHandlerTrait {
21 async fn handle_eth_tx_hash(
25 &self,
26 tx_hash_hex: String,
27 event_idx: u16,
28 ) -> Result<Json<SignedBridgeAction>, BridgeError>;
29 async fn handle_sui_tx_digest(
33 &self,
34 tx_digest_base58: String,
35 event_idx: u16,
36 ) -> Result<Json<SignedBridgeAction>, BridgeError>;
37
38 async fn handle_sui_token_transfer(
41 &self,
42 source_chain: u8,
43 message_type: u8,
44 bridge_seq_num: u64,
45 ) -> Result<Json<SignedBridgeAction>, BridgeError>;
46
47 async fn handle_governance_action(
49 &self,
50 action: BridgeAction,
51 ) -> Result<Json<SignedBridgeAction>, BridgeError>;
52}
53
54pub struct BridgeRequestHandler<SC> {
55 signer: Arc<BridgeAuthorityKeyPair>,
56 sui_client: Arc<SuiClient<SC>>,
57 eth_client: Arc<EthClient>,
58 governance_verifier: GovernanceVerifier,
59}
60
61impl<SC> BridgeRequestHandler<SC>
62where
63 SC: SuiClientInner + Send + Sync + 'static,
64{
65 pub fn new(
66 signer: BridgeAuthorityKeyPair,
67 sui_client: Arc<SuiClient<SC>>,
68 eth_client: Arc<EthClient>,
69 approved_governance_actions: Vec<BridgeAction>,
70 ) -> Self {
71 let signer = Arc::new(signer);
72
73 Self {
74 signer,
75 sui_client,
76 eth_client,
77 governance_verifier: GovernanceVerifier::new(approved_governance_actions).unwrap(),
78 }
79 }
80
81 fn sign(&self, bridge_action: BridgeAction) -> SignedBridgeAction {
82 let sig = BridgeAuthoritySignInfo::new(&bridge_action, &self.signer);
83 SignedBridgeAction::new_from_data_and_sig(bridge_action, sig)
84 }
85
86 async fn verify_eth(&self, key: (TxHash, u16)) -> BridgeResult<BridgeAction> {
87 let (tx_hash, event_idx) = key;
88 self.eth_client
89 .get_finalized_bridge_action_maybe(tx_hash, event_idx)
90 .await
91 .tap_ok(|action| info!("Eth action found: {:?}", action))
92 }
93
94 async fn verify_sui(&self, key: (TransactionDigest, u16)) -> BridgeResult<BridgeAction> {
95 let (tx_digest, event_idx) = key;
96 self.sui_client
97 .get_bridge_action_by_tx_digest_and_event_idx_maybe(&tx_digest, event_idx)
98 .await
99 .tap_ok(|action| info!("Sui action found: {:?}", action))
100 }
101
102 async fn verify_sui_message(
103 &self,
104 source_chain_id: u8,
105 _message_type: u8,
106 seq_number: u64,
107 ) -> BridgeResult<BridgeAction> {
108 let record = self
109 .sui_client
110 .get_bridge_record(source_chain_id, seq_number)
111 .await?
112 .ok_or_else(|| BridgeError::Generic(format!("message {seq_number} not found")))?;
113 if record.verified_signatures.is_some() {
114 return Err(BridgeError::Generic(format!(
115 "message {seq_number} already complete"
116 )));
117 }
118 BridgeAction::try_from_bridge_record(&record)
119 .tap_ok(|action| info!("Sui action found: {:?}", action))
120 }
121}
122
123#[async_trait]
124impl<SC> BridgeRequestHandlerTrait for BridgeRequestHandler<SC>
125where
126 SC: SuiClientInner + Send + Sync + 'static,
127{
128 async fn handle_eth_tx_hash(
129 &self,
130 tx_hash_hex: String,
131 event_idx: u16,
132 ) -> Result<Json<SignedBridgeAction>, BridgeError> {
133 let tx_hash = TxHash::from_str(&tx_hash_hex).map_err(|_| BridgeError::InvalidTxHash)?;
134 let bridge_action = self.verify_eth((tx_hash, event_idx)).await?;
135 Ok(Json(self.sign(bridge_action)))
136 }
137
138 async fn handle_sui_tx_digest(
139 &self,
140 tx_digest_base58: String,
141 event_idx: u16,
142 ) -> Result<Json<SignedBridgeAction>, BridgeError> {
143 let tx_digest = TransactionDigest::from_str(&tx_digest_base58)
144 .map_err(|_e| BridgeError::InvalidTxHash)?;
145
146 let bridge_action = self.verify_sui((tx_digest, event_idx)).await?;
147 Ok(Json(self.sign(bridge_action)))
148 }
149
150 async fn handle_sui_token_transfer(
151 &self,
152 source_chain: u8,
153 message_type: u8,
154 bridge_seq_num: u64,
155 ) -> Result<Json<SignedBridgeAction>, BridgeError> {
156 let bridge_action = self
157 .verify_sui_message(source_chain, message_type, bridge_seq_num)
158 .await?;
159 Ok(Json(self.sign(bridge_action)))
160 }
161
162 async fn handle_governance_action(
163 &self,
164 action: BridgeAction,
165 ) -> Result<Json<SignedBridgeAction>, BridgeError> {
166 if !action.is_governance_action() {
167 return Err(BridgeError::ActionIsNotGovernanceAction(Box::new(action)));
168 }
169 let bridge_action = self.governance_verifier.verify(action).await?;
170 Ok(Json(self.sign(bridge_action)))
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use std::collections::HashSet;
177
178 use super::*;
179 use crate::{
180 eth_mock_provider::EthMockService,
181 events::{MoveTokenDepositedEvent, SuiToEthTokenBridgeV1, init_all_struct_tags},
182 sui_mock_client::SuiMockClient,
183 test_utils::{
184 get_test_log_and_action, get_test_sui_to_eth_bridge_action, make_transaction_receipt,
185 mock_last_finalized_block,
186 },
187 types::{EmergencyAction, EmergencyActionType, LimitUpdateAction},
188 };
189 use alloy::{primitives::Address as EthAddress, rpc::types::TransactionReceipt};
190 use sui_json_rpc_types::{BcsEvent, SuiEvent};
191 use sui_types::bridge::{BridgeChainId, TOKEN_ID_USDC};
192 use sui_types::{base_types::SuiAddress, crypto::get_key_pair};
193
194 fn test_handler(
195 approved_actions: Vec<BridgeAction>,
196 ) -> (
197 BridgeRequestHandler<SuiMockClient>,
198 SuiMockClient,
199 EthMockService,
200 EthAddress,
201 ) {
202 let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair();
203 let sui_client_mock = SuiMockClient::default();
204
205 let eth_mock_service = EthMockService::default();
206 let contract_address = EthAddress::random();
207 let eth_client = EthClient::new_mocked(
208 eth_mock_service.clone(),
209 HashSet::from_iter(vec![contract_address]),
210 );
211
212 let handler = BridgeRequestHandler::new(
213 kp,
214 Arc::new(SuiClient::new_for_testing(sui_client_mock.clone())),
215 Arc::new(eth_client),
216 approved_actions,
217 );
218 (handler, sui_client_mock, eth_mock_service, contract_address)
219 }
220
221 #[tokio::test]
222 async fn test_sui_verify() {
223 let (handler, sui_client_mock, _, _) = test_handler(vec![]);
224
225 let sui_tx_digest = TransactionDigest::random();
226 let sui_event_idx = 0;
227
228 sui_client_mock.add_events_by_tx_digest_error(sui_tx_digest);
230 handler
231 .verify_sui((sui_tx_digest, sui_event_idx))
232 .await
233 .unwrap_err();
234
235 sui_client_mock.add_events_by_tx_digest(sui_tx_digest, vec![]);
237 assert!(matches!(
238 handler.verify_sui((sui_tx_digest, sui_event_idx)).await,
239 Err(BridgeError::NoBridgeEventsInTxPosition)
240 ));
241
242 let emitted_event_1 = MoveTokenDepositedEvent {
244 seq_num: 1,
245 source_chain: BridgeChainId::SuiCustom as u8,
246 sender_address: SuiAddress::random_for_testing_only().to_vec(),
247 target_chain: BridgeChainId::EthCustom as u8,
248 target_address: EthAddress::random().to_vec(),
249 token_type: TOKEN_ID_USDC,
250 amount_sui_adjusted: 12345,
251 };
252
253 init_all_struct_tags();
254
255 let mut sui_event_1 = SuiEvent::random_for_testing();
256 sui_event_1.type_ = SuiToEthTokenBridgeV1.get().unwrap().clone();
257 sui_event_1.bcs = BcsEvent::new(bcs::to_bytes(&emitted_event_1).unwrap());
258 let sui_tx_digest = sui_event_1.id.tx_digest;
259
260 let mut sui_event_2 = SuiEvent::random_for_testing();
261 sui_event_2.type_ = SuiToEthTokenBridgeV1.get().unwrap().clone();
262 sui_event_2.bcs = BcsEvent::new(bcs::to_bytes(&emitted_event_1).unwrap());
263 let sui_event_idx_2 = 1;
264 sui_client_mock.add_events_by_tx_digest(sui_tx_digest, vec![sui_event_2.clone()]);
265
266 sui_client_mock.add_events_by_tx_digest(
267 sui_tx_digest,
268 vec![sui_event_1.clone(), sui_event_2.clone()],
269 );
270 handler
271 .verify_sui((sui_tx_digest, sui_event_idx))
272 .await
273 .unwrap();
274 handler
275 .verify_sui((sui_tx_digest, sui_event_idx_2))
276 .await
277 .unwrap();
278 }
279
280 #[tokio::test]
281 async fn test_eth_verify() {
282 let (handler, _sui_client_mock, eth_mock_service, contract_address) = test_handler(vec![]);
283
284 let eth_tx_hash = TxHash::random();
286 let eth_event_idx = 0;
287 let (log, _action) = get_test_log_and_action(contract_address, eth_tx_hash, eth_event_idx);
288 eth_mock_service
289 .add_response::<[TxHash; 1], TransactionReceipt, TransactionReceipt>(
290 "eth_getTransactionReceipt",
291 [log.transaction_hash.unwrap()],
292 make_transaction_receipt(
293 EthAddress::default(),
294 log.block_number,
295 vec![log.clone()],
296 ),
297 )
298 .unwrap();
299 mock_last_finalized_block(ð_mock_service, log.block_number.unwrap());
300 eth_mock_service
302 .add_response(
303 "eth_getBlockByNumber",
304 (format!("0x{:x}", log.block_number.unwrap()), false),
305 make_transaction_receipt(
306 EthAddress::default(),
307 log.block_number,
308 vec![log.clone()],
309 ),
310 )
311 .unwrap();
312
313 handler
314 .verify_eth((eth_tx_hash, eth_event_idx))
315 .await
316 .unwrap();
317 }
318
319 #[tokio::test]
320 async fn test_signer_with_governance_verifier() {
321 let action_1 = BridgeAction::EmergencyAction(EmergencyAction {
322 chain_id: BridgeChainId::EthCustom,
323 nonce: 1,
324 action_type: EmergencyActionType::Pause,
325 });
326 let action_2 = BridgeAction::LimitUpdateAction(LimitUpdateAction {
327 chain_id: BridgeChainId::EthCustom,
328 sending_chain_id: BridgeChainId::SuiCustom,
329 nonce: 1,
330 new_usd_limit: 10000,
331 });
332
333 let (handler, _, _, _) = test_handler(vec![action_1.clone(), action_2.clone()]);
334 let verifier = handler.governance_verifier;
335 assert_eq!(
336 verifier.verify(action_1.clone()).await.unwrap(),
337 action_1.clone()
338 );
339 assert_eq!(
340 verifier.verify(action_2.clone()).await.unwrap(),
341 action_2.clone()
342 );
343
344 let action_3 = BridgeAction::EmergencyAction(EmergencyAction {
346 chain_id: BridgeChainId::EthCustom,
347 nonce: 1,
348 action_type: EmergencyActionType::Unpause,
349 });
350 assert!(matches!(
352 verifier.verify(action_3.clone()).await.unwrap_err(),
353 BridgeError::GovernanceActionIsNotApproved
354 ));
355
356 let action_4 = get_test_sui_to_eth_bridge_action(None, None, None, None, None, None, None);
358 assert!(matches!(
359 verifier.verify(action_4.clone()).await.unwrap_err(),
360 BridgeError::ActionIsNotGovernanceAction(..)
361 ));
362 }
363}