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 GraphQL extension to provide current STF and CP versions #2715

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

Conversation

rafal-ch
Copy link
Contributor

@rafal-ch rafal-ch commented Feb 13, 2025

Linked Issues/PRs

Closes #2596

Description

This PR adds new GraphQL extensions to provide:

  • current STF version
  • current consensus parameters version

Example response:

{
  "data": {
    "nodeInfo": {
      "nodeVersion": "0.41.6"
    }
  },
  "extensions": {
    "current_consensus_parameters_version": 0,
    "current_fuel_block_height": 0,
    "current_stf_version": 21
  }
}

Additional housekeeping changes:

  • all graphql extensions have been moved into a subdirectory

TODO

  • Add tests to check that correct versions are returned when STF or CP are bumped
  • Rework how we retrieve the current STF version

Checklist

  • Breaking changes are clearly marked as such in the PR description and changelog
  • New behavior is reflected in tests

Before requesting review

  • I have reviewed the code myself
  • I have created follow-up issues caused by this PR and linked them here

After merging, notify other teams

Comment on lines 44 to 53
if let Ok(view) = db.view() {
if let Ok(latest_block) = view.latest_block() {
let current_stf_version =
latest_block.header().state_transition_bytecode_version();
response.extensions.insert(
CURRENT_STF_VERSION.to_string(),
Value::Number(current_stf_version.into()),
);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We might prefer reading the current STF version directly from on chain DB table StateTransitionBytecodeVersions similarly to how it's done in BlockProducerDatabase::latest_state_transition_bytecode_version(), but I couldn't figure out how to access this in GraphQL.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to have a place where we cache this kind of information. Maybe, we could extend consensus parameters provider to also provide this kind of information, because fetch it from the database it a lot of waste work.

Copy link
Contributor

@acerone85 acerone85 Feb 17, 2025

Choose a reason for hiding this comment

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

If we do that, it might be useful to also add the block height to the information retrieved, as in this case all the extension metadata will come from the same place.

Btw, If I am not mistaken all the information we want to fetch comes from the block header right now, so maybe a HeaderProvider trait with a function that returns the whole block header, and then stf_version and consensus_parameters_version and block_height can be provided methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'll prefer to extend (and rename) the "Consensus Parameters Provider". However, I find caching the entire block header not optimal and my preference would be to cache exactly what we need. So, in addition to the current data, it'll store:

  • current STF version
  • current block height

wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, the STF version is cached in the "Consensus Parameters Provider", so we do not read from the DB every time, but handling of the current block height remains unchanged from the original implementation.

@rafal-ch rafal-ch marked this pull request as ready for review February 14, 2025 11:52
@rafal-ch
Copy link
Contributor Author

We're currently attaching some substantial number of bytes to every response. Even if user requests just the node version, he will get a bunch of additional data, which, AFAIK, cannot be opted-out from.

{
  "data": {
    "nodeInfo": {
      "nodeVersion": "0.41.6"
    }
  },
  "extensions": {
    "current_consensus_parameters_version": 0,
    "current_fuel_block_height": 0,
    "current_stf_version": 21
  }
}

Not sure if this should be a concern, but I thought it's worth pointing out.

@rymnc
Copy link
Member

rymnc commented Feb 14, 2025

We're currently attaching some substantial number of bytes to every response. Even if user requests just the node version, he will get a bunch of additional data, which, AFAIK, cannot be opted-out from.

{
  "data": {
    "nodeInfo": {
      "nodeVersion": "0.41.6"
    }
  },
  "extensions": {
    "current_consensus_parameters_version": 0,
    "current_fuel_block_height": 0,
    "current_stf_version": 21
  }
}

Not sure if this should be a concern, but I thought it's worth pointing out.

we miss the whole point of graphql here, if we provide data no one asked for :(

@rafal-ch
Copy link
Contributor Author

rafal-ch commented Feb 14, 2025

we miss the whole point of graphql here, if we provide data no one asked for :(

Exactly, that's why I was thinking that we should just provide this data via some info endpoint, but IIUC, the idea here is to provide this data proactively instead of requiring users to poll regularly.

Maybe there's a way to opt out from extensions which I'm just missing.

Comment on lines 293 to 295
.extension(RequiredFuelBlockHeightExtension::new())
.extension(CurrentStfVersionExtension::new())
.extension(CurrentConsensusParametersVersionExtension::new())
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think spawning a separate extension for the field it is too overkill=D It decreases performance without any reason

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can have one extension fully dedicated to return information to the end user. Like STFV, CPV, current block height(we can remove setting of the current height from the RequiredFuelBlockHeightExtension).

In this case we will have one extension that we can use to add any metadata

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The following changes have been made:

  • Both STF and CP extensions have been merged into "chain state info" extension
  • The "chain state info" extension is now also providing data about the current block height

Comment on lines 44 to 53
if let Ok(view) = db.view() {
if let Ok(latest_block) = view.latest_block() {
let current_stf_version =
latest_block.header().state_transition_bytecode_version();
response.extensions.insert(
CURRENT_STF_VERSION.to_string(),
Value::Number(current_stf_version.into()),
);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to have a place where we cache this kind of information. Maybe, we could extend consensus parameters provider to also provide this kind of information, because fetch it from the database it a lot of waste work.

vec![op::ret(1)].into_iter().collect::<Vec<u8>>()
}

pub fn get_graphql_extension_field_value(result_json: &str, name: &str) -> u64 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

fuel-core-client should fetch this information in the same way how it is done for the required block height. We have ExtensionsResponse where you can add several new fields.

Implementation details: In the case of the CPV and STFV we don't need any policy, we just can fetch the latest one and provide a getter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in ef9c445

@xgreenx
Copy link
Collaborator

xgreenx commented Feb 14, 2025

we miss the whole point of graphql here, if we provide data no one asked for :(

The extensions field is part of the GraphQL specification, and we can put there any data what we want. The user controls only the data field.

@rafal-ch rafal-ch force-pushed the rafal/2596_additional_graphql_extensions branch from 420319f to 3f3e971 Compare February 17, 2025 12:26
@rafal-ch rafal-ch changed the title Add GraphQL extensions to provide current STF and CP versions Add GraphQL extension to provide current STF and CP versions Feb 17, 2025
@rafal-ch
Copy link
Contributor Author

We need to have a place where we cache this kind of information. Maybe, we could extend consensus parameters provider to also provide this kind of information, because fetch it from the database it a lot of waste work.

Done in: b861a51

Copy link
Collaborator

@xgreenx xgreenx left a comment

Choose a reason for hiding this comment

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

Looks good to mee, small things to change and we are good to go=)

@@ -333,6 +388,8 @@ impl FuelClient {
.await
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

self.update_chain_state_info(&response);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to do it in the Self::decode_response function because we also want to handle the case of subscriptions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 3aa2d2d

Comment on lines 297 to 298
// `RequiredFuelBlockHeightExtension` uses the view set by the ViewExtension.
// Do not reorder this line before adding the `ViewExtension`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// `RequiredFuelBlockHeightExtension` uses the view set by the ViewExtension.
// Do not reorder this line before adding the `ViewExtension`.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3f32362

Comment on lines 213 to 215
let current_stf_version = block.header().state_transition_bytecode_version();
self.stf_parameters_provider
.cache_stf_version(current_stf_version);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can have this login inside of the ConsensusParametersProvider. We don't need to teach off-chain worker to provide this information=)

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, that was a bit of a code smell since the beginning.
Updated in 932a1c5

@@ -117,7 +117,7 @@ impl MessageCoin {
async fn asset_id(&self, ctx: &Context<'_>) -> AssetId {
let params = ctx
.data_unchecked::<ConsensusProvider>()
.latest_consensus_params();
.current_consensus_params();
Copy link
Collaborator

Choose a reason for hiding this comment

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

May I also ask you to replace Mutex in the ConsensusProvider with RwLock please?=)

Since we only read in most of the cases and write happens once per month

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in d8ea573

Comment on lines 42 to 44
pub(crate) consensus_parameters:
SharedMutex<HashMap<ConsensusParametersVersion, Arc<ConsensusParameters>>>,
pub(crate) latest_stf_version: SharedMutex<StateTransitionBytecodeVersion>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

May I also ask you to replace Mutex with RwLock for consensus_parameters and latest_stf_version please?=)

Since we only read in most of the cases and write happens once per month

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in d8ea573

Comment on lines 406 to 409
fn decode_response<R, E>(
&self,
response: FuelGraphQlResponse<R, E>,
inner_required_height: Option<Arc<Mutex<Option<BlockHeight>>>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you are able to use &self, then you can remove inner_required_height(move logic inside update_chain_state_info) and update everything in one function=)

Comment on lines 179 to 183
if !update_cached_value(
&self.shared_state.latest_consensus_parameters_version,
new_consensus_parameters_version,
|| self.shared_state.cache_consensus_parameters(new_consensus_parameters_version).map(|_| ())
) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You have a deadlock here. self.shared_state.cache_consensus_parameters is doing write lock inside and update_cached_value is doing upgradable lock

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.

Include the version of the STF and consensus parameters versions in all GQL responses via extensions
4 participants