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,