use alloy_consensus::{
transaction::{from_eip155_value, RlpEcdsaTx},
Header, TxEip1559, TxEip2930, TxEip4844, TxEip7702, TxLegacy,
};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
eip4895::Withdrawals,
};
use alloy_primitives::{
bytes::{Buf, BytesMut},
keccak256, PrimitiveSignature as Signature, TxHash, B256, U256,
};
use alloy_rlp::{Decodable, Error as RlpError, RlpDecodable};
use derive_more::{AsRef, Deref};
use op_alloy_consensus::TxDeposit;
use reth_downloaders::file_client::FileClientError;
use reth_primitives::transaction::{Transaction, TxType};
use serde::{Deserialize, Serialize};
use tokio_util::codec::Decoder;
#[allow(dead_code)]
pub(crate) struct OvmBlockFileCodec;
impl Decoder for OvmBlockFileCodec {
type Item = Block;
type Error = FileClientError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.is_empty() {
return Ok(None);
}
let buf_slice = &mut src.as_ref();
let body =
Block::decode(buf_slice).map_err(|err| FileClientError::Rlp(err, src.to_vec()))?;
src.advance(src.len() - buf_slice.len());
Ok(Some(body))
}
}
#[derive(Debug, Clone, PartialEq, Eq, RlpDecodable)]
pub struct Block {
pub header: Header,
pub body: BlockBody,
}
impl Block {
pub fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let header = Header::decode(buf)?;
let body = BlockBody::decode(buf)?;
Ok(Self { header, body })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, RlpDecodable)]
#[rlp(trailing)]
pub struct BlockBody {
pub transactions: Vec<TransactionSigned>,
pub ommers: Vec<Header>,
pub withdrawals: Option<Withdrawals>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Serialize, Deserialize)]
pub struct TransactionSigned {
pub hash: TxHash,
pub signature: Signature,
#[deref]
#[as_ref]
pub transaction: Transaction,
}
impl Default for TransactionSigned {
fn default() -> Self {
Self {
hash: Default::default(),
signature: Signature::test_signature(),
transaction: Default::default(),
}
}
}
impl AsRef<Self> for TransactionSigned {
fn as_ref(&self) -> &Self {
self
}
}
impl TransactionSigned {
pub fn recalculate_hash(&self) -> B256 {
keccak256(self.encoded_2718())
}
pub fn from_transaction_and_signature(transaction: Transaction, signature: Signature) -> Self {
let mut initial_tx = Self { transaction, hash: Default::default(), signature };
initial_tx.hash = initial_tx.recalculate_hash();
initial_tx
}
pub(crate) fn decode_rlp_legacy_transaction_tuple(
data: &mut &[u8],
) -> alloy_rlp::Result<(TxLegacy, TxHash, Signature)> {
let original_encoding = *data;
let header = alloy_rlp::Header::decode(data)?;
let remaining_len = data.len();
let transaction_payload_len = header.payload_length;
if transaction_payload_len > remaining_len {
return Err(RlpError::InputTooShort);
}
let mut transaction = TxLegacy {
nonce: Decodable::decode(data)?,
gas_price: Decodable::decode(data)?,
gas_limit: Decodable::decode(data)?,
to: Decodable::decode(data)?,
value: Decodable::decode(data)?,
input: Decodable::decode(data)?,
chain_id: None,
};
let v = Decodable::decode(data)?;
let r: U256 = Decodable::decode(data)?;
let s: U256 = Decodable::decode(data)?;
let tx_length = header.payload_length + header.length();
let hash = keccak256(&original_encoding[..tx_length]);
let (signature, chain_id) = if v == 0 && r.is_zero() && s.is_zero() {
(Signature::new(r, s, false), None)
} else {
let (parity, chain_id) = from_eip155_value(v)
.ok_or(alloy_rlp::Error::Custom("invalid parity for legacy transaction"))?;
(Signature::new(r, s, parity), chain_id)
};
transaction.chain_id = chain_id;
let decoded = remaining_len - data.len();
if decoded != transaction_payload_len {
return Err(RlpError::UnexpectedLength);
}
Ok((transaction, hash, signature))
}
pub fn decode_rlp_legacy_transaction(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
let (transaction, hash, signature) = Self::decode_rlp_legacy_transaction_tuple(data)?;
let signed = Self { transaction: Transaction::Legacy(transaction), hash, signature };
Ok(signed)
}
}
impl Decodable for TransactionSigned {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::network_decode(buf).map_err(Into::into)
}
}
impl Encodable2718 for TransactionSigned {
fn type_flag(&self) -> Option<u8> {
match self.transaction.tx_type() {
TxType::Legacy => None,
tx_type => Some(tx_type as u8),
}
}
fn encode_2718_len(&self) -> usize {
match &self.transaction {
Transaction::Legacy(legacy_tx) => legacy_tx.eip2718_encoded_length(&self.signature),
Transaction::Eip2930(access_list_tx) => {
access_list_tx.eip2718_encoded_length(&self.signature)
}
Transaction::Eip1559(dynamic_fee_tx) => {
dynamic_fee_tx.eip2718_encoded_length(&self.signature)
}
Transaction::Eip4844(blob_tx) => blob_tx.eip2718_encoded_length(&self.signature),
Transaction::Eip7702(set_code_tx) => {
set_code_tx.eip2718_encoded_length(&self.signature)
}
Transaction::Deposit(deposit_tx) => deposit_tx.eip2718_encoded_length(),
}
}
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
self.transaction.eip2718_encode(&self.signature, out)
}
}
impl Decodable2718 for TransactionSigned {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
TxType::Eip2930 => {
let (tx, signature, hash) = TxEip2930::rlp_decode_signed(buf)?.into_parts();
Ok(Self { transaction: Transaction::Eip2930(tx), signature, hash })
}
TxType::Eip1559 => {
let (tx, signature, hash) = TxEip1559::rlp_decode_signed(buf)?.into_parts();
Ok(Self { transaction: Transaction::Eip1559(tx), signature, hash })
}
TxType::Eip7702 => {
let (tx, signature, hash) = TxEip7702::rlp_decode_signed(buf)?.into_parts();
Ok(Self { transaction: Transaction::Eip7702(tx), signature, hash })
}
TxType::Eip4844 => {
let (tx, signature, hash) = TxEip4844::rlp_decode_signed(buf)?.into_parts();
Ok(Self { transaction: Transaction::Eip4844(tx), signature, hash })
}
TxType::Deposit => Ok(Self::from_transaction_and_signature(
Transaction::Deposit(TxDeposit::rlp_decode(buf)?),
TxDeposit::signature(),
)),
}
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
Ok(Self::decode_rlp_legacy_transaction(buf)?)
}
}
#[cfg(test)]
mod tests {
use crate::ovm_file_codec::TransactionSigned;
use alloy_primitives::{address, hex, TxKind, B256, U256};
use reth_primitives::transaction::Transaction;
const DEPOSIT_FUNCTION_SELECTOR: [u8; 4] = [0xb6, 0xb5, 0x5f, 0x25];
use alloy_rlp::Decodable;
#[test]
fn test_decode_legacy_transactions() {
let deposit_tx_bytes = hex!("f88881f0830f481c830c6e4594a75127121d28a9bf848f3b70e7eea26570aa770080a4b6b55f2500000000000000000000000000000000000000000000000000000000000710b238a0d5c622d92ddf37f9c18a3465a572f74d8b1aeaf50c1cfb10b3833242781fd45fa02c4f1d5819bf8b70bf651e7a063b7db63c55bd336799c6ae3e5bc72ad6ef3def");
let deposit_decoded = TransactionSigned::decode(&mut &deposit_tx_bytes[..]).unwrap();
let deposit_tx = match &deposit_decoded.transaction {
Transaction::Legacy(ref tx) => tx,
_ => panic!("Expected legacy transaction for NFT deposit"),
};
assert_eq!(
deposit_tx.to,
TxKind::Call(address!("a75127121d28a9bf848f3b70e7eea26570aa7700"))
);
assert_eq!(deposit_tx.nonce, 240);
assert_eq!(deposit_tx.gas_price, 1001500);
assert_eq!(deposit_tx.gas_limit, 814661);
assert_eq!(deposit_tx.value, U256::ZERO);
assert_eq!(&deposit_tx.input.as_ref()[0..4], DEPOSIT_FUNCTION_SELECTOR);
assert_eq!(deposit_tx.chain_id, Some(10));
assert_eq!(
deposit_decoded.signature.r(),
U256::from_str_radix(
"d5c622d92ddf37f9c18a3465a572f74d8b1aeaf50c1cfb10b3833242781fd45f",
16
)
.unwrap()
);
assert_eq!(
deposit_decoded.signature.s(),
U256::from_str_radix(
"2c4f1d5819bf8b70bf651e7a063b7db63c55bd336799c6ae3e5bc72ad6ef3def",
16
)
.unwrap()
);
let system_tx_bytes = hex!("f9026c830d899383124f808302a77e94a0cc33dd6f4819d473226257792afe230ec3c67f80b902046c459a280000000000000000000000004d73adb72bc3dd368966edd0f0b2148401a178e2000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000647fac7f00000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000084704316e5000000000000000000000000000000000000000000000000000000000000006e10975631049de3c008989b0d8c19fc720dc556ca01abfbd794c6eb5075dd000d000000000000000000000000000000000000000000000000000000000000001410975631049de3c008989b0d8c19fc720dc556ca01abfbd794c6eb5075dd000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082a39325251d44e11f3b6d92f9382438eb6c8b5068d4a488d4f177b26f2ca20db34ae53467322852afcc779f25eafd124c5586f54b9026497ba934403d4c578e3c1b5aa754c918ee2ecd25402df656c2419717e4017a7aecb84af3914fd3c7bf6930369c4e6ff76950246b98e354821775f02d33cdbee5ef6aed06c15b75691692d31c00000000000000000000000000000000000000000000000000000000000038a0e8991e95e66d809f4b6fb0af27c31368ca0f30e657165c428aa681ec5ea25bbea013ed325bd97365087ec713e9817d252b59113ea18430b71a5890c4eeb6b9efc4");
let system_decoded = TransactionSigned::decode(&mut &system_tx_bytes[..]).unwrap();
assert!(system_decoded.is_legacy());
let system_tx = match &system_decoded.transaction {
Transaction::Legacy(ref tx) => tx,
_ => panic!("Expected Legacy transaction"),
};
assert_eq!(system_tx.nonce, 887187);
assert_eq!(system_tx.gas_price, 1200000);
assert_eq!(system_tx.gas_limit, 173950);
assert_eq!(
system_tx.to,
TxKind::Call(address!("a0cc33dd6f4819d473226257792afe230ec3c67f"))
);
assert_eq!(system_tx.value, U256::ZERO);
assert_eq!(system_tx.chain_id, Some(10));
assert_eq!(
system_decoded.signature.r(),
U256::from_str_radix(
"e8991e95e66d809f4b6fb0af27c31368ca0f30e657165c428aa681ec5ea25bbe",
16
)
.unwrap()
);
assert_eq!(
system_decoded.signature.s(),
U256::from_str_radix(
"13ed325bd97365087ec713e9817d252b59113ea18430b71a5890c4eeb6b9efc4",
16
)
.unwrap()
);
assert_eq!(
system_decoded.hash,
B256::from(hex!("e20b11349681dd049f8df32f5cdbb4c68d46b537685defcd86c7fa42cfe75b9e"))
);
}
}