Description
The UpdateHighQC function in protocol/viewstates.go only checks if the new QC's block has a higher view than the current HighQC. It does not verify that the new QC extends the committed chain.
Impact
- Safety: Not violated (VoteRule's bLock check provides defense in depth)
- Liveness: At risk - node may try to propose on invalid forks
- Resources: Wasted on processing invalid branches
Root Cause
func (s *ViewStates) UpdateHighQC(qc hotstuff.QuorumCert) (bool, error) {
newBlock, ok := s.blockchain.Get(qc.BlockHash())
if !ok {
return false, fmt.Errorf("block not found")
}
s.mut.Lock()
defer s.mut.Unlock()
if newBlock.View() <= s.highQC.View() { // ❌ Only checks View
return false, nil
}
s.highQC = qc // Updates without checking if extends committed chain
return true, nil
}
Expected Behavior
UpdateHighQC should reject QCs whose blocks do not extend the committed chain.
Reproduction
See test case TestHighQCUpdateBug_Demonstration in twins/highqc_update_bug_test.go.