tempo_xtask/
get_dkg_outcome.rs1use alloy::{
4 primitives::{B256, Bytes},
5 providers::{Provider, ProviderBuilder},
6};
7use commonware_codec::{Encode as _, ReadExt as _};
8use commonware_consensus::types::{Epoch, Epocher as _, FixedEpocher};
9use commonware_cryptography::ed25519::PublicKey;
10use commonware_utils::{N3f1, NZU64};
11use eyre::{Context as _, eyre};
12use serde::Serialize;
13use tempo_dkg_onchain_artifacts::OnchainDkgOutcome;
14
15#[derive(Debug, clap::Args)]
16#[clap(group = clap::ArgGroup::new("target").required(true))]
17pub(crate) struct GetDkgOutcome {
18 #[arg(long)]
20 rpc_url: String,
21
22 #[arg(long, group = "target")]
24 block: Option<u64>,
25
26 #[arg(long, group = "target")]
28 block_hash: Option<B256>,
29
30 #[arg(long, group = "target", requires = "epoch_length")]
32 epoch: Option<u64>,
33
34 #[arg(long, requires = "epoch")]
36 epoch_length: Option<u64>,
37}
38
39#[derive(Serialize)]
40struct DkgOutcomeInfo {
41 epoch: u64,
43 block_number: u64,
45 block_hash: B256,
47 dealers: Vec<String>,
49 players: Vec<String>,
51 next_players: Vec<String>,
53 is_next_full_dkg: bool,
55 network_identity: Bytes,
57 threshold: u32,
59 total_participants: u32,
61}
62
63fn pubkey_to_hex(pk: &PublicKey) -> String {
64 const_hex::encode_prefixed(pk.as_ref())
65}
66
67impl GetDkgOutcome {
68 pub(crate) async fn run(self) -> eyre::Result<()> {
69 let provider = ProviderBuilder::new()
70 .connect(&self.rpc_url)
71 .await
72 .wrap_err("failed to connect to RPC")?;
73
74 let block = if let Some(hash) = self.block_hash {
75 provider
76 .get_block_by_hash(hash)
77 .await
78 .wrap_err_with(|| format!("failed to fetch block hash `{hash}`"))?
79 .ok_or_else(|| eyre!("block {hash} not found"))?
80 } else {
81 let block_number = if let Some(block) = self.block {
82 block
83 } else {
84 let epoch = self.epoch.expect("epoch required when block not provided");
85 let epoch_length = self.epoch_length.expect("epoch_length required with epoch");
86 let epocher = FixedEpocher::new(NZU64!(epoch_length));
87 epocher
88 .last(Epoch::new(epoch))
89 .expect("fixed epocher is valid for all epochs")
90 .get()
91 };
92
93 provider
94 .get_block_by_number(block_number.into())
95 .await
96 .wrap_err_with(|| format!("failed to fetch block number `{block_number}`"))?
97 .ok_or_else(|| eyre!("block {block_number} not found"))?
98 };
99
100 let block_number = block.header.number;
101 let block_hash = block.header.hash;
102 let extra_data = &block.header.inner.extra_data;
103
104 eyre::ensure!(
105 !extra_data.is_empty(),
106 "block {} has empty extra_data (not an epoch boundary?)",
107 block_number
108 );
109
110 let outcome = OnchainDkgOutcome::read(&mut extra_data.as_ref())
111 .wrap_err("failed to parse DKG outcome from extra_data")?;
112
113 let sharing = outcome.sharing();
114
115 let info = DkgOutcomeInfo {
116 epoch: outcome.epoch.get(),
117 block_number,
118 block_hash,
119 dealers: outcome.dealers().iter().map(pubkey_to_hex).collect(),
120 players: outcome.players().iter().map(pubkey_to_hex).collect(),
121 next_players: outcome.next_players().iter().map(pubkey_to_hex).collect(),
122 is_next_full_dkg: outcome.is_next_full_dkg,
123 network_identity: Bytes::copy_from_slice(&sharing.public().encode()),
124 threshold: sharing.required::<N3f1>(),
125 total_participants: sharing.total().get(),
126 };
127
128 println!("{}", serde_json::to_string_pretty(&info)?);
129
130 Ok(())
131 }
132}