use std::{collections::BTreeMap, sync::Arc};
use anyhow::Context as _;
use zksync_concurrency::{ctx, io, limiter, metrics::LatencyHistogramExt as _, scope};
use self::metrics::{CallLatencyType, CallType, RPC_METRICS};
pub(crate) use crate::proto::rpc::Capability;
use crate::{frame, mux};
pub(crate) mod consensus;
pub(crate) mod get_block;
mod metrics;
pub(crate) mod ping;
pub(crate) mod push_block_store_state;
pub(crate) mod push_tx;
pub(crate) mod push_validator_addrs;
#[cfg(test)]
pub(crate) mod testonly;
#[cfg(test)]
mod tests;
impl Capability {
pub(crate) fn id(self) -> mux::CapabilityId {
self as mux::CapabilityId
}
}
pub(crate) const MUX_CONFIG: mux::Config = mux::Config {
read_buffer_size: 160 * zksync_protobuf::kB as u64,
read_frame_size: 16 * zksync_protobuf::kB as u64,
read_frame_count: 100,
write_frame_size: 16 * zksync_protobuf::kB as u64,
};
pub(crate) trait Rpc: Sync + Send + 'static {
const CAPABILITY: Capability;
const INFLIGHT: u32;
const METHOD: &'static str;
type Req: zksync_protobuf::ProtoFmt + Send + Sync;
type Resp: zksync_protobuf::ProtoFmt + Send + Sync;
fn submethod(_req: &Self::Req) -> &'static str {
""
}
}
pub(crate) struct ReservedCall<R: Rpc> {
stream: mux::ReservedStream,
_rpc: std::marker::PhantomData<R>,
}
impl<R: Rpc> ReservedCall<R> {
pub(crate) async fn call(
self,
ctx: &ctx::Ctx,
req: &R::Req,
max_resp_size: usize,
) -> ctx::Result<R::Resp> {
let send_time = ctx.now();
let mut stream = self.stream.open(ctx).await?.context("open()")?;
let res = async {
let metric_labels = CallType::Client.to_labels::<R>(req);
let _guard = RPC_METRICS.inflight[&metric_labels].inc_guard(1);
let msg_size = frame::mux_send_proto(ctx, &mut stream.write, req)
.await
.context("mux_send_proto(req)")?;
RPC_METRICS.message_size[&CallType::ReqSent.to_labels::<R>(req)].observe(msg_size);
drop(stream.write);
frame::mux_recv_proto(ctx, &mut stream.read, max_resp_size).await
}
.await;
let now = ctx.now();
let metric_labels = CallLatencyType::ClientSendRecv.to_labels::<R>(req, &res);
RPC_METRICS.latency[&metric_labels].observe_latency(now - send_time);
let (res, msg_size) =
res.with_context(|| format!("{}.{}", R::METHOD, R::submethod(req)))?;
RPC_METRICS.message_size[&CallType::RespRecv.to_labels::<R>(req)].observe(msg_size);
Ok(res)
}
}
pub(crate) struct Client<R: Rpc> {
queue: Arc<mux::StreamQueue>,
_rpc: std::marker::PhantomData<R>,
}
impl<R: Rpc> Client<R> {
pub(crate) fn new(ctx: &ctx::Ctx, rate: limiter::Rate) -> Self {
Client {
queue: mux::StreamQueue::new(ctx, R::INFLIGHT, rate),
_rpc: std::marker::PhantomData,
}
}
pub(crate) async fn reserve(&self, ctx: &ctx::Ctx) -> ctx::OrCanceled<ReservedCall<R>> {
let reserve_time = ctx.now();
let stream = self.queue.reserve(ctx).await?;
RPC_METRICS.call_reserve_latency[&R::METHOD].observe_latency(ctx.now() - reserve_time);
Ok(ReservedCall {
stream,
_rpc: std::marker::PhantomData,
})
}
pub(crate) async fn call(
&self,
ctx: &ctx::Ctx,
req: &R::Req,
max_resp_size: usize,
) -> ctx::Result<R::Resp> {
self.reserve(ctx).await?.call(ctx, req, max_resp_size).await
}
}
#[async_trait::async_trait]
pub(crate) trait Handler<R: Rpc>: Sync + Send {
async fn handle(&self, ctx: &ctx::Ctx, req: R::Req) -> anyhow::Result<R::Resp>;
fn max_req_size(&self) -> usize;
}
struct Server<R: Rpc, H: Handler<R>> {
handler: H,
queue: Arc<mux::StreamQueue>,
_rpc: std::marker::PhantomData<R>,
}
#[async_trait::async_trait]
trait ServerTrait: Sync + Send {
async fn serve(&self, ctx: &ctx::Ctx) -> ctx::OrCanceled<()>;
}
#[async_trait::async_trait]
impl<R: Rpc, H: Handler<R>> ServerTrait for Server<R, H> {
async fn serve(&self, ctx: &ctx::Ctx) -> ctx::OrCanceled<()> {
scope::run!(ctx, |ctx, s| async {
loop {
let stream = self.queue.reserve(ctx).await?;
s.spawn::<()>(async {
let res = async {
let mut stream = stream.open(ctx).await??;
let recv_time = ctx.now();
let (req, msg_size) = frame::mux_recv_proto::<R::Req>(
ctx,
&mut stream.read,
self.handler.max_req_size(),
)
.await?;
let size_labels = CallType::ReqRecv.to_labels::<R>(&req);
let resp_size_labels = CallType::RespSent.to_labels::<R>(&req);
RPC_METRICS.message_size[&size_labels].observe(msg_size);
let inflight_labels = CallType::Server.to_labels::<R>(&req);
let _guard = RPC_METRICS.inflight[&inflight_labels].inc_guard(1);
let mut server_process_labels =
CallLatencyType::ServerProcess.to_labels::<R>(&req, &Ok(()));
let mut recv_send_labels =
CallLatencyType::ServerRecvSend.to_labels::<R>(&req, &Ok(()));
let process_time = ctx.now();
let res = self.handler.handle(ctx, req).await.context(R::METHOD);
server_process_labels.set_result(&res);
RPC_METRICS.latency[&server_process_labels]
.observe_latency(ctx.now() - process_time);
let res = frame::mux_send_proto(ctx, &mut stream.write, &res?).await;
recv_send_labels.set_result(&res);
RPC_METRICS.latency[&recv_send_labels]
.observe_latency(ctx.now() - recv_time);
let msg_size = res?;
RPC_METRICS.message_size[&resp_size_labels].observe(msg_size);
anyhow::Ok(())
}
.await;
if let Err(err) = res {
tracing::debug!("{err:#}");
}
Ok(())
});
}
})
.await
}
}
pub(crate) struct Service<'a> {
mux: mux::Mux,
servers: Vec<Box<dyn 'a + ServerTrait>>,
}
impl<'a> Service<'a> {
pub(crate) fn new() -> Self {
Self {
mux: mux::Mux {
cfg: Arc::new(MUX_CONFIG.clone()),
accept: BTreeMap::default(),
connect: BTreeMap::default(),
},
servers: vec![],
}
}
pub(crate) fn add_client<R: Rpc>(mut self, client: &Client<R>) -> Self {
if self
.mux
.accept
.insert(R::CAPABILITY.id(), client.queue.clone())
.is_some()
{
panic!(
"client for capability {:?} already registered",
R::CAPABILITY
);
}
self
}
pub(crate) fn add_server<R: Rpc>(
mut self,
ctx: &ctx::Ctx,
handler: impl Handler<R> + 'a,
rate: limiter::Rate,
) -> Self {
let queue = mux::StreamQueue::new(ctx, R::INFLIGHT, rate);
if self
.mux
.connect
.insert(R::CAPABILITY.id(), queue.clone())
.is_some()
{
panic!(
"server for capability {:?} already registered",
R::CAPABILITY
);
}
self.servers.push(Box::new(Server {
handler,
queue,
_rpc: std::marker::PhantomData,
}));
self
}
pub(crate) async fn run<S: io::AsyncRead + io::AsyncWrite + Send>(
self,
ctx: &ctx::Ctx,
transport: S,
) -> Result<(), mux::RunError> {
scope::run!(ctx, |ctx, s| async {
for server in &self.servers {
s.spawn(async { Ok(server.serve(ctx).await?) });
}
self.mux.run(ctx, transport).await
})
.await
}
}