mod data;
mod native;
mod runner;
#[cfg(feature = "wasm")]
mod wasm;
use self::runner::AirRunner;
use avm_interface::CallResults;
use anyhow::Context as _;
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
#[derive(Parser, Debug)]
#[clap(about = "Run AIR script with AquaVM")]
pub(crate) struct Args {
#[clap(long = "call-results")]
call_results_path: Option<PathBuf>,
#[clap(long)]
max_heap_size: Option<u64>,
#[clap(long, default_value = "info")]
tracing_params: String,
#[clap(long, default_value = "warn")]
runner_tracing_params: String,
#[clap(long)]
native: bool,
#[clap(
long = "interpreter",
env = "AIR_INTERPRETER_WASM_PATH",
default_value = "target/wasm32-wasi/release/air_interpreter_server.wasm"
)]
air_interpreter_path: PathBuf,
#[clap(long, help = "Execute several times; great for native profiling")]
repeat: Option<u32>,
#[clap(long, help = "Output JSON tracing info")]
json: bool,
#[clap(subcommand)]
source: Source,
}
#[derive(Subcommand, Debug)]
#[allow(clippy::large_enum_variant)]
enum Source {
#[clap(name = "--anomaly")]
Anomaly(self::data::anomaly::AnomalyDataArgs),
#[clap(name = "--plain")]
PlainData(self::data::plain::PlainDataArgs),
}
pub(crate) fn run(args: Args) -> anyhow::Result<()> {
let tracing_json = (!args.json) as u8;
#[cfg(feature = "wasm")]
let global_tracing_params = if args.native {
args.tracing_params.clone()
} else {
args.runner_tracing_params
};
#[cfg(not(feature = "wasm"))]
let global_tracing_params = args.tracing_params.clone();
init_tracing(global_tracing_params, tracing_json);
let mut runner = get_runner(args.native, &args.air_interpreter_path, args.max_heap_size)?;
let execution_data = match &args.source {
Source::Anomaly(anomaly) => data::anomaly::load(anomaly)?,
Source::PlainData(raw) => data::plain::load(raw)?,
};
let particle = execution_data.particle;
let call_results = read_call_results(args.call_results_path.as_deref())?;
let repeat = args.repeat.unwrap_or(1);
for _ in 0..repeat {
let result = runner
.call_tracing(
execution_data.air_script.clone(),
execution_data.prev_data.clone().into(),
execution_data.current_data.clone().into(),
particle.init_peer_id.clone().into_owned(),
particle.timestamp,
particle.ttl,
particle.current_peer_id.clone().into(),
call_results.clone(),
args.tracing_params.clone(),
tracing_json,
)
.context("Failed to execute the script")?;
if args.repeat.is_none() {
println!("{result:?}");
}
}
Ok(())
}
#[cfg(feature = "wasm")]
fn get_runner(
native: bool,
air_interpreter_wasm_path: &Path,
max_heap_size: Option<u64>,
) -> anyhow::Result<Box<dyn AirRunner>> {
if native {
self::native::create_native_avm_runner().context("Failed to instantiate a native AVM")
} else {
self::wasm::create_wasm_avm_runner(air_interpreter_wasm_path, max_heap_size)
.context("Failed to instantiate WASM AVM")
}
}
#[cfg(not(feature = "wasm"))]
fn get_runner(
_native: bool,
_air_interpreter_wasm_path: &Path,
_max_heap_size: Option<u64>,
) -> anyhow::Result<Box<dyn AirRunner>> {
self::native::create_native_avm_runner().context("Failed to instantiate a native AVM")
}
pub fn init_tracing(tracing_params: String, trace_mode: u8) {
use tracing_subscriber::fmt::format::FmtSpan;
let builder = tracing_subscriber::fmt()
.with_env_filter(tracing_params)
.with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE)
.with_writer(std::io::stderr);
if trace_mode == 0 {
builder.json().init();
} else {
builder.init();
}
}
fn read_call_results(call_results_path: Option<&Path>) -> anyhow::Result<CallResults> {
match call_results_path {
None => Ok(CallResults::default()),
Some(call_results_path) => {
let call_results_json =
load_data(call_results_path).context("failed to read call_results")?;
Ok(serde_json::from_str(&call_results_json)
.context("failed to parse call_results data")?)
}
}
}
fn load_data(data_path: &Path) -> anyhow::Result<String> {
Ok(std::fs::read_to_string(data_path)?)
}