Skip to content

Commit

Permalink
Merge pull request #65 from ethereum-optimism/0xkitsune/refactor
Browse files Browse the repository at this point in the history
chore(opt8n): Refactor `opt8n` into `cmd` modules
  • Loading branch information
refcell authored Aug 21, 2024
2 parents afca8f9 + b3366b9 commit f6bbb01
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 242 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/opt8n/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ revm.workspace = true
# OP Types
op-test-vectors.workspace = true
op-alloy-rpc-types.workspace = true
hyper = "1.4.1"
3 changes: 3 additions & 0 deletions bin/opt8n/src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod repl;
pub mod script;
pub mod server;
111 changes: 111 additions & 0 deletions bin/opt8n/src/cmd/repl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use anvil::cmd::NodeArgs;
use clap::{CommandFactory, FromArgMatches, Parser};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncBufReadExt, BufReader};

use crate::opt8n::{Opt8n, Opt8nArgs};

#[derive(Parser, Clone, Debug)]
pub struct ReplArgs {
#[command(flatten)]
opt8n_args: Opt8nArgs,
#[command(flatten)]
pub node_args: NodeArgs,
}

impl ReplArgs {
pub async fn run(&self) -> color_eyre::Result<()> {
let mut opt8n = Opt8n::new(
Some(self.node_args.clone()),
self.opt8n_args.output.clone(),
self.opt8n_args.genesis.clone(),
)
.await?;

repl(&mut opt8n).await?;

Ok(())
}
}

#[derive(Parser, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[clap(rename_all = "snake_case", infer_subcommands = true, multicall = true)]
pub enum ReplCommand {
#[command(visible_alias = "a")]
Anvil {
#[arg(index = 1, allow_hyphen_values = true)]
args: Vec<String>,
},
#[command(visible_alias = "c")]
Cast {
#[arg(index = 1, allow_hyphen_values = true)]
args: Vec<String>,
},
Dump,
RpcEndpoint,
// TODO: implement clear
// TODO: implement reset
#[command(visible_alias = "e")]
Exit,
}

/// Listens for commands, and new blocks from the block stream.
pub async fn repl(opt8n: &mut Opt8n) -> color_eyre::Result<()> {
let mut new_blocks = opt8n.eth_api.backend.new_block_notifications();

loop {
tokio::select! {
command = receive_command() => {
match command {
Ok(ReplCommand::Exit) => break,
Ok(command) => execute(opt8n, command).await?,
Err(e) => eprintln!("Error: {:?}", e),
}
}

new_block = new_blocks.next() => {
if let Some(new_block) = new_block {
if let Some(block) = opt8n.eth_api.backend.get_block_by_hash(new_block.hash) {
opt8n.generate_execution_fixture(block).await?;
}
}
}
}
}

Ok(())
}

async fn receive_command() -> color_eyre::Result<ReplCommand> {
let line = BufReader::new(tokio::io::stdin())
.lines()
.next_line()
.await?
.unwrap();
let words = shellwords::split(&line)?;

let matches = ReplCommand::command().try_get_matches_from(words)?;
Ok(ReplCommand::from_arg_matches(&matches)?)
}

async fn execute(opt8n: &mut Opt8n, command: ReplCommand) -> color_eyre::Result<()> {
match command {
ReplCommand::Dump => {
opt8n.mine_block().await;
}
ReplCommand::Anvil { mut args } => {
args.insert(0, "anvil".to_string());
let command = NodeArgs::command_for_update();
let matches = command.try_get_matches_from(args)?;
let node_args = NodeArgs::from_arg_matches(&matches)?;
node_args.run().await?;
}
ReplCommand::Cast { .. } => {}
ReplCommand::RpcEndpoint => {
println!("{}", opt8n.node_handle.http_endpoint());
}
ReplCommand::Exit => unreachable!(),
}
Ok(())
}
126 changes: 126 additions & 0 deletions bin/opt8n/src/cmd/script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use anvil::cmd::NodeArgs;
use clap::Parser;
use color_eyre::eyre::eyre;
use futures::StreamExt;

use crate::opt8n::{Opt8n, Opt8nArgs};

#[derive(Parser, Clone, Debug)]
pub struct ScriptArgs {
#[command(flatten)]
opt8n_args: Opt8nArgs,
#[command(flatten)]
inner: forge_script::ScriptArgs,
#[command(flatten)]
pub node_args: NodeArgs,
}

impl ScriptArgs {
pub async fn run(mut self) -> color_eyre::Result<()> {
let opt8n = Opt8n::new(
Some(self.node_args.clone()),
self.opt8n_args.output.clone(),
self.opt8n_args.genesis.clone(),
)
.await?;

foundry_common::shell::set_shell(foundry_common::shell::Shell::from_args(
self.inner.opts.silent,
self.inner.json,
))?;

self.inner.broadcast = true;
self.inner.evm_opts.sender = Some(
opt8n
.node_handle
.genesis_accounts()
.last()
.expect("Could not get genesis account"),
);
self.inner.unlocked = true;
self.inner.evm_opts.fork_url = Some(opt8n.node_handle.http_endpoint());

run_script(opt8n, Box::new(self.inner)).await?;

Ok(())
}
}

