diff --git a/.gitignore b/.gitignore index e3f377db9..2cdc945d3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ Cargo.lock /json-tests/target/ - +# jetbrains ide stuff +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 54b1b406e..489c1f27e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,16 +20,10 @@ time = "0.1" evmjit = { path = "rust-evmjit", optional = true } ethash = { path = "ethash" } num_cpus = "0.2" -docopt = "0.6" -docopt_macros = "0.6" -ctrlc = "1.0" clippy = "0.0.37" +crossbeam = "0.1.5" [features] jit = ["evmjit"] +evm_debug = [] test-heavy = [] -evm-debug = [] - -[[bin]] -name = "client" -path = "src/bin/client/main.rs" diff --git a/bin/Cargo.toml b/bin/Cargo.toml new file mode 100644 index 000000000..ba258b586 --- /dev/null +++ b/bin/Cargo.toml @@ -0,0 +1,20 @@ +[package] +description = "Ethcore client." +name = "ethcore-client" +version = "0.1.0" +license = "GPL-3.0" +authors = ["Ethcore "] + +[dependencies] +log = "0.3" +env_logger = "0.3" +rustc-serialize = "0.3" +docopt = "0.6" +docopt_macros = "0.6" +ctrlc = "1.0" +ethcore-util = { path = "../util" } +ethcore-rpc = { path = "../rpc", optional = true } +ethcore = { path = ".." } + +[features] +rpc = ["ethcore-rpc"] diff --git a/src/bin/client/main.rs b/bin/src/main.rs similarity index 86% rename from src/bin/client/main.rs rename to bin/src/main.rs index 45f45daf7..942a5cf24 100644 --- a/src/bin/client/main.rs +++ b/bin/src/main.rs @@ -1,5 +1,6 @@ #![feature(plugin)] #![plugin(docopt_macros)] +// required for serde, move it to a separate library extern crate docopt; extern crate rustc_serialize; extern crate ethcore_util as util; @@ -8,6 +9,9 @@ extern crate log; extern crate env_logger; extern crate ctrlc; +#[cfg(feature = "rpc")] +extern crate ethcore_rpc as rpc; + use std::env; use log::{LogLevelFilter}; use env_logger::LogBuilder; @@ -44,6 +48,23 @@ fn setup_log(init: &String) { builder.init().unwrap(); } + +#[cfg(feature = "rpc")] +fn setup_rpc_server(client: Arc) { + use rpc::*; + + let mut server = HttpServer::new(1); + server.add_delegate(Web3Client::new().to_delegate()); + server.add_delegate(EthClient::new(client.clone()).to_delegate()); + server.add_delegate(EthFilterClient::new(client).to_delegate()); + server.add_delegate(NetClient::new().to_delegate()); + server.start_async("127.0.0.1:3030"); +} + +#[cfg(not(feature = "rpc"))] +fn setup_rpc_server(_client: Arc) { +} + fn main() { let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit()); @@ -57,6 +78,7 @@ fn main() { let mut net_settings = NetworkConfiguration::new(); net_settings.boot_nodes = init_nodes; let mut service = ClientService::start(spec, net_settings).unwrap(); + setup_rpc_server(service.client()); let io_handler = Arc::new(ClientIoHandler { client: service.client(), info: Default::default(), sync: service.sync() }); service.io().register_handler(io_handler).expect("Error registering IO handler"); diff --git a/res/ethereum/tests b/res/ethereum/tests index dc86e6359..e838fd909 160000 --- a/res/ethereum/tests +++ b/res/ethereum/tests @@ -1 +1 @@ -Subproject commit dc86e6359675440aea59ddb48648a01c799925d8 +Subproject commit e838fd90998fc5502d0b7c9427a4c231f9a6953d diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml new file mode 100644 index 000000000..1f10180d6 --- /dev/null +++ b/rpc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +description = "Ethcore jsonrpc" +name = "ethcore-rpc" +version = "0.1.0" +license = "GPL-3.0" +authors = ["Marek Kotewicz , +} + +impl EthClient { + pub fn new(client: Arc) -> Self { + EthClient { + client: client + } + } +} + +impl Eth for EthClient { + fn protocol_version(&self, params: Params) -> Result { + match params { + Params::None => Ok(Value::U64(63)), + _ => Err(Error::invalid_params()) + } + } + + fn author(&self, params: Params) -> Result { + match params { + Params::None => to_value(&Address::new()), + _ => Err(Error::invalid_params()) + } + } + + fn gas_price(&self, params: Params) -> Result { + match params { + Params::None => Ok(Value::U64(0)), + _ => Err(Error::invalid_params()) + } + } + + fn block_number(&self, params: Params) -> Result { + match params { + Params::None => Ok(Value::U64(self.client.chain_info().best_block_number)), + _ => Err(Error::invalid_params()) + } + } + + fn is_mining(&self, params: Params) -> Result { + match params { + Params::None => Ok(Value::Bool(false)), + _ => Err(Error::invalid_params()) + } + } + + fn hashrate(&self, params: Params) -> Result { + match params { + Params::None => Ok(Value::U64(0)), + _ => Err(Error::invalid_params()) + } + } + + fn block_transaction_count(&self, _: Params) -> Result { + Ok(Value::U64(0)) + } + + fn block(&self, params: Params) -> Result { + match from_params::<(H256, bool)>(params) { + Ok((hash, _include_txs)) => match (self.client.block_header(&hash), self.client.block_total_difficulty(&hash)) { + (Some(bytes), Some(total_difficulty)) => { + let view = HeaderView::new(&bytes); + let block = Block { + hash: view.sha3(), + parent_hash: view.parent_hash(), + uncles_hash: view.uncles_hash(), + author: view.author(), + miner: view.author(), + state_root: view.state_root(), + transactions_root: view.transactions_root(), + receipts_root: view.receipts_root(), + number: U256::from(view.number()), + gas_used: view.gas_used(), + gas_limit: view.gas_limit(), + logs_bloom: view.log_bloom(), + timestamp: U256::from(view.timestamp()), + difficulty: view.difficulty(), + total_difficulty: total_difficulty, + uncles: vec![], + transactions: vec![] + }; + to_value(&block) + }, + _ => Ok(Value::Null) + }, + Err(err) => Err(err) + } + } +} + +pub struct EthFilterClient { + client: Arc +} + +impl EthFilterClient { + pub fn new(client: Arc) -> Self { + EthFilterClient { + client: client + } + } +} + +impl EthFilter for EthFilterClient { + fn new_block_filter(&self, _params: Params) -> Result { + Ok(Value::U64(0)) + } + + fn new_pending_transaction_filter(&self, _params: Params) -> Result { + Ok(Value::U64(1)) + } + + fn filter_changes(&self, _: Params) -> Result { + to_value(&self.client.chain_info().best_block_hash).map(|v| Value::Array(vec![v])) + } +} diff --git a/rpc/src/impls/mod.rs b/rpc/src/impls/mod.rs new file mode 100644 index 000000000..f10d613d0 --- /dev/null +++ b/rpc/src/impls/mod.rs @@ -0,0 +1,8 @@ +//! Ethereum rpc interface implementation. +mod web3; +mod eth; +mod net; + +pub use self::web3::Web3Client; +pub use self::eth::{EthClient, EthFilterClient}; +pub use self::net::NetClient; diff --git a/rpc/src/impls/net.rs b/rpc/src/impls/net.rs new file mode 100644 index 000000000..a1d36de54 --- /dev/null +++ b/rpc/src/impls/net.rs @@ -0,0 +1,19 @@ +//! Net rpc implementation. +use jsonrpc_core::*; +use traits::Net; + +pub struct NetClient; + +impl NetClient { + pub fn new() -> Self { NetClient } +} + +impl Net for NetClient { + fn version(&self, _: Params) -> Result { + Ok(Value::U64(63)) + } + + fn peer_count(&self, _params: Params) -> Result { + Ok(Value::U64(0)) + } +} diff --git a/rpc/src/impls/web3.rs b/rpc/src/impls/web3.rs new file mode 100644 index 000000000..50eb9c6f5 --- /dev/null +++ b/rpc/src/impls/web3.rs @@ -0,0 +1,18 @@ +use jsonrpc_core::*; +use traits::Web3; + +pub struct Web3Client; + +impl Web3Client { + pub fn new() -> Self { Web3Client } +} + +impl Web3 for Web3Client { + fn client_version(&self, params: Params) -> Result { + match params { + //Params::None => Ok(Value::String("parity/0.1.0/-/rust1.7-nightly".to_owned())), + Params::None => Ok(Value::String("surprise/0.1.0/surprise/surprise".to_owned())), + _ => Err(Error::invalid_params()) + } + } +} diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs new file mode 100644 index 000000000..43a24a1fb --- /dev/null +++ b/rpc/src/lib.rs @@ -0,0 +1,46 @@ +#![feature(custom_derive, custom_attribute, plugin)] +#![feature(slice_patterns)] +#![plugin(serde_macros)] + +extern crate serde; +extern crate serde_json; +extern crate jsonrpc_core; +extern crate jsonrpc_http_server; +extern crate ethcore_util as util; +extern crate ethcore; + +use self::jsonrpc_core::{IoHandler, IoDelegate}; + +macro_rules! rpcerr { + () => (Err(Error::internal_error())) +} + +pub mod traits; +mod impls; +mod types; + +pub use self::traits::{Web3, Eth, EthFilter, Net}; +pub use self::impls::*; + +pub struct HttpServer { + handler: IoHandler, + threads: usize +} + +impl HttpServer { + pub fn new(threads: usize) -> HttpServer { + HttpServer { + handler: IoHandler::new(), + threads: threads + } + } + + pub fn add_delegate(&mut self, delegate: IoDelegate) where D: Send + Sync + 'static { + self.handler.add_delegate(delegate); + } + + pub fn start_async(self, addr: &str) { + let server = jsonrpc_http_server::Server::new(self.handler, self.threads); + server.start_async(addr) + } +} diff --git a/rpc/src/traits/eth.rs b/rpc/src/traits/eth.rs new file mode 100644 index 000000000..63aadbc74 --- /dev/null +++ b/rpc/src/traits/eth.rs @@ -0,0 +1,66 @@ +//! Eth rpc interface. +use std::sync::Arc; +use jsonrpc_core::*; + +/// Eth rpc interface. +pub trait Eth: Sized + Send + Sync + 'static { + /// Returns protocol version. + fn protocol_version(&self, _: Params) -> Result { rpcerr!() } + + /// Returns block author. + fn author(&self, _: Params) -> Result { rpcerr!() } + + /// Returns current gas_price. + fn gas_price(&self, _: Params) -> Result { rpcerr!() } + + /// Returns highest block number. + fn block_number(&self, _: Params) -> Result { rpcerr!() } + + /// Returns block with given index / hash. + fn block(&self, _: Params) -> Result { rpcerr!() } + + /// Returns true if client is actively mining new blocks. + fn is_mining(&self, _: Params) -> Result { rpcerr!() } + + /// Returns the number of hashes per second that the node is mining with. + fn hashrate(&self, _: Params) -> Result { rpcerr!() } + + /// Returns the number of transactions in a block. + fn block_transaction_count(&self, _: Params) -> Result { rpcerr!() } + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("eth_protocolVersion", Eth::protocol_version); + delegate.add_method("eth_coinbase", Eth::author); + delegate.add_method("eth_gasPrice", Eth::gas_price); + delegate.add_method("eth_blockNumber", Eth::block_number); + delegate.add_method("eth_getBlockByHash", Eth::block); + delegate.add_method("eth_getBlockByNumber", Eth::block); + delegate.add_method("eth_mining", Eth::is_mining); + delegate.add_method("eth_hashrate", Eth::hashrate); + delegate.add_method("eth_getBlockTransactionCountByNumber", Eth::block_transaction_count); + delegate + } +} + +// TODO: do filters api properly +pub trait EthFilter: Sized + Send + Sync + 'static { + /// Returns id of new block filter + fn new_block_filter(&self, _: Params) -> Result { rpcerr!() } + + /// Returns id of new block filter + fn new_pending_transaction_filter(&self, _: Params) -> Result { rpcerr!() } + + /// Returns filter changes since last poll + fn filter_changes(&self, _: Params) -> Result { rpcerr!() } + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + 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 + } +} diff --git a/rpc/src/traits/mod.rs b/rpc/src/traits/mod.rs new file mode 100644 index 000000000..2fa52d538 --- /dev/null +++ b/rpc/src/traits/mod.rs @@ -0,0 +1,8 @@ +//! Ethereum rpc interfaces. +pub mod web3; +pub mod eth; +pub mod net; + +pub use self::web3::Web3; +pub use self::eth::{Eth, EthFilter}; +pub use self::net::Net; diff --git a/rpc/src/traits/net.rs b/rpc/src/traits/net.rs new file mode 100644 index 000000000..4df8d7114 --- /dev/null +++ b/rpc/src/traits/net.rs @@ -0,0 +1,20 @@ +//! Net rpc interface. +use std::sync::Arc; +use jsonrpc_core::*; + +/// Net rpc interface. +pub trait Net: Sized + Send + Sync + 'static { + /// Returns protocol version. + fn version(&self, _: Params) -> Result { rpcerr!() } + + /// Returns number of peers connected to node. + fn peer_count(&self, _: Params) -> Result { rpcerr!() } + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("net_version", Net::version); + delegate.add_method("net_peerCount", Net::peer_count); + delegate + } +} diff --git a/rpc/src/traits/web3.rs b/rpc/src/traits/web3.rs new file mode 100644 index 000000000..8e73d4304 --- /dev/null +++ b/rpc/src/traits/web3.rs @@ -0,0 +1,16 @@ +//! Web3 rpc interface. +use std::sync::Arc; +use jsonrpc_core::*; + +/// Web3 rpc interface. +pub trait Web3: Sized + Send + Sync + 'static { + /// Returns current client version. + fn client_version(&self, _: Params) -> Result { rpcerr!() } + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("web3_clientVersion", Web3::client_version); + delegate + } +} diff --git a/rpc/src/types/block.rs b/rpc/src/types/block.rs new file mode 100644 index 000000000..740cf3e09 --- /dev/null +++ b/rpc/src/types/block.rs @@ -0,0 +1,36 @@ +use util::hash::*; +use util::uint::*; + +#[derive(Default, Debug, Serialize)] +pub struct Block { + pub hash: H256, + #[serde(rename="parentHash")] + pub parent_hash: H256, + #[serde(rename="sha3Uncles")] + pub uncles_hash: H256, + pub author: Address, + // TODO: get rid of this one + pub miner: Address, + #[serde(rename="stateRoot")] + pub state_root: H256, + #[serde(rename="transactionsRoot")] + pub transactions_root: H256, + #[serde(rename="receiptsRoot")] + pub receipts_root: H256, + pub number: U256, + #[serde(rename="gasUsed")] + pub gas_used: U256, + #[serde(rename="gasLimit")] + pub gas_limit: U256, + // TODO: figure out how to properly serialize bytes + //#[serde(rename="extraData")] + //extra_data: Vec, + #[serde(rename="logsBloom")] + pub logs_bloom: H2048, + pub timestamp: U256, + pub difficulty: U256, + #[serde(rename="totalDifficulty")] + pub total_difficulty: U256, + pub uncles: Vec, + pub transactions: Vec +} diff --git a/rpc/src/types/mod.rs b/rpc/src/types/mod.rs new file mode 100644 index 000000000..7be32e84d --- /dev/null +++ b/rpc/src/types/mod.rs @@ -0,0 +1,3 @@ +mod block; + +pub use self::block::Block; diff --git a/src/client.rs b/src/client.rs index 33ba19147..1a59aefc5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -66,6 +66,9 @@ pub trait BlockChainClient : Sync + Send { /// Get block status by block header hash. fn block_status(&self, hash: &H256) -> BlockStatus; + /// Get block total difficulty. + fn block_total_difficulty(&self, hash: &H256) -> Option; + /// Get raw block header data by block number. fn block_header_at(&self, n: BlockNumber) -> Option; @@ -79,6 +82,9 @@ pub trait BlockChainClient : Sync + Send { /// Get block status by block number. fn block_status_at(&self, n: BlockNumber) -> BlockStatus; + /// Get block total difficulty. + fn block_total_difficulty_at(&self, n: BlockNumber) -> Option; + /// Get a tree route between `from` and `to`. /// See `BlockChain::tree_route`. fn tree_route(&self, from: &H256, to: &H256) -> Option; @@ -314,6 +320,10 @@ impl BlockChainClient for Client { fn block_status(&self, hash: &H256) -> BlockStatus { if self.chain.read().unwrap().is_known(&hash) { BlockStatus::InChain } else { BlockStatus::Unknown } } + + fn block_total_difficulty(&self, hash: &H256) -> Option { + self.chain.read().unwrap().block_details(hash).map(|d| d.total_difficulty) + } fn block_header_at(&self, n: BlockNumber) -> Option { self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_header(&h)) @@ -334,6 +344,10 @@ impl BlockChainClient for Client { } } + fn block_total_difficulty_at(&self, n: BlockNumber) -> Option { + self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_total_difficulty(&h)) + } + fn tree_route(&self, from: &H256, to: &H256) -> Option { self.chain.read().unwrap().tree_route(from.clone(), to.clone()) } diff --git a/src/evm/jit.rs b/src/evm/jit.rs index 9f990155d..e073a380d 100644 --- a/src/evm/jit.rs +++ b/src/evm/jit.rs @@ -64,7 +64,7 @@ impl IntoJit for H256 { for i in 0..self.bytes().len() { let rev = self.bytes().len() - 1 - i; let pos = rev / 8; - ret[pos] += (self.bytes()[i] as u64) << (rev % 8) * 8; + ret[pos] += (self.bytes()[i] as u64) << ((rev % 8) * 8); } evmjit::I256 { words: ret } } @@ -218,9 +218,11 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { } } - match self.ext.call(&call_gas, + match self.ext.call( + &call_gas, + &self.address, &receive_address, - &value, + Some(value), unsafe { slice::from_raw_parts(in_beg, in_size as usize) }, &code_address, unsafe { slice::from_raw_parts_mut(out_beg, out_size as usize) }) { @@ -262,7 +264,7 @@ impl<'a> evmjit::Ext for ExtAdapter<'a> { } let bytes_ref: &[u8] = slice::from_raw_parts(beg, size as usize); - self.ext.log(topics, bytes_ref.to_vec()); + self.ext.log(topics, bytes_ref); } } @@ -287,8 +289,8 @@ impl evm::Evm for JitEvm { assert!(params.gas <= U256::from(i64::max_value() as u64), "evmjit max gas is 2 ^ 63"); assert!(params.gas_price <= U256::from(i64::max_value() as u64), "evmjit max gas is 2 ^ 63"); - let call_data = params.data.unwrap_or(vec![]); - let code = params.code.unwrap_or(vec![]); + let call_data = params.data.unwrap_or_else(Vec::new); + let code = params.code.unwrap_or_else(Vec::new); let mut data = evmjit::RuntimeDataHandle::new(); data.gas = params.gas.low_u64() as i64; @@ -303,7 +305,10 @@ impl evm::Evm for JitEvm { data.address = params.address.into_jit(); data.caller = params.sender.into_jit(); data.origin = params.origin.into_jit(); - data.call_value = params.value.into_jit(); + data.call_value = match params.value { + ActionValue::Transfer(val) => val.into_jit(), + ActionValue::Apparent(val) => val.into_jit() + }; data.author = ext.env_info().author.clone().into_jit(); data.difficulty = ext.env_info().difficulty.into_jit(); diff --git a/src/evm/schedule.rs b/src/evm/schedule.rs index 70edfceea..d46b7ff11 100644 --- a/src/evm/schedule.rs +++ b/src/evm/schedule.rs @@ -2,67 +2,67 @@ /// Definition of the cost schedule and other parameterisations for the EVM. pub struct Schedule { - /// TODO [Gav Wood] Please document me + /// Does it support exceptional failed code deposit pub exceptional_failed_code_deposit: bool, - /// TODO [Gav Wood] Please document me + /// Does it have a delegate cal pub have_delegate_call: bool, - /// TODO [Tomusdrw] Please document me + /// VM stack limit pub stack_limit: usize, - /// TODO [Gav Wood] Please document me + /// Max number of nested calls/creates pub max_depth: usize, - /// TODO [Gav Wood] Please document me + /// Gas prices for instructions in all tiers pub tier_step_gas: [usize; 8], - /// TODO [Gav Wood] Please document me + /// Gas price for `EXP` opcode pub exp_gas: usize, - /// TODO [Gav Wood] Please document me + /// Additional gas for `EXP` opcode for each byte of exponent pub exp_byte_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for `SHA3` opcode pub sha3_gas: usize, - /// TODO [Gav Wood] Please document me + /// Additional gas for `SHA3` opcode for each word of hashed memory pub sha3_word_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for loading from storage pub sload_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for setting new value to storage (`storage==0`, `new!=0`) pub sstore_set_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for altering value in storage pub sstore_reset_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas refund for `SSTORE` clearing (when `storage!=0`, `new==0`) pub sstore_refund_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for `JUMPDEST` opcode pub jumpdest_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for `LOG*` pub log_gas: usize, - /// TODO [Gav Wood] Please document me + /// Additional gas for data in `LOG*` pub log_data_gas: usize, - /// TODO [Gav Wood] Please document me + /// Additional gas for each topic in `LOG*` pub log_topic_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for `CREATE` opcode pub create_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for `*CALL*` opcodes pub call_gas: usize, - /// TODO [Gav Wood] Please document me + /// Stipend for transfer for `CALL|CALLCODE` opcode when `value>0` pub call_stipend: usize, - /// TODO [Gav Wood] Please document me + /// Additional gas required for value transfer (`CALL|CALLCODE`) pub call_value_transfer_gas: usize, - /// TODO [Gav Wood] Please document me + /// Additional gas for creating new account (`CALL|CALLCODE`) pub call_new_account_gas: usize, - /// TODO [Gav Wood] Please document me + /// Refund for SUICIDE pub suicide_refund_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas for used memory pub memory_gas: usize, - /// TODO [Gav Wood] Please document me + /// Coefficient used to convert memory size to gas price for memory pub quad_coeff_div: usize, - /// TODO [Gav Wood] Please document me + /// Cost for contract length when executing `CREATE` pub create_data_gas: usize, - /// TODO [Gav Wood] Please document me + /// Transaction cost pub tx_gas: usize, - /// TODO [Gav Wood] Please document me + /// `CREATE` transaction cost pub tx_create_gas: usize, - /// TODO [Gav Wood] Please document me + /// Additional cost for empty data transaction pub tx_data_zero_gas: usize, - /// TODO [Gav Wood] Please document me + /// Aditional cost for non-empty data transaction pub tx_data_non_zero_gas: usize, - /// TODO [Gav Wood] Please document me + /// Gas price for copying memory pub copy_gas: usize, } diff --git a/src/evm/tests.rs b/src/evm/tests.rs index ad81cf877..d448ccb3a 100644 --- a/src/evm/tests.rs +++ b/src/evm/tests.rs @@ -215,6 +215,7 @@ fn test_origin(factory: super::Factory) { assert_eq!(ext.store.get(&H256::new()).unwrap(), &H256::from_str("000000000000000000000000cd1722f2947def4cf144679da39c4c32bdc35681").unwrap()); } +// TODO [todr] Fails with Signal 11 on JIT evm_test!{test_sender: test_sender_jit, test_sender_int} fn test_sender(factory: super::Factory) { let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); diff --git a/src/executive.rs b/src/executive.rs index 07ab4c4c5..b21026835 100644 --- a/src/executive.rs +++ b/src/executive.rs @@ -5,6 +5,12 @@ use engine::*; use evm::{self, Ext}; use externalities::*; use substate::*; +use crossbeam; + +/// Max depth to avoid stack overflow (when it's reached we start a new thread with VM) +/// TODO [todr] We probably need some more sophisticated calculations here (limit on my machine 132) +/// Maybe something like here: https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp +const MAX_VM_DEPTH_FOR_THREAD: usize = 128; /// Returns new address created from address and given nonce. pub fn contract_address(address: &Address, nonce: &U256) -> Address { @@ -161,6 +167,27 @@ impl<'a> Executive<'a> { Ok(try!(self.finalize(t, substate, res))) } + fn exec_vm(&mut self, params: ActionParams, unconfirmed_substate: &mut Substate, output_policy: OutputPolicy) -> evm::Result { + // Ordinary execution - keep VM in same thread + if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 { + let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy); + let vm_factory = self.engine.vm_factory(); + return vm_factory.create().exec(params, &mut ext); + } + + // Start in new thread to reset stack + // TODO [todr] No thread builder yet, so we need to reset once for a while + // https://github.com/aturon/crossbeam/issues/16 + crossbeam::scope(|scope| { + let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy); + let vm_factory = self.engine.vm_factory(); + + scope.spawn(move || { + vm_factory.create().exec(params, &mut ext) + }) + }).join() + } + /// Calls contract function with given contract params. /// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides). /// Modifies the substate and the output. @@ -200,8 +227,7 @@ impl<'a> Executive<'a> { let mut unconfirmed_substate = Substate::new(); let res = { - let mut ext = self.as_externalities(OriginInfo::from(¶ms), &mut unconfirmed_substate, OutputPolicy::Return(output)); - self.engine.vm_factory().create().exec(params, &mut ext) + self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output)) }; trace!("exec: sstore-clears={}\n", unconfirmed_substate.sstore_clears_count); @@ -234,8 +260,7 @@ impl<'a> Executive<'a> { } let res = { - let mut ext = self.as_externalities(OriginInfo::from(¶ms), &mut unconfirmed_substate, OutputPolicy::InitContract); - self.engine.vm_factory().create().exec(params, &mut ext) + self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract) }; self.enact_result(&res, substate, unconfirmed_substate, backup); res @@ -275,7 +300,6 @@ impl<'a> Executive<'a> { match result { Err(evm::Error::Internal) => Err(ExecutionError::Internal), - // TODO [ToDr] BadJumpDestination @debris - how to handle that? Err(_) => { Ok(Executed { gas: t.gas, @@ -300,7 +324,6 @@ impl<'a> Executive<'a> { } fn enact_result(&mut self, result: &evm::Result, substate: &mut Substate, un_substate: Substate, backup: State) { - // TODO: handle other evm::Errors same as OutOfGas once they are implemented match *result { Err(evm::Error::OutOfGas) | Err(evm::Error::BadJumpDestination {..}) diff --git a/src/externalities.rs b/src/externalities.rs index f1b8c1958..f9a79c3c0 100644 --- a/src/externalities.rs +++ b/src/externalities.rs @@ -215,7 +215,6 @@ impl<'a> Ext for Externalities<'a> { fn suicide(&mut self, refund_address: &Address) { let address = self.origin_info.address.clone(); let balance = self.balance(&address); - trace!("Suiciding {} -> {} (xfer: {})", address, refund_address, balance); self.state.transfer_balance(&address, refund_address, &balance); self.substate.suicides.insert(address); } diff --git a/src/lib.rs b/src/lib.rs index 262f8682f..8dd02b3bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,7 @@ extern crate num_cpus; extern crate evmjit; #[macro_use] extern crate ethcore_util as util; +extern crate crossbeam; // NOTE: Add doc parser exception for these pub declarations. diff --git a/src/sync/mod.rs b/src/sync/mod.rs index 3223b69bf..34a1e429d 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -9,7 +9,7 @@ /// extern crate ethcore; /// use std::env; /// use std::sync::Arc; -/// use util::network::{NetworkService,NetworkConfiguration}; +/// use util::network::{NetworkService, NetworkConfiguration}; /// use ethcore::client::Client; /// use ethcore::sync::EthSync; /// use ethcore::ethereum; diff --git a/src/tests/client.rs b/src/tests/client.rs new file mode 100644 index 000000000..eb58699b4 --- /dev/null +++ b/src/tests/client.rs @@ -0,0 +1,122 @@ +use client::{BlockChainClient,Client}; +use std::env; +use super::test_common::*; +use std::path::PathBuf; +use spec::*; + +#[cfg(test)] +fn get_random_temp_dir() -> PathBuf { + let mut dir = env::temp_dir(); + dir.push(H32::random().hex()); + dir +} + +#[cfg(test)] +fn get_test_spec() -> Spec { + Spec::new_test() +} + +#[cfg(test)] +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()); + 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; + + create_test_block(&block_header) +} + +#[cfg(test)] +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()); + block_header.timestamp = 40; + block_header.number = 1; + block_header.parent_hash = test_engine.spec().genesis_header().hash(); + block_header.state_root = x!(0xbad); + + create_test_block(&block_header) +} + +#[cfg(test)] +fn create_test_block(header: &Header) -> Bytes { + let mut rlp = RlpStream::new_list(3); + rlp.append(header); + rlp.append_raw(&rlp::EMPTY_LIST_RLP, 1); + rlp.append_raw(&rlp::EMPTY_LIST_RLP, 1); + rlp.out() +} + +#[cfg(test)] +fn get_test_client_with_blocks(blocks: Vec) -> Arc { + let client = Client::new(get_test_spec(), &get_random_temp_dir(), IoChannel::disconnected()).unwrap(); + for block in &blocks { + if let Err(_) = client.import_block(block.clone()) { + panic!("panic importing block which is well-formed"); + } + } + client.flush_queue(); + client.import_verified_blocks(&IoChannel::disconnected()); + client +} + + +#[test] +fn created() { + let client_result = Client::new(get_test_spec(), &get_random_temp_dir(), IoChannel::disconnected()); + assert!(client_result.is_ok()); +} + +#[test] +fn imports_from_empty() { + let client = Client::new(get_test_spec(), &get_random_temp_dir(), IoChannel::disconnected()).unwrap(); + client.import_verified_blocks(&IoChannel::disconnected()); + client.flush_queue(); +} + +#[test] +fn imports_good_block() { + let client = Client::new(get_test_spec(), &get_random_temp_dir(), IoChannel::disconnected()).unwrap(); + let good_block = get_good_dummy_block(); + if let Err(_) = client.import_block(good_block) { + panic!("error importing block being good by definition"); + } + client.flush_queue(); + client.import_verified_blocks(&IoChannel::disconnected()); + + let block = client.block_header_at(1).unwrap(); + assert!(!block.is_empty()); +} + +#[test] +fn query_none_block() { + let client = Client::new(get_test_spec(), &get_random_temp_dir(), IoChannel::disconnected()).unwrap(); + + let non_existant = client.block_header_at(188); + assert!(non_existant.is_none()); +} + +#[test] +fn query_bad_block() { + let client = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); + let bad_block:Option = client.block_header_at(1); + + assert!(bad_block.is_none()); +} + +#[test] +fn returns_chain_info() { + let dummy_block = get_good_dummy_block(); + let client = get_test_client_with_blocks(vec![dummy_block.clone()]); + let block = BlockView::new(&dummy_block); + let info = client.chain_info(); + assert_eq!(info.best_block_hash, block.header().hash()); +} \ No newline at end of file diff --git a/src/tests/executive.rs b/src/tests/executive.rs index a94fd1605..1df1b7eec 100644 --- a/src/tests/executive.rs +++ b/src/tests/executive.rs @@ -271,8 +271,8 @@ fn do_json_test_for(vm: &VMType, json_data: &[u8]) -> Vec { declare_test!{ExecutiveTests_vmArithmeticTest, "VMTests/vmArithmeticTest"} declare_test!{ExecutiveTests_vmBitwiseLogicOperationTest, "VMTests/vmBitwiseLogicOperationTest"} -// this one crashes with some vm internal error. Separately they pass. -declare_test!{ignore => ExecutiveTests_vmBlockInfoTest, "VMTests/vmBlockInfoTest"} +declare_test!{ExecutiveTests_vmBlockInfoTest, "VMTests/vmBlockInfoTest"} + // TODO [todr] Fails with Signal 11 when using JIT declare_test!{ExecutiveTests_vmEnvironmentalInfoTest, "VMTests/vmEnvironmentalInfoTest"} declare_test!{ExecutiveTests_vmIOandFlowOperationsTest, "VMTests/vmIOandFlowOperationsTest"} declare_test!{heavy => ExecutiveTests_vmInputLimits, "VMTests/vmInputLimits"} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 799c47230..290c2e293 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -4,4 +4,5 @@ mod test_common; mod transaction; mod executive; mod state; +mod client; mod chain; diff --git a/src/tests/state.rs b/src/tests/state.rs index 75b9b2493..325a8b646 100644 --- a/src/tests/state.rs +++ b/src/tests/state.rs @@ -73,7 +73,7 @@ fn do_json_test(json_data: &[u8]) -> Vec { declare_test!{StateTests_stBlockHashTest, "StateTests/stBlockHashTest"} declare_test!{StateTests_stCallCodes, "StateTests/stCallCodes"} -declare_test!{ignore => StateTests_stCallCreateCallCodeTest, "StateTests/stCallCreateCallCodeTest"} //<< Out of stack +declare_test!{StateTests_stCallCreateCallCodeTest, "StateTests/stCallCreateCallCodeTest"} declare_test!{StateTests_stDelegatecallTest, "StateTests/stDelegatecallTest"} declare_test!{StateTests_stExample, "StateTests/stExample"} declare_test!{StateTests_stInitCodeTest, "StateTests/stInitCodeTest"} @@ -81,12 +81,12 @@ declare_test!{StateTests_stLogTests, "StateTests/stLogTests"} declare_test!{heavy => StateTests_stMemoryStressTest, "StateTests/stMemoryStressTest"} declare_test!{heavy => StateTests_stMemoryTest, "StateTests/stMemoryTest"} declare_test!{StateTests_stPreCompiledContracts, "StateTests/stPreCompiledContracts"} -declare_test!{heavy => StateTests_stQuadraticComplexityTest, "StateTests/stQuadraticComplexityTest"} //<< Too long -declare_test!{ignore => StateTests_stRecursiveCreate, "StateTests/stRecursiveCreate"} //<< Out of stack +declare_test!{heavy => StateTests_stQuadraticComplexityTest, "StateTests/stQuadraticComplexityTest"} +declare_test!{StateTests_stRecursiveCreate, "StateTests/stRecursiveCreate"} declare_test!{StateTests_stRefundTest, "StateTests/stRefundTest"} declare_test!{StateTests_stSolidityTest, "StateTests/stSolidityTest"} -declare_test!{ignore => StateTests_stSpecialTest, "StateTests/stSpecialTest"} //<< Out of Stack -declare_test!{ignore => StateTests_stSystemOperationsTest, "StateTests/stSystemOperationsTest"} //<< Out of stack +declare_test!{StateTests_stSpecialTest, "StateTests/stSpecialTest"} +declare_test!{StateTests_stSystemOperationsTest, "StateTests/stSystemOperationsTest"} declare_test!{StateTests_stTransactionTest, "StateTests/stTransactionTest"} declare_test!{StateTests_stTransitionTest, "StateTests/stTransitionTest"} declare_test!{StateTests_stWalletTest, "StateTests/stWalletTest"} diff --git a/util/Cargo.toml b/util/Cargo.toml index a91bff962..362db33b2 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -25,6 +25,7 @@ itertools = "0.4" crossbeam = "0.2" slab = { git = "https://github.com/arkpar/slab.git" } sha3 = { path = "sha3" } +serde = "0.6.7" clippy = "*" # Always newest, since we use nightly [dev-dependencies] diff --git a/util/src/hash.rs b/util/src/hash.rs index 8b55c01cd..b3154b57a 100644 --- a/util/src/hash.rs +++ b/util/src/hash.rs @@ -8,6 +8,8 @@ use rand::os::OsRng; use bytes::{BytesConvertable,Populatable}; use from_json::*; use uint::{Uint, U256}; +use rustc_serialize::hex::ToHex; +use serde; /// Trait for a fixed-size byte array to be used as the output of hash functions. /// @@ -215,6 +217,41 @@ macro_rules! impl_hash { } } + impl serde::Serialize for $from { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: serde::Serializer { + let mut hex = "0x".to_owned(); + hex.push_str(self.to_hex().as_ref()); + serializer.visit_str(hex.as_ref()) + } + } + + impl serde::Deserialize for $from { + fn deserialize(deserializer: &mut D) -> Result<$from, D::Error> + where D: serde::Deserializer { + struct HashVisitor; + + impl serde::de::Visitor for HashVisitor { + type Value = $from; + + fn visit_str(&mut self, value: &str) -> Result where E: serde::Error { + // 0x + len + if value.len() != 2 + $size * 2 { + return Err(serde::Error::syntax("Invalid length.")); + } + + value[2..].from_hex().map(|ref v| $from::from_slice(v)).map_err(|_| serde::Error::syntax("Invalid valid hex.")) + } + + fn visit_string(&mut self, value: String) -> Result where E: serde::Error { + self.visit_str(value.as_ref()) + } + } + + deserializer.visit(HashVisitor) + } + } + impl FromJson for $from { fn from_json(json: &Json) -> Self { match *json { @@ -479,6 +516,18 @@ impl<'_> From<&'_ U256> for H256 { } } +impl From for U256 { + fn from(value: H256) -> U256 { + U256::from(value.bytes()) + } +} + +impl<'_> From<&'_ H256> for U256 { + fn from(value: &'_ H256) -> U256 { + U256::from(value.bytes()) + } +} + impl From for Address { fn from(value: H256) -> Address { unsafe { @@ -572,6 +621,7 @@ pub static ZERO_H256: H256 = H256([0x00; 32]); #[cfg(test)] mod tests { use hash::*; + use uint::*; use std::str::FromStr; #[test] @@ -645,5 +695,18 @@ mod tests { // too short. assert_eq!(H64::from(0), H64::from("0x34567890abcdef")); } + + #[test] + fn from_and_to_u256() { + let u: U256 = x!(0x123456789abcdef0u64); + let h = H256::from(u); + assert_eq!(H256::from(u), H256::from("000000000000000000000000000000000000000000000000123456789abcdef0")); + let h_ref = H256::from(&u); + assert_eq!(h, h_ref); + let r_ref: U256 = From::from(&h); + assert_eq!(r_ref, u); + let r: U256 = From::from(h); + assert_eq!(r, u); + } } diff --git a/util/src/lib.rs b/util/src/lib.rs index 970c0713c..b1b93968c 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -55,6 +55,7 @@ extern crate secp256k1; extern crate arrayvec; extern crate elastic_array; extern crate crossbeam; +extern crate serde; /// TODO [Gav Wood] Please document me pub mod standard; diff --git a/util/src/rlp/rlperrors.rs b/util/src/rlp/rlperrors.rs index 8946098bd..6ac162236 100644 --- a/util/src/rlp/rlperrors.rs +++ b/util/src/rlp/rlperrors.rs @@ -23,6 +23,8 @@ pub enum DecoderError { RlpListLenWithZeroPrefix, /// TODO [debris] Please document me RlpInvalidIndirection, + /// Returned when declared length is inconsistent with data specified after + RlpInconsistentLengthAndData } impl StdError for DecoderError { diff --git a/util/src/rlp/tests.rs b/util/src/rlp/tests.rs index f33cec177..84c00e633 100644 --- a/util/src/rlp/tests.rs +++ b/util/src/rlp/tests.rs @@ -4,7 +4,7 @@ use self::json_tests::rlp as rlptest; use std::{fmt, cmp}; use std::str::FromStr; use rlp; -use rlp::{UntrustedRlp, RlpStream, View, Stream}; +use rlp::{UntrustedRlp, RlpStream, View, Stream, DecoderError}; use uint::U256; #[test] @@ -351,3 +351,56 @@ fn test_decoding_array() { assert_eq!(arr[0], 5); assert_eq!(arr[1], 2); } + +#[test] +fn test_rlp_data_length_check() +{ + let data = vec![0x84, b'c', b'a', b't']; + let rlp = UntrustedRlp::new(&data); + + let as_val: Result = rlp.as_val(); + assert_eq!(Err(DecoderError::RlpInconsistentLengthAndData), as_val); +} + +#[test] +fn test_rlp_long_data_length_check() +{ + let mut data: Vec = vec![0xb8, 255]; + for _ in 0..253 { + data.push(b'c'); + } + + let rlp = UntrustedRlp::new(&data); + + let as_val: Result = rlp.as_val(); + assert_eq!(Err(DecoderError::RlpInconsistentLengthAndData), as_val); +} + +#[test] +fn test_the_exact_long_string() +{ + let mut data: Vec = vec![0xb8, 255]; + for _ in 0..255 { + data.push(b'c'); + } + + let rlp = UntrustedRlp::new(&data); + + let as_val: Result = rlp.as_val(); + assert!(as_val.is_ok()); +} + +#[test] +fn test_rlp_2bytes_data_length_check() +{ + let mut data: Vec = vec![0xb9, 2, 255]; // 512+255 + for _ in 0..700 { + data.push(b'c'); + } + + let rlp = UntrustedRlp::new(&data); + + let as_val: Result = rlp.as_val(); + assert_eq!(Err(DecoderError::RlpInconsistentLengthAndData), as_val); +} + diff --git a/util/src/rlp/untrusted_rlp.rs b/util/src/rlp/untrusted_rlp.rs index d7921a25b..07a9a4cde 100644 --- a/util/src/rlp/untrusted_rlp.rs +++ b/util/src/rlp/untrusted_rlp.rs @@ -334,18 +334,31 @@ impl<'a> Decoder for BasicDecoder<'a> { Some(l @ 0...0x7f) => Ok(try!(f(&[l]))), // 0-55 bytes Some(l @ 0x80...0xb7) => { - let d = &bytes[1..(1 + l as usize - 0x80)]; + let last_index_of = 1 + l as usize - 0x80; + if bytes.len() < last_index_of { + return Err(DecoderError::RlpInconsistentLengthAndData); + } + let d = &bytes[1..last_index_of]; if l == 0x81 && d[0] < 0x80 { return Err(DecoderError::RlpInvalidIndirection); } + Ok(try!(f(d))) }, // longer than 55 bytes Some(l @ 0xb8...0xbf) => { let len_of_len = l as usize - 0xb7; let begin_of_value = 1 as usize + len_of_len; + if bytes.len() < begin_of_value { + return Err(DecoderError::RlpInconsistentLengthAndData); + } let len = try!(usize::from_bytes(&bytes[1..begin_of_value])); - Ok(try!(f(&bytes[begin_of_value..begin_of_value + len]))) + + let last_index_of_value = begin_of_value + len; + if bytes.len() < last_index_of_value { + return Err(DecoderError::RlpInconsistentLengthAndData); + } + Ok(try!(f(&bytes[begin_of_value..last_index_of_value]))) } // we are reading value, not a list! _ => Err(DecoderError::RlpExpectedToBeData) diff --git a/util/src/uint.rs b/util/src/uint.rs index ab136d7c6..de05ca4a8 100644 --- a/util/src/uint.rs +++ b/util/src/uint.rs @@ -23,6 +23,8 @@ use standard::*; use from_json::*; +use rustc_serialize::hex::ToHex; +use serde; macro_rules! impl_map_from { ($thing:ident, $from:ty, $to:ty) => { @@ -436,6 +438,17 @@ macro_rules! construct_uint { } } + impl serde::Serialize for $name { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: serde::Serializer { + let mut hex = "0x".to_owned(); + let mut bytes = [0u8; 8 * $n_words]; + self.to_bytes(&mut bytes); + hex.push_str(bytes.to_hex().as_ref()); + serializer.visit_str(hex.as_ref()) + } + } + impl From for $name { fn from(value: u64) -> $name { let mut ret = [0; $n_words];