alloy_consensus/transaction/
pooled.rs1use super::EthereumTxEnvelope;
5use crate::{error::ValueError, Signed, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
6use alloy_eips::eip7594::{BlobTransactionSidecarEip7594, Encodable7594};
7
8pub type PooledTransaction =
20 EthereumTxEnvelope<TxEip4844WithSidecar<BlobTransactionSidecarEip7594>>;
21
22impl<T: Encodable7594> EthereumTxEnvelope<TxEip4844WithSidecar<T>> {
23 pub fn into_envelope(self) -> EthereumTxEnvelope<TxEip4844Variant<T>> {
25 match self {
26 Self::Legacy(tx) => tx.into(),
27 Self::Eip2930(tx) => tx.into(),
28 Self::Eip1559(tx) => tx.into(),
29 Self::Eip7702(tx) => tx.into(),
30 Self::Eip4844(tx) => tx.into(),
31 }
32 }
33}
34
35impl<T: Encodable7594> TryFrom<Signed<TxEip4844Variant<T>>>
36 for EthereumTxEnvelope<TxEip4844WithSidecar<T>>
37{
38 type Error = ValueError<Signed<TxEip4844Variant<T>>>;
39
40 fn try_from(value: Signed<TxEip4844Variant<T>>) -> Result<Self, Self::Error> {
41 let (value, signature, hash) = value.into_parts();
42 match value {
43 tx @ TxEip4844Variant::TxEip4844(_) => Err(ValueError::new_static(
44 Signed::new_unchecked(tx, signature, hash),
45 "pooled transaction requires 4844 sidecar",
46 )),
47 TxEip4844Variant::TxEip4844WithSidecar(tx) => {
48 Ok(Signed::new_unchecked(tx, signature, hash).into())
49 }
50 }
51 }
52}
53
54impl<T: Encodable7594> TryFrom<EthereumTxEnvelope<TxEip4844Variant<T>>>
55 for EthereumTxEnvelope<TxEip4844WithSidecar<T>>
56{
57 type Error = ValueError<EthereumTxEnvelope<TxEip4844Variant<T>>>;
58
59 fn try_from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Result<Self, Self::Error> {
60 value.try_into_pooled()
61 }
62}
63
64impl<T: Encodable7594> TryFrom<EthereumTxEnvelope<TxEip4844>>
65 for EthereumTxEnvelope<TxEip4844WithSidecar<T>>
66{
67 type Error = ValueError<EthereumTxEnvelope<TxEip4844>>;
68
69 fn try_from(value: EthereumTxEnvelope<TxEip4844>) -> Result<Self, Self::Error> {
70 value.try_into_pooled()
71 }
72}
73
74impl<T: Encodable7594> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>>
75 for EthereumTxEnvelope<TxEip4844Variant<T>>
76{
77 fn from(tx: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
78 tx.into_envelope()
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::Transaction;
86 use alloy_eips::{Decodable2718, Encodable2718};
87 use alloy_primitives::{address, hex, Bytes};
88 use alloy_rlp::Decodable;
89 use std::path::PathBuf;
90
91 #[test]
92 fn invalid_legacy_pooled_decoding_input_too_short() {
93 let input_too_short = [
94 &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
96 &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
102 &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
103 &hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
106 &hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
107 ];
108
109 for hex_data in &input_too_short {
110 let input_rlp = &mut &hex_data[..];
111 let res = PooledTransaction::decode(input_rlp);
112
113 assert!(
114 res.is_err(),
115 "expected err after decoding rlp input: {:x?}",
116 Bytes::copy_from_slice(hex_data)
117 );
118
119 let input_rlp = &mut &hex_data[..];
121 let res = PooledTransaction::decode_2718(input_rlp);
122
123 assert!(
124 res.is_err(),
125 "expected err after decoding enveloped rlp input: {:x?}",
126 Bytes::copy_from_slice(hex_data)
127 );
128 }
129 }
130
131 #[test]
133 fn decode_eip1559_enveloped() {
134 let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
135);
136
137 let res = PooledTransaction::decode_2718(&mut &data[..]).unwrap();
138 assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c")));
139 }
140
141 #[test]
142 fn legacy_valid_pooled_decoding() {
143 let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
154
155 let input_rlp = &mut &data[..];
156 let res = PooledTransaction::decode(input_rlp);
157 assert!(res.is_ok());
158 assert!(input_rlp.is_empty());
159
160 let res = PooledTransaction::decode_2718(&mut &data[..]);
162 assert!(res.is_ok());
163 }
164
165 #[test]
166 fn decode_encode_raw_4844_rlp() {
167 type VariantPooledTransaction = EthereumTxEnvelope<TxEip4844WithSidecar>;
170
171 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
172 let dir = std::fs::read_dir(path).expect("Unable to read folder");
173 for entry in dir {
174 let entry = entry.unwrap();
175 let content = std::fs::read_to_string(entry.path()).unwrap();
176 let raw = hex::decode(content.trim()).unwrap();
177 let tx = VariantPooledTransaction::decode_2718(&mut raw.as_ref())
178 .map_err(|err| {
179 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
180 })
181 .unwrap();
182 assert!(tx.is_eip4844());
184 let encoded = tx.encoded_2718();
185 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
186 }
187 }
188
189 #[test]
190 #[cfg(feature = "kzg")]
191 fn convert_to_eip7594() {
192 type VariantPooledTransaction = EthereumTxEnvelope<TxEip4844WithSidecar>;
195
196 let kzg_settings = alloy_eips::eip4844::env_settings::EnvKzgSettings::default();
197 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
198 let dir = std::fs::read_dir(path).expect("Unable to read folder");
199 for entry in dir {
200 let entry = entry.unwrap();
201 let content = std::fs::read_to_string(entry.path()).unwrap();
202 let raw = hex::decode(content.trim()).unwrap();
203 let VariantPooledTransaction::Eip4844(tx) =
204 VariantPooledTransaction::decode_2718(&mut raw.as_ref())
205 .map_err(|err| {
206 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
207 })
208 .unwrap()
209 else {
210 panic!("Expected EIP-4844 transaction");
211 };
212 let tx = tx.into_parts().0;
213 assert!(!tx.sidecar.blobs().is_empty());
214 assert!(tx.validate_blob(kzg_settings.get()).is_ok());
215
216 let tx = tx
217 .try_map_sidecar(|sidecar| {
218 sidecar.try_convert_into_eip7594_with_settings(kzg_settings.get())
219 })
220 .unwrap();
221
222 assert!(!tx.sidecar.blobs().is_empty());
223 assert!(tx.validate_blob(kzg_settings.get()).is_ok());
224 }
225 }
226
227 #[test]
230 #[cfg(feature = "kzg")]
231 fn pooled_transaction_eip7594_roundtrip() {
232 type VariantPooledTransaction = EthereumTxEnvelope<TxEip4844WithSidecar>;
235
236 let kzg_settings = alloy_eips::eip4844::env_settings::EnvKzgSettings::default();
237 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
238 let dir = std::fs::read_dir(path).expect("Unable to read folder");
239 for entry in dir {
240 let entry = entry.unwrap();
241 let content = std::fs::read_to_string(entry.path()).unwrap();
242 let raw = hex::decode(content.trim()).unwrap();
243 let VariantPooledTransaction::Eip4844(tx) =
244 VariantPooledTransaction::decode_2718(&mut raw.as_ref())
245 .map_err(|err| {
246 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
247 })
248 .unwrap()
249 else {
250 panic!("Expected EIP-4844 transaction");
251 };
252
253 let (tx_with_sidecar, sig, hash) = tx.into_parts();
255 let tx_eip7594 = tx_with_sidecar
256 .try_map_sidecar(|sidecar| {
257 sidecar.try_into_eip7594_with_settings(kzg_settings.get())
258 })
259 .unwrap();
260
261 let pooled_tx: PooledTransaction = Signed::new_unchecked(tx_eip7594, sig, hash).into();
263 assert!(pooled_tx.is_eip4844());
264
265 let encoded = pooled_tx.encoded_2718();
266 let decoded = PooledTransaction::decode_2718(&mut encoded.as_ref()).unwrap();
267 assert_eq!(pooled_tx, decoded);
268 }
269 }
270}