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

EVM module: add eth_getTransactionByHash endpoint #491

Merged
merged 7 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,9 @@ 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 = { path = "../foundry/anvil" }
bkolad marked this conversation as resolved.
Show resolved Hide resolved
anvil-core = { git = "https://github.com/foundry-rs/foundry", features = ["fastrlp", "serde"], version = "0.1.0" }
#anvil-core = { path = "../foundry/anvil/core", features = ["fastrlp", "serde", "impersonated-tx"] }
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"
bkolad marked this conversation as resolved.
Show resolved Hide resolved
# 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 @@ -70,7 +70,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
bkolad marked this conversation as resolved.
Show resolved Hide resolved
Empty file.
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