@@ -11,8 +11,18 @@ struct CardanoTransactionProofQueryParams {
1111}
1212
1313impl CardanoTransactionProofQueryParams {
14- pub fn split_transactions_hashes ( & self ) -> Vec < & str > {
15- self . transaction_hashes . split ( ',' ) . collect ( )
14+ pub fn split_transactions_hashes ( & self ) -> Vec < String > {
15+ self . transaction_hashes
16+ . split ( ',' )
17+ . map ( |s| s. to_string ( ) )
18+ . collect ( )
19+ }
20+
21+ pub fn sanitize ( & self ) -> Vec < String > {
22+ let mut transaction_hashes = self . split_transactions_hashes ( ) ;
23+ transaction_hashes. sort ( ) ;
24+ transaction_hashes. dedup ( ) ;
25+ transaction_hashes
1626 }
1727}
1828
@@ -32,6 +42,11 @@ fn proof_cardano_transaction(
3242 . and ( middlewares:: with_signed_entity_service (
3343 dependency_manager. clone ( ) ,
3444 ) )
45+ . and (
46+ middlewares:: validators:: with_prover_transations_hash_validator (
47+ dependency_manager. clone ( ) ,
48+ ) ,
49+ )
3550 . and ( middlewares:: with_prover_service ( dependency_manager) )
3651 . and_then ( handlers:: proof_cardano_transaction)
3752}
@@ -47,7 +62,7 @@ mod handlers {
4762 use warp:: http:: StatusCode ;
4863
4964 use crate :: {
50- http_server:: routes:: reply,
65+ http_server:: { routes:: reply, validators :: ProverTransactionsHashValidator } ,
5166 message_adapters:: ToCardanoTransactionsProofsMessageAdapter ,
5267 services:: { ProverService , SignedEntityService } ,
5368 unwrap_to_internal_server_error,
@@ -58,18 +73,22 @@ mod handlers {
5873 pub async fn proof_cardano_transaction (
5974 transaction_parameters : CardanoTransactionProofQueryParams ,
6075 signed_entity_service : Arc < dyn SignedEntityService > ,
76+ validator : ProverTransactionsHashValidator ,
6177 prover_service : Arc < dyn ProverService > ,
6278 ) -> Result < impl warp:: Reply , Infallible > {
63- let transaction_hashes = transaction_parameters
64- . split_transactions_hashes ( )
65- . iter ( )
66- . map ( |s| s. to_string ( ) )
67- . collect :: < Vec < String > > ( ) ;
79+ let transaction_hashes = transaction_parameters. split_transactions_hashes ( ) ;
6880 debug ! (
6981 "⇄ HTTP SERVER: proof_cardano_transaction?transaction_hashes={}" ,
7082 transaction_parameters. transaction_hashes
7183 ) ;
7284
85+ if let Err ( error) = validator. validate ( & transaction_hashes) {
86+ warn ! ( "proof_cardano_transaction::bad_request" ) ;
87+ return Ok ( reply:: bad_request ( error. label , error. message ) ) ;
88+ }
89+
90+ let sanitized_hashes = transaction_parameters. sanitize ( ) ;
91+
7392 match unwrap_to_internal_server_error ! (
7493 signed_entity_service
7594 . get_last_cardano_transaction_snapshot( )
@@ -78,7 +97,7 @@ mod handlers {
7897 ) {
7998 Some ( signed_entity) => {
8099 let message = unwrap_to_internal_server_error ! (
81- build_response_message( prover_service, signed_entity, transaction_hashes ) . await ,
100+ build_response_message( prover_service, signed_entity, sanitized_hashes ) . await ,
82101 "proof_cardano_transaction"
83102 ) ;
84103 Ok ( reply:: json ( & message, StatusCode :: OK ) )
@@ -123,7 +142,7 @@ mod tests {
123142
124143 use mithril_common:: {
125144 entities:: { CardanoTransactionsSetProof , CardanoTransactionsSnapshot , SignedEntity } ,
126- test_utils:: apispec:: APISpec ,
145+ test_utils:: { apispec:: APISpec , assert_equivalent , fake_data } ,
127146 } ;
128147
129148 use crate :: services:: MockSignedEntityService ;
@@ -199,7 +218,9 @@ mod tests {
199218 let response = request ( )
200219 . method ( method)
201220 . path ( & format ! (
202- "/{SERVER_BASE_PATH}{path}?transaction_hashes=tx-123,tx-456"
221+ "/{SERVER_BASE_PATH}{path}?transaction_hashes={},{}" ,
222+ fake_data:: transaction_hashes( ) [ 0 ] ,
223+ fake_data:: transaction_hashes( ) [ 1 ]
203224 ) )
204225 . reply ( & setup_router ( Arc :: new ( dependency_manager) ) )
205226 . await ;
@@ -228,7 +249,9 @@ mod tests {
228249 let response = request ( )
229250 . method ( method)
230251 . path ( & format ! (
231- "/{SERVER_BASE_PATH}{path}?transaction_hashes=tx-123,tx-456"
252+ "/{SERVER_BASE_PATH}{path}?transaction_hashes={},{}" ,
253+ fake_data:: transaction_hashes( ) [ 0 ] ,
254+ fake_data:: transaction_hashes( ) [ 1 ]
232255 ) )
233256 . reply ( & setup_router ( Arc :: new ( dependency_manager) ) )
234257 . await ;
@@ -262,7 +285,9 @@ mod tests {
262285 let response = request ( )
263286 . method ( method)
264287 . path ( & format ! (
265- "/{SERVER_BASE_PATH}{path}?transaction_hashes=tx-123,tx-456"
288+ "/{SERVER_BASE_PATH}{path}?transaction_hashes={},{}" ,
289+ fake_data:: transaction_hashes( ) [ 0 ] ,
290+ fake_data:: transaction_hashes( ) [ 1 ]
266291 ) )
267292 . reply ( & setup_router ( Arc :: new ( dependency_manager) ) )
268293 . await ;
@@ -278,4 +303,81 @@ mod tests {
278303 )
279304 . unwrap ( ) ;
280305 }
306+
307+ #[ tokio:: test]
308+ async fn proof_cardano_transaction_return_bad_request_with_invalid_hashes ( ) {
309+ let config = Configuration :: new_sample ( ) ;
310+ let mut builder = DependenciesBuilder :: new ( config) ;
311+ let dependency_manager = builder. build_dependency_container ( ) . await . unwrap ( ) ;
312+
313+ let method = Method :: GET . as_str ( ) ;
314+ let path = "/proof/cardano-transaction" ;
315+
316+ let response = request ( )
317+ . method ( method)
318+ . path ( & format ! (
319+ "/{SERVER_BASE_PATH}{path}?transaction_hashes=invalid%3A%2F%2Fid,,tx-456"
320+ ) )
321+ . reply ( & setup_router ( Arc :: new ( dependency_manager) ) )
322+ . await ;
323+
324+ APISpec :: verify_conformity (
325+ APISpec :: get_all_spec_files ( ) ,
326+ method,
327+ path,
328+ "application/json" ,
329+ & Null ,
330+ & response,
331+ & StatusCode :: BAD_REQUEST ,
332+ )
333+ . unwrap ( ) ;
334+ }
335+
336+ #[ tokio:: test]
337+ async fn proof_cardano_transaction_route_deduplicate_hashes ( ) {
338+ let tx = fake_data:: transaction_hashes ( ) [ 0 ] . to_string ( ) ;
339+ let config = Configuration :: new_sample ( ) ;
340+ let mut builder = DependenciesBuilder :: new ( config) ;
341+ let mut dependency_manager = builder. build_dependency_container ( ) . await . unwrap ( ) ;
342+ let mut mock_signed_entity_service = MockSignedEntityService :: new ( ) ;
343+ mock_signed_entity_service
344+ . expect_get_last_cardano_transaction_snapshot ( )
345+ . returning ( || Ok ( Some ( SignedEntity :: < CardanoTransactionsSnapshot > :: dummy ( ) ) ) ) ;
346+ dependency_manager. signed_entity_service = Arc :: new ( mock_signed_entity_service) ;
347+
348+ let mut mock_prover_service = MockProverService :: new ( ) ;
349+ let txs_expected = vec ! [ tx. clone( ) ] ;
350+ mock_prover_service
351+ . expect_compute_transactions_proofs ( )
352+ . withf ( move |_, transaction_hashes| transaction_hashes == txs_expected)
353+ . returning ( |_, _| Ok ( vec ! [ CardanoTransactionsSetProof :: dummy( ) ] ) ) ;
354+ dependency_manager. prover_service = Arc :: new ( mock_prover_service) ;
355+
356+ let method = Method :: GET . as_str ( ) ;
357+ let path = "/proof/cardano-transaction" ;
358+
359+ let response = request ( )
360+ . method ( method)
361+ . path ( & format ! (
362+ "/{SERVER_BASE_PATH}{path}?transaction_hashes={tx},{tx}" ,
363+ ) )
364+ . reply ( & setup_router ( Arc :: new ( dependency_manager) ) )
365+ . await ;
366+
367+ assert_eq ! ( StatusCode :: OK , response. status( ) ) ;
368+ }
369+
370+ #[ test]
371+ fn sanitize_cardano_transaction_proof_query_params_remove_duplicate ( ) {
372+ let tx1 = fake_data:: transaction_hashes ( ) [ 0 ] . to_string ( ) ;
373+ let tx2 = fake_data:: transaction_hashes ( ) [ 1 ] . to_string ( ) ;
374+
375+ // We are testing on an unordered list of transaction hashes
376+ // as some rust dedup methods only remove consecutive duplicates
377+ let params = CardanoTransactionProofQueryParams {
378+ transaction_hashes : format ! ( "{tx1},{tx2},{tx2},{tx1},{tx2}" , ) ,
379+ } ;
380+
381+ assert_equivalent ( params. sanitize ( ) , vec ! [ tx1, tx2] ) ;
382+ }
281383}
0 commit comments