Supporting eth_sign in Signer (#1787)

* Making ConfirmationsQueue a bit more generic [WiP]

* Generalizing cofirmations

* New confirmations types - tests

* Separating transaction type in queue. Closes #1310

* Handling sign requests

* Speeding up tests

* Renaming methods

* eth_postSign

* Bumping ui
This commit is contained in:
Tomasz Drwięga 2016-08-03 10:36:54 +02:00 committed by Gav Wood
parent 087ebcf94e
commit 9fb5623569
16 changed files with 538 additions and 219 deletions

10
Cargo.lock generated
View File

@ -924,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "parity-dapps"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -938,7 +938,7 @@ dependencies = [
[[package]]
name = "parity-dapps-home"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]
@ -946,7 +946,7 @@ dependencies = [
[[package]]
name = "parity-dapps-signer"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]
@ -954,7 +954,7 @@ dependencies = [
[[package]]
name = "parity-dapps-status"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]
@ -962,7 +962,7 @@ dependencies = [
[[package]]
name = "parity-dapps-wallet"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]

View File

@ -21,5 +21,5 @@ mod signing_queue;
pub use self::poll_manager::PollManager;
pub use self::poll_filter::PollFilter;
pub use self::requests::{TransactionRequest, TransactionConfirmation, CallRequest};
pub use self::requests::{TransactionRequest, FilledTransactionRequest, ConfirmationRequest, ConfirmationPayload, CallRequest};
pub use self::signing_queue::{ConfirmationsQueue, ConfirmationPromise, ConfirmationResult, SigningQueue, QueueEvent};

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::{Address, U256};
use util::{Address, U256, Bytes, H256};
/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
@ -30,18 +30,42 @@ pub struct TransactionRequest {
/// Value of transaction in wei
pub value: Option<U256>,
/// Additional data sent with transaction
pub data: Option<Vec<u8>>,
pub data: Option<Bytes>,
/// Transaction's nonce
pub nonce: Option<U256>,
}
/// Transaction confirmation waiting in a queue
/// Transaction request coming from RPC with default values filled in.
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
pub struct TransactionConfirmation {
/// Id of this confirmation
pub id: U256,
/// TransactionRequest
pub transaction: TransactionRequest,
pub struct FilledTransactionRequest {
/// Sender
pub from: Address,
/// Recipient
pub to: Option<Address>,
/// Gas Price
pub gas_price: U256,
/// Gas
pub gas: U256,
/// Value of transaction in wei
pub value: U256,
/// Additional data sent with transaction
pub data: Bytes,
/// Transaction's nonce
pub nonce: Option<U256>,
}
impl From<FilledTransactionRequest> for TransactionRequest {
fn from(r: FilledTransactionRequest) -> Self {
TransactionRequest {
from: r.from,
to: r.to,
gas_price: Some(r.gas_price),
gas: Some(r.gas),
value: Some(r.value),
data: Some(r.data),
nonce: r.nonce,
}
}
}
/// Call request
@ -62,3 +86,21 @@ pub struct CallRequest {
/// Nonce
pub nonce: Option<U256>,
}
/// Confirmation object
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ConfirmationRequest {
/// Id of this confirmation
pub id: U256,
/// Payload to confirm
pub payload: ConfirmationPayload,
}
/// Payload to confirm in Trusted Signer
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ConfirmationPayload {
/// Transaction
Transaction(FilledTransactionRequest),
/// Sign request
Sign(Address, H256),
}

View File

@ -17,10 +17,10 @@
use std::thread;
use std::time::{Instant, Duration};
use std::sync::{mpsc, Arc};
use std::collections::HashMap;
use std::collections::BTreeMap;
use jsonrpc_core;
use util::{Mutex, RwLock, U256};
use v1::helpers::{TransactionRequest, TransactionConfirmation};
use v1::helpers::{ConfirmationRequest, ConfirmationPayload};
/// Result that can be returned from JSON RPC.
pub type RpcResult = Result<jsonrpc_core::Value, jsonrpc_core::Error>;
@ -54,41 +54,41 @@ pub type QueueEventReceiver = mpsc::Receiver<QueueEvent>;
pub trait SigningQueue: Send + Sync {
/// Add new request to the queue.
/// Returns a `ConfirmationPromise` that can be used to await for resolution of given request.
fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise;
fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise;
/// Removes a request from the queue.
/// Notifies possible token holders that transaction was rejected.
fn request_rejected(&self, id: U256) -> Option<TransactionConfirmation>;
/// Notifies possible token holders that request was rejected.
fn request_rejected(&self, id: U256) -> Option<ConfirmationRequest>;
/// Removes a request from the queue.
/// Notifies possible token holders that transaction was confirmed and given hash was assigned.
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<TransactionConfirmation>;
/// Notifies possible token holders that request was confirmed and given hash was assigned.
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<ConfirmationRequest>;
/// Returns a request if it is contained in the queue.
fn peek(&self, id: &U256) -> Option<TransactionConfirmation>;
fn peek(&self, id: &U256) -> Option<ConfirmationRequest>;
/// Return copy of all the requests in the queue.
fn requests(&self) -> Vec<TransactionConfirmation>;
fn requests(&self) -> Vec<ConfirmationRequest>;
/// Returns number of transactions awaiting confirmation.
/// Returns number of requests awaiting confirmation.
fn len(&self) -> usize;
/// Returns true if there are no transactions awaiting confirmation.
/// Returns true if there are no requests awaiting confirmation.
fn is_empty(&self) -> bool;
}
#[derive(Debug, Clone, PartialEq)]
/// Result of a pending transaction.
/// Result of a pending confirmation request.
pub enum ConfirmationResult {
/// The transaction has not yet been confirmed nor rejected.
/// The request has not yet been confirmed nor rejected.
Waiting,
/// The transaction has been rejected.
/// The request has been rejected.
Rejected,
/// The transaction has been confirmed.
/// The request has been confirmed.
Confirmed(RpcResult),
}
/// Time you need to confirm the transaction in UI.
/// Time you need to confirm the request in UI.
/// This is the amount of time token holder will wait before
/// returning `None`.
/// Unless we have a multi-threaded RPC this will lock
@ -100,12 +100,14 @@ const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20;
pub struct ConfirmationToken {
result: Arc<Mutex<ConfirmationResult>>,
handle: thread::Thread,
request: TransactionConfirmation,
request: ConfirmationRequest,
timeout: Duration,
}
pub struct ConfirmationPromise {
id: U256,
result: Arc<Mutex<ConfirmationResult>>,
timeout: Duration,
}
impl ConfirmationToken {
@ -121,6 +123,7 @@ impl ConfirmationToken {
ConfirmationPromise {
id: self.request.id,
result: self.result.clone(),
timeout: self.timeout,
}
}
}
@ -134,8 +137,7 @@ impl ConfirmationPromise {
/// Returns `None` if transaction was rejected or timeout reached.
/// Returns `Some(result)` if transaction was confirmed.
pub fn wait_with_timeout(&self) -> Option<RpcResult> {
let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC);
let res = self.wait_until(Instant::now() + timeout);
let res = self.wait_until(Instant::now() + self.timeout);
match res {
ConfirmationResult::Confirmed(h) => Some(h),
ConfirmationResult::Rejected | ConfirmationResult::Waiting => None,
@ -146,16 +148,16 @@ impl ConfirmationPromise {
pub fn result(&self) -> ConfirmationResult { self.wait_until(Instant::now()) }
/// Blocks current thread and awaits for
/// resolution of the transaction (rejected / confirmed)
/// Returns `None` if transaction was rejected or timeout reached.
/// Returns `Some(result)` if transaction was confirmed.
/// resolution of the request (rejected / confirmed)
/// Returns `None` if request was rejected or timeout reached.
/// Returns `Some(result)` if request was confirmed.
pub fn wait_until(&self, deadline: Instant) -> ConfirmationResult {
trace!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id);
trace!(target: "own_tx", "Signer: Awaiting confirmation... ({:?}).", self.id);
loop {
let now = Instant::now();
// Check the result...
match *self.result.lock() {
// Waiting and deadline not yet passed continue looping.
// Waiting and deadline not yet passed continue looping.
ConfirmationResult::Waiting if now < deadline => {}
// Anything else - return.
ref a => return a.clone(),
@ -166,12 +168,13 @@ impl ConfirmationPromise {
}
}
/// Queue for all unconfirmed transactions.
/// Queue for all unconfirmed requests.
pub struct ConfirmationsQueue {
id: Mutex<U256>,
queue: RwLock<HashMap<U256, ConfirmationToken>>,
queue: RwLock<BTreeMap<U256, ConfirmationToken>>,
sender: Mutex<mpsc::Sender<QueueEvent>>,
receiver: Mutex<Option<mpsc::Receiver<QueueEvent>>>,
timeout: Duration,
}
impl Default for ConfirmationsQueue {
@ -180,14 +183,23 @@ impl Default for ConfirmationsQueue {
ConfirmationsQueue {
id: Mutex::new(U256::from(0)),
queue: RwLock::new(HashMap::new()),
queue: RwLock::new(BTreeMap::new()),
sender: Mutex::new(send),
receiver: Mutex::new(Some(recv)),
timeout: Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC),
}
}
}
impl ConfirmationsQueue {
#[cfg(test)]
/// Creates new confirmations queue with specified timeout
pub fn with_timeout(timeout: Duration) -> Self {
let mut queue = Self::default();
queue.timeout = timeout;
queue
}
/// Blocks the thread and starts listening for notifications regarding all actions in the queue.
/// For each event, `listener` callback will be invoked.
/// This method can be used only once (only single consumer of events can exist).
@ -221,9 +233,9 @@ impl ConfirmationsQueue {
let _ = self.sender.lock().send(message);
}
/// Removes transaction from this queue and notifies `ConfirmationPromise` holders about the result.
/// Removes requests from this queue and notifies `ConfirmationPromise` holders about the result.
/// Notifies also a receiver about that event.
fn remove(&self, id: U256, result: Option<RpcResult>) -> Option<TransactionConfirmation> {
fn remove(&self, id: U256, result: Option<RpcResult>) -> Option<ConfirmationRequest> {
let token = self.queue.write().remove(&id);
if let Some(token) = token {
@ -248,7 +260,7 @@ impl Drop for ConfirmationsQueue {
}
impl SigningQueue for ConfirmationsQueue {
fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise {
fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise {
// Increment id
let id = {
let mut last_id = self.id.lock();
@ -257,16 +269,19 @@ impl SigningQueue for ConfirmationsQueue {
};
// Add request to queue
let res = {
debug!(target: "own_tx", "Signer: New entry ({:?}) in confirmation queue.", id);
trace!(target: "own_tx", "Signer: ({:?}) : {:?}", id, request);
let mut queue = self.queue.write();
queue.insert(id, ConfirmationToken {
result: Arc::new(Mutex::new(ConfirmationResult::Waiting)),
handle: thread::current(),
request: TransactionConfirmation {
request: ConfirmationRequest {
id: id,
transaction: transaction,
payload: request,
},
timeout: self.timeout,
});
debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id);
queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.")
};
// Notify listeners
@ -275,21 +290,21 @@ impl SigningQueue for ConfirmationsQueue {
}
fn peek(&self, id: &U256) -> Option<TransactionConfirmation> {
fn peek(&self, id: &U256) -> Option<ConfirmationRequest> {
self.queue.read().get(id).map(|token| token.request.clone())
}
fn request_rejected(&self, id: U256) -> Option<TransactionConfirmation> {
debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id);
fn request_rejected(&self, id: U256) -> Option<ConfirmationRequest> {
debug!(target: "own_tx", "Signer: Request rejected ({:?}).", id);
self.remove(id, None)
}
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<TransactionConfirmation> {
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<ConfirmationRequest> {
debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id);
self.remove(id, Some(result))
}
fn requests(&self) -> Vec<TransactionConfirmation> {
fn requests(&self) -> Vec<ConfirmationRequest> {
let queue = self.queue.read();
queue.values().map(|token| token.request.clone()).collect()
}
@ -312,20 +327,20 @@ mod test {
use std::thread;
use std::sync::Arc;
use util::{Address, U256, H256, Mutex};
use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, TransactionRequest};
use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload};
use v1::types::H256 as NH256;
use jsonrpc_core::to_value;
fn request() -> TransactionRequest {
TransactionRequest {
fn request() -> ConfirmationPayload {
ConfirmationPayload::Transaction(FilledTransactionRequest {
from: Address::from(1),
to: Some(Address::from(2)),
gas_price: None,
gas: None,
value: Some(U256::from(10_000_000)),
data: None,
gas_price: 0.into(),
gas: 10_000.into(),
value: 10_000_000.into(),
data: vec![],
nonce: None,
}
})
}
#[test]
@ -391,6 +406,6 @@ mod test {
assert_eq!(all.len(), 1);
let el = all.get(0).unwrap();
assert_eq!(el.id, U256::from(1));
assert_eq!(el.transaction, request);
assert_eq!(el.payload, request);
}
}

View File

@ -23,24 +23,21 @@ use ethcore::client::MiningBlockChainClient;
use util::{U256, Address, H256, Mutex};
use transient_hashmap::TransientHashMap;
use ethcore::account_provider::AccountProvider;
use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, TransactionRequest as TRequest};
use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest};
use v1::traits::EthSigning;
use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256};
use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error};
use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error, signer_disabled_error};
fn fill_optional_fields<C, M>(request: &mut TRequest, client: &C, miner: &M)
fn fill_optional_fields<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest
where C: MiningBlockChainClient, M: MinerService {
if request.value.is_none() {
request.value = Some(U256::from(0));
}
if request.gas.is_none() {
request.gas = Some(miner.sensible_gas_limit());
}
if request.gas_price.is_none() {
request.gas_price = Some(default_gas_price(client, miner));
}
if request.data.is_none() {
request.data = Some(Vec::new());
FilledRequest {
from: request.from,
to: request.to,
nonce: request.nonce,
gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)),
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
value: request.value.unwrap_or_else(|| 0.into()),
data: request.data.unwrap_or_else(Vec::new),
}
}
@ -74,10 +71,26 @@ impl<C, M> EthSigningQueueClient<C, M> where C: MiningBlockChainClient, M: Miner
Ok(())
}
fn dispatch<F: FnOnce(ConfirmationPromise) -> Result<Value, Error>>(&self, params: Params, f: F) -> Result<Value, Error> {
fn dispatch_sign<F: FnOnce(ConfirmationPromise) -> Result<Value, Error>>(&self, params: Params, f: F) -> Result<Value, Error> {
from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| {
let address: Address = address.into();
let msg: H256 = msg.into();
let accounts = take_weak!(self.accounts);
if accounts.is_unlocked(address) {
return to_value(&accounts.sign(address, msg).ok().map_or_else(RpcH520::default, Into::into));
}
let queue = take_weak!(self.queue);
let promise = queue.add_request(ConfirmationPayload::Sign(address, msg));
f(promise)
})
}
fn dispatch_transaction<F: FnOnce(ConfirmationPromise) -> Result<Value, Error>>(&self, params: Params, f: F) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let mut request: TRequest = request.into();
let request: TRequest = request.into();
let accounts = take_weak!(self.accounts);
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
@ -87,8 +100,8 @@ impl<C, M> EthSigningQueueClient<C, M> where C: MiningBlockChainClient, M: Miner
}
let queue = take_weak!(self.queue);
fill_optional_fields(&mut request, &*client, &*miner);
let promise = queue.add_request(request);
let request = fill_optional_fields(request, &*client, &*miner);
let promise = queue.add_request(ConfirmationPayload::Transaction(request));
f(promise)
})
}
@ -98,23 +111,32 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
where C: MiningBlockChainClient + 'static, M: MinerService + 'static
{
fn sign(&self, _params: Params) -> Result<Value, Error> {
fn sign(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
warn!("Invoking eth_sign is not yet supported with signer enabled.");
// TODO [ToDr] Implement sign when rest of the signing queue is ready.
rpc_unimplemented!()
self.dispatch_sign(params, |promise| {
promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH520::default()))
})
}
fn post_sign(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
self.dispatch_sign(params, |promise| {
let id = promise.id();
self.pending.lock().insert(id, promise);
to_value(&RpcU256::from(id))
})
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
self.dispatch(params, |promise| {
self.dispatch_transaction(params, |promise| {
promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH256::default()))
})
}
fn post_transaction(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
self.dispatch(params, |promise| {
self.dispatch_transaction(params, |promise| {
let id = promise.id();
self.pending.lock().insert(id, promise);
to_value(&RpcU256::from(id))
@ -193,13 +215,18 @@ impl<C, M> EthSigning for EthSigningUnsafeClient<C, M> where
})
}
fn post_sign(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(signer_disabled_error())
}
fn post_transaction(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(Error::invalid_params())
Err(signer_disabled_error())
}
fn check_transaction(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(Error::invalid_params())
Err(signer_disabled_error())
}
}

View File

@ -27,7 +27,7 @@ use ethcore::miner::MinerService;
use v1::traits::Ethcore;
use v1::types::{Bytes, U256};
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::impls::error_codes;
use v1::impls::signer_disabled_error;
/// Ethcore implementation.
pub struct EthcoreClient<C, M> where
@ -152,11 +152,7 @@ impl<C, M> Ethcore for EthcoreClient<C, M> where M: MinerService + 'static, C: M
fn unsigned_transactions_count(&self, _params: Params) -> Result<Value, Error> {
try!(self.active());
match self.confirmations_queue {
None => Err(Error {
code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED),
message: "Trusted Signer is disabled. This API is not available.".into(),
data: None
}),
None => Err(signer_disabled_error()),
Some(ref queue) => to_value(&queue.len()),
}
}

View File

@ -54,7 +54,7 @@ pub use self::traces::TracesClient;
pub use self::rpc::RpcClient;
use v1::helpers::TransactionRequest;
use v1::types::H256 as NH256;
use v1::types::{H256 as RpcH256, H520 as RpcH520};
use ethcore::error::Error as EthcoreError;
use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient;
@ -80,7 +80,7 @@ mod error_codes {
fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<Value, Error>
where C: MiningBlockChainClient, M: MinerService {
let hash = NH256::from(signed_transaction.hash());
let hash = RpcH256::from(signed_transaction.hash());
let import = miner.import_own_transaction(client, signed_transaction);
@ -89,6 +89,12 @@ fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedT
.and_then(|_| to_value(&hash))
}
fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result<Value, Error> {
accounts.sign_with_password(address, pass, hash)
.map_err(password_error)
.and_then(|hash| to_value(&RpcH520::from(hash)))
}
fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService {
Transaction {
nonce: request.nonce
@ -105,9 +111,10 @@ fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest)
}
}
fn unlock_sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address, password: String) -> Result<Value, Error>
fn unlock_sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result<Value, Error>
where C: MiningBlockChainClient, M: MinerService {
let address = request.from;
let signed_transaction = {
let t = prepare_transaction(client, miner, request);
let hash = t.hash();
@ -140,6 +147,14 @@ fn default_gas_price<C, M>(client: &C, miner: &M) -> U256 where C: MiningBlockCh
.unwrap_or_else(|_| miner.sensible_gas_price())
}
fn signer_disabled_error() -> Error {
Error {
code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED),
message: "Trusted Signer is disabled. This API is not available.".into(),
data: None
}
}
fn signing_error(error: AccountError) -> Error {
Error {
code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED),

View File

@ -105,10 +105,9 @@ impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBl
from_params::<(TransactionRequest, String)>(params)
.and_then(|(request, password)| {
let request: TRequest = request.into();
let sender = request.from;
let accounts = take_weak!(self.accounts);
unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, sender, password)
unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, password)
})
}

