Skip to content

Commit 836c56e

Browse files
authored
EVM: basic implementation of eth_sendRawTransaction (#487)
1 parent a691a28 commit 836c56e

File tree

15 files changed

+495
-82
lines changed

15 files changed

+495
-82
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"examples/demo-nft-module",
1212
"full-node/db/sov-db",
1313
"full-node/sov-sequencer",
14+
"full-node/sov-ethereum",
1415

1516
"module-system/sov-modules-stf-template",
1617
"module-system/sov-modules-macros",

adapters/celestia/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ serde_cbor = "0.11.2"
2626
serde_json = { workspace = true }
2727
tokio = { workspace = true, optional = true }
2828
thiserror = { workspace = true }
29-
tracing = "0.1.37"
29+
tracing = { workspace = true }
3030

3131
sov-rollup-interface = { path = "../../rollup-interface" }
3232
nmt-rs = { git = "https://github.com/Sovereign-Labs/nmt-rs.git", rev = "dd37588444fca72825d11fe4a46838f66525c49f", features = ["serde", "borsh"] }

examples/demo-rollup/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jupiter = { path = "../../adapters/celestia" }
2828
demo-stf = { path = "../demo-stf", features = ["native"] }
2929
sov-rollup-interface = { path = "../../rollup-interface" }
3030
sov-db = { path = "../../full-node/db/sov-db" }
31+
sov-ethereum = { path = "../../full-node/sov-ethereum", optional = true }
3132
sov-sequencer = { path = "../../full-node/sov-sequencer" }
3233
risc0-adapter = { path = "../../adapters/risc0" }
3334
sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template" }

examples/demo-rollup/rollup_config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ start_height = 1
44

55
[da]
66
# The JWT used to authenticate with the celestia light client. Instructions for generating this token can be found in the README
7-
celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.YNNrJz6mCD6ypuH9Ay6NuCy1B6oQVTa2wi5-etSeY-8"
7+
celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.yEqKNCDpXwddRTQYQeMNkQ59O22LI95CS-HeZMcoN-k"
88
# The address of the *trusted* Celestia light client to interact with
99
celestia_rpc_address = "http://127.0.0.1:26658"
1010
# The largest response the rollup will accept from the Celestia node. Defaults to 100 MB

examples/demo-rollup/src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use jupiter::types::NamespaceId;
1616
use jupiter::verifier::{CelestiaVerifier, ChainValidityCondition, RollupParams};
1717
use risc0_adapter::host::Risc0Verifier;
1818
use sov_db::ledger_db::{LedgerDB, SlotCommit};
19+
#[cfg(feature = "experimental")]
20+
use sov_ethereum::get_ethereum_rpc;
1921
use sov_modules_api::RpcRunner;
2022
use sov_rollup_interface::crypto::NoOpHasher;
2123
use sov_rollup_interface::da::{BlockHeaderTrait, DaVerifier};
@@ -140,9 +142,13 @@ async fn main() -> Result<(), anyhow::Error> {
140142
let batch_builder = demo_runner.take_batch_builder().unwrap();
141143

142144
let r = get_sequencer_rpc(batch_builder, da_service.clone());
143-
144145
methods.merge(r).expect("Failed to merge Txs RPC modules");
145146

147+
#[cfg(feature = "experimental")]
148+
let ethereum_rpc = get_ethereum_rpc(rollup_config.da.clone());
149+
#[cfg(feature = "experimental")]
150+
methods.merge(ethereum_rpc).unwrap();
151+
146152
let _handle = tokio::spawn(async move {
147153
start_rpc_server(methods, address).await;
148154
});

examples/demo-stf/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ sov-sequencer-registry = { path = "../../module-system/module-implementations/so
3333
sov-bank = { path = "../../module-system/module-implementations/sov-bank", default-features = false }
3434
sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template" } # no features available
3535
sov-value-setter = { path = "../../module-system/module-implementations/examples/sov-value-setter", default-features = false }
36-
sov-accounts = { path = "../../module-system/module-implementations/sov-accounts", default-features = false }
36+
sov-accounts = { path = "../../module-system/module-implementations/sov-accounts", default-features = false}
3737
sov-state = { path = "../../module-system/sov-state", default-features = false }
3838
sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false }
3939
sov-modules-macros = { path = "../../module-system/sov-modules-macros" }
@@ -43,13 +43,19 @@ sov-db = { path = "../../full-node/db/sov-db", optional = true }
4343
const-rollup-config = { path = "../const-rollup-config" }
4444
sov-sequencer = { path = "../../full-node/sov-sequencer", optional = true }
4545

46+
# Only enable the evm on "experimental" feature
47+
sov-evm = { path = "../../module-system/module-implementations/sov-evm", default-features = false, optional = true}
48+
4649
[dev-dependencies]
4750
sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] }
4851
tempfile = { workspace = true }
4952
rand = "0.8"
5053

