Skip to content
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a8f5f41
Start structuring middleware
acerone85 Dec 5, 2024
778dd7c
Use a static atomic to keep track of the current block height
acerone85 Dec 5, 2024
d1b1c33
Add changelog entry
acerone85 Dec 5, 2024
b77506c
Add tests and fix middleware
acerone85 Dec 5, 2024
b764cd4
Update crates/fuel-core/src/graphql_api/api_service.rs
acerone85 Dec 6, 2024
d9091e6
Set headers in fuel client
acerone85 Dec 6, 2024
90c411f
AxumRequest/AxumResponse -> http::Request/http::Response
acerone85 Dec 6, 2024
625bf8f
Fix compilation
acerone85 Dec 6, 2024
085d173
Extension instead of Axum middleware
acerone85 Dec 7, 2024
a98ef9b
Merge branch 'master' into 1897-rpc-consistency-proposal
xgreenx Dec 12, 2024
c95f09a
Add response header
acerone85 Dec 12, 2024
6099651
Better handling of response
acerone85 Dec 14, 2024
3b81537
Use BlockHeight type when possible
acerone85 Dec 14, 2024
11e6876
Fix compilation error
acerone85 Dec 14, 2024
88b9c52
Use Request data to retrieve required block height
acerone85 Dec 14, 2024
c5f82ff
Remove useless .into
acerone85 Dec 15, 2024
844e4e6
Move up const definitions
acerone85 Dec 15, 2024
71f8087
Remove useless .into()
acerone85 Dec 16, 2024
cbea694
Inject request data in subscription handler
acerone85 Dec 16, 2024
da7f057
Improvements
acerone85 Dec 16, 2024
f5e63f6
Add documentation
acerone85 Dec 16, 2024
5f4500b
Do not fetch the read view twice
acerone85 Dec 16, 2024
37dd63e
Remove commented code
acerone85 Dec 16, 2024
9913620
Typo
acerone85 Dec 16, 2024
f849eb5
Update crates/fuel-core/src/graphql_api/api_service.rs
acerone85 Dec 16, 2024
1fddd80
Improve setting headers in client
acerone85 Dec 16, 2024
e1f71d4
Revert to setting header in graphql extension when possible
acerone85 Dec 16, 2024
be7f927
Update comment
acerone85 Dec 16, 2024
0b1e31f
Remove use of Arc<Mutex<_>>
acerone85 Dec 16, 2024
8e1b0a5
Remove stray comment
acerone85 Dec 16, 2024
abe13c2
WIP
acerone85 Dec 16, 2024
eae1e22
Example of how it can be implemented via `extensions`
xgreenx Dec 16, 2024
0a3da1f
Merge branch 'master' into 1897-rpc-consistency-proposal
acerone85 Dec 18, 2024
e7e1db7
Add current height header on failed requests
acerone85 Dec 18, 2024
6b76769
Return current level after request has been executed
acerone85 Dec 19, 2024
b6f206d
WIP: Use only graphql extensions: tests still to be adjusted
acerone85 Dec 31, 2024
2f52aaa
Merge branch 'master' into 1897-rpc-consistency-proposal
acerone85 Jan 20, 2025
cd3be50
Add capability to the client to set required_fuel_block_height + fix …
acerone85 Jan 20, 2025
dbc5cdc
Fix other tests
acerone85 Jan 20, 2025
c5db12f
Rename test file
acerone85 Jan 20, 2025
24b8d69
Fix BlockHeight base format in extension error
acerone85 Jan 20, 2025
4a8d839
Fix typo
acerone85 Jan 21, 2025
51ec4a9
Merge branch 'master' into 1897-rpc-consistency-proposal
acerone85 Jan 21, 2025
37b03f1
Fix compilation error after rename
acerone85 Jan 21, 2025
396a953
Reference follow-up issue
acerone85 Jan 21, 2025
af0c6d6
Fix rustfmt
acerone85 Jan 21, 2025
b1e89d6
Fix subscriptions feature
acerone85 Jan 21, 2025
6e50f3b
Use HashMap::new instead of Default::default
acerone85 Jan 21, 2025
dcec472
Downgrade netlink-proto
acerone85 Jan 21, 2025
8c6af1f
Revert "Downgrade netlink-proto"
acerone85 Jan 21, 2025
818a717
Merge branch 'master' into 1897-rpc-consistency-proposal
acerone85 Jan 21, 2025
0717b2d
Merge branch 'master' into 1897-rpc-consistency-proposal
acerone85 Feb 3, 2025
6397726
Update crates/client/src/client.rs
acerone85 Feb 3, 2025
f6c9e70
Update crates/client/src/client.rs
acerone85 Feb 3, 2025
a87f998
Update crates/fuel-core/src/graphql_api/api_service.rs
acerone85 Feb 3, 2025
27cebde
Update crates/fuel-core/src/graphql_api/api_service.rs
acerone85 Feb 3, 2025
3bd9b96
Update crates/fuel-core/src/graphql_api/api_service.rs
acerone85 Feb 3, 2025
e41b8ab
Update tests/tests/required_fuel_block_height_extension.rs
acerone85 Feb 3, 2025
ee36ce1
Update tests/tests/required_fuel_block_height_extension.rs
acerone85 Feb 3, 2025
d479306
Address comments
acerone85 Feb 3, 2025
14fcc5f
Merge remote-tracking branch 'origin/master' into 1897-rpc-consistenc…
acerone85 Feb 4, 2025
3429123
Fix conflicts carried over
acerone85 Feb 4, 2025
bf3f85d
Fix no-default-features compilation
acerone85 Feb 4, 2025
2972279
Update changelog
acerone85 Feb 4, 2025
1219775
Merge branch 'refs/heads/master' into 1897-rpc-consistency-proposal
xgreenx Feb 7, 2025
cf656bf
Merge branch 'master' into 1897-rpc-consistency-proposal
acerone85 Feb 11, 2025
e630285
Test precondition failed from extensions field
acerone85 Feb 11, 2025
2435cfe
Remove newline
acerone85 Feb 11, 2025
385eda2
Merge branch 'master' into 1897-rpc-consistency-proposal
xgreenx Feb 11, 2025
703bc24
Merge remote-tracking branch 'origin/1897-rpc-consistency-proposal' i…
xgreenx Feb 11, 2025
511431f
Fixed CHANGELOG.md
xgreenx Feb 11, 2025
48d508d
Implement required block height support on the `fuel-core-client` side
xgreenx Feb 11, 2025
461fbe7
Merge branch 'master' into 1897-rpc-consistency-proposal
xgreenx Feb 11, 2025
af2484a
Make CI happy
xgreenx Feb 12, 2025
44cffec
Added more tests
xgreenx Feb 12, 2025
5cb1639
Use random determenistic port of compatibility tests
xgreenx Feb 12, 2025
eb25459
Merge branch 'master' into 1897-rpc-consistency-proposal
xgreenx Feb 12, 2025
0f6f319
Merge branch 'master' into 1897-rpc-consistency-proposal
xgreenx Feb 12, 2025
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 @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Changed

