diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50607d2b670..7bab2096ecf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ As a minor extension, we have adopted a slightly different versioning convention
- **UNSTABLE**:
- Support for **DMQ consumer deduplication** to prevent processing the same message multiple times upon reconnection to the DMQ server.
- Support for SNARK-friendly signer registration.
+ - Support for SNARK-friendly certificate chain.
| Crate | Version |
| ----- | ------- |
diff --git a/Cargo.lock b/Cargo.lock
index 29e086f7e5b..caa0cdab044 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3932,7 +3932,7 @@ dependencies = [
[[package]]
name = "mithril-aggregator"
-version = "0.8.33"
+version = "0.8.34"
dependencies = [
"anyhow",
"async-trait",
@@ -4137,7 +4137,7 @@ dependencies = [
[[package]]
name = "mithril-client"
-version = "0.13.4"
+version = "0.13.5"
dependencies = [
"anyhow",
"async-trait",
@@ -4176,7 +4176,7 @@ dependencies = [
[[package]]
name = "mithril-client-cli"
-version = "0.12.42"
+version = "0.12.43"
dependencies = [
"anyhow",
"async-trait",
@@ -4229,7 +4229,7 @@ dependencies = [
[[package]]
name = "mithril-common"
-version = "0.6.55"
+version = "0.6.56"
dependencies = [
"anyhow",
"async-trait",
@@ -4314,7 +4314,7 @@ dependencies = [
[[package]]
name = "mithril-end-to-end"
-version = "0.4.121"
+version = "0.4.122"
dependencies = [
"anyhow",
"async-recursion",
@@ -4464,7 +4464,7 @@ dependencies = [
[[package]]
name = "mithril-signer"
-version = "0.3.22"
+version = "0.3.23"
dependencies = [
"anyhow",
"async-trait",
@@ -4507,7 +4507,7 @@ dependencies = [
[[package]]
name = "mithril-stm"
-version = "0.9.28"
+version = "0.9.29"
dependencies = [
"anyhow",
"blake2 0.10.6",
diff --git a/docs/runbook/genesis-manually/README.md b/docs/runbook/genesis-manually/README.md
index 0b3f00f3a7e..b74949f288a 100644
--- a/docs/runbook/genesis-manually/README.md
+++ b/docs/runbook/genesis-manually/README.md
@@ -32,6 +32,7 @@ export NETWORK_MAGIC=**NETWORK_MAGIC**
export DATA_STORES_DIRECTORY=**DATA_STORES_DIRECTORY**
export CARDANO_NODE_SOCKET_PATH=**CARDANO_NODE_SOCKET_PATH**
export CHAIN_OBSERVER_TYPE=**CHAIN_OBSERVER_TYPE**
+export MITHRIL_ERA=**MITHRIL_ERA**
```
And create genesis dir:
@@ -49,9 +50,11 @@ docker exec -it mithril-aggregator bash
Once connected to the aggregator container, export the genesis payload to sign:
```bash
-/app/bin/mithril-aggregator -vvv genesis export --target-path /mithril-aggregator/mithril/genesis/genesis-payload-to-sign.txt
+/app/bin/mithril-aggregator -vvv genesis export --target-path /mithril-aggregator/mithril/genesis/genesis-payload-to-sign.txt [--mithril-era $MITHRIL_ERA]
```
+> The `--mithril-era` parameter is optional when only one era exists, and required when multiple eras are supported. It specifies which Mithril era to use for the genesis certificate (e.g. `pythagoras`, `lagrange`).
+
Then disconnect from the aggregator container:
```bash
diff --git a/docs/website/root/manual/develop/nodes/mithril-aggregator.md b/docs/website/root/manual/develop/nodes/mithril-aggregator.md
index 93b83e90047..46baba6e1ad 100644
--- a/docs/website/root/manual/develop/nodes/mithril-aggregator.md
+++ b/docs/website/root/manual/develop/nodes/mithril-aggregator.md
@@ -550,27 +550,31 @@ Here is a list of the available parameters for the serve command:
`genesis bootstrap` command:
-| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory |
-| -------------------------- | ------------------- | :------------------: | -------------------------- | ------------------------------------------------------------------ | ------------- | ---------------------------------- | :----------------: |
-| `genesis_secret_key` | - | - | `GENESIS_SECRET_KEY` | Genesis secret key, :warning: for test only | - | - | :heavy_check_mark: |
-| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator databases | - | `./mithril-aggregator/stores` | :heavy_check_mark: |
-| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: |
-| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - |
-| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Chain observer type that can be `cardano-cli`, `pallas` or `fake`. | `pallas` | - | :heavy_check_mark: |
-| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: |
-| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano network magic number (for `testnet` and `devnet`) | - | `1097911063` or `42` | - |
+| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory |
+| -------------------------- | ---------------------- | :------------------: | -------------------------- | -------------------------------------------------------------------------------------- | ------------- | ---------------------------------- | :----------------: |
+| `genesis_secret_key` | `--genesis-secret-key` | - | `GENESIS_SECRET_KEY` | Genesis Secret Key (test only) | - | - | :heavy_check_mark: |
+| `mithril_era` | `--mithril-era` | - | - | Mithril era to use for the genesis certificate | - | - | - |
+| `help` | `--help` | `-h` | - | Print help (see more with '--help') | - | - | - |
+| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - |
+| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: |
+| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano Network Magic number
useful for TestNet & DevNet | - | `1097911063` or `42` | - |
+| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: |
+| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Cardano chain observer type | - | - | :heavy_check_mark: |
+| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator data (Certificates, Snapshots, Protocol Parameters, ...) | - | `./mithril-aggregator/stores` | :heavy_check_mark: |
`genesis export` command:
-| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory |
-| -------------------------- | ------------------- | :------------------: | -------------------------- | ------------------------------------------------------------------ | ------------- | ---------------------------------- | :----------------: |
-| `target_path` | `--target-path` | - | - | Path of the file to export the payload to. | - | - | :heavy_check_mark: |
-| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator databases | - | `./mithril-aggregator/stores` | :heavy_check_mark: |
-| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: |
-| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - |
-| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Chain observer type that can be `cardano-cli`, `pallas` or `fake`. | `pallas` | - | :heavy_check_mark: |
-| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: |
-| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano network magic number (for `testnet` and `devnet`) | - | `1097911063` or `42` | - |
+| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory |
+| -------------------------- | ------------------- | :------------------: | -------------------------- | -------------------------------------------------------------------------------------- | ------------- | ---------------------------------- | :----------------: |
+| `target_path` | `--target-path` | - | - | Target path | - | - | :heavy_check_mark: |
+| `mithril_era` | `--mithril-era` | - | - | Mithril era to use for the genesis certificate | - | - | - |
+| `help` | `--help` | `-h` | - | Print help (see more with '--help') | - | - | - |
+| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - |
+| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: |
+| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano Network Magic number
useful for TestNet & DevNet | - | `1097911063` or `42` | - |
+| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: |
+| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Cardano chain observer type | - | - | :heavy_check_mark: |
+| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator data (Certificates, Snapshots, Protocol Parameters, ...) | - | `./mithril-aggregator/stores` | :heavy_check_mark: |
`genesis import` command:
diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml
index 11784945c1a..7b989968816 100644
--- a/mithril-aggregator/Cargo.toml
+++ b/mithril-aggregator/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mithril-aggregator"
-version = "0.8.33"
+version = "0.8.34"
description = "A Mithril Aggregator server"
authors = { workspace = true }
edition = { workspace = true }
diff --git a/mithril-aggregator/src/commands/genesis_command.rs b/mithril-aggregator/src/commands/genesis_command.rs
index 7ffd0697792..1ca85e56e2f 100644
--- a/mithril-aggregator/src/commands/genesis_command.rs
+++ b/mithril-aggregator/src/commands/genesis_command.rs
@@ -12,7 +12,7 @@ use mithril_common::{
crypto_helper::{
ProtocolGenesisSecretKey, ProtocolGenesisSigner, ProtocolGenesisVerificationKey,
},
- entities::{HexEncodedGenesisSecretKey, HexEncodedGenesisVerificationKey},
+ entities::{HexEncodedGenesisSecretKey, HexEncodedGenesisVerificationKey, SupportedEra},
};
use mithril_doc::{Documenter, StructDoc};
@@ -21,6 +21,27 @@ use crate::{
extract_all, tools::GenesisTools,
};
+/// Resolve the Mithril era for the genesis command.
+///
+/// If the era is explicitly provided, it is returned as-is.
+/// If not provided and only one era exists, that era is used automatically.
+/// If not provided and multiple eras exist, an error is returned.
+fn resolve_mithril_era(mithril_era: Option) -> StdResult {
+ match mithril_era {
+ Some(era) => Ok(era),
+ None => {
+ let eras = SupportedEra::eras();
+ if eras.len() == 1 {
+ Ok(eras[0])
+ } else {
+ Err(anyhow::anyhow!(
+ "Multiple Mithril eras are supported ({eras:?}), please specify which era to use with --mithril-era"
+ ))
+ }
+ }
+ }
+}
+
#[derive(Debug, Clone, Deserialize, Documenter)]
pub struct GenesisCommandConfiguration {
/// Cardano CLI tool path
@@ -163,6 +184,12 @@ pub struct ExportGenesisSubCommand {
/// Target path
#[clap(long)]
target_path: PathBuf,
+
+ /// Mithril era to use for the genesis certificate
+ ///
+ /// Optional when only one era exists, required when multiple eras are supported.
+ #[clap(long)]
+ mithril_era: Option,
}
impl ExportGenesisSubCommand {
@@ -181,11 +208,15 @@ impl ExportGenesisSubCommand {
"Genesis export payload to sign to {}",
self.target_path.display()
);
+ let mithril_era = resolve_mithril_era(self.mithril_era)?;
let mut dependencies_builder =
DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
- let dependencies = dependencies_builder.create_genesis_container().await.with_context(
- || "Dependencies Builder can not create genesis command dependencies container",
- )?;
+ let dependencies = dependencies_builder
+ .create_genesis_container(mithril_era)
+ .await
+ .with_context(
+ || "Dependencies Builder can not create genesis command dependencies container",
+ )?;
let genesis_tools = GenesisTools::from_dependencies(dependencies)
.await
@@ -228,11 +259,15 @@ impl ImportGenesisSubCommand {
"Genesis import signed payload from {}",
self.signed_payload_path.to_string_lossy()
);
+ let mithril_era = resolve_mithril_era(None)?;
let mut dependencies_builder =
DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
- let dependencies = dependencies_builder.create_genesis_container().await.with_context(
- || "Dependencies Builder can not create genesis command dependencies container",
- )?;
+ let dependencies = dependencies_builder
+ .create_genesis_container(mithril_era)
+ .await
+ .with_context(
+ || "Dependencies Builder can not create genesis command dependencies container",
+ )?;
let genesis_tools = GenesisTools::from_dependencies(dependencies)
.await
@@ -296,6 +331,12 @@ pub struct BootstrapGenesisSubCommand {
/// Genesis Secret Key (test only)
#[clap(long, env = "GENESIS_SECRET_KEY")]
genesis_secret_key: HexEncodedGenesisSecretKey,
+
+ /// Mithril era to use for the genesis certificate
+ ///
+ /// Optional when only one era exists, required when multiple eras are supported.
+ #[clap(long)]
+ mithril_era: Option,
}
impl BootstrapGenesisSubCommand {
@@ -311,11 +352,15 @@ impl BootstrapGenesisSubCommand {
.with_context(|| "configuration deserialize error")?;
debug!(root_logger, "BOOTSTRAP GENESIS command"; "config" => format!("{config:?}"));
println!("Genesis bootstrap for test only!");
+ let mithril_era = resolve_mithril_era(self.mithril_era)?;
let mut dependencies_builder =
DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
- let dependencies = dependencies_builder.create_genesis_container().await.with_context(
- || "Dependencies Builder can not create genesis command dependencies container",
- )?;
+ let dependencies = dependencies_builder
+ .create_genesis_container(mithril_era)
+ .await
+ .with_context(
+ || "Dependencies Builder can not create genesis command dependencies container",
+ )?;
let genesis_tools = GenesisTools::from_dependencies(dependencies)
.await
@@ -372,6 +417,24 @@ mod tests {
use super::*;
+ #[test]
+ fn resolve_mithril_era_returns_provided_era() {
+ let era = resolve_mithril_era(Some(SupportedEra::Lagrange)).unwrap();
+ assert_eq!(SupportedEra::Lagrange, era);
+ }
+
+ #[test]
+ fn resolve_mithril_era_returns_error_when_multiple_eras_and_none_provided() {
+ let eras = SupportedEra::eras();
+ if eras.len() > 1 {
+ let result = resolve_mithril_era(None);
+ assert!(
+ result.is_err(),
+ "Should error when multiple eras exist and none is provided"
+ );
+ }
+ }
+
#[tokio::test]
async fn create_container_does_not_panic() {
let config = GenesisCommandConfiguration {
@@ -386,7 +449,7 @@ mod tests {
DependenciesBuilder::new(TestLogger::stdout(), Arc::new(config));
dependencies_builder
- .create_genesis_container()
+ .create_genesis_container(SupportedEra::Pythagoras)
.await
.expect("Expected container creation to succeed without panicking");
}
diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs
index 1d289986cb8..9fe404a2c2e 100644
--- a/mithril-aggregator/src/database/migration.rs
+++ b/mithril-aggregator/src/database/migration.rs
@@ -302,5 +302,13 @@ alter table signer_registration add column verification_key_for_snark text;
alter table signer_registration add column verification_key_signature_for_snark text;
"#,
),
+ // Migration 41
+ // Add `aggregate_verification_key_snark` column to `certificate` table.
+ SqlMigration::new(
+ 41,
+ r#"
+alter table certificate add column aggregate_verification_key_snark text;
+ "#,
+ ),
]
}
diff --git a/mithril-aggregator/src/database/query/certificate/conditions.rs b/mithril-aggregator/src/database/query/certificate/conditions.rs
index c6687ffc7f5..b5c7a34cea0 100644
--- a/mithril-aggregator/src/database/query/certificate/conditions.rs
+++ b/mithril-aggregator/src/database/query/certificate/conditions.rs
@@ -14,6 +14,7 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where
message, \
signature, \
aggregate_verification_key, \
+ aggregate_verification_key_snark, \
epoch, \
network, \
signed_entity_type_id, \
@@ -25,7 +26,7 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where
initiated_at, \
sealed_at)";
let values_columns: Vec<&str> = repeat_n(
- "(?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*)",
+ "(?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*)",
certificates_records.len(),
)
.collect();
@@ -33,6 +34,15 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where
let values: Vec = certificates_records
.into_iter()
.flat_map(|certificate_record| {
+ #[cfg(feature = "future_snark")]
+ let aggregate_verification_key_snark =
+ match certificate_record.aggregate_verification_key_snark {
+ Some(key) => Value::String(key),
+ None => Value::Null,
+ };
+ #[cfg(not(feature = "future_snark"))]
+ let aggregate_verification_key_snark = Value::Null;
+
vec![
Value::String(certificate_record.certificate_id),
match certificate_record.parent_certificate_id {
@@ -42,6 +52,7 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where
Value::String(certificate_record.message),
Value::String(certificate_record.signature),
Value::String(certificate_record.aggregate_verification_key),
+ aggregate_verification_key_snark,
Value::Integer(certificate_record.epoch.try_into().unwrap()),
Value::String(certificate_record.network),
Value::Integer(certificate_record.signed_entity_type.index() as i64),
diff --git a/mithril-aggregator/src/database/record/certificate.rs b/mithril-aggregator/src/database/record/certificate.rs
index f20416de275..df03f74ed50 100644
--- a/mithril-aggregator/src/database/record/certificate.rs
+++ b/mithril-aggregator/src/database/record/certificate.rs
@@ -3,8 +3,8 @@ use chrono::{DateTime, Utc};
use mithril_common::StdError;
use mithril_common::entities::{
Certificate, CertificateMetadata, CertificateSignature, Epoch,
- HexEncodedAggregateVerificationKey, HexEncodedKey, ProtocolMessage, ProtocolParameters,
- ProtocolVersion, SignedEntityType, StakeDistributionParty,
+ HexEncodedAggregateVerificationKey, HexEncodedKey, HexEncodedVerificationKeyForSnark,
+ ProtocolMessage, ProtocolParameters, ProtocolVersion, SignedEntityType, StakeDistributionParty,
};
use mithril_common::messages::{
CertificateListItemMessage, CertificateListItemMessageMetadata, CertificateMessage,
@@ -40,6 +40,9 @@ pub struct CertificateRecord {
/// Note: used only if signature is a multi-signature
pub aggregate_verification_key: HexEncodedAggregateVerificationKey,
+ /// Aggregate verification key for SNARK
+ pub aggregate_verification_key_snark: Option,
+
/// Epoch of creation of the certificate.
pub epoch: Epoch,
@@ -109,6 +112,7 @@ impl CertificateRecord {
aggregate_verification_key: fake_keys::aggregate_verification_key_for_concatenation()
[0]
.to_owned(),
+ aggregate_verification_key_snark: None,
epoch,
network: fake_data::network().to_string(),
signed_entity_type,
@@ -142,12 +146,22 @@ impl TryFrom for CertificateRecord {
}
};
+ #[cfg(feature = "future_snark")]
+ let aggregate_verification_key_snark = other
+ .aggregate_verification_key_snark
+ .as_ref()
+ .map(|avk| avk.to_bytes_hex())
+ .transpose()?;
+ #[cfg(not(feature = "future_snark"))]
+ let aggregate_verification_key_snark: Option = None;
+
let certificate_record = CertificateRecord {
certificate_id: other.hash,
parent_certificate_id,
message: other.signed_message,
signature,
aggregate_verification_key: other.aggregate_verification_key.to_json_hex()?,
+ aggregate_verification_key_snark,
epoch: other.epoch,
network: other.metadata.network,
signed_entity_type,
@@ -189,6 +203,12 @@ impl TryFrom for Certificate {
),
};
+ #[cfg(feature = "future_snark")]
+ let aggregate_verification_key_snark = other
+ .aggregate_verification_key_snark
+ .map(|hex| hex.as_str().try_into())
+ .transpose()?;
+
let certificate = Certificate {
hash: other.certificate_id,
previous_hash,
@@ -197,6 +217,8 @@ impl TryFrom for Certificate {
signed_message: other.protocol_message.compute_hash(),
protocol_message: other.protocol_message,
aggregate_verification_key: other.aggregate_verification_key.try_into()?,
+ #[cfg(feature = "future_snark")]
+ aggregate_verification_key_snark,
signature,
};
@@ -229,6 +251,8 @@ impl From for CertificateMessage {
protocol_message: value.protocol_message,
signed_message: value.message,
aggregate_verification_key: value.aggregate_verification_key,
+ #[cfg(feature = "future_snark")]
+ aggregate_verification_key_snark: value.aggregate_verification_key_snark,
multi_signature,
genesis_signature,
}
@@ -269,16 +293,18 @@ impl SqLiteEntity for CertificateRecord {
let message = row.read::<&str, _>(2).to_string();
let signature = row.read::<&str, _>(3).to_string();
let aggregate_verification_key = row.read::<&str, _>(4).to_string();
- let epoch_int = row.read::(5);
- let network = row.read::<&str, _>(6).to_string();
- let signed_entity_type_id = row.read::(7);
- let signed_entity_beacon_string = Hydrator::read_signed_entity_beacon_column(&row, 8);
- let protocol_version = row.read::<&str, _>(9).to_string();
- let protocol_parameters_string = row.read::<&str, _>(10);
- let protocol_message_string = row.read::<&str, _>(11);
- let signers_string = row.read::<&str, _>(12);
- let initiated_at = row.read::<&str, _>(13);
- let sealed_at = row.read::<&str, _>(14);
+ let aggregate_verification_key_snark: Option =
+ row.read::