/ ledger / src / advance.rs
advance.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use super::*;
 17  
 18  use anyhow::Context;
 19  
 20  impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
 21      /// Returns a candidate for the next block in the ledger, using a committed subdag and its transmissions.
 22      /// This candidate can then be passed to [`Ledger::advance_to_next_block`] to be added to the ledger.
 23      ///
 24      /// # Panics
 25      /// This function panics if called from an async context.
 26      pub fn prepare_advance_to_next_quorum_block<R: Rng + CryptoRng>(
 27          &self,
 28          subdag: Subdag<N>,
 29          transmissions: IndexMap<TransmissionID<N>, Transmission<N>>,
 30          rng: &mut R,
 31      ) -> Result<Block<N>> {
 32          // Retrieve the latest block as the previous block (for the next block).
 33          let previous_block = self.latest_block();
 34  
 35          // Decouple the transmissions into ratifications, solutions, and transactions.
 36          let (ratifications, solutions, transactions) = decouple_transmissions(transmissions.into_iter())?;
 37          // Currently, we do not support ratifications from the memory pool.
 38          ensure!(ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
 39          // Construct the block template.
 40          let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) =
 41              self.construct_block_template(&previous_block, Some(&subdag), ratifications, solutions, transactions, rng)?;
 42  
 43          // Construct the new quorum block.
 44          Block::new_quorum(
 45              previous_block.hash(),
 46              header,
 47              subdag,
 48              ratifications,
 49              solutions,
 50              aborted_solution_ids,
 51              transactions,
 52              aborted_transaction_ids,
 53          )
 54      }
 55  
 56      /// Returns a candidate for the next block in the ledger.
 57      /// This candidate can then be passed to [`Ledger::advance_to_next_block`] to be added to the ledger.
 58      ///
 59      /// Note, that beacon blocks are only used for testing purposes.
 60      /// Production code will most likely used `[Ledger::prepare_advance_to_next_quorum_block`] instead.
 61      ///
 62      /// # Panics
 63      /// This function panics if called from an async context.
 64      pub fn prepare_advance_to_next_beacon_block<R: Rng + CryptoRng>(
 65          &self,
 66          private_key: &PrivateKey<N>,
 67          candidate_ratifications: Vec<Ratify<N>>,
 68          candidate_solutions: Vec<Solution<N>>,
 69          candidate_transactions: Vec<Transaction<N>>,
 70          rng: &mut R,
 71      ) -> Result<Block<N>> {
 72          // Currently, we do not support ratifications from the memory pool.
 73          ensure!(candidate_ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
 74  
 75          // Retrieve the latest block as the previous block (for the next block).
 76          let previous_block = self.latest_block();
 77  
 78          // Construct the block template.
 79          let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) = self
 80              .construct_block_template(
 81                  &previous_block,
 82                  None,
 83                  candidate_ratifications,
 84                  candidate_solutions,
 85                  candidate_transactions,
 86                  rng,
 87              )?;
 88  
 89          // Construct the new beacon block.
 90          Block::new_beacon(
 91              private_key,
 92              previous_block.hash(),
 93              header,
 94              ratifications,
 95              solutions,
 96              aborted_solution_ids,
 97              transactions,
 98              aborted_transaction_ids,
 99              rng,
100          )
101      }
102  
103      /// Adds the given block as the next block in the ledger.
104      ///
105      /// This function expects a valid block, that either was created by a trusted source, or successfully passed
106      /// the blocks checks (e.g. [`Ledger::check_next_block`]).
107      /// Note, that it is still possible that this function returns an error for a valid block, if there are concurrent tasks
108      /// updating the ledger.
109      ///
110      /// # Panics
111      /// This function panics if called from an async context.
112      pub fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
113          // Acquire the write lock on the current block.
114          let mut current_block = self.current_block.write();
115          // Check again for any possible race conditions.
116          if current_block.is_genesis()? {
117              // current block is initialized as the genesis block, but the ledger will
118              // also advance to it on startup.
119              ensure!(
120                  current_block.height() == block.height() || current_block.height() + 1 == block.height(),
121                  "The given block is not the direct successor of the latest block"
122              );
123          } else {
124              ensure!(block.height() != 0, "Non-genesis blocks cannot have height 0");
125              ensure!(
126                  current_block.height() + 1 == block.height(),
127                  "The given block is not the direct successor of the latest block"
128              );
129          }
130          // Update the VM.
131          self.vm.add_next_block(block).with_context(|| "Failed to add block to VM")?;
132          // Update the current block.
133          *current_block = block.clone();
134          // Drop the write lock on the current block.
135          drop(current_block);
136  
137          // Update the cached committee from storage.
138          if let Ok(current_committee) = self.vm.finalize_store().committee_store().current_committee() {
139              *self.current_committee.write() = Some(current_committee);
140          }
141  
142          // If the block is the start of a new epoch, or the epoch hash has not been set,
143          // update the current epoch hash and clear the epoch prover cache.
144          if block.height() % N::NUM_BLOCKS_PER_EPOCH == 0 || self.current_epoch_hash.read().is_none() {
145              // Update and log the current epoch hash.
146              match self.get_epoch_hash(block.height()).ok() {
147                  Some(epoch_hash) => {
148                      trace!("Updating the current epoch hash at block {} to '{epoch_hash}'", block.height());
149                      *self.current_epoch_hash.write() = Some(epoch_hash);
150                  }
151                  None => {
152                      error!("Failed to update the current epoch hash at block {}", block.height());
153                  }
154              }
155              // Clear the epoch provers cache.
156              self.epoch_provers_cache.write().clear();
157          } else {
158              // If the block is not part of a new epoch, add the new provers to the epoch prover cache.
159              if let Some(solutions) = block.solutions().as_deref() {
160                  let mut epoch_provers_cache = self.epoch_provers_cache.write();
161                  for (_, s) in solutions.iter() {
162                      let _ = *epoch_provers_cache.entry(s.address()).and_modify(|e| *e += 1).or_insert(1);
163                  }
164              }
165          }
166  
167          Ok(())
168      }
169  }
170  
171  /// Splits candidate solutions into a collection of accepted ones and aborted ones.
172  pub fn split_candidate_solutions<T, F>(
173      mut candidate_solutions: Vec<T>,
174      max_solutions: usize,
175      mut verification_fn: F,
176  ) -> (Vec<T>, Vec<T>)
177  where
178      T: Sized + Copy,
179      F: FnMut(&mut T) -> bool,
180  {
181      // Separate the candidate solutions into valid and aborted solutions.
182      let mut valid_candidate_solutions = Vec::with_capacity(max_solutions);
183      let mut aborted_candidate_solutions = Vec::new();
184      // Reverse the candidate solutions in order to be able to chunk them more efficiently.
185      candidate_solutions.reverse();
186      // Verify the candidate solutions in chunks. This is done so that we can potentially
187      // perform these operations in parallel while keeping the end result deterministic.
188      let chunk_size = 16;
189      while !candidate_solutions.is_empty() {
190          // Check if the collection of valid solutions is full.
191          if valid_candidate_solutions.len() >= max_solutions {
192              // If that's the case, mark the rest of the candidates as aborted.
193              aborted_candidate_solutions.extend(candidate_solutions.into_iter().rev());
194              break;
195          }
196  
197          // Split off a chunk of the candidate solutions.
198          let mut candidates_chunk = if candidate_solutions.len() > chunk_size {
199              candidate_solutions.split_off(candidate_solutions.len() - chunk_size)
200          } else {
201              std::mem::take(&mut candidate_solutions)
202          };
203  
204          // Verify the solutions in the chunk.
205          let verification_results = candidates_chunk.iter_mut().rev().map(|solution| {
206              let verified = verification_fn(solution);
207              (solution, verified)
208          });
209  
210          // Process the results of the verification.
211          for (solution, is_valid) in verification_results.into_iter() {
212              if is_valid && valid_candidate_solutions.len() < max_solutions {
213                  valid_candidate_solutions.push(*solution);
214              } else {
215                  aborted_candidate_solutions.push(*solution);
216              }
217          }
218      }
219  
220      // The `aborted_candidate_solutions` can contain both verified and unverified solutions.
221      // When `check_solution_mut` is used as `verification_fn`, these aborted solutions
222      // may include both mutated and un-mutated variants. This occurs because the verification
223      // check is skipped once the `max_solutions` limit is reached.
224      //
225      // This approach is SAFE because currently, only the `solutionID` of aborted solutions is stored.
226      // However, if full aborted solutions need to be stored in the future, this logic will need to be revisited.
227      (valid_candidate_solutions, aborted_candidate_solutions)
228  }
229  
230  impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
231      /// Constructs a block template for the next block in the ledger.
232      ///
233      /// # Panics
234      /// This function panics if called from an async context.
235      #[allow(clippy::type_complexity)]
236      fn construct_block_template<R: Rng + CryptoRng>(
237          &self,
238          previous_block: &Block<N>,
239          subdag: Option<&Subdag<N>>,
240          candidate_ratifications: Vec<Ratify<N>>,
241          candidate_solutions: Vec<Solution<N>>,
242          candidate_transactions: Vec<Transaction<N>>,
243          rng: &mut R,
244      ) -> Result<(Header<N>, Ratifications<N>, Solutions<N>, Vec<SolutionID<N>>, Transactions<N>, Vec<N::TransactionID>)>
245      {
246          // Construct the solutions.
247          let (solutions, aborted_solutions, solutions_root, combined_proof_target) = match candidate_solutions.is_empty()
248          {
249              true => (None, vec![], Field::<N>::zero(), 0u128),
250              false => {
251                  // Retrieve the latest epoch hash.
252                  let latest_epoch_hash = self.latest_epoch_hash()?;
253                  // Retrieve the latest proof target.
254                  let latest_proof_target = self.latest_proof_target();
255                  // Separate the candidate solutions into valid and aborted solutions.
256                  let mut accepted_solutions: IndexMap<Address<N>, u64> = IndexMap::new();
257                  let (valid_candidate_solutions, aborted_candidate_solutions) =
258                      split_candidate_solutions(candidate_solutions, N::MAX_SOLUTIONS, |solution| {
259                          let prover_address = solution.address();
260                          let num_accepted_solutions = accepted_solutions.get(&prover_address).copied().unwrap_or(0);
261                          // Check if the prover has reached their solution limit.
262                          if self.is_solution_limit_reached(&prover_address, num_accepted_solutions) {
263                              return false;
264                          }
265                          // Check if the solution is valid and update the number of accepted solutions.
266                          match self.puzzle().check_solution_mut(solution, latest_epoch_hash, latest_proof_target) {
267                              // Increment the number of accepted solutions for the prover.
268                              Ok(()) => {
269                                  *accepted_solutions.entry(prover_address).or_insert(0) += 1;
270                                  true
271                              }
272                              // The solution is invalid, so we do not increment the number of accepted solutions.
273                              Err(_) => false,
274                          }
275                      });
276  
277                  // Check if there are any valid solutions.
278                  match valid_candidate_solutions.is_empty() {
279                      true => (None, aborted_candidate_solutions, Field::<N>::zero(), 0u128),
280                      false => {
281                          // Construct the solutions.
282                          let solutions = PuzzleSolutions::new(valid_candidate_solutions)?;
283                          // Compute the solutions root.
284                          let solutions_root = solutions.to_accumulator_point()?;
285                          // Compute the combined proof target.
286                          let combined_proof_target = self.puzzle().get_combined_proof_target(&solutions)?;
287                          // Output the solutions, solutions root, and combined proof target.
288                          (Some(solutions), aborted_candidate_solutions, solutions_root, combined_proof_target)
289                      }
290                  }
291              }
292          };
293          // Prepare the solutions.
294          let solutions = Solutions::from(solutions);
295  
296          // Construct the aborted solution IDs.
297          let aborted_solution_ids = aborted_solutions.iter().map(Solution::id).collect::<Vec<_>>();
298  
299          // Retrieve the latest state root.
300          let latest_state_root = self.latest_state_root();
301          // Retrieve the latest cumulative proof target.
302          let latest_cumulative_proof_target = previous_block.cumulative_proof_target();
303          // Retrieve the latest coinbase target.
304          let latest_coinbase_target = previous_block.coinbase_target();
305          // Retrieve the latest cumulative weight.
306          let latest_cumulative_weight = previous_block.cumulative_weight();
307          // Retrieve the last coinbase target.
308          let last_coinbase_target = previous_block.last_coinbase_target();
309          // Retrieve the last coinbase timestamp.
310          let last_coinbase_timestamp = previous_block.last_coinbase_timestamp();
311  
312          // Compute the next round number.
313          let next_round = match subdag {
314              Some(subdag) => subdag.anchor_round(),
315              None => previous_block.round().saturating_add(1),
316          };
317          // Compute the next height.
318          let next_height = previous_block.height().saturating_add(1);
319          // Determine the timestamp for the next block.
320          let next_timestamp = match subdag {
321              Some(subdag) => {
322                  // Retrieve the previous committee lookback.
323                  let previous_committee_lookback = {
324                      // Calculate the penultimate round, which is the round before the anchor round.
325                      let penultimate_round = subdag.anchor_round().saturating_sub(1);
326                      // Output the committee lookback for the penultimate round.
327                      self.get_committee_lookback_for_round(penultimate_round)?
328                          .ok_or(anyhow!("Failed to fetch committee lookback for round {penultimate_round}"))?
329                  };
330                  // Return the timestamp for the given committee lookback.
331                  subdag.timestamp(&previous_committee_lookback)
332              }
333              None => OffsetDateTime::now_utc().unix_timestamp(),
334          };
335  
336          // Calculate the next coinbase targets and timestamps.
337          let (
338              next_coinbase_target,
339              next_proof_target,
340              next_cumulative_proof_target,
341              next_cumulative_weight,
342              next_last_coinbase_target,
343              next_last_coinbase_timestamp,
344          ) = to_next_targets::<N>(
345              latest_cumulative_proof_target,
346              combined_proof_target,
347              latest_coinbase_target,
348              latest_cumulative_weight,
349              last_coinbase_target,
350              last_coinbase_timestamp,
351              next_timestamp,
352          )?;
353  
354          // Calculate the coinbase reward.
355          let coinbase_reward = coinbase_reward::<N>(
356              next_height,
357              next_timestamp,
358              N::GENESIS_TIMESTAMP,
359              N::STARTING_SUPPLY,
360              N::ANCHOR_TIME,
361              N::ANCHOR_HEIGHT,
362              N::BLOCK_TIME,
363              combined_proof_target,
364              u64::try_from(latest_cumulative_proof_target)?,
365              latest_coinbase_target,
366          )?;
367  
368          // Determine if the block timestamp should be included.
369          let next_block_timestamp =
370              (next_height >= N::CONSENSUS_HEIGHT(ConsensusVersion::V12).unwrap_or_default()).then_some(next_timestamp);
371          // Construct the finalize state.
372          let state = FinalizeGlobalState::new::<N>(
373              next_round,
374              next_height,
375              next_block_timestamp,
376              next_cumulative_weight,
377              next_cumulative_proof_target,
378              previous_block.hash(),
379          )?;
380          // Speculate over the ratifications, solutions, and transactions.
381          let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = self.vm.speculate(
382              state,
383              next_timestamp.saturating_sub(previous_block.timestamp()),
384              Some(coinbase_reward),
385              candidate_ratifications,
386              &solutions,
387              candidate_transactions.iter(),
388              rng,
389          )?;
390  
391          // Compute the ratifications root.
392          let ratifications_root = ratifications.to_ratifications_root()?;
393  
394          // Construct the subdag root.
395          let subdag_root = match subdag {
396              Some(subdag) => subdag.to_subdag_root()?,
397              None => Field::zero(),
398          };
399  
400          // Construct the metadata.
401          let metadata = Metadata::new(
402              N::ID,
403              next_round,
404              next_height,
405              next_cumulative_weight,
406              next_cumulative_proof_target,
407              next_coinbase_target,
408              next_proof_target,
409              next_last_coinbase_target,
410              next_last_coinbase_timestamp,
411              next_timestamp,
412          )?;
413  
414          // Construct the header.
415          let header = Header::from(
416              latest_state_root,
417              transactions.to_transactions_root()?,
418              transactions.to_finalize_root(ratified_finalize_operations)?,
419              ratifications_root,
420              solutions_root,
421              subdag_root,
422              metadata,
423          )?;
424  
425          // Return the block template.
426          Ok((header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids))
427      }
428  }