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 }