From 405a6bfc04e7d22ed895da96e5017184d8da727d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 10:58:51 +0200 Subject: [PATCH 01/41] Removing match on constant --- ethcore/src/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs index 6901996bc..87f2a05be 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -138,7 +138,7 @@ impl Account { /// get someone who knows to call `note_code`. pub fn code(&self) -> Option<&[u8]> { match self.code_hash { - Some(SHA3_EMPTY) | None if self.code_cache.is_empty() => Some(&self.code_cache), + Some(c) if c == SHA3_EMPTY && self.code_cache.is_empty() => Some(&self.code_cache), Some(_) if !self.code_cache.is_empty() => Some(&self.code_cache), None => Some(&self.code_cache), _ => None, From d5f9cccf5e4c41f25bdaa1cb9bcdba1f8d28995d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 10:58:51 +0200 Subject: [PATCH 02/41] Removing match on constant --- ethcore/src/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs index 6901996bc..87f2a05be 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -138,7 +138,7 @@ impl Account { /// get someone who knows to call `note_code`. pub fn code(&self) -> Option<&[u8]> { match self.code_hash { - Some(SHA3_EMPTY) | None if self.code_cache.is_empty() => Some(&self.code_cache), + Some(c) if c == SHA3_EMPTY && self.code_cache.is_empty() => Some(&self.code_cache), Some(_) if !self.code_cache.is_empty() => Some(&self.code_cache), None => Some(&self.code_cache), _ => None, From dc91e57c2ffb0b57d5f2c7e2bea6362485604607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 19:07:27 +0200 Subject: [PATCH 03/41] Additional logging and error messages --- Cargo.lock | 6 ++-- parity/main.rs | 81 ++++++++++++++++++++++++++++++++++---------------- rpc/Cargo.toml | 2 +- rpc/src/lib.rs | 23 +++++++++----- 4 files changed, 76 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b24bc9219..5c54ae66d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,7 +245,7 @@ dependencies = [ "ethminer 1.1.0", "ethsync 1.1.0", "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -452,8 +452,8 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "4.0.0" +source = "git+https://github.com/tomusdrw/jsonrpc-http-server.git#46bd4e7cf8352e0efc940cf76d3dff99f1a3da15" dependencies = [ "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/parity/main.rs b/parity/main.rs index c5e0dce54..465a21fee 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -269,10 +269,8 @@ fn setup_rpc_server( sync: Arc, secret_store: Arc, miner: Arc, - url: &str, - cors_domain: &str, apis: Vec<&str> -) -> Option> { +) -> Option { use rpc::v1::*; let server = rpc::RpcServer::new(); @@ -290,7 +288,7 @@ fn setup_rpc_server( } } } - Some(server.start_http(url, cors_domain, ::num_cpus::get())) + Some(server) } #[cfg(not(feature = "rpc"))] @@ -299,8 +297,6 @@ fn setup_rpc_server( _sync: Arc, _secret_store: Arc, _miner: Arc, - _url: &str, - _cors_domain: &str, _apis: Vec<&str> ) -> Option> { None @@ -569,7 +565,10 @@ impl Configuration { let account_service = Arc::new(self.account_service()); // Build client - let mut service = ClientService::start(self.client_config(), spec, net_settings, &Path::new(&self.path())).unwrap(); + let mut service = ClientService::start( + self.client_config(), spec, net_settings, &Path::new(&self.path()) + ).unwrap_or_else(|e| die_with_error(e)); + panic_handler.forward_from(&service); let client = service.client(); @@ -584,7 +583,23 @@ impl Configuration { let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone()); // Setup rpc - if self.args.flag_jsonrpc || self.args.flag_rpc { + let rpc_server = if self.args.flag_jsonrpc || self.args.flag_rpc { + // TODO: use this as the API list. + let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis); + setup_rpc_server( + service.client(), + sync.clone(), + account_service.clone(), + miner.clone(), + apis.split(',').collect() + ) + } else { + None + }; + let rpc_handle = rpc_server.map(|server| { + panic_handler.forward_from(&server); + server + }).map(|server| { let url = format!("{}:{}", match self.args.flag_rpcaddr.as_ref().unwrap_or(&self.args.flag_jsonrpc_interface).as_str() { "all" => "0.0.0.0", @@ -594,22 +609,14 @@ impl Configuration { 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(), - account_service.clone(), - miner.clone(), - &url, - cors, - apis.split(',').collect() - ); - if let Some(handler) = server_handler { - panic_handler.forward_from(handler.deref()); + let cors_domain = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors); + let start_result = server.start_http(&url, cors_domain, ::num_cpus::get()); + match start_result { + Ok(handle) => handle, + Err(rpc::RpcServerError::IoError(err)) => die_with_io_error(err), + Err(e) => die!("{:?}", e), } - } + }); // Register IO handler let io_handler = Arc::new(ClientIoHandler { @@ -621,11 +628,11 @@ impl Configuration { service.io().register_handler(io_handler).expect("Error registering IO handler"); // Handle exit - wait_for_exit(panic_handler); + wait_for_exit(panic_handler, rpc_handle); } } -fn wait_for_exit(panic_handler: Arc) { +fn wait_for_exit(panic_handler: Arc, _rpc_handle: Option) { let exit = Arc::new(Condvar::new()); // Handle possible exits @@ -639,6 +646,30 @@ fn wait_for_exit(panic_handler: Arc) { // Wait for signal let mutex = Mutex::new(()); let _ = exit.wait(mutex.lock().unwrap()).unwrap(); + info!("Closing...."); +} + +fn die_with_error(e: ethcore::error::Error) -> ! { + use ethcore::error::Error; + + match e { + Error::Util(UtilError::StdIo(e)) => die_with_io_error(e), + _ => die!("{:?}", e), + } +} +fn die_with_io_error(e: std::io::Error) -> ! { + match e.kind() { + std::io::ErrorKind::PermissionDenied => { + die!("We don't have permission to bind to this port.") + }, + std::io::ErrorKind::AddrInUse => { + die!("Specified address is already in use. Please make sure that nothing is listening on specified port or use different one") + }, + std::io::ErrorKind::AddrNotAvailable => { + die!("Couldn't use specified interface or given address is invalid.") + }, + _ => die!("{:?}", e), + } } fn main() { diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index c28f598fd..6fdf9a55e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -13,7 +13,7 @@ log = "0.3" serde = "0.7.0" serde_json = "0.7.0" jsonrpc-core = "2.0" -jsonrpc-http-server = "3.0" +jsonrpc-http-server = { git = "https://github.com/tomusdrw/jsonrpc-http-server.git" } ethcore-util = { path = "../util" } ethcore = { path = "../ethcore" } ethash = { path = "../ethash" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 3096a45c9..119d12a0f 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -34,14 +34,16 @@ extern crate transient_hashmap; use std::sync::Arc; use std::thread; -use util::panics::PanicHandler; +use util::panics::{MayPanic, PanicHandler, OnPanicListener}; use self::jsonrpc_core::{IoHandler, IoDelegate}; +pub use jsonrpc_http_server::{Listening, RpcServerError}; pub mod v1; /// Http server. pub struct RpcServer { handler: Arc, + panic_handler: Arc } impl RpcServer { @@ -49,6 +51,7 @@ impl RpcServer { pub fn new() -> RpcServer { RpcServer { handler: Arc::new(IoHandler::new()), + panic_handler: PanicHandler::new_in_arc(), } } @@ -58,17 +61,23 @@ impl RpcServer { } /// Start server asynchronously in new thread and returns panic handler. - pub fn start_http(&self, addr: &str, cors_domain: &str, threads: usize) -> Arc { + pub fn start_http(&self, addr: &str, cors_domain: &str, threads: usize) -> Result { let addr = addr.to_owned(); let cors_domain = cors_domain.to_owned(); - let panic_handler = PanicHandler::new_in_arc(); - let ph = panic_handler.clone(); + let ph = self.panic_handler.clone(); let server = jsonrpc_http_server::Server::new(self.handler.clone()); + thread::Builder::new().name("jsonrpc_http".to_string()).spawn(move || { ph.catch_panic(move || { - server.start(addr.as_ref(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain), threads); + server.start(addr.as_ref(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain), threads) }).unwrap() - }).expect("Error while creating jsonrpc http thread"); - panic_handler + }).expect("Error while creating jsonrpc http thread").join().unwrap() } } + +impl MayPanic for RpcServer { + fn on_panic(&self, closure: F) where F: OnPanicListener { + self.panic_handler.on_panic(closure); + } +} + From a52043d5b3ab37c29abfdd03505846aa9003b848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 19:14:05 +0200 Subject: [PATCH 04/41] Removing additional thread from JSON-RPC --- parity/main.rs | 4 +--- rpc/src/lib.rs | 18 +----------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 1b1ca27e6..6fffa4b53 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -594,10 +594,8 @@ impl Configuration { } else { None }; + let rpc_handle = rpc_server.map(|server| { - panic_handler.forward_from(&server); - server - }).map(|server| { let url = format!("{}:{}", match self.args.flag_rpcaddr.as_ref().unwrap_or(&self.args.flag_jsonrpc_interface).as_str() { "all" => "0.0.0.0", diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 119d12a0f..9787db9cd 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -33,8 +33,6 @@ extern crate ethminer; extern crate transient_hashmap; use std::sync::Arc; -use std::thread; -use util::panics::{MayPanic, PanicHandler, OnPanicListener}; use self::jsonrpc_core::{IoHandler, IoDelegate}; pub use jsonrpc_http_server::{Listening, RpcServerError}; @@ -43,7 +41,6 @@ pub mod v1; /// Http server. pub struct RpcServer { handler: Arc, - panic_handler: Arc } impl RpcServer { @@ -51,7 +48,6 @@ impl RpcServer { pub fn new() -> RpcServer { RpcServer { handler: Arc::new(IoHandler::new()), - panic_handler: PanicHandler::new_in_arc(), } } @@ -64,20 +60,8 @@ impl RpcServer { pub fn start_http(&self, addr: &str, cors_domain: &str, threads: usize) -> Result { let addr = addr.to_owned(); let cors_domain = cors_domain.to_owned(); - let ph = self.panic_handler.clone(); let server = jsonrpc_http_server::Server::new(self.handler.clone()); - thread::Builder::new().name("jsonrpc_http".to_string()).spawn(move || { - ph.catch_panic(move || { - server.start(addr.as_ref(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain), threads) - }).unwrap() - }).expect("Error while creating jsonrpc http thread").join().unwrap() + server.start(addr.as_ref(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain), threads) } } - -impl MayPanic for RpcServer { - fn on_panic(&self, closure: F) where F: OnPanicListener { - self.panic_handler.on_panic(closure); - } -} - From fd03f58eaebc7fd9b3fd795904c813d45184df32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 19:22:10 +0200 Subject: [PATCH 05/41] Rewriting messages --- parity/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 6fffa4b53..1f06298be 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -656,13 +656,13 @@ fn die_with_error(e: ethcore::error::Error) -> ! { fn die_with_io_error(e: std::io::Error) -> ! { match e.kind() { std::io::ErrorKind::PermissionDenied => { - die!("We don't have permission to bind to this port.") + die!("No permissions to bind to specified port.") }, std::io::ErrorKind::AddrInUse => { - die!("Specified address is already in use. Please make sure that nothing is listening on specified port or use different one") + die!("Specified address is already in use. Please make sure that nothing is listening on the same port or try using a different one.") }, std::io::ErrorKind::AddrNotAvailable => { - die!("Couldn't use specified interface or given address is invalid.") + die!("Could not use specified interface or given address is invalid.") }, _ => die!("{:?}", e), } From 3438cda4324106c164eac3686ff1a60efc7a3372 Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 6 Apr 2016 23:03:07 +0200 Subject: [PATCH 06/41] Propagate transaction queue --- miner/src/lib.rs | 5 +++- miner/src/miner.rs | 5 ++++ rpc/src/v1/impls/eth.rs | 8 +++---- rpc/src/v1/tests/helpers/miner_service.rs | 4 ++++ rpc/src/v1/tests/helpers/sync_provider.rs | 5 +--- sync/src/chain.rs | 28 +++++++++-------------- sync/src/lib.rs | 10 +------- 7 files changed, 29 insertions(+), 36 deletions(-) diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 9f757fb67..c6e07a953 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -105,9 +105,12 @@ pub trait MinerService : Send + Sync { /// Get the sealing work package and if `Some`, apply some transform. fn map_sealing_work(&self, chain: &BlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T; - /// Query pending transactions for hash + /// Query pending transactions for hash. fn transaction(&self, hash: &H256) -> Option; + /// Get a list of all pending transactions. + fn pending_transactions(&self) -> Vec; + /// Returns highest transaction nonce for given address. fn last_nonce(&self, address: &Address) -> Option; diff --git a/miner/src/miner.rs b/miner/src/miner.rs index 0e31c504f..3b4d1f32d 100644 --- a/miner/src/miner.rs +++ b/miner/src/miner.rs @@ -228,6 +228,11 @@ impl MinerService for Miner { queue.find(hash) } + fn pending_transactions(&self) -> Vec { + let queue = self.transaction_queue.lock().unwrap(); + queue.top_transactions() + } + fn last_nonce(&self, address: &Address) -> Option { self.transaction_queue.lock().unwrap().last_nonce(address) } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 30dc79662..ad4b037df 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -186,7 +186,7 @@ impl EthClient }.fake_sign(from)) } - fn dispatch_transaction(&self, signed_transaction: SignedTransaction, raw_transaction: Vec) -> Result { + fn dispatch_transaction(&self, signed_transaction: SignedTransaction) -> Result { let hash = signed_transaction.hash(); let import = { @@ -203,7 +203,6 @@ impl EthClient match import.into_iter().collect::, _>>() { Ok(_) => { - take_weak!(self.sync).new_transaction(raw_transaction); to_value(&hash) } Err(e) => { @@ -504,8 +503,7 @@ impl Eth for EthClient data: request.data.map_or_else(Vec::new, |d| d.to_vec()), }.sign(&secret) }; - let raw_transaction = encode(&signed_transaction).to_vec(); - self.dispatch_transaction(signed_transaction, raw_transaction) + self.dispatch_transaction(signed_transaction) }, Err(_) => { to_value(&H256::zero()) } } @@ -517,7 +515,7 @@ impl Eth for EthClient .and_then(|(raw_transaction, )| { let raw_transaction = raw_transaction.to_vec(); match UntrustedRlp::new(&raw_transaction).as_val() { - Ok(signed_transaction) => self.dispatch_transaction(signed_transaction, raw_transaction), + Ok(signed_transaction) => self.dispatch_transaction(signed_transaction), Err(_) => to_value(&H256::zero()), } }) diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 80a5e356d..815085d3b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -98,6 +98,10 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().unwrap().get(hash).cloned() } + fn pending_transactions(&self) -> Vec { + self.pending_transactions.lock().unwrap().values().cloned().collect() + } + fn last_nonce(&self, address: &Address) -> Option { self.last_nonces.read().unwrap().get(address).cloned() } diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index 59188f0a7..633e0d45b 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -16,7 +16,7 @@ //! Test implementation of SyncProvider. -use util::{U256, Bytes}; +use util::{U256}; use ethsync::{SyncProvider, SyncStatus, SyncState}; use std::sync::{RwLock}; @@ -59,8 +59,5 @@ impl SyncProvider for TestSyncProvider { fn status(&self) -> SyncStatus { self.status.read().unwrap().clone() } - - fn new_transaction(&self, _raw_transaction: Bytes) { - } } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 1a7d11f51..74bcb8d38 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -217,10 +217,6 @@ pub struct ChainSync { network_id: U256, /// Miner miner: Arc, - - /// Transactions to propagate - // TODO: reconsider where this is in the codebase - seems a little dodgy to have here. - transactions_to_send: Vec, } type RlpResponseResult = Result, PacketDecodeError>; @@ -247,7 +243,6 @@ impl ChainSync { max_download_ahead_blocks: max(MAX_HEADERS_TO_REQUEST, config.max_download_ahead_blocks), network_id: config.network_id, miner: miner, - transactions_to_send: vec![], } } @@ -950,11 +945,6 @@ impl ChainSync { } } - /// Place a new transaction on the wire. - pub fn new_transaction(&mut self, raw_transaction: Bytes) { - self.transactions_to_send.push(raw_transaction); - } - /// Called when peer sends us new transactions fn on_peer_transactions(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { // accepting transactions once only fully synced @@ -1296,11 +1286,16 @@ impl ChainSync { return 0; } - let mut packet = RlpStream::new_list(self.transactions_to_send.len()); - for tx in &self.transactions_to_send { - packet.append_raw(tx, 1); + let mut transactions = self.miner.pending_transactions(); + if transactions.is_empty() { + return 0; + } + + let mut packet = RlpStream::new_list(transactions.len()); + let tx_count = transactions.len(); + for tx in transactions.drain(..) { + packet.append(&tx); } - self.transactions_to_send.clear(); let rlp = packet.out(); let lucky_peers = { @@ -1319,13 +1314,12 @@ impl ChainSync { for peer_id in lucky_peers { self.send_packet(io, peer_id, TRANSACTIONS_PACKET, rlp.clone()); } + trace!(target: "sync", "Sent {} transactions to {} peers.", tx_count, sent); sent } fn propagate_latest_blocks(&mut self, io: &mut SyncIo) { - if !self.transactions_to_send.is_empty() { - self.propagate_new_transactions(io); - } + self.propagate_new_transactions(io); let chain_info = io.chain().chain_info(); if (((chain_info.best_block_number as i64) - (self.last_sent_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION { let blocks = self.propagate_blocks(&chain_info, io); diff --git a/sync/src/lib.rs b/sync/src/lib.rs index ea4a1daea..a4f6eff38 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -66,7 +66,7 @@ use std::ops::*; use std::sync::*; use util::network::{NetworkProtocolHandler, NetworkService, NetworkContext, PeerId}; use util::TimerToken; -use util::{U256, Bytes, ONE_U256}; +use util::{U256, ONE_U256}; use ethcore::client::Client; use ethcore::service::SyncMessage; use ethminer::Miner; @@ -101,9 +101,6 @@ impl Default for SyncConfig { pub trait SyncProvider: Send + Sync { /// Get sync status fn status(&self) -> SyncStatus; - - /// Note that a user has submitted a new transaction. - fn new_transaction(&self, raw_transaction: Bytes); } /// Ethereum network protocol handler @@ -143,11 +140,6 @@ impl SyncProvider for EthSync { fn status(&self) -> SyncStatus { self.sync.read().unwrap().status() } - - /// Note that a user has submitted a new transaction. - fn new_transaction(&self, raw_transaction: Bytes) { - self.sync.write().unwrap().new_transaction(raw_transaction); - } } impl NetworkProtocolHandler for EthSync { From d4f0902968a338345024cd75fabd969149c71a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 23:45:19 +0200 Subject: [PATCH 07/41] Tracing shutdown and changed order of IoManager shutdown process --- ethcore/src/block_queue.rs | 2 ++ util/src/io/service.rs | 4 +++- util/src/io/worker.rs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ethcore/src/block_queue.rs b/ethcore/src/block_queue.rs index 4a52d6a6b..433e8b40b 100644 --- a/ethcore/src/block_queue.rs +++ b/ethcore/src/block_queue.rs @@ -431,12 +431,14 @@ impl MayPanic for BlockQueue { impl Drop for BlockQueue { fn drop(&mut self) { + trace!(target: "shutdown", "[BlockQueue] Closing..."); self.clear(); self.deleting.store(true, AtomicOrdering::Release); self.more_to_verify.notify_all(); for t in self.verifiers.drain(..) { t.join().unwrap(); } + trace!(target: "shutdown", "[BlockQueue] Closed."); } } diff --git a/util/src/io/service.rs b/util/src/io/service.rs index 8a34ee80a..24cc1181a 100644 --- a/util/src/io/service.rs +++ b/util/src/io/service.rs @@ -231,8 +231,8 @@ impl Handler for IoManager where Message: Send + Clone + Sync fn notify(&mut self, event_loop: &mut EventLoop, msg: Self::Message) { match msg { IoMessage::Shutdown => { - self.workers.clear(); event_loop.shutdown(); + self.workers.clear(); }, IoMessage::AddHandler { handler } => { let handler_id = { @@ -376,8 +376,10 @@ impl IoService where Message: Send + Sync + Clone + 'static { impl Drop for IoService where Message: Send + Sync + Clone { fn drop(&mut self) { + trace!(target: "shutdown", "[IoService] Closing..."); self.host_channel.send(IoMessage::Shutdown).unwrap(); self.thread.take().unwrap().join().ok(); + trace!(target: "shutdown", "[IoService] Closed."); } } diff --git a/util/src/io/worker.rs b/util/src/io/worker.rs index b874ea0a4..917b1ad79 100644 --- a/util/src/io/worker.rs +++ b/util/src/io/worker.rs @@ -120,10 +120,12 @@ impl Worker { impl Drop for Worker { fn drop(&mut self) { + trace!(target: "shutdown", "[IoWorker] Closing..."); let _ = self.wait_mutex.lock(); self.deleting.store(true, AtomicOrdering::Release); self.wait.notify_all(); let thread = mem::replace(&mut self.thread, None).unwrap(); thread.join().ok(); + trace!(target: "shutdown", "[IoWorker] Closed"); } } From f27d88f6aba8b662645586530e551f51b2fdc9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 6 Apr 2016 23:58:23 +0200 Subject: [PATCH 08/41] More descriptive message when closing --- parity/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity/main.rs b/parity/main.rs index 1f06298be..06710a3a4 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -642,7 +642,7 @@ fn wait_for_exit(panic_handler: Arc, _rpc_handle: Option ! { From 730d60e5e4b9a0a8105f7b610cd462dc77c501d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 00:20:03 +0200 Subject: [PATCH 09/41] Avoid signalling readiness when app is about to be closed --- ethcore/src/block_queue.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ethcore/src/block_queue.rs b/ethcore/src/block_queue.rs index 4a52d6a6b..d9f629f11 100644 --- a/ethcore/src/block_queue.rs +++ b/ethcore/src/block_queue.rs @@ -116,6 +116,7 @@ struct VerifyingBlock { } struct QueueSignal { + deleting: Arc, signalled: AtomicBool, message_channel: IoChannel, } @@ -123,10 +124,16 @@ struct QueueSignal { impl QueueSignal { #[cfg_attr(feature="dev", allow(bool_comparison))] fn set(&self) { + // Do not signal when we are about to close + if self.deleting.load(AtomicOrdering::Relaxed) { + return; + } + if self.signalled.compare_and_swap(false, true, AtomicOrdering::Relaxed) == false { self.message_channel.send(UserMessage(SyncMessage::BlockVerified)).expect("Error sending BlockVerified message"); } } + fn reset(&self) { self.signalled.store(false, AtomicOrdering::Relaxed); } @@ -150,8 +157,12 @@ impl BlockQueue { bad: Mutex::new(HashSet::new()), }); let more_to_verify = Arc::new(Condvar::new()); - let ready_signal = Arc::new(QueueSignal { signalled: AtomicBool::new(false), message_channel: message_channel }); let deleting = Arc::new(AtomicBool::new(false)); + let ready_signal = Arc::new(QueueSignal { + deleting: deleting.clone(), + signalled: AtomicBool::new(false), + message_channel: message_channel + }); let empty = Arc::new(Condvar::new()); let panic_handler = PanicHandler::new_in_arc(); From d1d3d847ab431ced381fc42ff15867ab18b7264b Mon Sep 17 00:00:00 2001 From: debris Date: Thu, 7 Apr 2016 00:33:55 +0200 Subject: [PATCH 10/41] fixed #895 --- rpc/src/v1/traits/eth.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 8a48e0dfe..5d36da670 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -190,9 +190,6 @@ pub trait EthFilter: Sized + Send + Sync + 'static { /// Returns filter changes since last poll. fn filter_changes(&self, _: Params) -> Result { rpc_unimplemented!() } - /// Returns filter logs. - fn filter_logs(&self, _: Params) -> Result { rpc_unimplemented!() } - /// Uninstalls filter. fn uninstall_filter(&self, _: Params) -> Result { rpc_unimplemented!() } @@ -203,7 +200,7 @@ pub trait EthFilter: Sized + Send + Sync + 'static { delegate.add_method("eth_newBlockFilter", EthFilter::new_block_filter); delegate.add_method("eth_newPendingTransactionFilter", EthFilter::new_pending_transaction_filter); delegate.add_method("eth_getFilterChanges", EthFilter::filter_changes); - delegate.add_method("eth_getFilterLogs", EthFilter::filter_logs); + delegate.add_method("eth_getFilterLogs", EthFilter::filter_changes); delegate.add_method("eth_uninstallFilter", EthFilter::uninstall_filter); delegate } From 12d1dcddeb17b7ffb5c48851620543b7017411f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 10:31:42 +0200 Subject: [PATCH 11/41] Updating rpc comments --- rpc/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 9787db9cd..4de405211 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -44,7 +44,7 @@ pub struct RpcServer { } impl RpcServer { - /// Construct new http server object with given number of threads. + /// Construct new http server object. pub fn new() -> RpcServer { RpcServer { handler: Arc::new(IoHandler::new()), @@ -56,7 +56,7 @@ impl RpcServer { self.handler.add_delegate(delegate); } - /// Start server asynchronously in new thread and returns panic handler. + /// Start server asynchronously and returns result with `Listening` handle on success or an error. pub fn start_http(&self, addr: &str, cors_domain: &str, threads: usize) -> Result { let addr = addr.to_owned(); let cors_domain = cors_domain.to_owned(); From e3ce5d94e1067b301af7f54fd73e825f01945237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 10:49:00 +0200 Subject: [PATCH 12/41] Adding webapps crate --- Cargo.lock | 88 +++++++++++++++++++++++++ Cargo.toml | 8 ++- cov.sh | 2 + doc.sh | 1 + fmt.sh | 1 + hook.sh | 2 +- parity/main.rs | 2 + test.sh | 1 + webapp/Cargo.toml | 21 ++++++ webapp/src/lib.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 webapp/Cargo.toml create mode 100644 webapp/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 471f35067..9783413d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "ethcore-devtools 1.1.0", "ethcore-rpc 1.1.0", "ethcore-util 1.1.0", + "ethcore-webapp 1.1.0", "ethminer 1.1.0", "ethsync 1.1.0", "fdlimit 0.1.0", @@ -107,6 +108,14 @@ dependencies = [ "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "conduit-mime-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cookie" version = "0.1.21" @@ -185,6 +194,15 @@ dependencies = [ "regex 0.1.61 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "eth-secp256k1" version = "0.5.4" @@ -291,6 +309,20 @@ dependencies = [ "vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethcore-webapp" +version = "1.1.0" +dependencies = [ + "clippy 0.0.61 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-rpc 1.1.0", + "ethcore-util 1.1.0", + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "iron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git)", + "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethjson" version = "0.1.0" @@ -427,6 +459,23 @@ dependencies = [ "xmltree 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "iron" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itertools" version = "0.4.11" @@ -461,6 +510,16 @@ dependencies = [ "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jsonrpc-http-server" +version = "4.0.0" +source = "git+https://github.com/tomusdrw/jsonrpc-http-server.git#46bd4e7cf8352e0efc940cf76d3dff99f1a3da15" +dependencies = [ + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.1" @@ -573,6 +632,11 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "modifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "net2" version = "0.2.23" @@ -637,6 +701,14 @@ name = "odds" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "plugin" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "primal" version = "0.2.3" @@ -938,6 +1010,14 @@ name = "typeable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicase" version = "1.4.0" @@ -964,6 +1044,14 @@ name = "unicode-xid" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unsafe-any" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "0.2.38" diff --git a/Cargo.toml b/Cargo.toml index f38fe5b10..3fe923db7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,15 +28,19 @@ ethsync = { path = "sync" } ethminer = { path = "miner" } ethcore-devtools = { path = "devtools" } ethcore-rpc = { path = "rpc", optional = true } +ethcore-webapp = { path = "webapp", optional = true } + [dependencies.hyper] version = "0.8" default-features = false [features] -default = ["rpc"] +default = ["rpc", "webapp"] rpc = ["ethcore-rpc"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev"] +webapp = ["ethcore-webapp"] +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev", +"ethcore-webapp/dev"] travis-beta = ["ethcore/json-tests"] travis-nightly = ["ethcore/json-tests", "dev"] diff --git a/cov.sh b/cov.sh index d60ef223d..1698d6f36 100755 --- a/cov.sh +++ b/cov.sh @@ -23,6 +23,7 @@ cargo test \ -p ethcore-rpc \ -p parity \ -p ethminer \ + -p ethcore-webapp \ --no-run || exit $? rm -rf target/coverage mkdir -p target/coverage @@ -33,5 +34,6 @@ kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage t kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethsync-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-* +kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_webapp-* kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethminer-* xdg-open target/coverage/index.html diff --git a/doc.sh b/doc.sh index a5e5e2e13..9b0f13817 100755 --- a/doc.sh +++ b/doc.sh @@ -7,5 +7,6 @@ cargo doc --no-deps --verbose \ -p ethcore \ -p ethsync \ -p ethcore-rpc \ + -p ethcore-webapp \ -p parity \ -p ethminer diff --git a/fmt.sh b/fmt.sh index a16d5ac1f..4d835967f 100755 --- a/fmt.sh +++ b/fmt.sh @@ -9,6 +9,7 @@ $RUSTFMT ./json/src/lib.rs $RUSTFMT ./miner/src/lib.rs $RUSTFMT ./parity/main.rs $RUSTFMT ./rpc/src/lib.rs +$RUSTFMT ./webapp/src/lib.rs $RUSTFMT ./sync/src/lib.rs $RUSTFMT ./util/src/lib.rs diff --git a/hook.sh b/hook.sh index 58bff20ab..d98297835 100755 --- a/hook.sh +++ b/hook.sh @@ -7,6 +7,6 @@ echo "set -e" >> $FILE echo "cargo build --release --features dev" >> $FILE # Build tests echo "cargo test --no-run --features dev \\" >> $FILE -echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer" >> $FILE +echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer -p ethcore-webapp" >> $FILE echo "" >> $FILE chmod +x $FILE diff --git a/parity/main.rs b/parity/main.rs index f5f5cfcd4..2141a5795 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -42,6 +42,8 @@ extern crate rpassword; #[cfg(feature = "rpc")] extern crate ethcore_rpc as rpc; +#[cfg(feature = "webapp")] +extern crate ethcore_webapp as webapp; use std::io::{BufRead, BufReader}; use std::fs::File; diff --git a/test.sh b/test.sh index 4957fd762..5094158cc 100755 --- a/test.sh +++ b/test.sh @@ -7,6 +7,7 @@ cargo test --features ethcore/json-tests $1 \ -p ethcore \ -p ethsync \ -p ethcore-rpc \ + -p ethcore-webapp \ -p parity \ -p ethminer \ -p bigint diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml new file mode 100644 index 000000000..f6503e163 --- /dev/null +++ b/webapp/Cargo.toml @@ -0,0 +1,21 @@ +[package] +description = "Parity WebApplications crate" +name = "ethcore-webapp" +version = "1.1.0" +license = "GPL-3.0" +authors = ["Ethcore . + +//! Ethcore Webapplications for Parity +#![warn(missing_docs)] +#![cfg_attr(feature="nightly", plugin(clippy))] + +#[macro_use] +extern crate log; +extern crate hyper; +extern crate iron; +extern crate jsonrpc_core; +extern crate jsonrpc_http_server; +extern crate ethcore_rpc as rpc; +extern crate ethcore_util as util; + +use rpc::v1::*; +use std::sync::Arc; +use std::thread; + +use util::panics::PanicHandler; +use iron::request::Url; +use self::jsonrpc_core::{IoHandler, IoDelegate}; +use jsonrpc_http_server::ServerHandler; + +/// Http server. +pub struct WebappServer { + handler: Arc, +} + +impl WebappServer { + /// Construct new http server object + pub fn new() -> Self { + let server = WebappServer { + handler: Arc::new(IoHandler::new()), + }; + // TODO add more + server.add_delegate(Web3Client::new().to_delegate()); + + server + } + + /// Add io delegate. + fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { + self.handler.add_delegate(delegate); + } + + /// Start server asynchronously in new thread and returns panic handler. + pub fn start_http(&self, addr: &str, threads: usize) -> Arc { + let addr = addr.to_owned(); + let panic_handler = PanicHandler::new_in_arc(); + let ph = panic_handler.clone(); + let handler = self.handler.clone(); + + thread::Builder::new().name("jsonrpc_http".to_string()).spawn(move || { + let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; + let rpc = ServerHandler::new(handler, cors_domain); + let router = Router::new(rpc); + + ph.catch_panic(move || { + hyper::Server::http(addr.as_ref() as &str).unwrap() + .handle_threads(router, threads) + .unwrap(); + }).unwrap() + }).expect("Error while creating jsonrpc http thread"); + + panic_handler + } +} + + +struct Router { + rpc: ServerHandler, + // admin: Page, + // wallet: Page, + // mist: Page, +} + +impl Router { + pub fn new(rpc: ServerHandler) -> Self { + Router { + rpc: rpc, + // admin: Page { app: AdminApp::default() }, + // wallet: Page { app: WalletApp::default() }, + // mist: Page { app: MistApp::default() }, + } + } + + fn extract_url(req: &hyper::server::Request) -> Option { + match req.uri { + hyper::uri::RequestUri::AbsoluteUri(ref url) => { + match Url::from_generic_url(url.clone()) { + Ok(url) => Some(url), + _ => None, + } + }, + hyper::uri::RequestUri::AbsolutePath(ref path) => { + // Attempt to prepend the Host header (mandatory in HTTP/1.1) + let url_string = match req.headers.get::() { + Some(ref host) => { + format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) + }, + None => return None + }; + + match Url::parse(&url_string) { + Ok(url) => Some(url), + _ => None, + } + } + _ => None, + } + } + + fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) { + let url = Router::extract_url(&req); + match url { + Some(url) => { + let part = url.path[0].clone(); + let url = url.path[1..].join("/"); + req.uri = hyper::uri::RequestUri::AbsolutePath(url); + (Some(part), req) + }, + None => { + (None, req) + } + } + } +} + +impl hyper::server::Handler for Router { + fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) { + let (path, req) = Router::extract_request_path(req); + match path { + // Some(ref url) if url == "admin" => { + // self.admin.handle(req, res); + // }, + // Some(ref url) if url == "wallet" => { + // self.wallet.handle(req, res); + // }, + // Some(ref url) if url == "mist" => { + // self.mist.handle(req, res); + // }, + _ => self.rpc.handle(req, res), + } + } +} From 5d6ca1498e8c1943b331d2c7fa43e7a6cc8c85da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 11:06:49 +0200 Subject: [PATCH 13/41] CLI options to run webapp server --- parity/main.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/parity/main.rs b/parity/main.rs index 2141a5795..ecd72e0ff 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -120,7 +120,7 @@ Networking Options: string or input to SHA3 operation. API and Console Options: - -j --jsonrpc Enable the JSON-RPC API sever. + -j --jsonrpc Enable the JSON-RPC API server. --jsonrpc-interface IP Specify the hostname portion of the JSONRPC API server, IP should be an interface's IP address, or all (all interfaces) or local [default: local]. @@ -132,6 +132,10 @@ API and Console Options: interface. APIS is a comma-delimited list of API name. Possible name are web3, eth and net. [default: web3,eth,net,personal]. + -w --webap Enable the web applications server (e.g. status page). + --webapp-port PORT Specify the port portion of the WebApps server + [default: 8080]. + Sealing/Mining Options: --usd-per-tx USD Amount of USD to be paid for a basic transaction @@ -216,6 +220,8 @@ struct Args { flag_jsonrpc_port: u16, flag_jsonrpc_cors: String, flag_jsonrpc_apis: String, + flag_webapp: bool, + flag_webapp_port: u16, flag_author: String, flag_usd_per_tx: String, flag_usd_per_eth: String, @@ -295,6 +301,13 @@ fn setup_rpc_server( } Some(server.start_http(url, cors_domain, ::num_cpus::get())) } +#[cfg(feature = "webapp")] +fn setup_webapp_server( + url: &str +) -> Option> { + let server = webapp::WebappServer::new(); + Some(server.start_http(url, ::num_cpus::get())) +} #[cfg(not(feature = "rpc"))] fn setup_rpc_server( @@ -309,6 +322,13 @@ fn setup_rpc_server( None } +#[cfg(not(feature = "webapp"))] +fn setup_webapp_server( + _url: &str +) -> Option> { + None +} + fn print_version() { println!("\ Parity @@ -611,6 +631,15 @@ impl Configuration { } } + if self.args.flag_webapp { + let url = format!("0.0.0.0:{}", self.args.flag_webapp_port); + setup_webapp_server( + &url, + ).map(|handler| { + panic_handler.forward_from(handler.deref()); + }); + } + // Register IO handler let io_handler = Arc::new(ClientIoHandler { client: service.client(), From ad37b7fd2a5b3297904aa4d90974390d01e196fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:10:26 +0200 Subject: [PATCH 14/41] Adding webapps router --- Cargo.lock | 15 +++++ parity/main.rs | 5 +- webapp/Cargo.toml | 4 ++ webapp/src/apps.rs | 37 ++++++++++++ webapp/src/lib.rs | 118 ++++++--------------------------------- webapp/src/page/mod.rs | 67 ++++++++++++++++++++++ webapp/src/router/mod.rs | 89 +++++++++++++++++++++++++++++ 7 files changed, 233 insertions(+), 102 deletions(-) create mode 100644 webapp/src/apps.rs create mode 100644 webapp/src/page/mod.rs create mode 100644 webapp/src/router/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9783413d4..62093c293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,8 @@ dependencies = [ "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", + "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", ] [[package]] @@ -701,6 +703,19 @@ name = "odds" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "parity-wallet" +version = "0.1.0" +source = "git+https://github.com/tomusdrw/parity-wallet.git#9b0253f5cb88b31417450ca8be708cab2e437dfc" +dependencies = [ + "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", +] + +[[package]] +name = "parity-webapp" +version = "0.1.0" +source = "git+https://github.com/tomusdrw/parity-webapp.git#a24297256bae0ae0712c6478cd1ad681828b3800" + [[package]] name = "plugin" version = "0.2.6" diff --git a/parity/main.rs b/parity/main.rs index ecd72e0ff..b8498d520 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -132,7 +132,7 @@ API and Console Options: interface. APIS is a comma-delimited list of API name. Possible name are web3, eth and net. [default: web3,eth,net,personal]. - -w --webap Enable the web applications server (e.g. status page). + -w --webapp Enable the web applications server (e.g. status page). --webapp-port PORT Specify the port portion of the WebApps server [default: 8080]. @@ -305,7 +305,10 @@ fn setup_rpc_server( fn setup_webapp_server( url: &str ) -> Option> { + use rpc::v1::*; + let server = webapp::WebappServer::new(); + server.add_delegate(Web3Client::new().to_delegate()); Some(server.start_http(url, ::num_cpus::get())) } diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index f6503e163..377b0b556 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -15,7 +15,11 @@ hyper = { version = "0.8", default-features = false } iron = { version = "0.3" } ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } +parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" } +# List of apps +parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true } clippy = { version = "0.0.61", optional = true} [features] +default = ["parity-wallet"] dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] diff --git a/webapp/src/apps.rs b/webapp/src/apps.rs new file mode 100644 index 000000000..6a43decab --- /dev/null +++ b/webapp/src/apps.rs @@ -0,0 +1,37 @@ +// 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 . + +use std::collections::HashMap; +use page::{Page, PageHandler}; + +extern crate parity_wallet; + +pub type Pages = HashMap>; + +pub fn all_pages() -> Pages { + let mut pages = Pages::new(); + wallet_page(&mut pages); + pages +} + +#[cfg(feature="parity-wallet")] +fn wallet_page(pages: &mut Pages) { + pages.insert("wallet".to_owned(), Box::new(PageHandler { app: parity_wallet::App::default() })); +} + +#[cfg(not(feature="parity-wallet"))] +fn wallet_page(_pages: &mut Pages) {} + diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index 8fd20fdeb..7a3620e1a 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -26,16 +26,17 @@ extern crate jsonrpc_core; extern crate jsonrpc_http_server; extern crate ethcore_rpc as rpc; extern crate ethcore_util as util; +extern crate parity_webapp; -use rpc::v1::*; use std::sync::Arc; -use std::thread; - use util::panics::PanicHandler; -use iron::request::Url; use self::jsonrpc_core::{IoHandler, IoDelegate}; use jsonrpc_http_server::ServerHandler; +mod apps; +mod page; +mod router; + /// Http server. pub struct WebappServer { handler: Arc, @@ -44,117 +45,32 @@ pub struct WebappServer { impl WebappServer { /// Construct new http server object pub fn new() -> Self { - let server = WebappServer { + WebappServer { handler: Arc::new(IoHandler::new()), - }; - // TODO add more - server.add_delegate(Web3Client::new().to_delegate()); - - server + } } /// Add io delegate. - fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { + pub fn add_delegate(&self, delegate: IoDelegate) where D: Send + Sync + 'static { self.handler.add_delegate(delegate); } - /// Start server asynchronously in new thread and returns panic handler. + /// Start server asynchronously and returns panic handler. pub fn start_http(&self, addr: &str, threads: usize) -> Arc { let addr = addr.to_owned(); let panic_handler = PanicHandler::new_in_arc(); - let ph = panic_handler.clone(); let handler = self.handler.clone(); - thread::Builder::new().name("jsonrpc_http".to_string()).spawn(move || { - let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; - let rpc = ServerHandler::new(handler, cors_domain); - let router = Router::new(rpc); + let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; + let rpc = ServerHandler::new(handler, cors_domain); + let router = router::Router::new(rpc, apps::all_pages()); - ph.catch_panic(move || { - hyper::Server::http(addr.as_ref() as &str).unwrap() - .handle_threads(router, threads) - .unwrap(); - }).unwrap() - }).expect("Error while creating jsonrpc http thread"); + panic_handler.catch_panic(move || { + hyper::Server::http(addr.as_ref() as &str).unwrap() + .handle_threads(router, threads) + .unwrap(); + }).unwrap(); panic_handler } } - - -struct Router { - rpc: ServerHandler, - // admin: Page, - // wallet: Page, - // mist: Page, -} - -impl Router { - pub fn new(rpc: ServerHandler) -> Self { - Router { - rpc: rpc, - // admin: Page { app: AdminApp::default() }, - // wallet: Page { app: WalletApp::default() }, - // mist: Page { app: MistApp::default() }, - } - } - - fn extract_url(req: &hyper::server::Request) -> Option { - match req.uri { - hyper::uri::RequestUri::AbsoluteUri(ref url) => { - match Url::from_generic_url(url.clone()) { - Ok(url) => Some(url), - _ => None, - } - }, - hyper::uri::RequestUri::AbsolutePath(ref path) => { - // Attempt to prepend the Host header (mandatory in HTTP/1.1) - let url_string = match req.headers.get::() { - Some(ref host) => { - format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) - }, - None => return None - }; - - match Url::parse(&url_string) { - Ok(url) => Some(url), - _ => None, - } - } - _ => None, - } - } - - fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) { - let url = Router::extract_url(&req); - match url { - Some(url) => { - let part = url.path[0].clone(); - let url = url.path[1..].join("/"); - req.uri = hyper::uri::RequestUri::AbsolutePath(url); - (Some(part), req) - }, - None => { - (None, req) - } - } - } -} - -impl hyper::server::Handler for Router { - fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) { - let (path, req) = Router::extract_request_path(req); - match path { - // Some(ref url) if url == "admin" => { - // self.admin.handle(req, res); - // }, - // Some(ref url) if url == "wallet" => { - // self.wallet.handle(req, res); - // }, - // Some(ref url) if url == "mist" => { - // self.mist.handle(req, res); - // }, - _ => self.rpc.handle(req, res), - } - } -} diff --git a/webapp/src/page/mod.rs b/webapp/src/page/mod.rs new file mode 100644 index 000000000..94a25769e --- /dev/null +++ b/webapp/src/page/mod.rs @@ -0,0 +1,67 @@ +// 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 . + +use std::io::Write; +use hyper::uri::RequestUri; +use hyper::server; +use hyper::header; +use hyper::status::StatusCode; +use parity_webapp::WebApp; + +pub trait Page : Send + Sync { + fn serve_file(&self, mut path: &str, mut res: server::Response); +} + +pub struct PageHandler { + pub app: T +} + +impl Page for PageHandler { + fn serve_file(&self, mut path: &str, mut res: server::Response) { + // Support index file + if path == "" { + path = "index.html" + } + let file = self.app.file(path); + if let Some(f) = file { + *res.status_mut() = StatusCode::Ok; + res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap())); + + let _ = match res.start() { + Ok(mut raw_res) => { + for chunk in f.content.chunks(1024 * 20) { + let _ = raw_res.write(chunk); + } + raw_res.end() + }, + Err(_) => { + println!("Error while writing response."); + Ok(()) + } + }; + } + } +} + +impl server::Handler for Page { + fn handle(&self, req: server::Request, mut res: server::Response) { + *res.status_mut() = StatusCode::NotFound; + + if let RequestUri::AbsolutePath(ref path) = req.uri { + self.serve_file(path, res); + } + } +} diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs new file mode 100644 index 000000000..4ed32fab9 --- /dev/null +++ b/webapp/src/router/mod.rs @@ -0,0 +1,89 @@ +// 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 . + +//! Router implementation + +use hyper; +use apps::Pages; +use iron::request::Url; +use jsonrpc_http_server::ServerHandler; + +pub struct Router { + rpc: ServerHandler, + pages: Pages, +} + +impl hyper::server::Handler for Router { + fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) { + let (path, req) = Router::extract_request_path(req); + match path { + Some(ref url) if self.pages.contains_key(url) => { + self.pages.get(url).unwrap().handle(req, res); + } + _ => self.rpc.handle(req, res), + } + } +} + +impl Router { + pub fn new(rpc: ServerHandler, pages: Pages) -> Self { + Router { + rpc: rpc, + pages: pages, + } + } + + fn extract_url(req: &hyper::server::Request) -> Option { + match req.uri { + hyper::uri::RequestUri::AbsoluteUri(ref url) => { + match Url::from_generic_url(url.clone()) { + Ok(url) => Some(url), + _ => None, + } + }, + hyper::uri::RequestUri::AbsolutePath(ref path) => { + // Attempt to prepend the Host header (mandatory in HTTP/1.1) + let url_string = match req.headers.get::() { + Some(ref host) => { + format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) + }, + None => return None + }; + + match Url::parse(&url_string) { + Ok(url) => Some(url), + _ => None, + } + } + _ => None, + } + } + + fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) { + let url = Router::extract_url(&req); + match url { + Some(url) => { + let part = url.path[0].clone(); + let url = url.path[1..].join("/"); + req.uri = hyper::uri::RequestUri::AbsolutePath(url); + (Some(part), req) + }, + None => { + (None, req) + } + } + } +} From da05aa51fefe34c4356d4e26fa9021d6b3f83f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:12:02 +0200 Subject: [PATCH 15/41] Adding all APIs to webapp rpc server --- parity/main.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/parity/main.rs b/parity/main.rs index b8498d520..0e82ea4d3 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -303,12 +303,20 @@ fn setup_rpc_server( } #[cfg(feature = "webapp")] fn setup_webapp_server( + client: Arc, + sync: Arc, + secret_store: Arc, + miner: Arc, url: &str ) -> Option> { use rpc::v1::*; let server = webapp::WebappServer::new(); server.add_delegate(Web3Client::new().to_delegate()); + server.add_delegate(NetClient::new(&sync).to_delegate()); + server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); + server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); + server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); Some(server.start_http(url, ::num_cpus::get())) } @@ -327,6 +335,10 @@ fn setup_rpc_server( #[cfg(not(feature = "webapp"))] fn setup_webapp_server( + _client: Arc, + _sync: Arc, + _secret_store: Arc, + _miner: Arc, _url: &str ) -> Option> { None @@ -637,6 +649,10 @@ impl Configuration { if self.args.flag_webapp { let url = format!("0.0.0.0:{}", self.args.flag_webapp_port); setup_webapp_server( + service.client(), + sync.clone(), + account_service.clone(), + miner.clone(), &url, ).map(|handler| { panic_handler.forward_from(handler.deref()); From 1e9e0c32faf5987717a34a875fd778218592860e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:13:06 +0200 Subject: [PATCH 16/41] Disabling webapp server by default --- Cargo.toml | 2 +- parity/main.rs | 29 +++++++++++++---------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fe923db7..276159f84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ version = "0.8" default-features = false [features] -default = ["rpc", "webapp"] +default = ["rpc"] rpc = ["ethcore-rpc"] webapp = ["ethcore-webapp"] dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev", diff --git a/parity/main.rs b/parity/main.rs index 0e82ea4d3..088a61550 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -281,7 +281,7 @@ fn setup_rpc_server( url: &str, cors_domain: &str, apis: Vec<&str> -) -> Option> { +) -> Arc { use rpc::v1::*; let server = rpc::RpcServer::new(); @@ -299,7 +299,7 @@ fn setup_rpc_server( } } } - Some(server.start_http(url, cors_domain, ::num_cpus::get())) + server.start_http(url, cors_domain, ::num_cpus::get()) } #[cfg(feature = "webapp")] fn setup_webapp_server( @@ -308,7 +308,7 @@ fn setup_webapp_server( secret_store: Arc, miner: Arc, url: &str -) -> Option> { +) -> Arc { use rpc::v1::*; let server = webapp::WebappServer::new(); @@ -317,7 +317,7 @@ fn setup_webapp_server( server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); - Some(server.start_http(url, ::num_cpus::get())) + server.start_http(url, ::num_cpus::get()) } #[cfg(not(feature = "rpc"))] @@ -329,8 +329,8 @@ fn setup_rpc_server( _url: &str, _cors_domain: &str, _apis: Vec<&str> -) -> Option> { - None +) -> Arc { + die!("Your Parity version has been compiled without JSON-RPC support.") } #[cfg(not(feature = "webapp"))] @@ -340,8 +340,8 @@ fn setup_webapp_server( _secret_store: Arc, _miner: Arc, _url: &str -) -> Option> { - None +) -> Arc { + die!("Your Parity version has been compiled without WebApps support.") } fn print_version() { @@ -632,7 +632,7 @@ impl Configuration { 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( + let handler = setup_rpc_server( service.client(), sync.clone(), account_service.clone(), @@ -641,22 +641,19 @@ impl Configuration { cors, apis.split(',').collect() ); - if let Some(handler) = server_handler { - panic_handler.forward_from(handler.deref()); - } + panic_handler.forward_from(handler.deref()); } if self.args.flag_webapp { let url = format!("0.0.0.0:{}", self.args.flag_webapp_port); - setup_webapp_server( + let handler = setup_webapp_server( service.client(), sync.clone(), account_service.clone(), miner.clone(), &url, - ).map(|handler| { - panic_handler.forward_from(handler.deref()); - }); + ); + panic_handler.forward_from(handler.deref()); } // Register IO handler From 91f1f4c174ab3216f4ec77bdfb64d47c2eff9611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:20:35 +0200 Subject: [PATCH 17/41] Changing default setup to be safer for now --- parity/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity/main.rs b/parity/main.rs index 088a61550..c047e5eb3 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -645,7 +645,7 @@ impl Configuration { } if self.args.flag_webapp { - let url = format!("0.0.0.0:{}", self.args.flag_webapp_port); + let url = format!("127.0.0.1:{}", self.args.flag_webapp_port); let handler = setup_webapp_server( service.client(), sync.clone(), From ccd417f713b793399d8325216642fb0ccc7d2840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:27:54 +0200 Subject: [PATCH 18/41] Reverting order of shutdown event --- util/src/io/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/src/io/service.rs b/util/src/io/service.rs index 24cc1181a..95aa19e47 100644 --- a/util/src/io/service.rs +++ b/util/src/io/service.rs @@ -231,8 +231,8 @@ impl Handler for IoManager where Message: Send + Clone + Sync fn notify(&mut self, event_loop: &mut EventLoop, msg: Self::Message) { match msg { IoMessage::Shutdown => { - event_loop.shutdown(); self.workers.clear(); + event_loop.shutdown(); }, IoMessage::AddHandler { handler } => { let handler_id = { From d1e3c633e5de6e88b9596415361d2dc8f2b5e310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:49:20 +0200 Subject: [PATCH 19/41] Fixing compilation with rpc feature disabled --- parity/main.rs | 55 ++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 06710a3a4..11f9dd91d 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -43,6 +43,7 @@ extern crate rpassword; #[cfg(feature = "rpc")] extern crate ethcore_rpc as rpc; +use std::any::Any; use std::io::{BufRead, BufReader}; use std::fs::File; use std::net::{SocketAddr, IpAddr}; @@ -270,8 +271,10 @@ fn setup_rpc_server( sync: Arc, secret_store: Arc, miner: Arc, + url: &str, + cors_domain: &str, apis: Vec<&str> -) -> Option { +) -> Option> { use rpc::v1::*; let server = rpc::RpcServer::new(); @@ -289,7 +292,12 @@ fn setup_rpc_server( } } } - Some(server) + let start_result = server.start_http(url, cors_domain, ::num_cpus::get()); + match start_result { + Err(rpc::RpcServerError::IoError(err)) => die_with_io_error(err), + Err(e) => die!("{:?}", e), + Ok(handle) => Some(Box::new(handle)), + } } #[cfg(not(feature = "rpc"))] @@ -298,9 +306,11 @@ fn setup_rpc_server( _sync: Arc, _secret_store: Arc, _miner: Arc, + _url: &str, + _cors_domain: &str, _apis: Vec<&str> -) -> Option> { - None +) -> ! { + die!("Your Parity version has been compiled without JSON-RPC support.") } fn print_version() { @@ -582,20 +592,7 @@ impl Configuration { // Setup rpc let rpc_server = if self.args.flag_jsonrpc || self.args.flag_rpc { - // TODO: use this as the API list. let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis); - setup_rpc_server( - service.client(), - sync.clone(), - account_service.clone(), - miner.clone(), - apis.split(',').collect() - ) - } else { - None - }; - - let rpc_handle = rpc_server.map(|server| { let url = format!("{}:{}", match self.args.flag_rpcaddr.as_ref().unwrap_or(&self.args.flag_jsonrpc_interface).as_str() { "all" => "0.0.0.0", @@ -606,13 +603,19 @@ impl Configuration { ); SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url)); let cors_domain = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors); - let start_result = server.start_http(&url, cors_domain, ::num_cpus::get()); - match start_result { - Ok(handle) => handle, - Err(rpc::RpcServerError::IoError(err)) => die_with_io_error(err), - Err(e) => die!("{:?}", e), - } - }); + + setup_rpc_server( + service.client(), + sync.clone(), + account_service.clone(), + miner.clone(), + &url, + &cors_domain, + apis.split(',').collect() + ) + } else { + None + }; // Register IO handler let io_handler = Arc::new(ClientIoHandler { @@ -624,11 +627,11 @@ impl Configuration { service.io().register_handler(io_handler).expect("Error registering IO handler"); // Handle exit - wait_for_exit(panic_handler, rpc_handle); + wait_for_exit(panic_handler, rpc_server); } } -fn wait_for_exit(panic_handler: Arc, _rpc_handle: Option) { +fn wait_for_exit(panic_handler: Arc, _rpc_server: Option>) { let exit = Arc::new(Condvar::new()); // Handle possible exits From 2adeb9fe8800f8edb6f54bff03658f1cb9d9cbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 12:55:06 +0200 Subject: [PATCH 20/41] Removing Option from setup_rpc_server method return type --- parity/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 11f9dd91d..011d761ef 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -274,7 +274,7 @@ fn setup_rpc_server( url: &str, cors_domain: &str, apis: Vec<&str> -) -> Option> { +) -> Box { use rpc::v1::*; let server = rpc::RpcServer::new(); @@ -296,7 +296,7 @@ fn setup_rpc_server( match start_result { Err(rpc::RpcServerError::IoError(err)) => die_with_io_error(err), Err(e) => die!("{:?}", e), - Ok(handle) => Some(Box::new(handle)), + Ok(handle) => Box::new(handle), } } @@ -604,7 +604,7 @@ impl Configuration { SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url)); let cors_domain = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors); - setup_rpc_server( + Some(setup_rpc_server( service.client(), sync.clone(), account_service.clone(), @@ -612,7 +612,7 @@ impl Configuration { &url, &cors_domain, apis.split(',').collect() - ) + )) } else { None }; From 6279237c86abe721da278f189da7c32d5ae48f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 13:15:59 +0200 Subject: [PATCH 21/41] Adding documentation --- webapp/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index 6150a2b39..104a8ce45 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -83,7 +83,9 @@ impl Drop for Listening { /// Webapp Server startup error #[derive(Debug)] pub enum WebappServerError { + /// Wrapped `std::io::Error` IoError(std::io::Error), + /// Other `hyper` error Other(hyper::error::Error), } From 8074fee28c806857f7b604f3562f1a3e8f6bfecb Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 7 Apr 2016 14:24:52 +0200 Subject: [PATCH 22/41] Use new json RPC server --- Cargo.lock | 60 ++++++++++++++++++++++++++++++++++++++++++++++---- parity/main.rs | 20 ++++++++++------- rpc/Cargo.toml | 2 +- rpc/src/lib.rs | 12 +++++----- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7554e3b52..71b6873f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,7 +246,7 @@ dependencies = [ "ethminer 1.1.0", "ethsync 1.1.0", "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git)", + "jsonrpc-http-server 5.0.0 (git+https://github.com/debris/jsonrpc-http-server.git)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -415,6 +415,27 @@ dependencies = [ "url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hyper" +version = "0.9.0-mio" +source = "git+https://github.com/hyperium/hyper?branch=mio#d55a70dc56dac1f0f03bc4c3a83db0314d48e69a" +dependencies = [ + "cookie 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "igd" version = "0.4.2" @@ -453,10 +474,10 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" -version = "4.0.0" -source = "git+https://github.com/tomusdrw/jsonrpc-http-server.git#46bd4e7cf8352e0efc940cf76d3dff99f1a3da15" +version = "5.0.0" +source = "git+https://github.com/debris/jsonrpc-http-server.git#76fa443982b40665721fe6b1ece42fc0a53be996" dependencies = [ - "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.0-mio (git+https://github.com/hyperium/hyper?branch=mio)", "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -696,6 +717,11 @@ dependencies = [ "syntex_syntax 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quick-error" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "quine-mc_cluskey" version = "0.2.2" @@ -745,6 +771,18 @@ dependencies = [ "librocksdb-sys 0.2.3 (git+https://github.com/arkpar/rust-rocksdb.git)", ] +[[package]] +name = "rotor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rpassword" version = "0.1.3" @@ -1000,6 +1038,15 @@ dependencies = [ "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "vecio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vergen" version = "0.1.0" @@ -1009,6 +1056,11 @@ dependencies = [ "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "void" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.6" diff --git a/parity/main.rs b/parity/main.rs index 011d761ef..192f88132 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -43,7 +43,6 @@ extern crate rpassword; #[cfg(feature = "rpc")] extern crate ethcore_rpc as rpc; -use std::any::Any; use std::io::{BufRead, BufReader}; use std::fs::File; use std::net::{SocketAddr, IpAddr}; @@ -64,6 +63,8 @@ use ethminer::{Miner, MinerService}; use docopt::Docopt; use daemonize::Daemonize; use number_prefix::{binary_prefix, Standalone, Prefixed}; +#[cfg(feature = "rpc")] +use rpc::Server as RpcServer; mod price_info; @@ -271,10 +272,10 @@ fn setup_rpc_server( sync: Arc, secret_store: Arc, miner: Arc, - url: &str, + url: &SocketAddr, cors_domain: &str, apis: Vec<&str> -) -> Box { +) -> RpcServer { use rpc::v1::*; let server = rpc::RpcServer::new(); @@ -292,14 +293,17 @@ fn setup_rpc_server( } } } - let start_result = server.start_http(url, cors_domain, ::num_cpus::get()); + let start_result = server.start_http(url, cors_domain); match start_result { Err(rpc::RpcServerError::IoError(err)) => die_with_io_error(err), Err(e) => die!("{:?}", e), - Ok(handle) => Box::new(handle), + Ok(server) => server, } } +#[cfg(not(feature = "rpc"))] +struct RpcServer; + #[cfg(not(feature = "rpc"))] fn setup_rpc_server( _client: Arc, @@ -601,7 +605,7 @@ impl Configuration { }, 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 addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url)); let cors_domain = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors); Some(setup_rpc_server( @@ -609,7 +613,7 @@ impl Configuration { sync.clone(), account_service.clone(), miner.clone(), - &url, + &addr, &cors_domain, apis.split(',').collect() )) @@ -631,7 +635,7 @@ impl Configuration { } } -fn wait_for_exit(panic_handler: Arc, _rpc_server: Option>) { +fn wait_for_exit(panic_handler: Arc, _rpc_server: Option) { let exit = Arc::new(Condvar::new()); // Handle possible exits diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 4c62a45c1..183b0fa9f 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -13,7 +13,7 @@ log = "0.3" serde = "0.7.0" serde_json = "0.7.0" jsonrpc-core = "2.0" -jsonrpc-http-server = { git = "https://github.com/tomusdrw/jsonrpc-http-server.git" } +jsonrpc-http-server = { git = "https://github.com/debris/jsonrpc-http-server.git" } ethcore-util = { path = "../util" } ethcore = { path = "../ethcore" } ethash = { path = "../ethash" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 4de405211..f059750d2 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -33,9 +33,10 @@ extern crate ethminer; extern crate transient_hashmap; use std::sync::Arc; +use std::net::SocketAddr; use self::jsonrpc_core::{IoHandler, IoDelegate}; -pub use jsonrpc_http_server::{Listening, RpcServerError}; +pub use jsonrpc_http_server::{Server, RpcServerError}; pub mod v1; /// Http server. @@ -56,12 +57,9 @@ impl RpcServer { self.handler.add_delegate(delegate); } - /// Start server asynchronously and returns result with `Listening` handle on success or an error. - pub fn start_http(&self, addr: &str, cors_domain: &str, threads: usize) -> Result { - let addr = addr.to_owned(); + /// Start server asynchronously and returns result with `Server` handle on success or an error. + pub fn start_http(&self, addr: &SocketAddr, cors_domain: &str) -> Result { let cors_domain = cors_domain.to_owned(); - let server = jsonrpc_http_server::Server::new(self.handler.clone()); - - server.start(addr.as_ref(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain), threads) + Server::start(addr, self.handler.clone(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain)) } } From ed633bd0b701412129f471c5fd433f83cff0dc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 15:14:39 +0200 Subject: [PATCH 23/41] Adding StatusPage and main page support --- Cargo.lock | 9 +++++++++ webapp/Cargo.toml | 1 + webapp/src/apps.rs | 10 +++++++--- webapp/src/lib.rs | 2 +- webapp/src/router/mod.rs | 19 +++++++++++++++---- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af361da49..feda5db77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-status 0.1.1 (git+https://github.com/tomusdrw/parity-status.git)", "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", ] @@ -693,6 +694,14 @@ name = "odds" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "parity-status" +version = "0.1.1" +source = "git+https://github.com/tomusdrw/parity-status.git#31cdfd631af970b58c4ddb4e7d7e927af2e07ce3" +dependencies = [ + "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", +] + [[package]] name = "parity-wallet" version = "0.1.0" diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index 377b0b556..b377bb991 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -17,6 +17,7 @@ ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" } # List of apps +parity-status = { git = "https://github.com/tomusdrw/parity-status.git" } parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true } clippy = { version = "0.0.61", optional = true} diff --git a/webapp/src/apps.rs b/webapp/src/apps.rs index 6a43decab..364186d04 100644 --- a/webapp/src/apps.rs +++ b/webapp/src/apps.rs @@ -17,21 +17,25 @@ use std::collections::HashMap; use page::{Page, PageHandler}; +extern crate parity_status; extern crate parity_wallet; pub type Pages = HashMap>; +pub fn main_page() -> Box { + Box::new(PageHandler { app: parity_status::App::default() }) +} + pub fn all_pages() -> Pages { let mut pages = Pages::new(); wallet_page(&mut pages); pages } -#[cfg(feature="parity-wallet")] +#[cfg(feature = "parity-wallet")] fn wallet_page(pages: &mut Pages) { pages.insert("wallet".to_owned(), Box::new(PageHandler { app: parity_wallet::App::default() })); } -#[cfg(not(feature="parity-wallet"))] +#[cfg(not(feature = "parity-wallet"))] fn wallet_page(_pages: &mut Pages) {} - diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index 104a8ce45..d8e524be8 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -60,7 +60,7 @@ impl WebappServer { let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; let rpc = ServerHandler::new(handler, cors_domain); - let router = router::Router::new(rpc, apps::all_pages()); + let router = router::Router::new(rpc, apps::main_page(), apps::all_pages()); try!(hyper::Server::http(addr.as_ref() as &str)) .handle_threads(router, threads) diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index 4ed32fab9..fad8593b9 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -17,12 +17,14 @@ //! Router implementation use hyper; +use page::Page; use apps::Pages; use iron::request::Url; use jsonrpc_http_server::ServerHandler; pub struct Router { rpc: ServerHandler, + main_page: Box, pages: Pages, } @@ -33,15 +35,19 @@ impl hyper::server::Handler for Router { Some(ref url) if self.pages.contains_key(url) => { self.pages.get(url).unwrap().handle(req, res); } - _ => self.rpc.handle(req, res), + _ if req.method == hyper::method::Method::Post => { + self.rpc.handle(req, res) + }, + _ => self.main_page.handle(req, res), } } } impl Router { - pub fn new(rpc: ServerHandler, pages: Pages) -> Self { + pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self { Router { rpc: rpc, + main_page: main_page, pages: pages, } } @@ -75,13 +81,18 @@ impl Router { fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) { let url = Router::extract_url(&req); match url { - Some(url) => { + Some(ref url) if url.path.len() > 1 => { let part = url.path[0].clone(); let url = url.path[1..].join("/"); req.uri = hyper::uri::RequestUri::AbsolutePath(url); (Some(part), req) }, - None => { + Some(url) => { + let url = url.path.join("/"); + req.uri = hyper::uri::RequestUri::AbsolutePath(url); + (None, req) + }, + _ => { (None, req) } } From 4569c251275e8858bf56c109c5be68d24be95260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 15:59:45 +0200 Subject: [PATCH 24/41] Specifying webapp interface --- parity/main.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/parity/main.rs b/parity/main.rs index 062d62244..945e0bdad 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -136,6 +136,9 @@ API and Console Options: -w --webapp Enable the web applications server (e.g. status page). --webapp-port PORT Specify the port portion of the WebApps server [default: 8080]. + --webapp-interface IP Specify the hostname portion of the WebApps + server, IP should be an interface's IP address, or + all (all interfaces) or local [default: local]. Sealing/Mining Options: @@ -223,6 +226,7 @@ struct Args { flag_jsonrpc_apis: String, flag_webapp: bool, flag_webapp_port: u16, + flag_webapp_interface: String, flag_author: String, flag_usd_per_tx: String, flag_usd_per_eth: String, @@ -662,7 +666,14 @@ impl Configuration { }; let webapp_server = if self.args.flag_webapp { - let url = format!("127.0.0.1:{}", self.args.flag_webapp_port); + let url = format!("{}:{}", + match self.args.flag_webapp_interface.as_str() { + "all" => "0.0.0.0", + "local" => "127.0.0.1", + x => x, + }, + self.args.flag_webapp_port + ); Some(setup_webapp_server( service.client(), sync.clone(), From b7c790d7410ccfebaece73e4f1b29f7c7d43d66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 16:22:02 +0200 Subject: [PATCH 25/41] Disabling rpc until we switch to async hyper --- Cargo.lock | 2 +- parity/main.rs | 6 +++--- webapp/Cargo.toml | 2 +- webapp/src/router/mod.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4c4f5970..f6ce8941d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,7 +319,7 @@ dependencies = [ "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git)", + "jsonrpc-http-server 5.0.0 (git+https://github.com/debris/jsonrpc-http-server.git)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-status 0.1.1 (git+https://github.com/tomusdrw/parity-status.git)", "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", diff --git a/parity/main.rs b/parity/main.rs index 8dcd0c067..7ea8282f3 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -67,7 +67,7 @@ use daemonize::Daemonize; use number_prefix::{binary_prefix, Standalone, Prefixed}; #[cfg(feature = "rpc")] use rpc::Server as RpcServer; -use webapp::WebappServer; +use webapp::Listening as WebappServer; mod price_info; @@ -324,7 +324,7 @@ fn setup_webapp_server( ) -> WebappServer { use rpc::v1::*; - let server = WebappServer::new(); + let server = webapp::WebappServer::new(); server.add_delegate(Web3Client::new().to_delegate()); server.add_delegate(NetClient::new(&sync).to_delegate()); server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); @@ -334,7 +334,7 @@ fn setup_webapp_server( match start_result { Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err), Err(e) => die!("{:?}", e), - Ok(handle) => Box::new(handle), + Ok(handle) => handle, } } diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index b377bb991..28df2b8f8 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -10,7 +10,7 @@ authors = ["Ethcore { - self.rpc.handle(req, res) + // self.rpc.handle(req, res) }, _ => self.main_page.handle(req, res), } From 9bd41761fcd8750d64029b757661ce7a5734b590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 16:30:49 +0200 Subject: [PATCH 26/41] Reverting back to old-hyper version of rpc --- Cargo.lock | 12 +++++++++++- parity/main.rs | 1 + webapp/Cargo.toml | 2 +- webapp/src/router/mod.rs | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6ce8941d..d702bd4fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,7 +319,7 @@ dependencies = [ "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 5.0.0 (git+https://github.com/debris/jsonrpc-http-server.git)", + "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "parity-status 0.1.1 (git+https://github.com/tomusdrw/parity-status.git)", "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", @@ -524,6 +524,16 @@ dependencies = [ "syntex 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jsonrpc-http-server" +version = "4.0.0" +source = "git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper#46bd4e7cf8352e0efc940cf76d3dff99f1a3da15" +dependencies = [ + "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "jsonrpc-http-server" version = "5.0.0" diff --git a/parity/main.rs b/parity/main.rs index 7ea8282f3..b9f95eee3 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -67,6 +67,7 @@ use daemonize::Daemonize; use number_prefix::{binary_prefix, Standalone, Prefixed}; #[cfg(feature = "rpc")] use rpc::Server as RpcServer; +#[cfg(feature = "webapp")] use webapp::Listening as WebappServer; mod price_info; diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index 28df2b8f8..b63c04bf5 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -10,7 +10,7 @@ authors = ["Ethcore { - // self.rpc.handle(req, res) + self.rpc.handle(req, res) }, _ => self.main_page.handle(req, res), } From 1852f1b462657995157a1598a72346ff31fe2ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 18:22:53 +0200 Subject: [PATCH 27/41] REST-API PoC --- webapp/src/router/api.rs | 53 ++++++++++++++++++++++++++++++++++++++++ webapp/src/router/mod.rs | 13 ++++++++-- 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 webapp/src/router/api.rs diff --git a/webapp/src/router/api.rs b/webapp/src/router/api.rs new file mode 100644 index 000000000..1044af407 --- /dev/null +++ b/webapp/src/router/api.rs @@ -0,0 +1,53 @@ +// 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 . + +//! Simple REST API + +use std::sync::Arc; +use hyper; +use hyper::status::StatusCode; +use hyper::header; +use hyper::uri::RequestUri::AbsolutePath as Path; +use apps::Pages; + +pub struct RestApi { + pub pages: Arc, +} + +impl RestApi { + fn list_pages(&self) -> String { + let mut s = "[".to_owned(); + for name in self.pages.keys() { + s.push_str(&format!("\"{}\",", name)); + } + s.push_str("\"rpc\""); + s.push_str("]"); + s + } +} + +impl hyper::server::Handler for RestApi { + fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, mut res: hyper::server::Response<'a>) { + match req.uri { + Path(ref path) if path == "apps" => { + *res.status_mut() = StatusCode::Ok; + res.headers_mut().set(header::ContentType("application/json".parse().unwrap())); + let _ = res.send(self.list_pages().as_bytes()); + }, + _ => () + } + } +} diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index fad8593b9..f0399aba8 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -16,16 +16,20 @@ //! Router implementation +use std::sync::Arc; use hyper; use page::Page; use apps::Pages; use iron::request::Url; use jsonrpc_http_server::ServerHandler; +mod api; + pub struct Router { rpc: ServerHandler, + api: api::RestApi, main_page: Box, - pages: Pages, + pages: Arc, } impl hyper::server::Handler for Router { @@ -34,7 +38,10 @@ impl hyper::server::Handler for Router { match path { Some(ref url) if self.pages.contains_key(url) => { self.pages.get(url).unwrap().handle(req, res); - } + }, + Some(ref url) if url == "api" => { + self.api.handle(req, res); + }, _ if req.method == hyper::method::Method::Post => { self.rpc.handle(req, res) }, @@ -45,8 +52,10 @@ impl hyper::server::Handler for Router { impl Router { pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self { + let pages = Arc::new(pages); Router { rpc: rpc, + api: api::RestApi { pages: pages.clone() }, main_page: main_page, pages: pages, } From 6395095659e88a26c3bc2c58e6d80e44b18a36be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 7 Apr 2016 18:42:51 +0200 Subject: [PATCH 28/41] Updating version of parity-status --- Cargo.lock | 6 +++--- webapp/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d702bd4fc..210b23666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,7 +321,7 @@ dependencies = [ "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-status 0.1.1 (git+https://github.com/tomusdrw/parity-status.git)", + "parity-status 0.1.4 (git+https://github.com/tomusdrw/parity-status.git)", "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", ] @@ -727,8 +727,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-status" -version = "0.1.1" -source = "git+https://github.com/tomusdrw/parity-status.git#31cdfd631af970b58c4ddb4e7d7e927af2e07ce3" +version = "0.1.4" +source = "git+https://github.com/tomusdrw/parity-status.git#380d13c8aafc3847a731968a6532edb09c78f2cf" dependencies = [ "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", ] diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index b63c04bf5..59452f32d 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -17,7 +17,7 @@ ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" } # List of apps -parity-status = { git = "https://github.com/tomusdrw/parity-status.git" } +parity-status = { git = "https://github.com/tomusdrw/parity-status.git", version = "0.1.4" } parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true } clippy = { version = "0.0.61", optional = true} From f129126a8216a1804e697c6467500fa9db516dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 8 Apr 2016 10:13:42 +0200 Subject: [PATCH 29/41] Adding couple of missing commas --- webapp/src/lib.rs | 2 +- webapp/src/page/mod.rs | 4 ++-- webapp/src/router/api.rs | 2 +- webapp/src/router/mod.rs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index d8e524be8..35ebb4a44 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -93,7 +93,7 @@ impl From for WebappServerError { fn from(err: hyper::error::Error) -> Self { match err { hyper::error::Error::Io(e) => WebappServerError::IoError(e), - e => WebappServerError::Other(e) + e => WebappServerError::Other(e), } } } diff --git a/webapp/src/page/mod.rs b/webapp/src/page/mod.rs index 94a25769e..ce7b32947 100644 --- a/webapp/src/page/mod.rs +++ b/webapp/src/page/mod.rs @@ -26,7 +26,7 @@ pub trait Page : Send + Sync { } pub struct PageHandler { - pub app: T + pub app: T, } impl Page for PageHandler { @@ -50,7 +50,7 @@ impl Page for PageHandler { Err(_) => { println!("Error while writing response."); Ok(()) - } + }, }; } } diff --git a/webapp/src/router/api.rs b/webapp/src/router/api.rs index 1044af407..046b75957 100644 --- a/webapp/src/router/api.rs +++ b/webapp/src/router/api.rs @@ -47,7 +47,7 @@ impl hyper::server::Handler for RestApi { res.headers_mut().set(header::ContentType("application/json".parse().unwrap())); let _ = res.send(self.list_pages().as_bytes()); }, - _ => () + _ => (), } } } diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index f0399aba8..bd0d2ff18 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -75,14 +75,14 @@ impl Router { Some(ref host) => { format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) }, - None => return None + None => return None, }; match Url::parse(&url_string) { Ok(url) => Some(url), _ => None, } - } + }, _ => None, } } @@ -103,7 +103,7 @@ impl Router { }, _ => { (None, req) - } + }, } } } From 508daad011b8c580b9ee8b9c6ea2b0262cc762c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 8 Apr 2016 11:18:46 +0200 Subject: [PATCH 30/41] Enabling webapps by default --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 276159f84..3fe923db7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ version = "0.8" default-features = false [features] -default = ["rpc"] +default = ["rpc", "webapp"] rpc = ["ethcore-rpc"] webapp = ["ethcore-webapp"] dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev", From 8f16515d824c039889a25be64d032e27e76c2669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 8 Apr 2016 15:25:20 +0200 Subject: [PATCH 31/41] HTTP Authorization support in router --- Cargo.lock | 6 +- webapp/Cargo.toml | 2 +- webapp/src/router/auth.rs | 120 ++++++++++++++++++++++++++++++++++++++ webapp/src/router/mod.rs | 56 ++++++++++-------- 4 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 webapp/src/router/auth.rs diff --git a/Cargo.lock b/Cargo.lock index 210b23666..f0f64af94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,7 +321,7 @@ dependencies = [ "jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-status 0.1.4 (git+https://github.com/tomusdrw/parity-status.git)", + "parity-status 0.1.5 (git+https://github.com/tomusdrw/parity-status.git)", "parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)", "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", ] @@ -727,8 +727,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-status" -version = "0.1.4" -source = "git+https://github.com/tomusdrw/parity-status.git#380d13c8aafc3847a731968a6532edb09c78f2cf" +version = "0.1.5" +source = "git+https://github.com/tomusdrw/parity-status.git#6a075228e9248055a37c55dec41461856f5a9f19" dependencies = [ "parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)", ] diff --git a/webapp/Cargo.toml b/webapp/Cargo.toml index 59452f32d..126c5fbfb 100644 --- a/webapp/Cargo.toml +++ b/webapp/Cargo.toml @@ -17,7 +17,7 @@ ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" } # List of apps -parity-status = { git = "https://github.com/tomusdrw/parity-status.git", version = "0.1.4" } +parity-status = { git = "https://github.com/tomusdrw/parity-status.git", version = "0.1.5" } parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true } clippy = { version = "0.0.61", optional = true} diff --git a/webapp/src/router/auth.rs b/webapp/src/router/auth.rs new file mode 100644 index 000000000..96a27f189 --- /dev/null +++ b/webapp/src/router/auth.rs @@ -0,0 +1,120 @@ +// 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 Authorization implementations + +use std::collections::HashMap; +use hyper::{header, server}; +use hyper::status::StatusCode; + +/// Authorization result +pub enum Authorized<'a, 'b> where 'b : 'a { + /// Authorization was successful. Request and Response are returned for further processing. + Yes(server::Request<'a, 'b>, server::Response<'a>), + /// Unsuccessful authorization. Request and Response has been consumed. + No, +} + +/// Authorization interface +pub trait Authorization { + /// Handle authorization process and return `Request` and `Response` when authorization is successful. + fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b>; +} + +/// HTTP Basic Authorization handler +pub struct HttpBasicAuth { + users: HashMap, +} + +/// No-authorization implementation (authorization disabled) +pub struct NoAuth; + +impl Authorization for NoAuth { + fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> { + Authorized::Yes(req, res) + } +} + +impl Authorization for HttpBasicAuth { + + fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> { + let auth = self.check_auth(&req); + + match auth { + Access::Denied => { + self.respond_with_unauthorized(res); + Authorized::No + }, + Access::AuthRequired => { + self.respond_with_auth_required(res); + Authorized::No + }, + Access::Granted => { + Authorized::Yes(req, res) + }, + } + } +} + +enum Access { + Granted, + Denied, + AuthRequired, +} + +impl HttpBasicAuth { + /// Creates `HttpBasicAuth` instance with only one user. + pub fn single_user(username: &str, password: &str) -> Self { + let mut users = HashMap::new(); + users.insert(username.to_owned(), password.to_owned()); + HttpBasicAuth { + users: users + } + } + + fn is_authorized(&self, username: &str, password: &str) -> bool { + self.users.get(&username.to_owned()).map_or(false, |pass| pass == password) + } + + fn check_auth(&self, req: &server::Request) -> Access { + match req.headers.get::>() { + Some(&header::Authorization(header::Basic { ref username, password: Some(ref password) })) => { + if self.is_authorized(username, password) { + Access::Granted + } else { + Access::Denied + } + }, + Some(&header::Authorization(header::Basic { username: _, password: None })) => { + Access::Denied + }, + None => { + Access::AuthRequired + }, + } + } + + fn respond_with_unauthorized(&self, mut res: server::Response) { + *res.status_mut() = StatusCode::Unauthorized; + let _ = res.send(b"Unauthorized"); + } + + fn respond_with_auth_required(&self, mut res: server::Response) { + *res.status_mut() = StatusCode::Unauthorized; + res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]); + } +} + diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index bd0d2ff18..a545d81c0 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -15,37 +15,46 @@ // along with Parity. If not, see . //! Router implementation +//! Processes request handling authorization and dispatching it to proper application. + +mod api; +mod auth; use std::sync::Arc; use hyper; +use hyper::{server, uri, header}; use page::Page; use apps::Pages; use iron::request::Url; use jsonrpc_http_server::ServerHandler; - -mod api; +use self::auth::{Authorization, NoAuth, Authorized}; pub struct Router { + auth: NoAuth, rpc: ServerHandler, api: api::RestApi, main_page: Box, pages: Arc, } -impl hyper::server::Handler for Router { - fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, res: hyper::server::Response<'a>) { - let (path, req) = Router::extract_request_path(req); - match path { - Some(ref url) if self.pages.contains_key(url) => { - self.pages.get(url).unwrap().handle(req, res); - }, - Some(ref url) if url == "api" => { - self.api.handle(req, res); - }, - _ if req.method == hyper::method::Method::Post => { - self.rpc.handle(req, res) - }, - _ => self.main_page.handle(req, res), +impl server::Handler for Router { + fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>) { + let auth = self.auth.handle(req, res); + + if let Authorized::Yes(req, res) = auth { + let (path, req) = Router::extract_request_path(req); + match path { + Some(ref url) if self.pages.contains_key(url) => { + self.pages.get(url).unwrap().handle(req, res); + }, + Some(ref url) if url == "api" => { + self.api.handle(req, res); + }, + _ if req.method == hyper::method::Method::Post => { + self.rpc.handle(req, res) + }, + _ => self.main_page.handle(req, res), + } } } } @@ -54,6 +63,7 @@ impl Router { pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self { let pages = Arc::new(pages); Router { + auth: NoAuth, rpc: rpc, api: api::RestApi { pages: pages.clone() }, main_page: main_page, @@ -61,17 +71,17 @@ impl Router { } } - fn extract_url(req: &hyper::server::Request) -> Option { + fn extract_url(req: &server::Request) -> Option { match req.uri { - hyper::uri::RequestUri::AbsoluteUri(ref url) => { + uri::RequestUri::AbsoluteUri(ref url) => { match Url::from_generic_url(url.clone()) { Ok(url) => Some(url), _ => None, } }, - hyper::uri::RequestUri::AbsolutePath(ref path) => { + uri::RequestUri::AbsolutePath(ref path) => { // Attempt to prepend the Host header (mandatory in HTTP/1.1) - let url_string = match req.headers.get::() { + let url_string = match req.headers.get::() { Some(ref host) => { format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) }, @@ -87,18 +97,18 @@ impl Router { } } - fn extract_request_path<'a, 'b>(mut req: hyper::server::Request<'a, 'b>) -> (Option, hyper::server::Request<'a, 'b>) { + fn extract_request_path<'a, 'b>(mut req: server::Request<'a, 'b>) -> (Option, server::Request<'a, 'b>) { let url = Router::extract_url(&req); match url { Some(ref url) if url.path.len() > 1 => { let part = url.path[0].clone(); let url = url.path[1..].join("/"); - req.uri = hyper::uri::RequestUri::AbsolutePath(url); + req.uri = uri::RequestUri::AbsolutePath(url); (Some(part), req) }, Some(url) => { let url = url.path.join("/"); - req.uri = hyper::uri::RequestUri::AbsolutePath(url); + req.uri = uri::RequestUri::AbsolutePath(url); (None, req) }, _ => { From dab54cf2a7c65eab060f529207d59b63bc4dfc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 8 Apr 2016 16:11:58 +0200 Subject: [PATCH 32/41] HTTP Basic Authorization for WebApps server. --- parity/main.rs | 43 ++++++++++++++++++++++++++++++++------- webapp/src/lib.rs | 19 ++++++++++++++--- webapp/src/router/auth.rs | 2 +- webapp/src/router/mod.rs | 30 +++++++++++++++------------ 4 files changed, 70 insertions(+), 24 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index b9f95eee3..d9382e645 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -136,13 +136,19 @@ API and Console Options: interface. APIS is a comma-delimited list of API name. Possible name are web3, eth and net. [default: web3,eth,net,personal]. - -w --webapp Enable the web applications server (e.g. status page). + -w --webapp Enable the web applications server (e.g. + status page). --webapp-port PORT Specify the port portion of the WebApps server [default: 8080]. --webapp-interface IP Specify the hostname portion of the WebApps server, IP should be an interface's IP address, or all (all interfaces) or local [default: local]. - + --webapp-user USERNAME Specify username for WebApps server. It will be + used in HTTP Basic Authentication Scheme. + If --webapp-pass is not specified you will be + asked for password on startup. + --webapp-pass PASSWORD Specify password for WebApps server. Use only in + conjunction with --webapp-user. Sealing/Mining Options: --usd-per-tx USD Amount of USD to be paid for a basic transaction @@ -230,6 +236,8 @@ struct Args { flag_webapp: bool, flag_webapp_port: u16, flag_webapp_interface: String, + flag_webapp_user: Option, + flag_webapp_pass: Option, flag_author: String, flag_usd_per_tx: String, flag_usd_per_eth: String, @@ -288,7 +296,7 @@ fn setup_rpc_server( miner: Arc, url: &SocketAddr, cors_domain: &str, - apis: Vec<&str> + apis: Vec<&str>, ) -> RpcServer { use rpc::v1::*; @@ -321,7 +329,8 @@ fn setup_webapp_server( sync: Arc, secret_store: Arc, miner: Arc, - url: &str + url: &str, + auth: Option<(String, String)>, ) -> WebappServer { use rpc::v1::*; @@ -331,7 +340,14 @@ fn setup_webapp_server( server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); - let start_result = server.start_http(url, ::num_cpus::get()); + let start_result = match auth { + None => { + server.start_unsecure_http(url, ::num_cpus::get()) + }, + Some((username, password)) => { + server.start_basic_auth_http(url, ::num_cpus::get(), &username, &password) + }, + }; match start_result { Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err), Err(e) => die!("{:?}", e), @@ -351,7 +367,7 @@ fn setup_rpc_server( _miner: Arc, _url: &str, _cors_domain: &str, - _apis: Vec<&str> + _apis: Vec<&str>, ) -> ! { die!("Your Parity version has been compiled without JSON-RPC support.") } @@ -365,7 +381,8 @@ fn setup_webapp_server( _sync: Arc, _secret_store: Arc, _miner: Arc, - _url: &str + _url: &str, + _auth: Option<(String, String)>, ) -> ! { die!("Your Parity version has been compiled without WebApps support.") } @@ -683,12 +700,24 @@ impl Configuration { }, self.args.flag_webapp_port ); + let auth = self.args.flag_webapp_user.as_ref().map(|username| { + let password = self.args.flag_webapp_pass.as_ref().map_or_else(|| { + use rpassword::read_password; + println!("Type password for WebApps server (user: {}): ", username); + let pass = read_password().unwrap(); + println!("OK, got it. Starting server..."); + pass + }, |pass| pass.to_owned()); + (username.to_owned(), password) + }); + Some(setup_webapp_server( service.client(), sync.clone(), account_service.clone(), miner.clone(), &url, + auth, )) } else { None diff --git a/webapp/src/lib.rs b/webapp/src/lib.rs index 35ebb4a44..ed9a13967 100644 --- a/webapp/src/lib.rs +++ b/webapp/src/lib.rs @@ -35,6 +35,8 @@ mod apps; mod page; mod router; +use router::auth::{Authorization, NoAuth, HttpBasicAuth}; + /// Http server. pub struct WebappServer { handler: Arc, @@ -53,14 +55,25 @@ impl WebappServer { self.handler.add_delegate(delegate); } - /// Start server asynchronously and returns result with `Listening` handle on success or an error. - pub fn start_http(&self, addr: &str, threads: usize) -> Result { + /// Asynchronously start server with no authentication, + /// return result with `Listening` handle on success or an error. + pub fn start_unsecure_http(&self, addr: &str, threads: usize) -> Result { + self.start_http(addr, threads, NoAuth) + } + + /// Asynchronously start server with `HTTP Basic Authentication`, + /// return result with `Listening` handle on success or an error. + pub fn start_basic_auth_http(&self, addr: &str, threads: usize, username: &str, password: &str) -> Result { + self.start_http(addr, threads, HttpBasicAuth::single_user(username, password)) + } + + fn start_http(&self, addr: &str, threads: usize, authorization: A) -> Result { let addr = addr.to_owned(); let handler = self.handler.clone(); let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null; let rpc = ServerHandler::new(handler, cors_domain); - let router = router::Router::new(rpc, apps::main_page(), apps::all_pages()); + let router = router::Router::new(rpc, apps::main_page(), apps::all_pages(), authorization); try!(hyper::Server::http(addr.as_ref() as &str)) .handle_threads(router, threads) diff --git a/webapp/src/router/auth.rs b/webapp/src/router/auth.rs index 96a27f189..6122b9309 100644 --- a/webapp/src/router/auth.rs +++ b/webapp/src/router/auth.rs @@ -29,7 +29,7 @@ pub enum Authorized<'a, 'b> where 'b : 'a { } /// Authorization interface -pub trait Authorization { +pub trait Authorization : Send + Sync { /// Handle authorization process and return `Request` and `Response` when authorization is successful. fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b>; } diff --git a/webapp/src/router/mod.rs b/webapp/src/router/mod.rs index a545d81c0..070f94a34 100644 --- a/webapp/src/router/mod.rs +++ b/webapp/src/router/mod.rs @@ -18,7 +18,7 @@ //! Processes request handling authorization and dispatching it to proper application. mod api; -mod auth; +pub mod auth; use std::sync::Arc; use hyper; @@ -27,22 +27,22 @@ use page::Page; use apps::Pages; use iron::request::Url; use jsonrpc_http_server::ServerHandler; -use self::auth::{Authorization, NoAuth, Authorized}; +use self::auth::{Authorization, Authorized}; -pub struct Router { - auth: NoAuth, +pub struct Router { + authorization: A, rpc: ServerHandler, api: api::RestApi, main_page: Box, pages: Arc, } -impl server::Handler for Router { +impl server::Handler for Router { fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>) { - let auth = self.auth.handle(req, res); + let auth = self.authorization.handle(req, res); if let Authorized::Yes(req, res) = auth { - let (path, req) = Router::extract_request_path(req); + let (path, req) = self.extract_request_path(req); match path { Some(ref url) if self.pages.contains_key(url) => { self.pages.get(url).unwrap().handle(req, res); @@ -59,11 +59,15 @@ impl server::Handler for Router { } } -impl Router { - pub fn new(rpc: ServerHandler, main_page: Box, pages: Pages) -> Self { +impl Router { + pub fn new( + rpc: ServerHandler, + main_page: Box, + pages: Pages, + authorization: A) -> Self { let pages = Arc::new(pages); Router { - auth: NoAuth, + authorization: authorization, rpc: rpc, api: api::RestApi { pages: pages.clone() }, main_page: main_page, @@ -71,7 +75,7 @@ impl Router { } } - fn extract_url(req: &server::Request) -> Option { + fn extract_url(&self, req: &server::Request) -> Option { match req.uri { uri::RequestUri::AbsoluteUri(ref url) => { match Url::from_generic_url(url.clone()) { @@ -97,8 +101,8 @@ impl Router { } } - fn extract_request_path<'a, 'b>(mut req: server::Request<'a, 'b>) -> (Option, server::Request<'a, 'b>) { - let url = Router::extract_url(&req); + fn extract_request_path<'a, 'b>(&self, mut req: server::Request<'a, 'b>) -> (Option, server::Request<'a, 'b>) { + let url = self.extract_url(&req); match url { Some(ref url) if url.path.len() > 1 => { let part = url.path[0].clone(); From 0ef6de930fae162bfaf58d0dfbcc5b3fd6005fda Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 8 Apr 2016 17:42:17 -0700 Subject: [PATCH 33/41] Update account.rs --- ethcore/src/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs index 87f2a05be..f2942a7e1 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -138,7 +138,7 @@ impl Account { /// get someone who knows to call `note_code`. pub fn code(&self) -> Option<&[u8]> { match self.code_hash { - Some(c) if c == SHA3_EMPTY && self.code_cache.is_empty() => Some(&self.code_cache), + Some(c) if c == SHA3_EMPTY && self.code_cache.is_empty() => Some(&self.code_cache), Some(_) if !self.code_cache.is_empty() => Some(&self.code_cache), None => Some(&self.code_cache), _ => None, From 1d5b29fb48aa6e42d6fe82ae74ff65955bcc2a76 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 8 Apr 2016 17:51:20 -0700 Subject: [PATCH 34/41] Update auth.rs --- webapp/src/router/auth.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/webapp/src/router/auth.rs b/webapp/src/router/auth.rs index 6122b9309..95c558bef 100644 --- a/webapp/src/router/auth.rs +++ b/webapp/src/router/auth.rs @@ -91,19 +91,11 @@ impl HttpBasicAuth { fn check_auth(&self, req: &server::Request) -> Access { match req.headers.get::>() { - Some(&header::Authorization(header::Basic { ref username, password: Some(ref password) })) => { - if self.is_authorized(username, password) { - Access::Granted - } else { - Access::Denied - } - }, - Some(&header::Authorization(header::Basic { username: _, password: None })) => { - Access::Denied - }, - None => { - Access::AuthRequired - }, + Some(&header::Authorization( + header::Basic { ref username, password: Some(ref password) } + )) if self.is_authorized(username, password) => Access::Granted, + Some(_) => Access::Denied, + None => Access::AuthRequired, } } From 373284ca0aaa2efa44824ed983294d7e94f942e1 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Sat, 9 Apr 2016 19:20:35 +0200 Subject: [PATCH 35/41] spec loading cleanup (#858) * spec loading cleanup in progress * changed engine field in json spec * refactored engine params * polishing spec loading refactor * fixed compiling json tests * fixed compiling parity * removed warnings * removed commented out code * fixed failing test * bringing back removed TODO in spec. --- ethcore/res/ethereum/frontier.json | 29 ++- ethcore/res/ethereum/frontier_like_test.json | 29 ++- ethcore/res/ethereum/frontier_test.json | 29 ++- ethcore/res/ethereum/homestead_test.json | 29 ++- ethcore/res/ethereum/morden.json | 29 ++- ethcore/res/ethereum/olympic.json | 29 ++- ethcore/res/null_homestead_morden.json | 19 +- ethcore/res/null_morden.json | 19 +- ethcore/src/block.rs | 26 +- ethcore/src/block_queue.rs | 6 +- ethcore/src/builtin.rs | 84 +++---- ethcore/src/client/client.rs | 7 +- ethcore/src/engine.rs | 36 ++- ethcore/src/env_info.rs | 37 +-- ethcore/src/ethereum/ethash.rs | 155 ++++++------ ethcore/src/ethereum/mod.rs | 23 +- ethcore/src/externalities.rs | 6 +- ethcore/src/json_tests/executive.rs | 29 +-- ethcore/src/json_tests/state.rs | 6 +- ethcore/src/log_entry.rs | 12 - ethcore/src/null_engine.rs | 39 +-- ethcore/src/pod_account.rs | 18 +- ethcore/src/pod_state.rs | 62 +---- ethcore/src/service.rs | 2 +- ethcore/src/spec/genesis.rs | 32 +-- ethcore/src/spec/mod.rs | 1 + ethcore/src/spec/seal.rs | 81 +++++++ ethcore/src/spec/spec.rs | 240 ++++++------------- ethcore/src/state.rs | 8 +- ethcore/src/tests/client.rs | 3 +- ethcore/src/tests/helpers.rs | 65 ++--- ethcore/src/verification/verification.rs | 8 +- json/src/blockchain/blockchain.rs | 10 +- json/src/blockchain/state.rs | 2 +- json/src/spec/account.rs | 16 +- json/src/spec/builtin.rs | 18 +- json/src/spec/engine.rs | 63 +++++ json/src/spec/ethash.rs | 75 ++++++ json/src/spec/genesis.rs | 27 +-- json/src/spec/mod.rs | 10 +- json/src/spec/params.rs | 43 ++-- json/src/spec/seal.rs | 73 ++++++ json/src/spec/spec.rs | 63 +++-- json/src/spec/state.rs | 44 ++++ json/src/uint.rs | 17 +- parity/main.rs | 2 +- 46 files changed, 957 insertions(+), 704 deletions(-) create mode 100644 ethcore/src/spec/seal.rs create mode 100644 json/src/spec/engine.rs create mode 100644 json/src/spec/ethash.rs create mode 100644 json/src/spec/seal.rs create mode 100644 json/src/spec/state.rs diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 41e69d24d..a8a26b1c3 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -1,24 +1,33 @@ { "name": "Frontier/Homestead", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/frontier_like_test.json b/ethcore/res/ethereum/frontier_like_test.json index 376b369c4..fec7c4371 100644 --- a/ethcore/res/ethereum/frontier_like_test.json +++ b/ethcore/res/ethereum/frontier_like_test.json @@ -1,24 +1,33 @@ { "name": "Frontier (Test)", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/frontier_test.json b/ethcore/res/ethereum/frontier_test.json index 92e8f5877..4f0284d14 100644 --- a/ethcore/res/ethereum/frontier_test.json +++ b/ethcore/res/ethereum/frontier_test.json @@ -1,24 +1,33 @@ { "name": "Frontier (Test)", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0xffffffffffffffff", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/homestead_test.json b/ethcore/res/ethereum/homestead_test.json index 0f0e630dd..962592404 100644 --- a/ethcore/res/ethereum/homestead_test.json +++ b/ethcore/res/ethereum/homestead_test.json @@ -1,24 +1,33 @@ { "name": "Homestead (Test)", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": 0, "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "tieBreakingGas": false, - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", "networkID" : "0x1" }, "genesis": { - "nonce": "0x0000000000000042", + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/morden.json b/ethcore/res/ethereum/morden.json index 2cf4785db..0723e2e9d 100644 --- a/ethcore/res/ethereum/morden.json +++ b/ethcore/res/ethereum/morden.json @@ -1,24 +1,33 @@ { "name": "Morden", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar": "" + } + } + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/ethereum/olympic.json b/ethcore/res/ethereum/olympic.json index 0cc2e6a57..8ee9f1d66 100644 --- a/ethcore/res/ethereum/olympic.json +++ b/ethcore/res/ethereum/olympic.json @@ -1,24 +1,33 @@ { "name": "Olympic", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x08", + "blockReward": "0x14D1120D7B160000", + "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050" + } + } + }, "params": { "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0xffffffffffffffff", "maximumExtraDataSize": "0x0400", - "tieBreakingGas": false, "minGasLimit": "125000", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x08", - "blockReward": "0x14D1120D7B160000", - "registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050", "networkID" : "0x0" }, "genesis": { - "nonce": "0x000000000000002a", + "seal": { + "ethereum": { + "nonce": "0x000000000000002a", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, "difficulty": "0x20000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/null_homestead_morden.json b/ethcore/res/null_homestead_morden.json index abd3f4de9..145540853 100644 --- a/ethcore/res/null_homestead_morden.json +++ b/ethcore/res/null_homestead_morden.json @@ -1,24 +1,23 @@ { "name": "Morden", - "engineName": "NullEngine", + "engine": { + "Null": null + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/res/null_morden.json b/ethcore/res/null_morden.json index 86148d640..48631a7ce 100644 --- a/ethcore/res/null_morden.json +++ b/ethcore/res/null_morden.json @@ -1,24 +1,23 @@ { "name": "Morden", - "engineName": "NullEngine", + "engine": { + "Null": null + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index c5e1ca003..b8af3f733 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -497,16 +497,16 @@ mod tests { use tests::helpers::*; use super::*; use common::*; - use engine::*; #[test] fn open_block() { use spec::*; - let engine = Spec::new_test().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = Spec::new_test(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]); let b = b.close_and_lock(); @@ -516,19 +516,20 @@ mod tests { #[test] fn enact_block() { use spec::*; - let engine = Spec::new_test().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = Spec::new_test(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), x!(3141562), vec![]).close_and_lock().seal(engine.deref(), vec![]).unwrap(); let orig_bytes = b.rlp_bytes(); let orig_db = b.drain(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let e = enact_and_seal(&orig_bytes, engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()]).unwrap(); assert_eq!(e.rlp_bytes(), orig_bytes); @@ -541,12 +542,13 @@ mod tests { #[test] fn enact_block_with_uncle() { use spec::*; - let engine = Spec::new_test().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = Spec::new_test(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let mut open_block = OpenBlock::new(engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), x!(3141562), vec![]); let mut uncle1_header = Header::new(); uncle1_header.extra_data = b"uncle1".to_vec(); @@ -561,7 +563,7 @@ mod tests { let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let e = enact_and_seal(&orig_bytes, engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()]).unwrap(); let bytes = e.rlp_bytes(); diff --git a/ethcore/src/block_queue.rs b/ethcore/src/block_queue.rs index bc1da2f8f..bf11f5f1f 100644 --- a/ethcore/src/block_queue.rs +++ b/ethcore/src/block_queue.rs @@ -464,7 +464,7 @@ mod tests { fn get_test_queue() -> BlockQueue { let spec = get_test_spec(); - let engine = spec.to_engine().unwrap(); + let engine = spec.engine; BlockQueue::new(BlockQueueConfig::default(), Arc::new(engine), IoChannel::disconnected()) } @@ -472,7 +472,7 @@ mod tests { fn can_be_created() { // TODO better test let spec = Spec::new_test(); - let engine = spec.to_engine().unwrap(); + let engine = spec.engine; let _ = BlockQueue::new(BlockQueueConfig::default(), Arc::new(engine), IoChannel::disconnected()); } @@ -533,7 +533,7 @@ mod tests { #[test] fn test_mem_limit() { let spec = get_test_spec(); - let engine = spec.to_engine().unwrap(); + let engine = spec.engine; let mut config = BlockQueueConfig::default(); config.max_mem_use = super::MIN_MEM_LIMIT; // empty queue uses about 15000 let queue = BlockQueue::new(config, Arc::new(engine), IoChannel::disconnected()); diff --git a/ethcore/src/builtin.rs b/ethcore/src/builtin.rs index 2ee8e24b1..3c15dd1c2 100644 --- a/ethcore/src/builtin.rs +++ b/ethcore/src/builtin.rs @@ -18,6 +18,7 @@ use util::*; use crypto::sha2::Sha256; use crypto::ripemd160::Ripemd160; use crypto::digest::Digest; +use ethjson; /// Definition of a contract whose implementation is built-in. pub struct Builtin { @@ -46,13 +47,12 @@ impl Builtin { } /// Create a new object from a builtin-function name with a linear cost associated with input size. - pub fn from_named_linear(name: &str, base_cost: usize, word_cost: usize) -> Option { - new_builtin_exec(name).map(|b| { - let cost = Box::new(move|s: usize| -> U256 { - U256::from(base_cost) + U256::from(word_cost) * U256::from((s + 31) / 32) - }); - Self::new(cost, b) - }) + pub fn from_named_linear(name: &str, base_cost: usize, word_cost: usize) -> Builtin { + let cost = Box::new(move|s: usize| -> U256 { + U256::from(base_cost) + U256::from(word_cost) * U256::from((s + 31) / 32) + }); + + Self::new(cost, new_builtin_exec(name)) } /// Simple forwarder for cost. @@ -60,24 +60,15 @@ impl Builtin { /// Simple forwarder for execute. pub fn execute(&self, input: &[u8], output: &mut[u8]) { (*self.execute)(input, output); } +} - /// Create a builtin from JSON. - /// - /// JSON must be of the form `{ "name": "identity", "pricing": {"base": 10, "word": 20} }`. - pub fn from_json(json: &Json) -> Option { - // NICE: figure out a more convenient means of handing errors here. - if let Json::String(ref name) = json["name"] { - if let Json::Object(ref o) = json["pricing"] { - if let Json::Object(ref o) = o["linear"] { - if let Json::U64(ref word) = o["word"] { - if let Json::U64(ref base) = o["base"] { - return Self::from_named_linear(&name[..], *base as usize, *word as usize); - } - } - } +impl From for Builtin { + fn from(b: ethjson::spec::Builtin) -> Self { + match b.pricing { + ethjson::spec::Pricing::Linear(linear) => { + Self::from_named_linear(b.name.as_ref(), linear.base, linear.word) } } - None } } @@ -92,14 +83,14 @@ pub fn copy_to(src: &[u8], dest: &mut[u8]) { /// Create a new builtin executor according to `name`. /// TODO: turn in to a factory with dynamic registration. -pub fn new_builtin_exec(name: &str) -> Option> { +pub fn new_builtin_exec(name: &str) -> Box { match name { - "identity" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + "identity" => Box::new(move|input: &[u8], output: &mut[u8]| { for i in 0..min(input.len(), output.len()) { output[i] = input[i]; } - })), - "ecrecover" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + }), + "ecrecover" => Box::new(move|input: &[u8], output: &mut[u8]| { #[repr(packed)] #[derive(Debug)] struct InType { @@ -122,8 +113,8 @@ pub fn new_builtin_exec(name: &str) -> Option> { } } } - })), - "sha256" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + }), + "sha256" => Box::new(move|input: &[u8], output: &mut[u8]| { let mut sha = Sha256::new(); sha.input(input); if output.len() >= 32 { @@ -133,21 +124,23 @@ pub fn new_builtin_exec(name: &str) -> Option> { sha.result(ret.as_slice_mut()); copy_to(&ret, output); } - })), - "ripemd160" => Some(Box::new(move|input: &[u8], output: &mut[u8]| { + }), + "ripemd160" => Box::new(move|input: &[u8], output: &mut[u8]| { let mut sha = Ripemd160::new(); sha.input(input); let mut ret = H256::new(); sha.result(&mut ret.as_slice_mut()[12..32]); copy_to(&ret, output); - })), - _ => None + }), + _ => { + panic!("invalid builtin name {}", name); + } } } #[test] fn identity() { - let f = new_builtin_exec("identity").unwrap(); + let f = new_builtin_exec("identity"); let i = [0u8, 1, 2, 3]; let mut o2 = [255u8; 2]; @@ -167,7 +160,7 @@ fn identity() { #[test] fn sha256() { use rustc_serialize::hex::FromHex; - let f = new_builtin_exec("sha256").unwrap(); + let f = new_builtin_exec("sha256"); let i = [0u8; 0]; let mut o = [255u8; 32]; @@ -186,7 +179,7 @@ fn sha256() { #[test] fn ripemd160() { use rustc_serialize::hex::FromHex; - let f = new_builtin_exec("ripemd160").unwrap(); + let f = new_builtin_exec("ripemd160"); let i = [0u8; 0]; let mut o = [255u8; 32]; @@ -213,7 +206,7 @@ fn ecrecover() { let s = k.sign(&m).unwrap(); println!("Signed: {}", s);*/ - let f = new_builtin_exec("ecrecover").unwrap(); + let f = new_builtin_exec("ecrecover"); let i = FromHex::from_hex("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03").unwrap(); let mut o = [255u8; 32]; @@ -260,9 +253,15 @@ fn ecrecover() { assert_eq!(&o[..], &(FromHex::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap())[..]);*/ } +#[test] +#[should_panic] +fn from_unknown_linear() { + let _ = Builtin::from_named_linear("dw", 10, 20); +} + #[test] fn from_named_linear() { - let b = Builtin::from_named_linear("identity", 10, 20).unwrap(); + let b = Builtin::from_named_linear("identity", 10, 20); assert_eq!((*b.cost)(0), U256::from(10)); assert_eq!((*b.cost)(1), U256::from(30)); assert_eq!((*b.cost)(32), U256::from(30)); @@ -276,9 +275,14 @@ fn from_named_linear() { #[test] fn from_json() { - let text = r#"{"name": "identity", "pricing": {"linear": {"base": 10, "word": 20}}}"#; - let json = Json::from_str(text).unwrap(); - let b = Builtin::from_json(&json).unwrap(); + let b = Builtin::from(ethjson::spec::Builtin { + name: "identity".to_owned(), + pricing: ethjson::spec::Pricing::Linear(ethjson::spec::Linear { + base: 10, + word: 20, + }) + }); + assert_eq!((*b.cost)(0), U256::from(10)); assert_eq!((*b.cost)(1), U256::from(30)); assert_eq!((*b.cost)(32), U256::from(30)); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 554a839e1..781bea7fa 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -140,14 +140,15 @@ impl Client where V: Verifier { let mut state_path = path.to_path_buf(); state_path.push("state"); - let engine = Arc::new(try!(spec.to_engine())); let state_path_str = state_path.to_str().unwrap(); let mut state_db = journaldb::new(state_path_str, config.pruning); - if state_db.is_empty() && engine.spec().ensure_db_good(state_db.as_hashdb_mut()) { - state_db.commit(0, &engine.spec().genesis_header().hash(), None).expect("Error commiting genesis state to state DB"); + if state_db.is_empty() && spec.ensure_db_good(state_db.as_hashdb_mut()) { + state_db.commit(0, &spec.genesis_header().hash(), None).expect("Error commiting genesis state to state DB"); } + let engine = Arc::new(spec.engine); + let block_queue = BlockQueue::new(config.queue, engine.clone(), message_channel); let panic_handler = PanicHandler::new_in_arc(); panic_handler.forward_from(&block_queue); diff --git a/ethcore/src/engine.rs b/ethcore/src/engine.rs index 7698af529..74b9dff26 100644 --- a/ethcore/src/engine.rs +++ b/ethcore/src/engine.rs @@ -16,7 +16,7 @@ use common::*; use block::ExecutedBlock; -use spec::Spec; +use spec::CommonParams; use evm::Schedule; use evm::Factory; @@ -25,7 +25,7 @@ use evm::Factory; pub trait Engine : Sync + Send { /// The name of this engine. fn name(&self) -> &str; - /// The version of this engine. Should be of the form + /// The version of this engine. Should be of the form fn version(&self) -> SemanticVersion { SemanticVersion::new(0, 0, 0) } /// The number of additional header fields required for this engine. @@ -35,7 +35,7 @@ pub trait Engine : Sync + Send { fn extra_info(&self, _header: &Header) -> HashMap { HashMap::new() } /// Get the general parameters of the chain. - fn spec(&self) -> &Spec; + fn params(&self) -> &CommonParams; /// Get current EVM factory fn vm_factory(&self) -> &Factory; @@ -43,29 +43,33 @@ pub trait Engine : Sync + Send { /// Get the EVM schedule for the given `env_info`. fn schedule(&self, env_info: &EnvInfo) -> Schedule; + /// Builtin-contracts we would like to see in the chain. + /// (In principle these are just hints for the engine since that has the last word on them.) + fn builtins(&self) -> &BTreeMap; + /// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`. - fn maximum_extra_data_size(&self) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) } + fn maximum_extra_data_size(&self) -> usize { self.params().maximum_extra_data_size } /// Maximum number of uncles a block is allowed to declare. fn maximum_uncle_count(&self) -> usize { 2 } /// The number of generations back that uncles can be. fn maximum_uncle_age(&self) -> usize { 6 } /// The nonce with which accounts begin. - fn account_start_nonce(&self) -> U256 { decode(&self.spec().engine_params.get("accountStartNonce").unwrap()) } + fn account_start_nonce(&self) -> U256 { self.params().account_start_nonce } /// Block transformation functions, before the transactions. fn on_new_block(&self, _block: &mut ExecutedBlock) {} /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} - /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) + /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. fn verify_block_basic(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } - /// Phase 2 verification. Perform costly checks such as transaction signatures. `block` (the header's full block) + /// Phase 2 verification. Perform costly checks such as transaction signatures. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } - /// Phase 3 verification. Check block information against parent and uncles. `block` (the header's full block) + /// Phase 3 verification. Check block information against parent and uncles. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. fn verify_block_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) } @@ -94,23 +98,13 @@ pub trait Engine : Sync + Send { // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. /// Determine whether a particular address is a builtin contract. - fn is_builtin(&self, a: &Address) -> bool { self.spec().builtins.contains_key(a) } + fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) } /// Determine the code execution cost of the builtin contract with address `a`. /// Panics if `is_builtin(a)` is not true. - fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.spec().builtins.get(a).unwrap().cost(input.len()) } + fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.builtins().get(a).unwrap().cost(input.len()) } /// Execution the builtin contract `a` on `input` and return `output`. /// Panics if `is_builtin(a)` is not true. - fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.spec().builtins.get(a).unwrap().execute(input, output); } + fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.builtins().get(a).unwrap().execute(input, output); } // TODO: sealing stuff - though might want to leave this for later. - - /// Get a named parameter from the `spec()`'s `engine_params` item and convert to u64, or 0 if that fails. - fn u64_param(&self, name: &str) -> u64 { - self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a)) - } - - /// Get a named parameter from the `spec()`'s `engine_params` item and convert to U256, or 0 if that fails. - fn u256_param(&self, name: &str) -> U256 { - self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a)) - } } diff --git a/ethcore/src/env_info.rs b/ethcore/src/env_info.rs index 1a4e71c67..18b8af856 100644 --- a/ethcore/src/env_info.rs +++ b/ethcore/src/env_info.rs @@ -55,21 +55,6 @@ impl Default for EnvInfo { } } -impl FromJson for EnvInfo { - fn from_json(json: &Json) -> EnvInfo { - let current_number: u64 = xjson!(&json["currentNumber"]); - EnvInfo { - number: current_number, - author: xjson!(&json["currentCoinbase"]), - difficulty: xjson!(&json["currentDifficulty"]), - gas_limit: xjson!(&json["currentGasLimit"]), - timestamp: xjson!(&json["currentTimestamp"]), - last_hashes: (1..cmp::min(current_number + 1, 257)).map(|i| format!("{}", current_number - i).as_bytes().sha3()).collect(), - gas_used: x!(0), - } - } -} - impl From for EnvInfo { fn from(e: ethjson::vm::Env) -> Self { let number = e.number.into(); @@ -90,24 +75,20 @@ mod tests { extern crate rustc_serialize; use super::*; - use rustc_serialize::*; - use util::from_json::FromJson; use util::hash::*; + use util::numbers::U256; use std::str::FromStr; + use ethjson; #[test] fn it_serializes_form_json() { - let env_info = EnvInfo::from_json(&json::Json::from_str( -r#" - { - "currentCoinbase": "0x000000f00000000f000000000000f00000000f00", - "currentNumber": 1112339, - "currentDifficulty": 50000, - "currentGasLimit" : 40000, - "currentTimestamp" : 1100 - } -"# - ).unwrap()); + let env_info = EnvInfo::from(ethjson::vm::Env { + author: ethjson::hash::Address(Address::from_str("000000f00000000f000000000000f00000000f00").unwrap()), + number: ethjson::uint::Uint(U256::from(1_112_339)), + difficulty: ethjson::uint::Uint(U256::from(50_000)), + gas_limit: ethjson::uint::Uint(U256::from(40_000)), + timestamp: ethjson::uint::Uint(U256::from(1_100)) + }); assert_eq!(env_info.number, 1112339); assert_eq!(env_info.author, Address::from_str("000000f00000000f000000000000f00000000f00").unwrap()); diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index d53ab09de..9d4a87eed 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -19,42 +19,63 @@ extern crate ethash; use self::ethash::{quick_get_difficulty, EthashManager, H256 as EH256}; use common::*; use block::*; -use spec::*; +use spec::CommonParams; use engine::*; -use evm::Schedule; -use evm::Factory; +use evm::{Schedule, Factory}; +use ethjson; + +/// Ethash params. +#[derive(Debug, PartialEq)] +pub struct EthashParams { + /// Tie breaking gas. + pub tie_breaking_gas: bool, + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Minimum difficulty. + pub minimum_difficulty: U256, + /// Difficulty bound divisor. + pub difficulty_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// Block reward. + pub block_reward: U256, + /// Namereg contract address. + pub registrar: Address, +} + +impl From for EthashParams { + fn from(p: ethjson::spec::EthashParams) -> Self { + EthashParams { + tie_breaking_gas: p.tie_breaking_gas, + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + minimum_difficulty: p.minimum_difficulty.into(), + difficulty_bound_divisor: p.difficulty_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + block_reward: p.block_reward.into(), + registrar: p.registrar.into(), + } + } +} /// Engine using Ethash proof-of-work consensus algorithm, suitable for Ethereum /// mainnet chains in the Olympic, Frontier and Homestead eras. pub struct Ethash { - spec: Spec, + params: CommonParams, + ethash_params: EthashParams, + builtins: BTreeMap, pow: EthashManager, factory: Factory, - u64_params: RwLock>, - u256_params: RwLock>, } impl Ethash { - /// Create a new boxed instance of Ethash engine - pub fn new_boxed(spec: Spec) -> Box { - Box::new(Ethash { - spec: spec, - pow: EthashManager::new(), - // TODO [todr] should this return any specific factory? - factory: Factory::default(), - u64_params: RwLock::new(HashMap::new()), - u256_params: RwLock::new(HashMap::new()) - }) - } - - #[cfg(test)] - fn new_test(spec: Spec) -> Ethash { + /// Create a new instance of Ethash engine + pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap) -> Self { Ethash { - spec: spec, + params: params, + ethash_params: ethash_params, + builtins: builtins, pow: EthashManager::new(), factory: Factory::default(), - u64_params: RwLock::new(HashMap::new()), - u256_params: RwLock::new(HashMap::new()) } } } @@ -65,17 +86,23 @@ impl Engine for Ethash { // Two fields - mix fn seal_fields(&self) -> usize { 2 } + fn params(&self) -> &CommonParams { &self.params } + + fn builtins(&self) -> &BTreeMap { + &self.builtins + } + /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, _header: &Header) -> HashMap { HashMap::new() } - fn spec(&self) -> &Spec { &self.spec } fn vm_factory(&self) -> &Factory { &self.factory } fn schedule(&self, env_info: &EnvInfo) -> Schedule { - trace!(target: "client", "Creating schedule. param={:?}, fCML={}", self.spec().engine_params.get("frontierCompatibilityModeLimit"), self.u64_param("frontierCompatibilityModeLimit")); - if env_info.number < self.u64_param("frontierCompatibilityModeLimit") { + trace!(target: "client", "Creating schedule. fCML={}", self.params.frontier_compatibility_mode_limit); + + if env_info.number < self.params.frontier_compatibility_mode_limit { Schedule::new_frontier() } else { Schedule::new_homestead() @@ -86,7 +113,7 @@ impl Engine for Ethash { header.difficulty = self.calculate_difficuty(header, parent); header.gas_limit = { let gas_limit = parent.gas_limit; - let bound_divisor = self.u256_param("gasLimitBoundDivisor"); + let bound_divisor = self.ethash_params.gas_limit_bound_divisor; if gas_limit < gas_floor_target { min(gas_floor_target, gas_limit + gas_limit / bound_divisor - x!(1)) } else { @@ -100,7 +127,7 @@ impl Engine for Ethash { /// Apply the block reward on finalisation of the block. /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current). fn on_close_block(&self, block: &mut ExecutedBlock) { - let reward = self.spec().engine_params.get("blockReward").map_or(U256::from(0u64), |a| decode(&a)); + let reward = self.ethash_params.block_reward; let fields = block.fields_mut(); // Bestow block reward @@ -125,7 +152,7 @@ impl Engine for Ethash { try!(UntrustedRlp::new(&header.seal[1]).as_val::()); // TODO: consider removing these lines. - let min_difficulty = decode(self.spec().engine_params.get("minimumDifficulty").unwrap()); + let min_difficulty = self.ethash_params.minimum_difficulty; if header.difficulty < min_difficulty { return Err(From::from(BlockError::DifficultyOutOfBounds(OutOfBounds { min: Some(min_difficulty), max: None, found: header.difficulty }))) } @@ -170,7 +197,7 @@ impl Engine for Ethash { if header.difficulty != expected_difficulty { return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: expected_difficulty, found: header.difficulty }))) } - let gas_limit_divisor = decode(self.spec().engine_params.get("gasLimitBoundDivisor").unwrap()); + let gas_limit_divisor = self.ethash_params.gas_limit_bound_divisor; let min_gas = parent.gas_limit - parent.gas_limit / gas_limit_divisor; let max_gas = parent.gas_limit + parent.gas_limit / gas_limit_divisor; if header.gas_limit <= min_gas || header.gas_limit >= max_gas { @@ -180,7 +207,7 @@ impl Engine for Ethash { } fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> { - if header.number() >= self.u64_param("frontierCompatibilityModeLimit") { + if header.number() >= self.params.frontier_compatibility_mode_limit { try!(t.check_low_s()); } Ok(()) @@ -189,16 +216,6 @@ impl Engine for Ethash { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } - - fn u64_param(&self, name: &str) -> u64 { - *self.u64_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| - self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a))) - } - - fn u256_param(&self, name: &str) -> U256 { - *self.u256_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| - self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a))) - } } #[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self @@ -209,10 +226,11 @@ impl Ethash { panic!("Can't calculate genesis block difficulty"); } - let min_difficulty = self.u256_param("minimumDifficulty"); - let difficulty_bound_divisor = self.u256_param("difficultyBoundDivisor"); - let duration_limit = self.u64_param("durationLimit"); - let frontier_limit = self.u64_param("frontierCompatibilityModeLimit"); + let min_difficulty = self.ethash_params.minimum_difficulty; + let difficulty_bound_divisor = self.ethash_params.difficulty_bound_divisor; + let duration_limit = self.ethash_params.duration_limit; + let frontier_limit = self.params.frontier_compatibility_mode_limit; + let mut target = if header.number < frontier_limit { if header.timestamp >= parent.timestamp + duration_limit { parent.difficulty - (parent.difficulty / difficulty_bound_divisor) @@ -283,16 +301,16 @@ mod tests { use block::*; use engine::*; use tests::helpers::*; - use super::{Ethash}; use super::super::new_morden; #[test] fn on_close_block() { - let engine = new_morden().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = new_morden(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]); let b = b.close(); @@ -301,11 +319,12 @@ mod tests { #[test] fn on_close_block_with_uncle() { - let engine = new_morden().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = new_morden(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let mut b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]); let mut uncle = Header::new(); @@ -320,27 +339,20 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = Ethash::new_boxed(new_morden()); + let engine = new_morden().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } - #[test] - fn can_return_params() { - let engine = Ethash::new_test(new_morden()); - assert!(engine.u64_param("durationLimit") > 0); - assert!(engine.u256_param("minimumDifficulty") > U256::zero()); - } - #[test] fn can_return_factory() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; engine.vm_factory(); } #[test] fn can_return_schedule() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: x!(0), @@ -368,7 +380,8 @@ mod tests { #[test] fn can_do_seal_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; + //let engine = Ethash::new_test(new_morden()); let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -382,7 +395,7 @@ mod tests { #[test] fn can_do_difficulty_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]); @@ -397,7 +410,7 @@ mod tests { #[test] fn can_do_proof_of_work_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]); header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap()); @@ -413,7 +426,7 @@ mod tests { #[test] fn can_do_seal_unordered_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_unordered(&header, None); @@ -427,7 +440,7 @@ mod tests { #[test] fn can_do_seal256_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]); let verify_result = engine.verify_block_unordered(&header, None); @@ -441,7 +454,7 @@ mod tests { #[test] fn can_do_proof_of_work_unordered_verification_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&H256::from("b251bd2e0283d0658f2cadfdc8ca619b5de94eca5742725e2e757dd13ed7503d")).to_vec(), rlp::encode(&H64::zero()).to_vec()]); header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap()); @@ -457,7 +470,7 @@ mod tests { #[test] fn can_verify_block_family_genesis_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let header: Header = Header::default(); let parent_header: Header = Header::default(); @@ -472,7 +485,7 @@ mod tests { #[test] fn can_verify_block_family_difficulty_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_number(2); let mut parent_header: Header = Header::default(); @@ -489,7 +502,7 @@ mod tests { #[test] fn can_verify_block_family_gas_fail() { - let engine = Ethash::new_test(new_morden()); + let engine = new_morden().engine; let mut header: Header = Header::default(); header.set_number(2); header.set_difficulty(U256::from_str("0000000000000000000000000000000000000000000000000000000000020000").unwrap()); diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 8c2ae6b37..4cc98ee2b 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -30,22 +30,22 @@ pub use self::denominations::*; use super::spec::*; /// Create a new Olympic chain spec. -pub fn new_olympic() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/olympic.json")) } +pub fn new_olympic() -> Spec { Spec::load(include_bytes!("../../res/ethereum/olympic.json")) } /// Create a new Frontier mainnet chain spec. -pub fn new_frontier() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier.json")) } +pub fn new_frontier() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier.json")) } /// Create a new Frontier chain spec as though it never changes to Homestead. -pub fn new_frontier_test() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier_test.json")) } +pub fn new_frontier_test() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier_test.json")) } /// Create a new Homestead chain spec as though it never changed from Frontier. -pub fn new_homestead_test() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/homestead_test.json")) } +pub fn new_homestead_test() -> Spec { Spec::load(include_bytes!("../../res/ethereum/homestead_test.json")) } /// Create a new Frontier main net chain spec without genesis accounts. -pub fn new_mainnet_like() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier_like_test.json")) } +pub fn new_mainnet_like() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier_like_test.json")) } /// Create a new Morden chain spec. -pub fn new_morden() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/morden.json")) } +pub fn new_morden() -> Spec { Spec::load(include_bytes!("../../res/ethereum/morden.json")) } #[cfg(test)] mod tests { @@ -57,11 +57,12 @@ mod tests { #[test] fn ensure_db_good() { - let engine = new_morden().to_engine().unwrap(); - let genesis_header = engine.spec().genesis_header(); + let spec = new_morden(); + let engine = &spec.engine; + let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); - engine.spec().ensure_db_good(db.as_hashdb_mut()); + spec.ensure_db_good(db.as_hashdb_mut()); let s = State::from_existing(db, genesis_header.state_root.clone(), engine.account_start_nonce()); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000001")), U256::from(1u64)); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000002")), U256::from(1u64)); @@ -79,7 +80,7 @@ mod tests { let genesis = morden.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap()); - let _ = morden.to_engine(); + let _ = morden.engine; } #[test] @@ -90,6 +91,6 @@ mod tests { let genesis = frontier.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3").unwrap()); - let _ = frontier.to_engine(); + let _ = frontier.engine; } } diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 3f9d4ff08..24b5fe3a4 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -208,7 +208,7 @@ impl<'a> Ext for Externalities<'a> { }, OutputPolicy::Return(BytesRef::Flexible(ref mut vec), ref mut copy) => { handle_copy(copy); - + vec.clear(); vec.reserve(data.len()); unsafe { @@ -225,7 +225,7 @@ impl<'a> Ext for Externalities<'a> { false => Ok(*gas) } } - + handle_copy(copy); let mut code = vec![]; @@ -327,7 +327,7 @@ mod tests { fn new() -> Self { TestSetup { state: get_temp_state(), - engine: get_test_spec().to_engine().unwrap(), + engine: get_test_spec().engine, sub_state: Substate::new(false), env_info: get_test_env_info() } diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 283394204..bd971efdc 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -27,33 +27,6 @@ use substate::*; use tests::helpers::*; use ethjson; -struct TestEngineFrontier { - vm_factory: Factory, - spec: Spec, - max_depth: usize -} - -impl TestEngineFrontier { - fn new(max_depth: usize, vm_type: VMType) -> TestEngineFrontier { - TestEngineFrontier { - vm_factory: Factory::new(vm_type), - spec: ethereum::new_frontier_test(), - max_depth: max_depth - } - } -} - -impl Engine for TestEngineFrontier { - fn name(&self) -> &str { "TestEngine" } - fn spec(&self) -> &Spec { &self.spec } - fn vm_factory(&self) -> &Factory { &self.vm_factory } - fn schedule(&self, _env_info: &EnvInfo) -> Schedule { - let mut schedule = Schedule::new_frontier(); - schedule.max_depth = self.max_depth; - schedule - } -} - #[derive(Debug, PartialEq)] struct CallCreate { data: Bytes, @@ -206,7 +179,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { let mut state = state_result.reference_mut(); state.populate_from(From::from(vm.pre_state.clone())); let info = From::from(vm.env); - let engine = TestEngineFrontier::new(1, vm_type.clone()); + let engine = TestEngine::new(1, Factory::new(vm_type.clone())); let params = ActionParams::from(vm.transaction); let mut substate = Substate::new(false); diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 1ed7dcadb..cf7f56ce1 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -30,9 +30,9 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec { let tests = ethjson::state::Test::load(json_data).unwrap(); let mut failed = Vec::new(); let engine = match era { - ChainEra::Frontier => ethereum::new_mainnet_like(), - ChainEra::Homestead => ethereum::new_homestead_test(), - }.to_engine().unwrap(); + ChainEra::Frontier => ethereum::new_mainnet_like().engine, + ChainEra::Homestead => ethereum::new_homestead_test().engine + }; for (name, test) in tests.into_iter() { let mut fail = false; diff --git a/ethcore/src/log_entry.rs b/ethcore/src/log_entry.rs index e0443cc0d..2a7ca080f 100644 --- a/ethcore/src/log_entry.rs +++ b/ethcore/src/log_entry.rs @@ -76,18 +76,6 @@ impl From for LogEntry { } } -impl FromJson for LogEntry { - /// Convert given JSON object to a LogEntry. - fn from_json(json: &Json) -> LogEntry { - // TODO: check bloom. - LogEntry { - address: xjson!(&json["address"]), - topics: xjson!(&json["topics"]), - data: xjson!(&json["data"]), - } - } -} - /// Log localized in a blockchain. #[derive(Default, Debug, PartialEq, Clone)] pub struct LocalizedLogEntry { diff --git a/ethcore/src/null_engine.rs b/ethcore/src/null_engine.rs index 99231add4..505ff5bf6 100644 --- a/ethcore/src/null_engine.rs +++ b/ethcore/src/null_engine.rs @@ -14,26 +14,29 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::BTreeMap; +use util::hash::Address; +use builtin::Builtin; use engine::Engine; -use spec::Spec; -use evm::Schedule; -use evm::Factory; +use spec::CommonParams; +use evm::{Schedule, Factory}; use env_info::EnvInfo; /// An engine which does not provide any consensus mechanism. pub struct NullEngine { - spec: Spec, - factory: Factory + params: CommonParams, + builtins: BTreeMap, + factory: Factory, } impl NullEngine { /// Returns new instance of NullEngine with default VM Factory - pub fn new_boxed(spec: Spec) -> Box { - Box::new(NullEngine{ - spec: spec, - // TODO [todr] should this return any specific factory? + pub fn new(params: CommonParams, builtins: BTreeMap) -> Self { + NullEngine{ + params: params, + builtins: builtins, factory: Factory::default() - }) + } } } @@ -41,13 +44,21 @@ impl Engine for NullEngine { fn vm_factory(&self) -> &Factory { &self.factory } - - fn name(&self) -> &str { "NullEngine" } - fn spec(&self) -> &Spec { &self.spec } + fn name(&self) -> &str { + "NullEngine" + } + + fn params(&self) -> &CommonParams { + &self.params + } + + fn builtins(&self) -> &BTreeMap { + &self.builtins + } fn schedule(&self, env_info: &EnvInfo) -> Schedule { - if env_info.number < self.u64_param("frontierCompatibilityModeLimit") { + if env_info.number < self.params.frontier_compatibility_mode_limit { Schedule::new_frontier() } else { Schedule::new_homestead() diff --git a/ethcore/src/pod_account.rs b/ethcore/src/pod_account.rs index 623211ae6..27ddc8a97 100644 --- a/ethcore/src/pod_account.rs +++ b/ethcore/src/pod_account.rs @@ -80,12 +80,22 @@ impl From for PodAccount { balance: a.balance.into(), nonce: a.nonce.into(), code: a.code.into(), - storage: a.storage.into_iter().fold(BTreeMap::new(), |mut acc, (key, value)| { + storage: a.storage.into_iter().map(|(key, value)| { let key: U256 = key.into(); let value: U256 = value.into(); - acc.insert(H256::from(key), H256::from(value)); - acc - }) + (H256::from(key), H256::from(value)) + }).collect() + } + } +} + +impl From for PodAccount { + fn from(a: ethjson::spec::Account) -> Self { + PodAccount { + balance: a.balance.map_or_else(U256::zero, Into::into), + nonce: a.nonce.map_or_else(U256::zero, Into::into), + code: vec![], + storage: BTreeMap::new() } } } diff --git a/ethcore/src/pod_state.rs b/ethcore/src/pod_state.rs index 7ebfed78b..d9d0cf764 100644 --- a/ethcore/src/pod_state.rs +++ b/ethcore/src/pod_state.rs @@ -46,33 +46,20 @@ impl PodState { pub fn drain(self) -> BTreeMap { self.0 } } -impl FromJson for PodState { - /// Translate the JSON object into a hash map of account information ready for insertion into State. - fn from_json(json: &Json) -> PodState { - PodState(json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut state, (address, acc)| { - let balance = acc.find("balance").map(&U256::from_json); - let nonce = acc.find("nonce").map(&U256::from_json); - let storage = acc.find("storage").map(&BTreeMap::from_json); - let code = acc.find("code").map(&Bytes::from_json); - if balance.is_some() || nonce.is_some() || storage.is_some() || code.is_some() { - state.insert(address_from_hex(address), PodAccount{ - balance: balance.unwrap_or_else(U256::zero), - nonce: nonce.unwrap_or_else(U256::zero), - storage: storage.unwrap_or_else(BTreeMap::new), - code: code.unwrap_or_else(Vec::new) - }); - } - state - })) +impl From for PodState { + fn from(s: ethjson::blockchain::State) -> PodState { + let state = s.into_iter().map(|(addr, acc)| (addr.into(), PodAccount::from(acc))).collect(); + PodState(state) } } -impl From for PodState { - fn from(s: ethjson::blockchain::State) -> PodState { - PodState(s.0.into_iter().fold(BTreeMap::new(), |mut acc, (key, value)| { - acc.insert(key.into(), PodAccount::from(value)); - acc - })) +impl From for PodState { + fn from(s: ethjson::spec::State) -> PodState { + let state: BTreeMap<_,_> = s.into_iter() + .filter(|pair| !pair.1.is_empty()) + .map(|(addr, acc)| (addr.into(), PodAccount::from(acc))) + .collect(); + PodState(state) } } @@ -85,30 +72,3 @@ impl fmt::Display for PodState { } } -#[cfg(test)] -mod tests { - extern crate rustc_serialize; - - use super::*; - use rustc_serialize::*; - use util::from_json::FromJson; - use util::hash::*; - - #[test] - fn it_serializes_form_json() { - let pod_state = PodState::from_json(&json::Json::from_str( -r#" - { - "0000000000000000000000000000000000000000": { - "balance": "1000", - "nonce": "100", - "storage": {}, - "code" : [] - } - } -"# - ).unwrap()); - - assert!(pod_state.get().get(&ZERO_ADDRESS).is_some()); - } -} diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 95a891198..38bd873b8 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -60,7 +60,7 @@ impl ClientService { panic_handler.forward_from(&net_service); info!("Starting {}", net_service.host_info()); - info!("Configured for {} using {} engine", spec.name, spec.engine_name); + info!("Configured for {} using {:?} engine", spec.name, spec.engine.name()); let client = try!(Client::new(config, spec, db_path, net_service.io().channel())); panic_handler.forward_from(client.deref()); let client_io = Arc::new(ClientIoHandler { diff --git a/ethcore/src/spec/genesis.rs b/ethcore/src/spec/genesis.rs index 686e8f6d1..b6c214fd6 100644 --- a/ethcore/src/spec/genesis.rs +++ b/ethcore/src/spec/genesis.rs @@ -16,26 +16,9 @@ use util::rlp::*; use util::numbers::{Uint, U256}; -use util::hash::{H64, Address, H256}; +use util::hash::{Address, H256}; use ethjson; - -/// Genesis seal type. -pub enum Seal { - /// Classic ethereum seal. - Ethereum { - /// Seal nonce. - nonce: H64, - /// Seal mix hash. - mix_hash: H256, - }, - /// Generic seal. - Generic { - /// Number of seal fields. - fields: usize, - /// Seal rlp. - rlp: Vec, - }, -} +use super::seal::Seal; /// Genesis components. pub struct Genesis { @@ -66,16 +49,7 @@ pub struct Genesis { impl From for Genesis { fn from(g: ethjson::spec::Genesis) -> Self { Genesis { - seal: match (g.nonce, g.mix_hash) { - (Some(nonce), Some(mix_hash)) => Seal::Ethereum { - nonce: nonce.into(), - mix_hash: mix_hash.into(), - }, - _ => Seal::Generic { - fields: g.seal_fields.unwrap(), - rlp: g.seal_rlp.unwrap().into(), - } - }, + seal: From::from(g.seal), difficulty: g.difficulty.into(), author: g.author.into(), timestamp: g.timestamp.into(), diff --git a/ethcore/src/spec/mod.rs b/ethcore/src/spec/mod.rs index b85165d89..356f3b219 100644 --- a/ethcore/src/spec/mod.rs +++ b/ethcore/src/spec/mod.rs @@ -17,6 +17,7 @@ //! Blockchain params. mod genesis; +mod seal; pub mod spec; pub use self::spec::*; diff --git a/ethcore/src/spec/seal.rs b/ethcore/src/spec/seal.rs new file mode 100644 index 000000000..600de701a --- /dev/null +++ b/ethcore/src/spec/seal.rs @@ -0,0 +1,81 @@ +// 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 . + +//! Spec seal. + +use util::rlp::*; +use util::hash::{H64, H256}; +use ethjson; + +/// Classic ethereum seal. +pub struct Ethereum { + /// Seal nonce. + pub nonce: H64, + /// Seal mix hash. + pub mix_hash: H256, +} + +impl Into for Ethereum { + fn into(self) -> Generic { + let mut s = RlpStream::new(); + s.append(&self.mix_hash); + s.append(&self.nonce); + Generic { + fields: 2, + rlp: s.out() + } + } +} + +/// Generic seal. +pub struct Generic { + /// Number of seal fields. + pub fields: usize, + /// Seal rlp. + pub rlp: Vec, +} + +/// Genesis seal type. +pub enum Seal { + /// Classic ethereum seal. + Ethereum(Ethereum), + /// Generic seal. + Generic(Generic), +} + +impl From for Seal { + fn from(s: ethjson::spec::Seal) -> Self { + match s { + ethjson::spec::Seal::Ethereum(eth) => Seal::Ethereum(Ethereum { + nonce: eth.nonce.into(), + mix_hash: eth.mix_hash.into() + }), + ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic { + fields: g.fields, + rlp: g.rlp.into() + }) + } + } +} + +impl Into for Seal { + fn into(self) -> Generic { + match self { + Seal::Generic(generic) => generic, + Seal::Ethereum(eth) => eth.into() + } + } +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f97d8c8dc..353232ef6 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -21,55 +21,51 @@ use engine::*; use pod_state::*; use null_engine::*; use account_db::*; +use super::genesis::Genesis; +use super::seal::Generic as GenericSeal; use ethereum; -use super::genesis::{Seal as GenesisSeal, Genesis}; +use ethjson; -/// Convert JSON value to equivalent RLP representation. -// TODO: handle container types. -fn json_to_rlp(json: &Json) -> Bytes { - match *json { - Json::Boolean(o) => encode(&(if o {1u64} else {0})).to_vec(), - Json::I64(o) => encode(&(o as u64)).to_vec(), - Json::U64(o) => encode(&o).to_vec(), - Json::String(ref s) if s.len() >= 2 && &s[0..2] == "0x" && U256::from_str(&s[2..]).is_ok() => { - encode(&U256::from_str(&s[2..]).unwrap()).to_vec() - }, - Json::String(ref s) => { - encode(s).to_vec() - }, - _ => panic!() - } +/// Parameters common to all engines. +#[derive(Debug, PartialEq, Clone)] +pub struct CommonParams { + /// Account start nonce. + pub account_start_nonce: U256, + /// Frontier compatibility mode limit. + pub frontier_compatibility_mode_limit: u64, + /// Maximum size of extra data. + pub maximum_extra_data_size: usize, + /// Network id. + pub network_id: U256, + /// Minimum gas limit. + pub min_gas_limit: U256, } -/// Convert JSON to a string->RLP map. -fn json_to_rlp_map(json: &Json) -> HashMap { - json.as_object().unwrap().iter().map(|(k, v)| (k, json_to_rlp(v))).fold(HashMap::new(), |mut acc, kv| { - acc.insert(kv.0.clone(), kv.1); - acc - }) +impl From for CommonParams { + fn from(p: ethjson::spec::Params) -> Self { + CommonParams { + account_start_nonce: p.account_start_nonce.into(), + frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.into(), + maximum_extra_data_size: p.maximum_extra_data_size.into(), + network_id: p.network_id.into(), + min_gas_limit: p.min_gas_limit.into(), + } + } } /// Parameters for a block chain; includes both those intrinsic to the design of the /// chain and those to be interpreted by the active chain engine. -#[derive(Debug)] pub struct Spec { /// User friendly spec name pub name: String, /// What engine are we using for this? - pub engine_name: String, + pub engine: Box, /// Known nodes on the network in enode format. pub nodes: Vec, - /// Network ID - pub network_id: U256, - /// Parameters concerning operation of the specific engine we're using. - /// Maps the parameter name to an RLP-encoded value. - pub engine_params: HashMap, - - /// Builtin-contracts we would like to see in the chain. - /// (In principle these are just hints for the engine since that has the last word on them.) - pub builtins: BTreeMap, + /// Parameters common to all engines. + pub params: CommonParams, /// The genesis block's parent hash field. pub parent_hash: H256, @@ -101,15 +97,41 @@ pub struct Spec { genesis_state: PodState, } -#[cfg_attr(feature="dev", allow(wrong_self_convention))] // because to_engine(self) should be to_engine(&self) +impl From for Spec { + fn from(s: ethjson::spec::Spec) -> Self { + let builtins = s.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect(); + let g = Genesis::from(s.genesis); + let seal: GenericSeal = g.seal.into(); + let params = CommonParams::from(s.params); + Spec { + name: s.name.into(), + params: params.clone(), + engine: Spec::engine(s.engine, params, builtins), + nodes: s.nodes.unwrap_or_else(Vec::new), + parent_hash: g.parent_hash, + transactions_root: g.transactions_root, + receipts_root: g.receipts_root, + author: g.author, + difficulty: g.difficulty, + gas_limit: g.gas_limit, + gas_used: g.gas_used, + timestamp: g.timestamp, + extra_data: g.extra_data, + seal_fields: seal.fields, + seal_rlp: seal.rlp, + state_root_memo: RwLock::new(g.state_root), + genesis_state: From::from(s.accounts) + } + } +} + impl Spec { - /// Convert this object into a boxed Engine of the right underlying type. - // TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. - pub fn to_engine(self) -> Result, Error> { - match self.engine_name.as_ref() { - "NullEngine" => Ok(NullEngine::new_boxed(self)), - "Ethash" => Ok(ethereum::Ethash::new_boxed(self)), - _ => Err(Error::UnknownEngineName(self.engine_name.clone())) + /// Convert engine spec into a boxed Engine of the right underlying type. + /// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. + fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap) -> Box { + match engine_spec { + ethjson::spec::Engine::Null => Box::new(NullEngine::new(params, builtins)), + ethjson::spec::Engine::Ethash(ethash) => Box::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)) } } @@ -125,7 +147,7 @@ impl Spec { pub fn nodes(&self) -> &Vec { &self.nodes } /// Get the configured Network ID. - pub fn network_id(&self) -> U256 { self.network_id } + pub fn network_id(&self) -> U256 { self.params.network_id } /// Get the header of the genesis block. pub fn genesis_header(&self) -> Header { @@ -168,49 +190,9 @@ impl Spec { ret.out() } - /// Overwrite the genesis components with the given JSON, assuming standard Ethereum test format. - pub fn overwrite_genesis(&mut self, genesis: &Json) { - let (seal_fields, seal_rlp) = { - if genesis.find("mixHash").is_some() && genesis.find("nonce").is_some() { - let mut s = RlpStream::new(); - s.append(&H256::from_json(&genesis["mixHash"])); - s.append(&H64::from_json(&genesis["nonce"])); - (2, s.out()) - } else { - // backup algo that will work with sealFields/sealRlp (and without). - ( - u64::from_json(&genesis["sealFields"]) as usize, - Bytes::from_json(&genesis["sealRlp"]) - ) - } - }; - - self.parent_hash = H256::from_json(&genesis["parentHash"]); - self.transactions_root = genesis.find("transactionsTrie").and_then(|_| Some(H256::from_json(&genesis["transactionsTrie"]))).unwrap_or(SHA3_NULL_RLP.clone()); - self.receipts_root = genesis.find("receiptTrie").and_then(|_| Some(H256::from_json(&genesis["receiptTrie"]))).unwrap_or(SHA3_NULL_RLP.clone()); - self.author = Address::from_json(&genesis["coinbase"]); - self.difficulty = U256::from_json(&genesis["difficulty"]); - self.gas_limit = U256::from_json(&genesis["gasLimit"]); - self.gas_used = U256::from_json(&genesis["gasUsed"]); - self.timestamp = u64::from_json(&genesis["timestamp"]); - self.extra_data = Bytes::from_json(&genesis["extraData"]); - self.seal_fields = seal_fields; - self.seal_rlp = seal_rlp; - self.state_root_memo = RwLock::new(genesis.find("stateRoot").and_then(|_| Some(H256::from_json(&genesis["stateRoot"])))); - } - /// Overwrite the genesis components. pub fn overwrite_genesis_params(&mut self, g: Genesis) { - let (seal_fields, seal_rlp) = match g.seal { - GenesisSeal::Generic { fields, rlp } => (fields, rlp), - GenesisSeal::Ethereum { nonce, mix_hash } => { - let mut s = RlpStream::new(); - s.append(&mix_hash); - s.append(&nonce); - (2, s.out()) - } - }; - + let seal: GenericSeal = g.seal.into(); self.parent_hash = g.parent_hash; self.transactions_root = g.transactions_root; self.receipts_root = g.receipts_root; @@ -220,8 +202,8 @@ impl Spec { self.gas_used = g.gas_used; self.timestamp = g.timestamp; self.extra_data = g.extra_data; - self.seal_fields = seal_fields; - self.seal_rlp = seal_rlp; + self.seal_fields = seal.fields; + self.seal_rlp = seal.rlp; self.state_root_memo = RwLock::new(g.state_root); } @@ -235,74 +217,7 @@ impl Spec { pub fn is_state_root_valid(&self) -> bool { self.state_root_memo.read().unwrap().clone().map_or(true, |sr| sr == self.genesis_state.root()) } -} -impl FromJson for Spec { - /// Loads a chain-specification from a json data structure - fn from_json(json: &Json) -> Spec { - // once we commit ourselves to some json parsing library (serde?) - // move it to proper data structure - let mut builtins = BTreeMap::new(); - let mut state = PodState::new(); - - if let Some(&Json::Object(ref accounts)) = json.find("accounts") { - for (address, acc) in accounts.iter() { - let addr = Address::from_str(address).unwrap(); - if let Some(ref builtin_json) = acc.find("builtin") { - if let Some(builtin) = Builtin::from_json(builtin_json) { - builtins.insert(addr.clone(), builtin); - } - } - } - state = xjson!(&json["accounts"]); - } - - let nodes = if let Some(&Json::Array(ref ns)) = json.find("nodes") { - ns.iter().filter_map(|n| if let Json::String(ref s) = *n { Some(s.clone()) } else {None}).collect() - } else { Vec::new() }; - - let genesis = &json["genesis"];//.as_object().expect("No genesis object in JSON"); - - let (seal_fields, seal_rlp) = { - if genesis.find("mixHash").is_some() && genesis.find("nonce").is_some() { - let mut s = RlpStream::new(); - s.append(&H256::from_str(&genesis["mixHash"].as_string().expect("mixHash not a string.")[2..]).expect("Invalid mixHash string value")); - s.append(&H64::from_str(&genesis["nonce"].as_string().expect("nonce not a string.")[2..]).expect("Invalid nonce string value")); - (2, s.out()) - } else { - // backup algo that will work with sealFields/sealRlp (and without). - ( - usize::from_str(&genesis["sealFields"].as_string().unwrap_or("0x")[2..]).expect("Invalid sealFields integer data"), - genesis["sealRlp"].as_string().unwrap_or("0x")[2..].from_hex().expect("Invalid sealRlp hex data") - ) - } - }; - - Spec { - name: json.find("name").map_or("unknown", |j| j.as_string().unwrap()).to_owned(), - engine_name: json["engineName"].as_string().unwrap().to_owned(), - engine_params: json_to_rlp_map(&json["params"]), - nodes: nodes, - network_id: U256::from_str(&json["params"]["networkID"].as_string().unwrap()[2..]).unwrap(), - builtins: builtins, - parent_hash: H256::from_str(&genesis["parentHash"].as_string().unwrap()[2..]).unwrap(), - author: Address::from_str(&genesis["author"].as_string().unwrap()[2..]).unwrap(), - difficulty: U256::from_str(&genesis["difficulty"].as_string().unwrap()[2..]).unwrap(), - gas_limit: U256::from_str(&genesis["gasLimit"].as_string().unwrap()[2..]).unwrap(), - gas_used: U256::from(0u8), - timestamp: u64::from_str(&genesis["timestamp"].as_string().unwrap()[2..]).unwrap(), - transactions_root: SHA3_NULL_RLP.clone(), - receipts_root: SHA3_NULL_RLP.clone(), - extra_data: genesis["extraData"].as_string().unwrap()[2..].from_hex().unwrap(), - genesis_state: state, - seal_fields: seal_fields, - seal_rlp: seal_rlp, - state_root_memo: RwLock::new(genesis.find("stateRoot").and_then(|_| genesis["stateRoot"].as_string()).map(|s| H256::from_str(&s[2..]).unwrap())), - } - } -} - -impl Spec { /// Ensure that the given state DB has the trie nodes in for the genesis state. pub fn ensure_db_good(&self, db: &mut HashDB) -> bool { if !db.contains(&self.state_root()) { @@ -321,21 +236,20 @@ impl Spec { } else { false } } - /// Create a new Spec from a JSON UTF-8 data resource `data`. - pub fn from_json_utf8(data: &[u8]) -> Spec { - Self::from_json_str(::std::str::from_utf8(data).unwrap()) - } - - /// Create a new Spec from a JSON string. - pub fn from_json_str(s: &str) -> Spec { - Self::from_json(&Json::from_str(s).expect("Json is invalid")) + /// Loads spec from json file. + pub fn load(reader: &[u8]) -> Self { + From::from(ethjson::spec::Spec::load(reader).expect("invalid json file")) } /// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus. - pub fn new_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_morden.json")) } + pub fn new_test() -> Spec { + Spec::load(include_bytes!("../../res/null_morden.json")) + } /// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus. - pub fn new_homestead_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_homestead_morden.json")) } + pub fn new_homestead_test() -> Spec { + Spec::load(include_bytes!("../../res/null_homestead_morden.json")) + } } #[cfg(test)] @@ -353,7 +267,5 @@ mod tests { assert_eq!(test_spec.state_root(), H256::from_str("f3f4696bbf3b3b07775128eb7a3763279a394e382130f27c21e70233e04946a9").unwrap()); let genesis = test_spec.genesis_block(); assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap()); - - let _ = test_spec.to_engine(); } } diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index fca578a09..e1fb6aad0 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -548,7 +548,7 @@ fn should_not_trace_call_transaction_to_builtin() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; let t = Transaction { nonce: x!(0), @@ -573,7 +573,7 @@ fn should_not_trace_subcall_transaction_to_builtin() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; let t = Transaction { nonce: x!(0), @@ -611,7 +611,7 @@ fn should_not_trace_callcode() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; let t = Transaction { nonce: x!(0), @@ -651,7 +651,7 @@ fn should_not_trace_delegatecall() { let mut info = EnvInfo::default(); info.gas_limit = x!(1_000_000); info.number = 0x789b0; - let engine = Spec::new_test().to_engine().unwrap(); + let engine = Spec::new_test().engine; println!("schedule.have_delegate_call: {:?}", engine.schedule(&info).have_delegate_call); diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 3b51feec6..539a33d10 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -40,8 +40,7 @@ fn returns_state_root_basic() { let client_result = generate_dummy_client(6); let client = client_result.reference(); let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - let state_root = test_engine.spec().genesis_header().state_root; + let state_root = test_spec.genesis_header().state_root; assert!(client.state_data(&state_root).is_some()); } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index dc3068560..152ac0ef6 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -52,7 +52,7 @@ impl GuardedTempResult { pub struct TestEngine { factory: Factory, - spec: Spec, + engine: Box, max_depth: usize } @@ -60,18 +60,29 @@ impl TestEngine { pub fn new(max_depth: usize, factory: Factory) -> TestEngine { TestEngine { factory: factory, - spec: ethereum::new_frontier_test(), + engine: ethereum::new_frontier_test().engine, max_depth: max_depth } } } impl Engine for TestEngine { - fn name(&self) -> &str { "TestEngine" } - fn spec(&self) -> &Spec { &self.spec } + fn name(&self) -> &str { + "TestEngine" + } + + fn params(&self) -> &CommonParams { + self.engine.params() + } + + fn builtins(&self) -> &BTreeMap { + self.engine.builtins() + } + fn vm_factory(&self) -> &Factory { &self.factory } + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { let mut schedule = Schedule::new_frontier(); schedule.max_depth = self.max_depth; @@ -136,17 +147,17 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap(); let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - let state_root = test_engine.spec().genesis_header().state_root; - let mut rolling_hash = test_engine.spec().genesis_header().hash(); + let test_engine = &test_spec.engine; + let state_root = test_spec.genesis_header().state_root; + let mut rolling_hash = test_spec.genesis_header().hash(); let mut rolling_block_number = 1; let mut rolling_timestamp = 40; for _ in 0..block_number { let mut header = Header::new(); - header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + header.gas_limit = test_engine.params().min_gas_limit; + header.difficulty = U256::from(0x20000); header.timestamp = rolling_timestamp; header.number = rolling_block_number; header.parent_hash = rolling_hash; @@ -171,8 +182,9 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting_number: usize, block_number: usize) { let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - let state_root = test_engine.spec().genesis_header().state_root; + let test_engine = &test_spec.engine; + //let test_engine = test_spec.to_engine().unwrap(); + let state_root = test_spec.genesis_header().state_root; let mut rolling_hash = client.chain_info().best_block_hash; let mut rolling_block_number = starting_number as u64; let mut rolling_timestamp = timestamp_salt + starting_number as u64 * 10; @@ -180,8 +192,8 @@ pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting for _ in 0..block_number { let mut header = Header::new(); - header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + header.gas_limit = test_engine.params().min_gas_limit; + header.difficulty = U256::from(0x20000); header.timestamp = rolling_timestamp; header.number = rolling_block_number; header.parent_hash = rolling_hash; @@ -279,24 +291,23 @@ pub fn get_temp_state_in(path: &Path) -> State { pub fn get_good_dummy_block_seq(count: usize) -> Vec { let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - get_good_dummy_block_fork_seq(1, count, &test_engine.spec().genesis_header().hash()) + get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash()) } pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec { let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); + let test_engine = &test_spec.engine; let mut rolling_timestamp = start_number as u64 * 10; let mut parent = *parent_hash; let mut r = Vec::new(); for i in start_number .. start_number + count + 1 { let mut block_header = Header::new(); - block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); + block_header.gas_limit = test_engine.params().min_gas_limit; block_header.difficulty = U256::from(i).mul(U256([0, 1, 0, 0])); block_header.timestamp = rolling_timestamp; block_header.number = i as u64; block_header.parent_hash = parent; - block_header.state_root = test_engine.spec().genesis_header().state_root; + block_header.state_root = test_spec.genesis_header().state_root; parent = block_header.hash(); rolling_timestamp = rolling_timestamp + 10; @@ -310,13 +321,13 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h pub fn get_good_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + let test_engine = &test_spec.engine; + block_header.gas_limit = test_engine.params().min_gas_limit; + block_header.difficulty = U256::from(0x20000); block_header.timestamp = 40; block_header.number = 1; - block_header.parent_hash = test_engine.spec().genesis_header().hash(); - block_header.state_root = test_engine.spec().genesis_header().state_root; + block_header.parent_hash = test_spec.genesis_header().hash(); + block_header.state_root = test_spec.genesis_header().state_root; create_test_block(&block_header) } @@ -324,12 +335,12 @@ pub fn get_good_dummy_block() -> Bytes { pub fn get_bad_state_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = get_test_spec(); - let test_engine = test_spec.to_engine().unwrap(); - block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap()); - block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap()); + let test_engine = &test_spec.engine; + block_header.gas_limit = test_engine.params().min_gas_limit; + block_header.difficulty = U256::from(0x20000); block_header.timestamp = 40; block_header.number = 1; - block_header.parent_hash = test_engine.spec().genesis_header().hash(); + block_header.parent_hash = test_spec.genesis_header().hash(); block_header.state_root = x!(0xbad); create_test_block(&block_header) diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index bd0ce426f..a3cc21032 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -183,7 +183,7 @@ fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> { if header.gas_used > header.gas_limit { return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(header.gas_limit), min: None, found: header.gas_used }))); } - let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap()); + let min_gas_limit = engine.params().min_gas_limit; if header.gas_limit < min_gas_limit { return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: header.gas_limit }))); } @@ -336,12 +336,10 @@ mod tests { // Test against morden let mut good = Header::new(); let spec = Spec::new_test(); - let engine = spec.to_engine().unwrap(); + let engine = &spec.engine; - let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap()); - let min_difficulty = decode(engine.spec().engine_params.get("minimumDifficulty").unwrap()); + let min_gas_limit = engine.params().min_gas_limit; good.gas_limit = min_gas_limit; - good.difficulty = min_difficulty; good.timestamp = 40; good.number = 10; diff --git a/json/src/blockchain/blockchain.rs b/json/src/blockchain/blockchain.rs index 98392b983..5d8933a07 100644 --- a/json/src/blockchain/blockchain.rs +++ b/json/src/blockchain/blockchain.rs @@ -21,7 +21,7 @@ use hash::H256; use blockchain::state::State; use blockchain::header::Header; use blockchain::block::Block; -use spec::Genesis; +use spec::{Genesis, Seal, Ethereum}; /// Blockchain deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -54,10 +54,10 @@ impl BlockChain { /// Returns spec compatible genesis struct. pub fn genesis(&self) -> Genesis { Genesis { - nonce: Some(self.genesis_block.nonce.clone()), - mix_hash: Some(self.genesis_block.mix_hash.clone()), - seal_fields: None, - seal_rlp: None, + seal: Seal::Ethereum(Ethereum { + nonce: self.genesis_block.nonce.clone(), + mix_hash: self.genesis_block.mix_hash.clone(), + }), difficulty: self.genesis_block.difficulty, author: self.genesis_block.author.clone(), timestamp: self.genesis_block.timestamp, diff --git a/json/src/blockchain/state.rs b/json/src/blockchain/state.rs index 7779bd861..5aa4b09a3 100644 --- a/json/src/blockchain/state.rs +++ b/json/src/blockchain/state.rs @@ -22,7 +22,7 @@ use blockchain::account::Account; /// Blockchain test state deserializer. #[derive(Debug, PartialEq, Deserialize, Clone)] -pub struct State(pub BTreeMap); +pub struct State(BTreeMap); impl IntoIterator for State { type Item = as IntoIterator>::Item; diff --git a/json/src/spec/account.rs b/json/src/spec/account.rs index 1440b1bdc..6ad142df3 100644 --- a/json/src/spec/account.rs +++ b/json/src/spec/account.rs @@ -22,9 +22,19 @@ use spec::builtin::Builtin; /// Spec account. #[derive(Debug, PartialEq, Deserialize)] pub struct Account { - builtin: Option, - balance: Option, - nonce: Option, + /// Builtin contract. + pub builtin: Option, + /// Balance. + pub balance: Option, + /// Nonce. + pub nonce: Option, +} + +impl Account { + /// Returns true if account does not have nonce and balance. + pub fn is_empty(&self) -> bool { + self.balance.is_none() && self.nonce.is_none() + } } #[cfg(test)] diff --git a/json/src/spec/builtin.rs b/json/src/spec/builtin.rs index 21f8a2ac1..454199883 100644 --- a/json/src/spec/builtin.rs +++ b/json/src/spec/builtin.rs @@ -17,14 +17,16 @@ //! Spec builtin deserialization. /// Linear pricing. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, PartialEq, Deserialize, Clone)] pub struct Linear { - base: u64, - word: u64, + /// Base price. + pub base: usize, + /// Price for word. + pub word: usize, } /// Pricing variants. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, PartialEq, Deserialize, Clone)] pub enum Pricing { /// Linear pricing. #[serde(rename="linear")] @@ -32,10 +34,12 @@ pub enum Pricing { } /// Spec builtin. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, PartialEq, Deserialize, Clone)] pub struct Builtin { - name: String, - pricing: Pricing, + /// Builtin name. + pub name: String, + /// Builtin pricing. + pub pricing: Pricing, } #[cfg(test)] diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs new file mode 100644 index 000000000..6b56d7b71 --- /dev/null +++ b/json/src/spec/engine.rs @@ -0,0 +1,63 @@ +// 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 . + +//! Engine deserialization. + +use serde::{Deserializer, Error}; +use serde::de::Visitor; +use spec::Ethash; + +/// Engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub enum Engine { + /// Null engine. + Null, + /// Ethash engine. + Ethash(Ethash), +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::Engine; + + #[test] + fn engine_deserialization() { + let s = r#"{ + "Null": null + }"#; + + let deserialized: Engine = serde_json::from_str(s).unwrap(); + assert_eq!(Engine::Null, deserialized); + + let s = r#"{ + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }"#; + + let _deserialized: Engine = serde_json::from_str(s).unwrap(); + } +} + diff --git a/json/src/spec/ethash.rs b/json/src/spec/ethash.rs new file mode 100644 index 000000000..85f855dde --- /dev/null +++ b/json/src/spec/ethash.rs @@ -0,0 +1,75 @@ +// 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 . + +//! Ethash params deserialization. + +use uint::Uint; +use hash::Address; + +/// Ethash params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct EthashParams { + /// Tie breaking gas. + #[serde(rename="tieBreakingGas")] + pub tie_breaking_gas: bool, + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Minimum difficulty. + #[serde(rename="minimumDifficulty")] + pub minimum_difficulty: Uint, + /// Difficulty bound divisor. + #[serde(rename="difficultyBoundDivisor")] + pub difficulty_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Block reward. + #[serde(rename="blockReward")] + pub block_reward: Uint, + /// Namereg contract address. + pub registrar: Address, +} + +/// Ethash engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Ethash { + /// Ethash params. + pub params: EthashParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::ethash::Ethash; + + #[test] + fn ethash_deserialization() { + let s = r#"{ + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + }"#; + + let _deserialized: Ethash = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/genesis.rs b/json/src/spec/genesis.rs index a2b484397..272e7bcc2 100644 --- a/json/src/spec/genesis.rs +++ b/json/src/spec/genesis.rs @@ -17,27 +17,15 @@ //! Spec genesis deserialization. use uint::Uint; -use hash::{H64, Address, H256}; +use hash::{Address, H256}; use bytes::Bytes; +use spec::Seal; /// Spec genesis. #[derive(Debug, PartialEq, Deserialize)] pub struct Genesis { - // old seal - /// Seal nonce. - pub nonce: Option, - #[serde(rename="mixHash")] - /// Seal mix hash. - pub mix_hash: Option, - - // new seal // TODO: consider moving it to a separate seal structure - #[serde(rename="sealFields")] - /// Number of seal fields. - pub seal_fields: Option, - #[serde(rename="sealRlp")] - /// Seal rlp. - pub seal_rlp: Option, - + /// Seal. + pub seal: Seal, /// Difficulty. pub difficulty: Uint, /// Block author. @@ -77,7 +65,12 @@ mod tests { let s = r#"{ "nonce": "0x0000000000000042", "difficulty": "0x400000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index 8783563d1..72ea2a42e 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -21,9 +21,17 @@ pub mod builtin; pub mod genesis; pub mod params; pub mod spec; +pub mod seal; +pub mod engine; +pub mod state; +pub mod ethash; pub use self::account::Account; -pub use self::builtin::Builtin; +pub use self::builtin::{Builtin, Pricing, Linear}; pub use self::genesis::Genesis; pub use self::params::Params; pub use self::spec::Spec; +pub use self::seal::{Seal, Ethereum, Generic}; +pub use self::engine::Engine; +pub use self::state::State; +pub use self::ethash::{Ethash, EthashParams}; diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index e55f7fc48..2370368ba 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -17,34 +17,25 @@ //! Spec params deserialization. use uint::Uint; -use hash::Address; /// Spec params. #[derive(Debug, PartialEq, Deserialize)] pub struct Params { + /// Account start nonce. #[serde(rename="accountStartNonce")] - account_start_nonce: Uint, + pub account_start_nonce: Uint, + /// Homestead transition block number. #[serde(rename="frontierCompatibilityModeLimit")] - frontier_compatibility_mode_limit: Uint, + pub frontier_compatibility_mode_limit: Uint, + /// Maximum size of extra data. #[serde(rename="maximumExtraDataSize")] - maximum_extra_data_size: Uint, - #[serde(rename="tieBreakingGas")] - tie_breaking_gas: bool, - #[serde(rename="minGasLimit")] - min_gas_limit: Uint, - #[serde(rename="gasLimitBoundDivisor")] - gas_limit_bound_divisor: Uint, - #[serde(rename="minimumDifficulty")] - minimum_difficulty: Uint, - #[serde(rename="difficultyBoundDivisor")] - difficulty_bound_divisor: Uint, - #[serde(rename="durationLimit")] - duration_limit: Uint, - #[serde(rename="blockReward")] - block_reward: Uint, - registrar: Address, + pub maximum_extra_data_size: Uint, + /// Network id. #[serde(rename="networkID")] - network_id: Uint, + pub network_id: Uint, + /// Minimum gas limit. + #[serde(rename="minGasLimit")] + pub min_gas_limit: Uint, } #[cfg(test)] @@ -55,19 +46,13 @@ mod tests { #[test] fn params_deserialization() { let s = r#"{ - "accountStartNonce": "0x00", "frontierCompatibilityModeLimit": "0x118c30", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, + "networkID" : "0x1", "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", - "networkID" : "0x1" + "accountStartNonce": "0x00" }"#; + let _deserialized: Params = serde_json::from_str(s).unwrap(); // TODO: validate all fields } diff --git a/json/src/spec/seal.rs b/json/src/spec/seal.rs new file mode 100644 index 000000000..a1a53819a --- /dev/null +++ b/json/src/spec/seal.rs @@ -0,0 +1,73 @@ +// 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 . + +//! Spec seal deserialization. + +use hash::{H64, H256}; +use bytes::Bytes; + +/// Ethereum seal. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Ethereum { + /// Seal nonce. + pub nonce: H64, + /// Seal mix hash. + #[serde(rename="mixHash")] + pub mix_hash: H256, +} + +/// Generic seal. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Generic { + /// Number of fields. + pub fields: usize, + /// Their rlp. + pub rlp: Bytes, +} + +/// Seal variants. +#[derive(Debug, PartialEq, Deserialize)] +pub enum Seal { + /// Ethereum seal. + #[serde(rename="ethereum")] + Ethereum(Ethereum), + /// Generic seal. + #[serde(rename="generic")] + Generic(Generic), +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::Seal; + + #[test] + fn builtin_deserialization() { + let s = r#"[{ + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + },{ + "generic": { + "fields": 1, + "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + } + }]"#; + let _deserialized: Vec = serde_json::from_str(s).unwrap(); + // TODO: validate all fields + } +} diff --git a/json/src/spec/spec.rs b/json/src/spec/spec.rs index 2dd4ac486..ad73ffbf3 100644 --- a/json/src/spec/spec.rs +++ b/json/src/spec/spec.rs @@ -16,21 +16,33 @@ //! Spec deserialization. -use std::collections::BTreeMap; -use hash::Address; -use spec::account::Account; -use spec::params::Params; -use spec::genesis::Genesis; +use std::io::Read; +use serde_json; +use serde_json::Error; +use spec::{Params, Genesis, Engine, State}; /// Spec deserialization. #[derive(Debug, PartialEq, Deserialize)] pub struct Spec { - name: String, - #[serde(rename="engineName")] - engine_name: String, // TODO: consider making it an enum - params: Params, - genesis: Genesis, - accounts: BTreeMap, + /// Spec name. + pub name: String, + /// Engine. + pub engine: Engine, + /// Spec params. + pub params: Params, + /// Genesis header. + pub genesis: Genesis, + /// Genesis state. + pub accounts: State, + /// Boot nodes. + pub nodes: Option>, +} + +impl Spec { + /// Loads test from json. + pub fn load(reader: R) -> Result where R: Read { + serde_json::from_reader(reader) + } } #[cfg(test)] @@ -42,25 +54,34 @@ mod tests { fn spec_deserialization() { let s = r#"{ "name": "Morden", - "engineName": "Ethash", + "engine": { + "Ethash": { + "params": { + "tieBreakingGas": false, + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + } + } + }, "params": { "accountStartNonce": "0x0100000", "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", - "tieBreakingGas": false, "minGasLimit": "0x1388", - "gasLimitBoundDivisor": "0x0400", - "minimumDifficulty": "0x020000", - "difficultyBoundDivisor": "0x0800", - "durationLimit": "0x0d", - "blockReward": "0x4563918244F40000", - "registrar": "", "networkID" : "0x2" }, "genesis": { - "nonce": "0x00006d6f7264656e", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x00006d6f7264656e" + } + }, "difficulty": "0x20000", - "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", "author": "0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/json/src/spec/state.rs b/json/src/spec/state.rs new file mode 100644 index 000000000..bde3d6dd8 --- /dev/null +++ b/json/src/spec/state.rs @@ -0,0 +1,44 @@ +// 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 . + +//! Blockchain test state deserializer. + +use std::collections::BTreeMap; +use hash::Address; +use spec::{Account, Builtin}; + +/// Blockchain test state deserializer. +#[derive(Debug, PartialEq, Deserialize)] +pub struct State(BTreeMap); + +impl State { + /// Returns all builtins. + pub fn builtins(&self) -> BTreeMap { + self.0 + .iter() + .filter_map(|ref pair| pair.1.builtin.clone().map(|b| (pair.0.clone(), b.clone()))) + .collect() + } +} + +impl IntoIterator for State { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/json/src/uint.rs b/json/src/uint.rs index 2ebdca910..e77001461 100644 --- a/json/src/uint.rs +++ b/json/src/uint.rs @@ -23,7 +23,7 @@ use util::numbers::{U256, Uint as U}; /// Lenient uint json deserialization for test json files. #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct Uint(U256); +pub struct Uint(pub U256); impl Into for Uint { fn into(self) -> U256 { @@ -37,9 +37,15 @@ impl Into for Uint { } } +impl Into for Uint { + fn into(self) -> usize { + // TODO: clean it after util conversions refactored. + u64::from(self.0) as usize + } +} impl Into for Uint { fn into(self) -> u8 { - >::into(self) as u8 + u64::from(self.0) as u8 } } @@ -55,6 +61,10 @@ struct UintVisitor; impl Visitor for UintVisitor { type Value = Uint; + fn visit_u64(&mut self, value: u64) -> Result where E: Error { + Ok(Uint(U256::from(value))) + } + fn visit_str(&mut self, value: &str) -> Result where E: Error { let value = match value.len() { 0 => U256::from(0), @@ -83,12 +93,13 @@ mod test { #[test] fn uint_deserialization() { - let s = r#"["0xa", "10", "", "0x"]"#; + let s = r#"["0xa", "10", "", "0x", 0]"#; let deserialized: Vec = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, vec![ Uint(U256::from(10)), Uint(U256::from(10)), Uint(U256::from(0)), + Uint(U256::from(0)), Uint(U256::from(0)) ]); } diff --git a/parity/main.rs b/parity/main.rs index d9382e645..8ee9c6f63 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -476,7 +476,7 @@ impl Configuration { "frontier" | "homestead" | "mainnet" => ethereum::new_frontier(), "morden" | "testnet" => ethereum::new_morden(), "olympic" => ethereum::new_olympic(), - f => Spec::from_json_utf8(contents(f).unwrap_or_else(|_| { + f => Spec::load(contents(f).unwrap_or_else(|_| { die!("{}: Couldn't read chain specification file. Sure it exists?", f) }).as_ref()), } From 6ebd5009fc967c5c9b444007d67c6d7e8d3ecc04 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 9 Apr 2016 12:58:13 -0700 Subject: [PATCH 36/41] --unlock is comma-delimited. --- parity/main.rs | 20 +++++++++++--------- rpc/src/v1/impls/eth.rs | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 8ee9c6f63..287392d4e 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -104,7 +104,8 @@ Protocol Options: --identity NAME Specify your node's name. Account Options: - --unlock ACCOUNT Unlock ACCOUNT for the duration of the execution. + --unlock ACCOUNTS Unlock ACCOUNTS for the duration of the execution. + ACCOUNTS is a comma-delimited list of addresses. --password FILE Provide a file containing a password for unlocking an account. @@ -213,7 +214,7 @@ struct Args { flag_chain: String, flag_db_path: String, flag_identity: String, - flag_unlock: Vec, + flag_unlock: Option, flag_password: Vec, flag_cache: Option, flag_keys_path: String, @@ -617,14 +618,15 @@ impl Configuration { .collect::>() .into_iter() }).collect::>(); - let account_service = AccountService::new_in(Path::new(&self.keys_path())); - for d in &self.args.flag_unlock { - let a = Address::from_str(clean_0x(&d)).unwrap_or_else(|_| { - die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d) - }); - if passwords.iter().find(|p| account_service.unlock_account_no_expire(&a, p).is_ok()).is_none() { - die!("No password given to unlock account {}. Pass the password using `--password`.", a); + if let Some(ref unlocks) = self.args.flag_unlock { + for d in unlocks.split(',') { + let a = Address::from_str(clean_0x(&d)).unwrap_or_else(|_| { + die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d) + }); + if passwords.iter().find(|p| account_service.unlock_account_no_expire(&a, p).is_ok()).is_none() { + die!("No password given to unlock account {}. Pass the password using `--password`.", a); + } } } account_service diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index ad4b037df..5ecbfefa2 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -465,7 +465,7 @@ impl Eth for EthClient fn submit_work(&self, params: Params) -> Result { from_params::<(H64, H256, H256)>(params).and_then(|(nonce, pow_hash, mix_hash)| { -// trace!("Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash); + trace!(target: "miner", "submit_work: Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash); let miner = take_weak!(self.miner); let client = take_weak!(self.client); let seal = vec![encode(&mix_hash).to_vec(), encode(&nonce).to_vec()]; From 219e88a0236b20f004af6c2037a4afdb4a3c153b Mon Sep 17 00:00:00 2001 From: NikVolf Date: Sun, 10 Apr 2016 14:20:48 +0300 Subject: [PATCH 37/41] create provided custom dir for keys if none --- util/src/keys/store.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index 5dec27fc3..3cd85fbc2 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -184,6 +184,7 @@ impl SecretStore { /// new instance of Secret Store in specific directory pub fn new_in(path: &Path) -> Self { + ::std::fs::create_dir_all(&path).expect("Cannot access requested key directory - critical"); SecretStore { directory: KeyDirectory::new(path), unlocks: RwLock::new(HashMap::new()), From 5f7cc437dd734b284bd32feb380b5b93542fb070 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Sun, 10 Apr 2016 14:38:57 +0300 Subject: [PATCH 38/41] removing lower-level defaults --- util/src/keys/store.rs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index 3cd85fbc2..36edb0ff1 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -121,22 +121,7 @@ impl AccountProvider for AccountService { } } -impl Default for AccountService { - fn default() -> Self { - AccountService::new() - } -} - impl AccountService { - /// New account service with the keys store in default location - pub fn new() -> Self { - let secret_store = RwLock::new(SecretStore::new()); - secret_store.write().unwrap().try_import_existing(); - AccountService { - secret_store: secret_store - } - } - /// New account service with the keys store in specific location pub fn new_in(path: &Path) -> Self { let secret_store = RwLock::new(SecretStore::new_in(path)); @@ -165,23 +150,7 @@ impl AccountService { } } - -impl Default for SecretStore { - fn default() -> Self { - SecretStore::new() - } -} - impl SecretStore { - /// new instance of Secret Store in default home directory - pub fn new() -> Self { - let mut path = ::std::env::home_dir().expect("Failed to get home dir"); - path.push(".parity"); - path.push("keys"); - ::std::fs::create_dir_all(&path).expect("Should panic since it is critical to be able to access home dir"); - Self::new_in(&path) - } - /// new instance of Secret Store in specific directory pub fn new_in(path: &Path) -> Self { ::std::fs::create_dir_all(&path).expect("Cannot access requested key directory - critical"); From 8340f135fc694a79d24603f4297e6b9a155b3694 Mon Sep 17 00:00:00 2001 From: debris Date: Sun, 10 Apr 2016 19:43:42 +0200 Subject: [PATCH 39/41] fixed eth_getLogs --- rpc/src/v1/helpers/poll_manager.rs | 1 - rpc/src/v1/impls/eth.rs | 26 ++++++++++++++++++++++++-- rpc/src/v1/traits/eth.rs | 5 ++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/rpc/src/v1/helpers/poll_manager.rs b/rpc/src/v1/helpers/poll_manager.rs index 9735d7d5d..bc3518830 100644 --- a/rpc/src/v1/helpers/poll_manager.rs +++ b/rpc/src/v1/helpers/poll_manager.rs @@ -61,7 +61,6 @@ impl PollManager where T: Timer { } // Implementation is always using `poll_mut` - #[cfg(test)] /// Get a reference to stored poll filter pub fn poll(&mut self, id: &PollId) -> Option<&F> { self.polls.prune(); diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index ad4b037df..861b7c5ac 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -636,10 +636,11 @@ impl EthFilter for EthFilterClient to_value(&diff) }, - PollFilter::Logs(ref mut block_number, ref mut filter) => { + PollFilter::Logs(ref mut block_number, ref filter) => { + let mut filter = filter.clone(); filter.from_block = BlockId::Number(*block_number); filter.to_block = BlockId::Latest; - let logs = client.logs(filter.clone()) + let logs = client.logs(filter) .into_iter() .map(From::from) .collect::>(); @@ -654,6 +655,27 @@ impl EthFilter for EthFilterClient }) } + fn filter_logs(&self, params: Params) -> Result { + from_params::<(Index,)>(params) + .and_then(|(index,)| { + let mut polls = self.polls.lock().unwrap(); + match polls.poll(&index.value()) { + Some(&PollFilter::Logs(ref _block_number, ref filter)) => { + let logs = take_weak!(self.client).logs(filter.clone()) + .into_iter() + .map(From::from) + .collect::>(); + to_value(&logs) + }, + // just empty array + _ => Ok(Value::Array(vec![] as Vec)), + } + }) + + + + } + fn uninstall_filter(&self, params: Params) -> Result { from_params::<(Index,)>(params) .and_then(|(index,)| { diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 5d36da670..a28f72c5c 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -190,6 +190,9 @@ pub trait EthFilter: Sized + Send + Sync + 'static { /// Returns filter changes since last poll. fn filter_changes(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Returns all logs matching given filter (in a range 'from' - 'to'). + fn filter_logs(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Uninstalls filter. fn uninstall_filter(&self, _: Params) -> Result { rpc_unimplemented!() } @@ -200,7 +203,7 @@ pub trait EthFilter: Sized + Send + Sync + 'static { delegate.add_method("eth_newBlockFilter", EthFilter::new_block_filter); delegate.add_method("eth_newPendingTransactionFilter", EthFilter::new_pending_transaction_filter); delegate.add_method("eth_getFilterChanges", EthFilter::filter_changes); - delegate.add_method("eth_getFilterLogs", EthFilter::filter_changes); + delegate.add_method("eth_getFilterLogs", EthFilter::filter_logs); delegate.add_method("eth_uninstallFilter", EthFilter::uninstall_filter); delegate } From fed853593bc7863a0d16b4146181f1fe72d962f0 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Sun, 10 Apr 2016 20:42:03 +0200 Subject: [PATCH 40/41] fixed eth_getLogs (#915) * fixed eth_getLogs * removed empty lines --- rpc/src/v1/helpers/poll_manager.rs | 1 - rpc/src/v1/impls/eth.rs | 23 +++++++++++++++++++++-- rpc/src/v1/traits/eth.rs | 5 ++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/rpc/src/v1/helpers/poll_manager.rs b/rpc/src/v1/helpers/poll_manager.rs index 9735d7d5d..bc3518830 100644 --- a/rpc/src/v1/helpers/poll_manager.rs +++ b/rpc/src/v1/helpers/poll_manager.rs @@ -61,7 +61,6 @@ impl PollManager where T: Timer { } // Implementation is always using `poll_mut` - #[cfg(test)] /// Get a reference to stored poll filter pub fn poll(&mut self, id: &PollId) -> Option<&F> { self.polls.prune(); diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index ad4b037df..8e1ca72fb 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -636,10 +636,11 @@ impl EthFilter for EthFilterClient to_value(&diff) }, - PollFilter::Logs(ref mut block_number, ref mut filter) => { + PollFilter::Logs(ref mut block_number, ref filter) => { + let mut filter = filter.clone(); filter.from_block = BlockId::Number(*block_number); filter.to_block = BlockId::Latest; - let logs = client.logs(filter.clone()) + let logs = client.logs(filter) .into_iter() .map(From::from) .collect::>(); @@ -654,6 +655,24 @@ impl EthFilter for EthFilterClient }) } + fn filter_logs(&self, params: Params) -> Result { + from_params::<(Index,)>(params) + .and_then(|(index,)| { + let mut polls = self.polls.lock().unwrap(); + match polls.poll(&index.value()) { + Some(&PollFilter::Logs(ref _block_number, ref filter)) => { + let logs = take_weak!(self.client).logs(filter.clone()) + .into_iter() + .map(From::from) + .collect::>(); + to_value(&logs) + }, + // just empty array + _ => Ok(Value::Array(vec![] as Vec)), + } + }) + } + fn uninstall_filter(&self, params: Params) -> Result { from_params::<(Index,)>(params) .and_then(|(index,)| { diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 5d36da670..a28f72c5c 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -190,6 +190,9 @@ pub trait EthFilter: Sized + Send + Sync + 'static { /// Returns filter changes since last poll. fn filter_changes(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Returns all logs matching given filter (in a range 'from' - 'to'). + fn filter_logs(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Uninstalls filter. fn uninstall_filter(&self, _: Params) -> Result { rpc_unimplemented!() } @@ -200,7 +203,7 @@ pub trait EthFilter: Sized + Send + Sync + 'static { delegate.add_method("eth_newBlockFilter", EthFilter::new_block_filter); delegate.add_method("eth_newPendingTransactionFilter", EthFilter::new_pending_transaction_filter); delegate.add_method("eth_getFilterChanges", EthFilter::filter_changes); - delegate.add_method("eth_getFilterLogs", EthFilter::filter_changes); + delegate.add_method("eth_getFilterLogs", EthFilter::filter_logs); delegate.add_method("eth_uninstallFilter", EthFilter::uninstall_filter); delegate } From 6c35c5e604ae6efe512a3933f8b270eb4060b7dd Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 10 Apr 2016 14:01:41 -0700 Subject: [PATCH 41/41] --unlock is comma-delimited. (#916) --- parity/main.rs | 20 +++++++++++--------- rpc/src/v1/impls/eth.rs | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/parity/main.rs b/parity/main.rs index 8ee9c6f63..287392d4e 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -104,7 +104,8 @@ Protocol Options: --identity NAME Specify your node's name. Account Options: - --unlock ACCOUNT Unlock ACCOUNT for the duration of the execution. + --unlock ACCOUNTS Unlock ACCOUNTS for the duration of the execution. + ACCOUNTS is a comma-delimited list of addresses. --password FILE Provide a file containing a password for unlocking an account. @@ -213,7 +214,7 @@ struct Args { flag_chain: String, flag_db_path: String, flag_identity: String, - flag_unlock: Vec, + flag_unlock: Option, flag_password: Vec, flag_cache: Option, flag_keys_path: String, @@ -617,14 +618,15 @@ impl Configuration { .collect::>() .into_iter() }).collect::>(); - let account_service = AccountService::new_in(Path::new(&self.keys_path())); - for d in &self.args.flag_unlock { - let a = Address::from_str(clean_0x(&d)).unwrap_or_else(|_| { - die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d) - }); - if passwords.iter().find(|p| account_service.unlock_account_no_expire(&a, p).is_ok()).is_none() { - die!("No password given to unlock account {}. Pass the password using `--password`.", a); + if let Some(ref unlocks) = self.args.flag_unlock { + for d in unlocks.split(',') { + let a = Address::from_str(clean_0x(&d)).unwrap_or_else(|_| { + die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d) + }); + if passwords.iter().find(|p| account_service.unlock_account_no_expire(&a, p).is_ok()).is_none() { + die!("No password given to unlock account {}. Pass the password using `--password`.", a); + } } } account_service diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 8e1ca72fb..1d973f933 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -465,7 +465,7 @@ impl Eth for EthClient fn submit_work(&self, params: Params) -> Result { from_params::<(H64, H256, H256)>(params).and_then(|(nonce, pow_hash, mix_hash)| { -// trace!("Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash); + trace!(target: "miner", "submit_work: Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash); let miner = take_weak!(self.miner); let client = take_weak!(self.client); let seal = vec![encode(&mix_hash).to_vec(), encode(&nonce).to_vec()];