#![allow(unused_variables)]
use crate::{
cli::{
init_logging,
run::consensus::PoATriggerArgs,
DEFAULT_DB_PATH,
},
FuelService,
};
use anyhow::{
anyhow,
Context,
};
use clap::Parser;
use fuel_core::{
chain_config::{
default_consensus_dev_key,
ChainConfig,
},
producer::Config as ProducerConfig,
service::{
config::Trigger,
Config,
DbType,
RelayerVerifierConfig,
ServiceTrait,
VMConfig,
},
txpool::Config as TxPoolConfig,
types::{
blockchain::primitives::SecretKeyWrapper,
fuel_tx::Address,
fuel_vm::SecretKey,
secrecy::{
ExposeSecret,
Secret,
},
},
};
use std::{
env,
net,
ops::Deref,
path::PathBuf,
str::FromStr,
};
use tracing::{
info,
log::warn,
trace,
};
pub const CONSENSUS_KEY_ENV: &str = "CONSENSUS_KEY_SECRET";
const DEFAULT_DATABASE_CACHE_SIZE: usize = 1024 * 1024 * 1024;
#[cfg(feature = "p2p")]
mod p2p;
mod consensus;
#[cfg(feature = "relayer")]
mod relayer;
#[derive(Debug, Clone, Parser)]
pub struct Command {
#[clap(long = "ip", default_value = "127.0.0.1", value_parser, env)]
pub ip: net::IpAddr,
#[clap(long = "port", default_value = "4000", env)]
pub port: u16,
#[clap(long = "service-name", default_value = "fuel-core", value_parser, env)]
pub service_name: String,
#[arg(
long = "max-database-cache-size",
default_value_t = DEFAULT_DATABASE_CACHE_SIZE,
env
)]
pub max_database_cache_size: usize,
#[clap(
name = "DB_PATH",
long = "db-path",
value_parser,
default_value = (*DEFAULT_DB_PATH).to_str().unwrap(),
env
)]
pub database_path: PathBuf,
#[clap(
long = "db-type",
default_value = "rocks-db",
value_enum,
ignore_case = true,
env
)]
pub database_type: DbType,
#[arg(
name = "CHAIN_CONFIG",
long = "chain",
default_value = "local_testnet",
env
)]
pub chain_config: String,
#[arg(long = "manual_blocks_enabled", env)]
pub manual_blocks_enabled: bool,
#[arg(long = "vm-backtrace", env)]
pub vm_backtrace: bool,
#[arg(long = "utxo-validation", env)]
pub utxo_validation: bool,
#[arg(long = "min-gas-price", default_value = "0", env)]
pub min_gas_price: u64,
#[arg(long = "consensus-key", env)]
pub consensus_key: Option<String>,
#[clap(flatten)]
pub poa_trigger: PoATriggerArgs,
#[arg(long = "dev-keys", default_value = "true", env)]
pub consensus_dev_key: bool,
#[arg(long = "coinbase-recipient", env)]
pub coinbase_recipient: Option<String>,
#[cfg(feature = "relayer")]
#[clap(flatten)]
pub relayer_args: relayer::RelayerArgs,
#[cfg_attr(feature = "p2p", clap(flatten))]
#[cfg(feature = "p2p")]
pub p2p_args: p2p::P2PArgs,
#[cfg_attr(feature = "p2p", clap(flatten))]
#[cfg(feature = "p2p")]
pub sync_args: p2p::SyncArgs,
#[arg(long = "metrics", env)]
pub metrics: bool,
#[clap(long = "verify_max_da_lag", default_value = "10", env)]
pub max_da_lag: u64,
#[clap(long = "verify_max_relayer_wait", default_value = "30s", env)]
pub max_wait_time: humantime::Duration,
#[clap(long = "tx-pool-ttl", default_value = "5m", env)]
pub tx_pool_ttl: humantime::Duration,
}
impl Command {
pub fn get_config(self) -> anyhow::Result<Config> {
let Command {
ip,
port,
service_name: name,
max_database_cache_size,
database_path,
database_type,
chain_config,
vm_backtrace,
manual_blocks_enabled,
utxo_validation,
min_gas_price,
consensus_key,
poa_trigger,
consensus_dev_key,
coinbase_recipient,
#[cfg(feature = "relayer")]
relayer_args,
#[cfg(feature = "p2p")]
p2p_args,
#[cfg(feature = "p2p")]
sync_args,
metrics,
max_da_lag,
max_wait_time,
tx_pool_ttl,
} = self;
let addr = net::SocketAddr::new(ip, port);
let chain_conf: ChainConfig = chain_config.as_str().parse()?;
#[cfg(feature = "p2p")]
let p2p_cfg = p2p_args.into_config(metrics)?;
let trigger: Trigger = poa_trigger.into();
if trigger != Trigger::Never {
info!("Block production mode: {:?}", &trigger);
} else {
info!("Block production disabled");
}
let consensus_key = load_consensus_key(consensus_key)?.or_else(|| {
if consensus_dev_key && trigger != Trigger::Never {
let key = default_consensus_dev_key();
warn!(
"Fuel Core is using an insecure test key for consensus. Public key: {}",
key.public_key()
);
Some(Secret::new(key.into()))
} else {
None
}
});
if consensus_key.is_some() && trigger == Trigger::Never {
warn!("Consensus key configured but block production is disabled!")
}
let coinbase_recipient = if let Some(coinbase_recipient) = coinbase_recipient {
Address::from_str(coinbase_recipient.as_str()).map_err(|err| anyhow!(err))?
} else {
consensus_key
.as_ref()
.cloned()
.map(|key| {
let sk = key.expose_secret().deref();
Address::from(*sk.public_key().hash())
})
.unwrap_or_default()
};
let verifier = RelayerVerifierConfig {
max_da_lag: max_da_lag.into(),
max_wait_time: max_wait_time.into(),
};
Ok(Config {
addr,
max_database_cache_size,
database_path,
database_type,
chain_conf: chain_conf.clone(),
utxo_validation,
manual_blocks_enabled,
block_production: trigger,
vm: VMConfig {
backtrace: vm_backtrace,
},
txpool: TxPoolConfig::new(
chain_conf,
min_gas_price,
utxo_validation,
metrics,
tx_pool_ttl.into(),
),
block_producer: ProducerConfig {
utxo_validation,
coinbase_recipient,
metrics,
},
block_executor: Default::default(),
block_importer: Default::default(),
#[cfg(feature = "relayer")]
relayer: relayer_args.into(),
#[cfg(feature = "p2p")]
p2p: p2p_cfg,
#[cfg(feature = "p2p")]
sync: sync_args.into(),
consensus_key,
name,
verifier,
})
}
}
pub async fn exec(command: Command) -> anyhow::Result<()> {
let config = command.get_config()?;
let network_name = {
#[cfg(feature = "p2p")]
{
config
.p2p
.as_ref()
.map(|config| config.network_name.clone())
.unwrap_or_else(|| "default_network".to_string())
}
#[cfg(not(feature = "p2p"))]
"default_network".to_string()
};
init_logging().await?;
info!("Fuel Core version v{}", env!("CARGO_PKG_VERSION"));
trace!("Initializing in TRACE mode.");
let server = FuelService::new_node(config).await?;
tokio::select! {
result = server.await_stop() => {
result?;
}
_ = shutdown_signal() => {}
}
server.stop_and_await().await?;
Ok(())
}
fn load_consensus_key(
cli_arg: Option<String>,
) -> anyhow::Result<Option<Secret<SecretKeyWrapper>>> {
let secret_string = if let Some(cli_arg) = cli_arg {
warn!("Consensus key configured insecurely using cli args. Consider setting the {} env var instead.", CONSENSUS_KEY_ENV);
Some(cli_arg)
} else {
env::var(CONSENSUS_KEY_ENV).ok()
};
if let Some(key) = secret_string {
let key =
SecretKey::from_str(&key).context("failed to parse consensus signing key")?;
Ok(Some(Secret::new(key.into())))
} else {
Ok(None)
}
}
async fn shutdown_signal() -> anyhow::Result<()> {
#[cfg(unix)]
{
let mut sigterm =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
let mut sigint =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())?;
loop {
tokio::select! {
_ = sigterm.recv() => {
tracing::info!("sigterm received");
break;
}
_ = sigint.recv() => {
tracing::log::info!("sigint received");
break;
}
}
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c().await?;
tracing::log::info!("CTRL+C received");
}
Ok(())
}