iota_graphql_rpc/
mutation.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use async_graphql::*;
6use fastcrypto::{
7    encoding::{Base64, Encoding},
8    traits::ToFromBytes,
9};
10use iota_json_rpc_types::IotaTransactionBlockResponseOptions;
11use iota_sdk::IotaClient;
12use iota_types::{
13    effects::TransactionEffects as NativeTransactionEffects,
14    event::Event as NativeEvent,
15    quorum_driver_types::ExecuteTransactionRequestType,
16    signature::GenericSignature,
17    transaction::{SenderSignedData, Transaction},
18};
19
20use crate::{
21    error::Error,
22    types::{
23        execution_result::ExecutionResult,
24        transaction_block_effects::{TransactionBlockEffects, TransactionBlockEffectsKind},
25    },
26};
27pub struct Mutation;
28
29/// Mutations are used to write to the IOTA network.
30#[Object]
31impl Mutation {
32    /// Execute a transaction, committing its effects on chain.
33    ///
34    /// - `txBytes` is a `TransactionData` struct that has been BCS-encoded and
35    ///   then Base64-encoded.
36    /// - `signatures` are a list of `flag || signature || pubkey` bytes,
37    ///   Base64-encoded.
38    ///
39    /// Waits until the transaction has reached finality on chain to return its
40    /// transaction digest, or returns the error that prevented finality if
41    /// that was not possible. A transaction is final when its effects are
42    /// guaranteed on chain (it cannot be revoked).
43    ///
44    /// There may be a delay between transaction finality and when GraphQL
45    /// requests (including the request that issued the transaction) reflect
46    /// its effects. As a result, queries that depend on indexing the state
47    /// of the chain (e.g. contents of output objects, address-level balance
48    /// information at the time of the transaction), must wait for indexing to
49    /// catch up by polling for the transaction digest using
50    /// `Query.transactionBlock`.
51    async fn execute_transaction_block(
52        &self,
53        ctx: &Context<'_>,
54        tx_bytes: String,
55        signatures: Vec<String>,
56    ) -> Result<ExecutionResult> {
57        let iota_sdk_client: &Option<IotaClient> = ctx
58            .data()
59            .map_err(|_| Error::Internal("Unable to fetch IOTA SDK client".to_string()))
60            .extend()?;
61        let iota_sdk_client = iota_sdk_client
62            .as_ref()
63            .ok_or_else(|| Error::Internal("IOTA SDK client not initialized".to_string()))
64            .extend()?;
65        let tx_data = bcs::from_bytes(
66            &Base64::decode(&tx_bytes)
67                .map_err(|e| {
68                    Error::Client(format!(
69                        "Unable to deserialize transaction bytes from Base64: {e}"
70                    ))
71                })
72                .extend()?,
73        )
74        .map_err(|e| {
75            Error::Client(format!(
76                "Unable to deserialize transaction bytes as BCS: {e}"
77            ))
78        })
79        .extend()?;
80
81        let mut sigs = Vec::new();
82        for sig in signatures {
83            sigs.push(
84                GenericSignature::from_bytes(
85                    &Base64::decode(&sig)
86                        .map_err(|e| {
87                            Error::Client(format!(
88                                "Unable to deserialize signature bytes {sig} from Base64: {e}"
89                            ))
90                        })
91                        .extend()?,
92                )
93                .map_err(|e| Error::Client(format!("Unable to create signature from bytes: {e}")))
94                .extend()?,
95            );
96        }
97        let transaction = Transaction::from_generic_sig_data(tx_data, sigs);
98        let options = IotaTransactionBlockResponseOptions::new()
99            .with_events()
100            .with_raw_input()
101            .with_raw_effects();
102
103        let result = iota_sdk_client
104            .quorum_driver_api()
105            .execute_transaction_block(
106                transaction,
107                options,
108                Some(ExecuteTransactionRequestType::WaitForEffectsCert),
109            )
110            .await
111            // TODO: use proper error type as this could be a client error or internal error
112            // depending on the specific error returned
113            .map_err(|e| Error::Internal(format!("Unable to execute transaction: {e}")))
114            .extend()?;
115
116        let native: NativeTransactionEffects = bcs::from_bytes(&result.raw_effects)
117            .map_err(|e| Error::Internal(format!("Unable to deserialize transaction effects: {e}")))
118            .extend()?;
119        let tx_data: SenderSignedData = bcs::from_bytes(&result.raw_transaction)
120            .map_err(|e| Error::Internal(format!("Unable to deserialize transaction data: {e}")))
121            .extend()?;
122
123        let events = result
124            .events
125            .ok_or_else(|| {
126                Error::Internal("No events are returned from transaction execution".to_string())
127            })?
128            .data
129            .into_iter()
130            .map(|e| NativeEvent {
131                package_id: e.package_id,
132                transaction_module: e.transaction_module,
133                sender: e.sender,
134                type_: e.type_,
135                contents: e.bcs.into_bytes(),
136            })
137            .collect();
138
139        Ok(ExecutionResult {
140            errors: if result.errors.is_empty() {
141                None
142            } else {
143                Some(result.errors)
144            },
145            effects: TransactionBlockEffects {
146                kind: TransactionBlockEffectsKind::Executed {
147                    tx_data,
148                    native,
149                    events,
150                },
151                // set to u64::MAX, as the executed transaction has not been indexed yet
152                checkpoint_viewed_at: u64::MAX,
153            },
154        })
155    }
156}