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

RPC Consistency Proposal #2473

Merged
merged 78 commits into from
Feb 12, 2025
Merged

RPC Consistency Proposal #2473

merged 78 commits into from
Feb 12, 2025

Conversation

acerone85
Copy link
Contributor

@acerone85 acerone85 commented Dec 5, 2024

Linked Issues/PRs

Related issue: #1897
Closes #2605

See https://www.notion.so/fuellabs/RPC-Consistency-Proposal-V2-13b2f2293f31809bbce0d93a4c28d633 for the proposal specs.

This PR implements a variation of the proposal in the link, due to techincal limitations of the HTTP protocol when using graphql subscriptions.

for context: Headers cannot be used, the reason being that subscriptions will return multiple graphql responses in a single HTTP response (and therefore have a single HeaderMap), while we require that each item in the response has its associated CURRENT_FUEL_BLOCK_HEIGHT field. Moreover the Response metadata for subscriptions is returned before the actual response body (as it is standard with HTTP).

in the end graphql has a (very undocumented) way to add an extensions field to responses, at the same level of data/errors field. This does not require to tweak anything at the HTTP level.

We will implement an improvement where the server node will wait for a configurable number of blocks before returning a PRECONDITION FAILED response, in a follow-up PR.

Entrypoint for reviews:

Manual testing

TODO:

  • Tests

Description

Checklist

  • Breaking changes are clearly marked as such in the PR description and changelog
  • New behavior is reflected in tests
  • The specification matches the implemented behavior (link update PR if changes are needed)

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

[Add or remove entries as needed]

@acerone85 acerone85 requested a review from Voxelot December 5, 2024 12:09
@acerone85 acerone85 linked an issue Dec 5, 2024 that may be closed by this pull request
@acerone85 acerone85 requested a review from a team December 5, 2024 12:09
@acerone85 acerone85 self-assigned this Dec 5, 2024
crates/client/src/client.rs Outdated Show resolved Hide resolved
netrome
netrome previously approved these changes Dec 5, 2024
Copy link
Contributor

@netrome netrome left a comment

Choose a reason for hiding this comment

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

Nice stuff! I have some thoughts and comments, but no showstoppers.

crates/client/src/client.rs Outdated Show resolved Hide resolved
crates/client/src/client.rs Outdated Show resolved Hide resolved
crates/client/src/client.rs Outdated Show resolved Hide resolved
crates/fuel-core/src/graphql_api/worker_service.rs Outdated Show resolved Hide resolved
crates/fuel-core/src/graphql_api/api_service.rs Outdated Show resolved Hide resolved
crates/fuel-core/src/graphql_api/api_service.rs Outdated Show resolved Hide resolved
crates/fuel-core/src/graphql_api/api_service.rs Outdated Show resolved Hide resolved
@Torres-ssf
Copy link

Hi @acerone85,

Could you kindly confirm if the response header for subscriptions is also intended to include current_fuel_block_height?

@acerone85
Copy link
Contributor Author

Hi @acerone85,

Could you kindly confirm if the response header for subscriptions is also intended to include current_fuel_block_height?

In the current implementation there is no response header for the current_fuel_block_height. Would it be desirable to have one?

@acerone85 acerone85 force-pushed the 1897-rpc-consistency-proposal branch from 1219775 to cf656bf Compare February 11, 2025 11:47
xgreenx
xgreenx previously approved these changes Feb 11, 2025
/// #
/// # #[derive(cynic::QueryFragment)]
/// # #[cynic(
/// # schema_path = "../schemas/starwars.schema.graphql",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

would be nice to have an example related to how we use the reqwest_ext module, but not a big deal at this point :)

Comment on lines +53 to +57
#[cfg(not(target_arch = "wasm32"))]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

#[cfg(target_arch = "wasm32")]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

curious to know why the Send trait bound is not available/needed on the wasm32 architecture

Copy link
Contributor

Choose a reason for hiding this comment

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

TIL: It seems like it's a general assumption that the wasm32-unknown-unknown target is single-threaded https://users.rust-lang.org/t/multithreading-in-wasm-how-did-that-come-out/87518 - although there seem to be work to implement multi-threading in WASM coming up in the future. Interesting.

Copy link
Contributor

Choose a reason for hiding this comment

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

