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.

The lookup is by `Nonce`. I'm not sure if we would prefer some other
discriminators, but that seems like the obvious choice when you're
searching the entire space--IDK if it's the most efficient though.

#1323

---------

Co-authored-by: Green Baneling <[email protected]>
  • Loading branch information
MitchTurner and xgreenx authored Sep 20, 2023
1 parent 10bd655 commit c6b0df7
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 14 deletions.
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
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 @@ -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 @@ -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> {
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

0 comments on commit c6b0df7

Please sign in to comment.