Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 42 additions & 84 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,41 +121,75 @@ jobs:
- name: Build E2E runner
shell: bash
run: |
cargo build --release --bin mithril-end-to-end
cargo build --release --bin mithril-end-to-end ${{ inputs.additional-nodes-build-args }}

- name: Upload E2E runner
uses: actions/upload-artifact@v7
with:
name: e2e-runner
path: target/release/mithril-end-to-end

minimal-e2e:
e2e:
runs-on: ubuntu-24.04
needs: [prepare-env-variables, prepare-binaries, prepare-e2e-runner]
strategy:
fail-fast: false
matrix:
cardano_node_version: ${{ fromJSON(needs.prepare-env-variables.outputs.cardano_node_versions) }}
aggregate_signature_type: ["Concatenation"] # Todo: add "Snark" once the snark certificate chain is ready
extra_args: ["--cardano-slot-length 1.0 --cardano-epoch-length 60"]
mode:
[
"minimal-concatenation",
"minimal-snark",
"minimal-ivc-snark",
"full-concatenation",
]
include:
- mode: "minimal-concatenation"
aggregate_signature_type: "Concatenation"
run_args: "--cardano-slot-length 1.0 --cardano-epoch-length 60"
scenario_args: "minimal --signed-entity-type ${{ inputs.minimal-scenario-signed-entity-types }}"
timeout_minutes: 120
skip: false
- mode: "minimal-snark"
aggregate_signature_type: "Snark"
run_args: "--mithril-era=lagrange --cardano-slot-length 1.0 --cardano-epoch-length 600 --mithril-run-interval 60000"
scenario_args: "minimal --signed-entity-type ${{ inputs.minimal-scenario-signed-entity-types }}"
timeout_minutes: 100
skip: false
- mode: "minimal-ivc-snark"
aggregate_signature_type: "IvcSnark"
run_args: "--mithril-era=lagrange --cardano-slot-length 1.0 --cardano-epoch-length 900 --mithril-run-interval 120000"
scenario_args: "minimal --signed-entity-type ${{ inputs.minimal-scenario-signed-entity-types }}"
timeout_minutes: 150
skip: false
- mode: "full-concatenation"
aggregate_signature_type: "Concatenation"
run_args: "--cardano-slot-length 1.0 --cardano-epoch-length 60"
scenario_args: "full --signed-entity-types ${{ inputs.full-scenario-signed-entity-types }}"
timeout_minutes: 350
skip: false

steps:
- name: Checkout
if: ${{ !matrix.skip }}
uses: actions/checkout@v6

- name: Download binaries
if: ${{ !matrix.skip }}
uses: actions/download-artifact@v8
with:
name: mithril-binaries
path: ./mithril-binaries

- name: Download e2e runner
if: ${{ !matrix.skip }}
uses: actions/download-artifact@v8
with:
name: e2e-runner
path: ./mithril-binaries

- name: Prepare binaries
if: ${{ !matrix.skip }}
shell: bash
run: |
chmod +x ./mithril-binaries/mithril-aggregator
Expand All @@ -166,12 +200,13 @@ jobs:
mkdir -p artifacts

- name: Run E2E tests
if: ${{ !matrix.skip }}
uses: nick-fields/retry@v4
with:
shell: bash
max_attempts: 3
retry_on_exit_code: 2
timeout_minutes: 120
timeout_minutes: ${{ matrix.timeout_minutes }}
warning_on_retry: true
command: |
./mithril-binaries/mithril-end-to-end -vvv \
Expand All @@ -180,7 +215,7 @@ jobs:
--devnet-scripts-directory=./mithril-test-lab/cardano-devnet \
--cardano-node-version ${{ matrix.cardano_node_version }} \
--aggregate-signature-type ${{ matrix.aggregate_signature_type }} \
${{ matrix.extra_args }} minimal --signed-entity-type ${{ inputs.minimal-scenario-signed-entity-types }}
${{ matrix.run_args }} ${{ matrix.scenario_args }}

EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
Expand All @@ -197,84 +232,7 @@ jobs:
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: mithril-e2e-tests-artifacts-run_${{ github.run_number }}-attempt_${{ github.run_attempt }}-sig_type_${{ matrix.aggregate_signature_type }}-scenario_minimal-cardano-${{ matrix.cardano_node_version }}
path: |
./artifacts/*
# including node.sock makes the upload fails so exclude them:
!./artifacts/**/node.sock
# exclude cardano tools, saving ~500mb of data:
!./artifacts/devnet/bin/
if-no-files-found: error

full-e2e:
runs-on: ubuntu-24.04
needs: [prepare-env-variables, prepare-binaries, prepare-e2e-runner]
strategy:
fail-fast: false
matrix:
cardano_node_version: ${{ fromJSON(needs.prepare-env-variables.outputs.cardano_node_versions) }}
aggregate_signature_type: ["Concatenation"]
extra_args: ["--cardano-slot-length 1.0 --cardano-epoch-length 60"]

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Download binaries
uses: actions/download-artifact@v8
with:
name: mithril-binaries
path: ./mithril-binaries

