mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 11:41:51 +00:00
Sidecar inclusion proof (#4900)
* Refactor BlobSidecar to new type * Fix some compile errors * Gossip verification compiles * Fix http api types take 1 * Fix another round of compile errors * Beacon node crate compiles * EF tests compile * Remove all blob signing from VC * fmt * Tests compile * Fix some tests * Fix more http tests * get compiling * Fix gossip conditions and tests * Add basic proof generation and verification * remove unnecessary ssz decode * add back build_sidecar * remove default at fork for blobs * fix beacon chain tests * get relase tests compiling * fix lints * fix existing spec tests * add new ef tests * fix gossip duplicate rule * lints * add back sidecar signature check in gossip * add finalized descendant check to blob sidecar gossip * fix error conversion * fix release tests * sidecar inclusion self review cleanup * Add proof verification and computation metrics * Remove accidentally committed file * Unify some block and blob errors; add slashing conditions for sidecars * Address review comment * Clean up re-org tests (#4957) * Address more review comments * Add Comments & Eliminate Unnecessary Clones * update names * Update beacon_node/beacon_chain/src/metrics.rs Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Update beacon_node/network/src/network_beacon_processor/tests.rs Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * pr feedback * fix test compile * Sidecar Inclusion proof small refactor and updates (#4967) * Update some comments, variables and small cosmetic fixes. * Couple blobs and proofs into a tuple in `PayloadAndBlobs` for simplicity and safety. * Update function comment. * Update testing/ef_tests/src/cases/merkle_proof_validity.rs Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Rename the block and blob wrapper types used in the beacon API interfaces. * make sure gossip invalid blobs are passed to the slasher (#4970) * Add blob headers to slasher before adding to DA checker * Replace Vec with HashSet in BlockQueue * fmt * Rename gindex -> index * Simplify gossip condition --------- Co-authored-by: realbigsean <seananderson33@gmail.com> Co-authored-by: realbigsean <sean@sigmaprime.io> Co-authored-by: Michael Sproul <michael@sigmaprime.io> Co-authored-by: Mark Mackey <mark@sigmaprime.io> Co-authored-by: Jimmy Chen <jchen.tc@gmail.com>
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
use super::*;
|
||||
use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
|
||||
use ::fork_choice::PayloadVerificationStatus;
|
||||
use ::fork_choice::{PayloadVerificationStatus, ProposerHeadError};
|
||||
use beacon_chain::beacon_proposer_cache::compute_proposer_duties_from_head;
|
||||
use beacon_chain::blob_verification::GossipBlobError;
|
||||
use beacon_chain::chain_config::{
|
||||
DisallowedReOrgOffsets, DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, DEFAULT_RE_ORG_THRESHOLD,
|
||||
};
|
||||
use beacon_chain::slot_clock::SlotClock;
|
||||
use beacon_chain::{
|
||||
attestation_verification::{
|
||||
@@ -20,7 +25,7 @@ use std::time::Duration;
|
||||
use types::{
|
||||
Attestation, AttesterSlashing, BeaconBlock, BeaconState, BlobSidecar, BlobsList, Checkpoint,
|
||||
EthSpec, ExecutionBlockHash, ForkName, Hash256, IndexedAttestation, KzgProof,
|
||||
ProgressiveBalancesMode, Signature, SignedBeaconBlock, SignedBlobSidecar, Slot, Uint256,
|
||||
ProgressiveBalancesMode, ProposerPreparationData, SignedBeaconBlock, Slot, Uint256,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Clone, Deserialize, Decode)]
|
||||
@@ -38,6 +43,13 @@ pub struct Head {
|
||||
root: Hash256,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ShouldOverrideFcu {
|
||||
validator_is_connected: bool,
|
||||
result: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Checks {
|
||||
@@ -50,6 +62,8 @@ pub struct Checks {
|
||||
u_justified_checkpoint: Option<Checkpoint>,
|
||||
u_finalized_checkpoint: Option<Checkpoint>,
|
||||
proposer_boost_root: Option<Hash256>,
|
||||
get_proposer_head: Option<Hash256>,
|
||||
should_override_forkchoice_update: Option<ShouldOverrideFcu>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -256,6 +270,8 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
||||
u_justified_checkpoint,
|
||||
u_finalized_checkpoint,
|
||||
proposer_boost_root,
|
||||
get_proposer_head,
|
||||
should_override_forkchoice_update: should_override_fcu,
|
||||
} = checks.as_ref();
|
||||
|
||||
if let Some(expected_head) = head {
|
||||
@@ -294,6 +310,14 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
||||
if let Some(expected_proposer_boost_root) = proposer_boost_root {
|
||||
tester.check_expected_proposer_boost_root(*expected_proposer_boost_root)?;
|
||||
}
|
||||
|
||||
if let Some(should_override_fcu) = should_override_fcu {
|
||||
tester.check_should_override_fcu(*should_override_fcu)?;
|
||||
}
|
||||
|
||||
if let Some(expected_proposer_head) = get_proposer_head {
|
||||
tester.check_expected_proposer_head(*expected_proposer_head)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,6 +349,7 @@ impl<E: EthSpec> Tester<E> {
|
||||
}
|
||||
|
||||
let harness = BeaconChainHarness::<EphemeralHarnessType<E>>::builder(E::default())
|
||||
.logger(logging::test_logger())
|
||||
.spec(spec.clone())
|
||||
.keypairs(vec![])
|
||||
.chain_config(ChainConfig {
|
||||
@@ -413,6 +438,8 @@ impl<E: EthSpec> Tester<E> {
|
||||
) -> Result<(), Error> {
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let mut blob_success = true;
|
||||
|
||||
// Convert blobs and kzg_proofs into sidecars, then plumb them into the availability tracker
|
||||
if let Some(blobs) = blobs.clone() {
|
||||
let proofs = kzg_proofs.unwrap();
|
||||
@@ -432,25 +459,32 @@ impl<E: EthSpec> Tester<E> {
|
||||
.zip(commitments.into_iter())
|
||||
.enumerate()
|
||||
{
|
||||
let signed_sidecar = SignedBlobSidecar {
|
||||
message: Arc::new(BlobSidecar {
|
||||
block_root,
|
||||
index: i as u64,
|
||||
slot: block.slot(),
|
||||
block_parent_root: block.parent_root(),
|
||||
proposer_index: block.message().proposer_index(),
|
||||
blob,
|
||||
kzg_commitment,
|
||||
kzg_proof,
|
||||
}),
|
||||
signature: Signature::empty(),
|
||||
_phantom: Default::default(),
|
||||
};
|
||||
let result = self.block_on_dangerous(
|
||||
self.harness
|
||||
.chain
|
||||
.process_gossip_blob(GossipVerifiedBlob::__assumed_valid(signed_sidecar)),
|
||||
)?;
|
||||
let blob_sidecar = Arc::new(BlobSidecar {
|
||||
index: i as u64,
|
||||
blob,
|
||||
kzg_commitment,
|
||||
kzg_proof,
|
||||
signed_block_header: block.signed_block_header(),
|
||||
kzg_commitment_inclusion_proof: block
|
||||
.message()
|
||||
.body()
|
||||
.kzg_commitment_merkle_proof(i)
|
||||
.unwrap(),
|
||||
});
|
||||
|
||||
let chain = self.harness.chain.clone();
|
||||
let blob =
|
||||
match GossipVerifiedBlob::new(blob_sidecar.clone(), blob_sidecar.index, &chain)
|
||||
{
|
||||
Ok(gossip_verified_blob) => gossip_verified_blob,
|
||||
Err(GossipBlobError::KzgError(_)) => {
|
||||
blob_success = false;
|
||||
GossipVerifiedBlob::__assumed_valid(blob_sidecar)
|
||||
}
|
||||
Err(_) => GossipVerifiedBlob::__assumed_valid(blob_sidecar),
|
||||
};
|
||||
let result =
|
||||
self.block_on_dangerous(self.harness.chain.process_gossip_blob(blob))?;
|
||||
if valid {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
@@ -466,7 +500,7 @@ impl<E: EthSpec> Tester<E> {
|
||||
|| Ok(()),
|
||||
))?
|
||||
.map(|avail: AvailabilityProcessingStatus| avail.try_into());
|
||||
let success = result.as_ref().map_or(false, |inner| inner.is_ok());
|
||||
let success = blob_success && result.as_ref().map_or(false, |inner| inner.is_ok());
|
||||
if success != valid {
|
||||
return Err(Error::DidntFail(format!(
|
||||
"block with root {} was valid={} whilst test expects valid={}. result: {:?}",
|
||||
@@ -703,6 +737,82 @@ impl<E: EthSpec> Tester<E> {
|
||||
expected_proposer_boost_root,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check_expected_proposer_head(
|
||||
&self,
|
||||
expected_proposer_head: Hash256,
|
||||
) -> Result<(), Error> {
|
||||
let mut fc = self.harness.chain.canonical_head.fork_choice_write_lock();
|
||||
let slot = self.harness.chain.slot().unwrap();
|
||||
let canonical_head = fc.get_head(slot, &self.harness.spec).unwrap();
|
||||
let proposer_head_result = fc.get_proposer_head(
|
||||
slot,
|
||||
canonical_head,
|
||||
DEFAULT_RE_ORG_THRESHOLD,
|
||||
&DisallowedReOrgOffsets::default(),
|
||||
DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION,
|
||||
);
|
||||
let proposer_head = match proposer_head_result {
|
||||
Ok(head) => head.parent_node.root,
|
||||
Err(ProposerHeadError::DoNotReOrg(_)) => canonical_head,
|
||||
_ => panic!("Unexpected error in get proposer head"),
|
||||
};
|
||||
|
||||
check_equal("proposer_head", proposer_head, expected_proposer_head)
|
||||
}
|
||||
|
||||
pub fn check_should_override_fcu(
|
||||
&self,
|
||||
expected_should_override_fcu: ShouldOverrideFcu,
|
||||
) -> Result<(), Error> {
|
||||
// Determine proposer.
|
||||
let cached_head = self.harness.chain.canonical_head.cached_head();
|
||||
let next_slot = cached_head.snapshot.beacon_block.slot() + 1;
|
||||
let next_slot_epoch = next_slot.epoch(E::slots_per_epoch());
|
||||
let (proposer_indices, decision_root, _, fork) =
|
||||
compute_proposer_duties_from_head(next_slot_epoch, &self.harness.chain).unwrap();
|
||||
let proposer_index = proposer_indices[next_slot.as_usize() % E::slots_per_epoch() as usize];
|
||||
|
||||
// Ensure the proposer index cache is primed.
|
||||
self.harness
|
||||
.chain
|
||||
.beacon_proposer_cache
|
||||
.lock()
|
||||
.insert(next_slot_epoch, decision_root, proposer_indices, fork)
|
||||
.unwrap();
|
||||
|
||||
// Update the execution layer proposer preparation to match the test config.
|
||||
let el = self.harness.chain.execution_layer.clone().unwrap();
|
||||
self.block_on_dangerous(async {
|
||||
if expected_should_override_fcu.validator_is_connected {
|
||||
el.update_proposer_preparation(
|
||||
next_slot_epoch,
|
||||
&[ProposerPreparationData {
|
||||
validator_index: dbg!(proposer_index) as u64,
|
||||
fee_recipient: Default::default(),
|
||||
}],
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
el.clear_proposer_preparation(proposer_index as u64).await;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Check forkchoice override.
|
||||
let canonical_fcu_params = cached_head.forkchoice_update_parameters();
|
||||
let fcu_params = self
|
||||
.harness
|
||||
.chain
|
||||
.overridden_forkchoice_update_params(canonical_fcu_params)
|
||||
.unwrap();
|
||||
|
||||
check_equal(
|
||||
"should_override_forkchoice_update",
|
||||
fcu_params != canonical_fcu_params,
|
||||
expected_should_override_fcu.result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the `head` checkpoint from the beacon chain head matches the `fc` checkpoint gleaned
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::*;
|
||||
use crate::case_result::compare_result;
|
||||
use beacon_chain::kzg_utils::validate_blob;
|
||||
use eth2_network_config::TRUSTED_SETUP_BYTES;
|
||||
use kzg::{Kzg, KzgCommitment, KzgProof, TrustedSetup};
|
||||
use kzg::{Error as KzgError, Kzg, KzgCommitment, KzgProof, TrustedSetup};
|
||||
use serde::Deserialize;
|
||||
use std::convert::TryInto;
|
||||
use std::marker::PhantomData;
|
||||
@@ -91,8 +91,14 @@ impl<E: EthSpec> Case for KZGVerifyBlobKZGProof<E> {
|
||||
|
||||
let kzg = get_kzg()?;
|
||||
let result = parse_input(&self.input).and_then(|(blob, commitment, proof)| {
|
||||
validate_blob::<E>(&kzg, &blob, commitment, proof)
|
||||
.map_err(|e| Error::InternalError(format!("Failed to validate blob: {:?}", e)))
|
||||
match validate_blob::<E>(&kzg, &blob, commitment, proof) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(KzgError::KzgVerificationFailed) => Ok(false),
|
||||
Err(e) => Err(Error::InternalError(format!(
|
||||
"Failed to validate blob: {:?}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
});
|
||||
|
||||
compare_result::<bool, _>(&result, &self.output)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use crate::case_result::compare_result;
|
||||
use beacon_chain::kzg_utils::validate_blobs;
|
||||
use kzg::Error as KzgError;
|
||||
use serde::Deserialize;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
@@ -53,10 +54,23 @@ impl<E: EthSpec> Case for KZGVerifyBlobKZGProofBatch<E> {
|
||||
};
|
||||
|
||||
let kzg = get_kzg()?;
|
||||
let result = parse_input(&self.input).and_then(|(commitments, blobs, proofs)| {
|
||||
validate_blobs::<E>(&kzg, &commitments, blobs.iter().collect(), &proofs)
|
||||
.map_err(|e| Error::InternalError(format!("Failed to validate blobs: {:?}", e)))
|
||||
});
|
||||
|
||||
let result =
|
||||
parse_input(&self.input).and_then(
|
||||
|(commitments, blobs, proofs)| match validate_blobs::<E>(
|
||||
&kzg,
|
||||
&commitments,
|
||||
blobs.iter().collect(),
|
||||
&proofs,
|
||||
) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(KzgError::KzgVerificationFailed) => Ok(false),
|
||||
Err(e) => Err(Error::InternalError(format!(
|
||||
"Failed to validate blobs: {:?}",
|
||||
e
|
||||
))),
|
||||
},
|
||||
);
|
||||
|
||||
compare_result::<bool, _>(&result, &self.output)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::*;
|
||||
use crate::decode::{ssz_decode_state, yaml_decode_file};
|
||||
use crate::decode::{ssz_decode_file, ssz_decode_state, yaml_decode_file};
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
use tree_hash::Hash256;
|
||||
use types::{BeaconState, EthSpec, ForkName};
|
||||
use types::{BeaconBlockBody, BeaconBlockBodyDeneb, BeaconState, EthSpec, ForkName};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Metadata {
|
||||
@@ -82,3 +82,72 @@ impl<E: EthSpec> Case for MerkleProofValidity<E> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
pub struct KzgInclusionMerkleProofValidity<E: EthSpec> {
|
||||
pub metadata: Option<Metadata>,
|
||||
pub block: BeaconBlockBody<E>,
|
||||
pub merkle_proof: MerkleProof,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> LoadCase for KzgInclusionMerkleProofValidity<E> {
|
||||
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
|
||||
let block = match fork_name {
|
||||
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
|
||||
return Err(Error::InternalError(format!(
|
||||
"KZG inclusion merkle proof validity test skipped for {:?}",
|
||||
fork_name
|
||||
)))
|
||||
}
|
||||
ForkName::Deneb => {
|
||||
ssz_decode_file::<BeaconBlockBodyDeneb<E>>(&path.join("object.ssz_snappy"))?
|
||||
}
|
||||
};
|
||||
let merkle_proof = yaml_decode_file(&path.join("proof.yaml"))?;
|
||||
// Metadata does not exist in these tests but it is left like this just in case.
|
||||
let meta_path = path.join("meta.yaml");
|
||||
let metadata = if meta_path.exists() {
|
||||
Some(yaml_decode_file(&meta_path)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
metadata,
|
||||
block: block.into(),
|
||||
merkle_proof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Case for KzgInclusionMerkleProofValidity<E> {
|
||||
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
|
||||
let Ok(proof) = self.block.to_ref().kzg_commitment_merkle_proof(0) else {
|
||||
return Err(Error::FailedToParseTest(
|
||||
"Could not retrieve merkle proof".to_string(),
|
||||
));
|
||||
};
|
||||
let proof_len = proof.len();
|
||||
let branch_len = self.merkle_proof.branch.len();
|
||||
if proof_len != branch_len {
|
||||
return Err(Error::NotEqual(format!(
|
||||
"Branches not equal in length computed: {}, expected {}",
|
||||
proof_len, branch_len
|
||||
)));
|
||||
}
|
||||
|
||||
for (i, proof_leaf) in proof.iter().enumerate().take(proof_len) {
|
||||
let expected_leaf = self.merkle_proof.branch[i];
|
||||
if *proof_leaf != expected_leaf {
|
||||
return Err(Error::NotEqual(format!(
|
||||
"Leaves not equal in merkle proof computed: {}, expected: {}",
|
||||
hex::encode(proof_leaf),
|
||||
hex::encode(expected_leaf)
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,6 +560,13 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No FCU override tests prior to bellatrix.
|
||||
if self.handler_name == "should_override_forkchoice_update"
|
||||
&& (fork_name == ForkName::Base || fork_name == ForkName::Altair)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// These tests check block validity (which may include signatures) and there is no need to
|
||||
// run them with fake crypto.
|
||||
cfg!(not(feature = "fake_crypto"))
|
||||
@@ -786,6 +793,34 @@ impl<E: EthSpec + TypeName> Handler for MerkleProofValidityHandler<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Default(bound = ""))]
|
||||
pub struct KzgInclusionMerkleProofValidityHandler<E>(PhantomData<E>);
|
||||
|
||||
impl<E: EthSpec + TypeName> Handler for KzgInclusionMerkleProofValidityHandler<E> {
|
||||
type Case = cases::KzgInclusionMerkleProofValidity<E>;
|
||||
|
||||
fn config_name() -> &'static str {
|
||||
E::name()
|
||||
}
|
||||
|
||||
fn runner_name() -> &'static str {
|
||||
"merkle_proof"
|
||||
}
|
||||
|
||||
fn handler_name(&self) -> String {
|
||||
"single_merkle_proof".into()
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
|
||||
// Enabled in Deneb
|
||||
fork_name != ForkName::Base
|
||||
&& fork_name != ForkName::Altair
|
||||
&& fork_name != ForkName::Merge
|
||||
&& fork_name != ForkName::Capella
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Default(bound = ""))]
|
||||
pub struct OperationsHandler<E, O>(PhantomData<(E, O)>);
|
||||
|
||||
@@ -78,7 +78,6 @@ type_name!(ProposerSlashing);
|
||||
type_name_generic!(SignedAggregateAndProof);
|
||||
type_name_generic!(SignedBeaconBlock);
|
||||
type_name!(SignedBeaconBlockHeader);
|
||||
type_name_generic!(SignedBlobSidecar);
|
||||
type_name_generic!(SignedContributionAndProof);
|
||||
type_name!(SignedVoluntaryExit);
|
||||
type_name!(SigningData);
|
||||
|
||||
Reference in New Issue
Block a user