Skip to content

Commit

Permalink
EVM module: add eth_getTransactionByHash endpoint (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkolad authored Jul 13, 2023
1 parent 27214bc commit 3e6917c
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 107 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ toml = "0.7.3"
jsonrpsee = "0.16.2"
tempfile = "3.5"
tokio = { version = "1", features = ["full"] }

ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
anvil = { git = "https://github.com/foundry-rs/foundry", version = "0.1.0" }
anvil-core = { git = "https://github.com/foundry-rs/foundry", features = ["fastrlp", "serde"], version = "0.1.0" }

3 changes: 3 additions & 0 deletions examples/demo-rollup/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ prometheus = "0.11.0"
prettytable-rs = "^0.10"
criterion = "0.5.1"

[features]
experimental = ["sov-ethereum/experimental"]

[[bench]]
name = "rollup_bench"
harness = false
Expand Down
2 changes: 1 addition & 1 deletion examples/demo-rollup/rollup_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ start_height = 1

[da]
# The JWT used to authenticate with the celestia light client. Instructions for generating this token can be found in the README
celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.yEqKNCDpXwddRTQYQeMNkQ59O22LI95CS-HeZMcoN-k"
celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.jp_hghhRkcur3g0o-QhQnkLt89YPu4oykLtZW52_ZA0"
# The address of the *trusted* Celestia light client to interact with
celestia_rpc_address = "http://127.0.0.1:26658"
# The largest response the rollup will accept from the Celestia node. Defaults to 100 MB
Expand Down
2 changes: 1 addition & 1 deletion examples/demo-stf/src/genesis_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub fn create_demo_genesis_config<C: Context>(
EvmConfig {
data: vec![AccountData {
address: genesis_evm_address,
balance: AccountData::balance(1000000000),
balance: AccountData::balance(u64::MAX),
code_hash: AccountData::empty_code(),
code: vec![],
nonce: 0,
Expand Down
4 changes: 3 additions & 1 deletion full-node/sov-ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ anyhow = { workspace = true }
jsonrpsee = { workspace = true, features = ["http-client", "server"] }
serde = { workspace = true, features = ["derive"] }
sov-rollup-interface = { path = "../../rollup-interface" }
ethers = {version = "2.0.7"}

sov-evm = { path = "../../module-system/module-implementations/sov-evm", default-features = false }
demo-stf = { path = "../../examples/demo-stf", features = ["native"] }
Expand All @@ -27,6 +26,9 @@ jupiter = { path = "../../adapters/celestia", features = ["native"] }
borsh = { workspace = true }
serde_json = { workspace = true }

anvil-core = { workspace = true }
ethers = { workspace = true }


[dev-dependencies]
sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] }
Expand Down
89 changes: 55 additions & 34 deletions full-node/sov-ethereum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
#[cfg(feature = "experimental")]
pub use experimental::{get_ethereum_rpc, Ethereum};

#[cfg(feature = "experimental")]
pub mod experimental {
use std::collections::HashMap;
use std::sync::Mutex;

use anvil_core::eth::transaction::TypedTransaction;
use borsh::ser::BorshSerialize;
use const_rollup_config::ROLLUP_NAMESPACE_RAW;
use demo_stf::app::DefaultPrivateKey;
use demo_stf::runtime::{DefaultContext, Runtime};
use ethers::types::transaction::eip2718::TypedTransaction;
use ethers::types::Bytes;
use ethers::utils::rlp::Rlp;
use ethers::utils::rlp;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::core::params::ArrayParams;
use jsonrpsee::http_client::{HeaderMap, HttpClient};
use jsonrpsee::RpcModule;
use jupiter::da_service::DaServiceConfig;
use sov_evm::call::CallMessage;
use sov_evm::evm::EvmTransaction;
use sov_evm::evm::{EthAddress, EvmTransaction};
use sov_modules_api::transaction::Transaction;

const GAS_PER_BYTE: usize = 120;

#[cfg(feature = "experimental")]
pub fn get_ethereum_rpc(config: DaServiceConfig) -> RpcModule<Ethereum> {
let mut rpc = RpcModule::new(Ethereum { config });
let mut rpc = RpcModule::new(Ethereum {
config,
nonces: Default::default(),
});
register_rpc_methods(&mut rpc).expect("Failed to register sequencer RPC methods");
rpc
}

pub struct Ethereum {
config: DaServiceConfig,
nonces: Mutex<HashMap<EthAddress, u64>>,
}

impl Ethereum {
Expand All @@ -50,7 +60,7 @@ pub mod experimental {
&self,
raw: Vec<u8>,
) -> Result<serde_json::Value, jsonrpsee::core::Error> {
let blob = vec![raw].try_to_vec().unwrap();
let blob = vec![raw].try_to_vec()?;
let client = self.make_client();
let fee: u64 = 2000;
let namespace = ROLLUP_NAMESPACE_RAW.to_vec();
Expand All @@ -67,57 +77,68 @@ pub mod experimental {
}
}

#[cfg(feature = "experimental")]
fn register_rpc_methods(rpc: &mut RpcModule<Ethereum>) -> Result<(), jsonrpsee::core::Error> {
rpc.register_async_method(
"eth_sendRawTransaction",
|parameters, ethereum| async move {
let data: Bytes = parameters.one().unwrap();
let data = data.as_ref();

// todo handle panics and unwraps.
if data[0] > 0x7f {
panic!("Invalid transaction type")
if data.is_empty() {
return Err(jsonrpsee::core::Error::Custom(
"Empty raw transaction data".to_owned(),
));
}

let rlp = Rlp::new(data);
let (decoded_tx, _decoded_sig) = TypedTransaction::decode_signed(&rlp).unwrap();
let tx_hash = decoded_tx.sighash();
if data[0] > 0x7f {
return Err(jsonrpsee::core::Error::Custom(
"Legacy transaction not supported".to_owned(),
));
}

let tx_request = match decoded_tx {
TypedTransaction::Legacy(_) => panic!("Legacy transaction type not supported"),
TypedTransaction::Eip2930(_) => panic!("Eip2930 not supported"),
TypedTransaction::Eip1559(request) => request,
let extend = rlp::encode(&data);
let typed_transaction = match rlp::decode::<TypedTransaction>(&extend[..]) {
Ok(transaction) => transaction,
Err(e) => {
return Err(jsonrpsee::core::Error::Custom(format!(
"Failed to decode signed transaction: {}",
e
)))
}
};

let evm_tx = EvmTransaction {
caller: tx_request.from.unwrap().into(),
data: tx_request.data.unwrap().to_vec(),
// todo set `gas limit`
gas_limit: u64::MAX,
// todo set `gas price`
gas_price: Default::default(),
// todo set `max_priority_fee_per_gas`
max_priority_fee_per_gas: Default::default(),
// todo `set to`
to: None,
value: tx_request.value.unwrap().into(),
nonce: tx_request.nonce.unwrap().as_u64(),
access_lists: vec![],
let transaction = match typed_transaction {
TypedTransaction::Legacy(_) => {
return Err(jsonrpsee::core::Error::Custom(
"Legacy transaction not supported".to_owned(),
))
}
TypedTransaction::EIP2930(_) => {
return Err(jsonrpsee::core::Error::Custom(
"EIP2930 not supported".to_owned(),
))
}
TypedTransaction::EIP1559(tx) => tx,
};

// todo set nonce
let raw = make_raw_tx(evm_tx, 0).unwrap();
ethereum.send_tx_to_da(raw).await?;
let tx_hash = transaction.hash();
let evm_transaction: EvmTransaction = transaction.into();
let sender = evm_transaction.sender;

let raw_tx = {
let mut nonces = ethereum.nonces.lock().unwrap();
let n = nonces.entry(sender).and_modify(|n| *n += 1).or_insert(0);
make_raw_tx(evm_transaction, *n)?
};

ethereum.send_tx_to_da(raw_tx).await?;
Ok(tx_hash)
},
)?;

Ok(())
}

#[cfg(feature = "experimental")]
fn make_raw_tx(evm_tx: EvmTransaction, nonce: u64) -> Result<Vec<u8>, std::io::Error> {
let tx = CallMessage { tx: evm_tx };
let message = Runtime::<DefaultContext>::encode_evm_call(tx);
Expand Down
7 changes: 5 additions & 2 deletions module-system/module-implementations/sov-evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@ ethers-middleware = { version = "2.0.7"}
ethers-providers = { version = "2.0.7"}
ethers-signers = { version = "2.0.7", default-features = false }

anvil-core = { workspace = true }
ethers = { workspace = true }

[dev-dependencies]
anvil = { workspace = true }
primitive-types = "0.12.1"
sov-modules-api = { path = "../../sov-modules-api", version = "0.1" }

tokio = { workspace = true }
tempfile = { workspace = true }
bytes = { workspace = true }

sov-modules-api = { path = "../../sov-modules-api", version = "0.1" }

[features]
default = ["native"]
serde = ["dep:serde", "dep:serde_json"]
Expand Down
7 changes: 6 additions & 1 deletion module-system/module-implementations/sov-evm/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ impl<C: sov_modules_api::Context> Evm<C> {
) -> Result<CallResponse> {
let cfg_env = CfgEnv::default();
let block_env = self.block_env.get(working_set).unwrap_or_default();

self.transactions.set(&tx.hash, &tx, working_set);

let evm_db: EvmDb<'_, C> = self.get_db(working_set);

// It is ok to use the unwrap here because the error type is `Infallible`.
executor::execute_tx(evm_db, block_env, tx, cfg_env).unwrap();
let result = executor::execute_tx(evm_db, block_env, tx, cfg_env).unwrap();

println!("Result {:?}", result);
Ok(CallResponse::default())
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anvil_core::eth::transaction::EIP1559Transaction;
use bytes::Bytes;
use ethers_core::types::{OtherFields, Transaction};
use revm::primitives::{
AccountInfo as ReVmAccountInfo, BlockEnv as ReVmBlockEnv, Bytecode, CreateScheme, TransactTo,
TxEnv, B160, B256, U256,
Expand Down Expand Up @@ -70,17 +72,79 @@ impl From<EvmTransaction> for TxEnv {
.collect();

Self {
caller: B160::from_slice(&tx.caller),
caller: B160::from_slice(&tx.sender),
data: Bytes::from(tx.data),
gas_limit: tx.gas_limit,
gas_price: tx.gas_price.map(U256::from_le_bytes).unwrap_or_default(),
gas_priority_fee: tx.max_priority_fee_per_gas.map(U256::from_le_bytes),
gas_price: U256::from_be_bytes(tx.gas_price),
gas_priority_fee: Some(U256::from_be_bytes(tx.max_priority_fee_per_gas)),
transact_to: to,
value: U256::from_le_bytes(tx.value),
value: U256::from_be_bytes(tx.value),
nonce: Some(tx.nonce),
//TODO: handle chain_id
chain_id: None,
chain_id: Some(tx.chain_id),
access_list,
}
}
}

impl From<EvmTransaction> for Transaction {
fn from(evm_tx: EvmTransaction) -> Self {
Self {
hash: evm_tx.hash.into(),
nonce: evm_tx.nonce.into(),
from: evm_tx.sender.into(),
to: evm_tx.to.map(|addr| addr.into()),
value: evm_tx.value.into(),
// https://github.com/foundry-rs/foundry/blob/master/anvil/core/src/eth/transaction/mod.rs#L1251
gas_price: Some(evm_tx.max_fee_per_gas.into()),
input: evm_tx.data.into(),
v: (evm_tx.odd_y_parity as u8).into(),
r: evm_tx.r.into(),
s: evm_tx.s.into(),
transaction_type: Some(2.into()),
access_list: None,
max_priority_fee_per_gas: Some(evm_tx.max_priority_fee_per_gas.into()),
max_fee_per_gas: Some(evm_tx.max_fee_per_gas.into()),
chain_id: Some(evm_tx.chain_id.into()),
// todo
block_hash: Some([0; 32].into()),
// todo
block_number: Some(1.into()),
// todo
transaction_index: Some(1.into()),
// todo
gas: Default::default(),
// todo
other: OtherFields::default(),
}
}
}

impl From<EIP1559Transaction> for EvmTransaction {
fn from(transaction: EIP1559Transaction) -> Self {
let to = transaction.kind.as_call().map(|addr| (*addr).into());
let tx_hash = transaction.hash();
// todo error handling
let sender = transaction.recover().unwrap();

Self {
sender: sender.into(),
data: transaction.input.to_vec(),
gas_limit: transaction.gas_limit.as_u64(),
// https://github.com/foundry-rs/foundry/blob/master/anvil/core/src/eth/transaction/mod.rs#L1251C20-L1251C20
gas_price: transaction.max_fee_per_gas.into(),
max_priority_fee_per_gas: transaction.max_priority_fee_per_gas.into(),
max_fee_per_gas: transaction.max_fee_per_gas.into(),
to,
value: transaction.value.into(),
nonce: transaction.nonce.as_u64(),
// todo
access_lists: vec![],
chain_id: transaction.chain_id,
// todo remove it
hash: tx_hash.into(),
odd_y_parity: transaction.odd_y_parity,
r: transaction.r.into(),
s: transaction.s.into(),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) mod test_helpers;
mod tests;
pub(crate) mod transaction;

pub(crate) type EthAddress = [u8; 20];
pub type EthAddress = [u8; 20];
pub(crate) type Bytes32 = [u8; 32];

pub use transaction::EvmTransaction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,42 @@ pub struct AccessListItem {
)]
#[derive(borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone)]
pub struct EvmTransaction {
pub caller: [u8; 20],
pub sender: EthAddress,
pub data: Vec<u8>,
pub gas_limit: u64,
pub gas_price: Option<Bytes32>,
pub max_priority_fee_per_gas: Option<Bytes32>,
pub gas_price: Bytes32,
pub max_priority_fee_per_gas: Bytes32,
pub max_fee_per_gas: Bytes32,
pub to: Option<[u8; 20]>,
pub value: Bytes32,
pub nonce: u64,
pub access_lists: Vec<AccessListItem>,
pub chain_id: u64,
pub odd_y_parity: bool,
pub r: [u8; 32],
pub s: [u8; 32],
// todo remove it
pub hash: [u8; 32],
}

impl Default for EvmTransaction {
fn default() -> Self {
Self {
caller: Default::default(),
sender: Default::default(),
data: Default::default(),
gas_limit: u64::MAX,
gas_price: Default::default(),
max_priority_fee_per_gas: Default::default(),
max_fee_per_gas: Default::default(),
to: Default::default(),
value: Default::default(),
nonce: Default::default(),
access_lists: Default::default(),
chain_id: 1,
hash: Default::default(),
odd_y_parity: Default::default(),
r: Default::default(),
s: Default::default(),
}
}
}
Loading

0 comments on commit 3e6917c

Please sign in to comment.