Skip to content

Commit

Permalink
fix(rpc): validators endpoint fail during quorum rotation
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek committed Oct 15, 2024
1 parent 525544e commit 3b70797
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 22 deletions.
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}
}

// 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
}

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

0 comments on commit 3b70797

Please sign in to comment.