use std::{
pin::Pin,
task::{ready, Context, Poll},
};
use anyhow::Context as _;
use zksync_concurrency::{
ctx, io,
io::{AsyncRead as _, AsyncWrite as _},
};
use zksync_consensus_crypto::{keccak256::Keccak256, ByteFmt};
use super::bytes;
use crate::metrics::MeteredStream;
fn params() -> snow::params::NoiseParams {
snow::params::NoiseParams {
name: "zksync-bft".to_string(),
base: snow::params::BaseChoice::Noise,
handshake: snow::params::HandshakeChoice {
pattern: snow::params::HandshakePattern::NN,
modifiers: snow::params::HandshakeModifierList { list: vec![] },
},
dh: snow::params::DHChoice::Curve25519,
cipher: snow::params::CipherChoice::ChaChaPoly,
hash: snow::params::HashChoice::SHA256,
}
}
const MAX_TRANSPORT_MSG_LEN: usize = 65535;
const AUTHDATA_LEN: usize = 16;
const MAX_PAYLOAD_LEN: usize = MAX_TRANSPORT_MSG_LEN - AUTHDATA_LEN;
const LENGTH_FIELD_LEN: usize = size_of::<u16>();
const MAX_FRAME_LEN: usize = MAX_TRANSPORT_MSG_LEN + LENGTH_FIELD_LEN;
struct Buffer {
payload: bytes::Buffer,
frame: bytes::Buffer,
}
impl Default for Buffer {
fn default() -> Self {
Self {
payload: bytes::Buffer::new(MAX_PAYLOAD_LEN),
frame: bytes::Buffer::new(MAX_FRAME_LEN),
}
}
}
#[pin_project::pin_project(project = StreamProject)]
pub(crate) struct Stream<S = MeteredStream> {
id: Keccak256,
#[pin]
inner: S,
noise: snow::TransportState,
read_buf: Box<Buffer>,
write_buf: Box<Buffer>,
}
impl<S> std::ops::Deref for Stream<S> {
type Target = S;
fn deref(&self) -> &S {
&self.inner
}
}
impl<S> Stream<S>
where
S: io::AsyncRead + io::AsyncWrite + Unpin,
{
pub(crate) async fn server_handshake(ctx: &ctx::Ctx, stream: S) -> ctx::Result<Self> {
Self::handshake(
ctx,
stream,
snow::Builder::new(params()).build_responder().unwrap(),
)
.await
}
pub(crate) async fn client_handshake(ctx: &ctx::Ctx, stream: S) -> ctx::Result<Self> {
Self::handshake(
ctx,
stream,
snow::Builder::new(params()).build_initiator().unwrap(),
)
.await
}
async fn handshake(
ctx: &ctx::Ctx,
mut stream: S,
mut hs: snow::HandshakeState,
) -> ctx::Result<Self> {
let mut buf = vec![0; 65536];
let mut payload = vec![];
loop {
if hs.is_handshake_finished() {
return Ok(Self {
id: ByteFmt::decode(hs.get_handshake_hash()).unwrap(),
inner: stream,
noise: hs.into_transport_mode().context("into_transport_mode()")?,
read_buf: Box::default(),
write_buf: Box::default(),
});
}
if hs.is_my_turn() {
let n = hs
.write_message(&payload, &mut buf)
.context("write_message()")?;
io::write_all(ctx, &mut stream, &u16::to_le_bytes(n as u16))
.await?
.context("write(len)")?;
io::write_all(ctx, &mut stream, &buf[..n])
.await?
.context("write(msg")?;
io::flush(ctx, &mut stream).await?.context("flush")?;
} else {
let mut msg_size = [0u8, 2];
io::read_exact(ctx, &mut stream, &mut msg_size)
.await?
.context("read_exact(len)")?;
let n = u16::from_le_bytes(msg_size) as usize;
io::read_exact(ctx, &mut stream, &mut buf[..n])
.await?
.context("read_exact(msg)")?;
hs.read_message(&buf[..n], &mut payload)
.context("read_message()")?;
}
}
}
pub(crate) fn id(&self) -> Keccak256 {
self.id
}
fn poll_read_frame(
this: &mut StreamProject<'_, S>,
cx: &mut Context<'_>,
) -> Poll<io::Result<Option<usize>>> {
loop {
if this.read_buf.frame.len() >= LENGTH_FIELD_LEN {
let n = u16::from_le_bytes(this.read_buf.frame.prefix()) as usize;
if this.read_buf.frame.len() >= LENGTH_FIELD_LEN + n {
return Poll::Ready(Ok(Some(n)));
}
}
let n = {
let mut frame = io::ReadBuf::new(this.read_buf.frame.as_mut_capacity());
ready!(Pin::new(&mut this.inner).poll_read(cx, &mut frame))?;
frame.filled().len()
};
if n == 0 {
return Poll::Ready(Ok(None));
}
this.read_buf.frame.extend(n);
}
}
fn poll_read_payload(
this: &mut StreamProject<'_, S>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
if this.read_buf.payload.len() > 0 {
return Poll::Ready(Ok(()));
}
let Some(n) = ready!(Self::poll_read_frame(this, cx))? else {
return Poll::Ready(Ok(()));
};
this.read_buf.payload.reset();
let m = this
.noise
.read_message(
&this.read_buf.frame.as_slice()[LENGTH_FIELD_LEN..LENGTH_FIELD_LEN + n],
this.read_buf.payload.as_mut_capacity(),
)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
this.read_buf.frame.take(LENGTH_FIELD_LEN + n);
this.read_buf.frame.shift();
this.read_buf.payload.extend(m);
Poll::Ready(Ok(()))
}
}
impl<S> io::AsyncRead for Stream<S>
where
S: io::AsyncRead + io::AsyncWrite + Unpin,
{
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut io::ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(Self::poll_read_payload(&mut this, cx))?;
let n = std::cmp::min(buf.remaining(), this.read_buf.payload.len());
buf.put_slice(&this.read_buf.payload.as_slice()[..n]);
this.read_buf.payload.take(n);
Poll::Ready(Ok(()))
}
}
impl<S> Stream<S>
where
S: io::AsyncRead + io::AsyncWrite + Unpin,
{
fn poll_flush_frame(
this: &mut StreamProject<'_, S>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
while this.write_buf.frame.len() > 0 {
let n =
ready!(Pin::new(&mut this.inner).poll_write(cx, this.write_buf.frame.as_slice()))?;
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
this.write_buf.frame.take(n);
}
Poll::Ready(Ok(()))
}
fn poll_flush_payload(
this: &mut StreamProject<'_, S>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
if this.write_buf.payload.len() == 0 {
return Poll::Ready(Ok(()));
}
ready!(Self::poll_flush_frame(this, cx))?;
this.write_buf.frame.reset();
let n = this
.noise
.write_message(
this.write_buf.payload.as_slice(),
&mut this.write_buf.frame.as_mut_capacity()[LENGTH_FIELD_LEN..],
)
.map_err(io::Error::other)?;
this.write_buf.frame.set_prefix((n as u16).to_le_bytes());
this.write_buf.frame.extend(LENGTH_FIELD_LEN + n);
this.write_buf.payload.take(this.write_buf.payload.len());
this.write_buf.payload.reset();
Poll::Ready(Ok(()))
}
}
impl<S> io::AsyncWrite for Stream<S>
where
S: io::AsyncRead + io::AsyncWrite + Unpin,
{
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let mut this = self.project();
if this.write_buf.payload.capacity() == 0 {
ready!(Self::poll_flush_payload(&mut this, cx))?;
}
let n = this.write_buf.payload.push(buf);
debug_assert!(n > 0);
Poll::Ready(Ok(n))
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(Self::poll_flush_payload(&mut this, cx))?;
ready!(Self::poll_flush_frame(&mut this, cx))?;
ready!(this.inner.poll_flush(cx))?;
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(Self::poll_flush_payload(&mut this, cx))?;
ready!(Self::poll_flush_frame(&mut this, cx))?;
ready!(this.inner.poll_shutdown(cx))?;
Poll::Ready(Ok(()))
}
}