Skip to content

Commit cd56f53

Browse files
authored
Merge pull request #1129 from input-output-hk/djo/1097/add-signers-registered-route-to-aggregator
Add signers registered route to aggregator
2 parents d4ac66d + b282f94 commit cd56f53

File tree

14 files changed

+448
-41
lines changed

14 files changed

+448
-41
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-aggregator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.3.65"
3+
version = "0.3.66"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/database/provider/signer_registration.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use mithril_common::{
88
crypto_helper::KESPeriod,
99
entities::{
1010
Epoch, HexEncodedOpCert, HexEncodedVerificationKey, HexEncodedVerificationKeySignature,
11-
PartyId, Signer, SignerWithStake, Stake,
11+
PartyId, Signer, SignerWithStake, Stake, StakeDistribution,
1212
},
1313
sqlite::{
1414
EntityCursor, HydrationError, Projection, Provider, SourceAlias, SqLiteEntity,
@@ -458,6 +458,26 @@ impl VerificationKeyStorer for SignerRegistrationStore {
458458

459459
Ok(())
460460
}
461+
462+
async fn get_stake_distribution_for_epoch(
463+
&self,
464+
epoch: Epoch,
465+
) -> Result<Option<StakeDistribution>, StoreError> {
466+
let connection = &*self.connection.lock().await;
467+
let provider = SignerRegistrationRecordProvider::new(connection);
468+
let cursor = provider
469+
.get_by_epoch(&epoch)
470+
.map_err(|e| AdapterError::GeneralError(format!("{e}")))?;
471+
472+
let stake_distribution = StakeDistribution::from_iter(
473+
cursor.map(|r| (r.signer_id, r.stake.unwrap_or_default())),
474+
);
475+
476+
match stake_distribution.is_empty() {
477+
true => Ok(None),
478+
false => Ok(Some(stake_distribution)),
479+
}
480+
}
461481
}
462482

463483
#[cfg(test)]

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;

0 commit comments

Comments
 (0)