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: basic implementation of eth_sendRawTransaction #487

Merged
merged 9 commits into from
Jul 11, 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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"examples/demo-nft-module",
"full-node/db/sov-db",
"full-node/sov-sequencer",
"full-node/sov-ethereum",

"module-system/sov-modules-stf-template",
"module-system/sov-modules-macros",
Expand Down
2 changes: 1 addition & 1 deletion adapters/celestia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ serde_cbor = "0.11.2"
serde_json = { workspace = true }
tokio = { workspace = true, optional = true }
thiserror = { workspace = true }
tracing = "0.1.37"
tracing = { workspace = true }

sov-rollup-interface = { path = "../../rollup-interface" }
nmt-rs = { git = "https://github.com/Sovereign-Labs/nmt-rs.git", rev = "dd37588444fca72825d11fe4a46838f66525c49f", features = ["serde", "borsh"] }
Expand Down
1 change: 1 addition & 0 deletions examples/demo-rollup/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jupiter = { path = "../../adapters/celestia" }
demo-stf = { path = "../demo-stf", features = ["native"] }
sov-rollup-interface = { path = "../../rollup-interface" }
sov-db = { path = "../../full-node/db/sov-db" }
sov-ethereum = { path = "../../full-node/sov-ethereum", optional = true }
sov-sequencer = { path = "../../full-node/sov-sequencer" }
risc0-adapter = { path = "../../adapters/risc0" }
sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template" }
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.YNNrJz6mCD6ypuH9Ay6NuCy1B6oQVTa2wi5-etSeY-8"
celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.yEqKNCDpXwddRTQYQeMNkQ59O22LI95CS-HeZMcoN-k"
# 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
8 changes: 7 additions & 1 deletion examples/demo-rollup/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use jupiter::types::NamespaceId;
use jupiter::verifier::{CelestiaVerifier, ChainValidityCondition, RollupParams};
use risc0_adapter::host::Risc0Verifier;
use sov_db::ledger_db::{LedgerDB, SlotCommit};
#[cfg(feature = "experimental")]
use sov_ethereum::get_ethereum_rpc;
use sov_modules_api::RpcRunner;
use sov_rollup_interface::crypto::NoOpHasher;
use sov_rollup_interface::da::DaVerifier;
Expand Down Expand Up @@ -141,9 +143,13 @@ async fn main() -> Result<(), anyhow::Error> {
let batch_builder = demo_runner.take_batch_builder().unwrap();

let r = get_sequencer_rpc(batch_builder, da_service.clone());

methods.merge(r).expect("Failed to merge Txs RPC modules");

#[cfg(feature = "experimental")]
let ethereum_rpc = get_ethereum_rpc(rollup_config.da.clone());
#[cfg(feature = "experimental")]
methods.merge(ethereum_rpc).unwrap();

let _handle = tokio::spawn(async move {
start_rpc_server(methods, address).await;
});
Expand Down
8 changes: 7 additions & 1 deletion examples/demo-stf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ sov-sequencer-registry = { path = "../../module-system/module-implementations/so
sov-bank = { path = "../../module-system/module-implementations/sov-bank", default-features = false }
sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template" } # no features available
sov-value-setter = { path = "../../module-system/module-implementations/examples/sov-value-setter", default-features = false }
sov-accounts = { path = "../../module-system/module-implementations/sov-accounts", default-features = false }
sov-accounts = { path = "../../module-system/module-implementations/sov-accounts", default-features = false}
sov-state = { path = "../../module-system/sov-state", default-features = false }
sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false }
sov-modules-macros = { path = "../../module-system/sov-modules-macros" }
Expand All @@ -43,13 +43,19 @@ sov-db = { path = "../../full-node/db/sov-db", optional = true }
const-rollup-config = { path = "../const-rollup-config" }
sov-sequencer = { path = "../../full-node/sov-sequencer", optional = true }

# Only enable the evm on "experimental" feature
sov-evm = { path = "../../module-system/module-implementations/sov-evm", default-features = false, optional = true}

[dev-dependencies]
sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] }
tempfile = { workspace = true }
rand = "0.8"

