|
12 | 12 | from lean_spec.spec.crypto.merkleization import hash_tree_root |
13 | 13 | from lean_spec.spec.forks import Slot, ValidatorIndex |
14 | 14 | from lean_spec.spec.forks.lstar.containers import ( |
| 15 | + BlockHeader, |
| 16 | + Checkpoint, |
| 17 | + HistoricalBlockHashes, |
15 | 18 | JustificationRoots, |
16 | 19 | JustificationValidators, |
17 | 20 | JustifiedSlots, |
| 21 | + State, |
18 | 22 | ) |
19 | 23 | from lean_spec.spec.forks.lstar.spec import LstarSpec |
20 | | -from lean_spec.spec.ssz import Boolean |
| 24 | +from lean_spec.spec.ssz import Boolean, Bytes32, Uint64 |
21 | 25 |
|
22 | 26 | pytestmark = pytest.mark.valid_until("Lstar") |
23 | 27 |
|
@@ -1374,3 +1378,100 @@ def test_rebased_finalization_prunes_stale_votes_and_preserves_future_votes( |
1374 | 1378 | ), |
1375 | 1379 | ), |
1376 | 1380 | ) |
| 1381 | + |
| 1382 | + |
| 1383 | +def test_finalization_rebase_drops_off_chain_pending_tally( |
| 1384 | + state_transition_test: StateTransitionTestFiller, |
| 1385 | +) -> None: |
| 1386 | + """ |
| 1387 | + Finalization drops a pending tally whose root is off the canonical chain. |
| 1388 | +
|
| 1389 | + Given |
| 1390 | + ----- |
| 1391 | + - 4 validators; a slot needs 3 votes (2/3) to be justified. |
| 1392 | + - a hand-built pre-state at slot 3 over the chain: |
| 1393 | + genesis(0) -> block_1(1) -> block_2(2) -> block_3(3) |
| 1394 | + - slot 1 is justified, rooted at block_1. |
| 1395 | + - finalized is slot 0, rooted at the genesis anchor. |
| 1396 | + - a phantom root carries a pending tally. |
| 1397 | + - the phantom root appears nowhere in the chain history. |
| 1398 | + - the phantom tally holds V0 alone of 4. |
| 1399 | + - block_4 carries V0, V1, V2's vote from block_1 to block_2. |
| 1400 | +
|
| 1401 | + When |
| 1402 | + ---- |
| 1403 | + - the chain processes block_4 on top of the pre-state. |
| 1404 | +
|
| 1405 | + Then |
| 1406 | + ---- |
| 1407 | + - the state slot is 4. |
| 1408 | + - block_4's supermajority justifies slot 2. |
| 1409 | + - justified slot is 2, rooted at block_2. |
| 1410 | + - the adjacent source slot 1 finalizes. |
| 1411 | + - finalized slot is 1, rooted at block_1. |
| 1412 | + - the justified-slots bitfield is [True, False] relative to slot 1. |
| 1413 | + - the off-chain phantom root is dropped from the pending roots. |
| 1414 | + - the phantom tally is dropped from the pending voters. |
| 1415 | + """ |
| 1416 | + genesis = build_genesis_state() |
| 1417 | + |
| 1418 | + genesis_anchor_root = Bytes32(b"\x11" * 32) |
| 1419 | + block_1_root = Bytes32(b"\x22" * 32) |
| 1420 | + block_2_root = Bytes32(b"\x33" * 32) |
| 1421 | + phantom_justification_root = Bytes32(b"\xff" * 32) |
| 1422 | + |
| 1423 | + pre = State( |
| 1424 | + config=genesis.config, |
| 1425 | + slot=Slot(3), |
| 1426 | + latest_block_header=BlockHeader( |
| 1427 | + slot=Slot(3), |
| 1428 | + proposer_index=ValidatorIndex.proposer_for_slot(Slot(3), Uint64(4)), |
| 1429 | + parent_root=block_2_root, |
| 1430 | + state_root=Bytes32.zero(), |
| 1431 | + body_root=genesis.latest_block_header.body_root, |
| 1432 | + ), |
| 1433 | + latest_justified=Checkpoint(root=block_1_root, slot=Slot(1)), |
| 1434 | + latest_finalized=Checkpoint(root=genesis_anchor_root, slot=Slot(0)), |
| 1435 | + historical_block_hashes=HistoricalBlockHashes( |
| 1436 | + data=[genesis_anchor_root, block_1_root, block_2_root] |
| 1437 | + ), |
| 1438 | + justified_slots=JustifiedSlots(data=[Boolean(True), Boolean(False)]), |
| 1439 | + validators=genesis.validators, |
| 1440 | + justifications_roots=JustificationRoots(data=[phantom_justification_root]), |
| 1441 | + justifications_validators=JustificationValidators( |
| 1442 | + data=[Boolean(True), Boolean(False), Boolean(False), Boolean(False)] |
| 1443 | + ), |
| 1444 | + ) |
| 1445 | + |
| 1446 | + state_transition_test( |
| 1447 | + pre=pre, |
| 1448 | + blocks=[ |
| 1449 | + BlockSpec( |
| 1450 | + slot=Slot(4), |
| 1451 | + forced_attestations=[ |
| 1452 | + AggregatedAttestationSpec( |
| 1453 | + validator_indices=[ |
| 1454 | + ValidatorIndex(0), |
| 1455 | + ValidatorIndex(1), |
| 1456 | + ValidatorIndex(2), |
| 1457 | + ], |
| 1458 | + slot=Slot(4), |
| 1459 | + source_slot=Slot(1), |
| 1460 | + source_root=block_1_root, |
| 1461 | + target_slot=Slot(2), |
| 1462 | + target_root=block_2_root, |
| 1463 | + ), |
| 1464 | + ], |
| 1465 | + ), |
| 1466 | + ], |
| 1467 | + post=StateExpectation( |
| 1468 | + slot=Slot(4), |
| 1469 | + latest_justified_slot=Slot(2), |
| 1470 | + latest_justified_root=block_2_root, |
| 1471 | + latest_finalized_slot=Slot(1), |
| 1472 | + latest_finalized_root=block_1_root, |
| 1473 | + justified_slots=JustifiedSlots(data=[Boolean(True), Boolean(False)]), |
| 1474 | + justifications_roots=JustificationRoots(data=[]), |
| 1475 | + justifications_validators=JustificationValidators(data=[]), |
| 1476 | + ), |
| 1477 | + ) |
0 commit comments