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): RLP encoding/decoding for transaction types #36

Merged
merged 7 commits into from
Nov 23, 2023
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
26 changes: 24 additions & 2 deletions crates/rpc-types/src/eth/transaction/access_list.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use std::mem;
use alloy_rlp::{RlpDecodable, RlpEncodable};
use alloy_primitives::{Address, U256, B256};
use serde::{Deserialize, Serialize};

/// A list of addresses and storage keys that the transaction plans to access.
/// Accesses outside the list are possible, but become more expensive.
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)]
#[derive(
Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default, RlpEncodable, RlpDecodable,
)]
#[serde(rename_all = "camelCase")]
pub struct AccessListItem {
/// Account addresses that would be loaded at the start of execution
Expand All @@ -12,8 +16,18 @@ pub struct AccessListItem {
pub storage_keys: Vec<B256>,
}

impl AccessListItem {
/// Calculates a heuristic for the in-memory size of the [AccessListItem].
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<Address>() + self.storage_keys.capacity() * mem::size_of::<U256>()
}
}

/// AccessList as defined in EIP-2930
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default)]
#[derive(
Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, Default, RlpEncodable, RlpDecodable,
)]
pub struct AccessList(pub Vec<AccessListItem>);

impl AccessList {
Expand Down Expand Up @@ -52,6 +66,14 @@ impl AccessList {
)
})
}

/// Calculates a heuristic for the in-memory size of the [AccessList].
#[inline]
pub fn size(&self) -> usize {
// take into account capacity
self.0.iter().map(AccessListItem::size).sum::<usize>()
+ self.0.capacity() * mem::size_of::<AccessListItem>()
}
}

/// Access list with gas used appended.
Expand Down
6 changes: 3 additions & 3 deletions crates/rpc-types/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ mod common;
mod receipt;
mod request;
mod signature;
mod tx_type;
mod typed;

pub use tx_type::*;

/// Transaction object used in RPC
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -66,9 +69,6 @@ pub struct Transaction {
#[serde(skip_serializing_if = "Option::is_none")]
pub access_list: Option<Vec<AccessListItem>>,
/// EIP2718
///
/// Transaction type, Some(2) for EIP-1559 transaction,
/// Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
}
Expand Down
168 changes: 167 additions & 1 deletion crates/rpc-types/src/eth/transaction/signature.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Signature related RPC values
use alloy_primitives::U256;
use alloy_rlp::{Bytes, Decodable, Encodable, Error as RlpError};
use serde::{Deserialize, Serialize};

/// Container type for all signature fields in RPC
Expand All @@ -23,10 +24,119 @@ pub struct Signature {
pub y_parity: Option<Parity>,
}

impl Signature {
/// Output the length of the signature without the length of the RLP header, using the legacy
/// scheme with EIP-155 support depends on chain_id.
pub fn payload_len_with_eip155_chain_id(&self, chain_id: Option<u64>) -> usize {
self.v(chain_id).length() + self.r.length() + self.s.length()
}

/// Encode the `v`, `r`, `s` values without a RLP header.
/// Encodes the `v` value using the legacy scheme with EIP-155 support depends on chain_id.
pub fn encode_with_eip155_chain_id(
&self,
out: &mut dyn alloy_rlp::BufMut,
chain_id: Option<u64>,
) {
self.v(chain_id).encode(out);
self.r.encode(out);
self.s.encode(out);
}

/// Output the `v` of the signature depends on chain_id
#[inline]
pub fn v(&self, chain_id: Option<u64>) -> u64 {
if let Some(chain_id) = chain_id {
// EIP-155: v = {0, 1} + CHAIN_ID * 2 + 35
let y_parity = u64::from(self.y_parity.unwrap_or(Parity(false)));
y_parity + chain_id * 2 + 35
} else {
u64::from(self.y_parity.unwrap_or(Parity(false))) + 27
}
}

/// Decodes the `v`, `r`, `s` values without a RLP header.
/// This will return a chain ID if the `v` value is [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) compatible.
pub fn decode_with_eip155_chain_id(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Option<u64>)> {
let v = u64::decode(buf)?;
let r = Decodable::decode(buf)?;
let s = Decodable::decode(buf)?;
if v >= 35 {
// EIP-155: v = {0, 1} + CHAIN_ID * 2 + 35
let y_parity = ((v - 35) % 2) != 0;
let chain_id = (v - 35) >> 1;
Ok((
Signature { r, s, y_parity: Some(Parity(y_parity)), v: U256::from(v) },
Some(chain_id),
))
} else {
// non-EIP-155 legacy scheme, v = 27 for even y-parity, v = 28 for odd y-parity
if v != 27 && v != 28 {
return Err(RlpError::Custom("invalid Ethereum signature (V is not 27 or 28)"));
}
let y_parity = v == 28;
Ok((Signature { r, s, y_parity: Some(Parity(y_parity)), v: U256::from(v) }, None))
}
}

/// Output the length of the signature without the length of the RLP header
pub fn payload_len(&self) -> usize {
let y_parity_len = match self.y_parity {
Some(parity) => parity.0 as usize,
None => 0_usize,
};
y_parity_len + self.r.length() + self.s.length()
}

/// Encode the `y_parity`, `r`, `s` values without a RLP header.
/// Panics if the y parity is not set.
pub fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.y_parity.expect("y_parity not set").encode(out);
self.r.encode(out);
self.s.encode(out);
}

