Fix RPC errors. Implement geth-compatibility option to return correct pending nonce. (#4837)
This commit is contained in:
parent
608c0dc613
commit
0ab0cb1173
@ -215,6 +215,7 @@ pub fn setup_rpc(stats: Arc<RpcStats>, deps: Arc<Dependencies>, apis: ApiSet) ->
|
|||||||
&deps.miner,
|
&deps.miner,
|
||||||
&deps.external_miner,
|
&deps.external_miner,
|
||||||
EthClientOptions {
|
EthClientOptions {
|
||||||
|
pending_nonce_from_queue: deps.geth_compatibility,
|
||||||
allow_pending_receipt_query: !deps.geth_compatibility,
|
allow_pending_receipt_query: !deps.geth_compatibility,
|
||||||
send_block_number_in_get_work: !deps.geth_compatibility,
|
send_block_number_in_get_work: !deps.geth_compatibility,
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ mod codes {
|
|||||||
pub const TRANSACTION_ERROR: i64 = -32010;
|
pub const TRANSACTION_ERROR: i64 = -32010;
|
||||||
pub const EXECUTION_ERROR: i64 = -32015;
|
pub const EXECUTION_ERROR: i64 = -32015;
|
||||||
pub const EXCEPTION_ERROR: i64 = -32016;
|
pub const EXCEPTION_ERROR: i64 = -32016;
|
||||||
|
pub const DATABASE_ERROR: i64 = -32017;
|
||||||
pub const ACCOUNT_LOCKED: i64 = -32020;
|
pub const ACCOUNT_LOCKED: i64 = -32020;
|
||||||
pub const PASSWORD_INVALID: i64 = -32021;
|
pub const PASSWORD_INVALID: i64 = -32021;
|
||||||
pub const ACCOUNT_ERROR: i64 = -32023;
|
pub const ACCOUNT_ERROR: i64 = -32023;
|
||||||
@ -100,6 +101,9 @@ pub fn account<T: fmt::Debug>(error: &str, details: T) -> Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal error signifying a logic error in code.
|
||||||
|
/// Should not be used when function can just fail
|
||||||
|
/// because of invalid parameters or incomplete node state.
|
||||||
pub fn internal<T: fmt::Debug>(error: &str, data: T) -> Error {
|
pub fn internal<T: fmt::Debug>(error: &str, data: T) -> Error {
|
||||||
Error {
|
Error {
|
||||||
code: ErrorCode::InternalError,
|
code: ErrorCode::InternalError,
|
||||||
@ -216,6 +220,14 @@ pub fn encryption_error<T: fmt::Debug>(error: T) -> Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn database_error<T: fmt::Debug>(error: T) -> Error {
|
||||||
|
Error {
|
||||||
|
code: ErrorCode::ServerError(codes::DATABASE_ERROR),
|
||||||
|
message: "Database error.".into(),
|
||||||
|
data: Some(Value::String(format!("{:?}", error))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_fetch_error<T: fmt::Debug>(error: T) -> Error {
|
pub fn from_fetch_error<T: fmt::Debug>(error: T) -> Error {
|
||||||
Error {
|
Error {
|
||||||
code: ErrorCode::ServerError(codes::FETCH_ERROR),
|
code: ErrorCode::ServerError(codes::FETCH_ERROR),
|
||||||
|
@ -58,15 +58,28 @@ const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched
|
|||||||
|
|
||||||
/// Eth RPC options
|
/// Eth RPC options
|
||||||
pub struct EthClientOptions {
|
pub struct EthClientOptions {
|
||||||
|
/// Return nonce from transaction queue when pending block not available.
|
||||||
|
pub pending_nonce_from_queue: bool,
|
||||||
/// Returns receipt from pending blocks
|
/// Returns receipt from pending blocks
|
||||||
pub allow_pending_receipt_query: bool,
|
pub allow_pending_receipt_query: bool,
|
||||||
/// Send additional block number when asking for work
|
/// Send additional block number when asking for work
|
||||||
pub send_block_number_in_get_work: bool,
|
pub send_block_number_in_get_work: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EthClientOptions {
|
||||||
|
/// Creates new default `EthClientOptions` and allows alterations
|
||||||
|
/// by provided function.
|
||||||
|
pub fn with<F: Fn(&mut Self)>(fun: F) -> Self {
|
||||||
|
let mut options = Self::default();
|
||||||
|
fun(&mut options);
|
||||||
|
options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for EthClientOptions {
|
impl Default for EthClientOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
EthClientOptions {
|
EthClientOptions {
|
||||||
|
pending_nonce_from_queue: false,
|
||||||
allow_pending_receipt_query: true,
|
allow_pending_receipt_query: true,
|
||||||
send_block_number_in_get_work: true,
|
send_block_number_in_get_work: true,
|
||||||
}
|
}
|
||||||
@ -227,7 +240,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
|||||||
store
|
store
|
||||||
.note_dapp_used(dapp.clone())
|
.note_dapp_used(dapp.clone())
|
||||||
.and_then(|_| store.dapp_addresses(dapp))
|
.and_then(|_| store.dapp_addresses(dapp))
|
||||||
.map_err(|e| errors::internal("Could not fetch accounts.", e))
|
.map_err(|e| errors::account("Could not fetch accounts.", e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,18 +365,16 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
|
|
||||||
fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
||||||
let address = address.into();
|
let address = address.into();
|
||||||
|
let client = take_weakf!(self.client);
|
||||||
|
|
||||||
let res = match num.0.clone() {
|
let res = match num.0.clone() {
|
||||||
BlockNumber::Pending => {
|
BlockNumber::Pending => {
|
||||||
let client = take_weakf!(self.client);
|
|
||||||
match take_weakf!(self.miner).balance(&*client, &address) {
|
match take_weakf!(self.miner).balance(&*client, &address) {
|
||||||
Some(balance) => Ok(balance.into()),
|
Some(balance) => Ok(balance.into()),
|
||||||
None => Err(errors::internal("Unable to load balance from database", ""))
|
None => Err(errors::database_error("latest balance missing"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id => {
|
id => {
|
||||||
let client = take_weakf!(self.client);
|
|
||||||
|
|
||||||
try_bf!(check_known(&*client, id.clone()));
|
try_bf!(check_known(&*client, id.clone()));
|
||||||
match client.balance(&address, id.into()) {
|
match client.balance(&address, id.into()) {
|
||||||
Some(balance) => Ok(balance.into()),
|
Some(balance) => Ok(balance.into()),
|
||||||
@ -384,7 +395,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let client = take_weakf!(self.client);
|
let client = take_weakf!(self.client);
|
||||||
match take_weakf!(self.miner).storage_at(&*client, &address, &H256::from(position)) {
|
match take_weakf!(self.miner).storage_at(&*client, &address, &H256::from(position)) {
|
||||||
Some(s) => Ok(s.into()),
|
Some(s) => Ok(s.into()),
|
||||||
None => Err(errors::internal("Unable to load storage from database", ""))
|
None => Err(errors::database_error("latest storage missing"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id => {
|
id => {
|
||||||
@ -403,17 +414,26 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
|
|
||||||
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
|
||||||
let address: Address = RpcH160::into(address);
|
let address: Address = RpcH160::into(address);
|
||||||
let res = match num.0.clone() {
|
|
||||||
BlockNumber::Pending => {
|
|
||||||
let client = take_weakf!(self.client);
|
let client = take_weakf!(self.client);
|
||||||
match take_weakf!(self.miner).nonce(&*client, &address) {
|
let miner = take_weakf!(self.miner);
|
||||||
|
|
||||||
|
let res = match num.0.clone() {
|
||||||
|
BlockNumber::Pending if self.options.pending_nonce_from_queue => {
|
||||||
|
let nonce = miner.last_nonce(&address)
|
||||||
|
.map(|n| n + 1.into())
|
||||||
|
.or_else(|| miner.nonce(&*client, &address));
|
||||||
|
match nonce {
|
||||||
Some(nonce) => Ok(nonce.into()),
|
Some(nonce) => Ok(nonce.into()),
|
||||||
None => Err(errors::internal("Unable to load nonce from database", ""))
|
None => Err(errors::database_error("latest nonce missing"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockNumber::Pending => {
|
||||||
|
match miner.nonce(&*client, &address) {
|
||||||
|
Some(nonce) => Ok(nonce.into()),
|
||||||
|
None => Err(errors::database_error("latest nonce missing"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id => {
|
id => {
|
||||||
let client = take_weakf!(self.client);
|
|
||||||
|
|
||||||
try_bf!(check_known(&*client, id.clone()));
|
try_bf!(check_known(&*client, id.clone()));
|
||||||
match client.nonce(&address, id.into()) {
|
match client.nonce(&address, id.into()) {
|
||||||
Some(nonce) => Ok(nonce.into()),
|
Some(nonce) => Ok(nonce.into()),
|
||||||
@ -464,7 +484,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
let client = take_weakf!(self.client);
|
let client = take_weakf!(self.client);
|
||||||
match take_weakf!(self.miner).code(&*client, &address) {
|
match take_weakf!(self.miner).code(&*client, &address) {
|
||||||
Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)),
|
Some(code) => Ok(code.map_or_else(Bytes::default, Bytes::new)),
|
||||||
None => Err(errors::internal("Unable to load code from database", ""))
|
None => Err(errors::database_error("latest code missing"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id => {
|
id => {
|
||||||
@ -597,7 +617,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
|||||||
number: None
|
number: None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).unwrap_or(Err(Error::internal_error())) // no work found.
|
}).unwrap_or(Err(errors::internal("No work found.", "")))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result<bool, Error> {
|
fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result<bool, Error> {
|
||||||
|
@ -263,7 +263,7 @@ impl Eth for EthClient {
|
|||||||
let accounts = self.accounts
|
let accounts = self.accounts
|
||||||
.note_dapp_used(dapp.clone())
|
.note_dapp_used(dapp.clone())
|
||||||
.and_then(|_| self.accounts.dapp_addresses(dapp))
|
.and_then(|_| self.accounts.dapp_addresses(dapp))
|
||||||
.map_err(|e| errors::internal("Could not fetch accounts.", e))
|
.map_err(|e| errors::account("Could not fetch accounts.", e))
|
||||||
.map(|accs| accs.into_iter().map(Into::<RpcH160>::into).collect());
|
.map(|accs| accs.into_iter().map(Into::<RpcH160>::into).collect());
|
||||||
|
|
||||||
future::done(accounts).boxed()
|
future::done(accounts).boxed()
|
||||||
|
@ -87,7 +87,7 @@ impl Parity for ParityClient {
|
|||||||
let dapp_accounts = store
|
let dapp_accounts = store
|
||||||
.note_dapp_used(dapp.clone().into())
|
.note_dapp_used(dapp.clone().into())
|
||||||
.and_then(|_| store.dapp_addresses(dapp.into()))
|
.and_then(|_| store.dapp_addresses(dapp.into()))
|
||||||
.map_err(|e| errors::internal("Could not fetch accounts.", e))?
|
.map_err(|e| errors::account("Could not fetch accounts.", e))?
|
||||||
.into_iter().collect::<HashSet<_>>();
|
.into_iter().collect::<HashSet<_>>();
|
||||||
|
|
||||||
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
||||||
|
@ -119,7 +119,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
let dapp_accounts = store
|
let dapp_accounts = store
|
||||||
.note_dapp_used(dapp.clone().into())
|
.note_dapp_used(dapp.clone().into())
|
||||||
.and_then(|_| store.dapp_addresses(dapp.into()))
|
.and_then(|_| store.dapp_addresses(dapp.into()))
|
||||||
.map_err(|e| errors::internal("Could not fetch accounts.", e))?
|
.map_err(|e| errors::account("Could not fetch accounts.", e))?
|
||||||
.into_iter().collect::<HashSet<_>>();
|
.into_iter().collect::<HashSet<_>>();
|
||||||
|
|
||||||
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
//! Test implementation of miner service.
|
//! Test implementation of miner service.
|
||||||
|
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
use util::{Address, H256, Bytes, U256, FixedHash, Uint};
|
use util::{Address, H256, Bytes, U256, FixedHash, Uint};
|
||||||
use util::standard::*;
|
use util::standard::*;
|
||||||
use ethcore::error::{Error, CallError};
|
use ethcore::error::{Error, CallError};
|
||||||
@ -72,6 +73,22 @@ impl Default for TestMinerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TestMinerService {
|
||||||
|
/// Increments last nonce for given address.
|
||||||
|
pub fn increment_last_nonce(&self, address: Address) {
|
||||||
|
let mut last_nonces = self.last_nonces.write();
|
||||||
|
match last_nonces.entry(address) {
|
||||||
|
Entry::Occupied(mut occupied) => {
|
||||||
|
let val = *occupied.get();
|
||||||
|
*occupied.get_mut() = val + 1.into();
|
||||||
|
},
|
||||||
|
Entry::Vacant(vacant) => {
|
||||||
|
vacant.insert(0.into());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MinerService for TestMinerService {
|
impl MinerService for TestMinerService {
|
||||||
|
|
||||||
/// Returns miner's status.
|
/// Returns miner's status.
|
||||||
|
@ -468,6 +468,32 @@ fn rpc_eth_transaction_count() {
|
|||||||
assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned()));
|
assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rpc_eth_transaction_count_next_nonce() {
|
||||||
|
let tester = EthTester::new_with_options(EthClientOptions::with(|mut options| {
|
||||||
|
options.pending_nonce_from_queue = true;
|
||||||
|
}));
|
||||||
|
tester.miner.increment_last_nonce(1.into());
|
||||||
|
|
||||||
|
let request1 = r#"{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getTransactionCount",
|
||||||
|
"params": ["0x0000000000000000000000000000000000000001", "pending"],
|
||||||
|
"id": 1
|
||||||
|
}"#;
|
||||||
|
let response1 = r#"{"jsonrpc":"2.0","result":"0x1","id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request1), Some(response1.to_owned()));
|
||||||
|
|
||||||
|
let request2 = r#"{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getTransactionCount",
|
||||||
|
"params": ["0x0000000000000000000000000000000000000002", "pending"],
|
||||||
|
"id": 1
|
||||||
|
}"#;
|
||||||
|
let response2 = r#"{"jsonrpc":"2.0","result":"0x0","id":1}"#;
|
||||||
|
assert_eq!(tester.io.handle_request_sync(request2), Some(response2.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_eth_block_transaction_count_by_hash() {
|
fn rpc_eth_block_transaction_count_by_hash() {
|
||||||
let request = r#"{
|
let request = r#"{
|
||||||
@ -1076,10 +1102,9 @@ fn rpc_get_work_returns_correct_work_package() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rpc_get_work_should_not_return_block_number() {
|
fn rpc_get_work_should_not_return_block_number() {
|
||||||
let eth_tester = EthTester::new_with_options(EthClientOptions {
|
let eth_tester = EthTester::new_with_options(EthClientOptions::with(|mut options| {
|
||||||
allow_pending_receipt_query: true,
|
options.send_block_number_in_get_work = false;
|
||||||
send_block_number_in_get_work: false,
|
}));
|
||||||
});
|
|
||||||
eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap());
|
eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap());
|
||||||
|
|
||||||
let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#;
|
let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#;
|
||||||
|
Loading…
Reference in New Issue
Block a user