optimism: use op-alloy TxDeposit (#10667)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Thomas Coratger
2024-09-05 08:50:33 -07:00
committed by GitHub
parent c267c1caf7
commit b0fddef46a
11 changed files with 104 additions and 251 deletions

3
Cargo.lock generated
View File

@ -5045,6 +5045,7 @@ dependencies = [
"alloy-primitives 0.8.0",
"alloy-rlp",
"alloy-serde",
"arbitrary",
"derive_more 1.0.0",
"serde",
"spin",
@ -6440,6 +6441,7 @@ dependencies = [
"arbitrary",
"bytes",
"modular-bitfield",
"op-alloy-consensus",
"proptest",
"proptest-arbitrary-interop",
"reth-codecs-derive",
@ -7960,6 +7962,7 @@ dependencies = [
"k256",
"modular-bitfield",
"once_cell",
"op-alloy-consensus",
"op-alloy-rpc-types",
"pprof",
"proptest",

View File

@ -453,9 +453,10 @@ alloy-transport-ipc = { version = "0.3.1", default-features = false }
alloy-transport-ws = { version = "0.3.1", default-features = false }
# op
op-alloy-rpc-types = "0.2.5"
op-alloy-rpc-types-engine = "0.2"
op-alloy-network = "0.2"
op-alloy-rpc-types = "0.2.8"
op-alloy-rpc-types-engine = "0.2.8"
op-alloy-network = "0.2.8"
op-alloy-consensus = "0.2.8"
# misc
aquamarine = "0.5"

View File

@ -252,7 +252,7 @@ mod tests {
to: TxKind::Create,
mint: None,
value: U256::ZERO,
gas_limit: 0u64,
gas_limit: 0,
is_system_transaction: false,
input: Default::default(),
});

View File

@ -33,6 +33,7 @@ alloy-eips = { workspace = true, features = ["serde"] }
# optimism
op-alloy-rpc-types = { workspace = true, optional = true }
op-alloy-consensus = { workspace = true, features = ["arbitrary"], optional = true }
# crypto
secp256k1 = { workspace = true, features = [
@ -105,7 +106,9 @@ optimism = [
"reth-chainspec/optimism",
"reth-ethereum-forks/optimism",
"revm-primitives/optimism",
"reth-codecs?/optimism",
"dep:reth-optimism-chainspec",
"dep:op-alloy-consensus",
]
alloy-compat = ["reth-primitives-traits/alloy-compat", "dep:alloy-rpc-types", "dep:alloy-serde", "dep:op-alloy-rpc-types"]
test-utils = ["reth-primitives-traits/test-utils"]

View File

@ -204,10 +204,7 @@ impl TryFrom<WithOtherFields<alloy_rpc_types::Transaction>> for Transaction {
to: TxKind::from(tx.to),
mint: fields.mint.filter(|n| *n != 0),
value: tx.value,
gas_limit: tx
.gas
.try_into()
.map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?,
gas_limit: tx.gas,
is_system_transaction: fields.is_system_tx.unwrap_or(false),
input: tx.input,
}))

View File

@ -95,7 +95,7 @@ impl FillTxEnv for TransactionSigned {
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
tx_env.access_list.clear();
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_limit = tx.gas_limit as u64;
tx_env.gas_price = U256::ZERO;
tx_env.gas_priority_fee = None;
tx_env.transact_to = tx.to;

View File

@ -171,7 +171,11 @@ impl<'a> arbitrary::Arbitrary<'a> for Transaction {
Self::Eip7702(tx)
}
#[cfg(feature = "optimism")]
TxType::Deposit => Self::Deposit(TxDeposit::arbitrary(u)?),
TxType::Deposit => {
let mut tx = TxDeposit::arbitrary(u)?;
tx.gas_limit = (tx.gas_limit as u64).into();
Self::Deposit(tx)
}
})
}
}
@ -250,7 +254,7 @@ impl Transaction {
Self::Eip4844(_) => TxType::Eip4844,
Self::Eip7702(_) => TxType::Eip7702,
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.tx_type(),
Self::Deposit(_) => TxType::Deposit,
}
}
@ -315,7 +319,7 @@ impl Transaction {
Self::Eip7702(TxEip7702 { gas_limit, .. }) |
Self::Eip2930(TxEip2930 { gas_limit, .. }) => *gas_limit as u64,
#[cfg(feature = "optimism")]
Self::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit,
Self::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit as u64,
}
}
@ -556,7 +560,7 @@ impl Transaction {
with_header,
),
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.encode(out, with_header),
Self::Deposit(deposit_tx) => deposit_tx.encode_inner(out, with_header),
}
}
@ -569,7 +573,7 @@ impl Transaction {
Self::Eip4844(tx) => tx.gas_limit = gas_limit.into(),
Self::Eip7702(tx) => tx.gas_limit = gas_limit.into(),
#[cfg(feature = "optimism")]
Self::Deposit(tx) => tx.gas_limit = gas_limit,
Self::Deposit(tx) => tx.gas_limit = gas_limit.into(),
}
}
@ -839,7 +843,7 @@ impl Encodable for Transaction {
}
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => {
deposit_tx.encode(out, true);
deposit_tx.encode_inner(out, true);
}
}
}
@ -852,7 +856,7 @@ impl Encodable for Transaction {
Self::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(),
Self::Eip7702(set_code_tx) => set_code_tx.payload_len_for_signature(),
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.payload_len(),
Self::Deposit(deposit_tx) => deposit_tx.encoded_len(true),
}
}
}
@ -1243,7 +1247,7 @@ impl TransactionSigned {
true,
),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
Transaction::Deposit(deposit_tx) => deposit_tx.encoded_len(true),
}
}
@ -1370,7 +1374,7 @@ impl TransactionSigned {
TxType::Eip4844 => Transaction::Eip4844(TxEip4844::decode_fields(data)?),
TxType::Eip7702 => Transaction::Eip7702(TxEip7702::decode_fields(data)?),
#[cfg(feature = "optimism")]
TxType::Deposit => Transaction::Deposit(TxDeposit::decode_inner(data)?),
TxType::Deposit => Transaction::Deposit(TxDeposit::decode_fields(data)?),
TxType::Legacy => return Err(RlpError::Custom("unexpected legacy tx type")),
};
@ -1455,7 +1459,7 @@ impl TransactionSigned {
false,
),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len_without_header(),
Transaction::Deposit(deposit_tx) => deposit_tx.encoded_len(false),
}
}
}

