Skip to content

Commit

Permalink
Define & add /signers/registered/{epoch} to aggregator
Browse files Browse the repository at this point in the history
  • Loading branch information
Alenar committed Aug 3, 2023
1 parent 61317d2 commit b22b80a
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 29 deletions.
4 changes: 4 additions & 0 deletions mithril-aggregator/src/entities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
//!
//! This module provide domain entities for the services & state machine.
mod open_message;
mod signer_registration_message;

pub use open_message::OpenMessage;
pub use signer_registration_message::{
SignerRegistrationsListItemMessage, SignerRegistrationsMessage,
};
41 changes: 41 additions & 0 deletions mithril-aggregator/src/entities/signer_registration_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use mithril_common::entities::{Epoch, PartyId, Stake, StakeDistribution};
use serde::{Deserialize, Serialize};

/// Message structure of signer registrations for an epoch.
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct SignerRegistrationsMessage {
/// The epoch at which the registration was sent.
pub registered_at: Epoch,

/// The epoch at which the registration was able to send signatures.
pub signing_at: Epoch,

/// The signer registrations
pub registrations: Vec<SignerRegistrationsListItemMessage>,
}

/// Message structure of a signer registration
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct SignerRegistrationsListItemMessage {
/// The registered signer party id
pub party_id: PartyId,

/// The registered signer stake
pub stake: Stake,
}

impl SignerRegistrationsMessage {
/// Build a [SignerRegistrationsMessage] from a [stake distribution][StakeDistribution].
pub fn new(registered_at: Epoch, stake_distribution: StakeDistribution) -> Self {
let registrations: Vec<SignerRegistrationsListItemMessage> = stake_distribution
.into_iter()
.map(|(party_id, stake)| SignerRegistrationsListItemMessage { party_id, stake })
.collect();

Self {
registered_at,
signing_at: registered_at.offset_to_signer_signing_offset(),
registrations,
}
}
}
9 changes: 8 additions & 1 deletion mithril-aggregator/src/http_server/routes/middlewares.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
services::CertifierService,
services::{SignedEntityService, TickerService},
CertificatePendingStore, Configuration, DependencyContainer, ProtocolParametersStorer,
SignerRegisterer,
SignerRegisterer, VerificationKeyStorer,
};

use mithril_common::BeaconProvider;
Expand Down Expand Up @@ -81,3 +81,10 @@ pub fn with_signed_entity_service(
) -> impl Filter<Extract = (Arc<dyn SignedEntityService>,), Error = Infallible> + Clone {
warp::any().map(move || dependency_manager.signed_entity_service.clone())
}

/// With verification key store
pub fn with_verification_key_store(
dependency_manager: Arc<DependencyContainer>,
) -> impl Filter<Extract = (Arc<dyn VerificationKeyStorer>,), Error = Infallible> + Clone {
warp::any().map(move || dependency_manager.verification_key_store.clone())
}
205 changes: 194 additions & 11 deletions mithril-aggregator/src/http_server/routes/signer_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const MITHRIL_SIGNER_VERSION_HEADER: &str = "signer-node-version";
pub fn routes(
dependency_manager: Arc<DependencyContainer>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
register_signer(dependency_manager)
register_signer(dependency_manager.clone()).or(registered_signers(dependency_manager))
}

/// POST /register-signer
Expand All @@ -31,10 +31,22 @@ fn register_signer(
.and_then(handlers::register_signer)
}

/// Get /signers/registered/:epoch
fn registered_signers(
dependency_manager: Arc<DependencyContainer>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
warp::path!("signers" / "registered" / String)
.and(warp::get())
.and(middlewares::with_verification_key_store(dependency_manager))
.and_then(handlers::registered_signers)
}

mod handlers {
use crate::entities::SignerRegistrationsMessage;
use crate::event_store::{EventMessage, TransmitterService};
use crate::FromRegisterSignerAdapter;
use crate::{http_server::routes::reply, SignerRegisterer, SignerRegistrationError};
use crate::{FromRegisterSignerAdapter, VerificationKeyStorer};
use mithril_common::entities::Epoch;
use mithril_common::messages::{RegisterSignerMessage, TryFromMessageAdapter};
use mithril_common::BeaconProvider;
use slog_scope::{debug, warn};
Expand Down Expand Up @@ -139,21 +151,68 @@ mod handlers {
}
}
}

