ethrex_common/types/
requests.rs1use crate::H256;
2use bytes::Bytes;
3use ethereum_types::Address;
4use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError};
5use serde::{Deserialize, Serialize};
6use sha2::{Digest, Sha256};
7use tracing::error;
8
9use super::{Bytes48, Receipt};
10use crate::constants::DEPOSIT_TOPIC;
11use crate::serde_utils;
12
13pub type Bytes32 = [u8; 32];
14pub type Bytes96 = [u8; 96];
15const DEPOSIT_TYPE: u8 = 0x00;
16const WITHDRAWAL_TYPE: u8 = 0x01;
17const CONSOLIDATION_TYPE: u8 = 0x02;
18
19#[derive(Clone, Debug)]
20pub struct EncodedRequests(pub Bytes);
21
22impl EncodedRequests {
23 pub fn is_empty(&self) -> bool {
24 self.0.len() <= 1
25 }
26}
27
28impl<'de> Deserialize<'de> for EncodedRequests {
29 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30 where
31 D: serde::Deserializer<'de>,
32 {
33 Ok(EncodedRequests(serde_utils::bytes::deserialize(
34 deserializer,
35 )?))
36 }
37}
38
39impl Serialize for EncodedRequests {
40 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41 where
42 S: serde::Serializer,
43 {
44 serde_utils::bytes::serialize(&self.0, serializer)
45 }
46}
47
48impl RLPEncode for EncodedRequests {
49 fn encode(&self, buf: &mut dyn bytes::BufMut) {
50 self.0.encode(buf)
51 }
52}
53
54impl RLPDecode for EncodedRequests {
55 fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
56 let (bytes, rest) = RLPDecode::decode_unfinished(rlp)?;
57 Ok((EncodedRequests(bytes), rest))
58 }
59}
60
61#[derive(Clone, Debug)]
62pub enum Requests {
63 Deposit(Vec<Deposit>),
64 Withdrawal(Vec<u8>),
65 Consolidation(Vec<u8>),
66}
67
68impl Requests {
69 pub fn encode(&self) -> EncodedRequests {
70 let bytes: Vec<u8> = match self {
71 Requests::Deposit(deposits) => {
72 let deposit_data = deposits.iter().flat_map(|d| d.to_summarized_byte_array());
73 std::iter::once(DEPOSIT_TYPE).chain(deposit_data).collect()
74 }
75 Requests::Withdrawal(data) => std::iter::once(WITHDRAWAL_TYPE)
76 .chain(data.iter().cloned())
77 .collect(),
78 Requests::Consolidation(data) => std::iter::once(CONSOLIDATION_TYPE)
79 .chain(data.iter().cloned())
80 .collect(),
81 };
82
83 EncodedRequests(Bytes::from(bytes))
84 }
85
86 pub fn from_deposit_receipts(
88 deposit_contract_address: Address,
89 receipts: &[Receipt],
90 ) -> Option<Requests> {
91 let mut deposits = vec![];
92
93 for r in receipts {
94 for log in &r.logs {
95 if log.address == deposit_contract_address
96 && log
97 .topics
98 .first()
99 .is_some_and(|topic| topic == &*DEPOSIT_TOPIC)
100 {
101 deposits.push(Deposit::from_abi_byte_array(&log.data)?);
102 }
103 }
104 }
105 Some(Self::Deposit(deposits))
106 }
107
108 pub fn from_withdrawals_data(data: Vec<u8>) -> Requests {
109 Requests::Withdrawal(data)
110 }
111
112 pub fn from_consolidation_data(data: Vec<u8>) -> Requests {
113 Requests::Consolidation(data)
114 }
115}
116
117#[derive(Debug, Clone)]
118pub struct Deposit {
119 pub pub_key: Bytes48,
120 pub withdrawal_credentials: Bytes32,
121 pub amount: u64,
122 pub signature: Bytes96,
123 pub index: u64,
124}
125
126impl Deposit {
129 pub fn from_abi_byte_array(data: &[u8]) -> Option<Deposit> {
130 if data.len() != 576 {
131 error!("Wrong data length when parsing deposit.");
132 return None;
133 }
134
135 const WORD: usize = 32;
147 const U32_TAIL: usize = WORD - 4;
148
149 const PUB_KEY_OFFSET: u32 = 160;
150 const WITHDRAWAL_CREDENTIALS_OFFSET: u32 = 256;
151 const AMOUNT_OFFSET: u32 = 320;
152 const SIGNATURE_OFFSET: u32 = 384;
153 const INDEX_OFFSET: u32 = 512;
154
155 const OFFSETS: [u32; 5] = [
156 PUB_KEY_OFFSET,
157 WITHDRAWAL_CREDENTIALS_OFFSET,
158 AMOUNT_OFFSET,
159 SIGNATURE_OFFSET,
160 INDEX_OFFSET,
161 ];
162
163 const PUB_KEY_SIZE: u32 = 48;
164 const WITHDRAWAL_CREDENTIALS_SIZE: u32 = 32;
165 const AMOUNT_SIZE: u32 = 8;
166 const SIGNATURE_SIZE: u32 = 96;
167 const INDEX_SIZE: u32 = 8;
168
169 const SIZES: [u32; 5] = [
170 PUB_KEY_SIZE,
171 WITHDRAWAL_CREDENTIALS_SIZE,
172 AMOUNT_SIZE,
173 SIGNATURE_SIZE,
174 INDEX_SIZE,
175 ];
176
177 for (i, (expected_offset, expected_size)) in
179 OFFSETS.into_iter().zip(SIZES.into_iter()).enumerate()
180 {
181 let offset = fixed_bytes::<WORD>(data, i * WORD)?;
182 let size = fixed_bytes::<WORD>(data, expected_offset as usize)?;
183 if offset[U32_TAIL..] != expected_offset.to_be_bytes()
184 || size[U32_TAIL..] != expected_size.to_be_bytes()
185 {
186 return None;
187 }
188 }
189
190 let pub_key: Bytes48 =
192 fixed_bytes::<{ PUB_KEY_SIZE as usize }>(data, PUB_KEY_OFFSET as usize + WORD)?;
193 let withdrawal_credentials: Bytes32 =
194 fixed_bytes::<{ WITHDRAWAL_CREDENTIALS_SIZE as usize }>(
195 data,
196 WITHDRAWAL_CREDENTIALS_OFFSET as usize + WORD,
197 )?;
198 let amount: u64 = u64::from_le_bytes(fixed_bytes::<{ AMOUNT_SIZE as usize }>(
199 data,
200 AMOUNT_OFFSET as usize + WORD,
201 )?);
202 let signature: Bytes96 =
203 fixed_bytes::<{ SIGNATURE_SIZE as usize }>(data, SIGNATURE_OFFSET as usize + WORD)?;
204 let index: u64 = u64::from_le_bytes(fixed_bytes::<{ INDEX_SIZE as usize }>(
205 data,
206 INDEX_OFFSET as usize + WORD,
207 )?);
208
209 Some(Deposit {
210 pub_key,
211 withdrawal_credentials,
212 amount,
213 signature,
214 index,
215 })
216 }
217
218 pub fn to_summarized_byte_array(&self) -> [u8; 192] {
219 let mut buffer = [0u8; 192];
220 let mut p = 0;
222 buffer[p..48].clone_from_slice(&self.pub_key);
223 p += 48;
224 buffer[p..p + 32].clone_from_slice(&self.withdrawal_credentials);
225 p += 32;
226 buffer[p..p + 8].clone_from_slice(&self.amount.to_le_bytes());
227 p += 8;
228 buffer[p..p + 96].clone_from_slice(&self.signature);
229 p += 96;
230 buffer[p..p + 8].clone_from_slice(&self.index.to_le_bytes());
231
232 buffer
233 }
234}
235
236fn fixed_bytes<const N: usize>(data: &[u8], offset: usize) -> Option<[u8; N]> {
237 data.get(offset..offset + N)?.try_into().ok()
238}
239
240pub fn compute_requests_hash(requests: &[EncodedRequests]) -> H256 {
242 let mut hasher = Sha256::new();
243 for request in requests {
244 let request_bytes = request.0.as_ref();
245 if request_bytes.len() > 1 {
246 hasher.update(Sha256::digest(request_bytes));
247 }
248 }
249 H256::from_slice(&hasher.finalize())
250}