Skip to content

Commit 8101ed2

Browse files
committed
feat(client-cli): add a 'aggregator-discovery' command
1 parent 6258d53 commit 8101ed2

File tree

6 files changed

+284
-32
lines changed

6 files changed

+284
-32
lines changed

internal/mithril-aggregator-discovery/src/model.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ impl MithrilNetwork {
2525
}
2626
}
2727

28+
impl From<String> for MithrilNetwork {
29+
fn from(name: String) -> Self {
30+
MithrilNetwork::new(name)
31+
}
32+
}
33+
2834
/// Representation of an aggregator endpoint
2935
#[derive(Debug, Clone, PartialEq, Eq)]
3036
pub struct AggregatorEndpoint {
@@ -57,3 +63,9 @@ impl From<AggregatorEndpoint> for String {
5763
endpoint.url
5864
}
5965
}
66+
67+
impl std::fmt::Display for AggregatorEndpoint {
68+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69+
write!(f, "{}", self.url)
70+
}
71+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use clap::Parser;
2+
3+
use mithril_client::{
4+
AggregatorDiscoveryType, ClientBuilder, MithrilNetwork, MithrilResult,
5+
RequiredAggregatorCapabilities,
6+
common::{AggregateSignatureType, SignedEntityTypeDiscriminants},
7+
};
8+
9+
/// Clap command to select an aggregator from the available ones with automatic discovery.
10+
#[derive(Parser, Debug, Clone)]
11+
pub struct AggregatorSelectCommand {
12+
/// Path to the Cardano node database directory.
13+
#[clap(long)]
14+
network: MithrilNetwork,
15+
16+
/// Maximum number of entries to retrieve
17+
#[clap(long, default_value_t = 1)]
18+
max_entries: usize,
19+
20+
/// Signed entity types to consider for the discovery
21+
///
22+
/// If not provided, all signed entity types are considered.
23+
#[clap(long, value_parser, num_args = 0.., value_delimiter = ',')]
24+
signed_entity_types: Vec<SignedEntityTypeDiscriminants>,
25+
26+
/// Aggregate signature types to consider for the discovery
27+
///
28+
/// If not provided, all aggregate signature types are considered.
29+
#[clap(long, value_parser, num_args = 0.., value_delimiter = ',')]
30+
aggregate_signature_types: Vec<AggregateSignatureType>,
31+
}
32+
33+
impl AggregatorSelectCommand {
34+
/// Main command execution
35+
pub async fn execute(&self) -> MithrilResult<()> {
36+
let required_capabilities = self.build_required_capabilities();
37+
let client_builder =
38+
ClientBuilder::new(AggregatorDiscoveryType::Automatic(self.network.clone()))
39+
.with_capabilities(required_capabilities)
40+
.with_default_aggregator_discoverer();
41+
let aggregator_endpoints = client_builder
42+
.discover_aggregator(&self.network)?
43+
.take(self.max_entries);
44+
45+
println!(
46+
"Discovering at most {} aggregator endpoints:",
47+
self.max_entries,
48+
);
49+
let mut found_aggregators = 0;
50+
for endpoint in aggregator_endpoints {
51+
println!("- Found: {endpoint}");
52+
found_aggregators += 1;
53+
}
54+
if found_aggregators == 0 {
55+
println!("- No aggregator endpoint found matching the requirements.");
56+
}
57+
58+
Ok(())
59+
}
60+
61+
fn build_required_capabilities(&self) -> RequiredAggregatorCapabilities {
62+
if self.signed_entity_types.is_empty() && self.aggregate_signature_types.is_empty() {
63+
return RequiredAggregatorCapabilities::All;
64+
}
65+
66+
let mut required_capabilities = vec![];
67+
if !self.signed_entity_types.is_empty() {
68+
let mut required_capabilities_signed_entity_types = vec![];
69+
for signed_entity_type in &self.signed_entity_types {
70+
required_capabilities_signed_entity_types.push(
71+
RequiredAggregatorCapabilities::SignedEntityType(*signed_entity_type),
72+
);
73+
}
74+
required_capabilities.push(RequiredAggregatorCapabilities::Or(
75+
required_capabilities_signed_entity_types,
76+
));
77+
}
78+
79+
if !self.aggregate_signature_types.is_empty() {
80+
let mut required_capabilities_aggregate_signature_types = vec![];
81+
for aggregate_signature_type in &self.aggregate_signature_types {
82+
required_capabilities_aggregate_signature_types.push(
83+
RequiredAggregatorCapabilities::AggregateSignatureType(
84+
*aggregate_signature_type,
85+
),
86+
);
87+
}
88+
required_capabilities.push(RequiredAggregatorCapabilities::Or(
89+
required_capabilities_aggregate_signature_types,
90+
));
91+
}
92+
if required_capabilities.len() == 1 {
93+
return required_capabilities.into_iter().next().unwrap();
94+
} else {
95+
return RequiredAggregatorCapabilities::And(required_capabilities);
96+
}
97+
}
98+
}
99+
100+
#[cfg(test)]
101+
mod tests {
102+
use super::*;
103+
use mithril_client::common::SignedEntityTypeDiscriminants;
104+
105+
#[test]
106+
fn test_build_required_capabilities_all() {
107+
let command = AggregatorSelectCommand {
108+
network: MithrilNetwork::dummy(),
109+
max_entries: 1,
110+
signed_entity_types: vec![],
111+
aggregate_signature_types: vec![],
112+
};
113+
114+
let required_capabilities = command.build_required_capabilities();
115+
assert_eq!(required_capabilities, RequiredAggregatorCapabilities::All);
116+
}
117+
118+
#[test]
119+
fn test_build_required_capabilities_signed_entity_types() {
120+
let command = AggregatorSelectCommand {
121+
network: MithrilNetwork::dummy(),
122+
max_entries: 1,
123+
signed_entity_types: vec![
124+
SignedEntityTypeDiscriminants::CardanoTransactions,
125+
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
126+
],
127+
aggregate_signature_types: vec![],
128+
};
129+
130+
let required_capabilities = command.build_required_capabilities();
131+
132+
assert_eq!(
133+
required_capabilities,
134+
RequiredAggregatorCapabilities::Or(vec![
135+
RequiredAggregatorCapabilities::SignedEntityType(
136+
SignedEntityTypeDiscriminants::CardanoTransactions
137+
),
138+
RequiredAggregatorCapabilities::SignedEntityType(
139+
SignedEntityTypeDiscriminants::CardanoStakeDistribution
140+
),
141+
])
142+
);
143+
}
144+
145+
#[test]
146+
fn test_build_required_capabilities_aggregate_signature_types() {
147+
let command = AggregatorSelectCommand {
148+
network: MithrilNetwork::dummy(),
149+
max_entries: 1,
150+
signed_entity_types: vec![],
151+
aggregate_signature_types: vec![AggregateSignatureType::Concatenation],
152+
};
153+
let required_capabilities = command.build_required_capabilities();
154+
155+
assert_eq!(
156+
required_capabilities,
157+
RequiredAggregatorCapabilities::Or(vec![
158+
RequiredAggregatorCapabilities::AggregateSignatureType(
159+
AggregateSignatureType::Concatenation
160+
),
161+
])
162+
);
163+
}
164+
165+
#[test]
166+
fn test_build_required_capabilities_both() {
167+
let command = AggregatorSelectCommand {
168+
network: MithrilNetwork::dummy(),
169+
max_entries: 1,
170+
signed_entity_types: vec![
171+
SignedEntityTypeDiscriminants::CardanoTransactions,
172+
SignedEntityTypeDiscriminants::CardanoStakeDistribution,
173+
],
174+
aggregate_signature_types: vec![AggregateSignatureType::Concatenation],
175+
};
176+
let required_capabilities = command.build_required_capabilities();
177+
178+
assert_eq!(
179+
required_capabilities,
180+
RequiredAggregatorCapabilities::And(vec![
181+
RequiredAggregatorCapabilities::Or(vec![
182+
RequiredAggregatorCapabilities::SignedEntityType(
183+
SignedEntityTypeDiscriminants::CardanoTransactions
184+
),
185+
RequiredAggregatorCapabilities::SignedEntityType(
186+
SignedEntityTypeDiscriminants::CardanoStakeDistribution
187+
),
188+
]),
189+
RequiredAggregatorCapabilities::Or(vec![
190+
RequiredAggregatorCapabilities::AggregateSignatureType(
191+
AggregateSignatureType::Concatenation
192+
),
193+
]),
194+
])
195+
);
196+
}
197+
}

