Skip to content

Commit

Permalink
feat: graphql endpoint to fetch the blob byte code by its blob ID (#2096
Browse files Browse the repository at this point in the history
)

## Linked Issues/PRs
- Closes #2084 

## Description
This PR adds a graphql query to fetch blob bytecode by their ID

## Checklist
- [x] Breaking changes are clearly marked as such in the PR description
and changelog
- [x] New behavior is reflected in tests
- [x] [The specification](https://github.com/FuelLabs/fuel-specs/)
matches the implemented behavior (link update PR if changes are needed)

### Before requesting review
- [x] I have reviewed the code myself
- [x] I have created follow-up issues caused by this PR and linked them
here

### After merging, notify other teams

- [ ] [Platform
documentation](https://github.com/FuelLabs/devrel-requests/issues/new?assignees=&labels=new+request&projects=&template=NEW-REQUEST.yml&title=%5BRequest%5D%3A+)

---------

Co-authored-by: Mitchell Turner <james.mitchell.turner@gmail.com>
  • Loading branch information
netrome and MitchTurner authored Aug 20, 2024
1 parent b1f2bf4 commit 4c0cd47
Show file tree
Hide file tree
Showing 25 changed files with 239 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- [2051](https://github.com/FuelLabs/fuel-core/pull/2051): Add support for AWS KMS signing for the PoA consensus module. The new key can be specified with `--consensus-aws-kms AWS_KEY_ARN`.
- [2092](https://github.com/FuelLabs/fuel-core/pull/2092): Allow iterating by keys in rocksdb, and other storages.
- [2096](https://github.com/FuelLabs/fuel-core/pull/2096): GraphQL endpoint to fetch blob byte code by its blob ID.

### Changed

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ clap = "4.4"
derivative = { version = "2" }
derive_more = { version = "0.99" }
enum-iterator = "1.2"
hex = { version = "0.4", features = ["serde"] }
hyper = { version = "0.14.26" }
num-rational = "0.4.2"
primitive-types = { version = "0.12", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion bin/e2e-test-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fuel-core-chain-config = { workspace = true, features = ["default"] }
fuel-core-client = { workspace = true }
fuel-core-types = { workspace = true, features = ["test-helpers"] }
futures = { workspace = true }
hex = "0.4"
hex = { workspace = true }
humantime-serde = "1.1"
itertools = { workspace = true }
libtest-mimic = "0.6.0"
Expand Down
2 changes: 1 addition & 1 deletion bin/fuel-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fuel-core = { workspace = true, features = ["wasm-executor"] }
fuel-core-chain-config = { workspace = true }
fuel-core-poa = { workspace = true }
fuel-core-types = { workspace = true }
hex = "0.4"
hex = { workspace = true }
humantime = "2.1"
pyroscope = "0.5"
pyroscope_pprofrs = "0.2"
Expand Down
2 changes: 1 addition & 1 deletion crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ derive_more = { workspace = true }
eventsource-client = { version = "0.12.2", optional = true }
fuel-core-types = { workspace = true, features = ["serde"] }
futures = { workspace = true, optional = true }
hex = "0.4"
hex = { workspace = true }
# Included to enable webpki in the eventsource client
hyper-rustls = { version = "0.24", features = [
"webpki-tokio",
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 @@ -44,6 +44,11 @@ input BalanceFilterInput {
owner: Address!
}

type Blob {
id: BlobId!
bytecode: HexString!
}

scalar BlobId

type Block {
Expand Down Expand Up @@ -859,6 +864,12 @@ type Query {
assetId: AssetId!
): Balance!
balances(filter: BalanceFilterInput!, first: Int, after: String, last: Int, before: String): BalanceConnection!
blob(
"""
ID of the Blob
"""
id: BlobId!
): Blob
block(
"""
ID of the block
Expand Down
11 changes: 10 additions & 1 deletion crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ use fuel_core_types::{
Word,
},
fuel_tx::{
BlobId,
Bytes32,
Receipt,
Transaction,
TxId,
},
fuel_types,
fuel_types::{
self,
canonical::Serialize,
BlockHeight,
Nonce,
Expand All @@ -69,6 +70,7 @@ use pagination::{
};
use schema::{
balance::BalanceArgs,
blob::BlobByIdArgs,
block::BlockByIdArgs,
coins::CoinByIdArgs,
contract::ContractByIdArgs,
Expand Down Expand Up @@ -778,6 +780,13 @@ impl FuelClient {
Ok(block)
}

/// Retrieve a blob by its ID
pub async fn blob(&self, id: BlobId) -> io::Result<Option<types::Blob>> {
let query = schema::blob::BlobByIdQuery::build(BlobByIdArgs { id: id.into() });
let blob = self.query(query).await?.blob.map(Into::into);
Ok(blob)
}

/// Retrieve multiple blocks
pub async fn blocks(
&self,
Expand Down
1 change: 1 addition & 0 deletions crates/client/src/client/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::client::pagination::{
pub use primitives::*;

pub mod balance;
pub mod blob;
pub mod block;
pub mod chain;
pub mod coins;
Expand Down
28 changes: 28 additions & 0 deletions crates/client/src/client/schema/blob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::client::schema::{
schema,
BlobId,
HexString,
};

#[derive(cynic::QueryVariables, Debug)]
pub struct BlobByIdArgs {
pub id: BlobId,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
variables = "BlobByIdArgs"
)]
pub struct BlobByIdQuery {
#[arguments(id: $id)]
pub blob: Option<Blob>,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct Blob {
pub id: BlobId,
pub bytecode: HexString,
}
3 changes: 3 additions & 0 deletions crates/client/src/client/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod balance;
pub mod blob;
pub mod block;
pub mod chain_info;
pub mod coins;
Expand All @@ -11,6 +12,7 @@ pub mod message;
pub mod node_info;

pub use balance::Balance;
pub use blob::Blob;
pub use block::{
Block,
Consensus,
Expand Down Expand Up @@ -67,6 +69,7 @@ pub mod primitives {
fuel_types::{
Address,
AssetId,
BlobId,
Bytes32,
Bytes64,
ChainId,
Expand Down
24 changes: 24 additions & 0 deletions crates/client/src/client/types/blob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::client::{
schema,
types::primitives::{
BlobId,
Bytes,
},
};

#[derive(Debug, Clone, PartialEq)]
pub struct Blob {
pub id: BlobId,
pub bytecode: Bytes,
}

// GraphQL Translation

impl From<schema::blob::Blob> for Blob {
fn from(value: schema::blob::Blob) -> Self {
Self {
id: value.id.into(),
bytecode: value.bytecode.into(),
}
}
}
2 changes: 1 addition & 1 deletion crates/fuel-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fuel-core-txpool = { workspace = true }
fuel-core-types = { workspace = true, features = ["serde"] }
fuel-core-upgradable-executor = { workspace = true }
futures = { workspace = true }
hex = { version = "0.4", features = ["serde"] }
hex = { workspace = true }
hyper = { workspace = true }
indicatif = { workspace = true, default-features = true }
itertools = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/fuel-core/src/graphql_api/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use fuel_core_storage::{
IterDirection,
},
tables::{
BlobData,
Coins,
ContractsAssets,
ContractsRawCode,
Expand Down Expand Up @@ -117,6 +118,7 @@ pub trait OnChainDatabase:
+ DatabaseBlocks
+ DatabaseMessages
+ StorageInspect<Coins, Error = StorageError>
+ StorageInspect<BlobData, Error = StorageError>
+ DatabaseContracts
+ DatabaseChain
+ DatabaseMessageProof
Expand Down
2 changes: 2 additions & 0 deletions crates/fuel-core/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod balance;
mod blob;
mod block;
mod chain;
mod coin;
Expand All @@ -9,6 +10,7 @@ mod tx;

// TODO: Remove reexporting of everything
pub use balance::*;
pub use blob::*;
pub use block::*;
pub use chain::*;
pub use coin::*;
Expand Down
32 changes: 32 additions & 0 deletions crates/fuel-core/src/query/blob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::graphql_api::ports::{
OffChainDatabase,
OnChainDatabase,
};
use fuel_core_storage::{
not_found,
tables::BlobData,
Result as StorageResult,
StorageAsRef,
};
use fuel_core_types::fuel_tx::BlobId;

pub trait BlobQueryData: Send + Sync {
fn blob_exists(&self, id: BlobId) -> StorageResult<bool>;
fn blob_bytecode(&self, id: BlobId) -> StorageResult<Vec<u8>>;
}

impl<D: OnChainDatabase + OffChainDatabase + ?Sized> BlobQueryData for D {
fn blob_exists(&self, id: BlobId) -> StorageResult<bool> {
self.storage::<BlobData>().contains_key(&id)
}

fn blob_bytecode(&self, id: BlobId) -> StorageResult<Vec<u8>> {
let blob = self
.storage::<BlobData>()
.get(&id)?
.ok_or(not_found!(BlobData))?
.into_owned();

Ok(blob.into())
}
}
11 changes: 3 additions & 8 deletions crates/fuel-core/src/query/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use fuel_core_types::{
};

pub trait ContractQueryData: Send + Sync {
fn contract_id(&self, id: ContractId) -> StorageResult<ContractId>;
fn contract_exists(&self, id: ContractId) -> StorageResult<bool>;

fn contract_bytecode(&self, id: ContractId) -> StorageResult<Vec<u8>>;

Expand All @@ -46,13 +46,8 @@ pub trait ContractQueryData: Send + Sync {
}

impl<D: OnChainDatabase + OffChainDatabase + ?Sized> ContractQueryData for D {
fn contract_id(&self, id: ContractId) -> StorageResult<ContractId> {
let contract_exists = self.storage::<ContractsRawCode>().contains_key(&id)?;
if contract_exists {
Ok(id)
} else {
Err(not_found!(ContractsRawCode))
}
fn contract_exists(&self, id: ContractId) -> StorageResult<bool> {
self.storage::<ContractsRawCode>().contains_key(&id)
}

fn contract_bytecode(&self, id: ContractId) -> StorageResult<Vec<u8>> {
Expand Down
2 changes: 2 additions & 0 deletions crates/fuel-core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use itertools::Itertools;
use std::borrow::Cow;

pub mod balance;
pub mod blob;
pub mod block;
pub mod chain;
pub mod coins;
Expand All @@ -46,6 +47,7 @@ pub mod relayed_tx;
pub struct Query(
dap::DapQuery,
balance::BalanceQuery,
blob::BlobQuery,
block::BlockQuery,
chain::ChainQuery,
tx::TxQuery,
Expand Down
69 changes: 69 additions & 0 deletions crates/fuel-core/src/schema/blob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::{
fuel_core_graphql_api::QUERY_COSTS,
graphql_api::IntoApiResult,
query::BlobQueryData,
schema::{
scalars::{
BlobId,
HexString,
},
ReadViewProvider,
},
};
use async_graphql::{
Context,
Object,
};
use fuel_core_storage::{
not_found,
tables::BlobData,
};
use fuel_core_types::fuel_types;

pub struct Blob(fuel_types::BlobId);

#[Object]
impl Blob {
async fn id(&self) -> BlobId {
self.0.into()
}

#[graphql(complexity = "QUERY_COSTS.bytecode_read")]
async fn bytecode(&self, ctx: &Context<'_>) -> async_graphql::Result<HexString> {
let query = ctx.read_view()?;
query
.blob_bytecode(self.0)
.map(HexString)
.map_err(async_graphql::Error::from)
}
}

impl From<fuel_types::BlobId> for Blob {
fn from(value: fuel_types::BlobId) -> Self {
Self(value)
}
}

#[derive(Default)]
pub struct BlobQuery;

#[Object]
impl BlobQuery {
async fn blob(
&self,
ctx: &Context<'_>,
#[graphql(desc = "ID of the Blob")] id: BlobId,
) -> async_graphql::Result<Option<Blob>> {
let query = ctx.read_view()?;
query
.blob_exists(id.0)
.and_then(|blob_exists| {
if blob_exists {
Ok(id.0)
} else {
Err(not_found!(BlobData))
}
})
.into_api_result()
}
}
Loading

0 comments on commit 4c0cd47

Please sign in to comment.