Skip to content

Commit

Permalink
Merge branch 'main' into importer-component
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriel-aranha-cw committed Jul 31, 2024
2 parents 86f383e + 91a0b1e commit f8df837
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 86 deletions.
9 changes: 8 additions & 1 deletion e2e/test/automine/e2e-json-rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ describe("JSON-RPC", () => {
(await sendExpect("eth_getBalance", [ALICE])).eq(TEST_BALANCE);
(await sendExpect("eth_getBalance", [ALICE, "latest"])).eq(TEST_BALANCE);
});
it("eth_getCode", () => {
describe("eth_getCode", () => {
it("contract code is available in the block it was deployed", async () => {
await sendReset();
const contract = await deployTestContractBalances();
(await sendExpect("eth_getCode", [contract.target, "latest"])).not.eq("0x");
(await sendExpect("eth_getCode", [contract.target, "0x1"])).not.eq("0x");
});
it("code for non-existent contract is 0x", async () => {
(await sendExpect("eth_getCode", [ALICE.address, "latest"])).eq("0x");
});
Expand All @@ -91,6 +97,7 @@ describe("JSON-RPC", () => {

describe("Block", () => {
it("eth_blockNumber", async function () {
await sendReset();
(await sendExpect("eth_blockNumber")).eq(ZERO);
await sendEvmMine();
(await sendExpect("eth_blockNumber")).eq(ONE);
Expand Down
12 changes: 11 additions & 1 deletion e2e/test/external/e2e-json-rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,16 @@ describe("JSON-RPC", () => {
(await sendExpect("eth_getBalance", [ALICE])).eq(TEST_BALANCE);
(await sendExpect("eth_getBalance", [ALICE, "latest"])).eq(TEST_BALANCE);
});
it("eth_getCode", () => {

describe("eth_getCode", () => {
it("contract code is available in the block it was deployed", async () => {
await sendReset();
const contract = await deployTestContractBalances();
await sendEvmMine();
(await sendExpect("eth_getCode", [contract.target, "latest"])).not.eq("0x");
(await sendExpect("eth_getCode", [contract.target, "0x1"])).not.eq("0x");
});

it("code for non-existent contract is 0x", async () => {
(await sendExpect("eth_getCode", [ALICE.address, "latest"])).eq("0x");
});
Expand All @@ -98,6 +107,7 @@ describe("JSON-RPC", () => {

describe("Block", () => {
it("eth_blockNumber", async function () {
await sendReset();
(await sendExpect("eth_blockNumber")).eq(ZERO);
await sendEvmMine();
(await sendExpect("eth_blockNumber")).eq(ONE);
Expand Down
50 changes: 34 additions & 16 deletions src/eth/executor/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ use crate::alias::RevmAddress;
use crate::alias::RevmBytecode;
use crate::eth::executor::EvmExecutionResult;
use crate::eth::executor::EvmInput;
use crate::eth::executor::ExecutorConfig;
use crate::eth::primitives::Account;
use crate::eth::primitives::Address;
use crate::eth::primitives::Bytes;
use crate::eth::primitives::ChainId;
use crate::eth::primitives::EvmExecution;
use crate::eth::primitives::EvmExecutionMetrics;
use crate::eth::primitives::ExecutionAccountChanges;
Expand Down Expand Up @@ -54,8 +54,8 @@ pub struct Evm {
impl Evm {
/// Creates a new instance of the Evm.
#[allow(clippy::arc_with_non_send_sync)]
pub fn new(storage: Arc<StratusStorage>, chain_id: ChainId) -> Self {
tracing::info!(%chain_id, "creating revm");
pub fn new(storage: Arc<StratusStorage>, config: ExecutorConfig) -> Self {
tracing::info!(?config, "creating revm");

// configure handler
let mut handler = Handler::mainnet_with_spec(SpecId::LONDON);
Expand All @@ -75,15 +75,16 @@ impl Evm {
handler.set_instruction_table(instructions);

// configure revm
let chain_id = config.executor_chain_id;
let mut evm = RevmEvm::builder()
.with_external_context(())
.with_db(RevmSession::new(storage))
.with_db(RevmSession::new(storage, config))
.with_handler(handler)
.build();

// global general config
let cfg_env = evm.cfg_mut();
cfg_env.chain_id = chain_id.into();
cfg_env.chain_id = chain_id;
cfg_env.limit_contract_code_size = Some(usize::MAX);

// global block config
Expand Down Expand Up @@ -155,10 +156,16 @@ impl Evm {
account: state.into(),
}),

// storage error
Err(EVMError::Database(e)) => {
tracing::warn!(reason = ?e, "evm storage error");
Err(e)
}

// unexpected errors
Err(e) => {
tracing::warn!(reason = ?e, "evm execution error");
Err(StratusError::TransactionFailed(e))
tracing::warn!(reason = ?e, "evm transaction error");
Err(StratusError::TransactionFailed(e.to_string()))
}
};

Expand All @@ -182,6 +189,9 @@ impl Evm {

/// Contextual data that is read or set durint the execution of a transaction in the EVM.
struct RevmSession {
/// Executor configuration.
config: ExecutorConfig,

/// Service to communicate with the storage.
storage: Arc<StratusStorage>,

Expand All @@ -197,8 +207,9 @@ struct RevmSession {

impl RevmSession {
/// Creates the base session to be used with REVM.
pub fn new(storage: Arc<StratusStorage>) -> Self {
pub fn new(storage: Arc<StratusStorage>, config: ExecutorConfig) -> Self {
Self {
config,
storage,
input: EvmInput::default(),
storage_changes: HashMap::default(),
Expand All @@ -215,9 +226,9 @@ impl RevmSession {
}

impl Database for RevmSession {
type Error = anyhow::Error;
type Error = StratusError;

fn basic(&mut self, revm_address: RevmAddress) -> anyhow::Result<Option<AccountInfo>> {
fn basic(&mut self, revm_address: RevmAddress) -> Result<Option<AccountInfo>, StratusError> {
self.metrics.account_reads += 1;

// retrieve account
Expand All @@ -226,8 +237,12 @@ impl Database for RevmSession {

// warn if the loaded account is the `to` account and it does not have a bytecode
if let Some(ref to_address) = self.input.to {
if &address == to_address && not(account.is_contract()) && not(self.input.data.is_empty()) {
tracing::warn!(%address, "evm to_account does not have bytecode");
if account.bytecode.is_none() && &address == to_address && self.input.is_contract_call() {
if self.config.executor_reject_not_contract {
return Err(StratusError::TransactionAccountNotContract { address: *to_address });
} else {
tracing::warn!(%address, "evm to_account is not a contract because does not have bytecode");
}
}
}

Expand All @@ -243,11 +258,11 @@ impl Database for RevmSession {
Ok(Some(revm_account))
}

fn code_by_hash(&mut self, _: B256) -> anyhow::Result<RevmBytecode> {
fn code_by_hash(&mut self, _: B256) -> Result<RevmBytecode, StratusError> {
todo!()
}

fn storage(&mut self, revm_address: RevmAddress, revm_index: U256) -> anyhow::Result<U256> {
fn storage(&mut self, revm_address: RevmAddress, revm_index: U256) -> Result<U256, StratusError> {
self.metrics.slot_reads += 1;

// convert slot
Expand All @@ -265,15 +280,18 @@ impl Database for RevmSession {
}
None => {
tracing::error!(reason = "reading slot without account loaded", %address, %index);
return Err(anyhow!("Account '{}' was expected to be loaded by EVM, but it was not", address));
return Err(StratusError::Unexpected(anyhow!(
"Account '{}' was expected to be loaded by EVM, but it was not",
address
)));
}
};
}

Ok(slot.value.into())
}

fn block_hash(&mut self, _: U256) -> anyhow::Result<B256> {
fn block_hash(&mut self, _: U256) -> Result<B256, StratusError> {
todo!()
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/eth/executor/evm_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::eth::primitives::TransactionInput;
use crate::eth::primitives::UnixTime;
use crate::eth::primitives::Wei;
use crate::eth::storage::StoragePointInTime;
use crate::ext::not;
use crate::ext::OptionExt;
use crate::if_else;
use crate::log_and_err;
Expand Down Expand Up @@ -149,4 +150,11 @@ impl EvmInput {
},
})
}

/// Checks if the input is a contract call.
///
/// It is when there is a `to` address and the `data` field is also populated.
pub fn is_contract_call(&self) -> bool {
self.to.is_some() && not(self.data.is_empty())
}
}
24 changes: 11 additions & 13 deletions src/eth/executor/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::eth::executor::ExecutorConfig;
use crate::eth::miner::Miner;
use crate::eth::primitives::BlockFilter;
use crate::eth::primitives::CallInput;
use crate::eth::primitives::ChainId;
use crate::eth::primitives::EvmExecution;
use crate::eth::primitives::EvmExecutionMetrics;
use crate::eth::primitives::ExternalBlock;
Expand Down Expand Up @@ -82,11 +81,9 @@ struct Evms {
impl Evms {
/// Spawns EVM tasks in background.
fn spawn(storage: Arc<StratusStorage>, config: &ExecutorConfig) -> Self {
let chain_id: ChainId = config.chain_id.into();

// function executed by evm threads
fn evm_loop(task_name: &str, storage: Arc<StratusStorage>, chain_id: ChainId, task_rx: crossbeam_channel::Receiver<EvmTask>) {
let mut evm = Evm::new(storage, chain_id);
fn evm_loop(task_name: &str, storage: Arc<StratusStorage>, config: ExecutorConfig, task_rx: crossbeam_channel::Receiver<EvmTask>) {
let mut evm = Evm::new(storage, config);

// keep executing transactions until the channel is closed
while let Ok(task) = task_rx.recv() {
Expand All @@ -110,25 +107,26 @@ impl Evms {
let (evm_tx, evm_rx) = crossbeam_channel::unbounded::<EvmTask>();

for evm_index in 1..=num_evms {
let evm_task_name = format!("{}-{}", task_name, evm_index);
let evm_storage = Arc::clone(&storage);
let evm_config = config.clone();
let evm_rx = evm_rx.clone();
let task_name = format!("{}-{}", task_name, evm_index);
let thread_name = task_name.clone();
let thread_name = evm_task_name.clone();
spawn_thread(&thread_name, move || {
evm_loop(&task_name, evm_storage, chain_id, evm_rx);
evm_loop(&evm_task_name, evm_storage, evm_config, evm_rx);
});
}
evm_tx
};

let tx_parallel = match config.strategy {
let tx_parallel = match config.executor_strategy {
ExecutorStrategy::Serial => spawn_evms("evm-tx-unused", 1), // should not really be used if strategy is serial, but keep 1 for fallback
ExecutorStrategy::Paralell => spawn_evms("evm-tx-parallel", config.num_evms),
ExecutorStrategy::Paralell => spawn_evms("evm-tx-parallel", config.executor_evms),
};
let tx_serial = spawn_evms("evm-tx-serial", 1);
let tx_external = spawn_evms("evm-tx-external", 1);
let call_present = spawn_evms("evm-call-present", max(config.num_evms / 2, 1));
let call_past = spawn_evms("evm-call-past", max(config.num_evms / 4, 1));
let call_present = spawn_evms("evm-call-present", max(config.executor_evms / 2, 1));
let call_past = spawn_evms("evm-call-past", max(config.executor_evms / 4, 1));

Evms {
tx_parallel,
Expand Down Expand Up @@ -372,7 +370,7 @@ impl Executor {
// execute according to the strategy
const INFINITE_ATTEMPTS: usize = usize::MAX;

let tx_execution = match self.config.strategy {
let tx_execution = match self.config.executor_strategy {
// Executes transactions in serial mode:
// * Uses a Mutex, so a new transactions starts executing only after the previous one is executed and persisted.
// * Without a Mutex, conflict can happen because the next transactions starts executing before the previous one is saved.
Expand Down
25 changes: 18 additions & 7 deletions src/eth/executor/executor_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,27 @@ use crate::eth::storage::StratusStorage;
#[derive(Parser, DebugAsJson, Clone, serde::Serialize)]
pub struct ExecutorConfig {
/// Chain ID of the network.
#[arg(long = "chain-id", env = "CHAIN_ID")]
pub chain_id: u64,
#[arg(long = "executor-chain-id", alias = "chain-id", env = "EXECUTOR_CHAIN_ID")]
pub executor_chain_id: u64,

/// Number of EVM instances to run.
#[arg(long = "evms", env = "EVMS")]
pub num_evms: usize,
///
/// TODO: should be configured for each kind of EvmRoute instead of being a single value.
#[arg(long = "executor-evms", alias = "evms", env = "EXECUTOR_EVMS")]
pub executor_evms: usize,

/// EVM execution strategy.
#[arg(long = "strategy", env = "STRATEGY", default_value = "serial")]
pub strategy: ExecutorStrategy,
#[arg(long = "executor-strategy", alias = "strategy", env = "EXECUTOR_STRATEGY", default_value = "serial")]
pub executor_strategy: ExecutorStrategy,

/// Should reject contract transactions and calls to accounts that are not contracts?
#[arg(
long = "executor-reject-not-contract",
alias = "reject-not-contract",
env = "EXECUTOR_REJECT_NOT_CONTRACT",
default_value = "true"
)]
pub executor_reject_not_contract: bool,
}

impl ExecutorConfig {
Expand All @@ -30,7 +41,7 @@ impl ExecutorConfig {
/// Note: Should be called only after async runtime is initialized.
pub fn init(&self, storage: Arc<StratusStorage>, miner: Arc<Miner>) -> Arc<Executor> {
let mut config = self.clone();
config.num_evms = max(config.num_evms, 1);
config.executor_evms = max(config.executor_evms, 1);
tracing::info!(?config, "creating executor");

let executor = Executor::new(storage, miner, config);
Expand Down
9 changes: 2 additions & 7 deletions src/eth/primitives/execution_account_changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,8 @@ impl ExecutionAccountChanges {
}
}

/// Checks if the account was created by this transaction.
pub fn is_creation(&self) -> bool {
self.new_account
}

/// Checks if nonce, balance or bytecode were modified.
pub fn is_changed(&self) -> bool {
/// Checks if account nonce, balance or bytecode were modified.
pub fn is_account_modified(&self) -> bool {
self.nonce.is_modified() || self.balance.is_modified() || self.bytecode.is_modified()
}
}
8 changes: 6 additions & 2 deletions src/eth/primitives/stratus_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use jsonrpsee::types::error::INVALID_PARAMS_CODE;
use jsonrpsee::types::error::INVALID_REQUEST_CODE;
use jsonrpsee::types::error::SERVER_IS_BUSY_CODE;
use jsonrpsee::types::ErrorObjectOwned;
use revm::primitives::EVMError;
use strum::EnumProperty;

use crate::alias::JsonValue;
use crate::eth::primitives::Address;
use crate::eth::primitives::BlockFilter;
use crate::eth::primitives::BlockNumber;
use crate::eth::primitives::Bytes;
Expand Down Expand Up @@ -65,6 +65,10 @@ pub enum StratusError {
// -------------------------------------------------------------------------
// Transaction
// -------------------------------------------------------------------------
#[error("Account at {address} is not a contract.")]
#[strum(props(kind = "execution"))]
TransactionAccountNotContract { address: Address },

#[error("Transaction execution conflicts: {0:?}.")]
#[strum(props(kind = "execution"))]
TransactionConflict(Box<ExecutionConflicts>),
Expand All @@ -75,7 +79,7 @@ pub enum StratusError {

#[error("Failed to executed transaction in EVM: {0:?}.")]
#[strum(props(kind = "execution"))]
TransactionFailed(EVMError<anyhow::Error>), // split this in multiple errors
TransactionFailed(String), // split this in multiple errors

#[error("Failed to forward transaction to leader node.")]
#[strum(props(kind = "execution"))]
Expand Down
10 changes: 8 additions & 2 deletions src/eth/storage/permanent_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::eth::storage::redis::RedisPermanentStorage;
use crate::eth::storage::InMemoryPermanentStorage;
use crate::eth::storage::RocksPermanentStorage;
use crate::eth::storage::StoragePointInTime;
use crate::log_and_err;

/// Permanent (committed) storage operations.
pub trait PermanentStorage: Send + Sync + 'static {
Expand Down Expand Up @@ -84,7 +85,7 @@ pub struct PermanentStorageConfig {
pub perm_storage_kind: PermanentStorageKind,

/// Storage connection URL.
#[arg(long = "perm-storage-url", env = "PERM_STORAGE_URL")]
#[arg(long = "perm-storage-url", env = "PERM_STORAGE_URL", required_if_eq_any([("perm_storage_kind", "redis")]))]
pub perm_storage_url: Option<String>,

/// RocksDB storage path prefix to execute multiple local Stratus instances.
Expand Down Expand Up @@ -112,7 +113,12 @@ impl PermanentStorageConfig {
let perm: Box<dyn PermanentStorage> = match self.perm_storage_kind {
PermanentStorageKind::InMemory => Box::<InMemoryPermanentStorage>::default(),

PermanentStorageKind::Redis => Box::new(RedisPermanentStorage::new()?),
PermanentStorageKind::Redis => {
let Some(url) = self.perm_storage_url.as_deref() else {
return log_and_err!("redis connection url not provided when it was expected to be present");
};
Box::new(RedisPermanentStorage::new(url)?)
}

PermanentStorageKind::Rocks => {
let prefix = self.rocks_path_prefix.clone();
Expand Down
Loading

0 comments on commit f8df837

Please sign in to comment.