Skip to content

Commit 47c2bb8

Browse files
authored
Merge pull request #1774 from input-output-hk/ensemble/1757/limit-cardano-transactions-prover-input
Ensure validation of transaction hashes to provide to the prover
2 parents f5bf868 + 9f63490 commit 47c2bb8

File tree

21 files changed

+506
-42
lines changed

21 files changed

+506
-42
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-aggregator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.5.25"
3+
version = "0.5.26"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/configuration.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ pub struct Configuration {
167167
/// Cardano transactions signing configuration
168168
#[example = "`{ security_parameter: 3000, step: 120 }`"]
169169
pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
170+
171+
/// Maximum number of transactions hashes allowed by request to the prover
172+
pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
170173
}
171174

172175
/// Uploader needed to copy the snapshot once computed.
@@ -245,6 +248,7 @@ impl Configuration {
245248
security_parameter: 100,
246249
step: 15,
247250
},
251+
cardano_transactions_prover_max_hashes_allowed_by_request: 100,
248252
}
249253
}
250254

@@ -358,6 +362,9 @@ pub struct DefaultConfiguration {
358362

359363
/// Cardano transactions signing configuration
360364
pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
365+
366+
/// Maximum number of transactions hashes allowed by request to the prover
367+
pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
361368
}
362369

363370
impl Default for DefaultConfiguration {
@@ -384,6 +391,7 @@ impl Default for DefaultConfiguration {
384391
security_parameter: 3000,
385392
step: 120,
386393
},
394+
cardano_transactions_prover_max_hashes_allowed_by_request: 100,
387395
}
388396
}
389397
}
@@ -463,7 +471,7 @@ impl Source for DefaultConfiguration {
463471
into_value(myself.cardano_transactions_prover_cache_pool_size),
464472
);
465473
result.insert(
466-
"cardano_transactions_prover_cache_pool_size".to_string(),
474+
"cardano_transactions_database_connection_pool_size".to_string(),
467475
into_value(myself.cardano_transactions_database_connection_pool_size),
468476
);
469477
result.insert(
@@ -483,6 +491,10 @@ impl Source for DefaultConfiguration {
483491
),
484492
])),
485493
);
494+
result.insert(
495+
"cardano_transactions_prover_max_hashes_allowed_by_request".to_string(),
496+
into_value(myself.cardano_transactions_prover_max_hashes_allowed_by_request),
497+
);
486498

487499
Ok(result)
488500
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod routes;
2+
pub mod validators;
23

34
pub const SERVER_BASE_PATH: &str = "aggregator";

mithril-aggregator/src/http_server/routes/middlewares.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,20 @@ pub fn with_prover_service(
112112
) -> impl Filter<Extract = (Arc<dyn ProverService>,), Error = Infallible> + Clone {
113113
warp::any().map(move || dependency_manager.prover_service.clone())
114114
}
115+
116+
pub mod validators {
117+
use crate::http_server::validators::ProverTransactionsHashValidator;
118+
119+
use super::*;
120+
121+
/// With Prover Transactions Hash Validator
122+
pub fn with_prover_transations_hash_validator(
123+
dependency_manager: Arc<DependencyContainer>,
124+
) -> impl Filter<Extract = (ProverTransactionsHashValidator,), Error = Infallible> + Clone {
125+
let max_hashes = dependency_manager
126+
.config
127+
.cardano_transactions_prover_max_hashes_allowed_by_request;
128+
129+
warp::any().map(move || ProverTransactionsHashValidator::new(max_hashes))
130+
}
131+
}

mithril-aggregator/src/http_server/routes/proof_routes.rs

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,18 @@ struct CardanoTransactionProofQueryParams {
1111
}
1212

1313
impl 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

Comments
 (0)