diff --git a/bin/trin/src/cli.rs b/bin/trin/src/cli.rs index 82ec8a1ab..9a8e55ca0 100644 --- a/bin/trin/src/cli.rs +++ b/bin/trin/src/cli.rs @@ -9,6 +9,7 @@ use clap::{ }; use ethportal_api::{ types::{ + distance::Distance, network::Subnetwork, portal_wire::{NetworkSpec, MAINNET}, }, @@ -25,11 +26,14 @@ use portalnet::{ use rpc::config::RpcConfig; use trin_storage::config::StorageCapacityConfig; use trin_utils::cli::{ - check_private_key_length, network_parser, subnetwork_parser, Web3TransportType, + check_private_key_length, max_radius_parser, network_parser, subnetwork_parser, + Web3TransportType, }; use url::Url; const DEFAULT_SUBNETWORKS: &str = "history"; +/// Default max radius value percentage out of 100. +const DEFAULT_MAX_RADIUS: &str = "5"; pub const DEFAULT_STORAGE_CAPACITY_MB: &str = "1000"; pub const DEFAULT_WEB3_TRANSPORT: &str = "ipc"; @@ -204,6 +208,14 @@ pub struct TrinConfig { default_value_t = DEFAULT_UTP_TRANSFER_LIMIT, )] pub utp_transfer_limit: usize, + + #[arg( + long, + help = "The maximum radius our node will use. The default is 5% of the network size. The max is 100%", + default_value = DEFAULT_MAX_RADIUS, + value_parser = max_radius_parser, + )] + pub max_radius: Distance, } impl Default for TrinConfig { @@ -235,6 +247,8 @@ impl Default for TrinConfig { ws_port: DEFAULT_WEB3_WS_PORT, utp_transfer_limit: DEFAULT_UTP_TRANSFER_LIMIT, network: MAINNET.clone(), + max_radius: max_radius_parser(DEFAULT_MAX_RADIUS) + .expect("Parsing static DEFAULT_MAX_RADIUS to work"), } } } diff --git a/bin/trin/src/lib.rs b/bin/trin/src/lib.rs index ab33dc339..2ee285355 100644 --- a/bin/trin/src/lib.rs +++ b/bin/trin/src/lib.rs @@ -7,7 +7,9 @@ use std::sync::Arc; use cli::TrinConfig; use ethportal_api::{ - types::network::Subnetwork, utils::bytes::hex_encode, version::get_trin_version, + types::{distance::Distance, network::Subnetwork}, + utils::bytes::hex_encode, + version::get_trin_version, }; use portalnet::{ discovery::{Discovery, Discv5UdpSocket}, @@ -103,7 +105,7 @@ pub async fn run_trin( &discovery, utp_socket.clone(), portalnet_config.clone(), - storage_config_factory.create(&Subnetwork::State)?, + storage_config_factory.create(&Subnetwork::State, trin_config.max_radius)?, header_oracle.clone(), ) .await? @@ -123,7 +125,7 @@ pub async fn run_trin( &discovery, utp_socket.clone(), portalnet_config.clone(), - storage_config_factory.create(&Subnetwork::Beacon)?, + storage_config_factory.create(&Subnetwork::Beacon, Distance::MAX)?, header_oracle.clone(), ) .await? @@ -146,7 +148,7 @@ pub async fn run_trin( &discovery, utp_socket.clone(), portalnet_config.clone(), - storage_config_factory.create(&Subnetwork::History)?, + storage_config_factory.create(&Subnetwork::History, trin_config.max_radius)?, header_oracle.clone(), ) .await? diff --git a/crates/storage/src/config.rs b/crates/storage/src/config.rs index 69bd8364d..2033a5eb8 100644 --- a/crates/storage/src/config.rs +++ b/crates/storage/src/config.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use discv5::enr::NodeId; -use ethportal_api::types::network::Subnetwork; +use ethportal_api::types::{distance::Distance, network::Subnetwork}; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; @@ -54,6 +54,7 @@ impl PortalStorageConfigFactory { pub fn create( &self, subnetwork: &Subnetwork, + max_radius: Distance, ) -> Result { let capacity_bytes = match &self.capacity_config { StorageCapacityConfig::Combined { @@ -104,6 +105,7 @@ impl PortalStorageConfigFactory { node_data_dir: self.node_data_dir.clone(), distance_fn: DistanceFunction::Xor, sql_connection_pool: self.sql_connection_pool.clone(), + max_radius, }) } @@ -124,6 +126,7 @@ pub struct PortalStorageConfig { pub node_data_dir: PathBuf, pub distance_fn: DistanceFunction, pub sql_connection_pool: Pool, + pub max_radius: Distance, } #[cfg(test)] @@ -167,11 +170,11 @@ mod tests { .unwrap(); match expected_capacity_bytes { Some(expected_capacity_bytes) => { - let config = factory.create(&subnetwork).unwrap(); + let config = factory.create(&subnetwork, Distance::MAX).unwrap(); assert_eq!(config.storage_capacity_bytes, expected_capacity_bytes); } None => assert!( - factory.create(&subnetwork).is_err(), + factory.create(&subnetwork, Distance::MAX).is_err(), "Storage config is expected to fail" ), } @@ -193,21 +196,21 @@ mod tests { .unwrap(); assert_eq!( factory - .create(&Subnetwork::Beacon) + .create(&Subnetwork::Beacon, Distance::MAX) .unwrap() .storage_capacity_bytes, 100_000_000, ); assert_eq!( factory - .create(&Subnetwork::History) + .create(&Subnetwork::History, Distance::MAX) .unwrap() .storage_capacity_bytes, 200_000_000, ); assert_eq!( factory - .create(&Subnetwork::State) + .create(&Subnetwork::State, Distance::MAX) .unwrap() .storage_capacity_bytes, 300_000_000, @@ -230,17 +233,17 @@ mod tests { .unwrap(); assert_eq!( factory - .create(&Subnetwork::History) + .create(&Subnetwork::History, Distance::MAX) .unwrap() .storage_capacity_bytes, 100_000_000, ); assert!( - factory.create(&Subnetwork::Beacon).is_err(), + factory.create(&Subnetwork::Beacon, Distance::MAX).is_err(), "Creating for Beacon should fail" ); assert!( - factory.create(&Subnetwork::State).is_err(), + factory.create(&Subnetwork::State, Distance::MAX).is_err(), "Creating for State should fail" ); temp_dir.close().unwrap(); @@ -261,19 +264,19 @@ mod tests { .unwrap(); assert_eq!( factory - .create(&Subnetwork::Beacon) + .create(&Subnetwork::Beacon, Distance::MAX) .unwrap() .storage_capacity_bytes, 0, ); assert_eq!( factory - .create(&Subnetwork::History) + .create(&Subnetwork::History, Distance::MAX) .unwrap() .storage_capacity_bytes, 100_000_000, ); - assert!(factory.create(&Subnetwork::State).is_err()); + assert!(factory.create(&Subnetwork::State, Distance::MAX).is_err()); temp_dir.close().unwrap(); } } diff --git a/crates/storage/src/test_utils.rs b/crates/storage/src/test_utils.rs index 4a1a29872..b5644e125 100644 --- a/crates/storage/src/test_utils.rs +++ b/crates/storage/src/test_utils.rs @@ -1,5 +1,5 @@ use discv5::enr::NodeId; -use ethportal_api::types::network::Subnetwork; +use ethportal_api::types::{distance::Distance, network::Subnetwork}; use tempfile::TempDir; use crate::{ @@ -21,7 +21,7 @@ pub fn create_test_portal_storage_config_with_capacity( temp_dir.path().to_path_buf(), ) .unwrap() - .create(&Subnetwork::History) + .create(&Subnetwork::History, Distance::MAX) .unwrap(); Ok((temp_dir, config)) } diff --git a/crates/storage/src/versioned/id_indexed_v1/config.rs b/crates/storage/src/versioned/id_indexed_v1/config.rs index 71f18d22d..7b5f69000 100644 --- a/crates/storage/src/versioned/id_indexed_v1/config.rs +++ b/crates/storage/src/versioned/id_indexed_v1/config.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use discv5::enr::NodeId; -use ethportal_api::types::network::Subnetwork; +use ethportal_api::types::{distance::Distance, network::Subnetwork}; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; @@ -19,6 +19,7 @@ pub struct IdIndexedV1StoreConfig { pub sql_connection_pool: Pool, pub distance_fn: DistanceFunction, pub pruning_config: PruningConfig, + pub max_radius: Distance, } impl IdIndexedV1StoreConfig { @@ -37,6 +38,7 @@ impl IdIndexedV1StoreConfig { distance_fn: config.distance_fn, // consider making this a parameter if we start using non-default value pruning_config: PruningConfig::default(), + max_radius: config.max_radius, } } } diff --git a/crates/storage/src/versioned/id_indexed_v1/pruning_strategy.rs b/crates/storage/src/versioned/id_indexed_v1/pruning_strategy.rs index 56ef4ccde..bc360312a 100644 --- a/crates/storage/src/versioned/id_indexed_v1/pruning_strategy.rs +++ b/crates/storage/src/versioned/id_indexed_v1/pruning_strategy.rs @@ -196,7 +196,7 @@ mod tests { use std::path::PathBuf; use discv5::enr::NodeId; - use ethportal_api::types::network::Subnetwork; + use ethportal_api::types::{distance::Distance, network::Subnetwork}; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; use rstest::rstest; @@ -220,6 +220,7 @@ mod tests { sql_connection_pool: Pool::new(SqliteConnectionManager::memory()).unwrap(), distance_fn: DistanceFunction::Xor, pruning_config: PruningConfig::default(), + max_radius: Distance::MAX, }; PruningStrategy::new(config) } diff --git a/crates/storage/src/versioned/id_indexed_v1/store.rs b/crates/storage/src/versioned/id_indexed_v1/store.rs index e9b7a213b..8bc14b7ea 100644 --- a/crates/storage/src/versioned/id_indexed_v1/store.rs +++ b/crates/storage/src/versioned/id_indexed_v1/store.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::{cmp::min, marker::PhantomData}; use ethportal_api::{types::distance::Distance, OverlayContentKey, RawContentValue}; use r2d2::Pool; @@ -84,12 +84,12 @@ impl VersionedContentStore for IdIndexedV1Store< let pruning_strategy = PruningStrategy::new(config.clone()); let mut store = Self { - config, - radius: Distance::MAX, + radius: config.max_radius, pruning_strategy, usage_stats: UsageStats::default(), metrics: StorageMetricsReporter::new(subnetwork), _phantom_content_key: PhantomData, + config, }; store.init()?; Ok(store) @@ -133,11 +133,12 @@ impl IdIndexedV1Store { } else { debug!( Db = %self.config.content_type, - "Used capacity ({}) is below target capacity ({}) -> Using MAX radius", + "Used capacity ({}) is below target capacity ({}) -> Using MAX radius ({})", self.usage_stats.total_entry_size_bytes, - self.pruning_strategy.target_capacity_bytes() + self.pruning_strategy.target_capacity_bytes(), + self.config.max_radius, ); - self.radius = Distance::MAX; + self.radius = self.config.max_radius; self.metrics.report_radius(self.radius); } @@ -420,7 +421,7 @@ impl IdIndexedV1Store { /// Sets `self.radius` to the distance to the farthest stored content. /// - /// If no content is found, it sets radius to `Distance::MAX`. + /// If no content is found, it sets radius to `config.max_radius`. fn set_radius_to_farthest(&mut self) -> Result<(), ContentStoreError> { match self.lookup_farthest()? { None => { @@ -432,11 +433,14 @@ impl IdIndexedV1Store { self.radius = Distance::ZERO; } else { error!(Db = %self.config.content_type, "Farthest not found!"); - self.radius = Distance::MAX; + self.radius = self.config.max_radius; } } Some(farthest) => { - self.radius = self.distance_to_content_id(&farthest.content_id); + self.radius = min( + self.distance_to_content_id(&farthest.content_id), + self.config.max_radius, + ); } } self.metrics.report_radius(self.radius); @@ -572,6 +576,7 @@ mod tests { sql_connection_pool: setup_sql(temp_dir.path()).unwrap(), storage_capacity_bytes, pruning_config: PruningConfig::default(), + max_radius: Distance::MAX, } } diff --git a/crates/utils/src/cli.rs b/crates/utils/src/cli.rs index d60616bb0..e83480e67 100644 --- a/crates/utils/src/cli.rs +++ b/crates/utils/src/cli.rs @@ -1,8 +1,9 @@ use core::fmt; use std::{str::FromStr, sync::Arc}; -use alloy::primitives::B256; +use alloy::primitives::{B256, U256}; use ethportal_api::types::{ + distance::Distance, network::Subnetwork, portal_wire::{NetworkSpec, ANGELFOOD, MAINNET}, }; @@ -72,3 +73,30 @@ pub fn subnetwork_parser(subnetwork_string: &str) -> Result> Ok(Arc::new(subnetworks)) } + +pub fn max_radius_parser(max_radius_str: &str) -> Result { + let max_radius_percentage = match max_radius_str.parse::() { + Ok(val) => val, + Err(err) => { + return Err(format!( + "Invalid max radius percentage, expected a number, received: {err}" + )) + } + }; + + if max_radius_percentage > U256::from(100) { + return Err(format!( + "Invalid max radius percentage, expected 0 to 100, received: {max_radius_percentage}" + )); + } + + // If the max radius is 100% return it, as arithmetic multiplication loses precision and will be + // off by 5. + if max_radius_percentage == U256::from(100) { + return Ok(Distance::MAX); + } + + Ok(Distance::from( + U256::MAX / U256::from(100) * max_radius_percentage, + )) +} diff --git a/testing/ethportal-peertest/src/lib.rs b/testing/ethportal-peertest/src/lib.rs index 798826b6c..3d610f8f3 100644 --- a/testing/ethportal-peertest/src/lib.rs +++ b/testing/ethportal-peertest/src/lib.rs @@ -108,6 +108,8 @@ fn generate_trin_config( "--unsafe-private-key", private_key.as_str(), "--ephemeral", + "--max-radius", + "100", ]; TrinConfig::new_from(trin_config_args).unwrap() } diff --git a/testing/ethportal-peertest/src/scenarios/put_content.rs b/testing/ethportal-peertest/src/scenarios/put_content.rs index af1737c98..172d5f691 100644 --- a/testing/ethportal-peertest/src/scenarios/put_content.rs +++ b/testing/ethportal-peertest/src/scenarios/put_content.rs @@ -365,6 +365,8 @@ fn fresh_node_config() -> (String, TrinConfig) { test_discovery_port.to_string().as_ref(), "--bootnodes", "none", + "--max-radius", + "100", "--unsafe-private-key", // node id: 0x27128939ed60d6f4caef0374da15361a2c1cd6baa1a5bccebac1acd18f485900 "0x9ca7889c09ef1162132251b6284bd48e64bd3e71d75ea33b959c37be0582a2fd", diff --git a/testing/ethportal-peertest/tests/rpc_server.rs b/testing/ethportal-peertest/tests/rpc_server.rs index 841548459..7b9843b86 100644 --- a/testing/ethportal-peertest/tests/rpc_server.rs +++ b/testing/ethportal-peertest/tests/rpc_server.rs @@ -51,6 +51,8 @@ async fn setup_web3_server() -> (RpcServerHandle, RootProvider, &test_discovery_port.to_string(), "--bootnodes", "none", + "--max-radius", + "100", ]) .unwrap(); @@ -87,6 +89,8 @@ async fn test_batch_call() { &test_discovery_port.to_string(), "--bootnodes", "none", + "--max-radius", + "100", ]) .unwrap(); diff --git a/testing/ethportal-peertest/tests/self_peertest.rs b/testing/ethportal-peertest/tests/self_peertest.rs index cc7f08ccc..53cbb4ac9 100644 --- a/testing/ethportal-peertest/tests/self_peertest.rs +++ b/testing/ethportal-peertest/tests/self_peertest.rs @@ -342,6 +342,8 @@ async fn peertest_ping_cross_discv5_protocol_id() { test_discovery_port.to_string().as_ref(), "--bootnodes", "none", + "--max-radius", + "100", ]) .unwrap(); let mainnet_handle = trin::run_trin(trin_config).await.unwrap(); @@ -395,6 +397,8 @@ async fn setup_peertest( test_discovery_port.to_string().as_ref(), "--bootnodes", "none", + "--max-radius", + "100", ]) .unwrap(); @@ -449,6 +453,8 @@ async fn setup_peertest_bridge( test_discovery_port.to_string().as_ref(), "--bootnodes", &peertest.bootnode.enr.to_base64(), + "--max-radius", + "100", ]) .unwrap();