ic_web3_rs/
confirm.rs

1//! Easy to use utilities for confirmations.
2
3use crate::{
4    api::{Eth, Namespace},
5    error,
6    transports::ic_http_client::CallOptions,
7    types::{Bytes, TransactionReceipt, TransactionRequest, H256, U64},
8    Transport,
9};
10use futures::{Future, StreamExt};
11use std::time::Duration;
12
13/// Checks whether an event has been confirmed.
14pub trait ConfirmationCheck {
15    /// Future resolved when is known whether an event has been confirmed.
16    type Check: Future<Output = error::Result<Option<U64>>>;
17
18    /// Should be called to get future which resolves when confirmation state is known.
19    fn check(&self) -> Self::Check;
20}
21
22impl<F, T> ConfirmationCheck for F
23where
24    F: Fn() -> T,
25    T: Future<Output = error::Result<Option<U64>>>,
26{
27    type Check = T;
28
29    fn check(&self) -> Self::Check {
30        (*self)()
31    }
32}
33
34///// Should be used to wait for confirmations
35//pub async fn wait_for_confirmations<T, V, F>(
36//    eth: Eth<T>,
37//    eth_filter: EthFilter<T>,
38//    poll_interval: Duration,
39//    confirmations: usize,
40//    check: V,
41//    options: CallOptions,
42//) -> error::Result<()>
43//where
44//    T: Transport,
45//    V: ConfirmationCheck<Check = F>,
46//    F: Future<Output = error::Result<Option<U64>>>,
47//{
48//    let filter = eth_filter.create_blocks_filter().await?;
49//    // TODO #396: The stream should have additional checks.
50//    // * We should not continue calling next on a stream that has completed (has returned None). We expect this to never
51//    //   happen for the blocks filter but to be safe we should handle this case for example by `fuse`ing the stream or
52//    //   erroring when it does complete.
53//    // * We do not handle the case where the stream returns an error which means we are wrongly counting it as a
54//    //   confirmation.
55//    let filter_stream = filter.stream(poll_interval).skip(confirmations);
56//    futures::pin_mut!(filter_stream);
57//    loop {
58//        let _ = filter_stream.next().await;
59//        if let Some(confirmation_block_number) = check.check().await? {
60//            let block_number = eth.block_number(options).await?;
61//            if confirmation_block_number.low_u64() + confirmations as u64 <= block_number.low_u64() {
62//                return Ok(());
63//            }
64//        }
65//    }
66//}
67
68async fn transaction_receipt_block_number_check<T: Transport>(
69    eth: &Eth<T>,
70    hash: H256,
71    options: CallOptions,
72) -> error::Result<Option<U64>> {
73    let receipt = eth.transaction_receipt(hash, options).await?;
74    Ok(receipt.and_then(|receipt| receipt.block_number))
75}
76
77async fn send_transaction_with_confirmation_<T: Transport>(
78    hash: H256,
79    transport: T,
80    poll_interval: Duration,
81    confirmations: usize,
82    options: CallOptions,
83) -> error::Result<TransactionReceipt> {
84    let eth = Eth::new(transport.clone());
85    // TODO
86    //if confirmations > 0 {
87    //    let confirmation_check = || transaction_receipt_block_number_check(&eth, hash, options);
88    //    let eth_filter = EthFilter::new(transport.clone());
89    //    let eth = eth.clone();
90    //    wait_for_confirmations(
91    //        eth,
92    //        eth_filter,
93    //        poll_interval,
94    //        confirmations,
95    //        confirmation_check,
96    //        options,
97    //    )
98    //    .await?;
99    //}
100    // TODO #397: We should remove this `expect`. No matter what happens inside the node, this shouldn't be a panic.
101    let receipt = eth
102        .transaction_receipt(hash, options)
103        .await?
104        .expect("receipt can't be null after wait for confirmations; qed");
105    Ok(receipt)
106}
107
108/// Sends transaction and returns future resolved after transaction is confirmed
109pub async fn send_transaction_with_confirmation<T>(
110    transport: T,
111    tx: TransactionRequest,
112    poll_interval: Duration,
113    confirmations: usize,
114    options: CallOptions,
115) -> error::Result<TransactionReceipt>
116where
117    T: Transport,
118{
119    let hash = Eth::new(&transport).send_transaction(tx, options.clone()).await?;
120    send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations, options).await
121}
122
123/// Sends raw transaction and returns future resolved after transaction is confirmed
124pub async fn send_raw_transaction_with_confirmation<T>(
125    transport: T,
126    tx: Bytes,
127    poll_interval: Duration,
128    confirmations: usize,
129    options: CallOptions,
130) -> error::Result<TransactionReceipt>
131where
132    T: Transport,
133{
134    let hash = Eth::new(&transport).send_raw_transaction(tx, options.clone()).await?;
135    send_transaction_with_confirmation_(hash, transport, poll_interval, confirmations, options).await
136}
137
138pub async fn get_transaction_receipt<T: Transport>(
139    transport: T,
140    hash: H256,
141    options: CallOptions,
142) -> error::Result<Option<TransactionReceipt>> {
143    Eth::new(&transport).transaction_receipt(hash, options).await
144}
145
146#[cfg(test)]
147mod tests {
148    use super::send_transaction_with_confirmation;
149    use crate::{
150        rpc::Value,
151        transports::{ic_http_client::CallOptions, test::TestTransport},
152        types::{Address, TransactionReceipt, TransactionRequest, H256, U64},
153    };
154    use serde_json::json;
155    use std::time::Duration;
156
157    #[test]
158    fn test_send_transaction_with_confirmation() {
159        let mut transport = TestTransport::default();
160        let confirmations = 3;
161        let transaction_request = TransactionRequest {
162            from: Address::from_low_u64_be(0x123),
163            to: Some(Address::from_low_u64_be(0x123)),
164            gas: None,
165            gas_price: Some(1.into()),
166            value: Some(1.into()),
167            data: None,
168            nonce: None,
169            condition: None,
170            transaction_type: None,
171            access_list: None,
172            max_fee_per_gas: None,
173            max_priority_fee_per_gas: None,
174        };
175
176        let transaction_receipt = TransactionReceipt {
177            transaction_hash: H256::zero(),
178            transaction_index: U64::zero(),
179            block_hash: Some(H256::zero()),
180            block_number: Some(2.into()),
181            from: Address::from_low_u64_be(0x123),
182            to: Some(Address::from_low_u64_be(0x123)),
183            cumulative_gas_used: 0.into(),
184            gas_used: Some(0.into()),
185            contract_address: None,
186            logs: vec![],
187            status: Some(1.into()),
188            root: Some(H256::zero()),
189            logs_bloom: Default::default(),
190            transaction_type: None,
191            effective_gas_price: Default::default(),
192        };
193
194        let poll_interval = Duration::from_secs(0);
195        transport.add_response(Value::String(
196            r#"0x0000000000000000000000000000000000000000000000000000000000000111"#.into(),
197        ));
198        transport.add_response(Value::String("0x123".into()));
199        transport.add_response(Value::Array(vec![
200            Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000456"#.into()),
201            Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000457"#.into()),
202        ]));
203        transport.add_response(Value::Array(vec![Value::String(
204            r#"0x0000000000000000000000000000000000000000000000000000000000000458"#.into(),
205        )]));
206        transport.add_response(Value::Array(vec![Value::String(
207            r#"0x0000000000000000000000000000000000000000000000000000000000000459"#.into(),
208        )]));
209        transport.add_response(Value::Null);
210        transport.add_response(Value::Array(vec![
211            Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000460"#.into()),
212            Value::String(r#"0x0000000000000000000000000000000000000000000000000000000000000461"#.into()),
213        ]));
214        transport.add_response(Value::Null);
215        transport.add_response(json!(transaction_receipt));
216        transport.add_response(Value::String("0x6".into()));
217        transport.add_response(json!(transaction_receipt));
218        transport.add_response(Value::Bool(true));
219
220        let confirmation = {
221            let future = send_transaction_with_confirmation(
222                &transport,
223                transaction_request,
224                poll_interval,
225                confirmations,
226                CallOptions::default(),
227            );
228            futures::executor::block_on(future)
229        };
230
231        transport.assert_request("eth_sendTransaction", &[r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#.into()]);
232        //transport.assert_request("eth_newBlockFilter", &[]);
233        //transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
234        //transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
235        //transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
236
237        transport.assert_request(
238            "eth_getTransactionReceipt",
239            &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
240        );
241        //transport.assert_request("eth_getFilterChanges", &[r#""0x123""#.into()]);
242        //transport.assert_request(
243        //    "eth_getTransactionReceipt",
244        //    &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
245        //);
246        //transport.assert_request(
247        //    "eth_getTransactionReceipt",
248        //    &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
249        //);
250        //transport.assert_request("eth_blockNumber", &[]);
251        //transport.assert_request(
252        //    "eth_getTransactionReceipt",
253        //    &[r#""0x0000000000000000000000000000000000000000000000000000000000000111""#.into()],
254        //);
255        transport.assert_no_more_requests();
256        //assert_eq!(confirmation, Ok(transaction_receipt));
257    }
258}