Skip to content
Draft
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/aggregation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ mod tests {
use super::*;
use ethlambda_storage::backend::InMemoryBackend;
use ethlambda_types::{
block::{Block, BlockBody, BlockHeader, MultiMessageAggregate, SignedBlock},
block::{Block, BlockBody, BlockHeader, BlockProof, SignedBlock},
checkpoint::Checkpoint,
state::{ChainConfig, JustificationValidators, JustifiedSlots, State},
};
Expand Down Expand Up @@ -862,7 +862,7 @@ mod tests {
state_root: H256::ZERO,
body: BlockBody::default(),
},
proof: MultiMessageAggregate::default(),
proof: BlockProof::default(),
};
store
.insert_signed_block(root, signed_block)
Expand Down
87 changes: 60 additions & 27 deletions crates/blockchain/src/block_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use std::{
use ethlambda_crypto::aggregate_proofs;
use ethlambda_state_transition::{
attestation_data_matches_chain, justified_slots_ops, process_block, process_slots,
slot_is_justifiable_after,
};
use ethlambda_types::{
ShortRoot,
attestation::{AggregatedAttestation, AggregationBits, AttestationData},
block::{AggregatedAttestations, Block, BlockBody, SingleMessageAggregate},
attestation::{AggregatedAttestation, AggregationBits, Attestation, AttestationData},
block::{AggregatedAttestations, Block, BlockBody, HeartbeatVotes, SingleMessageAggregate},
checkpoint::Checkpoint,
primitives::{H256, HashTreeRoot as _},
signature::ValidatorSignature,
state::{JustifiedSlots, State},
};
use tracing::{info, trace};
Expand Down Expand Up @@ -89,8 +89,17 @@ pub(crate) fn build_block(
parent_root: H256,
known_block_roots: &HashSet<H256>,
aggregated_payloads: &HashMap<H256, (AttestationData, Vec<SingleMessageAggregate>)>,
heartbeat_votes: HashMap<u64, (AttestationData, ValidatorSignature)>,
config: ProposerConfig,
) -> Result<(Block, Vec<SingleMessageAggregate>, PostBlockCheckpoints), StoreError> {
) -> Result<
(
Block,
Vec<ValidatorSignature>,
Vec<SingleMessageAggregate>,
PostBlockCheckpoints,
),
StoreError,
> {
info!(slot, proposer_index, "Building block");

let select_start = Instant::now();
Expand Down Expand Up @@ -125,6 +134,21 @@ pub(crate) fn build_block(
let (aggregated_attestations, aggregated_signatures): (Vec<_>, Vec<_>) =
compacted.into_iter().unzip();

let (heartbeat_data, heartbeat_signatures): (Vec<_>, Vec<_>) = heartbeat_votes
.into_iter()
.map(|(vid, (data, sig))| {
(
Attestation {
validator_id: vid,
data,
},
sig,
)
})
.unzip();

let heartbeat: HeartbeatVotes = heartbeat_data.try_into().expect("max committee size");

let attestations: AggregatedAttestations = aggregated_attestations
.try_into()
.expect("attestation count exceeds limit");
Expand All @@ -133,7 +157,10 @@ pub(crate) fn build_block(
proposer_index,
parent_root,
state_root: H256::ZERO,
body: BlockBody { attestations },
body: BlockBody {
heartbeat,
attestations,
},
};
let mut post_state = head_state.clone();
// ethlambda runs the STF once after selection (it projects justification
Expand All @@ -154,7 +181,12 @@ pub(crate) fn build_block(
finalized: post_state.latest_finalized,
};

Ok((final_block, aggregated_signatures, post_checkpoints))
Ok((
final_block,
heartbeat_signatures,
aggregated_signatures,
post_checkpoints,
))
}

/// Tiered greedy attestation selection for block proposal.
Expand Down Expand Up @@ -408,16 +440,14 @@ impl ProjectedState {
let total = prior_count + new_voters.len();
let crosses_2_3 = 3 * total >= 2 * validator_count;

// 3SF-mini finalization requires the source to lie past the finalized
// boundary (a source at or behind it is already final and must not
// re-finalize) and no slot strictly between source.slot and target.slot
// to still be justifiable (so source and target are consecutive
// justified checkpoints in the projected post-state). Mirrors
// The simple BFT finality condition finalizes the source when it lies past
// the finalized boundary (a source at or behind it is already final and must
// not re-finalize) and the target is its immediate successor, so the two are
// consecutive justified checkpoints in the projected post-state. Mirrors
// `try_finalize` in the state transition.
let finalizes = crosses_2_3
&& att_data.source.slot > self.finalized_slot
&& (att_data.source.slot + 1..att_data.target.slot)
.all(|s| !slot_is_justifiable_after(s, self.finalized_slot));
&& att_data.source.slot + 1 == att_data.target.slot;

let tier = if is_genesis_self_vote(att_data) || !crosses_2_3 {
Tier::Build
Expand All @@ -441,9 +471,8 @@ impl ProjectedState {
///
/// Mirrors `state_transition::is_valid_vote`: the entry's head must be
/// known, its source must be justified, its (source, target) must match
/// the candidate-block chain view, `target.slot > source.slot`, target
/// must not already be justified, and target must be a justifiable slot
/// relative to the projected finalized slot. The genesis self-vote
/// the candidate-block chain view, `target.slot > source.slot`, and target
/// must not already be justified. The genesis self-vote
/// (source == target == slot 0) is exempt from the `target.slot >
/// source.slot` and `target_already_justified` checks since fork-choice
/// bootstrapping needs it; STF will silently drop it, but it carries
Expand Down Expand Up @@ -480,11 +509,6 @@ impl ProjectedState {
{
return Err("target_already_justified");
}
if !is_genesis_self_vote
&& !slot_is_justifiable_after(att_data.target.slot, self.finalized_slot)
{
return Err("target_not_justifiable");
}
Ok(())
}
}
Expand Down Expand Up @@ -877,8 +901,12 @@ fn trace_skipped_attestation(reason: &'static str, att: &AttestationData, data_r
mod tests {
use super::*;
use ethlambda_types::{
attestation::{AggregatedAttestation, AggregationBits, AttestationData},
block::{ByteList512KiB, MultiMessageAggregate, SignedBlock, SingleMessageAggregate},
attestation::{
AggregatedAttestation, AggregationBits, AttestationData, blank_xmss_signature,
},
block::{
BlockProof, ByteList512KiB, MultiMessageAggregate, SignedBlock, SingleMessageAggregate,
},
checkpoint::Checkpoint,
state::State,
};
Expand Down Expand Up @@ -1087,11 +1115,16 @@ mod tests {
);

// Substitute a worst-case-size proof to model what `propose_block`
// would attach. The actual SNARK can't be built without lean-multisig,
// but the size cap (`ByteList512KiB`) bounds the worst case.
// would attach: a 512 KiB attestation aggregate plus the fixed-size
// proposer signature. The actual SNARK can't be built without
// lean-multisig, but the size cap bounds the worst case.
let _ = signatures;
let proof = MultiMessageAggregate::new(
ByteList512KiB::try_from(vec![0xAB; 512 * 1024]).expect("worst-case proof fits in cap"),
let proof = BlockProof::new(
blank_xmss_signature(),
MultiMessageAggregate::new(
ByteList512KiB::try_from(vec![0xAB; 512 * 1024])
.expect("worst-case proof fits in cap"),
),
);
let signed_block = SignedBlock {
message: block,
Expand Down
4 changes: 2 additions & 2 deletions crates/blockchain/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ mod tests {
use super::*;
use ethlambda_storage::{ForkCheckpoints, backend::InMemoryBackend};
use ethlambda_types::{
block::{Block, BlockBody, MultiMessageAggregate, SignedBlock},
block::{Block, BlockBody, BlockProof, SignedBlock},
state::State,
};
use std::sync::Arc;
Expand Down Expand Up @@ -455,7 +455,7 @@ mod tests {
state_root,
body: BlockBody::default(),
},
proof: MultiMessageAggregate::default(),
proof: BlockProof::default(),
};
store
.insert_signed_block(root, signed_block)
Expand Down
Loading