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:
parent
087ebcf94e
commit
9fb5623569
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -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)",
|
||||
]
|
||||
|
@ -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};
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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,11 +148,11 @@ 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...
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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, )| {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}"#;
|
||||
|
@ -206,6 +206,10 @@ 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
|
||||
@ -226,6 +230,7 @@ pub trait EthSigning: Sized + Send + Sync + 'static {
|
||||
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);
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
150
rpc/src/v1/types/confirmations.rs
Normal file
150
rpc/src/v1/types/confirmations.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user