Skip to content

Commit fb696b7

Browse files
author
Adrian Nagy
committed
feat(GraphQL): Add per request context caching + expand daemonStatus endpoint
1 parent c827402 commit fb696b7

File tree

2 files changed

+147
-27
lines changed

2 files changed

+147
-27
lines changed

node/native/src/graphql/constants.rs

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use juniper::GraphQLObject;
2-
use node::rpc::{
3-
ConsensusTimeQuery, PeerConnectionStatus, RpcConsensusTimeGetResponse, RpcPeerInfo, RpcRequest,
2+
use node::{
3+
rpc::{
4+
ConsensusTimeQuery, PeerConnectionStatus, RpcConsensusTimeGetResponse, RpcPeerInfo,
5+
RpcRequest,
6+
},
7+
BuildEnv,
48
};
59
use openmina_core::{
610
consensus::{ConsensusConstants, ConsensusTime},
@@ -19,7 +23,7 @@ impl GraphQLDaemonStatus {
1923
context: &Context,
2024
) -> juniper::FieldResult<GraphQLConsensusConfiguration> {
2125
let consensus_constants: ConsensusConstants = context
22-
.0
26+
.rpc_sender
2327
.oneshot_request(RpcRequest::ConsensusConstantsGet)
2428
.await
2529
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -28,7 +32,7 @@ impl GraphQLDaemonStatus {
2832

2933
async fn peers(&self, context: &Context) -> juniper::FieldResult<Vec<GraphQLRpcPeerInfo>> {
3034
let peers: Vec<RpcPeerInfo> = context
31-
.0
35+
.rpc_sender
3236
.oneshot_request(RpcRequest::PeersGet)
3337
.await
3438
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -47,7 +51,7 @@ impl GraphQLDaemonStatus {
4751
context: &Context,
4852
) -> juniper::FieldResult<GraphQLConsensusTime> {
4953
let consensus_time: RpcConsensusTimeGetResponse = context
50-
.0
54+
.rpc_sender
5155
.oneshot_request(RpcRequest::ConsensusTimeGet(ConsensusTimeQuery::Now))
5256
.await
5357
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -66,7 +70,7 @@ impl GraphQLDaemonStatus {
6670
context: &Context,
6771
) -> juniper::FieldResult<GraphQLConsensusTime> {
6872
let consensus_time_res: RpcConsensusTimeGetResponse = context
69-
.0
73+
.rpc_sender
7074
.oneshot_request(RpcRequest::ConsensusTimeGet(ConsensusTimeQuery::BestTip))
7175
.await
7276
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -79,6 +83,84 @@ impl GraphQLDaemonStatus {
7983
)),
8084
}
8185
}
86+
87+
async fn consensus_mechanism(&self, _context: &Context) -> juniper::FieldResult<String> {
88+
Ok("proof_of_stake".to_string())
89+
}
90+
91+
async fn blockchain_length(&self, context: &Context) -> juniper::FieldResult<Option<i32>> {
92+
let status = context.get_or_fetch_status().await;
93+
94+
Ok(status.and_then(|status| {
95+
status
96+
.transition_frontier
97+
.best_tip
98+
.map(|block_summary| block_summary.height as i32)
99+
}))
100+
}
101+
102+
async fn chain_id(&self, context: &Context) -> juniper::FieldResult<Option<String>> {
103+
let status = context.get_or_fetch_status().await;
104+
105+
Ok(status.and_then(|status| status.chain_id))
106+
}
107+
108+
async fn commit_id(&self, _context: &Context) -> juniper::FieldResult<String> {
109+
Ok(BuildEnv::get().git.commit_hash.to_string())
110+
}
111+
112+
async fn global_slot_since_genesis_best_tip(
113+
&self,
114+
context: &Context,
115+
) -> juniper::FieldResult<Option<i32>> {
116+
let best_tip = context.get_or_fetch_best_tip().await;
117+
Ok(best_tip.and_then(|best_tip| {
118+
println!("best_tip OK");
119+
best_tip.global_slot_since_genesis().try_into().ok()
120+
}))
121+
}
122+
123+
// highestUnvalidatedBlockLengthReceived
124+
async fn highest_unvalidated_block_length_received(
125+
&self,
126+
context: &Context,
127+
) -> juniper::FieldResult<Option<i32>> {
128+
let status = context.get_or_fetch_status().await;
129+
Ok(status.and_then(|status| {
130+
status
131+
.transition_frontier
132+
.best_tip
133+
.map(|best_tip| best_tip.height as i32)
134+
.or_else(|| {
135+
status
136+
.transition_frontier
137+
.sync
138+
.target
139+
.map(|target| target.height as i32)
140+
})
141+
}))
142+
}
143+
144+
//highestBlockLengthReceived
145+
async fn highest_block_length_received(
146+
&self,
147+
context: &Context,
148+
) -> juniper::FieldResult<Option<i32>> {
149+
let status = context.get_or_fetch_status().await;
150+
Ok(status.and_then(|status| {
151+
status
152+
.transition_frontier
153+
.best_tip
154+
.map(|best_tip| best_tip.height as i32)
155+
.or_else(|| {
156+
status
157+
.transition_frontier
158+
.sync
159+
.target
160+
.map(|target| target.height as i32)
161+
})
162+
}))
163+
}
82164
}
83165

84166
#[derive(GraphQLObject, Clone, Debug)]

node/native/src/graphql/mod.rs

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use node::{
1212
RpcGetBlockResponse, RpcPooledUserCommandsResponse, RpcPooledZkappCommandsResponse,
1313
RpcRequest, RpcSnarkPoolCompletedJobsResponse, RpcSnarkPoolPendingJobsGetResponse,
1414
RpcSyncStatsGetResponse, RpcTransactionInjectResponse, RpcTransactionStatusGetResponse,
15-
SyncStatsQuery,
15+
SyncStatsQuery, RpcStatusGetResponse, RpcNodeStatus, RpcBestChainResponse
1616
},
1717
stats::sync::SyncKind,
1818
BuildEnv,
@@ -24,6 +24,7 @@ use openmina_core::{
2424
use openmina_node_common::rpc::RpcSender;
2525
use snark::GraphQLPendingSnarkWork;
2626
use std::str::FromStr;
27+
use tokio::sync::OnceCell;
2728
use transaction::GraphQLTransactionStatus;
2829
use warp::{Filter, Rejection, Reply};
2930
use zkapp::GraphQLZkapp;
@@ -81,6 +82,8 @@ pub enum ConversionError {
8182
Custom(String),
8283
#[error(transparent)]
8384
FieldHelpers(#[from] FieldHelpersError),
85+
#[error("Failed to convert integer to i32")]
86+
Integer,
8487
}
8588

8689
impl From<ConversionError> for Error {
@@ -89,19 +92,54 @@ impl From<ConversionError> for Error {
8992
}
9093
}
9194

92-
pub(crate) struct Context(RpcSender);
95+
/// Context for the GraphQL API
96+
///
97+
/// This is used to share state between the GraphQL queries and mutations.
98+
///
99+
/// The caching used here is only valid for the lifetime of the context
100+
/// i.e. for one request which is the goal as we can have multiple sources for one request.
101+
/// This optimizes the number of request to the state machine
102+
pub(crate) struct Context {
103+
rpc_sender: RpcSender,
104+
statemachine_status_cache: OnceCell<Option<RpcNodeStatus>>,
105+
best_tip_cache: OnceCell<Option<AppliedBlock>>,
106+
}
93107

94108
impl juniper::Context for Context {}
95109

96-
// impl Context {
97-
// pub(crate) async fn get_or_fetch_status(&self) -> RpcStatusGetResponse {
98-
// let result: RpcStatusGetResponse = self
99-
// .0
100-
// .oneshot_request(RpcRequest::StatusGet)
101-
// .await
102-
// .flatten();
103-
// }
104-
// }
110+
impl Context {
111+
pub fn new(rpc_sender: RpcSender) -> Self {
112+
Self {
113+
rpc_sender,
114+
statemachine_status_cache: OnceCell::new(),
115+
best_tip_cache: OnceCell::new(),
116+
}
117+
}
118+
119+
pub(crate) async fn get_or_fetch_status(&self) -> RpcStatusGetResponse {
120+
self.statemachine_status_cache
121+
.get_or_init(|| async {
122+
self.rpc_sender
123+
.oneshot_request(RpcRequest::StatusGet)
124+
.await
125+
.flatten()
126+
})
127+
.await
128+
.clone()
129+
}
130+
131+
pub(crate) async fn get_or_fetch_best_tip(&self) -> Option<AppliedBlock> {
132+
self.best_tip_cache
133+
.get_or_init(|| async {
134+
self.rpc_sender
135+
.oneshot_request(RpcRequest::BestChain(1))
136+
.await
137+
.and_then(|blocks: RpcBestChainResponse| blocks.first().cloned())
138+
})
139+
.await
140+
.clone()
141+
}
142+
}
105143

106144
#[derive(Clone, Copy, Debug, GraphQLEnum)]
107145
#[allow(clippy::upper_case_acronyms)]
@@ -185,7 +223,7 @@ impl Query {
185223
let token_id = TokenIdKeyHash::from_str(&token)?;
186224
let public_key = AccountPublicKey::from_str(&public_key)?;
187225
let accounts: Vec<Account> = context
188-
.0
226+
.rpc_sender
189227
.oneshot_request(RpcRequest::LedgerAccountsGet(
190228
AccountQuery::PubKeyWithTokenId(public_key, token_id),
191229
))
@@ -201,7 +239,7 @@ impl Query {
201239

202240
async fn sync_status(context: &Context) -> juniper::FieldResult<SyncStatus> {
203241
let state: RpcSyncStatsGetResponse = context
204-
.0
242+
.rpc_sender
205243
.oneshot_request(RpcRequest::SyncStatsGet(SyncStatsQuery { limit: Some(1) }))
206244
.await
207245
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -225,7 +263,7 @@ impl Query {
225263
context: &Context,
226264
) -> juniper::FieldResult<Vec<GraphQLBlock>> {
227265
let best_chain: Vec<AppliedBlock> = context
228-
.0
266+
.rpc_sender
229267
.oneshot_request(RpcRequest::BestChain(max_length as u32))
230268
.await
231269
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -246,7 +284,7 @@ impl Query {
246284
context: &Context,
247285
) -> juniper::FieldResult<constants::GraphQLGenesisConstants> {
248286
let consensus_constants: ConsensusConstants = context
249-
.0
287+
.rpc_sender
250288
.oneshot_request(RpcRequest::ConsensusConstantsGet)
251289
.await
252290
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -285,7 +323,7 @@ impl Query {
285323
.into());
286324
};
287325
let res: RpcTransactionStatusGetResponse = context
288-
.0
326+
.rpc_sender
289327
.oneshot_request(RpcRequest::TransactionStatusGet(tx))
290328
.await
291329
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -311,7 +349,7 @@ impl Query {
311349
};
312350

313351
let res: Option<RpcGetBlockResponse> = context
314-
.0
352+
.rpc_sender
315353
.oneshot_request(RpcRequest::GetBlock(query.clone()))
316354
.await;
317355

@@ -461,7 +499,7 @@ where
461499
R: TryFrom<MinaBaseUserCommandStableV2>,
462500
{
463501
let res: RpcTransactionInjectResponse = context
464-
.0
502+
.rpc_sender
465503
.oneshot_request(RpcRequest::TransactionInject(vec![cmd]))
466504
.await
467505
.ok_or(Error::StateMachineEmptyResponse)?;
@@ -527,7 +565,7 @@ impl Mutation {
527565
.map_err(|e| Error::Conversion(ConversionError::Base58Check(e)))?;
528566

529567
let accounts: Vec<Account> = context
530-
.0
568+
.rpc_sender
531569
.oneshot_request(RpcRequest::LedgerAccountsGet(
532570
AccountQuery::PubKeyWithTokenId(public_key, token_id),
533571
))
@@ -557,7 +595,7 @@ impl Mutation {
557595

558596
// Grab the sender's account to get the infered nonce
559597
let accounts: Vec<Account> = context
560-
.0
598+
.rpc_sender
561599
.oneshot_request(RpcRequest::LedgerAccountsGet(
562600
AccountQuery::PubKeyWithTokenId(public_key, token_id),
563601
))
@@ -577,7 +615,7 @@ impl Mutation {
577615
pub fn routes(
578616
rpc_sernder: RpcSender,
579617
) -> impl Filter<Error = Rejection, Extract = impl Reply> + Clone {
580-
let state = warp::any().map(move || Context(rpc_sernder.clone()));
618+
let state = warp::any().map(move || Context::new(rpc_sernder.clone()));
581619
let schema = RootNode::new(Query, Mutation, EmptySubscription::<Context>::new());
582620
let graphql_filter = juniper_warp::make_graphql_filter(schema, state.boxed());
583621
let graphiql_filter = juniper_warp::graphiql_filter("/graphql", None);

0 commit comments

Comments
 (0)