diff --git a/ethcore/blockchain/src/blockchain.rs b/ethcore/blockchain/src/blockchain.rs index c5759d3462e..547ce201309 100644 --- a/ethcore/blockchain/src/blockchain.rs +++ b/ethcore/blockchain/src/blockchain.rs @@ -592,7 +592,7 @@ impl BlockChain { let best_block_rlp = bc.block(&best_block_hash) .expect("Best block is from a known block hash; qed"); - // and write them + // and write them to the cache. let mut best_block = bc.best_block.write(); *best_block = BestBlock { total_difficulty: best_block_total_difficulty, @@ -858,12 +858,31 @@ impl BlockChain { } } - /// clears all caches for testing purposes + /// clears all caches, re-loads best block from disk for testing purposes pub fn clear_cache(&self) { self.block_bodies.write().clear(); self.block_details.write().clear(); self.block_hashes.write().clear(); self.block_headers.write().clear(); + // Fetch best block details from disk + let best_block_hash = self.db.key_value().get(db::COL_EXTRA, b"best") + .expect("Low-level database error when fetching 'best' block. Some issue with disk?") + .as_ref() + .map(|r| H256::from_slice(r)) + .unwrap(); + let best_block_total_difficulty = self.block_details(&best_block_hash) + .expect("Best block is from a known block hash; a known block hash always comes with a known block detail; qed") + .total_difficulty; + let best_block_rlp = self.block(&best_block_hash) + .expect("Best block is from a known block hash; qed"); + + // and write them to the cache + let mut best_block = self.best_block.write(); + *best_block = BestBlock { + total_difficulty: best_block_total_difficulty, + header: best_block_rlp.decode_header(), + block: best_block_rlp, + }; } /// Update the best ancient block to the given hash, after checking that diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 12372b83ee1..7cc6a70627f 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -17,21 +17,23 @@ use std::cmp; use std::collections::{HashSet, BTreeMap, VecDeque}; use std::str::FromStr; +use std::str::from_utf8; use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::sync::{Arc, Weak}; -use std::time::{Instant, Duration}; +use std::io::{BufReader, BufRead}; +use std::time::{Duration, Instant}; use blockchain::{BlockReceipts, BlockChain, BlockChainDB, BlockProvider, TreeRoute, ImportRoute, TransactionAddress, ExtrasInsert, BlockNumberKey}; use bytes::Bytes; -use call_contract::{CallContract, RegistryInfo}; -use ethcore_miner::pool::VerifiedTransaction; -use ethereum_types::{H256, H264, Address, U256}; -use evm::Schedule; +use rlp::PayloadInfo; +use bytes::ToPretty; +use error::Error; +use ethereum_types::{Address, H256, H264, U256}; use hash::keccak; -use io::IoChannel; +use call_contract::CallContract; +use ethcore_miner::pool::VerifiedTransaction; use itertools::Itertools; -use journaldb; -use kvdb::{DBValue, KeyValueDB, DBTransaction}; +use kvdb::{DBTransaction, DBValue, KeyValueDB}; use parking_lot::{Mutex, RwLock}; use rand::OsRng; use types::transaction::{self, LocalizedTransaction, UnverifiedTransaction, SignedTransaction, Action}; @@ -43,6 +45,7 @@ use types::log_entry::LocalizedLogEntry; use types::receipt::{Receipt, LocalizedReceipt}; use types::{BlockNumber, header::{Header, ExtendedHeader}}; use vm::{EnvInfo, LastHashes}; +use types::data_format::DataFormat; use block::{LockedBlock, Drain, ClosedBlock, OpenBlock, enact_verified, SealedBlock}; use client::ancient_import::AncientVerifier; @@ -51,8 +54,9 @@ use client::{ ReopenBlock, PrepareOpenBlock, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, StateOrBlock, StateInfo, StateClient, Call, AccountData, BlockChain as BlockChainTrait, BlockProducer, SealedBlockImporter, - ClientIoMessage, BlockChainReset + ClientIoMessage, BlockChainReset, ImportExportBlocks }; +use rustc_hex::FromHex; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, TraceFilter, CallAnalytics, Mode, @@ -80,7 +84,9 @@ use verification::queue::kind::blocks::Unverified; use verification::{PreverifiedBlock, Verifier, BlockQueue}; use verification; use ansi_term::Colour; - +use call_contract::RegistryInfo; +use io::IoChannel; +use vm::Schedule; // re-export pub use types::blockchain_info::BlockChainInfo; pub use types::block_status::BlockStatus; @@ -2568,6 +2574,116 @@ impl Drop for Client { } } +impl ImportExportBlocks for Client { + fn export_blocks<'a>( + &self, + mut out: Box, + from: BlockId, + to: BlockId, + format: Option + ) -> Result<(), String> { + let from = self.block_number(from).ok_or("Starting block could not be found")?; + let to = self.block_number(to).ok_or("End block could not be found")?; + let format = format.unwrap_or_default(); + + for i in from..=to { + if i % 10000 == 0 { + info!("#{}", i); + } + let b = self.block(BlockId::Number(i)).ok_or("Error exporting incomplete chain")?.into_inner(); + match format { + DataFormat::Binary => { + out.write(&b).map_err(|e| format!("Couldn't write to stream. Cause: {}", e))?; + } + DataFormat::Hex => { + out.write_fmt(format_args!("{}\n", b.pretty())).map_err(|e| format!("Couldn't write to stream. Cause: {}", e))?; + } + } + } + Ok(()) + } + + fn import_blocks<'a>( + &self, + mut source: Box, + format: Option + ) -> Result<(), String> { + const READAHEAD_BYTES: usize = 8; + + let mut first_bytes: Vec = vec![0; READAHEAD_BYTES]; + let mut first_read = 0; + + let format = match format { + Some(format) => format, + None => { + first_read = source.read(&mut first_bytes).map_err(|_| "Error reading from the file/stream.")?; + match first_bytes[0] { + 0xf9 => DataFormat::Binary, + _ => DataFormat::Hex, + } + } + }; + + let do_import = |bytes: Vec| { + let block = Unverified::from_rlp(bytes).map_err(|_| "Invalid block rlp")?; + let number = block.header.number(); + while self.queue_info().is_full() { std::thread::sleep(Duration::from_secs(1)); } + match self.import_block(block) { + Err(Error(EthcoreErrorKind::Import(ImportErrorKind::AlreadyInChain), _)) => { + trace!("Skipping block #{}: already in chain.", number); + } + Err(e) => { + return Err(format!("Cannot import block #{}: {:?}", number, e)); + }, + Ok(_) => {}, + } + Ok(()) + }; + + match format { + DataFormat::Binary => { + loop { + let (mut bytes, n) = if first_read > 0 { + (first_bytes.clone(), first_read) + } else { + let mut bytes = vec![0; READAHEAD_BYTES]; + let n = source.read(&mut bytes) + .map_err(|err| format!("Error reading from the file/stream: {:?}", err))?; + (bytes, n) + }; + if n == 0 { break; } + first_read = 0; + let s = PayloadInfo::from(&bytes) + .map_err(|e| format!("Invalid RLP in the file/stream: {:?}", e))?.total(); + bytes.resize(s, 0); + source.read_exact(&mut bytes[n..]) + .map_err(|err| format!("Error reading from the file/stream: {:?}", err))?; + do_import(bytes)?; + } + } + DataFormat::Hex => { + for line in BufReader::new(source).lines() { + let s = line + .map_err(|err| format!("Error reading from the file/stream: {:?}", err))?; + let s = if first_read > 0 { + from_utf8(&first_bytes) + .map_err(|err| format!("Invalid UTF-8: {:?}", err))? + .to_owned() + &(s[..]) + } else { + s + }; + first_read = 0; + let bytes = s.from_hex() + .map_err(|err| format!("Invalid hex in file/stream: {:?}", err))?; + do_import(bytes)?; + } + } + }; + self.flush_queue(); + Ok(()) + } +} + /// Returns `LocalizedReceipt` given `LocalizedTransaction` /// and a vector of receipts from given block up to transaction index. fn transaction_receipt( diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index d710a6b4ddb..8c2ded3fe75 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -38,7 +38,7 @@ pub use self::chain_notify::{ChainNotify, NewBlocks, ChainRoute, ChainRouteType, pub use self::traits::{ Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, PrepareOpenBlock, TransactionInfo, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter, BadBlocks, - BlockChainReset + BlockChainReset, ImportExportBlocks }; pub use state::StateInfo; pub use self::traits::{BlockChainClient, EngineClient, ProvingBlockChainClient, IoClient}; diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 8e4abc01cde..29d3002424f 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -38,6 +38,7 @@ use types::ids::*; use types::log_entry::LocalizedLogEntry; use types::pruning_info::PruningInfo; use types::receipt::LocalizedReceipt; +use types::data_format::DataFormat; use types::trace_filter::Filter as TraceFilter; use vm::LastHashes; @@ -477,3 +478,30 @@ pub trait BlockChainReset { /// reset to best_block - n fn reset(&self, num: u32) -> Result<(), String>; } + +/// Provides a method for importing/exporting blocks +pub trait ImportExportBlocks { + /// Export blocks to destination, with the given from, to and format argument. + /// destination could be a file or stdout. + /// If the format is hex, each block is written on a new line. + /// For binary exports, all block data is written to the same line. + fn export_blocks<'a>( + &self, + destination: Box, + from: BlockId, + to: BlockId, + format: Option + ) -> Result<(), String>; + + /// Import blocks from destination, with the given format argument + /// Source could be a file or stdout. + /// For hex format imports, it attempts to read the blocks on a line by line basis. + /// For binary format imports, reads the 8 byte RLP header in order to decode the block + /// length to be read. + fn import_blocks<'a>( + &self, + source: Box, + format: Option + ) -> Result<(), String>; +} + diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 9086d07e08a..6c75cf06758 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -use std::str::FromStr; +use std::str::{FromStr, from_utf8}; use std::sync::Arc; use ethereum_types::{U256, Address}; @@ -22,14 +22,23 @@ use ethkey::KeyPair; use hash::keccak; use io::IoChannel; use tempdir::TempDir; -use types::transaction::{PendingTransaction, Transaction, Action, Condition}; -use types::filter::Filter; -use types::view; -use types::views::BlockView; - -use client::{BlockChainClient, BlockChainReset, Client, ClientConfig, BlockId, ChainInfo, BlockInfo, PrepareOpenBlock, ImportSealedBlock, ImportBlock}; -use ethereum; -use executive::{Executive, TransactOptions}; +use types::{ + data_format::DataFormat, + ids::BlockId, + transaction::{PendingTransaction, Transaction, Action, Condition}, + filter::Filter, + verification::Unverified, + view, + views::BlockView, +}; + +use client::{Client, ClientConfig, PrepareOpenBlock, ImportSealedBlock}; +use client_traits::{ + BlockInfo, BlockChainClient, BlockChainReset, ChainInfo, + ImportExportBlocks, Tick, ImportBlock +}; +use spec; +use machine::executive::{Executive, TransactOptions}; use miner::{Miner, PendingOrdering, MinerService}; use spec::Spec; use state::{self, State, CleanupMode}; @@ -38,7 +47,7 @@ use test_helpers::{ generate_dummy_client, push_blocks_to_client, get_test_client_with_blocks, get_good_dummy_block_seq, generate_dummy_client_with_data, get_good_dummy_block, get_bad_state_dummy_block }; -use verification::queue::kind::blocks::Unverified; +use rustc_hex::ToHex; #[test] fn imports_from_empty() { @@ -386,3 +395,79 @@ fn reset_blockchain() { assert!(client.block_header(BlockId::Number(15)).is_some()); } + +#[test] +fn import_export_hex() { + let client = get_test_client_with_blocks(get_good_dummy_block_seq(19)); + let block_rlps = (15..20) + .filter_map(|num| client.block(BlockId::Number(num))) + .map(|header| { + header.raw().to_hex() + }) + .collect::>(); + + let mut out = Vec::new(); + + client.export_blocks( + Box::new(&mut out), + BlockId::Number(15), + BlockId::Number(20), + Some(DataFormat::Hex) + ).unwrap(); + + let written = from_utf8(&out) + .unwrap() + .split("\n") + // last line is empty, ignore it. + .take(5) + .collect::>(); + assert_eq!(block_rlps, written); + + assert!(client.reset(5).is_ok()); + client.chain().clear_cache(); + + assert!(client.block_header(BlockId::Number(20)).is_none()); + assert!(client.block_header(BlockId::Number(19)).is_none()); + assert!(client.block_header(BlockId::Number(18)).is_none()); + assert!(client.block_header(BlockId::Number(17)).is_none()); + assert!(client.block_header(BlockId::Number(16)).is_none()); + + client.import_blocks(Box::new(&*out), Some(DataFormat::Hex)).unwrap(); + + assert!(client.block_header(BlockId::Number(20)).is_some()); + assert!(client.block_header(BlockId::Number(19)).is_some()); + assert!(client.block_header(BlockId::Number(18)).is_some()); + assert!(client.block_header(BlockId::Number(17)).is_some()); + assert!(client.block_header(BlockId::Number(16)).is_some()); +} + +#[test] +fn import_export_binary() { + let client = get_test_client_with_blocks(get_good_dummy_block_seq(19)); + + let mut out = Vec::new(); + + client.export_blocks( + Box::new(&mut out), + BlockId::Number(15), + BlockId::Number(20), + Some(DataFormat::Binary) + ).unwrap(); + + assert!(client.reset(5).is_ok()); + client.chain().clear_cache(); + + assert!(client.block_header(BlockId::Number(20)).is_none()); + assert!(client.block_header(BlockId::Number(19)).is_none()); + assert!(client.block_header(BlockId::Number(18)).is_none()); + assert!(client.block_header(BlockId::Number(17)).is_none()); + assert!(client.block_header(BlockId::Number(16)).is_none()); + + client.import_blocks(Box::new(&*out), Some(DataFormat::Binary)).unwrap(); + + assert!(client.block_header(BlockId::Number(19)).is_some()); + assert!(client.block_header(BlockId::Number(18)).is_some()); + assert!(client.block_header(BlockId::Number(20)).is_some()); + assert!(client.block_header(BlockId::Number(17)).is_some()); + assert!(client.block_header(BlockId::Number(16)).is_some()); +} diff --git a/ethcore/types/src/data_format.rs b/ethcore/types/src/data_format.rs new file mode 100644 index 00000000000..8cfe5cf2be3 --- /dev/null +++ b/ethcore/types/src/data_format.rs @@ -0,0 +1,43 @@ +// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// This file is part of Parity Ethereum. + +// Parity Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Ethereum. If not, see . + +//! Data format for importing/exporting blocks from disk +use std::str::FromStr; + +/// Format for importing/exporting blocks +#[derive(Debug, PartialEq)] +pub enum DataFormat { + Hex, + Binary, +} + +impl Default for DataFormat { + fn default() -> Self { + DataFormat::Binary + } +} + +impl FromStr for DataFormat { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "binary" | "bin" => Ok(DataFormat::Binary), + "hex" => Ok(DataFormat::Hex), + x => Err(format!("Invalid format: {}", x)) + } + } +} diff --git a/ethcore/types/src/lib.rs b/ethcore/types/src/lib.rs index 3223db72206..d187704fdbc 100644 --- a/ethcore/types/src/lib.rs +++ b/ethcore/types/src/lib.rs @@ -74,6 +74,7 @@ pub mod trace_filter; pub mod transaction; pub mod tree_route; pub mod verification_queue_info; +pub mod data_format; /// Type for block number. pub type BlockNumber = u64; diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 262b98ae2c5..0b2e0ec8af2 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . -use std::str::{FromStr, from_utf8}; +use std::str::from_utf8; use std::{io, fs}; use std::io::{BufReader, BufRead}; use std::time::{Instant, Duration}; @@ -28,6 +28,8 @@ use rlp::PayloadInfo; use ethcore::client::{ Mode, DatabaseCompactionProfile, VMType, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, ImportBlock, BlockChainReset }; +use ethcore::client::ImportExportBlocks; +use types::data_format::DataFormat; use ethcore::error::{ImportErrorKind, ErrorKind as EthcoreErrorKind, Error as EthcoreError}; use ethcore::miner::Miner; use ethcore::verification::queue::VerifierSettings; @@ -43,30 +45,6 @@ use ethcore_private_tx; use db; use ansi_term::Colour; -#[derive(Debug, PartialEq)] -pub enum DataFormat { - Hex, - Binary, -} - -impl Default for DataFormat { - fn default() -> Self { - DataFormat::Binary - } -} - -impl FromStr for DataFormat { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "binary" | "bin" => Ok(DataFormat::Binary), - "hex" => Ok(DataFormat::Hex), - x => Err(format!("Invalid format: {}", x)) - } - } -} - #[derive(Debug, PartialEq)] pub enum BlockchainCmd { Kill(KillBlockchain), @@ -406,27 +384,11 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { let client = service.client(); - let mut instream: Box = match cmd.file_path { + let instream: Box = match cmd.file_path { Some(f) => Box::new(fs::File::open(&f).map_err(|_| format!("Cannot open given file: {}", f))?), None => Box::new(io::stdin()), }; - const READAHEAD_BYTES: usize = 8; - - let mut first_bytes: Vec = vec![0; READAHEAD_BYTES]; - let mut first_read = 0; - - let format = match cmd.format { - Some(format) => format, - None => { - first_read = instream.read(&mut first_bytes).map_err(|_| "Error reading from the file/stream.")?; - match first_bytes[0] { - 0xf9 => DataFormat::Binary, - _ => DataFormat::Hex, - } - } - }; - let informant = Arc::new(Informant::new( FullNodeInformantData { client: client.clone(), @@ -440,49 +402,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { service.register_io_handler(informant).map_err(|_| "Unable to register informant handler".to_owned())?; - let do_import = |bytes| { - let block = Unverified::from_rlp(bytes).map_err(|_| "Invalid block rlp")?; - while client.queue_info().is_full() { sleep(Duration::from_secs(1)); } - match client.import_block(block) { - Err(EthcoreError(EthcoreErrorKind::Import(ImportErrorKind::AlreadyInChain), _)) => { - trace!("Skipping block already in chain."); - } - Err(e) => { - return Err(format!("Cannot import block: {:?}", e)); - }, - Ok(_) => {}, - } - Ok(()) - }; - - match format { - DataFormat::Binary => { - loop { - let mut bytes = if first_read > 0 {first_bytes.clone()} else {vec![0; READAHEAD_BYTES]}; - let n = if first_read > 0 { - first_read - } else { - instream.read(&mut bytes).map_err(|_| "Error reading from the file/stream.")? - }; - if n == 0 { break; } - first_read = 0; - let s = PayloadInfo::from(&bytes).map_err(|e| format!("Invalid RLP in the file/stream: {:?}", e))?.total(); - bytes.resize(s, 0); - instream.read_exact(&mut bytes[n..]).map_err(|_| "Error reading from the file/stream.")?; - do_import(bytes)?; - } - } - DataFormat::Hex => { - for line in BufReader::new(instream).lines() { - let s = line.map_err(|_| "Error reading from the file/stream.")?; - let s = if first_read > 0 {from_utf8(&first_bytes).unwrap().to_owned() + &(s[..])} else {s}; - first_read = 0; - let bytes = s.from_hex().map_err(|_| "Invalid hex in file/stream.")?; - do_import(bytes)?; - } - } - } - client.flush_queue(); + client.import_blocks(instream, cmd.format)?; // save user defaults user_defaults.pruning = algorithm; @@ -611,32 +531,14 @@ fn execute_export(cmd: ExportBlockchain) -> Result<(), String> { false, cmd.max_round_blocks_to_import, )?; - let format = cmd.format.unwrap_or_default(); - let client = service.client(); - let mut out: Box = match cmd.file_path { + let out: Box = match cmd.file_path { Some(f) => Box::new(fs::File::create(&f).map_err(|_| format!("Cannot write to file given: {}", f))?), None => Box::new(io::stdout()), }; - let from = client.block_number(cmd.from_block).ok_or("From block could not be found")?; - let to = client.block_number(cmd.to_block).ok_or("To block could not be found")?; - - for i in from..(to + 1) { - if i % 10000 == 0 { - info!("#{}", i); - } - let b = client.block(BlockId::Number(i)).ok_or("Error exporting incomplete chain")?.into_inner(); - match format { - DataFormat::Binary => { - out.write(&b).map_err(|e| format!("Couldn't write to stream. Cause: {}", e))?; - } - DataFormat::Hex => { - out.write_fmt(format_args!("{}", b.pretty())).map_err(|e| format!("Couldn't write to stream. Cause: {}", e))?; - } - } - } + client.export_blocks(out, cmd.from_block, cmd.to_block, cmd.format)?; info!("Export completed."); Ok(()) diff --git a/parity/configuration.rs b/parity/configuration.rs index c2f18150b20..46f75072e15 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -50,7 +50,8 @@ use ethcore_private_tx::{ProviderConfig, EncryptorConfig}; use secretstore::{NodeSecretKey, Configuration as SecretStoreConfiguration, ContractAddress as SecretStoreContractAddress}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; -use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat, ResetBlockchain}; +use types::data_format::DataFormat; +use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, ResetBlockchain}; use export_hardcoded_sync::ExportHsyncCmd; use presale::ImportWallet; use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts}; @@ -1200,9 +1201,10 @@ mod tests { use miner::pool::PrioritizationStrategy; use parity_rpc::NetworkSettings; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; - + use types::ids::BlockId; + use types::data_format::DataFormat; use account::{AccountCmd, NewAccount, ImportAccounts, ListAccounts}; - use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, DataFormat, ExportState}; + use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, ExportState}; use cli::Args; use dir::{Directories, default_hypervisor_path}; use helpers::{default_network_config};