mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
490 lines
21 KiB
Rust
490 lines
21 KiB
Rust
// Copyright 2025 DataHaven
|
|
// This file is part of DataHaven.
|
|
|
|
// DataHaven is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// DataHaven 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 General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with DataHaven. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use crate::config;
|
|
use crate::service::frontier_database_dir;
|
|
use crate::{
|
|
benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder},
|
|
chain_spec::{self, NetworkType},
|
|
cli::{Cli, ProviderType, StorageLayer, Subcommand},
|
|
service,
|
|
};
|
|
use datahaven_runtime_common::Block;
|
|
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE};
|
|
use sc_cli::SubstrateCli;
|
|
use sc_service::{ChainType, DatabaseSource};
|
|
use serde::Deserialize;
|
|
use shc_client::builder::{
|
|
BlockchainServiceOptions, BspChargeFeesOptions, BspMoveBucketOptions, BspSubmitProofOptions,
|
|
BspUploadFileOptions, FishermanOptions, IndexerOptions, MspChargeFeesOptions,
|
|
MspMoveBucketOptions,
|
|
};
|
|
use shc_rpc::RpcConfig;
|
|
use shp_types::StorageDataUnit;
|
|
|
|
/// Configuration for the provider.
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct ProviderOptions {
|
|
/// Provider type.
|
|
pub provider_type: ProviderType,
|
|
/// Storage layer.
|
|
pub storage_layer: StorageLayer,
|
|
/// RocksDB Path.
|
|
pub storage_path: Option<String>,
|
|
/// Maximum storage capacity of the Storage Provider (bytes).
|
|
pub max_storage_capacity: Option<StorageDataUnit>,
|
|
/// Jump capacity (bytes).
|
|
pub jump_capacity: Option<StorageDataUnit>,
|
|
/// RPC configuration options.
|
|
#[serde(default)]
|
|
pub rpc_config: RpcConfig,
|
|
/// MSP charging fees frequency.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub msp_charging_period: Option<u32>,
|
|
/// Configuration options for MSP charge fees task.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub msp_charge_fees: Option<MspChargeFeesOptions>,
|
|
/// Configuration options for MSP move bucket task.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub msp_move_bucket: Option<MspMoveBucketOptions>,
|
|
/// Configuration options for BSP upload file task.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub bsp_upload_file: Option<BspUploadFileOptions>,
|
|
/// Configuration options for BSP move bucket task.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub bsp_move_bucket: Option<BspMoveBucketOptions>,
|
|
/// Configuration options for BSP charge fees task.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub bsp_charge_fees: Option<BspChargeFeesOptions>,
|
|
/// Configuration options for BSP submit proof task.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub bsp_submit_proof: Option<BspSubmitProofOptions>,
|
|
/// Configuration options for blockchain service.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub blockchain_service: Option<BlockchainServiceOptions>,
|
|
// Whether the node is running in maintenance mode. We are not supporting maintenance mode.
|
|
// pub maintenance_mode: bool,
|
|
}
|
|
|
|
/// Role configuration enum that ensures mutual exclusivity between Provider and Fisherman roles.
|
|
#[derive(Debug, Clone)]
|
|
pub enum RoleOptions {
|
|
/// Storage Provider configuration
|
|
Provider(ProviderOptions),
|
|
/// Fisherman configuration
|
|
Fisherman(FishermanOptions),
|
|
}
|
|
|
|
impl SubstrateCli for Cli {
|
|
fn impl_name() -> String {
|
|
"DataHaven Node".into()
|
|
}
|
|
|
|
fn impl_version() -> String {
|
|
env!("SUBSTRATE_CLI_IMPL_VERSION").into()
|
|
}
|
|
|
|
fn description() -> String {
|
|
env!("CARGO_PKG_DESCRIPTION").into()
|
|
}
|
|
|
|
fn author() -> String {
|
|
env!("CARGO_PKG_AUTHORS").into()
|
|
}
|
|
|
|
fn support_url() -> String {
|
|
"https://github.com/datahaven-xyz/datahaven/issues/new".into()
|
|
}
|
|
|
|
fn copyright_start_year() -> i32 {
|
|
2025
|
|
}
|
|
|
|
fn load_spec(&self, id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> {
|
|
Ok(match id {
|
|
"dev" | "stagenet-dev" => Box::new(chain_spec::stagenet::development_chain_spec()?),
|
|
"" | "local" | "stagenet-local" => Box::new(chain_spec::stagenet::local_chain_spec()?),
|
|
"testnet-dev" => Box::new(chain_spec::testnet::development_chain_spec()?),
|
|
"testnet-local" => Box::new(chain_spec::testnet::local_chain_spec()?),
|
|
"mainnet-dev" => Box::new(chain_spec::mainnet::development_chain_spec()?),
|
|
"mainnet-local" => Box::new(chain_spec::mainnet::local_chain_spec()?),
|
|
path => Box::new(chain_spec::ChainSpec::from_json_file(
|
|
std::path::PathBuf::from(path),
|
|
)?),
|
|
})
|
|
}
|
|
}
|
|
|
|
macro_rules! construct_async_run {
|
|
(|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{
|
|
let runner = $cli.create_runner($cmd)?;
|
|
match runner.config().chain_spec {
|
|
ref spec if spec.is_mainnet() => {
|
|
runner.async_run(|$config| {
|
|
let $components = service::new_partial::<datahaven_mainnet_runtime::Runtime, datahaven_mainnet_runtime::RuntimeApi>(
|
|
&$config,
|
|
&mut $cli.eth.clone(),
|
|
false,
|
|
)?;
|
|
let task_manager = $components.task_manager;
|
|
{ $( $code )* }.map(|v| (v, task_manager))
|
|
})
|
|
}
|
|
ref spec if spec.is_testnet() => {
|
|
runner.async_run(|$config| {
|
|
let $components = service::new_partial::<datahaven_testnet_runtime::Runtime, datahaven_testnet_runtime::RuntimeApi>(
|
|
&$config,
|
|
&mut $cli.eth.clone(),
|
|
false,
|
|
)?;
|
|
let task_manager = $components.task_manager;
|
|
{ $( $code )* }.map(|v| (v, task_manager))
|
|
})
|
|
}
|
|
_ => {
|
|
runner.async_run(|$config| {
|
|
let $components = service::new_partial::<datahaven_stagenet_runtime::Runtime, datahaven_stagenet_runtime::RuntimeApi>(
|
|
&$config,
|
|
&mut $cli.eth.clone(),
|
|
false,
|
|
)?;
|
|
let task_manager = $components.task_manager;
|
|
{ $( $code )* }.map(|v| (v, task_manager))
|
|
})
|
|
}
|
|
}
|
|
}}
|
|
}
|
|
|
|
macro_rules! construct_benchmark_partials {
|
|
($cli:expr, $config:expr, |$partials:ident| $code:expr) => {
|
|
match $config.chain_spec {
|
|
ref spec if spec.is_mainnet() => {
|
|
let $partials = service::new_partial::<
|
|
datahaven_mainnet_runtime::Runtime,
|
|
datahaven_mainnet_runtime::RuntimeApi,
|
|
>(&$config, &mut $cli.eth.clone(), false)?;
|
|
$code
|
|
}
|
|
ref spec if spec.is_testnet() => {
|
|
let $partials = service::new_partial::<
|
|
datahaven_testnet_runtime::Runtime,
|
|
datahaven_testnet_runtime::RuntimeApi,
|
|
>(&$config, &mut $cli.eth.clone(), false)?;
|
|
$code
|
|
}
|
|
_ => {
|
|
let $partials = service::new_partial::<
|
|
datahaven_stagenet_runtime::Runtime,
|
|
datahaven_stagenet_runtime::RuntimeApi,
|
|
>(&$config, &mut $cli.eth.clone(), false)?;
|
|
$code
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Parse and run command line arguments
|
|
pub fn run() -> sc_cli::Result<()> {
|
|
let cli = Cli::from_args();
|
|
|
|
match &cli.subcommand {
|
|
Some(Subcommand::Key(cmd)) => cmd.run(&cli),
|
|
Some(Subcommand::BuildSpec(cmd)) => {
|
|
let runner = cli.create_runner(cmd)?;
|
|
runner.sync_run(|config| cmd.run(config.chain_spec, config.network))
|
|
}
|
|
Some(Subcommand::CheckBlock(cmd)) => {
|
|
construct_async_run!(|components, cli, cmd, config| {
|
|
Ok(cmd.run(components.client, components.import_queue))
|
|
})
|
|
}
|
|
Some(Subcommand::ExportBlocks(cmd)) => {
|
|
construct_async_run!(|components, cli, cmd, config| {
|
|
Ok(cmd.run(components.client, config.database))
|
|
})
|
|
}
|
|
Some(Subcommand::ExportState(cmd)) => {
|
|
construct_async_run!(|components, cli, cmd, config| {
|
|
Ok(cmd.run(components.client, config.chain_spec))
|
|
})
|
|
}
|
|
Some(Subcommand::ImportBlocks(cmd)) => {
|
|
construct_async_run!(|components, cli, cmd, config| {
|
|
Ok(cmd.run(components.client, components.import_queue))
|
|
})
|
|
}
|
|
Some(Subcommand::PurgeChain(cmd)) => {
|
|
let runner = cli.create_runner(cmd)?;
|
|
runner.sync_run(|config| {
|
|
// Remove Frontier offchain db
|
|
let frontier_database_config = match config.database {
|
|
DatabaseSource::RocksDb { .. } => DatabaseSource::RocksDb {
|
|
path: frontier_database_dir(&config, "db"),
|
|
cache_size: 0,
|
|
},
|
|
DatabaseSource::ParityDb { .. } => DatabaseSource::ParityDb {
|
|
path: frontier_database_dir(&config, "paritydb"),
|
|
},
|
|
_ => {
|
|
return Err(format!("Cannot purge `{:?}` database", config.database).into())
|
|
}
|
|
};
|
|
cmd.run(frontier_database_config)
|
|
})
|
|
}
|
|
Some(Subcommand::Revert(cmd)) => {
|
|
construct_async_run!(|components, cli, cmd, config| {
|
|
let aux_revert =
|
|
Box::new(|client: Arc<service::FullClient<_>>, backend, blocks| {
|
|
sc_consensus_babe::revert(client.clone(), backend, blocks)?;
|
|
sc_consensus_grandpa::revert(client, blocks)?;
|
|
Ok(())
|
|
});
|
|
Ok(cmd.run(components.client, components.backend, Some(aux_revert)))
|
|
})
|
|
}
|
|
Some(Subcommand::Benchmark(cmd)) => {
|
|
let runner = cli.create_runner(cmd)?;
|
|
|
|
runner.sync_run(|config| {
|
|
// This switch needs to be in the client, since the client decides
|
|
// which sub-commands it wants to support.
|
|
match cmd {
|
|
BenchmarkCmd::Pallet(cmd) => {
|
|
if !cfg!(feature = "runtime-benchmarks") {
|
|
return Err(
|
|
"Runtime benchmarking wasn't enabled when building the node. \
|
|
You can enable it with `--features runtime-benchmarks`."
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
cmd.run_with_spec::<sp_runtime::traits::HashingFor<Block>, ()>(Some(
|
|
config.chain_spec,
|
|
))
|
|
}
|
|
BenchmarkCmd::Block(cmd) => {
|
|
construct_benchmark_partials!(cli, config, |partials| cmd
|
|
.run(partials.client))
|
|
}
|
|
#[cfg(not(feature = "runtime-benchmarks"))]
|
|
BenchmarkCmd::Storage(_) => Err(
|
|
"Storage benchmarking can be enabled with `--features runtime-benchmarks`."
|
|
.into(),
|
|
),
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
BenchmarkCmd::Storage(cmd) => {
|
|
construct_benchmark_partials!(cli, config, |partials| {
|
|
let db = partials.backend.expose_db();
|
|
let storage = partials.backend.expose_storage();
|
|
|
|
cmd.run(config, partials.client.clone(), db, storage)
|
|
})
|
|
}
|
|
BenchmarkCmd::Overhead(cmd) => {
|
|
construct_benchmark_partials!(cli, config, |partials| {
|
|
let ext_builder = RemarkBuilder::new(partials.client.clone());
|
|
cmd.run(
|
|
config.chain_spec.name().to_string(),
|
|
partials.client,
|
|
inherent_benchmark_data()?,
|
|
Vec::new(),
|
|
&ext_builder,
|
|
false,
|
|
)
|
|
})
|
|
}
|
|
BenchmarkCmd::Extrinsic(cmd) => {
|
|
construct_benchmark_partials!(cli, config, |partials| {
|
|
// Register the *Remark* and *TKA* builders.
|
|
let ext_factory = ExtrinsicFactory(vec![
|
|
Box::new(RemarkBuilder::new(partials.client.clone())),
|
|
Box::new(TransferKeepAliveBuilder::new(
|
|
partials.client.clone(),
|
|
datahaven_stagenet_runtime::genesis_config_presets::alith(),
|
|
// Assume the existential deposit is the same for all runtimes
|
|
datahaven_stagenet_runtime::ExistentialDeposit::get(),
|
|
)),
|
|
]);
|
|
|
|
cmd.run(
|
|
partials.client,
|
|
inherent_benchmark_data()?,
|
|
Vec::new(),
|
|
&ext_factory,
|
|
)
|
|
})
|
|
}
|
|
BenchmarkCmd::Machine(cmd) => {
|
|
cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
Some(Subcommand::ChainInfo(cmd)) => {
|
|
let runner = cli.create_runner(cmd)?;
|
|
runner.sync_run(|config| cmd.run::<Block>(&config))
|
|
}
|
|
None => {
|
|
let mut role_options = None;
|
|
let mut indexer_options = None;
|
|
let runner = cli.create_runner(&cli.run)?;
|
|
|
|
// If we have a provider config file
|
|
if let Some(provider_config_file) = cli.provider_config_file {
|
|
let config = config::read_config(&provider_config_file);
|
|
if let Some(c) = config {
|
|
// Check for mutual exclusivity in config file
|
|
let has_provider = matches!(
|
|
c.provider.provider_type,
|
|
ProviderType::Bsp | ProviderType::Msp
|
|
);
|
|
let has_fisherman = !c.fisherman.database_url.is_empty();
|
|
|
|
if has_provider && has_fisherman {
|
|
return Err("Cannot configure both provider and fisherman in the same config file. Please choose one role.".into());
|
|
}
|
|
|
|
if has_provider {
|
|
let provider = c.provider;
|
|
role_options = Some(RoleOptions::Provider(provider));
|
|
} else if has_fisherman {
|
|
let fisherman = c.fisherman;
|
|
role_options = Some(RoleOptions::Fisherman(fisherman));
|
|
}
|
|
|
|
indexer_options = Some(c.indexer);
|
|
};
|
|
};
|
|
|
|
if cli.provider_config.provider && cli.fisherman_config.fisherman {
|
|
return Err(
|
|
"Cannot run as a fisherman and a provider at the same time. Please choose one role."
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
if cli.provider_config.provider {
|
|
role_options = Some(RoleOptions::Provider(
|
|
cli.provider_config.provider_options(),
|
|
));
|
|
};
|
|
|
|
if cli.indexer_config.indexer {
|
|
indexer_options = cli.indexer_config.indexer_options();
|
|
};
|
|
|
|
if cli.fisherman_config.fisherman {
|
|
role_options = Some(RoleOptions::Fisherman(
|
|
cli.fisherman_config
|
|
.fisherman_options()
|
|
.expect("Clap/TOML configurations should prevent this from ever failing"),
|
|
));
|
|
};
|
|
|
|
runner.run_node_until_exit(|config| async move {
|
|
let sealing_mode = match (cli.sealing, config.chain_spec.chain_type()) {
|
|
(Some(mode), ChainType::Development) => Some(mode),
|
|
(Some(_), _) => {
|
|
log::warn!(
|
|
"`--sealing` is only supported on development chains; ignoring."
|
|
);
|
|
None
|
|
}
|
|
(None, _) => None,
|
|
};
|
|
|
|
match config.network.network_backend {
|
|
// TODO: Litep2p becomes standard with Polkadot SDK stable2412-7 (should move None to other arm)
|
|
// cfr. https://github.com/paritytech/polkadot-sdk/releases/tag/polkadot-stable2412-7
|
|
Some(sc_network::config::NetworkBackendType::Libp2p) | None => {
|
|
match config.chain_spec {
|
|
ref spec if spec.is_mainnet() => {
|
|
service::new_full::<
|
|
datahaven_mainnet_runtime::Runtime,
|
|
datahaven_mainnet_runtime::RuntimeApi,
|
|
sc_network::NetworkWorker<_, _>,
|
|
>(
|
|
config, cli.eth, role_options, indexer_options, sealing_mode
|
|
)
|
|
.await
|
|
}
|
|
ref spec if spec.is_testnet() => {
|
|
service::new_full::<
|
|
datahaven_testnet_runtime::Runtime,
|
|
datahaven_testnet_runtime::RuntimeApi,
|
|
sc_network::NetworkWorker<_, _>,
|
|
>(
|
|
config, cli.eth, role_options, indexer_options, sealing_mode
|
|
)
|
|
.await
|
|
}
|
|
_ => {
|
|
service::new_full::<
|
|
datahaven_stagenet_runtime::Runtime,
|
|
datahaven_stagenet_runtime::RuntimeApi,
|
|
sc_network::NetworkWorker<_, _>,
|
|
>(
|
|
config, cli.eth, role_options, indexer_options, sealing_mode
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
.map_err(sc_cli::Error::Service)
|
|
}
|
|
Some(sc_network::config::NetworkBackendType::Litep2p) => {
|
|
match config.chain_spec {
|
|
ref spec if spec.is_mainnet() => {
|
|
service::new_full::<
|
|
datahaven_mainnet_runtime::Runtime,
|
|
datahaven_mainnet_runtime::RuntimeApi,
|
|
sc_network::Litep2pNetworkBackend,
|
|
>(
|
|
config, cli.eth, role_options, indexer_options, sealing_mode
|
|
)
|
|
.await
|
|
}
|
|
ref spec if spec.is_testnet() => {
|
|
service::new_full::<
|
|
datahaven_testnet_runtime::Runtime,
|
|
datahaven_testnet_runtime::RuntimeApi,
|
|
sc_network::Litep2pNetworkBackend,
|
|
>(
|
|
config, cli.eth, role_options, indexer_options, sealing_mode
|
|
)
|
|
.await
|
|
}
|
|
_ => {
|
|
service::new_full::<
|
|
datahaven_stagenet_runtime::Runtime,
|
|
datahaven_stagenet_runtime::RuntimeApi,
|
|
sc_network::Litep2pNetworkBackend,
|
|
>(
|
|
config, cli.eth, role_options, indexer_options, sealing_mode
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
.map_err(sc_cli::Error::Service)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|