- name: Download e2e runner
uses: actions/download-artifact@v8
with:
name: e2e-runner
path: ./mithril-binaries

- name: Prepare binaries
shell: bash
run: |
chmod +x ./mithril-binaries/mithril-aggregator
chmod +x ./mithril-binaries/mithril-client
chmod +x ./mithril-binaries/mithril-signer
chmod +x ./mithril-binaries/mithril-relay
chmod +x ./mithril-binaries/mithril-end-to-end
mkdir -p artifacts

- name: Run E2E tests
uses: nick-fields/retry@v4
with:
shell: bash
max_attempts: 3
retry_on_exit_code: 2
timeout_minutes: 120
warning_on_retry: true
command: |
./mithril-binaries/mithril-end-to-end -vvv \
--bin-directory ./mithril-binaries/ \
--work-directory=./artifacts \
--devnet-scripts-directory=./mithril-test-lab/cardano-devnet \
--cardano-node-version ${{ matrix.cardano_node_version }} \
--aggregate-signature-type ${{ matrix.aggregate_signature_type }} \
${{ matrix.extra_args }} full --signed-entity-types ${{ inputs.full-scenario-signed-entity-types }}

EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "RESULT=success" >> $GITHUB_ENV
elif [ $EXIT_CODE -eq 3 ]; then
echo "RESULT=incompatible" >> $GITHUB_ENV
exit 0
else
echo "RESULT=failure" >> $GITHUB_ENV
fi
exit $EXIT_CODE