[features]
default = ["native"]
experimental =["sov-evm/experimental"]


native = [
"dep:sov-db",
"dep:sov-schema-db",
Expand Down
18 changes: 18 additions & 0 deletions examples/demo-stf/src/genesis_config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use sov_election::ElectionConfig;
#[cfg(feature = "experimental")]
use sov_evm::{AccountData, EvmConfig};
pub use sov_modules_api::default_context::DefaultContext;
use sov_modules_api::default_signature::private_key::DefaultPrivateKey;
use sov_modules_api::{Context, Hasher, PublicKey, Spec};
Expand Down Expand Up @@ -52,12 +54,28 @@ pub fn create_demo_genesis_config<C: Context>(
admin: election_admin_private_key.pub_key().to_address(),
};

#[cfg(feature = "experimental")]
let genesis_evm_address = hex::decode("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
.unwrap()
.try_into()
.expect("EVM module initialized with invalid address");

GenesisConfig::new(
bank_config,
sequencer_registry_config,
election_config,
value_setter_config,
sov_accounts::AccountConfig { pub_keys: vec![] },
#[cfg(feature = "experimental")]
EvmConfig {
data: vec![AccountData {
address: genesis_evm_address,
balance: AccountData::balance(1000000000),
code_hash: AccountData::empty_code(),
code: vec![],
nonce: 0,
}],
},
)
}

Expand Down
21 changes: 21 additions & 0 deletions examples/demo-stf/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use sov_bank::query::{BankRpcImpl, BankRpcServer};
#[cfg(feature = "native")]
use sov_election::query::{ElectionRpcImpl, ElectionRpcServer};
#[cfg(feature = "native")]
#[cfg(feature = "experimental")]
use sov_evm::query::{EvmRpcImpl, EvmRpcServer};
#[cfg(feature = "native")]
pub use sov_modules_api::default_context::DefaultContext;
use sov_modules_api::Context;
#[cfg(feature = "native")]
Expand Down Expand Up @@ -50,6 +53,23 @@ use sov_value_setter::query::{ValueSetterRpcImpl, ValueSetterRpcServer};
/// Similar mechanism works for queries with the difference that queries are submitted by users directly to the rollup node
/// instead of going through the DA layer.

#[cfg(not(feature = "experimental"))]
#[cfg_attr(
feature = "native",
cli_parser(DefaultContext),
expose_rpc(DefaultContext)
)]
#[derive(Genesis, DispatchCall, MessageCodec, DefaultRuntime)]
#[serialization(borsh::BorshDeserialize, borsh::BorshSerialize)]
pub struct Runtime<C: Context> {
pub bank: sov_bank::Bank<C>,
pub sequencer_registry: sov_sequencer_registry::SequencerRegistry<C>,
pub election: sov_election::Election<C>,
pub value_setter: sov_value_setter::ValueSetter<C>,
pub accounts: sov_accounts::Accounts<C>,
}

#[cfg(feature = "experimental")]
#[cfg_attr(
feature = "native",
cli_parser(DefaultContext),
Expand All @@ -63,4 +83,5 @@ pub struct Runtime<C: Context> {
pub election: sov_election::Election<C>,
pub value_setter: sov_value_setter::ValueSetter<C>,
pub accounts: sov_accounts::Accounts<C>,
pub evm: sov_evm::Evm<C>,
}
40 changes: 40 additions & 0 deletions full-node/sov-ethereum/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[package]
name = "sov-ethereum"
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
rust-version = { workspace = true }
version = { workspace = true }
readme = "README.md"
resolver = "2"


[dependencies]
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", "experimental"] }
sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false }
const-rollup-config = { path = "../../examples/const-rollup-config" }
jupiter = { path = "../../adapters/celestia", features = ["native"] }

borsh = { workspace = true }
serde_json = { workspace = true }


[dev-dependencies]
sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] }
tokio = { workspace = true }