/// Decodes the `y_parity`, `r`, `s` values without a RLP header.
pub fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let mut sig =
Signature {
y_parity: Some(Decodable::decode(buf)?),
r: Decodable::decode(buf)?,
s: Decodable::decode(buf)?,
v: U256::ZERO,
};
sig.v = sig.y_parity.unwrap().into();
Ok(sig)
}

/// Turn this signature into its byte
/// (hex) representation.
/// Panics: if the y_parity field is not set.
pub fn to_bytes(&self) -> [u8; 65] {
let mut sig = [0u8; 65];
sig[..32].copy_from_slice(&self.r.to_be_bytes::<32>());
sig[32..64].copy_from_slice(&self.s.to_be_bytes::<32>());
let v = u8::from(self.y_parity.expect("y_parity not set")) + 27;
sig[64] = v;
sig
}

/// Turn this signature into its hex-encoded representation.
pub fn to_hex_bytes(&self) -> Bytes {
alloy_primitives::hex::encode(self.to_bytes()).into()
}

/// Calculates a heuristic for the in-memory size of the [Signature].
#[inline]
pub fn size(&self) -> usize {
std::mem::size_of::<Self>()
}
}

/// Type that represents the signature parity byte, meant for use in RPC.
///
/// This will be serialized as "0x0" if false, and "0x1" if true.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Parity(
#[serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity")] pub bool,
);
Expand All @@ -37,6 +147,62 @@ impl From<bool> for Parity {
}
}

impl From<U256> for Parity {
fn from(value: U256) -> Self {
match value {
U256::ZERO => Self(false),
_ => Self(true),
}
}
}

impl From<Parity> for U256 {
fn from(p: Parity) -> Self {
if p.0 {
U256::from(1)
} else {
U256::ZERO
}
}
}

impl From<Parity> for u64 {
fn from(p: Parity) -> Self {
if p.0 {
1
} else {
0
}
}
}

impl From<Parity> for u8 {
fn from(value: Parity) -> Self {
match value.0 {
true => 1,
false => 0,
}
}
}

impl Encodable for Parity {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
let v = u8::from(*self);
v.encode(out);
}

fn length(&self) -> usize {
1
}
}

impl Decodable for Parity {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let v = u8::decode(buf)?;
Ok(Self(v != 0))
}
}

fn serialize_parity<S>(parity: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
Expand Down
52 changes: 52 additions & 0 deletions crates/rpc-types/src/eth/transaction/tx_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use alloy_primitives::U8;
use serde::{Deserialize, Serialize};

/// Identifier for legacy transaction, however a legacy tx is technically not
/// typed.
pub const LEGACY_TX_TYPE_ID: u8 = 0;

/// Identifier for an EIP2930 transaction.
pub const EIP2930_TX_TYPE_ID: u8 = 1;

/// Identifier for an EIP1559 transaction.
pub const EIP1559_TX_TYPE_ID: u8 = 2;

/// Identifier for an EIP4844 transaction.
pub const EIP4844_TX_TYPE_ID: u8 = 3;

/// Transaction Type
///
/// Currently being used as 2-bit type when encoding it to Compact on
/// crate::TransactionSignedNoHash (see Reth's Compact encoding). Adding more transaction types will break the codec and
/// database format on Reth.
///
/// Other required changes when adding a new type can be seen on [PR#3953](https://github.com/paradigmxyz/reth/pull/3953/files).
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum TxType {
/// Legacy transaction pre EIP-2929
#[default]
Legacy = 0_isize,
/// AccessList transaction
EIP2930 = 1_isize,
/// Transaction with Priority fee
EIP1559 = 2_isize,
/// Shard Blob Transactions - EIP-4844
EIP4844 = 3_isize,
}

impl From<TxType> for u8 {
fn from(value: TxType) -> Self {
match value {
TxType::Legacy => LEGACY_TX_TYPE_ID,
TxType::EIP2930 => EIP2930_TX_TYPE_ID,
TxType::EIP1559 => EIP1559_TX_TYPE_ID,
TxType::EIP4844 => EIP4844_TX_TYPE_ID,
}
}
}

impl From<TxType> for U8 {
fn from(value: TxType) -> Self {
U8::from(u8::from(value))
}
}
Loading
Loading