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

Estimate gas price API Endpoint #1650

Merged
merged 18 commits into from
Feb 9, 2024
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Description of the upcoming release here.

### Changed

- [#1650](https://github.com/FuelLabs/fuel-core/pull/1650): Add api endpoint for getting estimates for future gas prices
- [#1649](https://github.com/FuelLabs/fuel-core/pull/1649): Add api endpoint for getting latest gas price
- [#1600](https://github.com/FuelLabs/fuel-core/pull/1640): Upgrade to fuel-vm 0.45.0
- [#1633](https://github.com/FuelLabs/fuel-core/pull/1633): Notify services about importing of the genesis block.
- [#1625](https://github.com/FuelLabs/fuel-core/pull/1625): Making relayer independent from the executor and preparation for the force transaction inclusion.
Expand Down
11 changes: 11 additions & 0 deletions crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ type DryRunTransactionExecutionStatus {

union DryRunTransactionStatus = DryRunSuccessStatus | DryRunFailureStatus

type EstimateGasPrice {
gasPrice: U64!
}

input ExcludeInput {
"""
Utxos to exclude from the selection.
Expand Down Expand Up @@ -518,6 +522,11 @@ type InputMessage {
}


type LatestGasPrice {
gasPrice: U64!
blockHeight: U32!
}

type LightOperation {
base: U64!
unitsPerGas: U64!
Expand Down Expand Up @@ -806,6 +815,8 @@ type Query {
contractBalance(contract: ContractId!, asset: AssetId!): ContractBalance!
contractBalances(filter: ContractBalanceFilterInput!, first: Int, after: String, last: Int, before: String): ContractBalanceConnection!
nodeInfo: NodeInfo!
latestGasPrice: LatestGasPrice!
estimateGasPrice(blockHorizon: U32): EstimateGasPrice!
message(nonce: Nonce!): Message
messages(owner: Address, first: Int, after: String, last: Int, before: String): MessageConnection!
messageProof(transactionId: TransactionId!, nonce: Nonce!, commitBlockId: BlockId, commitBlockHeight: U32): MessageProof
Expand Down
15 changes: 15 additions & 0 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use crate::client::{
SpendQueryElementInput,
},
contract::ContractBalanceQueryArgs,
gas_price::EstimateGasPrice,
message::MessageStatusArgs,
tx::DryRunArg,
Tai64Timestamp,
TransactionId,
},
types::{
gas_price::LatestGasPrice,
message::MessageStatus,
primitives::{
Address,
Expand Down Expand Up @@ -348,6 +350,19 @@ impl FuelClient {
self.query(query).await.map(|r| r.node_info.into())
}

pub async fn latest_gas_price(&self) -> io::Result<LatestGasPrice> {
let query = schema::gas_price::QueryLatestGasPrice::build(());
self.query(query).await.map(|r| r.latest_gas_price.into())
}

pub async fn estimate_gas_price(
&self,
block_horizon: u32,
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make more sense to accept block_horizon as an instance of BlockHorizon here, rather than converting in the function itself? Or maybe some T that can .into() into a BlockHorizon?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't have a strong opinion either way, so I went with the precedence that had already been set, for example with client's endpoint block_by_height

pub async fn block_by_height(&self, height: u32) -> io::Result<Option<types::Block>> {
        let query = schema::block::BlockByHeightQuery::build(BlockByHeightArgs {
            height: Some(U32(height)),
        });

        let block = self.query(query).await?.block.map(Into::into);

        Ok(block)
    }

) -> io::Result<EstimateGasPrice> {
let query = schema::gas_price::QueryEstimateGasPrice::build(block_horizon.into());
self.query(query).await.map(|r| r.estimate_gas_price)
}

pub async fn connected_peers_info(&self) -> io::Result<Vec<PeerInfo>> {
let query = schema::node_info::QueryPeersInfo::build(());
self.query(query)
Expand Down
2 changes: 2 additions & 0 deletions crates/client/src/client/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub mod coins;
pub mod contract;
pub mod message;
pub mod node_info;

pub mod gas_price;
pub mod primitives;
pub mod tx;

Expand Down
68 changes: 68 additions & 0 deletions crates/client/src/client/schema/gas_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::client::schema::{
schema,
U32,
U64,
};

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct LatestGasPrice {
pub gas_price: U64,
pub block_height: U32,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "Query")]
pub struct QueryLatestGasPrice {
pub latest_gas_price: LatestGasPrice,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct EstimateGasPrice {
pub gas_price: U64,
}

#[derive(cynic::QueryVariables, Debug)]
pub struct BlockHorizonArgs {
pub block_horizon: Option<U32>,
}

impl From<u32> for BlockHorizonArgs {
fn from(block_horizon: u32) -> Self {
Self {
block_horizon: Some(block_horizon.into()),
}
}
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
variables = "BlockHorizonArgs"
)]
pub struct QueryEstimateGasPrice {
#[arguments(blockHorizon: $block_horizon)]
pub estimate_gas_price: EstimateGasPrice,
}

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

#[test]
fn latest_gas_price_query_gql_output() {
use cynic::QueryBuilder;
let operation = QueryLatestGasPrice::build(());
insta::assert_snapshot!(operation.query)
}

#[test]
fn estimate_gas_price_query_gql_output() {
use cynic::QueryBuilder;
let arbitrary_horizon = 10;
let operation = QueryEstimateGasPrice::build(arbitrary_horizon.into());
insta::assert_snapshot!(operation.query)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: crates/client/src/client/schema/gas_price.rs
expression: operation.query
---
query($blockHorizon: U32) {
estimateGasPrice(blockHorizon: $blockHorizon) {
gasPrice
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/client/src/client/schema/gas_price.rs
expression: operation.query
---
query {
latestGasPrice {
gasPrice
blockHeight
}
}


2 changes: 2 additions & 0 deletions crates/client/src/client/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pub mod chain_info;
pub mod coins;
pub mod contract;
pub mod gas_costs;

pub mod gas_price;
pub mod merkle_proof;
pub mod message;
pub mod node_info;
Expand Down
28 changes: 28 additions & 0 deletions crates/client/src/client/types/gas_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::client::schema;

pub struct LatestGasPrice {
pub gas_price: u64,
pub block_height: u32,
}

// GraphQL Translation
impl From<schema::gas_price::LatestGasPrice> for LatestGasPrice {
fn from(value: schema::gas_price::LatestGasPrice) -> Self {
Self {
gas_price: value.gas_price.into(),
block_height: value.block_height.into(),
}
}
}

pub struct EstimateGasPrice {
pub gas_price: u64,
}

impl From<schema::gas_price::EstimateGasPrice> for EstimateGasPrice {
fn from(value: schema::gas_price::EstimateGasPrice) -> Self {
Self {
gas_price: value.gas_price.into(),
}
}
}
4 changes: 4 additions & 0 deletions crates/fuel-core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub mod dap;
pub mod health;
pub mod message;
pub mod node_info;

pub mod gas_price;
pub mod scalars;
pub mod tx;

Expand All @@ -43,6 +45,8 @@ pub struct Query(
contract::ContractQuery,
contract::ContractBalanceQuery,
node_info::NodeQuery,
gas_price::LatestGasPriceQuery,
gas_price::EstimateGasPriceQuery,
message::MessageQuery,
);

Expand Down
89 changes: 89 additions & 0 deletions crates/fuel-core/src/schema/gas_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use super::scalars::{
U32,
U64,
};
use crate::{
fuel_core_graphql_api::{
database::ReadView,
Config as GraphQLConfig,
},
query::BlockQueryData,
};
use async_graphql::{
Context,
Object,
};
use fuel_core_types::blockchain::block::Block;

pub struct LatestGasPrice {
pub gas_price: U64,
pub block_height: U32,
}

#[Object]
impl LatestGasPrice {
async fn gas_price(&self) -> U64 {
self.gas_price
}

async fn block_height(&self) -> U32 {
self.block_height
}
}

#[derive(Default)]
pub struct LatestGasPriceQuery {}

#[Object]
impl LatestGasPriceQuery {
async fn latest_gas_price(
&self,
ctx: &Context<'_>,
) -> async_graphql::Result<LatestGasPrice> {
let config = ctx.data_unchecked::<GraphQLConfig>();

let query: &ReadView = ctx.data_unchecked();
let latest_block: Block<_> = query.latest_block()?;
let block_height = u32::from(*latest_block.header().height());

Ok(LatestGasPrice {
gas_price: config.min_gas_price.into(),
block_height: block_height.into(),
})
}
}

pub struct EstimateGasPrice {
pub gas_price: U64,
}

#[Object]
impl EstimateGasPrice {
async fn gas_price(&self) -> U64 {
self.gas_price
}
}

#[derive(Default)]
pub struct EstimateGasPriceQuery {}

#[Object]
impl EstimateGasPriceQuery {
async fn estimate_gas_price(
&self,
ctx: &Context<'_>,
#[graphql(
desc = "Number of blocks into the future to estimate the gas price for"
)]
block_horizon: Option<U32>,
) -> async_graphql::Result<EstimateGasPrice> {
// TODO: implement dynamic calculation based on block horizon
// https://github.com/FuelLabs/fuel-core/issues/1653
let _ = block_horizon;

let config = ctx.data_unchecked::<GraphQLConfig>();
let gas_price = config.min_gas_price.into();

Ok(EstimateGasPrice { gas_price })
}
}
32 changes: 32 additions & 0 deletions tests/tests/gas_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use fuel_core::service::{
Config,
FuelService,
};
use fuel_core_client::client::{
schema::gas_price::EstimateGasPrice,
types::gas_price::LatestGasPrice,
FuelClient,
};

#[tokio::test]
async fn latest_gas_price() {
let node_config = Config::local_node();
let srv = FuelService::new_node(node_config.clone()).await.unwrap();
let client = FuelClient::from(srv.bound_address);

let LatestGasPrice { gas_price, .. } = client.latest_gas_price().await.unwrap();
assert_eq!(gas_price, node_config.txpool.min_gas_price);
}

#[tokio::test]
async fn estimate_gas_price() {
let node_config = Config::local_node();
let srv = FuelService::new_node(node_config.clone()).await.unwrap();
let client = FuelClient::from(srv.bound_address);

let arbitrary_horizon = 10;

let EstimateGasPrice { gas_price } =
client.estimate_gas_price(arbitrary_horizon).await.unwrap();
assert_eq!(u64::from(gas_price), node_config.txpool.min_gas_price);
}
2 changes: 2 additions & 0 deletions tests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod dap;
mod debugger;
mod deployment;
mod fee_collection_contract;

mod gas_price;
mod health;
mod helpers;
mod messages;
Expand Down