reth_primitives/transaction/
sidecar.rs#![cfg_attr(docsrs, doc(cfg(feature = "c-kzg")))]
use crate::{Transaction, TransactionSigned};
use alloy_consensus::{transaction::RlpEcdsaTx, TxEip4844WithSidecar};
use alloy_eips::eip4844::BlobTransactionSidecar;
use alloy_primitives::{PrimitiveSignature as Signature, TxHash};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlobTransaction {
pub hash: TxHash,
pub signature: Signature,
#[serde(flatten)]
pub transaction: TxEip4844WithSidecar,
}
impl BlobTransaction {
pub fn try_from_signed(
tx: TransactionSigned,
sidecar: BlobTransactionSidecar,
) -> Result<Self, (TransactionSigned, BlobTransactionSidecar)> {
let TransactionSigned { transaction, signature, hash } = tx;
match transaction {
Transaction::Eip4844(transaction) => Ok(Self {
hash,
transaction: TxEip4844WithSidecar { tx: transaction, sidecar },
signature,
}),
transaction => {
let tx = TransactionSigned { transaction, signature, hash };
Err((tx, sidecar))
}
}
}
#[cfg(feature = "c-kzg")]
pub fn validate(
&self,
proof_settings: &c_kzg::KzgSettings,
) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
self.transaction.validate_blob(proof_settings)
}
pub fn into_parts(self) -> (TransactionSigned, BlobTransactionSidecar) {
let transaction = TransactionSigned {
transaction: Transaction::Eip4844(self.transaction.tx),
hash: self.hash,
signature: self.signature,
};
(transaction, self.transaction.sidecar)
}
pub(crate) fn decode_inner(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
let (transaction, signature, hash) =
TxEip4844WithSidecar::rlp_decode_signed(data)?.into_parts();
Ok(Self { transaction, hash, signature })
}
}
#[cfg(all(test, feature = "c-kzg"))]
mod tests {
use super::*;
use crate::{kzg::Blob, PooledTransactionsElement};
use alloc::vec::Vec;
use alloy_eips::{
eip2718::{Decodable2718, Encodable2718},
eip4844::Bytes48,
};
use alloy_primitives::hex;
use std::{fs, path::PathBuf, str::FromStr};
#[test]
fn test_blob_transaction_sidecar_generation() {
let json_content = fs::read_to_string(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
)
.expect("Failed to read the blob data file");
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
let blobs: Vec<Blob> = vec![Blob::from_hex(
json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
)
.unwrap()];
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
assert_eq!(
sidecar.commitments,
vec![
Bytes48::from_str(json_value.get("commitment").unwrap().as_str().unwrap()).unwrap()
]
);
}
#[test]
fn test_blob_transaction_sidecar_size() {
let mut blobs: Vec<Blob> = Vec::new();
for entry in fs::read_dir(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/"),
)
.expect("Failed to read blob_data folder")
{
let entry = entry.expect("Failed to read directory entry");
let file_path = entry.path();
if !file_path.is_file() || file_path.extension().unwrap_or_default() != "json" {
continue
}
let json_content =
fs::read_to_string(file_path).expect("Failed to read the blob data file");
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
if let Some(data) = json_value.get("data") {
if let Some(data_str) = data.as_str() {
if let Ok(blob) = Blob::from_hex(data_str) {
blobs.push(blob);
}
}
}
}
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
assert_eq!(sidecar.size(), 524672);
}
#[test]
fn test_blob_transaction_sidecar_rlp_encode() {
let json_content = fs::read_to_string(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
)
.expect("Failed to read the blob data file");
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
let blobs: Vec<Blob> = vec![Blob::from_hex(
json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
)
.unwrap()];
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
let mut encoded_rlp = Vec::new();
sidecar.rlp_encode_fields(&mut encoded_rlp);
assert_eq!(json_value.get("rlp").unwrap().as_str().unwrap(), hex::encode(&encoded_rlp));
}
#[test]
fn test_blob_transaction_sidecar_rlp_decode() {
let json_content = fs::read_to_string(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/transaction/blob_data/blob1.json"),
)
.expect("Failed to read the blob data file");
let json_value: serde_json::Value =
serde_json::from_str(&json_content).expect("Failed to deserialize JSON");
let blobs: Vec<Blob> = vec![Blob::from_hex(
json_value.get("data").unwrap().as_str().expect("Data is not a valid string"),
)
.unwrap()];
let sidecar = BlobTransactionSidecar::try_from_blobs(blobs).unwrap();
let mut encoded_rlp = Vec::new();
sidecar.rlp_encode_fields(&mut encoded_rlp);
let decoded_sidecar =
BlobTransactionSidecar::rlp_decode_fields(&mut encoded_rlp.as_slice()).unwrap();
assert_eq!(sidecar, decoded_sidecar);
}
#[test]
fn decode_encode_raw_4844_rlp() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
let dir = fs::read_dir(path).expect("Unable to read folder");
for entry in dir {
let entry = entry.unwrap();
let content = fs::read_to_string(entry.path()).unwrap();
let raw = hex::decode(content.trim()).unwrap();
let tx = PooledTransactionsElement::decode_2718(&mut raw.as_ref())
.map_err(|err| {
panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
})
.unwrap();
assert!(tx.is_eip4844());
let encoded = tx.encoded_2718();
assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
}
}
}