diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index 614483d4a2..73fb3b3287 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -473,7 +473,7 @@ impl<'a, T: BlockEventDispatcher, U: RewardSetProvider, B: BurnchainHeaderReader let canonical_sortition_tip = SortitionDB::get_canonical_sortition_tip(sortition_db.conn()).unwrap(); - let atlas_config = atlas_config.unwrap_or(AtlasConfig::default(false)); + let atlas_config = atlas_config.unwrap_or(AtlasConfig::new(false)); let atlas_db = AtlasDB::connect(atlas_config.clone(), &format!("{}/atlas", path), true).unwrap(); diff --git a/src/chainstate/coordinator/tests.rs b/src/chainstate/coordinator/tests.rs index f9e1676103..0a3657fc1d 100644 --- a/src/chainstate/coordinator/tests.rs +++ b/src/chainstate/coordinator/tests.rs @@ -4343,7 +4343,7 @@ fn atlas_stop_start() { let initial_balances = vec![(signer_pk.clone().into(), balance)]; let atlas_qci = QualifiedContractIdentifier::new(signer_pk.clone().into(), atlas_name.clone()); // include our simple contract in the atlas config - let mut atlas_config = AtlasConfig::default(false); + let mut atlas_config = AtlasConfig::new(false); atlas_config.contracts.insert(atlas_qci.clone()); setup_states( diff --git a/src/net/atlas/mod.rs b/src/net/atlas/mod.rs index 2b5ed683f8..c620115181 100644 --- a/src/net/atlas/mod.rs +++ b/src/net/atlas/mod.rs @@ -54,6 +54,11 @@ lazy_static! { pub static ref BNS_CHARS_REGEX: Regex = Regex::new("^([a-z0-9]|[-_])*$").unwrap(); } +const ATTACHMENTS_MAX_SIZE_MIN: u32 = 1_048_576; +const MAX_UNINSTANTIATED_ATTACHMENTS_MIN: u32 = 50_000; +const UNINSTANTIATED_ATTACHMENTS_EXPIRE_AFTER_MIN: u32 = 86_400; +const UNRESOLVED_ATTACHMENT_INSTANCES_EXPIRE_AFTER_MIN: u32 = 172_800; + #[derive(Debug, Clone)] pub struct AtlasConfig { pub contracts: HashSet, @@ -65,18 +70,49 @@ pub struct AtlasConfig { } impl AtlasConfig { - pub fn default(mainnet: bool) -> AtlasConfig { + pub fn new(mainnet: bool) -> AtlasConfig { let mut contracts = HashSet::new(); contracts.insert(boot_code_id("bns", mainnet)); AtlasConfig { contracts, - attachments_max_size: 1_048_576, - max_uninstantiated_attachments: 50_000, - uninstantiated_attachments_expire_after: 86_400, - unresolved_attachment_instances_expire_after: 172_800, + attachments_max_size: ATTACHMENTS_MAX_SIZE_MIN, + max_uninstantiated_attachments: MAX_UNINSTANTIATED_ATTACHMENTS_MIN, + uninstantiated_attachments_expire_after: UNINSTANTIATED_ATTACHMENTS_EXPIRE_AFTER_MIN, + unresolved_attachment_instances_expire_after: + UNRESOLVED_ATTACHMENT_INSTANCES_EXPIRE_AFTER_MIN, genesis_attachments: None, } } + + pub fn validate(&self) -> Result<(), String> { + if self.attachments_max_size < ATTACHMENTS_MAX_SIZE_MIN { + Err(format!( + "Invalid value for `attachments_max_size`: {}. Expected {} or greater", + self.attachments_max_size, ATTACHMENTS_MAX_SIZE_MIN + )) + } else if self.max_uninstantiated_attachments < MAX_UNINSTANTIATED_ATTACHMENTS_MIN { + Err(format!( + "Invalid value for `max_uninstantiated_attachments`: {}. Expected {} or greater", + self.max_uninstantiated_attachments, MAX_UNINSTANTIATED_ATTACHMENTS_MIN + )) + } else if self.uninstantiated_attachments_expire_after + < UNINSTANTIATED_ATTACHMENTS_EXPIRE_AFTER_MIN + { + Err(format!( + "Invalid value for `uninstantiated_attachments_expire_after`: {}. Expected {} or greater", + self.uninstantiated_attachments_expire_after, UNINSTANTIATED_ATTACHMENTS_EXPIRE_AFTER_MIN + )) + } else if self.unresolved_attachment_instances_expire_after + < UNRESOLVED_ATTACHMENT_INSTANCES_EXPIRE_AFTER_MIN + { + Err(format!( + "Invalid value for `unresolved_attachment_instances_expire_after`: {}. Expected {} or greater", + self.unresolved_attachment_instances_expire_after, UNRESOLVED_ATTACHMENT_INSTANCES_EXPIRE_AFTER_MIN + )) + } else { + Ok(()) + } + } } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/net/mod.rs b/src/net/mod.rs index d2956b865a..b273fa918a 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -2713,8 +2713,7 @@ pub mod test { } let atlasdb_path = format!("{}/atlas.sqlite", &test_path); - let atlasdb = - AtlasDB::connect(AtlasConfig::default(false), &atlasdb_path, true).unwrap(); + let atlasdb = AtlasDB::connect(AtlasConfig::new(false), &atlasdb_path, true).unwrap(); let conf = config.clone(); let post_flight_callback = move |clarity_tx: &mut ClarityTx| { diff --git a/src/net/p2p.rs b/src/net/p2p.rs index 9e340b0222..263af365f0 100644 --- a/src/net/p2p.rs +++ b/src/net/p2p.rs @@ -5540,7 +5540,7 @@ mod test { initial_neighbors, ) .unwrap(); - let atlas_config = AtlasConfig::default(false); + let atlas_config = AtlasConfig::new(false); let atlasdb = AtlasDB::connect_memory(atlas_config).unwrap(); let local_peer = PeerDB::get_local_peer(db.conn()).unwrap(); diff --git a/testnet/stacks-node/Stacks.toml b/testnet/stacks-node/Stacks.toml index 022f19a762..0e4aed80a9 100644 --- a/testnet/stacks-node/Stacks.toml +++ b/testnet/stacks-node/Stacks.toml @@ -84,3 +84,13 @@ amount = 100000000 # "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.contract.nft-token", # "stx" # ] + +## Atlas database +## The Atlas database, which handles file attachments, can be configured here +## The values used in the example below are the minimum values which Atlas will accept +## +#[atlas] +#attachments_max_size = 1048576 +#max_uninstantiated_attachments = 10000 +#uninstantiated_attachments_expire_after = 3600 +#unresolved_attachment_instances_expire_after = 172800 diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 6b3f6df012..43051c9c68 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -30,6 +30,7 @@ use stacks::cost_estimates::metrics::ProportionalDotProduct; use stacks::cost_estimates::CostEstimator; use stacks::cost_estimates::FeeEstimator; use stacks::cost_estimates::PessimisticEstimator; +use stacks::net::atlas::AtlasConfig; use stacks::net::connection::ConnectionOptions; use stacks::net::{Neighbor, NeighborKey, PeerAddress}; use stacks::util::get_epoch_time_ms; @@ -55,6 +56,7 @@ pub struct ConfigFile { pub connection_options: Option, pub fee_estimation: Option, pub miner: Option, + pub atlas: Option, } #[derive(Clone, Deserialize, Default)] @@ -360,6 +362,7 @@ pub struct Config { pub connection_options: ConnectionOptions, pub miner: MinerConfig, pub estimation: FeeEstimationConfig, + pub atlas: AtlasConfig, } lazy_static! { @@ -1107,6 +1110,16 @@ impl Config { None => FeeEstimationConfig::default(), }; + let mainnet = burnchain.mode == "mainnet"; + let atlas = match config_file.atlas { + Some(f) => f.into_config(mainnet), + None => AtlasConfig::new(mainnet), + }; + + atlas + .validate() + .map_err(|e| format!("Atlas config error: {e}"))?; + Ok(Config { node, burnchain, @@ -1115,6 +1128,7 @@ impl Config { connection_options, estimation, miner, + atlas, }) } @@ -1266,6 +1280,7 @@ impl std::default::Default for Config { let connection_options = HELIUM_DEFAULT_CONNECTION_OPTIONS.clone(); let estimation = FeeEstimationConfig::default(); + let mainnet = burnchain.mode == "mainnet"; Config { burnchain, @@ -1275,6 +1290,7 @@ impl std::default::Default for Config { connection_options, estimation, miner: MinerConfig::default(), + atlas: AtlasConfig::new(mainnet), } } } @@ -1940,7 +1956,7 @@ pub struct NodeConfigFile { pub chain_liveness_poll_time_secs: Option, } -#[derive(Clone, Deserialize, Debug)] +#[derive(Clone, Deserialize, Default, Debug)] pub struct FeeEstimationConfigFile { pub cost_estimator: Option, pub fee_estimator: Option, @@ -1951,20 +1967,6 @@ pub struct FeeEstimationConfigFile { pub fee_rate_window_size: Option, } -impl Default for FeeEstimationConfigFile { - fn default() -> Self { - Self { - cost_estimator: None, - fee_estimator: None, - cost_metric: None, - disabled: None, - log_error: None, - fee_rate_fuzzer_fraction: None, - fee_rate_window_size: None, - } - } -} - #[derive(Clone, Deserialize, Default, Debug)] pub struct MinerConfigFile { pub min_tx_fee: Option, @@ -1978,6 +1980,34 @@ pub struct MinerConfigFile { pub candidate_retry_cache_size: Option, } +#[derive(Clone, Deserialize, Default, Debug)] +pub struct AtlasConfigFile { + pub attachments_max_size: Option, + pub max_uninstantiated_attachments: Option, + pub uninstantiated_attachments_expire_after: Option, + pub unresolved_attachment_instances_expire_after: Option, +} + +impl AtlasConfigFile { + // Can't inplement `Into` trait because this takes a parameter + fn into_config(&self, mainnet: bool) -> AtlasConfig { + let mut conf = AtlasConfig::new(mainnet); + if let Some(val) = self.attachments_max_size { + conf.attachments_max_size = val + } + if let Some(val) = self.max_uninstantiated_attachments { + conf.max_uninstantiated_attachments = val + } + if let Some(val) = self.uninstantiated_attachments_expire_after { + conf.uninstantiated_attachments_expire_after = val + } + if let Some(val) = self.unresolved_attachment_instances_expire_after { + conf.unresolved_attachment_instances_expire_after = val + } + conf + } +} + #[derive(Clone, Deserialize, Default, Debug)] pub struct EventObserverConfigFile { pub endpoint: String, diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index f69b8e94a3..8e7d533aa8 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -4100,7 +4100,7 @@ impl StacksNode { let config = runloop.config().clone(); let is_miner = runloop.is_miner(); let burnchain = runloop.get_burnchain(); - let atlas_config = AtlasConfig::default(config.is_mainnet()); + let atlas_config = config.atlas.clone(); let keychain = Keychain::default(config.node.seed.clone()); // we can call _open_ here rather than _connect_, since connect is first called in @@ -4110,7 +4110,7 @@ impl StacksNode { true, burnchain.pox_constants.clone(), ) - .expect("Error while instantiating sor/tition db"); + .expect("Error while instantiating sortition db"); Self::setup_ast_size_precheck(&config, &mut sortdb); diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index 10da48829f..4f29e94e00 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -446,7 +446,7 @@ impl Node { } fn make_atlas_config() -> AtlasConfig { - AtlasConfig::default(false) + AtlasConfig::new(false) } pub fn make_atlas_db(&self) -> AtlasDB { diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 5fa06bbc3f..5182ceddd1 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -504,7 +504,7 @@ impl RunLoop { let use_test_genesis_data = use_test_genesis_chainstate(&self.config); // load up genesis Atlas attachments - let mut atlas_config = AtlasConfig::default(self.config.is_mainnet()); + let mut atlas_config = AtlasConfig::new(self.config.is_mainnet()); let genesis_attachments = GenesisData::new(use_test_genesis_data) .read_name_zonefiles() .into_iter() @@ -515,7 +515,7 @@ impl RunLoop { let chain_state_db = self.boot_chainstate(burnchain_config); // NOTE: re-instantiate AtlasConfig so we don't have to keep the genesis attachments around - let moved_atlas_config = AtlasConfig::default(self.config.is_mainnet()); + let moved_atlas_config = self.config.atlas.clone(); let moved_config = self.config.clone(); let moved_burnchain_config = burnchain_config.clone(); let mut coordinator_dispatcher = self.event_dispatcher.clone(); diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 3fc4bc4602..03b380f464 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -7681,7 +7681,7 @@ fn atlas_stress_integration_test() { let mut attachment_hashes = HashMap::new(); { let atlasdb_path = conf_bootstrap_node.get_atlas_db_file_path(); - let atlasdb = AtlasDB::connect(AtlasConfig::default(false), &atlasdb_path, false).unwrap(); + let atlasdb = AtlasDB::connect(AtlasConfig::new(false), &atlasdb_path, false).unwrap(); for ibh in index_block_hashes.iter() { let indexes = query_rows::( &atlasdb.conn,