diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eddbccf323..23e5fdcf7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,25 @@ jobs: target/release/papyrus_node --base_layer.node_url ${{ secrets.CI_BASE_LAYER_NODE_URL }} & sleep 30 ; kill $! + executable-run-no-rpc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: Noelware/setup-protoc@1.1.0 + with: + version: ${{env.PROTOC_VERSION}} + - run: mkdir data + + - name: Build node + run: cargo build -r --no-default-features + + - name: Run executable + run: > + target/release/papyrus_node --base_layer.node_url ${{ secrets.CI_BASE_LAYER_NODE_URL }} + & sleep 30 ; kill $! + test: runs-on: ubuntu-latest steps: @@ -91,6 +110,20 @@ jobs: cargo test -r --test '*' -- --include-ignored --skip test_gw_integration_testnet; cargo run -r -p papyrus_node --bin central_source_integration_test + test-no-rpc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: Noelware/setup-protoc@1.1.0 + + - run: | + cargo test -p papyrus_node --no-default-features + env: + SEED: 0 + + rustfmt: runs-on: ubuntu-latest steps: diff --git a/commitlint.config.js b/commitlint.config.js index 1d7f6cfb27..6b474f2606 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -28,6 +28,7 @@ const Configuration = { 'load_test', 'monitoring', 'network', + 'node', 'release', 'starknet_client', 'storage', diff --git a/crates/papyrus_node/Cargo.toml b/crates/papyrus_node/Cargo.toml index 543e47b32e..6664bd36a6 100644 --- a/crates/papyrus_node/Cargo.toml +++ b/crates/papyrus_node/Cargo.toml @@ -8,6 +8,10 @@ license-file.workspace = true [package.metadata.cargo-udeps.ignore] normal = ["papyrus_base_layer"] +[features] +default = ["rpc"] +rpc = ["papyrus_rpc"] + [dependencies] anyhow.workspace = true async-stream.workspace = true @@ -25,13 +29,13 @@ papyrus_common = { path = "../papyrus_common", version = "0.3.0" } papyrus_monitoring_gateway = { path = "../papyrus_monitoring_gateway", version = "0.3.0" } papyrus_network = { path = "../papyrus_network", version = "0.3.0" } papyrus_p2p_sync = { path = "../papyrus_p2p_sync", version = "0.3.0" } -papyrus_rpc = { path = "../papyrus_rpc", version = "0.3.0" } +papyrus_rpc = { path = "../papyrus_rpc", version = "0.3.0", optional = true } papyrus_storage = { path = "../papyrus_storage", version = "0.3.0" } papyrus_sync = { path = "../papyrus_sync", version = "0.3.0" } reqwest = { workspace = true, features = ["json", "blocking"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } -starknet_api.workspace = true +starknet_api = { workspace = true, features = ["testing"] } starknet_client = { path = "../starknet_client" } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } diff --git a/crates/papyrus_node/src/bin/dump_config.rs b/crates/papyrus_node/src/bin/dump_config.rs index 63589eb84b..6f2012ffba 100644 --- a/crates/papyrus_node/src/bin/dump_config.rs +++ b/crates/papyrus_node/src/bin/dump_config.rs @@ -2,14 +2,23 @@ // within this crate #![cfg_attr(coverage_nightly, feature(coverage_attribute))] +#[cfg(feature = "rpc")] use papyrus_config::dumping::SerializeConfig; -use papyrus_node::config::{NodeConfig, CONFIG_POINTERS, DEFAULT_CONFIG_PATH}; +#[cfg(feature = "rpc")] +use papyrus_node::config::pointers::CONFIG_POINTERS; +#[cfg(feature = "rpc")] +use papyrus_node::config::{NodeConfig, DEFAULT_CONFIG_PATH}; /// Updates the default config file by: /// cargo run --bin dump_config -q #[cfg_attr(coverage_nightly, coverage_attribute)] fn main() { + #[cfg(feature = "rpc")] NodeConfig::default() .dump_to_file(&CONFIG_POINTERS, DEFAULT_CONFIG_PATH) .expect("dump to file error"); + // TODO(shahak): Try to find a way to remove this binary altogether when the feature rpc is + // turned off. + #[cfg(not(feature = "rpc"))] + panic!("Can't dump config when the rpc feature is deactivated"); } diff --git a/crates/papyrus_node/src/config/config_test.rs b/crates/papyrus_node/src/config/config_test.rs index 2169430785..1c8d08f9ad 100644 --- a/crates/papyrus_node/src/config/config_test.rs +++ b/crates/papyrus_node/src/config/config_test.rs @@ -22,7 +22,9 @@ use tempfile::NamedTempFile; use test_utils::get_absolute_path; use validator::Validate; -use crate::config::{node_command, NodeConfig, CONFIG_POINTERS, DEFAULT_CONFIG_PATH}; +#[cfg(feature = "rpc")] +use crate::config::pointers::CONFIG_POINTERS; +use crate::config::{node_command, NodeConfig, DEFAULT_CONFIG_PATH}; // Returns the required and generated params in default_config.json with the default value from the // config presentation. @@ -76,6 +78,9 @@ fn load_http_headers() { assert_eq!(config.central.http_headers.unwrap(), target_http_headers); } +// insta doesn't work well with features, so if the output between two features are different we +// can only test one of them. We chose to test rpc over testing not(rpc). +#[cfg(feature = "rpc")] #[test] // Regression test which checks that the default config dumping hasn't changed. fn test_dump_default_config() { @@ -98,15 +103,20 @@ fn test_default_config_process() { #[test] fn test_update_dumped_config_by_command() { - let args = - get_args(vec!["--rpc.max_events_keys", "1234", "--storage.db_config.path_prefix", "/abc"]); + let args = get_args(vec![ + "--central.retry_config.retry_max_delay_millis", + "1234", + "--storage.db_config.path_prefix", + "/abc", + ]); env::set_current_dir(get_absolute_path("")).expect("Couldn't set working dir."); let config = NodeConfig::load_and_process(args).unwrap(); - assert_eq!(config.rpc.max_events_keys, 1234); + assert_eq!(config.central.retry_config.retry_max_delay_millis, 1234); assert_eq!(config.storage.db_config.path_prefix.to_str(), Some("/abc")); } +#[cfg(feature = "rpc")] #[test] fn default_config_file_is_up_to_date() { env::set_current_dir(get_absolute_path("")).expect("Couldn't set working dir."); diff --git a/crates/papyrus_node/src/config/mod.rs b/crates/papyrus_node/src/config/mod.rs index b64c8cf8d0..0e0860397f 100644 --- a/crates/papyrus_node/src/config/mod.rs +++ b/crates/papyrus_node/src/config/mod.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod config_test; +#[cfg(feature = "rpc")] +pub mod pointers; use std::collections::{BTreeMap, HashMap}; use std::fs::File; @@ -14,6 +16,8 @@ use clap::{arg, value_parser, Arg, ArgMatches, Command}; use itertools::{chain, Itertools}; use lazy_static::lazy_static; use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerConfig; +#[cfg(not(feature = "rpc"))] +use papyrus_config::dumping::ser_param; use papyrus_config::dumping::{ append_sub_config_name, ser_optional_sub_config, @@ -21,10 +25,13 @@ use papyrus_config::dumping::{ SerializeConfig, }; use papyrus_config::loading::load_and_process_config; +#[cfg(not(feature = "rpc"))] +use papyrus_config::ParamPrivacyInput; use papyrus_config::{ConfigError, ParamPath, SerializedParam}; use papyrus_monitoring_gateway::MonitoringGatewayConfig; use papyrus_network::NetworkConfig; use papyrus_p2p_sync::{P2PSync, P2PSyncConfig}; +#[cfg(feature = "rpc")] use papyrus_rpc::RpcConfig; use papyrus_storage::db::DbConfig; use papyrus_storage::StorageConfig; @@ -41,40 +48,10 @@ use crate::version::VERSION_FULL; // The path of the default configuration file, provided as part of the crate. pub const DEFAULT_CONFIG_PATH: &str = "config/default_config.json"; -lazy_static! { - /// Returns vector of (pointer target name, pointer target serialized param, vec) - /// to be applied on the dumped node config. - /// The config updates will be performed on the shared pointer targets, and finally, the values - /// will be propagated to the pointer params. - pub static ref CONFIG_POINTERS: Vec<((ParamPath, SerializedParam), Vec)> = vec![( - ser_pointer_target_param( - "chain_id", - &ChainId("SN_MAIN".to_string()), - "The chain to follow. For more details see https://docs.starknet.io/documentation/architecture_and_concepts/Blocks/transactions/#chain-id.", - ), - vec!["storage.db_config.chain_id".to_owned(), "rpc.chain_id".to_owned()], - ), - ( - ser_pointer_target_param( - "starknet_url", - &"https://alpha-mainnet.starknet.io/".to_string(), - "The URL of a centralized Starknet gateway.", - ), - vec!["rpc.starknet_url".to_owned(), "central.url".to_owned(), "monitoring_gateway.starknet_url".to_owned()], - ), - ( - ser_pointer_target_param( - "collect_metrics", - &false, - "If true, collect metrics for the node.", - ), - vec!["rpc.collect_metrics".to_owned(), "monitoring_gateway.collect_metrics".to_owned()], - )]; -} - /// The configurations of the various components of the node. #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Validate)] pub struct NodeConfig { + #[cfg(feature = "rpc")] #[validate] pub rpc: RpcConfig, pub central: CentralSourceConfig, @@ -98,6 +75,7 @@ impl Default for NodeConfig { NodeConfig { central: CentralSourceConfig::default(), base_layer: EthereumBaseLayerConfig::default(), + #[cfg(feature = "rpc")] rpc: RpcConfig::default(), monitoring_gateway: MonitoringGatewayConfig::default(), storage: StorageConfig::default(), @@ -110,17 +88,21 @@ impl Default for NodeConfig { impl SerializeConfig for NodeConfig { fn dump(&self) -> BTreeMap { - chain!( + #[allow(unused_mut)] + let mut sub_configs = vec![ + append_sub_config_name(self.central.dump(), "central"), append_sub_config_name(self.central.dump(), "central"), append_sub_config_name(self.base_layer.dump(), "base_layer"), - append_sub_config_name(self.rpc.dump(), "rpc"), append_sub_config_name(self.monitoring_gateway.dump(), "monitoring_gateway"), append_sub_config_name(self.storage.dump(), "storage"), ser_optional_sub_config(&self.sync, "sync"), ser_optional_sub_config(&self.p2p_sync, "p2p_sync"), ser_optional_sub_config(&self.network, "network"), - ) - .collect() + ]; + #[cfg(feature = "rpc")] + sub_configs.push(append_sub_config_name(self.rpc.dump(), "rpc")); + + sub_configs.into_iter().flatten().collect() } } diff --git a/crates/papyrus_node/src/config/pointers.rs b/crates/papyrus_node/src/config/pointers.rs new file mode 100644 index 0000000000..02adedc39a --- /dev/null +++ b/crates/papyrus_node/src/config/pointers.rs @@ -0,0 +1,72 @@ +use std::collections::{BTreeMap, HashMap}; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::mem::discriminant; +use std::ops::IndexMut; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use std::{env, fs, io}; + +use clap::{arg, value_parser, Arg, ArgMatches, Command}; +use itertools::{chain, Itertools}; +use lazy_static::lazy_static; +use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerConfig; +#[cfg(not(feature = "rpc"))] +use papyrus_config::dumping::ser_param; +use papyrus_config::dumping::{ + append_sub_config_name, + ser_optional_sub_config, + ser_pointer_target_param, + SerializeConfig, +}; +use papyrus_config::loading::load_and_process_config; +#[cfg(not(feature = "rpc"))] +use papyrus_config::ParamPrivacyInput; +use papyrus_config::{ConfigError, ParamPath, SerializedParam}; +use papyrus_monitoring_gateway::MonitoringGatewayConfig; +use papyrus_network::NetworkConfig; +use papyrus_p2p_sync::{P2PSync, P2PSyncConfig}; +#[cfg(feature = "rpc")] +use papyrus_rpc::RpcConfig; +use papyrus_storage::db::DbConfig; +use papyrus_storage::StorageConfig; +use papyrus_sync::sources::central::CentralSourceConfig; +use papyrus_sync::SyncConfig; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +use starknet_api::core::ChainId; +use starknet_client::RetryConfig; +use validator::Validate; + +use crate::version::VERSION_FULL; + +lazy_static! { + /// Returns vector of (pointer target name, pointer target serialized param, vec) + /// to be applied on the dumped node config. + /// The config updates will be performed on the shared pointer targets, and finally, the values + /// will be propagated to the pointer params. + pub static ref CONFIG_POINTERS: Vec<((ParamPath, SerializedParam), Vec)> = vec![( + ser_pointer_target_param( + "chain_id", + &ChainId("SN_MAIN".to_string()), + "The chain to follow. For more details see https://docs.starknet.io/documentation/architecture_and_concepts/Blocks/transactions/#chain-id.", + ), + vec!["storage.db_config.chain_id".to_owned(), "rpc.chain_id".to_owned()], + ), + ( + ser_pointer_target_param( + "starknet_url", + &"https://alpha-mainnet.starknet.io/".to_string(), + "The URL of a centralized Starknet gateway.", + ), + vec!["rpc.starknet_url".to_owned(), "central.url".to_owned(), "monitoring_gateway.starknet_url".to_owned()], + ), + ( + ser_pointer_target_param( + "collect_metrics", + &false, + "If true, collect metrics for the node.", + ), + vec!["rpc.collect_metrics".to_owned(), "monitoring_gateway.collect_metrics".to_owned()], + )]; +} diff --git a/crates/papyrus_node/src/main.rs b/crates/papyrus_node/src/main.rs index ad7336db39..a828680124 100644 --- a/crates/papyrus_node/src/main.rs +++ b/crates/papyrus_node/src/main.rs @@ -2,7 +2,7 @@ mod main_test; use std::env::args; -use std::future::{self, pending}; +use std::future::{pending, Future}; use std::process::exit; use std::sync::Arc; use std::time::Duration; @@ -22,6 +22,7 @@ use papyrus_network::{network_manager, NetworkConfig, Protocol, Query, ResponseR use papyrus_node::config::NodeConfig; use papyrus_node::version::VERSION_FULL; use papyrus_p2p_sync::{P2PSync, P2PSyncConfig, P2PSyncError}; +#[cfg(feature = "rpc")] use papyrus_rpc::run_server; use papyrus_storage::{open_storage, update_storage_metrics, StorageReader, StorageWriter}; use papyrus_sync::sources::base_layer::{BaseLayerSourceError, EthereumBaseLayerSource}; @@ -34,7 +35,7 @@ use starknet_api::stark_felt; use starknet_client::reader::objects::pending_data::{PendingBlock, PendingBlockOrDeprecated}; use starknet_client::reader::PendingData; use tokio::sync::RwLock; -use tokio::task::JoinHandle; +use tokio::task::{JoinError, JoinHandle}; use tracing::metadata::LevelFilter; use tracing::{debug_span, error, info, warn, Instrument}; use tracing_subscriber::prelude::*; @@ -47,13 +48,44 @@ const DEFAULT_LEVEL: LevelFilter = LevelFilter::INFO; // Duration between updates to the storage metrics (those in the collect_storage_metrics function). const STORAGE_METRICS_UPDATE_INTERVAL: Duration = Duration::from_secs(10); +#[cfg(feature = "rpc")] +async fn create_rpc_server_future( + config: &NodeConfig, + shared_highest_block: Arc>>, + pending_data: Arc>, + pending_classes: Arc>, + storage_reader: StorageReader, +) -> anyhow::Result>> { + let (_, server_handle) = run_server( + &config.rpc, + shared_highest_block, + pending_data, + pending_classes, + storage_reader, + VERSION_FULL, + ) + .await?; + Ok(tokio::spawn(server_handle.stopped())) +} + +#[cfg(not(feature = "rpc"))] +async fn create_rpc_server_future( + _config: &NodeConfig, + _shared_highest_block: Arc>>, + _pending_data: Arc>, + _pending_classes: Arc>, + _storage_reader: StorageReader, +) -> anyhow::Result>> { + Ok(pending()) +} + async fn run_threads(config: NodeConfig) -> anyhow::Result<()> { let (storage_reader, storage_writer) = open_storage(config.storage.clone())?; let storage_metrics_handle = if config.monitoring_gateway.collect_metrics { spawn_storage_metrics_collector(storage_reader.clone(), STORAGE_METRICS_UPDATE_INTERVAL) } else { - tokio::spawn(future::pending()) + tokio::spawn(pending()) }; // Monitoring server. @@ -80,16 +112,14 @@ async fn run_threads(config: NodeConfig) -> anyhow::Result<()> { let pending_classes = Arc::new(RwLock::new(PendingClasses::default())); // JSON-RPC server. - let (_, server_handle) = run_server( - &config.rpc, + let server_handle_future = create_rpc_server_future( + &config, shared_highest_block.clone(), pending_data.clone(), pending_classes.clone(), storage_reader.clone(), - VERSION_FULL, ) .await?; - let server_handle_future = tokio::spawn(server_handle.stopped()); // P2P network. let (network_future, maybe_query_sender_and_response_receivers) = diff --git a/crates/papyrus_node/src/main_test.rs b/crates/papyrus_node/src/main_test.rs index 809db535cf..151eb9dd91 100644 --- a/crates/papyrus_node/src/main_test.rs +++ b/crates/papyrus_node/src/main_test.rs @@ -2,13 +2,27 @@ use std::time::Duration; use metrics_exporter_prometheus::PrometheusBuilder; use papyrus_node::config::NodeConfig; +#[cfg(feature = "rpc")] use papyrus_rpc::RpcConfig; use papyrus_storage::{open_storage, StorageConfig}; use tempfile::TempDir; -use test_utils::{get_absolute_path, prometheus_is_contained}; +#[cfg(feature = "rpc")] +use test_utils::get_absolute_path; +use test_utils::prometheus_is_contained; use crate::{run_threads, spawn_storage_metrics_collector}; +#[cfg(feature = "rpc")] +fn fix_execution_config_path(config: &mut NodeConfig) { + let default_execution_config_path = RpcConfig::default().execution_config; + config.rpc.execution_config = + get_absolute_path(default_execution_config_path.to_str().unwrap()); +} + +// If there's no RPC, there's no execution, so we don't need to fix anything +#[cfg(not(feature = "rpc"))] +fn fix_execution_config_path(_config: &mut NodeConfig) {} + // The mission of this test is to ensure that if an error is returned from one of the spawned tasks, // the node will stop, and this error will be returned. This is done by checking the case of an // illegal central URL, which will cause the sync task to return an error. @@ -18,10 +32,7 @@ async fn run_threads_stop() { let temp_dir = TempDir::new().unwrap(); config.storage.db_config.path_prefix = temp_dir.path().into(); - // Fix the path to the execution config. - let default_execution_config_path = RpcConfig::default().execution_config; - config.rpc.execution_config = - get_absolute_path(default_execution_config_path.to_str().unwrap()); + fix_execution_config_path(&mut config); // Error when not supplying legal central URL. config.central.url = "_not_legal_url".to_string();