tempo_primitives/
subblock.rs

1use crate::TempoTxEnvelope;
2use alloy_primitives::{Address, B256, Bytes, U256, keccak256, wrap_fixed_bytes};
3use alloy_rlp::{BufMut, Decodable, Encodable, RlpDecodable, RlpEncodable};
4use reth_primitives_traits::{Recovered, crypto::RecoveryError};
5
6/// Magic byte for the subblock signature hash.
7const SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE: u8 = 0x78;
8
9/// Nonce key prefix marking a subblock transaction.
10pub const TEMPO_SUBBLOCK_NONCE_KEY_PREFIX: u8 = 0x5b;
11
12/// Returns true if the given nonce key has the [`TEMPO_SUBBLOCK_NONCE_KEY_PREFIX`].
13#[inline]
14pub fn has_sub_block_nonce_key_prefix(nonce_key: &U256) -> bool {
15    nonce_key.byte(31) == TEMPO_SUBBLOCK_NONCE_KEY_PREFIX
16}
17
18wrap_fixed_bytes! {
19    /// Partial validator public key encoded inside the nonce key.
20    pub struct PartialValidatorKey<15>;
21}
22
23impl PartialValidatorKey {
24    /// Returns whether this partial public key matches the given validator public key.
25    pub fn matches(&self, validator: impl AsRef<[u8]>) -> bool {
26        validator.as_ref().starts_with(self.as_slice())
27    }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
32pub enum SubBlockVersion {
33    /// Subblock version 1.
34    V1 = 1,
35}
36
37impl From<SubBlockVersion> for u8 {
38    fn from(value: SubBlockVersion) -> Self {
39        value as Self
40    }
41}
42
43impl TryFrom<u8> for SubBlockVersion {
44    type Error = u8;
45
46    fn try_from(value: u8) -> Result<Self, Self::Error> {
47        match value {
48            1 => Ok(Self::V1),
49            _ => Err(value),
50        }
51    }
52}
53
54impl Encodable for SubBlockVersion {
55    fn encode(&self, out: &mut dyn BufMut) {
56        u8::from(*self).encode(out);
57    }
58
59    fn length(&self) -> usize {
60        u8::from(*self).length()
61    }
62}
63
64impl Decodable for SubBlockVersion {
65    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
66        u8::decode(buf)?
67            .try_into()
68            .map_err(|_| alloy_rlp::Error::Custom("invalid subblock version"))
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
74pub struct SubBlock {
75    /// Version of the subblock.
76    pub version: SubBlockVersion,
77    /// Hash of the parent block. This subblock can only be included as
78    /// part of the block building on top of the specified parent.
79    pub parent_hash: B256,
80    /// Recipient of the fees for the subblock.
81    pub fee_recipient: Address,
82    /// Transactions included in the subblock.
83    pub transactions: Vec<TempoTxEnvelope>,
84}
85
86impl SubBlock {
87    /// Returns the hash for the signature.
88    pub fn signature_hash(&self) -> B256 {
89        let mut buf = Vec::with_capacity(self.length() + 1);
90        buf.put_u8(SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE);
91        self.encode(&mut buf);
92        keccak256(&buf)
93    }
94
95    fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
96        self.version.encode(out);
97        self.parent_hash.encode(out);
98        self.fee_recipient.encode(out);
99        self.transactions.encode(out);
100    }
101
102    fn rlp_encoded_fields_length(&self) -> usize {
103        self.version.length()
104            + self.parent_hash.length()
105            + self.fee_recipient.length()
106            + self.transactions.length()
107    }
108
109    fn rlp_header(&self) -> alloy_rlp::Header {
110        alloy_rlp::Header {
111            list: true,
112            payload_length: self.rlp_encoded_fields_length(),
113        }
114    }
115
116    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
117        Ok(Self {
118            version: Decodable::decode(buf)?,
119            parent_hash: Decodable::decode(buf)?,
120            fee_recipient: Decodable::decode(buf)?,
121            transactions: Decodable::decode(buf)?,
122        })
123    }
124
125    /// Returns the total length of the transactions in the subblock.
126    pub fn total_tx_size(&self) -> usize {
127        self.transactions.iter().map(|tx| tx.length()).sum()
128    }
129}
130
131impl Encodable for SubBlock {
132    fn encode(&self, out: &mut dyn BufMut) {
133        self.rlp_header().encode(out);
134        self.rlp_encode_fields(out);
135    }
136}
137
138/// A subblock with a signature.
139#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut, PartialEq, Eq)]
140#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
141#[cfg_attr(test, reth_codecs::add_arbitrary_tests(rlp))]
142pub struct SignedSubBlock {
143    /// The subblock.
144    #[deref]
145    #[deref_mut]
146    pub inner: SubBlock,
147    /// The signature of the subblock.
148    pub signature: Bytes,
149}
150
151impl SignedSubBlock {
152    /// Attempts to recover the senders and convert the subblock into a [`RecoveredSubBlock`].
153    ///
154    /// Note that the validator is assumed to be pre-validated to match the submitted signature.
155    pub fn try_into_recovered(self, validator: B256) -> Result<RecoveredSubBlock, RecoveryError> {
156        let senders =
157            reth_primitives_traits::transaction::recover::recover_signers(&self.transactions)?;
158
159        Ok(RecoveredSubBlock {
160            inner: self,
161            senders,
162            validator,
163        })
164    }
165
166    fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
167        self.inner.rlp_encode_fields(out);
168        self.signature.encode(out);
169    }
170
171    fn rlp_encoded_fields_length(&self) -> usize {
172        self.inner.rlp_encoded_fields_length() + self.signature.length()
173    }
174
175    fn rlp_header(&self) -> alloy_rlp::Header {
176        alloy_rlp::Header {
177            list: true,
178            payload_length: self.rlp_encoded_fields_length(),
179        }
180    }
181
182    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
183        Ok(Self {
184            inner: SubBlock::rlp_decode_fields(buf)?,
185            signature: Decodable::decode(buf)?,
186        })
187    }
188}
189
190impl Encodable for SignedSubBlock {
191    fn encode(&self, out: &mut dyn BufMut) {
192        self.rlp_header().encode(out);
193        self.rlp_encode_fields(out);
194    }
195
196    fn length(&self) -> usize {
197        self.rlp_header().length_with_payload()
198    }
199}
200
201impl Decodable for SignedSubBlock {
202    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
203        let header = alloy_rlp::Header::decode(buf)?;
204        if !header.list {
205            return Err(alloy_rlp::Error::UnexpectedString);
206        }
207
208        let remaining = buf.len();
209
210        let this = Self::rlp_decode_fields(buf)?;
211
212        if buf.len() + header.payload_length != remaining {
213            return Err(alloy_rlp::Error::UnexpectedLength);
214        }
215
216        Ok(this)
217    }
218}
219
220/// A subblock with recovered senders.
221#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
222pub struct RecoveredSubBlock {
223    /// Inner subblock.
224    #[deref]
225    #[deref_mut]
226    inner: SignedSubBlock,
227
228    /// The senders of the transactions.
229    senders: Vec<Address>,
230
231    /// The validator that submitted the subblock.
232    validator: B256,
233}
234
235impl RecoveredSubBlock {
236    /// Creates a new [`RecoveredSubBlock`] without validating the signatures.
237    pub fn new_unchecked(inner: SignedSubBlock, senders: Vec<Address>, validator: B256) -> Self {
238        Self {
239            inner,
240            senders,
241            validator,
242        }
243    }
244
245    /// Returns an iterator over `Recovered<&Transaction>`
246    #[inline]
247    pub fn transactions_recovered(&self) -> impl Iterator<Item = Recovered<&TempoTxEnvelope>> + '_ {
248        self.senders
249            .iter()
250            .zip(self.inner.transactions.iter())
251            .map(|(sender, tx)| Recovered::new_unchecked(tx, *sender))
252    }
253
254    /// Returns the validator that submitted the subblock.
255    pub fn validator(&self) -> B256 {
256        self.validator
257    }
258
259    /// Returns the metadata for the subblock.
260    pub fn metadata(&self) -> SubBlockMetadata {
261        SubBlockMetadata {
262            validator: self.validator,
263            fee_recipient: self.fee_recipient,
264            version: self.version,
265            signature: self.signature.clone(),
266        }
267    }
268}
269
270/// Metadata for an included subblock.
271#[derive(Debug, Clone, RlpEncodable, RlpDecodable)]
272pub struct SubBlockMetadata {
273    /// Version of the subblock.
274    pub version: SubBlockVersion,
275    /// Validator that submitted the subblock.
276    pub validator: B256,
277    /// Recipient of the fees for the subblock.
278    pub fee_recipient: Address,
279    /// Signature of the subblock.
280    pub signature: Bytes,
281}