View File

@ -1,231 +1 @@
use crate::{Address, Bytes, TxKind, TxType, B256, U256};
use alloy_rlp::{
length_of_length, Decodable, Encodable, Error as DecodeError, Header, EMPTY_STRING_CODE,
};
use bytes::Buf;
use core::mem;
#[cfg(any(test, feature = "reth-codec"))]
use reth_codecs::Compact;
use serde::{Deserialize, Serialize};
/// Deposit transactions, also known as deposits are initiated on L1, and executed on L2.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
#[cfg_attr(any(test, feature = "reth-codec"), derive(Compact))]
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
pub struct TxDeposit {
/// Hash that uniquely identifies the source of the deposit.
pub source_hash: B256,
/// The address of the sender account.
pub from: Address,
/// The address of the recipient account, or the null (zero-length) address if the deposited
/// transaction is a contract creation.
pub to: TxKind,
/// The ETH value to mint on L2.
pub mint: Option<u128>,
/// The ETH value to send to the recipient account.
pub value: U256,
/// The gas limit for the L2 transaction.
pub gas_limit: u64,
/// Field indicating if this transaction is exempt from the L2 gas limit.
pub is_system_transaction: bool,
/// Input has two uses depending if transaction is Create or Call (if `to` field is None or
/// Some).
pub input: Bytes,
}
impl TxDeposit {
/// Calculates a heuristic for the in-memory size of the [`TxDeposit`] transaction.
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<B256>() + // source_hash
mem::size_of::<Address>() + // from
self.to.size() + // to
mem::size_of::<Option<u128>>() + // mint
mem::size_of::<U256>() + // value
mem::size_of::<u64>() + // gas_limit
mem::size_of::<bool>() + // is_system_transaction
self.input.len() // input
}
/// Decodes the inner [`TxDeposit`] fields from RLP bytes.
///
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
/// RLP fields in the following order:
///
/// - `source_hash`
/// - `from`
/// - `to`
/// - `mint`
/// - `value`
/// - `gas_limit`
/// - `is_system_transaction`
/// - `input`
pub fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
Ok(Self {
source_hash: Decodable::decode(buf)?,
from: Decodable::decode(buf)?,
to: Decodable::decode(buf)?,
mint: if *buf.first().ok_or(DecodeError::InputTooShort)? == EMPTY_STRING_CODE {
buf.advance(1);
None
} else {
Some(Decodable::decode(buf)?)
},
value: Decodable::decode(buf)?,
gas_limit: Decodable::decode(buf)?,
is_system_transaction: Decodable::decode(buf)?,
input: Decodable::decode(buf)?,
})
}
/// Outputs the length of the transaction's fields, without a RLP header or length of the
/// eip155 fields.
pub(crate) fn fields_len(&self) -> usize {
self.source_hash.length() +
self.from.length() +
self.to.length() +
self.mint.map_or(1, |mint| mint.length()) +
self.value.length() +
self.gas_limit.length() +
self.is_system_transaction.length() +
self.input.0.length()
}
/// Encodes only the transaction's fields into the desired buffer, without a RLP header.
/// <https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/deposits.md#the-deposited-transaction-type>
pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
self.source_hash.encode(out);
self.from.encode(out);
self.to.encode(out);
if let Some(mint) = self.mint {
mint.encode(out);
} else {
out.put_u8(EMPTY_STRING_CODE);
}
self.value.encode(out);
self.gas_limit.encode(out);
self.is_system_transaction.encode(out);
self.input.encode(out);
}
/// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
/// hash that for eip2718 does not require rlp header
pub(crate) fn encode(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
let payload_length = self.fields_len();
if with_header {
Header {
list: false,
payload_length: 1 + length_of_length(payload_length) + payload_length,
}
.encode(out);
}
out.put_u8(self.tx_type() as u8);
let header = Header { list: true, payload_length };
header.encode(out);
self.encode_fields(out);
}
/// Output the length of the RLP signed transaction encoding. This encodes with a RLP header.
pub(crate) fn payload_len(&self) -> usize {
let payload_length = self.fields_len();
// 'tx type' + 'header length' + 'payload length'
let len = 1 + length_of_length(payload_length) + payload_length;
length_of_length(len) + len
}
pub(crate) fn payload_len_without_header(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Get the transaction type
pub(crate) const fn tx_type(&self) -> TxType {
TxType::Deposit
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{revm_primitives::hex_literal::hex, TransactionSigned};
use bytes::BytesMut;
#[test]
fn test_rlp_roundtrip() {
let bytes = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"));
let tx_a = TransactionSigned::decode_enveloped(&mut bytes.as_ref()).unwrap();
let tx_b = TransactionSigned::decode(&mut &bytes[..]).unwrap();
let mut buf_a = BytesMut::default();
tx_a.encode_enveloped(&mut buf_a);
assert_eq!(&buf_a[..], &bytes[..]);
let mut buf_b = BytesMut::default();
tx_b.encode_enveloped(&mut buf_b);
assert_eq!(&buf_b[..], &bytes[..]);
}
#[test]
fn test_encode_decode_fields() {
let original = TxDeposit {
source_hash: B256::default(),
from: Address::default(),
to: TxKind::default(),
mint: Some(100),
value: U256::default(),
gas_limit: 50000,
is_system_transaction: true,
input: Bytes::default(),
};
let mut buffer = BytesMut::new();
original.encode_fields(&mut buffer);
let decoded = TxDeposit::decode_inner(&mut &buffer[..]).expect("Failed to decode");
assert_eq!(original, decoded);
}
#[test]
fn test_encode_with_and_without_header() {
let tx_deposit = TxDeposit {
source_hash: B256::default(),
from: Address::default(),
to: TxKind::default(),
mint: Some(100),
value: U256::default(),
gas_limit: 50000,
is_system_transaction: true,
input: Bytes::default(),
};
let mut buffer_with_header = BytesMut::new();
tx_deposit.encode(&mut buffer_with_header, true);
let mut buffer_without_header = BytesMut::new();
tx_deposit.encode(&mut buffer_without_header, false);
assert!(buffer_with_header.len() > buffer_without_header.len());
}
#[test]
fn test_payload_length() {
let tx_deposit = TxDeposit {
source_hash: B256::default(),
from: Address::default(),
to: TxKind::default(),
mint: Some(100),
value: U256::default(),
gas_limit: 50000,
is_system_transaction: true,
input: Bytes::default(),
};
let total_len = tx_deposit.payload_len();
let len_without_header = tx_deposit.payload_len_without_header();
assert!(total_len > len_without_header);
}
}
pub use op_alloy_consensus::TxDeposit;

View File

@ -21,6 +21,9 @@ alloy-genesis = { workspace = true, optional = true }
alloy-primitives.workspace = true
alloy-trie = { workspace = true, optional = true }
# optimism
op-alloy-consensus = { workspace = true, optional = true }
# misc
bytes.workspace = true
modular-bitfield = { workspace = true, optional = true }
@ -55,3 +58,4 @@ alloy = [
"dep:alloy-trie",
"dep:serde"
]
optimism = ["alloy", "dep:op-alloy-consensus"]

View File

@ -3,6 +3,8 @@ mod eip2930;
mod eip4844;
mod eip7702;
mod legacy;
#[cfg(feature = "optimism")]
mod optimism;
#[cfg(test)]
mod tests {
@ -13,7 +15,9 @@ mod tests {
// this check is to ensure we do not inadvertently add too many fields to a struct which would
// expand the flags field and break backwards compatibility
use super::{
#[cfg(feature = "optimism")]
use crate::alloy::transaction::optimism::TxDeposit;
use crate::alloy::transaction::{
eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702,
legacy::TxLegacy,
};
@ -26,4 +30,10 @@ mod tests {
assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3);
assert_eq!(TxEip7702::bitflag_encoded_bytes(), 4);
}
#[cfg(feature = "optimism")]
#[test]
fn test_ensure_backwards_compatibility_optimism() {
assert_eq!(TxDeposit::bitflag_encoded_bytes(), 2);
}
}

View File

@ -0,0 +1,61 @@
use crate::Compact;
use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
use op_alloy_consensus::TxDeposit as AlloyTxDeposit;
use reth_codecs_derive::add_arbitrary_tests;
use serde::{Deserialize, Serialize};
/// Deposit transactions, also known as deposits are initiated on L1, and executed on L2.
///
/// This is a helper type to use derive on it instead of manually managing `bitfield`.
///
/// By deriving `Compact` here, any future changes or enhancements to the `Compact` derive
/// will automatically apply to this type.
///
/// Notice: Make sure this struct is 1:1 with [`op_alloy_consensus::TxDeposit`]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize, Compact)]
#[cfg_attr(test, derive(arbitrary::Arbitrary))]
#[add_arbitrary_tests(compact)]
pub(crate) struct TxDeposit {
source_hash: B256,
from: Address,
to: TxKind,
mint: Option<u128>,
value: U256,
gas_limit: u64,
is_system_transaction: bool,
input: Bytes,
}
impl Compact for AlloyTxDeposit {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
let tx = TxDeposit {
source_hash: self.source_hash,
from: self.from,
to: self.to,
mint: self.mint,
value: self.value,
gas_limit: self.gas_limit as u64,
is_system_transaction: self.is_system_transaction,
input: self.input.clone(),
};
tx.to_compact(buf)
}
fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
let (tx, _) = TxDeposit::from_compact(buf, len);
let alloy_tx = Self {
source_hash: tx.source_hash,
from: tx.from,
to: tx.to,
mint: tx.mint,
value: tx.value,
gas_limit: tx.gas_limit as u128,
is_system_transaction: tx.is_system_transaction,
input: tx.input,
};
(alloy_tx, buf)
}
}