Skip to content

Fix ValidatorSetFromProto crash on nil proposer in stored state#3102

Draft
masih wants to merge 3 commits intomainfrom
cursor/validator-set-proposer-error-4334
Draft

Fix ValidatorSetFromProto crash on nil proposer in stored state#3102
masih wants to merge 3 commits intomainfrom
cursor/validator-set-proposer-error-4334

Conversation

@masih
Copy link
Collaborator

@masih masih commented Mar 20, 2026

Describe your changes and provide context

Fixes the node startup crash:

Error: error creating node: error="fromProto: validatorSet proposer error: nil validator" closerError=""

Root cause

There is a serialization/deserialization asymmetry between ValidatorSet.ToProto() and ValidatorSetFromProto():

  • ToProto() returned &tmproto.ValidatorSet{} (non-nil, empty proto with nil Proposer) when the validator set was nil or empty
  • ValidatorSetFromProto() checks if the proto is nil, but did not handle a non-nil proto with a nil Proposer field — it unconditionally called ValidatorFromProto(vp.GetProposer()), which fails when GetProposer() returns nil

This means any state saved with a nil/empty ValidatorSet (e.g. LastValidators during bootstrap, state sync edge cases, or proto version mismatches across binary upgrades) produced a proto that could never be loaded back, preventing the node from starting.

Fix (two sides)

1. Root cause — ValidatorSet.ToProto(): Return nil instead of &tmproto.ValidatorSet{} for nil/empty sets. A nil proto field is correctly handled everywhere: proto serialization omits it, and ValidatorSetFromProto(nil) returns a clear "nil validator set" error. The old empty-but-non-nil proto silently succeeded on write but crashed on read.

2. Backward compat — ValidatorSetFromProto(): Handle nil Proposer gracefully for already-persisted data. If the proto has validators but no Proposer, derive the proposer using findProposer() (same fallback already used by GetProposer()). If both are absent, ValidateBasic() returns ErrValidatorSetEmpty.

3. Propagation — LightBlock.ToProto(): Return ErrValidatorSetEmpty when a present-but-empty ValidatorSet serializes to nil, catching semantically invalid light blocks at serialization time.

Testing performed to validate your change

  • All existing tests pass (types, state, light, statesync packages)
  • Added TestValidatorSetFromProto_NilProposerWithValidators: constructs a proto with valid validators but nil Proposer, verifies the proposer is correctly derived and is a member of the set
  • Added TestValidatorSetFromProto_EmptyProtoRoundtrip: verifies ToProto on empty set returns nil and FromProto(nil) returns error
  • Updated TestLightBlockProtobuf to expect ToProto error on empty validator sets (previously only caught at FromProto time)
  • gofmt -s -l produces no output
Open in Web Open in Cursor 

When a ValidatorSet is serialized via ToProto() with an empty/nil set, it
produces a proto with no Proposer field. On deserialization,
ValidatorSetFromProto() unconditionally tried to deserialize the Proposer,
which fails with 'nil validator' when GetProposer() returns nil.

This roundtrip asymmetry causes nodes to fail startup with:
  error creating node: fromProto: validatorSet proposer error: nil validator

Fix: when the proto has no Proposer but has validators, derive the proposer
using findProposer() (same logic as GetProposer() fallback). When both
validators and proposer are absent, let ValidateBasic() return the
appropriate empty set error.

Co-authored-by: Masih H. Derkani <m@derkani.org>
@github-actions
Copy link

github-actions bot commented Mar 20, 2026

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMar 21, 2026, 7:04 PM

@codecov
Copy link

codecov bot commented Mar 20, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.59%. Comparing base (6d0e8dc) to head (ede6d84).

Files with missing lines Patch % Lines
sei-tendermint/types/validator_set.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #3102   +/-   ##
=======================================
  Coverage   58.58%   58.59%           
=======================================
  Files        2096     2096           
  Lines      173413   173418    +5     
=======================================
+ Hits       101601   101616   +15     
+ Misses      62757    62748    -9     
+ Partials     9055     9054    -1     
Flag Coverage Δ
sei-chain-pr 81.97% <80.00%> (?)
sei-db 70.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/types/light.go 82.52% <100.00%> (+2.32%) ⬆️
sei-tendermint/types/validator_set.go 94.06% <75.00%> (+0.96%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

cursoragent and others added 2 commits March 21, 2026 19:01
- ValidatorSet.ToProto(): return nil instead of &tmproto.ValidatorSet{}
  for nil/empty sets. The old empty-but-non-nil proto had a nil Proposer
  field that ValidatorSetFromProto could not decode, creating a
  serialization roundtrip that silently succeeds on write but crashes on
  read.

- LightBlock.ToProto(): return ErrValidatorSetEmpty when a present
  ValidatorSet serializes to nil, catching semantically invalid states
  at serialization time rather than letting them propagate.

- Add TestValidatorSetFromProto_NilProposerWithValidators: directly
  constructs a proto with valid validators but nil Proposer and verifies
  the proposer is correctly derived via findProposer().

- Add TestValidatorSetFromProto_EmptyProtoRoundtrip: verifies ToProto
  on an empty set returns nil and FromProto(nil) returns an error.

Co-authored-by: Masih H. Derkani <m@derkani.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants