Skip to content

Commit

Permalink
graphql: Event.transactionBlock (#19669)
Browse files Browse the repository at this point in the history
## Description

Add the ability to fetch the transaction block that emitted the event.

## Test plan

New E2E tests:

```
sui$ cargo nextest run -p sui-graphql-e2e-tests
sui$ cargo nextest run -p sui-graphql-rpc -- test_schema_sdl_export
sui$ cargo nextest run -p sui-graphql-rpc --features staging -- test_schema_sdl_export
```

## Stack

- #19670 
- #19671 
- #19672 

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] Indexer: 
- [ ] JSON-RPC: 
- [x] GraphQL: Add `Event.transactionBlock` for fetching the transaction
that emitted the event, as long as the event is indexed (not just
executed).
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] REST API:
  • Loading branch information
amnn authored Oct 10, 2024
1 parent 6a579d6 commit 157c732
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
processed 10 tasks
processed 11 tasks

init:
A: object(0,0)
Expand Down Expand Up @@ -297,3 +297,87 @@ Response: {
}
}
}

task 10, lines 122-138:
//# run-graphql
Response: {
"data": {
"transactionBlocks": {
"nodes": [
{
"digest": "FPhSSzT7tHmrPhs3H9GT1n4Dqj3eyCgaFLkQSc9FEDVV",
"effects": {
"events": {
"nodes": []
}
}
},
{
"digest": "GJMTYHH46d31ohELwH3ZfvGvbiDZ7GCqNcyg4fGGFJJQ",
"effects": {
"events": {
"nodes": []
}
}
},
{
"digest": "48LNnMV9MFXiXfXwDRWzu6SndwxY3ZfKUNEKJiDqJqM7",
"effects": {
"events": {
"nodes": [
{
"transactionBlock": {
"digest": "48LNnMV9MFXiXfXwDRWzu6SndwxY3ZfKUNEKJiDqJqM7"
}
}
]
}
}
},
{
"digest": "FhpR5hSSP2wf8ABXKcDVVTzo7H1DZYUUgABWPtADHcQA",
"effects": {
"events": {
"nodes": [
{
"transactionBlock": {
"digest": "FhpR5hSSP2wf8ABXKcDVVTzo7H1DZYUUgABWPtADHcQA"
}
},
{
"transactionBlock": {
"digest": "FhpR5hSSP2wf8ABXKcDVVTzo7H1DZYUUgABWPtADHcQA"
}
}
]
}
}
},
{
"digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX",
"effects": {
"events": {
"nodes": [
{
"transactionBlock": {
"digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX"
}
},
{
"transactionBlock": {
"digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX"
}
},
{
"transactionBlock": {
"digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX"
}
}
]
}
}
}
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,21 @@ module Test::M1 {
}
}
}

//# run-graphql
{
transactionBlocks {
nodes {
digest
effects {
events {
nodes {
transactionBlock {
digest
}
}
}
}
}
}
}
6 changes: 6 additions & 0 deletions crates/sui-graphql-rpc/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,12 @@ type EpochEdge {
}

type Event {
"""
The transaction block that emitted this event. This information is only available for
events from indexed transactions, and not from transactions that have just been executed or
dry-run.
"""
transactionBlock: TransactionBlock
"""
The Move module containing some function that when called by
a programmable transaction block (PTB) emitted this event.
Expand Down
18 changes: 17 additions & 1 deletion crates/sui-graphql-rpc/src/types/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::str::FromStr;
use super::cursor::{Page, Target};
use super::{
address::Address, base64::Base64, date_time::DateTime, move_module::MoveModule,
move_value::MoveValue,
move_value::MoveValue, transaction_block::TransactionBlock,
};
use crate::data::{self, DbConnection, QueryExecutor};
use crate::query;
Expand Down Expand Up @@ -50,6 +50,22 @@ type Query<ST, GB> = data::Query<ST, events::table, GB>;

#[Object]
impl Event {
/// The transaction block that emitted this event. This information is only available for
/// events from indexed transactions, and not from transactions that have just been executed or
/// dry-run.
async fn transaction_block(&self, ctx: &Context<'_>) -> Result<Option<TransactionBlock>> {
let Some(stored) = &self.stored else {
return Ok(None);
};

TransactionBlock::query(
ctx,
TransactionBlock::by_seq(stored.tx_sequence_number as u64, self.checkpoint_viewed_at),
)
.await
.extend()
}

/// The Move module containing some function that when called by
/// a programmable transaction block (PTB) emitted this event.
/// For example, if a PTB invokes A::m1::foo, which internally
Expand Down
10 changes: 7 additions & 3 deletions crates/sui-graphql-rpc/src/types/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use sui_types::object::{
MoveObject as NativeMoveObject, Object as NativeObject, Owner as NativeOwner,
};
use sui_types::TypeTag;

#[derive(Clone, Debug)]
pub(crate) struct Object {
pub address: SuiAddress,
Expand Down Expand Up @@ -626,9 +627,12 @@ impl ObjectImpl<'_> {
};
let digest = native.previous_transaction;

TransactionBlock::query(ctx, digest.into(), self.0.checkpoint_viewed_at)
.await
.extend()
TransactionBlock::query(
ctx,
TransactionBlock::by_digest(digest.into(), self.0.checkpoint_viewed_at),
)
.await
.extend()
}

pub(crate) async fn storage_rebate(&self) -> Option<BigInt> {
Expand Down
5 changes: 2 additions & 3 deletions crates/sui-graphql-rpc/src/types/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,8 @@ impl Query {
digest: Digest,
) -> Result<Option<TransactionBlock>> {
let Watermark { checkpoint, .. } = *ctx.data()?;
TransactionBlock::query(ctx, digest, checkpoint)
.await
.extend()
let lookup = TransactionBlock::by_digest(digest, checkpoint);
TransactionBlock::query(ctx, lookup).await.extend()
}

/// The coin objects that exist in the network.
Expand Down
132 changes: 124 additions & 8 deletions crates/sui-graphql-rpc/src/types/transaction_block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ pub(crate) enum TransactionBlockKindInput {
ProgrammableTx = 1,
}

/// Filter for a point query of a TransactionBlock.
pub(crate) enum TransactionBlockLookup {
ByDigest {
digest: Digest,
checkpoint_viewed_at: u64,
},
BySeq {
tx_sequence_number: u64,
checkpoint_viewed_at: u64,
},
}

type Query<ST, GB> = data::Query<ST, transactions::table, GB>;

/// The cursor returned for each `TransactionBlock` in a connection's page of results. The
Expand All @@ -110,14 +122,22 @@ pub(crate) struct TransactionBlockCursor {
pub tx_checkpoint_number: u64,
}

/// `DataLoader` key for fetching a `TransactionBlock` by its digest, optionally constrained by a
/// consistency cursor.
/// `DataLoader` key for fetching a `TransactionBlock` by its digest, constrained by a consistency
/// cursor.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
struct DigestKey {
pub digest: Digest,
pub checkpoint_viewed_at: u64,
}

/// `DataLoader` key for fetching a `TransactionBlock` by its sequence number, constrained by a
/// consistency cursor.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
struct SeqKey {
pub tx_sequence_number: u64,
pub checkpoint_viewed_at: u64,
}

#[Object]
impl TransactionBlock {
/// A 32-byte hash that uniquely identifies the transaction block contents, encoded in Base58.
Expand Down Expand Up @@ -230,21 +250,60 @@ impl TransactionBlock {
}
}

/// Look-up the transaction block by its transaction digest.
pub(crate) fn by_digest(digest: Digest, checkpoint_viewed_at: u64) -> TransactionBlockLookup {
TransactionBlockLookup::ByDigest {
digest,
checkpoint_viewed_at,
}
}

/// Look-up the transaction block by its sequence number (this is not usually exposed through
/// the GraphQL schema, but internally, othe entities in the DB will refer to transactions at
/// their sequence number).
pub(crate) fn by_seq(
tx_sequence_number: u64,
checkpoint_viewed_at: u64,
) -> TransactionBlockLookup {
TransactionBlockLookup::BySeq {
tx_sequence_number,
checkpoint_viewed_at,
}
}

/// Look up a `TransactionBlock` in the database, by its transaction digest. Treats it as if it
/// is being viewed at the `checkpoint_viewed_at` (e.g. the state of all relevant addresses will
/// be at that checkpoint).
pub(crate) async fn query(
ctx: &Context<'_>,
digest: Digest,
checkpoint_viewed_at: u64,
lookup: TransactionBlockLookup,
) -> Result<Option<Self>, Error> {
let DataLoader(loader) = ctx.data_unchecked();
loader
.load_one(DigestKey {

match lookup {
TransactionBlockLookup::ByDigest {
digest,
checkpoint_viewed_at,
})
.await
} => {
loader
.load_one(DigestKey {
digest,
checkpoint_viewed_at,
})
.await
}
TransactionBlockLookup::BySeq {
tx_sequence_number,
checkpoint_viewed_at,
} => {
loader
.load_one(SeqKey {
tx_sequence_number,
checkpoint_viewed_at,
})
.await
}
}
}

/// Look up multiple `TransactionBlock`s by their digests. Returns a map from those digests to
Expand Down Expand Up @@ -497,6 +556,63 @@ impl Loader<DigestKey> for Db {
}
}

#[async_trait::async_trait]
impl Loader<SeqKey> for Db {
type Value = TransactionBlock;
type Error = Error;

async fn load(&self, keys: &[SeqKey]) -> Result<HashMap<SeqKey, TransactionBlock>, Error> {
use transactions::dsl as tx;

let seqs: Vec<_> = keys.iter().map(|k| k.tx_sequence_number as i64).collect();

let transactions: Vec<StoredTransaction> = self
.execute(move |conn| {
async move {
conn.results(move || {
tx::transactions
.select(StoredTransaction::as_select())
.filter(tx::tx_sequence_number.eq_any(seqs.clone()))
})
.await
}
.scope_boxed()
})
.await
.map_err(|e| Error::Internal(format!("Failed to fetch transactions: {e}")))?;

let seq_to_stored: BTreeMap<_, _> = transactions
.into_iter()
.map(|tx| (tx.tx_sequence_number as u64, tx))
.collect();

let mut results = HashMap::new();
for key in keys {
let Some(stored) = seq_to_stored.get(&key.tx_sequence_number).cloned() else {
continue;
};

// Filter by key's checkpoint viewed at here. Doing this in memory because it should be
// quite rare that this query actually filters something, but encoding it in SQL is
// complicated.
if key.checkpoint_viewed_at < stored.checkpoint_sequence_number as u64 {
continue;
}

let inner = TransactionBlockInner::try_from(stored)?;
results.insert(
*key,
TransactionBlock {
inner,
checkpoint_viewed_at: key.checkpoint_viewed_at,
},
);
}

Ok(results)
}
}

impl TryFrom<StoredTransaction> for TransactionBlockInner {
type Error = Error;

Expand Down
6 changes: 6 additions & 0 deletions crates/sui-graphql-rpc/staging.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,12 @@ type EpochEdge {
}

type Event {
"""
The transaction block that emitted this event. This information is only available for
events from indexed transactions, and not from transactions that have just been executed or
dry-run.
"""
transactionBlock: TransactionBlock
"""
The Move module containing some function that when called by
a programmable transaction block (PTB) emitted this event.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,12 @@ type EpochEdge {
}

type Event {
"""
The transaction block that emitted this event. This information is only available for
events from indexed transactions, and not from transactions that have just been executed or
dry-run.
"""
transactionBlock: TransactionBlock
"""
The Move module containing some function that when called by
a programmable transaction block (PTB) emitted this event.
Expand Down
Loading

0 comments on commit 157c732

Please sign in to comment.