[features]
default = ["native", "experimental"]
experimental =["demo-stf/experimental", "sov-evm/experimental"]

native = ["demo-stf/native", "sov-evm/native"]
127 changes: 127 additions & 0 deletions full-node/sov-ethereum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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 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_modules_api::transaction::Transaction;

const GAS_PER_BYTE: usize = 120;

pub fn get_ethereum_rpc(config: DaServiceConfig) -> RpcModule<Ethereum> {
let mut rpc = RpcModule::new(Ethereum { config });
register_rpc_methods(&mut rpc).expect("Failed to register sequencer RPC methods");
rpc
}

pub struct Ethereum {
config: DaServiceConfig,
}

impl Ethereum {
fn make_client(&self) -> HttpClient {
let mut headers = HeaderMap::new();
headers.insert(
"Authorization",
format!("Bearer {}", self.config.celestia_rpc_auth_token.clone())
.parse()
.unwrap(),
);

jsonrpsee::http_client::HttpClientBuilder::default()
.set_headers(headers)
.max_request_body_size(default_max_response_size()) // 100 MB
.build(self.config.celestia_rpc_address.clone())
.expect("Client initialization is valid")
}

async fn send_tx_to_da(
&self,
raw: Vec<u8>,
) -> Result<serde_json::Value, jsonrpsee::core::Error> {
let blob = vec![raw].try_to_vec().unwrap();
let client = self.make_client();
let fee: u64 = 2000;
let namespace = ROLLUP_NAMESPACE_RAW.to_vec();
let gas_limit = (blob.len() + 512) * GAS_PER_BYTE + 1060;

let mut params = ArrayParams::new();
params.insert(namespace)?;
params.insert(blob)?;
params.insert(fee.to_string())?;
params.insert(gas_limit)?;
client
.request::<serde_json::Value, _>("state.SubmitPayForBlob", params)
.await
}
}

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")
}

let rlp = Rlp::new(data);
let (decoded_tx, _decoded_sig) = TypedTransaction::decode_signed(&rlp).unwrap();
let tx_hash = decoded_tx.sighash();

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 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![],
};

// todo set nonce
let raw = make_raw_tx(evm_tx, 0).unwrap();
ethereum.send_tx_to_da(raw).await?;

Ok(tx_hash)
},
)?;

Ok(())
}

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);
// todo don't generate sender here.
let sender = DefaultPrivateKey::generate();
let tx = Transaction::<DefaultContext>::new_signed_tx(&sender, message, nonce);
tx.try_to_vec()
}

fn default_max_response_size() -> u32 {
1024 * 1024 * 100 // 100 MB
}
10 changes: 5 additions & 5 deletions module-system/module-implementations/sov-evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ thiserror = { workspace = true }
borsh = { workspace = true, features = ["rc"] }
hex = { workspace = true }
jsonrpsee = { workspace = true, features = ["macros", "client-core", "server"], optional = true }
tracing = { workspace = true }



[dev-dependencies]
# TODO: move these dependencies to the workspace when the EVM module is no longer in the experimental stage
ethereum-types = "0.14.1"
ethers-core = "2.0.7"
ethers-contract = "2.0.7"
ethers-middleware = { version = "2.0.7"}
ethers-providers = { version = "2.0.7"}
ethers-signers = { version = "2.0.7", default-features = false }
primitive-types = "0.12.1"


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

tokio = { workspace = true }
Expand All @@ -50,4 +50,4 @@ bytes = { workspace = true }
default = ["native"]
serde = ["dep:serde", "dep:serde_json"]
native = ["serde", "sov-state/native", "dep:jsonrpsee", "sov-modules-api/native"]
experimental = []
experimental = ["native"]
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) mod transaction;
pub(crate) type EthAddress = [u8; 20];
pub(crate) type Bytes32 = [u8; 32];

pub use transaction::EvmTransaction;
// Stores information about an EVM account
#[derive(borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone, Default)]
pub(crate) struct AccountInfo {
Expand Down
Loading