mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 00:42:42 +00:00
BeaconBlock & PayloadStatus Finished
This commit is contained in:
@@ -678,7 +678,7 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockElectra<E, Payload>
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockEIP7732<E, Payload> {
|
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockEIP7732<E, Payload> {
|
||||||
/// Return a Electra block where the block has maximum size.
|
/// Return a EIP7732 block where the block has maximum size.
|
||||||
pub fn full(spec: &ChainSpec) -> Self {
|
pub fn full(spec: &ChainSpec) -> Self {
|
||||||
let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec);
|
let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec);
|
||||||
let indexed_attestation: IndexedAttestationElectra<E> = IndexedAttestationElectra {
|
let indexed_attestation: IndexedAttestationElectra<E> = IndexedAttestationElectra {
|
||||||
@@ -722,6 +722,15 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockEIP7732<E, Payload>
|
|||||||
sync_committee_signature: AggregateSignature::empty(),
|
sync_committee_signature: AggregateSignature::empty(),
|
||||||
sync_committee_bits: BitVector::default(),
|
sync_committee_bits: BitVector::default(),
|
||||||
};
|
};
|
||||||
|
let payload_attestations = vec![
|
||||||
|
PayloadAttestation::<E> {
|
||||||
|
aggregation_bits: BitList::with_capacity(E::PTCSize::to_usize()).unwrap(),
|
||||||
|
slot: Slot::new(0),
|
||||||
|
payload_status: PayloadStatus::PayloadPresent,
|
||||||
|
};
|
||||||
|
E::max_payload_attestations()
|
||||||
|
]
|
||||||
|
.into();
|
||||||
BeaconBlockEIP7732 {
|
BeaconBlockEIP7732 {
|
||||||
slot: spec.genesis_slot,
|
slot: spec.genesis_slot,
|
||||||
proposer_index: 0,
|
proposer_index: 0,
|
||||||
@@ -735,6 +744,7 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockEIP7732<E, Payload>
|
|||||||
voluntary_exits: base_block.body.voluntary_exits,
|
voluntary_exits: base_block.body.voluntary_exits,
|
||||||
bls_to_execution_changes,
|
bls_to_execution_changes,
|
||||||
signed_execution_bid: SignedExecutionBid::empty(),
|
signed_execution_bid: SignedExecutionBid::empty(),
|
||||||
|
payload_attestations,
|
||||||
sync_aggregate,
|
sync_aggregate,
|
||||||
randao_reveal: Signature::empty(),
|
randao_reveal: Signature::empty(),
|
||||||
eth1_data: Eth1Data {
|
eth1_data: Eth1Data {
|
||||||
@@ -804,6 +814,7 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockEIP7
|
|||||||
sync_aggregate: SyncAggregate::empty(),
|
sync_aggregate: SyncAggregate::empty(),
|
||||||
bls_to_execution_changes: VariableList::empty(),
|
bls_to_execution_changes: VariableList::empty(),
|
||||||
signed_execution_bid: SignedExecutionBid::empty(),
|
signed_execution_bid: SignedExecutionBid::empty(),
|
||||||
|
payload_attestations: VariableList::empty(),
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ pub struct BeaconBlockBody<E: EthSpec, Payload: AbstractExecPayload<E> = FullPay
|
|||||||
pub execution_requests: ExecutionRequests<E>,
|
pub execution_requests: ExecutionRequests<E>,
|
||||||
#[superstruct(only(EIP7732))]
|
#[superstruct(only(EIP7732))]
|
||||||
pub signed_execution_bid: SignedExecutionBid,
|
pub signed_execution_bid: SignedExecutionBid,
|
||||||
|
#[superstruct(only(EIP7732))]
|
||||||
|
pub payload_attestations: VariableList<PayloadAttestation<E>, E::MaxPayloadAttestations>,
|
||||||
#[superstruct(only(Base, Altair, EIP7732))]
|
#[superstruct(only(Base, Altair, EIP7732))]
|
||||||
#[metastruct(exclude_from(fields))]
|
#[metastruct(exclude_from(fields))]
|
||||||
#[ssz(skip_serializing, skip_deserializing)]
|
#[ssz(skip_serializing, skip_deserializing)]
|
||||||
@@ -748,6 +750,7 @@ impl<E: EthSpec> From<BeaconBlockBodyEIP7732<E, BlindedPayload<E>>>
|
|||||||
sync_aggregate,
|
sync_aggregate,
|
||||||
bls_to_execution_changes,
|
bls_to_execution_changes,
|
||||||
signed_execution_bid,
|
signed_execution_bid,
|
||||||
|
payload_attestations,
|
||||||
_phantom,
|
_phantom,
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
@@ -763,6 +766,7 @@ impl<E: EthSpec> From<BeaconBlockBodyEIP7732<E, BlindedPayload<E>>>
|
|||||||
sync_aggregate,
|
sync_aggregate,
|
||||||
bls_to_execution_changes,
|
bls_to_execution_changes,
|
||||||
signed_execution_bid,
|
signed_execution_bid,
|
||||||
|
payload_attestations,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -787,6 +791,7 @@ impl<E: EthSpec> From<BeaconBlockBodyEIP7732<E, FullPayload<E>>>
|
|||||||
sync_aggregate,
|
sync_aggregate,
|
||||||
bls_to_execution_changes,
|
bls_to_execution_changes,
|
||||||
signed_execution_bid,
|
signed_execution_bid,
|
||||||
|
payload_attestations,
|
||||||
_phantom,
|
_phantom,
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
@@ -803,6 +808,7 @@ impl<E: EthSpec> From<BeaconBlockBodyEIP7732<E, FullPayload<E>>>
|
|||||||
sync_aggregate,
|
sync_aggregate,
|
||||||
bls_to_execution_changes,
|
bls_to_execution_changes,
|
||||||
signed_execution_bid,
|
signed_execution_bid,
|
||||||
|
payload_attestations,
|
||||||
_phantom: PhantomData,
|
_phantom: PhantomData,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ pub enum Domain {
|
|||||||
SyncCommittee,
|
SyncCommittee,
|
||||||
ContributionAndProof,
|
ContributionAndProof,
|
||||||
SyncCommitteeSelectionProof,
|
SyncCommitteeSelectionProof,
|
||||||
|
BeaconBuilder,
|
||||||
|
PTCAttester,
|
||||||
ApplicationMask(ApplicationDomain),
|
ApplicationMask(ApplicationDomain),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +112,8 @@ pub struct ChainSpec {
|
|||||||
pub(crate) domain_voluntary_exit: u32,
|
pub(crate) domain_voluntary_exit: u32,
|
||||||
pub(crate) domain_selection_proof: u32,
|
pub(crate) domain_selection_proof: u32,
|
||||||
pub(crate) domain_aggregate_and_proof: u32,
|
pub(crate) domain_aggregate_and_proof: u32,
|
||||||
|
pub(crate) domain_beacon_builder: u32,
|
||||||
|
pub(crate) domain_ptc_attester: u32,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fork choice
|
* Fork choice
|
||||||
@@ -483,6 +487,8 @@ impl ChainSpec {
|
|||||||
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
|
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
|
||||||
Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(),
|
Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(),
|
||||||
Domain::BlsToExecutionChange => self.domain_bls_to_execution_change,
|
Domain::BlsToExecutionChange => self.domain_bls_to_execution_change,
|
||||||
|
Domain::BeaconBuilder => self.domain_beacon_builder,
|
||||||
|
Domain::PTCAttester => self.domain_ptc_attester,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,6 +713,8 @@ impl ChainSpec {
|
|||||||
domain_voluntary_exit: 4,
|
domain_voluntary_exit: 4,
|
||||||
domain_selection_proof: 5,
|
domain_selection_proof: 5,
|
||||||
domain_aggregate_and_proof: 6,
|
domain_aggregate_and_proof: 6,
|
||||||
|
domain_beacon_builder: 0x1B,
|
||||||
|
domain_ptc_attester: 0x0C,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fork choice
|
* Fork choice
|
||||||
@@ -1030,6 +1038,8 @@ impl ChainSpec {
|
|||||||
domain_voluntary_exit: 4,
|
domain_voluntary_exit: 4,
|
||||||
domain_selection_proof: 5,
|
domain_selection_proof: 5,
|
||||||
domain_aggregate_and_proof: 6,
|
domain_aggregate_and_proof: 6,
|
||||||
|
domain_beacon_builder: 0x1B,
|
||||||
|
domain_ptc_attester: 0x0C,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fork choice
|
* Fork choice
|
||||||
@@ -1945,6 +1955,8 @@ mod tests {
|
|||||||
&spec,
|
&spec,
|
||||||
);
|
);
|
||||||
test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec);
|
test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec);
|
||||||
|
test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec);
|
||||||
|
test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec);
|
||||||
|
|
||||||
// The builder domain index is zero
|
// The builder domain index is zero
|
||||||
let builder_domain_pre_mask = [0; 4];
|
let builder_domain_pre_mask = [0; 4];
|
||||||
|
|||||||
@@ -161,9 +161,10 @@ pub trait EthSpec:
|
|||||||
type MaxWithdrawalRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
type MaxWithdrawalRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* New in EIP-7736
|
* New in EIP-7732
|
||||||
*/
|
*/
|
||||||
type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||||
|
type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||||
|
|
||||||
fn default_spec() -> ChainSpec;
|
fn default_spec() -> ChainSpec;
|
||||||
|
|
||||||
@@ -376,6 +377,11 @@ pub trait EthSpec:
|
|||||||
Self::MaxWithdrawalRequestsPerPayload::to_usize()
|
Self::MaxWithdrawalRequestsPerPayload::to_usize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `MaxPayloadAttestations` constant for this specification.
|
||||||
|
fn max_payload_attestations() -> usize {
|
||||||
|
Self::MaxPayloadAttestations::to_usize()
|
||||||
|
}
|
||||||
|
|
||||||
fn kzg_commitments_inclusion_proof_depth() -> usize {
|
fn kzg_commitments_inclusion_proof_depth() -> usize {
|
||||||
Self::KzgCommitmentsInclusionProofDepth::to_usize()
|
Self::KzgCommitmentsInclusionProofDepth::to_usize()
|
||||||
}
|
}
|
||||||
@@ -444,6 +450,7 @@ impl EthSpec for MainnetEthSpec {
|
|||||||
type MaxAttestationsElectra = U8;
|
type MaxAttestationsElectra = U8;
|
||||||
type MaxWithdrawalRequestsPerPayload = U16;
|
type MaxWithdrawalRequestsPerPayload = U16;
|
||||||
type PTCSize = U512;
|
type PTCSize = U512;
|
||||||
|
type MaxPayloadAttestations = U4;
|
||||||
|
|
||||||
fn default_spec() -> ChainSpec {
|
fn default_spec() -> ChainSpec {
|
||||||
ChainSpec::mainnet()
|
ChainSpec::mainnet()
|
||||||
@@ -510,7 +517,8 @@ impl EthSpec for MinimalEthSpec {
|
|||||||
MaxConsolidationRequestsPerPayload,
|
MaxConsolidationRequestsPerPayload,
|
||||||
MaxAttesterSlashingsElectra,
|
MaxAttesterSlashingsElectra,
|
||||||
MaxAttestationsElectra,
|
MaxAttestationsElectra,
|
||||||
PTCSize
|
PTCSize,
|
||||||
|
MaxPayloadAttestations
|
||||||
});
|
});
|
||||||
|
|
||||||
fn default_spec() -> ChainSpec {
|
fn default_spec() -> ChainSpec {
|
||||||
@@ -577,6 +585,7 @@ impl EthSpec for GnosisEthSpec {
|
|||||||
type BytesPerCell = U2048;
|
type BytesPerCell = U2048;
|
||||||
type KzgCommitmentsInclusionProofDepth = U4;
|
type KzgCommitmentsInclusionProofDepth = U4;
|
||||||
type PTCSize = U512;
|
type PTCSize = U512;
|
||||||
|
type MaxPayloadAttestations = U4;
|
||||||
|
|
||||||
fn default_spec() -> ChainSpec {
|
fn default_spec() -> ChainSpec {
|
||||||
ChainSpec::gnosis()
|
ChainSpec::gnosis()
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ pub use crate::payload::{
|
|||||||
FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra,
|
FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra,
|
||||||
FullPayloadRef, OwnedExecPayload,
|
FullPayloadRef, OwnedExecPayload,
|
||||||
};
|
};
|
||||||
pub use crate::payload_attestation::PayloadAttestation;
|
pub use crate::payload_attestation::{PayloadAttestation, PayloadStatus};
|
||||||
pub use crate::payload_attestation_data::PayloadAttestationData;
|
pub use crate::payload_attestation_data::PayloadAttestationData;
|
||||||
pub use crate::payload_attestation_message::PayloadAttestationMessage;
|
pub use crate::payload_attestation_message::PayloadAttestationMessage;
|
||||||
pub use crate::pending_attestation::PendingAttestation;
|
pub use crate::pending_attestation::PendingAttestation;
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use derivative::Derivative;
|
||||||
|
use rand::Rng;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use ssz::{Decode, Encode};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use test_random_derive::TestRandom;
|
use test_random_derive::TestRandom;
|
||||||
|
use tree_hash::TreeHash;
|
||||||
use tree_hash_derive::TreeHash;
|
use tree_hash_derive::TreeHash;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
@@ -11,24 +15,146 @@ use tree_hash_derive::TreeHash;
|
|||||||
TreeHash,
|
TreeHash,
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
Encode,
|
Encode,
|
||||||
Decode,
|
Decode,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
|
Derivative,
|
||||||
)]
|
)]
|
||||||
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
|
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
|
||||||
#[arbitrary(bound = "E: EthSpec")]
|
#[arbitrary(bound = "E: EthSpec")]
|
||||||
|
#[derivative(PartialEq, Hash)]
|
||||||
pub struct PayloadAttestation<E: EthSpec> {
|
pub struct PayloadAttestation<E: EthSpec> {
|
||||||
pub aggregation_bits: BitList<E::PTCSize>,
|
pub aggregation_bits: BitList<E::PTCSize>,
|
||||||
pub slot: Slot,
|
pub slot: Slot,
|
||||||
pub payload_status: u8,
|
pub payload_status: PayloadStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(arbitrary::Arbitrary, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum PayloadStatus {
|
||||||
|
PayloadAbsent = 0,
|
||||||
|
PayloadPresent = 1,
|
||||||
|
PayloadWithheld = 2,
|
||||||
|
PayloadInvalidStatus = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for PayloadStatus {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(byte: u8) -> Result<Self, Self::Error> {
|
||||||
|
match byte {
|
||||||
|
0 => Ok(PayloadStatus::PayloadAbsent),
|
||||||
|
1 => Ok(PayloadStatus::PayloadPresent),
|
||||||
|
2 => Ok(PayloadStatus::PayloadWithheld),
|
||||||
|
3 => Ok(PayloadStatus::PayloadInvalidStatus),
|
||||||
|
_ => Err(format!("Invalid byte for PayloadStatus: {}", byte)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestRandom for PayloadStatus {
|
||||||
|
fn random_for_test(rng: &mut impl rand::RngCore) -> Self {
|
||||||
|
rng.gen_range(0u8..=3u8)
|
||||||
|
.try_into()
|
||||||
|
.expect("PayloadStatus: random byte is valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeHash for PayloadStatus {
|
||||||
|
fn tree_hash_type() -> tree_hash::TreeHashType {
|
||||||
|
u8::tree_hash_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding {
|
||||||
|
(*self as u8).tree_hash_packed_encoding()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree_hash_packing_factor() -> usize {
|
||||||
|
u8::tree_hash_packing_factor()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree_hash_root(&self) -> Hash256 {
|
||||||
|
(*self as u8).tree_hash_root()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ssz(enum_behaviour = "tag") would probably work but we want to ensure
|
||||||
|
// that the mapping between the variant and u8 matches the spec
|
||||||
|
impl Encode for PayloadStatus {
|
||||||
|
fn is_ssz_fixed_len() -> bool {
|
||||||
|
<u8 as Encode>::is_ssz_fixed_len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssz_fixed_len() -> usize {
|
||||||
|
<u8 as Encode>::ssz_fixed_len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssz_bytes_len(&self) -> usize {
|
||||||
|
<u8 as Encode>::ssz_bytes_len(&(*self as u8))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssz_append(&self, buf: &mut Vec<u8>) {
|
||||||
|
<u8 as Encode>::ssz_append(&(*self as u8), buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for PayloadStatus {
|
||||||
|
fn is_ssz_fixed_len() -> bool {
|
||||||
|
<u8 as Decode>::is_ssz_fixed_len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssz_fixed_len() -> usize {
|
||||||
|
<u8 as Decode>::ssz_fixed_len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
|
||||||
|
(*bytes
|
||||||
|
// the u8 is just the first byte of the slice
|
||||||
|
.get(0)
|
||||||
|
.ok_or(ssz::DecodeError::InvalidByteLength {
|
||||||
|
len: 0,
|
||||||
|
expected: 1,
|
||||||
|
})?)
|
||||||
|
.try_into()
|
||||||
|
.map_err(|s| ssz::DecodeError::BytesInvalid(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for PayloadStatus {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serde_utils::quoted_u8::Quoted::<u8>::serialize(
|
||||||
|
&serde_utils::quoted_u8::Quoted {
|
||||||
|
value: (*self as u8),
|
||||||
|
},
|
||||||
|
serializer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for PayloadStatus {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let quoted = serde_utils::quoted_u8::Quoted::<u8>::deserialize(deserializer)?;
|
||||||
|
PayloadStatus::try_from(quoted.value).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod payload_attestation_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
ssz_and_tree_hash_tests!(PayloadAttestation<MainnetEthSpec>);
|
ssz_and_tree_hash_tests!(PayloadAttestation<MainnetEthSpec>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod payload_status_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
ssz_and_tree_hash_tests!(PayloadStatus);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user