use crate::{NodeActor, actors::CancellableContext};
use async_trait::async_trait;
use kona_p2p::P2pRpcRequest;
use kona_rpc::{
AdminApiServer, AdminRpc, DevEngineApiServer, DevEngineRpc, HealthzResponse, NetworkAdminQuery,
OpP2PApiServer, RollupNodeApiServer, SequencerAdminQuery, WsRPC, WsServer,
};
use std::time::Duration;
use jsonrpsee::{
RpcModule,
core::RegisterMethodError,
server::{Server, ServerHandle, middleware::http::ProxyGetRequestLayer},
};
use kona_engine::EngineQueries;
use kona_rpc::{L1WatcherQueries, P2pRpc, RollupRpc, RpcBuilder};
use tokio::sync::mpsc;
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
#[derive(Debug, thiserror::Error)]
pub enum RpcActorError {
#[error("Failed to register the healthz endpoint")]
RegisterHealthz(#[from] RegisterMethodError),
#[error(transparent)]
LaunchFailed(#[from] std::io::Error),
#[error("RPC server stopped unexpectedly")]
ServerStopped,
#[error("Failed to stop the RPC server")]
StopFailed,
}
#[derive(Debug)]
pub struct RpcActor {
config: RpcBuilder,
}
impl RpcActor {
pub const fn new(config: RpcBuilder) -> Self {
Self { config }
}
}
#[derive(Debug)]
pub struct RpcContext {
pub p2p_network: mpsc::Sender<P2pRpcRequest>,
pub network_admin: mpsc::Sender<NetworkAdminQuery>,
pub sequencer_admin: Option<mpsc::Sender<SequencerAdminQuery>>,
pub l1_watcher_queries: mpsc::Sender<L1WatcherQueries>,
pub engine_query: mpsc::Sender<EngineQueries>,
pub cancellation: CancellationToken,
}
impl CancellableContext for RpcContext {
fn cancelled(&self) -> WaitForCancellationFuture<'_> {
self.cancellation.cancelled()
}
}
async fn launch(
config: &RpcBuilder,
module: RpcModule<()>,
) -> Result<ServerHandle, std::io::Error> {
let middleware = tower::ServiceBuilder::new()
.layer(
ProxyGetRequestLayer::new([("/healthz", "healthz")])
.expect("Critical: Failed to build GET method proxy"),
)
.timeout(Duration::from_secs(2));
let server = Server::builder().set_http_middleware(middleware).build(config.socket).await?;
if let Ok(addr) = server.local_addr() {
info!(target: "rpc", addr = ?addr, "RPC server bound to address");
} else {
error!(target: "rpc", "Failed to get local address for RPC server");
}
Ok(server.start(module))
}
#[async_trait]
impl NodeActor for RpcActor {
type Error = RpcActorError;
type OutboundData = RpcContext;
type InboundData = ();
type Builder = RpcBuilder;
fn build(config: Self::Builder) -> (Self::InboundData, Self) {
((), Self::new(config))
}
async fn start(
mut self,
RpcContext {
cancellation,
p2p_network,
l1_watcher_queries,
engine_query,
network_admin,
sequencer_admin,
}: Self::OutboundData,
) -> Result<(), Self::Error> {
let mut modules = RpcModule::new(());
modules.register_method("healthz", |_, _, _| {
let response = HealthzResponse { version: std::env!("CARGO_PKG_VERSION").to_string() };
jsonrpsee::core::RpcResult::Ok(response)
})?;
modules.merge(P2pRpc::new(p2p_network).into_rpc())?;
modules.merge(
AdminRpc { sequencer_sender: sequencer_admin, network_sender: network_admin }
.into_rpc(),
)?;
let rollup_rpc = RollupRpc::new(engine_query.clone(), l1_watcher_queries);
modules.merge(rollup_rpc.into_rpc())?;
if self.config.dev_enabled() {
let dev_rpc = DevEngineRpc::new(engine_query.clone());
modules.merge(dev_rpc.into_rpc())?;
}
if self.config.ws_enabled() {
modules.merge(WsRPC::new(engine_query).into_rpc())?;
}
let restarts = self.config.restart_count();
let mut handle = launch(&self.config, modules.clone()).await?;
for _ in 0..=restarts {
tokio::select! {
_ = handle.clone().stopped() => {
match launch(&self.config, modules.clone()).await {
Ok(h) => handle = h,
Err(err) => {
error!(target: "rpc", ?err, "Failed to launch rpc server");
cancellation.cancel();
return Err(RpcActorError::ServerStopped);
}
}
}
_ = cancellation.cancelled() => {
handle.stop().map_err(|_| RpcActorError::StopFailed)?;
return Ok(());
}
}
}
cancellation.cancel();
return Err(RpcActorError::ServerStopped);
}
}
#[cfg(test)]
mod tests {
use std::net::SocketAddr;
use super::*;
#[tokio::test]
async fn test_launch_no_modules() {
let launcher = RpcBuilder {
socket: SocketAddr::from(([127, 0, 0, 1], 8080)),
no_restart: false,
enable_admin: false,
admin_persistence: None,
ws_enabled: false,
dev_enabled: false,
};
let result = launch(&launcher, RpcModule::new(())).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_launch_with_modules() {
let launcher = RpcBuilder {
socket: SocketAddr::from(([127, 0, 0, 1], 8081)),
no_restart: false,
enable_admin: false,
admin_persistence: None,
ws_enabled: false,
dev_enabled: false,
};
let mut modules = RpcModule::new(());
modules.merge(RpcModule::new(())).expect("module merge");
modules.merge(RpcModule::new(())).expect("module merge");
modules.merge(RpcModule::new(())).expect("module merge");
let result = launch(&launcher, modules).await;
assert!(result.is_ok());
}
}