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

chore(opt8n): Refactor opt8n into cmd modules #65

Merged
merged 11 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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.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)
}
25 changes: 25 additions & 0 deletions bin/opt8n/src/cmd/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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<()> {
// let opt8n = Opt8n::new(
// Some(self.node_args.clone()),
// self.opt8n_args.output.clone(),
// self.opt8n_args.genesis.clone(),
// )
// .await?;

Ok(())
0xKitsune marked this conversation as resolved.
Show resolved Hide resolved
}
}
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