Skip to content

Commit

Permalink
Peer API fix for library users of fuel-core (#1542)
Browse files Browse the repository at this point in the history
This PR restructures the changes from #1524 to fix an error that would
block users from calling `client.chain_info()` without having the p2p
feature enabled.

While throwing errors on apis that use disabled features is our current
convention, it would mean that the sdk testing harness would fail when
trying to get the consensus parameters unless it also enabled the p2p
feature flag.

What changed in this PR:

- Information about peers now part of NodeInfo instead of ChainInfo.
This seemed like a more suitable location, as chain info relates more
towards global network settings and state. NodeInfo on the other hand
shows details about the current node instance you've connected to.
- Peers are not returned in the default query for NodeInfo, and instead
use a special query fragment that's used to only return info about
peers. This is beneficial since it will allow users without p2p enabled
to still fetch NodeInfo, and also reduce api bandwidth by only returning
potentially bulky information about peers through a dedicated api
request instead of by default.

*Note: This is a release blocker for 0.22 as it will prevent the sdk
from working properly*
  • Loading branch information
Voxelot authored Dec 12, 2023
1 parent fb6111f commit d134579
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 192 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Description of the upcoming release here.

#### Breaking
- [#1536](https://github.com/FuelLabs/fuel-core/pull/1536): The change fixes the contracts tables to not touch SMT nodes of foreign contracts. Before, it was possible to invalidate the SMT from another contract. It is a breaking change and requires re-calculating the whole state from the beginning with new SMT roots.

- [#1542](https://github.com/FuelLabs/fuel-core/pull/1542): Migrates information about peers to NodeInfo instead of ChainInfo. It also elides information about peers in the default node_info query.

## [Version 0.21.0]

Expand Down
2 changes: 1 addition & 1 deletion crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ type ChainInfo {
name: String!
latestBlock: Block!
daHeight: U64!
peers: [PeerInfo!]!
consensusParameters: ConsensusParameters!
gasCosts: GasCosts!
}
Expand Down Expand Up @@ -613,6 +612,7 @@ type NodeInfo {
maxTx: U64!
maxDepth: U64!
nodeVersion: String!
peers: [PeerInfo!]!
}

scalar Nonce
Expand Down
8 changes: 8 additions & 0 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use fuel_core_types::{
BlockHeight,
Nonce,
},
services::p2p::PeerInfo,
};
#[cfg(feature = "subscriptions")]
use futures::StreamExt;
Expand Down Expand Up @@ -341,6 +342,13 @@ impl FuelClient {
self.query(query).await.map(|r| r.node_info.into())
}

pub async fn connected_peers_info(&self) -> io::Result<Vec<PeerInfo>> {
let query = schema::node_info::QueryPeersInfo::build(());
self.query(query)
.await
.map(|r| r.node_info.peers.into_iter().map(Into::into).collect())
}

pub async fn chain_info(&self) -> io::Result<types::ChainInfo> {
let query = schema::chain::ChainQuery::build(());
self.query(query).await.map(|r| r.chain.into())
Expand Down
12 changes: 0 additions & 12 deletions crates/client/src/client/schema/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,22 +322,10 @@ pub struct ChainQuery {
pub struct ChainInfo {
pub da_height: U64,
pub name: String,
pub peers: Vec<PeerInfo>,
pub latest_block: Block,
pub consensus_parameters: ConsensusParameters,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "PeerInfo")]
pub struct PeerInfo {
pub id: String,
pub addresses: Vec<String>,
pub client_version: Option<String>,
pub block_height: Option<U32>,
pub last_heartbeat_ms: U64,
pub app_score: f64,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
62 changes: 62 additions & 0 deletions crates/client/src/client/schema/node_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
use crate::client::schema::{
schema,
U32,
U64,
};
use fuel_core_types::services::p2p::{
HeartbeatData,
PeerId,
};
use std::{
str::FromStr,
time::{
Duration,
UNIX_EPOCH,
},
};

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
Expand All @@ -20,6 +32,49 @@ pub struct QueryNodeInfo {
pub node_info: NodeInfo,
}

// Use a separate GQL query for showing peer info, as the endpoint is bulky and may return an error
// if the `p2p` feature is disabled.

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "NodeInfo")]
pub struct PeersInfo {
pub peers: Vec<PeerInfo>,
}

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

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct PeerInfo {
pub id: String,
pub addresses: Vec<String>,
pub client_version: Option<String>,
pub block_height: Option<U32>,
pub last_heartbeat_ms: U64,
pub app_score: f64,
}

impl From<PeerInfo> for fuel_core_types::services::p2p::PeerInfo {
fn from(info: PeerInfo) -> Self {
Self {
id: PeerId::from_str(info.id.as_str()).unwrap_or_default(),
peer_addresses: info.addresses.into_iter().collect(),
client_version: info.client_version,
heartbeat_data: HeartbeatData {
block_height: info.block_height.map(|h| h.0.into()),
last_heartbeat: UNIX_EPOCH
.checked_add(Duration::from_millis(info.last_heartbeat_ms.0))
.unwrap_or(UNIX_EPOCH),
},
app_score: info.app_score,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -30,4 +85,11 @@ mod tests {
let operation = QueryNodeInfo::build(());
insta::assert_snapshot!(operation.query)
}

#[test]
fn peers_info_query_gql_output() {
use cynic::QueryBuilder;
let operation = QueryPeersInfo::build(());
insta::assert_snapshot!(operation.query)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ query {
chain {
daHeight
name
peers {
id
addresses
clientVersion
blockHeight
lastHeartbeatMs
appScore
}
latestBlock {
id
header {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: crates/client/src/client/schema/node_info.rs
expression: operation.query
---
query {
nodeInfo {
peers {
id
addresses
clientVersion
blockHeight
lastHeartbeatMs
appScore
}
}
}


32 changes: 1 addition & 31 deletions crates/client/src/client/types/chain_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,13 @@ use crate::client::{
types::Block,
};
use fuel_core_types::{
self,
fuel_tx::ConsensusParameters,
services::p2p::{
HeartbeatData,
PeerId,
PeerInfo,
},
};
use std::{
str::FromStr,
time::{
Duration,
UNIX_EPOCH,
},
};

pub struct ChainInfo {
pub da_height: u64,
pub name: String,
pub peers: Vec<PeerInfo>,
pub latest_block: Block,
pub consensus_parameters: ConsensusParameters,
}
Expand All @@ -33,26 +21,8 @@ impl From<schema::chain::ChainInfo> for ChainInfo {
Self {
da_height: value.da_height.into(),
name: value.name,
peers: value.peers.into_iter().map(|info| info.into()).collect(),
latest_block: value.latest_block.into(),
consensus_parameters: value.consensus_parameters.into(),
}
}
}

impl From<schema::chain::PeerInfo> for PeerInfo {
fn from(info: schema::chain::PeerInfo) -> Self {
Self {
id: PeerId::from_str(info.id.as_str()).unwrap_or_default(),
peer_addresses: info.addresses.into_iter().collect(),
client_version: info.client_version,
heartbeat_data: HeartbeatData {
block_height: info.block_height.map(|h| h.0.into()),
last_heartbeat: UNIX_EPOCH
.checked_add(Duration::from_millis(info.last_heartbeat_ms.0))
.unwrap_or(UNIX_EPOCH),
},
app_score: info.app_score,
}
}
}
61 changes: 0 additions & 61 deletions crates/fuel-core/src/schema/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use async_graphql::{
Union,
};
use fuel_core_types::fuel_tx;
use std::time::UNIX_EPOCH;

pub struct ChainInfo;
pub struct ConsensusParameters(fuel_tx::ConsensusParameters);
Expand Down Expand Up @@ -659,49 +658,6 @@ impl GasCosts {
}
}

struct PeerInfo(fuel_core_types::services::p2p::PeerInfo);

#[Object]
impl PeerInfo {
/// The libp2p peer id
async fn id(&self) -> String {
self.0.id.to_string()
}

/// The advertised multi-addrs that can be used to connect to this peer
async fn addresses(&self) -> Vec<String> {
self.0.peer_addresses.iter().cloned().collect()
}

/// The self-reported version of the client the peer is using
async fn client_version(&self) -> Option<String> {
self.0.client_version.clone()
}

/// The last reported height of the peer
async fn block_height(&self) -> Option<U32> {
self.0
.heartbeat_data
.block_height
.map(|height| (*height).into())
}

/// The last heartbeat from this peer in unix epoch time ms
async fn last_heartbeat_ms(&self) -> U64 {
let time = self.0.heartbeat_data.last_heartbeat;
let time = time
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
U64(time.try_into().unwrap_or_default())
}

/// The internal fuel p2p reputation of this peer
async fn app_score(&self) -> f64 {
self.0.app_score
}
}

#[Object]
impl LightOperation {
async fn base(&self) -> U64 {
Expand Down Expand Up @@ -748,23 +704,6 @@ impl ChainInfo {
height.0.into()
}

async fn peers(&self, _ctx: &Context<'_>) -> anyhow::Result<Vec<PeerInfo>> {
#[cfg(feature = "p2p")]
{
let p2p: &crate::fuel_core_graphql_api::service::P2pService =
_ctx.data_unchecked();
let peer_info = p2p.all_peer_info().await?;
let peers = peer_info.into_iter().map(PeerInfo).collect();
Ok(peers)
}
#[cfg(not(feature = "p2p"))]
{
Err(anyhow::anyhow!(
"Peering is disabled in this build, try using the `p2p` feature flag."
))
}
}

async fn consensus_parameters(
&self,
ctx: &Context<'_>,
Expand Down
Loading

0 comments on commit d134579

Please sign in to comment.