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

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

maschad
Copy link
Member

@maschad maschad commented Nov 20, 2024

Linked Issues/PRs

Description

This introduces a new GraphQL endpoint that returns info about an asset.

The off-chain worker indexes Mint and Burn events to store this metadata about an AssetId

Checklist

  • New behavior is reflected in tests

Before requesting review

  • I have reviewed the code myself

After merging, notify other teams

[Add or remove entries as needed]

@maschad maschad self-assigned this Nov 20, 2024
tests/tests/assets.rs Outdated Show resolved Hide resolved
@maschad maschad requested a review from xgreenx November 22, 2024 20:41
@maschad maschad marked this pull request as ready for review November 22, 2024 20:41
Copy link
Contributor

@AurelienFT AurelienFT left a comment

Choose a reason for hiding this comment

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

I think we need the test to be working before merging this.

tests/tests/assets.rs Outdated Show resolved Hide resolved
@maschad
Copy link
Member Author

maschad commented Dec 2, 2024

Thanks for all your help @AurelienFT ❤️

@@ -2,6 +2,12 @@ scalar Address

scalar AssetId

type AssetInfoDetails {
contractId: HexString!
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure here, but maybe we want to use ContractId! here, as in other places.


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

fn asset_exists(&self, asset_id: &AssetId) -> StorageResult<bool>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like asset_exists is not used, can we remove it?

},
};

/// Contract info
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Contract info
/// Asset info

/// Contract info
pub struct AssetsInfo;

pub type AssetDetails = (ContractId, Bytes32, u64); // (contract_id, sub_id, total_amount)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use struct with named fields? Something similar to what we have here.

db.storage::<AssetsInfo>()
.insert(&asset_id, &(*contract_id, **sub_id, current_count))?;
}
_ => {}
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.

Comment on lines +408 to +410
.map(|info| {
info.2
.checked_add(*val)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
.map(|info| {
info.2
.checked_add(*val)
.map(|info| {
let (_, _, count) = *info;
count
.checked_add(*val)

May also affect the Receipt::Burn branch.

Comment on lines +399 to +407
let current_count = match db.storage::<AssetsInfo>().get(&asset_id) {
Ok(count) => count,
Err(_) => {
// If asset doesn't exist yet, create it with 0 count
db.storage::<AssetsInfo>()
.insert(&asset_id, &(*contract_id, **sub_id, 0))?;
Some(Cow::Owned((*contract_id, **sub_id, 0)))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we shouldn't treat all storage errors as "value not found". Some of them should just be bubbled up.

Maybe something like this will work:

                        let current_count = match db.storage::<AssetsInfo>().get(&asset_id)? {
                            Some(count) => Some(count),
                            None => {
                                // If asset doesn't exist yet, create it with 0 count
                                db.storage::<AssetsInfo>()
                                    .insert(&asset_id, &(*contract_id, **sub_id, 0))?;
                                Some(Cow::Owned((*contract_id, **sub_id, 0)))
                            }
                        }

If you agree, please note that now two branches always return Some, so maybe we can get rid of the follow-up map() as well.

})
.map(|info| {
info.2
.checked_sub(*val)
Copy link
Contributor

Choose a reason for hiding this comment

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

If we go down to 0 on Burn should we remove the asset from the index? Unless we need to distinguish between "never minted" and "minted and totally burned" cases.

Anyway, currently the index may grow indefinitely.

Which also makes me think that I should consider this for the balances indexation I added recently :-)

cc: @xgreenx, wdyt?

.map(|info| {
info.2
.checked_add(*val)
.ok_or(anyhow::anyhow!("Asset count overflow"))
Copy link
Contributor

Choose a reason for hiding this comment

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

You may consider integrating this with the IndexationError available here: https://github.com/FuelLabs/fuel-core/blob/master/crates/fuel-core/src/graphql_api/indexation.rs#L28

It's a common type for all errors related to integration and it'll grow even more variants after the "coins to spend" indexation is merged. See here.

.0;

// Then
// We should have the minted amount first
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// We should have the minted amount first
// We should have the minted amount reduced by the burned amount

}
.map(|info| {
info.2
.checked_add(*val)
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider the following scenario:

  • client v.0.40 running
  • asset A in contract B is minted (count=10)
  • we update the client to the one that supports index
  • asset A in contract B is minted again (count=10, for a total count of 20)
  • we query the asset info
  • it notices there is no index, initializes it with 0 and adds 10
  • we have inconsistency (10 vs 20)

I may not have full picture, but if you see this as a possible scenario, please consider enabling access to this endpoint conditionally, ie. it should be available only when the client was started with a clear database and have a chance to sync from genesis (and build the complete index). This is what we did with other indexes (see for example around here).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add indexation AssetId -> (ContractId, SubId)
4 participants