From 841c87830c2d1f8dd747dc93168c91c07a798c18 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 1 Oct 2022 14:15:43 +0200 Subject: [PATCH] feat: add transaction request --- Cargo.lock | 16 +++ crates/rpc-types/Cargo.toml | 3 + crates/rpc-types/src/eth/mod.rs | 8 +- crates/rpc-types/src/eth/transaction/mod.rs | 5 + .../rpc-types/src/eth/transaction/request.rs | 115 ++++++++++++++++++ crates/rpc-types/src/eth/transaction/typed.rs | 111 +++++++++++++++++ crates/rpc-types/src/lib.rs | 4 +- 7 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 crates/rpc-types/src/eth/transaction/mod.rs create mode 100644 crates/rpc-types/src/eth/transaction/request.rs create mode 100644 crates/rpc-types/src/eth/transaction/typed.rs diff --git a/Cargo.lock b/Cargo.lock index 1722b49dd..9626f00c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1143,11 +1143,25 @@ dependencies = [ "ethers-core", ] +[[package]] +name = "reth-rpc" +version = "0.1.0" +dependencies = [ + "reth-primitives", + "reth-rpc-api", + "reth-rpc-types", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "reth-rpc-api" version = "0.1.0" dependencies = [ "jsonrpsee", + "reth-primitives", + "reth-rpc-types", "serde", "serde_json", "thiserror", @@ -1157,6 +1171,8 @@ dependencies = [ name = "reth-rpc-types" version = "0.1.0" dependencies = [ + "fastrlp", + "reth-primitives", "serde", "serde_json", "thiserror", diff --git a/crates/rpc-types/Cargo.toml b/crates/rpc-types/Cargo.toml index 905c6f7e3..d2aba6f00 100644 --- a/crates/rpc-types/Cargo.toml +++ b/crates/rpc-types/Cargo.toml @@ -12,6 +12,9 @@ Reth RPC types # reth reth-primitives = { path = "../primitives" } +# eth +fastrlp = { version = "0.1" } + # misc serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/crates/rpc-types/src/eth/mod.rs b/crates/rpc-types/src/eth/mod.rs index 521a0b07d..e1c80bbf5 100644 --- a/crates/rpc-types/src/eth/mod.rs +++ b/crates/rpc-types/src/eth/mod.rs @@ -1 +1,7 @@ -//! Ethereum related types \ No newline at end of file +//! Ethereum related types + +pub use reth_primitives::{BlockId, BlockNumber}; + +mod transaction; + +pub use transaction::*; \ No newline at end of file diff --git a/crates/rpc-types/src/eth/transaction/mod.rs b/crates/rpc-types/src/eth/transaction/mod.rs new file mode 100644 index 000000000..9815cede8 --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/mod.rs @@ -0,0 +1,5 @@ +mod request; +mod typed; + +pub use request::TransactionRequest; +pub use typed::*; diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs new file mode 100644 index 000000000..801462162 --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/request.rs @@ -0,0 +1,115 @@ +use crate::eth::transaction::typed::{ + EIP1559TransactionRequest, EIP2930TransactionRequest, LegacyTransactionRequest, + TransactionKind, TypedTransactionRequest, +}; + +use reth_primitives::{ + transaction::eip2930::AccessListItem, Address, Bytes, U256, +}; +use serde::{Deserialize, Serialize}; + +/// Represents _all_ transaction requests received from RPC +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct TransactionRequest { + /// from address + pub from: Option
, + /// to address + pub to: Option
, + /// legacy, gas Price + #[serde(default)] + pub gas_price: Option, + /// max base fee per gas sender is willing to pay + #[serde(default)] + pub max_fee_per_gas: Option, + /// miner tip + #[serde(default)] + pub max_priority_fee_per_gas: Option, + /// gas + pub gas: Option, + /// value of th tx in wei + pub value: Option, + /// Any additional data sent + pub data: Option, + /// Transaction nonce + pub nonce: Option, + /// warm storage access pre-payment + #[serde(default)] + pub access_list: Option>, + /// EIP-2718 type + #[serde(rename = "type")] + pub transaction_type: Option, +} + +// == impl TransactionRequest == + +impl TransactionRequest { + /// Converts the request into a [TypedTransactionRequest] + pub fn into_typed_request(self) -> Option { + let TransactionRequest { + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + data, + nonce, + mut access_list, + .. + } = self; + match (gas_price, max_fee_per_gas, access_list.take()) { + // legacy transaction + (Some(_), None, None) => { + Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { + nonce: nonce.unwrap_or(U256::zero()), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::zero()), + input: data.unwrap_or_default(), + kind: match to { + Some(to) => TransactionKind::Call(to), + None => TransactionKind::Create, + }, + chain_id: None, + })) + } + // EIP2930 + (_, None, Some(access_list)) => { + Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest { + nonce: nonce.unwrap_or(U256::zero()), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::zero()), + input: data.unwrap_or_default(), + kind: match to { + Some(to) => TransactionKind::Call(to), + None => TransactionKind::Create, + }, + chain_id: 0, + access_list, + })) + } + // EIP1559 + (None, Some(_), access_list) | (None, None, access_list @ None) => { + // Empty fields fall back to the canonical transaction schema. + Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest { + nonce: nonce.unwrap_or(U256::zero()), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or(U256::zero()), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::zero()), + input: data.unwrap_or_default(), + kind: match to { + Some(to) => TransactionKind::Call(to), + None => TransactionKind::Create, + }, + chain_id: 0, + access_list: access_list.unwrap_or_default(), + })) + } + _ => None, + } + } +} diff --git a/crates/rpc-types/src/eth/transaction/typed.rs b/crates/rpc-types/src/eth/transaction/typed.rs new file mode 100644 index 000000000..d3a7d210a --- /dev/null +++ b/crates/rpc-types/src/eth/transaction/typed.rs @@ -0,0 +1,111 @@ +#![allow(missing_docs)] + +use fastrlp::{RlpDecodable, RlpEncodable}; +use reth_primitives::{transaction::eip2930::AccessListItem, Address, Bytes, U256}; +use serde::{Deserialize, Serialize}; + +/// Container type for various Ethereum transaction requests +/// +/// Its variants correspond to specific allowed transactions: +/// 1. Legacy (pre-EIP2718) [`LegacyTransactionRequest`] +/// 2. EIP2930 (state access lists) [`EIP2930TransactionRequest`] +/// 3. EIP1559 [`EIP1559TransactionRequest`] +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TypedTransactionRequest { + Legacy(LegacyTransactionRequest), + EIP2930(EIP2930TransactionRequest), + EIP1559(EIP1559TransactionRequest), +} + +/// Represents a legacy transaction request +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LegacyTransactionRequest { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub kind: TransactionKind, + pub value: U256, + pub input: Bytes, + pub chain_id: Option, +} + +/// Represents an EIP-2930 transaction request +#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] +pub struct EIP2930TransactionRequest { + pub chain_id: u64, + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub kind: TransactionKind, + pub value: U256, + pub input: Bytes, + pub access_list: Vec, +} + +/// Represents an EIP-1559 transaction request +#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] +pub struct EIP1559TransactionRequest { + pub chain_id: u64, + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub kind: TransactionKind, + pub value: U256, + pub input: Bytes, + pub access_list: Vec, +} + +/// Represents the `to` field of a transaction request +/// +/// This determines what kind of transaction this is +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TransactionKind { + /// Transaction will call this address or transfer funds to this address + Call(Address), + /// No `to` field set, this transaction will create a contract + Create, +} + +// == impl TransactionKind == + +impl TransactionKind { + /// If this transaction is a call this returns the address of the callee + pub fn as_call(&self) -> Option<&Address> { + match self { + TransactionKind::Call(to) => Some(to), + TransactionKind::Create => None, + } + } +} + +impl fastrlp::Encodable for TransactionKind { + fn length(&self) -> usize { + match self { + TransactionKind::Call(to) => to.length(), + TransactionKind::Create => ([]).length(), + } + } + fn encode(&self, out: &mut dyn fastrlp::BufMut) { + match self { + TransactionKind::Call(to) => to.encode(out), + TransactionKind::Create => ([]).encode(out), + } + } +} + +impl fastrlp::Decodable for TransactionKind { + fn decode(buf: &mut &[u8]) -> Result { + if let Some(&first) = buf.first() { + if first == 0x80 { + *buf = &buf[1..]; + Ok(TransactionKind::Create) + } else { + let addr =
::decode(buf)?; + Ok(TransactionKind::Call(addr)) + } + } else { + Err(fastrlp::DecodeError::InputTooShort) + } + } +} diff --git a/crates/rpc-types/src/lib.rs b/crates/rpc-types/src/lib.rs index 90ebee58d..64cf0a00c 100644 --- a/crates/rpc-types/src/lib.rs +++ b/crates/rpc-types/src/lib.rs @@ -9,4 +9,6 @@ //! //! Provides all relevant types for the various RPC endpoints, grouped by namespace. -mod eth; \ No newline at end of file +mod eth; + +pub use eth::*; \ No newline at end of file