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