1use crate::{
4 api::{Eth, EthFilter, Namespace},
5 error,
6 types::{Bytes, TransactionReceipt, TransactionRequest, H256, U64},
7 Transport,
8};
9use futures::{Future, StreamExt};
10use std::time::Duration;
11
12pub trait ConfirmationCheck {
14 type Check: Future<Output = error::Result<Option<U64>>>;
16
17 fn check(&self) -> Self::Check;
19}
20
21impl<F, T> ConfirmationCheck for F
22where
23 F: Fn() -> T,
24 T: Future<Output = error::Result<Option<U64>>>,
25{
26 type Check = T;
27
28 fn check(&self) -> Self::Check {
29 (*self)()
30 }
31}
32
33pub async fn wait_for_confirmations<T, V, F>(
35 eth: Eth<T>,
36 eth_filter: EthFilter<T>,
37 poll_interval: Duration,
38 confirmations: usize,
39 check: V,
40) -> error::Result<()>
41where
42 T: Transport,
43 V: ConfirmationCheck<Check = F>,
44 F: Future<Output = error::Result<Option<U64>>>,
45{
46 let filter = eth_filter.create_blocks_filter().await?;
47 let filter_stream = filter.stream(poll_interval).skip(confirmations);
54 futures::pin_mut!(filter_stream);
55 loop {
56 let _ = filter_stream.next().await;
57 if let Some(confirmation_block_number) = check.check().await? {
58 let block_number = eth.block_number().await?;
59 if confirmation_block_number.low_u64() + confirmations as u64 <= block_number.low_u64() {
60 return Ok(());
61 }
62 }
63 }
64}
65
66async fn transaction_receipt_block_number_check<T: Transport>(eth: &Eth<T>, hash: H256) -> error::Result<Option<U64>> {
67 let receipt = eth.transaction_receipt(hash).await?;
68 Ok(receipt.and_then(|receipt| receipt.block_number))
69}
70
71async fn send_transaction_with_confirmation_<T: Transport>(
72 hash: H256,
73 transport: T,
74 poll_interval: Duration,
75 confirmations: usize,
76) -> error::Result<TransactionReceipt> {
77 let eth = Eth::new(transport.clone());
78 if confirmations > 0 {
79 let confirmation_check = || transaction_receipt_block_number_check(ð, hash);
80 let eth_filter = EthFilter::new(transport.clone());
81 let eth = eth.clone();
82 wait_for_confirmations(eth, eth_filter, poll_interval, confirmations, confirmation_check).await?;
83 }
84 let receipt = eth
86 .transaction_receipt(hash)
87 .await?
88 .expect("receipt can't be null after wait for confirmations; qed");
89 Ok(receipt)
90}
91
92pub async fn send_transaction_with_confirmation<T>(
94 transport: T,
95 tx: TransactionRequest,
96 poll_interval: Duration,
97 confirmations: usize,
98) -> error::Result<TransactionReceipt>
99where
100 T: Transport,
101{
102 let hash = Eth::new(&transport).send_transaction(tx).await?;
103 send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations).await
104}
105
106pub async fn send_raw_transaction_with_confirmation<T>(
108 transport: T,
109 tx: Bytes,
110 poll_interval: Duration,
111 confirmations: usize,
112) -> error::Result<TransactionReceipt>
113where
114 T: Transport,
115{
116 let hash = Eth::new(&transport).send_raw_transaction(tx).await?;
117 send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations).await
118}
119
120#[cfg(test)]
121mod tests {
122 use super::send_transaction_with_confirmation;
123 use crate::{
124 rpc::Value,
125 transports::test::TestTransport,
126 types::{Address, TransactionReceipt, TransactionRequest, H256, U64},
127 };
128 use serde_json::json;
129 use std::time::Duration;
130
131 #[test]
132 fn test_send_transaction_with_confirmation() {
133 let mut transport = TestTransport::default();
134 let confirmations = 3;
135 let transaction_request = TransactionRequest {
136 from: Address::from_low_u64_be(0x123),
137 to: Some(Address::from_low_u64_be(0x123)),
138 gas: None,
139 gas_price: Some(1.into()),
140 value: Some(1.into()),
141 data: None,
142 nonce: None,
143 condition: None,
144 transaction_type: None,
145 access_list: None,
146 max_fee_per_gas: None,
147 max_priority_fee_per_gas: None,
148 };
149
150 let transaction_receipt = TransactionReceipt {
151 transaction_hash: H256::zero(),
152 transaction_index: U64::zero(),
153 block_hash: Some(H256::zero()),
154 block_number: Some(2.into()),
155 from: Address::from_low_u64_be(0x123),
156 to: Some(Address::from_low_u64_be(0x123)),
157 cumulative_gas_used: 0.into(),
158 gas_used: Some(0.into()),
159 contract_address: None,
160 logs: vec![],
161 status: Some(1.into()),
162 root: Some(H256::zero()),
163 logs_bloom: Default::default(),
164 transaction_type: None,
165 effective_gas_price: Default::default(),
166 };
167
168 let poll_interval = Duration::from_secs(0);
169 transport.add_response(Value::String(
170 r#"0x0000000000000000000000000000000000000000000000000000000000000111"#.into(),
171 ));
172 transport.add_response(Value::String("0x123".into()));
173 transport.add_response(Value::Array(vec![
174 Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000456"#.into()),
175 Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000457"#.into()),
176 ]));
177 transport.add_response(Value::Array(vec![Value::String(
178 r#"0x0000000000000000000000000000000000000000000000000000000000000458"#.into(),
179 )]));
180 transport.add_response(Value::Array(vec![Value::String(
181 r#"0x0000000000000000000000000000000000000000000000000000000000000459"#.into(),
182 )]));
183 transport.add_response(Value::Null);
184 transport.add_response(Value::Array(vec![
185 Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000460"#.into()),
186 Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000461"#.into()),
187 ]));
188 transport.add_response(Value::Null);
189 transport.add_response(json!(transaction_receipt));
190 transport.add_response(Value::String("0x6".into()));
191 transport.add_response(json!(transaction_receipt));
192 transport.add_response(Value::Bool(true));
193
194 let confirmation = {
195 let future =
196 send_transaction_with_confirmation(&transport, transaction_request, poll_interval, confirmations);
197 futures::executor::block_on(future)
198 };
199
200 transport.assert_request("eth_sendTransaction", &[r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#.into()]);
201 transport.assert_request("eth_newBlockFilter", &[]);
202 transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
203 transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
204 transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
205 transport.assert_request(
206 "eth_getTransactionReceipt",
207 &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
208 );
209 transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
210 transport.assert_request(
211 "eth_getTransactionReceipt",
212 &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
213 );
214 transport.assert_request(
215 "eth_getTransactionReceipt",
216 &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
217 );
218 transport.assert_request("eth_blockNumber", &[]);
219 transport.assert_request(
220 "eth_getTransactionReceipt",
221 &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
222 );
223 transport.assert_no_more_requests();
224 assert_eq!(confirmation, Ok(transaction_receipt));
225 }
226}