Merge remote-tracking branch 'origin/master' into vmtracing

This commit is contained in:
Gav Wood
2016-06-02 11:40:39 +02:00
92 changed files with 2666 additions and 1573 deletions

View File

@@ -18,7 +18,6 @@ ethcore-util = { path = "../util" }
ethcore = { path = "../ethcore" }
ethash = { path = "../ethash" }
ethsync = { path = "../sync" }
ethminer = { path = "../miner" }
ethjson = { path = "../json" }
ethcore-devtools = { path = "../devtools" }
rustc-serialize = "0.3"
@@ -34,4 +33,4 @@ syntex = "^0.32.0"
[features]
default = ["serde_codegen"]
nightly = ["serde_macros"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethminer/dev"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev"]

View File

@@ -30,7 +30,6 @@ extern crate jsonrpc_http_server;
extern crate ethcore_util as util;
extern crate ethcore;
extern crate ethsync;
extern crate ethminer;
extern crate transient_hashmap;
extern crate json_ipc_server as ipc;
@@ -45,12 +44,26 @@ use self::jsonrpc_core::{IoHandler, IoDelegate};
pub use jsonrpc_http_server::{Server, RpcServerError};
pub mod v1;
pub use v1::{SigningQueue, ConfirmationsQueue};
/// An object that can be extended with `IoDelegates`
pub trait Extendable {
/// Add `Delegate` to this object.
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>);
}
/// Http server.
pub struct RpcServer {
handler: Arc<jsonrpc_core::io::IoHandler>,
}
impl Extendable for RpcServer {
/// Add io delegate.
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
self.handler.add_delegate(delegate);
}
}
impl RpcServer {
/// Construct new http server object.
pub fn new() -> RpcServer {
@@ -59,11 +72,6 @@ impl RpcServer {
}
}
/// Add io delegate.
pub fn add_delegate<D>(&self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Start http server asynchronously and returns result with `Server` handle on success or an error.
pub fn start_http(&self, addr: &SocketAddr, cors_domains: Vec<String>) -> Result<Server, RpcServerError> {
let cors_domains = cors_domains.into_iter()

View File

@@ -16,6 +16,8 @@
mod poll_manager;
mod poll_filter;
mod signing_queue;
pub use self::poll_manager::PollManager;
pub use self::poll_filter::PollFilter;
pub use self::signing_queue::{ConfirmationsQueue, SigningQueue};

View File

@@ -0,0 +1,108 @@
// 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://www.gnu.org/licenses/>.
use std::sync::Mutex;
use std::collections::HashMap;
use v1::types::{TransactionRequest, TransactionConfirmation};
use util::U256;
/// A queue of transactions awaiting to be confirmed and signed.
pub trait SigningQueue: Send + Sync {
/// Add new request to the queue.
fn add_request(&self, transaction: TransactionRequest) -> U256;
/// Remove request from the queue.
fn remove_request(&self, id: U256) -> Option<TransactionConfirmation>;
/// Return copy of all the requests in the queue.
fn requests(&self) -> Vec<TransactionConfirmation>;
}
/// Queue for all unconfirmed transactions.
pub struct ConfirmationsQueue {
id: Mutex<U256>,
queue: Mutex<HashMap<U256, TransactionConfirmation>>,
}
impl Default for ConfirmationsQueue {
fn default() -> Self {
ConfirmationsQueue {
id: Mutex::new(U256::from(0)),
queue: Mutex::new(HashMap::new()),
}
}
}
impl SigningQueue for ConfirmationsQueue {
fn add_request(&self, transaction: TransactionRequest) -> U256 {
// Increment id
let id = {
let mut last_id = self.id.lock().unwrap();
*last_id = *last_id + U256::from(1);
*last_id
};
let mut queue = self.queue.lock().unwrap();
queue.insert(id, TransactionConfirmation {
id: id,
transaction: transaction,
});
id
}
fn remove_request(&self, id: U256) -> Option<TransactionConfirmation> {
self.queue.lock().unwrap().remove(&id)
}
fn requests(&self) -> Vec<TransactionConfirmation> {
let queue = self.queue.lock().unwrap();
queue.values().cloned().collect()
}
}
#[cfg(test)]
mod test {
use util::hash::Address;
use util::numbers::U256;
use v1::types::TransactionRequest;
use super::*;
#[test]
fn should_work_for_hashset() {
// given
let queue = ConfirmationsQueue::default();
let request = TransactionRequest {
from: Address::from(1),
to: Some(Address::from(2)),
gas_price: None,
gas: None,
value: Some(U256::from(10_000_000)),
data: None,
nonce: None,
};
// when
queue.add_request(request.clone());
let all = queue.requests();
// then
assert_eq!(all.len(), 1);
let el = all.get(0).unwrap();
assert_eq!(el.id, U256::from(1));
assert_eq!(el.transaction, request);
}
}

View File

@@ -18,17 +18,16 @@
extern crate ethash;
use std::collections::HashSet;
use std::sync::{Arc, Weak, Mutex};
use std::ops::Deref;
use ethsync::{SyncProvider, SyncState};
use ethminer::{MinerService, ExternalMinerService};
use ethcore::miner::{MinerService, ExternalMinerService};
use jsonrpc_core::*;
use util::numbers::*;
use util::sha3::*;
use util::rlp::{encode, decode, UntrustedRlp, View};
use util::keys::store::AccountProvider;
use ethcore::client::{BlockChainClient, BlockID, TransactionID, UncleID};
use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID};
use ethcore::block::IsBlock;
use ethcore::views::*;
use ethcore::ethereum::Ethash;
@@ -36,15 +35,14 @@ use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Act
use ethcore::log_entry::LogEntry;
use ethcore::filter::Filter as EthcoreFilter;
use self::ethash::SeedHashCompute;
use v1::traits::{Eth, EthFilter};
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt};
use v1::helpers::{PollFilter, PollManager};
use v1::impls::{dispatch_transaction, sign_and_dispatch};
use v1::traits::Eth;
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, OptionalValue, Index, Filter, Log, Receipt};
use v1::impls::dispatch_transaction;
use serde;
/// Eth rpc implementation.
pub struct EthClient<C, S, A, M, EM> where
C: BlockChainClient,
C: MiningBlockChainClient,
S: SyncProvider,
A: AccountProvider,
M: MinerService,
@@ -59,7 +57,7 @@ pub struct EthClient<C, S, A, M, EM> where
}
impl<C, S, A, M, EM> EthClient<C, S, A, M, EM> where
C: BlockChainClient,
C: MiningBlockChainClient,
S: SyncProvider,
A: AccountProvider,
M: MinerService,
@@ -170,6 +168,25 @@ impl<C, S, A, M, EM> EthClient<C, S, A, M, EM> where
}
}
pub fn pending_logs<M>(miner: &M, filter: &EthcoreFilter) -> Vec<Log> where M: MinerService {
let receipts = miner.pending_receipts();
let pending_logs = receipts.into_iter()
.flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::<Vec<(H256, LogEntry)>>())
.collect::<Vec<(H256, LogEntry)>>();
let result = pending_logs.into_iter()
.filter(|pair| filter.matches(&pair.1))
.map(|pair| {
let mut log = Log::from(pair.1);
log.transaction_hash = Some(pair.0);
log
})
.collect();
result
}
const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6.
fn params_len(params: &Params) -> usize {
@@ -193,25 +210,6 @@ fn from_params_default_third<F1, F2>(params: Params) -> Result<(F1, F2, BlockNum
}
}
fn pending_logs<M>(miner: &M, filter: &EthcoreFilter) -> Vec<Log> where M: MinerService {
let receipts = miner.pending_receipts();
let pending_logs = receipts.into_iter()
.flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::<Vec<(H256, LogEntry)>>())
.collect::<Vec<(H256, LogEntry)>>();
let result = pending_logs.into_iter()
.filter(|pair| filter.matches(&pair.1))
.map(|pair| {
let mut log = Log::from(pair.1);
log.transaction_hash = Some(pair.0);
log
})
.collect();
result
}
// must be in range [-32099, -32000]
const UNSUPPORTED_REQUEST_CODE: i64 = -32000;
@@ -224,7 +222,7 @@ fn make_unsupported_err() -> Error {
}
impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
C: BlockChainClient + 'static,
C: MiningBlockChainClient + 'static,
S: SyncProvider + 'static,
A: AccountProvider + 'static,
M: MinerService + 'static,
@@ -496,23 +494,6 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
})
}
fn sign(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, H256)>(params).and_then(|(addr, msg)| {
to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero()))
})
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let accounts = take_weak!(self.accounts);
match accounts.account_secret(&request.from) {
Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret),
Err(_) => to_value(&H256::zero())
}
})
}
fn send_raw_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(Bytes, )>(params)
.and_then(|(raw_transaction, )| {
@@ -563,186 +544,3 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM> where
rpc_unimplemented!()
}
}
/// Eth filter rpc implementation.
pub struct EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
client: Weak<C>,
miner: Weak<M>,
polls: Mutex<PollManager<PollFilter>>,
}
impl<C, M> EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
/// Creates new Eth filter client.
pub fn new(client: &Arc<C>, miner: &Arc<M>) -> Self {
EthFilterClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
polls: Mutex::new(PollManager::new()),
}
}
}
impl<C, M> EthFilter for EthFilterClient<C, M> where
C: BlockChainClient + 'static,
M: MinerService + 'static {
fn new_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Filter,)>(params)
.and_then(|(filter,)| {
let mut polls = self.polls.lock().unwrap();
let block_number = take_weak!(self.client).chain_info().best_block_number;
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
to_value(&U256::from(id))
})
}
fn new_block_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn new_pending_transaction_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes();
let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn filter_changes(&self, params: Params) -> Result<Value, Error> {
let client = take_weak!(self.client);
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll_mut(&index.value()) {
None => Ok(Value::Array(vec![] as Vec<Value>)),
Some(filter) => match *filter {
PollFilter::Block(ref mut block_number) => {
// + 1, cause we want to return hashes including current block hash.
let current_number = client.chain_info().best_block_number + 1;
let hashes = (*block_number..current_number).into_iter()
.map(BlockID::Number)
.filter_map(|id| client.block_hash(id))
.collect::<Vec<H256>>();
*block_number = current_number;
to_value(&hashes)
},
PollFilter::PendingTransaction(ref mut previous_hashes) => {
// get hashes of pending transactions
let current_hashes = take_weak!(self.miner).pending_transactions_hashes();
let new_hashes =
{
let previous_hashes_set = previous_hashes.iter().collect::<HashSet<_>>();
// find all new hashes
current_hashes
.iter()
.filter(|hash| !previous_hashes_set.contains(hash))
.cloned()
.collect::<Vec<H256>>()
};
// save all hashes of pending transactions
*previous_hashes = current_hashes;
// return new hashes
to_value(&new_hashes)
},
PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => {
// retrive the current block number
let current_number = client.chain_info().best_block_number;
// check if we need to check pending hashes
let include_pending = filter.to_block == Some(BlockNumber::Pending);
// build appropriate filter
let mut filter: EthcoreFilter = filter.clone().into();
filter.from_block = BlockID::Number(*block_number);
filter.to_block = BlockID::Latest;
// retrieve logs in range from_block..min(BlockID::Latest..to_block)
let mut logs = client.logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
// additionally retrieve pending logs
if include_pending {
let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter);
// remove logs about which client was already notified about
let new_pending_logs: Vec<_> = pending_logs.iter()
.filter(|p| !previous_logs.contains(p))
.cloned()
.collect();
// save all logs retrieved by client
*previous_logs = pending_logs.into_iter().collect();
// append logs array with new pending logs
logs.extend(new_pending_logs);
}
// save current block number as next from block number
*block_number = current_number;
to_value(&logs)
}
}
}
})
}
fn filter_logs(&self, params: Params) -> Result<Value, Error> {
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 _previous_log, ref filter)) => {
let include_pending = filter.to_block == Some(BlockNumber::Pending);
let filter: EthcoreFilter = filter.clone().into();
let mut logs = take_weak!(self.client).logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
if include_pending {
logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter));
}
to_value(&logs)
},
// just empty array
_ => Ok(Value::Array(vec![] as Vec<Value>)),
}
})
}
fn uninstall_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
self.polls.lock().unwrap().remove_poll(&index.value());
to_value(&true)
})
}
}

