penumbra_sdk_mock_consensus/block/
signature.rs

1use {
2    super::Builder,
3    crate::TestNode,
4    sha2::{Digest, Sha256},
5    tendermint::{
6        abci::types::{BlockSignatureInfo, CommitInfo, VoteInfo},
7        account,
8        block::{BlockIdFlag, Commit, CommitSig, Round},
9        vote::Power,
10    },
11};
12
13/// Helper functions for generating [commit signatures].
14mod sign {
15    use tendermint::{account::Id, block::CommitSig, time::Time};
16
17    /// Returns a [commit signature] saying this validator voted for the block.
18    ///
19    /// [commit signature]: CommitSig
20    pub(super) fn commit(
21        validator_address: Id,
22        validator_key: &ed25519_consensus::SigningKey,
23        canonical: &tendermint::vote::CanonicalVote,
24    ) -> CommitSig {
25        // Create a vote to be signed
26        // https://github.com/informalsystems/tendermint-rs/blob/14fd628e82ae51b9f15c135a6db8870219fe3c33/testgen/src/commit.rs#L214
27        // https://github.com/informalsystems/tendermint-rs/blob/14fd628e82ae51b9f15c135a6db8870219fe3c33/testgen/src/commit.rs#L104
28
29        use tendermint_proto::v0_37::types::CanonicalVote as RawCanonicalVote;
30        let sign_bytes =
31            tendermint_proto::Protobuf::<RawCanonicalVote>::encode_length_delimited_vec(
32                canonical.clone(),
33            );
34
35        let signature: tendermint::Signature = validator_key
36            .sign(sign_bytes.as_slice())
37            .try_into()
38            .unwrap();
39
40        // encode to stable-json deterministic JSON wire encoding,
41        // https://github.com/informalsystems/tendermint-rs/blob/14fd628e82ae51b9f15c135a6db8870219fe3c33/testgen/src/helpers.rs#L43C1-L44C1
42
43        CommitSig::BlockIdFlagCommit {
44            validator_address,
45            timestamp: canonical.timestamp.expect("timestamp should be present"),
46            signature: Some(signature.into()),
47        }
48    }
49
50    /// Returns a [commit signature] saying this validator voted nil.
51    ///
52    /// [commit signature]: CommitSig
53    #[allow(dead_code)]
54    pub(super) fn nil(validator_address: Id, timestamp: Time) -> CommitSig {
55        CommitSig::BlockIdFlagNil {
56            validator_address,
57            timestamp,
58            signature: None,
59        }
60    }
61}
62
63// === impl TestNode ===
64
65impl<C> TestNode<C> {
66    // TODO(kate): other interfaces may be helpful to add in the future, and these may eventually
67    // warrant being made `pub`. we defer doing so for now, only defining what is needed to provide
68    // commit signatures from all of the validators.
69
70    /// Returns an [`Iterator`] of signatures for validators in the keyring.
71    /// Signatures sign the given block header.
72    pub(super) fn generate_signatures(
73        &self,
74        header: &tendermint::block::Header,
75    ) -> impl Iterator<Item = CommitSig> + '_ {
76        let block_id = tendermint::block::Id {
77            hash: header.hash(),
78            part_set_header: tendermint::block::parts::Header::new(0, tendermint::Hash::None)
79                .unwrap(),
80        };
81        let canonical = tendermint::vote::CanonicalVote {
82            // The mock consensus engine ONLY has precommit votes right now
83            vote_type: tendermint::vote::Type::Precommit,
84            height: tendermint::block::Height::from(header.height),
85            // round is always 0
86            round: 0u8.into(),
87            block_id: Some(block_id),
88            // Block header time is used throughout
89            timestamp: Some(header.time.clone()),
90            // timestamp: Some(last_commit_info.timestamp),
91            chain_id: self.chain_id.clone(),
92        };
93        tracing::trace!(vote=?canonical,"canonical vote constructed");
94
95        return self
96            .keyring
97            .iter()
98            .map(|(vk, sk)| {
99                (
100                    <Sha256 as Digest>::digest(vk).as_slice()[0..20]
101                        .try_into()
102                        .expect(""),
103                    sk,
104                )
105            })
106            .map(move |(id, sk)| self::sign::commit(account::Id::new(id), sk, &canonical));
107    }
108}
109
110// === impl Builder ===
111
112impl<'e, C: 'e> Builder<'e, C> {
113    /// Returns [`CommitInfo`] given a block's [`Commit`].
114    pub(super) fn last_commit_info(last_commit: Option<Commit>) -> CommitInfo {
115        let Some(Commit {
116            round, signatures, ..
117        }) = last_commit
118        else {
119            // If there is no commit information about the last block, return an empty object.
120            return CommitInfo {
121                round: Round::default(),
122                votes: Vec::default(),
123            };
124        };
125
126        CommitInfo {
127            round,
128            votes: signatures.into_iter().filter_map(Self::vote).collect(),
129        }
130    }
131
132    /// Returns a [`VoteInfo`] for this [`CommitSig`].
133    ///
134    /// If no validator voted, returns [`None`].
135    fn vote(commit_sig: CommitSig) -> Option<VoteInfo> {
136        use tendermint::abci::types::Validator;
137
138        // TODO(kate): upstream this into the `tendermint` library.
139        let sig_info = BlockSignatureInfo::Flag(match commit_sig {
140            CommitSig::BlockIdFlagAbsent => BlockIdFlag::Absent,
141            CommitSig::BlockIdFlagCommit { .. } => BlockIdFlag::Commit,
142            CommitSig::BlockIdFlagNil { .. } => BlockIdFlag::Nil,
143        });
144
145        let address: [u8; 20] = commit_sig
146            .validator_address()?
147            // TODO(kate): upstream an accessor to retrieve this as the [u8; 20] that it is.
148            .as_bytes()
149            .try_into()
150            .expect("validator address should be 20 bytes");
151        let power = Power::from(1_u8); // TODO(kate): for now, hard-code voting power to 1.
152        let validator = Validator { address, power };
153
154        Some(VoteInfo {
155            validator,
156            sig_info,
157        })
158    }
159}