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