Skip to content

feat(iota-core): gRPC PoC to replace the REST-API for the checkpoint fetching of indexer #7511

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

Open
wants to merge 102 commits into
base: feat/grpc
Choose a base branch
from

Conversation

bingyanglin
Copy link
Contributor

@bingyanglin bingyanglin commented Jun 24, 2025

Description of change

Currently the indexer syncs the checkpoints via a REST-API via polling, or via filesystem.
It would be a nice proof of concept to replace those methods with a gRPC API that supports subscriptions to get new checkpoints. This should reduce the latency between checkpoint creation and indexing.

The profiling results by using a local-network (4 validator, 4 nodes, 2 indexers, 2 Postgres DBs):
image

More details and steps to reproduce the profiling results were documented and discussed at #6995

Links to any relevant issues

#6995

How the change has been tested

  • Basic tests (linting, compilation, formatting, unit/integration tests)
  • Patch-specific tests (correctness, functionality coverage)
  • I have added tests that prove my fix is effective or that my feature works
  • I have checked that new and existing unit tests pass locally with my changes

Release Notes

  • Protocol:
  • Nodes (Validators and Full nodes): add "internal" gRPC API
  • Indexer:
  • JSON-RPC:
  • GraphQL:
  • CLI:
  • Rust SDK:
  • REST API:
  • gRPC API: add gRPC streaming for checkpoint data and checkpoint summary

@bingyanglin bingyanglin self-assigned this Jun 24, 2025
@bingyanglin bingyanglin added the node Issues related to the Core Node team label Jun 24, 2025
Copy link

vercel bot commented Jun 24, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

6 Skipped Deployments
Name Status Preview Comments Updated (UTC)
apps-backend ⬜️ Ignored (Inspect) Visit Preview Jul 18, 2025 1:34pm
apps-ui-kit ⬜️ Ignored (Inspect) Visit Preview Jul 18, 2025 1:34pm
iota-evm-bridge ⬜️ Ignored (Inspect) Visit Preview Jul 18, 2025 1:34pm
iota-multisig-toolkit ⬜️ Ignored (Inspect) Visit Preview Jul 18, 2025 1:34pm
rebased-explorer ⬜️ Ignored (Inspect) Visit Preview Jul 18, 2025 1:34pm
wallet-dashboard ⬜️ Ignored (Inspect) Visit Preview Jul 18, 2025 1:34pm

// Modifications Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! gRPC-specific versioned types for forward compatibility.
Copy link
Member

Choose a reason for hiding this comment

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

These should probably be in the iota-grpc-api crate, but depends on how these evolve when we extend the API

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Previously we put the types in the iota-gprc-api, and then we moved to the iota-types. Maybe we can decide it later depends on how we extend the API.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would then make it the other way around, as long as it's for internal use, let' put them in iota-grpc-api as alex said. They are only used there for now anyways, right?

@bingyanglin bingyanglin force-pushed the feat/grpc-poc branch 4 times, most recently from 9a7c4fa to ca34492 Compare June 30, 2025 06:05
@bingyanglin bingyanglin changed the base branch from develop to feat/grpc July 4, 2025 08:14
@bingyanglin bingyanglin marked this pull request as ready for review July 4, 2025 08:14
@bingyanglin bingyanglin requested review from a team as code owners July 4, 2025 08:14
@github-actions github-actions bot added the ci Issues related to our CI pipeline label Jul 6, 2025
Copy link
Contributor

github-actions bot commented Jul 6, 2025

✅ Vercel Preview Deployment is ready!

View Preview

Copy link
Contributor

@muXxer muXxer left a comment

Choose a reason for hiding this comment

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

I didn't finish my review yet, but I submitted it partly.

Comment on lines 858 to 870
let rest_read_store = std::sync::Arc::new(RestReadStore::new(state.clone(), rocks));
// Use the shared broadcast channel and buffer for gRPC checkpoint streaming
let grpc_service =
CheckpointGrpcService::new(rest_read_store, summary_tx.clone(), data_tx.clone());
tokio::spawn(async move {
info!("Starting gRPC server on {grpc_api_address}");
tonic::transport::Server::builder()
.add_service(CheckpointServiceServer::new(grpc_service))
.serve(grpc_api_address)
.await
.expect("gRPC server failed");
});
(Some(summary_tx), Some(data_tx))
Copy link
Contributor

Choose a reason for hiding this comment

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

We should refactor that into it's own function. I'm also not sure if a validator should run these APIs at all. So maybe better check how we build the JSON and REST server in build_http_server or start_graphiql_server and do something similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it at af6baf4. Thanks!

#[serde(default, skip_serializing_if = "Option::is_none")]
pub grpc_api_address: Option<SocketAddr>,
/// Flag to enable the gRPC API.
#[serde(default = "bool_false")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't the normal default false anyways?

@@ -405,6 +418,10 @@ pub fn bool_true() -> bool {
true
}

pub fn bool_false() -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

If false is the default, we can remove that function.

@@ -1059,6 +1060,14 @@ impl TestClusterBuilder {
self
}

pub fn with_fullnode_grpc_api_address(mut self, addr: SocketAddr) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't that overwrite the existing fullnode_grpc_api_config that might already be passed by with_fullnode_grpc_api_config? Wouldn't it be better to remove this function, and let the caller decide to pass the config with the right address and the defaults for the other values? Or do we have this pattern somewhere else in the codebase already?

// Modifications Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! gRPC-specific versioned types for forward compatibility.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would then make it the other way around, as long as it's for internal use, let' put them in iota-grpc-api as alex said. They are only used there for now anyways, right?

}

pub fn with_fullnode_grpc_api_address(mut self, addr: SocketAddr) -> Self {
self.fullnode_grpc_api_config = Some(iota_grpc_api::Config {
Copy link
Contributor

Choose a reason for hiding this comment

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

same here, it would overwrite existing configs. Maybe remove the with_fullnode_grpc_api_address.

@@ -397,6 +401,16 @@ impl FullnodeConfigBuilder {
self
}

pub fn with_grpc_api_address(mut self, addr: SocketAddr) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's pass the full config instead of only the address.

let _ = data_tx.send(grpc_data);
}

info!(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this really be an info message? That will spam the logs.

@@ -459,6 +503,7 @@ impl CheckpointExecutor {
.await
.expect("Failed to accumulate running root");
self.bump_highest_executed_checkpoint(checkpoint);
self.broadcast_grpc_checkpoint(checkpoint, all_tx_digests);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add a comment:

// `broadcast_grpc_checkpoint` for the last checkpoint of an epoch is handled specially in `check_epoch_last_checkpoint`

Copy link
Contributor

Choose a reason for hiding this comment

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

Also here, maybe we already have checkpoint_data from the option above, so we could pass that here. Then we don't need to load it before broadcasting.

@@ -736,6 +781,8 @@ impl CheckpointExecutor {

self.bump_highest_executed_checkpoint(checkpoint);

self.broadcast_grpc_checkpoint(checkpoint, &all_tx_digests);
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO here you would already have the checkpoint_data, so you could pass that instead of loading it in the broadcast_grpc_checkpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci Issues related to our CI pipeline core-protocol documentation Improvements or additions to documentation node Issues related to the Core Node team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants