1use crate::TempoTxEnvelope;
2use alloc::vec::Vec;
3use alloy_consensus::transaction::Recovered;
4use alloy_primitives::{Address, B256, Bytes, U256, keccak256, wrap_fixed_bytes};
5use alloy_rlp::{BufMut, Decodable, Encodable, RlpDecodable, RlpEncodable};
6
7const SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE: u8 = 0x78;
9
10pub const TEMPO_SUBBLOCK_NONCE_KEY_PREFIX: u8 = 0x5b;
12
13#[inline]
15pub fn has_sub_block_nonce_key_prefix(nonce_key: &U256) -> bool {
16 nonce_key.byte(31) == TEMPO_SUBBLOCK_NONCE_KEY_PREFIX
17}
18
19wrap_fixed_bytes! {
20 pub struct PartialValidatorKey<15>;
22}
23
24impl PartialValidatorKey {
25 pub fn matches(&self, validator: impl AsRef<[u8]>) -> bool {
27 validator.as_ref().starts_with(self.as_slice())
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
33pub enum SubBlockVersion {
34 V1 = 1,
36}
37
38impl From<SubBlockVersion> for u8 {
39 fn from(value: SubBlockVersion) -> Self {
40 value as Self
41 }
42}
43
44impl TryFrom<u8> for SubBlockVersion {
45 type Error = u8;
46
47 fn try_from(value: u8) -> Result<Self, Self::Error> {
48 match value {
49 1 => Ok(Self::V1),
50 _ => Err(value),
51 }
52 }
53}
54
55impl Encodable for SubBlockVersion {
56 fn encode(&self, out: &mut dyn BufMut) {
57 u8::from(*self).encode(out);
58 }
59
60 fn length(&self) -> usize {
61 u8::from(*self).length()
62 }
63}
64
65impl Decodable for SubBlockVersion {
66 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
67 u8::decode(buf)?
68 .try_into()
69 .map_err(|_| alloy_rlp::Error::Custom("invalid subblock version"))
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
75pub struct SubBlock {
76 pub version: SubBlockVersion,
78 pub parent_hash: B256,
81 pub fee_recipient: Address,
83 pub transactions: Vec<TempoTxEnvelope>,
85}
86
87impl SubBlock {
88 pub fn signature_hash(&self) -> B256 {
90 let mut buf = Vec::with_capacity(self.length() + 1);
91 buf.put_u8(SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE);
92 self.encode(&mut buf);
93 keccak256(&buf)
94 }
95
96 fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
97 self.version.encode(out);
98 self.parent_hash.encode(out);
99 self.fee_recipient.encode(out);
100 self.transactions.encode(out);
101 }
102
103 fn rlp_encoded_fields_length(&self) -> usize {
104 self.version.length()
105 + self.parent_hash.length()
106 + self.fee_recipient.length()
107 + self.transactions.length()
108 }
109
110 fn rlp_header(&self) -> alloy_rlp::Header {
111 alloy_rlp::Header {
112 list: true,
113 payload_length: self.rlp_encoded_fields_length(),
114 }
115 }
116
117 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
118 Ok(Self {
119 version: Decodable::decode(buf)?,
120 parent_hash: Decodable::decode(buf)?,
121 fee_recipient: Decodable::decode(buf)?,
122 transactions: Decodable::decode(buf)?,
123 })
124 }
125
126 pub fn total_tx_size(&self) -> usize {
128 self.transactions.iter().map(|tx| tx.length()).sum()
129 }
130}
131
132impl Encodable for SubBlock {
133 fn encode(&self, out: &mut dyn BufMut) {
134 self.rlp_header().encode(out);
135 self.rlp_encode_fields(out);
136 }
137}
138
139#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut, PartialEq, Eq)]
141#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
142#[cfg_attr(test, reth_codecs::add_arbitrary_tests(rlp))]
143pub struct SignedSubBlock {
144 #[deref]
146 #[deref_mut]
147 pub inner: SubBlock,
148 pub signature: Bytes,
150}
151
152impl SignedSubBlock {
153 #[cfg(feature = "reth")]
157 pub fn try_into_recovered(
158 self,
159 validator: B256,
160 ) -> Result<RecoveredSubBlock, alloy_consensus::crypto::RecoveryError> {
161 let senders =
162 reth_primitives_traits::transaction::recover::recover_signers(&self.transactions)?;
163
164 Ok(RecoveredSubBlock {
165 inner: self,
166 senders,
167 validator,
168 })
169 }
170
171 fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
172 self.inner.rlp_encode_fields(out);
173 self.signature.encode(out);
174 }
175
176 fn rlp_encoded_fields_length(&self) -> usize {
177 self.inner.rlp_encoded_fields_length() + self.signature.length()
178 }
179
180 fn rlp_header(&self) -> alloy_rlp::Header {
181 alloy_rlp::Header {
182 list: true,
183 payload_length: self.rlp_encoded_fields_length(),
184 }
185 }
186
187 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
188 Ok(Self {
189 inner: SubBlock::rlp_decode_fields(buf)?,
190 signature: Decodable::decode(buf)?,
191 })
192 }
193}
194
195impl Encodable for SignedSubBlock {
196 fn encode(&self, out: &mut dyn BufMut) {
197 self.rlp_header().encode(out);
198 self.rlp_encode_fields(out);
199 }
200
201 fn length(&self) -> usize {
202 self.rlp_header().length_with_payload()
203 }
204}
205
206impl Decodable for SignedSubBlock {
207 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
208 let header = alloy_rlp::Header::decode(buf)?;
209 if !header.list {
210 return Err(alloy_rlp::Error::UnexpectedString);
211 }
212
213 let remaining = buf.len();
214
215 let this = Self::rlp_decode_fields(buf)?;
216
217 if buf.len() + header.payload_length != remaining {
218 return Err(alloy_rlp::Error::UnexpectedLength);
219 }
220
221 Ok(this)
222 }
223}
224
225#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
227pub struct RecoveredSubBlock {
228 #[deref]
230 #[deref_mut]
231 inner: SignedSubBlock,
232
233 senders: Vec<Address>,
235
236 validator: B256,
238}
239
240impl RecoveredSubBlock {
241 pub fn new_unchecked(inner: SignedSubBlock, senders: Vec<Address>, validator: B256) -> Self {
243 Self {
244 inner,
245 senders,
246 validator,
247 }
248 }
249
250 #[inline]
252 pub fn transactions_recovered(&self) -> impl Iterator<Item = Recovered<&TempoTxEnvelope>> + '_ {
253 self.senders
254 .iter()
255 .zip(self.inner.transactions.iter())
256 .map(|(sender, tx)| Recovered::new_unchecked(tx, *sender))
257 }
258
259 pub fn validator(&self) -> B256 {
261 self.validator
262 }
263
264 pub fn metadata(&self) -> SubBlockMetadata {
266 SubBlockMetadata {
267 validator: self.validator,
268 fee_recipient: self.fee_recipient,
269 version: self.version,
270 signature: self.signature.clone(),
271 }
272 }
273}
274
275#[derive(Debug, Clone, RlpEncodable, RlpDecodable)]
277pub struct SubBlockMetadata {
278 pub version: SubBlockVersion,
280 pub validator: B256,
282 pub fee_recipient: Address,
284 pub signature: Bytes,
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_has_sub_block_nonce_key_prefix() {
294 let with_prefix = U256::from(TEMPO_SUBBLOCK_NONCE_KEY_PREFIX) << 248;
296 assert!(has_sub_block_nonce_key_prefix(&with_prefix));
297
298 assert!(!has_sub_block_nonce_key_prefix(&U256::ZERO));
300
301 assert!(!has_sub_block_nonce_key_prefix(&U256::MAX));
303
304 assert!(!has_sub_block_nonce_key_prefix(&U256::from(
306 TEMPO_SUBBLOCK_NONCE_KEY_PREFIX
307 )));
308 }
309
310 #[test]
311 fn test_partial_validator_key_matches() {
312 let partial =
314 PartialValidatorKey::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
315
316 let matching_key = [
318 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
319 ];
320 assert!(
321 partial.matches(matching_key),
322 "Should match when validator starts with partial"
323 );
324
325 let exact_match: [u8; 15] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
327 assert!(partial.matches(exact_match), "Should match exact length");
328
329 let non_matching = [
331 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
332 ];
333 assert!(
334 !partial.matches(non_matching),
335 "Should not match with different first byte"
336 );
337
338 let partial_mismatch = [
340 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 99, 16, 17, 18,
341 ];
342 assert!(
343 !partial.matches(partial_mismatch),
344 "Should not match with different byte in partial range"
345 );
346
347 let too_short: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
349 assert!(
350 !partial.matches(too_short),
351 "Should not match if validator is shorter than partial"
352 );
353
354 let empty: [u8; 0] = [];
356 assert!(!partial.matches(empty), "Should not match empty validator");
357
358 let zero_partial = PartialValidatorKey::ZERO;
360 let zeros = [0u8; 20];
361 assert!(
362 zero_partial.matches(zeros),
363 "Zero partial should match zeros"
364 );
365 }
366
367 #[test]
368 fn test_subblock_signature_and_recovery() {
369 let subblock = SubBlock {
370 version: SubBlockVersion::V1,
371 parent_hash: B256::random(),
372 fee_recipient: Address::random(),
373 transactions: vec![],
374 };
375
376 let hash1 = subblock.signature_hash();
378 let hash2 = subblock.signature_hash();
379 assert_eq!(hash1, hash2, "signature_hash should be deterministic");
380 assert_ne!(hash1, B256::ZERO);
381
382 let subblock2 = SubBlock {
384 version: SubBlockVersion::V1,
385 parent_hash: B256::random(),
386 fee_recipient: Address::random(),
387 transactions: vec![],
388 };
389 assert_ne!(subblock.signature_hash(), subblock2.signature_hash());
390
391 let mut expected_buf = Vec::with_capacity(subblock.length() + 1);
393 expected_buf.put_u8(SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE);
394 subblock.encode(&mut expected_buf);
395 assert_eq!(hash1, keccak256(&expected_buf));
396
397 let signed = SignedSubBlock {
399 inner: subblock.clone(),
400 signature: Bytes::from(vec![1, 2, 3, 4]),
401 };
402
403 let mut buf = Vec::new();
405 signed.encode(&mut buf);
406 assert_eq!(buf.len(), signed.length());
407 let decoded = SignedSubBlock::decode(&mut buf.as_slice()).unwrap();
408 assert_eq!(decoded, signed);
409
410 assert_eq!(signed.version, SubBlockVersion::V1);
412 assert_eq!(signed.fee_recipient, subblock.fee_recipient);
413
414 let validator = B256::random();
416 let recovered = RecoveredSubBlock::new_unchecked(signed.clone(), vec![], validator);
417
418 assert_eq!(recovered.validator(), validator);
420 assert!(recovered.transactions_recovered().next().is_none()); let meta = recovered.metadata();
424 assert_eq!(meta.version, SubBlockVersion::V1);
425 assert_eq!(meta.validator, validator);
426 assert_eq!(meta.fee_recipient, subblock.fee_recipient);
427 assert_eq!(meta.signature, Bytes::from(vec![1, 2, 3, 4]));
428 }
429
430 #[test]
431 fn test_subblock_version_conversion() {
432 assert_eq!(SubBlockVersion::try_from(1u8), Ok(SubBlockVersion::V1));
434 assert_eq!(u8::from(SubBlockVersion::V1), 1);
435
436 assert_eq!(SubBlockVersion::try_from(0u8), Err(0));
438 assert_eq!(SubBlockVersion::try_from(2u8), Err(2));
439 assert_eq!(SubBlockVersion::try_from(255u8), Err(255));
440
441 let mut buf = Vec::new();
443 SubBlockVersion::V1.encode(&mut buf);
444 assert_eq!(buf.len(), SubBlockVersion::V1.length());
445 let decoded = SubBlockVersion::decode(&mut buf.as_slice()).unwrap();
446 assert_eq!(decoded, SubBlockVersion::V1);
447
448 let invalid_buf = [2u8];
450 assert!(SubBlockVersion::decode(&mut invalid_buf.as_slice()).is_err());
451 }
452}