Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(rpc): validators endpoint fail during quorum rotation #959

Open
wants to merge 7 commits into
base: v1.3-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ func NewHeightRoundProposerSelector(vset *types.ValidatorSet, currentHeight int6

// proposerFromStore determines the proposer for the given height and round using current or previous block read from
// the block store.
//
// Requires s.valSet to be set to correct validator set.
func (s *heightRoundProposerSelector) proposerFromStore(height int64, round int32) error {
if s.bs == nil {
return fmt.Errorf("block store is nil")
Expand All @@ -85,12 +87,15 @@ func (s *heightRoundProposerSelector) proposerFromStore(height int64, round int3

meta := s.bs.LoadBlockMeta(height)
if meta != nil {
valSetHash := s.valSet.Hash()
// block already saved to store, just take the proposer
if !meta.Header.ValidatorsHash.Equal(s.valSet.Hash()) {
if !meta.Header.ValidatorsHash.Equal(valSetHash) {
// we loaded the same block, so quorum should be the same
s.logger.Error("quorum rotation detected but not expected",
"height", height,
"validators_hash", meta.Header.ValidatorsHash, "quorum_hash", s.valSet.QuorumHash,
"validators_hash", meta.Header.ValidatorsHash,
"expected_validators_hash", valSetHash,
"next_validators_hash", meta.Header.NextValidatorsHash,
"validators", s.valSet)

return fmt.Errorf("quorum hash mismatch at height %d", height)
Expand Down
2 changes: 1 addition & 1 deletion internal/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func NewFromConfig(logger log.Logger, cfg *config.Config) (*Inspector, error) {
if err != nil {
return nil, err
}
ss := state.NewStore(sDB)
ss := state.NewStoreWithLogger(sDB, logger.With("module", "state_store"))
return New(cfg.RPC, bs, ss, sinks, logger), nil
}

Expand Down
77 changes: 59 additions & 18 deletions internal/state/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ func NewStore(db dbm.DB) Store {
return dbStore{db, log.NewNopLogger()}
}

// NewStoreWithLogger creates the dbStore of the state pkg.
func NewStoreWithLogger(db dbm.DB, logger log.Logger) Store {
return dbStore{db, logger}
}

lklimek marked this conversation as resolved.
Show resolved Hide resolved
// LoadState loads the State from the database.
func (store dbStore) Load() (State, error) {
return store.loadState(stateKey)
Expand Down Expand Up @@ -498,19 +503,19 @@ func (store dbStore) SaveValidatorSets(lowerHeight, upperHeight int64, vals *typ
return batch.WriteSync()
}

//-----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

// LoadValidators loads the ValidatorSet for a given height and round 0.
//
// Returns ErrNoValSetForHeight if the validator set can't be found for this height.
func (store dbStore) LoadValidators(height int64, bs selectproposer.BlockStore) (*types.ValidatorSet, error) {
// loadValidators is a helper that loads the validator set from height or last stored height.
// It does NOT set a correct proposer.
func (store dbStore) loadValidators(height int64) (*tmstate.ValidatorsInfo, error) {
valInfo, err := loadValidatorsInfo(store.db, height)
if err != nil {
return nil, ErrNoValSetForHeight{Height: height, Err: err}
}

if valInfo.ValidatorSet == nil {
lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged)
store.logger.Debug("Validator set is nil, loading last stored height", "height", height, "last_height_changed", valInfo.LastHeightChanged, "last_stored_height", lastStoredHeight)
valInfo, err = loadValidatorsInfo(store.db, lastStoredHeight)
if err != nil || valInfo.ValidatorSet == nil {
return nil,
Expand All @@ -522,6 +527,20 @@ func (store dbStore) LoadValidators(height int64, bs selectproposer.BlockStore)
}
}

return valInfo, nil
}

// LoadValidators loads the ValidatorSet for a given height and round 0.
//
// Returns ErrNoValSetForHeight if the validator set can't be found for this height.
func (store dbStore) LoadValidators(height int64, bs selectproposer.BlockStore) (*types.ValidatorSet, error) {
store.logger.Debug("Loading validators", "height", height)

valInfo, err := store.loadValidators(height)
if err != nil {
return nil, ErrNoValSetForHeight{Height: height, Err: err}
}

valSet, err := types.ValidatorSetFromProto(valInfo.ValidatorSet)
if err != nil {
return nil, err
Expand Down Expand Up @@ -562,27 +581,48 @@ func (store dbStore) LoadValidators(height int64, bs selectproposer.BlockStore)
return strategy.ValidatorSet(), nil
}

// If we have that height in the block store, we just take proposer from previous block and advance it.
// If we don't have that height in the block store, we just take proposer from previous block and advance it.
// We don't use current height block because we want to return proposer at round 0.
prevMeta := bs.LoadBlockMeta(height - 1)
if prevMeta == nil {
return nil, fmt.Errorf("could not find block meta for height %d", height-1)
}
// Configure proposer strategy; first set proposer from previous block
if err := valSet.SetProposer(prevMeta.Header.ProposerProTxHash); err != nil {
return nil, fmt.Errorf("could not set proposer: %w", err)
}
strategy, err := selectproposer.NewProposerSelector(cp, valSet, prevMeta.Header.Height, prevMeta.Round, bs, store.logger)
if err != nil {
return nil, fmt.Errorf("failed to create validator scoring strategy: %w", err)
}
// Configure proposer strategy; first check if we have a quorum change
if !prevMeta.Header.ValidatorsHash.Equal(prevMeta.Header.NextValidatorsHash) {
// rotation happened - we select 1st validator as proposer
valSetHash := valSet.Hash()
if !prevMeta.Header.NextValidatorsHash.Equal(valSetHash) {
return nil, ErrNoValSetForHeight{
height,
fmt.Errorf("quorum hash mismatch at height %d, expected %X, got %X", height,
prevMeta.Header.NextValidatorsHash, valSetHash),
}
}

if err = valSet.SetProposer(valSet.GetByIndex(0).ProTxHash); err != nil {
return nil, ErrNoValSetForHeight{height, fmt.Errorf("could not set proposer: %w", err)}
}

return valSet, nil
} else {

// now, advance to (height,0)
if err := strategy.UpdateHeightRound(height, 0); err != nil {
return nil, fmt.Errorf("failed to update validator scores at height %d, round 0: %w", height, err)
// set proposer from previous block
if err := valSet.SetProposer(prevMeta.Header.ProposerProTxHash); err != nil {
return nil, fmt.Errorf("could not set proposer: %w", err)
}
strategy, err := selectproposer.NewProposerSelector(cp, valSet, prevMeta.Header.Height, prevMeta.Round, bs, store.logger)
if err != nil {
return nil, fmt.Errorf("failed to create validator scoring strategy: %w", err)
}

// now, advance to (height,0)
if err := strategy.UpdateHeightRound(height, 0); err != nil {
return nil, fmt.Errorf("failed to update validator scores at height %d, round 0: %w", height, err)
}

return strategy.ValidatorSet(), nil
lklimek marked this conversation as resolved.
Show resolved Hide resolved
}

return strategy.ValidatorSet(), nil
}

func lastStoredHeightFor(height, lastHeightChanged int64) int64 {
Expand Down Expand Up @@ -643,6 +683,7 @@ func (store dbStore) saveValidatorsInfo(
return err
}

store.logger.Debug("saving validator set", "height", height, "last_height_changed", lastHeightChanged, "validators", valSet)
return batch.Set(validatorsKey(height), bz)
}

Expand Down
2 changes: 1 addition & 1 deletion node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func makeNode(
}
closers = append(closers, dbCloser)

stateStore := sm.NewStore(stateDB)
stateStore := sm.NewStoreWithLogger(stateDB, logger.With("module", "state_store"))

genDoc, err := genesisDocProvider()
if err != nil {
Expand Down
Loading