Commit fd2cc63
Use post-block checkpoints for proposer attestation source and target (#268)
## Motivation
A bug was found during devnet3 testing in the Qlean client (C++
implementation) where **finality was lagging more than 3 slots behind
head**. The root cause was identified through structured log analysis:
the proposer's attestation was computed **before** the block was
imported into fork choice, causing the attestation to carry stale
`source` (justified) and `target` checkpoint values.
### The ordering bug across clients
When a proposer builds a block containing attestations that advance
justification/finalization, the state transition updates these
checkpoints. The question is: does the proposer's own attestation
reflect the **pre-import** or **post-import** checkpoint values?
**Qlean (buggy ordering):**
```
produceBlockWithSignatures
├─ produceAttestation ← attestation BEFORE import
│ ├─ getHead ← stale head
│ └─ getLatestJustified ← stale justified
└─ onBlock ← import updates head/justified
```
**Zeam (correct ordering):**
```
validator.maybeDoProposal
├─ chain.produceBlock
│ ├─ forkChoice.onBlock ← import FIRST
│ └─ forkChoice.updateHead ← head/justified updated
└─ chain.constructAttestationData
├─ forkChoice.getHead ← fresh head
└─ forkChoice.getLatestJustified ← fresh justified
```
**ethlambda (was buggy, same as Qlean):**
```
propose_block()
├─ produce_block_with_signatures() ← build block
├─ Attestation { source: store.latest_justified(), ... } ← stale!
├─ sign attestation
├─ assemble SignedBlockWithAttestation
└─ process_block() → on_block() ← import AFTER attestation
```
### Why we can't just reorder
In ethlambda (and Qlean), the proposer attestation is embedded inside
`SignedBlockWithAttestation` → `BlockWithAttestation`. It's part of the
block's hash tree root and required for signature verification during
import. So unlike Zeam's architecture (where block and attestation are
separate), we **cannot** import the block first and then create the
attestation — it's a chicken-and-egg problem.
## Description
The fix exploits the fact that `build_block()` already runs the full
state transition to compute `state_root`. The resulting `post_state`
contains `latest_justified` and `latest_finalized` after processing the
block's attestations — but these values were previously discarded.
### Changes
1. **`PostBlockCheckpoints` struct** — carries the two checkpoints
(`justified` + `finalized`) from `build_block` back to the caller.
2. **`build_block` / `produce_block_with_signatures`** — return
`PostBlockCheckpoints` alongside the block, extracted from the
post-state transition result. No extra computation — just plumbing
values that were already computed.
3. **`get_attestation_target_with_checkpoints`** — parameterized version
of `get_attestation_target` that accepts justified/finalized checkpoints
instead of reading from the store. The existing `get_attestation_target`
becomes a thin wrapper that delegates with store values.
4. **`propose_block`** — uses `post_checkpoints.justified` as `source`
and calls `get_attestation_target_with_checkpoints` with post-block
checkpoints for `target`.
### Behavior
- **When block has no attestations** (or attestations don't advance
justification): `post_state.latest_justified ==
head_state.latest_justified`, so behavior is identical to before. No
regression.
- **When block attestations advance justification**: proposer's
attestation now correctly reflects the post-import justified checkpoint,
preventing finality lag.
## How to Test
```bash
make fmt # passes
make lint # passes
make test # all 97 tests pass
```
Full devnet validation is recommended to verify finality behavior under
load.
---------
Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com>1 parent 65c2907 commit fd2cc63
2 files changed
Lines changed: 59 additions & 16 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
216 | | - | |
| 216 | + | |
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
222 | 222 | | |
223 | | - | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
224 | 226 | | |
225 | 227 | | |
226 | 228 | | |
| |||
229 | 231 | | |
230 | 232 | | |
231 | 233 | | |
232 | | - | |
233 | | - | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
234 | 240 | | |
235 | 241 | | |
236 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
38 | 49 | | |
39 | 50 | | |
40 | 51 | | |
| |||
621 | 632 | | |
622 | 633 | | |
623 | 634 | | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
624 | 657 | | |
625 | 658 | | |
626 | 659 | | |
| |||
647 | 680 | | |
648 | 681 | | |
649 | 682 | | |
650 | | - | |
| 683 | + | |
651 | 684 | | |
652 | 685 | | |
653 | 686 | | |
| |||
661 | 694 | | |
662 | 695 | | |
663 | 696 | | |
664 | | - | |
| 697 | + | |
665 | 698 | | |
666 | 699 | | |
667 | 700 | | |
| |||
673 | 706 | | |
674 | 707 | | |
675 | 708 | | |
676 | | - | |
677 | | - | |
| 709 | + | |
678 | 710 | | |
679 | 711 | | |
680 | | - | |
| 712 | + | |
681 | 713 | | |
682 | 714 | | |
683 | | - | |
| 715 | + | |
684 | 716 | | |
685 | 717 | | |
686 | 718 | | |
| |||
757 | 789 | | |
758 | 790 | | |
759 | 791 | | |
760 | | - | |
| 792 | + | |
761 | 793 | | |
762 | 794 | | |
763 | 795 | | |
| |||
782 | 814 | | |
783 | 815 | | |
784 | 816 | | |
785 | | - | |
| 817 | + | |
786 | 818 | | |
787 | 819 | | |
788 | 820 | | |
| |||
791 | 823 | | |
792 | 824 | | |
793 | 825 | | |
794 | | - | |
| 826 | + | |
795 | 827 | | |
796 | 828 | | |
797 | 829 | | |
| |||
999 | 1031 | | |
1000 | 1032 | | |
1001 | 1033 | | |
1002 | | - | |
| 1034 | + | |
1003 | 1035 | | |
1004 | 1036 | | |
1005 | 1037 | | |
| |||
1099 | 1131 | | |
1100 | 1132 | | |
1101 | 1133 | | |
1102 | | - | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
1103 | 1140 | | |
1104 | 1141 | | |
1105 | 1142 | | |
| |||
1415 | 1452 | | |
1416 | 1453 | | |
1417 | 1454 | | |
1418 | | - | |
| 1455 | + | |
1419 | 1456 | | |
1420 | 1457 | | |
1421 | 1458 | | |
| |||
0 commit comments