penumbra_mock_consensus/block/
signature.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use {
    super::Builder,
    crate::TestNode,
    sha2::{Digest, Sha256},
    tendermint::{
        abci::types::{BlockSignatureInfo, CommitInfo, VoteInfo},
        account,
        block::{BlockIdFlag, Commit, CommitSig, Round},
        vote::Power,
    },
};

/// Helper functions for generating [commit signatures].
mod sign {
    use tendermint::{account::Id, block::CommitSig, time::Time};

    /// Returns a [commit signature] saying this validator voted for the block.
    ///
    /// [commit signature]: CommitSig
    pub(super) fn commit(
        validator_address: Id,
        validator_key: &ed25519_consensus::SigningKey,
        canonical: &tendermint::vote::CanonicalVote,
    ) -> CommitSig {
        // Create a vote to be signed
        // https://github.com/informalsystems/tendermint-rs/blob/14fd628e82ae51b9f15c135a6db8870219fe3c33/testgen/src/commit.rs#L214
        // https://github.com/informalsystems/tendermint-rs/blob/14fd628e82ae51b9f15c135a6db8870219fe3c33/testgen/src/commit.rs#L104

        use tendermint_proto::v0_37::types::CanonicalVote as RawCanonicalVote;
        let sign_bytes =
            tendermint_proto::Protobuf::<RawCanonicalVote>::encode_length_delimited_vec(
                canonical.clone(),
            );

        let signature: tendermint::Signature = validator_key
            .sign(sign_bytes.as_slice())
            .try_into()
            .unwrap();

        // encode to stable-json deterministic JSON wire encoding,
        // https://github.com/informalsystems/tendermint-rs/blob/14fd628e82ae51b9f15c135a6db8870219fe3c33/testgen/src/helpers.rs#L43C1-L44C1

        CommitSig::BlockIdFlagCommit {
            validator_address,
            timestamp: canonical.timestamp.expect("timestamp should be present"),
            signature: Some(signature.into()),
        }
    }

    /// Returns a [commit signature] saying this validator voted nil.
    ///
    /// [commit signature]: CommitSig
    #[allow(dead_code)]
    pub(super) fn nil(validator_address: Id, timestamp: Time) -> CommitSig {
        CommitSig::BlockIdFlagNil {
            validator_address,
            timestamp,
            signature: None,
        }
    }
}

// === impl TestNode ===

impl<C> TestNode<C> {
    // TODO(kate): other interfaces may be helpful to add in the future, and these may eventually
    // warrant being made `pub`. we defer doing so for now, only defining what is needed to provide
    // commit signatures from all of the validators.

    /// Returns an [`Iterator`] of signatures for validators in the keyring.
    /// Signatures sign the given block header.
    pub(super) fn generate_signatures(
        &self,
        header: &tendermint::block::Header,
    ) -> impl Iterator<Item = CommitSig> + '_ {
        let block_id = tendermint::block::Id {
            hash: header.hash(),
            part_set_header: tendermint::block::parts::Header::new(0, tendermint::Hash::None)
                .unwrap(),
        };
        let canonical = tendermint::vote::CanonicalVote {
            // The mock consensus engine ONLY has precommit votes right now
            vote_type: tendermint::vote::Type::Precommit,
            height: tendermint::block::Height::from(header.height),
            // round is always 0
            round: 0u8.into(),
            block_id: Some(block_id),
            // Block header time is used throughout
            timestamp: Some(header.time.clone()),
            // timestamp: Some(last_commit_info.timestamp),
            chain_id: self.chain_id.clone(),
        };
        tracing::trace!(vote=?canonical,"canonical vote constructed");

        return self
            .keyring
            .iter()
            .map(|(vk, sk)| {
                (
                    <Sha256 as Digest>::digest(vk).as_slice()[0..20]
                        .try_into()
                        .expect(""),
                    sk,
                )
            })
            .map(move |(id, sk)| self::sign::commit(account::Id::new(id), sk, &canonical));
    }
}

// === impl Builder ===

impl<'e, C: 'e> Builder<'e, C> {
    /// Returns [`CommitInfo`] given a block's [`Commit`].
    pub(super) fn last_commit_info(last_commit: Option<Commit>) -> CommitInfo {
        let Some(Commit {
            round, signatures, ..
        }) = last_commit
        else {
            // If there is no commit information about the last block, return an empty object.
            return CommitInfo {
                round: Round::default(),
                votes: Vec::default(),
            };
        };

        CommitInfo {
            round,
            votes: signatures.into_iter().filter_map(Self::vote).collect(),
        }
    }

    /// Returns a [`VoteInfo`] for this [`CommitSig`].
    ///
    /// If no validator voted, returns [`None`].
    fn vote(commit_sig: CommitSig) -> Option<VoteInfo> {
        use tendermint::abci::types::Validator;

        // TODO(kate): upstream this into the `tendermint` library.
        let sig_info = BlockSignatureInfo::Flag(match commit_sig {
            CommitSig::BlockIdFlagAbsent => BlockIdFlag::Absent,
            CommitSig::BlockIdFlagCommit { .. } => BlockIdFlag::Commit,
            CommitSig::BlockIdFlagNil { .. } => BlockIdFlag::Nil,
        });

        let address: [u8; 20] = commit_sig
            .validator_address()?
            // TODO(kate): upstream an accessor to retrieve this as the [u8; 20] that it is.
            .as_bytes()
            .try_into()
            .expect("validator address should be 20 bytes");
        let power = Power::from(1_u8); // TODO(kate): for now, hard-code voting power to 1.
        let validator = Validator { address, power };

        Some(VoteInfo {
            validator,
            sig_info,
        })
    }
}