View File

@@ -0,0 +1,214 @@
// 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://www.gnu.org/licenses/>.
//! Eth Filter RPC implementation
use std::ops::Deref;
use std::sync::{Arc, Weak, Mutex};
use std::collections::HashSet;
use jsonrpc_core::*;
use util::numbers::*;
use ethcore::miner::MinerService;
use ethcore::filter::Filter as EthcoreFilter;
use ethcore::client::{BlockChainClient, BlockID};
use v1::traits::EthFilter;
use v1::types::{BlockNumber, Index, Filter, Log};
use v1::helpers::{PollFilter, PollManager};
use v1::impls::eth::pending_logs;
/// Eth filter rpc implementation.
pub struct EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
client: Weak<C>,
miner: Weak<M>,
polls: Mutex<PollManager<PollFilter>>,
}
impl<C, M> EthFilterClient<C, M> where
C: BlockChainClient,
M: MinerService {
/// Creates new Eth filter client.
pub fn new(client: &Arc<C>, miner: &Arc<M>) -> Self {
EthFilterClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
polls: Mutex::new(PollManager::new()),
}
}
}
impl<C, M> EthFilter for EthFilterClient<C, M> where
C: BlockChainClient + 'static,
M: MinerService + 'static {
fn new_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Filter,)>(params)
.and_then(|(filter,)| {
let mut polls = self.polls.lock().unwrap();
let block_number = take_weak!(self.client).chain_info().best_block_number;
let id = polls.create_poll(PollFilter::Logs(block_number, Default::default(), filter));
to_value(&U256::from(id))
})
}
fn new_block_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn new_pending_transaction_filter(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => {
let mut polls = self.polls.lock().unwrap();
let pending_transactions = take_weak!(self.miner).pending_transactions_hashes();
let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions));
to_value(&U256::from(id))
},
_ => Err(Error::invalid_params())
}
}
fn filter_changes(&self, params: Params) -> Result<Value, Error> {
let client = take_weak!(self.client);
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll_mut(&index.value()) {
None => Ok(Value::Array(vec![] as Vec<Value>)),
Some(filter) => match *filter {
PollFilter::Block(ref mut block_number) => {
// + 1, cause we want to return hashes including current block hash.
let current_number = client.chain_info().best_block_number + 1;
let hashes = (*block_number..current_number).into_iter()
.map(BlockID::Number)
.filter_map(|id| client.block_hash(id))
.collect::<Vec<H256>>();
*block_number = current_number;
to_value(&hashes)
},
PollFilter::PendingTransaction(ref mut previous_hashes) => {
// get hashes of pending transactions
let current_hashes = take_weak!(self.miner).pending_transactions_hashes();
let new_hashes =
{
let previous_hashes_set = previous_hashes.iter().collect::<HashSet<_>>();
// find all new hashes
current_hashes
.iter()
.filter(|hash| !previous_hashes_set.contains(hash))
.cloned()
.collect::<Vec<H256>>()
};
// save all hashes of pending transactions
*previous_hashes = current_hashes;
// return new hashes
to_value(&new_hashes)
},
PollFilter::Logs(ref mut block_number, ref mut previous_logs, ref filter) => {
// retrive the current block number
let current_number = client.chain_info().best_block_number;
// check if we need to check pending hashes
let include_pending = filter.to_block == Some(BlockNumber::Pending);
// build appropriate filter
let mut filter: EthcoreFilter = filter.clone().into();
filter.from_block = BlockID::Number(*block_number);
filter.to_block = BlockID::Latest;
// retrieve logs in range from_block..min(BlockID::Latest..to_block)
let mut logs = client.logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
// additionally retrieve pending logs
if include_pending {
let pending_logs = pending_logs(take_weak!(self.miner).deref(), &filter);
// remove logs about which client was already notified about
let new_pending_logs: Vec<_> = pending_logs.iter()
.filter(|p| !previous_logs.contains(p))
.cloned()
.collect();
// save all logs retrieved by client
*previous_logs = pending_logs.into_iter().collect();
// append logs array with new pending logs
logs.extend(new_pending_logs);
}
// save current block number as next from block number
*block_number = current_number;
to_value(&logs)
}
}
}
})
}
fn filter_logs(&self, params: Params) -> Result<Value, Error> {
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 _previous_log, ref filter)) => {
let include_pending = filter.to_block == Some(BlockNumber::Pending);
let filter: EthcoreFilter = filter.clone().into();
let mut logs = take_weak!(self.client).logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
if include_pending {
logs.extend(pending_logs(take_weak!(self.miner).deref(), &filter));
}
to_value(&logs)
},
// just empty array
_ => Ok(Value::Array(vec![] as Vec<Value>)),
}
})
}
fn uninstall_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
self.polls.lock().unwrap().remove_poll(&index.value());
to_value(&true)
})
}
}

