ledger.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaOS 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 crate::{LedgerService, fmt_id, spawn_blocking}; 17 18 use alphaos_utilities::Stoppable; 19 20 use alphavm::{ 21 ledger::{ 22 Block, 23 Ledger, 24 PendingBlock, 25 Transaction, 26 committee::Committee, 27 narwhal::{BatchCertificate, Data, Subdag, Transmission, TransmissionID}, 28 puzzle::{Solution, SolutionID}, 29 store::ConsensusStorage, 30 }, 31 prelude::{ 32 Address, 33 ConsensusVersion, 34 Field, 35 FromBytes, 36 Network, 37 Result, 38 bail, 39 cfg_into_iter, 40 consensus_config_value_by_version, 41 deploy_compute_cost_in_microcredits, 42 deployment_cost, 43 execute_compute_cost_in_microcredits, 44 execution_cost, 45 }, 46 }; 47 48 use anyhow::ensure; 49 use indexmap::IndexMap; 50 #[cfg(feature = "locktick")] 51 use locktick::parking_lot::RwLock; 52 #[cfg(not(feature = "locktick"))] 53 use parking_lot::RwLock; 54 #[cfg(not(feature = "serial"))] 55 use rayon::prelude::*; 56 57 use std::{fmt, io::Read, ops::Range, sync::Arc}; 58 59 /// A core ledger service. 60 #[allow(clippy::type_complexity)] 61 pub struct CoreLedgerService<N: Network, C: ConsensusStorage<N>> { 62 ledger: Ledger<N, C>, 63 latest_leader: Arc<RwLock<Option<(u64, Address<N>)>>>, 64 stoppable: Arc<dyn Stoppable>, 65 } 66 67 impl<N: Network, C: ConsensusStorage<N>> CoreLedgerService<N, C> { 68 /// Initializes a new core ledger service. 69 pub fn new(ledger: Ledger<N, C>, stoppable: Arc<dyn Stoppable>) -> Self { 70 Self { ledger, latest_leader: Default::default(), stoppable } 71 } 72 } 73 74 impl<N: Network, C: ConsensusStorage<N>> fmt::Debug for CoreLedgerService<N, C> { 75 /// Implements a custom `fmt::Debug` for `CoreLedgerService`. 76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 77 f.debug_struct("CoreLedgerService").field("current_committee", &self.current_committee()).finish() 78 } 79 } 80 81 #[async_trait] 82 impl<N: Network, C: ConsensusStorage<N>> LedgerService<N> for CoreLedgerService<N, C> { 83 /// Returns the latest round in the ledger. 84 fn latest_round(&self) -> u64 { 85 self.ledger.latest_round() 86 } 87 88 /// Returns the latest block height in the ledger. 89 fn latest_block_height(&self) -> u32 { 90 self.ledger.latest_height() 91 } 92 93 /// Returns the latest block in the ledger. 94 fn latest_block(&self) -> Block<N> { 95 self.ledger.latest_block() 96 } 97 98 /// Returns the latest restrictions ID in the ledger. 99 fn latest_restrictions_id(&self) -> Field<N> { 100 self.ledger.vm().restrictions().restrictions_id() 101 } 102 103 /// Returns the latest cached leader and its associated round. 104 fn latest_leader(&self) -> Option<(u64, Address<N>)> { 105 *self.latest_leader.read() 106 } 107 108 /// Updates the latest cached leader and its associated round. 109 fn update_latest_leader(&self, round: u64, leader: Address<N>) { 110 *self.latest_leader.write() = Some((round, leader)); 111 } 112 113 /// Returns `true` if the given block height exists in the ledger. 114 fn contains_block_height(&self, height: u32) -> bool { 115 self.ledger.contains_block_height(height).unwrap_or(false) 116 } 117 118 /// Returns the block height for the given block hash, if it exists. 119 fn get_block_height(&self, hash: &N::BlockHash) -> Result<u32> { 120 self.ledger.get_height(hash) 121 } 122 123 /// Returns the block hash for the given block height, if it exists. 124 fn get_block_hash(&self, height: u32) -> Result<N::BlockHash> { 125 self.ledger.get_hash(height) 126 } 127 128 /// Returns the block round for the given block height, if it exists. 129 fn get_block_round(&self, height: u32) -> Result<u64> { 130 self.ledger.get_block(height).map(|block| block.round()) 131 } 132 133 /// Returns the block for the given block height. 134 fn get_block(&self, height: u32) -> Result<Block<N>> { 135 self.ledger.get_block(height) 136 } 137 138 /// Returns the blocks in the given block range. 139 /// The range is inclusive of the start and exclusive of the end. 140 fn get_blocks(&self, heights: Range<u32>) -> Result<Vec<Block<N>>> { 141 cfg_into_iter!(heights).map(|height| self.get_block(height)).collect() 142 } 143 144 /// Returns the solution for the given solution ID. 145 fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Solution<N>> { 146 self.ledger.get_solution(solution_id) 147 } 148 149 /// Returns the unconfirmed transaction for the given transaction ID. 150 fn get_unconfirmed_transaction(&self, transaction_id: N::TransactionID) -> Result<Transaction<N>> { 151 self.ledger.get_unconfirmed_transaction(&transaction_id) 152 } 153 154 /// Returns the batch certificate for the given batch certificate ID. 155 fn get_batch_certificate(&self, certificate_id: &Field<N>) -> Result<BatchCertificate<N>> { 156 match self.ledger.get_batch_certificate(certificate_id) { 157 Ok(Some(certificate)) => Ok(certificate), 158 Ok(None) => bail!("No batch certificate found for certificate ID {certificate_id} in the ledger"), 159 Err(error) => Err(error), 160 } 161 } 162 163 /// Returns the current committee. 164 fn current_committee(&self) -> Result<Committee<N>> { 165 self.ledger.latest_committee() 166 } 167 168 /// Returns the committee for the given round. 169 fn get_committee_for_round(&self, round: u64) -> Result<Committee<N>> { 170 match self.ledger.get_committee_for_round(round)? { 171 Some(committee) => Ok(committee), 172 None => bail!("No committee found for round {round} in the ledger"), 173 } 174 } 175 176 /// Returns the committee lookback for the given round. 177 fn get_committee_lookback_for_round(&self, round: u64) -> Result<Committee<N>> { 178 // Get the round number for the previous committee. Note, we subtract 2 from odd rounds, 179 // because committees are updated in even rounds. 180 let previous_round = match round % 2 == 0 { 181 true => round.saturating_sub(1), 182 false => round.saturating_sub(2), 183 }; 184 185 // Get the committee lookback round. 186 let committee_lookback_round = previous_round.saturating_sub(Committee::<N>::COMMITTEE_LOOKBACK_RANGE); 187 188 // Retrieve the committee for the committee lookback round. 189 self.get_committee_for_round(committee_lookback_round) 190 } 191 192 /// Returns `true` if the ledger contains the given certificate ID in block history. 193 fn contains_certificate(&self, certificate_id: &Field<N>) -> Result<bool> { 194 self.ledger.contains_certificate(certificate_id) 195 } 196 197 /// Returns `true` if the transmission exists in the ledger. 198 fn contains_transmission(&self, transmission_id: &TransmissionID<N>) -> Result<bool> { 199 match transmission_id { 200 TransmissionID::Ratification => Ok(false), 201 TransmissionID::Solution(solution_id, _) => self.ledger.contains_solution_id(solution_id), 202 TransmissionID::Transaction(transaction_id, _) => self.ledger.contains_transaction_id(transaction_id), 203 } 204 } 205 206 /// Ensures that the given transmission is not a fee and matches the given transmission ID. 207 fn ensure_transmission_is_well_formed( 208 &self, 209 transmission_id: TransmissionID<N>, 210 transmission: &mut Transmission<N>, 211 ) -> Result<()> { 212 match (transmission_id, transmission) { 213 (TransmissionID::Ratification, Transmission::Ratification) => { 214 bail!("Ratification transmissions are currently not supported.") 215 } 216 ( 217 TransmissionID::Transaction(expected_transaction_id, expected_checksum), 218 Transmission::Transaction(transaction_data), 219 ) => { 220 // Deserialize the transaction. If the transaction exceeds the maximum size, then return an error. 221 let transaction = match transaction_data.clone() { 222 Data::Object(transaction) => transaction, 223 Data::Buffer(bytes) => Transaction::<N>::read_le(&mut bytes.take(N::MAX_TRANSACTION_SIZE as u64))?, 224 }; 225 // Ensure the transaction ID matches the expected transaction ID. 226 if transaction.id() != expected_transaction_id { 227 bail!( 228 "Received mismatching transaction ID - expected {}, found {}", 229 fmt_id(expected_transaction_id), 230 fmt_id(transaction.id()), 231 ); 232 } 233 234 // Ensure the transmission checksum matches the expected checksum. 235 let checksum = transaction_data.to_checksum::<N>()?; 236 if checksum != expected_checksum { 237 bail!( 238 "Received mismatching checksum for transaction {} - expected {expected_checksum} but found {checksum}", 239 fmt_id(expected_transaction_id) 240 ); 241 } 242 243 // Ensure the transaction is not a fee transaction. 244 if transaction.is_fee() { 245 bail!("Received a fee transaction in a transmission"); 246 } 247 248 // Update the transmission with the deserialized transaction. 249 *transaction_data = Data::Object(transaction); 250 } 251 ( 252 TransmissionID::Solution(expected_solution_id, expected_checksum), 253 Transmission::Solution(solution_data), 254 ) => { 255 match solution_data.clone().deserialize_blocking() { 256 Ok(solution) => { 257 if solution.id() != expected_solution_id { 258 bail!( 259 "Received mismatching solution ID - expected {}, found {}", 260 fmt_id(expected_solution_id), 261 fmt_id(solution.id()), 262 ); 263 } 264 265 // Ensure the transmission checksum matches the expected checksum. 266 let checksum = solution_data.to_checksum::<N>()?; 267 if checksum != expected_checksum { 268 bail!( 269 "Received mismatching checksum for solution {} - expected {expected_checksum} but found {checksum}", 270 fmt_id(expected_solution_id) 271 ); 272 } 273 274 // Update the transmission with the deserialized solution. 275 *solution_data = Data::Object(solution); 276 } 277 Err(err) => { 278 bail!("Failed to deserialize solution: {err}"); 279 } 280 } 281 } 282 _ => { 283 bail!("Mismatching `(transmission_id, transmission)` pair"); 284 } 285 } 286 287 Ok(()) 288 } 289 290 /// Checks the given solution is well-formed. 291 async fn check_solution_basic(&self, solution_id: SolutionID<N>, solution: Data<Solution<N>>) -> Result<()> { 292 // Deserialize the solution. 293 let solution = spawn_blocking!(solution.deserialize_blocking())?; 294 // Ensure the solution ID matches in the solution. 295 if solution_id != solution.id() { 296 bail!("Invalid solution - expected {solution_id}, found {}", solution.id()); 297 } 298 299 // Check if the prover has reached their solution limit. 300 // While snarkVM will ultimately abort any excess solutions for safety, performing this check 301 // here prevents the to-be aborted solutions from propagating through the network. 302 let prover_address = solution.address(); 303 if self.ledger.is_solution_limit_reached(&prover_address, 0) { 304 bail!( 305 "Invalid Solution '{}' - Prover '{prover_address}' has reached their solution limit for the current epoch", 306 fmt_id(solution.id()) 307 ); 308 } 309 // Compute the current epoch hash. 310 let epoch_hash = self.ledger.latest_epoch_hash()?; 311 // Retrieve the current proof target. 312 let proof_target = self.ledger.latest_proof_target(); 313 314 // Ensure that the solution is valid for the given epoch. 315 let puzzle = self.ledger.puzzle().clone(); 316 match spawn_blocking!(puzzle.check_solution(&solution, epoch_hash, proof_target)) { 317 Ok(()) => Ok(()), 318 Err(e) => bail!("Invalid solution '{}' for the current epoch - {e}", fmt_id(solution_id)), 319 } 320 } 321 322 /// Checks the given transaction is well-formed and unique. 323 async fn check_transaction_basic( 324 &self, 325 transaction_id: N::TransactionID, 326 transaction: Transaction<N>, 327 ) -> Result<()> { 328 // Ensure the transaction ID matches in the transaction. 329 if transaction_id != transaction.id() { 330 bail!("Invalid transaction - expected {transaction_id}, found {}", transaction.id()); 331 } 332 // Check if the transmission is a fee transaction. 333 if transaction.is_fee() { 334 bail!("Invalid transaction - 'Transaction::fee' type is not valid at this stage ({})", transaction.id()); 335 } 336 // Check the transaction is well-formed. 337 let ledger = self.ledger.clone(); 338 spawn_blocking!(ledger.check_transaction_basic(&transaction, None, &mut rand::thread_rng())) 339 } 340 341 fn check_block_subdag(&self, block: Block<N>, prefix: &[PendingBlock<N>]) -> Result<PendingBlock<N>> { 342 Ok(self.ledger.check_block_subdag(block, prefix)?) 343 } 344 345 fn check_block_content(&self, block: PendingBlock<N>) -> Result<Block<N>> { 346 Ok(self.ledger.check_block_content(block, &mut rand::thread_rng())?) 347 } 348 349 /// Checks the given block is valid next block. 350 fn check_next_block(&self, block: &Block<N>) -> Result<()> { 351 self.ledger.check_next_block(block, &mut rand::thread_rng()) 352 } 353 354 /// Returns a candidate for the next block in the ledger, using a committed subdag and its transmissions. 355 #[cfg(feature = "ledger-write")] 356 fn prepare_advance_to_next_quorum_block( 357 &self, 358 subdag: Subdag<N>, 359 transmissions: IndexMap<TransmissionID<N>, Transmission<N>>, 360 ) -> Result<Block<N>> { 361 self.ledger.prepare_advance_to_next_quorum_block(subdag, transmissions, &mut rand::thread_rng()) 362 } 363 364 /// Adds the given block as the next block in the ledger. 365 #[cfg(feature = "ledger-write")] 366 fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> { 367 // If the Ctrl-C handler registered the signal, then skip advancing to the next block. 368 if self.stoppable.is_stopped() { 369 bail!("Skipping advancing to block {} - The node is shutting down", block.height()); 370 } 371 // Advance to the next block. 372 self.ledger.advance_to_next_block(block)?; 373 // Update BFT metrics. 374 #[cfg(feature = "metrics")] 375 { 376 let num_sol = block.solutions().len(); 377 let num_tx = block.transactions().len(); 378 379 metrics::gauge(metrics::bft::HEIGHT, block.height() as f64); 380 metrics::gauge(metrics::bft::LAST_COMMITTED_ROUND, block.round() as f64); 381 metrics::increment_gauge(metrics::blocks::SOLUTIONS, num_sol as f64); 382 metrics::increment_gauge(metrics::blocks::TRANSACTIONS, num_tx as f64); 383 metrics::update_block_metrics(block); 384 } 385 386 tracing::info!("\n\nAdvanced to block {} at round {} - {}\n", block.height(), block.round(), block.hash()); 387 Ok(()) 388 } 389 390 /// Returns the spend for a transaction in microcredits. 391 /// This is used to limit the amount of compute in the block generation hot 392 /// path. This does NOT represent the full costs which a user has to pay. 393 fn transaction_spend_in_microcredits( 394 &self, 395 transaction: &Transaction<N>, 396 consensus_version: ConsensusVersion, 397 ) -> Result<u64> { 398 let transaction_spend_limit = 399 consensus_config_value_by_version!(N, TRANSACTION_SPEND_LIMIT, consensus_version).unwrap(); 400 let id = transaction.id(); 401 match transaction { 402 Transaction::Deploy(_, _, _, deployment, _) => { 403 let (_, cost_details) = 404 deployment_cost(&self.ledger.vm().process().read(), deployment, consensus_version)?; 405 let compute_spend = deploy_compute_cost_in_microcredits(cost_details, consensus_version)?; 406 ensure!( 407 compute_spend <= transaction_spend_limit, 408 "Transaction '{id}' exceeds the transaction spend limit with compute_spend: '{compute_spend}'" 409 ); 410 Ok(compute_spend) 411 } 412 Transaction::Execute(_, _, execution, _) => { 413 let (_, cost_details) = 414 execution_cost(&self.ledger.vm().process().read(), execution, consensus_version)?; 415 let compute_spend = execute_compute_cost_in_microcredits(cost_details, consensus_version)?; 416 if consensus_version >= ConsensusVersion::V11 { 417 // From V11, add this check for consistency with our deployment checks. 418 ensure!( 419 compute_spend <= transaction_spend_limit, 420 "Transaction '{id}' exceeds the transaction spend limit with compute_spend: '{compute_spend}'" 421 ); 422 } 423 Ok(compute_spend) 424 } 425 Transaction::Fee(..) => { 426 bail!("Fee transactions are internal to the VM, transaction {id} is invalid.") 427 } 428 } 429 } 430 }