Skip to content

Commit

Permalink
refactor: implement TransactionTrace conversion methods in firehose-p…
Browse files Browse the repository at this point in the history
…rotos
  • Loading branch information
suchapalaver committed Oct 25, 2024
1 parent 4f9c3bb commit 2ef3122
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 264 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/firehose-protos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ doctest = false
path = "src/lib.rs"

[dependencies]
alloy-consensus.workspace = true
alloy-eip2930.workspace = true
alloy-primitives.workspace = true
ethportal-api.workspace = true
Expand Down
14 changes: 14 additions & 0 deletions crates/firehose-protos/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use thiserror::Error;

use crate::ethereum_v2::transaction::EcdsaComponent;

#[derive(Error, Debug)]
pub enum ProtosError {
#[error("Block conversion error")]
Expand All @@ -17,12 +19,18 @@ pub enum ProtosError {
#[error("Invalid access tuple storage key: {0}")]
InvalidAccessTupleStorageKey(String),

#[error("Invalid BigInt: {0}")]
InvalidBigInt(String),

#[error("Invalid log address: {0}")]
InvalidLogAddress(String),

#[error("Invalid log topic: {0}")]
InvalidLogTopic(String),

#[error("Invalid trace signature {0:?} component: {1}")]
InvalidTraceSignature(EcdsaComponent, String),

#[error("KzgCommitmentInvalid")]
KzgCommitmentInvalid,

Expand Down Expand Up @@ -58,4 +66,10 @@ pub enum ProtosError {

#[error("SSZ Types error: {0}")]
SszTypesError(String),

#[error("Transaction missing call")]
TransactionMissingCall,

#[error("TxTypeConversionError")]
TxTypeConversion,
}
244 changes: 242 additions & 2 deletions crates/firehose-protos/src/ethereum_v2/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use reth_primitives::TxType;
use alloy_consensus::{TxEip1559, TxEip2930, TxLegacy};
use alloy_eip2930::{AccessList, AccessListItem};
use alloy_primitives::{
hex, Address, Bytes, ChainId, FixedBytes, Parity, TxKind, Uint, U128, U256,
};
use reth_primitives::{Signature, Transaction, TransactionSigned, TxType};

use super::transaction_trace::Type;
use crate::error::ProtosError;

use super::{transaction_trace::Type, BigInt, CallType, TransactionTrace};

impl From<Type> for TxType {
fn from(tx_type: Type) -> Self {
Expand All @@ -23,3 +30,236 @@ impl From<Type> for TxType {
}
}
}

impl TransactionTrace {
pub fn is_success(&self) -> bool {
self.status == 1
}

pub fn parity(&self) -> Result<Parity, ProtosError> {
// Extract the first byte of the V value (Ethereum's V value).
let v: u8 = if self.v.is_empty() { 0 } else { self.v[0] };

let parity = match v {
// V values 0 and 1 directly indicate Y parity.
0 | 1 => v == 1,

// V values 27 and 28 are commonly used in Ethereum and indicate Y parity.
27 | 28 => v - 27 == 1,

// V values 37 and 38 are less common but still valid and represent Y parity.
37 | 38 => v - 37 == 1,

// If V is outside the expected range, return an error.
_ => {
return Err(ProtosError::InvalidTraceSignature(
EcdsaComponent::V,
v.to_string(),
))
}
};

Ok(parity.into())
}
}

#[derive(Clone, Debug)]
pub enum EcdsaComponent {
R,
S,
V,
}

impl TryFrom<&TransactionTrace> for TxKind {
type Error = ProtosError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
let first_call = trace
.calls
.first()
.ok_or(ProtosError::TransactionMissingCall)?;

match first_call.call_type() {
CallType::Create => Ok(TxKind::Create),
_ => {
// If not, interpret the transaction as a Call
let address = Address::from_slice(trace.to.as_slice());
Ok(TxKind::Call(address))
}
}
}
}

impl TryFrom<&TransactionTrace> for Signature {
type Error = ProtosError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
use EcdsaComponent::*;

// Extract the R value from the trace and ensure it's a valid 32-byte array.
let r_bytes: [u8; 32] = trace
.r
.as_slice()
.try_into()
.map_err(|_| Self::Error::InvalidTraceSignature(R, hex::encode(&trace.r)))?;
let r = U256::from_be_bytes(r_bytes);

// Extract the S value from the trace and ensure it's a valid 32-byte array.
let s_bytes: [u8; 32] = trace
.s
.as_slice()
.try_into()
.map_err(|_| Self::Error::InvalidTraceSignature(S, hex::encode(&trace.s)))?;
let s = U256::from_be_bytes(s_bytes);

// Extract the Y parity from the V value.
let odd_y_parity = trace.parity()?;

Ok(Signature::new(r, s, odd_y_parity))
}
}

