mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 20:57:10 +00:00
range sync
This commit is contained in:
@@ -3150,7 +3150,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
};
|
||||
|
||||
// Import the blocks into the chain.
|
||||
for signature_verified_block in signature_verified_blocks {
|
||||
for (signature_verified_block, _envelope) in signature_verified_blocks {
|
||||
let block_slot = signature_verified_block.slot();
|
||||
match self
|
||||
.process_block(
|
||||
|
||||
@@ -60,6 +60,7 @@ use crate::execution_payload::{
|
||||
};
|
||||
use crate::kzg_utils::blobs_to_data_column_sidecars;
|
||||
use crate::observed_block_producers::SeenBlock;
|
||||
use crate::payload_envelope_verification::{AvailableEnvelope, EnvelopeError};
|
||||
use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS;
|
||||
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
|
||||
use crate::{
|
||||
@@ -320,6 +321,20 @@ pub enum BlockError {
|
||||
bid_parent_root: Hash256,
|
||||
block_parent_root: Hash256,
|
||||
},
|
||||
/// The child block is known but its parent execution payload envelope has not been received yet.
|
||||
///
|
||||
/// ## Peer scoring
|
||||
///
|
||||
/// It's unclear if this block is valid, but it cannot be fully verified without the parent's
|
||||
/// execution payload envelope.
|
||||
ParentEnvelopeUnknown { parent_root: Hash256 },
|
||||
/// An error occurred while processing the execution payload envelope during range sync.
|
||||
EnvelopeError(Box<EnvelopeError>),
|
||||
|
||||
PayloadEnvelopeError {
|
||||
e: Box<EnvelopeError>,
|
||||
penalize_peer: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Which specific signature(s) are invalid in a SignedBeaconBlock
|
||||
@@ -486,6 +501,36 @@ impl From<ArithError> for BlockError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EnvelopeError> for BlockError {
|
||||
fn from(e: EnvelopeError) -> Self {
|
||||
let penalize_peer = match &e {
|
||||
// REJECT per spec: peer sent invalid envelope data
|
||||
EnvelopeError::BadSignature
|
||||
| EnvelopeError::BuilderIndexMismatch { .. }
|
||||
| EnvelopeError::BlockHashMismatch { .. }
|
||||
| EnvelopeError::SlotMismatch { .. }
|
||||
| EnvelopeError::IncorrectBlockProposer { .. } => true,
|
||||
// IGNORE per spec: not the peer's fault
|
||||
EnvelopeError::BlockRootUnknown { .. }
|
||||
| EnvelopeError::PriorToFinalization { .. }
|
||||
| EnvelopeError::UnknownValidator { .. } => false,
|
||||
// Internal errors: not the peer's fault
|
||||
EnvelopeError::BeaconChainError(_)
|
||||
| EnvelopeError::BeaconStateError(_)
|
||||
| EnvelopeError::BlockProcessingError(_)
|
||||
| EnvelopeError::EnvelopeProcessingError(_)
|
||||
| EnvelopeError::ExecutionPayloadError(_)
|
||||
| EnvelopeError::BlockError(_)
|
||||
| EnvelopeError::InternalError(_)
|
||||
| EnvelopeError::OptimisticSyncNotSupported { .. } => false,
|
||||
};
|
||||
BlockError::PayloadEnvelopeError {
|
||||
e: Box::new(e),
|
||||
penalize_peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores information about verifying a payload against an execution engine.
|
||||
#[derive(Debug, PartialEq, Clone, Encode, Decode)]
|
||||
pub struct PayloadVerificationOutcome {
|
||||
@@ -583,10 +628,17 @@ pub(crate) fn process_block_slash_info<T: BeaconChainTypes, TErr: BlockBlobError
|
||||
/// The given `chain_segment` must contain only blocks from the same epoch, otherwise an error
|
||||
/// will be returned.
|
||||
#[instrument(skip_all)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
mut chain_segment: Vec<(Hash256, RangeSyncBlock<T::EthSpec>)>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Vec<SignatureVerifiedBlock<T>>, BlockError> {
|
||||
) -> Result<
|
||||
Vec<(
|
||||
SignatureVerifiedBlock<T>,
|
||||
Option<Box<AvailableEnvelope<T::EthSpec>>>,
|
||||
)>,
|
||||
BlockError,
|
||||
> {
|
||||
if chain_segment.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
@@ -615,14 +667,29 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
let consensus_context =
|
||||
ConsensusContext::new(block.slot()).set_current_block_root(block_root);
|
||||
|
||||
let available_block = block.into_available_block();
|
||||
let (available_block, envelope) = match block {
|
||||
RangeSyncBlock::Base(ab) => (ab, None),
|
||||
RangeSyncBlock::Gloas { block, envelope } => {
|
||||
let ab = AvailableBlock::new(
|
||||
block,
|
||||
AvailableBlockData::NoData,
|
||||
&chain.data_availability_checker,
|
||||
chain.spec.clone(),
|
||||
)
|
||||
.map_err(BlockError::AvailabilityCheck)?;
|
||||
(ab, envelope)
|
||||
}
|
||||
};
|
||||
available_blocks.push(available_block.clone());
|
||||
signature_verified_blocks.push(SignatureVerifiedBlock {
|
||||
block: MaybeAvailableBlock::Available(available_block),
|
||||
block_root,
|
||||
parent: None,
|
||||
consensus_context,
|
||||
});
|
||||
signature_verified_blocks.push((
|
||||
SignatureVerifiedBlock {
|
||||
block: MaybeAvailableBlock::Available(available_block),
|
||||
block_root,
|
||||
parent: None,
|
||||
consensus_context,
|
||||
},
|
||||
envelope,
|
||||
));
|
||||
}
|
||||
|
||||
chain
|
||||
@@ -632,7 +699,7 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
// verify signatures
|
||||
let pubkey_cache = get_validator_pubkey_cache(chain)?;
|
||||
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
|
||||
for svb in &mut signature_verified_blocks {
|
||||
for (svb, _) in &mut signature_verified_blocks {
|
||||
signature_verifier
|
||||
.include_all_signatures(svb.block.as_block(), &mut svb.consensus_context)?;
|
||||
}
|
||||
@@ -643,7 +710,7 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
|
||||
drop(pubkey_cache);
|
||||
|
||||
if let Some(signature_verified_block) = signature_verified_blocks.first_mut() {
|
||||
if let Some((signature_verified_block, _)) = signature_verified_blocks.first_mut() {
|
||||
signature_verified_block.parent = Some(parent);
|
||||
}
|
||||
|
||||
@@ -1191,7 +1258,7 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
|
||||
let result = info_span!("signature_verify").in_scope(|| signature_verifier.verify());
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// gloas blocks are always available.
|
||||
// Gloas blocks are always available — data arrives via the envelope.
|
||||
let maybe_available = if chain
|
||||
.spec
|
||||
.fork_name_at_slot::<T::EthSpec>(block.slot())
|
||||
@@ -1946,6 +2013,21 @@ fn load_parent<T: BeaconChainTypes, B: AsBlock<T::EthSpec>>(
|
||||
BlockError::from(BeaconChainError::MissingBeaconBlock(block.parent_root()))
|
||||
})?;
|
||||
|
||||
// For post-Gloas parent blocks, the execution payload arrives via the envelope.
|
||||
// If the parent's execution payload envelope hasn't arrived yet,
|
||||
// return an unknown parent error so the block gets sent to the
|
||||
// reprocess queue.
|
||||
if chain
|
||||
.spec
|
||||
.fork_name_at_slot::<T::EthSpec>(parent_block.slot())
|
||||
.gloas_enabled()
|
||||
{
|
||||
let _envelope = chain
|
||||
.store
|
||||
.get_payload_envelope(&root)?
|
||||
.ok_or(BlockError::ParentEnvelopeUnknown { parent_root: root })?;
|
||||
}
|
||||
|
||||
// Load the parent block's state from the database, returning an error if it is not found.
|
||||
// It is an error because if we know the parent block we should also know the parent state.
|
||||
// Retrieve any state that is advanced through to at most `block.slot()`: this is
|
||||
|
||||
@@ -2,10 +2,11 @@ use crate::data_availability_checker::{AvailabilityCheckError, DataAvailabilityC
|
||||
pub use crate::data_availability_checker::{
|
||||
AvailableBlock, AvailableBlockData, MaybeAvailableBlock,
|
||||
};
|
||||
use crate::payload_envelope_verification::AvailableEnvelope;
|
||||
use crate::{BeaconChainTypes, PayloadVerificationOutcome};
|
||||
use educe::Educe;
|
||||
use state_processing::ConsensusContext;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
use types::data::BlobIdentifier;
|
||||
use types::{
|
||||
@@ -45,38 +46,60 @@ impl<E: EthSpec> LookupBlock<E> {
|
||||
/// This includes any and all blobs/columns required, including zero if
|
||||
/// none are required. This can happen if the block is pre-deneb or if
|
||||
/// it's simply past the DA boundary.
|
||||
#[derive(Clone, Educe)]
|
||||
#[educe(Hash(bound(E: EthSpec)))]
|
||||
pub struct RangeSyncBlock<E: EthSpec> {
|
||||
block: AvailableBlock<E>,
|
||||
pub enum RangeSyncBlock<E: EthSpec> {
|
||||
Base(AvailableBlock<E>),
|
||||
Gloas {
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
envelope: Option<Box<AvailableEnvelope<E>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Hash for RangeSyncBlock<E> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.block_root().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Debug for RangeSyncBlock<E> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "RpcBlock({:?})", self.block_root())
|
||||
write!(f, "RangeSyncBlock({:?})", self.block_root())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> RangeSyncBlock<E> {
|
||||
pub fn block_root(&self) -> Hash256 {
|
||||
self.block.block_root()
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => block.block_root(),
|
||||
RangeSyncBlock::Gloas { block, .. } => block.canonical_root(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_block(&self) -> &SignedBeaconBlock<E> {
|
||||
self.block.block()
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => block.block(),
|
||||
RangeSyncBlock::Gloas { block, .. } => block,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_cloned(&self) -> Arc<SignedBeaconBlock<E>> {
|
||||
self.block.block_cloned()
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => block.block_cloned(),
|
||||
RangeSyncBlock::Gloas { block, .. } => block.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_data(&self) -> &AvailableBlockData<E> {
|
||||
self.block.data()
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => block.data(),
|
||||
RangeSyncBlock::Gloas { .. } => {
|
||||
unreachable!("block_data called on Gloas variant — use envelope data instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> RangeSyncBlock<E> {
|
||||
/// Constructs an `RangeSyncBlock` from a block and availability data.
|
||||
/// Constructs a `RangeSyncBlock` from a block and availability data.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -94,32 +117,53 @@ impl<E: EthSpec> RangeSyncBlock<E> {
|
||||
T: BeaconChainTypes<EthSpec = E>,
|
||||
{
|
||||
let available_block = AvailableBlock::new(block, block_data, da_checker, spec)?;
|
||||
Ok(Self {
|
||||
block: available_block,
|
||||
})
|
||||
Ok(Self::Base(available_block))
|
||||
}
|
||||
|
||||
pub fn new_gloas(
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
envelope: Option<Box<AvailableEnvelope<E>>>,
|
||||
) -> Self {
|
||||
Self::Gloas { block, envelope }
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deconstruct(self) -> (Hash256, Arc<SignedBeaconBlock<E>>, AvailableBlockData<E>) {
|
||||
self.block.deconstruct()
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => block.deconstruct(),
|
||||
RangeSyncBlock::Gloas { .. } => {
|
||||
unreachable!("deconstruct called on Gloas variant")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn n_blobs(&self) -> usize {
|
||||
match self.block_data() {
|
||||
AvailableBlockData::NoData | AvailableBlockData::DataColumns(_) => 0,
|
||||
AvailableBlockData::Blobs(blobs) => blobs.len(),
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => match block.data() {
|
||||
AvailableBlockData::NoData | AvailableBlockData::DataColumns(_) => 0,
|
||||
AvailableBlockData::Blobs(blobs) => blobs.len(),
|
||||
},
|
||||
RangeSyncBlock::Gloas { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn n_data_columns(&self) -> usize {
|
||||
match self.block_data() {
|
||||
AvailableBlockData::NoData | AvailableBlockData::Blobs(_) => 0,
|
||||
AvailableBlockData::DataColumns(columns) => columns.len(),
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => match block.data() {
|
||||
AvailableBlockData::NoData | AvailableBlockData::Blobs(_) => 0,
|
||||
AvailableBlockData::DataColumns(columns) => columns.len(),
|
||||
},
|
||||
RangeSyncBlock::Gloas { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_available_block(self) -> AvailableBlock<E> {
|
||||
self.block
|
||||
match self {
|
||||
RangeSyncBlock::Base(block) => block,
|
||||
RangeSyncBlock::Gloas { .. } => {
|
||||
unreachable!("into_available_block called on Gloas variant")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,31 +431,31 @@ impl<E: EthSpec> AsBlock<E> for AvailableBlock<E> {
|
||||
|
||||
impl<E: EthSpec> AsBlock<E> for RangeSyncBlock<E> {
|
||||
fn slot(&self) -> Slot {
|
||||
self.as_block().slot()
|
||||
RangeSyncBlock::as_block(self).slot()
|
||||
}
|
||||
fn epoch(&self) -> Epoch {
|
||||
self.as_block().epoch()
|
||||
RangeSyncBlock::as_block(self).epoch()
|
||||
}
|
||||
fn parent_root(&self) -> Hash256 {
|
||||
self.as_block().parent_root()
|
||||
RangeSyncBlock::as_block(self).parent_root()
|
||||
}
|
||||
fn state_root(&self) -> Hash256 {
|
||||
self.as_block().state_root()
|
||||
RangeSyncBlock::as_block(self).state_root()
|
||||
}
|
||||
fn signed_block_header(&self) -> SignedBeaconBlockHeader {
|
||||
self.as_block().signed_block_header()
|
||||
RangeSyncBlock::as_block(self).signed_block_header()
|
||||
}
|
||||
fn message(&self) -> BeaconBlockRef<'_, E> {
|
||||
self.as_block().message()
|
||||
RangeSyncBlock::as_block(self).message()
|
||||
}
|
||||
fn as_block(&self) -> &SignedBeaconBlock<E> {
|
||||
self.block.as_block()
|
||||
RangeSyncBlock::as_block(self)
|
||||
}
|
||||
fn block_cloned(&self) -> Arc<SignedBeaconBlock<E>> {
|
||||
self.block.block_cloned()
|
||||
RangeSyncBlock::block_cloned(self)
|
||||
}
|
||||
fn canonical_root(&self) -> Hash256 {
|
||||
self.block.block_root()
|
||||
self.block_root()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use tracing::{debug, error, instrument};
|
||||
use types::data::{BlobIdentifier, FixedBlobSidecarList, PartialDataColumn};
|
||||
use types::{
|
||||
BlobSidecar, BlobSidecarList, BlockImportSource, ChainSpec, DataColumnSidecar,
|
||||
DataColumnSidecarList, Epoch, EthSpec, Hash256, PartialDataColumnSidecarError,
|
||||
DataColumnSidecarList, Epoch, EthSpec, ForkName, Hash256, PartialDataColumnSidecarError,
|
||||
PartialDataColumnSidecarRef, SignedBeaconBlock, Slot, new_non_zero_usize,
|
||||
};
|
||||
|
||||
@@ -540,6 +540,11 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
self.da_check_required_for_epoch(epoch) && self.spec.is_peer_das_enabled_for_epoch(epoch)
|
||||
}
|
||||
|
||||
/// Determines if execution payload envelopes are required for an epoch (Gloas and later).
|
||||
pub fn envelopes_required_for_epoch(&self, epoch: Epoch) -> bool {
|
||||
self.spec.fork_name_at_epoch(epoch) >= ForkName::Gloas
|
||||
}
|
||||
|
||||
/// See `Self::blobs_required_for_epoch`
|
||||
fn blobs_required_for_block(&self, block: &SignedBeaconBlock<T::EthSpec>) -> bool {
|
||||
block.num_expected_blobs() > 0 && self.blobs_required_for_epoch(block.epoch())
|
||||
|
||||
@@ -417,6 +417,9 @@ pub enum Work<E: EthSpec> {
|
||||
RpcBlobs {
|
||||
process_fn: AsyncFn,
|
||||
},
|
||||
RpcPayloadEnvelope {
|
||||
process_fn: AsyncFn,
|
||||
},
|
||||
RpcCustodyColumn(AsyncFn),
|
||||
ColumnReconstruction(AsyncFn),
|
||||
IgnoredRpcBlock {
|
||||
@@ -483,6 +486,7 @@ pub enum WorkType {
|
||||
GossipLightClientOptimisticUpdate,
|
||||
RpcBlock,
|
||||
RpcBlobs,
|
||||
RpcPayloadEnvelope,
|
||||
RpcCustodyColumn,
|
||||
ColumnReconstruction,
|
||||
IgnoredRpcBlock,
|
||||
@@ -545,6 +549,7 @@ impl<E: EthSpec> Work<E> {
|
||||
Work::GossipProposerPreferences(_) => WorkType::GossipProposerPreferences,
|
||||
Work::RpcBlock { .. } => WorkType::RpcBlock,
|
||||
Work::RpcBlobs { .. } => WorkType::RpcBlobs,
|
||||
Work::RpcPayloadEnvelope { .. } => WorkType::RpcPayloadEnvelope,
|
||||
Work::RpcCustodyColumn { .. } => WorkType::RpcCustodyColumn,
|
||||
Work::ColumnReconstruction(_) => WorkType::ColumnReconstruction,
|
||||
Work::IgnoredRpcBlock { .. } => WorkType::IgnoredRpcBlock,
|
||||
@@ -1183,7 +1188,9 @@ impl<E: EthSpec> BeaconProcessor<E> {
|
||||
Work::GossipLightClientOptimisticUpdate { .. } => work_queues
|
||||
.lc_gossip_optimistic_update_queue
|
||||
.push(work, work_id),
|
||||
Work::RpcBlock { .. } | Work::IgnoredRpcBlock { .. } => {
|
||||
Work::RpcBlock { .. }
|
||||
| Work::IgnoredRpcBlock { .. }
|
||||
| Work::RpcPayloadEnvelope { .. } => {
|
||||
work_queues.rpc_block_queue.push(work, work_id)
|
||||
}
|
||||
Work::RpcBlobs { .. } => work_queues.rpc_blob_queue.push(work, work_id),
|
||||
@@ -1318,7 +1325,9 @@ impl<E: EthSpec> BeaconProcessor<E> {
|
||||
WorkType::GossipLightClientOptimisticUpdate => {
|
||||
work_queues.lc_gossip_optimistic_update_queue.len()
|
||||
}
|
||||
WorkType::RpcBlock => work_queues.rpc_block_queue.len(),
|
||||
WorkType::RpcBlock | WorkType::RpcPayloadEnvelope => {
|
||||
work_queues.rpc_block_queue.len()
|
||||
}
|
||||
WorkType::RpcBlobs | WorkType::IgnoredRpcBlock => {
|
||||
work_queues.rpc_blob_queue.len()
|
||||
}
|
||||
@@ -1513,6 +1522,7 @@ impl<E: EthSpec> BeaconProcessor<E> {
|
||||
beacon_block_root: _,
|
||||
}
|
||||
| Work::RpcBlobs { process_fn }
|
||||
| Work::RpcPayloadEnvelope { process_fn }
|
||||
| Work::RpcCustodyColumn(process_fn)
|
||||
| Work::ColumnReconstruction(process_fn) => task_spawner.spawn_async(process_fn),
|
||||
Work::IgnoredRpcBlock { process_fn } => task_spawner.spawn_blocking(process_fn),
|
||||
|
||||
@@ -31,6 +31,10 @@ pub enum SyncRequestId {
|
||||
BlobsByRange(BlobsByRangeRequestId),
|
||||
/// Data columns by range request
|
||||
DataColumnsByRange(DataColumnsByRangeRequestId),
|
||||
/// Request searching for an execution payload envelope given a block root.
|
||||
SinglePayloadEnvelope { id: SingleLookupReqId },
|
||||
/// Payload envelopes by range request
|
||||
PayloadEnvelopesByRange(PayloadEnvelopesByRangeRequestId),
|
||||
}
|
||||
|
||||
/// Request ID for data_columns_by_root requests. Block lookups do not issue this request directly.
|
||||
@@ -76,6 +80,14 @@ pub enum DataColumnsByRangeRequester {
|
||||
CustodyBackfillSync(CustodyBackFillBatchRequestId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct PayloadEnvelopesByRangeRequestId {
|
||||
/// Id to identify this attempt at a payload_envelopes_by_range request for `parent_request_id`
|
||||
pub id: Id,
|
||||
/// The Id of the overall By Range request for block components.
|
||||
pub parent_request_id: ComponentsByRangeRequestId,
|
||||
}
|
||||
|
||||
/// Block components by range request for range sync. Includes an ID for downstream consumers to
|
||||
/// handle retries and tie all their sub requests together.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
@@ -252,6 +264,12 @@ macro_rules! impl_display {
|
||||
impl_display!(BlocksByRangeRequestId, "{}/{}", id, parent_request_id);
|
||||
impl_display!(BlobsByRangeRequestId, "{}/{}", id, parent_request_id);
|
||||
impl_display!(DataColumnsByRangeRequestId, "{}/{}", id, parent_request_id);
|
||||
impl_display!(
|
||||
PayloadEnvelopesByRangeRequestId,
|
||||
"{}/{}",
|
||||
id,
|
||||
parent_request_id
|
||||
);
|
||||
impl_display!(ComponentsByRangeRequestId, "{}/{}", id, requester);
|
||||
impl_display!(DataColumnsByRootRequestId, "{}/{}", id, requester);
|
||||
impl_display!(SingleLookupReqId, "{}/Lookup/{}", req_id, lookup_id);
|
||||
|
||||
Reference in New Issue
Block a user