From 7b0a5fb52af0a8d49d2a3967491f0542d635930a Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 20 Nov 2024 12:49:06 +0100 Subject: [PATCH] feat: extract wallet api to own service --- Cargo.lock | 26 ++++++++-- Cargo.toml | 4 +- bin/odyssey/Cargo.toml | 1 - bin/odyssey/src/main.rs | 37 +------------ bin/relay/Cargo.toml | 45 ++++++++++++++++ bin/relay/src/main.rs | 37 +++++++++++++ crates/wallet/Cargo.toml | 6 +-- crates/wallet/src/lib.rs | 109 +++++++++++++++------------------------ 8 files changed, 150 insertions(+), 115 deletions(-) create mode 100644 bin/relay/Cargo.toml create mode 100644 bin/relay/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 1faabf3..04e67df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4556,7 +4556,6 @@ dependencies = [ "clap", "eyre", "odyssey-node", - "odyssey-wallet", "odyssey-walltime", "reth-cli-util", "reth-node-builder", @@ -4609,21 +4608,38 @@ dependencies = [ "tracing", ] +[[package]] +name = "odyssey-relay" +version = "0.0.0" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-signer-local", + "clap", + "eyre", + "odyssey-node", + "odyssey-wallet", + "odyssey-walltime", + "reth-cli-util", + "reth-node-builder", + "reth-optimism-cli", + "reth-optimism-node", + "reth-provider", + "tracing", +] + [[package]] name = "odyssey-wallet" version = "0.0.0" dependencies = [ - "alloy-eips", "alloy-network", "alloy-primitives", + "alloy-provider", "alloy-rpc-types", "jsonrpsee", "metrics 0.23.0", "metrics-derive", "reth-optimism-rpc", - "reth-rpc-eth-api", - "reth-storage-api", - "revm-primitives", "serde", "serde_json", "thiserror 1.0.69", diff --git a/Cargo.toml b/Cargo.toml index bec8d4a..280c418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "bin/odyssey/", + "bin/relay/", "crates/node", "crates/e2e-tests", "crates/wallet", @@ -148,6 +149,7 @@ alloy-consensus = "0.6.4" alloy-eips = "0.6.4" alloy-network = "0.6.4" alloy-primitives = "0.8.11" +alloy-provider = "0.6.4" alloy-rpc-types = "0.6.4" alloy-signer-local = { version = "0.6.4", features = ["mnemonic"] } @@ -159,7 +161,6 @@ reth-chainspec = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211a reth-cli = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-cli-util = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-evm = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-node-api = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-node-builder = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-node-core = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac", features = [ @@ -185,7 +186,6 @@ reth-provider = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aa "optimism", ] } reth-revm = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } -reth-storage-api = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-tracing = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } reth-trie-db = { git = "https://github.com/paradigmxyz/reth.git", rev = "f211aac" } diff --git a/bin/odyssey/Cargo.toml b/bin/odyssey/Cargo.toml index c8fb97c..818fc70 100644 --- a/bin/odyssey/Cargo.toml +++ b/bin/odyssey/Cargo.toml @@ -16,7 +16,6 @@ alloy-signer-local.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true odyssey-node.workspace = true -odyssey-wallet.workspace = true odyssey-walltime.workspace = true eyre.workspace = true tracing.workspace = true diff --git a/bin/odyssey/src/main.rs b/bin/odyssey/src/main.rs index 6030d53..2fa5d78 100644 --- a/bin/odyssey/src/main.rs +++ b/bin/odyssey/src/main.rs @@ -23,19 +23,14 @@ //! - `min-debug-logs`: Disables all logs below `debug` level. //! - `min-trace-logs`: Disables all logs below `trace` level. -use alloy_network::EthereumWallet; -use alloy_primitives::Address; -use alloy_signer_local::PrivateKeySigner; use clap::Parser; -use eyre::Context; use odyssey_node::{chainspec::OdysseyChainSpecParser, node::OdysseyNode}; -use odyssey_wallet::{OdysseyWallet, OdysseyWalletApiServer}; use odyssey_walltime::{OdysseyWallTime, OdysseyWallTimeRpcApiServer}; use reth_node_builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}; use reth_optimism_cli::Cli; use reth_optimism_node::{args::RollupArgs, node::OpAddOns}; use reth_provider::{providers::BlockchainProvider2, CanonStateSubscriptions}; -use tracing::{info, warn}; +use tracing::info; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -56,36 +51,6 @@ fn main() { .with_components(OdysseyNode::components(&rollup_args)) .with_add_ons(OpAddOns::new(rollup_args.sequencer_http)) .extend_rpc_modules(move |ctx| { - // register odyssey wallet namespace - if let Ok(sk) = std::env::var("EXP1_SK") { - let signer: PrivateKeySigner = - sk.parse().wrap_err("Invalid EXP0001 secret key.")?; - let wallet = EthereumWallet::from(signer); - - let raw_delegations = std::env::var("EXP1_WHITELIST") - .wrap_err("No EXP0001 delegations specified")?; - let valid_delegations: Vec
= raw_delegations - .split(',') - .map(|addr| Address::parse_checksummed(addr, None)) - .collect::>() - .wrap_err("No valid EXP0001 delegations specified")?; - - ctx.modules.merge_configured( - OdysseyWallet::new( - ctx.provider().clone(), - wallet, - ctx.registry.eth_api().clone(), - ctx.config().chain.chain().id(), - valid_delegations, - ) - .into_rpc(), - )?; - - info!(target: "reth::cli", "EXP0001 wallet configured"); - } else { - warn!(target: "reth::cli", "EXP0001 wallet not configured"); - } - let walltime = OdysseyWallTime::spawn(ctx.provider().canonical_state_stream()); ctx.modules.merge_configured(walltime.into_rpc())?; info!(target: "reth::cli", "Walltime configured"); diff --git a/bin/relay/Cargo.toml b/bin/relay/Cargo.toml new file mode 100644 index 0000000..89dd57d --- /dev/null +++ b/bin/relay/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "odyssey-relay" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +description = "Odyssey Relay is an EIP-7702 native transaction batcher and sponsor." + +[lints] +workspace = true + +[dependencies] +alloy-signer-local.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +odyssey-node.workspace = true +odyssey-wallet.workspace = true +odyssey-walltime.workspace = true +eyre.workspace = true +tracing.workspace = true +reth-cli-util.workspace = true +reth-node-builder.workspace = true +reth-optimism-node.workspace = true +reth-optimism-cli.workspace = true +reth-provider.workspace = true +clap = { workspace = true, features = ["derive"] } + +[features] +default = ["jemalloc"] + +asm-keccak = ["reth-optimism-cli/asm-keccak"] + +jemalloc = ["reth-cli-util/jemalloc"] +jemalloc-prof = ["jemalloc", "reth-cli-util/jemalloc-prof"] + +min-error-logs = ["tracing/release_max_level_error"] +min-warn-logs = ["tracing/release_max_level_warn"] +min-info-logs = ["tracing/release_max_level_info"] +min-debug-logs = ["tracing/release_max_level_debug"] +min-trace-logs = ["tracing/release_max_level_trace"] + +[[bin]] +name = "odyssey" +path = "src/main.rs" diff --git a/bin/relay/src/main.rs b/bin/relay/src/main.rs new file mode 100644 index 0000000..5bfcb8e --- /dev/null +++ b/bin/relay/src/main.rs @@ -0,0 +1,37 @@ +//! # Odyssey Relay +//! +//! TBD + +use alloy_signer_local::PrivateKeySigner; +use eyre::Context; + +fn run() -> eyre::Result<()> { + let signer: PrivateKeySigner = std::env::var("RELAY_SK") + .wrap_err("Environment variable `RELAY_SK` must be set") + .and_then(|sk| sk.parse().wrap_err("Invalid signing key set in `RELAY_SK`"))?; + + Ok(()) +} + +struct Cli { + port: u16, + upstream: Vec, // todo make url + constraints: Constraints, +} + +struct Constraints { + max_gas: u64, +} + +#[doc(hidden)] +fn main() { + // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + if let Err(err) = run() { + eprint!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/crates/wallet/Cargo.toml b/crates/wallet/Cargo.toml index 221b51b..a039448 100644 --- a/crates/wallet/Cargo.toml +++ b/crates/wallet/Cargo.toml @@ -10,17 +10,13 @@ keywords.workspace = true categories.workspace = true [dependencies] -alloy-eips.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true +alloy-provider.workspace = true alloy-rpc-types.workspace = true -reth-storage-api.workspace = true -reth-rpc-eth-api.workspace = true reth-optimism-rpc.workspace = true -revm-primitives.workspace = true - jsonrpsee = { workspace = true, features = ["server", "macros"] } serde = { workspace = true, features = ["derive"] } thiserror.workspace = true diff --git a/crates/wallet/src/lib.rs b/crates/wallet/src/lib.rs index 67fa618..f1af2f0 100644 --- a/crates/wallet/src/lib.rs +++ b/crates/wallet/src/lib.rs @@ -19,10 +19,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use alloy_eips::BlockId; -use alloy_network::{ - eip2718::Encodable2718, Ethereum, EthereumWallet, NetworkWallet, TransactionBuilder, -}; +use alloy_network::{Ethereum, EthereumWallet, NetworkWallet, TransactionBuilder}; use alloy_primitives::{map::HashMap, Address, ChainId, TxHash, TxKind, U256, U64}; use alloy_rpc_types::TransactionRequest; use jsonrpsee::{ @@ -31,9 +28,7 @@ use jsonrpsee::{ }; use metrics::Counter; use metrics_derive::Metrics; -use reth_rpc_eth_api::helpers::{EthCall, EthTransactions, FullEthApi, LoadFee, LoadState}; -use reth_storage_api::{StateProvider, StateProviderFactory}; -use revm_primitives::Bytecode; + use serde::{Deserialize, Serialize}; use std::sync::Arc; use tracing::{trace, warn}; @@ -161,23 +156,21 @@ impl From for jsonrpsee::types::error::ErrorObject<'static> /// Implementation of the Odyssey `wallet_` namespace. #[derive(Debug)] -pub struct OdysseyWallet { - inner: Arc>, +pub struct OdysseyWallet { + inner: Arc>, } -impl OdysseyWallet { +impl OdysseyWallet { /// Create a new Odyssey wallet module. pub fn new( provider: Provider, wallet: EthereumWallet, - eth_api: Eth, chain_id: ChainId, valid_designations: Vec
, ) -> Self { let inner = OdysseyWalletInner { provider, wallet, - eth_api, chain_id, capabilities: WalletCapabilities(HashMap::from_iter([( U64::from(chain_id), @@ -195,10 +188,9 @@ impl OdysseyWallet { } #[async_trait] -impl OdysseyWalletApiServer for OdysseyWallet +impl OdysseyWalletApiServer for OdysseyWallet where - Provider: StateProviderFactory + Send + Sync + 'static, - Eth: FullEthApi + Send + Sync + 'static, + Provider: alloy_provider::Provider + 'static, { fn get_capabilities(&self) -> RpcResult { trace!(target: "rpc::wallet", "Serving wallet_getCapabilities"); @@ -219,24 +211,27 @@ where // if this is an eip-1559 tx, ensure that it is an account that delegates to a // whitelisted address (false, Some(TxKind::Call(addr))) => { - let state = self.inner.provider.latest().map_err(|_| { - self.inner.metrics.invalid_send_transaction_calls.increment(1); - OdysseyWalletError::InternalError - })?; - let delegated_address = state - .account_code(addr) - .ok() - .flatten() - .and_then(|code| match code.0 { - Bytecode::Eip7702(code) => Some(code.address()), - _ => None, - }) - .unwrap_or_default(); - - // not eip-7702 bytecode - if delegated_address == Address::ZERO { - self.inner.metrics.invalid_send_transaction_calls.increment(1); - return Err(OdysseyWalletError::IllegalDestination.into()); + let code = self + .inner + .provider + .get_code_at(addr) + .await + .map_err(|_| OdysseyWalletError::InternalError)?; + match code.as_ref() { + // A valid EIP-7702 delegation + [0xef, 0x01, 0x00, address @ ..] => { + let addr = Address::from_slice(address); + // the delegation was cleared + if addr.is_zero() { + self.inner.metrics.invalid_send_transaction_calls.increment(1); + return Err(OdysseyWalletError::IllegalDestination.into()); + } + } + // Not an EIP-7702 delegation, or an empty (cleared) delegation + _ => { + self.inner.metrics.invalid_send_transaction_calls.increment(1); + return Err(OdysseyWalletError::IllegalDestination.into()); + } } } // if it's an eip-7702 tx, let it through @@ -251,18 +246,6 @@ where // we acquire the permit here so that all following operations are performed exclusively let _permit = self.inner.permit.lock().await; - // set nonce - let next_nonce = LoadState::next_available_nonce( - &self.inner.eth_api, - NetworkWallet::::default_signer_address(&self.inner.wallet), - ) - .await - .map_err(|err| { - self.inner.metrics.invalid_send_transaction_calls.increment(1); - err.into() - })?; - request.nonce = Some(next_nonce); - // set chain id request.chain_id = Some(self.chain_id()); @@ -271,28 +254,27 @@ where // `tx.origin` request.from = Some(NetworkWallet::::default_signer_address(&self.inner.wallet)); let (estimate, base_fee) = tokio::join!( - EthCall::estimate_gas_at(&self.inner.eth_api, request.clone(), BlockId::latest(), None), - LoadFee::eip1559_fees(&self.inner.eth_api, None, None) + self.inner.provider.estimate_gas(&request), + self.inner.provider.estimate_eip1559_fees(None) ); let estimate = estimate.map_err(|err| { self.inner.metrics.invalid_send_transaction_calls.increment(1); - err.into() + // err.into() + OdysseyWalletError::InvalidTransactionRequest })?; - - if estimate >= U256::from(350_000) { + if estimate >= 350_000 { self.inner.metrics.invalid_send_transaction_calls.increment(1); - return Err(OdysseyWalletError::GasEstimateTooHigh { estimate: estimate.to() }.into()); + return Err(OdysseyWalletError::GasEstimateTooHigh { estimate }.into()); } - request.gas = Some(estimate.to()); + request.gas = Some(estimate); // set gas price - let (base_fee, _) = base_fee.map_err(|_| { + let fee_estimate = base_fee.map_err(|_| { self.inner.metrics.invalid_send_transaction_calls.increment(1); OdysseyWalletError::InvalidTransactionRequest })?; - let max_priority_fee_per_gas = 1_000_000_000; // 1 gwei - request.max_fee_per_gas = Some(base_fee.to::() + max_priority_fee_per_gas); - request.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + request.max_fee_per_gas = Some(fee_estimate.max_fee_per_gas); + request.max_priority_fee_per_gas = Some(fee_estimate.max_priority_fee_per_gas); request.gas_price = None; // build and sign @@ -310,22 +292,17 @@ where // all checks passed, increment the valid calls counter self.inner.metrics.valid_send_transaction_calls.increment(1); - // this uses the internal `OpEthApi` to either forward the tx to the sequencer, or add it to - // the txpool - // - // see: https://github.com/paradigmxyz/reth/blob/b67f004fbe8e1b7c05f84f314c4c9f2ed9be1891/crates/optimism/rpc/src/eth/transaction.rs#L35-L57 - EthTransactions::send_raw_transaction(&self.inner.eth_api, envelope.encoded_2718().into()) - .await - .inspect_err(|err| warn!(target: "rpc::wallet", ?err, "Error adding sequencer-sponsored tx to pool")) - .map_err(Into::into) + self.inner.provider + .send_tx_envelope(envelope) + .await.inspect_err(|err| warn!(target: "rpc::wallet", ?err, "Error adding sequencer-sponsored tx to pool")) + .map_err(|_| OdysseyWalletError::InvalidTransactionRequest.into()).map(|pending| *pending.tx_hash()) } } /// Implementation of the Odyssey `wallet_` namespace. #[derive(Debug)] -struct OdysseyWalletInner { +struct OdysseyWalletInner { provider: Provider, - eth_api: Eth, wallet: EthereumWallet, chain_id: ChainId, capabilities: WalletCapabilities,