amareleo_node_bft/helpers/
proposal_cache.rs1use crate::helpers::{Proposal, SignedProposals, ledger_files::proposal_cache_path};
17use amareleo_chain_tracing::TracingHandlerGuard;
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;
25use indexmap::IndexSet;
26use std::fs;
27
28#[derive(Debug, PartialEq, Eq)]
30pub struct ProposalCache<N: Network> {
31 latest_round: u64,
33 proposal: Option<Proposal<N>>,
35 signed_proposals: SignedProposals<N>,
37 pending_certificates: IndexSet<BatchCertificate<N>>,
39}
40
41impl<N: Network> ProposalCache<N> {
42 pub fn new(
44 latest_round: u64,
45 proposal: Option<Proposal<N>>,
46 signed_proposals: SignedProposals<N>,
47 pending_certificates: IndexSet<BatchCertificate<N>>,
48 ) -> Self {
49 Self { latest_round, proposal, signed_proposals, pending_certificates }
50 }
51
52 pub fn is_valid(&self, expected_signer: Address<N>) -> bool {
54 self.proposal
55 .as_ref()
56 .map(|proposal| {
57 proposal.batch_header().author() == expected_signer && self.latest_round == proposal.round()
58 })
59 .unwrap_or(true)
60 && self.signed_proposals.is_valid(expected_signer)
61 }
62
63 pub fn exists(storage_mode: &StorageMode) -> bool {
65 proposal_cache_path(storage_mode).map_or(false, |path| path.exists())
66 }
67
68 pub fn load(
70 expected_signer: Address<N>,
71 storage_mode: &StorageMode,
72 tracing: &dyn TracingHandlerGuard,
73 ) -> Result<Self> {
74 let path = proposal_cache_path(storage_mode)?;
75
76 let proposal_cache = match fs::read(&path) {
78 Ok(bytes) => match Self::from_bytes_le(&bytes) {
79 Ok(proposal_cache) => proposal_cache,
80 Err(_) => bail!("Couldn't deserialize the proposal stored at {}", path.display()),
81 },
82 Err(_) => bail!("Couldn't read the proposal stored at {}", path.display()),
83 };
84
85 if !proposal_cache.is_valid(expected_signer) {
87 bail!("The proposal cache is invalid for the given address {expected_signer}");
88 }
89
90 guard_info!(
91 tracing,
92 "Loaded the proposal cache from {} at round {}",
93 path.display(),
94 proposal_cache.latest_round
95 );
96
97 Ok(proposal_cache)
98 }
99
100 pub fn store(&self, storage_mode: &StorageMode, tracing: &dyn TracingHandlerGuard) -> Result<()> {
102 let path = proposal_cache_path(storage_mode)?;
103 guard_info!(tracing, "Storing the proposal cache to {}...", path.display());
104
105 let bytes = self.to_bytes_le()?;
107 fs::write(&path, bytes)
109 .map_err(|err| anyhow!("Couldn't write the proposal cache to {} - {err}", path.display()))?;
110
111 Ok(())
112 }
113
114 pub fn into(self) -> (u64, Option<Proposal<N>>, SignedProposals<N>, IndexSet<BatchCertificate<N>>) {
116 (self.latest_round, self.proposal, self.signed_proposals, self.pending_certificates)
117 }
118}
119
120impl<N: Network> ToBytes for ProposalCache<N> {
121 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
122 self.latest_round.write_le(&mut writer)?;
124 self.proposal.is_some().write_le(&mut writer)?;
126 if let Some(proposal) = &self.proposal {
127 proposal.write_le(&mut writer)?;
128 }
129 self.signed_proposals.write_le(&mut writer)?;
131 u32::try_from(self.pending_certificates.len()).map_err(error)?.write_le(&mut writer)?;
133 for certificate in &self.pending_certificates {
135 certificate.write_le(&mut writer)?;
136 }
137
138 Ok(())
139 }
140}
141
142impl<N: Network> FromBytes for ProposalCache<N> {
143 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
144 let latest_round = u64::read_le(&mut reader)?;
146 let has_proposal: bool = FromBytes::read_le(&mut reader)?;
148 let proposal = match has_proposal {
149 true => Some(Proposal::read_le(&mut reader)?),
150 false => None,
151 };
152 let signed_proposals = SignedProposals::read_le(&mut reader)?;
154 let num_certificates = u32::read_le(&mut reader)?;
156 if num_certificates > 2u32.saturating_pow(SUBDAG_CERTIFICATES_DEPTH as u32) {
158 return Err(error(format!(
159 "Number of certificates ({num_certificates}) exceeds the maximum ({})",
160 2u32.saturating_pow(SUBDAG_CERTIFICATES_DEPTH as u32)
161 )));
162 };
163 let pending_certificates =
165 (0..num_certificates).map(|_| BatchCertificate::read_le(&mut reader)).collect::<IoResult<IndexSet<_>>>()?;
166
167 Ok(Self::new(latest_round, proposal, signed_proposals, pending_certificates))
168 }
169}
170
171impl<N: Network> Default for ProposalCache<N> {
172 fn default() -> Self {
174 Self::new(0, None, Default::default(), Default::default())
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::helpers::{proposal::tests::sample_proposal, signed_proposals::tests::sample_signed_proposals};
182 use snarkvm::{
183 console::{account::PrivateKey, network::MainnetV0},
184 ledger::narwhal::batch_certificate::test_helpers::sample_batch_certificates,
185 utilities::TestRng,
186 };
187
188 type CurrentNetwork = MainnetV0;
189
190 const ITERATIONS: usize = 100;
191
192 pub(crate) fn sample_proposal_cache(
193 signer: &PrivateKey<CurrentNetwork>,
194 rng: &mut TestRng,
195 ) -> ProposalCache<CurrentNetwork> {
196 let proposal = sample_proposal(rng);
197 let signed_proposals = sample_signed_proposals(signer, rng);
198 let round = proposal.round();
199 let pending_certificates = sample_batch_certificates(rng);
200
201 ProposalCache::new(round, Some(proposal), signed_proposals, pending_certificates)
202 }
203
204 #[test]
205 fn test_bytes() {
206 let rng = &mut TestRng::default();
207 let singer_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
208
209 for _ in 0..ITERATIONS {
210 let expected = sample_proposal_cache(&singer_private_key, rng);
211 let expected_bytes = expected.to_bytes_le().unwrap();
213 assert_eq!(expected, ProposalCache::read_le(&expected_bytes[..]).unwrap());
214 }
215 }
216}