Skip to content

Commit

Permalink
Merge pull request #1887 from input-output-hk/dlachaume/1881/implemen…
Browse files Browse the repository at this point in the history
…t-cardano-stake-distribution-in-client-wasm

 Implement Cardano stake distribution in `mithril-client` WASM
  • Loading branch information
dlachaume authored Aug 14, 2024
2 parents dea7b54 + 1d6cd99 commit 7b8579b
Show file tree
Hide file tree
Showing 29 changed files with 3,215 additions and 1,329 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ As a minor extension, we have adopted a slightly different versioning convention
- Implement the HTTP routes related to the signed entity type `CardanoStakeDistribution` on the aggregator REST API.
- Added support in the `mithril-client` library for retrieving `CardanoStakeDistribution` by epoch or by hash, and for listing all available `CardanoStakeDistribution`.
- Implement `CardanoStakeDistribution` commands under the `--unstable` flag in the Mithril client CLI to list all available `CardanoStakeDistribution` and to download artifact by epoch or hash.
- Implement `mithril-client` library functions related to `CardanoStakeDistribution` within the WASM library.

- Crates versions:

Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/mithril-build-script/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-build-script"
version = "0.2.8"
version = "0.2.9"
description = "A toolbox for Mithril crates build scripts"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
96 changes: 96 additions & 0 deletions internal/mithril-build-script/src/fake_aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub struct FakeAggregatorData {
ctx_snapshots_list: FileContent,
individual_ctx_snapshots: BTreeMap<ArtifactId, FileContent>,
ctx_proofs: BTreeMap<ArtifactId, FileContent>,

csds_list: FileContent,
individual_csds: BTreeMap<ArtifactId, FileContent>,
}

