mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-18 21:38:31 +00:00
Use checked arithmetic in types and state proc (#1009)
This commit is contained in:
@@ -23,6 +23,7 @@ log = "0.4.8"
|
||||
merkle_proof = { path = "../utils/merkle_proof" }
|
||||
rayon = "1.2.0"
|
||||
rand = "0.7.2"
|
||||
safe_arith = { path = "../utils/safe_arith" }
|
||||
serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
slog = "2.5.2"
|
||||
|
||||
@@ -7,6 +7,7 @@ use compare_fields_derive::CompareFields;
|
||||
use eth2_hashing::hash;
|
||||
use int_to_bytes::{int_to_bytes4, int_to_bytes8};
|
||||
use pubkey_cache::PubkeyCache;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::ssz_encode;
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -75,6 +76,10 @@ pub enum Error {
|
||||
deposit_count: u64,
|
||||
deposit_index: u64,
|
||||
},
|
||||
/// An arithmetic operation occurred which would have overflowed or divided by 0.
|
||||
///
|
||||
/// This represents a serious bug in either the spec or Lighthouse!
|
||||
ArithError(ArithError),
|
||||
}
|
||||
|
||||
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
|
||||
@@ -411,7 +416,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let candidate_index = indices[compute_shuffled_index(
|
||||
i % indices.len(),
|
||||
i.safe_rem(indices.len())?,
|
||||
indices.len(),
|
||||
seed,
|
||||
spec.shuffle_round_count,
|
||||
@@ -419,17 +424,19 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
.ok_or(Error::UnableToShuffle)?];
|
||||
let random_byte = {
|
||||
let mut preimage = seed.to_vec();
|
||||
preimage.append(&mut int_to_bytes8((i / 32) as u64));
|
||||
preimage.append(&mut int_to_bytes8(i.safe_div(32)? as u64));
|
||||
let hash = hash(&preimage);
|
||||
hash[i % 32]
|
||||
hash[i.safe_rem(32)?]
|
||||
};
|
||||
let effective_balance = self.validators[candidate_index].effective_balance;
|
||||
if effective_balance * MAX_RANDOM_BYTE
|
||||
>= spec.max_effective_balance * u64::from(random_byte)
|
||||
if effective_balance.safe_mul(MAX_RANDOM_BYTE)?
|
||||
>= spec
|
||||
.max_effective_balance
|
||||
.safe_mul(u64::from(random_byte))?
|
||||
{
|
||||
return Ok(candidate_index);
|
||||
}
|
||||
i += 1;
|
||||
i.increment()?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,8 +483,8 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn get_latest_block_roots_index(&self, slot: Slot) -> Result<usize, Error> {
|
||||
if (slot < self.slot) && (self.slot <= slot + self.block_roots.len() as u64) {
|
||||
Ok(slot.as_usize() % self.block_roots.len())
|
||||
if slot < self.slot && self.slot <= slot + self.block_roots.len() as u64 {
|
||||
Ok(slot.as_usize().safe_rem(self.block_roots.len())?)
|
||||
} else {
|
||||
Err(BeaconStateError::SlotOutOfBounds)
|
||||
}
|
||||
@@ -529,7 +536,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
let len = T::EpochsPerHistoricalVector::to_u64();
|
||||
|
||||
if current_epoch < epoch + len && epoch <= allow_next_epoch.upper_bound_of(current_epoch) {
|
||||
Ok(epoch.as_usize() % len as usize)
|
||||
Ok(epoch.as_usize().safe_rem(len as usize)?)
|
||||
} else {
|
||||
Err(Error::EpochOutOfBounds)
|
||||
}
|
||||
@@ -543,7 +550,9 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn update_randao_mix(&mut self, epoch: Epoch, signature: &Signature) -> Result<(), Error> {
|
||||
let i = epoch.as_usize() % T::EpochsPerHistoricalVector::to_usize();
|
||||
let i = epoch
|
||||
.as_usize()
|
||||
.safe_rem(T::EpochsPerHistoricalVector::to_usize())?;
|
||||
|
||||
let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature)));
|
||||
|
||||
@@ -573,8 +582,8 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn get_latest_state_roots_index(&self, slot: Slot) -> Result<usize, Error> {
|
||||
if (slot < self.slot) && (self.slot <= slot + Slot::from(self.state_roots.len())) {
|
||||
Ok(slot.as_usize() % self.state_roots.len())
|
||||
if slot < self.slot && self.slot <= slot + self.state_roots.len() as u64 {
|
||||
Ok(slot.as_usize().safe_rem(self.state_roots.len())?)
|
||||
} else {
|
||||
Err(BeaconStateError::SlotOutOfBounds)
|
||||
}
|
||||
@@ -628,7 +637,9 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
if current_epoch < epoch + T::EpochsPerSlashingsVector::to_u64()
|
||||
&& epoch <= allow_next_epoch.upper_bound_of(current_epoch)
|
||||
{
|
||||
Ok(epoch.as_usize() % T::EpochsPerSlashingsVector::to_usize())
|
||||
Ok(epoch
|
||||
.as_usize()
|
||||
.safe_rem(T::EpochsPerSlashingsVector::to_usize())?)
|
||||
} else {
|
||||
Err(Error::EpochOutOfBounds)
|
||||
}
|
||||
@@ -687,20 +698,20 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
// == 0`.
|
||||
let mix = {
|
||||
let i = epoch + T::EpochsPerHistoricalVector::to_u64() - spec.min_seed_lookahead - 1;
|
||||
self.randao_mixes[i.as_usize() % self.randao_mixes.len()]
|
||||
self.randao_mixes[i.as_usize().safe_rem(self.randao_mixes.len())?]
|
||||
};
|
||||
let domain_bytes = int_to_bytes4(spec.get_domain_constant(domain_type));
|
||||
let epoch_bytes = int_to_bytes8(epoch.as_u64());
|
||||
|
||||
const NUM_DOMAIN_BYTES: usize = 4;
|
||||
const NUM_EPOCH_BYTES: usize = 8;
|
||||
const MIX_OFFSET: usize = NUM_DOMAIN_BYTES + NUM_EPOCH_BYTES;
|
||||
const NUM_MIX_BYTES: usize = 32;
|
||||
|
||||
let mut preimage = [0; NUM_DOMAIN_BYTES + NUM_EPOCH_BYTES + NUM_MIX_BYTES];
|
||||
preimage[0..NUM_DOMAIN_BYTES].copy_from_slice(&domain_bytes);
|
||||
preimage[NUM_DOMAIN_BYTES..NUM_DOMAIN_BYTES + NUM_EPOCH_BYTES]
|
||||
.copy_from_slice(&epoch_bytes);
|
||||
preimage[NUM_DOMAIN_BYTES + NUM_EPOCH_BYTES..].copy_from_slice(mix.as_bytes());
|
||||
preimage[NUM_DOMAIN_BYTES..MIX_OFFSET].copy_from_slice(&epoch_bytes);
|
||||
preimage[MIX_OFFSET..].copy_from_slice(mix.as_bytes());
|
||||
|
||||
Ok(Hash256::from_slice(&hash(&preimage)))
|
||||
}
|
||||
@@ -734,9 +745,10 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
pub fn get_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
|
||||
Ok(std::cmp::max(
|
||||
spec.min_per_epoch_churn_limit,
|
||||
self.committee_cache(RelativeEpoch::Current)?
|
||||
.active_validator_count() as u64
|
||||
/ spec.churn_limit_quotient,
|
||||
(self
|
||||
.committee_cache(RelativeEpoch::Current)?
|
||||
.active_validator_count() as u64)
|
||||
.safe_div(spec.churn_limit_quotient)?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -766,7 +778,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
) -> Result<u64, Error> {
|
||||
validator_indices.iter().try_fold(0_u64, |acc, i| {
|
||||
self.get_effective_balance(*i, spec)
|
||||
.and_then(|bal| Ok(bal + acc))
|
||||
.and_then(|bal| Ok(acc.safe_add(bal)?))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1072,3 +1084,9 @@ impl From<tree_hash::Error> for Error {
|
||||
Error::TreeHashError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
fn from(e: ArithError) -> Error {
|
||||
Error::ArithError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use super::BeaconState;
|
||||
use crate::*;
|
||||
use core::num::NonZeroUsize;
|
||||
@@ -43,7 +45,7 @@ impl CommitteeCache {
|
||||
}
|
||||
|
||||
let committees_per_slot =
|
||||
T::get_committee_count_per_slot(active_validator_indices.len(), spec) as u64;
|
||||
T::get_committee_count_per_slot(active_validator_indices.len(), spec)? as u64;
|
||||
|
||||
let seed = state.get_seed(epoch, Domain::BeaconAttester, spec)?;
|
||||
|
||||
@@ -56,7 +58,7 @@ impl CommitteeCache {
|
||||
.ok_or_else(|| Error::UnableToShuffle)?;
|
||||
|
||||
// The use of `NonZeroUsize` reduces the maximum number of possible validators by one.
|
||||
if state.validators.len() > usize::max_value() - 1 {
|
||||
if state.validators.len() == usize::max_value() {
|
||||
return Err(Error::TooManyValidators);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::{BeaconStateError, ChainSpec, Epoch, Validator};
|
||||
use safe_arith::SafeArith;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -50,7 +51,10 @@ impl ExitCache {
|
||||
/// Must only be called once per exiting validator.
|
||||
pub fn record_validator_exit(&mut self, exit_epoch: Epoch) -> Result<(), BeaconStateError> {
|
||||
self.check_initialized()?;
|
||||
*self.exits_per_epoch.entry(exit_epoch).or_insert(0) += 1;
|
||||
self.exits_per_epoch
|
||||
.entry(exit_epoch)
|
||||
.or_insert(0)
|
||||
.increment()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ impl PubkeyCache {
|
||||
///
|
||||
/// The added index must equal the number of validators already added to the map. This ensures
|
||||
/// that an index is never skipped.
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
pub fn insert(&mut self, pubkey: PublicKeyBytes, index: ValidatorIndex) -> bool {
|
||||
if index == self.len {
|
||||
self.map.insert(pubkey, index);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use super::Error;
|
||||
use crate::{BeaconState, EthSpec, Hash256, Unsigned, Validator};
|
||||
use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache};
|
||||
@@ -195,7 +197,7 @@ impl ValidatorsListTreeHashCache {
|
||||
/// This function makes assumptions that the `validators` list will only change in accordance
|
||||
/// with valid per-block/per-slot state transitions.
|
||||
fn recalculate_tree_hash_root(&mut self, validators: &[Validator]) -> Result<Hash256, Error> {
|
||||
let mut list_arena = std::mem::replace(&mut self.list_arena, CacheArena::default());
|
||||
let mut list_arena = std::mem::take(&mut self.list_arena);
|
||||
|
||||
let leaves = self
|
||||
.values
|
||||
|
||||
@@ -238,10 +238,10 @@ impl ChainSpec {
|
||||
/*
|
||||
* Gwei values
|
||||
*/
|
||||
min_deposit_amount: u64::pow(2, 0) * u64::pow(10, 9),
|
||||
max_effective_balance: u64::pow(2, 5) * u64::pow(10, 9),
|
||||
ejection_balance: u64::pow(2, 4) * u64::pow(10, 9),
|
||||
effective_balance_increment: u64::pow(2, 0) * u64::pow(10, 9),
|
||||
min_deposit_amount: u64::pow(2, 0).saturating_mul(u64::pow(10, 9)),
|
||||
max_effective_balance: u64::pow(2, 5).saturating_mul(u64::pow(10, 9)),
|
||||
ejection_balance: u64::pow(2, 4).saturating_mul(u64::pow(10, 9)),
|
||||
effective_balance_increment: u64::pow(2, 0).saturating_mul(u64::pow(10, 9)),
|
||||
|
||||
/*
|
||||
* Initial Values
|
||||
@@ -522,6 +522,7 @@ impl Default for YamlConfig {
|
||||
|
||||
/// Spec v0.11.1
|
||||
impl YamlConfig {
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
pub fn from_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
// ChainSpec
|
||||
@@ -557,7 +558,7 @@ impl YamlConfig {
|
||||
proposer_reward_quotient: spec.proposer_reward_quotient,
|
||||
inactivity_penalty_quotient: spec.inactivity_penalty_quotient,
|
||||
min_slashing_penalty_quotient: spec.min_slashing_penalty_quotient,
|
||||
genesis_fork_version: spec.genesis_fork_version.clone(),
|
||||
genesis_fork_version: spec.genesis_fork_version,
|
||||
safe_slots_to_update_justified: spec.safe_slots_to_update_justified,
|
||||
domain_beacon_proposer: spec.domain_beacon_proposer,
|
||||
domain_beacon_attester: spec.domain_beacon_attester,
|
||||
@@ -642,7 +643,7 @@ impl YamlConfig {
|
||||
effective_balance_increment: self.effective_balance_increment,
|
||||
genesis_slot: Slot::from(self.genesis_slot),
|
||||
bls_withdrawal_prefix_byte: self.bls_withdrawal_prefix,
|
||||
milliseconds_per_slot: self.seconds_per_slot * 1000,
|
||||
milliseconds_per_slot: self.seconds_per_slot.saturating_mul(1000),
|
||||
min_attestation_inclusion_delay: self.min_attestation_inclusion_delay,
|
||||
min_seed_lookahead: Epoch::from(self.min_seed_lookahead),
|
||||
max_seed_lookahead: Epoch::from(self.max_seed_lookahead),
|
||||
@@ -662,7 +663,7 @@ impl YamlConfig {
|
||||
domain_deposit: self.domain_deposit,
|
||||
domain_voluntary_exit: self.domain_voluntary_exit,
|
||||
boot_nodes: chain_spec.boot_nodes.clone(),
|
||||
genesis_fork_version: self.genesis_fork_version.clone(),
|
||||
genesis_fork_version: self.genesis_fork_version,
|
||||
eth1_follow_distance: self.eth1_follow_distance,
|
||||
..*chain_spec
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::*;
|
||||
use safe_arith::SafeArith;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_types::typenum::{
|
||||
Unsigned, U0, U1, U1024, U1099511627776, U128, U16, U16777216, U2, U2048, U32, U4, U4096, U64,
|
||||
@@ -63,16 +64,21 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
|
||||
/// the `active_validator_count` during the slot's epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn get_committee_count_per_slot(active_validator_count: usize, spec: &ChainSpec) -> usize {
|
||||
fn get_committee_count_per_slot(
|
||||
active_validator_count: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<usize, Error> {
|
||||
let slots_per_epoch = Self::SlotsPerEpoch::to_usize();
|
||||
|
||||
std::cmp::max(
|
||||
Ok(std::cmp::max(
|
||||
1,
|
||||
std::cmp::min(
|
||||
spec.max_committees_per_slot,
|
||||
active_validator_count / slots_per_epoch / spec.target_committee_size,
|
||||
active_validator_count
|
||||
.safe_div(slots_per_epoch)?
|
||||
.safe_div(spec.target_committee_size)?,
|
||||
),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the minimum number of validators required for this spec.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
// Required for big type-level numbers
|
||||
#![recursion_limit = "128"]
|
||||
// Clippy lint set up
|
||||
#![deny(clippy::integer_arithmetic)]
|
||||
|
||||
#[macro_use]
|
||||
pub mod test_utils;
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::test_utils::TestRandom;
|
||||
use crate::SignedRoot;
|
||||
use rand::RngCore;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use slog;
|
||||
use ssz::{ssz_encode, Decode, DecodeError, Encode};
|
||||
use std::cmp::{Ord, Ordering};
|
||||
use std::fmt;
|
||||
@@ -38,7 +37,7 @@ impl Slot {
|
||||
}
|
||||
|
||||
pub fn epoch(self, slots_per_epoch: u64) -> Epoch {
|
||||
Epoch::from(self.0 / slots_per_epoch)
|
||||
Epoch::from(self.0) / Epoch::from(slots_per_epoch)
|
||||
}
|
||||
|
||||
pub fn max_value() -> Slot {
|
||||
@@ -77,8 +76,8 @@ impl Epoch {
|
||||
let start = self.start_slot(slots_per_epoch);
|
||||
let end = self.end_slot(slots_per_epoch);
|
||||
|
||||
if (slot >= start) && (slot <= end) {
|
||||
Some(slot.as_usize() - start.as_usize())
|
||||
if slot >= start && slot <= end {
|
||||
slot.as_usize().checked_sub(start.as_usize())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -110,7 +109,7 @@ impl<'a> Iterator for SlotIter<'a> {
|
||||
} else {
|
||||
let start_slot = self.epoch.start_slot(self.slots_per_epoch);
|
||||
let previous = self.current_iteration;
|
||||
self.current_iteration += 1;
|
||||
self.current_iteration = self.current_iteration.checked_add(1)?;
|
||||
Some(start_slot + previous)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,20 +107,21 @@ macro_rules! impl_math_between {
|
||||
|
||||
fn div(self, rhs: $other) -> $main {
|
||||
let rhs: u64 = rhs.into();
|
||||
if rhs == 0 {
|
||||
panic!("Cannot divide by zero-valued Slot/Epoch")
|
||||
}
|
||||
$main::from(self.0 / rhs)
|
||||
$main::from(
|
||||
self.0
|
||||
.checked_div(rhs)
|
||||
.expect("Cannot divide by zero-valued Slot/Epoch"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<$other> for $main {
|
||||
fn div_assign(&mut self, rhs: $other) {
|
||||
let rhs: u64 = rhs.into();
|
||||
if rhs == 0 {
|
||||
panic!("Cannot divide by zero-valued Slot/Epoch")
|
||||
}
|
||||
self.0 = self.0 / rhs
|
||||
self.0 = self
|
||||
.0
|
||||
.checked_div(rhs)
|
||||
.expect("Cannot divide by zero-valued Slot/Epoch");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +130,11 @@ macro_rules! impl_math_between {
|
||||
|
||||
fn rem(self, modulus: $other) -> $main {
|
||||
let modulus: u64 = modulus.into();
|
||||
$main::from(self.0 % modulus)
|
||||
$main::from(
|
||||
self.0
|
||||
.checked_rem(modulus)
|
||||
.expect("Cannot divide by zero-valued Slot/Epoch"),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -234,7 +239,7 @@ macro_rules! impl_ssz {
|
||||
}
|
||||
|
||||
fn tree_hash_packing_factor() -> usize {
|
||||
32 / 8
|
||||
32usize.wrapping_div(8)
|
||||
}
|
||||
|
||||
fn tree_hash_root(&self) -> tree_hash::Hash256 {
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::super::{generate_deterministic_keypairs, KeypairsFile};
|
||||
use crate::test_utils::{AttestationTestTask, TestingPendingAttestationBuilder};
|
||||
use crate::*;
|
||||
use bls::get_withdrawal_credentials;
|
||||
use dirs;
|
||||
use log::debug;
|
||||
use rayon::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -36,7 +36,7 @@ impl TestingDepositBuilder {
|
||||
let mut secret_key = keypair.sk.clone();
|
||||
|
||||
match test_task {
|
||||
DepositTestTask::BadPubKey => pubkeybytes = PublicKeyBytes::from(new_key.pk.clone()),
|
||||
DepositTestTask::BadPubKey => pubkeybytes = PublicKeyBytes::from(new_key.pk),
|
||||
DepositTestTask::InvalidPubKey => {
|
||||
// Creating invalid public key bytes
|
||||
let mut public_key_bytes: Vec<u8> = vec![0; 48];
|
||||
|
||||
@@ -50,7 +50,7 @@ impl TestingProposerSlashingBuilder {
|
||||
message: BeaconBlockHeader {
|
||||
parent_root: hash_2,
|
||||
slot: slot_2,
|
||||
..signed_header_1.message.clone()
|
||||
..signed_header_1.message
|
||||
},
|
||||
signature: Signature::empty_signature(),
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod builders;
|
||||
|
||||
@@ -32,12 +32,10 @@ impl CachedTreeHash<TreeHashCache> for Validator {
|
||||
// Fields pubkey and withdrawal_credentials are constant
|
||||
if (i == 0 || i == 1) && cache.initialized {
|
||||
None
|
||||
} else if process_field_by_index(self, i, leaf, !cache.initialized) {
|
||||
Some(i)
|
||||
} else {
|
||||
if process_field_by_index(self, i, leaf, !cache.initialized) {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use hex;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user