Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support parity_pendingTransactions #451

Merged
merged 3 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions src/api/parity.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
api::Namespace,
helpers::{self, CallFuture},
types::{Bytes, CallRequest},
rpc::Value,
types::{Bytes, CallRequest, ParityPendingTransactionFilter, Transaction},
Transport,
};

Expand Down Expand Up @@ -31,6 +32,23 @@ impl<T: Transport> Parity<T> {

CallFuture::new(self.transport.execute("parity_call", vec![reqs]))
}

/// Get pending transactions
/// Blocked by https://github.com/openethereum/openethereum/issues/159
pub fn pending_transactions(
&self,
limit: Option<usize>,
filter: Option<ParityPendingTransactionFilter>,
) -> CallFuture<Vec<Transaction>, T::Out> {
let params = match (limit, filter) {
(Some(l), Some(f)) => vec![l.into(), helpers::serialize(&f)],
(Some(l), _) => vec![l.into()],
(_, Some(f)) => vec![Value::Null, helpers::serialize(&f)],
_ => vec![],
};
mohoff marked this conversation as resolved.
Show resolved Hide resolved

CallFuture::new(self.transport.execute("parity_pendingTransactions", params))
}
}

#[cfg(test)]
Expand All @@ -39,10 +57,24 @@ mod tests {
use crate::{
api::Namespace,
rpc::Value,
types::{Address, CallRequest},
types::{Address, CallRequest, FilterCondition, ParityPendingTransactionFilterBuilder, Transaction, U64},
};
use hex_literal::hex;

const EXAMPLE_PENDING_TX: &str = r#"{
"hash": "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b",
"nonce": "0x0",
"blockHash": null,
"blockNumber": null,
"transactionIndex": null,
"from": "0x407d73d8a49eeb85d32cf465507dd71d507100c1",
"to": "0x85dd43d8a49eeb85d32cf465507dd71d507100c1",
"value": "0x7f110",
"gas": "0x7f110",
"gasPrice": "0x09184e72a000",
"input": "0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360"
}"#;

rpc_test!(
Parity:call,
vec![
Expand Down Expand Up @@ -75,4 +107,18 @@ mod tests {
];
Value::Array(vec![Value::String("0x010203".into()), Value::String("0x7198ab".into()), Value::String("0xde763f".into())]) => vec![hex!("010203").into(), hex!("7198ab").into(), hex!("de763f").into()]
);

rpc_test!(
Parity:pending_transactions,
1,
ParityPendingTransactionFilterBuilder::default()
.from(FilterCondition::Eq(Address::from_low_u64_be(0x32)))
.gas_price(FilterCondition::Gt(U64::from(100_000_000_000 as u64)))
.build()
=> "parity_pendingTransactions",
vec![r#"1"#, r#"{"from":{"eq":"0x0000000000000000000000000000000000000032"},"gas_price":{"gt":"0x174876e800"}}"#]
;
Value::Array(vec![::serde_json::from_str(EXAMPLE_PENDING_TX).unwrap()])
=> vec![::serde_json::from_str::<Transaction>(EXAMPLE_PENDING_TX).unwrap()]
);
}
4 changes: 4 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod bytes;
mod bytes_array;
mod log;
mod parity_peers;
mod parity_pending_transaction;
mod recovery;
mod signed;
mod sync_state;
Expand All @@ -25,6 +26,9 @@ pub use self::{
parity_peers::{
EthProtocolInfo, ParityPeerInfo, ParityPeerType, PeerNetworkInfo, PeerProtocolsInfo, PipProtocolInfo,
},
parity_pending_transaction::{
FilterCondition, ParityPendingTransactionFilter, ParityPendingTransactionFilterBuilder, ToFilter,
},
recovery::{Recovery, RecoveryMessage},
signed::{SignedData, SignedTransaction, TransactionParameters},
sync_state::{SyncInfo, SyncState},
Expand Down
135 changes: 135 additions & 0 deletions src/types/parity_pending_transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use serde::{
ser::{SerializeMap, Serializer},
Serialize,
};

use super::{Address, U256, U64};

/// Condition to filter pending transactions
#[derive(Clone)]
pub enum FilterCondition<T> {
/// Lower Than
Lt(T),
mohoff marked this conversation as resolved.
Show resolved Hide resolved
/// Equal
Eq(T),
/// Greater Than
Gt(T),
}

/// To Filter
#[derive(Clone)]
pub enum ToFilter {
/// Address
Address(Address),
/// Action (i.e. contract creation)
Action,
}

/// Filter for pending transactions (only openethereum/Parity)
#[derive(Clone, Default, Serialize)]
pub struct ParityPendingTransactionFilter {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add ::builder() function to make it easy to create builder struct without importing the type?

impl ParityPendingTransactionFilter {
  pub fn builder() -> ParityPendingTransactionFilterBuilder {
    Default::default()
  }  
}

/// From address
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<FilterCondition<Address>>,
/// To address or action, i.e. contract creation
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<ToFilter>,
/// Gas
#[serde(skip_serializing_if = "Option::is_none")]
pub gas: Option<FilterCondition<U64>>,
/// Gas Price
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<FilterCondition<U64>>,
/// Value
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<FilterCondition<U256>>,
/// Nonce
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<FilterCondition<U256>>,
}

impl<T> Serialize for FilterCondition<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
match self {
Self::Lt(v) => map.serialize_entry("lt", v),
mohoff marked this conversation as resolved.
Show resolved Hide resolved
Self::Eq(v) => map.serialize_entry("eq", v),
Self::Gt(v) => map.serialize_entry("gt", v),
}?;
map.end()
}
}

impl Serialize for ToFilter {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;

match self {
Self::Address(a) => map.serialize_entry("eq", a)?,
mohoff marked this conversation as resolved.
Show resolved Hide resolved
Self::Action => map.serialize_entry("action", "contract_creation")?,
}
map.end()
}
}

/// Filter Builder
#[derive(Default, Clone)]
pub struct ParityPendingTransactionFilterBuilder {
filter: ParityPendingTransactionFilter,
}

impl ParityPendingTransactionFilterBuilder {
/// Sets `from`
pub fn from(mut self, from: FilterCondition<Address>) -> Self {
if let FilterCondition::Eq(_) = from {
self.filter.from = Some(from);
self
} else {
panic!("Must use FilterConditon::Eq to apply filter for `from` address")
mohoff marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Sets `to`
pub fn to(mut self, to_or_action: ToFilter) -> Self {
self.filter.to = Some(to_or_action);
self
}

/// Sets `gas`
pub fn gas(mut self, gas: FilterCondition<U64>) -> Self {
mohoff marked this conversation as resolved.
Show resolved Hide resolved
self.filter.gas = Some(gas);
self
}

/// Sets `gas_price`
pub fn gas_price(mut self, gas_price: FilterCondition<U64>) -> Self {
self.filter.gas_price = Some(gas_price);
self
}

/// Sets `value`
pub fn value(mut self, value: FilterCondition<U256>) -> Self {
self.filter.value = Some(value);
self
}

/// Sets `nonce`
pub fn nonce(mut self, nonce: FilterCondition<U256>) -> Self {
self.filter.nonce = Some(nonce);
self
}

/// Returns filter
pub fn build(&self) -> ParityPendingTransactionFilter {
self.filter.clone()
}
}