/// Get Registered Signers for a given epoch
pub async fn registered_signers(
registered_at: String,
verification_key_store: Arc<dyn VerificationKeyStorer>,
) -> Result<impl warp::Reply, Infallible> {
debug!("⇄ HTTP SERVER: signers/registered/{:?}", registered_at);

let registered_at = match registered_at.parse::<u64>() {
Ok(epoch) => Epoch(epoch),
Err(err) => {
warn!("registered_signers::invalid_epoch"; "error" => ?err);
return Ok(reply::bad_request(
"invalid_epoch".to_string(),
err.to_string(),
));
}
};

// The given epoch is the epoch at which the signer registered, the store works on
// the recording epoch so we need to offset.
match verification_key_store
.get_stake_distribution_for_epoch(registered_at.offset_to_recording_epoch())
.await
{
Ok(Some(stake_distribution)) => {
let message = SignerRegistrationsMessage::new(registered_at, stake_distribution);
Ok(reply::json(&message, StatusCode::OK))
}
Ok(None) => {
warn!("registered_signers::not_found");
Ok(reply::empty(StatusCode::NOT_FOUND))
}
Err(err) => {
warn!("registered_signers::error"; "error" => ?err);
Ok(reply::internal_server_error(err.to_string()))
}
}
}
}

#[cfg(test)]
mod tests {
use mithril_common::crypto_helper::ProtocolRegistrationError;
use mithril_common::messages::RegisterSignerMessage;
use mithril_common::test_utils::apispec::APISpec;
use mithril_common::test_utils::fake_data;
use warp::http::Method;
use warp::test::request;
use mithril_common::entities::Epoch;
use mithril_common::{
crypto_helper::ProtocolRegistrationError,
entities::StakeDistribution,
messages::RegisterSignerMessage,
store::adapter::AdapterError,
test_utils::{apispec::APISpec, fake_data},
};
use mockall::predicate::eq;
use serde_json::Value::Null;
use warp::{http::Method, test::request};

use crate::{
http_server::SERVER_BASE_PATH, initialize_dependencies,
signer_registerer::MockSignerRegisterer, store::MockVerificationKeyStorer,
SignerRegistrationError,
};

use super::*;
use crate::http_server::SERVER_BASE_PATH;
use crate::signer_registerer::MockSignerRegisterer;
use crate::{initialize_dependencies, SignerRegistrationError};

fn setup_router(
dependency_manager: Arc<DependencyContainer>,
Expand Down Expand Up @@ -316,4 +375,128 @@ mod tests {
&response,
);
}

#[tokio::test]
async fn test_registered_signers_get_offset_given_epoch_to_registration_epoch() {
let asked_epoch = Epoch(1);
let expected_retrieval_epoch = asked_epoch.offset_to_recording_epoch();
let stake_distribution = StakeDistribution::from_iter(
fake_data::signers_with_stakes(3)
.into_iter()
.map(|s| (s.party_id, s.stake)),
);
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
mock_verification_key_store
.expect_get_stake_distribution_for_epoch()
.with(eq(expected_retrieval_epoch))
.return_once(|_| Ok(Some(stake_distribution)))
.once();
let mut dependency_manager = initialize_dependencies().await;
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);

let method = Method::GET.as_str();
let base_path = "/signers/registered";

let response = request()
.method(method)
.path(&format!("/{SERVER_BASE_PATH}{base_path}/{}", asked_epoch))
.reply(&setup_router(Arc::new(dependency_manager)))
.await;

assert!(
response.status().is_success(),
"expected the response to succeed, was: {response:#?}"
);
}

#[tokio::test]
async fn test_registered_signers_get_ok() {
let stake_distribution = StakeDistribution::from_iter(
fake_data::signers_with_stakes(3)
.into_iter()
.map(|s| (s.party_id, s.stake)),
);
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
mock_verification_key_store
.expect_get_stake_distribution_for_epoch()
.return_once(|_| Ok(Some(stake_distribution)))
.once();
let mut dependency_manager = initialize_dependencies().await;
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);

let base_path = "/signers/registered";
let method = Method::GET.as_str();

