Skip to content

Commit 909be88

Browse files
committed
Devnet4 store and verification: dual-key verification, remove proposer attestation
- verify_signatures(): attestation proofs use get_attestation_pubkey(), proposer signature verified with get_proposal_pubkey() over block root - on_block_core(): remove proposer attestation processing (~40 lines) — no more gossip signature insert or dummy proof for proposer - Gossip attestation/aggregation verification: get_attestation_pubkey() - Remove StoreError::ProposerAttestationMismatch variant - Storage: write/read BlockSignatures directly (no wrapper), fix field access .message.block → .message - Table docs: BlockSignatures stores BlockSignatures (not WithAttestation)
1 parent 8228ecf commit 909be88

3 files changed

Lines changed: 45 additions & 119 deletions

File tree

crates/blockchain/src/store.rs

Lines changed: 31 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ use ethlambda_types::{
1111
AggregatedAttestation, AggregationBits, Attestation, AttestationData,
1212
SignedAggregatedAttestation, SignedAttestation, validator_indices,
1313
},
14-
block::{
15-
AggregatedAttestations, AggregatedSignatureProof, Block, BlockBody,
16-
SignedBlockWithAttestation,
17-
},
14+
block::{AggregatedAttestations, AggregatedSignatureProof, Block, BlockBody, SignedBlock},
1815
checkpoint::Checkpoint,
1916
primitives::{H256, ssz::TreeHash},
2017
signature::ValidatorSignature,
@@ -160,7 +157,7 @@ fn aggregate_committee_signatures(store: &mut Store) -> Vec<SignedAggregatedAtte
160157
let Some(validator) = validators.get(*vid as usize) else {
161158
continue;
162159
};
163-
let Ok(pubkey) = validator.get_pubkey() else {
160+
let Ok(pubkey) = validator.get_attestation_pubkey() else {
164161
continue;
165162
};
166163
sigs.push(sig.clone());
@@ -378,7 +375,7 @@ pub fn on_gossip_attestation(
378375
return Err(StoreError::InvalidValidatorIndex);
379376
}
380377
let validator_pubkey = target_state.validators[validator_id as usize]
381-
.get_pubkey()
378+
.get_attestation_pubkey()
382379
.map_err(|_| StoreError::PubkeyDecodingFailed(validator_id))?;
383380

384381
// Verify the validator's XMSS signature
@@ -449,7 +446,7 @@ pub fn on_gossip_aggregated_attestation(
449446
.iter()
450447
.map(|&vid| {
451448
validators[vid as usize]
452-
.get_pubkey()
449+
.get_attestation_pubkey()
453450
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
454451
})
455452
.collect::<Result<_, _>>()?;
@@ -508,7 +505,8 @@ pub fn on_gossip_aggregated_attestation(
508505
/// and stores them for future block building. Use this for all production paths.
509506
pub fn on_block(
510507
store: &mut Store,
511-
signed_block: SignedBlockWithAttestation,
508+
signed_block: SignedBlock,
509+
local_validator_ids: &[u64],
512510
) -> Result<(), StoreError> {
513511
on_block_core(store, signed_block, true)
514512
}
@@ -519,7 +517,7 @@ pub fn on_block(
519517
/// where signatures are absent or irrelevant (e.g., fork choice spec tests).
520518
pub fn on_block_without_verification(
521519
store: &mut Store,
522-
signed_block: SignedBlockWithAttestation,
520+
signed_block: SignedBlock,
523521
) -> Result<(), StoreError> {
524522
on_block_core(store, signed_block, false)
525523
}
@@ -530,13 +528,14 @@ pub fn on_block_without_verification(
530528
/// for future block building. When false, all signature checks are skipped.
531529
fn on_block_core(
532530
store: &mut Store,
533-
signed_block: SignedBlockWithAttestation,
531+
signed_block: SignedBlock,
534532
verify: bool,
533+
_local_validator_ids: &[u64],
535534
) -> Result<(), StoreError> {
536535
let _timing = metrics::time_fork_choice_block_processing();
537536
let block_start = std::time::Instant::now();
538537

539-
let block = &signed_block.block.block;
538+
let block = &signed_block.message;
540539
let block_root = block.tree_hash_root();
541540
let slot = block.slot;
542541

@@ -563,8 +562,7 @@ fn on_block_core(
563562
}
564563
let sig_verification = sig_verification_start.elapsed();
565564

566-
let block = signed_block.block.block.clone();
567-
let proposer_attestation = signed_block.block.proposer_attestation.clone();
565+
let block = signed_block.message.clone();
568566

569567
// Execute state transition function to compute post-block state
570568
let state_transition_start = std::time::Instant::now();
@@ -594,7 +592,6 @@ fn on_block_core(
594592
let aggregated_attestations = &block.body.attestations;
595593
let attestation_signatures = &signed_block.signature.attestation_signatures;
596594

597-
// Process block body attestations.
598595
// Store attestation data by root and proofs in known aggregated payloads.
599596
let mut att_data_entries: Vec<(H256, AttestationData)> = Vec::new();
600597
let mut known_entries: Vec<(SignatureKey, StoredAggregatedPayload)> = Vec::new();
@@ -617,43 +614,13 @@ fn on_block_core(
617614
}
618615
}
619616

620-
// Process proposer attestation as pending (enters "new" stage via gossip path)
621-
// The proposer's attestation should NOT affect this block's fork choice position.
622-
let proposer_vid = proposer_attestation.validator_id;
623-
let proposer_data_root = proposer_attestation.data.tree_hash_root();
624-
att_data_entries.push((proposer_data_root, proposer_attestation.data.clone()));
625-
626-
// Batch-insert all attestation data (body + proposer) in a single commit
617+
// Batch-insert attestation data and known aggregated payloads
627618
store.insert_attestation_data_by_root_batch(att_data_entries);
628619
store.insert_known_aggregated_payloads_batch(known_entries);
629620

630621
// Update forkchoice head based on new block and attestations
631-
// IMPORTANT: This must happen BEFORE processing proposer attestation
632-
// to prevent the proposer from gaining circular weight advantage.
633622
update_head(store, false);
634623

635-
if !verify {
636-
// Without sig verification, insert directly with a dummy proof
637-
let participants = aggregation_bits_from_validator_indices(&[proposer_vid]);
638-
let payload = StoredAggregatedPayload {
639-
slot: proposer_attestation.data.slot,
640-
proof: AggregatedSignatureProof::empty(participants),
641-
};
642-
store.insert_new_aggregated_payload((proposer_vid, proposer_data_root), payload);
643-
} else {
644-
// Store the proposer's signature unconditionally for future block building.
645-
// Subnet filtering is handled at the P2P subscription layer.
646-
let proposer_sig =
647-
ValidatorSignature::from_bytes(&signed_block.signature.proposer_signature)
648-
.map_err(|_| StoreError::SignatureDecodingFailed)?;
649-
store.insert_gossip_signature(
650-
proposer_data_root,
651-
proposer_attestation.data.slot,
652-
proposer_vid,
653-
proposer_sig,
654-
);
655-
}
656-
657624
let block_total = block_start.elapsed();
658625
info!(
659626
%slot,
@@ -944,14 +911,6 @@ pub enum StoreError {
944911

945912
#[error("Validator {validator_index} is not the proposer for slot {slot}")]
946913
NotProposer { validator_index: u64, slot: u64 },
947-
948-
#[error(
949-
"Proposer attestation validator_id {attestation_id} does not match block proposer_index {proposer_index}"
950-
)]
951-
ProposerAttestationMismatch {
952-
attestation_id: u64,
953-
proposer_index: u64,
954-
},
955914
}
956915

957916
/// Build an AggregationBits bitfield from a list of validator indices.
@@ -1147,16 +1106,13 @@ fn build_block(
11471106
/// Verify all signatures in a signed block.
11481107
///
11491108
/// Each attestation has a corresponding proof in the signature list.
1150-
fn verify_signatures(
1151-
state: &State,
1152-
signed_block: &SignedBlockWithAttestation,
1153-
) -> Result<(), StoreError> {
1109+
fn verify_signatures(state: &State, signed_block: &SignedBlock) -> Result<(), StoreError> {
11541110
use ethlambda_crypto::verify_aggregated_signature;
11551111
use ethlambda_types::signature::ValidatorSignature;
11561112

11571113
let total_start = std::time::Instant::now();
11581114

1159-
let block = &signed_block.block.block;
1115+
let block = &signed_block.message;
11601116
let attestations = &block.body.attestations;
11611117
let attestation_signatures = &signed_block.signature.attestation_signatures;
11621118

@@ -1179,14 +1135,14 @@ fn verify_signatures(
11791135
let slot: u32 = attestation.data.slot.try_into().expect("slot exceeds u32");
11801136
let message = attestation.data.tree_hash_root();
11811137

1182-
// Collect public keys with bounds check in a single pass
1138+
// Collect attestation public keys with bounds check in a single pass
11831139
let public_keys: Vec<_> = validator_indices(&attestation.aggregation_bits)
11841140
.map(|vid| {
11851141
if vid >= num_validators {
11861142
return Err(StoreError::InvalidValidatorIndex);
11871143
}
11881144
validators[vid as usize]
1189-
.get_pubkey()
1145+
.get_attestation_pubkey()
11901146
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
11911147
})
11921148
.collect::<Result<_, _>>()?;
@@ -1207,15 +1163,7 @@ fn verify_signatures(
12071163

12081164
let proposer_start = std::time::Instant::now();
12091165

1210-
let proposer_attestation = &signed_block.block.proposer_attestation;
1211-
1212-
if proposer_attestation.validator_id != block.proposer_index {
1213-
return Err(StoreError::ProposerAttestationMismatch {
1214-
attestation_id: proposer_attestation.validator_id,
1215-
proposer_index: block.proposer_index,
1216-
});
1217-
}
1218-
1166+
// Verify proposer signature over block root using proposal key
12191167
let proposer_signature =
12201168
ValidatorSignature::from_bytes(&signed_block.signature.proposer_signature)
12211169
.map_err(|_| StoreError::ProposerSignatureDecodingFailed)?;
@@ -1225,17 +1173,13 @@ fn verify_signatures(
12251173
.ok_or(StoreError::InvalidValidatorIndex)?;
12261174

12271175
let proposer_pubkey = proposer
1228-
.get_pubkey()
1176+
.get_proposal_pubkey()
12291177
.map_err(|_| StoreError::PubkeyDecodingFailed(proposer.index))?;
12301178

1231-
let slot = proposer_attestation
1232-
.data
1233-
.slot
1234-
.try_into()
1235-
.expect("slot exceeds u32");
1236-
let message = proposer_attestation.data.tree_hash_root();
1179+
let slot: u32 = block.slot.try_into().expect("slot exceeds u32");
1180+
let block_root = block.tree_hash_root();
12371181

1238-
if !proposer_signature.is_valid(&proposer_pubkey, slot, &message) {
1182+
if !proposer_signature.is_valid(&proposer_pubkey, slot, &block_root) {
12391183
return Err(StoreError::ProposerSignatureVerificationFailed);
12401184
}
12411185
let proposer_elapsed = proposer_start.elapsed();
@@ -1303,11 +1247,8 @@ fn reorg_depth(old_head: H256, new_head: H256, store: &Store) -> Option<u64> {
13031247
mod tests {
13041248
use super::*;
13051249
use ethlambda_types::{
1306-
attestation::{AggregatedAttestation, AggregationBits, Attestation, AttestationData},
1307-
block::{
1308-
AggregatedSignatureProof, BlockBody, BlockSignatures, BlockWithAttestation,
1309-
SignedBlockWithAttestation,
1310-
},
1250+
attestation::{AggregatedAttestation, AggregationBits, AttestationData},
1251+
block::{AggregatedSignatureProof, BlockBody, BlockSignatures, SignedBlock},
13111252
checkpoint::Checkpoint,
13121253
state::State,
13131254
};
@@ -1336,26 +1277,20 @@ mod tests {
13361277

13371278
let attestation = AggregatedAttestation {
13381279
aggregation_bits: attestation_bits,
1339-
data: attestation_data.clone(),
1280+
data: attestation_data,
13401281
};
13411282
let proof = AggregatedSignatureProof::empty(proof_bits);
13421283

13431284
let attestations = AggregatedAttestations::new(vec![attestation]).unwrap();
13441285
let attestation_signatures = ssz_types::VariableList::new(vec![proof]).unwrap();
13451286

1346-
let signed_block = SignedBlockWithAttestation {
1347-
block: BlockWithAttestation {
1348-
block: Block {
1349-
slot: 0,
1350-
proposer_index: 0,
1351-
parent_root: H256::ZERO,
1352-
state_root: H256::ZERO,
1353-
body: BlockBody { attestations },
1354-
},
1355-
proposer_attestation: Attestation {
1356-
validator_id: 0,
1357-
data: attestation_data,
1358-
},
1287+
let signed_block = SignedBlock {
1288+
message: Block {
1289+
slot: 0,
1290+
proposer_index: 0,
1291+
parent_root: H256::ZERO,
1292+
state_root: H256::ZERO,
1293+
body: BlockBody { attestations },
13591294
},
13601295
signature: BlockSignatures {
13611296
attestation_signatures,

crates/storage/src/api/tables.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub enum Table {
55
BlockHeaders,
66
/// Block body storage: H256 -> BlockBody
77
BlockBodies,
8-
/// Block signatures storage: H256 -> BlockSignaturesWithAttestation
8+
/// Block signatures storage: H256 -> BlockSignatures
99
///
1010
/// Stored separately from blocks because the genesis block has no signatures.
1111
/// All other blocks must have an entry in this table.

crates/storage/src/store.rs

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ use crate::types::{StoredAggregatedPayload, StoredSignature};
1313

1414
use ethlambda_types::{
1515
attestation::AttestationData,
16-
block::{
17-
AggregatedSignatureProof, Block, BlockBody, BlockHeader, BlockSignaturesWithAttestation,
18-
BlockWithAttestation, SignedBlockWithAttestation,
19-
},
16+
block::{Block, BlockBody, BlockHeader, BlockSignatures, SignedBlock},
2017
checkpoint::Checkpoint,
2118
primitives::{
2219
H256,
@@ -742,7 +739,7 @@ impl Store {
742739
///
743740
/// When the block is later processed via [`insert_signed_block`](Self::insert_signed_block),
744741
/// the same keys are overwritten (idempotent) and a `LiveChain` entry is added.
745-
pub fn insert_pending_block(&mut self, root: H256, signed_block: SignedBlockWithAttestation) {
742+
pub fn insert_pending_block(&mut self, root: H256, signed_block: SignedBlock) {
746743
let mut batch = self.backend.begin_write().expect("write batch");
747744
write_signed_block(batch.as_mut(), &root, signed_block);
748745
batch.commit().expect("commit");
@@ -755,7 +752,7 @@ impl Store {
755752
/// only storing signatures for non-genesis blocks.
756753
///
757754
/// Takes ownership to avoid cloning large signature data.
758-
pub fn insert_signed_block(&mut self, root: H256, signed_block: SignedBlockWithAttestation) {
755+
pub fn insert_signed_block(&mut self, root: H256, signed_block: SignedBlock) {
759756
let mut batch = self.backend.begin_write().expect("write batch");
760757
let block = write_signed_block(batch.as_mut(), &root, signed_block);
761758

@@ -774,7 +771,7 @@ impl Store {
774771
///
775772
/// Returns None if any of the components are not found.
776773
/// Note: Genesis block has no entry in BlockSignatures table.
777-
pub fn get_signed_block(&self, root: &H256) -> Option<SignedBlockWithAttestation> {
774+
pub fn get_signed_block(&self, root: &H256) -> Option<SignedBlock> {
778775
let view = self.backend.begin_read().expect("read view");
779776
let key = root.as_ssz_bytes();
780777

@@ -792,10 +789,12 @@ impl Store {
792789
};
793790

794791
let block = Block::from_header_and_body(header, body);
795-
let signatures =
796-
BlockSignaturesWithAttestation::from_ssz_bytes(&sig_bytes).expect("valid signatures");
792+
let signature = BlockSignatures::from_ssz_bytes(&sig_bytes).expect("valid signatures");
797793

798-
Some(signatures.to_signed_block(block))
794+
Some(SignedBlock {
795+
message: block,
796+
signature,
797+
})
799798
}
800799

801800
// ============ States ============
@@ -1149,21 +1148,13 @@ impl Store {
11491148
fn write_signed_block(
11501149
batch: &mut dyn StorageWriteBatch,
11511150
root: &H256,
1152-
signed_block: SignedBlockWithAttestation,
1151+
signed_block: SignedBlock,
11531152
) -> Block {
1154-
let SignedBlockWithAttestation {
1155-
block: BlockWithAttestation {
1156-
block,
1157-
proposer_attestation,
1158-
},
1153+
let SignedBlock {
1154+
message: block,
11591155
signature,
11601156
} = signed_block;
11611157

1162-
let signatures = BlockSignaturesWithAttestation {
1163-
proposer_attestation,
1164-
signatures: signature,
1165-
};
1166-
11671158
let header = block.header();
11681159
let root_bytes = root.as_ssz_bytes();
11691160

@@ -1180,7 +1171,7 @@ fn write_signed_block(
11801171
.expect("put block body");
11811172
}
11821173

1183-
let sig_entries = vec![(root_bytes, signatures.as_ssz_bytes())];
1174+
let sig_entries = vec![(root_bytes, signature.as_ssz_bytes())];
11841175
batch
11851176
.put_batch(Table::BlockSignatures, sig_entries)
11861177
.expect("put block signatures");

0 commit comments

Comments
 (0)