Verify KZG in Bulk During Block Sync (#4903)

This commit is contained in:
ethDreamer 2023-11-08 22:05:44 -06:00 committed by GitHub
parent a380f6ef1f
commit 7818100777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 41 deletions

View File

@ -583,29 +583,33 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
&chain.spec,
)?;
// unzip chain segment and verify kzg in bulk
let (roots, blocks): (Vec<_>, Vec<_>) = chain_segment.into_iter().unzip();
let maybe_available_blocks = chain
.data_availability_checker
.verify_kzg_for_rpc_blocks(blocks)?;
// zip it back up
let mut signature_verified_blocks = roots
.into_iter()
.zip(maybe_available_blocks)
.map(|(block_root, maybe_available_block)| {
let consensus_context = ConsensusContext::new(maybe_available_block.slot())
.set_current_block_root(block_root);
SignatureVerifiedBlock {
block: maybe_available_block,
block_root,
parent: None,
consensus_context,
}
})
.collect::<Vec<_>>();
// verify signatures
let pubkey_cache = get_validator_pubkey_cache(chain)?;
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
let mut signature_verified_blocks = Vec::with_capacity(chain_segment.len());
for (block_root, block) in &chain_segment {
let mut consensus_context =
ConsensusContext::new(block.slot()).set_current_block_root(*block_root);
signature_verifier.include_all_signatures(block.as_block(), &mut consensus_context)?;
let maybe_available_block = chain
.data_availability_checker
.check_rpc_block_availability(block.clone())?;
// Save the block and its consensus context. The context will have had its proposer index
// and attesting indices filled in, which can be used to accelerate later block processing.
signature_verified_blocks.push(SignatureVerifiedBlock {
block: maybe_available_block,
block_root: *block_root,
parent: None,
consensus_context,
});
for svb in &mut signature_verified_blocks {
signature_verifier
.include_all_signatures(svb.block.as_block(), &mut svb.consensus_context)?;
}
if signature_verifier.verify().is_err() {
@ -1159,10 +1163,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for Arc<SignedBeaconBlock
.map_err(|e| BlockSlashInfo::SignatureNotChecked(self.signed_block_header(), e))?;
let maybe_available = chain
.data_availability_checker
.check_rpc_block_availability(RpcBlock::new_without_blobs(
Some(block_root),
self.clone(),
))
.verify_kzg_for_rpc_block(RpcBlock::new_without_blobs(Some(block_root), self.clone()))
.map_err(|e| {
BlockSlashInfo::SignatureNotChecked(
self.signed_block_header(),
@ -1192,7 +1193,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for RpcBlock<T::EthSpec>
.map_err(|e| BlockSlashInfo::SignatureNotChecked(self.signed_block_header(), e))?;
let maybe_available = chain
.data_availability_checker
.check_rpc_block_availability(self.clone())
.verify_kzg_for_rpc_block(self.clone())
.map_err(|e| {
BlockSlashInfo::SignatureNotChecked(
self.signed_block_header(),

View File

@ -45,6 +45,13 @@ impl<E: EthSpec> RpcBlock<E> {
RpcBlockInner::BlockAndBlobs(block, _) => block,
}
}
pub fn blobs(&self) -> Option<&BlobSidecarList<E>> {
match &self.block {
RpcBlockInner::Block(_) => None,
RpcBlockInner::BlockAndBlobs(_, blobs) => Some(blobs),
}
}
}
/// Note: This variant is intentionally private because we want to safely construct the

View File

@ -240,9 +240,12 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
.put_pending_executed_block(executed_block)
}
/// Checks if a block is available, returns a `MaybeAvailableBlock` that may include the fully
/// available block.
pub fn check_rpc_block_availability(
/// Verifies kzg commitments for an RpcBlock, returns a `MaybeAvailableBlock` that may
/// include the fully available block.
///
/// WARNING: This function assumes all required blobs are already present, it does NOT
/// check if there are any missing blobs.
pub fn verify_kzg_for_rpc_block(
&self,
block: RpcBlock<T::EthSpec>,
) -> Result<MaybeAvailableBlock<T::EthSpec>, AvailabilityCheckError> {
@ -279,6 +282,68 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
}
}
/// Checks if a vector of blocks are available. Returns a vector of `MaybeAvailableBlock`
/// This is more efficient than calling `verify_kzg_for_rpc_block` in a loop as it does
/// all kzg verification at once
///
/// WARNING: This function assumes all required blobs are already present, it does NOT
/// check if there are any missing blobs.
pub fn verify_kzg_for_rpc_blocks(
&self,
blocks: Vec<RpcBlock<T::EthSpec>>,
) -> Result<Vec<MaybeAvailableBlock<T::EthSpec>>, AvailabilityCheckError> {
let mut results = Vec::with_capacity(blocks.len());
let all_blobs: BlobSidecarList<T::EthSpec> = blocks
.iter()
.filter(|block| self.blobs_required_for_block(block.as_block()))
// this clone is cheap as it's cloning an Arc
.filter_map(|block| block.blobs().cloned())
.flatten()
.collect::<Vec<_>>()
.into();
// verify kzg for all blobs at once
if !all_blobs.is_empty() {
let kzg = self
.kzg
.as_ref()
.ok_or(AvailabilityCheckError::KzgNotInitialized)?;
verify_kzg_for_blob_list(&all_blobs, kzg)?;
}
for block in blocks {
let (block_root, block, blobs) = block.deconstruct();
match blobs {
None => {
if self.blobs_required_for_block(&block) {
results.push(MaybeAvailableBlock::AvailabilityPending { block_root, block })
} else {
results.push(MaybeAvailableBlock::Available(AvailableBlock {
block_root,
block,
blobs: None,
}))
}
}
Some(blob_list) => {
let verified_blobs = if self.blobs_required_for_block(&block) {
Some(blob_list)
} else {
None
};
// already verified kzg for all blobs
results.push(MaybeAvailableBlock::Available(AvailableBlock {
block_root,
block,
blobs: verified_blobs,
}))
}
}
}
Ok(results)
}
/// Determines the blob requirements for a block. If the block is pre-deneb, no blobs are required.
/// If the block's epoch is from prior to the data availability boundary, no blobs are required.
fn blobs_required_for_block(&self, block: &SignedBeaconBlock<T::EthSpec>) -> bool {

View File

@ -140,7 +140,7 @@ async fn produces_attestations() {
available_block,
) = chain
.data_availability_checker
.check_rpc_block_availability(rpc_block)
.verify_kzg_for_rpc_block(rpc_block)
.unwrap()
else {
panic!("block should be available")
@ -218,7 +218,7 @@ async fn early_attester_cache_old_request() {
harness
.chain
.data_availability_checker
.check_rpc_block_availability(rpc_block)
.verify_kzg_for_rpc_block(rpc_block)
.unwrap()
else {
panic!("block should be available")

View File

@ -2464,10 +2464,10 @@ async fn weak_subjectivity_sync_test(slots: Vec<Slot>, checkpoint_slot: Slot) {
if let MaybeAvailableBlock::Available(block) = harness
.chain
.data_availability_checker
.check_rpc_block_availability(
.verify_kzg_for_rpc_block(
RpcBlock::new(Some(block_root), Arc::new(full_block), Some(blobs)).unwrap(),
)
.expect("should check availability")
.expect("should verify kzg")
{
available_blocks.push(block);
}

View File

@ -560,14 +560,10 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
downloaded_blocks: Vec<RpcBlock<T::EthSpec>>,
) -> (usize, Result<(), ChainSegmentFailed>) {
let total_blocks = downloaded_blocks.len();
let available_blocks = match downloaded_blocks
.into_iter()
.map(|block| {
self.chain
.data_availability_checker
.check_rpc_block_availability(block)
})
.collect::<Result<Vec<_>, _>>()
let available_blocks = match self
.chain
.data_availability_checker
.verify_kzg_for_rpc_blocks(downloaded_blocks)
{
Ok(blocks) => blocks
.into_iter()