let response = request()
.method(method)
.path(&format!("/{SERVER_BASE_PATH}{base_path}/1"))
.reply(&setup_router(Arc::new(dependency_manager)))
.await;

APISpec::verify_conformity(
APISpec::get_all_spec_files(),
method,
&format!("{base_path}/{{epoch}}"),
"application/json",
&Null,
&response,
);
}

#[tokio::test]
async fn test_registered_signers_get_ok_noregistration() {
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
mock_verification_key_store
.expect_get_stake_distribution_for_epoch()
.return_once(|_| Ok(None))
.once();
let mut dependency_manager = initialize_dependencies().await;
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);

let method = Method::GET.as_str();
let base_path = "/signers/registered";

let response = request()
.method(method)
.path(&format!("/{SERVER_BASE_PATH}{base_path}/3"))
.reply(&setup_router(Arc::new(dependency_manager)))
.await;

APISpec::verify_conformity(
APISpec::get_all_spec_files(),
method,
&format!("{base_path}/{{epoch}}"),
"application/json",
&Null,
&response,
);
}

#[tokio::test]
async fn test_registered_signers_get_ko() {
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
mock_verification_key_store
.expect_get_stake_distribution_for_epoch()
.return_once(|_| Err(AdapterError::GeneralError("invalid query".to_string()).into()));
let mut dependency_manager = initialize_dependencies().await;
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);

let method = Method::GET.as_str();
let base_path = "/signers/registered";

let response = request()
.method(method)
.path(&format!("/{SERVER_BASE_PATH}{base_path}/1"))
.reply(&setup_router(Arc::new(dependency_manager)))
.await;

APISpec::verify_conformity(
APISpec::get_all_spec_files(),
method,
&format!("{base_path}/{{epoch}}"),
"application/json",
&Null,
&response,
);
}
}
2 changes: 2 additions & 0 deletions mithril-aggregator/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ pub use verification_key_store::{VerificationKeyStore, VerificationKeyStorer};
pub use verification_key_store::test_suite as verification_key_store_test_suite;
#[cfg(test)]
pub(crate) use verification_key_store::test_verification_key_storer;
#[cfg(test)]
pub use verification_key_store::MockVerificationKeyStorer;
4 changes: 4 additions & 0 deletions mithril-aggregator/src/store/verification_key_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ use tokio::sync::RwLock;
use mithril_common::entities::{Epoch, PartyId, Signer, SignerWithStake, StakeDistribution};
use mithril_common::store::{adapter::StoreAdapter, StoreError};

#[cfg(test)]
use mockall::automock;

type Adapter = Box<dyn StoreAdapter<Key = Epoch, Record = HashMap<PartyId, SignerWithStake>>>;

/// Store and get signers verification keys for given epoch.
///
/// Important note: This store works on the **recording** epoch, the epoch at which the signers
/// are signed into a certificate so they can sign single signatures at the next epoch.
#[cfg_attr(test, automock)]
#[async_trait]
pub trait VerificationKeyStorer: Sync + Send {
/// Save the verification key, for the given [Signer] for the given [Epoch], returns the
Expand Down
9 changes: 9 additions & 0 deletions mithril-common/src/entities/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ impl Epoch {
/// The epoch offset used for aggregator protocol parameters recording.
pub const PROTOCOL_PARAMETERS_RECORDING_OFFSET: u64 = 2;

/// The epoch offset used to retrieve, given the epoch at which a signer registered, the epoch
/// at which the signer can send single signatures.
pub const SIGNER_SIGNING_OFFSET: u64 = 2;

/// Computes a new Epoch by applying an epoch offset.
///
/// Will fail if the computed epoch is negative.
Expand Down Expand Up @@ -60,6 +64,11 @@ impl Epoch {
*self + Self::PROTOCOL_PARAMETERS_RECORDING_OFFSET
}

/// Apply the [signer signing offset][Self::SIGNER_SIGNING_OFFSET] to this epoch
pub fn offset_to_signer_signing_offset(&self) -> Self {
*self + Self::SIGNER_SIGNING_OFFSET
}

/// Computes the next Epoch
pub fn next(&self) -> Self {
*self + 1
Expand Down
Loading

0 comments on commit b22b80a

Please sign in to comment.