Add timeout for eth_getWork call (#1975)

This commit is contained in:
Nipunn Koorapati 2016-08-23 08:07:00 -07:00 committed by Gav Wood
parent 59ede63eda
commit 2a550c2adf
7 changed files with 55 additions and 6 deletions

1
Cargo.lock generated
View File

@ -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)",
]

View File

@ -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<u64>,
}
#[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
}

View File

@ -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 }

View File

@ -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;

View File

@ -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),

View File

@ -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<C, S: ?Sized, M, EM> Eth for EthClient<C, S, M, EM> where
fn work(&self, params: Params) -> Result<Value, Error> {
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<C, S: ?Sized, M, EM> Eth for EthClient<C, S, M, EM> 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 {

View File

@ -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<TestBlockChainClient> {
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()));
}