1use super::*;
17
18impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
19 pub fn prepare_advance_to_next_quorum_block<R: Rng + CryptoRng>(
21 &self,
22 subdag: Subdag<N>,
23 transmissions: IndexMap<TransmissionID<N>, Transmission<N>>,
24 rng: &mut R,
25 ) -> Result<Block<N>> {
26 let previous_block = self.latest_block();
28
29 let (ratifications, solutions, transactions) = decouple_transmissions(transmissions.into_iter())?;
31 ensure!(ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
33 let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) =
35 self.construct_block_template(&previous_block, Some(&subdag), ratifications, solutions, transactions, rng)?;
36
37 Block::new_quorum(
39 previous_block.hash(),
40 header,
41 subdag,
42 ratifications,
43 solutions,
44 aborted_solution_ids,
45 transactions,
46 aborted_transaction_ids,
47 )
48 }
49
50 pub fn prepare_advance_to_next_beacon_block<R: Rng + CryptoRng>(
52 &self,
53 private_key: &PrivateKey<N>,
54 candidate_ratifications: Vec<Ratify<N>>,
55 candidate_solutions: Vec<Solution<N>>,
56 candidate_transactions: Vec<Transaction<N>>,
57 rng: &mut R,
58 ) -> Result<Block<N>> {
59 ensure!(candidate_ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
61
62 let previous_block = self.latest_block();
64
65 let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) = self
67 .construct_block_template(
68 &previous_block,
69 None,
70 candidate_ratifications,
71 candidate_solutions,
72 candidate_transactions,
73 rng,
74 )?;
75
76 Block::new_beacon(
78 private_key,
79 previous_block.hash(),
80 header,
81 ratifications,
82 solutions,
83 aborted_solution_ids,
84 transactions,
85 aborted_transaction_ids,
86 rng,
87 )
88 }
89
90 pub fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
92 let mut current_block = self.current_block.write();
94 if current_block.is_genesis() {
96 ensure!(
99 current_block.height() == block.height() || current_block.height() + 1 == block.height(),
100 "The given block is not the direct successor of the latest block"
101 );
102 } else {
103 ensure!(
104 current_block.height() + 1 == block.height(),
105 "The given block is not the direct successor of the latest block"
106 );
107 }
108 self.vm.add_next_block(block)?;
110 *current_block = block.clone();
112 drop(current_block);
114
115 if let Ok(current_committee) = self.vm.finalize_store().committee_store().current_committee() {
117 *self.current_committee.write() = Some(current_committee);
118 }
119
120 if block.height() % N::NUM_BLOCKS_PER_EPOCH == 0 || self.current_epoch_hash.read().is_none() {
123 match self.get_epoch_hash(block.height()).ok() {
125 Some(epoch_hash) => {
126 trace!("Updating the current epoch hash at block {} to '{epoch_hash}'", block.height());
127 *self.current_epoch_hash.write() = Some(epoch_hash);
128 }
129 None => {
130 error!("Failed to update the current epoch hash at block {}", block.height());
131 }
132 }
133 self.epoch_provers_cache.write().clear();
135 } else {
136 if let Some(solutions) = block.solutions().as_deref() {
138 let mut epoch_provers_cache = self.epoch_provers_cache.write();
139 for (_, s) in solutions.iter() {
140 let _ = *epoch_provers_cache.entry(s.address()).and_modify(|e| *e += 1).or_insert(1);
141 }
142 }
143 }
144
145 Ok(())
146 }
147}
148
149pub fn split_candidate_solutions<T, F>(
151 mut candidate_solutions: Vec<T>,
152 max_solutions: usize,
153 mut verification_fn: F,
154) -> (Vec<T>, Vec<T>)
155where
156 T: Sized + Copy,
157 F: FnMut(&mut T) -> bool,
158{
159 let mut valid_candidate_solutions = Vec::with_capacity(max_solutions);
161 let mut aborted_candidate_solutions = Vec::new();
162 candidate_solutions.reverse();
164 let chunk_size = 16;
167 while !candidate_solutions.is_empty() {
168 if valid_candidate_solutions.len() >= max_solutions {
170 aborted_candidate_solutions.extend(candidate_solutions.into_iter().rev());
172 break;
173 }
174
175 let mut candidates_chunk = if candidate_solutions.len() > chunk_size {
177 candidate_solutions.split_off(candidate_solutions.len() - chunk_size)
178 } else {
179 std::mem::take(&mut candidate_solutions)
180 };
181
182 let verification_results = candidates_chunk.iter_mut().rev().map(|solution| {
184 let verified = verification_fn(solution);
185 (solution, verified)
186 });
187
188 for (solution, is_valid) in verification_results.into_iter() {
190 if is_valid && valid_candidate_solutions.len() < max_solutions {
191 valid_candidate_solutions.push(*solution);
192 } else {
193 aborted_candidate_solutions.push(*solution);
194 }
195 }
196 }
197
198 (valid_candidate_solutions, aborted_candidate_solutions)
206}
207
208impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
209 #[allow(clippy::type_complexity)]
211 fn construct_block_template<R: Rng + CryptoRng>(
212 &self,
213 previous_block: &Block<N>,
214 subdag: Option<&Subdag<N>>,
215 candidate_ratifications: Vec<Ratify<N>>,
216 candidate_solutions: Vec<Solution<N>>,
217 candidate_transactions: Vec<Transaction<N>>,
218 rng: &mut R,
219 ) -> Result<(Header<N>, Ratifications<N>, Solutions<N>, Vec<SolutionID<N>>, Transactions<N>, Vec<N::TransactionID>)>
220 {
221 let (solutions, aborted_solutions, solutions_root, combined_proof_target) = match candidate_solutions.is_empty()
223 {
224 true => (None, vec![], Field::<N>::zero(), 0u128),
225 false => {
226 let latest_epoch_hash = self.latest_epoch_hash()?;
228 let latest_proof_target = self.latest_proof_target();
230 let mut accepted_solutions: IndexMap<Address<N>, u64> = IndexMap::new();
232 let (valid_candidate_solutions, aborted_candidate_solutions) =
233 split_candidate_solutions(candidate_solutions, N::MAX_SOLUTIONS, |solution| {
234 let prover_address = solution.address();
235 let num_accepted_solutions = accepted_solutions.get(&prover_address).copied().unwrap_or(0);
236 if self.is_solution_limit_reached(&prover_address, num_accepted_solutions) {
238 return false;
239 }
240 match self.puzzle().check_solution_mut(solution, latest_epoch_hash, latest_proof_target) {
242 Ok(()) => {
244 *accepted_solutions.entry(prover_address).or_insert(0) += 1;
245 true
246 }
247 Err(_) => false,
249 }
250 });
251
252 match valid_candidate_solutions.is_empty() {
254 true => (None, aborted_candidate_solutions, Field::<N>::zero(), 0u128),
255 false => {
256 let solutions = PuzzleSolutions::new(valid_candidate_solutions)?;
258 let solutions_root = solutions.to_accumulator_point()?;
260 let combined_proof_target = self.puzzle().get_combined_proof_target(&solutions)?;
262 (Some(solutions), aborted_candidate_solutions, solutions_root, combined_proof_target)
264 }
265 }
266 }
267 };
268 let solutions = Solutions::from(solutions);
270
271 let aborted_solution_ids = aborted_solutions.iter().map(Solution::id).collect::<Vec<_>>();
273
274 let latest_state_root = self.latest_state_root();
276 let latest_cumulative_proof_target = previous_block.cumulative_proof_target();
278 let latest_coinbase_target = previous_block.coinbase_target();
280 let latest_cumulative_weight = previous_block.cumulative_weight();
282 let last_coinbase_target = previous_block.last_coinbase_target();
284 let last_coinbase_timestamp = previous_block.last_coinbase_timestamp();
286
287 let next_round = match subdag {
289 Some(subdag) => subdag.anchor_round(),
290 None => previous_block.round().saturating_add(1),
291 };
292 let next_height = previous_block.height().saturating_add(1);
294 let next_timestamp = match subdag {
296 Some(subdag) => {
297 let previous_committee_lookback = {
299 let penultimate_round = subdag.anchor_round().saturating_sub(1);
301 self.get_committee_lookback_for_round(penultimate_round)?
303 .ok_or(anyhow!("Failed to fetch committee lookback for round {penultimate_round}"))?
304 };
305 subdag.timestamp(&previous_committee_lookback)
307 }
308 None => OffsetDateTime::now_utc().unix_timestamp(),
309 };
310
311 let (
313 next_coinbase_target,
314 next_proof_target,
315 next_cumulative_proof_target,
316 next_cumulative_weight,
317 next_last_coinbase_target,
318 next_last_coinbase_timestamp,
319 ) = to_next_targets::<N>(
320 latest_cumulative_proof_target,
321 combined_proof_target,
322 latest_coinbase_target,
323 latest_cumulative_weight,
324 last_coinbase_target,
325 last_coinbase_timestamp,
326 next_timestamp,
327 )?;
328
329 let coinbase_reward = coinbase_reward::<N>(
331 next_height,
332 next_timestamp,
333 N::GENESIS_TIMESTAMP,
334 N::STARTING_SUPPLY,
335 N::ANCHOR_TIME,
336 N::ANCHOR_HEIGHT,
337 N::BLOCK_TIME,
338 combined_proof_target,
339 u64::try_from(latest_cumulative_proof_target)?,
340 latest_coinbase_target,
341 )?;
342
343 let state = FinalizeGlobalState::new::<N>(
345 next_round,
346 next_height,
347 next_cumulative_weight,
348 next_cumulative_proof_target,
349 previous_block.hash(),
350 )?;
351 let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = self.vm.speculate(
353 state,
354 next_timestamp.saturating_sub(previous_block.timestamp()),
355 Some(coinbase_reward),
356 candidate_ratifications,
357 &solutions,
358 candidate_transactions.iter(),
359 rng,
360 )?;
361
362 let ratifications_root = ratifications.to_ratifications_root()?;
364
365 let subdag_root = match subdag {
367 Some(subdag) => subdag.to_subdag_root()?,
368 None => Field::zero(),
369 };
370
371 let metadata = Metadata::new(
373 N::ID,
374 next_round,
375 next_height,
376 next_cumulative_weight,
377 next_cumulative_proof_target,
378 next_coinbase_target,
379 next_proof_target,
380 next_last_coinbase_target,
381 next_last_coinbase_timestamp,
382 next_timestamp,
383 )?;
384
385 let header = Header::from(
387 latest_state_root,
388 transactions.to_transactions_root()?,
389 transactions.to_finalize_root(ratified_finalize_operations)?,
390 ratifications_root,
391 solutions_root,
392 subdag_root,
393 metadata,
394 )?;
395
396 Ok((header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids))
398 }
399}