diff --git a/Cargo.lock b/Cargo.lock index a79e2ba3b6..ee0b074165 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3578,7 +3578,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.6.7" +version = "0.6.8" dependencies = [ "anyhow", "async-trait", @@ -3738,7 +3738,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.4.99" +version = "0.4.100" dependencies = [ "anyhow", "async-trait", @@ -3809,7 +3809,7 @@ dependencies = [ [[package]] name = "mithril-end-to-end" -version = "0.4.58" +version = "0.4.59" dependencies = [ "anyhow", "async-recursion", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 7564366076..e9b6a5bb73 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.6.7" +version = "0.6.8" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-aggregator/src/database/record/signed_entity.rs b/mithril-aggregator/src/database/record/signed_entity.rs index 66441c0663..5966d7998d 100644 --- a/mithril-aggregator/src/database/record/signed_entity.rs +++ b/mithril-aggregator/src/database/record/signed_entity.rs @@ -177,6 +177,7 @@ impl TryFrom for CardanoDatabaseSnapshotMessage { fn try_from(value: SignedEntityRecord) -> Result { let artifact = serde_json::from_str::(&value.artifact)?; let cardano_database_snapshot_message = CardanoDatabaseSnapshotMessage { + hash: artifact.hash, merkle_root: artifact.merkle_root, beacon: artifact.beacon, certificate_hash: value.certificate_id, @@ -197,6 +198,7 @@ impl TryFrom for CardanoDatabaseSnapshotListItemMessage { fn try_from(value: SignedEntityRecord) -> Result { let artifact = serde_json::from_str::(&value.artifact)?; let cardano_database_snapshot_list_item_message = CardanoDatabaseSnapshotListItemMessage { + hash: artifact.hash, merkle_root: artifact.merkle_root, beacon: artifact.beacon, certificate_hash: value.certificate_id, diff --git a/mithril-aggregator/src/http_server/routes/artifact_routes/cardano_database.rs b/mithril-aggregator/src/http_server/routes/artifact_routes/cardano_database.rs index bbf2aaa1cf..c571823a1f 100644 --- a/mithril-aggregator/src/http_server/routes/artifact_routes/cardano_database.rs +++ b/mithril-aggregator/src/http_server/routes/artifact_routes/cardano_database.rs @@ -235,7 +235,7 @@ mod tests { async fn test_cardano_database_detail_increments_artifact_detail_total_served_since_startup_metric( ) { let method = Method::GET.as_str(); - let path = "/artifact/cardano-database/{merkle_root}"; + let path = "/artifact/cardano-database/{hash}"; let dependency_manager = Arc::new(initialize_dependencies().await); let initial_counter_value = dependency_manager .metrics_service @@ -270,7 +270,7 @@ mod tests { dependency_manager.message_service = Arc::new(mock_http_message_service); let method = Method::GET.as_str(); - let path = "/artifact/cardano-database/{merkle_root}"; + let path = "/artifact/cardano-database/{hash}"; let response = request() .method(method) @@ -304,7 +304,7 @@ mod tests { dependency_manager.message_service = Arc::new(mock_http_message_service); let method = Method::GET.as_str(); - let path = "/artifact/cardano-database/{merkle_root}"; + let path = "/artifact/cardano-database/{hash}"; let response = request() .method(method) @@ -337,7 +337,7 @@ mod tests { dependency_manager.message_service = Arc::new(mock_http_message_service); let method = Method::GET.as_str(); - let path = "/artifact/cardano-database/{merkle_root}"; + let path = "/artifact/cardano-database/{hash}"; let response = request() .method(method) diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index fddc90740e..8215e85354 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.4.99" +version = "0.4.100" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/src/entities/cardano_database.rs b/mithril-common/src/entities/cardano_database.rs index f6ee441f5c..a7b78d8249 100644 --- a/mithril-common/src/entities/cardano_database.rs +++ b/mithril-common/src/entities/cardano_database.rs @@ -1,5 +1,6 @@ use semver::Version; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use strum::EnumDiscriminants; use crate::{ @@ -10,6 +11,9 @@ use crate::{ /// Cardano database snapshot. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CardanoDatabaseSnapshot { + /// Unique hash of the Cardano database snapshot. + pub hash: String, + /// Merkle root of the Cardano database snapshot. pub merkle_root: String, @@ -40,15 +44,27 @@ impl CardanoDatabaseSnapshot { cardano_node_version: &Version, ) -> Self { let cardano_node_version = format!("{cardano_node_version}"); - - Self { + let mut cardano_database_snapshot = Self { + hash: "".to_string(), merkle_root, beacon, locations, total_db_size_uncompressed, compression_algorithm, cardano_node_version, - } + }; + cardano_database_snapshot.hash = cardano_database_snapshot.compute_hash(); + + cardano_database_snapshot + } + + /// Cardano database snapshot hash computation + fn compute_hash(&self) -> String { + let mut hasher = Sha256::new(); + hasher.update(self.beacon.epoch.to_be_bytes()); + hasher.update(self.merkle_root.as_bytes()); + + hex::encode(hasher.finalize()) } } @@ -106,6 +122,112 @@ pub struct ArtifactsLocations { #[typetag::serde] impl Artifact for CardanoDatabaseSnapshot { fn get_id(&self) -> String { - self.merkle_root.clone() + self.hash.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn dummy() -> CardanoDatabaseSnapshot { + CardanoDatabaseSnapshot::new( + "mk-root-1111111111".to_string(), + CardanoDbBeacon::new(2222, 55555), + 0, + ArtifactsLocations { + digests: vec![], + immutables: vec![], + ancillary: vec![], + }, + CompressionAlgorithm::Gzip, + &Version::new(1, 0, 0), + ) + } + + #[test] + fn test_cardano_database_snapshot_compute_hash() { + let cardano_database_snapshot = CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + }; + + assert_eq!( + "b1cc5e0deaa7856e8e811e349d6e639fa667aa70288602955f438c5893ce29c8", + cardano_database_snapshot.compute_hash() + ); + } + + #[test] + fn compute_hash_returns_same_hash_with_same_cardano_database_snapshot() { + assert_eq!( + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + } + .compute_hash(), + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + } + .compute_hash() + ); + } + + #[test] + fn compute_hash_returns_different_hash_with_different_merkle_root() { + assert_ne!( + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + } + .compute_hash(), + CardanoDatabaseSnapshot { + merkle_root: "mk-root-456".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + } + .compute_hash() + ); + } + + #[test] + fn compute_hash_returns_different_hash_with_same_epoch_in_beacon() { + assert_eq!( + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + } + .compute_hash(), + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 12), + ..dummy() + } + .compute_hash() + ); + } + + #[test] + fn compute_hash_returns_different_hash_with_different_beacon() { + assert_ne!( + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(123, 98), + ..dummy() + } + .compute_hash(), + CardanoDatabaseSnapshot { + merkle_root: "mk-root-123".to_string(), + beacon: CardanoDbBeacon::new(456, 98), + ..dummy() + } + .compute_hash() + ); } } diff --git a/mithril-common/src/messages/cardano_database.rs b/mithril-common/src/messages/cardano_database.rs index 0882478ed9..b7bba37000 100644 --- a/mithril-common/src/messages/cardano_database.rs +++ b/mithril-common/src/messages/cardano_database.rs @@ -30,6 +30,9 @@ impl From for ArtifactsLocationsMessagePart { /// Cardano database snapshot. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CardanoDatabaseSnapshotMessage { + /// Hash of the Cardano database snapshot. + pub hash: String, + /// Merkle root of the Cardano database snapshot. pub merkle_root: String, @@ -59,6 +62,7 @@ impl CardanoDatabaseSnapshotMessage { /// Return a dummy test entity (test-only). pub fn dummy() -> Self { Self { + hash: "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb".to_string(), merkle_root: "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6" .to_string(), beacon: CardanoDbBeacon { @@ -99,6 +103,7 @@ mod tests { const ACTUAL_JSON: &str = r#" { + "hash": "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb", "merkle_root": "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6", "beacon": { "epoch": 123, @@ -137,6 +142,7 @@ mod tests { fn golden_actual_message() -> CardanoDatabaseSnapshotMessage { CardanoDatabaseSnapshotMessage { + hash: "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb".to_string(), merkle_root: "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6" .to_string(), beacon: CardanoDbBeacon { diff --git a/mithril-common/src/messages/cardano_database_list.rs b/mithril-common/src/messages/cardano_database_list.rs index d9237295ae..80f886bad0 100644 --- a/mithril-common/src/messages/cardano_database_list.rs +++ b/mithril-common/src/messages/cardano_database_list.rs @@ -9,6 +9,9 @@ pub type CardanoDatabaseSnapshotListMessage = Vec Self { Self { + hash: "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb".to_string(), merkle_root: "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6" .to_string(), beacon: CardanoDbBeacon { @@ -60,6 +64,7 @@ mod tests { const ACTUAL_JSON: &str = r#" [ { + "hash": "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb", "merkle_root": "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6", "beacon": { "epoch": 123, @@ -75,6 +80,7 @@ mod tests { fn golden_actual_message() -> CardanoDatabaseSnapshotListMessage { vec![CardanoDatabaseSnapshotListItemMessage { + hash: "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb".to_string(), merkle_root: "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6" .to_string(), beacon: CardanoDbBeacon { diff --git a/mithril-test-lab/mithril-end-to-end/Cargo.toml b/mithril-test-lab/mithril-end-to-end/Cargo.toml index f24a574c01..fd84c5dde1 100644 --- a/mithril-test-lab/mithril-end-to-end/Cargo.toml +++ b/mithril-test-lab/mithril-end-to-end/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-end-to-end" -version = "0.4.58" +version = "0.4.59" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true } diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/check.rs b/mithril-test-lab/mithril-end-to-end/src/assertions/check.rs index 10c5ab0969..b62f8425dd 100644 --- a/mithril-test-lab/mithril-end-to-end/src/assertions/check.rs +++ b/mithril-test-lab/mithril-end-to-end/src/assertions/check.rs @@ -194,9 +194,7 @@ pub async fn assert_node_producing_cardano_database_snapshot( let url = format!("{aggregator_endpoint}/artifact/cardano-database"); info!("Waiting for the aggregator to produce a Cardano database snapshot"); - async fn fetch_last_cardano_database_snapshot_merkle_root( - url: String, - ) -> StdResult> { + async fn fetch_last_cardano_database_snapshot_hash(url: String) -> StdResult> { match reqwest::get(url.clone()).await { Ok(response) => match response.status() { StatusCode::OK => match response @@ -205,7 +203,7 @@ pub async fn assert_node_producing_cardano_database_snapshot( .as_deref() { Ok([cardano_database_snapshot, ..]) => { - Ok(Some(cardano_database_snapshot.merkle_root.clone())) + Ok(Some(cardano_database_snapshot.hash.clone())) } Ok(&[]) => Ok(None), Err(err) => Err(anyhow!("Invalid Cardano database snapshot body : {err}",)), @@ -218,11 +216,11 @@ pub async fn assert_node_producing_cardano_database_snapshot( // todo: reduce the number of attempts if we can reduce the delay between two immutables match attempt!(45, Duration::from_millis(2000), { - fetch_last_cardano_database_snapshot_merkle_root(url.clone()).await + fetch_last_cardano_database_snapshot_hash(url.clone()).await }) { - AttemptResult::Ok(merkle_root) => { - info!("Aggregator produced a Cardano database snapshot"; "merkle_root" => &merkle_root); - Ok(merkle_root) + AttemptResult::Ok(hash) => { + info!("Aggregator produced a Cardano database snapshot"; "hash" => &hash); + Ok(hash) } AttemptResult::Err(error) => Err(error), AttemptResult::Timeout() => Err(anyhow!( @@ -233,13 +231,13 @@ pub async fn assert_node_producing_cardano_database_snapshot( pub async fn assert_signer_is_signing_cardano_database_snapshot( aggregator_endpoint: &str, - merkle_root: &str, + hash: &str, expected_epoch_min: Epoch, ) -> StdResult { - let url = format!("{aggregator_endpoint}/artifact/cardano-database/{merkle_root}"); + let url = format!("{aggregator_endpoint}/artifact/cardano-database/{hash}"); info!( "Asserting the aggregator is signing the Cardano database snapshot message `{}` with an expected min epoch of `{}`", - merkle_root, + hash, expected_epoch_min ); diff --git a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs b/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs index df3f53cbcd..dbdb2e6cb5 100644 --- a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs @@ -188,12 +188,12 @@ impl<'a> Spec<'a> { // Verify that Cardano database snapshot artifacts are produced and signed correctly if self.is_signing_cardano_database { - let merkle_root = + let hash = assertions::assert_node_producing_cardano_database_snapshot(&aggregator_endpoint) .await?; let certificate_hash = assertions::assert_signer_is_signing_cardano_database_snapshot( &aggregator_endpoint, - &merkle_root, + &hash, expected_epoch_min, ) .await?; diff --git a/openapi.yaml b/openapi.yaml index 643407cb28..1533843671 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4,7 +4,7 @@ info: # `mithril-common/src/lib.rs` file. If you plan to update it # here to reflect changes in the API, please also update the constant in the # Rust file. - version: 0.1.39 + version: 0.1.40 title: Mithril Aggregator Server description: | The REST API provided by a Mithril Aggregator Node in a Mithril network. @@ -297,15 +297,15 @@ paths: schema: $ref: "#/components/schemas/Error" - /artifact/cardano-database/{merkle_root}: + /artifact/cardano-database/{hash}: get: summary: Get Cardano database snapshot information description: | Returns the information of a Cardano database snapshot and where to retrieve its binary contents parameters: - - name: merkle_root + - name: hash in: path - description: Merkle root of the Cardano database snapshot + description: Hash of the Cardano database snapshot required: true schema: type: string @@ -1900,6 +1900,7 @@ components: examples: [ { + "hash": "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb", "merkle_root": "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6", "beacon": { @@ -1920,12 +1921,17 @@ components: type: object additionalProperties: false required: + - hash - merkle_root - beacon - certificate_hash - total_db_size_uncompressed - created_at properties: + hash: + description: Hash of the Cardano database snapshot + type: string + format: bytes merkle_root: description: Merkle root of the Cardano database snapshot type: string @@ -1952,6 +1958,7 @@ components: type: string examples: { + "hash": "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb", "merkle_root": "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6", "beacon": { @@ -1971,6 +1978,7 @@ components: type: object additionalProperties: false required: + - hash - merkle_root - beacon - certificate_hash @@ -1978,6 +1986,10 @@ components: - created_at - locations properties: + hash: + description: Hash of the Cardano database snapshot + type: string + format: bytes merkle_root: description: Merkle root of the Cardano database snapshot type: string @@ -2006,6 +2018,7 @@ components: type: string examples: { + "hash": "d4071d518a3ace0f6c04a9c0745b9e9560e3e2af1b373bafc4e0398423e9abfb", "merkle_root": "c8224920b9f5ad7377594eb8a15f34f08eb3103cc5241d57cafc5638403ec7c6", "beacon": {