mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-06 21:34:00 -05:00
328 lines
11 KiB
Rust
328 lines
11 KiB
Rust
/* This file is part of DarkFi (https://dark.fi)
|
|
*
|
|
* Copyright (C) 2020-2026 Dyne.org foundation
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use std::sync::Arc;
|
|
|
|
use smol::{fs::read_to_string, stream::StreamExt};
|
|
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
|
|
use tracing::{debug, error, info};
|
|
|
|
use darkfi::{
|
|
async_daemonize,
|
|
blockchain::BlockInfo,
|
|
cli_desc,
|
|
net::settings::SettingsOpt,
|
|
rpc::settings::RpcSettingsOpt,
|
|
util::{
|
|
encoding::base64,
|
|
path::{expand_path, get_config_path},
|
|
},
|
|
validator::{Validator, ValidatorConfig},
|
|
Error, Result,
|
|
};
|
|
use darkfi_sdk::crypto::keypair::Network;
|
|
use darkfi_serial::deserialize_async;
|
|
|
|
use darkfid::{task::consensus::ConsensusInitTaskConfig, Darkfid};
|
|
|
|
const CONFIG_FILE: &str = "darkfid_config.toml";
|
|
const CONFIG_FILE_CONTENTS: &str = include_str!("../darkfid_config.toml");
|
|
/// Note:
|
|
/// If you change these don't forget to remove their corresponding database folder,
|
|
/// since if it already has a genesis block, provided one is ignored.
|
|
const GENESIS_BLOCK_LOCALNET: &str = include_str!("../genesis_block_localnet");
|
|
const GENESIS_BLOCK_TESTNET: &str = include_str!("../genesis_block_testnet");
|
|
const GENESIS_BLOCK_MAINNET: &str = include_str!("../genesis_block_mainnet");
|
|
|
|
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
|
|
#[serde(default)]
|
|
#[structopt(name = "darkfid", about = cli_desc!())]
|
|
struct Args {
|
|
#[structopt(short, long)]
|
|
/// Configuration file to use
|
|
config: Option<String>,
|
|
|
|
#[structopt(short, long, default_value = "testnet")]
|
|
/// Blockchain network to use
|
|
network: String,
|
|
|
|
#[structopt(short, long)]
|
|
/// Reset validator state to given block height
|
|
reset: Option<u32>,
|
|
|
|
#[structopt(short, long)]
|
|
/// Purge pending sync headers
|
|
purge_sync: bool,
|
|
|
|
#[structopt(long)]
|
|
/// Fully validates existing blockchain state
|
|
validate: bool,
|
|
|
|
#[structopt(long)]
|
|
/// Fully rebuild the difficulties database based on existing blockchain state
|
|
rebuild_difficulties: bool,
|
|
|
|
#[structopt(short, long)]
|
|
/// Set log file to ouput into
|
|
log: Option<String>,
|
|
|
|
#[structopt(short, parse(from_occurrences))]
|
|
/// Increase verbosity (-vvv supported)
|
|
verbose: u8,
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
|
|
#[structopt()]
|
|
/// Defines a blockchain network configuration.
|
|
/// Default values correspond to a local network.
|
|
pub struct BlockchainNetwork {
|
|
#[structopt(long, default_value = "~/.local/share/darkfi/darkfid/localnet")]
|
|
/// Path to blockchain database
|
|
database: String,
|
|
|
|
#[structopt(long, default_value = "3")]
|
|
/// Confirmation threshold, denominated by number of blocks
|
|
threshold: usize,
|
|
|
|
#[structopt(long, default_value = "120")]
|
|
/// PoW block production target, in seconds
|
|
pow_target: u32,
|
|
|
|
#[structopt(long)]
|
|
/// Optional fixed PoW difficulty, used for testing
|
|
pow_fixed_difficulty: Option<usize>,
|
|
|
|
#[structopt(long)]
|
|
/// Skip syncing process and start node right away
|
|
skip_sync: bool,
|
|
|
|
#[structopt(long)]
|
|
/// Disable transaction's fee verification, used for testing
|
|
skip_fees: bool,
|
|
|
|
#[structopt(long)]
|
|
/// Optional sync checkpoint height
|
|
checkpoint_height: Option<u32>,
|
|
|
|
#[structopt(long)]
|
|
/// Optional sync checkpoint hash
|
|
checkpoint: Option<String>,
|
|
|
|
#[structopt(long)]
|
|
/// Garbage collection task transactions batch size
|
|
txs_batch_size: Option<usize>,
|
|
|
|
#[structopt(flatten)]
|
|
/// P2P network settings
|
|
net: SettingsOpt,
|
|
|
|
#[structopt(flatten)]
|
|
/// Main server JSON-RPC settings
|
|
rpc: RpcSettingsOpt,
|
|
|
|
#[structopt(flatten)]
|
|
/// Management server JSON-RPC settings
|
|
management_rpc: RpcSettingsOpt,
|
|
|
|
#[structopt(skip)]
|
|
/// Stratum server JSON-RPC settings (optional)
|
|
stratum_rpc: Option<RpcSettingsOpt>,
|
|
|
|
#[structopt(skip)]
|
|
/// Merge mining server JSON-RPC settings (optional)
|
|
mm_rpc: Option<RpcSettingsOpt>,
|
|
}
|
|
|
|
async_daemonize!(realmain);
|
|
async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
|
|
info!(target: "darkfid", "Initializing DarkFi node...");
|
|
|
|
// Grab blockchain network configuration
|
|
let ((network, blockchain_config), genesis_block) = match args.network.as_str() {
|
|
"localnet" => {
|
|
(parse_blockchain_config(args.config, "localnet").await?, GENESIS_BLOCK_LOCALNET)
|
|
}
|
|
"testnet" => {
|
|
(parse_blockchain_config(args.config, "testnet").await?, GENESIS_BLOCK_TESTNET)
|
|
}
|
|
"mainnet" => {
|
|
(parse_blockchain_config(args.config, "mainnet").await?, GENESIS_BLOCK_MAINNET)
|
|
}
|
|
_ => {
|
|
error!("Unsupported chain `{}`", args.network);
|
|
return Err(Error::UnsupportedChain)
|
|
}
|
|
};
|
|
|
|
// Parse the genesis block
|
|
let bytes = base64::decode(genesis_block.trim()).unwrap();
|
|
let genesis_block: BlockInfo = deserialize_async(&bytes).await?;
|
|
|
|
// Initialize or open sled database
|
|
let db_path = expand_path(&blockchain_config.database)?;
|
|
let sled_db = sled_overlay::sled::open(&db_path)?;
|
|
|
|
// Initialize validator configuration
|
|
let pow_fixed_difficulty = if let Some(diff) = blockchain_config.pow_fixed_difficulty {
|
|
info!(target: "darkfid", "Node is configured to run with fixed PoW difficulty: {diff}");
|
|
Some(diff.into())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let config = ValidatorConfig {
|
|
confirmation_threshold: blockchain_config.threshold,
|
|
pow_target: blockchain_config.pow_target,
|
|
pow_fixed_difficulty,
|
|
genesis_block,
|
|
verify_fees: !blockchain_config.skip_fees,
|
|
};
|
|
|
|
// Check if reset was requested
|
|
if let Some(height) = args.reset {
|
|
info!(target: "darkfid", "Node will reset validator state to height: {height}");
|
|
let validator = Validator::new(&sled_db, &config).await?;
|
|
validator.reset_to_height(height).await?;
|
|
info!(target: "darkfid", "Validator state reset successfully!");
|
|
return Ok(())
|
|
}
|
|
|
|
// Check if sync headers purge was requested
|
|
if args.purge_sync {
|
|
info!(target: "darkfid", "Node will purge all pending sync headers.");
|
|
let validator = Validator::new(&sled_db, &config).await?;
|
|
validator.blockchain.headers.remove_all_sync()?;
|
|
info!(target: "darkfid", "Validator pending sync headers purged successfully!");
|
|
return Ok(())
|
|
}
|
|
|
|
// Check if validate was requested
|
|
if args.validate {
|
|
info!(target: "darkfid", "Node will validate existing blockchain state.");
|
|
let validator = Validator::new(&sled_db, &config).await?;
|
|
validator.validate_blockchain(config.pow_target, config.pow_fixed_difficulty).await?;
|
|
info!(target: "darkfid", "Validator blockchain state validated successfully!");
|
|
return Ok(())
|
|
}
|
|
|
|
// Check if rebuild difficulties was requested
|
|
if args.rebuild_difficulties {
|
|
info!(target: "darkfid", "Node will rebuild difficulties of existing blockchain state.");
|
|
let validator = Validator::new(&sled_db, &config).await?;
|
|
validator
|
|
.rebuild_block_difficulties(config.pow_target, config.pow_fixed_difficulty)
|
|
.await?;
|
|
info!(target: "darkfid", "Validator difficulties rebuilt successfully!");
|
|
return Ok(())
|
|
}
|
|
|
|
let p2p_settings: darkfi::net::Settings =
|
|
(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), blockchain_config.net).try_into()?;
|
|
|
|
// Generate the daemon
|
|
let daemon = Darkfid::init(
|
|
network,
|
|
&sled_db,
|
|
&config,
|
|
&p2p_settings,
|
|
&blockchain_config.txs_batch_size,
|
|
&ex,
|
|
)
|
|
.await?;
|
|
|
|
// Start the daemon
|
|
let config = ConsensusInitTaskConfig {
|
|
skip_sync: blockchain_config.skip_sync,
|
|
checkpoint_height: blockchain_config.checkpoint_height,
|
|
checkpoint: blockchain_config.checkpoint,
|
|
};
|
|
daemon
|
|
.start(
|
|
&ex,
|
|
&blockchain_config.rpc.into(),
|
|
&blockchain_config.management_rpc.into(),
|
|
&blockchain_config.stratum_rpc.map(|stratum_rpc_opts| stratum_rpc_opts.into()),
|
|
&blockchain_config.mm_rpc.map(|mm_rpc_opts| mm_rpc_opts.into()),
|
|
&config,
|
|
)
|
|
.await?;
|
|
|
|
// Signal handling for graceful termination.
|
|
let (signals_handler, signals_task) = SignalHandler::new(ex)?;
|
|
signals_handler.wait_termination(signals_task).await?;
|
|
info!(target: "darkfid", "Caught termination signal, cleaning up and exiting...");
|
|
|
|
daemon.stop().await?;
|
|
|
|
info!(target: "darkfid", "Shut down successfully");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Auxiliary function to parse darkfid configuration file and extract requested
|
|
/// blockchain network config.
|
|
pub async fn parse_blockchain_config(
|
|
config: Option<String>,
|
|
network: &str,
|
|
) -> Result<(Network, BlockchainNetwork)> {
|
|
// Grab network prefix
|
|
let used_net = match network {
|
|
"mainnet" | "localnet" => Network::Mainnet,
|
|
"testnet" => Network::Testnet,
|
|
_ => return Err(Error::ParseFailed("Invalid blockchain network")),
|
|
};
|
|
|
|
// Grab config path
|
|
let config_path = get_config_path(config, CONFIG_FILE)?;
|
|
debug!(target: "darkfid", "Parsing configuration file: {config_path:?}");
|
|
|
|
// Parse TOML file contents
|
|
let contents = read_to_string(&config_path).await?;
|
|
let contents: toml::Value = match toml::from_str(&contents) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
error!(target: "darkfid", "Failed parsing TOML config: {e}");
|
|
return Err(Error::ParseFailed("Failed parsing TOML config"))
|
|
}
|
|
};
|
|
|
|
// Grab requested network config
|
|
let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
|
|
let Some(network_configs) = table.get("network_config") else {
|
|
return Err(Error::ParseFailed("TOML does not contain network configurations"))
|
|
};
|
|
let Some(network_configs) = network_configs.as_table() else {
|
|
return Err(Error::ParseFailed("`network_config` not a map"))
|
|
};
|
|
let Some(network_config) = network_configs.get(network) else {
|
|
return Err(Error::ParseFailed("TOML does not contain requested network configuration"))
|
|
};
|
|
let network_config = toml::to_string(&network_config).unwrap();
|
|
let network_config =
|
|
match BlockchainNetwork::from_iter_with_toml::<Vec<String>>(&network_config, vec![]) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
error!(target: "darkfid", "Failed parsing requested network configuration: {e}");
|
|
return Err(Error::ParseFailed("Failed parsing requested network configuration"))
|
|
}
|
|
};
|
|
debug!(target: "darkfid", "Parsed network configuration: {network_config:?}");
|
|
|
|
Ok((used_net, network_config))
|
|
}
|