- name: Upload E2E Tests Artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: mithril-e2e-tests-artifacts-run_${{ github.run_number }}-attempt_${{ github.run_attempt }}-sig_type_${{ matrix.aggregate_signature_type }}-scenario_full-cardano-${{ matrix.cardano_node_version }}
name: mithril-e2e-tests-artifacts-run_${{ github.run_number }}-attempt_${{ github.run_attempt }}-mode_${{ matrix.mode }}-cardano-${{ matrix.cardano_node_version }}
path: |
./artifacts/*
# including node.sock makes the upload fails so exclude them:
Expand Down
89 changes: 89 additions & 0 deletions mithril-aggregator/src/commands/serve_command.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "future_snark")]
use std::time::Instant;
use std::{
collections::HashMap,
net::IpAddr,
Expand All @@ -20,6 +22,11 @@ use mithril_cli_helper::{
};
use mithril_common::StdResult;
use mithril_common::entities::SignedEntityTypeDiscriminants;
#[cfg(feature = "future_snark")]
use mithril_common::{
AggregateSignatureType,
crypto_helper::{ProtocolParameters as ProverProtocolParameters, SnarkProverSetupWarmer},
};
use mithril_doc::{Documenter, DocumenterDefault, StructDoc};
use mithril_metric::MetricsServer;

Expand Down Expand Up @@ -118,6 +125,84 @@ impl Source for ServeCommand {
}

impl ServeCommand {
/// Materializes the aggregate signature prover setup in the background, so the first signing
/// round does not pay it inside its aggregation.
///
/// Best effort: on any failure the setup is materialized on first use, as it would be without
/// this. A concatenation aggregate signature has no setup to materialize.
#[cfg(feature = "future_snark")]
async fn warm_aggregate_signature_prover(
dependencies_builder: &mut DependenciesBuilder,
configuration: &ServeCommandConfiguration,
logger: &Logger,
) {
let aggregate_signature_type = configuration.aggregate_signature_type;
if aggregate_signature_type == AggregateSignatureType::Concatenation {
return;
}

let protocol_parameters = match Self::protocol_parameters_for_aggregation(
dependencies_builder,
)
.await
{
Ok(protocol_parameters) => protocol_parameters,
Err(error) => {
warn!(
logger,
"Could not read the protocol parameters to precompute the aggregate signature prover";
"error" => ?error
);
return;
}
};

info!(
logger,
"Precomputing the aggregate signature prover, which takes a few minutes; the first \
signing round waits for it if it is not finished by then";
"aggregate_signature_type" => %aggregate_signature_type
);
let logger = logger.clone();

tokio::task::spawn_blocking(move || {
let started_at = Instant::now();
match SnarkProverSetupWarmer::warm(&protocol_parameters, aggregate_signature_type) {
Ok(()) => info!(
logger, "Aggregate signature prover precomputed";
"elapsed_seconds" => started_at.elapsed().as_secs()
),
Err(error) => warn!(
logger, "Failed to precompute the aggregate signature prover";
"error" => ?error
),
}
});
}

/// Protocol parameters the current epoch aggregates with.
#[cfg(feature = "future_snark")]
async fn protocol_parameters_for_aggregation(
dependencies_builder: &mut DependenciesBuilder,
) -> StdResult<ProverProtocolParameters> {
let epoch = dependencies_builder
.get_ticker_service()
.await?
.get_current_epoch()
.await?;
let network_configuration = dependencies_builder
.get_mithril_network_configuration_provider()
.await?
.get_network_configuration(epoch)
.await?;

Ok(ProverProtocolParameters::from(
network_configuration
.configuration_for_aggregation
.protocol_parameters,
))
}

pub async fn execute(
&self,
root_logger: Logger,
Expand Down Expand Up @@ -160,6 +245,10 @@ impl ServeCommand {
)
.await?;

#[cfg(feature = "future_snark")]
Self::warm_aggregate_signature_prover(&mut dependencies_builder, &config, &root_logger)
.await;

// Start the aggregator runtime
let mut runtime = dependencies_builder
.create_aggregator_runner()
Expand Down
2 changes: 2 additions & 0 deletions mithril-common/src/crypto_helper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub use mithril_merkle_tree::{
Bytes, MKMap, MKMapKey, MKMapNode, MKMapProof, MKMapValue, MKProof, MKTree, MKTreeLeafIndexer,
MKTreeLeafPosition, MKTreeNode, MKTreeStoreInMemory, MKTreeStorer,
};
#[cfg(feature = "future_snark")]
pub use mithril_stm::SnarkProverSetupWarmer;
pub use types::*;

/// The current protocol version
Expand Down
3 changes: 2 additions & 1 deletion mithril-stm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ pub use circuits::{CIRCUIT_VERIFICATION_KEY_DIGEST_SIZE, CircuitVerificationKeyD

#[cfg(feature = "future_snark")]
pub use proof_system::{
AggregateVerificationKeyForSnark, MERKLE_TREE_DEPTH_FOR_SNARK, SnarkProof, SnarkVerifierData,
AggregateVerificationKeyForSnark, MERKLE_TREE_DEPTH_FOR_SNARK, SnarkProof,
SnarkProverSetupWarmer, SnarkVerifierData,
};

#[cfg(feature = "future_snark")]
Expand Down
4 changes: 4 additions & 0 deletions mithril-stm/src/proof_system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ mod halo2_snark;
mod snark_prover_factory;
#[cfg(feature = "future_snark")]
mod snark_setup_cache;
#[cfg(feature = "future_snark")]
mod snark_setup_warmer;

/// Serialized `ParamsVerifierKZG` (i.e. `s_g2`) from the Midnight production trusted SRS.
///
Expand Down Expand Up @@ -80,3 +82,5 @@ pub(crate) use snark_prover_factory::MockSnarkProverFactory;
pub(crate) use snark_prover_factory::{NonDeterministicSnarkProverFactory, SnarkProverFactory};
#[cfg(feature = "future_snark")]
pub(crate) use snark_setup_cache::SnarkProverSetupReuse;
#[cfg(feature = "future_snark")]
pub use snark_setup_warmer::SnarkProverSetupWarmer;
54 changes: 54 additions & 0 deletions mithril-stm/src/proof_system/snark_setup_warmer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Ahead-of-time materialization of the SNARK prover setups.
//!
//! A node materializes its prover setup on the first aggregation it runs, which is the longest part
//! of a cold start and lands inside a signing round. [`SnarkProverSetupWarmer`] does it beforehand,
//! into the same process-wide cache the provers read, so the first aggregation finds it ready.

use crate::{
AggregateSignatureType, MERKLE_TREE_DEPTH_FOR_SNARK, Parameters, StmResult,
proof_system::snark_setup_cache::SnarkProverSetupReuse,
};

/// Materializes the SNARK prover setups a node needs, ahead of its first aggregation.
pub struct SnarkProverSetupWarmer;

impl SnarkProverSetupWarmer {
/// Materializes the setups `aggregate_signature_type` needs for `parameters` into the cache the
/// provers read, deriving and storing their keys on a miss. Nothing to materialize for a
/// concatenation aggregate signature.
///
/// The setups are kept resident, since the process calling this is the one that will aggregate.
pub fn warm(
parameters: &Parameters,
aggregate_signature_type: AggregateSignatureType,
) -> StmResult<()> {
match aggregate_signature_type {
AggregateSignatureType::Concatenation => (),
AggregateSignatureType::Snark => {
SnarkProverSetupReuse::Enabled
.certificate_setup(parameters, MERKLE_TREE_DEPTH_FOR_SNARK)?;
}
AggregateSignatureType::IvcSnark => {
SnarkProverSetupReuse::Enabled.ivc_setup(parameters)?;
}
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn concatenation_has_no_setup_to_derive() {
let parameters = Parameters {
m: 9,
k: 5,
phi_f: 0.95,
};

SnarkProverSetupWarmer::warm(&parameters, AggregateSignatureType::Concatenation).unwrap();
}
}
Loading