Local transactions RPC
This commit is contained in:
parent
66e327dfcb
commit
2cd2b10327
@ -21,6 +21,9 @@ use transaction::SignedTransaction;
|
||||
use error::TransactionError;
|
||||
use util::{U256, H256};
|
||||
|
||||
/// Status of local transaction.
|
||||
/// Can indicate that the transaction is currently part of the queue (`Pending/Future`)
|
||||
/// or gives a reason why the transaction was removed.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Status {
|
||||
/// The transaction is currently in the transaction queue.
|
||||
|
@ -23,6 +23,7 @@ use account_provider::AccountProvider;
|
||||
use views::{BlockView, HeaderView};
|
||||
use state::{State, CleanupMode};
|
||||
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics};
|
||||
use client::TransactionImportResult;
|
||||
use executive::contract_address;
|
||||
use block::{ClosedBlock, SealedBlock, IsBlock, Block};
|
||||
use error::*;
|
||||
@ -33,10 +34,11 @@ use engines::Engine;
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
use miner::banning_queue::{BanningTransactionQueue, Threshold};
|
||||
use miner::work_notify::WorkPoster;
|
||||
use client::TransactionImportResult;
|
||||
use miner::price_info::PriceInfo;
|
||||
use miner::local_transactions::{Status as LocalTransactionStatus};
|
||||
use header::BlockNumber;
|
||||
|
||||
|
||||
/// Different possible definitions for pending transaction set.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PendingSet {
|
||||
@ -845,6 +847,14 @@ impl MinerService for Miner {
|
||||
queue.top_transactions()
|
||||
}
|
||||
|
||||
fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> {
|
||||
let queue = self.transaction_queue.lock();
|
||||
queue.local_transactions()
|
||||
.iter()
|
||||
.map(|(hash, status)| (*hash, status.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn pending_transactions(&self, best_block: BlockNumber) -> Vec<SignedTransaction> {
|
||||
let queue = self.transaction_queue.lock();
|
||||
match self.options.pending_set {
|
||||
|
@ -52,6 +52,7 @@ mod work_notify;
|
||||
pub use self::external::{ExternalMiner, ExternalMinerService};
|
||||
pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit};
|
||||
pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
pub use self::local_transactions::{Status as LocalTransactionStatus};
|
||||
pub use client::TransactionImportResult;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
@ -146,6 +147,9 @@ pub trait MinerService : Send + Sync {
|
||||
/// Get a list of all pending transactions.
|
||||
fn pending_transactions(&self, best_block: BlockNumber) -> Vec<SignedTransaction>;
|
||||
|
||||
/// Get a list of local transactions with statuses.
|
||||
fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus>;
|
||||
|
||||
/// Get a list of all pending receipts.
|
||||
fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap<H256, Receipt>;
|
||||
|
||||
|
@ -86,12 +86,13 @@ use std::ops::Deref;
|
||||
use std::cmp::Ordering;
|
||||
use std::cmp;
|
||||
use std::collections::{HashSet, HashMap, BTreeSet, BTreeMap};
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use util::{Address, H256, Uint, U256};
|
||||
use util::table::Table;
|
||||
use transaction::*;
|
||||
use error::{Error, TransactionError};
|
||||
use client::TransactionImportResult;
|
||||
use miner::local_transactions::LocalTransactionsList;
|
||||
use miner::local_transactions::{LocalTransactionsList, Status as LocalTransactionStatus};
|
||||
|
||||
/// Transaction origin
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@ -901,6 +902,11 @@ impl TransactionQueue {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns local transactions (some of them might not be part of the queue anymore).
|
||||
pub fn local_transactions(&self) -> &LinkedHashMap<H256, LocalTransactionStatus> {
|
||||
self.local_transactions.all_transactions()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn future_transactions(&self) -> Vec<SignedTransaction> {
|
||||
self.future.by_priority
|
||||
|
@ -22,7 +22,7 @@ macro_rules! rpc_unimplemented {
|
||||
|
||||
use std::fmt;
|
||||
use rlp::DecoderError;
|
||||
use ethcore::error::{Error as EthcoreError, CallError};
|
||||
use ethcore::error::{Error as EthcoreError, CallError, TransactionError};
|
||||
use ethcore::account_provider::{Error as AccountError};
|
||||
use fetch::FetchError;
|
||||
use jsonrpc_core::{Error, ErrorCode, Value};
|
||||
@ -227,11 +227,10 @@ pub fn from_password_error(error: AccountError) -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_transaction_error(error: EthcoreError) -> Error {
|
||||
pub fn transaction_message(error: TransactionError) -> String {
|
||||
use ethcore::error::TransactionError::*;
|
||||
|
||||
if let EthcoreError::Transaction(e) = error {
|
||||
let msg = match e {
|
||||
match error {
|
||||
AlreadyImported => "Transaction with the same hash was already imported.".into(),
|
||||
Old => "Transaction nonce is too low. Try incrementing the nonce.".into(),
|
||||
TooCheapToReplace => {
|
||||
@ -252,15 +251,20 @@ pub fn from_transaction_error(error: EthcoreError) -> Error {
|
||||
GasLimitExceeded { limit, got } => {
|
||||
format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got)
|
||||
},
|
||||
InvalidNetworkId => "Invalid network id.".into(),
|
||||
InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(),
|
||||
SenderBanned => "Sender is banned in local queue.".into(),
|
||||
RecipientBanned => "Recipient is banned in local queue.".into(),
|
||||
CodeBanned => "Code is banned in local queue.".into(),
|
||||
e => format!("{}", e).into(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_transaction_error(error: EthcoreError) -> Error {
|
||||
|
||||
if let EthcoreError::Transaction(e) = error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::TRANSACTION_ERROR),
|
||||
message: msg,
|
||||
message: transaction_message(e),
|
||||
data: None,
|
||||
}
|
||||
} else {
|
||||
|
@ -34,7 +34,11 @@ use ethcore::account_provider::AccountProvider;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::traits::Parity;
|
||||
use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram, TransactionStats};
|
||||
use v1::types::{
|
||||
Bytes, U256, H160, H256, H512,
|
||||
Peers, Transaction, RpcSettings, Histogram,
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
};
|
||||
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
|
||||
use v1::helpers::dispatch::DEFAULT_MAC;
|
||||
|
||||
@ -269,6 +273,17 @@ impl<C, M, S: ?Sized> Parity for ParityClient<C, M, S> where
|
||||
)
|
||||
}
|
||||
|
||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error> {
|
||||
try!(self.active());
|
||||
|
||||
let transactions = take_weak!(self.miner).local_transactions();
|
||||
Ok(transactions
|
||||
.into_iter()
|
||||
.map(|(hash, status)| (hash.into(), status.into()))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
fn signer_port(&self) -> Result<u16, Error> {
|
||||
try!(self.active());
|
||||
|
||||
|
@ -24,7 +24,7 @@ use ethcore::block::{ClosedBlock, IsBlock};
|
||||
use ethcore::header::BlockNumber;
|
||||
use ethcore::transaction::SignedTransaction;
|
||||
use ethcore::receipt::{Receipt, RichReceipt};
|
||||
use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult};
|
||||
use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus};
|
||||
|
||||
/// Test miner service.
|
||||
pub struct TestMinerService {
|
||||
@ -34,6 +34,8 @@ pub struct TestMinerService {
|
||||
pub latest_closed_block: Mutex<Option<ClosedBlock>>,
|
||||
/// Pre-existed pending transactions
|
||||
pub pending_transactions: Mutex<HashMap<H256, SignedTransaction>>,
|
||||
/// Pre-existed local transactions
|
||||
pub local_transactions: Mutex<BTreeMap<H256, LocalTransactionStatus>>,
|
||||
/// Pre-existed pending receipts
|
||||
pub pending_receipts: Mutex<BTreeMap<H256, Receipt>>,
|
||||
/// Last nonces.
|
||||
@ -53,6 +55,7 @@ impl Default for TestMinerService {
|
||||
imported_transactions: Mutex::new(Vec::new()),
|
||||
latest_closed_block: Mutex::new(None),
|
||||
pending_transactions: Mutex::new(HashMap::new()),
|
||||
local_transactions: Mutex::new(BTreeMap::new()),
|
||||
pending_receipts: Mutex::new(BTreeMap::new()),
|
||||
last_nonces: RwLock::new(HashMap::new()),
|
||||
min_gas_price: RwLock::new(U256::from(20_000_000)),
|
||||
@ -195,6 +198,10 @@ impl MinerService for TestMinerService {
|
||||
self.pending_transactions.lock().values().cloned().collect()
|
||||
}
|
||||
|
||||
fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> {
|
||||
self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect()
|
||||
}
|
||||
|
||||
fn pending_transactions(&self, _best_block: BlockNumber) -> Vec<SignedTransaction> {
|
||||
self.pending_transactions.lock().values().cloned().collect()
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ use std::sync::Arc;
|
||||
use util::log::RotatingLogger;
|
||||
use util::Address;
|
||||
use ethsync::ManageNetwork;
|
||||
use ethcore::client::{TestBlockChainClient};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::{TestBlockChainClient};
|
||||
use ethcore::miner::LocalTransactionStatus;
|
||||
use ethstore::ethkey::{Generator, Random};
|
||||
|
||||
use jsonrpc_core::IoHandler;
|
||||
@ -367,3 +368,16 @@ fn rpc_parity_transactions_stats() {
|
||||
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_parity_local_transactions() {
|
||||
let deps = Dependencies::new();
|
||||
let io = deps.default_client();
|
||||
deps.miner.local_transactions.lock().insert(10.into(), LocalTransactionStatus::Pending);
|
||||
deps.miner.local_transactions.lock().insert(15.into(), LocalTransactionStatus::Future);
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_localTransactions", "params":[], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"0x000000000000000000000000000000000000000000000000000000000000000a":{"status":"pending"},"0x000000000000000000000000000000000000000000000000000000000000000f":{"status":"future"}},"id":1}"#;
|
||||
|
||||
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,11 @@ use jsonrpc_core::Error;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use v1::helpers::auto_args::Wrap;
|
||||
use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram, TransactionStats};
|
||||
use v1::types::{
|
||||
H160, H256, H512, U256, Bytes,
|
||||
Peers, Transaction, RpcSettings, Histogram,
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
};
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Parity-specific rpc interface.
|
||||
@ -119,6 +123,10 @@ build_rpc_trait! {
|
||||
#[rpc(name = "parity_pendingTransactionsStats")]
|
||||
fn pending_transactions_stats(&self) -> Result<BTreeMap<H256, TransactionStats>, Error>;
|
||||
|
||||
/// Returns a list of current and past local transactions with status details.
|
||||
#[rpc(name = "parity_localTransactions")]
|
||||
fn local_transactions(&self) -> Result<BTreeMap<H256, LocalTransactionStatus>, Error>;
|
||||
|
||||
/// Returns current Trusted Signer port or an error if signer is disabled.
|
||||
#[rpc(name = "parity_signerPort")]
|
||||
fn signer_port(&self) -> Result<u16, Error>;
|
||||
|
@ -44,7 +44,7 @@ pub use self::hash::{H64, H160, H256, H512, H520, H2048};
|
||||
pub use self::index::Index;
|
||||
pub use self::log::Log;
|
||||
pub use self::sync::{SyncStatus, SyncInfo, Peers, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthereumProtocolInfo, TransactionStats};
|
||||
pub use self::transaction::Transaction;
|
||||
pub use self::transaction::{Transaction, LocalTransactionStatus};
|
||||
pub use self::transaction_request::TransactionRequest;
|
||||
pub use self::receipt::Receipt;
|
||||
pub use self::rpc_settings::RpcSettings;
|
||||
|
@ -14,8 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
use ethcore::miner;
|
||||
use ethcore::contract_address;
|
||||
use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction};
|
||||
use v1::helpers::errors;
|
||||
use v1::types::{Bytes, H160, H256, U256, H512};
|
||||
|
||||
/// Transaction
|
||||
@ -62,6 +65,74 @@ pub struct Transaction {
|
||||
pub s: H256,
|
||||
}
|
||||
|
||||
/// Local Transaction Status
|
||||
#[derive(Debug)]
|
||||
pub enum LocalTransactionStatus {
|
||||
/// Transaction is pending
|
||||
Pending,
|
||||
/// Transaction is in future part of the queue
|
||||
Future,
|
||||
/// Transaction is already mined.
|
||||
Mined(Transaction),
|
||||
/// Transaction was dropped because of limit.
|
||||
Dropped(Transaction),
|
||||
/// Transaction was replaced by transaction with higher gas price.
|
||||
Replaced(Transaction, U256, H256),
|
||||
/// Transaction never got into the queue.
|
||||
Rejected(Transaction, String),
|
||||
/// Transaction is invalid.
|
||||
Invalid(Transaction),
|
||||
}
|
||||
|
||||
impl Serialize for LocalTransactionStatus {
|
||||
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
use self::LocalTransactionStatus::*;
|
||||
|
||||
let elems = match *self {
|
||||
Pending | Future => 1,
|
||||
Mined(..) | Dropped(..) | Invalid(..) => 2,
|
||||
Rejected(..) => 3,
|
||||
Replaced(..) => 4,
|
||||
};
|
||||
|
||||
let status = "status";
|
||||
let transaction = "transaction";
|
||||
|
||||
let mut state = try!(serializer.serialize_struct("LocalTransactionStatus", elems));
|
||||
match *self {
|
||||
Pending => try!(serializer.serialize_struct_elt(&mut state, status, "pending")),
|
||||
Future => try!(serializer.serialize_struct_elt(&mut state, status, "future")),
|
||||
Mined(ref tx) => {
|
||||
try!(serializer.serialize_struct_elt(&mut state, status, "mined"));
|
||||
try!(serializer.serialize_struct_elt(&mut state, transaction, tx));
|
||||
},
|
||||
Dropped(ref tx) => {
|
||||
try!(serializer.serialize_struct_elt(&mut state, status, "dropped"));
|
||||
try!(serializer.serialize_struct_elt(&mut state, transaction, tx));
|
||||
},
|
||||
Invalid(ref tx) => {
|
||||
try!(serializer.serialize_struct_elt(&mut state, status, "invalid"));
|
||||
try!(serializer.serialize_struct_elt(&mut state, transaction, tx));
|
||||
},
|
||||
Rejected(ref tx, ref reason) => {
|
||||
try!(serializer.serialize_struct_elt(&mut state, status, "rejected"));
|
||||
try!(serializer.serialize_struct_elt(&mut state, transaction, tx));
|
||||
try!(serializer.serialize_struct_elt(&mut state, "error", reason));
|
||||
},
|
||||
Replaced(ref tx, ref gas_price, ref hash) => {
|
||||
try!(serializer.serialize_struct_elt(&mut state, status, "replaced"));
|
||||
try!(serializer.serialize_struct_elt(&mut state, transaction, tx));
|
||||
try!(serializer.serialize_struct_elt(&mut state, "hash", hash));
|
||||
try!(serializer.serialize_struct_elt(&mut state, "gasPrice", gas_price));
|
||||
},
|
||||
}
|
||||
serializer.serialize_struct_end(state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<LocalizedTransaction> for Transaction {
|
||||
fn from(t: LocalizedTransaction) -> Transaction {
|
||||
let signature = t.signature();
|
||||
@ -124,9 +195,24 @@ impl From<SignedTransaction> for Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<miner::LocalTransactionStatus> for LocalTransactionStatus {
|
||||
fn from(s: miner::LocalTransactionStatus) -> Self {
|
||||
use ethcore::miner::LocalTransactionStatus::*;
|
||||
match s {
|
||||
Pending => LocalTransactionStatus::Pending,
|
||||
Future => LocalTransactionStatus::Future,
|
||||
Mined(tx) => LocalTransactionStatus::Mined(tx.into()),
|
||||
Dropped(tx) => LocalTransactionStatus::Dropped(tx.into()),
|
||||
Rejected(tx, err) => LocalTransactionStatus::Rejected(tx.into(), errors::transaction_message(err)),
|
||||
Replaced(tx, gas_price, hash) => LocalTransactionStatus::Replaced(tx.into(), gas_price.into(), hash.into()),
|
||||
Invalid(tx) => LocalTransactionStatus::Invalid(tx.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Transaction;
|
||||
use super::{Transaction, LocalTransactionStatus};
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
@ -135,5 +221,50 @@ mod tests {
|
||||
let serialized = serde_json::to_string(&t).unwrap();
|
||||
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"v":0,"r":"0x0000000000000000000000000000000000000000000000000000000000000000","s":"0x0000000000000000000000000000000000000000000000000000000000000000"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_transaction_status_serialize() {
|
||||
let tx_ser = serde_json::to_string(&Transaction::default()).unwrap();
|
||||
let status1 = LocalTransactionStatus::Pending;
|
||||
let status2 = LocalTransactionStatus::Future;
|
||||
let status3 = LocalTransactionStatus::Mined(Transaction::default());
|
||||
let status4 = LocalTransactionStatus::Dropped(Transaction::default());
|
||||
let status5 = LocalTransactionStatus::Invalid(Transaction::default());
|
||||
let status6 = LocalTransactionStatus::Rejected(Transaction::default(), "Just because".into());
|
||||
let status7 = LocalTransactionStatus::Replaced(Transaction::default(), 5.into(), 10.into());
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status1).unwrap(),
|
||||
r#"{"status":"pending"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status2).unwrap(),
|
||||
r#"{"status":"future"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status3).unwrap(),
|
||||
r#"{"status":"mined","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status4).unwrap(),
|
||||
r#"{"status":"dropped","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status5).unwrap(),
|
||||
r#"{"status":"invalid","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status6).unwrap(),
|
||||
r#"{"status":"rejected","transaction":"#.to_owned() +
|
||||
&format!("{}", tx_ser) +
|
||||
r#","error":"Just because"}"#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&status7).unwrap(),
|
||||
r#"{"status":"replaced","transaction":"#.to_owned() +
|
||||
&format!("{}", tx_ser) +
|
||||
r#","hash":"0x000000000000000000000000000000000000000000000000000000000000000a","gasPrice":"0x5"}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user