- [2473](https://github.com/FuelLabs/fuel-core/pull/2473): Graphql requests and responses make use of a new `extensions` object to specify request/response metadata. A request `extensions` object can contain an integer-valued `required_fuel_block_height` field. When specified, the request will return an error unless the node's current fuel block height is at least the value specified in the `required_fuel_block_height` field. All graphql responses now contain an integer-valued `current_fuel_block_height` field in the `extensions` object, which contains the block height of the last block processed by the node.
- [2653](https://github.com/FuelLabs/fuel-core/pull/2653): Added cleaner error for wasm-executor upon failed deserialization.


Expand Down
212 changes: 174 additions & 38 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
#[cfg(feature = "subscriptions")]
use crate::client::types::StatusWithTransaction;
use crate::client::{
schema::{
block::BlockByHeightArgs,
coins::{
ExcludeInput,
SpendQueryElementInput,
use crate::{
client::{
schema::{
block::BlockByHeightArgs,
coins::{
ExcludeInput,
SpendQueryElementInput,
},
contract::ContractBalanceQueryArgs,
gas_price::EstimateGasPrice,
message::MessageStatusArgs,
relayed_tx::RelayedTransactionStatusArgs,
tx::DryRunArg,
Tai64Timestamp,
TransactionId,
},
contract::ContractBalanceQueryArgs,
gas_price::EstimateGasPrice,
message::MessageStatusArgs,
relayed_tx::RelayedTransactionStatusArgs,
tx::DryRunArg,
Tai64Timestamp,
TransactionId,
},
types::{
asset::AssetDetail,
gas_price::LatestGasPrice,
message::MessageStatus,
primitives::{
Address,
AssetId,
BlockId,
ContractId,
UtxoId,
types::{
asset::AssetDetail,
gas_price::LatestGasPrice,
message::MessageStatus,
primitives::{
Address,
AssetId,
BlockId,
ContractId,
UtxoId,
},
upgrades::StateTransitionBytecode,
RelayedTransactionStatus,
},
upgrades::StateTransitionBytecode,
RelayedTransactionStatus,
},
reqwest_ext::{
FuelGraphQlResponse,
FuelOperation,
ReqwestExt,
},
};
use anyhow::Context;
Expand All @@ -39,8 +46,6 @@ use base64::prelude::{
#[cfg(feature = "subscriptions")]
use cynic::StreamingOperation;
use cynic::{
http::ReqwestExt,
GraphQlResponse,
Id,
MutationBuilder,
Operation,
Expand Down Expand Up @@ -129,6 +134,10 @@ use std::{
self,
FromStr,
},
sync::{
Arc,
Mutex,
},
};
use tai64::Tai64;
use tracing as _;
Expand All @@ -151,12 +160,58 @@ pub mod types;

type RegisterId = u32;

#[derive(Debug, derive_more::Display, derive_more::From)]
#[non_exhaustive]
/// Error occurring during interaction with the FuelClient
// anyhow::Error is wrapped inside a custom Error type,
// so that we can specific error variants in the future.
pub enum Error {
/// Unknown or not expected(by architecture) error.
#[from]
Other(anyhow::Error),
}

/// Consistency policy for the [`FuelClient`] to define the strategy
/// for the required height feature.
#[derive(Debug)]
pub enum ConsistencyPolicy {
/// Automatically fetch the next block height from the response and
/// use it as an input to the next query to guarantee consistency
/// of the results for the queries.
Auto {
/// The required block height for the queries.
height: Arc<Mutex<Option<BlockHeight>>>,
},
/// Use manually sets the block height for all queries
/// via the [`FuelClient::with_required_fuel_block_height`].
Manual {
/// The required block height for the queries.
height: Option<BlockHeight>,
},
}

impl Clone for ConsistencyPolicy {
fn clone(&self) -> Self {
match self {
Self::Auto { height } => Self::Auto {
// We don't want to share the same mutex between the different
// instances of the `FuelClient`.
height: Arc::new(Mutex::new(height.lock().ok().and_then(|h| *h))),
},
Self::Manual { height } => Self::Manual {
height: height.clone(),
},
}
}
}

#[derive(Debug, Clone)]
pub struct FuelClient {
client: reqwest::Client,
#[cfg(feature = "subscriptions")]
cookie: std::sync::Arc<reqwest::cookie::Jar>,
url: reqwest::Url,
require_height: ConsistencyPolicy,
}

impl FromStr for FuelClient {
Expand Down Expand Up @@ -184,13 +239,20 @@ impl FromStr for FuelClient {
client,
cookie,
url,
require_height: ConsistencyPolicy::Auto {
height: Arc::new(Mutex::new(None)),
},
})
}

#[cfg(not(feature = "subscriptions"))]
{
let client = reqwest::Client::new();
Ok(Self { client, url })
Ok(Self {
client,
url,
extensions: HashMap::new(),
})
}
}
}
Expand Down Expand Up @@ -223,6 +285,36 @@ impl FuelClient {
Self::from_str(url.as_ref())
}

pub fn with_required_fuel_block_height(
&mut self,
new_height: Option<BlockHeight>,
) -> &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?

}
ConsistencyPolicy::Manual { height } => {
*height = new_height;
}
}
self
}

pub fn use_manual_consistency_policy(
&mut self,
height: Option<BlockHeight>,
) -> &mut Self {
self.require_height = ConsistencyPolicy::Manual { height };
self
}

pub fn required_block_height(&self) -> Option<BlockHeight> {
match &self.require_height {
ConsistencyPolicy::Auto { height } => height.lock().ok().and_then(|h| *h),
ConsistencyPolicy::Manual { height } => *height,
}
}

/// Send the GraphQL query to the client.
pub async fn query<ResponseData, Vars>(
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about fn subscribe? We also want to support setting heights for the submit_and_await_commit like queries

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 d479306

&self,
Expand All @@ -232,20 +324,53 @@ impl FuelClient {
Vars: serde::Serialize,
ResponseData: serde::de::DeserializeOwned + 'static,
{
let required_fuel_block_height = self.required_block_height();
let fuel_operation = FuelOperation::new(q, required_fuel_block_height);
let response = self
.client
.post(self.url.clone())
.run_graphql(q)
.run_fuel_graphql(fuel_operation)
.await
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

Self::decode_response(response)
let inner_required_height = match &self.require_height {
ConsistencyPolicy::Auto { height } => Some(height.clone()),
_ => None,
};

Self::decode_response(response, inner_required_height)
}

fn decode_response<R>(response: GraphQlResponse<R>) -> io::Result<R>
fn decode_response<R, E>(
response: FuelGraphQlResponse<R, E>,
inner_required_height: Option<Arc<Mutex<Option<BlockHeight>>>>,
) -> io::Result<R>
Comment on lines +344 to +347
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.

where
R: serde::de::DeserializeOwned + 'static,
{
if let Some(inner_required_height) = inner_required_height {
if let Some(current_fuel_block_height) =
response.extensions.current_fuel_block_height
{
let mut lock = inner_required_height.lock().expect("Mutex poisoned");

if current_fuel_block_height >= lock.unwrap_or_default() {
*lock = Some(current_fuel_block_height);
}
}
}

if let Some(failed) = response.extensions.fuel_block_height_precondition_failed {
if failed {
return Err(io::Error::new(
io::ErrorKind::Other,
"The required block height was not met",
));
}
}

let response = response.response;

match (response.data, response.errors) {
(Some(d), _) => Ok(d),
(_, Some(e)) => Err(from_strings_errors_to_std_error(
Expand All @@ -271,7 +396,11 @@ impl FuelClient {
use reqwest::cookie::CookieStore;
let mut url = self.url.clone();
url.set_path("/v1/graphql-sub");
let json_query = serde_json::to_string(&q)?;

let required_fuel_block_height = self.required_block_height();
let fuel_operation = FuelOperation::new(q, required_fuel_block_height);

let json_query = serde_json::to_string(&fuel_operation)?;
let mut client_builder = es::ClientBuilder::for_url(url.as_str())
.map_err(|e| {
io::Error::new(
Expand Down Expand Up @@ -329,18 +458,25 @@ impl FuelClient {

let mut last = None;

let inner_required_height = match &self.require_height {
ConsistencyPolicy::Auto { height } => Some(height.clone()),
_ => None,
};

let stream = es::Client::stream(&client)
.take_while(|result| {
.zip(futures::stream::repeat(inner_required_height))
.take_while(|(result, _)| {
futures::future::ready(!matches!(result, Err(es::Error::Eof)))
})
.filter_map(move |result| {
.filter_map(move |(result, inner_required_height)| {
tracing::debug!("Got result: {result:?}");
let r = match result {
Ok(es::SSE::Event(es::Event { data, .. })) => {
match serde_json::from_str::<GraphQlResponse<ResponseData>>(&data)
{
match serde_json::from_str::<FuelGraphQlResponse<ResponseData>>(
&data,
) {
Ok(resp) => {
match Self::decode_response(resp) {
match Self::decode_response(resp, inner_required_height) {
Ok(resp) => {
match last.replace(data) {
// Remove duplicates
Expand Down
1 change: 1 addition & 0 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![deny(unused_crate_dependencies)]
#![deny(warnings)]
pub mod client;
pub mod reqwest_ext;
pub mod schema;

/// The GraphQL schema used by the library.
Expand Down
Loading
Loading