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
102#[derive(Clone, Debug)]
104pub struct BlobVersionedHashItems<'a> {
105 items: eth_valkyoth_codec::RlpListItems<'a>,
106}
107
108impl Iterator for BlobVersionedHashItems<'_> {
109 type Item = Result<B256, BlobTransactionDecodeError>;
110
111 fn next(&mut self) -> Option<Self::Item> {
112 self.items.next().map(decode_blob_versioned_hash_item)
113 }
114}
115
116impl core::iter::FusedIterator for BlobVersionedHashItems<'_> {}
117
118#[non_exhaustive]
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
121pub enum BlobTransactionField {
122 Payload,
124 ChainId,
126 Nonce,
128 MaxPriorityFeePerGas,
130 MaxFeePerGas,
132 GasLimit,
134 To,
136 Value,
138 Data,
140 AccessList,
142 MaxFeePerBlobGas,
144 BlobVersionedHashes,
146 SignatureYParity,
148 SignatureR,
150 SignatureS,
152}
153
154pub fn decode_blob_transaction<'a>(
156 input: &'a [u8],
157 limits: DecodeLimits,
158) -> Result<UnvalidatedBlobTransaction<'a>, BlobTransactionDecodeError> {
159 match decode_transaction_envelope(input, limits)
160 .map_err(BlobTransactionDecodeError::Envelope)?
161 {
162 TransactionEnvelope::Typed(typed)
163 if typed.transaction_type.get() == BLOB_TRANSACTION_TYPE =>
164 {
165 decode_blob_payload(typed.payload, limits)
166 }
167 TransactionEnvelope::Typed(typed) => {
168 Err(BlobTransactionDecodeError::WrongTransactionType {
169 type_byte: typed.transaction_type.get(),
170 })
171 }
172 TransactionEnvelope::Legacy(_) => {
173 Err(BlobTransactionDecodeError::WrongTransactionType { type_byte: 0 })
174 }
175 }
176}
177
178fn decode_blob_payload<'a>(
179 payload: &'a [u8],
180 limits: DecodeLimits,
181) -> Result<UnvalidatedBlobTransaction<'a>, BlobTransactionDecodeError> {
182 let list = eth_valkyoth_codec::decode_rlp_list(payload, limits)
183 .map_err(|source| field_error(BlobTransactionField::Payload, source))?;
184 if list.item_count() != BLOB_TRANSACTION_FIELD_COUNT {
185 return Err(BlobTransactionDecodeError::WrongFieldCount {
186 expected: BLOB_TRANSACTION_FIELD_COUNT,
187 found: list.item_count(),
188 });
189 }
190
191 let mut fields = list.items();
192 let chain_id = decode_shared_chain_id(&mut fields, BlobTransactionField::ChainId, field_error)?;
193 let nonce = Nonce::new(decode_shared_u64_field(
194 &mut fields,
195 BlobTransactionField::Nonce,
196 field_error,
197 )?);
198 let max_priority_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
199 &mut fields,
200 BlobTransactionField::MaxPriorityFeePerGas,
201 field_error,
202 )?);
203 let max_fee_per_gas = Wei::from_be_bytes(decode_shared_u256_field(
204 &mut fields,
205 BlobTransactionField::MaxFeePerGas,
206 field_error,
207 )?);
208 let gas_limit = Gas::new(decode_shared_u64_field(
209 &mut fields,
210 BlobTransactionField::GasLimit,
211 field_error,
212 )?);
213 let to = decode_required_address(
214 next_shared_scalar(&mut fields, BlobTransactionField::To, field_error)?,
215 |found| BlobTransactionDecodeError::InvalidToLength { found },
216 )?;
217 let value = Wei::from_be_bytes(decode_shared_u256_field(
218 &mut fields,
219 BlobTransactionField::Value,
220 field_error,
221 )?);
222 let input = next_shared_scalar(&mut fields, BlobTransactionField::Data, field_error)?.payload();
223 limits
224 .check_single_allocation_limit(input.len())
225 .map_err(|source| field_error(BlobTransactionField::Data, source))?;
226 let access_list = decode_access_list(next_shared_list(
227 &mut fields,
228 BlobTransactionField::AccessList,
229 field_error,
230 )?)
231 .map_err(map_access_list_error)?;
232 let max_fee_per_blob_gas = Wei::from_be_bytes(decode_shared_u256_field(
233 &mut fields,
234 BlobTransactionField::MaxFeePerBlobGas,
235 field_error,
236 )?);
237 let blob_versioned_hashes = decode_blob_versioned_hashes(next_shared_list(
238 &mut fields,
239 BlobTransactionField::BlobVersionedHashes,
240 field_error,
241 )?)?;
242 let y_parity = SignatureYParity::try_new(decode_shared_u64_field(
243 &mut fields,
244 BlobTransactionField::SignatureYParity,
245 field_error,
246 )?)
247 .map_err(|error| BlobTransactionDecodeError::InvalidYParity {
248 value: error.value(),
249 })?;
250 let r = decode_shared_u256_field(&mut fields, BlobTransactionField::SignatureR, field_error)?;
251 let s = decode_shared_u256_field(&mut fields, BlobTransactionField::SignatureS, field_error)?;
252
253 Ok(UnvalidatedBlobTransaction {
254 chain_id,
255 nonce,
256 max_priority_fee_per_gas,
257 max_fee_per_gas,
258 gas_limit,
259 to,
260 value,
261 input,
262 access_list,
263 max_fee_per_blob_gas,
264 blob_versioned_hashes,
265 y_parity,
266 r,
267 s,
268 })
269}
270
271fn decode_blob_versioned_hashes(
272 list: RlpList<'_>,
273) -> Result<BlobVersionedHashes<'_>, BlobTransactionDecodeError> {
274 for item in list.items() {
275 let _ = decode_blob_versioned_hash_item(item)?;
276 }
277 Ok(BlobVersionedHashes { list })
278}
279
280fn decode_blob_versioned_hash_item(
281 item: Result<RlpItem<'_>, DecodeError>,
282) -> Result<B256, BlobTransactionDecodeError> {
283 let item =
284 item.map_err(|source| field_error(BlobTransactionField::BlobVersionedHashes, source))?;
285 let RlpItem::Scalar(scalar) = item else {
286 return Err(field_error(
287 BlobTransactionField::BlobVersionedHashes,
288 DecodeError::UnexpectedList,
289 ));
290 };
291 let found = scalar.payload().len();
292 let bytes: [u8; B256_BYTES] = scalar
293 .payload()
294 .try_into()
295 .map_err(|_| BlobTransactionDecodeError::InvalidBlobVersionedHashLength { found })?;
296 Ok(B256::from_bytes(bytes))
297}
298
299const fn field_error(
300 field: BlobTransactionField,
301 source: DecodeError,
302) -> BlobTransactionDecodeError {
303 BlobTransactionDecodeError::FieldDecode { field, source }
304}
305
306const fn map_access_list_error(error: AccessListDecodeError) -> BlobTransactionDecodeError {
307 match error {
308 AccessListDecodeError::FieldDecode(source) => {
309 field_error(BlobTransactionField::AccessList, source)
310 }
311 AccessListDecodeError::InvalidAccessListEntryFieldCount { found } => {
312 BlobTransactionDecodeError::InvalidAccessListEntryFieldCount { found }
313 }
314 AccessListDecodeError::InvalidAccessListAddressLength { found } => {
315 BlobTransactionDecodeError::InvalidAccessListAddressLength { found }
316 }
317 AccessListDecodeError::InvalidStorageKeyLength { found } => {
318 BlobTransactionDecodeError::InvalidStorageKeyLength { found }
319 }
320 }
321}