use anyhow::anyhow;
use clap::{Parser, ValueEnum};
use codec::{Compact, Decode, Encode};
use corevm_host::{fs as corevm_fs, AudioMode, CoreVmInstruction, VideoMode};
use corevm_tooling::{parsing::CoreVmFsNode, ArcNodeExt, CoreVmCodeInfo};
use futures::StreamExt;
use jam_bootstrap_service_common::Instruction;
use jam_program_blob_common::ConventionalMetadata as Metadata;
use jam_std_common::{hash_raw, BlockDesc, ChainSubUpdate, Node, VersionedParameters};
use jam_tooling::{
format,
parsing::{self, Blob, DataId, DataIdLen},
CodeInfo, CommonArgs, NodeExt,
};
use jam_types::{
core_count, min_turnaround_period, slot_period_sec, AnyBytes, AnyHash, Balance, CoreIndex,
Hash, HeaderHash, Memo, ServiceId, SignedGas, Slot, ToAny, UnsignedGas, WorkPackageHash,
};
use jsonrpsee::ws_client::WsClient;
use std::{
collections::btree_map::BTreeMap,
ffi::CString,
path::{PathBuf, MAIN_SEPARATOR},
sync::Arc,
time::Duration,
};
use tokio::task::spawn_blocking;
mod formatting;
mod cached_desc;
use self::cached_desc::*;
mod bootstrap;
use self::{bootstrap::*, package::PackageBuilder};
mod package;
mod queue;
mod service;
mod vm;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
pub enum BlockId {
Best,
Final,
}
#[derive(clap::Parser, Debug, Clone)]
pub enum Inspections {
Slot,
Service {
#[arg(value_parser = parsing::service_id)]
id: ServiceId,
},
Storage {
#[arg(value_parser = parsing::service_id)]
id: ServiceId,
#[arg(value_parser = parsing::blob)]
key: Blob,
#[arg(long, default_value_t = false)]
raw: bool,
},
Preimage {
#[arg(value_parser = parsing::service_id)]
id: ServiceId,
#[arg(value_parser = parsing::data_id)]
key: DataId,
},
Request {
#[arg(value_parser = parsing::service_id)]
id: ServiceId,
#[arg(value_parser = parsing::data_id_len)]
key: DataIdLen,
},
}
#[derive(clap::Args, Clone, Debug)]
pub struct VmArgs {
#[arg(
short = 'r',
long = "root",
value_name = "SERVICE_ID:HASH|DIR",
value_parser = corevm_tooling::parsing::corevm_fs_dir_or_local_dir
)]
root_dir: Option<CoreVmFsNode>,
#[arg(short = 'e', long = "env", value_name = "KEY=VALUE")]
env: Vec<CString>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true, value_name = "ARGS...")]
args: Vec<CString>,
#[clap(
long = "gas",
value_name = "GAS",
default_value_t = SignedGas::MAX as UnsignedGas
)]
gas: UnsignedGas,
#[arg(
long = "video-input",
value_name = "WIDTH x HEIGHT @ REFRESH_RATE, param1=value1, ...",
value_parser = corevm_tooling::parsing::corevm_video_mode,
verbatim_doc_comment
)]
video_input: Option<VideoMode>,
#[arg(
long = "audio-input",
value_name = "SAMPLE_RATE:CHANNELS:SAMPLE_FORMAT",
value_parser = corevm_tooling::parsing::corevm_audio_mode,
verbatim_doc_comment
)]
audio_input: Option<AudioMode>,
#[arg(long = "input-key", value_name = "HEX", value_parser = parsing::ed25519_public_key)]
input_key: Option<[u8; 32]>,
}
#[derive(clap::Subcommand, Debug, Clone)]
pub enum VmCommands {
New {
#[arg(value_parser = parsing::blob)]
code: Blob,
#[arg(default_value_t = 0)]
amount: Balance,
#[arg(short = 'G', long, default_value_t = 1_000_000)]
min_item_gas: UnsignedGas,
#[arg(short = 'g', long, default_value_t = 1_000_000)]
min_memo_gas: UnsignedGas,
#[command(flatten)]
args: VmArgs,
},
Upgrade {
#[arg(value_parser = parsing::service_id)]
service_id: ServiceId,
#[arg(value_parser = parsing::blob)]
code: Blob,
#[command(flatten)]
args: VmArgs,
},
Destroy {
#[arg(value_parser = parsing::service_id)]
target: ServiceId,
},
Preimages {
#[arg(value_parser = parsing::service_id)]
target: ServiceId,
},
}
#[derive(clap::Subcommand, Debug, Clone)]
pub enum FsCommands {
Upload {
#[arg(
long = "block-size",
value_name = "BYTES",
value_parser = parsing::exact_bytes,
default_value = format::exact_bytes(corevm_fs::MAX_BLOCK_SIZE as u64),
)]
block_size: u64,
#[arg(value_name = "PATH")]
paths: Vec<PathBuf>,
},
Download {
#[arg(
value_name = "SERVICE_ID:HASH",
value_parser = corevm_tooling::parsing::corevm_fs_block_ref
)]
block_ref: corevm_fs::BlockRef,
#[arg(value_name = "PATH")]
output_path: PathBuf,
},
}
#[derive(clap::Subcommand, Debug, Clone)]
pub enum Commands {
CreateService {
#[arg(value_parser = parsing::data_id_len)]
code: DataIdLen,
#[arg(default_value_t = 0)]
amount: Balance,
#[arg(value_parser = parsing::memo, default_value = "")]
memo: Memo,
#[arg(short = 'G', long, default_value_t = 1_000_000)]
min_item_gas: UnsignedGas,
#[arg(short = 'g', long, default_value_t = 1_000_000)]
min_memo_gas: UnsignedGas,
#[arg(short = 'r', long)]
register: Option<String>,
#[arg(long, default_value_t = false)]
raw: bool,
},
Item {
#[arg(value_parser = parsing::service_id)]
service_id: ServiceId,
#[arg(value_parser = parsing::blob, default_value = "")]
payload: Blob,
#[arg(short = 'G', long)]
refine_gas: Option<UnsignedGas>,
#[arg(short = 'g', long)]
accumulate_gas: Option<UnsignedGas>,
#[arg(short = 'e', long, default_value_t = 0)]
exports: u16,
#[arg(short = 'i', long, value_parser = parsing::import_spec)]
import: Vec<jam_types::ImportSpec>,
},
Transfer {
#[arg(value_parser = parsing::service_id)]
service_id: ServiceId,
#[arg(default_value_t = 0)]
amount: Balance,
#[arg(value_parser = parsing::memo, default_value = "")]
memo: Memo,
#[arg(short = 'g', long, value_parser = parsing::gas)]
gas: Option<UnsignedGas>,
},
Inspect {
#[arg(default_value = "best")]
id: BlockId,
#[command(subcommand)]
field: Inspections,
},
List {
#[arg(default_value = "best")]
id: BlockId,
},
Provide {
#[arg(value_parser = parsing::service_id)]
requester: ServiceId,
#[arg(value_parser = parsing::blob)]
preimage: Blob,
},
Queue,
Pack,
PackList {
#[arg(value_parser = parsing::service_id)]
service_id: ServiceId,
#[arg(value_parser = parsing::blob)]
payloads: Vec<Blob>,
#[arg(short = 'G', long)]
refine_gas: Option<UnsignedGas>,
#[arg(short = 'g', long)]
accumulate_gas: Option<UnsignedGas>,
},
Vm {
#[command(subcommand)]
command: VmCommands,
},
Fs {
#[command(subcommand)]
command: FsCommands,
},
}
#[derive(ValueEnum, Debug, Default, Clone, Copy, Eq, PartialEq)]
pub enum Provision {
None,
Direct,
#[default]
Bootstrap,
}
#[derive(Parser, Debug, Clone)]
pub struct PackageOptions {
#[arg(short = 'p', long, value_enum, default_value_t = Provision::Bootstrap)]
pub provision: Provision,
#[arg(short = 'c', long)]
pub force_core: Option<CoreIndex>,
#[arg(short = 'q', long, default_value_t = false)]
pub queue: bool,
}
impl PackageOptions {
pub fn core(&self) -> CoreIndex {
self.force_core.unwrap_or(0)
}
}
#[derive(Parser, Debug)]
#[command(name = "jamt", version, long_version = format!("{}\nGP {}", env!("CARGO_PKG_VERSION"), jam_types::GP_VERSION))]
pub struct BootArgs {
#[command(flatten)]
common: CommonArgs,
#[arg(short = 'b', long, value_parser = parsing::service_id, default_value_t = 0)]
bootstrap_service_id: ServiceId,
#[command(flatten)]
package: PackageOptions,
#[command(subcommand)]
command: Commands,
}
pub struct BootstrapProvision {
node: Arc<WsClient>,
preimages: Vec<(Hash, Vec<u8>)>,
service_id: ServiceId,
}
impl BootstrapProvision {
pub async fn ensure(
&mut self,
header_hash: HeaderHash,
data: Vec<u8>,
) -> anyhow::Result<(bool, Hash)> {
let hash = hash_raw(&data);
Ok((
match self
.node
.service_request(header_hash, self.service_id, hash, data.len() as u32)
.await?
.map(|v| v.len())
{
Some(1) => false,
_ => {
self.preimages.push((hash, data));
true
},
},
hash,
))
}
async fn provide(self, context: &mut Context) -> anyhow::Result<BTreeMap<Hash, Slot>> {
context.boot_solicit(self.preimages.iter().map(|(h, d)| (*h, d.len()))).await?;
let mut items = Vec::new();
for (hash, data) in self.preimages.into_iter() {
let len = data.len() as u32;
let mut sub =
self.node.subscribe_service_request(self.service_id, hash, len, false).await?;
loop {
let ChainSubUpdate { value, .. } = sub
.next()
.await
.ok_or(anyhow!("Subscription to monitor creation terminated unexpectedly"))??;
match value.map(|v| v.len()) {
Some(0) => {
items.push((hash, data.len() as u32));
self.node.submit_preimage(self.service_id, data.into()).await?;
break
},
Some(1) | Some(3) => {
break
},
_ => {
},
}
}
}
let mut map = BTreeMap::new();
for (hash, len) in items.into_iter() {
let mut sub =
self.node.subscribe_service_request(self.service_id, hash, len, false).await?;
let slot = loop {
let ChainSubUpdate { value, .. } = sub
.next()
.await
.ok_or(anyhow!("Subscription to monitor creation terminated unexpectedly"))??;
match value.map(|v| (v.len(), v)) {
Some((1, v)) | Some((3, v)) => {
break *v.last().expect("Already matched length 1; qed")
},
_ => {
},
}
};
map.insert(hash, slot);
}
eprintln!("Provided {} item(s)", map.len());
Ok(map)
}
}
struct Context {
args: BootArgs,
node: Arc<WsClient>,
bootstrap_service: Option<Arc<BootstrapService>>,
}
impl Context {
async fn new(args: BootArgs) -> anyhow::Result<Self> {
let node = Arc::new(args.common.connect_rpc(0).await?);
let VersionedParameters::V1(params) = node.parameters().await?;
params.apply().map_err(|s| anyhow!("{}", s))?;
if let Some(core) = args.package.force_core {
if core >= core_count() {
return Err(anyhow!(
"Invalid core ({core}); the number of cores is {}",
core_count()
));
}
}
let s = Self { args, node, bootstrap_service: None };
Ok(s)
}
fn bootstrap_provider(&self) -> BootstrapProvision {
BootstrapProvision {
node: self.node.clone(),
service_id: self.args.bootstrap_service_id,
preimages: vec![],
}
}
fn package_builder(&self) -> PackageBuilder<WsClient> {
PackageBuilder::new(
self.node.clone(),
self.args.bootstrap_service_id,
self.args.package.clone(),
)
}
async fn ensure_boot(&mut self) -> anyhow::Result<Arc<BootstrapService>> {
match self.bootstrap_service {
Some(ref service) => Ok(service.clone()),
None => {
let BlockDesc { header_hash: head, .. } = self.node.best_block().await?;
let id = self.args.bootstrap_service_id;
let service = BootstrapService::query(self.node.clone(), head, id).await?;
Ok(self.bootstrap_service.insert(Arc::new(service)).clone())
},
}
}
async fn execute(mut self) -> anyhow::Result<()> {
match self.args.command.clone() {
Commands::CreateService {
code,
amount,
memo,
min_item_gas,
min_memo_gas,
register,
raw,
} => {
let boot = self.ensure_boot().await?;
boot.create_service(
code,
amount,
memo,
min_item_gas,
min_memo_gas,
register,
raw,
&self.args.package,
)
.await?;
},
Commands::Transfer { service_id, amount, memo, gas } => {
let boot = self.ensure_boot().await?;
self.package_builder()
.transfer(boot.as_ref(), service_id, amount, memo, gas)
.await?;
},
Commands::Item { service_id, payload, refine_gas, accumulate_gas, exports, import } => {
self.package_builder()
.item(service_id, payload, refine_gas, accumulate_gas, exports, import)
.await?;
},
Commands::PackList { service_id, payloads, refine_gas, accumulate_gas } => {
self.package_builder()
.pack_list(service_id, payloads, refine_gas, accumulate_gas)
.await?;
},
Commands::Pack => {
let boot = self.ensure_boot().await?;
self.package_builder().pack(boot.as_ref()).await?;
},
Commands::Queue => queue::queue_status()?,
Commands::Inspect { id, field } => {
let desc = match id {
BlockId::Best => self.node.best_block().await?,
BlockId::Final => self.node.finalized_block().await?,
};
match field {
Inspections::Slot => println!("{}", desc.slot),
Inspections::Service { id } => {
let (_service, maybe_meta, maybe_inner_meta) =
self.node.query_service_and_corevm_guest(desc.header_hash, id).await?;
if let CodeInfo::Known(meta) = maybe_meta {
println!("Service {meta}");
} else {
eprintln!("Service code not found");
}
match maybe_inner_meta {
None => {},
Some(CoreVmCodeInfo::Undefined(block_ref)) => {
eprintln!(
"CoreVM guest code {block_ref} is defined but unreadable"
);
},
Some(CoreVmCodeInfo::NotProvided(block_ref)) => {
eprintln!("File {block_ref} is not provided");
},
Some(CoreVmCodeInfo::Known(meta)) => {
println!("Guest {meta}");
},
}
},
Inspections::Storage { id, key, raw } => {
let value = self.node.service_value(desc.header_hash, id, &key).await?;
if let Some(value) = value {
if raw {
println!("0x{}", jam_types::hex::hex(&value));
} else {
println!("{}", AnyBytes::from(value));
}
} else {
return Err(anyhow!("No value found for key {:?}", key))
}
},
Inspections::Request { id, key } => {
let hash = key.hash();
let len = key.len() as u32;
let maybe_request =
self.node.service_request(desc.header_hash, id, hash, len).await?;
let Some(request) = maybe_request else {
return Err(anyhow!(
"No preimage request found for hash {:?}",
hash.any()
));
};
match request.len() {
0 => {
println!("Unprovided request found");
},
1 => {
println!("Request active, provided at slot {}", request[0]);
},
2 => {
println!(
"Request inactive since {}, provided at slot {}",
request[1], request[0]
);
},
3 => {
println!(
"Request active since {}, unrequested at slot {}, provided at slot {}",
request[2], request[1], request[0]
);
},
_ => unreachable!(),
}
},
Inspections::Preimage { id, key } => {
let hash = key.hash();
let maybe_data =
self.node.service_preimage(desc.header_hash, id, hash).await?;
let Some(data) = maybe_data else {
return Err(anyhow!("No preimage provided for hash {:?}", hash.any()));
};
println!("Preimage found of length {}", data.len());
},
}
},
Commands::List { id } => {
let desc = match id {
BlockId::Best => self.node.best_block().await?,
BlockId::Final => self.node.finalized_block().await?,
};
let services = self.node.list_services(desc.header_hash).await?;
let infos = futures::future::try_join_all(services.iter().map(|&id| {
let node = self.node.clone();
async move {
let (_, meta, inner) =
node.query_service_and_corevm_guest(desc.header_hash, id).await?;
anyhow::Ok((id, meta, inner))
}
}))
.await?;
for (id, meta, inner) in infos {
let name = match (&meta, &inner) {
(CodeInfo::Known(m), Some(CoreVmCodeInfo::Known(im))) =>
if m.name == "corevm" {
format!("vm: {}", im.name)
} else {
format!("{}: {}", m.name, im.name)
},
(CodeInfo::Known(m), _) => m.name.clone(),
(CodeInfo::Undefined(h), _) | (CodeInfo::CodeNotProvided(h), _) =>
format!("{}", AnyHash(*h)),
};
println!("{id:08x}\t{name}");
}
},
Commands::Provide { requester: service_id, preimage: pre_image } => {
let boot = self.ensure_boot().await?;
let at = boot.provide(service_id, pre_image).await?;
eprintln!("Preimage provided at slot {at}");
},
Commands::Vm { command } => match command {
VmCommands::New {
code,
amount,
min_item_gas,
min_memo_gas,
args: VmArgs { gas, root_dir, env, args, video_input, audio_input, input_key },
} => {
let gas: SignedGas =
gas.try_into().map_err(|_| anyhow::anyhow!("Too much gas"))?;
if let Ok((_, Metadata::Info(i))) =
<(Compact<u32>, Metadata)>::decode(&mut &corevm_bin::BLOB[..])
{
eprintln!("Using CoreVM module {i}");
}
if let Ok((_, Metadata::Info(i))) =
<(Compact<u32>, Metadata)>::decode(&mut &code[..])
{
eprintln!("Guest identifies as {i}");
} else {
eprintln!("Guest code metadata not found");
}
if (video_input.is_some() || audio_input.is_some()) && input_key.is_none() {
return Err(anyhow!(
"You should specify input key when audio/video input is enabled"
));
}
assert_eq!(
self.args.package.provision,
Provision::Bootstrap,
"Only provision via bootstrap service is supported"
);
self.node.wait_for_sync().await?;
let (memo, code_ref, root_dir_ref) = self
.reset_vm_instruction(
gas,
code,
root_dir,
args,
env,
video_input,
audio_input,
input_key,
true,
)
.await?;
let inst = Instruction::CreateService {
code_hash: corevm_bin::HASH.into(),
code_len: corevm_bin::BLOB.len() as u64,
min_item_gas,
min_memo_gas,
endowment: amount,
memo,
registration: None,
};
let boot = self.ensure_boot().await?;
let core = self.args.package.force_core.unwrap_or(0);
boot.submit_instructions(vec![inst], core).await?;
let id = boot.wait_for_the_service_to_be_created().await?;
println!("Service id: {id:x}");
println!("Code file: {code_ref}");
if !root_dir_ref.hash.is_zero() {
println!("Root directory: {root_dir_ref}");
}
},
VmCommands::Upgrade {
service_id,
code,
args: VmArgs { gas, root_dir, env, args, video_input, audio_input, input_key },
} => {
let gas: SignedGas =
gas.try_into().map_err(|_| anyhow::anyhow!("Too much gas"))?;
if (video_input.is_some() || audio_input.is_some()) && input_key.is_none() {
return Err(anyhow!(
"You should specify input key when audio/video input is enabled"
));
}
let best = self.node.best_block().await?;
let (service, _corevm_code) =
service::query(self.node.as_ref(), best.header_hash, service_id, "corevm")
.await?;
let (memo, code_ref, root_dir_ref) = self
.reset_vm_instruction(
gas,
code,
root_dir,
args,
env,
video_input,
audio_input,
input_key,
false,
)
.await?;
let inst = Instruction::Transfer {
destination: service_id,
amount: 0,
gas_limit: service.min_memo_gas,
memo,
};
let boot = self.ensure_boot().await?;
let core = self.args.package.force_core.unwrap_or(0);
boot.submit_instructions(vec![inst], core).await?;
eprintln!("Code file: {code_ref}");
if !root_dir_ref.hash.is_zero() {
eprintln!("Root directory: {root_dir_ref}");
}
},
VmCommands::Destroy { target } => {
vm::destroy(
self.node,
target,
self.args.bootstrap_service_id,
self.args.package.force_core.unwrap_or(0),
)
.await?;
eprintln!(
"Success! The service can be ejected in {}",
format::exact_duration(Duration::from_secs(
u64::from(min_turnaround_period()) * slot_period_sec()
)),
);
},
VmCommands::Preimages { target } => {
let preimages = vm::preimages(self.node, target).await?;
for (service_id, hash, len) in preimages.into_iter() {
println!("{service_id:8x} {} {len}", format::hash(&hash));
}
},
},
Commands::Fs { command } => match command {
FsCommands::Upload { paths, block_size } => {
let block_size = block_size as usize;
if paths.is_empty() {
return Err(anyhow::anyhow!("No files specified."))
}
assert_eq!(
self.args.package.provision,
Provision::Bootstrap,
"Only provision via bootstrap service is supported"
);
self.node.wait_for_sync().await?;
let provider = self.bootstrap_provider();
let (block_refs, writer) = spawn_blocking(move || {
let mut writer = PreimageBlockWriter::new(provider);
let mut block_refs = Vec::with_capacity(paths.len());
for path in paths.into_iter() {
let block_ref = if path.is_dir() {
let dir_reader =
VerboseDirRead(corevm_fs::StdDirReader::new(path.clone())?);
corevm_fs::copy_dir_in(
dir_reader,
self.args.bootstrap_service_id,
&mut writer,
block_size,
)?
} else {
use std::io::Seek;
let mut file = std::fs::File::open(&path)?;
let file_size = file.seek(std::io::SeekFrom::End(0))?;
file.rewind()?;
eprintln!("Copying {}... [{file_size} B]", path.display());
corevm_fs::copy_file_in(
&mut file,
self.args.bootstrap_service_id,
&mut writer,
block_size,
)?
};
block_refs.push((path, block_ref));
}
Ok::<_, anyhow::Error>((block_refs, writer))
})
.await??;
let m = writer.finish(&mut self).await?;
eprintln!("Provided {} preimages to Bootstrap service", m.len());
for (path, block_ref) in block_refs.into_iter() {
eprintln!("{}: {block_ref}", path.display());
}
},
FsCommands::Download { block_ref, output_path } => {
assert_eq!(
self.args.package.provision,
Provision::Bootstrap,
"Only provision via bootstrap service is supported"
);
self.node.wait_for_sync().await?;
let best = self.node.best_block().await?;
spawn_blocking(move || {
let mut reader = corevm_tooling::PreimageReader::new(
self.node.clone(),
best.header_hash,
);
let fs_writer = VerboseFsWrite(corevm_fs::StdWriter::new(output_path));
corevm_fs::copy_out(&block_ref, &mut reader, fs_writer)
})
.await??;
},
},
}
Ok(())
}
async fn boot_solicit(
&mut self,
items: impl IntoIterator<Item = (Hash, usize)>,
) -> anyhow::Result<WorkPackageHash> {
let boot = self.ensure_boot().await?;
boot.boot_solicit(items, &self.args.package).await
}
async fn upload_exec_env(
&mut self,
code: Vec<u8>,
root_dir: Option<CoreVmFsNode>,
args: Vec<CString>,
env: Vec<CString>,
video_input: Option<VideoMode>,
audio_input: Option<AudioMode>,
input_key: Option<[u8; 32]>,
provide_corevm_code: bool,
) -> anyhow::Result<(corevm_host::ExecEnv, corevm_fs::BlockRef)> {
let mut provider = self.bootstrap_provider();
if provide_corevm_code {
let best = self.node.best_block().await?;
if provider.ensure(best.header_hash, corevm_bin::BLOB.to_vec()).await?.0 {
eprintln!("Providing CoreVM module code...");
} else {
eprintln!("CoreVM module already provided.");
}
}
let service_id = self.args.bootstrap_service_id;
let (exec_ref, exec, writer) = spawn_blocking(move || {
let mut writer = PreimageBlockWriter::new(provider);
let code_ref = corevm_fs::copy_file_in(
&mut std::io::Cursor::new(&code[..]),
service_id,
&mut writer,
corevm_fs::MAX_BLOCK_SIZE,
)?;
let root_dir_ref = match root_dir {
None => corevm_fs::BlockRef { service_id: 0, hash: Default::default() },
Some(CoreVmFsNode::BlockRef(block_ref)) => block_ref,
Some(CoreVmFsNode::Local(path)) => {
let dir_reader = VerboseDirRead(corevm_fs::StdDirReader::new(path)?);
corevm_fs::copy_dir_in(
dir_reader,
service_id,
&mut writer,
corevm_fs::MAX_BLOCK_SIZE,
)?
},
};
let exec = corevm_host::ExecEnv {
program: code_ref,
root_dir: root_dir_ref,
args: args.into_iter().map(corevm_host::Arg).collect(),
env: env.into_iter().map(corevm_host::Arg).collect(),
video_input,
audio_input,
input_key,
};
let exec_encoded = exec.encode();
let exec_ref = corevm_fs::copy_file_in(
&mut std::io::Cursor::new(&exec_encoded[..]),
service_id,
&mut writer,
corevm_fs::MAX_BLOCK_SIZE,
)?;
Ok::<_, anyhow::Error>((exec_ref, exec, writer))
})
.await??;
writer.finish(self).await?;
Ok((exec, exec_ref))
}
async fn reset_vm_instruction(
&mut self,
gas: SignedGas,
code: Vec<u8>,
root_dir: Option<CoreVmFsNode>,
args: Vec<CString>,
env: Vec<CString>,
video_input: Option<VideoMode>,
audio_input: Option<AudioMode>,
input_key: Option<[u8; 32]>,
provide_corevm_code: bool,
) -> anyhow::Result<(Memo, corevm_fs::BlockRef, corevm_fs::BlockRef)> {
let (exec, exec_ref) = self
.upload_exec_env(
code,
root_dir,
args,
env,
video_input,
audio_input,
input_key,
provide_corevm_code,
)
.await?;
let mut memo = Memo::zero();
CoreVmInstruction::Reset { gas, exec_ref }.encode_to(&mut &mut memo[..]);
Ok((memo, exec.program, exec.root_dir))
}
}
pub async fn boot_main() -> anyhow::Result<()> {
Context::new(BootArgs::parse()).await?.execute().await
}
struct PreimageBlockWriter {
provider: BootstrapProvision,
best_block: CachedBestBlock,
block_counter: usize,
}
impl PreimageBlockWriter {
fn new(provider: BootstrapProvision) -> Self {
Self { block_counter: 0, provider, best_block: CachedBestBlock::new() }
}
async fn finish(self, context: &mut Context) -> anyhow::Result<BTreeMap<Hash, Slot>> {
self.provider.provide(context).await
}
}
impl corevm_fs::WriteBlock for PreimageBlockWriter {
fn write_block(&mut self, service_id: ServiceId, buf: &[u8]) -> Result<(), corevm_fs::IoError> {
assert_eq!(self.provider.service_id, service_id);
let preimage_len = buf.len();
log::debug!("Soliciting file block ({preimage_len} B)");
let rt = tokio::runtime::Handle::current();
let best = rt.block_on(self.best_block.get(self.provider.node.as_ref())).map_err(|e| {
log::debug!("Failed to get best block: {e}");
corevm_fs::IoError
})?;
let (providing, _) =
rt.block_on(self.provider.ensure(best.header_hash, buf.to_vec())).map_err(|e| {
log::debug!("Failed to solicit file block ({preimage_len} B): {e}");
corevm_fs::IoError
})?;
if providing {
self.block_counter += 1;
log::debug!("Providing file block {}...", self.block_counter);
}
Ok(())
}
}
struct VerboseDirRead(pub corevm_fs::StdDirReader);
impl corevm_fs::HostDirRead<std::fs::File> for VerboseDirRead {
type Id = (u64, u64);
fn next_entry(&mut self) -> Option<Result<corevm_fs::HostDirEntry, corevm_fs::IoError>> {
self.0.next_entry()
}
fn open_file(
&mut self,
name: &corevm_fs::FileName,
) -> Result<std::fs::File, corevm_fs::IoError> {
use std::io::Seek;
let mut file = self.0.open_file(name)?;
let file_size = file.seek(std::io::SeekFrom::End(0)).map_err(|_| corevm_fs::IoError)?;
file.rewind().map_err(|_| corevm_fs::IoError)?;
let name = name.as_ref().to_string_lossy();
eprintln!("Copying {}{MAIN_SEPARATOR}{name}... [{file_size} B]", self.0.path().display());
Ok(file)
}
fn open_dir(
&mut self,
name: &corevm_fs::FileName,
) -> Result<(Self, Option<Self::Id>), corevm_fs::IoError> {
let name_str = name.as_ref().to_string_lossy();
eprintln!("Copying {}{MAIN_SEPARATOR}{name_str}...", self.0.path().display());
self.0.open_dir(name).map(|(dir, dir_id)| (Self(dir), dir_id))
}
}
struct VerboseDirWrite(pub corevm_fs::StdDirWriter);
impl corevm_fs::HostDirWrite for VerboseDirWrite {
type FileWrite = std::fs::File;
fn create_file(
&mut self,
name: &corevm_fs::FileName,
) -> Result<std::fs::File, corevm_fs::IoError> {
let name_str = name.as_ref().to_string_lossy();
eprintln!("Creating file {}{MAIN_SEPARATOR}{name_str}...", self.0.path().display());
self.0.create_file(name)
}
fn create_dir(&mut self, name: &corevm_fs::FileName) -> Result<Self, corevm_fs::IoError> {
let name_str = name.as_ref().to_string_lossy();
eprintln!("Creating directory {}{MAIN_SEPARATOR}{name_str}...", self.0.path().display());
self.0.create_dir(name).map(Self)
}
}
struct VerboseFsWrite(pub corevm_fs::StdWriter);
impl corevm_fs::HostWrite for VerboseFsWrite {
type FileWrite = std::fs::File;
type DirWrite = VerboseDirWrite;
fn into_file_writer(self) -> Result<Self::FileWrite, corevm_fs::IoError> {
eprintln!("Writing {}...", self.0.path().display());
self.0.into_file_writer()
}
fn into_dir_writer(self) -> Result<Self::DirWrite, corevm_fs::IoError> {
self.0.into_dir_writer().map(VerboseDirWrite)
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn args_work() {
BootArgs::command().debug_assert();
}
}