View File

@@ -0,0 +1,111 @@
// 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://www.gnu.org/licenses/>.
//! Eth Signing RPC implementation.
use std::sync::{Arc, Weak};
use jsonrpc_core::*;
use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient;
use util::numbers::*;
use util::keys::store::AccountProvider;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::traits::EthSigning;
use v1::types::TransactionRequest;
use v1::impls::sign_and_dispatch;
/// Implementation of functions that require signing when no trusted signer is used.
pub struct EthSigningQueueClient {
queue: Weak<ConfirmationsQueue>,
}
impl EthSigningQueueClient {
/// Creates a new signing queue client given shared signing queue.
pub fn new(queue: &Arc<ConfirmationsQueue>) -> Self {
EthSigningQueueClient {
queue: Arc::downgrade(queue),
}
}
}
impl EthSigning for EthSigningQueueClient {
fn sign(&self, _params: Params) -> Result<Value, Error> {
// TODO [ToDr] Implement sign when rest of the signing queue is ready.
rpc_unimplemented!()
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let queue = take_weak!(self.queue);
queue.add_request(request);
// TODO [ToDr] Block and wait for confirmation?
to_value(&H256::zero())
})
}
}
/// Implementation of functions that require signing when no trusted signer is used.
pub struct EthSigningUnsafeClient<C, A, M> where
C: MiningBlockChainClient,
A: AccountProvider,
M: MinerService {
client: Weak<C>,
accounts: Weak<A>,
miner: Weak<M>,
}
impl<C, A, M> EthSigningUnsafeClient<C, A, M> where
C: MiningBlockChainClient,
A: AccountProvider,
M: MinerService {
/// Creates new EthClient.
pub fn new(client: &Arc<C>, accounts: &Arc<A>, miner: &Arc<M>)
-> Self {
EthSigningUnsafeClient {
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
accounts: Arc::downgrade(accounts),
}
}
}
impl<C, A, M> EthSigning for EthSigningUnsafeClient<C, A, M> where
C: MiningBlockChainClient + 'static,
A: AccountProvider + 'static,
M: MinerService + 'static {
fn sign(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, H256)>(params).and_then(|(addr, msg)| {
to_value(&take_weak!(self.accounts).sign(&addr, &msg).unwrap_or(H520::zero()))
})
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let accounts = take_weak!(self.accounts);
match accounts.account_secret(&request.from) {
Ok(secret) => sign_and_dispatch(&self.client, &self.miner, request, secret),
Err(_) => to_value(&H256::zero())
}
})
}
}

View File

@@ -22,10 +22,10 @@ use std::sync::{Arc, Weak};
use std::ops::Deref;
use std::collections::BTreeMap;
use jsonrpc_core::*;
use ethminer::MinerService;
use ethcore::miner::MinerService;
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
use ethcore::client::BlockChainClient;
use ethcore::trace::VMTrace;
use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action};
use v1::traits::Ethcore;
use v1::types::{Bytes, CallRequest};

View File