mithril-client-cli/src/commands/tools/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
//! Provides utility subcommands such as converting restored InMemory UTxO-HD ledger snapshot
44
//! to different flavors (Legacy, LMDB).
55
6+
mod aggregator_discovery;
67
mod snapshot_converter;
78

9+
pub use aggregator_discovery::*;
810
pub use snapshot_converter::*;
911

1012
use anyhow::anyhow;
@@ -18,13 +20,17 @@ pub enum ToolsCommands {
1820
/// UTxO-HD related commands
1921
#[clap(subcommand, name = "utxo-hd")]
2022
UTxOHD(UTxOHDCommands),
23+
/// Aggregator discovery related commands
24+
#[clap(subcommand, name = "aggregator-discovery")]
25+
AggregatorDiscovery(AggregatorDiscoveryCommands),
2126
}
2227

2328
impl ToolsCommands {
2429
/// Execute Tools command
2530
pub async fn execute(&self) -> MithrilResult<()> {
2631
match self {
2732
Self::UTxOHD(cmd) => cmd.execute().await,
33+
Self::AggregatorDiscovery(cmd) => cmd.execute().await,
2834
}
2935
}
3036
}
@@ -52,3 +58,20 @@ impl UTxOHDCommands {
5258
}
5359
}
5460
}
61+
62+
/// Aggregator discovery related commands
63+
#[derive(Subcommand, Debug, Clone)]
64+
pub enum AggregatorDiscoveryCommands {
65+
/// Select an aggregator from the available ones with automatic discovery
66+
#[clap(arg_required_else_help = false)]
67+
Select(AggregatorSelectCommand),
68+
}
69+
70+
impl AggregatorDiscoveryCommands {
71+
/// Execute Aggregator discovery command
72+
pub async fn execute(&self) -> MithrilResult<()> {
73+
match self {
74+
Self::Select(cmd) => cmd.execute().await,
75+
}
76+
}
77+
}

