Reorganize ledger-tool run sub-command (#31572)

* Rename ledger-tool run subcommand to program subcommand

* Reorganize ledger-tool program command to multiple subcommands
This commit is contained in:
Dmitri Makarov 2023-05-10 15:25:38 -04:00 committed by GitHub
parent 04425b81ce
commit fb79e2bbb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 122 additions and 97 deletions

View File

@ -1,6 +1,6 @@
#![allow(clippy::integer_arithmetic)] #![allow(clippy::integer_arithmetic)]
use { use {
crate::{args::*, bigtable::*, ledger_path::*, ledger_utils::*, output::*, run::*}, crate::{args::*, bigtable::*, ledger_path::*, ledger_utils::*, output::*, program::*},
chrono::{DateTime, Utc}, chrono::{DateTime, Utc},
clap::{ clap::{
crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App, crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App,
@ -106,7 +106,7 @@ mod bigtable;
mod ledger_path; mod ledger_path;
mod ledger_utils; mod ledger_utils;
mod output; mod output;
mod run; mod program;
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
enum LedgerOutputMethod { enum LedgerOutputMethod {
@ -1286,7 +1286,7 @@ fn main() {
.takes_value(true) .takes_value(true)
.possible_values(&["json", "json-compact"]) .possible_values(&["json", "json-compact"])
.help("Return information in specified output format, \ .help("Return information in specified output format, \
currently only available for bigtable and run subcommands"), currently only available for bigtable and program subcommands"),
) )
.arg( .arg(
Arg::with_name("verbose") Arg::with_name("verbose")
@ -2028,7 +2028,7 @@ fn main() {
If no file name is specified, it will print the metadata of all ledger files.") If no file name is specified, it will print the metadata of all ledger files.")
) )
) )
.run_subcommand() .program_subcommand()
.get_matches(); .get_matches();
info!("{} {}", crate_name!(), solana_version::version!()); info!("{} {}", crate_name!(), solana_version::version!());
@ -4058,8 +4058,8 @@ fn main() {
eprintln!("{err}"); eprintln!("{err}");
} }
} }
("run", Some(arg_matches)) => { ("program", Some(arg_matches)) => {
run(&ledger_path, arg_matches); program(&ledger_path, arg_matches);
} }
("", _) => { ("", _) => {
eprintln!("{}", matches.usage()); eprintln!("{}", matches.usage());

View File

@ -1,6 +1,6 @@
use { use {
crate::{args::*, ledger_utils::*}, crate::{args::*, ledger_utils::*},
clap::{value_t, App, Arg, ArgMatches, SubCommand}, clap::{value_t, App, AppSettings, Arg, ArgMatches, SubCommand},
log::*, log::*,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
serde_json::Result, serde_json::Result,
@ -131,18 +131,52 @@ fn load_blockstore(ledger_path: &Path, arg_matches: &ArgMatches<'_>) -> Arc<Bank
bank bank
} }
pub trait RunSubCommand { pub trait ProgramSubCommand {
fn run_subcommand(self) -> Self; fn program_subcommand(self) -> Self;
} }
impl RunSubCommand for App<'_, '_> { impl ProgramSubCommand for App<'_, '_> {
fn run_subcommand(self) -> Self { fn program_subcommand(self) -> Self {
let program_arg = Arg::with_name("PROGRAM")
.help(
"Program file to use. This is either an ELF shared-object file to be executed, \
or an assembly file to be assembled and executed.",
)
.required(true)
.index(1);
let max_genesis_arg = Arg::with_name("max_genesis_archive_unpacked_size")
.long("max-genesis-archive-unpacked-size")
.value_name("NUMBER")
.takes_value(true)
.default_value("10485760")
.help("maximum total uncompressed size of unpacked genesis archive");
self.subcommand( self.subcommand(
SubCommand::with_name("program")
.about("Run to test, debug, and analyze on-chain programs.")
.setting(AppSettings::InferSubcommands)
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("cfg")
.about("generates Control Flow Graph of the program.")
.arg(&max_genesis_arg)
.arg(&program_arg)
)
.subcommand(
SubCommand::with_name("disassemble")
.about("dumps disassembled code of the program.")
.arg(&max_genesis_arg)
.arg(&program_arg)
)
.subcommand(
SubCommand::with_name("run") SubCommand::with_name("run")
.about( .about(
r##"Run to test, debug, and analyze on-chain programs. "The command executes on-chain programs in a mocked environment.",
)
The tool executes on-chain programs in a mocked environment. .arg(
Arg::with_name("input")
.help(
r##"Input for the program to run on, where FILE is a name of a JSON file
with input data, or BYTES is the number of 0-valued bytes to allocate for program parameters"
The input data for a program execution have to be in JSON format The input data for a program execution have to be in JSON format
and the following fields are required and the following fields are required
@ -161,21 +195,6 @@ and the following fields are required
"instruction_data": [31, 32, 23, 24] "instruction_data": [31, 32, 23, 24]
} }
"##, "##,
)
.arg(
Arg::with_name("PROGRAM")
.help(
"Program file to use. This is either an ELF shared-object file to be executed, \
or an assembly file to be assembled and executed.",
)
.required(true)
.index(1)
)
.arg(
Arg::with_name("input")
.help(
"Input for the program to run on, where FILE is a name of a JSON file \
with input data, or BYTES is the number of 0-valued bytes to allocate for program parameters",
) )
.short("i") .short("i")
.long("input") .long("input")
@ -183,6 +202,7 @@ with input data, or BYTES is the number of 0-valued bytes to allocate for progra
.takes_value(true) .takes_value(true)
.default_value("0"), .default_value("0"),
) )
.arg(&max_genesis_arg)
.arg( .arg(
Arg::with_name("memory") Arg::with_name("memory")
.help("Heap memory for the program to run on") .help("Heap memory for the program to run on")
@ -193,19 +213,18 @@ with input data, or BYTES is the number of 0-valued bytes to allocate for progra
.default_value("0"), .default_value("0"),
) )
.arg( .arg(
Arg::with_name("use") Arg::with_name("mode")
.help( .help(
"Method of execution to use, where 'cfg' generates Control Flow Graph \ "Mode of execution, where 'interpreter' runs \
of the program, 'disassembler' dumps disassembled code of the program, 'interpreter' runs \ the program in the virtual machine's interpreter, 'debugger' is the same as 'interpreter' \
the program in the virtual machine's interpreter, 'debugger' is the same as 'interpreter' \ but hosts a GDB interface, and 'jit' precompiles the program to native machine code \
but hosts a GDB interface, and 'jit' precompiles the program to native machine code \ before execting it in the virtual machine.",
before execting it in the virtual machine.",
) )
.short("u") .short("e")
.long("use") .long("mode")
.takes_value(true) .takes_value(true)
.value_name("VALUE") .value_name("VALUE")
.possible_values(&["cfg", "disassembler", "interpreter", "debugger", "jit"]) .possible_values(&["interpreter", "debugger", "jit"])
.default_value("jit"), .default_value("jit"),
) )
.arg( .arg(
@ -216,14 +235,6 @@ before execting it in the virtual machine.",
.value_name("COUNT") .value_name("COUNT")
.default_value("9223372036854775807"), .default_value("9223372036854775807"),
) )
.arg(
Arg::with_name("max_genesis_archive_unpacked_size")
.long("max-genesis-archive-unpacked-size")
.value_name("NUMBER")
.takes_value(true)
.default_value("10485760")
.help("maximum total uncompressed size of unpacked genesis archive")
)
.arg( .arg(
Arg::with_name("port") Arg::with_name("port")
.help("Port to use for the connection with a remote debugger") .help("Port to use for the connection with a remote debugger")
@ -240,6 +251,8 @@ before execting it in the virtual machine.",
.takes_value(true) .takes_value(true)
.value_name("FILE"), .value_name("FILE"),
) )
.arg(&program_arg)
)
) )
} }
} }
@ -311,7 +324,18 @@ fn output_trace(
} }
} }
pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) { pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) {
enum Action {
Cfg,
Dis,
Run,
}
let (action, matches) = match matches.subcommand() {
("cfg", Some(arg_matches)) => (Action::Cfg, arg_matches),
("disassembler", Some(arg_matches)) => (Action::Dis, arg_matches),
("run", Some(arg_matches)) => (Action::Run, arg_matches),
_ => unreachable!(),
};
let bank = load_blockstore(ledger_path, matches); let bank = load_blockstore(ledger_path, matches);
let loader_id = bpf_loader_upgradeable::id(); let loader_id = bpf_loader_upgradeable::id();
let mut transaction_accounts = Vec::new(); let mut transaction_accounts = Vec::new();
@ -410,7 +434,7 @@ pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) {
program_id, // ID of the loaded program. It can modify accounts with the same owner key program_id, // ID of the loaded program. It can modify accounts with the same owner key
AccountSharedData::new(0, 0, &loader_id), AccountSharedData::new(0, 0, &loader_id),
)); ));
let interpreted = matches.value_of("use").unwrap() != "jit"; let interpreted = matches.value_of("mode").unwrap() != "jit";
with_mock_invoke_context!( with_mock_invoke_context!(
invoke_context, invoke_context,
transaction_context, transaction_context,
@ -516,8 +540,8 @@ pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) {
verified_executable.jit_compile().unwrap(); verified_executable.jit_compile().unwrap();
let mut analysis = LazyAnalysis::new(verified_executable.get_executable()); let mut analysis = LazyAnalysis::new(verified_executable.get_executable());
match matches.value_of("use") { match action {
Some("cfg") => { Action::Cfg => {
let mut file = File::create("cfg.dot").unwrap(); let mut file = File::create("cfg.dot").unwrap();
analysis analysis
.analyze() .analyze()
@ -525,13 +549,14 @@ pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) {
.unwrap(); .unwrap();
return; return;
} }
Some("disassembler") => { Action::Dis => {
let stdout = std::io::stdout(); let stdout = std::io::stdout();
analysis.analyze().disassemble(&mut stdout.lock()).unwrap(); analysis.analyze().disassemble(&mut stdout.lock()).unwrap();
return; return;
} }
_ => {} _ => {}
} };
create_vm!( create_vm!(
vm, vm,
&verified_executable, &verified_executable,
@ -541,7 +566,7 @@ pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) {
); );
let mut vm = vm.unwrap(); let mut vm = vm.unwrap();
let start_time = Instant::now(); let start_time = Instant::now();
if matches.value_of("use").unwrap() == "debugger" { if matches.value_of("mode").unwrap() == "debugger" {
vm.debug_port = Some(matches.value_of("port").unwrap().parse::<u16>().unwrap()); vm.debug_port = Some(matches.value_of("port").unwrap().parse::<u16>().unwrap());
} }
let (instruction_count, result) = vm.execute_program(interpreted); let (instruction_count, result) = vm.execute_program(interpreted);