Add the following line to your Cargo.toml
[dependencies]
grevm = { git = "https://github.com/Galxe/grevm.git", branch = "main" }Using Grevm is straightforward and efficient. You just need to create a GrevmScheduler and then call GrevmScheduler::parallel_execute to get the results of parallel transaction execution. That’s all there is to it—simple and streamlined, making the process remarkably easy.
grevm::scheduler
impl<DB> GrevmScheduler<DB>
where
DB: DatabaseRef + Send + Sync + 'static,
DB::Error: Send + Sync,
pub fn new(spec_id: SpecId, env: Env, db: DB, txs: Vec<TxEnv>) -> Self
pub fn parallel_execute(mut self) -> Result<ExecuteOutput, GrevmError<DB::Error>In the code above, the generic constraint for DB is relatively relaxed; DB only needs to implement DatabaseRef, a read-only database trait provided by revm for Ethereum Virtual Machine interactions. Because this is a read-only interface, it naturally satisfies the Send and Sync traits. However, the 'static constraint, which prevents DB from containing reference types (&), may not be met by all DB implementations. This 'static requirement is introduced by tokio in Grevm, as tokio requires that objects have lifetimes independent of any references.
Grevm addresses this limitation by ensuring that internal object lifetimes do not exceed its own, offering an unsafe alternative method, new_grevm_scheduler, for creating GrevmScheduler. This method allows developers to bypass the 'static constraint if they are confident in managing lifetimes safely.
pub fn new_grevm_scheduler<DB>(
spec_id: SpecId,
env: Env,
db: DB,
txs: Arc<Vec<TxEnv>>,
state: Option<Box<State>>,
) -> GrevmScheduler<DatabaseWrapper<DB::Error>>
where
DB: DatabaseRef + Send + Sync,
DB::Error: Clone + Send + Sync + 'static,Reth provides the BlockExecutorProvider trait for creating Executor for executing a single block in live sync and BatchExecutor for executing a batch of blocks in historical sync. However, the associated type constraints in BlockExecutorProvider do not meet the DB constraints required for parallel block execution. To avoid imposing constraints on the DB satisfying ParallelDatabase on all structs that implement the BlockExecutorProvider trait in Reth, we have designed the ParallelExecutorProvider trait for creating Executor and BatchExecutor that satisfy the ParallelDatabase constraint. We have also extended the BlockExecutorProvider trait with a try_into_parallel_provider method. If a BlockExecutorProvider needs to support parallel execution, the implementation of try_into_parallel_provider is required to return a struct that implements the ParallelExecutorProvider trait; otherwise, it defaults to return None. Through this design, without imposing stronger constraints on the DB in the BlockExecutorProvider trait, developers are provided with the optional ability to extend BlockExecutorProvider to produce parallel block executors. For EthExecutorProvider, which provides executors to execute regular ethereum blocks, we have implemented GrevmExecutorProvider to extend its capability for parallel execution of ethereum blocks. GrevmExecutorProvider can create GrevmBlockExecutor and GrevmBatchExecutor that execute blocks using Grevm parallel executor.
// crates/evm/src/execute.rs
pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static {
...
type ParallelProvider<'a>: ParallelExecutorProvider;
/// Try to create a parallel provider from this provider.
/// Return None if the block provider does not support parallel execution.
fn try_into_parallel_provider(&self) -> Option<Self::ParallelProvider<'_>> {
None
}
}
/// A type that can create a new parallel executor for block execution.
pub trait ParallelExecutorProvider {
type Executor<DB: ParallelDatabase>: for<'a> Executor<
DB,
Input<'a> = BlockExecutionInput<'a, BlockWithSenders>,
Output = BlockExecutionOutput<Receipt>,
Error = BlockExecutionError,
>;
type BatchExecutor<DB: ParallelDatabase>: for<'a> BatchExecutor<
DB,
Input<'a> = BlockExecutionInput<'a, BlockWithSenders>,
Output = ExecutionOutcome,
Error = BlockExecutionError,
>;
fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
where
DB: ParallelDatabase;
fn batch_executor<DB>(&self, db: DB) -> Self::BatchExecutor<DB>
where
DB: ParallelDatabase;
}When there is a need to execute a block, we expect developers to call BlockExecutorProvider::try_into_parallel_provider and decide whether to use the parallel execution solution provided by BlockExecutorProvider based on the returned value, or resort to sequential block execution if there is no corresponding parallel execution implementation in BlockExecutorProvider. By making such calls, we can also control the return value of try_into_parallel_provider by setting the environment variable EVM_DISABLE_GREVM, reverting to Reth's native executor based on revm for block execution. This feature is handy for conducting comparative experiments.
// crates/blockchain-tree/src/chain.rs
// AppendableChain::validate_and_execute
let state = if let Some(parallel_provider) =
externals.executor_factory.try_into_parallel_provider()
{
parallel_provider.executor(db).execute((&block, U256::MAX).into())?
} else {
externals.executor_factory.executor(db).execute((&block, U256::MAX).into())?
};// crates/blockchain-tree/src/chain.rs
// ExecutionStage::execute
let mut executor =
if let Some(parallel_provider) = self.executor_provider.try_into_parallel_provider() {
EitherBatchExecutor::Parallel(parallel_provider.batch_executor(Arc::new(db)))
} else {
EitherBatchExecutor::Sequential(self.executor_provider.batch_executor(db))
};The integration code for connecting Grevm to Reth is already complete. You can check it out here: Introduce Grevm to Reth
To monitor and observe execution performance, Grevm uses the metrics crate from crates.io. Users can integrate the Prometheus exporter within their code to expose metrics data, which can then be configured in Grafana for real-time monitoring and visualization. Below is a table that provides the names and descriptions of various metric indicators:
| Metric Name | Description |
|---|---|
| grevm.parallel_round_calls | Number of times parallel execution is called. |
| grevm.sequential_execute_calls | Number of times sequential execution is called. |
| grevm.total_tx_cnt | Total number of transactions. |
| grevm.parallel_tx_cnt | Number of transactions executed in parallel. |
| grevm.sequential_tx_cnt | Number of transactions executed sequentially. |
| grevm.finality_tx_cnt | Number of transactions that encountered conflicts. |
| grevm.conflict_tx_cnt | Number of transactions that reached finality. |
| grevm.unconfirmed_tx_cnt | Number of transactions that are unconfirmed. |
| grevm.reusable_tx_cnt | Number of reusable transactions. |
| grevm.skip_validation_cnt | Number of transactions that skip validation |
| grevm.concurrent_partition_num | Number of concurrent partitions. |
| grevm.partition_execution_time_diff | Execution time difference between partitions(in nanoseconds). |
| grevm.partition_num_tx_diff | Number of transactions difference between partitions. |
| grevm.parse_hints_time | Time taken to parse execution hints(in nanoseconds). |
| grevm.partition_tx_time | Time taken to partition transactions(in nanoseconds). |
| grevm.parallel_execute_time | Time taken to validate transactions(in nanoseconds). |
| grevm.validate_time | Time taken to execute(in nanoseconds). |
| grevm.merge_write_set_time | Time taken to merge write set(in nanoseconds). |
| grevm.commit_transition_time | Time taken to commit transition(in nanoseconds). |
| grevm.build_output_time | Time taken to build output(in nanoseconds). |