use std::sync::{Arc, Mutex};
use linera_base::vm::VmRuntime;
use linera_views::common::from_bytes_option;
use revm::{primitives::keccak256, Database, DatabaseCommit, DatabaseRef};
use revm_context::BlockEnv;
use revm_context_interface::block::BlobExcessGasAndPrice;
use revm_database::{AccountState, DBErrorMarker};
use revm_primitives::{address, Address, B256, U256};
use revm_state::{AccountInfo, Bytecode, EvmState};
use crate::{ApplicationId, BaseRuntime, Batch, ContractRuntime, ExecutionError, ServiceRuntime};
pub const EVM_SERVICE_GAS_LIMIT: u64 = 20_000_000;
const SLOAD_COST: u64 = 2100;
const SSTORE_COST_SET: u64 = 20000;
const SSTORE_COST_NO_OPERATION: u64 = 100;
const SSTORE_COST_RESET: u64 = 2900;
const SSTORE_REFUND_RELEASE: u64 = 4800;
#[derive(Clone, Default)]
pub(crate) struct StorageStats {
key_no_operation: u64,
key_reset: u64,
key_set: u64,
key_release: u64,
key_read: u64,
}
impl StorageStats {
pub fn storage_costs(&self) -> u64 {
let mut storage_costs = 0;
storage_costs += self.key_no_operation * SSTORE_COST_NO_OPERATION;
storage_costs += self.key_reset * SSTORE_COST_RESET;
storage_costs += self.key_set * SSTORE_COST_SET;
storage_costs += self.key_read * SLOAD_COST;
storage_costs
}
pub fn storage_refund(&self) -> u64 {
self.key_release * SSTORE_REFUND_RELEASE
}
}
pub(crate) struct DatabaseRuntime<Runtime> {
storage_stats: Arc<Mutex<StorageStats>>,
pub contract_address: Address,
pub runtime: Arc<Mutex<Runtime>>,
pub changes: EvmState,
}
impl<Runtime> Clone for DatabaseRuntime<Runtime> {
fn clone(&self) -> Self {
Self {
storage_stats: self.storage_stats.clone(),
contract_address: self.contract_address,
runtime: self.runtime.clone(),
changes: self.changes.clone(),
}
}
}
#[repr(u8)]
pub enum KeyCategory {
AccountInfo,
AccountState,
Storage,
}
fn application_id_to_address(application_id: ApplicationId) -> Address {
let application_id: [u64; 4] = <[u64; 4]>::from(application_id.application_description_hash);
let application_id: [u8; 32] = linera_base::crypto::u64_array_to_be_bytes(application_id);
Address::from_slice(&application_id[0..20])
}
impl<Runtime: BaseRuntime> DatabaseRuntime<Runtime> {
fn get_linera_key(key_prefix: &[u8], index: U256) -> Result<Vec<u8>, ExecutionError> {
let mut key = key_prefix.to_vec();
bcs::serialize_into(&mut key, &index)?;
Ok(key)
}
fn get_address_key(prefix: u8, address: Address) -> Vec<u8> {
let mut key = vec![prefix];
key.extend(address);
key
}
pub fn new(runtime: Runtime) -> Self {
let storage_stats = StorageStats::default();
Self {
storage_stats: Arc::new(Mutex::new(storage_stats)),
contract_address: Address::ZERO,
runtime: Arc::new(Mutex::new(runtime)),
changes: EvmState::default(),
}
}
pub fn take_storage_stats(&self) -> StorageStats {
let mut storage_stats_read = self
.storage_stats
.lock()
.expect("The lock should be possible");
let storage_stats = storage_stats_read.clone();
*storage_stats_read = StorageStats::default();
storage_stats
}
}
impl DBErrorMarker for ExecutionError {}
impl<Runtime> Database for DatabaseRuntime<Runtime>
where
Runtime: BaseRuntime,
{
type Error = ExecutionError;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, ExecutionError> {
self.basic_ref(address)
}
fn code_by_hash(&mut self, _code_hash: B256) -> Result<Bytecode, ExecutionError> {
panic!("Functionality code_by_hash not implemented");
}
fn storage(&mut self, address: Address, index: U256) -> Result<U256, ExecutionError> {
self.storage_ref(address, index)
}
fn block_hash(&mut self, number: u64) -> Result<B256, ExecutionError> {
<Self as DatabaseRef>::block_hash_ref(self, number)
}
}
impl<Runtime> DatabaseCommit for DatabaseRuntime<Runtime>
where
Runtime: BaseRuntime,
{
fn commit(&mut self, changes: EvmState) {
self.changes = changes;
}
}
impl<Runtime> DatabaseRef for DatabaseRuntime<Runtime>
where
Runtime: BaseRuntime,
{
type Error = ExecutionError;
fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, ExecutionError> {
if !self.changes.is_empty() {
let account = self.changes.get(&address).unwrap();
return Ok(Some(account.info.clone()));
}
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let key_info = Self::get_address_key(KeyCategory::AccountInfo as u8, address);
let promise = runtime.read_value_bytes_new(key_info)?;
let result = runtime.read_value_bytes_wait(&promise)?;
let account_info = from_bytes_option::<AccountInfo>(&result)?;
Ok(account_info)
}
fn code_by_hash_ref(&self, _code_hash: B256) -> Result<Bytecode, ExecutionError> {
panic!("Functionality code_by_hash_ref not implemented");
}
fn storage_ref(&self, address: Address, index: U256) -> Result<U256, ExecutionError> {
if !self.changes.is_empty() {
let account = self.changes.get(&address).unwrap();
return Ok(match account.storage.get(&index) {
None => U256::ZERO,
Some(slot) => slot.present_value(),
});
}
let key_prefix = Self::get_address_key(KeyCategory::Storage as u8, address);
let key = Self::get_linera_key(&key_prefix, index)?;
{
let mut storage_stats = self
.storage_stats
.lock()
.expect("The lock should be possible");
storage_stats.key_read += 1;
}
let result = {
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let promise = runtime.read_value_bytes_new(key)?;
runtime.read_value_bytes_wait(&promise)
}?;
Ok(from_bytes_option::<U256>(&result)?.unwrap_or_default())
}
fn block_hash_ref(&self, number: u64) -> Result<B256, ExecutionError> {
Ok(keccak256(number.to_string().as_bytes()))
}
}
impl<Runtime> DatabaseRuntime<Runtime>
where
Runtime: ContractRuntime,
{
pub fn commit_changes(&mut self) -> Result<(), ExecutionError> {
let mut storage_stats = self
.storage_stats
.lock()
.expect("The lock should be possible");
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let mut batch = Batch::new();
for (address, account) in &self.changes {
if !account.is_touched() {
continue;
}
let key_prefix = Self::get_address_key(KeyCategory::Storage as u8, *address);
let key_info = Self::get_address_key(KeyCategory::AccountInfo as u8, *address);
let key_state = Self::get_address_key(KeyCategory::AccountState as u8, *address);
if account.is_selfdestructed() {
batch.delete_key_prefix(key_prefix);
batch.put_key_value(key_info, &AccountInfo::default())?;
batch.put_key_value(key_state, &AccountState::NotExisting)?;
} else {
let is_newly_created = account.is_created();
batch.put_key_value(key_info, &account.info)?;
let account_state = if is_newly_created {
batch.delete_key_prefix(key_prefix.clone());
AccountState::StorageCleared
} else {
let promise = runtime.read_value_bytes_new(key_state.clone())?;
let result = runtime.read_value_bytes_wait(&promise)?;
let account_state =
from_bytes_option::<AccountState>(&result)?.unwrap_or_default();
if account_state.is_storage_cleared() {
AccountState::StorageCleared
} else {
AccountState::Touched
}
};
batch.put_key_value(key_state, &account_state)?;
for (index, value) in &account.storage {
if value.present_value() == value.original_value() {
storage_stats.key_no_operation += 1;
} else {
let key = Self::get_linera_key(&key_prefix, *index)?;
if value.original_value() == U256::ZERO {
batch.put_key_value(key, &value.present_value())?;
storage_stats.key_set += 1;
} else if value.present_value() == U256::ZERO {
batch.delete_key(key);
storage_stats.key_release += 1;
} else {
batch.put_key_value(key, &value.present_value())?;
storage_stats.key_reset += 1;
}
}
}
}
}
runtime.write_batch(batch)?;
self.changes.clear();
Ok(())
}
}
impl<Runtime> DatabaseRuntime<Runtime>
where
Runtime: BaseRuntime,
{
pub fn get_nonce(&self, address: &Address) -> Result<u64, ExecutionError> {
let account_info = self.basic_ref(*address)?;
Ok(match account_info {
None => 0,
Some(account_info) => account_info.nonce,
})
}
pub fn set_contract_address(&mut self) -> Result<(), ExecutionError> {
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let application_id = runtime.application_id()?;
self.contract_address = application_id_to_address(application_id);
Ok(())
}
pub fn is_initialized(&self) -> Result<bool, ExecutionError> {
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let evm_address = runtime.application_id()?.evm_address();
let key_info = Self::get_address_key(KeyCategory::AccountInfo as u8, evm_address);
let promise = runtime.contains_key_new(key_info)?;
let result = runtime.contains_key_wait(&promise)?;
Ok(result)
}
pub fn get_block_env(&self) -> Result<BlockEnv, ExecutionError> {
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let block_height_linera = runtime.block_height()?;
let block_height_evm = block_height_linera.0;
let beneficiary = address!("00000000000000000000000000000000000000bb");
let difficulty = U256::ZERO;
let gas_limit = u64::MAX;
let timestamp_linera = runtime.read_system_timestamp()?;
let timestamp_evm = timestamp_linera.micros() / 1_000_000;
let basefee = 0;
let chain_id = runtime.chain_id()?;
let entry = format!("{}{}", chain_id, block_height_linera);
let prevrandao = keccak256(entry.as_bytes());
let entry = BlobExcessGasAndPrice {
excess_blob_gas: 0,
blob_gasprice: 1,
};
let blob_excess_gas_and_price = Some(entry);
Ok(BlockEnv {
number: block_height_evm,
beneficiary,
difficulty,
gas_limit,
timestamp: timestamp_evm,
basefee,
prevrandao: Some(prevrandao),
blob_excess_gas_and_price,
})
}
pub fn constructor_argument(&self) -> Result<Vec<u8>, ExecutionError> {
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let constructor_argument = runtime.application_parameters()?;
Ok(serde_json::from_slice::<Vec<u8>>(&constructor_argument)?)
}
}
impl<Runtime> DatabaseRuntime<Runtime>
where
Runtime: ContractRuntime,
{
pub fn get_contract_block_env(&self) -> Result<BlockEnv, ExecutionError> {
let mut block_env = self.get_block_env()?;
let mut runtime = self.runtime.lock().expect("The lock should be possible");
let gas_limit = runtime.maximum_fuel_per_block(VmRuntime::Evm)?;
block_env.gas_limit = gas_limit;
Ok(block_env)
}
}
impl<Runtime> DatabaseRuntime<Runtime>
where
Runtime: ServiceRuntime,
{
pub fn get_service_block_env(&self) -> Result<BlockEnv, ExecutionError> {
let mut block_env = self.get_block_env()?;
block_env.gas_limit = EVM_SERVICE_GAS_LIMIT;
Ok(block_env)
}
}