View File

@ -22,9 +22,9 @@ use ethcore::account_provider::AccountProvider;
use ethcore::client::MiningBlockChainClient;
use ethcore::miner::MinerService;
use v1::traits::PersonalSigner;
use v1::types::{TransactionModification, TransactionConfirmation, U256};
use v1::impls::unlock_sign_and_dispatch;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::types::{TransactionModification, ConfirmationRequest, U256};
use v1::impls::{unlock_sign_and_dispatch, signature_with_password};
use v1::helpers::{SigningQueue, ConfirmationsQueue, ConfirmationPayload};
/// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
@ -55,14 +55,16 @@ impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient,
impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
fn transactions_to_confirm(&self, _params: Params) -> Result<Value, Error> {
fn requests_to_confirm(&self, _params: Params) -> Result<Value, Error> {
try!(self.active());
let queue = take_weak!(self.queue);
to_value(&queue.requests().into_iter().map(From::from).collect::<Vec<TransactionConfirmation>>())
to_value(&queue.requests().into_iter().map(From::from).collect::<Vec<ConfirmationRequest>>())
}
fn confirm_transaction(&self, params: Params) -> Result<Value, Error> {
fn confirm_request(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
// TODO [ToDr] TransactionModification is redundant for some calls
// might be better to replace it in future
from_params::<(U256, TransactionModification, String)>(params).and_then(
|(id, modification, pass)| {
let id = id.into();
@ -70,17 +72,23 @@ impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: Mini
let queue = take_weak!(self.queue);
let client = take_weak!(self.client);
let miner = take_weak!(self.miner);
queue.peek(&id).map(|confirmation| {
let mut request = confirmation.transaction;
// apply modification
if let Some(gas_price) = modification.gas_price {
request.gas_price = Some(gas_price.into());
}
let sender = request.from;
let result = unlock_sign_and_dispatch(&*client, &*miner, request, &*accounts, sender, pass);
if let Ok(ref hash) = result {
queue.request_confirmed(id, Ok(hash.clone()));
queue.peek(&id).map(|confirmation| {
let result = match confirmation.payload {
ConfirmationPayload::Transaction(mut request) => {
// apply modification
if let Some(gas_price) = modification.gas_price {
request.gas_price = gas_price.into();
}
unlock_sign_and_dispatch(&*client, &*miner, request.into(), &*accounts, pass)
},
ConfirmationPayload::Sign(address, hash) => {
signature_with_password(&*accounts, address, hash, pass)
}
};
if let Ok(ref response) = result {
queue.request_confirmed(id, Ok(response.clone()));
}
result
}).unwrap_or_else(|| Err(Error::invalid_params()))
@ -88,7 +96,7 @@ impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: Mini
)
}
fn reject_transaction(&self, params: Params) -> Result<Value, Error> {
fn reject_request(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
from_params::<(U256, )>(params).and_then(
|(id, )| {

View File

@ -16,13 +16,14 @@
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use jsonrpc_core::IoHandler;
use v1::impls::EthSigningQueueClient;
use v1::traits::EthSigning;
use v1::helpers::{ConfirmationsQueue, SigningQueue};
use v1::tests::helpers::TestMinerService;
use util::{Address, FixedHash};
use util::numbers::{Uint, U256};
use util::numbers::{Uint, U256, H256};
use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
@ -37,7 +38,7 @@ struct EthSigningTester {
impl Default for EthSigningTester {
fn default() -> Self {
let queue = Arc::new(ConfirmationsQueue::default());
let queue = Arc::new(ConfirmationsQueue::with_timeout(Duration::from_millis(1)));
let client = Arc::new(TestBlockChainClient::default());
let miner = Arc::new(TestMinerService::default());
let accounts = Arc::new(AccountProvider::transient_provider());
@ -58,6 +59,78 @@ fn eth_signing() -> EthSigningTester {
EthSigningTester::default()
}
#[test]
fn should_add_sign_to_queue() {
// given
let tester = eth_signing();
let address = Address::random();
assert_eq!(tester.queue.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_sign",
"params": [
""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"0x0000000000000000000000000000000000000000000000000000000000000005"
],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}
#[test]
fn should_post_sign_to_queue() {
// given
let tester = eth_signing();
let address = Address::random();
assert_eq!(tester.queue.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_postSign",
"params": [
""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"0x0000000000000000000000000000000000000000000000000000000000000005"
],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x01","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}
#[test]
fn should_sign_if_account_is_unlocked() {
// given
let tester = eth_signing();
let hash: H256 = 5.into();
let acc = tester.accounts.new_account("test").unwrap();
tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap();
let signature = tester.accounts.sign(acc, hash).unwrap();
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_sign",
"params": [
""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"",
""# + format!("0x{:?}", hash).as_ref() + r#""
],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", signature).as_ref() + r#"","id":1}"#;
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 0);
}
#[test]
fn should_add_transaction_to_queue() {
@ -81,7 +154,6 @@ fn should_add_transaction_to_queue() {
}"#;
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

@ -23,7 +23,7 @@ use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
use v1::{SignerClient, PersonalSigner};
use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, ConfirmationsQueue, TransactionRequest};
use v1::helpers::{SigningQueue, ConfirmationsQueue, FilledTransactionRequest, ConfirmationPayload};
struct PersonalSignerTester {
queue: Arc<ConfirmationsQueue>,
@ -68,22 +68,28 @@ fn signer_tester() -> PersonalSignerTester {
#[test]
fn should_return_list_of_transactions_in_queue() {
fn should_return_list_of_items_to_confirm() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
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,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
tester.queue.add_request(ConfirmationPayload::Sign(1.into(), 5.into()));
// 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}"#;
let request = r#"{"jsonrpc":"2.0","method":"personal_requestsToConfirm","params":[],"id":1}"#;
let response = concat!(
r#"{"jsonrpc":"2.0","result":["#,
r#"{"id":"0x01","payload":{"transaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}},"#,
r#"{"id":"0x02","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#,
r#"],"id":1}"#
);
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
@ -94,19 +100,19 @@ fn should_return_list_of_transactions_in_queue() {
fn should_reject_transaction_from_queue_without_dispatching() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
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,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#;
let request = r#"{"jsonrpc":"2.0","method":"personal_rejectRequest","params":["0x01"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
// then
@ -119,19 +125,35 @@ fn should_reject_transaction_from_queue_without_dispatching() {
fn should_not_remove_transaction_if_password_is_invalid() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
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,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_confirmTransaction","params":["0x01",{},"xxx"],"id":1}"#;
let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}
#[test]
fn should_not_remove_sign_if_password_is_invalid() {
// given
let tester = signer_tester();
tester.queue.add_request(ConfirmationPayload::Sign(0.into(), 5.into()));
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#;
// then
@ -145,15 +167,15 @@ fn should_confirm_transaction_and_dispatch() {
let tester = signer_tester();
let address = tester.accounts.new_account("test").unwrap();
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
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,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
let t = Transaction {
nonce: U256::zero(),
@ -172,7 +194,7 @@ fn should_confirm_transaction_and_dispatch() {
// when
let request = r#"{
"jsonrpc":"2.0",
"method":"personal_confirmTransaction",
"method":"personal_confirmRequest",
"params":["0x01", {"gasPrice":"0x1000"}, "test"],
"id":1
}"#;

View File

@ -206,26 +206,31 @@ pub trait EthSigning: Sized + Send + Sync + 'static {
/// Signs the data with given address signature.
fn sign(&self, _: Params) -> Result<Value, Error>;
/// Posts sign request asynchronously.
/// Will return a confirmation ID for later use with check_transaction.
fn post_sign(&self, _: Params) -> Result<Value, Error>;
/// Sends transaction; will block for 20s to try to return the
/// transaction hash.
/// If it cannot yet be signed, it will return a transaction ID for
/// later use with check_transaction.
/// later use with check_transaction.
fn send_transaction(&self, _: Params) -> Result<Value, Error>;
/// Posts transaction asynchronously.
/// Will return a transaction ID for later use with check_transaction.
/// Will return a transaction ID for later use with check_transaction.
fn post_transaction(&self, _: Params) -> Result<Value, Error>;
/// Checks the progress of a previously posted transaction.
/// Should be given a valid send_transaction ID.
/// Returns the transaction hash, the zero hash (not yet available),
/// or an error.
/// or an error.
fn check_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_postSign", EthSigning::post_sign);
delegate.add_method("eth_sendTransaction", EthSigning::send_transaction);
delegate.add_method("eth_postTransaction", EthSigning::post_transaction);
delegate.add_method("eth_checkTransaction", EthSigning::check_transaction);

View File

@ -61,24 +61,24 @@ pub trait Personal: Sized + Send + Sync + 'static {
}
}
/// Personal extension for transactions confirmations rpc interface.
/// Personal extension for 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>;
/// Returns a list of items to confirm.
fn requests_to_confirm(&self, _: Params) -> Result<Value, Error>;
/// Confirm and send a specific transaction.
fn confirm_transaction(&self, _: Params) -> Result<Value, Error>;
/// Confirm specific request.
fn confirm_request(&self, _: Params) -> Result<Value, Error>;
/// Reject the transaction request.
fn reject_transaction(&self, _: Params) -> Result<Value, Error>;
/// Reject the confirmation request.
fn reject_request(&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.add_method("personal_requestsToConfirm", PersonalSigner::requests_to_confirm);
delegate.add_method("personal_confirmRequest", PersonalSigner::confirm_request);
delegate.add_method("personal_rejectRequest", PersonalSigner::reject_request);
delegate
}
}

View File

@ -0,0 +1,150 @@
// 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/>.
//! Types used in Confirmations queue (Trusted Signer)
use v1::types::{U256, TransactionRequest, H160, H256};
use v1::helpers;
/// Confirmation waiting in a queue
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct ConfirmationRequest {
/// Id of this confirmation
pub id: U256,
/// Payload
pub payload: ConfirmationPayload,
}
impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
fn from(c: helpers::ConfirmationRequest) -> Self {
ConfirmationRequest {
id: c.id.into(),
payload: c.payload.into(),
}
}
}
/// Sign request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct SignRequest {
/// Address
pub address: H160,
/// Hash to sign
pub hash: H256,
}
/// Confirmation payload, i.e. the thing to be confirmed
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub enum ConfirmationPayload {
/// Transaction
#[serde(rename="transaction")]
Transaction(TransactionRequest),
/// Signature
#[serde(rename="sign")]
Sign(SignRequest),
}
impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
fn from(c: helpers::ConfirmationPayload) -> Self {
match c {
helpers::ConfirmationPayload::Transaction(t) => ConfirmationPayload::Transaction(t.into()),
helpers::ConfirmationPayload::Sign(address, hash) => ConfirmationPayload::Sign(SignRequest {
address: address.into(),
hash: hash.into(),
}),
}
}
}
/// Possible modifications to the confirmed transaction sent by `Trusted Signer`
#[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;
use serde_json;
use v1::types::U256;
use v1::helpers;
use super::*;
#[test]
fn should_serialize_sign_confirmation() {
// given
let request = helpers::ConfirmationRequest {
id: 15.into(),
payload: helpers::ConfirmationPayload::Sign(1.into(), 5.into()),
};
// when
let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0x0f","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#;
// then
assert_eq!(res.unwrap(), expected.to_owned());
}
#[test]
fn should_serialize_transaction_confirmation() {
// given
let request = helpers::ConfirmationRequest {
id: 15.into(),
payload: helpers::ConfirmationPayload::Transaction(helpers::FilledTransactionRequest {
from: 0.into(),
to: None,
gas: 15_000.into(),
gas_price: 10_000.into(),
value: 100_000.into(),
data: vec![1, 2, 3],
nonce: Some(1.into()),
}),
};
// when
let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0x0f","payload":{"transaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x0186a0","data":"0x010203","nonce":"0x01"}}}"#;
// then
assert_eq!(res.unwrap(), expected.to_owned());
}
#[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,
});
}
}

View File

@ -17,6 +17,8 @@
mod bytes;
mod block;
mod block_number;
mod call_request;
mod confirmations;
mod filter;
mod hash;
mod index;
@ -24,7 +26,6 @@ mod log;
mod sync;
mod transaction;
mod transaction_request;
mod call_request;
mod receipt;
mod trace;
mod trace_filter;
@ -33,14 +34,15 @@ mod uint;
pub use self::bytes::Bytes;
pub use self::block::{Block, BlockTransactions};
pub use self::block_number::BlockNumber;
pub use self::call_request::CallRequest;
pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification};
pub use self::filter::Filter;
pub use self::hash::{H64, H160, H256, H520, H2048};
pub use self::index::Index;
pub use self::log::Log;
pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction;
pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification};
pub use self::call_request::CallRequest;
pub use self::transaction_request::TransactionRequest;
pub use self::receipt::Receipt;
pub use self::trace::{LocalizedTrace, TraceResults};
pub use self::trace_filter::TraceFilter;

View File

@ -17,7 +17,7 @@
//! `TransactionRequest` type
use v1::types::{Bytes, H160, U256};
use v1::helpers::{TransactionRequest as Request, TransactionConfirmation as Confirmation};
use v1::helpers;
/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
@ -39,8 +39,8 @@ pub struct TransactionRequest {
pub nonce: Option<U256>,
}
impl From<Request> for TransactionRequest {
fn from(r: Request) -> Self {
impl From<helpers::TransactionRequest> for TransactionRequest {
fn from(r: helpers::TransactionRequest) -> Self {
TransactionRequest {
from: r.from.into(),
to: r.to.map(Into::into),
@ -53,9 +53,23 @@ impl From<Request> for TransactionRequest {
}
}
impl Into<Request> for TransactionRequest {
fn into(self) -> Request {
Request {
impl From<helpers::FilledTransactionRequest> for TransactionRequest {
fn from(r: helpers::FilledTransactionRequest) -> Self {
TransactionRequest {
from: r.from.into(),
to: r.to.map(Into::into),
gas_price: Some(r.gas_price.into()),
gas: Some(r.gas.into()),
value: Some(r.value.into()),
data: Some(r.data.into()),
nonce: r.nonce.map(Into::into),
}
}
}
impl Into<helpers::TransactionRequest> for TransactionRequest {
fn into(self) -> helpers::TransactionRequest {
helpers::TransactionRequest {
from: self.from.into(),
to: self.to.map(Into::into),
gas_price: self.gas_price.map(Into::into),
@ -67,32 +81,6 @@ impl Into<Request> for TransactionRequest {
}
}
/// 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,
}
impl From<Confirmation> for TransactionConfirmation {
fn from(c: Confirmation) -> Self {
TransactionConfirmation {
id: c.id.into(),
transaction: c.transaction.into(),
}
}
}
/// Possible modifications to the confirmed transaction sent by `SignerUI`
#[derive(Debug, PartialEq, Deserialize)]
pub struct TransactionModification {
/// Modified gas price
#[serde(rename="gasPrice")]
pub gas_price: Option<U256>,
}
#[cfg(test)]
mod tests {
@ -188,7 +176,6 @@ mod tests {
});
}
#[test]
fn transaction_request_deserialize_error() {
let s = r#"{
@ -203,26 +190,5 @@ mod tests {
assert!(deserialized.is_err(), "Should be error because to is empty");
}
#[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,
});
}
}