Skip to main content

tempo_xtask/
get_dkg_outcome.rs

1//! Dump DKG outcome from a block's extra_data.
2
3use 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    /// RPC endpoint URL (http://, https://, ws://, or wss://)
19    #[arg(long)]
20    rpc_url: String,
21
22    /// Block number to query directly (use when epoch length varies)
23    #[arg(long, group = "target")]
24    block: Option<u64>,
25
26    /// Block hash to query directly
27    #[arg(long, group = "target")]
28    block_hash: Option<B256>,
29
30    /// Epoch number to query (requires --epoch-length)
31    #[arg(long, group = "target", requires = "epoch_length")]
32    epoch: Option<u64>,
33
34    /// Epoch length in blocks (required with --epoch)
35    #[arg(long, requires = "epoch")]
36    epoch_length: Option<u64>,
37}
38
39#[derive(Serialize)]
40struct DkgOutcomeInfo {
41    /// The epoch for which this outcome is used
42    epoch: u64,
43    /// Block number where this outcome was stored
44    block_number: u64,
45    /// Block hash where this outcome was stored
46    block_hash: B256,
47    /// Dealers that contributed to the outcome of this DKG ceremony (ed25519 public keys)
48    dealers: Vec<String>,
49    /// Players that received a share from this DKG ceremony (ed25519 public keys)
50    players: Vec<String>,
51    /// Players for the next DKG ceremony (ed25519 public keys)
52    next_players: Vec<String>,
53    /// Whether the next DKG should be a full ceremony (new polynomial)
54    is_next_full_dkg: bool,
55    /// The network identity (group public key)
56    network_identity: Bytes,
57    /// Threshold required for signing
58    threshold: u32,
59    /// Total number of participants
60    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}