mithril-client/src/client.rs

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use std::collections::HashMap;
99
use std::sync::Arc;
1010

1111
use mithril_aggregator_discovery::{
12-
AggregatorDiscoverer, CapableAggregatorDiscoverer, HttpConfigAggregatorDiscoverer,
13-
MithrilNetwork, RequiredAggregatorCapabilities,
12+
AggregatorDiscoverer, AggregatorEndpoint, CapableAggregatorDiscoverer,
13+
HttpConfigAggregatorDiscoverer, MithrilNetwork, RequiredAggregatorCapabilities,
1414
};
1515
use mithril_common::api_version::APIVersionProvider;
1616
use mithril_common::{MITHRIL_CLIENT_TYPE_HEADER, MITHRIL_ORIGIN_TAG_HEADER};
@@ -294,8 +294,7 @@ impl ClientBuilder {
294294
None => {
295295
return Err(anyhow!(
296296
"The genesis verification key must be provided to build the client with the 'with_genesis_verification_key' function"
297-
)
298-
.into());
297+
));
299298
}
300299
};
301300

@@ -405,40 +404,47 @@ impl ClientBuilder {
405404
})
406405
}
407406

407+
/// Discover available aggregator endpoints for the given Mithril network and required capabilities.
408+
pub fn discover_aggregator(
409+
&self,
410+
network: &MithrilNetwork,
411+
) -> MithrilResult<impl Iterator<Item = AggregatorEndpoint>> {
412+
match self.aggregator_discoverer.clone() {
413+
Some(discoverer) => {
414+
let discoverer = if let Some(capabilities) = &self.aggregator_capabilities {
415+
Arc::new(CapableAggregatorDiscoverer::new(
416+
capabilities.to_owned(),
417+
discoverer.clone(),
418+
)) as Arc<dyn AggregatorDiscoverer>
419+
} else {
420+
discoverer as Arc<dyn AggregatorDiscoverer>
421+
};
422+
tokio::task::block_in_place(move || {
423+
tokio::runtime::Handle::current().block_on(async move {
424+
discoverer
425+
.get_available_aggregators(network.to_owned())
426+
.await
427+
.with_context(|| "Discovering aggregator endpoint failed")
428+
})
429+
})
430+
}
431+
None => Err(anyhow!(
432+
"The aggregator discoverer must be provided to build the client with automatic discovery using the 'with_aggregator_discoverer' function"
433+
)),
434+
}
435+
}
436+
408437
fn build_aggregator_client(
409438
&self,
410439
logger: Logger,
411440
) -> Result<AggregatorHTTPClient, anyhow::Error> {
412441
let aggregator_endpoint = match self.aggregator_discovery {
413442
AggregatorDiscoveryType::Url(ref url) => url.clone(),
414-
AggregatorDiscoveryType::Automatic(ref network) => {
415-
match self.aggregator_discoverer.clone() {
416-
Some(discoverer) => {
417-
let discoverer = if let Some(capabilities) = &self.aggregator_capabilities {
418-
Arc::new(CapableAggregatorDiscoverer::new(
419-
capabilities.to_owned(),
420-
discoverer.clone(),
421-
)) as Arc<dyn AggregatorDiscoverer>
422-
} else {
423-
discoverer as Arc<dyn AggregatorDiscoverer>
424-
};
425-
tokio::task::block_in_place(move || {
426-
tokio::runtime::Handle::current().block_on(async move {
427-
discoverer
428-
.get_available_aggregators(network.to_owned())
429-
.await
430-
.with_context(|| "Discovering aggregator endpoint failed")?
431-
.next()
432-
.ok_or(anyhow!("No aggregator was available through discovery"))
433-
})
434-
})?
435-
.into()
436-
}
437-
None => {
438-
return Err(anyhow!("The aggregator discoverer must be provided to build the client with automatic discovery using the 'with_aggregator_discoverer' function").into());
439-
}
440-
}
441-
}
443+
AggregatorDiscoveryType::Automatic(ref network) => self
444+
.discover_aggregator(network)?
445+
.next()
446+
.ok_or_else(|| anyhow!("No aggregator was available through discovery"))?
447+
.into(),
442448
};
443449
let endpoint_url = Url::parse(&aggregator_endpoint).with_context(|| {
444450
format!("Invalid aggregator endpoint, it must be a correctly formed url: '{aggregator_endpoint}'")

mithril-client/src/type_alias.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ pub mod common {
8989

9090
/// Required capabilities for an aggregator.
9191
pub use mithril_aggregator_discovery::RequiredAggregatorCapabilities;
92+
93+
/// Mithril network
94+
pub use mithril_aggregator_discovery::MithrilNetwork;

mithril-stm/src/aggregate_signature/signature.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ impl<D: Clone + Digest + FixedOutput + Send + Sync> From<&AggregateSignature<D>>
6060
}
6161
}
6262

63+
impl From<String> for AggregateSignatureType {
64+
fn from(s: String) -> Self {
65+
match s.as_str() {
66+
"Concatenation" => AggregateSignatureType::Concatenation,
67+
#[cfg(feature = "future_proof_system")]
68+
"Future" => AggregateSignatureType::Future,
69+
_ => panic!("Unknown aggregate signature type: {}", s),
70+
}
71+
}
72+
}
73+
6374
impl Display for AggregateSignatureType {
6475
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6576
match self {

0 commit comments

Comments
 (0)