impl TryFrom<&TransactionTrace> for reth_primitives::TxType {
type Error = ProtosError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
match Type::try_from(trace.r#type) {
Ok(tx_type) => Ok(TxType::from(tx_type)),
Err(_) => Err(ProtosError::TxTypeConversion),
}
}
}

pub const CHAIN_ID: ChainId = 1;

impl TryFrom<&TransactionTrace> for Transaction {
type Error = ProtosError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
let tx_type = reth_primitives::TxType::try_from(trace)?;
let nonce = trace.nonce;
let gas_price = match &trace.gas_price {
Some(gas_price) => gas_price.clone(),
None => BigInt { bytes: vec![0] },
};
let gas_price = u128::try_from(&gas_price)?;
let gas_limit = trace.gas_limit;

let to = TxKind::try_from(trace)?;

let trace_value = match &trace.value {
Some(value) => value.clone(),
None => BigInt { bytes: vec![0] },
};
let value = Uint::from(u128::try_from(&trace_value)?);
let input = Bytes::copy_from_slice(trace.input.as_slice());

let transaction: Transaction = match tx_type {
TxType::Legacy => {
let v: u8 = if trace.v.is_empty() { 0 } else { trace.v[0] };

let chain_id: Option<ChainId> = if v == 27 || v == 28 {
None
} else {
Some(CHAIN_ID)
};

Transaction::Legacy(TxLegacy {
chain_id,
nonce,
gas_price,
gas_limit,
to,
value,
input,
})
}
TxType::Eip2930 => {
let access_list = AccessList::try_from(trace)?;

Transaction::Eip2930(TxEip2930 {
chain_id: CHAIN_ID,
nonce,
gas_price,
gas_limit,
to,
value,
access_list,
input,
})
}
TxType::Eip1559 => {
let access_list = AccessList::try_from(trace)?;

let trace_max_fee_per_gas = match trace.max_fee_per_gas.clone() {
Some(max_fee_per_gas) => max_fee_per_gas,
None => BigInt { bytes: vec![0] },
};
let max_fee_per_gas = u128::try_from(&trace_max_fee_per_gas)?;

let trace_max_priority_fee_per_gas = match trace.max_priority_fee_per_gas.clone() {
Some(max_priority_fee_per_gas) => max_priority_fee_per_gas,
None => BigInt { bytes: vec![0] },
};
let max_priority_fee_per_gas = u128::try_from(&trace_max_priority_fee_per_gas)?;

Transaction::Eip1559(TxEip1559 {
chain_id: CHAIN_ID,
nonce,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
access_list,
input,
})
}
TxType::Eip4844 => unimplemented!(),
TxType::Eip7702 => unimplemented!(),
};

Ok(transaction)
}
}

impl TryFrom<&TransactionTrace> for TransactionSigned {
type Error = ProtosError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
let transaction = Transaction::try_from(trace)?;
let signature = Signature::try_from(trace)?;
let hash = FixedBytes::from_slice(trace.hash.as_slice());

Ok(TransactionSigned {
transaction,
signature,
hash,
})
}
}

