1use super::*;
17
18use anyhow::Context;
19
20impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
21 pub fn prepare_advance_to_next_quorum_block<R: Rng + CryptoRng>(
23 &self,
24 subdag: Subdag<N>,
25 transmissions: IndexMap<TransmissionID<N>, Transmission<N>>,
26 rng: &mut R,
27 ) -> Result<Block<N>> {
28 let previous_block = self.latest_block();
30
31 let (ratifications, solutions, transactions) = decouple_transmissions(transmissions.into_iter())?;
33 ensure!(ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
35 let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) = self
37 .construct_block_template(&previous_block, Some(&subdag), ratifications, solutions, transactions, rng)
38 .with_context(|| "Failed to construct block template")?;
39
40 Block::new_quorum(
42 previous_block.hash(),
43 header,
44 subdag,
45 ratifications,
46 solutions,
47 aborted_solution_ids,
48 transactions,
49 aborted_transaction_ids,
50 )
51 }
52
53 pub fn prepare_advance_to_next_beacon_block<R: Rng + CryptoRng>(
55 &self,
56 private_key: &PrivateKey<N>,
57 candidate_ratifications: Vec<Ratify<N>>,
58 candidate_solutions: Vec<Solution<N>>,
59 candidate_transactions: Vec<Transaction<N>>,
60 rng: &mut R,
61 ) -> Result<Block<N>> {
62 ensure!(candidate_ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
64
65 let previous_block = self.latest_block();
67
68 let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) = self
70 .construct_block_template(
71 &previous_block,
72 None,
73 candidate_ratifications,
74 candidate_solutions,
75 candidate_transactions,
76 rng,
77 )
78 .with_context(|| "Failed to construct block template")?;
79
80 Block::new_beacon(
82 private_key,
83 previous_block.hash(),
84 header,
85 ratifications,
86 solutions,
87 aborted_solution_ids,
88 transactions,
89 aborted_transaction_ids,
90 rng,
91 )
92 }
93
94 pub fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
96 let mut current_block = self.current_block.write();
98 if current_block.is_genesis()? {
100 ensure!(
103 current_block.height() == block.height() || current_block.height() + 1 == block.height(),
104 "The given block is not the direct successor of the latest block"
105 );
106 } else {
107 ensure!(block.height() != 0, "Non-genesis blocks cannot have height 0");
108 ensure!(
109 current_block.height() + 1 == block.height(),
110 "The given block is not the direct successor of the latest block"
111 );
112 }
113 self.vm.add_next_block(block).with_context(|| "Failed to add block to VM")?;
115 *current_block = block.clone();
117 drop(current_block);
119
120 if let Ok(current_committee) = self.vm.finalize_store().committee_store().current_committee() {
122 *self.current_committee.write() = Some(current_committee);
123 }
124
125 if block.height() % N::NUM_BLOCKS_PER_EPOCH == 0 || self.current_epoch_hash.read().is_none() {
128 match self.get_epoch_hash(block.height()).ok() {
130 Some(epoch_hash) => {
131 trace!("Updating the current epoch hash at block {} to '{epoch_hash}'", block.height());
132 *self.current_epoch_hash.write() = Some(epoch_hash);
133 }
134 None => {
135 error!("Failed to update the current epoch hash at block {}", block.height());
136 }
137 }
138 self.epoch_provers_cache.write().clear();
140 } else {
141 if let Some(solutions) = block.solutions().as_deref() {
143 let mut epoch_provers_cache = self.epoch_provers_cache.write();
144 for (_, s) in solutions.iter() {
145 let _ = *epoch_provers_cache.entry(s.address()).and_modify(|e| *e += 1).or_insert(1);
146 }
147 }
148 }
149
150 Ok(())
151 }
152}
153
154pub fn split_candidate_solutions<T, F>(
156 mut candidate_solutions: Vec<T>,
157 max_solutions: usize,
158 mut verification_fn: F,
159) -> (Vec<T>, Vec<T>)
160where
161 T: Sized + Copy,
162 F: FnMut(&mut T) -> bool,
163{
164 let mut valid_candidate_solutions = Vec::with_capacity(max_solutions);
166 let mut aborted_candidate_solutions = Vec::new();
167 candidate_solutions.reverse();
169 let chunk_size = 16;
172 while !candidate_solutions.is_empty() {
173 if valid_candidate_solutions.len() >= max_solutions {
175 aborted_candidate_solutions.extend(candidate_solutions.into_iter().rev());
177 break;
178 }
179
180 let mut candidates_chunk = if candidate_solutions.len() > chunk_size {
182 candidate_solutions.split_off(candidate_solutions.len() - chunk_size)
183 } else {
184 std::mem::take(&mut candidate_solutions)
185 };
186
187 let verification_results = candidates_chunk.iter_mut().rev().map(|solution| {
189 let verified = verification_fn(solution);
190 (solution, verified)
191 });
192
193 for (solution, is_valid) in verification_results.into_iter() {
195 if is_valid && valid_candidate_solutions.len() < max_solutions {
196 valid_candidate_solutions.push(*solution);
197 } else {
198 aborted_candidate_solutions.push(*solution);
199 }
200 }
201 }
202
203 (valid_candidate_solutions, aborted_candidate_solutions)
211}
212
213impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
214 #[allow(clippy::type_complexity)]
216 fn construct_block_template<R: Rng + CryptoRng>(
217 &self,
218 previous_block: &Block<N>,
219 subdag: Option<&Subdag<N>>,
220 candidate_ratifications: Vec<Ratify<N>>,
221 candidate_solutions: Vec<Solution<N>>,
222 candidate_transactions: Vec<Transaction<N>>,
223 rng: &mut R,
224 ) -> Result<(Header<N>, Ratifications<N>, Solutions<N>, Vec<SolutionID<N>>, Transactions<N>, Vec<N::TransactionID>)>
225 {
226 let (solutions, aborted_solutions, solutions_root, combined_proof_target) = match candidate_solutions.is_empty()
228 {
229 true => (None, vec![], Field::<N>::zero(), 0u128),
230 false => {
231 let latest_epoch_hash = self.latest_epoch_hash()?;
233 let latest_proof_target = self.latest_proof_target();
235 let mut accepted_solutions: IndexMap<Address<N>, u64> = IndexMap::new();
237 let (valid_candidate_solutions, aborted_candidate_solutions) =
238 split_candidate_solutions(candidate_solutions, N::MAX_SOLUTIONS, |solution| {
239 let prover_address = solution.address();
240 let num_accepted_solutions = accepted_solutions.get(&prover_address).copied().unwrap_or(0);
241 if self.is_solution_limit_reached(&prover_address, num_accepted_solutions) {
243 return false;
244 }
245 match self.puzzle().check_solution_mut(solution, latest_epoch_hash, latest_proof_target) {
247 Ok(()) => {
249 *accepted_solutions.entry(prover_address).or_insert(0) += 1;
250 true
251 }
252 Err(_) => false,
254 }
255 });
256
257 match valid_candidate_solutions.is_empty() {
259 true => (None, aborted_candidate_solutions, Field::<N>::zero(), 0u128),
260 false => {
261 let solutions = PuzzleSolutions::new(valid_candidate_solutions)?;
263 let solutions_root = solutions.to_accumulator_point()?;
265 let combined_proof_target = self.puzzle().get_combined_proof_target(&solutions)?;
267 (Some(solutions), aborted_candidate_solutions, solutions_root, combined_proof_target)
269 }
270 }
271 }
272 };
273 let solutions = Solutions::from(solutions);
275
276 let aborted_solution_ids = aborted_solutions.iter().map(Solution::id).collect::<Vec<_>>();
278
279 let latest_state_root = self.latest_state_root();
281 let latest_cumulative_proof_target = previous_block.cumulative_proof_target();
283 let latest_coinbase_target = previous_block.coinbase_target();
285 let latest_cumulative_weight = previous_block.cumulative_weight();
287 let last_coinbase_target = previous_block.last_coinbase_target();
289 let last_coinbase_timestamp = previous_block.last_coinbase_timestamp();
291
292 let next_round = match subdag {
294 Some(subdag) => subdag.anchor_round(),
295 None => previous_block.round().saturating_add(1),
296 };
297 let next_height = previous_block.height().saturating_add(1);
299 let next_timestamp = match subdag {
301 Some(subdag) => {
302 let previous_committee_lookback = {
304 let penultimate_round = subdag.anchor_round().saturating_sub(1);
306 self.get_committee_lookback_for_round(penultimate_round)?
308 .ok_or(anyhow!("Failed to fetch committee lookback for round {penultimate_round}"))?
309 };
310 subdag.timestamp(&previous_committee_lookback)
312 }
313 None => OffsetDateTime::now_utc().unix_timestamp(),
314 };
315
316 let (
318 next_coinbase_target,
319 next_proof_target,
320 next_cumulative_proof_target,
321 next_cumulative_weight,
322 next_last_coinbase_target,
323 next_last_coinbase_timestamp,
324 ) = to_next_targets::<N>(
325 latest_cumulative_proof_target,
326 combined_proof_target,
327 latest_coinbase_target,
328 latest_cumulative_weight,
329 last_coinbase_target,
330 last_coinbase_timestamp,
331 next_timestamp,
332 )?;
333
334 let coinbase_reward = coinbase_reward::<N>(
336 next_height,
337 next_timestamp,
338 N::GENESIS_TIMESTAMP,
339 N::STARTING_SUPPLY,
340 N::ANCHOR_TIME,
341 N::ANCHOR_HEIGHT,
342 N::BLOCK_TIME,
343 combined_proof_target,
344 u64::try_from(latest_cumulative_proof_target)?,
345 latest_coinbase_target,
346 )?;
347
348 let next_block_timestamp =
350 (next_height >= N::CONSENSUS_HEIGHT(ConsensusVersion::V12).unwrap_or_default()).then_some(next_timestamp);
351 let state = FinalizeGlobalState::new::<N>(
353 next_round,
354 next_height,
355 next_block_timestamp,
356 next_cumulative_weight,
357 next_cumulative_proof_target,
358 previous_block.hash(),
359 )?;
360 let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = self.vm.speculate(
362 state,
363 next_timestamp.saturating_sub(previous_block.timestamp()),
364 Some(coinbase_reward),
365 candidate_ratifications,
366 &solutions,
367 candidate_transactions.iter(),
368 rng,
369 )?;
370
371 let ratifications_root = ratifications.to_ratifications_root()?;
373
374 let subdag_root = match subdag {
376 Some(subdag) => subdag.to_subdag_root()?,
377 None => Field::zero(),
378 };
379
380 let metadata = Metadata::new(
382 N::ID,
383 next_round,
384 next_height,
385 next_cumulative_weight,
386 next_cumulative_proof_target,
387 next_coinbase_target,
388 next_proof_target,
389 next_last_coinbase_target,
390 next_last_coinbase_timestamp,
391 next_timestamp,
392 )?;
393
394 let header = Header::from(
396 latest_state_root,
397 transactions.to_transactions_root()?,
398 transactions.to_finalize_root(ratified_finalize_operations)?,
399 ratifications_root,
400 solutions_root,
401 subdag_root,
402 metadata,
403 )?;
404
405 Ok((header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids))
407 }
408}