alloy_consensus/transaction/
pooled.rs1use super::EthereumTxEnvelope;
5use crate::{error::ValueError, Signed, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
6use alloy_eips::eip7594::{
7 BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant, Encodable7594,
8};
9
10pub type PooledTransaction =
27 EthereumTxEnvelope<TxEip4844WithSidecar<BlobTransactionSidecarEip7594>>;
28
29impl EthereumTxEnvelope<TxEip4844WithSidecar<BlobTransactionSidecarEip7594>> {
30 pub fn clear_eip7594_blobs(&mut self) {
37 if let Self::Eip4844(tx) = self {
38 tx.tx_mut().sidecar.clear_eip7594_blobs();
39 }
40 }
41}
42
43impl EthereumTxEnvelope<TxEip4844WithSidecar<BlobTransactionSidecarVariant>> {
44 pub fn clear_eip7594_blobs(&mut self) {
51 if let Self::Eip4844(tx) = self {
52 tx.tx_mut().sidecar.clear_eip7594_blobs();
53 }
54 }
55}
56
57impl<T: Encodable7594> EthereumTxEnvelope<TxEip4844WithSidecar<T>> {
58 pub fn into_envelope(self) -> EthereumTxEnvelope<TxEip4844Variant<T>> {
60 match self {
61 Self::Legacy(tx) => tx.into(),
62 Self::Eip2930(tx) => tx.into(),
63 Self::Eip1559(tx) => tx.into(),
64 Self::Eip7702(tx) => tx.into(),
65 Self::Eip4844(tx) => tx.into(),
66 }
67 }
68}
69
70impl<T: Encodable7594> TryFrom<Signed<TxEip4844Variant<T>>>
71 for EthereumTxEnvelope<TxEip4844WithSidecar<T>>
72{
73 type Error = ValueError<Signed<TxEip4844Variant<T>>>;
74
75 fn try_from(value: Signed<TxEip4844Variant<T>>) -> Result<Self, Self::Error> {
76 let (value, signature, hash) = value.into_parts();
77 match value {
78 tx @ TxEip4844Variant::TxEip4844(_) => Err(ValueError::new_static(
79 Signed::new_unchecked(tx, signature, hash),
80 "pooled transaction requires 4844 sidecar",
81 )),
82 TxEip4844Variant::TxEip4844WithSidecar(tx) => {
83 Ok(Signed::new_unchecked(tx, signature, hash).into())
84 }
85 }
86 }
87}
88
89impl<T: Encodable7594> TryFrom<EthereumTxEnvelope<TxEip4844Variant<T>>>
90 for EthereumTxEnvelope<TxEip4844WithSidecar<T>>
91{
92 type Error = ValueError<EthereumTxEnvelope<TxEip4844Variant<T>>>;
93
94 fn try_from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Result<Self, Self::Error> {
95 value.try_into_pooled()
96 }
97}
98
99impl<T: Encodable7594> TryFrom<EthereumTxEnvelope<TxEip4844>>
100 for EthereumTxEnvelope<TxEip4844WithSidecar<T>>
101{
102 type Error = ValueError<EthereumTxEnvelope<TxEip4844>>;
103
104 fn try_from(value: EthereumTxEnvelope<TxEip4844>) -> Result<Self, Self::Error> {
105 value.try_into_pooled()
106 }
107}
108
109impl<T: Encodable7594> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>>
110 for EthereumTxEnvelope<TxEip4844Variant<T>>
111{
112 fn from(tx: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
113 tx.into_envelope()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::Transaction;
121 use alloy_eips::{
122 eip4844::{Blob, Bytes48},
123 eip7594::CELLS_PER_EXT_BLOB,
124 Decodable2718, Encodable2718,
125 };
126 use alloy_primitives::{address, hex, Bytes, Signature, U256};
127 use alloy_rlp::Decodable;
128 use std::path::PathBuf;
129
130 fn eip7594_sidecar() -> BlobTransactionSidecarEip7594 {
131 BlobTransactionSidecarEip7594::new(
132 vec![Blob::repeat_byte(0x01)],
133 vec![Bytes48::repeat_byte(0x02)],
134 vec![Bytes48::repeat_byte(0x03); CELLS_PER_EXT_BLOB],
135 )
136 }
137
138 fn signature() -> Signature {
139 Signature::new(U256::from(1), U256::from(2), false)
140 }
141
142 #[test]
143 fn clear_eip7594_blobs_from_pooled_envelopes() {
144 let sidecar = eip7594_sidecar();
145 let commitments = sidecar.commitments.clone();
146 let cell_proofs = sidecar.cell_proofs.clone();
147 let tx = TxEip4844WithSidecar::from_tx_and_sidecar(TxEip4844::default(), sidecar);
148 let mut pooled: PooledTransaction = Signed::new_unhashed(tx, signature()).into();
149
150 pooled.clear_eip7594_blobs();
151
152 let sidecar = &pooled.as_eip4844().unwrap().tx().sidecar;
153 assert!(sidecar.blobs.is_empty());
154 assert_eq!(sidecar.commitments, commitments);
155 assert_eq!(sidecar.cell_proofs, cell_proofs);
156
157 let tx = TxEip4844WithSidecar::from_tx_and_sidecar(
158 TxEip4844::default(),
159 BlobTransactionSidecarVariant::Eip7594(eip7594_sidecar()),
160 );
161 let mut pooled = EthereumTxEnvelope::Eip4844(Signed::new_unhashed(tx, signature()));
162
163 pooled.clear_eip7594_blobs();
164
165 let sidecar = pooled.as_eip4844().unwrap().tx().sidecar.as_eip7594().unwrap();
166 assert!(sidecar.blobs.is_empty());
167 assert_eq!(sidecar.commitments, commitments);
168 assert_eq!(sidecar.cell_proofs, cell_proofs);
169 }
170
171 #[test]
172 fn invalid_legacy_pooled_decoding_input_too_short() {
173 let input_too_short = [
174 &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
176 &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
182 &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
183 &hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
186 &hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
187 ];
188
189 for hex_data in &input_too_short {
190 let input_rlp = &mut &hex_data[..];
191 let res = PooledTransaction::decode(input_rlp);
192
193 assert!(
194 res.is_err(),
195 "expected err after decoding rlp input: {:x?}",
196 Bytes::copy_from_slice(hex_data)
197 );
198
199 let input_rlp = &mut &hex_data[..];
201 let res = PooledTransaction::decode_2718(input_rlp);
202
203 assert!(
204 res.is_err(),
205 "expected err after decoding enveloped rlp input: {:x?}",
206 Bytes::copy_from_slice(hex_data)
207 );
208 }
209 }
210
211 #[test]
213 fn decode_eip1559_enveloped() {
214 let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
215);
216
217 let res = PooledTransaction::decode_2718(&mut &data[..]).unwrap();
218 assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c")));
219 }
220
221 #[test]
222 fn legacy_valid_pooled_decoding() {
223 let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
234
235 let input_rlp = &mut &data[..];
236 let res = PooledTransaction::decode(input_rlp);
237 assert!(res.is_ok());
238 assert!(input_rlp.is_empty());
239
240 let res = PooledTransaction::decode_2718(&mut &data[..]);
242 assert!(res.is_ok());
243 }
244
245 #[test]
246 fn decode_encode_raw_4844_rlp() {
247 type VariantPooledTransaction = EthereumTxEnvelope<TxEip4844WithSidecar>;
250
251 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
252 let dir = std::fs::read_dir(path).expect("Unable to read folder");
253 for entry in dir {
254 let entry = entry.unwrap();
255 let content = std::fs::read_to_string(entry.path()).unwrap();
256 let raw = hex::decode(content.trim()).unwrap();
257 let tx = VariantPooledTransaction::decode_2718(&mut raw.as_ref())
258 .map_err(|err| {
259 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
260 })
261 .unwrap();
262 assert!(tx.is_eip4844());
264 let encoded = tx.encoded_2718();
265 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
266 }
267 }
268
269 #[test]
270 #[cfg(feature = "kzg")]
271 fn convert_to_eip7594() {
272 type VariantPooledTransaction = EthereumTxEnvelope<TxEip4844WithSidecar>;
275
276 let kzg_settings = alloy_eips::eip4844::env_settings::EnvKzgSettings::default();
277 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
278 let dir = std::fs::read_dir(path).expect("Unable to read folder");
279 for entry in dir {
280 let entry = entry.unwrap();
281 let content = std::fs::read_to_string(entry.path()).unwrap();
282 let raw = hex::decode(content.trim()).unwrap();
283 let VariantPooledTransaction::Eip4844(tx) =
284 VariantPooledTransaction::decode_2718(&mut raw.as_ref())
285 .map_err(|err| {
286 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
287 })
288 .unwrap()
289 else {
290 panic!("Expected EIP-4844 transaction");
291 };
292 let tx = tx.into_parts().0;
293 assert!(!tx.sidecar.blobs().is_empty());
294 assert!(tx.validate_blob(kzg_settings.get()).is_ok());
295
296 let tx = tx
297 .try_map_sidecar(|sidecar| {
298 sidecar.try_convert_into_eip7594_with_settings(kzg_settings.get())
299 })
300 .unwrap();
301
302 assert!(!tx.sidecar.blobs().is_empty());
303 assert!(tx.validate_blob(kzg_settings.get()).is_ok());
304 }
305 }
306
307 #[test]
310 #[cfg(feature = "kzg")]
311 fn pooled_transaction_eip7594_roundtrip() {
312 type VariantPooledTransaction = EthereumTxEnvelope<TxEip4844WithSidecar>;
315
316 let kzg_settings = alloy_eips::eip4844::env_settings::EnvKzgSettings::default();
317 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
318 let dir = std::fs::read_dir(path).expect("Unable to read folder");
319 for entry in dir {
320 let entry = entry.unwrap();
321 let content = std::fs::read_to_string(entry.path()).unwrap();
322 let raw = hex::decode(content.trim()).unwrap();
323 let VariantPooledTransaction::Eip4844(tx) =
324 VariantPooledTransaction::decode_2718(&mut raw.as_ref())
325 .map_err(|err| {
326 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
327 })
328 .unwrap()
329 else {
330 panic!("Expected EIP-4844 transaction");
331 };
332
333 let (tx_with_sidecar, sig, hash) = tx.into_parts();
335 let tx_eip7594 = tx_with_sidecar
336 .try_map_sidecar(|sidecar| {
337 sidecar.try_into_eip7594_with_settings(kzg_settings.get())
338 })
339 .unwrap();
340
341 let pooled_tx: PooledTransaction = Signed::new_unchecked(tx_eip7594, sig, hash).into();
343 assert!(pooled_tx.is_eip4844());
344
345 let encoded = pooled_tx.encoded_2718();
346 let decoded = PooledTransaction::decode_2718(&mut encoded.as_ref()).unwrap();
347 assert_eq!(pooled_tx, decoded);
348 }
349 }
350}