snarkos_node_bft/helpers/
proposal_cache.rs1use crate::helpers::{Proposal, SignedProposals};
17
18use snarkvm::{
19 console::{account::Address, network::Network, program::SUBDAG_CERTIFICATES_DEPTH},
20 ledger::narwhal::BatchCertificate,
21 prelude::{FromBytes, IoResult, Read, Result, ToBytes, Write, anyhow, bail, error},
22};
23
24use aleo_std::{StorageMode, aleo_ledger_dir};
25use indexmap::IndexSet;
26use std::{fs, path::PathBuf};
27
28pub fn proposal_cache_path(network: u16, dev: Option<u16>) -> PathBuf {
30 const PROPOSAL_CACHE_FILE_NAME: &str = "current-proposal-cache";
31
32 let mut path = aleo_ledger_dir(network, StorageMode::from(dev));
34 path.pop();
36 match dev {
38 Some(id) => path.push(format!(".{PROPOSAL_CACHE_FILE_NAME}-{}-{}", network, id)),
39 None => path.push(format!("{PROPOSAL_CACHE_FILE_NAME}-{}", network)),
40 }
41
42 path
43}
44
45#[derive(Debug, PartialEq, Eq)]
47pub struct ProposalCache<N: Network> {
48 latest_round: u64,
50 proposal: Option<Proposal<N>>,
52 signed_proposals: SignedProposals<N>,
54 pending_certificates: IndexSet<BatchCertificate<N>>,
56}
57
58impl<N: Network> ProposalCache<N> {
59 pub fn new(
61 latest_round: u64,
62 proposal: Option<Proposal<N>>,
63 signed_proposals: SignedProposals<N>,
64 pending_certificates: IndexSet<BatchCertificate<N>>,
65 ) -> Self {
66 Self { latest_round, proposal, signed_proposals, pending_certificates }
67 }
68
69 pub fn is_valid(&self, expected_signer: Address<N>) -> bool {
71 self.proposal
72 .as_ref()
73 .map(|proposal| {
74 proposal.batch_header().author() == expected_signer && self.latest_round == proposal.round()
75 })
76 .unwrap_or(true)
77 && self.signed_proposals.is_valid(expected_signer)
78 }
79
80 pub fn exists(dev: Option<u16>) -> bool {
82 proposal_cache_path(N::ID, dev).exists()
83 }
84
85 pub fn load(expected_signer: Address<N>, dev: Option<u16>) -> Result<Self> {
87 let path = proposal_cache_path(N::ID, dev);
89
90 let proposal_cache = match fs::read(&path) {
92 Ok(bytes) => match Self::from_bytes_le(&bytes) {
93 Ok(proposal_cache) => proposal_cache,
94 Err(_) => bail!("Couldn't deserialize the proposal stored at {}", path.display()),
95 },
96 Err(_) => bail!("Couldn't read the proposal stored at {}", path.display()),
97 };
98
99 if !proposal_cache.is_valid(expected_signer) {
101 bail!("The proposal cache is invalid for the given address {expected_signer}");
102 }
103
104 info!("Loaded the proposal cache from {} at round {}", path.display(), proposal_cache.latest_round);
105
106 Ok(proposal_cache)
107 }
108
109 pub fn store(&self, dev: Option<u16>) -> Result<()> {
111 let path = proposal_cache_path(N::ID, dev);
112 info!("Storing the proposal cache to {}...", path.display());
113
114 let bytes = self.to_bytes_le()?;
116 fs::write(&path, bytes)
118 .map_err(|err| anyhow!("Couldn't write the proposal cache to {} - {err}", path.display()))?;
119
120 Ok(())
121 }
122
123 pub fn into(self) -> (u64, Option<Proposal<N>>, SignedProposals<N>, IndexSet<BatchCertificate<N>>) {
125 (self.latest_round, self.proposal, self.signed_proposals, self.pending_certificates)
126 }
127}
128
129impl<N: Network> ToBytes for ProposalCache<N> {
130 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
131 self.latest_round.write_le(&mut writer)?;
133 self.proposal.is_some().write_le(&mut writer)?;
135 if let Some(proposal) = &self.proposal {
136 proposal.write_le(&mut writer)?;
137 }
138 self.signed_proposals.write_le(&mut writer)?;
140 u32::try_from(self.pending_certificates.len()).map_err(error)?.write_le(&mut writer)?;
142 for certificate in &self.pending_certificates {
144 certificate.write_le(&mut writer)?;
145 }
146
147 Ok(())
148 }
149}
150
151impl<N: Network> FromBytes for ProposalCache<N> {
152 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
153 let latest_round = u64::read_le(&mut reader)?;
155 let has_proposal: bool = FromBytes::read_le(&mut reader)?;
157 let proposal = match has_proposal {
158 true => Some(Proposal::read_le(&mut reader)?),
159 false => None,
160 };
161 let signed_proposals = SignedProposals::read_le(&mut reader)?;
163 let num_certificates = u32::read_le(&mut reader)?;
165 if num_certificates > 2u32.saturating_pow(SUBDAG_CERTIFICATES_DEPTH as u32) {
167 return Err(error(format!(
168 "Number of certificates ({num_certificates}) exceeds the maximum ({})",
169 2u32.saturating_pow(SUBDAG_CERTIFICATES_DEPTH as u32)
170 )));
171 };
172 let pending_certificates =
174 (0..num_certificates).map(|_| BatchCertificate::read_le(&mut reader)).collect::<IoResult<IndexSet<_>>>()?;
175
176 Ok(Self::new(latest_round, proposal, signed_proposals, pending_certificates))
177 }
178}
179
180impl<N: Network> Default for ProposalCache<N> {
181 fn default() -> Self {
183 Self::new(0, None, Default::default(), Default::default())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::helpers::{proposal::tests::sample_proposal, signed_proposals::tests::sample_signed_proposals};
191 use snarkvm::{
192 console::{account::PrivateKey, network::MainnetV0},
193 ledger::narwhal::batch_certificate::test_helpers::sample_batch_certificates,
194 utilities::TestRng,
195 };
196
197 type CurrentNetwork = MainnetV0;
198
199 const ITERATIONS: usize = 100;
200
201 pub(crate) fn sample_proposal_cache(
202 signer: &PrivateKey<CurrentNetwork>,
203 rng: &mut TestRng,
204 ) -> ProposalCache<CurrentNetwork> {
205 let proposal = sample_proposal(rng);
206 let signed_proposals = sample_signed_proposals(signer, rng);
207 let round = proposal.round();
208 let pending_certificates = sample_batch_certificates(rng);
209
210 ProposalCache::new(round, Some(proposal), signed_proposals, pending_certificates)
211 }
212
213 #[test]
214 fn test_bytes() {
215 let rng = &mut TestRng::default();
216 let singer_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
217
218 for _ in 0..ITERATIONS {
219 let expected = sample_proposal_cache(&singer_private_key, rng);
220 let expected_bytes = expected.to_bytes_le().unwrap();
222 assert_eq!(expected, ProposalCache::read_le(&expected_bytes[..]).unwrap());
223 }
224 }
225}