main.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 deltavm_console::prelude::{
 17      CanaryV0,
 18      MainnetV0,
 19      Network,
 20      TEST_CONSENSUS_VERSION_HEIGHTS,
 21      TestRng,
 22      TestnetV0,
 23      ToBytes,
 24  };
 25  use deltavm_ledger::{
 26      Ledger,
 27      Transaction,
 28      store::helpers::rocksdb::ConsensusDB,
 29      test_helpers::{TestChainBuilder, chain_builder::GenerateBlocksOptions},
 30  };
 31  
 32  use alphastd::StorageMode;
 33  use anyhow::{Context, Result, bail};
 34  use clap::{Parser, builder::PossibleValuesParser};
 35  use std::{fs, path::Path, str::FromStr};
 36  use tracing::debug;
 37  
 38  #[derive(Parser)]
 39  struct Args {
 40      /// The number of validators active on the chain.
 41      /// (also corresponds to the number of certificates per round)
 42      num_validators: usize,
 43      /// The number of blocks to generator.
 44      num_blocks: usize,
 45      /// Load the genesis block for the chain from the specified path instead of generating it.
 46      #[clap(long)]
 47      genesis_path: Option<String>,
 48      /// Set a custom storage path for the ledger.
 49      /// By default, it will use the ledger of the first devnet validator.
 50      #[clap(long, short = 'p')]
 51      storage_path: Option<String>,
 52      /// Remove existing ledger if it already exists.
 53      #[clap(long, short = 'f')]
 54      force: bool,
 55      /// Load the transactions to be used with the generated blocks. They are expected to be
 56      /// stored in a JSON-encoded format.
 57      #[clap(long)]
 58      txs_path: Option<String>,
 59      /// The name of the network to generate the chain for.
 60      #[clap(long, value_parser=PossibleValuesParser::new(vec![CanaryV0::SHORT_NAME, TestnetV0::SHORT_NAME, MainnetV0::SHORT_NAME]), default_value=TestnetV0::SHORT_NAME)]
 61      network: String,
 62      /// Set the seed to used to generate the chain.
 63      #[clap(long)]
 64      seed: Option<u64>,
 65      /// Store serialized blocks directly on disk instead of going through ledger storage.
 66      #[clap(long, requires = "storage_path")]
 67      no_ledger: bool,
 68  }
 69  
 70  /// Removes an existing ledger (if any) from the filesystem.
 71  fn remove_ledger(network: u16, storage_mode: &StorageMode, force: bool) -> Result<()> {
 72      let path = alphastd::alpha_ledger_dir(network, storage_mode);
 73  
 74      if path.exists() && force {
 75          std::fs::remove_dir_all(&path).with_context(|| "Failed to remove existing ledger")?;
 76  
 77          println!("Removed existing ledger data at {path:?}");
 78      } else if path.exists() {
 79          bail!("There is already a ledger at {path:?}. Re-run with `--force` if you want to overwrite it");
 80      }
 81  
 82      Ok(())
 83  }
 84  
 85  fn main() -> Result<()> {
 86      // Enable logging.
 87      tracing_subscriber::fmt::init();
 88  
 89      let args = Args::parse();
 90  
 91      match args.network.as_str() {
 92          CanaryV0::SHORT_NAME => generate_testchain::<CanaryV0>(args),
 93          TestnetV0::SHORT_NAME => generate_testchain::<TestnetV0>(args),
 94          MainnetV0::SHORT_NAME => generate_testchain::<MainnetV0>(args),
 95          // This is caught by clap.
 96          _ => unreachable!(),
 97      }
 98  }
 99  
