tempo_primitives/
subblock.rs1use 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
6const SUBBLOCK_SIGNATURE_HASH_MAGIC_BYTE: u8 = 0x78;
8
9pub const TEMPO_SUBBLOCK_NONCE_KEY_PREFIX: u8 = 0x5b;
11
12#[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 pub struct PartialValidatorKey<15>;
21}
22
23impl PartialValidatorKey {
24 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 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 pub version: SubBlockVersion,
77 pub parent_hash: B256,
80 pub fee_recipient: Address,
82 pub transactions: Vec<TempoTxEnvelope>,
84}
85
86impl SubBlock {
87 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 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#[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 #[deref]
145 #[deref_mut]
146 pub inner: SubBlock,
147 pub signature: Bytes,
149}
150
151impl SignedSubBlock {
152 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#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
222pub struct RecoveredSubBlock {
223 #[deref]
225 #[deref_mut]
226 inner: SignedSubBlock,
227
228 senders: Vec<Address>,
230
231 validator: B256,
233}
234
235impl RecoveredSubBlock {
236 pub fn new_unchecked(inner: SignedSubBlock, senders: Vec<Address>, validator: B256) -> Self {
238 Self {
239 inner,
240 senders,
241 validator,
242 }
243 }
244
245 #[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 pub fn validator(&self) -> B256 {
256 self.validator
257 }
258
259 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#[derive(Debug, Clone, RlpEncodable, RlpDecodable)]
272pub struct SubBlockMetadata {
273 pub version: SubBlockVersion,
275 pub validator: B256,
277 pub fee_recipient: Address,
279 pub signature: Bytes,
281}