#[cfg(not(target_arch = "wasm32"))]
use once_cell::sync::Lazy;
#[cfg(not(target_arch = "wasm32"))]
use neo_types::{NeoError, NeoResult, NeoStorageContext};
#[cfg(not(target_arch = "wasm32"))]
use std::collections::{HashMap, HashSet};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::atomic::{AtomicU32, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::{Arc, RwLock};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) const DEFAULT_CONTRACT_HASH: [u8; 20] = [0u8; 20];
#[cfg(not(target_arch = "wasm32"))]
pub(crate) const MAX_SCRIPT_HASH_STACK_DEPTH: usize = 1024;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) const DEFAULT_CALL_FLAGS: i32 = 0x0F;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) type ContractStore = Arc<RwLock<HashMap<Vec<u8>, Vec<u8>>>>;
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy)]
pub(crate) struct ActiveScriptHashes {
pub(crate) calling: [u8; 20],
pub(crate) entry: [u8; 20],
pub(crate) executing: [u8; 20],
pub(crate) call_flags: i32,
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for ActiveScriptHashes {
fn default() -> Self {
Self {
calling: DEFAULT_CONTRACT_HASH,
entry: DEFAULT_CONTRACT_HASH,
executing: DEFAULT_CONTRACT_HASH,
call_flags: DEFAULT_CALL_FLAGS,
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_SCRIPT_HASHES: Lazy<RwLock<ActiveScriptHashes>> =
Lazy::new(|| RwLock::new(ActiveScriptHashes::default()));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_SCRIPT_HASH_STACK: Lazy<RwLock<Vec<ActiveScriptHashes>>> =
Lazy::new(|| RwLock::new(Vec::new()));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_WITNESSES: Lazy<RwLock<HashSet<Vec<u8>>>> =
Lazy::new(|| RwLock::new(HashSet::new()));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_CRYPTO_RESULTS: Lazy<RwLock<CryptoVerificationResults>> =
Lazy::new(|| RwLock::new(CryptoVerificationResults::default()));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_TIME: Lazy<RwLock<i64>> = Lazy::new(|| RwLock::new(0));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_RANDOM: Lazy<RwLock<i64>> = Lazy::new(|| RwLock::new(0));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_GAS_LEFT: Lazy<RwLock<i64>> = Lazy::new(|| RwLock::new(0));
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static ACTIVE_INVOCATION_COUNTER: Lazy<RwLock<i32>> = Lazy::new(|| RwLock::new(0));
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy, Default)]
pub(crate) struct CryptoVerificationResults {
pub(crate) check_sig: bool,
pub(crate) check_multisig: bool,
pub(crate) verify_with_ecdsa: bool,
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone)]
pub(crate) struct ContextHandle {
pub(crate) read_only: bool,
pub(crate) contract: [u8; 20],
pub(crate) store: ContractStore,
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::type_complexity)]
pub(crate) struct StorageState {
next_context: AtomicU32,
contexts: RwLock<HashMap<u32, ContextHandle>>,
contract_stores: RwLock<HashMap<[u8; 20], ContractStore>>,
}
#[cfg(not(target_arch = "wasm32"))]
impl StorageState {
pub(crate) fn new() -> Self {
Self {
next_context: AtomicU32::new(1),
contexts: RwLock::new(HashMap::new()),
contract_stores: RwLock::new(HashMap::new()),
}
}
pub(crate) fn create_context(
&self,
contract: [u8; 20],
read_only: bool,
) -> NeoResult<NeoStorageContext> {
let store = self.get_or_create_store(contract);
let id = self.next_context.fetch_add(1, Ordering::SeqCst);
let handle = ContextHandle {
read_only,
contract,
store,
};
self.contexts
.write()
.map_err(|_| NeoError::InvalidState)?
.insert(id, handle);
Ok(if read_only {
NeoStorageContext::read_only(id)
} else {
NeoStorageContext::new(id)
})
}
pub(crate) fn clone_as_read_only(
&self,
context: &NeoStorageContext,
) -> NeoResult<NeoStorageContext> {
let handle = self
.contexts
.read()
.map_err(|_| NeoError::InvalidState)?
.get(&context.id())
.cloned()
.ok_or(NeoError::InvalidState)?;
let id = self.next_context.fetch_add(1, Ordering::SeqCst);
let ro_handle = ContextHandle {
read_only: true,
contract: handle.contract,
store: handle.store,
};
self.contexts
.write()
.map_err(|_| NeoError::InvalidState)?
.insert(id, ro_handle);
Ok(NeoStorageContext::read_only(id))
}
pub(crate) fn get_handle(&self, context: &NeoStorageContext) -> NeoResult<ContextHandle> {
self.contexts
.read()
.map_err(|_| NeoError::InvalidState)?
.get(&context.id())
.cloned()
.ok_or(NeoError::InvalidState)
}
pub(crate) fn reset(&self) -> NeoResult<()> {
self.contexts
.write()
.map_err(|_| NeoError::InvalidState)?
.clear();
self.contract_stores
.write()
.map_err(|_| NeoError::InvalidState)?
.clear();
self.next_context.store(1, Ordering::SeqCst);
Ok(())
}
pub(crate) fn put(&self, key: Vec<u8>, value: Vec<u8>) {
if let Ok(mut map) = self
.get_or_create_store(crate::storage::current_executing_script_hash())
.write()
{
map.insert(key, value);
}
}
fn get_or_create_store(&self, contract: [u8; 20]) -> ContractStore {
let mut stores = match self.contract_stores.write() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
stores
.entry(contract)
.or_insert_with(|| Arc::new(RwLock::new(HashMap::new())))
.clone()
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) static STORAGE_STATE: Lazy<StorageState> = Lazy::new(StorageState::new);
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn current_calling_script_hash() -> [u8; 20] {
match ACTIVE_SCRIPT_HASHES.read() {
Ok(active) => active.calling,
Err(poisoned) => poisoned.into_inner().calling,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn current_entry_script_hash() -> [u8; 20] {
match ACTIVE_SCRIPT_HASHES.read() {
Ok(active) => active.entry,
Err(poisoned) => poisoned.into_inner().entry,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn current_executing_script_hash() -> [u8; 20] {
match ACTIVE_SCRIPT_HASHES.read() {
Ok(active) => active.executing,
Err(poisoned) => poisoned.into_inner().executing,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_current_contract_hash(hash: [u8; 20]) {
set_current_script_hashes(hash, hash, hash);
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_current_script_hashes(calling: [u8; 20], entry: [u8; 20], executing: [u8; 20]) {
match ACTIVE_SCRIPT_HASHES.write() {
Ok(mut active) => {
active.calling = calling;
active.entry = entry;
active.executing = executing;
}
Err(poisoned) => {
let mut active = poisoned.into_inner();
active.calling = calling;
active.entry = entry;
active.executing = executing;
}
}
clear_script_hash_stack();
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_current_calling_script_hash(hash: [u8; 20]) {
match ACTIVE_SCRIPT_HASHES.write() {
Ok(mut active) => active.calling = hash,
Err(poisoned) => poisoned.into_inner().calling = hash,
}
clear_script_hash_stack();
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_current_entry_script_hash(hash: [u8; 20]) {
match ACTIVE_SCRIPT_HASHES.write() {
Ok(mut active) => active.entry = hash,
Err(poisoned) => poisoned.into_inner().entry = hash,
}
clear_script_hash_stack();
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_current_executing_script_hash(hash: [u8; 20]) {
match ACTIVE_SCRIPT_HASHES.write() {
Ok(mut active) => active.executing = hash,
Err(poisoned) => poisoned.into_inner().executing = hash,
}
clear_script_hash_stack();
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn current_call_flags() -> i32 {
match ACTIVE_SCRIPT_HASHES.read() {
Ok(active) => active.call_flags,
Err(poisoned) => poisoned.into_inner().call_flags,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_current_call_flags(flags: i32) {
match ACTIVE_SCRIPT_HASHES.write() {
Ok(mut active) => active.call_flags = flags,
Err(poisoned) => poisoned.into_inner().call_flags = flags,
}
clear_script_hash_stack();
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn push_current_executing_script_hash(hash: [u8; 20], call_flags: i32) -> NeoResult<()> {
let mut active = match ACTIVE_SCRIPT_HASHES.write() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let mut stack = match ACTIVE_SCRIPT_HASH_STACK.write() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
if stack.len() >= MAX_SCRIPT_HASH_STACK_DEPTH {
return Err(NeoError::InvalidOperation);
}
stack.push(*active);
active.calling = active.executing;
active.executing = hash;
active.call_flags = call_flags;
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn pop_current_script_hash_frame() -> NeoResult<()> {
let mut active = match ACTIVE_SCRIPT_HASHES.write() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let mut stack = match ACTIVE_SCRIPT_HASH_STACK.write() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
if let Some(previous) = stack.pop() {
*active = previous;
Ok(())
} else {
Err(NeoError::InvalidState)
}
}
#[cfg(not(target_arch = "wasm32"))]
fn clear_script_hash_stack() {
match ACTIVE_SCRIPT_HASH_STACK.write() {
Ok(mut stack) => stack.clear(),
Err(poisoned) => poisoned.into_inner().clear(),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn reset_current_contract_hash() {
set_current_script_hashes(
DEFAULT_CONTRACT_HASH,
DEFAULT_CONTRACT_HASH,
DEFAULT_CONTRACT_HASH,
);
set_current_call_flags(DEFAULT_CALL_FLAGS);
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_active_witnesses<I>(witnesses: I)
where
I: IntoIterator<Item = Vec<u8>>,
{
let updated: HashSet<Vec<u8>> = witnesses.into_iter().collect();
match ACTIVE_WITNESSES.write() {
Ok(mut active) => *active = updated,
Err(poisoned) => *poisoned.into_inner() = updated,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn has_active_witness(account: &[u8]) -> bool {
match ACTIVE_WITNESSES.read() {
Ok(active) => active.contains(account),
Err(poisoned) => poisoned.into_inner().contains(account),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn clear_active_witnesses() {
match ACTIVE_WITNESSES.write() {
Ok(mut active) => active.clear(),
Err(poisoned) => poisoned.into_inner().clear(),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn set_crypto_verification_results(results: CryptoVerificationResults) {
match ACTIVE_CRYPTO_RESULTS.write() {
Ok(mut active) => *active = results,
Err(poisoned) => *poisoned.into_inner() = results,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn active_crypto_verification_results() -> CryptoVerificationResults {
match ACTIVE_CRYPTO_RESULTS.read() {
Ok(active) => *active,
Err(poisoned) => *poisoned.into_inner(),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn reset_crypto_verification_results() {
set_crypto_verification_results(CryptoVerificationResults::default());
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn create_context_recovers_when_store_lock_is_poisoned() {
let state = StorageState::new();
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _guard = state.contract_stores.write().unwrap();
panic!("poison contract stores lock");
}));
let result = state.create_context(DEFAULT_CONTRACT_HASH, false);
assert!(result.is_ok());
}
}