reth_optimism_flashblocks/ws/
decoding.rs

1use crate::{ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashBlock, Metadata};
2use alloy_primitives::bytes::Bytes;
3use alloy_rpc_types_engine::PayloadId;
4use serde::{Deserialize, Serialize};
5use std::{fmt::Debug, io};
6
7/// Internal helper for decoding
8#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize)]
9struct FlashblocksPayloadV1 {
10    /// The payload id of the flashblock
11    pub payload_id: PayloadId,
12    /// The index of the flashblock in the block
13    pub index: u64,
14    /// The base execution payload configuration
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub base: Option<ExecutionPayloadBaseV1>,
17    /// The delta/diff containing modified portions of the execution payload
18    pub diff: ExecutionPayloadFlashblockDeltaV1,
19    /// Additional metadata associated with the flashblock
20    pub metadata: serde_json::Value,
21}
22
23impl FlashBlock {
24    /// Decodes `bytes` into [`FlashBlock`].
25    ///
26    /// This function is specific to the Base Optimism websocket encoding.
27    ///
28    /// It is assumed that the `bytes` are encoded in JSON and optionally compressed using brotli.
29    /// Whether the `bytes` is compressed or not is determined by looking at the first
30    /// non ascii-whitespace character.
31    pub(crate) fn decode(bytes: Bytes) -> eyre::Result<Self> {
32        let bytes = try_parse_message(bytes)?;
33
34        let payload: FlashblocksPayloadV1 = serde_json::from_slice(&bytes)
35            .map_err(|e| eyre::eyre!("failed to parse message: {e}"))?;
36
37        let metadata: Metadata = serde_json::from_value(payload.metadata)
38            .map_err(|e| eyre::eyre!("failed to parse message metadata: {e}"))?;
39
40        Ok(Self {
41            payload_id: payload.payload_id,
42            index: payload.index,
43            base: payload.base,
44            diff: payload.diff,
45            metadata,
46        })
47    }
48}
49
50/// Maps `bytes` into a potentially different [`Bytes`].
51///
52/// If the bytes start with a "{" character, prepended by any number of ASCII-whitespaces,
53/// then it assumes that it is JSON-encoded and returns it as-is.
54///
55/// Otherwise, the `bytes` are passed through a brotli decompressor and returned.
56fn try_parse_message(bytes: Bytes) -> eyre::Result<Bytes> {
57    if bytes.trim_ascii_start().starts_with(b"{") {
58        return Ok(bytes);
59    }
60
61    let mut decompressor = brotli::Decompressor::new(bytes.as_ref(), 4096);
62    let mut decompressed = Vec::new();
63    io::copy(&mut decompressor, &mut decompressed)?;
64
65    Ok(decompressed.into())
66}