Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Correctness and Potential Bugs

  1. Block and State Insertion Logic: The change from store.insert_block(block_root, block.clone()); to store.insert_signed_block(block_root, &signed_block); needs to be validated to ensure that all downstream logic that expects an unsigned block can properly handle a signed one. Ensure that store.insert_signed_block is functionally equivalent or that the system is designed for this change.

Security Vulnerabilities

  1. Data Integrity and Consensus Safety: Ensure that signing the blocks does not introduce any potential weaknesses in verification or introduce unintended consensus consequences. Signed blocks should have their signatures verified promptly to prevent unauthorized state transitions.

Rust Best Practices and Idiomatic Patterns

  1. Borrowing Practices: Changing the ownership passing style from a cloned block to a borrowed &signed_block is generally a good practice to reduce unnecessary allocations, assuming lifetime issues are managed correctly.

Memory Safety and Proper Error Handling

  1. Handle Potential Errors from insert_signed_block: Ensure that insert_signed_block returns a Result and errors are handled, rather than ignored, to prevent unnoticed failures in block storage.

Code Readability and Maintainability

  1. Comments: The comment adjustment from Store block and state to Store signed block and state is minor but helpful for context. Ensure that comments are updated to reflect the logical changes accurately and across the entire codebase where impacted.

Performance Implications

  1. Clone vs. Reference: The change to use a reference for signed_block should be more efficient than cloning it, assuming no mutation occurs that would require a clone later, which indicates good practice barring any unforeseen side effects.

Consensus-layer Considerations

  • Ensure that modifying the insertion logic does not inadvertently affect the fork choice, attestation validation, or state transition accuracy.
  • No explicit comments on the impact on justification and finalization logic based on presented code; verify surrounding code logic handles signed_block correctly.

Overall, ensure thorough testing around this code to guarantee that expectations around signed_block are met without introducing hidden bugs or security issues.