/// Run a Forge script with the given arguments, and generate an execution fixture
/// from the broadcasted transactions.
pub async fn run_script(
opt8n: Opt8n,
script_args: Box<forge_script::ScriptArgs>,
) -> color_eyre::Result<()> {
let mut new_blocks = opt8n.eth_api.backend.new_block_notifications();

// Run the forge script and broadcast the transactions to the anvil node
let mut opt8n = broadcast_transactions(opt8n, script_args).await?;

// Mine the block and generate the execution fixture
opt8n.mine_block().await;

let block = new_blocks.next().await.ok_or(eyre!("No new block"))?;
if let Some(block) = opt8n.eth_api.backend.get_block_by_hash(block.hash) {
opt8n.generate_execution_fixture(block).await?;
}

Ok(())
}

async fn broadcast_transactions(
opt8n: Opt8n,
script_args: Box<forge_script::ScriptArgs>,
) -> color_eyre::Result<Opt8n> {
// Run the script, compile the transactions and broadcast to the anvil instance
let compiled = script_args.preprocess().await?.compile()?;

let pre_simulation = compiled
.link()
.await?
.prepare_execution()
.await?
.execute()
.await?
.prepare_simulation()
.await?;

let bundled = pre_simulation.fill_metadata().await?.bundle().await?;

let tx_count = bundled
.sequence
.sequences()
.iter()
.fold(0, |sum, sequence| sum + sequence.transactions.len());

// TODO: break into function
let broadcast = bundled.broadcast();

let pending_transactions = tokio::task::spawn(async move {
loop {
let pending_tx_count = opt8n
.eth_api
.txpool_content()
.await
.expect("Failed to get txpool content")
.pending
.len();

if pending_tx_count == tx_count {
return opt8n;
}
}
});

let opt8n = tokio::select! {
_ = broadcast => {
// TODO: Gracefully handle this error
return Err(eyre!("Script failed early"));
},
opt8n = pending_transactions => {
opt8n?
}
};

Ok(opt8n)
}
18 changes: 18 additions & 0 deletions bin/opt8n/src/cmd/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use anvil::cmd::NodeArgs;
use clap::Parser;

use crate::opt8n::Opt8nArgs;

#[derive(Parser, Clone, Debug)]
pub struct ServerArgs {
#[command(flatten)]
pub opt8n_args: Opt8nArgs,
#[command(flatten)]
pub node_args: NodeArgs,
}

impl ServerArgs {
pub async fn run(&self) -> color_eyre::Result<()> {
unimplemented!()
}
}
91 changes: 12 additions & 79 deletions bin/opt8n/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,102 +1,35 @@
pub mod cmd;
pub mod opt8n;
use std::path::PathBuf;

use anvil::cmd::NodeArgs;
use crate::cmd::script::ScriptArgs;
use clap::Parser;
use cmd::repl::ReplArgs;
use cmd::server::ServerArgs;
use color_eyre::eyre;
use forge_script::ScriptArgs;
use opt8n::Opt8n;

#[derive(Parser, Clone, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[command(subcommand)]
pub command: Commands,
#[command(flatten)]
pub node_args: NodeArgs,
}

#[derive(Parser, Clone, Debug)]
pub enum Commands {
/// Starts a REPL for running forge, anvil, and cast commands
#[command(visible_alias = "r")]
Repl {
#[command(flatten)]
opt8n_args: Opt8nArgs,
},
/// Uses a forge script to generate a test vector
#[command(visible_alias = "s")]
Script {
#[command(flatten)]
opt8n_args: Opt8nArgs,
#[command(flatten)]
script_args: Box<ScriptArgs>,
},
}

impl Commands {
fn get_opt8n_args(&self) -> &Opt8nArgs {
match self {
Commands::Repl { opt8n_args } => opt8n_args,
Commands::Script { opt8n_args, .. } => opt8n_args,
}
}
}

#[derive(Parser, Clone, Debug)]
pub struct Opt8nArgs {
#[clap(long, help = "Output file for the execution test fixture")]
pub output: PathBuf,
#[clap(long, help = "Path to genesis state")]
pub genesis: Option<PathBuf>,
Repl(ReplArgs),
Script(ScriptArgs),
Server(ServerArgs),
}

#[tokio::main]
async fn main() -> eyre::Result<()> {
color_eyre::install()?;
let args = Args::parse();
let node_args = args.node_args.clone();
let opt8n_args = args.command.get_opt8n_args();

if node_args.evm_opts.fork_url.is_some() || node_args.evm_opts.fork_block_number.is_some() {
return Err(eyre::eyre!(
"Forking is not supported in opt8n, please specify prestate with a genesis file"
));
}

let node_config = node_args.clone().into_node_config();
let mut opt8n = Opt8n::new(
Some(node_config),
opt8n_args.output.clone(),
opt8n_args.genesis.clone(),
)
.await?;

match args.command {
Commands::Repl { .. } => {
opt8n.repl().await?;
}
Commands::Script {
mut script_args, ..
} => {
foundry_common::shell::set_shell(foundry_common::shell::Shell::from_args(
script_args.opts.silent,
script_args.json,
))?;

script_args.broadcast = true;
script_args.evm_opts.sender = Some(
opt8n
.node_handle
.genesis_accounts()
.last()
.expect("Could not get genesis account"),
);
script_args.unlocked = true;
script_args.evm_opts.fork_url = Some(opt8n.node_handle.http_endpoint());
let command = Args::parse().command;

opt8n.run_script(script_args).await?;
}
match command {
Commands::Repl(cmd) => cmd.run().await?,
Commands::Script(cmd) => cmd.run().await?,
Commands::Server(cmd) => cmd.run().await?,
}

Ok(())
Expand Down
Loading

0 comments on commit f6bbb01

Please sign in to comment.