Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8304b37
refactor(blockchain): group on_tick conditionals by interval
MegaRedHand Jun 19, 2026
10e209f
docs: remove unnecessary comment
MegaRedHand Jun 19, 2026
8459726
docs: add closing interval-4 marker in on_tick
MegaRedHand Jun 19, 2026
d003f85
refactor(blockchain): gate proposal at the call site, ungate the on_t…
MegaRedHand Jun 19, 2026
7db241f
Merge branch 'main' into refactor/group-on-tick-by-interval
MegaRedHand Jun 22, 2026
2c6eb1c
feat(blockchain): pre-build proposer block one interval early
MegaRedHand Jun 22, 2026
d940222
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 22, 2026
2733099
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 23, 2026
1e58c02
fix(blockchain): publish proposer block at interval 4, aligned to slo…
MegaRedHand Jun 23, 2026
3b453d1
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 23, 2026
e48f43c
docs(blockchain): clarify interval-4 proposal after review
MegaRedHand Jun 23, 2026
f34e81c
fix(blockchain): drop the proposer's interval-0 attestation accept
MegaRedHand Jun 23, 2026
337f2d8
docs: interval 0 has no duty after dropping its attestation accept
MegaRedHand Jun 23, 2026
993e606
docs: clarify interval-0 publish; drop its attestation accept
MegaRedHand Jun 23, 2026
59ce2e0
refactor(blockchain): advance store to interval 0 in the prebuild
MegaRedHand Jun 23, 2026
31f129a
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 24, 2026
805a587
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 24, 2026
f4ecf95
test: remove useless unit test
MegaRedHand Jun 24, 2026
0d5e8d4
refactor: inline function
MegaRedHand Jun 24, 2026
854c632
refactor: remove dead code
MegaRedHand Jun 24, 2026
f7a2a2d
refactor(blockchain): inline block build/assemble into propose_block
MegaRedHand Jun 24, 2026
ac48403
refactor(blockchain): keep get_our_proposer/produce_attestations abov…
MegaRedHand Jun 25, 2026
255109c
refactor(blockchain): rename produce_block_on_head back to produce_bl…
MegaRedHand Jun 25, 2026
f14751b
docs: update architecture infographic for produce_block_with_signatur…
MegaRedHand Jun 25, 2026
747655f
refactor(blockchain): move interval-0 advance back into produce_block…
MegaRedHand Jun 25, 2026
3c89678
refactor: remove unused clone
MegaRedHand Jun 25, 2026
1a02073
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 25, 2026
1e92b05
Apply suggestion from @pablodeymo
pablodeymo Jun 25, 2026
c091517
refactor: address review comments
MegaRedHand Jun 25, 2026
8068337
Merge branch 'main' into feat/proposer-prebuild-aggregation
MegaRedHand Jun 25, 2026
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
95 changes: 94 additions & 1 deletion crates/blockchain/src/block_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use ethlambda_state_transition::{
use ethlambda_types::{
ShortRoot,
attestation::{AggregatedAttestation, AggregationBits, AttestationData},
block::{AggregatedAttestations, Block, BlockBody, TypeOneMultiSignature},
block::{AggregatedAttestations, Block, BlockBody, SignedBlock, TypeOneMultiSignature},
checkpoint::Checkpoint,
primitives::{H256, HashTreeRoot as _},
state::{JustifiedSlots, State},
Expand All @@ -43,6 +43,40 @@ pub struct PostBlockCheckpoints {
pub finalized: Checkpoint,
}

/// A block built ahead of its proposal slot (at the previous slot's interval 4)
/// and signed, awaiting publication at interval 0.
pub(crate) struct PreparedBlock {
/// Proposal slot this block targets.
pub(crate) slot: u64,
/// Validator that will propose it.
pub(crate) validator_id: u64,
/// Head the block was built on. Must still be the canonical head at
/// publish time, or a late block / reorg has invalidated it.
pub(crate) parent_root: H256,
/// Justified slot the build closed over. Per leanSpec #595 the published
/// block must not lag the store's justified checkpoint; if the store's
/// justified slot advanced past this between build and publish, fall back.
pub(crate) built_justified_slot: u64,
/// Fully assembled block + Type-2 proof, ready to process and publish.
pub(crate) signed_block: SignedBlock,
}

/// Decide whether a prepared block is still safe to publish at interval 0.
///
/// Pure so it can be unit-tested without an actor or store.
pub(crate) fn prebuilt_block_is_usable(
prepared: &PreparedBlock,
proposal_slot: u64,
proposer_id: u64,
live_head: H256,
store_justified_slot: u64,
) -> bool {
prepared.slot == proposal_slot
&& prepared.validator_id == proposer_id
&& prepared.parent_root == live_head
&& prepared.built_justified_slot >= store_justified_slot
}

/// Build a valid block on top of this state.
///
/// Selects attestations via `select_attestations`, compacts duplicate
Expand Down Expand Up @@ -1336,3 +1370,62 @@ mod tests {
assert_eq!(covered, HashSet::from([0, 1, 2, 3]));
}
}

#[cfg(test)]
mod prebuild_tests {
use super::*;
use ethlambda_types::block::{BlockBody, MultiMessageAggregate};

fn root(b: u8) -> H256 {
H256::from_slice(&[b; 32])
}

fn dummy_signed_block() -> SignedBlock {
SignedBlock {
message: Block {
slot: 0,
proposer_index: 0,
parent_root: H256::ZERO,
state_root: H256::ZERO,
body: BlockBody::default(),
},
proof: MultiMessageAggregate::default(),
}
}

fn prepared(slot: u64, vid: u64, parent: H256, just: u64) -> PreparedBlock {
PreparedBlock {
slot,
validator_id: vid,
parent_root: parent,
built_justified_slot: just,
signed_block: dummy_signed_block(),
}
}

#[test]
fn usable_when_head_and_justified_match() {
let p = prepared(10, 3, root(0xAB), 7);
assert!(prebuilt_block_is_usable(&p, 10, 3, root(0xAB), 7));
}

#[test]
fn unusable_when_head_moved() {
let p = prepared(10, 3, root(0xAB), 7);
assert!(!prebuilt_block_is_usable(&p, 10, 3, root(0xCD), 7));
}

#[test]
fn unusable_when_justified_advanced_past_build() {
let p = prepared(10, 3, root(0xAB), 7);
// store justified is now 8 > 7 → would regress justification.
assert!(!prebuilt_block_is_usable(&p, 10, 3, root(0xAB), 8));
}

#[test]
fn unusable_for_wrong_slot_or_proposer() {
let p = prepared(10, 3, root(0xAB), 7);
assert!(!prebuilt_block_is_usable(&p, 11, 3, root(0xAB), 7));
assert!(!prebuilt_block_is_usable(&p, 10, 4, root(0xAB), 7));
}
}
Loading
Loading