Skip to content

Commit b22b80a

Browse files
committed
Define & add /signers/registered/{epoch} to aggregator
1 parent 61317d2 commit b22b80a

File tree

8 files changed

+352
-29
lines changed

8 files changed

+352
-29
lines changed

mithril-aggregator/src/entities/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@
22
//!
33
//! This module provide domain entities for the services & state machine.
44
mod open_message;
5+
mod signer_registration_message;
56

67
pub use open_message::OpenMessage;
8+
pub use signer_registration_message::{
9+
SignerRegistrationsListItemMessage, SignerRegistrationsMessage,
10+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use mithril_common::entities::{Epoch, PartyId, Stake, StakeDistribution};
2+
use serde::{Deserialize, Serialize};
3+
4+
/// Message structure of signer registrations for an epoch.
5+
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
6+
pub struct SignerRegistrationsMessage {
7+
/// The epoch at which the registration was sent.
8+
pub registered_at: Epoch,
9+
10+
/// The epoch at which the registration was able to send signatures.
11+
pub signing_at: Epoch,
12+
13+
/// The signer registrations
14+
pub registrations: Vec<SignerRegistrationsListItemMessage>,
15+
}
16+
17+
/// Message structure of a signer registration
18+
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
19+
pub struct SignerRegistrationsListItemMessage {
20+
/// The registered signer party id
21+
pub party_id: PartyId,
22+
23+
/// The registered signer stake
24+
pub stake: Stake,
25+
}
26+
27+
impl SignerRegistrationsMessage {
28+
/// Build a [SignerRegistrationsMessage] from a [stake distribution][StakeDistribution].
29+
pub fn new(registered_at: Epoch, stake_distribution: StakeDistribution) -> Self {
30+
let registrations: Vec<SignerRegistrationsListItemMessage> = stake_distribution
31+
.into_iter()
32+
.map(|(party_id, stake)| SignerRegistrationsListItemMessage { party_id, stake })
33+
.collect();
34+
35+
Self {
36+
registered_at,
37+
signing_at: registered_at.offset_to_signer_signing_offset(),
38+
registrations,
39+
}
40+
}
41+
}

mithril-aggregator/src/http_server/routes/middlewares.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
services::CertifierService,
55
services::{SignedEntityService, TickerService},
66
CertificatePendingStore, Configuration, DependencyContainer, ProtocolParametersStorer,
7-
SignerRegisterer,
7+
SignerRegisterer, VerificationKeyStorer,
88
};
99

1010
use mithril_common::BeaconProvider;
@@ -81,3 +81,10 @@ pub fn with_signed_entity_service(
8181
) -> impl Filter<Extract = (Arc<dyn SignedEntityService>,), Error = Infallible> + Clone {
8282
warp::any().map(move || dependency_manager.signed_entity_service.clone())
8383
}
84+
85+
/// With verification key store
86+
pub fn with_verification_key_store(
87+
dependency_manager: Arc<DependencyContainer>,
88+
) -> impl Filter<Extract = (Arc<dyn VerificationKeyStorer>,), Error = Infallible> + Clone {
89+
warp::any().map(move || dependency_manager.verification_key_store.clone())
90+
}

mithril-aggregator/src/http_server/routes/signer_routes.rs

Lines changed: 194 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const MITHRIL_SIGNER_VERSION_HEADER: &str = "signer-node-version";
88
pub fn routes(
99
dependency_manager: Arc<DependencyContainer>,
1010
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
11-
register_signer(dependency_manager)
11+
register_signer(dependency_manager.clone()).or(registered_signers(dependency_manager))
1212
}
1313

1414
/// POST /register-signer
@@ -31,10 +31,22 @@ fn register_signer(
3131
.and_then(handlers::register_signer)
3232
}
3333

