fix(bft): guard election vote phase against nil sortition data - #589
Open
Alicepoltora wants to merge 1 commit into
Open
fix(bft): guard election vote phase against nil sortition data#589Alicepoltora wants to merge 1 commit into
Alicepoltora wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
StartElectionPhasepopulatesb.SortitionDataonly on its happy path. It has two early returns before that assignment - whenValidatorSet.GetValidator(b.PublicKey)fails, and whenLoadLastProposers(b.RootHeight)fails - and both merely log and return.HandlePhasecallsSetTimerForNextPhaseunconditionally after the phase handler, so an early return still advances the view toELECTION_VOTE.NewRoundsetsb.SortitionData = nil, so on that pathStartElectionVotePhaseruns with a nil pointer and dereferences it:GetElectionCandidates(b.SortitionData.VotingPower);SelectProposerFromCandidates->lib.WeightedPseudorandom, which readsp.LastProposerAddresses.The result is a panic in the consensus loop. The trigger is local rather than attacker-supplied:
LoadLastProposersreads the historical store (TimeMachine->store.NewReadOnly(height)), so any pruned-version or backing-store read error reaches it. The code deliberately chooses to log-and-continue there, and then dereferences unconditionally in the next phase.Related Issues
None found - I searched open and closed issues and PRs for sortition/election-phase nil handling and found nothing covering this.
Changes Made
StartElectionVotePhase: ifb.SortitionDatais nil, log the error and callRoundInterrupt()instead of proceeding.TestStartElectionVotePhaseWithNilSortitionData, which reproduces the reachable state and asserts the phase does not panic and recovers intoRoundInterrupt.I used
RoundInterrupt()rather than a barereturnbecause it is the established recovery path in this file (it is used at a dozen other bail-out sites): it stops the VDF service, resets the FSM, and gossips a pacemaker message so the node moves cleanly into the next round instead of sitting in a half-initialized view.Checklist
issue-#<issue-number>. (No issue exists for this; happy to rename if you'd like one opened first.)npm run prettierto format the web-wallet and/or block explorer (if applicable). (Not applicable.)Additional Notes
Verification on
development:invalid memory address or nil pointer dereference) and passes after it.go test ./bft/passes in full.go test $(go list ./... | grep -v cmd/auto-update)- the CI test command - passes across the repository.go build ./...succeeds;gofmtreports no changes for either touched file.If you would prefer the fix on the other side - having
StartElectionPhasepopulateSortitionDatabefore its fallible calls, or not advancing the phase when it bails - I am happy to rework it that way; this version was chosen as the smallest change consistent with the file's existing error handling.