5154
[features]
5255
default = ["native"]
56+
experimental =["sov-evm/experimental"]
57+
58+
5359
native = [
5460
"dep:sov-db",
5561
"dep:sov-schema-db",

examples/demo-stf/src/genesis_config.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use sov_election::ElectionConfig;
2+
#[cfg(feature = "experimental")]
3+
use sov_evm::{AccountData, EvmConfig};
24
pub use sov_modules_api::default_context::DefaultContext;
35
use sov_modules_api::default_signature::private_key::DefaultPrivateKey;
46
use sov_modules_api::{Context, Hasher, PublicKey, Spec};
@@ -52,12 +54,28 @@ pub fn create_demo_genesis_config<C: Context>(
5254
admin: election_admin_private_key.pub_key().to_address(),
5355
};
5456

57+
#[cfg(feature = "experimental")]
58+
let genesis_evm_address = hex::decode("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
59+
.unwrap()
60+
.try_into()
61+
.expect("EVM module initialized with invalid address");
62+
5563
GenesisConfig::new(
5664
bank_config,
5765
sequencer_registry_config,
5866
election_config,
5967
value_setter_config,
6068
sov_accounts::AccountConfig { pub_keys: vec![] },
69+
#[cfg(feature = "experimental")]
70+
EvmConfig {
71+
data: vec![AccountData {
72+
address: genesis_evm_address,
73+
balance: AccountData::balance(1000000000),
74+
code_hash: AccountData::empty_code(),
75+
code: vec![],
76+
nonce: 0,
77+
}],
78+
},
6179
)
6280
}
6381

examples/demo-stf/src/runtime.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use sov_bank::query::{BankRpcImpl, BankRpcServer};
55
#[cfg(feature = "native")]
66
use sov_election::query::{ElectionRpcImpl, ElectionRpcServer};
77
#[cfg(feature = "native")]
8+
#[cfg(feature = "experimental")]
9+
use sov_evm::query::{EvmRpcImpl, EvmRpcServer};
10+
#[cfg(feature = "native")]
811
pub use sov_modules_api::default_context::DefaultContext;
912
use sov_modules_api::Context;
1013
#[cfg(feature = "native")]
@@ -50,6 +53,23 @@ use sov_value_setter::query::{ValueSetterRpcImpl, ValueSetterRpcServer};
5053
/// Similar mechanism works for queries with the difference that queries are submitted by users directly to the rollup node
5154
/// instead of going through the DA layer.
5255
56+
#[cfg(not(feature = "experimental"))]
57+
#[cfg_attr(
58+
feature = "native",
59+
cli_parser(DefaultContext),
60+
expose_rpc(DefaultContext)
61+
)]
62+
#[derive(Genesis, DispatchCall, MessageCodec, DefaultRuntime)]
63+
#[serialization(borsh::BorshDeserialize, borsh::BorshSerialize)]
64+
pub struct Runtime<C: Context> {
65+
pub bank: sov_bank::Bank<C>,
66+
pub sequencer_registry: sov_sequencer_registry::SequencerRegistry<C>,
67+
pub election: sov_election::Election<C>,
68+
pub value_setter: sov_value_setter::ValueSetter<C>,
69+
pub accounts: sov_accounts::Accounts<C>,
70+
}
71+
72+
#[cfg(feature = "experimental")]
5373
#[cfg_attr(
5474
feature = "native",
5575
cli_parser(DefaultContext),
@@ -63,4 +83,5 @@ pub struct Runtime<C: Context> {
6383
pub election: sov_election::Election<C>,
6484
pub value_setter: sov_value_setter::ValueSetter<C>,
6585
pub accounts: sov_accounts::Accounts<C>,
86+
pub evm: sov_evm::Evm<C>,
6687
}

