Skip to content

Commit 873748d

Browse files
authored
simplify rpc codec logic (#6304)
* simplify rpc codec logic * Merge branch 'unstable' of github.com:sigp/lighthouse into simplify-rpc-codec * Merge branch 'unstable' of github.com:sigp/lighthouse into simplify-rpc-codec * Merge branch 'unstable' of github.com:sigp/lighthouse into simply-rpc-codec * Merge branch 'unstable' into simplify-rpc-codec * Merge branch 'unstable' into simplify-rpc-codec
1 parent d686138 commit 873748d

File tree

5 files changed

+257
-491
lines changed

5 files changed

+257
-491
lines changed

beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs renamed to beacon_node/lighthouse_network/src/rpc/codec.rs

Lines changed: 246 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::rpc::methods::*;
2-
use crate::rpc::{
3-
codec::base::OutboundCodec,
4-
protocol::{Encoding, ProtocolId, RPCError, SupportedProtocol, ERROR_TYPE_MAX, ERROR_TYPE_MIN},
2+
use crate::rpc::protocol::{
3+
Encoding, ProtocolId, RPCError, SupportedProtocol, ERROR_TYPE_MAX, ERROR_TYPE_MIN,
54
};
65
use crate::rpc::{InboundRequest, OutboundRequest};
6+
use libp2p::bytes::BufMut;
77
use libp2p::bytes::BytesMut;
88
use snap::read::FrameDecoder;
99
use snap::write::FrameEncoder;
@@ -57,13 +57,13 @@ impl<E: EthSpec> SSZSnappyInboundCodec<E> {
5757
max_packet_size,
5858
}
5959
}
60-
}
6160