@@ -31,24 +31,30 @@ macro_rules! rpc_unimplemented {
mod web3;
mod eth;
mod eth_filter;
mod eth_signing;
mod net;
mod personal;
mod personal_signer;
mod ethcore;
mod traces;
mod rpc;
pub use self::web3::Web3Client;
pub use self::eth::{EthClient, EthFilterClient};
pub use self::eth::EthClient;
pub use self::eth_filter::EthFilterClient;
pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient};
pub use self::net::NetClient;
pub use self::personal::PersonalClient;
pub use self::personal_signer::SignerClient;
pub use self::ethcore::EthcoreClient;
pub use self::traces::TracesClient;
pub use self::rpc::RpcClient;
use v1::types::TransactionRequest;
use std::sync::Weak;
use ethminer::{AccountDetails, MinerService};
use ethcore::client::BlockChainClient;
use ethcore::miner::{AccountDetails, MinerService};
use ethcore::client::MiningBlockChainClient;
use ethcore::transaction::{Action, SignedTransaction, Transaction};
use util::numbers::*;
use util::rlp::encode;
@@ -56,7 +62,7 @@ use util::bytes::ToPretty;
use jsonrpc_core::{Error, to_value, Value};
fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<Value, Error>
where C: BlockChainClient, M: MinerService {
where C: MiningBlockChainClient, M: MinerService {
let hash = signed_transaction.hash();
let import = miner.import_own_transaction(client, signed_transaction, |a: &Address| {
@@ -70,7 +76,7 @@ fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedT
}
fn sign_and_dispatch<C, M>(client: &Weak<C>, miner: &Weak<M>, request: TransactionRequest, secret: H256) -> Result<Value, Error>
where C: BlockChainClient, M: MinerService {
where C: MiningBlockChainClient, M: MinerService {
let client = take_weak!(client);
let miner = take_weak!(miner);
@@ -92,4 +98,4 @@ fn sign_and_dispatch<C, M>(client: &Weak<C>, miner: &Weak<M>, request: Transacti
trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty());
dispatch_transaction(&*client, &*miner, signed_transaction)
}
}

View File

@@ -20,21 +20,21 @@ use jsonrpc_core::*;
use v1::traits::Personal;
use v1::types::TransactionRequest;
use v1::impls::sign_and_dispatch;
use util::keys::store::*;
use util::keys::store::AccountProvider;
use util::numbers::*;
use ethcore::client::BlockChainClient;
use ethminer::MinerService;
use ethcore::client::MiningBlockChainClient;
use ethcore::miner::MinerService;
/// Account management (personal) rpc implementation.
pub struct PersonalClient<A, C, M>
where A: AccountProvider, C: BlockChainClient, M: MinerService {
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
accounts: Weak<A>,
client: Weak<C>,
miner: Weak<M>,
}
impl<A, C, M> PersonalClient<A, C, M>
where A: AccountProvider, C: BlockChainClient, M: MinerService {
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
/// Creates new PersonalClient
pub fn new(store: &Arc<A>, client: &Arc<C>, miner: &Arc<M>) -> Self {
PersonalClient {
@@ -46,7 +46,7 @@ impl<A, C, M> PersonalClient<A, C, M>
}
impl<A: 'static, C: 'static, M: 'static> Personal for PersonalClient<A, C, M>
where A: AccountProvider, C: BlockChainClient, M: MinerService {
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
fn accounts(&self, _: Params) -> Result<Value, Error> {
let store = take_weak!(self.accounts);
match store.accounts() {

View File

@@ -0,0 +1,93 @@
// 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://www.gnu.org/licenses/>.
//! Transactions Confirmations (personal) rpc implementation
use std::sync::{Arc, Weak};
use jsonrpc_core::*;
use v1::traits::PersonalSigner;
use v1::types::TransactionModification;
use v1::impls::sign_and_dispatch;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use util::keys::store::AccountProvider;
use util::numbers::*;
use ethcore::client::MiningBlockChainClient;
use ethcore::miner::MinerService;
/// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<A, C, M>
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
queue: Weak<ConfirmationsQueue>,
accounts: Weak<A>,
client: Weak<C>,
miner: Weak<M>,
}
impl<A: 'static, C: 'static, M: 'static> SignerClient<A, C, M>
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
/// Create new instance of signer client.
pub fn new(store: &Arc<A>, client: &Arc<C>, miner: &Arc<M>, queue: &Arc<ConfirmationsQueue>) -> Self {
SignerClient {
queue: Arc::downgrade(queue),
accounts: Arc::downgrade(store),
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
}
}
}
impl<A: 'static, C: 'static, M: 'static> PersonalSigner for SignerClient<A, C, M>
where A: AccountProvider, C: MiningBlockChainClient, M: MinerService {
fn transactions_to_confirm(&self, _params: Params) -> Result<Value, Error> {
let queue = take_weak!(self.queue);
to_value(&queue.requests())
}
fn confirm_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(U256, TransactionModification, String)>(params).and_then(
|(id, modification, pass)| {
let accounts = take_weak!(self.accounts);
let queue = take_weak!(self.queue);
queue.remove_request(id)
.and_then(|confirmation| {
let mut request = confirmation.transaction;
// apply modification
if let Some(gas_price) = modification.gas_price {
request.gas_price = Some(gas_price);
}
match accounts.locked_account_secret(&request.from, &pass) {
Ok(secret) => Some(sign_and_dispatch(&self.client, &self.miner, request, secret)),
Err(_) => None
}
})
.unwrap_or_else(|| to_value(&H256::zero()))
}
)
}
fn reject_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(U256, )>(params).and_then(
|(id, )| {
let queue = take_weak!(self.queue);
let res = queue.remove_request(id);
to_value(&res.is_some())
}
)
}
}

View File

@@ -25,5 +25,6 @@ pub mod traits;
pub mod tests;
pub mod types;
pub use self::traits::{Web3, Eth, EthFilter, Personal, Net, Ethcore, Traces, Rpc};
pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, Traces, Rpc};
pub use self::impls::*;
pub use self::helpers::{SigningQueue, ConfirmationsQueue};

View File

@@ -19,151 +19,26 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::str::FromStr;
use ethcore::client::{BlockChainClient, Client, ClientConfig};
use ethcore::spec::Genesis;
use ethcore::client::{MiningBlockChainClient, BlockChainClient, Client, ClientConfig};
use ethcore::ids::BlockID;
use ethcore::spec::{Genesis, Spec};
use ethcore::block::Block;
use ethcore::views::BlockView;
use ethcore::ethereum;
use ethcore::transaction::{Transaction, Action};
use ethminer::{MinerService, ExternalMiner};
use ethcore::miner::{MinerService, ExternalMiner, Miner};
use devtools::RandomTempPath;
use util::Hashable;
use util::io::IoChannel;
use util::hash::Address;
use util::numbers::{Uint, U256};
use util::hash::{Address, H256};
use util::numbers::U256;
use util::keys::{AccountProvider, TestAccount, TestAccountProvider};
use jsonrpc_core::IoHandler;
use ethjson::blockchain::BlockChain;
use v1::traits::eth::Eth;
use v1::impls::EthClient;
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService};
struct EthTester {
_client: Arc<BlockChainClient>,
_miner: Arc<MinerService>,
accounts: Arc<TestAccountProvider>,
handler: IoHandler,
}
#[test]
fn harness_works() {
let chain: BlockChain = extract_chain!("BlockchainTests/bcUncleTest");
chain_harness(chain, |_| {});
}
#[test]
fn eth_get_balance() {
let chain = extract_chain!("BlockchainTests/bcWalletTest", "wallet2outOf3txs");
chain_harness(chain, |tester| {
// final account state
let req_latest = r#"{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa", "latest"],
"id": 1
}"#;
let res_latest = r#"{"jsonrpc":"2.0","result":"0x09","id":1}"#.to_owned();
assert_eq!(tester.handler.handle_request(req_latest).unwrap(), res_latest);
// non-existant account
let req_new_acc = r#"{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
"id": 3
}"#;
let res_new_acc = r#"{"jsonrpc":"2.0","result":"0x00","id":3}"#.to_owned();
assert_eq!(tester.handler.handle_request(req_new_acc).unwrap(), res_new_acc);
});
}
#[test]
fn eth_block_number() {
let chain = extract_chain!("BlockchainTests/bcRPC_API_Test");
chain_harness(chain, |tester| {
let req_number = r#"{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}"#;
let res_number = r#"{"jsonrpc":"2.0","result":"0x20","id":1}"#.to_owned();
assert_eq!(tester.handler.handle_request(req_number).unwrap(), res_number);
});
}
#[cfg(test)]
#[test]
fn eth_transaction_count() {
let chain = extract_chain!("BlockchainTests/bcRPC_API_Test");
chain_harness(chain, |tester| {
let address = tester.accounts.new_account("123").unwrap();
let secret = tester.accounts.account_secret(&address).unwrap();
let req_before = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"],
"id": 15
}"#;
let res_before = r#"{"jsonrpc":"2.0","result":"0x00","id":15}"#;
assert_eq!(tester.handler.handle_request(&req_before).unwrap(), res_before);
let t = Transaction {
nonce: U256::zero(),
gas_price: U256::from(0x9184e72a000u64),
gas: U256::from(0x76c0),
action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
value: U256::from(0x9184e72au64),
data: vec![]
}.sign(&secret);
let req_send_trans = r#"{
"jsonrpc": "2.0",
"method": "eth_sendTransaction",
"params": [{
"from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a"
}],
"id": 16
}"#;
let res_send_trans = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":16}"#;
// dispatch the transaction.
assert_eq!(tester.handler.handle_request(&req_send_trans).unwrap(), res_send_trans);
// we have submitted the transaction -- but this shouldn't be reflected in a "latest" query.
let req_after_latest = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"],
"id": 17
}"#;
let res_after_latest = r#"{"jsonrpc":"2.0","result":"0x00","id":17}"#;
assert_eq!(&tester.handler.handle_request(&req_after_latest).unwrap(), res_after_latest);
// the pending transactions should have been updated.
let req_after_pending = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "pending"],
"id": 18
}"#;
let res_after_pending = r#"{"jsonrpc":"2.0","result":"0x01","id":18}"#;
assert_eq!(&tester.handler.handle_request(&req_after_pending).unwrap(), res_after_pending);
});
}
use v1::traits::eth::{Eth, EthSigning};
use v1::impls::{EthClient, EthSigningUnsafeClient};
use v1::tests::helpers::{TestSyncProvider, Config};
fn account_provider() -> Arc<TestAccountProvider> {
let mut accounts = HashMap::new();
@@ -179,51 +54,308 @@ fn sync_provider() -> Arc<TestSyncProvider> {
}))
}
fn miner_service() -> Arc<TestMinerService> {
Arc::new(TestMinerService::default())
fn miner_service(spec: Spec, accounts: Arc<AccountProvider>) -> Arc<Miner> {
Miner::with_accounts(true, spec, accounts)
}
// given a blockchain, this harness will create an EthClient wrapping it
// which tests can pass specially crafted requests to.
fn chain_harness<F, U>(chain: BlockChain, mut cb: F) -> U
where F: FnMut(&EthTester) -> U {
fn make_spec(chain: &BlockChain) -> Spec {
let genesis = Genesis::from(chain.genesis());
let mut spec = ethereum::new_frontier_test();
let state = chain.pre_state.clone().into();
spec.set_genesis_state(state);
spec.overwrite_genesis_params(genesis);
assert!(spec.is_state_root_valid());
spec
}
let dir = RandomTempPath::new();
let client = Client::new(ClientConfig::default(), spec, dir.as_path(), IoChannel::disconnected()).unwrap();
let sync_provider = sync_provider();
let miner_service = miner_service();
let account_provider = account_provider();
let external_miner = Arc::new(ExternalMiner::default());
struct EthTester {
client: Arc<Client>,
_miner: Arc<MinerService>,
accounts: Arc<TestAccountProvider>,
handler: IoHandler,
}
for b in &chain.blocks_rlp() {
if Block::is_good(&b) {
let _ = client.import_block(b.clone());
client.flush_queue();
client.import_verified_blocks(&IoChannel::disconnected());
impl EthTester {
fn from_chain(chain: &BlockChain) -> Self {
let tester = Self::from_spec_provider(|| make_spec(chain));
for b in &chain.blocks_rlp() {
if Block::is_good(&b) {
let _ = tester.client.import_block(b.clone());
tester.client.flush_queue();
tester.client.import_verified_blocks(&IoChannel::disconnected());
}
}
tester.client.flush_queue();
assert!(tester.client.chain_info().best_block_hash == chain.best_block.clone().into());
tester
}
fn from_spec_provider<F>(spec_provider: F) -> Self
where F: Fn() -> Spec {
let dir = RandomTempPath::new();
let account_provider = account_provider();
let miner_service = miner_service(spec_provider(), account_provider.clone());
let client = Client::new(ClientConfig::default(), spec_provider(), dir.as_path(), miner_service.clone(), IoChannel::disconnected()).unwrap();
let sync_provider = sync_provider();
let external_miner = Arc::new(ExternalMiner::default());
let eth_client = EthClient::new(
&client,
&sync_provider,
&account_provider,
&miner_service,
&external_miner
);
let eth_sign = EthSigningUnsafeClient::new(
&client,
&account_provider,
&miner_service
);
let handler = IoHandler::new();
handler.add_delegate(eth_client.to_delegate());
handler.add_delegate(eth_sign.to_delegate());
EthTester {
_miner: miner_service,
client: client,
accounts: account_provider,
handler: handler,
}
}
}
#[test]
fn harness_works() {
let chain: BlockChain = extract_chain!("BlockchainTests/bcUncleTest");
let _ = EthTester::from_chain(&chain);
}
#[test]
fn eth_get_balance() {
let chain = extract_chain!("BlockchainTests/bcWalletTest", "wallet2outOf3txs");
let tester = EthTester::from_chain(&chain);
// final account state
let req_latest = r#"{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa", "latest"],
"id": 1
}"#;
let res_latest = r#"{"jsonrpc":"2.0","result":"0x09","id":1}"#.to_owned();
assert_eq!(tester.handler.handle_request(req_latest).unwrap(), res_latest);
// non-existant account
let req_new_acc = r#"{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
"id": 3
}"#;
let res_new_acc = r#"{"jsonrpc":"2.0","result":"0x00","id":3}"#.to_owned();
assert_eq!(tester.handler.handle_request(req_new_acc).unwrap(), res_new_acc);
}
#[test]
fn eth_block_number() {
let chain = extract_chain!("BlockchainTests/bcRPC_API_Test");
let tester = EthTester::from_chain(&chain);
let req_number = r#"{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}"#;
let res_number = r#"{"jsonrpc":"2.0","result":"0x20","id":1}"#.to_owned();
assert_eq!(tester.handler.handle_request(req_number).unwrap(), res_number);
}
// a frontier-like test with an expanded gas limit and balance on known account.
const TRANSACTION_COUNT_SPEC: &'static [u8] = br#"{
"name": "Frontier (Test)",
"engine": {
"Ethash": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0xffffffffffffffff"
}
}
},
"params": {
"accountStartNonce": "0x00",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x50000",
"networkID" : "0x1"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x400000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
"gasLimit": "0x50000"
},
"accounts": {
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0000000000000000000000000000000000000002": { "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0000000000000000000000000000000000000003": { "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0000000000000000000000000000000000000004": { "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"faa34835af5c2ea724333018a515fbb7d5bc0b33": { "balance": "10000000000000", "nonce": "0" }
}
}
"#;
#[test]
fn eth_transaction_count() {
use util::crypto::Secret;
let address = Address::from_str("faa34835af5c2ea724333018a515fbb7d5bc0b33").unwrap();
let secret = Secret::from_str("8a283037bb19c4fed7b1c569e40c7dcff366165eb869110a1b11532963eb9cb2").unwrap();
let tester = EthTester::from_spec_provider(|| Spec::load(TRANSACTION_COUNT_SPEC));
tester.accounts.accounts.write().unwrap().insert(address, TestAccount {
unlocked: false,
password: "123".into(),
secret: secret
});
let req_before = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"],
"id": 15
}"#;
let res_before = r#"{"jsonrpc":"2.0","result":"0x00","id":15}"#;
assert_eq!(tester.handler.handle_request(&req_before).unwrap(), res_before);
let req_send_trans = r#"{
"jsonrpc": "2.0",
"method": "eth_sendTransaction",
"params": [{
"from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x30000",
"gasPrice": "0x01",
"value": "0x9184e72a"
}],
"id": 16
}"#;
// dispatch the transaction.
tester.handler.handle_request(&req_send_trans).unwrap();
// we have submitted the transaction -- but this shouldn't be reflected in a "latest" query.
let req_after_latest = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "latest"],
"id": 17
}"#;
let res_after_latest = r#"{"jsonrpc":"2.0","result":"0x00","id":17}"#;
assert_eq!(&tester.handler.handle_request(&req_after_latest).unwrap(), res_after_latest);
// the pending transactions should have been updated.
let req_after_pending = r#"{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "pending"],
"id": 18
}"#;
let res_after_pending = r#"{"jsonrpc":"2.0","result":"0x01","id":18}"#;
assert_eq!(&tester.handler.handle_request(&req_after_pending).unwrap(), res_after_pending);
}
fn verify_transaction_counts(name: String, chain: BlockChain) {
struct PanicHandler(String);
impl Drop for PanicHandler {
fn drop(&mut self) {
if ::std::thread::panicking() {
println!("Test failed: {}", self.0);
}
}
}
assert!(client.chain_info().best_block_hash == chain.best_block.into());
let _panic = PanicHandler(name);
let eth_client = EthClient::new(&client, &sync_provider, &account_provider,
&miner_service, &external_miner);
fn by_hash(hash: H256, count: usize, id: &mut usize) -> (String, String) {
let req = r#"{
"jsonrpc": "2.0",
"method": "eth_getBlockTransactionCountByHash",
"params": [
""#.to_owned() + format!("0x{:?}", hash).as_ref() + r#""
],
"id": "# + format!("{}", *id).as_ref() + r#"
}"#;
let handler = IoHandler::new();
let delegate = eth_client.to_delegate();
handler.add_delegate(delegate);
let res = r#"{"jsonrpc":"2.0","result":""#.to_owned()
+ format!("0x{:02x}", count).as_ref()
+ r#"","id":"#
+ format!("{}", *id).as_ref() + r#"}"#;
*id += 1;
(req, res)
}
let tester = EthTester {
_miner: miner_service,
_client: client,
accounts: account_provider,
handler: handler,
};
fn by_number(num: u64, count: usize, id: &mut usize) -> (String, String) {
let req = r#"{
"jsonrpc": "2.0",
"method": "eth_getBlockTransactionCountByNumber",
"params": [
"#.to_owned() + &::serde_json::to_string(&U256::from(num)).unwrap() + r#"
],
"id": "# + format!("{}", *id).as_ref() + r#"
}"#;
cb(&tester)
let res = r#"{"jsonrpc":"2.0","result":""#.to_owned()
+ format!("0x{:02x}", count).as_ref()
+ r#"","id":"#
+ format!("{}", *id).as_ref() + r#"}"#;
*id += 1;
(req, res)
}
let tester = EthTester::from_chain(&chain);
let mut id = 1;
for b in chain.blocks_rlp().iter().filter(|b| Block::is_good(b)).map(|b| BlockView::new(b)) {
let count = b.transactions_count();
let hash = b.sha3();
let number = b.header_view().number();
let (req, res) = by_hash(hash, count, &mut id);
assert_eq!(tester.handler.handle_request(&req), Some(res));
// uncles can share block numbers, so skip them.
if tester.client.block_hash(BlockID::Number(number)) == Some(hash) {
let (req, res) = by_number(number, count, &mut id);
assert_eq!(tester.handler.handle_request(&req), Some(res));
}
}
}
register_test!(eth_transaction_count_1, verify_transaction_counts, "BlockchainTests/bcWalletTest");
register_test!(eth_transaction_count_2, verify_transaction_counts, "BlockchainTests/bcTotalDifficultyTest");
register_test!(eth_transaction_count_3, verify_transaction_counts, "BlockchainTests/bcGasPricerTest");