34+
/// Get /signers/registered/:epoch
35+
fn registered_signers(
36+
dependency_manager: Arc<DependencyContainer>,
37+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
38+
warp::path!("signers" / "registered" / String)
39+
.and(warp::get())
40+
.and(middlewares::with_verification_key_store(dependency_manager))
41+
.and_then(handlers::registered_signers)
42+
}
43+
3444
mod handlers {
45+
use crate::entities::SignerRegistrationsMessage;
3546
use crate::event_store::{EventMessage, TransmitterService};
36-
use crate::FromRegisterSignerAdapter;
3747
use crate::{http_server::routes::reply, SignerRegisterer, SignerRegistrationError};
48+
use crate::{FromRegisterSignerAdapter, VerificationKeyStorer};
49+
use mithril_common::entities::Epoch;
3850
use mithril_common::messages::{RegisterSignerMessage, TryFromMessageAdapter};
3951
use mithril_common::BeaconProvider;
4052
use slog_scope::{debug, warn};
@@ -139,21 +151,68 @@ mod handlers {
139151
}
140152
}
141153
}
154+
155+
/// Get Registered Signers for a given epoch
156+
pub async fn registered_signers(
157+
registered_at: String,
158+
verification_key_store: Arc<dyn VerificationKeyStorer>,
159+
) -> Result<impl warp::Reply, Infallible> {
160+
debug!("⇄ HTTP SERVER: signers/registered/{:?}", registered_at);
161+
162+
let registered_at = match registered_at.parse::<u64>() {
163+
Ok(epoch) => Epoch(epoch),
164+
Err(err) => {
165+
warn!("registered_signers::invalid_epoch"; "error" => ?err);
166+
return Ok(reply::bad_request(
167+
"invalid_epoch".to_string(),
168+
err.to_string(),
169+
));
170+
}
171+
};
172+
173+
// The given epoch is the epoch at which the signer registered, the store works on
174+
// the recording epoch so we need to offset.
175+
match verification_key_store
176+
.get_stake_distribution_for_epoch(registered_at.offset_to_recording_epoch())
177+
.await
178+
{
179+
Ok(Some(stake_distribution)) => {
180+
let message = SignerRegistrationsMessage::new(registered_at, stake_distribution);
181+
Ok(reply::json(&message, StatusCode::OK))
182+
}
183+
Ok(None) => {
184+
warn!("registered_signers::not_found");
185+
Ok(reply::empty(StatusCode::NOT_FOUND))
186+
}
187+
Err(err) => {
188+
warn!("registered_signers::error"; "error" => ?err);
189+
Ok(reply::internal_server_error(err.to_string()))
190+
}
191+
}
192+
}
142193
}
143194

144195
#[cfg(test)]
145196
mod tests {
146-
use mithril_common::crypto_helper::ProtocolRegistrationError;
147-
use mithril_common::messages::RegisterSignerMessage;
148-
use mithril_common::test_utils::apispec::APISpec;
149-
use mithril_common::test_utils::fake_data;
150-
use warp::http::Method;
151-
use warp::test::request;
197+
use mithril_common::entities::Epoch;
198+
use mithril_common::{
199+
crypto_helper::ProtocolRegistrationError,
200+
entities::StakeDistribution,
201+
messages::RegisterSignerMessage,
202+
store::adapter::AdapterError,
203+
test_utils::{apispec::APISpec, fake_data},
204+
};
205+
use mockall::predicate::eq;
206+
use serde_json::Value::Null;
207+
use warp::{http::Method, test::request};
208+
209+
use crate::{
210+
http_server::SERVER_BASE_PATH, initialize_dependencies,
211+
signer_registerer::MockSignerRegisterer, store::MockVerificationKeyStorer,
212+
SignerRegistrationError,
213+
};
152214

153215
use super::*;
154-
use crate::http_server::SERVER_BASE_PATH;
155-
use crate::signer_registerer::MockSignerRegisterer;
156-
use crate::{initialize_dependencies, SignerRegistrationError};
157216

158217
fn setup_router(
159218
dependency_manager: Arc<DependencyContainer>,
@@ -316,4 +375,128 @@ mod tests {
316375
&response,
317376
);
318377
}
378+
379+
#[tokio::test]
380+
async fn test_registered_signers_get_offset_given_epoch_to_registration_epoch() {
381+
let asked_epoch = Epoch(1);
382+
let expected_retrieval_epoch = asked_epoch.offset_to_recording_epoch();
383+
let stake_distribution = StakeDistribution::from_iter(
384+
fake_data::signers_with_stakes(3)
385+
.into_iter()
386+
.map(|s| (s.party_id, s.stake)),
387+
);
388+
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
389+
mock_verification_key_store
390+
.expect_get_stake_distribution_for_epoch()
391+
.with(eq(expected_retrieval_epoch))
392+
.return_once(|_| Ok(Some(stake_distribution)))
393+
.once();
394+
let mut dependency_manager = initialize_dependencies().await;
395+
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
396+
397+
let method = Method::GET.as_str();
398+
let base_path = "/signers/registered";
399+
400+
let response = request()
401+
.method(method)
402+
.path(&format!("/{SERVER_BASE_PATH}{base_path}/{}", asked_epoch))
403+
.reply(&setup_router(Arc::new(dependency_manager)))
404+
.await;
405+
406+
assert!(
407+
response.status().is_success(),
408+
"expected the response to succeed, was: {response:#?}"
409+
);
410+
}
411+
412+
#[tokio::test]
413+
async fn test_registered_signers_get_ok() {
414+
let stake_distribution = StakeDistribution::from_iter(
415+
fake_data::signers_with_stakes(3)
416+
.into_iter()
417+
.map(|s| (s.party_id, s.stake)),
418+
);
419+
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
420+
mock_verification_key_store
421+
.expect_get_stake_distribution_for_epoch()
422+
.return_once(|_| Ok(Some(stake_distribution)))
423+
.once();
424+
let mut dependency_manager = initialize_dependencies().await;
425+
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
426+
427+
let base_path = "/signers/registered";
428+
let method = Method::GET.as_str();
429+
430+
let response = request()
431+
.method(method)
432+
.path(&format!("/{SERVER_BASE_PATH}{base_path}/1"))
433+
.reply(&setup_router(Arc::new(dependency_manager)))
434+
.await;
435+
436+
APISpec::verify_conformity(
437+
APISpec::get_all_spec_files(),
438+
method,
439+
&format!("{base_path}/{{epoch}}"),
440+
"application/json",
441+
&Null,
442+
&response,
443+
);
444+
}
445+
446+
#[tokio::test]
447+
async fn test_registered_signers_get_ok_noregistration() {
448+
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
449+
mock_verification_key_store
450+
.expect_get_stake_distribution_for_epoch()
451+
.return_once(|_| Ok(None))
452+
.once();
453+
let mut dependency_manager = initialize_dependencies().await;
454+
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
455+
456+
let method = Method::GET.as_str();
457+
let base_path = "/signers/registered";
458+
459+
let response = request()
460+
.method(method)
461+
.path(&format!("/{SERVER_BASE_PATH}{base_path}/3"))
462+
.reply(&setup_router(Arc::new(dependency_manager)))
463+
.await;
464+
465+
APISpec::verify_conformity(
466+
APISpec::get_all_spec_files(),
467+
method,
468+
&format!("{base_path}/{{epoch}}"),
469+
"application/json",
470+
&Null,
471+
&response,
472+
);
473+
}
474+
475+
#[tokio::test]
476+
async fn test_registered_signers_get_ko() {
477+
let mut mock_verification_key_store = MockVerificationKeyStorer::new();
478+
mock_verification_key_store
479+
.expect_get_stake_distribution_for_epoch()
480+
.return_once(|_| Err(AdapterError::GeneralError("invalid query".to_string()).into()));
481+
let mut dependency_manager = initialize_dependencies().await;
482+
dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
483+
484+
let method = Method::GET.as_str();
485+
let base_path = "/signers/registered";
486+
487+
let response = request()
488+
.method(method)
489+
.path(&format!("/{SERVER_BASE_PATH}{base_path}/1"))
490+
.reply(&setup_router(Arc::new(dependency_manager)))
491+
.await;
492+
493+
APISpec::verify_conformity(
494+
APISpec::get_all_spec_files(),
495+
method,
496+
&format!("{base_path}/{{epoch}}"),
497+
"application/json",
498+
&Null,
499+
&response,
500+
);
501+
}
319502
}