Expand Down
34 changes: 34 additions & 0 deletions crates/common/types/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 29 additions & 9 deletions crates/net/p2p/src/req_resp/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response>,
channel: request_response::ResponseChannel<Response>,
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(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Correctness and Potential Bugs:

  1. Error Handling for Not Found Block: There should be explicit error feedback for not found block rather than allowing a silent timeout. It is better to send a specific error response to help peer identify the issue.

Line 32:

warn!(%peer, %root, "Block not found for BlocksByRoot request");

Suggestion: Send an explicit error response indicating the block is not found.

  1. Multiple Roots Handling: The code currently only processes the first root. To be protocol compliant, it's suggested to implement handling for all roots, or at least acknowledge this limitation clearly in documentation until fully supported.

Security Considerations:

  1. Denial-of-Service (DoS) Vulnerability: Handling requests one at a time could be exploited for DoS. Consider implementing rate limiting, request prioritization, or sequential processing to mitigate.

Performance Implications:

  1. Batch Processing and Response Efficiency: Not fully utilizing the allowed batch processing may lead to inefficiencies, especially as requests might contain up to 1024 roots. If performance issues occur, opt for more parallel processing strategies to enhance throughput.

Rust Best Practices and Idiomatic Patterns:

  1. Using Option Pattern Effectively: It's good that the Option pattern is used, however, a more idiomatic approach could involve pattern matching directly on Options to allow for both presence and absence cases in a single expression.

Memory Safety and Proper Error Handling:

  • Memory Safety: The current approach looks memory-safe as Option and Result are used appropriately. However, ensure any added storage implementations follow Rust's safety guidelines.

Code Readability and Maintainability:

  1. Comments and Documentation: It would be beneficial to have more descriptive comments, especially around known limitations like single-block handling and potential future improvements.

Consensus-layer Considerations:

  • Fork Choice and Attestation Processing: These aspects aren't directly affected by the code in question but ensure the fork choice logic and attestation processing are accurately reflected elsewhere.
  • SSZ Encoding/Decoding: Ensure that when responses are sent, they comply with SSZ validity standards.

Overall, this PR has groundwork in place but requires additional handling and clear informative responses for better protocol adherence and robustness.

Expand Down
5 changes: 4 additions & 1 deletion crates/storage/src/api/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Correctness and Potential Bugs

  • Ensure that the new BlockSignatures enum variant is correctly integrated into all relevant areas of the code, especially where tables are manipulated or accessed. Without the full context of how Table is used, it's difficult to give a detailed assessment, but typically adding a new variant may necessitate updating match statements or other logic where Table is pattern matched or otherwise used.

Security Vulnerabilities

  • Adding a new table for block signatures warrants a review of access patterns and data manipulation operations involving this table. Ensure that any external input, especially from nodes or users, is properly validated before interacting with the BlockSignatures table to prevent vulnerabilities, such as unauthorized access or injection attacks.

Performance Implications

  • Adding a new table can have performance implications, especially if it involves frequent reads/writes in the consensus process. Benchmarking may be necessary to ensure the change does not introduce bottlenecks.

Rust Best Practices and Idiomatic Patterns

  • The code snippet provided adheres to fundamental Rust best practices. However, ensure that wherever the Table enum is used, pattern matching is exhaustive (using _ => if needed) to accommodate future changes more easily.

Memory Safety and Error Handling

  • As this is a merely structural addition, no immediate concerns about memory safety are visible from this change alone. Nevertheless, ensure error handling appropriately covers the new BlockSignatures table to handle cases where data might not exist or retrieval fails.

Code Readability and Maintainability

  • The change is simple and follows the current pattern, maintaining readability. Ensure that documentation or comments are updated elsewhere in the code base to reflect the introduction of the BlockSignatures table and what its intended use is.

Consensus-Layer Considerations

  • Given that this change involves storage enumeration related to block signatures, evaluate the effect on fork choice rule implementation, specifically related to attestation processing and validation if this is relevant.
  • Review justification and finalization logic to ensure that any block signature interactions are handled correctly.
  • Ensure proper SSZ encoding/decoding integration with the BlockSignatures.

In conclusion, while this change appears structurally sound, additional review of related parts of the code base outside of this snippet is necessary to address potential security, performance, and consensus issues regarding the introduction of the BlockSignatures table.

Expand Down
1 change: 1 addition & 0 deletions crates/storage/src/backend/rocksdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Correctness and Potential Bugs

  • In the provided code snippet, there is a clear addition of Table::BlockSignatures to the cf_name function, which appears to be correct and necessary for processing a new table type. Ensure all uses of cf_name are updated to handle this new table.

Security Vulnerabilities

  • No specific vulnerabilities are evident from this snippet. Ensure that any interaction with block_signatures is properly validated, especially considering potential implications for consensus logic.

Performance Implications

  • Introducing a new table does not inherently affect performance from the code snippet provided, but keep in mind potential database access latency when interacting with new tables.

Rust Best Practices and Idiomatic Patterns

  • Using a match on Table enums is idiomatic in Rust and ensures compile-time checking of all possible Table variants. Double-check that all operations involving the newly added table variant properly match the new entries.

Memory Safety and Proper Error Handling

  • Ensure that interactions with tables handle errors gracefully. It is crucial, particularly in consensus-related operations, as failures can lead to forks or loss of consensus.

Code Readability and Maintainability

  • This code is readable and straightforward. Make sure to maintain consistency across the codebase when adding new table types.

Consensus-layer Considerations

  • Ensure that the logic involving BlockSignatures does not interfere with critical consensus functions like fork choice, attestation processing, or state transitions. Consider comprehensive tests covering these parts if they interact with new table entries.

Based on the information provided, the code change looks appropriately applied; however, comprehensive testing and verification are required in the context of its impact throughout the codebase.

Expand Down
45 changes: 44 additions & 1 deletion crates/storage/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<SignedBlockWithAttestation> {
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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Correctness and Potential Bugs

  • Panic on Errors: The use of expect throughout the code (lines 318, 322, 327, 333) can lead to panics if there are errors during execution. Instead, consider using proper error handling techniques such as Result and propagate errors using the ? operator.
  • Error Handling: On line 350, expect is used with Block::from_ssz_bytes. If the block or its signatures are malformed, it can cause a panic, risking node failure. Use proper error handling to manage such cases gracefully.

Security Vulnerabilities

  • Unchecked Assumptions: The use of expect assumes that every operation will succeed. This is a risky practice in blockchain applications where data integrity is crucial.

Performance Implications

  • Batch Processing: On lines 314-324, using batch operations improves performance when writing multiple entries. Ensure that this practice is applied consistently across other similar operations in the codebase.

Rust Best Practices and Idiomatic Patterns

  • Error Propagation: Replace expect and panic with error propagation where possible. This improves both stability and comprehensibility of the code.

Memory Safety and Proper Error Handling

  • Error Propagation: As mentioned above, utilize Result types for better error management.

Code Readability and Maintainability

  • Documentation: The code is adequately commented, but some additional detail regarding the assumptions can provide clarity, especially for future maintainers.

Consensus-layer Considerations

  • SSZ Decoding: Ensure that proper validation is performed on SSZ-encoded data to prevent invalid data being processed, which can lead to consensus issues.
  • Data Integrity: It might be beneficial to verify the integrity of the blocks and signatures before processing, although not strictly required in this snippet.

Conclusion

The code needs improvements primarily in terms of error handling and assumption management. Ensuring graceful handling of errors will significantly increase robustness and security.

Expand Down
Loading