impl TryFrom<&TransactionTrace> for AccessList {
type Error = ProtosError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
let access_list_items = trace
.access_list
.iter()
.map(AccessListItem::try_from)
.collect::<Result<Vec<AccessListItem>, Self::Error>>()?;

Ok(AccessList(access_list_items))
}
}

impl TryFrom<&BigInt> for u128 {
type Error = ProtosError;

fn try_from(value: &BigInt) -> Result<Self, Self::Error> {
let slice = value.bytes.as_slice();
let n =
U128::try_from_be_slice(slice).ok_or(ProtosError::InvalidBigInt(hex::encode(slice)))?;
Ok(u128::from_le_bytes(n.to_le_bytes()))
}
}
5 changes: 2 additions & 3 deletions crates/flat-files-decoder/src/receipts/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use crate::transactions::tx_type::TransactionTypeError;
use firehose_protos::error::ProtosError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ReceiptError {
#[error("Invalid status")]
InvalidStatus,
#[error("Invalid tx type")]
InvalidTxType(#[from] TransactionTypeError),
#[error("Invalid address: {0}")]
InvalidAddress(String),
#[error("Invalid topic: {0}")]
Expand All @@ -22,4 +19,6 @@ pub enum ReceiptError {
MissingReceipt,
#[error("Protos error: {0}")]
ProtosError(#[from] ProtosError),
#[error("TryFromSliceError: {0}")]
TryFromSliceError(#[from] std::array::TryFromSliceError),
}
14 changes: 4 additions & 10 deletions crates/flat-files-decoder/src/receipts/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::receipts::error::ReceiptError;
use crate::transactions::tx_type::map_tx_type;
use alloy_primitives::{Bloom, FixedBytes};
use firehose_protos::ethereum_v2::TransactionTrace;
use reth_primitives::{Log, Receipt, ReceiptWithBloom};
Expand All @@ -14,8 +13,9 @@ impl TryFrom<&TransactionTrace> for FullReceipt {
type Error = ReceiptError;

fn try_from(trace: &TransactionTrace) -> Result<Self, Self::Error> {
let success = map_success(&trace.status)?;
let tx_type = map_tx_type(&trace.r#type)?;
let success = trace.is_success();
let tx_type = trace.try_into()?;

let trace_receipt = match &trace.receipt {
Some(receipt) => receipt,
None => return Err(ReceiptError::MissingReceipt),
Expand Down Expand Up @@ -48,15 +48,9 @@ impl TryFrom<&TransactionTrace> for FullReceipt {
}
}

fn map_success(status: &i32) -> Result<bool, ReceiptError> {
Ok(*status == 1)
}

fn map_bloom(slice: &[u8]) -> Result<Bloom, ReceiptError> {
if slice.len() == 256 {
let array: [u8; 256] = slice
.try_into()
.expect("Slice length doesn't match array length");
let array: [u8; 256] = slice.try_into()?;
Ok(Bloom(FixedBytes(array)))
} else {
Err(ReceiptError::InvalidBloom(hex::encode(slice)))
Expand Down
14 changes: 0 additions & 14 deletions crates/flat-files-decoder/src/transactions/access_list.rs

This file was deleted.

6 changes: 0 additions & 6 deletions crates/flat-files-decoder/src/transactions/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::transactions::signature::InvalidSignatureError;
use crate::transactions::tx_type::TransactionTypeError;
use firehose_protos::error::ProtosError;
use thiserror::Error;

Expand All @@ -15,10 +13,6 @@ pub enum TransactionError {
InvalidBigInt(String),
#[error("EIP-4844 not supported")]
EIP4844NotSupported,
#[error("Invalid Signature: {0}")]
InvalidSignature(#[from] InvalidSignatureError),
#[error("Invalid Transaction Type: {0}")]
InvalidType(#[from] TransactionTypeError),
#[error("Missing Gas Price")]
MissingGasPrice,
#[error("Missing Value")]
Expand Down
Loading

0 comments on commit 2ef3122

Please sign in to comment.