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 }