eth_valkyoth_protocol/transaction/
blob.rs1use eth_valkyoth_codec::{DecodeError, DecodeLimits, RlpItem, RlpList};
2use eth_valkyoth_primitives::{Address, B256, ChainId, Gas, Nonce, Wei};
3
4use super::access_list::{AccessListDecodeError, decode_access_list};
5use super::{AccessList, SignatureYParity, TransactionEnvelope, decode_transaction_envelope};
6use crate::transaction::fields::{
7 decode_chain_id as decode_shared_chain_id, decode_required_address,
8 decode_u64_field as decode_shared_u64_field, decode_u256_field as decode_shared_u256_field,
9 next_list as next_shared_list, next_scalar as next_shared_scalar,
10};
11
12mod error;
13
14pub use error::{BlobTransactionDecodeError, BlobTransactionDecodeErrorCategory};
15
16pub const BLOB_TRANSACTION_TYPE: u8 = 0x03;
18pub const BLOB_TRANSACTION_FIELD_COUNT: usize = 14;
20
21const B256_BYTES: usize = 32;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub struct UnvalidatedBlobTransaction<'a> {
31 pub chain_id: ChainId,
33 pub nonce: Nonce,
35 pub max_priority_fee_per_gas: Wei,
37 pub max_fee_per_gas: Wei,
39 pub gas_limit: Gas,
41 pub to: Address,
43 pub value: Wei,
45 pub input: &'a [u8],
47 pub access_list: AccessList<'a>,
49 pub max_fee_per_blob_gas: Wei,
51 pub blob_versioned_hashes: BlobVersionedHashes<'a>,
53 pub y_parity: SignatureYParity,
55 pub r: [u8; 32],
59 pub s: [u8; 32],
64}
65
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub struct BlobVersionedHashes<'a> {
69 list: RlpList<'a>,
70}
71
72impl<'a> BlobVersionedHashes<'a> {
73 #[must_use]
75 pub const fn len(self) -> usize {
76 self.list.item_count()
77 }
78
79 #[must_use]
85 pub const fn is_empty(self) -> bool {
86 self.list.is_empty()
87 }
88
89 #[must_use]
95 pub const fn hashes(self) -> BlobVersionedHashItems<'a> {
96 BlobVersionedHashItems {
97 items: self.list.items(),
98 }
99 }
100
101 pub(crate) const fn encoded_rlp_len(self) -> usize {
103 self.list.encoded_len()
104 }
105
106 pub(crate) fn encode_rlp(self, output: &mut [u8]) -> Result<usize, DecodeError> {
108 eth_valkyoth_codec::encode_decoded_list(self.list, output)
109 }
110}
111
112#[derive(Clone, Debug)]
114pub struct BlobVersionedHashItems<'a> {
115 items: eth_valkyoth_codec::RlpListItems<'a>,
116}
117
118impl Iterator for BlobVersionedHashItems<'_> {
119 type Item = Result<B256, BlobTransactionDecodeError>;
120
121 fn next(&mut self) -> Option<Self::Item> {
122 self.items.next().map(decode_blob_versioned_hash_item)
123 }
124}
125
126impl core::iter::FusedIterator for BlobVersionedHashItems<'_> {}
127
128#[non_exhaustive]
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub enum BlobTransactionField {
132 Payload,
134 ChainId,
136 Nonce,
138 MaxPriorityFeePerGas,
140 MaxFeePerGas,
142 GasLimit,
144 To,
146 Value,
148 Data,
150 AccessList,
152 MaxFeePerBlobGas,
154 BlobVersionedHashes,
156 SignatureYParity,
158 SignatureR,
160 SignatureS,
162}
163
164pub fn decode_blob_transaction<'a>(
166 input: &'a [u8],
167 limits: DecodeLimits,
168) -> Result<UnvalidatedBlobTransaction<'a>, BlobTransactionDecodeError> {
169 match decode_transaction_envelope(input, limits)
170 .map_err(BlobTransactionDecodeError::Envelope)?
171 {
172 TransactionEnvelope::Typed(typed)
173 if typed.transaction_type.get() == BLOB_TRANSACTION_TYPE =>
174 {
175 decode_blob_payload(typed.payload, limits)
176 }
177 TransactionEnvelope::Typed(typed) => {
178 Err(BlobTransactionDecodeError::WrongTransactionType {
179 type_byte: typed.transaction_type.get(),
180 })
181 }
182 TransactionEnvelope::Legacy(_) => {
183 Err(BlobTransactionDecodeError::WrongTransactionType { type_byte: 0 })
184 }
185 }
186}
187
188fn decode_blob_payload<'a>(
189 payload: &'a [u8],
190 limits: DecodeLimits,
191) -> Result<UnvalidatedBlobTransaction<'a>, BlobTransactionDecodeError> {
192 let list = eth_valkyoth_codec::decode_rlp_list(payload, limits)
193 .map_err(|source| field_error(BlobTransactionField::Payload, source))?;
194 if list.item_count() != BLOB_TRANSACTION_FIELD_COUNT {
195 return Err(BlobTransactionDecodeError::WrongFieldCount {
196 expected: BLOB_TRANSACTION_FIELD_COUNT,
197 found: list.item_count(),
198 });
199 }
200
201 let mut fields = list.items();
202 let chain_id = decode_shared_chain_id(&mut fields, BlobTransactionField::ChainId, field_error)?;
203 let nonce = Nonce::new(decode_shared_u64_field(
204 &mut fields,
205 BlobTransactionField::Nonce,
206 field_error,
207 )?);
208 let max_priority_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
209 &mut fields,
210 BlobTransactionField::MaxPriorityFeePerGas,
211 field_error,
212 )?);
213 let max_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
214 &mut fields,
215 BlobTransactionField::MaxFeePerGas,
216 field_error,
217 )?);
218 let gas_limit = Gas::new(decode_shared_u64_field(
219 &mut fields,
220 BlobTransactionField::GasLimit,
221 field_error,
222 )?);
223 let to = decode_required_address(
224 next_shared_scalar(&mut fields, BlobTransactionField::To, field_error)?,
225 |found| BlobTransactionDecodeError::InvalidToLength { found },
226 )?;
227 let value = Wei::from_be_bytes(decode_shared_u256_field(
228 &mut fields,
229 BlobTransactionField::Value,
230 field_error,
231 )?);
232 let input = next_shared_scalar(&mut fields, BlobTransactionField::Data, field_error)?.payload();
233 limits
234 .check_single_allocation_limit(input.len())
235 .map_err(|source| field_error(BlobTransactionField::Data, source))?;
236 let access_list = decode_access_list(next_shared_list(
237 &mut fields,
238 BlobTransactionField::AccessList,
239 field_error,
240 )?)
241 .map_err(map_access_list_error)?;
242 let max_fee_per_blob_gas = Wei::from_be_bytes(decode_shared_u256_field(
243 &mut fields,
244 BlobTransactionField::MaxFeePerBlobGas,
245 field_error,
246 )?);
247 let blob_versioned_hashes = decode_blob_versioned_hashes(next_shared_list(
248 &mut fields,
249 BlobTransactionField::BlobVersionedHashes,
250 field_error,
251 )?)?;
252 let y_parity = SignatureYParity::try_new(decode_shared_u64_field(
253 &mut fields,
254 BlobTransactionField::SignatureYParity,
255 field_error,
256 )?)
257 .map_err(|error| BlobTransactionDecodeError::InvalidYParity {
258 value: error.value(),
259 })?;
260 let r = decode_shared_u256_field(&mut fields, BlobTransactionField::SignatureR, field_error)?;
261 let s = decode_shared_u256_field(&mut fields, BlobTransactionField::SignatureS, field_error)?;
262
263 Ok(UnvalidatedBlobTransaction {
264 chain_id,
265 nonce,
266 max_priority_fee_per_gas,
267 max_fee_per_gas,
268 gas_limit,
269 to,
270 value,
271 input,
272 access_list,
273 max_fee_per_blob_gas,
274 blob_versioned_hashes,
275 y_parity,
276 r,
277 s,
278 })
279}
280
281fn decode_blob_versioned_hashes(
282 list: RlpList<'_>,
283) -> Result<BlobVersionedHashes<'_>, BlobTransactionDecodeError> {
284 for item in list.items() {
285 let _ = decode_blob_versioned_hash_item(item)?;
286 }
287 Ok(BlobVersionedHashes { list })
288}
289
290fn decode_blob_versioned_hash_item(
291 item: Result<RlpItem<'_>, DecodeError>,
292) -> Result<B256, BlobTransactionDecodeError> {
293 let item =
294 item.map_err(|source| field_error(BlobTransactionField::BlobVersionedHashes, source))?;
295 let RlpItem::Scalar(scalar) = item else {
296 return Err(field_error(
297 BlobTransactionField::BlobVersionedHashes,
298 DecodeError::UnexpectedList,
299 ));
300 };
301 let found = scalar.payload().len();
302 let bytes: [u8; B256_BYTES] = scalar
303 .payload()
304 .try_into()
305 .map_err(|_| BlobTransactionDecodeError::InvalidBlobVersionedHashLength { found })?;
306 Ok(B256::from_bytes(bytes))
307}
308
309const fn field_error(
310 field: BlobTransactionField,
311 source: DecodeError,
312) -> BlobTransactionDecodeError {
313 BlobTransactionDecodeError::FieldDecode { field, source }
314}
315
316const fn map_access_list_error(error: AccessListDecodeError) -> BlobTransactionDecodeError {
317 match error {
318 AccessListDecodeError::FieldDecode(source) => {
319 field_error(BlobTransactionField::AccessList, source)
320 }
321 AccessListDecodeError::InvalidAccessListEntryFieldCount { found } => {
322 BlobTransactionDecodeError::InvalidAccessListEntryFieldCount { found }
323 }
324 AccessListDecodeError::InvalidAccessListAddressLength { found } => {
325 BlobTransactionDecodeError::InvalidAccessListAddressLength { found }
326 }
327 AccessListDecodeError::InvalidStorageKeyLength { found } => {
328 BlobTransactionDecodeError::InvalidStorageKeyLength { found }
329 }
330 }
331}