iota_sdk/apis/
quorum_driver.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    sync::Arc,
7    time::{Duration, Instant},
8};
9
10use iota_json_rpc_api::{ReadApiClient, WriteApiClient};
11use iota_json_rpc_types::{IotaTransactionBlockResponse, IotaTransactionBlockResponseOptions};
12use iota_types::{quorum_driver_types::ExecuteTransactionRequestType, transaction::Transaction};
13
14use crate::{
15    RpcClient,
16    error::{Error, IotaRpcResult},
17};
18
19const WAIT_FOR_LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(60);
20const WAIT_FOR_LOCAL_EXECUTION_DELAY: Duration = Duration::from_millis(200);
21const WAIT_FOR_LOCAL_EXECUTION_INTERVAL: Duration = Duration::from_secs(2);
22
23/// Defines methods to execute transaction blocks and submit them to fullnodes.
24#[derive(Clone)]
25pub struct QuorumDriverApi {
26    api: Arc<RpcClient>,
27}
28
29impl QuorumDriverApi {
30    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
31        Self { api }
32    }
33
34    /// Execute a transaction with a FullNode client.
35    ///
36    /// The request type defaults to
37    /// [`ExecuteTransactionRequestType::WaitForLocalExecution`].
38    ///
39    /// When `WaitForLocalExecution` is used, but the returned
40    /// `confirmed_local_execution` is false, the client will wait for some time
41    /// before returning [Error::FailToConfirmTransactionStatus].
42    pub async fn execute_transaction_block(
43        &self,
44        tx: Transaction,
45        options: IotaTransactionBlockResponseOptions,
46        request_type: impl Into<Option<ExecuteTransactionRequestType>>,
47    ) -> IotaRpcResult<IotaTransactionBlockResponse> {
48        let (tx_bytes, signatures) = tx.to_tx_bytes_and_signatures();
49        let request_type = request_type
50            .into()
51            .unwrap_or_else(|| options.default_execution_request_type());
52
53        let start = Instant::now();
54        let response = self
55            .api
56            .http
57            .execute_transaction_block(
58                tx_bytes.clone(),
59                signatures.clone(),
60                Some(options.clone()),
61                // Ignore the request type as we emulate WaitForLocalExecution below.
62                // It will default to WaitForEffectsCert on the RPC nodes.
63                None,
64            )
65            .await?;
66
67        if let ExecuteTransactionRequestType::WaitForEffectsCert = request_type {
68            return Ok(response);
69        }
70
71        // JSON-RPC ignores WaitForLocalExecution, so simulate it by polling for the
72        // transaction.
73        let mut poll_response = tokio::time::timeout(WAIT_FOR_LOCAL_EXECUTION_TIMEOUT, async {
74            // Apply a short delay to give the full node a chance to catch up.
75            tokio::time::sleep(WAIT_FOR_LOCAL_EXECUTION_DELAY).await;
76
77            let mut interval = tokio::time::interval(WAIT_FOR_LOCAL_EXECUTION_INTERVAL);
78            loop {
79                interval.tick().await;
80
81                if let Ok(poll_response) = self
82                    .api
83                    .http
84                    .get_transaction_block(*tx.digest(), Some(options.clone()))
85                    .await
86                {
87                    break poll_response;
88                }
89            }
90        })
91        .await
92        .map_err(|_| {
93            Error::FailToConfirmTransactionStatus(*tx.digest(), start.elapsed().as_secs())
94        })?;
95
96        poll_response.confirmed_local_execution = Some(true);
97        Ok(poll_response)
98    }
99}