chain_builder.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 crate::{ 17 Block, Ledger, Transaction, Transmission, TransmissionID, 18 narwhal::{BatchCertificate, BatchHeader, Subdag}, 19 puzzle::Solution, 20 store::{ConsensusStore, helpers::memory::ConsensusMemory}, 21 }; 22 use alphavm_console::{ 23 account::{Address, PrivateKey}, 24 network::MainnetV0, 25 prelude::*, 26 }; 27 use alphavm_synthesizer::vm::VM; 28 29 use alpha_std::StorageMode; 30 31 use anyhow::{Context, Result}; 32 use indexmap::{IndexMap, IndexSet}; 33 use rand::SeedableRng; 34 use rand_chacha::ChaChaRng; 35 use std::collections::{BTreeMap, HashMap}; 36 use time::OffsetDateTime; 37 38 pub type CurrentNetwork = MainnetV0; 39 40 #[cfg(not(feature = "rocks"))] 41 pub type LedgerType<N> = alphavm_ledger_store::helpers::memory::ConsensusMemory<N>; 42 #[cfg(feature = "rocks")] 43 pub type LedgerType<N> = alphavm_ledger_store::helpers::rocksdb::ConsensusDB<N>; 44 45 /// Helper to build chains with custom structures for testing. 46 pub struct TestChainBuilder<N: Network> { 47 /// The keys of all validators. 48 private_keys: Vec<PrivateKey<N>>, 49 /// The underlying ledger. 50 ledger: Ledger<N, ConsensusMemory<N>>, 51 /// The round containing the leader certificate for the most recent block we generated. 52 last_block_round: u64, 53 /// The batch certificates of the last round we generated. 54 round_to_certificates: HashMap<u64, IndexMap<usize, BatchCertificate<N>>>, 55 /// The batch certificate of the last leader (if any). 56 previous_leader_certificate: Option<BatchCertificate<N>>, 57 /// The last round for each committee member where they created a batch. 58 /// Invariant: for any validator i, last_batch[i] <= last_committed_batch[i] 59 last_batch_round: HashMap<usize, u64>, 60 /// The last batch of a validator that was included in a block. 61 last_committed_batch_round: HashMap<usize, u64>, 62 /// The start of the test chain. 63 genesis_block: Block<N>, 64 } 65 66 /// Additional options you can pass to the builder when generating a set of blocks. 67 #[derive(Clone)] 68 pub struct GenerateBlocksOptions<N: Network> { 69 /// Do not include votes to the previous leader certificate 70 pub skip_votes: bool, 71 /// Do not generate certificates for the specific node indices (to simulate a partition). 72 pub skip_nodes: Vec<usize>, 73 /// A flag indicating that a number of initial "placeholder blocks" should be baked 74 /// wthout transactions in order to skip to the latest version of consensus. 75 pub skip_to_current_version: bool, 76 /// The number of validators. 77 pub num_validators: usize, 78 /// Preloaded transactions to populate the blocks with. 79 pub transactions: Vec<Transaction<N>>, 80 } 81 82 impl<N: Network> Default for GenerateBlocksOptions<N> { 83 fn default() -> Self { 84 Self { 85 skip_votes: false, 86 skip_nodes: Default::default(), 87 skip_to_current_version: false, 88 num_validators: 0, 89 transactions: Default::default(), 90 } 91 } 92 } 93 94 /// Additional options you can pass to the builder when generating a single block. 95 /// Note: As of now, all certificates for this block will have the given timestamp and contain listed transmissions. 96 #[derive(Clone)] 97 pub struct GenerateBlockOptions<N: Network> { 98 /// Do not include votes to the previous leader certificate 99 pub skip_votes: bool, 100 /// Do not generate certificates for the specific node indices (to simulate a partition). 101 pub skip_nodes: Vec<usize>, 102 /// The timestamp for this block. 103 pub timestamp: i64, 104 /// The transmissions to be included in the block. 105 pub solutions: Vec<Solution<N>>, 106 pub transactions: Vec<Transaction<N>>, 107 } 108 109 impl<N: Network> Default for GenerateBlockOptions<N> { 110 fn default() -> Self { 111 Self { 112 skip_votes: false, 113 skip_nodes: Default::default(), 114 transactions: Default::default(), 115 solutions: Default::default(), 116 timestamp: OffsetDateTime::now_utc().unix_timestamp(), 117 } 118 } 119 } 120 121 impl<N: Network> TestChainBuilder<N> { 122 /// Generate a new committee and genesis block. 123 pub fn initialize_components(committee_size: usize, rng: &mut TestRng) -> Result<(Vec<PrivateKey<N>>, Block<N>)> { 124 // Sample the genesis private key. 125 let private_key = PrivateKey::<N>::new(rng)?; 126 127 // Initialize the store. 128 let store = ConsensusStore::<_, ConsensusMemory<_>>::open(StorageMode::new_test(None)) 129 .with_context(|| "Failed to initialize consensus store")?; 130 // Create a genesis block with a seeded RNG to reproduce the same genesis private keys. 131 let seed: u64 = rng.r#gen(); 132 trace!("Using seed {seed} and key {} for genesis RNG", private_key); 133 let genesis_rng = &mut TestRng::from_seed(seed); 134 let genesis_block = VM::from(store).unwrap().genesis_beacon(&private_key, genesis_rng)?; 135 136 // Extract the private keys from the genesis committee by using the same RNG to sample private keys. 137 let genesis_rng = &mut TestRng::from_seed(seed); 138 let private_keys = (0..committee_size).map(|_| PrivateKey::new(genesis_rng).unwrap()).collect(); 139 140 trace!( 141 "Generated genesis block ({}) and private keys for all {committee_size} committee members", 142 genesis_block.hash() 143 ); 144 145 Ok((private_keys, genesis_block)) 146 } 147 148 /// Initialize the builder with the default quorum size. 149 pub fn new(rng: &mut TestRng) -> Result<Self> { 150 Self::new_with_quorum_size(4, rng) 151 } 152 153 /// Initialize the builder with the specified quorum size. 154 pub fn new_with_quorum_size(num_validators: usize, rng: &mut TestRng) -> Result<Self> { 155 let (private_keys, genesis) = Self::initialize_components(num_validators, rng)?; 156 Self::from_components(private_keys, genesis) 157 } 158 159 /// Initialize the builder with the specified genesis block.. 160 /// Note: this function mirrors the way the private keys are sampled in snarkOS `fn parse_genesis`. 161 pub fn new_with_quorum_size_and_genesis_block(num_validators: usize, genesis_path: String) -> Result<Self> { 162 // Attempts to load the genesis block file. 163 let buffer = std::fs::read(genesis_path)?; 164 // Return the genesis block. 165 let genesis = Block::from_bytes_le(&buffer)?; 166 /// The development mode RNG seed. 167 pub const DEVELOPMENT_MODE_RNG_SEED: u64 = 1234567890u64; 168 // Initialize the (fixed) RNG. 169 let mut rng = ChaChaRng::seed_from_u64(DEVELOPMENT_MODE_RNG_SEED); 170 // Initialize the development private keys. 171 let private_keys = (0..num_validators).map(|_| PrivateKey::new(&mut rng).unwrap()).collect(); 172 // Initialize the builder with the specified committee and genesis block. 173 Self::from_components(private_keys, genesis) 174 } 175 176 /// Initialize the builder with the specified committee and genesis block 177 pub fn from_components(private_keys: Vec<PrivateKey<N>>, genesis_block: Block<N>) -> Result<Self> { 178 // Initialize the ledger with the genesis block. 179 let ledger = Ledger::<N, ConsensusMemory<N>>::load(genesis_block.clone(), StorageMode::new_test(None)) 180 .with_context(|| "Failed to set up ledger for test chain")?; 181 182 ensure!(ledger.genesis_block == genesis_block); 183 184 Self::from_genesis(private_keys, genesis_block) 185 } 186 187 /// Initialize the builder with the specified committee and gensis block 188 pub fn from_genesis(private_keys: Vec<PrivateKey<N>>, genesis_block: Block<N>) -> Result<Self> { 189 // Initialize the ledger with the genesis block. 190 let ledger = Ledger::<N, ConsensusMemory<N>>::load(genesis_block.clone(), StorageMode::new_test(None)) 191 .with_context(|| "Failed to set up ledger for test chain")?; 192 193 Ok(Self { 194 private_keys, 195 ledger, 196 genesis_block, 197 last_batch_round: Default::default(), 198 last_committed_batch_round: Default::default(), 199 last_block_round: 0, 200 round_to_certificates: Default::default(), 201 previous_leader_certificate: Default::default(), 202 }) 203 } 204 205 /// Create multiple blocks, with fully-connected DAGs. 206 pub fn generate_blocks(&mut self, num_blocks: usize, rng: &mut TestRng) -> Result<Vec<Block<N>>> { 207 let num_validators = self.private_keys.len(); 208 209 self.generate_blocks_with_opts(num_blocks, GenerateBlocksOptions { num_validators, ..Default::default() }, rng) 210 } 211 212 /// Create multiple blocks, with additional parameters. 213 /// 214 /// # Panics 215 /// This function panics if called from an async context. 216 pub fn generate_blocks_with_opts( 217 &mut self, 218 num_blocks: usize, 219 mut options: GenerateBlocksOptions<N>, 220 rng: &mut TestRng, 221 ) -> Result<Vec<Block<N>>> { 222 assert!(num_blocks > 0, "Need to build at least one block"); 223 224 let mut result = vec![]; 225 226 // If configured, skip enough blocks to reach the current consensus version. 227 if options.skip_to_current_version { 228 let (version, target_height) = TEST_CONSENSUS_VERSION_HEIGHTS.last().unwrap(); 229 let mut current_height = self.ledger.latest_height(); 230 231 let diff = target_height.saturating_sub(current_height); 232 233 if diff > 0 { 234 println!("Skipping {diff} blocks to reach {version}"); 235 236 while current_height < *target_height && result.len() < num_blocks { 237 let options = GenerateBlockOptions { 238 skip_votes: options.skip_votes, 239 skip_nodes: options.skip_nodes.clone(), 240 ..Default::default() 241 }; 242 243 let block = self.generate_block_with_opts(options, rng)?; 244 current_height = block.height(); 245 result.push(block); 246 } 247 248 println!("Advanced to the current consensus version at height {target_height}"); 249 } else { 250 debug!("Already at the current consensus version. No blocks to skip."); 251 } 252 } 253 254 while result.len() < num_blocks { 255 let num_txs = (BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH * options.num_validators) 256 .min(options.transactions.len()); 257 258 let options = GenerateBlockOptions { 259 skip_votes: options.skip_votes, 260 skip_nodes: options.skip_nodes.clone(), 261 transactions: options.transactions.drain(..num_txs).collect(), 262 ..Default::default() 263 }; 264 265 let block = self.generate_block_with_opts(options, rng)?; 266 result.push(block); 267 } 268 269 Ok(result) 270 } 271 272 /// Create a new block, with a fully-connected DAG. 273 /// 274 /// This will "fill in" any gaps left in earlier rounds from non participating nodes. 275 pub fn generate_block(&mut self, rng: &mut TestRng) -> Result<Block<N>> { 276 self.generate_block_with_opts(GenerateBlockOptions::default(), rng) 277 } 278 279 /// Same as `generate_block` but with additional options/parameters. 280 pub fn generate_block_with_opts( 281 &mut self, 282 options: GenerateBlockOptions<N>, 283 rng: &mut TestRng, 284 ) -> Result<Block<N>> { 285 assert!( 286 options.skip_nodes.len() * 3 < self.private_keys.len(), 287 "Cannot mark more than f nodes as unavailable/skipped" 288 ); 289 290 let next_block_round = self.last_block_round + 2; 291 let mut cert_count = 0; 292 293 // SubDAGs can be at most GC rounds long. 294 // Batches from genesis round cannot be included in any block that isn't genesis 295 let mut round = next_block_round.checked_sub(BatchHeader::<N>::MAX_GC_ROUNDS as u64).unwrap_or(1).max(1); 296 297 let mut transmissions = IndexMap::default(); 298 299 for txn in options.transactions { 300 let txn_id = txn.id(); 301 let transmission = Transmission::from(txn); 302 let transmission_id = TransmissionID::Transaction(txn_id, transmission.to_checksum().unwrap().unwrap()); 303 304 transmissions.insert(transmission_id, transmission); 305 } 306 307 for solution in options.solutions { 308 let transmission = Transmission::from(solution); 309 let transmission_id = TransmissionID::Solution(solution.id(), transmission.to_checksum().unwrap().unwrap()); 310 311 transmissions.insert(transmission_id, transmission); 312 } 313 314 // ======================================= 315 // Create certificates for the new block. 316 // ======================================= 317 loop { 318 let mut created_anchor = false; 319 320 let previous_certificate_ids = if round == 1 { 321 IndexSet::default() 322 } else { 323 self.round_to_certificates 324 .get(&(round - 1)) 325 .unwrap() 326 .iter() 327 .filter_map(|(_, cert)| { 328 // If votes are skipped, remove previous leader cert from the set. 329 let skip = if let Some(leader) = &self.previous_leader_certificate { 330 options.skip_votes && leader.id() == cert.id() 331 } else { 332 false 333 }; 334 335 if skip { None } else { Some(cert.id()) } 336 }) 337 .collect() 338 }; 339 340 let committee = self.ledger.get_committee_lookback_for_round(round).unwrap().unwrap_or_else(|| { 341 panic!("No committee for round {round}"); 342 }); 343 344 for (key1_idx, private_key_1) in self.private_keys.iter().enumerate() { 345 if options.skip_nodes.contains(&key1_idx) { 346 continue; 347 } 348 // Don't recreate batches that already exist. 349 if self.last_batch_round.get(&key1_idx).unwrap_or(&0) >= &round { 350 continue; 351 } 352 353 let transmission_ids: IndexSet<_> = transmissions 354 .keys() 355 .skip(key1_idx * BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH) 356 .take(BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH) 357 .copied() 358 .collect(); 359 360 let batch_header = BatchHeader::new( 361 private_key_1, 362 round, 363 options.timestamp, 364 committee.id(), 365 transmission_ids.clone(), 366 previous_certificate_ids.clone(), 367 rng, 368 ) 369 .unwrap(); 370 371 // Add signatures for the batch header. 372 let signatures = self 373 .private_keys 374 .iter() 375 .enumerate() 376 .filter(|&(key2_idx, _)| key1_idx != key2_idx) 377 .map(|(_, private_key_2)| private_key_2.sign(&[batch_header.batch_id()], rng).unwrap()) 378 .collect(); 379 380 // Update the round at which this validator last created a batch. 381 self.last_batch_round.insert(key1_idx, round); 382 383 // Insert certificate into the round_to_certificates mapping. 384 self.round_to_certificates 385 .entry(round) 386 .or_default() 387 .insert(key1_idx, BatchCertificate::from(batch_header, signatures).unwrap()); 388 389 cert_count += 1; 390 391 // Check if this batch was an anchor. 392 if round % 2 == 0 { 393 let leader = committee.get_leader(round).unwrap(); 394 if leader == Address::try_from(private_key_1).unwrap() { 395 created_anchor = true; 396 } 397 } 398 } 399 400 // Anchor was confirmed by more than a third of the validators. 401 if created_anchor && round % 2 == 0 && self.last_block_round < round { 402 self.last_block_round = round; 403 break; 404 } 405 406 round += 1; 407 } 408 409 // ============================================================== 410 // Build a subdag from the new certificates and create the block. 411 // ============================================================== 412 let commit_round = round; 413 414 let leader_committee = self.ledger.get_committee_lookback_for_round(round).unwrap().unwrap(); 415 let leader = leader_committee.get_leader(commit_round).unwrap(); 416 let (leader_idx, leader_certificate) = 417 self.round_to_certificates.get(&commit_round).unwrap().iter().find(|(_, c)| c.author() == leader).unwrap(); 418 let leader_idx = *leader_idx; 419 let leader_certificate = leader_certificate.clone(); 420 421 // Construct the subdag for the new block. 422 let mut subdag_map = BTreeMap::new(); 423 424 // Figure out what the earliest round for the subDAG could be. 425 let start_round = if commit_round < BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64 { 426 1 427 } else { 428 commit_round - BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64 + 2 429 }; 430 431 for round in start_round..commit_round { 432 let mut to_insert = IndexSet::new(); 433 for idx in 0..self.private_keys.len() { 434 // Some of the batches we in previous rounds might not be new, 435 // and already included in a previous block. 436 let cround = self.last_committed_batch_round.entry(idx).or_default(); 437 // Batch already included in another block 438 if *cround >= round { 439 continue; 440 } 441 442 if let Some(cert) = self.round_to_certificates.entry(round).or_default().get(&idx) { 443 to_insert.insert(cert.clone()); 444 *cround = round; 445 } 446 } 447 if !to_insert.is_empty() { 448 subdag_map.insert(round, to_insert); 449 } 450 } 451 452 // Add the leader certificate. 453 // (special case, because it is the only cert included from the commit round) 454 subdag_map.insert(commit_round, [leader_certificate.clone()].into()); 455 self.last_committed_batch_round.insert(leader_idx, commit_round); 456 457 trace!("Generated {cert_count} certificates for the next block"); 458 459 // Construct the block. 460 let subdag = Subdag::from(subdag_map).unwrap(); 461 462 let block = self.ledger.prepare_advance_to_next_quorum_block(subdag, transmissions, rng)?; 463 464 // Skip to increase performance. 465 //self.ledger.check_next_block(&block, rng).with_context(|| "Failed to (internally) check generated block")?; 466 467 trace!("Generated new block {} at height {}", block.hash(), block.height()); 468 469 // Update the ledger state. 470 self.ledger 471 .advance_to_next_block(&block) 472 .with_context(|| "Failed to (internally) advance to generated block")?; 473 self.previous_leader_certificate = Some(leader_certificate.clone()); 474 475 trace!("Updated internal ledger to height {}", block.height()); 476 Ok(block) 477 } 478 479 /// Return the genesis block associated with the test chain 480 pub fn genesis_block(&self) -> &Block<N> { 481 &self.genesis_block 482 } 483 484 /// Returns the private keys of the genesis committee of this test chain 485 pub fn private_keys(&self) -> &[PrivateKey<N>] { 486 &self.private_keys 487 } 488 489 /// Returns the private keys of the genesis committee of this test chain 490 pub fn validator_key(&self, index: usize) -> &PrivateKey<N> { 491 &self.private_keys[index] 492 } 493 494 /// Returns the address of the specified validator. 495 pub fn validator_address(&self, index: usize) -> Address<N> { 496 Address::try_from(*self.validator_key(index)).unwrap() 497 } 498 499 /// Create a test ledger with this builder's genesis block. 500 pub fn instantiate_ledger(&self) -> Ledger<N, LedgerType<N>> { 501 Ledger::load(self.genesis_block().clone(), StorageMode::new_test(None)).unwrap() 502 } 503 }