Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add query and handling for MessageStatus #1371

Merged
merged 9 commits into from
Sep 20, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Description of the upcoming release here.

### Added

- [#1371](https://github.com/FuelLabs/fuel-core/pull/1371): Add new client function for querying the `MessageStatus` for a specific message (by `Nonce`)
- [#1356](https://github.com/FuelLabs/fuel-core/pull/1356): Add peer reputation reporting to heartbeat code
- [#1355](https://github.com/FuelLabs/fuel-core/pull/1355): Added new metrics related to block importing, such as tps, sync delays etc
- [#1339](https://github.com/FuelLabs/fuel-core/pull/1339): Adds `baseAssetId` to `FeeParameters` in the GraphQL API.
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 @@ -555,6 +555,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 @@ -696,6 +706,7 @@ type Query {
nodeInfo: NodeInfo!
messages(owner: Address, first: Int, after: String, last: Int, before: String): MessageConnection!
messageProof(transactionId: TransactionId!, nonce: Nonce!, commitBlockId: BlockId, commitBlockHeight: U32): MessageProof
messageStatus(nonce: Nonce!): MessageStatus!
}

type Receipt {
Expand Down
25 changes: 19 additions & 6 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 @@ -874,6 +878,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
33 changes: 32 additions & 1 deletion crates/client/src/client/schema/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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 @@ -126,7 +140,7 @@ pub struct MessageProof {
pub struct MessageProofArgs {
/// Transaction id that contains the output message.
pub transaction_id: TransactionId,
/// Message id of the output message that requires a proof.
/// Nonce of the output message that requires a proof.
pub nonce: Nonce,

/// The query supports either `commit_block_id`, or `commit_block_height` set on, not both.
Expand All @@ -139,6 +153,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
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 @@ -1024,7 +1024,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 @@ -3797,10 +3797,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 @@ -3860,10 +3860,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 @@ -270,3 +272,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 @@ -26,6 +26,7 @@ use async_graphql::{
EmptyFields,
},
Context,
Enum,
Object,
};
use fuel_core_types::entities;
Expand Down Expand Up @@ -141,6 +142,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 @@ -212,3 +223,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> {
MitchTurner marked this conversation as resolved.
Show resolved Hide resolved
self.message_is_spent(nonce)
}

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

impl DatabaseCoins for Database {
Expand Down
39 changes: 39 additions & 0 deletions crates/types/src/entities/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,42 @@ impl MessageProof {
)
}
}

/// Represents the status of a message
pub struct MessageStatus {
/// The message state
pub state: MessageState,
}

impl MessageStatus {
/// Constructor for `MessageStatus` that fills with `Unspent` state
pub fn unspent() -> Self {
Self {
state: MessageState::Unspent,
}
}

/// Constructor for `MessageStatus` that fills with `Spent` state
pub fn spent() -> Self {
Self {
state: MessageState::Spent,
}
}

/// Constructor for `MessageStatus` that fills with `Unknown` state
pub fn not_found() -> Self {
Self {
state: MessageState::NotFound,
}
}
}

/// The possible states a Message can be in
pub enum MessageState {
/// Message is still unspent
Unspent,
/// Message has already been spent
Spent,
/// There is no record of this Message
NotFound,
}
Loading