diff --git a/Cargo.lock b/Cargo.lock index 2ea22f715..7a10ec3c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,6 +426,7 @@ dependencies = [ "serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 212dead9a..be1e9da25 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -73,6 +73,8 @@ pub struct TestBlockChainClient { pub spec: Spec, /// VM Factory pub vm_factory: EvmFactory, + /// Timestamp assigned to latest sealed block + pub latest_block_timestamp: RwLock, } #[derive(Clone)] @@ -114,6 +116,7 @@ impl TestBlockChainClient { miner: Arc::new(Miner::with_spec(&spec)), spec: spec, vm_factory: EvmFactory::new(VMType::Interpreter), + latest_block_timestamp: RwLock::new(10_000_000), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().clone(); @@ -155,6 +158,11 @@ impl TestBlockChainClient { self.queue_size.store(size, AtomicOrder::Relaxed); } + /// Set timestamp assigned to latest sealed block + pub fn set_latest_block_timestamp(&self, ts: u64) { + *self.latest_block_timestamp.write() = ts; + } + /// Add blocks to test client. pub fn add_blocks(&self, count: usize, with: EachBlockWith) { let len = self.numbers.read().len(); @@ -279,7 +287,7 @@ impl MiningBlockChainClient for TestBlockChainClient { extra_data ).expect("Opening block for tests will not fail."); // TODO [todr] Override timestamp for predictability (set_timestamp_now kind of sucks) - open_block.set_timestamp(10_000_000); + open_block.set_timestamp(*self.latest_block_timestamp.read()); open_block } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 6a9478eb3..7a70f52c7 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -28,6 +28,7 @@ serde_macros = { version = "0.7.0", optional = true } clippy = { version = "0.0.85", optional = true} json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } ethcore-ipc = { path = "../ipc/rpc" } +time = "0.1" [build-dependencies] serde_codegen = { version = "0.7.0", optional = true } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 9322d8dbc..01a901732 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -35,6 +35,7 @@ extern crate ethsync; extern crate transient_hashmap; extern crate json_ipc_server as ipc; extern crate ethcore_ipc; +extern crate time; #[cfg(test)] extern crate ethjson; diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index bb62a80e5..fbd134e33 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -30,6 +30,7 @@ mod codes { pub const UNSUPPORTED_REQUEST: i64 = -32000; pub const NO_WORK: i64 = -32001; pub const NO_AUTHOR: i64 = -32002; + pub const NO_NEW_WORK: i64 = -32003; pub const UNKNOWN_ERROR: i64 = -32009; pub const TRANSACTION_ERROR: i64 = -32010; pub const ACCOUNT_LOCKED: i64 = -32020; @@ -114,6 +115,14 @@ pub fn no_work() -> Error { } } +pub fn no_new_work() -> Error { + Error { + code: ErrorCode::ServerError(codes::NO_NEW_WORK), + message: "Work has not changed.".into(), + data: None + } +} + pub fn no_author() -> Error { Error { code: ErrorCode::ServerError(codes::NO_AUTHOR), diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 64ca059af..3628f99a9 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -23,6 +23,7 @@ use std::process::{Command, Stdio}; use std::thread; use std::time::{Instant, Duration}; use std::sync::{Arc, Weak}; +use time::get_time; use ethsync::{SyncProvider, SyncState}; use ethcore::miner::{MinerService, ExternalMinerService}; use jsonrpc_core::*; @@ -516,7 +517,7 @@ impl Eth for EthClient where fn work(&self, params: Params) -> Result { try!(self.active()); - try!(expect_no_params(params)); + let (no_new_work_timeout,) = from_params::<(u64,)>(params).unwrap_or((0,)); let client = take_weak!(self.client); // check if we're still syncing and return empty strings in that case @@ -545,7 +546,9 @@ impl Eth for EthClient where let target = Ethash::difficulty_to_boundary(b.block().header().difficulty()); let seed_hash = self.seed_compute.lock().get_seedhash(b.block().header().number()); - if self.options.send_block_number_in_get_work { + if no_new_work_timeout > 0 && b.block().header().timestamp() + no_new_work_timeout < get_time().sec as u64 { + Err(errors::no_new_work()) + } else if self.options.send_block_number_in_get_work { let block_number = RpcU256::from(b.block().header().number()); to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number)) } else { diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 20d074ca6..4b880419a 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -30,6 +30,7 @@ use ethsync::SyncState; use v1::{Eth, EthClient, EthClientOptions, EthSigning, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; use rustc_serialize::hex::ToHex; +use time::get_time; fn blockchain_client() -> Arc { let client = TestBlockChainClient::new(); @@ -818,7 +819,7 @@ fn rpc_eth_compile_serpent() { } #[test] -fn returns_no_work_if_cant_mine() { +fn rpc_get_work_returns_no_work_if_cant_mine() { let eth_tester = EthTester::default(); eth_tester.client.set_queue_size(10); @@ -829,7 +830,7 @@ fn returns_no_work_if_cant_mine() { } #[test] -fn returns_correct_work_package() { +fn rpc_get_work_returns_correct_work_package() { let eth_tester = EthTester::default(); eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); @@ -840,7 +841,7 @@ fn returns_correct_work_package() { } #[test] -fn should_not_return_block_number() { +fn rpc_get_work_should_not_return_block_number() { let eth_tester = EthTester::new_with_options(EthClientOptions { allow_pending_receipt_query: true, send_block_number_in_get_work: false, @@ -852,3 +853,28 @@ fn should_not_return_block_number() { assert_eq!(eth_tester.io.handle_request(request), Some(response.to_owned())); } + +#[test] +fn rpc_get_work_should_timeout() { + let eth_tester = EthTester::default(); + eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); + eth_tester.client.set_latest_block_timestamp(get_time().sec as u64 - 1000); // Set latest block to 1000 seconds ago + let hash = eth_tester.miner.map_sealing_work(&*eth_tester.client, |b| b.hash()).unwrap(); + + // Request with timeout of 0 seconds. This should work since we're disabling timeout. + let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#; + let work_response = format!( + r#"{{"jsonrpc":"2.0","result":["0x{:?}","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000","0x01"],"id":1}}"#, + hash, + ); + assert_eq!(eth_tester.io.handle_request(request), Some(work_response.to_owned())); + + // Request with timeout of 10K seconds. This should work. + let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": ["10000"], "id": 1}"#; + assert_eq!(eth_tester.io.handle_request(request), Some(work_response.to_owned())); + + // Request with timeout of 10 seconds. This should fail. + let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": ["10"], "id": 1}"#; + let err_response = r#"{"jsonrpc":"2.0","error":{"code":-32003,"message":"Work has not changed.","data":null},"id":1}"#; + assert_eq!(eth_tester.io.handle_request(request), Some(err_response.to_owned())); +}