use super::{logger::initialize_terminal_logger, *};
use serde_json::json;
use std::net::SocketAddr;
use aleo_std_storage::StorageMode;
use snarkvm::{
ledger::store::helpers::memory::ConsensusMemory,
prelude::{Block, FromBytes, Ledger, PrivateKey, TEST_CONSENSUS_VERSION_HEIGHTS, TestnetV0},
};
use crate::cli::commands::devnode::rest::Rest;
#[derive(Parser, Debug)]
#[group(id = "start_args")]
pub struct Start {
#[clap(short = 'v', long, help = "devnode verbosity (0-2)", default_value = "2")]
pub(crate) verbosity: u8,
#[clap(long, help = "devnode REST API server address", default_value = "127.0.0.1:3030")]
pub(crate) socket_addr: String,
#[clap(long, help = "path to genesis block file", default_value = "blank")]
pub(crate) genesis_path: String,
#[clap(long, help = "disables automatic block creation after broadcast", default_value = "false")]
pub(crate) manual_block_creation: bool,
}
impl Command for Start {
type Input = Option<String>;
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, _context: Context) -> Result<Self::Input> {
Ok(None)
}
fn apply(self, _context: Context, private_key: Self::Input) -> Result<Self::Output> {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async { start_devnode(self, private_key).await })
}
}
async fn start_devnode(command: Start, private_key: Option<String>) -> Result<()> {
println!("Starting the Devnode server...");
let private_key = resolve_private_key(&private_key)?;
initialize_terminal_logger(command.verbosity).expect("Failed to initialize logger");
let socket_addr: SocketAddr = command
.socket_addr
.parse()
.map_err(|e| CliError::custom(format!("Failed to parse listener address '{}': {}", command.socket_addr, e)))?;
let rps = 999999999;
let genesis_block: Block<TestnetV0> = if command.genesis_path != "blank" {
Block::from_bytes_le(&std::fs::read(&command.genesis_path).map_err(|e| {
CliError::custom(format!("Failed to read genesis block file '{}': {}", command.genesis_path, e))
})?)?
} else {
Block::from_bytes_le(include_bytes!("resources/genesis_8d710d7e2_40val_snarkos_dev_network.bin"))?
};
let storage_mode = StorageMode::new_test(None);
let ledger: Ledger<TestnetV0, ConsensusMemory<TestnetV0>> =
tokio::task::spawn_blocking(move || Ledger::load(genesis_block, storage_mode))
.await
.map_err(|e| CliError::custom(format!("Failed to load ledger: {e}")))??;
Rest::start(socket_addr, rps, ledger, command.manual_block_creation, private_key)
.await
.expect("Failed to start the REST API server");
println!("Server running on http://{socket_addr}");
if !command.manual_block_creation {
println!("Advancing the Devnode to the latest consensus version");
let last_height = TEST_CONSENSUS_VERSION_HEIGHTS.last().unwrap().1;
let client = reqwest::Client::new();
let payload = json!({
"num_blocks": last_height,
});
let _response = client
.post(format!("http://{}/testnet/block/create", command.socket_addr))
.header("Content-Type", "application/json")
.json(&payload)
.send()
.await;
}
std::future::pending::<()>().await;
Ok(())
}
fn resolve_private_key(private_key: &Option<String>) -> Result<PrivateKey<TestnetV0>> {
match private_key {
Some(pk) => {
Ok(PrivateKey::<TestnetV0>::from_str(pk)
.map_err(|e| CliError::custom(format!("Invalid private key: {e}")))?)
}
None => {
let pk = std::env::var("PRIVATE_KEY").map_err(|e| {
CliError::custom(format!(
"
Failed to load `PRIVATE_KEY` from the environment: {e}
Please either:
1. Use the --private-key flag: `leo devnode start --private-key <PRIVATE_KEY>`
2. Set the PRIVATE_KEY environment variable"
))
})?;
Ok(PrivateKey::<TestnetV0>::from_str(&pk)
.map_err(|e| CliError::custom(format!("Invalid private key: {e}")))?)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const VALID_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH";
const INVALID_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWa";
#[test]
fn test_blank_private_key_from_flag() {
let err = resolve_private_key(&Some(String::new())).unwrap_err();
assert!(err.to_string().contains("Invalid private key"));
}
#[test]
fn test_invalid_private_key_from_flag() {
let err = resolve_private_key(&Some(INVALID_PRIVATE_KEY.to_string())).unwrap_err();
assert!(err.to_string().contains("Invalid private key"));
}
#[test]
fn test_valid_private_key_from_flag() {
assert!(resolve_private_key(&Some(VALID_PRIVATE_KEY.to_string())).is_ok());
}
}