Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc-types): {Try}From impl for OpTransactionReceipt + Transaction -> consensus types #183

Merged
merged 3 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion crates/rpc-types/src/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Receipt types for RPC

use alloy_consensus::{Receipt, ReceiptWithBloom};
use alloy_serde::OtherFields;
use op_alloy_consensus::OpReceiptEnvelope;
use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom, OpReceiptEnvelope};
use serde::{Deserialize, Serialize};

/// OP Transaction Receipt type
Expand Down Expand Up @@ -184,6 +185,55 @@ pub struct L1BlockInfo {

impl Eq for L1BlockInfo {}

impl From<OpTransactionReceipt> for OpReceiptEnvelope<alloy_primitives::Log> {
fn from(value: OpTransactionReceipt) -> Self {
let inner_envelope = value.inner.inner;

/// Helper function to convert the inner logs within a [ReceiptWithBloom] from RPC to
/// consensus types.
#[inline(always)]
fn convert_standard_receipt(
receipt: ReceiptWithBloom<alloy_rpc_types_eth::Log>,
) -> ReceiptWithBloom<alloy_primitives::Log> {
let ReceiptWithBloom { logs_bloom, receipt } = receipt;

let consensus_logs = receipt.logs.into_iter().map(|log| log.inner).collect();
ReceiptWithBloom {
receipt: Receipt {
status: receipt.status,
cumulative_gas_used: receipt.cumulative_gas_used,
logs: consensus_logs,
},
logs_bloom,
}
}

match inner_envelope {
OpReceiptEnvelope::Legacy(receipt) => Self::Legacy(convert_standard_receipt(receipt)),
OpReceiptEnvelope::Eip2930(receipt) => Self::Eip2930(convert_standard_receipt(receipt)),
OpReceiptEnvelope::Eip1559(receipt) => Self::Eip1559(convert_standard_receipt(receipt)),
OpReceiptEnvelope::Eip7702(receipt) => Self::Eip7702(convert_standard_receipt(receipt)),
OpReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { logs_bloom, receipt }) => {
let consensus_logs = receipt.inner.logs.into_iter().map(|log| log.inner).collect();
let consensus_receipt = OpDepositReceiptWithBloom {
receipt: OpDepositReceipt {
inner: Receipt {
status: receipt.inner.status,
cumulative_gas_used: receipt.inner.cumulative_gas_used,
logs: consensus_logs,
},
deposit_nonce: receipt.deposit_nonce,
deposit_receipt_version: receipt.deposit_receipt_version,
},
logs_bloom,
};
Self::Deposit(consensus_receipt)
}
_ => unreachable!("Unsupported OpReceiptEnvelope variant"),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
161 changes: 157 additions & 4 deletions crates/rpc-types/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
//! Optimism specific types related to transactions.

use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxKind, B256, U256};
use alloc::string::{String, ToString};
use alloy_consensus::{
SignableTransaction, Transaction as ConsensusTransaction, TxEip1559, TxEip2930, TxEip7702,
TxLegacy,
};
use alloy_eips::{eip2718::Eip2718Error, eip2930::AccessList, eip7702::SignedAuthorization};
use alloy_primitives::{Address, BlockHash, Bytes, ChainId, SignatureError, TxKind, B256, U256};
use alloy_serde::OtherFields;
use op_alloy_consensus::{OpTxEnvelope, OpTxType, TxDeposit};
use serde::{Deserialize, Serialize};

/// OP Transaction type
Expand All @@ -28,7 +34,7 @@ pub struct Transaction {
pub deposit_receipt_version: Option<u64>,
}

impl alloy_consensus::Transaction for Transaction {
impl ConsensusTransaction for Transaction {
fn chain_id(&self) -> Option<ChainId> {
self.inner.chain_id()
}
Expand Down Expand Up @@ -116,7 +122,7 @@ impl alloy_network_primitives::TransactionResponse for Transaction {
}

fn to(&self) -> Option<alloy_primitives::Address> {
self.inner.to()
ConsensusTransaction::to(&self.inner)
}
}

Expand Down Expand Up @@ -145,3 +151,150 @@ impl From<OpTransactionFields> for OtherFields {
serde_json::to_value(value).unwrap().try_into().unwrap()
}
}

/// Errors that can occur when converting a [Transaction] to an [OpTxEnvelope].
#[derive(Debug)]
pub enum TransactionConversionError {
/// The transaction type is not supported.
UnsupportedTransactionType(Eip2718Error),
/// The transaction's signature could not be converted to the consensus type.
SignatureConversionError(SignatureError),
/// The transaction is missing a required field.
MissingRequiredField(String),
/// The transaction's signature is missing.
MissingSignature,
}

impl core::fmt::Display for TransactionConversionError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnsupportedTransactionType(e) => {
write!(f, "Unsupported transaction type: {}", e)
}
Self::SignatureConversionError(e) => {
write!(f, "Signature conversion error: {}", e)
}
Self::MissingRequiredField(field) => {
write!(f, "Missing required field for conversion: {}", field)
}
Self::MissingSignature => {
write!(f, "Missing signature")
}
}
}
}

