Skip to content

Commit

Permalink
Add query and handling for MessageStatus (#1371)
Browse files Browse the repository at this point in the history
The bridge UI requires an client endpoint for querying the status of a
specific `Message`, whether it has been spent or not. An additional
variant is added for unknown messages, i.e. messages that can't be found
in the DB.

---------

Co-authored-by: Green Baneling <[email protected]>

(cherry picked from commit c6b0df7)
  • Loading branch information
MitchTurner authored and xgreenx committed Sep 21, 2023
1 parent 63aee00 commit f439288
Show file tree
Hide file tree
Showing 16 changed files with 770 additions and 455 deletions.
856 changes: 443 additions & 413 deletions Cargo.lock

Large diffs are not rendered by default.

46 changes: 23 additions & 23 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,32 @@ homepage = "https://fuel.network/"
keywords = ["blockchain", "cryptocurrencies", "fuel-vm", "vm"]
license = "BUSL-1.1"
repository = "https://github.com/FuelLabs/fuel-core"
version = "0.20.4"
version = "0.20.5"

[workspace.dependencies]
# Workspace members
fuel-core = { version = "0.20.4", path = "./crates/fuel-core", default-features = false }
fuel-core-client-bin = { version = "0.20.4", path = "./bin/client" }
fuel-core-bin = { version = "0.20.4", path = "./bin/fuel-core" }
fuel-core-keygen = { version = "0.20.4", path = "./bin/keygen" }
fuel-core-chain-config = { version = "0.20.4", path = "./crates/chain-config" }
fuel-core-client = { version = "0.20.4", path = "./crates/client" }
fuel-core-database = { version = "0.20.4", path = "./crates/database" }
fuel-core-metrics = { version = "0.20.4", path = "./crates/metrics" }
fuel-core-services = { version = "0.20.4", path = "./crates/services" }
fuel-core-consensus-module = { version = "0.20.4", path = "./crates/services/consensus_module" }
fuel-core-bft = { version = "0.20.4", path = "./crates/services/consensus_module/bft" }
fuel-core-poa = { version = "0.20.4", path = "./crates/services/consensus_module/poa" }
fuel-core-executor = { version = "0.20.4", path = "./crates/services/executor" }
fuel-core-importer = { version = "0.20.4", path = "./crates/services/importer" }
fuel-core-p2p = { version = "0.20.4", path = "./crates/services/p2p" }
fuel-core-producer = { version = "0.20.4", path = "./crates/services/producer" }
fuel-core-relayer = { version = "0.20.4", path = "./crates/services/relayer" }
fuel-core-sync = { version = "0.20.4", path = "./crates/services/sync" }
fuel-core-txpool = { version = "0.20.4", path = "./crates/services/txpool" }
fuel-core-storage = { version = "0.20.4", path = "./crates/storage" }
fuel-core-trace = { version = "0.20.4", path = "./crates/trace" }
fuel-core-types = { version = "0.20.4", path = "./crates/types", default-features = false }
fuel-core = { version = "0.20.5", path = "./crates/fuel-core", default-features = false }
fuel-core-client-bin = { version = "0.20.5", path = "./bin/fuel-core-client" }
fuel-core-bin = { version = "0.20.5", path = "./bin/fuel-core" }
fuel-core-keygen = { version = "0.20.5", path = "./bin/keygen" }
fuel-core-chain-config = { version = "0.20.5", path = "./crates/chain-config" }
fuel-core-client = { version = "0.20.5", path = "./crates/client" }
fuel-core-database = { version = "0.20.5", path = "./crates/database" }
fuel-core-metrics = { version = "0.20.5", path = "./crates/metrics" }
fuel-core-services = { version = "0.20.5", path = "./crates/services" }
fuel-core-consensus-module = { version = "0.20.5", path = "./crates/services/consensus_module" }
fuel-core-bft = { version = "0.20.5", path = "./crates/services/consensus_module/bft" }
fuel-core-poa = { version = "0.20.5", path = "./crates/services/consensus_module/poa" }
fuel-core-executor = { version = "0.20.5", path = "./crates/services/executor" }
fuel-core-importer = { version = "0.20.5", path = "./crates/services/importer" }
fuel-core-p2p = { version = "0.20.5", path = "./crates/services/p2p" }
fuel-core-producer = { version = "0.20.5", path = "./crates/services/producer" }
fuel-core-relayer = { version = "0.20.5", path = "./crates/services/relayer" }
fuel-core-sync = { version = "0.20.5", path = "./crates/services/sync" }
fuel-core-txpool = { version = "0.20.5", path = "./crates/services/txpool" }
fuel-core-storage = { version = "0.20.5", path = "./crates/storage" }
fuel-core-trace = { version = "0.20.5", path = "./crates/trace" }
fuel-core-types = { version = "0.20.5", path = "./crates/types", default-features = false }
fuel-core-tests = { version = "0.0.0", path = "./tests" }
fuel-core-xtask = { version = "0.0.0", path = "./xtask" }

Expand Down
11 changes: 11 additions & 0 deletions crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,16 @@ type MessageProof {
data: HexString!
}

enum MessageState {
UNSPENT
SPENT
NOT_FOUND
}

type MessageStatus {
state: MessageState!
}

type Mutation {
startSession: ID!
endSession(id: ID!): Boolean!
Expand Down Expand Up @@ -684,6 +694,7 @@ type Query {
nodeInfo: NodeInfo!
messages(owner: Address, first: Int, after: String, last: Int, before: String): MessageConnection!
messageProof(transactionId: TransactionId!, messageId: MessageId!, commitBlockId: BlockId, commitBlockHeight: U32): MessageProof
messageStatus(nonce: Nonce!): MessageStatus!
}

type Receipt {
Expand Down
27 changes: 19 additions & 8 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ use crate::client::{
SpendQueryElementInput,
},
contract::ContractBalanceQueryArgs,
message::MessageStatusArgs,
tx::DryRunArg,
Tai64Timestamp,
TransactionId,
},
types::primitives::{
Address,
AssetId,
BlockId,
ContractId,
UtxoId,
types::{
message::MessageStatus,
primitives::{
Address,
AssetId,
BlockId,
ContractId,
UtxoId,
},
},
};
use anyhow::Context;
Expand Down Expand Up @@ -98,7 +102,6 @@ use std::{
sync::Arc,
};
use tai64::Tai64;
use tracing as _;
use types::{
TransactionResponse,
TransactionStatus,
Expand Down Expand Up @@ -218,7 +221,6 @@ impl FuelClient {
{
use core::ops::Deref;
use eventsource_client as es;
use hyper_rustls as _;
use reqwest::cookie::CookieStore;
let mut url = self.url.clone();
url.set_path("/graphql-sub");
Expand Down Expand Up @@ -862,6 +864,15 @@ impl FuelClient {
Ok(messages)
}

pub async fn message_status(&self, nonce: &Nonce) -> io::Result<MessageStatus> {
let query = schema::message::MessageStatusQuery::build(MessageStatusArgs {
nonce: (*nonce).into(),
});
let status = self.query(query).await?.message_status.into();

Ok(status)
}

/// Request a merkle proof of an output message.
pub async fn message_proof(
&self,
Expand Down
31 changes: 31 additions & 0 deletions crates/client/src/client/schema/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ pub struct Message {
pub da_height: U64,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct MessageStatus {
pub(crate) state: MessageState,
}

#[derive(cynic::Enum, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub enum MessageState {
Unspent,
Spent,
NotFound,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
Expand Down Expand Up @@ -140,6 +154,23 @@ pub struct MessageProofArgs {
pub commit_block_height: Option<U32>,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
variables = "MessageStatusArgs"
)]
pub struct MessageStatusQuery {
#[arguments(nonce: $nonce)]
pub message_status: MessageStatus,
}

#[derive(cynic::QueryVariables, Debug)]
pub struct MessageStatusArgs {
/// Nonce of the output message that requires a proof.
pub nonce: Nonce,
}

impl From<(Option<Address>, PaginationRequest<String>)> for OwnedMessagesConnectionArgs {
fn from(r: (Option<Address>, PaginationRequest<String>)) -> Self {
match r.1.direction {
Expand Down
17 changes: 17 additions & 0 deletions crates/client/src/client/types/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ pub struct MessageProof {
pub data: Bytes,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MessageStatus {
Unspent,
Spent,
NotFound,
}

impl From<schema::message::MessageStatus> for MessageStatus {
fn from(value: schema::message::MessageStatus) -> Self {
match value.state {
schema::message::MessageState::Unspent => Self::Unspent,
schema::message::MessageState::Spent => Self::Spent,
schema::message::MessageState::NotFound => Self::NotFound,
}
}
}

// GraphQL Translation

impl From<schema::message::Message> for Message {
Expand Down
3 changes: 1 addition & 2 deletions crates/fuel-core/src/database/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ use fuel_core_types::{
use itertools::Itertools;
use std::{
borrow::{
Borrow,
BorrowMut,
Cow,
},
Expand Down Expand Up @@ -271,7 +270,7 @@ impl Database {
.get(commit_block_height)?
.ok_or(not_found!(FuelBlockMerkleMetadata))?;

let storage = self.borrow();
let storage = self;
let tree: MerkleTree<FuelBlockMerkleData, _> =
MerkleTree::load(storage, commit_merkle_metadata.version)
.map_err(|err| StorageError::Other(err.into()))?;
Expand Down
8 changes: 6 additions & 2 deletions crates/fuel-core/src/database/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl Database {
.filter_map(|msg| {
// Return only unspent messages
if let Ok(msg) = msg {
match self.is_message_spent(msg.id()) {
match self.message_is_spent(msg.id()) {
Ok(false) => Some(Ok(msg)),
Ok(true) => None,
Err(e) => Some(Err(e)),
Expand All @@ -150,9 +150,13 @@ impl Database {
Ok(Some(configs))
}

pub fn is_message_spent(&self, id: &Nonce) -> StorageResult<bool> {
pub fn message_is_spent(&self, id: &Nonce) -> StorageResult<bool> {
fuel_core_storage::StorageAsRef::storage::<SpentMessages>(&self).contains_key(id)
}

pub fn message_exists(&self, id: &Nonce) -> StorageResult<bool> {
fuel_core_storage::StorageAsRef::storage::<Messages>(&self).contains_key(id)
}
}

// TODO: Reuse `fuel_vm::storage::double_key` macro.
Expand Down
10 changes: 5 additions & 5 deletions crates/fuel-core/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ where
..
}) => {
// Eagerly return already spent if status is known.
if db.is_message_spent(nonce)? {
if db.message_is_spent(nonce)? {
return Err(
TransactionValidityError::MessageAlreadySpent(*nonce).into()
)
Expand Down Expand Up @@ -3783,10 +3783,10 @@ mod tests {
// Successful execution consumes `message_coin` and `message_data`.
assert_eq!(block_db_transaction.all_messages(None, None).count(), 0);
assert!(block_db_transaction
.is_message_spent(&message_coin.nonce)
.message_is_spent(&message_coin.nonce)
.unwrap());
assert!(block_db_transaction
.is_message_spent(&message_data.nonce)
.message_is_spent(&message_data.nonce)
.unwrap());
assert_eq!(
block_db_transaction
Expand Down Expand Up @@ -3846,10 +3846,10 @@ mod tests {
// We should spend only `message_coin`. The `message_data` should be unspent.
assert_eq!(block_db_transaction.all_messages(None, None).count(), 1);
assert!(block_db_transaction
.is_message_spent(&message_coin.nonce)
.message_is_spent(&message_coin.nonce)
.unwrap());
assert!(!block_db_transaction
.is_message_spent(&message_data.nonce)
.message_is_spent(&message_data.nonce)
.unwrap());
assert_eq!(
block_db_transaction
Expand Down
4 changes: 4 additions & 0 deletions crates/fuel-core/src/graphql_api/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ pub trait DatabaseMessages:
start_message_id: Option<Nonce>,
direction: IterDirection,
) -> BoxedIter<'_, StorageResult<Message>>;

fn message_is_spent(&self, nonce: &Nonce) -> StorageResult<bool>;

fn message_exists(&self, nonce: &Nonce) -> StorageResult<bool>;
}

/// Trait that specifies all the getters required for coins.
Expand Down
15 changes: 15 additions & 0 deletions crates/fuel-core/src/query/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
fuel_core_graphql_api::{
ports::{
DatabaseMessageProof,
DatabaseMessages,
DatabasePort,
},
IntoApiResult,
Expand Down Expand Up @@ -33,6 +34,7 @@ use fuel_core_types::{
MerkleProof,
Message,
MessageProof,
MessageStatus,
},
fuel_merkle::binary::in_memory::MerkleTree,
fuel_tx::{
Expand Down Expand Up @@ -267,3 +269,16 @@ fn message_receipts_proof<T: MessageProofData + ?Sized>(
None => Ok(None),
}
}

pub fn message_status<T: DatabaseMessages + ?Sized>(
database: &T,
message_nonce: Nonce,
) -> StorageResult<MessageStatus> {
if database.message_is_spent(&message_nonce)? {
Ok(MessageStatus::spent())
} else if database.message_exists(&message_nonce)? {
Ok(MessageStatus::unspent())
} else {
Ok(MessageStatus::not_found())
}
}
37 changes: 37 additions & 0 deletions crates/fuel-core/src/schema/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use async_graphql::{
EmptyFields,
},
Context,
Enum,
Object,
};
use fuel_core_types::entities;
Expand Down Expand Up @@ -142,6 +143,16 @@ impl MessageQuery {
)?
.map(MessageProof))
}

async fn message_status(
&self,
ctx: &Context<'_>,
nonce: Nonce,
) -> async_graphql::Result<MessageStatus> {
let data: &Database = ctx.data_unchecked();
let status = crate::query::message_status(data.deref(), nonce.into())?;
Ok(status.into())
}
}
pub struct MerkleProof(pub(crate) entities::message::MerkleProof);

Expand Down Expand Up @@ -213,3 +224,29 @@ impl From<entities::message::MerkleProof> for MerkleProof {
MerkleProof(proof)
}
}

pub struct MessageStatus(pub(crate) entities::message::MessageStatus);

#[derive(Enum, Copy, Clone, Eq, PartialEq)]
enum MessageState {
Unspent,
Spent,
NotFound,
}

#[Object]
impl MessageStatus {
async fn state(&self) -> MessageState {
match self.0.state {
entities::message::MessageState::Unspent => MessageState::Unspent,
entities::message::MessageState::Spent => MessageState::Spent,
entities::message::MessageState::NotFound => MessageState::NotFound,
}
}
}

impl From<entities::message::MessageStatus> for MessageStatus {
fn from(status: entities::message::MessageStatus) -> Self {
MessageStatus(status)
}
}
8 changes: 8 additions & 0 deletions crates/fuel-core/src/service/adapters/graphql_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ impl DatabaseMessages for Database {
.map(|result| result.map_err(StorageError::from))
.into_boxed()
}

fn message_is_spent(&self, nonce: &Nonce) -> StorageResult<bool> {
self.message_is_spent(nonce)
}

fn message_exists(&self, nonce: &Nonce) -> StorageResult<bool> {
self.message_exists(nonce)
}
}

impl DatabaseCoins for Database {
Expand Down
Loading

0 comments on commit f439288

Please sign in to comment.