100  fn generate_testchain<N: Network>(args: Args) -> Result<()> {
101      let mut rng = if let Some(seed) = args.seed {
102          println!("Using seed of {seed}");
103          TestRng::from_seed(seed)
104      } else {
105          TestRng::default()
106      };
107  
108      let storage_mode = if let Some(path) = args.storage_path.clone() {
109          StorageMode::Custom(path.into(), None)
110      } else {
111          StorageMode::Development(0)
112      };
113  
114      remove_ledger(N::ID, &storage_mode, args.force)?;
115  
116      let mut txs = if let Some(path) = args.txs_path {
117          let path = Path::new(&path);
118  
119          if path.is_dir() {
120              println!("Attempting to load transactions from \"{}\"", path.display());
121          } else {
122              bail!("Cannot load transactions from \"{}\": not a valid directory", path.display());
123          }
124  
125          let mut txs = Vec::new();
126          for entry in fs::read_dir(path)? {
127              let entry = entry?;
128              let path = entry.path();
129  
130              let buffer = fs::read_to_string(path)?;
131              let tx = Transaction::<N>::from_str(&buffer)?;
132  
133              txs.push(tx);
134          }
135  
136          println!("Loaded {} tranactionss from \"{}\"", txs.len(), path.display());
137          txs
138      } else {
139          Default::default()
140      };
141  
142      let num_validators = args.num_validators;
143      let num_blocks = args.num_blocks;
144  
145      println!("Initializing test chain builder for {} with {num_validators} validators", N::SHORT_NAME);
146      let mut builder: TestChainBuilder<N> = match args.genesis_path {
147          Some(genesis_path) => TestChainBuilder::new_with_quorum_size_and_genesis_block(num_validators, genesis_path),
148          None => TestChainBuilder::new_with_quorum_size(num_validators, &mut rng),
149      }
150      .with_context(|| "Failed to set up test chain builder")?;
151  
152      println!("Generating {num_blocks} blocks");
153  
154      let mut pos = 0;
155      let mut blocks = vec![];
156  
157      // How many blocks to generate in a single batch.
158      const BATCH_SIZE: usize = 100;
159  
160      // How many transactions to insert per block.
161      let latest_consensus_height = TEST_CONSENSUS_VERSION_HEIGHTS.last().unwrap().1 as usize;
162      let num_txn_blocks = num_blocks.saturating_sub(latest_consensus_height);
163      let txns_per_block = txs.len().div_ceil(num_txn_blocks);
164  
165      while blocks.len() < num_blocks {
166          let current_height = blocks.len();
167          // How many blocks to generate in this batch.
168          let batch_size = (num_blocks - current_height).min(BATCH_SIZE);
169          // Generate set of transactions to insert in this batch.
170          let num_empty_blocks = latest_consensus_height.saturating_sub(current_height);
171          let num_txns = (batch_size.saturating_sub(num_empty_blocks)) * txns_per_block;
172          let transactions = txs.drain(..num_txns).collect();
173  
174          debug!("Generating next batch with {batch_size} blocks and {num_txns} transactions");
175          let mut batch = builder
176              .generate_blocks_with_opts(
177                  batch_size,
178                  GenerateBlocksOptions { transactions, skip_to_current_version: true, ..Default::default() },
179                  &mut rng,
180              )
181              .with_context(|| "Failed to generate blocks")?;
182  
183          pos += batch_size;
184          println!("Generated {pos} of {num_blocks} blocks");
185          blocks.append(&mut batch);
186      }
187  
188      if args.no_ledger {
189          let base_path = args.storage_path.unwrap();
190          fs::create_dir(base_path.clone())?;
191  
192          println!("Storing blocks as {base_path}/block{{height}}.data");
193  
194          // Store genesis block.
195          {
196              let path = format!("{base_path}/genesis.data");
197              let data = builder.genesis_block().to_bytes_le()?;
198              fs::write(path, data)?;
199          }
200  
201          // Store remaining blocks.
202          for block in blocks.into_iter() {
203              let path = format!("{base_path}/block{}.data", block.height());
204              let data = block.to_bytes_le()?;
205              fs::write(path, data)?;
206          }
207      } else {
208          println!("Done. Storing blocks to on-disk ledger.");
209  
210          let ledger = Ledger::<N, ConsensusDB<N>>::load_unchecked(builder.genesis_block().clone(), storage_mode)
211              .with_context(|| "Failed to initialize ledger")?;
212  
213          // Ensure there is only one active ledger at a time.
214          drop(builder);
215  
216          for block in blocks.into_iter() {
217              ledger.advance_to_next_block(&block)?;
218  
219              if block.height().is_multiple_of(100) {
220                  println!("Stored {} blocks out of {num_blocks} to disk", block.height());
221              }
222          }
223      }
224  
225      Ok(())
226  }