use crate::api::{Eth, EthFilter, Namespace};
use crate::types::{Bytes, TransactionReceipt, TransactionRequest, H256, U64};
use crate::{error, Transport};
use futures::{Future, StreamExt};
use std::time::Duration;
pub trait ConfirmationCheck {
type Check: Future<Output = error::Result<Option<U64>>>;
fn check(&self) -> Self::Check;
}
impl<F, T> ConfirmationCheck for F
where
F: Fn() -> T,
T: Future<Output = error::Result<Option<U64>>>,
{
type Check = T;
fn check(&self) -> Self::Check {
(*self)()
}
}
pub async fn wait_for_confirmations<T, V, F>(
eth: Eth<T>,
eth_filter: EthFilter<T>,
poll_interval: Duration,
confirmations: usize,
check: V,
) -> error::Result<()>
where
T: Transport,
V: ConfirmationCheck<Check = F>,
F: Future<Output = error::Result<Option<U64>>>,
{
let filter = eth_filter.create_blocks_filter().await?;
let filter_stream = filter.stream(poll_interval).skip(confirmations);
futures::pin_mut!(filter_stream);
loop {
let _ = filter_stream.next().await;
if let Some(confirmation_block_number) = check.check().await? {
let block_number = eth.block_number().await?;
if confirmation_block_number.low_u64() + confirmations as u64 <= block_number.low_u64() {
return Ok(());
}
}
}
}
async fn transaction_receipt_block_number_check<T: Transport>(eth: &Eth<T>, hash: H256) -> error::Result<Option<U64>> {
let receipt = eth.transaction_receipt(hash).await?;
Ok(receipt.and_then(|receipt| receipt.block_number))
}
async fn send_transaction_with_confirmation_<T: Transport>(
hash: H256,
transport: T,
poll_interval: Duration,
confirmations: usize,
) -> error::Result<TransactionReceipt> {
let eth = Eth::new(transport.clone());
if confirmations > 0 {
let confirmation_check = || transaction_receipt_block_number_check(ð, hash);
let eth_filter = EthFilter::new(transport.clone());
let eth = eth.clone();
wait_for_confirmations(eth, eth_filter, poll_interval, confirmations, confirmation_check).await?;
}
let receipt = eth
.transaction_receipt(hash)
.await?
.expect("receipt can't be null after wait for confirmations; qed");
Ok(receipt)
}
pub async fn send_transaction_with_confirmation<T>(
transport: T,
tx: TransactionRequest,
poll_interval: Duration,
confirmations: usize,
) -> error::Result<TransactionReceipt>
where
T: Transport,
{
let hash = Eth::new(&transport).send_transaction(tx).await?;
send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations).await
}
pub async fn send_raw_transaction_with_confirmation<T>(
transport: T,
tx: Bytes,
poll_interval: Duration,
confirmations: usize,
) -> error::Result<TransactionReceipt>
where
T: Transport,
{
let hash = Eth::new(&transport).send_raw_transaction(tx).await?;
send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations).await
}
#[cfg(test)]
mod tests {
use super::send_transaction_with_confirmation;
use crate::rpc::Value;
use crate::transports::test::TestTransport;
use crate::types::{Address, TransactionReceipt, TransactionRequest, H256, U64};
use serde_json::json;
use std::time::Duration;
#[test]
fn test_send_transaction_with_confirmation() {
let mut transport = TestTransport::default();
let confirmations = 3;
let transaction_request = TransactionRequest {
from: Address::from_low_u64_be(0x123),
to: Some(Address::from_low_u64_be(0x123)),
gas: None,
gas_price: Some(1.into()),
value: Some(1.into()),
data: None,
nonce: None,
condition: None,
};
let transaction_receipt = TransactionReceipt {
transaction_hash: H256::zero(),
transaction_index: U64::zero(),
block_hash: Some(H256::zero()),
block_number: Some(2.into()),
cumulative_gas_used: 0.into(),
gas_used: Some(0.into()),
contract_address: None,
logs: vec![],
status: Some(1.into()),
root: Some(H256::zero()),
logs_bloom: Default::default(),
};
let poll_interval = Duration::from_secs(0);
transport.add_response(Value::String(
r#"0x0000000000000000000000000000000000000000000000000000000000000111"#.into(),
));
transport.add_response(Value::String("0x123".into()));
transport.add_response(Value::Array(vec![
Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000456"#.into()),
Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000457"#.into()),
]));
transport.add_response(Value::Array(vec![Value::String(
r#"0x0000000000000000000000000000000000000000000000000000000000000458"#.into(),
)]));
transport.add_response(Value::Array(vec![Value::String(
r#"0x0000000000000000000000000000000000000000000000000000000000000459"#.into(),
)]));
transport.add_response(Value::Null);
transport.add_response(Value::Array(vec![
Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000460"#.into()),
Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000461"#.into()),
]));
transport.add_response(Value::Null);
transport.add_response(json!(transaction_receipt));
transport.add_response(Value::String("0x6".into()));
transport.add_response(json!(transaction_receipt));
transport.add_response(Value::Bool(true));
let confirmation = {
let future =
send_transaction_with_confirmation(&transport, transaction_request, poll_interval, confirmations);
futures::executor::block_on(future)
};
transport.assert_request("eth_sendTransaction", &[r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#.into()]);
transport.assert_request("eth_newBlockFilter", &[]);
transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
transport.assert_request(
"eth_getTransactionReceipt",
&[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
);
transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
transport.assert_request(
"eth_getTransactionReceipt",
&[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
);
transport.assert_request(
"eth_getTransactionReceipt",
&[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
);
transport.assert_request("eth_blockNumber", &[]);
transport.assert_request(
"eth_getTransactionReceipt",
&[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
);
transport.assert_no_more_requests();
assert_eq!(confirmation, Ok(transaction_receipt));
}
}