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