View File

@@ -19,11 +19,11 @@
use util::{Address, H256, Bytes, U256, FixedHash, Uint};
use util::standard::*;
use ethcore::error::{Error, ExecutionError};
use ethcore::client::{BlockChainClient, Executed};
use ethcore::client::{MiningBlockChainClient, Executed};
use ethcore::block::{ClosedBlock, IsBlock};
use ethcore::transaction::SignedTransaction;
use ethcore::receipt::Receipt;
use ethminer::{MinerService, MinerStatus, AccountDetails, TransactionImportResult};
use ethcore::miner::{MinerService, MinerStatus, AccountDetails, TransactionImportResult};
/// Test miner service.
pub struct TestMinerService {
@@ -132,7 +132,7 @@ impl MinerService for TestMinerService {
}
/// Imports transactions to transaction queue.
fn import_own_transaction<T>(&self, chain: &BlockChainClient, transaction: SignedTransaction, _fetch_account: T) ->
fn import_own_transaction<T>(&self, chain: &MiningBlockChainClient, transaction: SignedTransaction, _fetch_account: T) ->
Result<TransactionImportResult, Error>
where T: Fn(&Address) -> AccountDetails {
@@ -154,21 +154,21 @@ impl MinerService for TestMinerService {
}
/// Removes all transactions from the queue and restart mining operation.
fn clear_and_reset(&self, _chain: &BlockChainClient) {
fn clear_and_reset(&self, _chain: &MiningBlockChainClient) {
unimplemented!();
}
/// Called when blocks are imported to chain, updates transactions queue.
fn chain_new_blocks(&self, _chain: &BlockChainClient, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) {
fn chain_new_blocks(&self, _chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) {
unimplemented!();
}
/// New chain head event. Restart mining operation.
fn update_sealing(&self, _chain: &BlockChainClient) {
fn update_sealing(&self, _chain: &MiningBlockChainClient) {
unimplemented!();
}
fn map_sealing_work<F, T>(&self, _chain: &BlockChainClient, _f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T {
fn map_sealing_work<F, T>(&self, _chain: &MiningBlockChainClient, _f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T {
unimplemented!();
}
@@ -194,29 +194,29 @@ impl MinerService for TestMinerService {
/// Submit `seal` as a valid solution for the header of `pow_hash`.
/// Will check the seal, but not actually insert the block into the chain.
fn submit_seal(&self, _chain: &BlockChainClient, _pow_hash: H256, _seal: Vec<Bytes>) -> Result<(), Error> {
fn submit_seal(&self, _chain: &MiningBlockChainClient, _pow_hash: H256, _seal: Vec<Bytes>) -> Result<(), Error> {
unimplemented!();
}
fn balance(&self, _chain: &BlockChainClient, address: &Address) -> U256 {
fn balance(&self, _chain: &MiningBlockChainClient, address: &Address) -> U256 {
self.latest_closed_block.lock().unwrap().as_ref().map_or_else(U256::zero, |b| b.block().fields().state.balance(address).clone())
}
fn call(&self, _chain: &BlockChainClient, _t: &SignedTransaction, _vm_tracing: bool) -> Result<Executed, ExecutionError> {
fn call(&self, _chain: &MiningBlockChainClient, _t: &SignedTransaction, _vm_tracing: bool) -> Result<Executed, ExecutionError> {
unimplemented!();
}
fn storage_at(&self, _chain: &BlockChainClient, address: &Address, position: &H256) -> H256 {
fn storage_at(&self, _chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256 {
self.latest_closed_block.lock().unwrap().as_ref().map_or_else(H256::default, |b| b.block().fields().state.storage_at(address, position).clone())
}
fn nonce(&self, _chain: &BlockChainClient, address: &Address) -> U256 {
fn nonce(&self, _chain: &MiningBlockChainClient, address: &Address) -> U256 {
// we assume all transactions are in a pending block, ignoring the
// reality of gas limits.
self.last_nonce(address).unwrap_or(U256::zero())
}
fn code(&self, _chain: &BlockChainClient, address: &Address) -> Option<Bytes> {
fn code(&self, _chain: &MiningBlockChainClient, address: &Address) -> Option<Bytes> {
self.latest_closed_block.lock().unwrap().as_ref().map_or(None, |b| b.block().fields().state.code(address).clone())
}

View File

@@ -25,9 +25,9 @@ use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, Transaction
use ethcore::log_entry::{LocalizedLogEntry, LogEntry};
use ethcore::receipt::LocalizedReceipt;
use ethcore::transaction::{Transaction, Action};
use ethminer::{ExternalMiner, MinerService};
use ethcore::miner::{ExternalMiner, MinerService};
use ethsync::SyncState;
use v1::{Eth, EthClient};
use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient};
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService};
use rustc_serialize::hex::ToHex;
@@ -72,8 +72,11 @@ impl Default for EthTester {
let hashrates = Arc::new(RwLock::new(HashMap::new()));
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner).to_delegate();
let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(eth);
io.add_delegate(sign);
EthTester {
client: client,
sync: sync,

View File

@@ -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 <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use jsonrpc_core::IoHandler;
use v1::impls::EthSigningQueueClient;
use v1::traits::EthSigning;
use v1::helpers::{ConfirmationsQueue, SigningQueue};
use util::keys::TestAccount;
struct EthSigningTester {
pub queue: Arc<ConfirmationsQueue>,
pub io: IoHandler,
}
impl Default for EthSigningTester {
fn default() -> Self {
let queue = Arc::new(ConfirmationsQueue::default());
let io = IoHandler::new();
io.add_delegate(EthSigningQueueClient::new(&queue).to_delegate());
EthSigningTester {
queue: queue,
io: io,
}
}
}
fn eth_signing() -> EthSigningTester {
EthSigningTester::default()
}
#[test]
fn should_add_transaction_to_queue() {
// given
let tester = eth_signing();
let account = TestAccount::new("123");
let address = account.address();
assert_eq!(tester.queue.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_sendTransaction",
"params": [{
"from": ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a"
}],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}

View File

@@ -18,8 +18,8 @@ use std::sync::Arc;
use std::str::FromStr;
use jsonrpc_core::IoHandler;
use v1::{Ethcore, EthcoreClient};
use ethminer::MinerService;
use ethcore::client::{TestBlockChainClient};
use ethcore::miner::MinerService;
use v1::tests::helpers::TestMinerService;
use util::numbers::*;
use rustc_serialize::hex::FromHex;

View File

@@ -18,8 +18,10 @@
//! method calls properly.
mod eth;
mod eth_signing;
mod net;
mod web3;
mod personal;
mod personal_signer;
mod ethcore;
mod rpc;

View File

@@ -176,4 +176,4 @@ fn sign_and_send_transaction() {
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
assert_eq!(tester.io.handle_request(request.as_ref()), Some(response));
}
}

View File

@@ -0,0 +1,169 @@
// 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://www.gnu.org/licenses/>.
use std::sync::Arc;
use std::str::FromStr;
use std::collections::HashMap;
use jsonrpc_core::IoHandler;
use util::numbers::*;
use util::keys::{TestAccount, TestAccountProvider};
use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
use v1::{SignerClient, PersonalSigner};
use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::types::TransactionRequest;
struct PersonalSignerTester {
queue: Arc<ConfirmationsQueue>,
accounts: Arc<TestAccountProvider>,
io: IoHandler,
miner: Arc<TestMinerService>,
// these unused fields are necessary to keep the data alive
// as the handler has only weak pointers.
_client: Arc<TestBlockChainClient>,
}
fn blockchain_client() -> Arc<TestBlockChainClient> {
let client = TestBlockChainClient::new();
Arc::new(client)
}
fn accounts_provider() -> Arc<TestAccountProvider> {
let accounts = HashMap::new();
let ap = TestAccountProvider::new(accounts);
Arc::new(ap)
}
fn miner_service() -> Arc<TestMinerService> {
Arc::new(TestMinerService::default())
}
fn signer_tester() -> PersonalSignerTester {
let queue = Arc::new(ConfirmationsQueue::default());
let accounts = accounts_provider();
let client = blockchain_client();
let miner = miner_service();
let io = IoHandler::new();
io.add_delegate(SignerClient::new(&accounts, &client, &miner, &queue).to_delegate());
PersonalSignerTester {
queue: queue,
accounts: accounts,
io: io,
miner: miner,
_client: client,
}
}
#[test]
fn should_return_list_of_transactions_in_queue() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
nonce: None,
});
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
}
#[test]
fn should_reject_transaction_from_queue_without_dispatching() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
nonce: None,
});
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 0);
assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 0);
}
#[test]
fn should_confirm_transaction_and_dispatch() {
// given
let tester = signer_tester();
let account = TestAccount::new("test");
let address = account.address();
let secret = account.secret.clone();
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
tester.accounts.accounts
.write()
.unwrap()
.insert(address, account);
tester.queue.add_request(TransactionRequest {
from: address,
to: Some(recipient),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
nonce: None,
});
let t = Transaction {
nonce: U256::zero(),
gas_price: U256::from(0x1000),
gas: U256::from(10_000_000),
action: Action::Call(recipient),
value: U256::from(0x1),
data: vec![]
}.sign(&secret);
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{
"jsonrpc":"2.0",
"method":"personal_confirmTransaction",
"params":["0x01", {"gasPrice":"0x1000"}, "test"],
"id":1
}"#;
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 0);
assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 1);
}

View File

@@ -13,11 +13,15 @@ pub mod helpers;
// extract the chain with that name. This will panic if no chain by that name
// is found.
macro_rules! extract_chain {
($file:expr, $name:expr) => {{
(iter $file:expr) => {{
const RAW_DATA: &'static [u8] =
include_bytes!(concat!("../../../../ethcore/res/ethereum/tests/", $file, ".json"));
::ethjson::blockchain::Test::load(RAW_DATA).unwrap().into_iter()
}};
($file:expr, $name:expr) => {{
let mut chain = None;
for (name, c) in ::ethjson::blockchain::Test::load(RAW_DATA).unwrap() {
for (name, c) in extract_chain!(iter $file) {
if name == $name {
chain = Some(c);
break;
@@ -27,14 +31,41 @@ macro_rules! extract_chain {
}};
($file:expr) => {{
const RAW_DATA: &'static [u8] =
include_bytes!(concat!("../../../../ethcore/res/ethereum/tests/", $file, ".json"));
::ethjson::blockchain::Test::load(RAW_DATA)
.unwrap().into_iter().next().unwrap().1
extract_chain!(iter $file).next().unwrap().1
}};
}
macro_rules! register_test {
($name:ident, $cb:expr, $file:expr) => {
#[test]
fn $name() {
for (name, chain) in extract_chain!(iter $file) {
$cb(name, chain);
}
}
};
(heavy $name:ident, $cb:expr, $file:expr) => {
#[test]
#[cfg(feature = "test-heavy")]
fn $name() {
for (name, chain) in extract_chain!(iter $file) {
$cb(name, chain);
}
}
};
(ignore $name:ident, $cb:expr, $file:expr) => {
#[test]
#[ignore]
fn $name() {
for (name, chain) in extract_chain!(iter $file) {
$cb(name, chain);
}
}
};
}
#[cfg(test)]
mod mocked;
#[cfg(test)]

View File

@@ -74,12 +74,6 @@ pub trait Eth: Sized + Send + Sync + 'static {
/// Returns the code at given address at given time (block number).
fn code_at(&self, _: Params) -> Result<Value, Error>;
/// Signs the data with given address signature.
fn sign(&self, _: Params) -> Result<Value, Error>;
/// Sends transaction.
fn send_transaction(&self, _: Params) -> Result<Value, Error>;
/// Sends signed transaction.
fn send_raw_transaction(&self, _: Params) -> Result<Value, Error>;
@@ -150,8 +144,6 @@ pub trait Eth: Sized + Send + Sync + 'static {
delegate.add_method("eth_getUncleCountByBlockHash", Eth::block_uncles_count_by_hash);
delegate.add_method("eth_getUncleCountByBlockNumber", Eth::block_uncles_count_by_number);
delegate.add_method("eth_getCode", Eth::code_at);
delegate.add_method("eth_sign", Eth::sign);
delegate.add_method("eth_sendTransaction", Eth::send_transaction);
delegate.add_method("eth_sendRawTransaction", Eth::send_raw_transaction);
delegate.add_method("eth_call", Eth::call);
delegate.add_method("eth_estimateGas", Eth::estimate_gas);
@@ -208,3 +200,20 @@ pub trait EthFilter: Sized + Send + Sync + 'static {
delegate
}
}
/// Signing methods implementation relying on unlocked accounts.
pub trait EthSigning: Sized + Send + Sync + 'static {
/// Signs the data with given address signature.
fn sign(&self, _: Params) -> Result<Value, Error>;
/// Sends transaction.
fn send_transaction(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("eth_sign", EthSigning::sign);
delegate.add_method("eth_sendTransaction", EthSigning::send_transaction);
delegate
}
}

View File

@@ -25,9 +25,11 @@ pub mod traces;
pub mod rpc;
pub use self::web3::Web3;
pub use self::eth::{Eth, EthFilter};
pub use self::eth::{Eth, EthFilter, EthSigning};
pub use self::net::Net;
pub use self::personal::Personal;
pub use self::personal::{Personal, PersonalSigner};
pub use self::ethcore::Ethcore;
pub use self::traces::Traces;
pub use self::rpc::Rpc;

View File

@@ -43,3 +43,26 @@ pub trait Personal: Sized + Send + Sync + 'static {
delegate
}
}
/// Personal extension for transactions confirmations rpc interface.
pub trait PersonalSigner: Sized + Send + Sync + 'static {
/// Returns a list of transactions to confirm.
fn transactions_to_confirm(&self, _: Params) -> Result<Value, Error>;
/// Confirm and send a specific transaction.
fn confirm_transaction(&self, _: Params) -> Result<Value, Error>;
/// Reject the transaction request.
fn reject_transaction(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm);
delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction);
delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction);
delegate
}
}

View File

@@ -38,7 +38,7 @@ pub use self::log::Log;
pub use self::optionals::OptionalValue;
pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction;
pub use self::transaction_request::TransactionRequest;
pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification};
pub use self::call_request::CallRequest;
pub use self::receipt::Receipt;
pub use self::trace::Trace;

View File

@@ -21,7 +21,7 @@ use util::numbers::U256;
use v1::types::bytes::Bytes;
/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct TransactionRequest {
/// Sender
pub from: Address,
@@ -40,6 +40,24 @@ pub struct TransactionRequest {
pub nonce: Option<U256>,
}
/// Transaction confirmation waiting in a queue
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)]
pub struct TransactionConfirmation {
/// Id of this confirmation
pub id: U256,
/// TransactionRequest
pub transaction: TransactionRequest,
}
/// Possible modifications to the confirmed transaction sent by SystemUI
#[derive(Debug, PartialEq, Deserialize)]
pub struct TransactionModification {
/// Modified gas price
#[serde(rename="gasPrice")]
pub gas_price: Option<U256>,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
@@ -135,5 +153,26 @@ mod tests {
nonce: None,
});
}
#[test]
fn should_deserialize_modification() {
// given
let s1 = r#"{
"gasPrice":"0x0ba43b7400"
}"#;
let s2 = r#"{}"#;
// when
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
// then
assert_eq!(res1, TransactionModification {
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
});
assert_eq!(res2, TransactionModification {
gas_price: None,
});
}
}