diff --git a/Cargo.lock b/Cargo.lock index 247f435507..80e00586c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,20 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" +[[package]] +name = "assert_cmd" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-channel" version = "1.6.1" @@ -397,7 +411,9 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ + "lazy_static", "memchr", + "regex-automata", ] [[package]] @@ -509,6 +525,7 @@ dependencies = [ "num", "replace_with", "rpc", + "serde", "serde_json", "serialization", "static_assertions", @@ -930,6 +947,12 @@ dependencies = [ "quick-error 1.2.3", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "downcast" version = "0.11.0" @@ -2186,6 +2209,7 @@ dependencies = [ name = "mintlayer-test" version = "0.1.0" dependencies = [ + "common", "libtest-mimic", "node", "thiserror", @@ -2385,6 +2409,7 @@ name = "node" version = "0.1.0" dependencies = [ "anyhow", + "assert_cmd", "chainstate", "chainstate-storage", "clap 3.2.5", @@ -2393,10 +2418,12 @@ dependencies = [ "logging", "p2p", "rpc", + "serde", "strum", "subsystem", "thiserror", "tokio", + "toml", ] [[package]] @@ -2558,6 +2585,7 @@ dependencies = [ "parity-scale-codec", "portpicker", "rpc", + "serde", "serialization", "sscanf", "subsystem", @@ -3230,6 +3258,7 @@ dependencies = [ "async-trait", "jsonrpsee", "logging", + "serde", "subsystem", "tokio", ] diff --git a/chainstate/Cargo.toml b/chainstate/Cargo.toml index db8bb69b24..59e163ef83 100644 --- a/chainstate/Cargo.toml +++ b/chainstate/Cargo.toml @@ -7,7 +7,6 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = "0.1" chainstate-storage = {path = '../chainstate-storage'} chainstate-types = {path = '../chainstate-types'} common = {path = '../common'} @@ -18,12 +17,14 @@ serialization = {path = "../serialization"} subsystem = {path = '../subsystem'} utils = {path = '../utils'} +async-trait = "0.1" hex = "0.4" itertools = "0.10" jsonrpsee = {version = "0.14", features = ["macros"]} num = "0.4.0" replace_with = "0.1" thiserror = "1.0" +serde = { version = "1", features = ["derive"] } [dev-dependencies] mockall = "0.11" diff --git a/chainstate/src/config.rs b/chainstate/src/config.rs new file mode 100644 index 0000000000..00a11a0718 --- /dev/null +++ b/chainstate/src/config.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://spdx.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; + +/// The chainstate subsystem configuration. +#[derive(Serialize, Deserialize, Debug)] +pub struct ChainstateConfig { + /// The number of maximum attempts to process a block. + pub max_db_commit_attempts: usize, + /// The maximum capacity of the orphan blocks pool. + pub max_orphan_blocks: usize, +} + +impl ChainstateConfig { + /// Creates a new chainstate configuration isntance. + pub fn new() -> Self { + Self { + max_db_commit_attempts: 10, + max_orphan_blocks: 512, + } + } +} diff --git a/chainstate/src/detail/block_index_history_iter.rs b/chainstate/src/detail/block_index_history_iter.rs index 83c3b9a7f7..4ac1b52103 100644 --- a/chainstate/src/detail/block_index_history_iter.rs +++ b/chainstate/src/detail/block_index_history_iter.rs @@ -75,7 +75,7 @@ mod tests { primitives::{time, Idable, H256}, }; - use crate::{BlockSource, Chainstate}; + use crate::{BlockSource, Chainstate, ChainstateConfig}; use super::*; @@ -83,9 +83,16 @@ mod tests { fn history_iteration() { common::concurrency::model(|| { let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let mut chainstate = - Chainstate::new(chain_config.clone(), storage, None, Default::default()).unwrap(); + let mut chainstate = Chainstate::new( + chain_config.clone(), + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); // put three blocks in a chain after genesis let block1 = Block::new( diff --git a/chainstate/src/detail/chainstateref.rs b/chainstate/src/detail/chainstateref.rs index e94aac862a..f29ada65ed 100644 --- a/chainstate/src/detail/chainstateref.rs +++ b/chainstate/src/detail/chainstateref.rs @@ -32,7 +32,7 @@ use common::{ use logging::log; use utils::ensure; -use crate::{BlockError, BlockSource}; +use crate::{BlockError, BlockSource, ChainstateConfig}; use super::{ consensus_validator::{self, BlockIndexHandle}, @@ -44,6 +44,7 @@ use super::{ pub(crate) struct ChainstateRef<'a, S, O> { chain_config: &'a ChainConfig, + _chainstate_config: &'a ChainstateConfig, db_tx: S, orphan_blocks: O, time_getter: &'a TimeGetterFn, @@ -92,12 +93,14 @@ impl<'a, S: TransactionRw, O> ChainstateRef<' impl<'a, S: BlockchainStorageRead, O: OrphanBlocks> ChainstateRef<'a, S, O> { pub fn new_rw( chain_config: &'a ChainConfig, + chainstate_config: &'a ChainstateConfig, db_tx: S, orphan_blocks: O, time_getter: &'a TimeGetterFn, ) -> ChainstateRef<'a, S, O> { ChainstateRef { chain_config, + _chainstate_config: chainstate_config, db_tx, orphan_blocks, time_getter, @@ -106,12 +109,14 @@ impl<'a, S: BlockchainStorageRead, O: OrphanBlocks> ChainstateRef<'a, S, O> { pub fn new_ro( chain_config: &'a ChainConfig, + chainstate_config: &'a ChainstateConfig, db_tx: S, orphan_blocks: O, time_getter: &'a TimeGetterFn, ) -> ChainstateRef<'a, S, O> { ChainstateRef { chain_config, + _chainstate_config: chainstate_config, db_tx, orphan_blocks, time_getter, diff --git a/chainstate/src/detail/median_time.rs b/chainstate/src/detail/median_time.rs index dc8d343bd7..68248446eb 100644 --- a/chainstate/src/detail/median_time.rs +++ b/chainstate/src/detail/median_time.rs @@ -42,7 +42,7 @@ pub fn calculate_median_time_past( #[cfg(test)] mod test { - use crate::{detail::time_getter::TimeGetter, BlockSource, Chainstate}; + use crate::{detail::time_getter::TimeGetter, BlockSource, Chainstate, ChainstateConfig}; use super::*; use chainstate_storage::Store; @@ -86,9 +86,16 @@ mod test { fn blocks_median_time() { common::concurrency::model(|| { let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let mut chainstate = - Chainstate::new(chain_config, storage, None, Default::default()).unwrap(); + let mut chainstate = Chainstate::new( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); let block_count = 500; @@ -151,7 +158,10 @@ mod test { })); let storage = Store::new_empty().unwrap(); - let mut chainstate = Chainstate::new(chain_config, storage, None, time_getter).unwrap(); + let chainstate_config = ChainstateConfig::new(); + let mut chainstate = + Chainstate::new(chain_config, chainstate_config, storage, None, time_getter) + .unwrap(); // we use unordered block times, and ensure that the median will be in the right spot let block1_time = current_time.load(Ordering::SeqCst) as u32 + 1; diff --git a/chainstate/src/detail/mod.rs b/chainstate/src/detail/mod.rs index 96ad25f148..22051a9c3d 100644 --- a/chainstate/src/detail/mod.rs +++ b/chainstate/src/detail/mod.rs @@ -15,8 +15,7 @@ // // Author(s): S. Afach, A. Sinitsyn -use crate::detail::orphan_blocks::OrphanBlocksPool; -use crate::ChainstateEvent; +use crate::{detail::orphan_blocks::OrphanBlocksPool, ChainstateConfig, ChainstateEvent}; use chainstate_storage::Transactional; use chainstate_types::block_index::BlockIndex; use common::chain::block::{Block, BlockHeader}; @@ -58,6 +57,7 @@ use time_getter::TimeGetter; #[must_use] pub struct Chainstate { chain_config: Arc, + chainstate_config: ChainstateConfig, chainstate_storage: chainstate_storage::Store, orphan_blocks: OrphanBlocksPool, custom_orphan_error_hook: Option>, @@ -81,6 +81,7 @@ impl Chainstate { let db_tx = self.chainstate_storage.transaction_rw(); chainstateref::ChainstateRef::new_rw( &self.chain_config, + &self.chainstate_config, db_tx, self.orphan_blocks.as_rw_ref(), self.time_getter.getter(), @@ -92,6 +93,7 @@ impl Chainstate { let db_tx = self.chainstate_storage.transaction_ro(); chainstateref::ChainstateRef::new_ro( &self.chain_config, + &self.chainstate_config, db_tx, self.orphan_blocks.as_ro_ref(), self.time_getter.getter(), @@ -104,6 +106,7 @@ impl Chainstate { pub fn new( chain_config: Arc, + chainstate_config: ChainstateConfig, chainstate_storage: chainstate_storage::Store, custom_orphan_error_hook: Option>, time_getter: TimeGetter, @@ -112,6 +115,7 @@ impl Chainstate { let mut cons = Self::new_no_genesis( chain_config, + chainstate_config, chainstate_storage, custom_orphan_error_hook, time_getter, @@ -138,14 +142,17 @@ impl Chainstate { fn new_no_genesis( chain_config: Arc, + chainstate_config: ChainstateConfig, chainstate_storage: chainstate_storage::Store, custom_orphan_error_hook: Option>, time_getter: TimeGetter, ) -> Result { + let orphan_blocks = OrphanBlocksPool::new(chainstate_config.max_orphan_blocks); let cons = Self { chain_config, + chainstate_config, chainstate_storage, - orphan_blocks: OrphanBlocksPool::new_default(), + orphan_blocks, custom_orphan_error_hook, events_controller: EventsController::new(), time_getter, @@ -188,13 +195,10 @@ impl Chainstate { block_source: BlockSource, attempt_number: usize, ) -> Result, BlockError> { - // TODO: move to a configuration object that loads from command line arguments - const MAX_DB_COMMIT_COUNT: usize = 10; - - if attempt_number >= MAX_DB_COMMIT_COUNT { + if attempt_number >= self.chainstate_config.max_db_commit_attempts { Err(BlockError::DatabaseCommitError( block.get_id(), - MAX_DB_COMMIT_COUNT, + self.chainstate_config.max_db_commit_attempts, db_error, )) } else { diff --git a/chainstate/src/detail/orphan_blocks/pool.rs b/chainstate/src/detail/orphan_blocks/pool.rs index 921fb834c0..98cd94e85a 100644 --- a/chainstate/src/detail/orphan_blocks/pool.rs +++ b/chainstate/src/detail/orphan_blocks/pool.rs @@ -15,14 +15,12 @@ // // Author(s): S. Afach +use std::{collections::BTreeMap, sync::Arc}; + use super::{OrphanBlocksRef, OrphanBlocksRefMut}; use common::chain::block::Block; use common::primitives::{Id, Idable}; use crypto::random::SliceRandom; -use std::collections::BTreeMap; -use std::sync::Arc; - -pub const DEFAULT_MAX_ORPHAN_BLOCKS: usize = 512; // FIXME: The Arc here is unnecessary: https://github.com/mintlayer/mintlayer-core/issues/164 pub struct OrphanBlocksPool { @@ -38,17 +36,7 @@ pub enum OrphanAddError { } impl OrphanBlocksPool { - pub fn new_default() -> Self { - OrphanBlocksPool { - orphan_ids: Vec::new(), - orphan_by_id: BTreeMap::new(), - orphan_by_prev_id: BTreeMap::new(), - max_orphans: DEFAULT_MAX_ORPHAN_BLOCKS, - } - } - - #[allow(dead_code)] - pub fn new_custom(max_orphans: usize) -> Self { + pub fn new(max_orphans: usize) -> Self { OrphanBlocksPool { orphan_ids: Vec::new(), orphan_by_id: BTreeMap::new(), @@ -208,10 +196,11 @@ impl OrphanBlocksPool { mod tests { use super::*; use checkers::*; - use common::chain::block::Block; - use common::primitives::Id; + use common::{chain::block::Block, primitives::Id}; use helpers::*; + const MAX_ORPHAN_BLOCKS: usize = 512; + mod helpers { use super::*; use common::chain::block::timestamp::BlockTimestamp; @@ -323,24 +312,17 @@ mod tests { } } - #[test] - fn test_pool_default() { - let orphans_pool = OrphanBlocksPool::new_default(); - assert_eq!(orphans_pool.max_orphans, DEFAULT_MAX_ORPHAN_BLOCKS); - check_empty_pool(&orphans_pool); - } - #[test] fn test_pool_custom() { let max_orphans = 3; - let orphans_pool = OrphanBlocksPool::new_custom(max_orphans); + let orphans_pool = OrphanBlocksPool::new(max_orphans); assert_eq!(orphans_pool.max_orphans, max_orphans); check_empty_pool(&orphans_pool); } #[test] fn test_add_one_block_and_clear() { - let mut orphans_pool = OrphanBlocksPool::new_default(); + let mut orphans_pool = OrphanBlocksPool::new(MAX_ORPHAN_BLOCKS); // add a random block let block = gen_random_block(); @@ -357,7 +339,7 @@ mod tests { #[test] fn test_add_blocks_and_clear() { - let mut orphans_pool = OrphanBlocksPool::new_default(); + let mut orphans_pool = OrphanBlocksPool::new(MAX_ORPHAN_BLOCKS); // add a random block let block = gen_random_block(); @@ -406,7 +388,7 @@ mod tests { #[test] fn test_add_block_exceeds_max() { let max_orphans = 3; - let mut orphans_pool = OrphanBlocksPool::new_custom(max_orphans); + let mut orphans_pool = OrphanBlocksPool::new(max_orphans); let blocks = gen_random_blocks(max_orphans as u32 + 2); blocks.into_iter().for_each(|block| { @@ -418,7 +400,7 @@ mod tests { #[test] fn test_add_block_repeated() { - let mut orphans_pool = OrphanBlocksPool::new_default(); + let mut orphans_pool = OrphanBlocksPool::new(MAX_ORPHAN_BLOCKS); let blocks = gen_random_blocks(50); blocks.iter().for_each(|block| { @@ -437,7 +419,7 @@ mod tests { #[test] fn test_pool_drop_block() { - let mut orphans_pool = OrphanBlocksPool::new_default(); + let mut orphans_pool = OrphanBlocksPool::new(MAX_ORPHAN_BLOCKS); let blocks = gen_random_blocks(5); blocks.iter().for_each(|block| { @@ -461,7 +443,7 @@ mod tests { #[test] fn test_deepest_child_in_chain() { - let mut orphans_pool = OrphanBlocksPool::new_default(); + let mut orphans_pool = OrphanBlocksPool::new(MAX_ORPHAN_BLOCKS); // In `orphans_by_prev_id`: // [ @@ -516,7 +498,7 @@ mod tests { #[test] fn test_deepest_child_common_parent() { - let mut orphans_pool = OrphanBlocksPool::new_default(); + let mut orphans_pool = OrphanBlocksPool::new(MAX_ORPHAN_BLOCKS); // In `orphans_by_prev_id`: // [ // ( a, (b,c,d,e,f) ), @@ -574,7 +556,7 @@ mod tests { #[test] fn test_prune() { - let mut orphans_pool = OrphanBlocksPool::new_custom(12); + let mut orphans_pool = OrphanBlocksPool::new(12); // in `orphans_by_prev_id`: // [ // ( a, (b,c,d,e) ) @@ -646,7 +628,7 @@ mod tests { #[test] fn test_simple_take_all_children_of() { - let mut orphans_pool = OrphanBlocksPool::new_custom(20); + let mut orphans_pool = OrphanBlocksPool::new(20); let count = 9; // in `orphans_by_prev_id`: @@ -688,7 +670,7 @@ mod tests { #[test] fn test_mix_chain_take_all_children_of() { - let mut orphans_pool = OrphanBlocksPool::new_custom(20); + let mut orphans_pool = OrphanBlocksPool::new(20); let count = 9; // in `orphans_by_prev_id`: diff --git a/chainstate/src/detail/tests/events_tests.rs b/chainstate/src/detail/tests/events_tests.rs index f16b88ea4d..515d47ba8e 100644 --- a/chainstate/src/detail/tests/events_tests.rs +++ b/chainstate/src/detail/tests/events_tests.rs @@ -120,11 +120,18 @@ fn several_subscribers_several_events() { #[test] fn orphan_block() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); let (orphan_error_hook, errors) = orphan_error_hook(); - let mut chainstate = - Chainstate::new(config, storage, Some(orphan_error_hook), Default::default()).unwrap(); + let mut chainstate = Chainstate::new( + chain_config, + chainstate_config, + storage, + Some(orphan_error_hook), + Default::default(), + ) + .unwrap(); let events = subscribe(&mut chainstate, 1); assert!(!chainstate.events_controller.subscribers().is_empty()); @@ -143,11 +150,18 @@ fn orphan_block() { #[test] fn custom_orphan_error_hook() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); let (orphan_error_hook, errors) = orphan_error_hook(); - let mut chainstate = - Chainstate::new(config, storage, Some(orphan_error_hook), Default::default()).unwrap(); + let mut chainstate = Chainstate::new( + chain_config, + chainstate_config, + storage, + Some(orphan_error_hook), + Default::default(), + ) + .unwrap(); let events = subscribe(&mut chainstate, 1); assert!(!chainstate.events_controller.subscribers().is_empty()); diff --git a/chainstate/src/detail/tests/mod.rs b/chainstate/src/detail/tests/mod.rs index 73a499bf54..ee748d3a05 100644 --- a/chainstate/src/detail/tests/mod.rs +++ b/chainstate/src/detail/tests/mod.rs @@ -83,12 +83,16 @@ fn create_utxo_data( } fn setup_chainstate() -> Chainstate { - chainstate_with_config(create_unit_test_config()) + chainstate_with_config(create_unit_test_config(), ChainstateConfig::new()) } -fn chainstate_with_config(config: ChainConfig) -> Chainstate { +fn chainstate_with_config( + chain_config: ChainConfig, + chainstate_config: ChainstateConfig, +) -> Chainstate { Chainstate::new( - Arc::new(config), + Arc::new(chain_config), + chainstate_config, Store::new_empty().unwrap(), None, Default::default(), @@ -135,8 +139,9 @@ fn create_new_outputs(tx: &Transaction) -> Vec<(TxInput, TxOutput)> { #[ignore] #[test] fn generate_blocks_for_functional_tests() { - let config = create_regtest(); - let chainstate = chainstate_with_config(config); + let chain_config = create_regtest(); + let chainstate_config = ChainstateConfig::new(); + let chainstate = chainstate_with_config(chain_config, chainstate_config); let mut btf = BlockTestFramework::with_chainstate(chainstate); let difficulty = Uint256([0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF]); diff --git a/chainstate/src/detail/tests/processing_tests.rs b/chainstate/src/detail/tests/processing_tests.rs index 719181cf01..b25bd4582e 100644 --- a/chainstate/src/detail/tests/processing_tests.rs +++ b/chainstate/src/detail/tests/processing_tests.rs @@ -23,7 +23,7 @@ use crate::{ pow::error::ConsensusPoWError, tests::{test_framework::BlockTestFramework, *}, }, - make_chainstate, + make_chainstate, ChainstateConfig, }; use chainstate_storage::{BlockchainStorageRead, Store}; use common::{ @@ -42,13 +42,20 @@ use crypto::random::{self, Rng}; #[test] fn genesis_peer_block() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let mut chainstate = - Chainstate::new_no_genesis(config.clone(), storage, None, Default::default()).unwrap(); + let mut chainstate = Chainstate::new_no_genesis( + chain_config.clone(), + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); assert_eq!( chainstate - .process_block(config.genesis_block().clone(), BlockSource::Peer) + .process_block(chain_config.genesis_block().clone(), BlockSource::Peer) .unwrap_err(), BlockError::InvalidBlockSource ); @@ -58,10 +65,17 @@ fn genesis_peer_block() { #[test] fn process_genesis_block() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let mut chainstate = - Chainstate::new_no_genesis(config, storage, None, Default::default()).unwrap(); + let mut chainstate = Chainstate::new_no_genesis( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); let block_index = chainstate .process_block( @@ -137,10 +151,17 @@ fn test_orphans_chains() { #[test] fn empty_chainstate() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let chainstate = - Chainstate::new_no_genesis(config, storage, None, Default::default()).unwrap(); + let chainstate = Chainstate::new_no_genesis( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); assert_eq!(chainstate.get_best_block_id().unwrap(), None); assert_eq!( chainstate @@ -221,10 +242,17 @@ fn spend_inputs_simple() { #[test] fn straight_chain() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let mut chainstate = - Chainstate::new_no_genesis(config, storage, None, Default::default()).unwrap(); + let mut chainstate = Chainstate::new_no_genesis( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); let genesis_index = chainstate .process_block( @@ -495,8 +523,9 @@ fn consensus_type() { // This should succeed because config::Builder by default uses create_mainnet_genesis to // create the genesis_block, and this function creates a genesis block with // ConsenssuData::None, which agreess with the net_upgrades we defined above. - let config = ConfigBuilder::test_chain().net_upgrades(net_upgrades).build(); - let chainstate = chainstate_with_config(config); + let chain_config = ConfigBuilder::test_chain().net_upgrades(net_upgrades).build(); + let chainstate_config = ChainstateConfig::new(); + let chainstate = chainstate_with_config(chain_config, chainstate_config); let mut btf = BlockTestFramework::with_chainstate(chainstate); @@ -632,8 +661,9 @@ fn pow() { // This should succeed because TestChainConfig by default uses create_mainnet_genesis to // create the genesis_block, and this function creates a genesis block with // ConsenssuData::None, which agreess with the net_upgrades we defined above. - let config = ConfigBuilder::test_chain().net_upgrades(net_upgrades).build(); - let chainstate = chainstate_with_config(config); + let chain_config = ConfigBuilder::test_chain().net_upgrades(net_upgrades).build(); + let chainstate_config = ChainstateConfig::new(); + let chainstate = chainstate_with_config(chain_config, chainstate_config); let mut btf = BlockTestFramework::with_chainstate(chainstate); @@ -689,7 +719,8 @@ fn blocks_from_the_future() { })); let storage = Store::new_empty().unwrap(); - let mut chainstate = Chainstate::new(config, storage, None, time_getter).unwrap(); + let mut chainstate = + Chainstate::new(config, ChainstateConfig::new(), storage, None, time_getter).unwrap(); { // ensure no blocks are in chain, so that median time can be the genesis time @@ -769,9 +800,17 @@ fn blocks_from_the_future() { #[test] fn test_mainnet_initialization() { - let config = Arc::new(common::chain::config::create_mainnet()); + let chain_config = Arc::new(common::chain::config::create_mainnet()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - make_chainstate(config, storage, None, Default::default()).unwrap(); + make_chainstate( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); } fn make_invalid_pow_block( diff --git a/chainstate/src/detail/tests/reorgs_tests.rs b/chainstate/src/detail/tests/reorgs_tests.rs index 017f11f35c..d46854a0d8 100644 --- a/chainstate/src/detail/tests/reorgs_tests.rs +++ b/chainstate/src/detail/tests/reorgs_tests.rs @@ -17,9 +17,12 @@ use std::sync::Mutex; -use crate::detail::tests::{ - test_framework::{BlockTestFramework, TestBlockParams, TestSpentStatus}, - *, +use crate::{ + detail::tests::{ + test_framework::{BlockTestFramework, TestBlockParams, TestSpentStatus}, + *, + }, + ChainstateConfig, }; use chainstate_storage::{BlockchainStorageRead, Store}; use common::chain::config::create_unit_test_config; @@ -28,10 +31,17 @@ use common::chain::config::create_unit_test_config; #[test] fn reorg_simple() { common::concurrency::model(|| { - let config = Arc::new(create_unit_test_config()); + let chain_config = Arc::new(create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let storage = Store::new_empty().unwrap(); - let mut chainstate = - Chainstate::new_no_genesis(config, storage, None, Default::default()).unwrap(); + let mut chainstate = Chainstate::new_no_genesis( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(); // Process the genesis block. chainstate diff --git a/chainstate/src/lib.rs b/chainstate/src/lib.rs index 0fd87a0a02..b599fa750c 100644 --- a/chainstate/src/lib.rs +++ b/chainstate/src/lib.rs @@ -15,28 +15,27 @@ // // Author(s): S. Afach, A. Sinitsyn +mod config; mod detail; -pub mod rpc; - -pub mod chainstate_interface_impl; - pub mod chainstate_interface; +pub mod chainstate_interface_impl; +pub mod rpc; -pub use detail::ban_score; +pub use crate::{ + chainstate_interface_impl::ChainstateInterfaceImpl, + config::ChainstateConfig, + detail::{ban_score, BlockError, BlockSource, Chainstate}, +}; use std::sync::Arc; use chainstate_interface::ChainstateInterface; -pub use chainstate_interface_impl::ChainstateInterfaceImpl; use common::{ chain::{block::Block, ChainConfig}, primitives::{BlockHeight, Id}, }; -use detail::time_getter::TimeGetter; -pub use detail::BlockError; -use detail::PropertyQueryError; -pub use detail::{BlockSource, Chainstate}; +use detail::{time_getter::TimeGetter, PropertyQueryError}; #[derive(Debug, Clone)] pub enum ChainstateEvent { @@ -59,12 +58,14 @@ type ChainstateHandle = subsystem::Handle>; pub fn make_chainstate( chain_config: Arc, + chainstate_config: ChainstateConfig, chainstate_storage: chainstate_storage::Store, custom_orphan_error_hook: Option>, time_getter: TimeGetter, ) -> Result, ChainstateError> { let cons = Chainstate::new( chain_config, + chainstate_config, chainstate_storage, custom_orphan_error_hook, time_getter, diff --git a/chainstate/src/rpc.rs b/chainstate/src/rpc.rs index 898145e2a2..2aa95954d9 100644 --- a/chainstate/src/rpc.rs +++ b/chainstate/src/rpc.rs @@ -15,9 +15,7 @@ //! Chainstate subsystem RPC handler -use crate::ChainstateError; - -use crate::{Block, BlockSource}; +use crate::{Block, BlockSource, ChainstateError}; use common::primitives::BlockHeight; use serialization::Decode; use subsystem::subsystem::CallError; @@ -87,6 +85,7 @@ fn handle_error(e: Result, CallError>) -> rpc::Res #[cfg(test)] mod test { use super::*; + use crate::ChainstateConfig; use serde_json::Value; use std::{future::Future, sync::Arc}; @@ -94,11 +93,19 @@ mod test { proc: impl 'static + Send + FnOnce(crate::ChainstateHandle) -> F, ) { let storage = chainstate_storage::Store::new_empty().unwrap(); - let cfg = Arc::new(common::chain::config::create_unit_test_config()); + let chain_config = Arc::new(common::chain::config::create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let mut man = subsystem::Manager::new("rpctest"); let handle = man.add_subsystem( "chainstate", - crate::make_chainstate(cfg, storage, None, Default::default()).unwrap(), + crate::make_chainstate( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(), ); let _ = man.add_raw_subsystem( "test", diff --git a/common/src/chain/config/builder.rs b/common/src/chain/config/builder.rs index 1f4962c725..a0b7f71952 100644 --- a/common/src/chain/config/builder.rs +++ b/common/src/chain/config/builder.rs @@ -85,8 +85,6 @@ impl GenesisBlockInit { pub struct Builder { chain_type: ChainType, address_prefix: String, - rpc_port: u16, - p2p_port: u16, magic_bytes: [u8; 4], blockreward_maturity: BlockDistance, max_future_block_time_offset: Duration, @@ -116,8 +114,6 @@ impl Builder { max_block_size_with_smart_contracts: super::MAX_BLOCK_CONTRACTS_SIZE, max_future_block_time_offset: super::DEFAULT_MAX_FUTURE_BLOCK_TIME_OFFSET, target_block_spacing: super::DEFAULT_TARGET_BLOCK_SPACING, - p2p_port: 8978, - rpc_port: 15234, genesis_block: chain_type.default_genesis_init(), emission_schedule: EmissionScheduleInit::Mainnet, net_upgrades: chain_type.default_net_upgrades(), @@ -145,8 +141,6 @@ impl Builder { max_block_size_with_smart_contracts, max_future_block_time_offset, target_block_spacing, - p2p_port, - rpc_port, genesis_block, emission_schedule, net_upgrades, @@ -180,8 +174,6 @@ impl Builder { max_block_size_with_smart_contracts, max_future_block_time_offset, target_block_spacing, - p2p_port, - rpc_port, genesis_block_id: genesis_block.get_id(), genesis_block, height_checkpoint_data: BTreeMap::new(), @@ -207,8 +199,6 @@ macro_rules! builder_method { impl Builder { builder_method!(chain_type: ChainType); builder_method!(address_prefix: String); - builder_method!(rpc_port: u16); - builder_method!(p2p_port: u16); builder_method!(magic_bytes: [u8; 4]); builder_method!(blockreward_maturity: BlockDistance); builder_method!(max_future_block_time_offset: Duration); diff --git a/common/src/chain/config/mod.rs b/common/src/chain/config/mod.rs index 61c33b8b94..86ec3605d7 100644 --- a/common/src/chain/config/mod.rs +++ b/common/src/chain/config/mod.rs @@ -84,8 +84,6 @@ impl ChainType { pub struct ChainConfig { chain_type: ChainType, address_prefix: String, - rpc_port: u16, - p2p_port: u16, height_checkpoint_data: BTreeMap>, net_upgrades: NetUpgrades, magic_bytes: [u8; 4], @@ -135,14 +133,6 @@ impl ChainConfig { &self.net_upgrades } - pub fn p2p_port(&self) -> u16 { - self.p2p_port - } - - pub fn rpc_port(&self) -> u16 { - self.rpc_port - } - pub fn height_checkpoints(&self) -> &BTreeMap> { &self.height_checkpoint_data } diff --git a/common/src/primitives/semver.rs b/common/src/primitives/semver.rs index a0efddebca..0c31a8ab6d 100644 --- a/common/src/primitives/semver.rs +++ b/common/src/primitives/semver.rs @@ -14,6 +14,7 @@ // limitations under the License. // // Author(s): A. Altonen + use serialization::{Decode, Encode}; #[derive(Debug, PartialEq, Eq, Encode, Decode, Copy, Clone)] diff --git a/node/Cargo.toml b/node/Cargo.toml index 351dc1ece5..9dfaa7175f 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -23,3 +23,8 @@ jsonrpsee = { version = "0.14", features = ["macros"] } strum = "0.24" tokio = { version = "1.19", default-features = false } thiserror = "1.0" +serde = { version = "1", features = ["derive"] } +toml = "0.5" + +[dev-dependencies] +assert_cmd = "2" diff --git a/node/src/config.rs b/node/src/config.rs new file mode 100644 index 0000000000..5be15e3d88 --- /dev/null +++ b/node/src/config.rs @@ -0,0 +1,114 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://spdx.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The node configuration. + +use std::fs; + +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; + +use chainstate::ChainstateConfig; +use p2p::config::P2pConfig; +use rpc::RpcConfig; + +use crate::RunOptions; + +/// The node configuration. +#[derive(Serialize, Deserialize, Debug)] +pub struct NodeConfig { + // Subsystems configurations. + pub chainstate: ChainstateConfig, + pub p2p: P2pConfig, + pub rpc: RpcConfig, +} + +impl NodeConfig { + /// Creates a new `Config` instance for the specified chain type. + pub fn new() -> Result { + let chainstate = ChainstateConfig::new(); + let p2p = P2pConfig::new(); + let rpc = RpcConfig::new()?; + Ok(Self { + chainstate, + p2p, + rpc, + }) + } + + /// Reads a configuration from the path specified in options and overrides the provided + /// parameters. + pub fn read(options: &RunOptions) -> Result { + let config = fs::read_to_string(&options.config_path) + .with_context(|| format!("Failed to read '{:?}' config", options.config_path))?; + let NodeConfig { + chainstate, + p2p, + rpc, + } = toml::from_str(&config).context("Failed to parse config")?; + + let chainstate = chainstate_config(chainstate, options); + let p2p = p2p_config(p2p, options); + let rpc = rpc_config(rpc, options); + + Ok(Self { + chainstate, + p2p, + rpc, + }) + } +} + +fn chainstate_config(config: ChainstateConfig, options: &RunOptions) -> ChainstateConfig { + let ChainstateConfig { + max_db_commit_attempts, + max_orphan_blocks, + } = config; + + let max_db_commit_attempts = options.max_db_commit_attempts.unwrap_or(max_db_commit_attempts); + let max_orphan_blocks = options.max_orphan_blocks.unwrap_or(max_orphan_blocks); + + ChainstateConfig { + max_db_commit_attempts, + max_orphan_blocks, + } +} + +fn p2p_config(config: P2pConfig, options: &RunOptions) -> P2pConfig { + let P2pConfig { + bind_address, + ban_threshold, + outbound_connection_timeout, + } = config; + + let bind_address = options.p2p_addr.clone().unwrap_or(bind_address); + let ban_threshold = options.p2p_ban_threshold.unwrap_or(ban_threshold); + let outbound_connection_timeout = + options.p2p_outbound_connection_timeout.unwrap_or(outbound_connection_timeout); + + P2pConfig { + bind_address, + ban_threshold, + outbound_connection_timeout, + } +} + +fn rpc_config(config: RpcConfig, options: &RunOptions) -> RpcConfig { + let RpcConfig { bind_address } = config; + + let bind_address = options.rpc_addr.unwrap_or(bind_address); + + RpcConfig { bind_address } +} diff --git a/node/src/lib.rs b/node/src/lib.rs index f4b50452da..b64d55c710 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -15,12 +15,14 @@ //! Top-level node runner as a library +mod config; mod options; mod runner; pub type Error = anyhow::Error; -pub use options::Options; +pub use config::NodeConfig; +pub use options::{Command, Options, RunOptions}; pub use runner::{initialize, run}; pub fn init_logging(opts: &Options) { diff --git a/node/src/main.rs b/node/src/main.rs index 5e4b3d7449..b78568482a 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -15,22 +15,17 @@ //! Top-level node binary -mod options; -mod runner; - async fn run() -> anyhow::Result<()> { - let opts = options::Options::from_args(std::env::args_os()); - + let opts = node::Options::from_args(std::env::args_os()); logging::init_logging(opts.log_path.as_ref()); - logging::log::trace!("Command line options: {:?}", opts); - - runner::run(opts).await + logging::log::trace!("Command line options: {opts:?}"); + node::run(opts).await } #[tokio::main] async fn main() { run().await.unwrap_or_else(|err| { - eprintln!("ERROR: {}", err); + eprintln!("ERROR: {:?}", err); std::process::exit(1) }) } diff --git a/node/src/options.rs b/node/src/options.rs index 81b287611d..0524099f70 100644 --- a/node/src/options.rs +++ b/node/src/options.rs @@ -13,34 +13,75 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Node configuration options +//! The node command line options. -use std::ffi::OsString; -use std::net::SocketAddr; -use std::path::PathBuf; +use std::{ffi::OsString, net::SocketAddr, path::PathBuf}; + +use clap::{Args, Parser, Subcommand}; use strum::VariantNames; use common::chain::config::ChainType; /// Mintlayer node executable -#[derive(clap::Parser, Debug)] +#[derive(Parser, Debug)] #[clap(author, version, about)] pub struct Options { /// Where to write logs #[clap(long, value_name = "PATH")] pub log_path: Option, - /// Address to bind RPC to - #[clap(long, value_name = "ADDR", default_value = "127.0.0.1:3030")] - pub rpc_addr: SocketAddr, + #[clap(subcommand)] + pub command: Command, +} + +#[derive(Subcommand, Debug)] +pub enum Command { + /// Create a configuration file. + CreateConfig { + /// The path where config will be created. + // TODO: Use a system-specific location by default such as `%APPDATA%` on Windows and + // `~/Library/Application Support` on Mac. + #[clap(short, long, default_value = "./.mintlayer/mintlayer.toml")] + path: PathBuf, + }, + Run(RunOptions), +} + +#[derive(Args, Debug)] +pub struct RunOptions { + /// The path to the configuration file. + // TODO: Use a system-specific location by default such as `%APPDATA%` on Windows and + // `~/Library/Application Support` on Mac. + #[clap(short, long, default_value = "./.mintlayer/mintlayer.toml")] + pub config_path: PathBuf, - /// Blockchain type + /// Blockchain type. #[clap(long, possible_values = ChainType::VARIANTS, default_value = "mainnet")] pub net: ChainType, - /// Address to bind P2P to - #[clap(long, value_name = "ADDR", default_value = "/ip6/::1/tcp/3031")] - pub p2p_addr: String, + /// The number of maximum attempts to process a block. + #[clap(long)] + pub max_db_commit_attempts: Option, + + /// The maximum capacity of the orphan blocks pool in blocks. + #[clap(long)] + pub max_orphan_blocks: Option, + + /// Address to bind P2P to. + #[clap(long, value_name = "ADDR")] + pub p2p_addr: Option, + + /// The p2p score threshold after which a peer is baned. + #[clap(long)] + pub p2p_ban_threshold: Option, + + /// The p2p timeout value in seconds. + #[clap(long)] + pub p2p_outbound_connection_timeout: Option, + + /// Address to bind RPC to. + #[clap(long, value_name = "ADDR")] + pub rpc_addr: Option, } impl Options { diff --git a/node/src/runner.rs b/node/src/runner.rs index 0d0377ae5a..9b39f4c304 100644 --- a/node/src/runner.rs +++ b/node/src/runner.rs @@ -15,11 +15,19 @@ //! Node initialisation routine. -use crate::options::Options; +use std::{fs, sync::Arc}; + +use anyhow::{Context, Result}; + use chainstate::rpc::ChainstateRpcServer; use common::chain::config::ChainType; +use logging::log; use p2p::rpc::P2pRpcServer; -use std::sync::Arc; + +use crate::{ + config::NodeConfig, + options::{Command, Options}, +}; #[derive(Debug, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, thiserror::Error)] enum Error { @@ -28,16 +36,19 @@ enum Error { } /// Initialize the node, giving caller the opportunity to add more subsystems before start. -pub async fn initialize(opts: Options) -> anyhow::Result { - // Initialize storage and chain configuration +pub async fn initialize( + chain_type: ChainType, + node_config: NodeConfig, +) -> Result { + // Initialize storage. let storage = chainstate_storage::Store::new_empty()?; - // Chain configuration - let chain_config = match opts.net { - ChainType::Mainnet => Arc::new(common::chain::config::create_mainnet()), - ChainType::Regtest => Arc::new(common::chain::config::create_regtest()), + // Initialize chain configuration. + let chain_config = Arc::new(match chain_type { + ChainType::Mainnet => common::chain::config::create_mainnet(), + ChainType::Regtest => common::chain::config::create_regtest(), chain_ty => return Err(Error::UnsupportedChain(chain_ty).into()), - }; + }); // INITIALIZE SUBSYSTEMS @@ -49,6 +60,7 @@ pub async fn initialize(opts: Options) -> anyhow::Result { "chainstate", chainstate::make_chainstate( Arc::clone(&chain_config), + node_config.chainstate, storage.clone(), None, Default::default(), @@ -60,8 +72,8 @@ pub async fn initialize(opts: Options) -> anyhow::Result { "p2p", p2p::make_p2p::( Arc::clone(&chain_config), + node_config.p2p, chainstate.clone(), - opts.p2p_addr, ) .await .expect("The p2p subsystem initialization failed"), @@ -70,7 +82,7 @@ pub async fn initialize(opts: Options) -> anyhow::Result { // RPC subsystem let _rpc = manager.add_subsystem( "rpc", - rpc::Builder::new(opts.rpc_addr) + rpc::Builder::new(node_config.rpc) .register(chainstate.clone().into_rpc()) .register(NodeRpc::new(manager.make_shutdown_trigger()).into_rpc()) .register(p2p.clone().into_rpc()) @@ -81,12 +93,24 @@ pub async fn initialize(opts: Options) -> anyhow::Result { Ok(manager) } -/// Initialize and run the node -pub async fn run(opts: Options) -> anyhow::Result<()> { - let manager = initialize(opts).await?; - - #[allow(clippy::unit_arg)] - Ok(manager.main().await) +/// Processes options and potentially runs the node. +pub async fn run(options: Options) -> Result<()> { + match options.command { + Command::CreateConfig { path } => { + let config = NodeConfig::new()?; + let config = toml::to_string(&config).context("Failed to serialize config")?; + log::trace!("Saving config to {path:?}\n: {config:#?}"); + fs::write(path, config).context("Failed to write config")?; + Ok(()) + } + Command::Run(options) => { + let node_config = NodeConfig::read(&options).context("Failed to initialize config")?; + log::trace!("Starting with the following config\n: {node_config:#?}"); + let manager = initialize(options.net, node_config).await?; + #[allow(clippy::unit_arg)] + Ok(manager.main().await) + } + } } #[rpc::rpc(server, namespace = "node")] diff --git a/node/tests/cli.rs b/node/tests/cli.rs new file mode 100644 index 0000000000..23722b1adc --- /dev/null +++ b/node/tests/cli.rs @@ -0,0 +1,111 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://spdx.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{net::SocketAddr, path::Path, str::FromStr}; + +use assert_cmd::Command; + +use common::chain::config::ChainType; +use node::{NodeConfig, RunOptions}; + +const BIN_NAME: &str = env!("CARGO_BIN_EXE_node"); +const CONFIG_PATH: &str = concat!(env!("CARGO_TARGET_TMPDIR"), "/test_mintlayer.toml"); + +// This test is only needed because the node name ix hardcoded here, so if the name is changed we +// get an error that is easy to understand. +#[test] +fn node_path_is_correct() { + assert!(Path::new(BIN_NAME).is_file()); +} + +#[test] +fn no_args() { + Command::new(BIN_NAME).assert().failure(); +} + +#[test] +fn create_default_config() { + Command::new(BIN_NAME) + .arg("create-config") + .arg("--path") + .arg(CONFIG_PATH) + .assert() + .success(); + let options = RunOptions { + config_path: CONFIG_PATH.into(), + net: ChainType::Mainnet, + max_db_commit_attempts: None, + max_orphan_blocks: None, + p2p_addr: None, + p2p_ban_threshold: None, + p2p_outbound_connection_timeout: None, + rpc_addr: None, + }; + let config = NodeConfig::read(&options).unwrap(); + + assert_eq!(config.chainstate.max_db_commit_attempts, 10); + assert_eq!(config.chainstate.max_orphan_blocks, 512); + + assert_eq!(config.p2p.bind_address, "/ip6/::1/tcp/3031"); + assert_eq!(config.p2p.ban_threshold, 100); + assert_eq!(config.p2p.outbound_connection_timeout, 10); + + assert_eq!( + config.rpc.bind_address, + SocketAddr::from_str("127.0.0.1:3030").unwrap() + ); +} + +// Check that the config fields are overwritten by the run options. +#[test] +fn read_config_override_values() { + Command::new(BIN_NAME) + .arg("create-config") + .arg("--path") + .arg(CONFIG_PATH) + .assert() + .success(); + + let max_db_commit_attempts = 1; + let max_orphan_blocks = 2; + let p2p_addr = "address"; + let p2p_ban_threshold = 3; + let p2p_timeout = 10000; + let rpc_addr = SocketAddr::from_str("127.0.0.1:5432").unwrap(); + + let options = RunOptions { + config_path: CONFIG_PATH.into(), + net: ChainType::Mainnet, + max_db_commit_attempts: Some(max_db_commit_attempts), + max_orphan_blocks: Some(max_orphan_blocks), + p2p_addr: Some(p2p_addr.into()), + p2p_ban_threshold: Some(p2p_ban_threshold), + p2p_outbound_connection_timeout: Some(p2p_timeout), + rpc_addr: Some(rpc_addr), + }; + let config = NodeConfig::read(&options).unwrap(); + + assert_eq!( + config.chainstate.max_db_commit_attempts, + max_db_commit_attempts + ); + assert_eq!(config.chainstate.max_orphan_blocks, max_orphan_blocks); + + assert_eq!(config.p2p.bind_address, p2p_addr); + assert_eq!(config.p2p.ban_threshold, p2p_ban_threshold); + assert_eq!(config.p2p.outbound_connection_timeout, p2p_timeout); + + assert_eq!(config.rpc.bind_address, rpc_addr); +} diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 19b34b10f5..10571144be 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -12,6 +12,7 @@ parity-scale-codec = "3.1" sscanf = "0.2" thiserror = "1.0" void = "1.0" +serde = { version = "1", features = ["derive"] } # local dependencies common = { path = "../common/" } diff --git a/p2p/src/config.rs b/p2p/src/config.rs new file mode 100644 index 0000000000..1bdb790a33 --- /dev/null +++ b/p2p/src/config.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://spdx.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; + +/// The p2p subsystem configuration. +#[derive(Serialize, Deserialize, Debug)] +pub struct P2pConfig { + /// Address to bind P2P to. + pub bind_address: String, + /// The score threshold after which a peer is banned. + pub ban_threshold: u32, + /// The outbound connection timeout value in seconds. + pub outbound_connection_timeout: u64, +} + +impl P2pConfig { + /// Creates a new p2p configuration instance. + pub fn new() -> Self { + Self { + bind_address: "/ip6/::1/tcp/3031".into(), + ban_threshold: 100, + outbound_connection_timeout: 10, + } + } +} diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index 22d72c11e7..8819e567ef 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -14,7 +14,9 @@ // limitations under the License. // // Author(s): A. Altonen + use crate::{ + config::P2pConfig, error::{ConversionError, P2pError}, net::{ConnectivityService, NetworkingService, PubSubService, SyncingCodecService}, }; @@ -24,6 +26,7 @@ use logging::log; use std::{fmt::Debug, str::FromStr, sync::Arc, time::Duration}; use tokio::sync::{mpsc, oneshot}; +pub mod config; pub mod error; pub mod event; pub mod message; @@ -39,9 +42,6 @@ pub type Result = core::result::Result; // TODO: figure out proper channel sizes const CHANNEL_SIZE: usize = 64; -// TODO: this should come from a config -const TIMEOUT: Duration = Duration::from_secs(10); - pub struct P2pInterface { p2p: P2P, } @@ -151,21 +151,24 @@ where /// /// This function starts the networking backend and individual manager objects. pub async fn new( - bind_addr: String, - config: Arc, + chain_config: Arc, + p2p_config: P2pConfig, consensus_handle: subsystem::Handle>, ) -> crate::Result where ::Address: FromStr, <::Address as FromStr>::Err: Debug, { + let p2p_config = Arc::new(p2p_config); let (conn, pubsub, sync) = T::start( - bind_addr.parse::().map_err(|_| { - P2pError::ConversionError(ConversionError::InvalidAddress(bind_addr)) + p2p_config.bind_address.parse::().map_err(|_| { + P2pError::ConversionError(ConversionError::InvalidAddress( + p2p_config.bind_address.clone(), + )) })?, &[], - Arc::clone(&config), - TIMEOUT, + Arc::clone(&chain_config), + Duration::from_secs(p2p_config.outbound_connection_timeout), ) .await?; @@ -175,11 +178,12 @@ where let (_tx_sync, _rx_sync) = mpsc::channel(CHANNEL_SIZE); let (tx_pubsub, rx_pubsub) = mpsc::channel(CHANNEL_SIZE); - let swarm_config = Arc::clone(&config); + let swarm_config = Arc::clone(&chain_config); tokio::spawn(async move { - if let Err(e) = swarm::PeerManager::::new(swarm_config, conn, rx_swarm, tx_p2p_sync) - .run() - .await + if let Err(e) = + swarm::PeerManager::::new(swarm_config, p2p_config, conn, rx_swarm, tx_p2p_sync) + .run() + .await { log::error!("PeerManager failed: {:?}", e); } @@ -187,7 +191,7 @@ where let sync_handle = consensus_handle.clone(); let tx_swarm_sync = tx_swarm.clone(); - let sync_config = Arc::clone(&config); + let sync_config = Arc::clone(&chain_config); tokio::spawn(async move { if let Err(e) = sync::SyncManager::::new( sync_config, @@ -207,7 +211,7 @@ where let tx_swarm_pubsub = tx_swarm.clone(); tokio::spawn(async move { if let Err(e) = pubsub::PubSubMessageHandler::::new( - config, + chain_config, pubsub, consensus_handle, tx_swarm_pubsub, @@ -231,8 +235,8 @@ pub type P2pHandle = subsystem::Handle>; pub async fn make_p2p( chain_config: Arc, + p2p_config: P2pConfig, consensus_handle: subsystem::Handle>, - bind_addr: String, ) -> crate::Result> where T: NetworkingService + 'static, @@ -245,6 +249,6 @@ where <::PeerId as FromStr>::Err: Debug, { Ok(P2pInterface { - p2p: P2P::new(bind_addr, chain_config, consensus_handle).await?, + p2p: P2P::new(chain_config, p2p_config, consensus_handle).await?, }) } diff --git a/p2p/src/net/libp2p/tests/mod.rs b/p2p/src/net/libp2p/tests/mod.rs index 2ea91f6813..c6ad56bf47 100644 --- a/p2p/src/net/libp2p/tests/mod.rs +++ b/p2p/src/net/libp2p/tests/mod.rs @@ -14,6 +14,7 @@ // limitations under the License. // // Author(s): A. Altonen + use crate::net::{ self, libp2p::sync::*, diff --git a/p2p/src/net/mod.rs b/p2p/src/net/mod.rs index 4deb825c13..c4be65ab66 100644 --- a/p2p/src/net/mod.rs +++ b/p2p/src/net/mod.rs @@ -14,6 +14,7 @@ // limitations under the License. // // Author(s): A. Altonen + use crate::{error, message}; use async_trait::async_trait; use common::primitives; @@ -72,8 +73,6 @@ pub trait NetworkingService { /// /// `strategies` - list of strategies that are used for peer discovery /// - /// `topics` - list of pubsub topics that the implementation should subscribe to - /// /// `chain_config` - chain config of the node /// /// `timeout` - timeout for outbound connections diff --git a/p2p/src/swarm/mod.rs b/p2p/src/swarm/mod.rs index b7295624df..5b0cedac16 100644 --- a/p2p/src/swarm/mod.rs +++ b/p2p/src/swarm/mod.rs @@ -26,6 +26,7 @@ use crate::{ error::{P2pError, PeerError, ProtocolError}, event, net::{self, ConnectivityService, NetworkingService}, + P2pConfig, }; use chainstate::ban_score::BanScore; use common::{chain::ChainConfig, primitives::semver}; @@ -47,8 +48,11 @@ pub struct PeerManager where T: NetworkingService, { - /// Chain config - config: Arc, + /// Chain configuration. + chain_config: Arc, + + /// P2p configuration. + p2p_config: Arc, /// Handle for sending/receiving connectivity events handle: T::ConnectivityHandle, @@ -77,13 +81,15 @@ where <::Address as FromStr>::Err: Debug, { pub fn new( - config: Arc, + chain_config: Arc, + p2p_config: Arc, handle: T::ConnectivityHandle, rx_swarm: mpsc::Receiver>, tx_sync: mpsc::Sender>, ) -> Self { Self { - config, + chain_config, + p2p_config, handle, rx_swarm, tx_sync, @@ -163,7 +169,7 @@ where /// /// Make sure that local and remote peer have the same software version fn validate_version(&self, version: &semver::SemVer) -> bool { - version == self.config.version() + version == self.chain_config.version() } /// Handle connection established event @@ -175,16 +181,16 @@ where log::debug!("{}", info); ensure!( - info.magic_bytes == *self.config.magic_bytes(), + info.magic_bytes == *self.chain_config.magic_bytes(), P2pError::ProtocolError(ProtocolError::DifferentNetwork( - *self.config.magic_bytes(), + *self.chain_config.magic_bytes(), info.magic_bytes, )) ); ensure!( self.validate_version(&info.version), P2pError::ProtocolError(ProtocolError::InvalidVersion( - *self.config.version(), + *self.chain_config.version(), info.version )) ); @@ -279,8 +285,7 @@ where score }; - // TODO: from config - if score >= 100 { + if score >= self.p2p_config.ban_threshold { self.peerdb.ban_peer(&peer_id); return self.handle.ban_peer(peer_id).await; } diff --git a/p2p/src/swarm/tests/mod.rs b/p2p/src/swarm/tests/mod.rs index 2ee6700025..363b49d73d 100644 --- a/p2p/src/swarm/tests/mod.rs +++ b/p2p/src/swarm/tests/mod.rs @@ -20,6 +20,7 @@ mod tmp; use crate::{ net::{ConnectivityService, NetworkingService}, swarm::PeerManager, + P2pConfig, }; use std::{fmt::Debug, str::FromStr, sync::Arc}; @@ -50,5 +51,6 @@ where } }); - PeerManager::::new(Arc::clone(&config), conn, rx, tx_sync) + let p2p_config = Arc::new(P2pConfig::new()); + PeerManager::::new(Arc::clone(&config), p2p_config, conn, rx, tx_sync) } diff --git a/p2p/src/sync/tests/mod.rs b/p2p/src/sync/tests/mod.rs index 4f38446bc3..7c02f82f7a 100644 --- a/p2p/src/sync/tests/mod.rs +++ b/p2p/src/sync/tests/mod.rs @@ -14,12 +14,13 @@ // limitations under the License. // // Author(s): A. Altonen + use super::*; use crate::{ event::{PubSubControlEvent, SwarmEvent, SyncControlEvent}, net::{libp2p::Libp2pService, types::ConnectivityEvent, ConnectivityService}, }; -use chainstate::make_chainstate; +use chainstate::{make_chainstate, ChainstateConfig}; use libp2p::PeerId; #[cfg(test)] @@ -49,11 +50,19 @@ where let (tx_pubsub, rx_pubsub) = mpsc::channel(16); let (tx_swarm, rx_swarm) = mpsc::channel(16); let storage = chainstate_storage::Store::new_empty().unwrap(); - let cfg = Arc::new(common::chain::config::create_unit_test_config()); + let chain_config = Arc::new(common::chain::config::create_unit_test_config()); + let chainstate_config = ChainstateConfig::new(); let mut man = subsystem::Manager::new("TODO"); let handle = man.add_subsystem( "consensus", - make_chainstate(cfg, storage, None, Default::default()).unwrap(), + make_chainstate( + chain_config, + chainstate_config, + storage, + None, + Default::default(), + ) + .unwrap(), ); tokio::spawn(async move { man.main().await }); diff --git a/p2p/test-utils/src/lib.rs b/p2p/test-utils/src/lib.rs index 0200650068..abb88370de 100644 --- a/p2p/test-utils/src/lib.rs +++ b/p2p/test-utils/src/lib.rs @@ -17,7 +17,9 @@ #![allow(clippy::unwrap_used)] -use chainstate::{chainstate_interface::ChainstateInterface, make_chainstate, BlockSource}; +use chainstate::{ + chainstate_interface::ChainstateInterface, make_chainstate, BlockSource, ChainstateConfig, +}; use common::{ chain::{ block::{timestamp::BlockTimestamp, Block, ConsensusData}, @@ -133,13 +135,20 @@ fn anyonecanspend_address() -> Destination { } pub async fn start_chainstate( - config: Arc, + chain_config: Arc, ) -> subsystem::Handle> { let storage = chainstate_storage::Store::new_empty().unwrap(); let mut man = subsystem::Manager::new("TODO"); let handle = man.add_subsystem( "chainstate", - make_chainstate(config, storage, None, Default::default()).unwrap(), + make_chainstate( + chain_config, + ChainstateConfig::new(), + storage, + None, + Default::default(), + ) + .unwrap(), ); tokio::spawn(async move { man.main().await }); handle diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index abaaed2ae7..71884cd543 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -15,6 +15,7 @@ logging = { path = "../logging" } anyhow = "1.0" async-trait = "0.1" jsonrpsee = { version = "0.14", features = ["full"] } +serde = { version = "1", features = ["derive"] } [dev-dependencies] async-trait = "0.1" diff --git a/rpc/examples/simple_server.rs b/rpc/examples/simple_server.rs index c6f64f7a29..7e8aafd364 100644 --- a/rpc/examples/simple_server.rs +++ b/rpc/examples/simple_server.rs @@ -78,9 +78,10 @@ async fn main() -> anyhow::Result<()> { let mut app = subsystem::Manager::new("rpc-example"); app.install_signal_handlers(); let some_subsystem = app.add_subsystem("some_subsys", SomeSubsystem(0)); + let rpc_config = rpc::RpcConfig::new()?; let _rpc_subsystem = app.add_subsystem( "rpc", - rpc::Builder::new("127.0.0.1:3030".parse().expect("address parse error")) + rpc::Builder::new(rpc_config) .register(some_subsystem.clone().into_rpc()) .build() .await?, diff --git a/rpc/src/config.rs b/rpc/src/config.rs new file mode 100644 index 0000000000..25712bada2 --- /dev/null +++ b/rpc/src/config.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://spdx.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{net::SocketAddr, str::FromStr}; + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +/// The rpc subsystem configuration. +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcConfig { + /// Address to bind RPC to. + pub bind_address: SocketAddr, +} + +impl RpcConfig { + /// Creates a new rpc configuration instance. + pub fn new() -> Result { + Ok(Self { + bind_address: SocketAddr::from_str("127.0.0.1:3030")?, + }) + } +} diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 5679fc3060..b05faad420 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -15,14 +15,16 @@ use std::net::SocketAddr; -use jsonrpsee::http_server::HttpServerBuilder; -use jsonrpsee::http_server::HttpServerHandle; +use jsonrpsee::http_server::{HttpServerBuilder, HttpServerHandle}; +use logging::log; + +pub use config::RpcConfig; pub use jsonrpsee::core::server::rpc_module::Methods; pub use jsonrpsee::core::Error; pub use jsonrpsee::proc_macros::rpc; -use logging::log; +mod config; /// The Result type with RPC-specific error. pub type Result = core::result::Result; @@ -54,8 +56,8 @@ impl Builder { } /// New builder pre-populated with RPC info methods - pub fn new(address: SocketAddr) -> Self { - Self::new_empty(address).register(RpcInfo.into_rpc()) + pub fn new(rpc_config: RpcConfig) -> Self { + Self::new_empty(rpc_config.bind_address).register(RpcInfo.into_rpc()) } /// Add methods handlers to the RPC server @@ -129,10 +131,10 @@ mod tests { #[tokio::test] async fn rpc_server() -> anyhow::Result<()> { - let rpc = Builder::new("127.0.0.1:3030".parse().unwrap()) - .register(SubsystemRpcImpl.into_rpc()) - .build() - .await?; + let rpc_config = RpcConfig { + bind_address: "127.0.0.1:3030".parse().unwrap(), + }; + let rpc = Builder::new(rpc_config).register(SubsystemRpcImpl.into_rpc()).build().await?; let url = format!("http://{}", rpc.address()); let client = HttpClientBuilder::default().build(url)?; diff --git a/test/Cargo.toml b/test/Cargo.toml index 4d97a914df..96468d744a 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT" [dependencies] node = { path = "../node" } +common = { path = "../common" } tokio = { version = "1.19", features = ['full'] } diff --git a/test/functional/test_framework/test_config.toml b/test/functional/test_framework/test_config.toml new file mode 100644 index 0000000000..d257248586 --- /dev/null +++ b/test/functional/test_framework/test_config.toml @@ -0,0 +1,11 @@ +[chainstate] +max_db_commit_attempts = 10 +max_orphan_blocks = 512 + +[p2p] +bind_address = "/ip6/::1/tcp/3031" +ban_threshold = 100 +outbound_connection_timeout = 10 + +[rpc] +bind_address = "127.0.0.1:3030" diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 324455431e..dd9eb07ad7 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -99,11 +99,15 @@ def __init__(self, i, datadir, *, chain, rpchost, timewait, timeout_factor, bitc rpc_addr = self.init_rpc_url.split("http://")[-1].split('@')[-1] p2p_addr = p2p_url(self.index) + config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_config.toml") + # Configuration for logging is set as command-line args rather than in the bitcoin.conf file. # This means that starting a bitcoind using the temp dir to debug a failed test won't # spam debug.log. self.args = [ self.binary, + "run", + "--config-path={}".format(config_path), "--net=regtest", "--rpc-addr={}".format(rpc_addr), "--p2p-addr={}".format(p2p_addr),