// 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 .
use std::thread;
use std::time::{Instant, Duration};
use std::sync::{mpsc, Mutex, RwLock, Arc};
use std::collections::HashMap;
use jsonrpc_core;
use util::{U256, Lockable};
use v1::helpers::{TransactionRequest, TransactionConfirmation};
/// Result that can be returned from JSON RPC.
pub type RpcResult = Result;
/// Possible events happening in the queue that can be listened to.
#[derive(Debug, PartialEq)]
pub enum QueueEvent {
/// Receiver should stop work upon receiving `Finish` message.
Finish,
/// Informs about new request.
NewRequest(U256),
/// Request rejected.
RequestRejected(U256),
/// Request resolved.
RequestConfirmed(U256),
}
/// Defines possible errors returned from queue receiving method.
#[derive(Debug, PartialEq)]
pub enum QueueError {
/// Returned when method has been already used (no receiver available).
AlreadyUsed,
/// Returned when receiver encounters an error.
ReceiverError(mpsc::RecvError),
}
/// Message Receiver type
pub type QueueEventReceiver = mpsc::Receiver;
/// A queue of transactions awaiting to be confirmed and signed.
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;
/// Removes a request from the queue.
/// Notifies possible token holders that transaction was rejected.
fn request_rejected(&self, id: U256) -> Option;
/// 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;
/// Returns a request if it is contained in the queue.
fn peek(&self, id: &U256) -> Option;
/// Return copy of all the requests in the queue.
fn requests(&self) -> Vec;
/// Returns number of transactions awaiting confirmation.
fn len(&self) -> usize;
/// Returns true if there are no transactions awaiting confirmation.
fn is_empty(&self) -> bool;
}
#[derive(Debug, PartialEq)]
enum ConfirmationResult {
/// The transaction has not yet been confirmed nor rejected.
Waiting,
/// The transaction has been rejected.
Rejected,
/// The transaction has been confirmed.
Confirmed(RpcResult),
}
/// Time you need to confirm the transaction 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
/// any other incoming call!
const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20;
/// A handle to submitted request.
/// Allows to block and wait for a resolution of that request.
pub struct ConfirmationToken {
result: Arc>,
handle: thread::Thread,
request: TransactionConfirmation,
}
pub struct ConfirmationPromise {
id: U256,
result: Arc>,
}
impl ConfirmationToken {
/// Submit solution to all listeners
fn resolve(&self, result: Option) {
let mut res = self.result.locked();
*res = result.map_or(ConfirmationResult::Rejected, |h| ConfirmationResult::Confirmed(h));
// Notify listener
self.handle.unpark();
}
fn as_promise(&self) -> ConfirmationPromise {
ConfirmationPromise {
id: self.request.id,
result: self.result.clone(),
}
}
}
impl ConfirmationPromise {
/// 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.
pub fn wait_with_timeout(&self) -> Option {
let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC);
let deadline = Instant::now() + timeout;
info!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id);
loop {
let now = Instant::now();
if now >= deadline {
break;
}
// Park thread (may wake up spuriously)
thread::park_timeout(deadline - now);
// Take confirmation result
let res = self.result.locked();
// Check the result
match *res {
ConfirmationResult::Rejected => return None,
ConfirmationResult::Confirmed(ref h) => return Some(h.clone()),
ConfirmationResult::Waiting => continue,
}
}
// We reached the timeout. Just return `None`
trace!(target: "own_tx", "Signer: Confirmation timeout reached... ({:?}).", self.id);
None
}
}
/// Queue for all unconfirmed transactions.
pub struct ConfirmationsQueue {
id: Mutex,
queue: RwLock>,
sender: Mutex>,
receiver: Mutex