full-node/sov-ethereum/Cargo.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[package]
2+
name = "sov-ethereum"
3+
authors = { workspace = true }
4+
edition = { workspace = true }
5+
homepage = { workspace = true }
6+
license = { workspace = true }
7+
repository = { workspace = true }
8+
rust-version = { workspace = true }
9+
version = { workspace = true }
10+
readme = "README.md"
11+
resolver = "2"
12+
13+
14+
[dependencies]
15+
anyhow = { workspace = true }
16+
jsonrpsee = { workspace = true, features = ["http-client", "server"] }
17+
serde = { workspace = true, features = ["derive"] }
18+
sov-rollup-interface = { path = "../../rollup-interface" }
19+
ethers = {version = "2.0.7"}
20+
21+
sov-evm = { path = "../../module-system/module-implementations/sov-evm", default-features = false }
22+
demo-stf = { path = "../../examples/demo-stf", features = ["native", "experimental"] }
23+
sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false }
24+
const-rollup-config = { path = "../../examples/const-rollup-config" }
25+
jupiter = { path = "../../adapters/celestia", features = ["native"] }
26+
27+
borsh = { workspace = true }
28+
serde_json = { workspace = true }
29+
30+
31+
[dev-dependencies]
32+
sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] }
33+
tokio = { workspace = true }
34+
35+
36+
[features]
37+
default = ["native", "experimental"]
38+
experimental =["demo-stf/experimental", "sov-evm/experimental"]
39+
40+
native = ["demo-stf/native", "sov-evm/native"]

