Skip to content

Commit 514e4b1

Browse files
authored
feat: add isIndexedOnNode API (#8789)
# Description of change Feature branch that fixes #8591
2 parents eacc846 + a0b225d commit 514e4b1

File tree

25 files changed

+568
-149
lines changed

25 files changed

+568
-149
lines changed

.changeset/proud-pillows-pull.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@iota/graphql-transport': minor
3+
'@iota/iota-sdk': minor
4+
---
5+
6+
Support the new node method `isTransactionIndexedOnNode`

crates/iota-graphql-rpc/schema.graphql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3576,6 +3576,10 @@ type Query {
35763576
"""
35773577
dryRunTransactionBlock(txBytes: String!, txMeta: TransactionMetadata, skipChecks: Boolean): DryRunResult!
35783578
"""
3579+
Check if a transaction is indexed on the fullnode.
3580+
"""
3581+
isTransactionIndexedOnNode(digest: String!): Boolean!
3582+
"""
35793583
Look up an Owner by its IotaAddress.
35803584
35813585
`rootVersion` represents the version of the root object in some nested
@@ -4371,6 +4375,21 @@ type TransactionBlock {
43714375
and Base64 encoded.
43724376
"""
43734377
bcs: Base64
4378+
"""
4379+
Returns whether the transaction has been indexed on the fullnode.
4380+
4381+
This makes a request to the fullnode if the transaction is not part of
4382+
a checkpoint to resolve the index status on the node.
4383+
4384+
However, as this relies on the transaction data being already
4385+
constructed or fetched from the backing database, it only makes
4386+
sense to be used with `Mutation.executeTransactionBlock` on the
4387+
resulting effects.
4388+
4389+
Otherwise, it is recommended that you use
4390+
`Query.isTransactionIndexedOnNode` for optimal performance.
4391+
"""
4392+
indexedOnNode: Boolean
43744393
}
43754394

43764395
type TransactionBlockConnection {

crates/iota-graphql-rpc/src/server/builder.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
};
1111

1212
use async_graphql::{
13-
EmptySubscription, Schema, SchemaBuilder,
13+
Context, EmptySubscription, ResultExt, Schema, SchemaBuilder,
1414
extensions::{ApolloTracing, ExtensionFactory, Tracing},
1515
};
1616
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
@@ -34,7 +34,7 @@ use iota_indexer::{
3434
use iota_metrics::spawn_monitored_task;
3535
use iota_network_stack::callback::{CallbackLayer, MakeCallbackHandler, ResponseHandler};
3636
use iota_package_resolver::{PackageStoreWithLruCache, Resolver};
37-
use iota_sdk::IotaClientBuilder;
37+
use iota_sdk::{IotaClient, IotaClientBuilder};
3838
use tokio::{join, net::TcpListener, sync::OnceCell};
3939
use tokio_util::sync::CancellationToken;
4040
use tower::{Layer, Service};
@@ -550,6 +550,19 @@ fn schema_builder() -> SchemaBuilder<Query, Mutation, EmptySubscription> {
550550
.register_output_type::<IMoveDatatype>()
551551
}
552552

553+
pub(crate) fn get_fullnode_client<'ctx>(
554+
ctx: &'ctx Context<'_>,
555+
) -> async_graphql::Result<&'ctx IotaClient> {
556+
let iota_sdk_client: &Option<IotaClient> = ctx
557+
.data()
558+
.map_err(|_| Error::Internal("Unable to fetch IOTA SDK client".to_string()))
559+
.extend()?;
560+
iota_sdk_client
561+
.as_ref()
562+
.ok_or_else(|| Error::Internal("IOTA SDK client not initialized".to_string()))
563+
.extend()
564+
}
565+
553566
/// Return the string representation of the schema used by this server.
554567
pub fn export_schema() -> String {
555568
schema_builder().finish().sdl()

crates/iota-graphql-rpc/src/test_infra/cluster.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use iota_indexer::{
1313
test_utils::{IndexerTypeConfig, force_delete_database, start_test_indexer_impl},
1414
};
1515
use iota_swarm_config::genesis_config::{AccountConfig, DEFAULT_GAS_AMOUNT};
16-
use iota_types::storage::RestStateReader;
16+
use iota_types::{
17+
storage::RestStateReader,
18+
transaction::{Transaction, TransactionData},
19+
};
1720
use test_cluster::{TestCluster, TestClusterBuilder};
1821
use tokio::{join, task::JoinHandle};
1922
use tokio_util::sync::CancellationToken;
@@ -360,6 +363,25 @@ impl Cluster {
360363
self.cancellation_token.cancel();
361364
let _ = join!(self.graphql_server_join_handle, self.indexer_join_handle);
362365
}
366+
367+
/// Builds a transaction that transfers IOTA for testing.
368+
pub async fn build_transfer_iota_for_test(&self) -> TransactionData {
369+
let addresses = self.validator_fullnode_handle.wallet.get_addresses();
370+
371+
let recipient = addresses[1];
372+
self.validator_fullnode_handle
373+
.test_transaction_builder()
374+
.await
375+
.transfer_iota(Some(1_000), recipient)
376+
.build()
377+
}
378+
379+
/// Signs a transaction.
380+
pub fn sign_transaction(&self, transaction: &TransactionData) -> Transaction {
381+
self.validator_fullnode_handle
382+
.wallet
383+
.sign_transaction(transaction)
384+
}
363385
}
364386

365387
impl ExecutorCluster {

crates/iota-graphql-rpc/src/types/digest.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ impl From<TransactionDigest> for Digest {
7575
}
7676
}
7777

78+
impl From<Digest> for TransactionDigest {
79+
fn from(digest: Digest) -> Self {
80+
Self::new(digest.0)
81+
}
82+
}
83+
7884
impl fmt::Display for Digest {
7985
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8086
write!(f, "{}", Base58::encode(self.0))

crates/iota-graphql-rpc/src/types/query.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use std::str::FromStr;
66

77
use async_graphql::{connection::Connection, *};
88
use fastcrypto::encoding::{Base64, Encoding};
9+
use iota_json_rpc_api::ReadApiClient;
910
use iota_json_rpc_types::DevInspectArgs;
10-
use iota_sdk::IotaClient;
1111
use iota_types::{
1212
TypeTag,
1313
gas_coin::GAS,
@@ -21,7 +21,7 @@ use crate::{
2121
connection::ScanConnection,
2222
error::Error,
2323
mutation::Mutation,
24-
server::watermark_task::Watermark,
24+
server::{builder::get_fullnode_client, watermark_task::Watermark},
2525
types::{
2626
address::Address,
2727
available_range::AvailableRange,
@@ -114,14 +114,7 @@ impl Query {
114114
) -> Result<DryRunResult> {
115115
let skip_checks = skip_checks.unwrap_or(false);
116116

117-
let iota_sdk_client: &Option<IotaClient> = ctx
118-
.data()
119-
.map_err(|_| Error::Internal("Unable to fetch IOTA SDK client".to_string()))
120-
.extend()?;
121-
let iota_sdk_client = iota_sdk_client
122-
.as_ref()
123-
.ok_or_else(|| Error::Internal("IOTA SDK client not initialized".to_string()))
124-
.extend()?;
117+
let iota_sdk_client = get_fullnode_client(ctx)?;
125118

126119
let (sender_address, tx_kind, gas_price, gas_sponsor, gas_budget, gas_objects) =
127120
if let Some(TransactionMetadata {
@@ -190,6 +183,19 @@ impl Query {
190183
DryRunResult::try_from(res).extend()
191184
}
192185

186+
/// Check if a transaction is indexed on the fullnode.
187+
async fn is_transaction_indexed_on_node(
188+
&self,
189+
ctx: &Context<'_>,
190+
digest: Digest,
191+
) -> Result<bool> {
192+
let fullnode_client = get_fullnode_client(ctx)?;
193+
Ok(fullnode_client
194+
.http()
195+
.is_transaction_indexed_on_node(digest.into())
196+
.await?)
197+
}
198+
193199
/// Look up an Owner by its IotaAddress.
194200
///
195201
/// `rootVersion` represents the version of the root object in some nested

crates/iota-graphql-rpc/src/types/transaction_block/mod.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use iota_indexer::{
1313
models::transactions::{OptimisticTransaction, StoredTransaction},
1414
schema::{transactions, tx_digests},
1515
};
16+
use iota_json_rpc_api::ReadApiClient;
1617
use iota_types::{
1718
base_types::IotaAddress as NativeIotaAddress,
1819
effects::TransactionEffects as NativeTransactionEffects,
@@ -30,7 +31,7 @@ use crate::{
3031
connection::ScanConnection,
3132
data::{self, DataLoader, Db, DbConnection, QueryExecutor},
3233
error::Error,
33-
server::watermark_task::Watermark,
34+
server::{builder::get_fullnode_client, watermark_task::Watermark},
3435
types::{
3536
address::Address,
3637
base64::Base64,
@@ -88,6 +89,16 @@ pub(crate) enum TransactionBlockInner {
8889
},
8990
}
9091

92+
impl TransactionBlockInner {
93+
/// Returns if the transaction is included in a checkpoint.
94+
fn is_checkpointed(&self) -> bool {
95+
let TransactionBlockInner::Stored { stored_tx, .. } = &self else {
96+
return false;
97+
};
98+
stored_tx.checkpoint_sequence_number >= 0
99+
}
100+
}
101+
91102
/// An input filter selecting for either system or programmable transactions.
92103
#[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)]
93104
pub(crate) enum TransactionBlockKindInput {
@@ -234,6 +245,35 @@ impl TransactionBlock {
234245
TransactionBlockInner::DryRun { .. } => None,
235246
}
236247
}
248+
249+
/// Returns whether the transaction has been indexed on the fullnode.
250+
///
251+
/// This makes a request to the fullnode if the transaction is not part of
252+
/// a checkpoint to resolve the index status on the node.
253+
///
254+
/// However, as this relies on the transaction data being already
255+
/// constructed or fetched from the backing database, it only makes
256+
/// sense to be used with `Mutation.executeTransactionBlock` on the
257+
/// resulting effects.
258+
///
259+
/// Otherwise, it is recommended that you use
260+
/// `Query.isTransactionIndexedOnNode` for optimal performance.
261+
async fn indexed_on_node(&self, ctx: &Context<'_>) -> Result<Option<bool>> {
262+
if self.inner.is_checkpointed() {
263+
return Ok(Some(true));
264+
}
265+
let Some(digest) = self.native_signed_data().map(|d| d.digest()) else {
266+
// dry-run transactions are never indexed
267+
return Ok(Some(false));
268+
};
269+
let fullnode_client = get_fullnode_client(ctx)?;
270+
Ok(Some(
271+
fullnode_client
272+
.http()
273+
.is_transaction_indexed_on_node(digest)
274+
.await?,
275+
))
276+
}
237277
}
238278

239279
impl TransactionBlock {

0 commit comments

Comments
 (0)