Skip to main content

snarkos_node_bft/helpers/
signed_proposals.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use snarkos_node_sync::locators::NUM_RECENT_BLOCKS;
17use snarkvm::{
18    console::{
19        account::{Address, Signature},
20        network::Network,
21        types::Field,
22    },
23    prelude::{FromBytes, IoResult, Read, ToBytes, Write, error},
24};
25
26use std::{collections::HashMap, ops::Deref};
27
28/// The recently-signed batch proposals.
29/// A map of `address` to (`round`, `batch ID`, `signature`).
30#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct SignedProposals<N: Network>(pub HashMap<Address<N>, (u64, Field<N>, Signature<N>)>);
32
33impl<N: Network> SignedProposals<N> {
34    /// Ensure that every signed proposal is associated with the `expected_signer`.
35    pub fn is_valid(&self, expected_signer: Address<N>) -> bool {
36        self.0.iter().all(|(_, (_, _, signature))| signature.to_address() == expected_signer)
37    }
38}
39
40impl<N: Network> ToBytes for SignedProposals<N> {
41    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
42        // Write the number of signed proposals.
43        u32::try_from(self.0.len()).map_err(error)?.write_le(&mut writer)?;
44        // Serialize the signed proposals.
45        for (address, (round, batch_id, signature)) in &self.0 {
46            // Write the address.
47            address.write_le(&mut writer)?;
48            // Write the round.
49            round.write_le(&mut writer)?;
50            // Write the batch id.
51            batch_id.write_le(&mut writer)?;
52            // Write the signature.
53            signature.write_le(&mut writer)?;
54        }
55
56        Ok(())
57    }
58}
59
60impl<N: Network> FromBytes for SignedProposals<N> {
61    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
62        // Read the number of signed proposals.
63        let num_signed_proposals = u32::read_le(&mut reader)?;
64
65        let max_certificates = N::LATEST_MAX_CERTIFICATES();
66        // Ensure the number of signed proposals is within bounds
67        if num_signed_proposals as usize > max_certificates as usize * NUM_RECENT_BLOCKS {
68            return Err(error(format!(
69                "Number of signed proposals ({num_signed_proposals}) is greater than the maximum ({max_certificates} * {NUM_RECENT_BLOCKS})",
70            )));
71        }
72        // Deserialize the signed proposals.
73        let mut signed_proposals = HashMap::with_capacity(num_signed_proposals as usize);
74        for _ in 0..num_signed_proposals {
75            // Read the address.
76            let address = FromBytes::read_le(&mut reader)?;
77            // Read the round.
78            let round = FromBytes::read_le(&mut reader)?;
79            // Read the batch id.
80            let batch_id = FromBytes::read_le(&mut reader)?;
81            // Read the signature.
82            let signature = FromBytes::read_le(&mut reader)?;
83            // Insert the signed proposal.
84            signed_proposals.insert(address, (round, batch_id, signature));
85        }
86
87        Ok(Self(signed_proposals))
88    }
89}
90
91impl<N: Network> Deref for SignedProposals<N> {
92    type Target = HashMap<Address<N>, (u64, Field<N>, Signature<N>)>;
93
94    fn deref(&self) -> &Self::Target {
95        &self.0
96    }
97}
98
99impl<N: Network> Default for SignedProposals<N> {
100    /// Initializes a new instance of the signed proposals.
101    fn default() -> Self {
102        Self(Default::default())
103    }
104}
105
106#[cfg(test)]
107pub(crate) mod tests {
108    use super::*;
109    use snarkvm::{
110        console::{account::PrivateKey, network::MainnetV0},
111        utilities::{TestRng, Uniform},
112    };
113
114    use rand::Rng;
115
116    type CurrentNetwork = MainnetV0;
117
118    const ITERATIONS: usize = 100;
119
120    pub(crate) fn sample_signed_proposals(
121        signer: &PrivateKey<CurrentNetwork>,
122        rng: &mut TestRng,
123    ) -> SignedProposals<CurrentNetwork> {
124        let mut signed_proposals: HashMap<_, _> = Default::default();
125        for _ in 0..CurrentNetwork::LATEST_MAX_CERTIFICATES() {
126            let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
127            let address = Address::try_from(&private_key).unwrap();
128
129            // Add the signed proposal to the map.
130            let round = rng.r#gen();
131            let batch_id = Field::rand(rng);
132            let signature = signer.sign(&[batch_id], rng).unwrap();
133            signed_proposals.insert(address, (round, batch_id, signature));
134        }
135
136        SignedProposals(signed_proposals)
137    }
138
139    #[test]
140    fn test_bytes() {
141        let rng = &mut TestRng::default();
142        let singer_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
143
144        for _ in 0..ITERATIONS {
145            let expected = sample_signed_proposals(&singer_private_key, rng);
146            // Check the byte representation.
147            let expected_bytes = expected.to_bytes_le().unwrap();
148            assert_eq!(expected, SignedProposals::read_le(&expected_bytes[..]).unwrap());
149        }
150    }
151
152    #[test]
153    fn test_is_valid() {
154        let rng = &mut TestRng::default();
155
156        for _ in 0..ITERATIONS {
157            let singer_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
158            let singer_address = Address::try_from(&singer_private_key).unwrap();
159            let signed_proposals = sample_signed_proposals(&singer_private_key, rng);
160            // Ensure that the signed proposals are valid.
161            assert!(signed_proposals.is_valid(singer_address));
162        }
163    }
164}