mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 02:21:04 +00:00
Add BEEFY capabilities to Westend and Kusama (#7591)
* runtime: add BEEFY and MMR to Westend Signed-off-by: Adrian Catangiu <adrian@parity.io> * runtime: add BEEFY and MMR to Kusama Signed-off-by: Adrian Catangiu <adrian@parity.io> * node/service: enable BEEFY for Westend and Kusama Signed-off-by: Adrian Catangiu <adrian@parity.io> * node/service: regenerate genesis keys for westend-native and kusama-native Since these keys are only used for development/local chains, also publish the secret seeds used to generate the public keys, so that developers can recover/generate the private key pairs if needed. Signed-off-by: Adrian Catangiu <adrian@parity.io> * runtime: add session keys migration to add BEEFY to Westend and Kusama * runtime: fix migration * fix try-runtime build * cargo fmt * fix parachains slashing benchmark * address review comments * Apply suggestions from code review Co-authored-by: Bastian Köcher <git@kchr.de> * runtime: fix session keys migration --------- Signed-off-by: Adrian Catangiu <adrian@parity.io> Co-authored-by: parity-processbot <> Co-authored-by: Bastian Köcher <git@kchr.de>
This commit is contained in:
@@ -19,9 +19,11 @@ smallvec = "1.8.0"
|
||||
authority-discovery-primitives = { package = "sp-authority-discovery", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
babe-primitives = { package = "sp-consensus-babe", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
beefy-primitives = { package = "sp-consensus-beefy", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
binary-merkle-tree = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
inherents = { package = "sp-inherents", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
offchain-primitives = { package = "sp-offchain", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
@@ -45,6 +47,8 @@ pallet-authorship = { git = "https://github.com/paritytech/substrate", branch =
|
||||
pallet-babe = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-bags-list = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-beefy = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-collective = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-democracy = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-elections-phragmen = { package = "pallet-elections-phragmen", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
@@ -56,6 +60,7 @@ pallet-im-online = { git = "https://github.com/paritytech/substrate", branch = "
|
||||
pallet-indices = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-membership = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-message-queue = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-multisig = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-nomination-pools = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
pallet-offences = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
@@ -134,6 +139,8 @@ std = [
|
||||
"pallet-authority-discovery/std",
|
||||
"pallet-authorship/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-beefy/std",
|
||||
"pallet-beefy-mmr/std",
|
||||
"pallet-transaction-payment/std",
|
||||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
"pallet-collective/std",
|
||||
@@ -147,6 +154,7 @@ std = [
|
||||
"pallet-indices/std",
|
||||
"pallet-membership/std",
|
||||
"pallet-message-queue/std",
|
||||
"pallet-mmr/std",
|
||||
"beefy-primitives/std",
|
||||
"pallet-multisig/std",
|
||||
"pallet-nomination-pools/std",
|
||||
@@ -170,6 +178,7 @@ std = [
|
||||
"pallet-babe/std",
|
||||
"pallet-bags-list/std",
|
||||
"frame-executive/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-mmr-primitives/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
@@ -243,6 +252,8 @@ try-runtime = [
|
||||
"pallet-authority-discovery/try-runtime",
|
||||
"pallet-authorship/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-beefy/try-runtime",
|
||||
"pallet-beefy-mmr/try-runtime",
|
||||
"pallet-transaction-payment/try-runtime",
|
||||
"pallet-collective/try-runtime",
|
||||
"pallet-elections-phragmen/try-runtime",
|
||||
@@ -255,6 +266,7 @@ try-runtime = [
|
||||
"pallet-indices/try-runtime",
|
||||
"pallet-membership/try-runtime",
|
||||
"pallet-message-queue/try-runtime",
|
||||
"pallet-mmr/try-runtime",
|
||||
"pallet-multisig/try-runtime",
|
||||
"pallet-nomination-pools/try-runtime",
|
||||
"pallet-offences/try-runtime",
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
//! The Westend runtime. This can be compiled with `#[no_std]`, ready for Wasm.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
|
||||
#![recursion_limit = "256"]
|
||||
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 512.
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId;
|
||||
use beefy_primitives::ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature};
|
||||
use beefy_primitives::{
|
||||
ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature},
|
||||
mmr::{BeefyDataProvider, MmrLeafVersion},
|
||||
};
|
||||
use frame_election_provider_support::{bounds::ElectionBoundsBuilder, onchain, SequentialPhragmen};
|
||||
use frame_support::{
|
||||
construct_runtime, parameter_types,
|
||||
@@ -64,15 +67,14 @@ use runtime_parachains::{
|
||||
shared as parachains_shared,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{OpaqueMetadata, RuntimeDebug};
|
||||
use sp_mmr_primitives as mmr;
|
||||
use sp_core::{OpaqueMetadata, RuntimeDebug, H256};
|
||||
use sp_runtime::{
|
||||
create_runtime_str,
|
||||
curve::PiecewiseLinear,
|
||||
generic, impl_opaque_keys,
|
||||
traits::{
|
||||
AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Extrinsic as ExtrinsicT,
|
||||
OpaqueKeys, SaturatedConversion, Verify,
|
||||
Keccak256, OpaqueKeys, SaturatedConversion, Verify,
|
||||
},
|
||||
transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity},
|
||||
ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill,
|
||||
@@ -271,6 +273,81 @@ impl pallet_balances::Config for Runtime {
|
||||
type MaxFreezes = ConstU32<0>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BeefySetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get();
|
||||
}
|
||||
|
||||
impl pallet_beefy::Config for Runtime {
|
||||
type BeefyId = BeefyId;
|
||||
type MaxAuthorities = MaxAuthorities;
|
||||
type MaxNominators = MaxNominatorRewardedPerValidator;
|
||||
type MaxSetIdSessionEntries = BeefySetIdSessionEntries;
|
||||
type OnNewValidatorSet = BeefyMmrLeaf;
|
||||
type WeightInfo = ();
|
||||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
|
||||
type EquivocationReportSystem =
|
||||
pallet_beefy::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
}
|
||||
|
||||
impl pallet_mmr::Config for Runtime {
|
||||
const INDEXING_PREFIX: &'static [u8] = mmr::INDEXING_PREFIX;
|
||||
type Hashing = Keccak256;
|
||||
type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Runtime>;
|
||||
type WeightInfo = ();
|
||||
type LeafData = pallet_beefy_mmr::Pallet<Runtime>;
|
||||
}
|
||||
|
||||
/// MMR helper types.
|
||||
mod mmr {
|
||||
use super::Runtime;
|
||||
pub use pallet_mmr::primitives::*;
|
||||
|
||||
pub type Leaf = <<Runtime as pallet_mmr::Config>::LeafData as LeafDataProvider>::LeafData;
|
||||
pub type Hashing = <Runtime as pallet_mmr::Config>::Hashing;
|
||||
pub type Hash = <Hashing as sp_runtime::traits::Hash>::Output;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
/// Version of the produced MMR leaf.
|
||||
///
|
||||
/// The version consists of two parts;
|
||||
/// - `major` (3 bits)
|
||||
/// - `minor` (5 bits)
|
||||
///
|
||||
/// `major` should be updated only if decoding the previous MMR Leaf format from the payload
|
||||
/// is not possible (i.e. backward incompatible change).
|
||||
/// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE
|
||||
/// encoding does not prevent old leafs from being decoded.
|
||||
///
|
||||
/// Hence we expect `major` to be changed really rarely (think never).
|
||||
/// See [`MmrLeafVersion`] type documentation for more details.
|
||||
pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0);
|
||||
}
|
||||
|
||||
/// A BEEFY data provider that merkelizes all the parachain heads at the current block
|
||||
/// (sorted by their parachain id).
|
||||
pub struct ParaHeadsRootProvider;
|
||||
impl BeefyDataProvider<H256> for ParaHeadsRootProvider {
|
||||
fn extra_data() -> H256 {
|
||||
let mut para_heads: Vec<(u32, Vec<u8>)> = Paras::parachains()
|
||||
.into_iter()
|
||||
.filter_map(|id| Paras::para_head(&id).map(|head| (id.into(), head.0)))
|
||||
.collect();
|
||||
para_heads.sort_by_key(|k| k.0);
|
||||
binary_merkle_tree::merkle_root::<mmr::Hashing, _>(
|
||||
para_heads.into_iter().map(|pair| pair.encode()),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_beefy_mmr::Config for Runtime {
|
||||
type LeafVersion = LeafVersion;
|
||||
type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum;
|
||||
type LeafExtra = H256;
|
||||
type BeefyDataProvider = ParaHeadsRootProvider;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
|
||||
/// This value increases the priority of `Operational` transactions by adding
|
||||
@@ -307,6 +384,17 @@ parameter_types! {
|
||||
pub const Offset: BlockNumber = 0;
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct OldSessionKeys {
|
||||
pub grandpa: Grandpa,
|
||||
pub babe: Babe,
|
||||
pub im_online: ImOnline,
|
||||
pub para_validator: Initializer,
|
||||
pub para_assignment: ParaSessionInfo,
|
||||
pub authority_discovery: AuthorityDiscovery,
|
||||
}
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct SessionKeys {
|
||||
pub grandpa: Grandpa,
|
||||
@@ -315,6 +403,33 @@ impl_opaque_keys! {
|
||||
pub para_validator: Initializer,
|
||||
pub para_assignment: ParaSessionInfo,
|
||||
pub authority_discovery: AuthorityDiscovery,
|
||||
pub beefy: Beefy,
|
||||
}
|
||||
}
|
||||
|
||||
// remove this when removing `OldSessionKeys`
|
||||
fn transform_session_keys(v: AccountId, old: OldSessionKeys) -> SessionKeys {
|
||||
SessionKeys {
|
||||
grandpa: old.grandpa,
|
||||
babe: old.babe,
|
||||
im_online: old.im_online,
|
||||
para_validator: old.para_validator,
|
||||
para_assignment: old.para_assignment,
|
||||
authority_discovery: old.authority_discovery,
|
||||
beefy: {
|
||||
// From Session::upgrade_keys():
|
||||
//
|
||||
// Care should be taken that the raw versions of the
|
||||
// added keys are unique for every `ValidatorId, KeyTypeId` combination.
|
||||
// This is an invariant that the session pallet typically maintains internally.
|
||||
//
|
||||
// So, produce a dummy value that's unique for the `ValidatorId, KeyTypeId` combination.
|
||||
let mut id: BeefyId = sp_application_crypto::ecdsa::Public::from_raw([0u8; 33]).into();
|
||||
let id_raw: &mut [u8] = id.as_mut();
|
||||
id_raw[1..33].copy_from_slice(v.as_ref());
|
||||
id_raw[0..4].copy_from_slice(b"beef");
|
||||
id
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,6 +1282,14 @@ construct_runtime! {
|
||||
Staking: pallet_staking::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
|
||||
Offences: pallet_offences::{Pallet, Storage, Event} = 7,
|
||||
Historical: session_historical::{Pallet} = 27,
|
||||
|
||||
// BEEFY Bridges support.
|
||||
Beefy: pallet_beefy::{Pallet, Call, Storage, Config<T>, ValidateUnsigned} = 200,
|
||||
// MMR leaf construction must be before session in order to have leaf contents
|
||||
// refer to block<N-1> consistently. see substrate issue #11797 for details.
|
||||
Mmr: pallet_mmr::{Pallet, Storage} = 201,
|
||||
BeefyMmrLeaf: pallet_beefy_mmr::{Pallet, Storage} = 202,
|
||||
|
||||
Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>} = 8,
|
||||
Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config<T>, Event, ValidateUnsigned} = 10,
|
||||
ImOnline: pallet_im_online::{Pallet, Call, Storage, Event<T>, ValidateUnsigned, Config<T>} = 11,
|
||||
@@ -1284,6 +1407,16 @@ pub type Migrations = migrations::Unreleased;
|
||||
pub mod migrations {
|
||||
use super::*;
|
||||
|
||||
/// Upgrade Session keys to include BEEFY key.
|
||||
/// When this is removed, should also remove `OldSessionKeys`.
|
||||
pub struct UpgradeSessionKeys;
|
||||
impl frame_support::traits::OnRuntimeUpgrade for UpgradeSessionKeys {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
Session::upgrade_keys::<OldSessionKeys, _>(transform_session_keys);
|
||||
Perbill::from_percent(50) * BlockWeights::get().max_block
|
||||
}
|
||||
}
|
||||
|
||||
/// Unreleased migrations. Add new ones here:
|
||||
pub type Unreleased = (
|
||||
pallet_im_online::migration::v1::Migration<Runtime>,
|
||||
@@ -1291,6 +1424,7 @@ pub mod migrations {
|
||||
assigned_slots::migration::v1::VersionCheckedMigrateToV1<Runtime>,
|
||||
parachains_scheduler::migration::v1::MigrateToV1<Runtime>,
|
||||
parachains_configuration::migration::v8::MigrateToV8<Runtime>,
|
||||
UpgradeSessionKeys,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1561,64 +1695,94 @@ sp_api::impl_runtime_apis! {
|
||||
|
||||
impl beefy_primitives::BeefyApi<Block, BeefyId> for Runtime {
|
||||
fn beefy_genesis() -> Option<BlockNumber> {
|
||||
// dummy implementation due to lack of BEEFY pallet.
|
||||
None
|
||||
Beefy::genesis_block()
|
||||
}
|
||||
|
||||
fn validator_set() -> Option<beefy_primitives::ValidatorSet<BeefyId>> {
|
||||
// dummy implementation due to lack of BEEFY pallet.
|
||||
None
|
||||
Beefy::validator_set()
|
||||
}
|
||||
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
_equivocation_proof: beefy_primitives::EquivocationProof<
|
||||
equivocation_proof: beefy_primitives::EquivocationProof<
|
||||
BlockNumber,
|
||||
BeefyId,
|
||||
BeefySignature,
|
||||
>,
|
||||
_key_owner_proof: beefy_primitives::OpaqueKeyOwnershipProof,
|
||||
key_owner_proof: beefy_primitives::OpaqueKeyOwnershipProof,
|
||||
) -> Option<()> {
|
||||
None
|
||||
let key_owner_proof = key_owner_proof.decode()?;
|
||||
|
||||
Beefy::submit_unsigned_equivocation_report(
|
||||
equivocation_proof,
|
||||
key_owner_proof,
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_key_ownership_proof(
|
||||
_set_id: beefy_primitives::ValidatorSetId,
|
||||
_authority_id: BeefyId,
|
||||
authority_id: BeefyId,
|
||||
) -> Option<beefy_primitives::OpaqueKeyOwnershipProof> {
|
||||
None
|
||||
use parity_scale_codec::Encode;
|
||||
|
||||
Historical::prove((beefy_primitives::KEY_TYPE, authority_id))
|
||||
.map(|p| p.encode())
|
||||
.map(beefy_primitives::OpaqueKeyOwnershipProof::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl mmr::MmrApi<Block, Hash, BlockNumber> for Runtime {
|
||||
fn mmr_root() -> Result<Hash, mmr::Error> {
|
||||
Err(mmr::Error::PalletNotIncluded)
|
||||
fn mmr_root() -> Result<mmr::Hash, mmr::Error> {
|
||||
Ok(Mmr::mmr_root())
|
||||
}
|
||||
|
||||
fn mmr_leaf_count() -> Result<mmr::LeafIndex, mmr::Error> {
|
||||
Err(mmr::Error::PalletNotIncluded)
|
||||
Ok(Mmr::mmr_leaves())
|
||||
}
|
||||
|
||||
fn generate_proof(
|
||||
_block_numbers: Vec<BlockNumber>,
|
||||
_best_known_block_number: Option<BlockNumber>,
|
||||
) -> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::Proof<Hash>), mmr::Error> {
|
||||
Err(mmr::Error::PalletNotIncluded)
|
||||
block_numbers: Vec<BlockNumber>,
|
||||
best_known_block_number: Option<BlockNumber>,
|
||||
) -> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::Proof<mmr::Hash>), mmr::Error> {
|
||||
Mmr::generate_proof(block_numbers, best_known_block_number).map(
|
||||
|(leaves, proof)| {
|
||||
(
|
||||
leaves
|
||||
.into_iter()
|
||||
.map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf))
|
||||
.collect(),
|
||||
proof,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_proof(_leaves: Vec<mmr::EncodableOpaqueLeaf>, _proof: mmr::Proof<Hash>)
|
||||
fn verify_proof(leaves: Vec<mmr::EncodableOpaqueLeaf>, proof: mmr::Proof<mmr::Hash>)
|
||||
-> Result<(), mmr::Error>
|
||||
{
|
||||
|
||||
Err(mmr::Error::PalletNotIncluded)
|
||||
let leaves = leaves.into_iter().map(|leaf|
|
||||
leaf.into_opaque_leaf()
|
||||
.try_decode()
|
||||
.ok_or(mmr::Error::Verify)).collect::<Result<Vec<mmr::Leaf>, mmr::Error>>()?;
|
||||
Mmr::verify_leaves(leaves, proof)
|
||||
}
|
||||
|
||||
fn verify_proof_stateless(
|
||||
_root: Hash,
|
||||
_leaves: Vec<mmr::EncodableOpaqueLeaf>,
|
||||
_proof: mmr::Proof<Hash>
|
||||
root: mmr::Hash,
|
||||
leaves: Vec<mmr::EncodableOpaqueLeaf>,
|
||||
proof: mmr::Proof<mmr::Hash>
|
||||
) -> Result<(), mmr::Error> {
|
||||
let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect();
|
||||
pallet_mmr::verify_leaves_proof::<mmr::Hashing, _>(root, nodes, proof)
|
||||
}
|
||||
}
|
||||
|
||||
Err(mmr::Error::PalletNotIncluded)
|
||||
impl pallet_beefy_mmr::BeefyMmrApi<Block, Hash> for RuntimeApi {
|
||||
fn authority_set_proof() -> beefy_primitives::mmr::BeefyAuthoritySet<Hash> {
|
||||
BeefyMmrLeaf::authority_set_proof()
|
||||
}
|
||||
|
||||
fn next_authority_set_proof() -> beefy_primitives::mmr::BeefyNextAuthoritySet<Hash> {
|
||||
BeefyMmrLeaf::next_authority_set_proof()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,12 +46,7 @@ fn sample_size_is_sensible() {
|
||||
|
||||
#[test]
|
||||
fn call_size() {
|
||||
assert!(
|
||||
core::mem::size_of::<RuntimeCall>() <= 230,
|
||||
"size of RuntimeCall is more than 230 bytes: some calls have too big arguments, use Box to reduce \
|
||||
the size of RuntimeCall.
|
||||
If the limit is too strong, maybe consider increase the limit to 300.",
|
||||
);
|
||||
RuntimeCall::assert_size_under(256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user