full-node/sov-ethereum/src/lib.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use borsh::ser::BorshSerialize;
2+
use const_rollup_config::ROLLUP_NAMESPACE_RAW;
3+
use demo_stf::app::DefaultPrivateKey;
4+
use demo_stf::runtime::{DefaultContext, Runtime};
5+
use ethers::types::transaction::eip2718::TypedTransaction;
6+
use ethers::types::Bytes;
7+
use ethers::utils::rlp::Rlp;
8+
use jsonrpsee::core::client::ClientT;
9+
use jsonrpsee::core::params::ArrayParams;
10+
use jsonrpsee::http_client::{HeaderMap, HttpClient};
11+
use jsonrpsee::RpcModule;
12+
use jupiter::da_service::DaServiceConfig;
13+
use sov_evm::call::CallMessage;
14+
use sov_evm::evm::EvmTransaction;
15+
use sov_modules_api::transaction::Transaction;
16+
17+
const GAS_PER_BYTE: usize = 120;
18+
19+
pub fn get_ethereum_rpc(config: DaServiceConfig) -> RpcModule<Ethereum> {
20+
let mut rpc = RpcModule::new(Ethereum { config });
21+
register_rpc_methods(&mut rpc).expect("Failed to register sequencer RPC methods");
22+
rpc
23+
}
24+
25+
pub struct Ethereum {
26+
config: DaServiceConfig,
27+
}
28+
29+
impl Ethereum {
30+
fn make_client(&self) -> HttpClient {
31+
let mut headers = HeaderMap::new();
32+
headers.insert(
33+
"Authorization",
34+
format!("Bearer {}", self.config.celestia_rpc_auth_token.clone())
35+
.parse()
36+
.unwrap(),
37+
);
38+
39+
jsonrpsee::http_client::HttpClientBuilder::default()
40+
.set_headers(headers)
41+
.max_request_body_size(default_max_response_size()) // 100 MB
42+
.build(self.config.celestia_rpc_address.clone())
43+
.expect("Client initialization is valid")
44+
}
45+
46+
async fn send_tx_to_da(
47+
&self,
48+
raw: Vec<u8>,
49+
) -> Result<serde_json::Value, jsonrpsee::core::Error> {
50+
let blob = vec![raw].try_to_vec().unwrap();
51+
let client = self.make_client();
52+
let fee: u64 = 2000;
53+
let namespace = ROLLUP_NAMESPACE_RAW.to_vec();
54+
let gas_limit = (blob.len() + 512) * GAS_PER_BYTE + 1060;
55+
56+
let mut params = ArrayParams::new();
57+
params.insert(namespace)?;
58+
params.insert(blob)?;
59+
params.insert(fee.to_string())?;
60+
params.insert(gas_limit)?;
61+
client
62+
.request::<serde_json::Value, _>("state.SubmitPayForBlob", params)
63+
.await
64+
}
65+
}
66+
67+
fn register_rpc_methods(rpc: &mut RpcModule<Ethereum>) -> Result<(), jsonrpsee::core::Error> {
68+
rpc.register_async_method(
69+
"eth_sendRawTransaction",
70+
|parameters, ethereum| async move {
71+
let data: Bytes = parameters.one().unwrap();
72+
let data = data.as_ref();
73+
74+
// todo handle panics and unwraps.
75+
if data[0] > 0x7f {
76+
panic!("Invalid transaction type")
77+
}
78+
79+
let rlp = Rlp::new(data);
80+
let (decoded_tx, _decoded_sig) = TypedTransaction::decode_signed(&rlp).unwrap();
81+
let tx_hash = decoded_tx.sighash();
82+
83+
let tx_request = match decoded_tx {
84+
TypedTransaction::Legacy(_) => panic!("Legacy transaction type not supported"),
85+
TypedTransaction::Eip2930(_) => panic!("Eip2930 not supported"),
86+
TypedTransaction::Eip1559(request) => request,
87+
};
88+
89+
let evm_tx = EvmTransaction {
90+
caller: tx_request.from.unwrap().into(),
91+
data: tx_request.data.unwrap().to_vec(),
92+
// todo set `gas limit`
93+
gas_limit: u64::MAX,
94+
// todo set `gas price`
95+
gas_price: Default::default(),
96+
// todo set `max_priority_fee_per_gas`
97+
max_priority_fee_per_gas: Default::default(),
98+
// todo `set to`
99+
to: None,
100+
value: tx_request.value.unwrap().into(),
101+
nonce: tx_request.nonce.unwrap().as_u64(),
102+
access_lists: vec![],
103+
};
104+
105+
// todo set nonce
106+
let raw = make_raw_tx(evm_tx, 0).unwrap();
107+
ethereum.send_tx_to_da(raw).await?;
108+
109+
Ok(tx_hash)
110+
},
111+
)?;
112+
113+
Ok(())
114+
}
115+
116+
fn make_raw_tx(evm_tx: EvmTransaction, nonce: u64) -> Result<Vec<u8>, std::io::Error> {
117+
let tx = CallMessage { tx: evm_tx };
118+
let message = Runtime::<DefaultContext>::encode_evm_call(tx);
119+
// todo don't generate sender here.
120+
let sender = DefaultPrivateKey::generate();
121+
let tx = Transaction::<DefaultContext>::new_signed_tx(&sender, message, nonce);
122+
tx.try_to_vec()
123+
}
124+
125+
fn default_max_response_size() -> u32 {
126+
1024 * 1024 * 100 // 100 MB
127+
}

0 commit comments

Comments
 (0)