openethereum/parity/main.rs

516 lines
18 KiB
Rust
Raw Normal View History

2016-02-05 13:40:41 +01:00
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity 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 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. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore client application.
#![warn(missing_docs)]
2016-02-19 00:50:23 +01:00
#![cfg_attr(feature="dev", feature(plugin))]
2016-02-12 22:01:23 +01:00
#![cfg_attr(feature="dev", plugin(clippy))]
2016-01-23 23:53:20 +01:00
extern crate docopt;
extern crate rustc_serialize;
2015-12-22 22:19:50 +01:00
extern crate ethcore_util as util;
2016-01-07 16:08:12 +01:00
extern crate ethcore;
2016-01-29 15:56:06 +01:00
extern crate ethsync;
2016-02-18 12:42:01 +01:00
#[macro_use]
2016-01-31 11:08:04 +01:00
extern crate log as rlog;
2016-01-09 23:21:57 +01:00
extern crate env_logger;
extern crate ctrlc;
2016-02-05 13:49:36 +01:00
extern crate fdlimit;
2016-02-18 13:10:04 +01:00
extern crate daemonize;
2016-02-23 20:14:37 +01:00
extern crate time;
2016-02-25 14:09:39 +01:00
extern crate number_prefix;
2015-12-22 22:19:50 +01:00
#[cfg(feature = "rpc")]
2016-01-26 13:14:22 +01:00
extern crate ethcore_rpc as rpc;
2016-02-08 15:04:12 +01:00
use std::net::{SocketAddr};
use std::env;
2016-02-19 12:54:51 +01:00
use std::process::exit;
2016-02-15 18:36:34 +01:00
use std::path::PathBuf;
use env_logger::LogBuilder;
use ctrlc::CtrlC;
2016-01-16 13:30:27 +01:00
use util::*;
use util::panics::{MayPanic, ForwardPanic, PanicHandler};
use ethcore::spec::*;
2016-01-16 13:30:27 +01:00
use ethcore::client::*;
2016-01-21 23:33:52 +01:00
use ethcore::service::{ClientService, NetSyncMessage};
use ethcore::ethereum;
2016-02-24 21:23:58 +01:00
use ethsync::{EthSync, SyncConfig};
use docopt::Docopt;
use daemonize::Daemonize;
2016-02-25 14:09:39 +01:00
use number_prefix::{binary_prefix, Standalone, Prefixed};
2016-01-23 23:53:20 +01:00
fn die_with_message(msg: &str) -> ! {
println!("ERROR: {}", msg);
exit(1);
}
#[macro_export]
macro_rules! die {
($($arg:tt)*) => (die_with_message(&format!("{}", format_args!($($arg)*))));
}
const USAGE: &'static str = r#"
2016-01-23 23:53:20 +01:00
Parity. Ethereum Client.
By Wood/Paronyan/Kotewicz/Drwięga/Volf.
Copyright 2015, 2016 Ethcore (UK) Limited
2016-01-23 23:53:20 +01:00
Usage:
2016-02-18 14:28:24 +01:00
parity daemon <pid-file> [options] [ --no-bootstrap | <enode>... ]
2016-02-11 10:46:55 +01:00
parity [options] [ --no-bootstrap | <enode>... ]
2016-01-23 23:53:20 +01:00
Protocol Options:
--chain CHAIN Specify the blockchain type. CHAIN may be either a JSON chain specification file
or olympic, frontier, homestead, mainnet, morden, or testnet [default: homestead].
--testnet Equivalent to --chain testnet (geth-compatible).
--networkid INDEX Override the network identifier from the chain we are on.
--archive Client should not prune the state/storage trie.
-d --datadir PATH Specify the database & configuration directory path [default: $HOME/.parity]
2016-02-18 12:42:01 +01:00
--keys-path PATH Specify the path for JSON key files to be found [default: $HOME/.web3/keys]
--identity NAME Specify your node's name.
Networking Options:
2016-02-11 10:46:55 +01:00
--no-bootstrap Don't bother trying to connect to any nodes initially.
2016-02-08 15:04:12 +01:00
--listen-address URL Specify the IP/port on which to listen for peers [default: 0.0.0.0:30304].
2016-02-16 02:05:36 +01:00
--public-address URL Specify the IP/port on which peers may connect.
2016-02-08 15:04:12 +01:00
--address URL Equivalent to --listen-address URL --public-address URL.
2016-02-26 22:40:32 +01:00
--peers NUM Try to maintain that many peers [default: 25].
--no-discovery Disable new peer discovery.
2016-02-23 19:38:06 +01:00
--no-upnp Disable trying to figure out the correct public adderss over UPnP.
2016-02-22 18:10:21 +01:00
--node-key KEY Specify node secret key, either as 64-character hex string or input to SHA3 operation.
2016-02-08 15:04:12 +01:00
API and Console Options:
-j --jsonrpc Enable the JSON-RPC API sever.
--jsonrpc-addr HOST Specify the hostname portion of the JSONRPC API server [default: 127.0.0.1].
--jsonrpc-port PORT Specify the port portion of the JSONRPC API server [default: 8545].
--jsonrpc-cors URL Specify CORS header for JSON-RPC API responses [default: null].
--jsonrpc-apis APIS Specify the APIs available through the JSONRPC interface. APIS is a comma-delimited
list of API name. Possible name are web3, eth and net. [default: web3,eth,net].
--rpc Equivalent to --jsonrpc (geth-compatible).
--rpcaddr HOST Equivalent to --jsonrpc-addr HOST (geth-compatible).
--rpcport PORT Equivalent to --jsonrpc-port PORT (geth-compatible).
--rpcapi APIS Equivalent to --jsonrpc-apis APIS (geth-compatible).
--rpccorsdomain URL Equivalent to --jsonrpc-cors URL (geth-compatible).
Sealing/Mining Options:
--author ADDRESS Specify the block author (aka "coinbase") address for sending block rewards
from sealed blocks [default: 0037a6b811ffeb6e072da21179d11b1406371c63].
--extradata STRING Specify a custom extra-data for authored blocks, no more than 32 characters.
Memory Footprint Options:
--cache-pref-size BYTES Specify the prefered size of the blockchain cache in bytes [default: 16384].
--cache-max-size BYTES Specify the maximum size of the blockchain cache in bytes [default: 262144].
--queue-max-size BYTES Specify the maximum size of memory to use for block queue [default: 52428800].
2016-03-08 16:42:30 +01:00
--cache MEGABYTES Set total amount of cache to use for the entire system, mutually exclusive with
other cache options (geth-compatible).
Miscellaneous Options:
-l --logging LOGGING Specify the logging level.
-v --version Show information about version.
-h --help Show this screen.
"#;
#[derive(Debug, RustcDecodable)]
struct Args {
cmd_daemon: bool,
arg_pid_file: String,
arg_enode: Vec<String>,
flag_chain: String,
flag_testnet: bool,
2016-03-08 16:42:30 +01:00
flag_datadir: String,
flag_networkid: Option<String>,
flag_identity: String,
flag_cache: Option<usize>,
flag_keys_path: String,
flag_archive: bool,
flag_no_bootstrap: bool,
flag_listen_address: String,
flag_public_address: Option<String>,
flag_address: Option<String>,
flag_peers: usize,
flag_no_discovery: bool,
2016-02-23 19:38:06 +01:00
flag_no_upnp: bool,
flag_node_key: Option<String>,
flag_cache_pref_size: usize,
flag_cache_max_size: usize,
2016-02-25 14:09:39 +01:00
flag_queue_max_size: usize,
flag_jsonrpc: bool,
flag_jsonrpc_addr: String,
flag_jsonrpc_port: u16,
flag_jsonrpc_cors: String,
flag_jsonrpc_apis: String,
flag_rpc: bool,
flag_rpcaddr: Option<String>,
flag_rpcport: Option<u16>,
flag_rpccorsdomain: Option<String>,
flag_rpcapi: Option<String>,
flag_logging: Option<String>,
flag_version: bool,
flag_author: String,
flag_extra_data: Option<String>,
}
2015-12-22 22:19:50 +01:00
fn setup_log(init: &Option<String>) {
2016-02-23 20:14:37 +01:00
use rlog::*;
let mut builder = LogBuilder::new();
2016-01-15 01:44:57 +01:00
builder.filter(None, LogLevelFilter::Info);
if env::var("RUST_LOG").is_ok() {
builder.parse(&env::var("RUST_LOG").unwrap());
}
if let Some(ref s) = *init {
builder.parse(s);
}
2016-01-23 23:53:20 +01:00
2016-02-23 20:14:37 +01:00
let format = |record: &LogRecord| {
let timestamp = time::strftime("%Y-%m-%d %H:%M:%S %Z", &time::now()).unwrap();
if max_log_level() <= LogLevelFilter::Info {
format!("{}{}", timestamp, record.args())
} else {
format!("{}{}:{}: {}", timestamp, record.level(), record.target(), record.args())
}
};
builder.format(format);
builder.init().unwrap();
}
#[cfg(feature = "rpc")]
fn setup_rpc_server(client: Arc<Client>, sync: Arc<EthSync>, url: &str, cors_domain: &str, apis: Vec<&str>) -> Option<Arc<PanicHandler>> {
use rpc::v1::*;
let server = rpc::RpcServer::new();
for api in apis.into_iter() {
match api {
"web3" => server.add_delegate(Web3Client::new().to_delegate()),
"net" => server.add_delegate(NetClient::new(&sync).to_delegate()),
"eth" => {
server.add_delegate(EthClient::new(&client, &sync).to_delegate());
server.add_delegate(EthFilterClient::new(&client).to_delegate());
}
_ => {
die!("{}: Invalid API name to be enabled.", api);
}
}
}
Some(server.start_http(url, cors_domain, 1))
}
#[cfg(not(feature = "rpc"))]
2016-03-09 11:38:53 +01:00
fn setup_rpc_server(_client: Arc<Client>, _sync: Arc<EthSync>, _url: &str) -> Option<Arc<PanicHandler>> {
None
}
2016-02-10 18:11:10 +01:00
fn print_version() {
println!("\
2016-02-21 20:00:45 +01:00
Parity
version {}
Copyright 2015, 2016 Ethcore (UK) Limited
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
2016-02-09 17:23:25 +01:00
By Wood/Paronyan/Kotewicz/Drwięga/Volf.\
2016-02-21 20:00:45 +01:00
", version());
2016-02-10 18:11:10 +01:00
}
struct Configuration {
args: Args
}
impl Configuration {
fn parse() -> Self {
Configuration {
args: Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()),
2016-02-10 18:11:10 +01:00
}
}
2016-02-10 21:17:47 +01:00
fn path(&self) -> String {
2016-03-08 16:42:30 +01:00
self.args.flag_datadir.replace("$HOME", env::home_dir().unwrap().to_str().unwrap())
2016-02-10 21:17:47 +01:00
}
fn author(&self) -> Address {
Address::from_str(&self.args.flag_author).unwrap_or_else(|_| die!("{}: Invalid address for --author. Must be 40 hex characters, without the 0x at the beginning.", self.args.flag_author))
}
fn extra_data(&self) -> Bytes {
match self.args.flag_extra_data {
Some(ref x) if x.len() <= 32 => x.as_bytes().to_owned(),
None => version_data(),
Some(ref x) => { die!("{}: Extra data must be at most 32 characters.", x); }
}
}
fn _keys_path(&self) -> String {
self.args.flag_keys_path.replace("$HOME", env::home_dir().unwrap().to_str().unwrap())
2016-02-18 12:42:01 +01:00
}
2016-02-10 21:17:47 +01:00
fn spec(&self) -> Spec {
if self.args.flag_testnet {
return ethereum::new_morden();
}
2016-02-10 14:16:42 +01:00
match self.args.flag_chain.as_ref() {
"frontier" | "homestead" | "mainnet" => ethereum::new_frontier(),
"morden" | "testnet" => ethereum::new_morden(),
"olympic" => ethereum::new_olympic(),
2016-02-19 12:54:51 +01:00
f => Spec::from_json_utf8(contents(f).unwrap_or_else(|_| die!("{}: Couldn't read chain specification file. Sure it exists?", f)).as_ref()),
}
}
fn normalize_enode(e: &str) -> Option<String> {
if is_valid_node_url(e) {
Some(e.to_owned())
} else {
None
2016-02-19 20:02:23 +01:00
}
}
2016-02-10 21:17:47 +01:00
fn init_nodes(&self, spec: &Spec) -> Vec<String> {
2016-02-11 10:46:55 +01:00
if self.args.flag_no_bootstrap { Vec::new() } else {
match self.args.arg_enode.len() {
0 => spec.nodes().clone(),
2016-02-19 12:54:51 +01:00
_ => self.args.arg_enode.iter().map(|s| Self::normalize_enode(s).unwrap_or_else(||die!("{}: Invalid node address format given for a boot node.", s))).collect(),
2016-02-11 10:46:55 +01:00
}
}
}
#[cfg_attr(feature="dev", allow(useless_format))]
2016-02-16 02:05:36 +01:00
fn net_addresses(&self) -> (Option<SocketAddr>, Option<SocketAddr>) {
let mut listen_address = None;
let mut public_address = None;
2016-02-16 02:05:36 +01:00
if let Some(ref a) = self.args.flag_address {
2016-02-22 13:58:41 +01:00
public_address = Some(SocketAddr::from_str(a.as_ref()).unwrap_or_else(|_| die!("{}: Invalid listen/public address given with --address", a)));
2016-02-16 02:05:36 +01:00
listen_address = public_address;
}
2016-02-16 19:51:51 +01:00
if listen_address.is_none() {
2016-02-22 13:58:41 +01:00
listen_address = Some(SocketAddr::from_str(self.args.flag_listen_address.as_ref()).unwrap_or_else(|_| die!("{}: Invalid listen/public address given with --listen-address", self.args.flag_listen_address)));
2016-02-16 02:05:36 +01:00
}
if let Some(ref a) = self.args.flag_public_address {
if public_address.is_some() {
2016-02-22 13:58:41 +01:00
die!("Conflicting flags provided: --address and --public-address");
}
2016-02-22 13:58:41 +01:00
public_address = Some(SocketAddr::from_str(a.as_ref()).unwrap_or_else(|_| die!("{}: Invalid listen/public address given with --public-address", a)));
2016-02-16 02:05:36 +01:00
}
(listen_address, public_address)
}
2016-02-18 12:42:01 +01:00
2016-02-19 12:54:51 +01:00
fn net_settings(&self, spec: &Spec) -> NetworkConfiguration {
let mut ret = NetworkConfiguration::new();
2016-02-23 19:38:06 +01:00
ret.nat_enabled = !self.args.flag_no_upnp;
2016-02-19 12:54:51 +01:00
ret.boot_nodes = self.init_nodes(spec);
let (listen, public) = self.net_addresses();
ret.listen_address = listen;
ret.public_address = public;
2016-02-22 13:58:41 +01:00
ret.use_secret = self.args.flag_node_key.as_ref().map(|s| Secret::from_str(&s).unwrap_or_else(|_| s.sha3()));
ret.discovery_enabled = !self.args.flag_no_discovery;
ret.ideal_peers = self.args.flag_peers as u32;
let mut net_path = PathBuf::from(&self.path());
net_path.push("network");
ret.config_path = Some(net_path.to_str().unwrap().to_owned());
2016-02-19 20:02:23 +01:00
ret
2016-02-19 12:54:51 +01:00
}
2016-02-18 12:42:01 +01:00
fn execute(&self) {
if self.args.flag_version {
print_version();
return;
}
2016-02-18 13:10:04 +01:00
if self.args.cmd_daemon {
2016-02-19 12:54:51 +01:00
Daemonize::new()
.pid_file(self.args.arg_pid_file.clone())
.chown_pid_file(true)
.start()
.unwrap_or_else(|e| die!("Couldn't daemonize; {}", e));
2016-02-18 13:10:04 +01:00
}
2016-02-18 12:42:01 +01:00
self.execute_client();
}
fn execute_client(&self) {
// Setup panic handler
let panic_handler = PanicHandler::new_in_arc();
2016-02-18 12:42:01 +01:00
// Setup logging
setup_log(&self.args.flag_logging);
// Raise fdlimit
unsafe { ::fdlimit::raise_fd_limit(); }
let spec = self.spec();
2016-02-19 12:54:51 +01:00
let net_settings = self.net_settings(&spec);
2016-02-24 21:23:58 +01:00
let mut sync_config = SyncConfig::default();
sync_config.network_id = self.args.flag_networkid.as_ref().map(|id| U256::from_str(id).unwrap_or_else(|_| die!("{}: Invalid index given with --networkid", id))).unwrap_or(spec.network_id());
2016-02-18 12:42:01 +01:00
// Build client
2016-02-25 14:09:39 +01:00
let mut client_config = ClientConfig::default();
match self.args.flag_cache {
Some(mb) => {
client_config.blockchain.max_cache_size = mb * 1024 * 1024;
client_config.blockchain.pref_cache_size = client_config.blockchain.max_cache_size / 2;
}
None => {
client_config.blockchain.pref_cache_size = self.args.flag_cache_pref_size;
client_config.blockchain.max_cache_size = self.args.flag_cache_max_size;
}
}
2016-03-04 22:54:59 +01:00
client_config.prefer_journal = !self.args.flag_archive;
client_config.name = self.args.flag_identity.clone();
2016-02-25 14:09:39 +01:00
client_config.queue.max_mem_use = self.args.flag_queue_max_size;
let mut service = ClientService::start(client_config, spec, net_settings, &Path::new(&self.path())).unwrap();
panic_handler.forward_from(&service);
2016-02-18 12:42:01 +01:00
let client = service.client().clone();
client.set_author(self.author());
client.set_extra_data(self.extra_data());
2016-02-18 12:42:01 +01:00
// Sync
2016-02-24 21:23:58 +01:00
let sync = EthSync::register(service.network(), sync_config, client);
2016-02-18 12:42:01 +01:00
// Setup rpc
if self.args.flag_jsonrpc || self.args.flag_rpc {
let url = format!("{}:{}",
self.args.flag_rpcaddr.as_ref().unwrap_or(&self.args.flag_jsonrpc_addr),
self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port)
);
SocketAddr::from_str(&url).unwrap_or_else(|_|die!("{}: Invalid JSONRPC listen host/port given.", url));
let cors = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors);
// TODO: use this as the API list.
let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis);
let server_handler = setup_rpc_server(service.client(), sync.clone(), &url, cors, apis.split(",").collect());
if let Some(handler) = server_handler {
panic_handler.forward_from(handler.deref());
}
}
2016-02-18 12:42:01 +01:00
// Register IO handler
let io_handler = Arc::new(ClientIoHandler {
client: service.client(),
info: Default::default(),
2016-03-09 11:38:53 +01:00
sync: sync.clone(),
2016-02-18 12:42:01 +01:00
});
service.io().register_handler(io_handler).expect("Error registering IO handler");
// Handle exit
wait_for_exit(panic_handler);
2016-02-18 12:42:01 +01:00
}
}
fn wait_for_exit(panic_handler: Arc<PanicHandler>) {
let exit = Arc::new(Condvar::new());
2016-02-19 12:54:51 +01:00
// Handle possible exits
let e = exit.clone();
CtrlC::set_handler(move || { e.notify_all(); });
2016-02-19 12:54:51 +01:00
// Handle panics
let e = exit.clone();
panic_handler.on_panic(move |_reason| { e.notify_all(); });
2016-03-09 11:38:53 +01:00
// Wait for signal
let mutex = Mutex::new(());
let _ = exit.wait(mutex.lock().unwrap()).unwrap();
}
2015-12-22 22:19:50 +01:00
fn main() {
2016-02-18 12:42:01 +01:00
Configuration::parse().execute();
2015-12-22 22:19:50 +01:00
}
2016-01-18 23:23:32 +01:00
struct Informant {
chain_info: RwLock<Option<BlockChainInfo>>,
2016-02-25 14:09:39 +01:00
cache_info: RwLock<Option<BlockChainCacheSize>>,
report: RwLock<Option<ClientReport>>,
}
impl Default for Informant {
fn default() -> Self {
Informant {
chain_info: RwLock::new(None),
cache_info: RwLock::new(None),
report: RwLock::new(None),
}
}
2016-01-18 23:23:32 +01:00
}
impl Informant {
2016-02-25 14:09:39 +01:00
fn format_bytes(b: usize) -> String {
match binary_prefix(b as f64) {
Standalone(bytes) => format!("{} bytes", bytes),
Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix),
}
}
2016-01-22 04:54:38 +01:00
pub fn tick(&self, client: &Client, sync: &EthSync) {
2016-01-18 23:23:32 +01:00
// 5 seconds betwen calls. TODO: calculate this properly.
let dur = 5usize;
let chain_info = client.chain_info();
2016-01-22 04:54:38 +01:00
let queue_info = client.queue_info();
2016-02-25 14:09:39 +01:00
let cache_info = client.blockchain_cache_info();
2016-01-18 23:23:32 +01:00
let report = client.report();
2016-01-22 04:54:38 +01:00
let sync_info = sync.status();
2016-01-18 23:23:32 +01:00
2016-02-25 14:09:39 +01:00
if let (_, _, &Some(ref last_report)) = (self.chain_info.read().unwrap().deref(), self.cache_info.read().unwrap().deref(), self.report.read().unwrap().deref()) {
println!("[ #{} {} ]---[ {} blk/s | {} tx/s | {} gas/s //··· {}/{} peers, #{}, {}+{} queued ···// mem: {} db, {} chain, {} queue, {} sync ]",
2016-01-18 23:23:32 +01:00
chain_info.best_block_number,
chain_info.best_block_hash,
(report.blocks_imported - last_report.blocks_imported) / dur,
(report.transactions_applied - last_report.transactions_applied) / dur,
(report.gas_processed - last_report.gas_processed) / From::from(dur),
2016-01-22 04:54:38 +01:00
sync_info.num_active_peers,
sync_info.num_peers,
2016-02-11 22:14:06 +01:00
sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number),
queue_info.unverified_queue_size,
queue_info.verified_queue_size,
2016-01-22 04:54:38 +01:00
Informant::format_bytes(report.state_db_mem),
2016-02-25 14:09:39 +01:00
Informant::format_bytes(cache_info.total()),
Informant::format_bytes(queue_info.mem_used),
Informant::format_bytes(sync_info.mem_used),
2016-01-18 23:23:32 +01:00
);
}
*self.chain_info.write().unwrap().deref_mut() = Some(chain_info);
*self.cache_info.write().unwrap().deref_mut() = Some(cache_info);
*self.report.write().unwrap().deref_mut() = Some(report);
2016-01-18 23:23:32 +01:00
}
}
2016-01-16 13:30:27 +01:00
const INFO_TIMER: TimerToken = 0;
2016-01-16 13:30:27 +01:00
struct ClientIoHandler {
2016-01-21 23:33:52 +01:00
client: Arc<Client>,
2016-01-22 04:54:38 +01:00
sync: Arc<EthSync>,
2016-01-18 23:23:32 +01:00
info: Informant,
2016-01-16 13:30:27 +01:00
}
impl IoHandler<NetSyncMessage> for ClientIoHandler {
fn initialize(&self, io: &IoContext<NetSyncMessage>) {
io.register_timer(INFO_TIMER, 5000).expect("Error registering timer");
2016-01-16 13:30:27 +01:00
}
fn timeout(&self, _io: &IoContext<NetSyncMessage>, timer: TimerToken) {
if INFO_TIMER == timer {
2016-01-22 04:54:38 +01:00
self.info.tick(&self.client, &self.sync);
2016-01-16 13:30:27 +01:00
}
}
}
/// Parity needs at least 1 test to generate coverage reports correctly.
#[test]
fn if_works() {
}