(and if the target is single-threaded you should not have to require Send anywhere)

Comment on lines +127 to +134
impl<ResponseData, Errors> CynicReqwestBuilder<ResponseData, Errors> {
pub fn new(builder: reqwest::RequestBuilder) -> Self {
Self {
builder,
_marker: std::marker::PhantomData,
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

nit: is impl<ResponseData, Errors> From<reqwest::RequestBuilder>for CynicReqwestBuilder<ResponseData, Errors> more idiomatic?

return Err(response);
};

Ok(deserred)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

just wanted to check, we return Ok(_) even if the status is not 2xx?

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 it makes sense if we reserve the Error status for actual connection/deserialization errors, but wanted to check)

#[derive(Debug, Clone, serde::Deserialize)]
pub struct ExtensionsResponse {
pub required_fuel_block_height: Option<BlockHeight>,
pub current_fuel_block_height: Option<BlockHeight>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

nit: I think we require that this field is set in all graphql responses


#[derive(Debug, Clone, serde::Deserialize)]
pub struct ExtensionsResponse {
pub required_fuel_block_height: Option<BlockHeight>,
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 don't set this field in the response in the graphql extension. But happy to add it if needed

) -> &mut Self {
match &mut self.require_height {
ConsistencyPolicy::Auto { height } => {
*height.lock().expect("Mutex poisoned") = new_height;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should we use parking_lot Mutex, or use directly a AtomicU32 and avoid a Mutex altogether?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need locks for this at all? We have a mutable reference to self here already.

Copy link
Contributor

Choose a reason for hiding this comment

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

So I see that we need some type of interior mutability since the query function takes an immutable self receiver. However, I think it would be cleaner to change the signature of the query function to take a mutable self receiver.

Since that's a breaking change, how about we leave this as-is for now but create a follow-up to simplify this which we can include in the next breaking release?

Copy link
Contributor

@netrome netrome left a comment

Choose a reason for hiding this comment

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

The code seems correct to me, but I'm pretty annoyed by the Option<Arc<Mutex>> and would love to get rid of it as soon as possible. I understand that we might need some sort of interior mutability to preserve backwards compatibility in the client though.

) -> &mut Self {
match &mut self.require_height {
ConsistencyPolicy::Auto { height } => {
*height.lock().expect("Mutex poisoned") = new_height;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need locks for this at all? We have a mutable reference to self here already.

Comment on lines +344 to +347
fn decode_response<R, E>(
response: FuelGraphQlResponse<R, E>,
inner_required_height: Option<Arc<Mutex<Option<BlockHeight>>>>,
) -> io::Result<R>
Copy link
Contributor

Choose a reason for hiding this comment

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

While this is okay, I think it would be cleaner to separate the height extraction to a separate extract_required_height function.

) -> &mut Self {
match &mut self.require_height {
ConsistencyPolicy::Auto { height } => {
*height.lock().expect("Mutex poisoned") = new_height;
Copy link
Contributor

Choose a reason for hiding this comment

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

So I see that we need some type of interior mutability since the query function takes an immutable self receiver. However, I think it would be cleaner to change the signature of the query function to take a mutable self receiver.

Since that's a breaking change, how about we leave this as-is for now but create a follow-up to simplify this which we can include in the next breaking release?

Comment on lines +53 to +57
#[cfg(not(target_arch = "wasm32"))]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

#[cfg(target_arch = "wasm32")]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

TIL: It seems like it's a general assumption that the wasm32-unknown-unknown target is single-threaded https://users.rust-lang.org/t/multithreading-in-wasm-how-did-that-come-out/87518 - although there seem to be work to implement multi-threading in WASM coming up in the future. Interesting.

Comment on lines +53 to +57
#[cfg(not(target_arch = "wasm32"))]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

#[cfg(target_arch = "wasm32")]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

(and if the target is single-threaded you should not have to require Send anywhere)

@acerone85 acerone85 enabled auto-merge (squash) February 12, 2025 20:18
@acerone85 acerone85 merged commit f73a12e into master Feb 12, 2025
33 checks passed
@acerone85 acerone85 deleted the 1897-rpc-consistency-proposal branch February 12, 2025 20:43
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.

Extend fuel-client queries to return the "extensions" map from responses RPC Consistency Proposal
5 participants