mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Using associated trait bound for db error (#8951)
This commit is contained in:
@ -4,6 +4,8 @@ use crate::{
|
||||
dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
|
||||
EthEvmConfig,
|
||||
};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{sync::Arc, vec, vec::Vec};
|
||||
use reth_chainspec::{ChainSpec, MAINNET};
|
||||
use reth_ethereum_consensus::validate_block_post_execution;
|
||||
use reth_evm::{
|
||||
@ -29,15 +31,11 @@ use reth_revm::{
|
||||
};
|
||||
use revm_primitives::{
|
||||
db::{Database, DatabaseCommit},
|
||||
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState,
|
||||
BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ResultAndState,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{sync::Arc, vec, vec::Vec};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::{fmt::Display, sync::Arc};
|
||||
/// Provides executors to execute regular ethereum blocks
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EthExecutorProvider<EvmConfig = EthEvmConfig> {
|
||||
@ -70,7 +68,7 @@ where
|
||||
{
|
||||
fn eth_executor<DB>(&self, db: DB) -> EthBlockExecutor<EvmConfig, DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError>>,
|
||||
{
|
||||
EthBlockExecutor::new(
|
||||
self.chain_spec.clone(),
|
||||
@ -84,20 +82,22 @@ impl<EvmConfig> BlockExecutorProvider for EthExecutorProvider<EvmConfig>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
type Executor<DB: Database<Error = ProviderError>> = EthBlockExecutor<EvmConfig, DB>;
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + Display>> =
|
||||
EthBlockExecutor<EvmConfig, DB>;
|
||||
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>> = EthBatchExecutor<EvmConfig, DB>;
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> =
|
||||
EthBatchExecutor<EvmConfig, DB>;
|
||||
|
||||
fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
self.eth_executor(db)
|
||||
}
|
||||
|
||||
fn batch_executor<DB>(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
let executor = self.eth_executor(db);
|
||||
EthBatchExecutor {
|
||||
@ -145,7 +145,8 @@ where
|
||||
mut evm: Evm<'_, Ext, &mut State<DB>>,
|
||||
) -> Result<EthExecuteOutput, BlockExecutionError>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database,
|
||||
DB::Error: Into<ProviderError> + std::fmt::Display,
|
||||
{
|
||||
// apply pre execution changes
|
||||
apply_beacon_root_contract_call(
|
||||
@ -182,10 +183,17 @@ where
|
||||
|
||||
// Execute transaction.
|
||||
let ResultAndState { result, state } = evm.transact().map_err(move |err| {
|
||||
let new_err = match err {
|
||||
EVMError::Transaction(e) => EVMError::Transaction(e),
|
||||
EVMError::Header(e) => EVMError::Header(e),
|
||||
EVMError::Database(e) => EVMError::Database(e.into()),
|
||||
EVMError::Custom(e) => EVMError::Custom(e),
|
||||
EVMError::Precompile(e) => EVMError::Precompile(e),
|
||||
};
|
||||
// Ensure hash is calculated for error log, if not already done
|
||||
BlockValidationError::EVM {
|
||||
hash: transaction.recalculate_hash(),
|
||||
error: err.into(),
|
||||
error: Box::new(new_err),
|
||||
}
|
||||
})?;
|
||||
evm.db_mut().commit(state);
|
||||
@ -260,7 +268,7 @@ impl<EvmConfig, DB> EthBlockExecutor<EvmConfig, DB> {
|
||||
impl<EvmConfig, DB> EthBlockExecutor<EvmConfig, DB>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
/// Configures a new evm configuration and block environment for the given block.
|
||||
///
|
||||
@ -358,7 +366,7 @@ where
|
||||
impl<EvmConfig, DB> Executor<DB> for EthBlockExecutor<EvmConfig, DB>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
|
||||
type Output = BlockExecutionOutput<Receipt>;
|
||||
@ -408,7 +416,7 @@ impl<EvmConfig, DB> EthBatchExecutor<EvmConfig, DB> {
|
||||
impl<EvmConfig, DB> BatchExecutor<DB> for EthBatchExecutor<EvmConfig, DB>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
|
||||
type Output = ExecutionOutcome;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
//! Helper type that represents one of two possible executor types
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::execute::{
|
||||
BatchExecutor, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, Executor,
|
||||
};
|
||||
@ -18,13 +20,15 @@ where
|
||||
A: BlockExecutorProvider,
|
||||
B: BlockExecutorProvider,
|
||||
{
|
||||
type Executor<DB: Database<Error = ProviderError>> = Either<A::Executor<DB>, B::Executor<DB>>;
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>> =
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + Display>> =
|
||||
Either<A::Executor<DB>, B::Executor<DB>>;
|
||||
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> =
|
||||
Either<A::BatchExecutor<DB>, B::BatchExecutor<DB>>;
|
||||
|
||||
fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
match self {
|
||||
Self::Left(a) => Either::Left(a.executor(db)),
|
||||
@ -34,7 +38,7 @@ where
|
||||
|
||||
fn batch_executor<DB>(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
match self {
|
||||
Self::Left(a) => Either::Left(a.batch_executor(db, prune_modes)),
|
||||
@ -57,7 +61,7 @@ where
|
||||
Output = BlockExecutionOutput<Receipt>,
|
||||
Error = BlockExecutionError,
|
||||
>,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
|
||||
type Output = BlockExecutionOutput<Receipt>;
|
||||
@ -85,7 +89,7 @@ where
|
||||
Output = ExecutionOutcome,
|
||||
Error = BlockExecutionError,
|
||||
>,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
|
||||
type Output = ExecutionOutcome;
|
||||
|
||||
@ -5,6 +5,7 @@ use reth_primitives::{BlockNumber, BlockWithSenders, Receipt, Request, U256};
|
||||
use reth_prune_types::PruneModes;
|
||||
use revm::db::BundleState;
|
||||
use revm_primitives::db::Database;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
@ -142,7 +143,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static {
|
||||
///
|
||||
/// It is not expected to validate the state trie root, this must be done by the caller using
|
||||
/// the returned state.
|
||||
type Executor<DB: Database<Error = ProviderError>>: for<'a> Executor<
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + Display>>: for<'a> Executor<
|
||||
DB,
|
||||
Input<'a> = BlockExecutionInput<'a, BlockWithSenders>,
|
||||
Output = BlockExecutionOutput<Receipt>,
|
||||
@ -150,7 +151,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static {
|
||||
>;
|
||||
|
||||
/// An executor that can execute a batch of blocks given a database.
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>>: for<'a> BatchExecutor<
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>>: for<'a> BatchExecutor<
|
||||
DB,
|
||||
Input<'a> = BlockExecutionInput<'a, BlockWithSenders>,
|
||||
Output = ExecutionOutcome,
|
||||
@ -162,7 +163,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static {
|
||||
/// This is used to execute a single block and get the changed state.
|
||||
fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>;
|
||||
DB: Database<Error: Into<ProviderError> + Display>;
|
||||
|
||||
/// Creates a new batch executor with the given database and pruning modes.
|
||||
///
|
||||
@ -173,7 +174,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static {
|
||||
/// execution.
|
||||
fn batch_executor<DB>(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>;
|
||||
DB: Database<Error: Into<ProviderError> + Display>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -187,19 +188,19 @@ mod tests {
|
||||
struct TestExecutorProvider;
|
||||
|
||||
impl BlockExecutorProvider for TestExecutorProvider {
|
||||
type Executor<DB: Database<Error = ProviderError>> = TestExecutor<DB>;
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>> = TestExecutor<DB>;
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + Display>> = TestExecutor<DB>;
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> = TestExecutor<DB>;
|
||||
|
||||
fn executor<DB>(&self, _db: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
TestExecutor(PhantomData)
|
||||
}
|
||||
|
||||
fn batch_executor<DB>(&self, _db: DB, _prune_modes: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
TestExecutor(PhantomData)
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
//! A no operation block executor implementation.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use reth_execution_errors::BlockExecutionError;
|
||||
use reth_execution_types::ExecutionOutcome;
|
||||
use reth_primitives::{BlockNumber, BlockWithSenders, Receipt};
|
||||
@ -19,20 +21,20 @@ const UNAVAILABLE_FOR_NOOP: &str = "execution unavailable for noop";
|
||||
pub struct NoopBlockExecutorProvider;
|
||||
|
||||
impl BlockExecutorProvider for NoopBlockExecutorProvider {
|
||||
type Executor<DB: Database<Error = ProviderError>> = Self;
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + Display>> = Self;
|
||||
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>> = Self;
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> = Self;
|
||||
|
||||
fn executor<DB>(&self, _: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
Self
|
||||
}
|
||||
|
||||
fn batch_executor<DB>(&self, _: DB, _: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
Self
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ use reth_primitives::{BlockNumber, BlockWithSenders, Receipt};
|
||||
use reth_prune_types::PruneModes;
|
||||
use reth_storage_errors::provider::ProviderError;
|
||||
use revm_primitives::db::Database;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt::Display, sync::Arc};
|
||||
|
||||
/// A [`BlockExecutorProvider`] that returns mocked execution results.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -26,20 +26,20 @@ impl MockExecutorProvider {
|
||||
}
|
||||
|
||||
impl BlockExecutorProvider for MockExecutorProvider {
|
||||
type Executor<DB: Database<Error = ProviderError>> = Self;
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + Display>> = Self;
|
||||
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>> = Self;
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + Display>> = Self;
|
||||
|
||||
fn executor<DB>(&self, _: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn batch_executor<DB>(&self, _: DB, _: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + Display>,
|
||||
{
|
||||
self.clone()
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ use reth_revm::{
|
||||
};
|
||||
use revm_primitives::{
|
||||
db::{Database, DatabaseCommit},
|
||||
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState,
|
||||
BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ResultAndState,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::trace;
|
||||
@ -55,7 +55,7 @@ where
|
||||
{
|
||||
fn op_executor<DB>(&self, db: DB) -> OpBlockExecutor<EvmConfig, DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
OpBlockExecutor::new(
|
||||
self.chain_spec.clone(),
|
||||
@ -69,19 +69,21 @@ impl<EvmConfig> BlockExecutorProvider for OpExecutorProvider<EvmConfig>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
type Executor<DB: Database<Error = ProviderError>> = OpBlockExecutor<EvmConfig, DB>;
|
||||
type Executor<DB: Database<Error: Into<ProviderError> + std::fmt::Display>> =
|
||||
OpBlockExecutor<EvmConfig, DB>;
|
||||
|
||||
type BatchExecutor<DB: Database<Error = ProviderError>> = OpBatchExecutor<EvmConfig, DB>;
|
||||
type BatchExecutor<DB: Database<Error: Into<ProviderError> + std::fmt::Display>> =
|
||||
OpBatchExecutor<EvmConfig, DB>;
|
||||
fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
self.op_executor(db)
|
||||
}
|
||||
|
||||
fn batch_executor<DB>(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor<DB>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
let executor = self.op_executor(db);
|
||||
OpBatchExecutor {
|
||||
@ -118,7 +120,7 @@ where
|
||||
mut evm: Evm<'_, Ext, &mut State<DB>>,
|
||||
) -> Result<(Vec<Receipt>, u64), BlockExecutionError>
|
||||
where
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
// apply pre execution changes
|
||||
apply_beacon_root_contract_call(
|
||||
@ -179,10 +181,17 @@ where
|
||||
|
||||
// Execute transaction.
|
||||
let ResultAndState { result, state } = evm.transact().map_err(move |err| {
|
||||
let new_err = match err {
|
||||
EVMError::Transaction(e) => EVMError::Transaction(e),
|
||||
EVMError::Header(e) => EVMError::Header(e),
|
||||
EVMError::Database(e) => EVMError::Database(e.into()),
|
||||
EVMError::Custom(e) => EVMError::Custom(e),
|
||||
EVMError::Precompile(e) => EVMError::Precompile(e),
|
||||
};
|
||||
// Ensure hash is calculated for error log, if not already done
|
||||
BlockValidationError::EVM {
|
||||
hash: transaction.recalculate_hash(),
|
||||
error: err.into(),
|
||||
error: Box::new(new_err),
|
||||
}
|
||||
})?;
|
||||
|
||||
@ -255,7 +264,7 @@ impl<EvmConfig, DB> OpBlockExecutor<EvmConfig, DB> {
|
||||
impl<EvmConfig, DB> OpBlockExecutor<EvmConfig, DB>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
/// Configures a new evm configuration and block environment for the given block.
|
||||
///
|
||||
@ -337,7 +346,7 @@ where
|
||||
impl<EvmConfig, DB> Executor<DB> for OpBlockExecutor<EvmConfig, DB>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
|
||||
type Output = BlockExecutionOutput<Receipt>;
|
||||
@ -394,7 +403,7 @@ impl<EvmConfig, DB> OpBatchExecutor<EvmConfig, DB> {
|
||||
impl<EvmConfig, DB> BatchExecutor<DB> for OpBatchExecutor<EvmConfig, DB>
|
||||
where
|
||||
EvmConfig: ConfigureEvm,
|
||||
DB: Database<Error = ProviderError>,
|
||||
DB: Database<Error: Into<ProviderError> + std::fmt::Display>,
|
||||
{
|
||||
type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>;
|
||||
type Output = ExecutionOutcome;
|
||||
|
||||
@ -86,7 +86,7 @@ pub fn post_block_balance_increments(
|
||||
///
|
||||
/// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935
|
||||
#[inline]
|
||||
pub fn apply_blockhashes_update<DB: Database<Error = ProviderError> + DatabaseCommit>(
|
||||
pub fn apply_blockhashes_update<DB: Database<Error: Into<ProviderError>> + DatabaseCommit>(
|
||||
db: &mut DB,
|
||||
chain_spec: &ChainSpec,
|
||||
block_timestamp: u64,
|
||||
@ -108,7 +108,7 @@ where
|
||||
// nonce of 1, so it does not get deleted.
|
||||
let mut account: Account = db
|
||||
.basic(HISTORY_STORAGE_ADDRESS)
|
||||
.map_err(BlockValidationError::BlockHashAccountLoadingFailed)?
|
||||
.map_err(|err| BlockValidationError::BlockHashAccountLoadingFailed(err.into()))?
|
||||
.unwrap_or_else(|| AccountInfo {
|
||||
nonce: 1,
|
||||
code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
|
||||
@ -132,7 +132,7 @@ where
|
||||
///
|
||||
/// This calculates the correct storage slot in the `BLOCKHASH` history storage address, fetches the
|
||||
/// blockhash and creates a [`EvmStorageSlot`] with appropriate previous and new values.
|
||||
fn eip2935_block_hash_slot<DB: Database<Error = ProviderError>>(
|
||||
fn eip2935_block_hash_slot<DB: Database<Error: Into<ProviderError>>>(
|
||||
db: &mut DB,
|
||||
block_number: u64,
|
||||
block_hash: B256,
|
||||
@ -140,7 +140,7 @@ fn eip2935_block_hash_slot<DB: Database<Error = ProviderError>>(
|
||||
let slot = U256::from(block_number % BLOCKHASH_SERVE_WINDOW as u64);
|
||||
let current_hash = db
|
||||
.storage(HISTORY_STORAGE_ADDRESS, slot)
|
||||
.map_err(BlockValidationError::BlockHashAccountLoadingFailed)?;
|
||||
.map_err(|err| BlockValidationError::BlockHashAccountLoadingFailed(err.into()))?;
|
||||
|
||||
Ok((slot, EvmStorageSlot::new_changed(current_hash, block_hash.into())))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user