mithril-aggregator/src/store/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ pub use verification_key_store::{VerificationKeyStore, VerificationKeyStorer};
1010
pub use verification_key_store::test_suite as verification_key_store_test_suite;
1111
#[cfg(test)]
1212
pub(crate) use verification_key_store::test_verification_key_storer;
13+
#[cfg(test)]
14+
pub use verification_key_store::MockVerificationKeyStorer;

mithril-aggregator/src/store/verification_key_store.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ use tokio::sync::RwLock;
55
use mithril_common::entities::{Epoch, PartyId, Signer, SignerWithStake, StakeDistribution};
66
use mithril_common::store::{adapter::StoreAdapter, StoreError};
77

8+
#[cfg(test)]
9+
use mockall::automock;
10+
811
type Adapter = Box<dyn StoreAdapter<Key = Epoch, Record = HashMap<PartyId, SignerWithStake>>>;
912

1013
/// Store and get signers verification keys for given epoch.
1114
///
1215
/// Important note: This store works on the **recording** epoch, the epoch at which the signers
1316
/// are signed into a certificate so they can sign single signatures at the next epoch.
17+
#[cfg_attr(test, automock)]
1418
#[async_trait]
1519
pub trait VerificationKeyStorer: Sync + Send {
1620
/// Save the verification key, for the given [Signer] for the given [Epoch], returns the

mithril-common/src/entities/epoch.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ impl Epoch {
2929
/// The epoch offset used for aggregator protocol parameters recording.
3030
pub const PROTOCOL_PARAMETERS_RECORDING_OFFSET: u64 = 2;
3131

32+
/// The epoch offset used to retrieve, given the epoch at which a signer registered, the epoch
33+
/// at which the signer can send single signatures.
34+
pub const SIGNER_SIGNING_OFFSET: u64 = 2;
35+
3236
/// Computes a new Epoch by applying an epoch offset.
3337
///
3438
/// Will fail if the computed epoch is negative.
@@ -60,6 +64,11 @@ impl Epoch {
6064
*self + Self::PROTOCOL_PARAMETERS_RECORDING_OFFSET
6165
}
6266

67+
/// Apply the [signer signing offset][Self::SIGNER_SIGNING_OFFSET] to this epoch
68+
pub fn offset_to_signer_signing_offset(&self) -> Self {
69+
*self + Self::SIGNER_SIGNING_OFFSET
70+
}
71+
6372
/// Computes the next Epoch
6473
pub fn next(&self) -> Self {
6574
*self + 1

0 commit comments

Comments
 (0)