impl core::error::Error for TransactionConversionError {}

impl TryFrom<Transaction> for OpTxEnvelope {
type Error = TransactionConversionError;

fn try_from(value: Transaction) -> Result<Self, Self::Error> {
/// Helper function to extract the signature from an RPC [Transaction].
#[inline(always)]
fn extract_signature(
value: &Transaction,
) -> Result<alloy_primitives::Signature, TransactionConversionError> {
value
.inner
.signature
.ok_or(TransactionConversionError::MissingSignature)?
.try_into()
.map_err(TransactionConversionError::SignatureConversionError)
}

let ty = OpTxType::try_from(value.ty())
.map_err(TransactionConversionError::UnsupportedTransactionType)?;
match ty {
OpTxType::Legacy => {
let signature = extract_signature(&value)?;
let legacy = TxLegacy {
chain_id: value.chain_id(),
nonce: value.nonce(),
gas_price: value.gas_price().unwrap_or_default(),
gas_limit: value.gas_limit(),
to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create),
value: value.value(),
input: value.inner.input,
};
Ok(Self::Legacy(legacy.into_signed(signature)))
}
OpTxType::Eip2930 => {
let signature = extract_signature(&value)?;
let access_list_tx = TxEip2930 {
chain_id: value.chain_id().ok_or_else(|| {
TransactionConversionError::MissingRequiredField("chain_id".to_string())
})?,
nonce: value.nonce(),
gas_price: value.gas_price().unwrap_or_default(),
gas_limit: value.gas_limit(),
to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create),
value: value.value(),
input: value.inner.input,
access_list: value.inner.access_list.unwrap_or_default(),
};
Ok(Self::Eip2930(access_list_tx.into_signed(signature)))
}
OpTxType::Eip1559 => {
let signature = extract_signature(&value)?;
let dynamic_fee_tx = TxEip1559 {
chain_id: value.chain_id().ok_or_else(|| {
TransactionConversionError::MissingRequiredField("chain_id".to_string())
})?,
nonce: value.nonce(),
gas_limit: value.gas_limit(),
to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create),
value: value.value(),
input: value.inner.input,
access_list: value.inner.access_list.unwrap_or_default(),
max_fee_per_gas: value.inner.max_fee_per_gas.unwrap_or_default(),
max_priority_fee_per_gas: value
.inner
.max_priority_fee_per_gas
.unwrap_or_default(),
};
Ok(Self::Eip1559(dynamic_fee_tx.into_signed(signature)))
}
OpTxType::Eip7702 => {
let signature = extract_signature(&value)?;
let set_code_tx = TxEip7702 {
chain_id: value.chain_id().ok_or_else(|| {
TransactionConversionError::MissingRequiredField("chain_id".to_string())
})?,
nonce: value.nonce(),
gas_limit: value.gas_limit(),
to: value.inner.to.ok_or_else(|| {
TransactionConversionError::MissingRequiredField("to".to_string())
})?,
value: value.value(),
input: value.inner.input,
access_list: value.inner.access_list.unwrap_or_default(),
max_fee_per_gas: value.inner.max_fee_per_gas.unwrap_or_default(),
max_priority_fee_per_gas: value
.inner
.max_priority_fee_per_gas
.unwrap_or_default(),
authorization_list: value.inner.authorization_list.unwrap_or_default(),
};
Ok(Self::Eip7702(set_code_tx.into_signed(signature)))
}
OpTxType::Deposit => {
let deposit_tx = TxDeposit {
source_hash: value.source_hash.ok_or_else(|| {
TransactionConversionError::MissingRequiredField("source_hash".to_string())
})?,
from: value.inner.from,
to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create),
mint: value.mint,
value: value.inner.value,
gas_limit: value.gas_limit(),
is_system_transaction: value.is_system_tx.ok_or_else(|| {
TransactionConversionError::MissingRequiredField("is_system_tx".to_string())
})?,
input: value.inner.input,
};
Ok(Self::Deposit(deposit_tx))
}
}
}
}
Loading