reth_primitives/transaction/
sidecar.rs1#![cfg_attr(docsrs, doc(cfg(feature = "c-kzg")))]
2
3use crate::{Transaction, TransactionSigned};
4use alloy_consensus::{transaction::RlpEcdsaTx, Signed, TxEip4844WithSidecar};
5use alloy_eips::eip4844::BlobTransactionSidecar;
6use derive_more::Deref;
7use reth_primitives_traits::InMemorySize;
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Deref)]
16pub struct BlobTransaction(pub Signed<TxEip4844WithSidecar>);
17
18impl BlobTransaction {
19 pub fn try_from_signed(
24 tx: TransactionSigned,
25 sidecar: BlobTransactionSidecar,
26 ) -> Result<Self, (TransactionSigned, BlobTransactionSidecar)> {
27 let hash = tx.hash();
28 let TransactionSigned { transaction, signature, .. } = tx;
29 match transaction {
30 Transaction::Eip4844(transaction) => Ok(Self(Signed::new_unchecked(
31 TxEip4844WithSidecar { tx: transaction, sidecar },
32 signature,
33 hash,
34 ))),
35 transaction => {
36 let tx = TransactionSigned::new(transaction, signature, hash);
37 Err((tx, sidecar))
38 }
39 }
40 }
41
42 pub fn into_parts(self) -> (TransactionSigned, BlobTransactionSidecar) {
45 let (transaction, signature, hash) = self.0.into_parts();
46 let (transaction, sidecar) = transaction.into_parts();
47 let transaction = TransactionSigned::new(transaction.into(), signature, hash);
48 (transaction, sidecar)
49 }
50
51 pub(crate) fn decode_inner(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
60 let (transaction, signature, hash) =
61 TxEip4844WithSidecar::rlp_decode_signed(data)?.into_parts();
62 Ok(Self(Signed::new_unchecked(transaction, signature, hash)))
63 }
64}
65
66impl InMemorySize for BlobTransaction {
67 fn size(&self) -> usize {
68 self.0.hash().size() +
70 self.0.signature().size() +
71 self.0.tx().tx().size() +
72 self.0.tx().sidecar.size()
73 }
74}
75
76#[cfg(all(test, feature = "c-kzg"))]
77mod tests {
78 use super::*;
79 use crate::{kzg::Blob, PooledTransactionsElement};
80 use alloc::vec::Vec;
81 use alloy_eips::{
82 eip2718::{Decodable2718, Encodable2718},
83 eip4844::Bytes48,
84 };
85 use alloy_primitives::hex;
86 use std::{fs, path::PathBuf, str::FromStr};
87
88 #[test]
89 fn test_blob_transaction_sidecar_generation() {
90 let json_content = fs::read_to_string(
92 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
93 )
94 .expect("Failed to read the blob data file");
95
96 let json_value: serde_json::Value =
98 serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
99
100 let blobs: Vec<Blob> = vec![Blob::from_hex(
102 json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
103 )
104 .unwrap()];
105
106 let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
108
109 assert_eq!(
111 sidecar.commitments,
112 vec![
113 Bytes48::from_str(json_value.get("commitment").unwrap().as_str().unwrap()).unwrap()
114 ]
115 );
116 }
117
118 #[test]
119 fn test_blob_transaction_sidecar_size() {
120 let mut blobs: Vec<Blob> = Vec::new();
122
123 for entry in fs::read_dir(
125 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/"),
126 )
127 .expect("Failed to read blob_data folder")
128 {
129 let entry = entry.expect("Failed to read directory entry");
130 let file_path = entry.path();
131
132 if !file_path.is_file() || file_path.extension().unwrap_or_default() != "json" {
134 continue
135 }
136
137 let json_content =
139 fs::read_to_string(file_path).expect("Failed to read the blob data file");
140
141 let json_value: serde_json::Value =
143 serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
144
145 if let Some(data) = json_value.get("data") {
147 if let Some(data_str) = data.as_str() {
148 if let Ok(blob) = Blob::from_hex(data_str) {
149 blobs.push(blob);
150 }
151 }
152 }
153 }
154
155 let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
157
158 assert_eq!(sidecar.size(), 524672);
160 }
161
162 #[test]
163 fn test_blob_transaction_sidecar_rlp_encode() {
164 let json_content = fs::read_to_string(
166 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
167 )
168 .expect("Failed to read the blob data file");
169
170 let json_value: serde_json::Value =
172 serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
173
174 let blobs: Vec<Blob> = vec![Blob::from_hex(
176 json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
177 )
178 .unwrap()];
179
180 let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
182
183 let mut encoded_rlp = Vec::new();
185
186 sidecar.rlp_encode_fields(&mut encoded_rlp);
188
189 assert_eq!(json_value.get("rlp").unwrap().as_str().unwrap(), hex::encode(&encoded_rlp));
191 }
192
193 #[test]
194 fn test_blob_transaction_sidecar_rlp_decode() {
195 let json_content = fs::read_to_string(
197 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
198 )
199 .expect("Failed to read the blob data file");
200
201 let json_value: serde_json::Value =
203 serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
204
205 let blobs: Vec<Blob> = vec![Blob::from_hex(
207 json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
208 )
209 .unwrap()];
210
211 let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
213
214 let mut encoded_rlp = Vec::new();
216
217 sidecar.rlp_encode_fields(&mut encoded_rlp);
219
220 let decoded_sidecar =
222 BlobTransactionSidecar::rlp_decode_fields(&mut encoded_rlp.as_slice()).unwrap();
223
224 assert_eq!(sidecar, decoded_sidecar);
226 }
227
228 #[test]
229 fn decode_encode_raw_4844_rlp() {
230 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
231 let dir = fs::read_dir(path).expect("Unable to read folder");
232 for entry in dir {
233 let entry = entry.unwrap();
234 let content = fs::read_to_string(entry.path()).unwrap();
235 let raw = hex::decode(content.trim()).unwrap();
236 let tx = PooledTransactionsElement::decode_2718(&mut raw.as_ref())
237 .map_err(|err| {
238 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
239 })
240 .unwrap();
241 assert!(tx.is_eip4844());
243 let encoded = tx.encoded_2718();
244 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
245 }
246 }
247}