62-
// Encoder for inbound streams: Encodes RPC Responses sent to peers.
63-
impl<E: EthSpec> Encoder<RPCCodedResponse<E>> for SSZSnappyInboundCodec<E> {
64-
type Error = RPCError;
65-
66-
fn encode(&mut self, item: RPCCodedResponse<E>, dst: &mut BytesMut) -> Result<(), Self::Error> {
61+
/// Encodes RPC Responses sent to peers.
62+
fn encode_response(
63+
&mut self,
64+
item: RPCCodedResponse<E>,
65+
dst: &mut BytesMut,
66+
) -> Result<(), RPCError> {
6767
let bytes = match &item {
6868
RPCCodedResponse::Success(resp) => match &resp {
6969
RPCResponse::Status(res) => res.as_ssz_bytes(),
@@ -125,6 +125,21 @@ impl<E: EthSpec> Encoder<RPCCodedResponse<E>> for SSZSnappyInboundCodec<E> {
125125
}
126126
}
127127

128+
// Encoder for inbound streams: Encodes RPC Responses sent to peers.
129+
impl<E: EthSpec> Encoder<RPCCodedResponse<E>> for SSZSnappyInboundCodec<E> {
130+
type Error = RPCError;
131+
132+
fn encode(&mut self, item: RPCCodedResponse<E>, dst: &mut BytesMut) -> Result<(), Self::Error> {
133+
dst.clear();
134+
dst.reserve(1);
135+
dst.put_u8(
136+
item.as_u8()
137+
.expect("Should never encode a stream termination"),
138+
);
139+
self.encode_response(item, dst)
140+
}
141+
}
142+
128143
// Decoder for inbound streams: Decodes RPC requests from peers
129144
impl<E: EthSpec> Decoder for SSZSnappyInboundCodec<E> {
130145
type Item = InboundRequest<E>;
@@ -188,6 +203,8 @@ pub struct SSZSnappyOutboundCodec<E: EthSpec> {
188203
/// The fork name corresponding to the received context bytes.
189204
fork_name: Option<ForkName>,
190205
fork_context: Arc<ForkContext>,
206+
/// Keeps track of the current response code for a chunk.
207+
current_response_code: Option<u8>,
191208
phantom: PhantomData<E>,
192209
}
193210

@@ -209,66 +226,12 @@ impl<E: EthSpec> SSZSnappyOutboundCodec<E> {
209226
fork_name: None,
210227
fork_context,
211228
phantom: PhantomData,
229+
current_response_code: None,
212230
}
213231
}
214-
}
215-
216-
// Encoder for outbound streams: Encodes RPC Requests to peers
217-
impl<E: EthSpec> Encoder<OutboundRequest<E>> for SSZSnappyOutboundCodec<E> {
218-
type Error = RPCError;
219-
220-
fn encode(&mut self, item: OutboundRequest<E>, dst: &mut BytesMut) -> Result<(), Self::Error> {
221-
let bytes = match item {
222-
OutboundRequest::Status(req) => req.as_ssz_bytes(),
223-
OutboundRequest::Goodbye(req) => req.as_ssz_bytes(),
224-
OutboundRequest::BlocksByRange(r) => match r {
225-
OldBlocksByRangeRequest::V1(req) => req.as_ssz_bytes(),
226-
OldBlocksByRangeRequest::V2(req) => req.as_ssz_bytes(),
227-
},
228-
OutboundRequest::BlocksByRoot(r) => match r {
229-
BlocksByRootRequest::V1(req) => req.block_roots.as_ssz_bytes(),
230-
BlocksByRootRequest::V2(req) => req.block_roots.as_ssz_bytes(),
231-
},
232-
OutboundRequest::BlobsByRange(req) => req.as_ssz_bytes(),
233-
OutboundRequest::BlobsByRoot(req) => req.blob_ids.as_ssz_bytes(),
234-
OutboundRequest::DataColumnsByRange(req) => req.as_ssz_bytes(),
235-
OutboundRequest::DataColumnsByRoot(req) => req.data_column_ids.as_ssz_bytes(),
236-
OutboundRequest::Ping(req) => req.as_ssz_bytes(),
237-
OutboundRequest::MetaData(_) => return Ok(()), // no metadata to encode
238-
};
239-
// SSZ encoded bytes should be within `max_packet_size`
240-
if bytes.len() > self.max_packet_size {
241-
return Err(RPCError::InternalError(
242-
"attempting to encode data > max_packet_size",
243-
));
244-
}
245232

246-
// Inserts the length prefix of the uncompressed bytes into dst
247-
// encoded as a unsigned varint
248-
self.inner
249-
.encode(bytes.len(), dst)
250-
.map_err(RPCError::from)?;
251-
252-
let mut writer = FrameEncoder::new(Vec::new());
253-
writer.write_all(&bytes).map_err(RPCError::from)?;
254-
writer.flush().map_err(RPCError::from)?;
255-
256-
// Write compressed bytes to `dst`
257-
dst.extend_from_slice(writer.get_ref());
258-
Ok(())
259-
}
260-
}
261-
262-
// Decoder for outbound streams: Decodes RPC responses from peers.
263-
//
264-
// The majority of the decoding has now been pushed upstream due to the changing specification.
265-
// We prefer to decode blocks and attestations with extra knowledge about the chain to perform
266-
// faster verification checks before decoding entire blocks/attestations.
267-
impl<E: EthSpec> Decoder for SSZSnappyOutboundCodec<E> {
268-
type Item = RPCResponse<E>;
269-
type Error = RPCError;
270-
271-
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
233+
// Decode an Rpc response.
234+
fn decode_response(&mut self, src: &mut BytesMut) -> Result<Option<RPCResponse<E>>, RPCError> {
272235
// Read the context bytes if required
273236
if self.protocol.has_context_bytes() && self.fork_name.is_none() {
274237
if src.len() >= CONTEXT_BYTES_LEN {
@@ -318,15 +281,8 @@ impl<E: EthSpec> Decoder for SSZSnappyOutboundCodec<E> {
318281
Err(e) => handle_error(e, reader.get_ref().get_ref().position(), max_compressed_len),
319282
}
320283
}
321-
}
322-
323-
impl<E: EthSpec> OutboundCodec<OutboundRequest<E>> for SSZSnappyOutboundCodec<E> {
324-
type CodecErrorType = ErrorType;
325284

326-
fn decode_error(
327-
&mut self,
328-
src: &mut BytesMut,
329-
) -> Result<Option<Self::CodecErrorType>, RPCError> {
285+
fn decode_error(&mut self, src: &mut BytesMut) -> Result<Option<ErrorType>, RPCError> {
330286
let Some(length) = handle_length(&mut self.inner, &mut self.len, src)? else {
331287
return Ok(None);
332288
};
@@ -361,6 +317,95 @@ impl<E: EthSpec> OutboundCodec<OutboundRequest<E>> for SSZSnappyOutboundCodec<E>
361317
}
362318
}
363319

320+
// Encoder for outbound streams: Encodes RPC Requests to peers
321+
impl<E: EthSpec> Encoder<OutboundRequest<E>> for SSZSnappyOutboundCodec<E> {
322+
type Error = RPCError;
323+
324+
fn encode(&mut self, item: OutboundRequest<E>, dst: &mut BytesMut) -> Result<(), Self::Error> {
325+
let bytes = match item {
326+
OutboundRequest::Status(req) => req.as_ssz_bytes(),
327+
OutboundRequest::Goodbye(req) => req.as_ssz_bytes(),
328+
OutboundRequest::BlocksByRange(r) => match r {
329+
OldBlocksByRangeRequest::V1(req) => req.as_ssz_bytes(),
330+
OldBlocksByRangeRequest::V2(req) => req.as_ssz_bytes(),
331+
},
332+
OutboundRequest::BlocksByRoot(r) => match r {
333+
BlocksByRootRequest::V1(req) => req.block_roots.as_ssz_bytes(),
334+
BlocksByRootRequest::V2(req) => req.block_roots.as_ssz_bytes(),
335+
},
336+
OutboundRequest::BlobsByRange(req) => req.as_ssz_bytes(),
337+
OutboundRequest::BlobsByRoot(req) => req.blob_ids.as_ssz_bytes(),
338+
OutboundRequest::DataColumnsByRange(req) => req.as_ssz_bytes(),
339+
OutboundRequest::DataColumnsByRoot(req) => req.data_column_ids.as_ssz_bytes(),
340+
OutboundRequest::Ping(req) => req.as_ssz_bytes(),
341+
OutboundRequest::MetaData(_) => return Ok(()), // no metadata to encode
342+
};
343+
// SSZ encoded bytes should be within `max_packet_size`
344+
if bytes.len() > self.max_packet_size {
345+
return Err(RPCError::InternalError(
346+
"attempting to encode data > max_packet_size",
347+
));
348+
}
349+
350+
// Inserts the length prefix of the uncompressed bytes into dst
351+
// encoded as a unsigned varint
352+
self.inner
353+
.encode(bytes.len(), dst)
354+
.map_err(RPCError::from)?;
355+
356+
let mut writer = FrameEncoder::new(Vec::new());
357+
writer.write_all(&bytes).map_err(RPCError::from)?;
358+
writer.flush().map_err(RPCError::from)?;
359+
360+
// Write compressed bytes to `dst`
361+
dst.extend_from_slice(writer.get_ref());
362+
Ok(())
363+
}
364+
}
365+
366+
// Decoder for outbound streams: Decodes RPC responses from peers.
367+
//
368+
// The majority of the decoding has now been pushed upstream due to the changing specification.
369+
// We prefer to decode blocks and attestations with extra knowledge about the chain to perform
370+
// faster verification checks before decoding entire blocks/attestations.
371+
impl<E: EthSpec> Decoder for SSZSnappyOutboundCodec<E> {
372+
type Item = RPCCodedResponse<E>;
373+
type Error = RPCError;
374+
375+
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
376+
// if we have only received the response code, wait for more bytes
377+
if src.len() <= 1 {
378+
return Ok(None);
379+
}
380+
// using the response code determine which kind of payload needs to be decoded.
381+
let response_code = self.current_response_code.unwrap_or_else(|| {
382+
let resp_code = src.split_to(1)[0];
383+
self.current_response_code = Some(resp_code);
384+
resp_code
385+
});
386+
387+
let inner_result = {
388+
if RPCCodedResponse::<E>::is_response(response_code) {
389+
// decode an actual response and mutates the buffer if enough bytes have been read
390+
// returning the result.
391+
self.decode_response(src)
392+
.map(|r| r.map(RPCCodedResponse::Success))
393+
} else {
394+
// decode an error
395+
self.decode_error(src)
396+
.map(|r| r.map(|resp| RPCCodedResponse::from_error(response_code, resp)))
397+
}
398+
};
399+
// if the inner decoder was capable of decoding a chunk, we need to reset the current
400+
// response code for the next chunk
401+
if let Ok(Some(_)) = inner_result {
402+
self.current_response_code = None;
403+
}
404+
// return the result
405+
inner_result
406+
}
407+
}
408+
364409
/// Handle errors that we get from decoding an RPC message from the stream.
365410
/// `num_bytes_read` is the number of bytes the snappy decoder has read from the underlying stream.
366411
/// `max_compressed_len` is the maximum compressed size for a given uncompressed size.
@@ -1030,7 +1075,7 @@ mod tests {
10301075
let mut snappy_inbound_codec =
10311076
SSZSnappyInboundCodec::<Spec>::new(snappy_protocol_id, max_packet_size, fork_context);
10321077

1033-
snappy_inbound_codec.encode(message, &mut buf)?;
1078+
snappy_inbound_codec.encode_response(message, &mut buf)?;
10341079
Ok(buf)
10351080
}
10361081

@@ -1075,7 +1120,7 @@ mod tests {
10751120
let mut snappy_outbound_codec =
10761121
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, max_packet_size, fork_context);
10771122
// decode message just as snappy message
1078-
snappy_outbound_codec.decode(message)
1123+
snappy_outbound_codec.decode_response(message)
10791124
}
10801125

10811126
/// Encodes the provided protocol message as bytes and tries to decode the encoding bytes.
@@ -1847,4 +1892,129 @@ mod tests {
18471892
RPCError::InvalidData(_)
18481893
));
18491894
}
1895+
1896+
#[test]
1897+
fn test_decode_status_message() {
1898+
let message = hex::decode("0054ff060000734e615070590032000006e71e7b54989925efd6c9cbcb8ceb9b5f71216f5137282bf6a1e3b50f64e42d6c7fb347abe07eb0db8200000005029e2800").unwrap();
1899+
let mut buf = BytesMut::new();
1900+
buf.extend_from_slice(&message);
1901+
1902+
let snappy_protocol_id = ProtocolId::new(SupportedProtocol::StatusV1, Encoding::SSZSnappy);
1903+
1904+
let fork_context = Arc::new(fork_context(ForkName::Base));
1905+
1906+
let chain_spec = Spec::default_spec();
1907+
1908+
let mut snappy_outbound_codec = SSZSnappyOutboundCodec::<Spec>::new(
1909+
snappy_protocol_id,
1910+
max_rpc_size(&fork_context, chain_spec.max_chunk_size as usize),
1911+
fork_context,
1912+
);
1913+
1914+
// remove response code
1915+
let mut snappy_buf = buf.clone();
1916+
let _ = snappy_buf.split_to(1);
1917+
1918+
// decode message just as snappy message
1919+
let _snappy_decoded_message = snappy_outbound_codec
1920+
.decode_response(&mut snappy_buf)
1921+
.unwrap();
1922+
1923+
// decode message as ssz snappy chunk
1924+
let _snappy_decoded_chunk = snappy_outbound_codec.decode(&mut buf).unwrap();
1925+
}
1926+
1927+
#[test]
1928+
fn test_invalid_length_prefix() {
1929+
let mut uvi_codec: Uvi<u128> = Uvi::default();
1930+
let mut dst = BytesMut::with_capacity(1024);
1931+
1932+
// Smallest > 10 byte varint
1933+
let len: u128 = 2u128.pow(70);
1934+
1935+
// Insert length-prefix
1936+
uvi_codec.encode(len, &mut dst).unwrap();
1937+
1938+
let snappy_protocol_id = ProtocolId::new(SupportedProtocol::StatusV1, Encoding::SSZSnappy);
1939+
1940+
let fork_context = Arc::new(fork_context(ForkName::Base));
1941+
1942+
let chain_spec = Spec::default_spec();
1943+
1944+
let mut snappy_outbound_codec = SSZSnappyOutboundCodec::<Spec>::new(
1945+
snappy_protocol_id,
1946+
max_rpc_size(&fork_context, chain_spec.max_chunk_size as usize),
1947+
fork_context,
1948+
);
1949+
1950+
let snappy_decoded_message = snappy_outbound_codec.decode_response(&mut dst).unwrap_err();
1951+
1952+
assert_eq!(
1953+
snappy_decoded_message,
1954+
RPCError::IoError("input bytes exceed maximum".to_string()),
1955+
"length-prefix of > 10 bytes is invalid"
1956+
);
1957+
}
1958+
1959+
#[test]
1960+
fn test_length_limits() {
1961+
fn encode_len(len: usize) -> BytesMut {
1962+
let mut uvi_codec: Uvi<usize> = Uvi::default();
1963+
let mut dst = BytesMut::with_capacity(1024);
1964+
uvi_codec.encode(len, &mut dst).unwrap();
1965+
dst
1966+
}
1967+
1968+
let protocol_id = ProtocolId::new(SupportedProtocol::BlocksByRangeV1, Encoding::SSZSnappy);
1969+
1970+
// Response limits
1971+
let fork_context = Arc::new(fork_context(ForkName::Base));
1972+
1973+
let chain_spec = Spec::default_spec();
1974+
1975+
let max_rpc_size = max_rpc_size(&fork_context, chain_spec.max_chunk_size as usize);
1976+
let limit = protocol_id.rpc_response_limits::<Spec>(&fork_context);
1977+
let mut max = encode_len(limit.max + 1);
1978+
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
1979+
protocol_id.clone(),
1980+
max_rpc_size,
1981+
fork_context.clone(),
1982+
);
1983+
assert!(matches!(
1984+
codec.decode_response(&mut max).unwrap_err(),
1985+
RPCError::InvalidData(_)
1986+
));
1987+
1988+
let mut min = encode_len(limit.min - 1);
1989+
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
1990+
protocol_id.clone(),
1991+
max_rpc_size,
1992+
fork_context.clone(),
1993+
);
1994+
assert!(matches!(
1995+
codec.decode_response(&mut min).unwrap_err(),
1996+
RPCError::InvalidData(_)
1997+
));
1998+
1999+
// Request limits
2000+
let limit = protocol_id.rpc_request_limits(&fork_context.spec);
2001+
let mut max = encode_len(limit.max + 1);
2002+
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
2003+
protocol_id.clone(),
2004+
max_rpc_size,
2005+
fork_context.clone(),
2006+
);
2007+
assert!(matches!(
2008+
codec.decode_response(&mut max).unwrap_err(),
2009+
RPCError::InvalidData(_)
2010+
));
2011+
2012+
let mut min = encode_len(limit.min - 1);
2013+
let mut codec =
2014+
SSZSnappyOutboundCodec::<Spec>::new(protocol_id, max_rpc_size, fork_context);
2015+
assert!(matches!(
2016+
codec.decode_response(&mut min).unwrap_err(),
2017+
RPCError::InvalidData(_)
2018+
));
2019+
}
18502020
}

0 commit comments

Comments
 (0)