impl FakeAggregatorData {
Expand All @@ -53,6 +56,9 @@ impl FakeAggregatorData {
"snapshots-list.json" => {
data.snapshots_list = file_content;
}
"cardano-stake-distributions-list.json" => {
data.csds_list = file_content;
}
"certificates-list.json" => {
data.certificates_list = file_content;
}
Expand All @@ -65,6 +71,9 @@ impl FakeAggregatorData {
"snapshots.json" => {
data.individual_snapshots = Self::read_artifacts_json_file(&entry.path());
}
"cardano-stake-distributions.json" => {
data.individual_csds = Self::read_artifacts_json_file(&entry.path());
}
"certificates.json" => {
data.individual_certificates = Self::read_artifacts_json_file(&entry.path());
}
Expand Down Expand Up @@ -93,6 +102,14 @@ impl FakeAggregatorData {
"msd_hashes",
BTreeSet::from_iter(self.individual_msds.keys().cloned()),
),
generate_ids_array(
"csd_hashes",
BTreeSet::from_iter(self.individual_csds.keys().cloned()),
),
generate_ids_array(
"csd_epochs",
BTreeSet::from_iter(extract_csd_epochs(&self.individual_csds)),
),
generate_ids_array(
"certificate_hashes",
BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
Expand Down Expand Up @@ -126,6 +143,16 @@ impl FakeAggregatorData {
),
generate_artifact_getter("msds", self.individual_msds),
generate_list_getter("msd_list", self.msds_list),
generate_ids_array(
"csd_hashes",
BTreeSet::from_iter(self.individual_csds.keys().cloned()),
),
generate_ids_array(
"csd_epochs",
BTreeSet::from_iter(extract_csd_epochs(&self.individual_csds)),
),
generate_artifact_getter("csds", self.individual_csds),
generate_list_getter("csd_list", self.csds_list),
generate_ids_array(
"certificate_hashes",
BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
Expand Down Expand Up @@ -181,6 +208,28 @@ impl FakeAggregatorData {
}
}

pub fn extract_csd_epochs(individual_csds: &BTreeMap<ArtifactId, FileContent>) -> Vec<String> {
individual_csds
.values()
.map(|content| {
let json_value: serde_json::Value =
serde_json::from_str(content).unwrap_or_else(|err| {
panic!(
"Failed to parse JSON in csd content: {}\nError: {}",
content, err
);
});

json_value
.get("epoch")
.and_then(|epoch| epoch.as_u64().map(|s| s.to_string()))
.unwrap_or_else(|| {
panic!("Epoch not found or invalid in csd content: {}", content);
})
})
.collect()
}

fn extract_artifact_id_and_content(
key: &String,
value: &serde_json::Value,
Expand Down Expand Up @@ -358,4 +407,51 @@ fn b() {}
]);
assert_eq!(expected, id_per_json);
}

#[test]
fn extract_csd_epochs_with_valid_data() {
let mut csds = BTreeMap::new();
csds.insert(
"csd-123".to_string(),
r#"{"hash": "csd-123", "epoch": 123}"#.to_string(),
);
csds.insert(
"csd-456".to_string(),
r#"{"hash": "csd-456", "epoch": 456}"#.to_string(),
);

let epochs = extract_csd_epochs(&csds);

assert_eq!(epochs, vec![123.to_string(), 456.to_string()]);
}

#[test]
#[should_panic(expected = "Failed to parse JSON in csd content")]
fn extract_csd_epochs_with_invalid_json() {
let mut csds = BTreeMap::new();
csds.insert(
"csd-123".to_string(),
r#""hash": "csd-123", "epoch": "123"#.to_string(),
);

extract_csd_epochs(&csds);
}

#[test]
#[should_panic(expected = "Epoch not found or invalid in csd content")]
fn test_extract_csd_epochs_with_missing_epoch() {
let mut csds = BTreeMap::new();
csds.insert("csd-123".to_string(), r#"{"hash": "csd-123"}"#.to_string());

extract_csd_epochs(&csds);
}

#[test]
fn test_extract_csd_epochs_with_empty_map() {
let csds = BTreeMap::new();

let epochs = extract_csd_epochs(&csds);

assert!(epochs.is_empty());
}
}
2 changes: 1 addition & 1 deletion mithril-client-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-client-wasm"
version = "0.3.8"
version = "0.3.9"
description = "Mithril client WASM"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
161 changes: 159 additions & 2 deletions mithril-client-wasm/src/client_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;
use wasm_bindgen::prelude::*;

use mithril_client::{
common::Epoch,
feedback::{FeedbackReceiver, MithrilEvent},
CardanoTransactionsProofs, Client, ClientBuilder, MessageBuilder, MithrilCertificate,
};
Expand Down Expand Up @@ -292,6 +293,70 @@ impl MithrilUnstableClient {

Ok(serde_wasm_bindgen::to_value(&result)?)
}

/// Call the client to get a cardano stake distribution from a hash
#[wasm_bindgen]
pub async fn get_cardano_stake_distribution(&self, hash: &str) -> WasmResult {
let result = self
.client
.cardano_stake_distribution()
.get(hash)
.await
.map_err(|err| format!("{err:?}"))?
.ok_or(JsValue::from_str(&format!(
"No cardano stake distribution found for hash: '{hash}'"
)))?;

Ok(serde_wasm_bindgen::to_value(&result)?)
}

/// Call the client to get a cardano stake distribution from an epoch
/// The epoch represents the epoch at the end of which the Cardano stake distribution is computed by the Cardano node
#[wasm_bindgen]
pub async fn get_cardano_stake_distribution_by_epoch(&self, epoch: u64) -> WasmResult {
let result = self
.client
.cardano_stake_distribution()
.get_by_epoch(Epoch(epoch))
.await
.map_err(|err| format!("{err:?}"))?
.ok_or(JsValue::from_str(&format!(
"No cardano stake distribution found for epoch: '{epoch}'"
)))?;

Ok(serde_wasm_bindgen::to_value(&result)?)
}

/// Call the client for the list of available cardano stake distributions
#[wasm_bindgen]
pub async fn list_cardano_stake_distributions(&self) -> WasmResult {
let result = self
.client
.cardano_stake_distribution()
.list()
.await
.map_err(|err| format!("{err:?}"))?;

Ok(serde_wasm_bindgen::to_value(&result)?)
}

/// Call the client to compute a cardano stake distribution message
#[wasm_bindgen]
pub async fn compute_cardano_stake_distribution_message(
&self,
certificate: JsValue,
cardano_stake_distribution: JsValue,
) -> WasmResult {
let certificate =
serde_wasm_bindgen::from_value(certificate).map_err(|err| format!("{err:?}"))?;
let cardano_stake_distribution = serde_wasm_bindgen::from_value(cardano_stake_distribution)
.map_err(|err| format!("{err:?}"))?;
let result = MessageBuilder::new()
.compute_cardano_stake_distribution_message(&certificate, &cardano_stake_distribution)
.map_err(|err| format!("{err:?}"))?;

Ok(serde_wasm_bindgen::to_value(&result)?)
}
}

#[cfg(test)]
Expand All @@ -301,8 +366,9 @@ mod tests {
use wasm_bindgen_test::*;

use mithril_client::{
common::ProtocolMessage, CardanoTransactionSnapshot, MithrilCertificateListItem,
MithrilStakeDistribution, MithrilStakeDistributionListItem, Snapshot, SnapshotListItem,
common::ProtocolMessage, CardanoStakeDistribution, CardanoStakeDistributionListItem,
CardanoTransactionSnapshot, MithrilCertificateListItem, MithrilStakeDistribution,
MithrilStakeDistributionListItem, Snapshot, SnapshotListItem,
};

const GENESIS_VERIFICATION_KEY: &str = "5b33322c3235332c3138362c3230312c3137372c31312c3131372c3133352c3138372c3136372c3138312c3138382c32322c35392c3230362c3130352c3233312c3135302c3231352c33302c37382c3231322c37362c31362c3235322c3138302c37322c3133342c3133372c3234372c3136312c36385d";
Expand Down Expand Up @@ -556,4 +622,95 @@ mod tests {
.await
.expect("Compute tx proof message for matching cert failed");
}

#[wasm_bindgen_test]
async fn list_cardano_stake_distributions_should_return_value_convertible_in_rust_type() {
let csd_list_js_value = get_mithril_client()
.unstable
.list_cardano_stake_distributions()
.await
.expect("cardano_mithril_stake_distributions should not fail");
let csd_list = serde_wasm_bindgen::from_value::<Vec<CardanoStakeDistributionListItem>>(
csd_list_js_value,
)
.expect("conversion should not fail");

assert_eq!(
csd_list.len(),
// Aggregator return up to 20 items for a list route
test_data::csd_hashes().len().min(20)
);
}

#[wasm_bindgen_test]
async fn get_cardano_stake_distribution_should_return_value_convertible_in_rust_type() {
let csd_js_value = get_mithril_client()
.unstable
.get_cardano_stake_distribution(test_data::csd_hashes()[0])
.await
.expect("get_cardano_stake_distribution should not fail");
let csd = serde_wasm_bindgen::from_value::<CardanoStakeDistribution>(csd_js_value)
.expect("conversion should not fail");

assert_eq!(csd.hash, test_data::csd_hashes()[0]);
}

#[wasm_bindgen_test]
async fn get_cardano_stake_distribution_should_fail_with_unknown_hash() {
get_mithril_client()
.unstable
.get_cardano_stake_distribution("whatever")
.await
.expect_err("get_cardano_stake_distribution should fail");
}

#[wasm_bindgen_test]
async fn get_cardano_stake_distribution_by_epoch_should_return_value_convertible_in_rust_type()
{
let epoch: u64 = test_data::csd_epochs()[0].parse().unwrap();
let csd_js_value = get_mithril_client()
.unstable
.get_cardano_stake_distribution_by_epoch(epoch)
.await
.expect("get_cardano_stake_distribution_by_epoch should not fail");

let csd = serde_wasm_bindgen::from_value::<CardanoStakeDistribution>(csd_js_value)
.expect("conversion should not fail");

assert_eq!(csd.epoch, epoch);
}

#[wasm_bindgen_test]
async fn get_cardano_stake_distribution_by_epoch_should_fail_with_unknown_epoch() {
get_mithril_client()
.unstable
.get_cardano_stake_distribution_by_epoch(u64::MAX)
.await
.expect_err("get_cardano_stake_distribution_by_epoch should fail");
}

#[wasm_bindgen_test]
async fn compute_cardano_stake_distribution_message_should_return_value_convertible_in_rust_type(
) {
let client = get_mithril_client();
let csd_js_value = client
.unstable
.get_cardano_stake_distribution(test_data::csd_hashes()[0])
.await
.unwrap();
let csd = serde_wasm_bindgen::from_value::<CardanoStakeDistribution>(csd_js_value.clone())
.unwrap();
let certificate_js_value = client
.get_mithril_certificate(&csd.certificate_hash)
.await
.unwrap();

let message_js_value = client
.unstable
.compute_cardano_stake_distribution_message(certificate_js_value, csd_js_value)
.await
.expect("compute_cardano_stake_distribution_message should not fail");
serde_wasm_bindgen::from_value::<ProtocolMessage>(message_js_value)
.expect("conversion should not fail");
}
}
Loading

0 comments on commit 7b8579b

Please sign in to comment.