diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index e9e252c..5cbaf75 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -366,8 +366,8 @@ pub fn on_block( store.update_checkpoints(ForkCheckpoints::new(store.head(), justified, finalized)); } - // Store block and state - store.insert_block(block_root, block.clone()); + // Store signed block and state + store.insert_signed_block(block_root, &signed_block); store.insert_state(block_root, post_state); // Process block body attestations and their signatures diff --git a/crates/common/types/src/block.rs b/crates/common/types/src/block.rs index 88bcf3c..c0b4788 100644 --- a/crates/common/types/src/block.rs +++ b/crates/common/types/src/block.rs @@ -129,6 +129,40 @@ pub struct BlockWithAttestation { pub proposer_attestation: Attestation, } +/// Stored block signatures and proposer attestation. +/// +/// This type stores the data needed to reconstruct a `SignedBlockWithAttestation` +/// when combined with a `Block` from the blocks table. +#[derive(Clone, Encode, Decode)] +pub struct BlockSignaturesWithAttestation { + /// The proposer's attestation for this block. + pub proposer_attestation: Attestation, + + /// The aggregated signatures for the block. + pub signatures: BlockSignatures, +} + +impl BlockSignaturesWithAttestation { + /// Create from a SignedBlockWithAttestation. + pub fn from_signed_block(signed_block: &SignedBlockWithAttestation) -> Self { + Self { + proposer_attestation: signed_block.message.proposer_attestation.clone(), + signatures: signed_block.signature.clone(), + } + } + + /// Reconstruct a SignedBlockWithAttestation given the block. + pub fn to_signed_block(&self, block: Block) -> SignedBlockWithAttestation { + SignedBlockWithAttestation { + message: BlockWithAttestation { + block, + proposer_attestation: self.proposer_attestation.clone(), + }, + signature: self.signatures.clone(), + } + } +} + /// The header of a block, containing metadata. /// /// Block headers summarize blocks without storing full content. The header diff --git a/crates/net/p2p/src/req_resp/handlers.rs b/crates/net/p2p/src/req_resp/handlers.rs index 026ae9d..6d09425 100644 --- a/crates/net/p2p/src/req_resp/handlers.rs +++ b/crates/net/p2p/src/req_resp/handlers.rs @@ -94,21 +94,41 @@ async fn handle_status_response(status: Status, peer: PeerId) { } async fn handle_blocks_by_root_request( - _server: &mut P2PServer, + server: &mut P2PServer, request: BlocksByRootRequest, - _channel: request_response::ResponseChannel, + channel: request_response::ResponseChannel, peer: PeerId, ) { let num_roots = request.len(); info!(%peer, num_roots, "Received BlocksByRoot request"); - // TODO: Implement signed block storage and send response chunks - // For now, we don't send any response (drop the channel) - // In a full implementation, we would: - // 1. Look up each requested block root - // 2. Send a response chunk for each found block - // 3. Each chunk contains: result byte + encoded SignedBlockWithAttestation - warn!(%peer, num_roots, "BlocksByRoot request received but block storage not implemented"); + // TODO: Support multiple blocks per request (currently only handles first root) + // The protocol supports up to 1024 roots, but our response type only holds one block. + let Some(root) = request.first() else { + debug!(%peer, "BlocksByRoot request with no roots"); + return; + }; + + match server.store.get_signed_block(root) { + Some(signed_block) => { + let slot = signed_block.message.block.slot; + info!(%peer, %root, %slot, "Responding to BlocksByRoot request"); + + if let Err(err) = server.swarm.behaviour_mut().req_resp.send_response( + channel, + Response::new( + ResponseResult::Success, + ResponsePayload::BlocksByRoot(signed_block), + ), + ) { + warn!(%peer, %root, ?err, "Failed to send BlocksByRoot response"); + } + } + None => { + debug!(%peer, %root, "Block not found for BlocksByRoot request"); + // Drop channel without response - peer will timeout + } + } } async fn handle_blocks_by_root_response( diff --git a/crates/storage/src/api/tables.rs b/crates/storage/src/api/tables.rs index 0b99940..7fefd0f 100644 --- a/crates/storage/src/api/tables.rs +++ b/crates/storage/src/api/tables.rs @@ -3,6 +3,8 @@ pub enum Table { /// Block storage: H256 -> Block Blocks, + /// Block signatures storage: H256 -> BlockSignaturesWithAttestation + BlockSignatures, /// State storage: H256 -> State States, /// Known attestations: u64 -> AttestationData @@ -18,8 +20,9 @@ pub enum Table { } /// All table variants. -pub const ALL_TABLES: [Table; 7] = [ +pub const ALL_TABLES: [Table; 8] = [ Table::Blocks, + Table::BlockSignatures, Table::States, Table::LatestKnownAttestations, Table::LatestNewAttestations, diff --git a/crates/storage/src/backend/rocksdb.rs b/crates/storage/src/backend/rocksdb.rs index 72fb159..6790906 100644 --- a/crates/storage/src/backend/rocksdb.rs +++ b/crates/storage/src/backend/rocksdb.rs @@ -13,6 +13,7 @@ use std::sync::Arc; fn cf_name(table: Table) -> &'static str { match table { Table::Blocks => "blocks", + Table::BlockSignatures => "block_signatures", Table::States => "states", Table::LatestKnownAttestations => "latest_known_attestations", Table::LatestNewAttestations => "latest_new_attestations", diff --git a/crates/storage/src/store.rs b/crates/storage/src/store.rs index 6777018..60b2796 100644 --- a/crates/storage/src/store.rs +++ b/crates/storage/src/store.rs @@ -4,7 +4,10 @@ use crate::api::{StorageBackend, Table}; use ethlambda_types::{ attestation::AttestationData, - block::{AggregatedSignatureProof, Block, BlockBody}, + block::{ + AggregatedSignatureProof, Block, BlockBody, BlockSignaturesWithAttestation, + SignedBlockWithAttestation, + }, primitives::{Decode, Encode, H256, TreeHash}, signature::ValidatorSignature, state::{ChainConfig, Checkpoint, State}, @@ -305,6 +308,46 @@ impl Store { batch.commit().expect("commit"); } + // ============ Signed Blocks ============ + + /// Insert a signed block, storing the block and signatures separately. + pub fn insert_signed_block(&mut self, root: H256, signed_block: &SignedBlockWithAttestation) { + let block = &signed_block.message.block; + let signatures = BlockSignaturesWithAttestation::from_signed_block(signed_block); + + let mut batch = self.backend.begin_write().expect("write batch"); + batch + .put_batch( + Table::Blocks, + vec![(root.as_ssz_bytes(), block.as_ssz_bytes())], + ) + .expect("put block"); + batch + .put_batch( + Table::BlockSignatures, + vec![(root.as_ssz_bytes(), signatures.as_ssz_bytes())], + ) + .expect("put block signatures"); + batch.commit().expect("commit"); + } + + /// Get a signed block by combining block and signatures. + /// + /// Returns None if either the block or signatures are not found. + pub fn get_signed_block(&self, root: &H256) -> Option { + let view = self.backend.begin_read().expect("read view"); + let key = root.as_ssz_bytes(); + + let block_bytes = view.get(Table::Blocks, &key).expect("get")?; + let sig_bytes = view.get(Table::BlockSignatures, &key).expect("get")?; + + let block = Block::from_ssz_bytes(&block_bytes).expect("valid block"); + let signatures = + BlockSignaturesWithAttestation::from_ssz_bytes(&sig_bytes).expect("valid signatures"); + + Some(signatures.to_signed_block(block)) + } + // ============ States ============ /// Iterate over all (root, state) pairs.