use crate::{
log::RunResult,
mailbox::ActorMailbox,
manager::{Actors, Balance, ExtManager, MintMode},
program::{Program, ProgramIdWrapper},
};
use codec::{Decode, DecodeAll};
use colored::Colorize;
use env_logger::{Builder, Env};
use gear_core::{
ids::{CodeId, ProgramId},
message::Dispatch,
pages::GearPage,
};
use gear_lazy_pages::{LazyPagesStorage, LazyPagesVersion};
use gear_lazy_pages_common::LazyPagesInitContext;
use path_clean::PathClean;
use std::{borrow::Cow, cell::RefCell, env, fs, io::Write, path::Path, thread};
thread_local! {
static SYSTEM_INITIALIZED: RefCell<bool> = const { RefCell::new(false) };
}
#[derive(Decode)]
#[codec(crate = codec)]
struct PageKey {
_page_storage_prefix: [u8; 32],
program_id: ProgramId,
_memory_infix: u32,
page: GearPage,
}
#[derive(Debug)]
struct PagesStorage {
actors: Actors,
}
impl LazyPagesStorage for PagesStorage {
fn page_exists(&self, mut key: &[u8]) -> bool {
let PageKey {
program_id, page, ..
} = PageKey::decode_all(&mut key).expect("Invalid key");
self.actors
.borrow()
.get(&program_id)
.and_then(|(actor, _)| actor.get_pages_data())
.map(|pages_data| pages_data.contains_key(&page))
.unwrap_or(false)
}
fn load_page(&mut self, mut key: &[u8], buffer: &mut [u8]) -> Option<u32> {
let PageKey {
program_id, page, ..
} = PageKey::decode_all(&mut key).expect("Invalid key");
let actors = self.actors.borrow();
let (actor, _balance) = actors.get(&program_id)?;
let pages_data = actor.get_pages_data()?;
let page_buf = pages_data.get(&page)?;
buffer.copy_from_slice(page_buf);
Some(page_buf.len() as u32)
}
}
pub struct System(pub(crate) RefCell<ExtManager>);
impl System {
pub(crate) const PAGE_STORAGE_PREFIX: [u8; 32] = *b"gtestgtestgtestgtestgtestgtest00";
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
SYSTEM_INITIALIZED.with_borrow_mut(|initialized| {
if *initialized {
panic!("Impossible to have multiple instances of the `System`.");
}
let ext_manager = ExtManager::new();
let actors = ext_manager.actors.clone();
let pages_storage = PagesStorage { actors };
gear_lazy_pages::init(
LazyPagesVersion::Version1,
LazyPagesInitContext::new(Self::PAGE_STORAGE_PREFIX),
pages_storage,
)
.expect("Failed to init lazy-pages");
*initialized = true;
Self(RefCell::new(ext_manager))
})
}
pub fn init_logger(&self) {
self.init_logger_with_default_filter("gwasm=debug");
}
pub fn init_verbose_logger(&self) {
self.init_logger_with_default_filter("gwasm=debug,gtest=debug");
}
pub fn init_logger_with_default_filter<'a>(&self, default_filter: impl Into<Cow<'a, str>>) {
let _ = Builder::from_env(Env::default().default_filter_or(default_filter))
.format(|buf, record| {
let lvl = record.level().to_string().to_uppercase();
let target = record.target().to_string();
let mut msg = record.args().to_string();
if target == "gwasm" {
msg = msg.replacen("DEBUG: ", "", 1);
writeln!(
buf,
"[{} {}] {}",
lvl.blue(),
thread::current().name().unwrap_or("unknown").white(),
msg.white()
)
} else {
writeln!(
buf,
"[{} {}] {}",
target.red(),
thread::current().name().unwrap_or("unknown").white(),
msg.white()
)
}
})
.format_target(false)
.format_timestamp(None)
.try_init();
}
pub fn send_dispatch(&self, dispatch: Dispatch) -> RunResult {
self.0.borrow_mut().validate_and_run_dispatch(dispatch)
}
pub fn spend_blocks(&self, amount: u32) -> Vec<RunResult> {
let mut manager = self.0.borrow_mut();
let block_height = manager.blocks_manager.get().height;
(block_height..block_height + amount)
.map(|_| {
manager.check_epoch();
let block_info = manager.blocks_manager.next_block();
let next_block_number = block_info.height;
let mut results = manager.process_delayed_dispatches(next_block_number);
results.extend(manager.process_scheduled_wait_list(next_block_number));
results
})
.collect::<Vec<Vec<_>>>()
.concat()
}
pub fn block_height(&self) -> u32 {
self.0.borrow().blocks_manager.get().height
}
pub fn block_timestamp(&self) -> u64 {
self.0.borrow().blocks_manager.get().timestamp
}
pub fn get_program<ID: Into<ProgramIdWrapper>>(&self, id: ID) -> Option<Program> {
let id = id.into().0;
let manager = self.0.borrow();
if manager.is_program(&id) {
Some(Program {
id,
manager: &self.0,
})
} else {
None
}
}
pub fn last_program(&self) -> Option<Program> {
self.programs().into_iter().next_back()
}
pub fn programs(&self) -> Vec<Program> {
let manager = self.0.borrow();
let actors = manager.actors.borrow();
actors
.keys()
.copied()
.filter(|id| manager.is_program(id))
.map(|id| Program {
id,
manager: &self.0,
})
.collect()
}
pub fn is_active_program<ID: Into<ProgramIdWrapper>>(&self, id: ID) -> bool {
let program_id = id.into().0;
self.0.borrow().is_active_program(&program_id)
}
pub fn submit_code(&self, binary: impl Into<Vec<u8>>) -> CodeId {
self.0.borrow_mut().store_new_code(binary.into())
}
#[track_caller]
pub fn submit_code_file<P: AsRef<Path>>(&self, code_path: P) -> CodeId {
let code = fs::read(&code_path).unwrap_or_else(|_| {
panic!(
"Failed to read file {}",
code_path.as_ref().to_string_lossy()
)
});
self.0.borrow_mut().store_new_code(code)
}
#[track_caller]
pub fn submit_local_code_file<P: AsRef<Path>>(&self, code_path: P) -> CodeId {
let path = env::current_dir()
.expect("Unable to get root directory of the project")
.join(code_path)
.clean();
self.submit_code_file(path)
}
pub fn submitted_code(&self, code_id: CodeId) -> Option<Vec<u8>> {
self.0.borrow().read_code(code_id).map(|code| code.to_vec())
}
#[track_caller]
pub fn get_mailbox<ID: Into<ProgramIdWrapper>>(&self, id: ID) -> ActorMailbox {
let program_id = id.into().0;
if !self.0.borrow().is_user(&program_id) {
panic!("Mailbox available only for users");
}
ActorMailbox::new(program_id, &self.0)
}
pub fn mint_to<ID: Into<ProgramIdWrapper>>(&self, id: ID, value: Balance) {
let actor_id = id.into().0;
self.0
.borrow_mut()
.mint_to(&actor_id, value, MintMode::KeepAlive);
}
pub fn balance_of<ID: Into<ProgramIdWrapper>>(&self, id: ID) -> Balance {
let actor_id = id.into().0;
self.0.borrow().balance_of(&actor_id)
}
}
impl Drop for System {
fn drop(&mut self) {
SYSTEM_INITIALIZED.with_borrow_mut(|initialized| *initialized = false);
self.0.borrow().gas_tree.reset();
self.0.borrow().mailbox.reset();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Impossible to have multiple instances of the `System`.")]
fn test_system_being_singleton() {
let _first_instance = System::new();
let _second_instance = System::new();
}
#[test]
fn test_multithread_copy_singleton() {
let first_instance = System::new();
first_instance.spend_blocks(5);
assert_eq!(first_instance.block_height(), 5);
let h = std::thread::spawn(|| {
let second_instance = System::new();
second_instance.spend_blocks(10);
assert_eq!(second_instance.block_height(), 10);
});
h.join().expect("internal error failed joining thread");
}
}