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