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 fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
154 self.inner.rlp_encode_fields(out);
155 self.signature.encode(out);
156 }
157
158 fn rlp_encoded_fields_length(&self) -> usize {
159 self.inner.rlp_encoded_fields_length() + self.signature.length()
160 }
161
162 fn rlp_header(&self) -> alloy_rlp::Header {
163 alloy_rlp::Header {
164 list: true,
165 payload_length: self.rlp_encoded_fields_length(),
166 }
167 }
168
169 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
170 Ok(Self {
171 inner: SubBlock::rlp_decode_fields(buf)?,
172 signature: Decodable::decode(buf)?,
173 })
174 }
175}
176
177impl Encodable for SignedSubBlock {
178 fn encode(&self, out: &mut dyn BufMut) {
179 self.rlp_header().encode(out);
180 self.rlp_encode_fields(out);
181 }
182
183 fn length(&self) -> usize {
184 self.rlp_header().length_with_payload()
185 }
186}
187
188impl Decodable for SignedSubBlock {
189 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
190 let header = alloy_rlp::Header::decode(buf)?;
191 if !header.list {
192 return Err(alloy_rlp::Error::UnexpectedString);
193 }
194
195 let remaining = buf.len();
196
197 let this = Self::rlp_decode_fields(buf)?;
198
199 if buf.len() + header.payload_length != remaining {
200 return Err(alloy_rlp::Error::UnexpectedLength);
201 }
202
203 Ok(this)
204 }
205}
206
207#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
209pub struct RecoveredSubBlock {
210 #[deref]
212 #[deref_mut]
213 inner: SignedSubBlock,
214
215 senders: Vec<Address>,
217
218 validator: B256,
220}
221
222impl RecoveredSubBlock {
223 pub fn new_unchecked(inner: SignedSubBlock, senders: Vec<Address>, validator: B256) -> Self {
225 Self {
226 inner,
227 senders,
228 validator,
229 }
230 }
231
232 #[inline]
234 pub fn transactions_recovered(&self) -> impl Iterator<Item = Recovered<&TempoTxEnvelope>> + '_ {
235 self.senders
236 .iter()
237 .zip(self.inner.transactions.iter())
238 .map(|(sender, tx)| Recovered::new_unchecked(tx, *sender))
239 }
240
241 pub fn into_recovered_iter(self) -> impl Iterator<Item = Recovered<TempoTxEnvelope>> {
243 self.senders
244 .into_iter()
245 .zip(self.inner.inner.transactions)
246 .map(|(sender, tx)| Recovered::new_unchecked(tx, sender))
247 }
248
249 pub fn has_expired_transactions(&self, timestamp: u64) -> bool {
251 self.transactions.iter().any(|tx| {
252 tx.as_aa().is_some_and(|tx| {
253 tx.tx()
254 .valid_before
255 .is_some_and(|valid| valid.get() <= timestamp)
256 })
257 })
258 }
259
260 pub fn validator(&self) -> B256 {
262 self.validator
263 }
264
265 pub fn metadata(&self) -> SubBlockMetadata {
267 SubBlockMetadata {
268 validator: self.validator,
269 fee_recipient: self.fee_recipient,
270 version: self.version,
271 signature: self.signature.clone(),
272 }
273 }
274}
275
276#[derive(Debug, Clone, RlpEncodable, RlpDecodable)]
278pub struct SubBlockMetadata {
279 pub version: SubBlockVersion,
281 pub validator: B256,
283 pub fee_recipient: Address,
285 pub signature: Bytes,
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn test_has_sub_block_nonce_key_prefix() {
295 let with_prefix = U256::from(TEMPO_SUBBLOCK_NONCE_KEY_PREFIX) << 248;
297 assert!(has_sub_block_nonce_key_prefix(&with_prefix));
298
299 assert!(!has_sub_block_nonce_key_prefix(&U256::ZERO));
301
302 assert!(!has_sub_block_nonce_key_prefix(&U256::MAX));
304
305 assert!(!has_sub_block_nonce_key_prefix(&U256::from(
307 TEMPO_SUBBLOCK_NONCE_KEY_PREFIX
308 )));
309 }
310
311 #[test]
312 fn test_partial_validator_key_matches() {
313 let partial =
315 PartialValidatorKey::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
316
317 let matching_key = [
319 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
320 ];
321 assert!(
322 partial.matches(matching_key),
323 "Should match when validator starts with partial"
324 );
325
326 let exact_match: [u8; 15] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
328 assert!(partial.matches(exact_match), "Should match exact length");
329
330 let non_matching = [
332 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
333 ];
334 assert!(
335 !partial.matches(non_matching),
336 "Should not match with different first byte"
337 );
338
339 let partial_mismatch = [
341 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 99, 16, 17, 18,
342 ];
343 assert!(
344 !partial.matches(partial_mismatch),
345 "Should not match with different byte in partial range"
346 );
347
348 let too_short: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
350 assert!(
351 !partial.matches(too_short),
352 "Should not match if validator is shorter than partial"
353 );
354
355 let empty: [u8; 0] = [];
357 assert!(!partial.matches(empty), "Should not match empty validator");
358
359 let zero_partial = PartialValidatorKey::ZERO;
361 let zeros = [0u8; 20];
362 assert!(
363 zero_partial.matches(zeros),
364 "Zero partial should match zeros"
365 );
366 }
367
368 #[test]
369 fn test_subblock_signature_and_recovery() {
370 let subblock = SubBlock {
371 version: SubBlockVersion::V1,
372 parent_hash: B256::random(),
373 fee_recipient: Address::random(),
374 transactions: vec![],
375 };
376
377 let hash1 = subblock.signature_hash();
379 let hash2 = subblock.signature_hash();
380 assert_eq!(hash1, hash2, "signature_hash should be deterministic");
381 assert_ne!(hash1, B256::ZERO);
382
383 let subblock2 = SubBlock {
385 version: SubBlockVersion::V1,
386 parent_hash: B256::random(),
387 fee_recipient: Address::random(),
388 transactions: vec![],
389 };
390 assert_ne!(subblock.signature_hash(), subblock2.signature_hash());
391
392 let mut expected_buf = Vec::with_capacity(subblock.length() + 1);
394 expected_buf.put_u8(SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE);
395 subblock.encode(&mut expected_buf);
396 assert_eq!(hash1, keccak256(&expected_buf));
397
398 let signed = SignedSubBlock {
400 inner: subblock.clone(),
401 signature: Bytes::from(vec![1, 2, 3, 4]),
402 };
403
404 let mut buf = Vec::new();
406 signed.encode(&mut buf);
407 assert_eq!(buf.len(), signed.length());
408 let decoded = SignedSubBlock::decode(&mut buf.as_slice()).unwrap();
409 assert_eq!(decoded, signed);
410
411 assert_eq!(signed.version, SubBlockVersion::V1);
413 assert_eq!(signed.fee_recipient, subblock.fee_recipient);
414
415 let validator = B256::random();
417 let recovered = RecoveredSubBlock::new_unchecked(signed.clone(), vec![], validator);
418
419 assert_eq!(recovered.validator(), validator);
421 assert!(recovered.transactions_recovered().next().is_none()); let meta = recovered.metadata();
425 assert_eq!(meta.version, SubBlockVersion::V1);
426 assert_eq!(meta.validator, validator);
427 assert_eq!(meta.fee_recipient, subblock.fee_recipient);
428 assert_eq!(meta.signature, Bytes::from(vec![1, 2, 3, 4]));
429 }
430
431 #[test]
432 fn test_subblock_version_conversion() {
433 assert_eq!(SubBlockVersion::try_from(1u8), Ok(SubBlockVersion::V1));
435 assert_eq!(u8::from(SubBlockVersion::V1), 1);
436
437 assert_eq!(SubBlockVersion::try_from(0u8), Err(0));
439 assert_eq!(SubBlockVersion::try_from(2u8), Err(2));
440 assert_eq!(SubBlockVersion::try_from(255u8), Err(255));
441
442 let mut buf = Vec::new();
444 SubBlockVersion::V1.encode(&mut buf);
445 assert_eq!(buf.len(), SubBlockVersion::V1.length());
446 let decoded = SubBlockVersion::decode(&mut buf.as_slice()).unwrap();
447 assert_eq!(decoded, SubBlockVersion::V1);
448
449 let invalid_buf = [2u8];
451 assert!(SubBlockVersion::decode(&mut invalid_buf.as_slice()).is_err());
452 }
453}