Skip to main content

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::{
13    quorum_driver_types::ExecuteTransactionRequestType, transaction::TransactionEnvelope,
14};
15
16use crate::{
17    RpcClient,
18    error::{Error, IotaRpcResult},
19};
20
21const WAIT_FOR_LOCAL_EXECUTION_MIN_INTERVAL: Duration = Duration::from_millis(100);
22const WAIT_FOR_LOCAL_EXECUTION_MAX_INTERVAL: Duration = Duration::from_secs(2);
23
24/// Defines methods to execute transaction blocks and submit them to fullnodes.
25#[derive(Clone)]
26pub struct QuorumDriverApi {
27    api: Arc<RpcClient>,
28}
29
30impl QuorumDriverApi {
31    pub(crate) fn new(api: Arc<RpcClient>) -> Self {
32        Self { api }
33    }
34
35    /// Execute a transaction with a FullNode client.
36    ///
37    /// The request type defaults to
38    /// [`ExecuteTransactionRequestType::WaitForLocalExecution`].
39    ///
40    /// When `WaitForLocalExecution` is used, but the returned
41    /// `confirmed_local_execution` is false, the client will wait for some time
42    /// before returning [Error::FailToConfirmTransactionStatus].
43    pub async fn execute_transaction_block(
44        &self,
45        tx: TransactionEnvelope,
46        options: IotaTransactionBlockResponseOptions,
47        request_type: impl Into<Option<ExecuteTransactionRequestType>>,
48    ) -> IotaRpcResult<IotaTransactionBlockResponse> {
49        let (tx_bytes, signatures) = tx.to_tx_bytes_and_signatures();
50        let request_type = request_type
51            .into()
52            .unwrap_or_else(|| options.default_execution_request_type());
53
54        let start = Instant::now();
55        let response = self
56            .api
57            .http
58            .execute_transaction_block(
59                tx_bytes.clone(),
60                signatures.clone(),
61                Some(options.clone()),
62                // Ignore the request type as we emulate WaitForLocalExecution below.
63                // It will default to WaitForEffectsCert on the RPC nodes.
64                None,
65            )
66            .await?;
67
68        if let ExecuteTransactionRequestType::WaitForEffectsCert = request_type {
69            return Ok(response);
70        }
71
72        // JSON-RPC ignores WaitForLocalExecution, so simulate it by polling for the
73        // transaction.
74        let wait_for_local_execution_timeout: Duration = if cfg!(msim) {
75            // In simtests, fullnodes can stop receiving checkpoints for > 30s.
76            Duration::from_secs(120)
77        } else {
78            Duration::from_secs(60)
79        };
80        let mut poll_response = tokio::time::timeout(wait_for_local_execution_timeout, async {
81            let mut backoff = iota_common::backoff::ExponentialBackoff::new(
82                WAIT_FOR_LOCAL_EXECUTION_MIN_INTERVAL,
83                WAIT_FOR_LOCAL_EXECUTION_MAX_INTERVAL,
84            );
85            loop {
86                // Intentionally waiting for a short delay (MIN_INTERVAL) before the 1st
87                // iteration, to leave time for the checkpoint containing the
88                // transaction to be certified, propagate to the full node, and
89                // get executed.
90                tokio::time::sleep(backoff.next().unwrap()).await;
91
92                if let Ok(poll_response) = self
93                    .api
94                    .http
95                    .get_transaction_block(*tx.digest(), Some(options.clone()))
96                    .await
97                {
98                    break poll_response;
99                }
100            }
101        })
102        .await
103        .map_err(|_| {
104            Error::FailToConfirmTransactionStatus(*tx.digest(), start.elapsed().as_secs())
105        })?;
106
107        poll_response.confirmed_local_execution = Some(true);
108        Ok(poll_response)
109    }
110}