iota_graphql_rpc/types/
query.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::str::FromStr;
6
7use async_graphql::{connection::Connection, *};
8use fastcrypto::encoding::{Base64, Encoding};
9use iota_json_rpc_types::DevInspectArgs;
10use iota_sdk::IotaClient;
11use iota_types::{
12    TypeTag,
13    gas_coin::GAS,
14    transaction::{TransactionData, TransactionDataAPI, TransactionKind},
15};
16use move_core_types::account_address::AccountAddress;
17use serde::de::DeserializeOwned;
18
19use crate::{
20    config::ServiceConfig,
21    connection::ScanConnection,
22    error::Error,
23    mutation::Mutation,
24    server::watermark_task::Watermark,
25    types::{
26        address::Address,
27        available_range::AvailableRange,
28        base64::Base64 as GraphQLBase64,
29        chain_identifier::ChainIdentifier,
30        checkpoint::{self, Checkpoint, CheckpointId},
31        coin::Coin,
32        coin_metadata::CoinMetadata,
33        cursor::Page,
34        digest::Digest,
35        dry_run_result::DryRunResult,
36        epoch::Epoch,
37        event::{self, Event, EventFilter},
38        iota_address::IotaAddress,
39        iota_names_registration::{Domain, IotaNames},
40        move_package::{self, MovePackage, MovePackageCheckpointFilter, MovePackageVersionFilter},
41        move_type::MoveType,
42        object::{self, Object, ObjectFilter},
43        owner::Owner,
44        protocol_config::ProtocolConfigs,
45        transaction_block::{self, TransactionBlock, TransactionBlockFilter},
46        transaction_metadata::TransactionMetadata,
47        type_filter::ExactTypeFilter,
48        uint53::UInt53,
49        zklogin_verify_signature::{
50            ZkLoginIntentScope, ZkLoginVerifyResult, verify_zklogin_signature,
51        },
52    },
53};
54
55pub(crate) struct Query;
56pub(crate) type IotaGraphQLSchema = async_graphql::Schema<Query, Mutation, EmptySubscription>;
57
58#[Object]
59impl Query {
60    /// First four bytes of the network's genesis checkpoint digest (uniquely
61    /// identifies the network).
62    async fn chain_identifier(&self, ctx: &Context<'_>) -> Result<String> {
63        Ok(ChainIdentifier::query(ctx.data_unchecked())
64            .await
65            .extend()?
66            .to_string())
67    }
68
69    /// Range of checkpoints that the RPC has data available for (for data
70    /// that can be tied to a particular checkpoint).
71    async fn available_range(&self, ctx: &Context<'_>) -> Result<AvailableRange> {
72        let Watermark { checkpoint, .. } = *ctx.data()?;
73        AvailableRange::query(ctx.data_unchecked(), checkpoint)
74            .await
75            .extend()
76    }
77
78    /// Configuration for this RPC service
79    async fn service_config(&self, ctx: &Context<'_>) -> Result<ServiceConfig> {
80        ctx.data()
81            .map_err(|_| Error::Internal("Unable to fetch service configuration.".to_string()))
82            .cloned()
83            .extend()
84    }
85
86    /// Simulate running a transaction to inspect its effects without
87    /// committing to them on-chain.
88    ///
89    /// `txBytes` either a `TransactionData` struct or a `TransactionKind`
90    ///     struct, BCS-encoded and then Base64-encoded.  The expected
91    ///     type is controlled by the presence or absence of `txMeta`: If
92    ///     present, `txBytes` is assumed to be a `TransactionKind`, if
93    ///     absent, then `TransactionData`.
94    ///
95    /// `txMeta` the data that is missing from a `TransactionKind` to make
96    ///     a `TransactionData` (sender address and gas information).  All
97    ///     its fields are nullable.
98    ///
99    /// `skipChecks` optional flag to disable the usual verification
100    ///     checks that prevent access to objects that are owned by
101    ///     addresses other than the sender, and calling non-public,
102    ///     non-entry functions, and some other checks.  Defaults to false.
103    async fn dry_run_transaction_block(
104        &self,
105        ctx: &Context<'_>,
106        tx_bytes: String,
107        tx_meta: Option<TransactionMetadata>,
108        skip_checks: Option<bool>,
109    ) -> Result<DryRunResult> {
110        let skip_checks = skip_checks.unwrap_or(false);
111
112        let iota_sdk_client: &Option<IotaClient> = ctx
113            .data()
114            .map_err(|_| Error::Internal("Unable to fetch IOTA SDK client".to_string()))
115            .extend()?;
116        let iota_sdk_client = iota_sdk_client
117            .as_ref()
118            .ok_or_else(|| Error::Internal("IOTA SDK client not initialized".to_string()))
119            .extend()?;
120
121        let (sender_address, tx_kind, gas_price, gas_sponsor, gas_budget, gas_objects) =
122            if let Some(TransactionMetadata {
123                sender,
124                gas_price,
125                gas_objects,
126                gas_budget,
127                gas_sponsor,
128            }) = tx_meta
129            {
130                // This implies `TransactionKind`
131                let tx_kind = deserialize_tx_data::<TransactionKind>(&tx_bytes)?;
132
133                // Default is 0x0
134                let sender_address = sender.unwrap_or_else(|| AccountAddress::ZERO.into()).into();
135
136                let gas_sponsor = gas_sponsor.map(|addr| addr.into());
137
138                let gas_objects = gas_objects.map(|objs| {
139                    objs.into_iter()
140                        .map(|obj| (obj.address.into(), obj.version.into(), obj.digest.into()))
141                        .collect()
142                });
143
144                (
145                    sender_address,
146                    tx_kind,
147                    gas_price.map(|p| p.into()),
148                    gas_sponsor,
149                    gas_budget.map(|b| b.into()),
150                    gas_objects,
151                )
152            } else {
153                // This implies `TransactionData`
154                let tx_data = deserialize_tx_data::<TransactionData>(&tx_bytes)?;
155
156                (
157                    tx_data.sender(),
158                    tx_data.clone().into_kind(),
159                    Some(tx_data.gas_price().into()),
160                    Some(tx_data.gas_owner()),
161                    Some(tx_data.gas_budget().into()),
162                    Some(tx_data.gas().to_vec()),
163                )
164            };
165
166        let dev_inspect_args = DevInspectArgs {
167            gas_sponsor,
168            gas_budget,
169            gas_objects,
170            show_raw_txn_data_and_effects: Some(true),
171            skip_checks: Some(skip_checks),
172        };
173
174        let res = iota_sdk_client
175            .read_api()
176            .dev_inspect_transaction_block(
177                sender_address,
178                tx_kind,
179                gas_price,
180                None,
181                Some(dev_inspect_args),
182            )
183            .await?;
184
185        DryRunResult::try_from(res).extend()
186    }
187
188    /// Look up an Owner by its IotaAddress.
189    ///
190    /// `rootVersion` represents the version of the root object in some nested
191    /// chain of dynamic fields. It allows consistent historical queries for
192    /// the case of wrapped objects, which don't have a version. For
193    /// example, if querying the dynamic field of a table wrapped in a parent
194    /// object, passing the parent object's version here will ensure we get the
195    /// dynamic field's state at the moment that parent's version was
196    /// created.
197    ///
198    /// Also, if this Owner is an object itself, `rootVersion` will be used to
199    /// bound its version from above when querying `Owner.asObject`. This
200    /// can be used, for example, to get the contents of a dynamic object
201    /// field when its parent was at `rootVersion`.
202    ///
203    /// If `rootVersion` is omitted, dynamic fields will be from a consistent
204    /// snapshot of the IOTA state at the latest checkpoint known to the
205    /// GraphQL RPC. Similarly, `Owner.asObject` will return the object's
206    /// version at the latest checkpoint.
207    async fn owner(
208        &self,
209        ctx: &Context<'_>,
210        address: IotaAddress,
211        root_version: Option<UInt53>,
212    ) -> Result<Option<Owner>> {
213        let Watermark { checkpoint, .. } = *ctx.data()?;
214        Ok(Some(Owner {
215            address,
216            checkpoint_viewed_at: checkpoint,
217            root_version: root_version.map(|v| v.into()),
218        }))
219    }
220
221    /// The object corresponding to the given address at the (optionally) given
222    /// version. When no version is given, the latest version is returned.
223    async fn object(
224        &self,
225        ctx: &Context<'_>,
226        address: IotaAddress,
227        version: Option<UInt53>,
228    ) -> Result<Option<Object>> {
229        let Watermark { checkpoint, .. } = *ctx.data()?;
230        let key = match version {
231            Some(version) => Object::at_version(version.into(), checkpoint),
232            None => Object::latest_at(checkpoint),
233        };
234
235        Object::query(ctx, address, key).await.extend()
236    }
237
238    /// The package corresponding to the given address (at the optionally given
239    /// version).
240    ///
241    /// When no version is given, the package is loaded directly from the
242    /// address given. Otherwise, the address is translated before loading
243    /// to point to the package whose original ID matches the package at
244    /// `address`, but whose version is `version`. For non-system packages, this
245    /// might result in a different address than `address` because different
246    /// versions of a package, introduced by upgrades, exist at distinct
247    /// addresses.
248    ///
249    /// Note that this interpretation of `version` is different from a
250    /// historical object read (the interpretation of `version` for the
251    /// `object` query).
252    async fn package(
253        &self,
254        ctx: &Context<'_>,
255        address: IotaAddress,
256        version: Option<UInt53>,
257    ) -> Result<Option<MovePackage>> {
258        let Watermark { checkpoint, .. } = *ctx.data()?;
259        let key = match version {
260            Some(version) => MovePackage::by_version(version.into(), checkpoint),
261            None => MovePackage::by_id_at(checkpoint),
262        };
263
264        MovePackage::query(ctx, address, key).await.extend()
265    }
266
267    /// The latest version of the package at `address`.
268    ///
269    /// This corresponds to the package with the highest `version` that shares
270    /// its original ID with the package at `address`.
271    async fn latest_package(
272        &self,
273        ctx: &Context<'_>,
274        address: IotaAddress,
275    ) -> Result<Option<MovePackage>> {
276        let Watermark { checkpoint, .. } = *ctx.data()?;
277        MovePackage::query(ctx, address, MovePackage::latest_at(checkpoint))
278            .await
279            .extend()
280    }
281
282    /// Look-up an Account by its IotaAddress.
283    async fn address(&self, ctx: &Context<'_>, address: IotaAddress) -> Result<Option<Address>> {
284        let Watermark { checkpoint, .. } = *ctx.data()?;
285
286        Ok(Some(Address {
287            address,
288            checkpoint_viewed_at: checkpoint,
289        }))
290    }
291
292    /// Fetch a structured representation of a concrete type, including its
293    /// layout information. Fails if the type is malformed.
294    async fn type_(&self, type_: String) -> Result<MoveType> {
295        Ok(MoveType::new(
296            TypeTag::from_str(&type_)
297                .map_err(|e| Error::Client(format!("Bad type: {e}")))
298                .extend()?,
299        ))
300    }
301
302    /// Fetch epoch information by ID (defaults to the latest epoch).
303    async fn epoch(&self, ctx: &Context<'_>, id: Option<UInt53>) -> Result<Option<Epoch>> {
304        let Watermark { checkpoint, .. } = *ctx.data()?;
305        Epoch::query(ctx, id.map(|id| id.into()), checkpoint)
306            .await
307            .extend()
308    }
309
310    /// Fetch checkpoint information by sequence number or digest (defaults to
311    /// the latest available checkpoint).
312    async fn checkpoint(
313        &self,
314        ctx: &Context<'_>,
315        id: Option<CheckpointId>,
316    ) -> Result<Option<Checkpoint>> {
317        let Watermark { checkpoint, .. } = *ctx.data()?;
318        Checkpoint::query(ctx, id.unwrap_or_default(), checkpoint)
319            .await
320            .extend()
321    }
322
323    /// Fetch a transaction block by its transaction digest.
324    async fn transaction_block(
325        &self,
326        ctx: &Context<'_>,
327        digest: Digest,
328    ) -> Result<Option<TransactionBlock>> {
329        let Watermark { checkpoint, .. } = *ctx.data()?;
330        TransactionBlock::query(ctx, digest, checkpoint)
331            .await
332            .extend()
333    }
334
335    /// The coin objects that exist in the network.
336    ///
337    /// The type field is a string of the inner type of the coin by which to
338    /// filter (e.g. `0x2::iota::IOTA`). If no type is provided, it will
339    /// default to `0x2::iota::IOTA`.
340    async fn coins(
341        &self,
342        ctx: &Context<'_>,
343        first: Option<u64>,
344        after: Option<object::Cursor>,
345        last: Option<u64>,
346        before: Option<object::Cursor>,
347        type_: Option<ExactTypeFilter>,
348    ) -> Result<Connection<String, Coin>> {
349        let Watermark { checkpoint, .. } = *ctx.data()?;
350
351        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
352        let coin = type_.map_or_else(GAS::type_tag, |t| t.0);
353        Coin::paginate(
354            ctx.data_unchecked(),
355            page,
356            coin,
357            // owner
358            None,
359            checkpoint,
360        )
361        .await
362        .extend()
363    }
364
365    /// The checkpoints that exist in the network.
366    async fn checkpoints(
367        &self,
368        ctx: &Context<'_>,
369        first: Option<u64>,
370        after: Option<checkpoint::Cursor>,
371        last: Option<u64>,
372        before: Option<checkpoint::Cursor>,
373    ) -> Result<Connection<String, Checkpoint>> {
374        let Watermark { checkpoint, .. } = *ctx.data()?;
375
376        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
377        Checkpoint::paginate(
378            ctx.data_unchecked(),
379            page,
380            // epoch
381            None,
382            checkpoint,
383        )
384        .await
385        .extend()
386    }
387
388    /// The transaction blocks that exist in the network.
389    ///
390    /// `scanLimit` restricts the number of candidate transactions scanned when
391    /// gathering a page of results. It is required for queries that apply
392    /// more than two complex filters (on function, kind, sender, recipient,
393    /// input object, changed object, or ids), and can be at most
394    /// `serviceConfig.maxScanLimit`.
395    ///
396    /// When the scan limit is reached the page will be returned even if it has
397    /// fewer than `first` results when paginating forward (`last` when
398    /// paginating backwards). If there are more transactions to scan,
399    /// `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to
400    /// `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set
401    /// to the last transaction that was scanned as opposed to the last (or
402    /// first) transaction in the page.
403    ///
404    /// Requesting the next (or previous) page after this cursor will resume the
405    /// search, scanning the next `scanLimit` many transactions in the
406    /// direction of pagination, and so on until all transactions in the
407    /// scanning range have been visited.
408    ///
409    /// By default, the scanning range includes all transactions known to
410    /// GraphQL, but it can be restricted by the `after` and `before`
411    /// cursors, and the `beforeCheckpoint`, `afterCheckpoint` and
412    /// `atCheckpoint` filters.
413    async fn transaction_blocks(
414        &self,
415        ctx: &Context<'_>,
416        first: Option<u64>,
417        after: Option<transaction_block::Cursor>,
418        last: Option<u64>,
419        before: Option<transaction_block::Cursor>,
420        filter: Option<TransactionBlockFilter>,
421        scan_limit: Option<u64>,
422    ) -> Result<ScanConnection<String, TransactionBlock>> {
423        let Watermark { checkpoint, .. } = *ctx.data()?;
424
425        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
426
427        TransactionBlock::paginate(
428            ctx,
429            page,
430            filter.unwrap_or_default(),
431            checkpoint,
432            scan_limit,
433        )
434        .await
435        .extend()
436    }
437
438    /// Query events that are emitted in the network.
439    /// We currently do not support filtering by emitting module and event type
440    /// at the same time so if both are provided in one filter, the query will
441    /// error.
442    async fn events(
443        &self,
444        ctx: &Context<'_>,
445        first: Option<u64>,
446        after: Option<event::Cursor>,
447        last: Option<u64>,
448        before: Option<event::Cursor>,
449        filter: Option<EventFilter>,
450    ) -> Result<Connection<String, Event>> {
451        let Watermark { checkpoint, .. } = *ctx.data()?;
452
453        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
454        Event::paginate(
455            ctx.data_unchecked(),
456            page,
457            filter.unwrap_or_default(),
458            checkpoint,
459        )
460        .await
461        .extend()
462    }
463
464    /// The objects that exist in the network.
465    async fn objects(
466        &self,
467        ctx: &Context<'_>,
468        first: Option<u64>,
469        after: Option<object::Cursor>,
470        last: Option<u64>,
471        before: Option<object::Cursor>,
472        filter: Option<ObjectFilter>,
473    ) -> Result<Connection<String, Object>> {
474        let Watermark { checkpoint, .. } = *ctx.data()?;
475
476        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
477        Object::paginate(
478            ctx.data_unchecked(),
479            page,
480            filter.unwrap_or_default(),
481            checkpoint,
482        )
483        .await
484        .extend()
485    }
486
487    /// The Move packages that exist in the network, optionally filtered to be
488    /// strictly before `beforeCheckpoint` and/or strictly after
489    /// `afterCheckpoint`.
490    ///
491    /// This query returns all versions of a given user package that appear
492    /// between the specified checkpoints, but only records the latest
493    /// versions of system packages.
494    async fn packages(
495        &self,
496        ctx: &Context<'_>,
497        first: Option<u64>,
498        after: Option<move_package::Cursor>,
499        last: Option<u64>,
500        before: Option<move_package::Cursor>,
501        filter: Option<MovePackageCheckpointFilter>,
502    ) -> Result<Connection<String, MovePackage>> {
503        let Watermark { checkpoint, .. } = *ctx.data()?;
504
505        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
506        MovePackage::paginate_by_checkpoint(ctx.data_unchecked(), page, filter, checkpoint)
507            .await
508            .extend()
509    }
510
511    /// Fetch all versions of package at `address` (packages that share this
512    /// package's original ID), optionally bounding the versions exclusively
513    /// from below with `afterVersion`, or from above with `beforeVersion`.
514    async fn package_versions(
515        &self,
516        ctx: &Context<'_>,
517        first: Option<u64>,
518        after: Option<move_package::Cursor>,
519        last: Option<u64>,
520        before: Option<move_package::Cursor>,
521        address: IotaAddress,
522        filter: Option<MovePackageVersionFilter>,
523    ) -> Result<Connection<String, MovePackage>> {
524        let Watermark { checkpoint, .. } = *ctx.data()?;
525
526        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
527        MovePackage::paginate_by_version(ctx.data_unchecked(), page, address, filter, checkpoint)
528            .await
529            .extend()
530    }
531
532    /// Fetch the protocol config by protocol version (defaults to the latest
533    /// protocol version known to the GraphQL service).
534    async fn protocol_config(
535        &self,
536        ctx: &Context<'_>,
537        protocol_version: Option<UInt53>,
538    ) -> Result<ProtocolConfigs> {
539        ProtocolConfigs::query(ctx.data_unchecked(), protocol_version.map(|v| v.into()))
540            .await
541            .extend()
542    }
543
544    /// Resolves an IOTA-Names `domain` name to an address, if it has been
545    /// bound.
546    async fn resolve_iota_names_address(
547        &self,
548        ctx: &Context<'_>,
549        domain: Domain,
550    ) -> Result<Option<Address>> {
551        let Watermark { checkpoint, .. } = *ctx.data()?;
552
553        Ok(IotaNames::resolve_to_record(ctx, &domain, checkpoint)
554            .await
555            .extend()?
556            .and_then(|r| r.target_address)
557            .map(|a| Address {
558                address: a.into(),
559                checkpoint_viewed_at: checkpoint,
560            }))
561    }
562
563    /// The coin metadata associated with the given coin type.
564    async fn coin_metadata(
565        &self,
566        ctx: &Context<'_>,
567        coin_type: ExactTypeFilter,
568    ) -> Result<Option<CoinMetadata>> {
569        let Watermark { checkpoint, .. } = *ctx.data()?;
570        CoinMetadata::query(ctx.data_unchecked(), coin_type.0, checkpoint)
571            .await
572            .extend()
573    }
574
575    /// Verify a zkLogin signature based on the provided transaction or personal
576    /// message based on current epoch, chain id, and latest JWKs fetched
577    /// on-chain. If the signature is valid, the function returns a
578    /// `ZkLoginVerifyResult` with success as true and an empty list of
579    /// errors. If the signature is invalid, the function returns
580    /// a `ZkLoginVerifyResult` with success as false with a list of errors.
581    ///
582    /// - `bytes` is either the personal message in raw bytes or transaction
583    ///   data bytes in BCS-encoded and then Base64-encoded.
584    /// - `signature` is a serialized zkLogin signature that is Base64-encoded.
585    /// - `intentScope` is an enum that specifies the intent scope to be used to
586    ///   parse bytes.
587    /// - `author` is the address of the signer of the transaction or personal
588    ///   msg.
589    async fn verify_zklogin_signature(
590        &self,
591        ctx: &Context<'_>,
592        bytes: GraphQLBase64,
593        signature: GraphQLBase64,
594        intent_scope: ZkLoginIntentScope,
595        author: IotaAddress,
596    ) -> Result<ZkLoginVerifyResult> {
597        verify_zklogin_signature(ctx, bytes, signature, intent_scope, author)
598            .await
599            .extend()
600    }
601}
602
603fn deserialize_tx_data<T>(tx_bytes: &str) -> Result<T>
604where
605    T: DeserializeOwned,
606{
607    bcs::from_bytes(
608        &Base64::decode(tx_bytes)
609            .map_err(|e| {
610                Error::Client(format!(
611                    "Unable to deserialize transaction bytes from Base64: {e}"
612                ))
613            })
614            .extend()?,
615    )
616    .map_err(|e| {
617        Error::Client(format!(
618            "Unable to deserialize transaction bytes as BCS: {e}"
619        ))
620    })
621    .extend()
622}