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

Create new index for tracking Asset metadata #2445

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added
- [2445](https://github.com/FuelLabs/fuel-core/pull/2445): Added GQL endpoint for querying asset details.
- [2442](https://github.com/FuelLabs/fuel-core/pull/2442): Add uninitialized task for V1 gas price service
- [2154](https://github.com/FuelLabs/fuel-core/pull/2154): Added `Unknown` variant to `ConsensusParameters` graphql queries
- [2154](https://github.com/FuelLabs/fuel-core/pull/2154): Added `Unknown` variant to `Block` graphql queries
Expand Down
14 changes: 14 additions & 0 deletions crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ scalar Address

scalar AssetId

type AssetInfoDetails {
contractId: ContractId!
subId: SubId!
totalSupply: U128!
}

type Balance {
owner: Address!
amount: U64!
Expand Down Expand Up @@ -853,6 +859,12 @@ type ProgramState {
}

type Query {
assetDetails(
"""
ID of the Asset
"""
id: AssetId!
): AssetInfoDetails!
"""
Read register value by index.
"""
Expand Down Expand Up @@ -1121,6 +1133,8 @@ type StateTransitionPurpose {
}


scalar SubId

type SubmittedStatus {
time: Tai64Timestamp!
}
Expand Down
10 changes: 10 additions & 0 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::client::{
TransactionId,
},
types::{
asset::AssetDetail,
gas_price::LatestGasPrice,
message::MessageStatus,
primitives::{
Expand Down Expand Up @@ -78,6 +79,7 @@ use pagination::{
PaginationRequest,
};
use schema::{
assets::AssetInfoArg,
balance::BalanceArgs,
blob::BlobByIdArgs,
block::BlockByIdArgs,
Expand Down Expand Up @@ -1191,6 +1193,14 @@ impl FuelClient {
.transpose()?;
Ok(status)
}

pub async fn asset_info(&self, asset_id: &AssetId) -> io::Result<AssetDetail> {
let query = schema::assets::AssetInfoQuery::build(AssetInfoArg {
id: (*asset_id).into(),
});
let asset_info = self.query(query).await?.asset_details.into();
Ok(asset_info)
}
}

#[cfg(any(test, feature = "test-helpers"))]
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 @@ -26,6 +26,7 @@ use crate::client::pagination::{
};
pub use primitives::*;

pub mod assets;
pub mod balance;
pub mod blob;
pub mod block;
Expand Down
31 changes: 31 additions & 0 deletions crates/client/src/client/schema/assets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::client::schema::{
schema,
AssetId,
ContractId,
SubId,
U128,
};

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

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
variables = "AssetInfoArg"
)]
pub struct AssetInfoQuery {
#[arguments(id: $id)]
pub asset_details: AssetInfoDetails,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct AssetInfoDetails {
pub sub_id: SubId,
pub contract_id: ContractId,
pub total_supply: U128,
}
1 change: 1 addition & 0 deletions crates/client/src/client/schema/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fuel_type_scalar!(BlockId, Bytes32);
fuel_type_scalar!(AssetId, AssetId);
fuel_type_scalar!(BlobId, BlobId);
fuel_type_scalar!(ContractId, ContractId);
fuel_type_scalar!(SubId, Bytes32);
fuel_type_scalar!(Salt, Salt);
fuel_type_scalar!(TransactionId, Bytes32);
fuel_type_scalar!(RelayedTransactionId, Bytes32);
Expand Down
1 change: 1 addition & 0 deletions crates/client/src/client/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod contract;
pub mod gas_costs;
pub mod upgrades;

pub mod asset;
pub mod gas_price;
pub mod merkle_proof;
pub mod message;
Expand Down
24 changes: 24 additions & 0 deletions crates/client/src/client/types/asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::client::schema;
use fuel_core_types::{
fuel_tx::Bytes32,
fuel_types::ContractId,
};

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AssetDetail {
pub contract_id: ContractId,
pub sub_id: Bytes32,
pub total_supply: u128,
}

// GraphQL Translation

impl From<schema::assets::AssetInfoDetails> for AssetDetail {
fn from(value: schema::assets::AssetInfoDetails) -> Self {
AssetDetail {
contract_id: value.contract_id.into(),
sub_id: value.sub_id.into(),
total_supply: value.total_supply.into(),
}
}
}
9 changes: 8 additions & 1 deletion crates/fuel-core/src/graphql_api/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ use fuel_core_types::{
};
use std::sync::Arc;

use super::storage::balances::TotalBalanceAmount;
use super::storage::{
assets::AssetDetails,
balances::TotalBalanceAmount,
};

pub trait OffChainDatabase: Send + Sync {
fn block_height(&self, block_id: &BlockId) -> StorageResult<BlockHeight>;
Expand Down Expand Up @@ -128,6 +131,8 @@ pub trait OffChainDatabase: Send + Sync {
) -> StorageResult<Option<RelayedTransactionStatus>>;

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

fn asset_info(&self, asset_id: &AssetId) -> StorageResult<Option<AssetDetails>>;
}

/// The on chain database port expected by GraphQL API service.
Expand Down Expand Up @@ -289,6 +294,7 @@ pub mod worker {
},
},
graphql_api::storage::{
assets::AssetsInfo,
balances::{
CoinBalances,
MessageBalances,
Expand Down Expand Up @@ -371,6 +377,7 @@ pub mod worker {
+ StorageMutate<DaCompressionTemporalRegistryIndex, Error = StorageError>
+ StorageMutate<DaCompressionTemporalRegistryTimestamps, Error = StorageError>
+ StorageMutate<DaCompressionTemporalRegistryEvictorCache, Error = StorageError>
+ StorageMutate<AssetsInfo, Error = StorageError>
{
fn record_tx_id_owner(
&mut self,
Expand Down
3 changes: 3 additions & 0 deletions crates/fuel-core/src/graphql_api/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use fuel_core_types::{
};
use statistic::StatisticTable;

pub mod assets;
pub mod balances;
pub mod blocks;
pub mod coins;
Expand Down Expand Up @@ -118,6 +119,8 @@ pub enum Column {
CoinBalances = 23,
/// Message balances per account.
MessageBalances = 24,
/// See [`AssetsInfo`](assets::AssetsInfo)
AssetsInfo = 25,
}

impl Column {
Expand Down
52 changes: 52 additions & 0 deletions crates/fuel-core/src/graphql_api/storage/assets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use fuel_core_storage::{
blueprint::plain::Plain,
codec::{
postcard::Postcard,
raw::Raw,
},
structured_storage::TableWithBlueprint,
Mappable,
};
use fuel_core_types::fuel_tx::{
AssetId,
Bytes32,
ContractId,
};

/// Asset info table to store information about the asset like total minted amounts,
/// source contract, original sub id, etc.
pub struct AssetsInfo;

#[derive(Default, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AssetDetails {
pub contract_id: ContractId,
pub sub_id: Bytes32,
pub total_supply: u128,
}

impl Mappable for AssetsInfo {
type Key = AssetId;
type OwnedKey = Self::Key;
type Value = Self::OwnedValue;
type OwnedValue = AssetDetails;
}

impl TableWithBlueprint for AssetsInfo {
type Blueprint = Plain<Raw, Postcard>;
type Column = super::Column;

fn column() -> Self::Column {
Self::Column::AssetsInfo
}
}

#[cfg(test)]
mod test {
use super::*;

fuel_core_storage::basic_storage_tests!(
AssetsInfo,
<AssetsInfo as Mappable>::Key::default(),
<AssetsInfo as Mappable>::Value::default()
);
}
66 changes: 62 additions & 4 deletions crates/fuel-core/src/graphql_api/worker_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ use self::indexation::IndexationError;
use super::{
da_compression::da_compress_block,
indexation,
storage::old::{
OldFuelBlockConsensus,
OldFuelBlocks,
OldTransactions,
storage::{
assets::AssetsInfo,
old::{
OldFuelBlockConsensus,
OldFuelBlocks,
OldTransactions,
},
},
};
use crate::{
Expand All @@ -19,6 +22,7 @@ use crate::{
},
},
storage::{
assets::AssetDetails,
blocks::FuelBlockIdsToHeights,
coins::{
owner_coin_id_key,
Expand Down Expand Up @@ -70,8 +74,10 @@ use fuel_core_types::{
CoinSigned,
},
Contract,
ContractIdExt,
Input,
Output,
Receipt,
Transaction,
TxId,
UniqueIdentifier,
Expand All @@ -88,6 +94,7 @@ use fuel_core_types::{
},
executor::{
Event,
TransactionExecutionResult,
TransactionExecutionStatus,
},
txpool::from_executor_to_status,
Expand Down Expand Up @@ -381,6 +388,57 @@ where
)
.into());
}

// TODO: Do Asset indexation only if it is enabled.
// Enable the indexation only in the case of empty database

let TransactionExecutionResult::Success { receipts, .. } = result else {
continue
};

for receipt in receipts {
match receipt {
Receipt::Mint {
sub_id,
contract_id,
..
}
| Receipt::Burn {
sub_id,
contract_id,
..
} => {
let asset_id = contract_id.asset_id(sub_id);
let current_supply = db
.storage::<AssetsInfo>()
.get(&asset_id)?
.map(|info| info.total_supply)
.unwrap_or_default();

let new_supply = match receipt {
Receipt::Mint { val, .. } => current_supply
.checked_add(*val as u128)
.expect("Impossible to overflow, because `val` is `u64`"),
Receipt::Burn { val, .. } => {
current_supply.checked_sub(*val as u128).ok_or(
anyhow::anyhow!("Burned more than available amount"),
)?
}
_ => unreachable!(),
};

db.storage::<AssetsInfo>().insert(
&asset_id,
&AssetDetails {
contract_id: *contract_id,
sub_id: *sub_id,
total_supply: new_supply,
},
)?;
}
_ => {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please enumerate all variants explicitly to avoid surprises when new variant is added in the future.

}
}
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions crates/fuel-core/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod assets;
mod balance;
mod blob;
mod block;
Expand Down
19 changes: 19 additions & 0 deletions crates/fuel-core/src/query/assets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{
fuel_core_graphql_api::database::ReadView,
graphql_api::storage::assets::AssetDetails,
};
use fuel_core_storage::{
not_found,
Result as StorageResult,
};
use fuel_core_types::fuel_tx::AssetId;

impl ReadView {
pub fn get_asset_details(&self, id: &AssetId) -> StorageResult<AssetDetails> {
let asset = self
.off_chain
.asset_info(id)?
.ok_or(not_found!(AssetDetails))?;
Ok(asset)
}
}
Loading
Loading