Skip to content

Commit 4c352d7

Browse files
committed
fix(bft): error on signer absent from committee
1 parent c8f6f18 commit 4c352d7

2 files changed

Lines changed: 8 additions & 83 deletions

File tree

bft/engine.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,6 @@ func (engine *Engine) computeState(sum *chain.BlockSummary) (*bftState, error) {
453453
if js.posActive {
454454
weightState = engine.stater.NewState(js.weightRoot)
455455
}
456-
// The PoS-transition checkpoint block is PoA-scheduled, so its signer may not be
457-
// registered in the post-transition committee; it is the only block allowed to
458-
// weigh a missing validator as zero.
459-
transitionBlock := engine.forkConfig.HAYABUSA + thor.HayabusaTP()
460456

461457
h := header
462458
for h.Number() >= engine.forkConfig.FINALITY {
@@ -470,15 +466,9 @@ func (engine *Engine) computeState(sum *chain.BlockSummary) (*bftState, error) {
470466
}
471467

472468
if validator == nil {
473-
// A round's proposers are validated against its checkpoint committee,
474-
// so a signer absent from it is an invariant violation — except the
475-
// transition block, which is PoA-scheduled and weighs zero.
476-
if h.Number() != transitionBlock {
477-
return nil, errors.Errorf("signer %s absent from committee at block %d", signer, h.Number())
478-
}
479-
} else {
480-
weight = validator.Weight
469+
return nil, errors.Errorf("signer %s absent from committee at block %d", signer, h.Number())
481470
}
471+
weight = validator.Weight
482472
}
483473
js.AddBlock(signer, h.COM(), weight)
484474

bft/engine_pos_test.go

Lines changed: 6 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,73 +1388,11 @@ func TestCheckpointBlockOwnVoteUsesPostHousekeepWeight(t *testing.T) {
13881388
}
13891389
}
13901390

1391-
// TestComputeStateTransitionCheckpointSignerWeighsZero pins the sole exception to
1392-
// the committee-membership invariant: the PoS-transition checkpoint block
1393-
// (HAYABUSA+HayabusaTP) is PoA-scheduled, so its signer may not be registered in
1394-
// the post-transition committee and must weigh zero rather than error.
1395-
func TestComputeStateTransitionCheckpointSignerWeighsZero(t *testing.T) {
1396-
forkCfg := &thor.ForkConfig{
1397-
HAYABUSA: 1,
1398-
}
1399-
hayabusaTP := uint32(1)
1400-
thor.SetConfig(thor.Config{HayabusaTP: &hayabusaTP})
1401-
1402-
testBFT, err := newTestBftPos(forkCfg)
1403-
if err != nil {
1404-
t.Fatal(err)
1405-
}
1406-
1407-
transitionBlock := forkCfg.HAYABUSA + thor.HayabusaTP()
1408-
require.False(t, isCheckPoint(transitionBlock),
1409-
"test relies on the transition block not being a checkpoint so the injected justifier is reused")
1410-
1411-
bestChain := testBFT.repo.NewBestChain()
1412-
transitionSum, err := bestChain.GetBlockSummary(transitionBlock)
1413-
if err != nil {
1414-
t.Fatal(err)
1415-
}
1416-
genesisSum, err := testBFT.repo.GetBlockSummary(testBFT.repo.GenesisBlock().Header().ID())
1417-
if err != nil {
1418-
t.Fatal(err)
1419-
}
1420-
1421-
// Weight state = genesis: no validator registered, the transition block's signer
1422-
// is absent from it — the case the transition-block exception exists for.
1423-
js := newJustifier(0, getCheckPoint(transitionBlock), 0, 1000)
1424-
js.posActive = true
1425-
js.weightRoot = genesisSum.Root()
1426-
1427-
testBFT.engine.caches.state.Purge()
1428-
testBFT.engine.caches.justifier = cache.NewPrioCache(16)
1429-
testBFT.engine.caches.justifier.Set(transitionSum.Header.ParentID(), js, float64(transitionBlock))
1430-
1431-
st, err := testBFT.engine.computeState(transitionSum)
1432-
require.NoError(t, err, "transition checkpoint block must tolerate an absent signer")
1433-
1434-
signer, err := transitionSum.Header.Signer()
1435-
if err != nil {
1436-
t.Fatal(err)
1437-
}
1438-
entry := testBFT.engine.caches.justifier.Remove(transitionSum.Header.ID())
1439-
if entry == nil {
1440-
t.Fatal("expected justifier in cache")
1441-
}
1442-
reused := entry.Value.(*justifier)
1443-
require.Same(t, js, reused, "test setup: injected justifier must be the one reused")
1444-
1445-
v, ok := reused.votes[signer]
1446-
require.True(t, ok, "signer's vote must still be recorded")
1447-
assert.Zero(t, v.weight, "transition block signer's weight must be zero")
1448-
assert.Zero(t, reused.justifiedWeight)
1449-
assert.Zero(t, reused.comWeight)
1450-
assert.False(t, st.Justified)
1451-
assert.False(t, st.Committed)
1452-
}
1453-
1454-
// TestComputeStateSignerAbsentFromCommitteeErrors pins that, off the transition
1455-
// checkpoint, a signer with no validation entry in the round's weight state is an
1456-
// invariant violation and errors — a round's proposers are validated against its
1457-
// checkpoint committee, so an absent signer means corrupted state, not a zero vote.
1391+
// TestComputeStateSignerAbsentFromCommitteeErrors pins that a signer with no
1392+
// validation entry in the round's weight state is an invariant violation and errors
1393+
// — scheduling and proposer validation both run after SyncPOS, so every signer of a
1394+
// PoS round is drawn from its post-housekeep committee; an absent signer means
1395+
// corrupted state, not a zero vote.
14581396
func TestComputeStateSignerAbsentFromCommitteeErrors(t *testing.T) {
14591397
forkCfg := &thor.ForkConfig{
14601398
HAYABUSA: 1,
@@ -1476,13 +1414,10 @@ func TestComputeStateSignerAbsentFromCommitteeErrors(t *testing.T) {
14761414
if err != nil {
14771415
t.Fatal(err)
14781416
}
1479-
// A non-transition block: an absent signer here is not tolerated.
14801417
targetSum, err := bestChain.GetBlockSummary(thor.EpochLength() + 1)
14811418
if err != nil {
14821419
t.Fatal(err)
14831420
}
1484-
require.NotEqual(t, forkCfg.HAYABUSA+thor.HayabusaTP(), targetSum.Header.Number(),
1485-
"target must not be the transition checkpoint block")
14861421
genesisSum, err := testBFT.repo.GetBlockSummary(testBFT.repo.GenesisBlock().Header().ID())
14871422
if err != nil {
14881423
t.Fatal(err)
@@ -1498,6 +1433,6 @@ func TestComputeStateSignerAbsentFromCommitteeErrors(t *testing.T) {
14981433
testBFT.engine.caches.justifier.Set(checkpointSum.Header.ID(), js, float64(thor.EpochLength()))
14991434

15001435
_, err = testBFT.engine.computeState(targetSum)
1501-
require.Error(t, err, "absent signer off the transition block must error")
1436+
require.Error(t, err, "absent signer must error")
15021437
assert.Contains(t, err.Error(), "absent from committee")
15031438
}

0 commit comments

Comments
 (0)