diff --git a/internal/consensus/state.go b/internal/consensus/state.go index c344afdde..3d30b0240 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -695,27 +695,55 @@ func (cs *State) sendInternalMessage(ctx context.Context, mi msgInfo) { // Reconstruct LastCommit from SeenCommit, which we saved along with the block, // (which happens even before saving the state) func (cs *State) reconstructLastCommit(state sm.State) { - extCommit := cs.blockStore.LoadExtendedCommit(state.LastBlockHeight) - if extCommit == nil { - panic(fmt.Sprintf( - "failed to reconstruct last commit; commit for height %v not found", - state.LastBlockHeight, - )) - } - + requireExtensions := false requireHeight := cs.state.ConsensusParams.Vote.ExtensionRequireHeight - if requireHeight != 0 && extCommit.Height >= requireHeight { - if err := extCommit.EnsureExtensions(); err != nil { - panic(fmt.Sprintf("failed to reconstruct last commit; invalid vote extensions: %v", err)) - } + if requireHeight != 0 && state.LastBlockHeight >= requireHeight { + requireExtensions = true + } + votes, err := cs.votesFromExtendedCommit(state) + if err == nil { + cs.LastCommit = votes + return + } + if requireExtensions { + panic(fmt.Sprintf("failed to reconstruct last commit; %s", err)) + } + votes, err = cs.votesFromSeenCommit(state) + if err != nil { + panic(fmt.Sprintf("failed to reconstruct last commit; %s", err)) + } + cs.LastCommit = votes +} + +func (cs *State) votesFromExtendedCommit(state sm.State) (*types.VoteSet, error) { + ec := cs.blockStore.LoadExtendedCommit(state.LastBlockHeight) + if ec == nil { + return nil, fmt.Errorf("commit for height %v not found", state.LastBlockHeight) + } + if err := ec.EnsureExtensions(); err != nil { + return nil, fmt.Errorf("invalid vote extensions: %v", err) + } + vs := ec.ToVoteSet(state.ChainID, state.LastValidators) + if !vs.HasTwoThirdsMajority() { + return nil, errors.New("seen commit does not have +2/3 majority") + } + return vs, nil +} + +func (cs *State) votesFromSeenCommit(state sm.State) (*types.VoteSet, error) { + commit := cs.blockStore.LoadSeenCommit() + if commit == nil || commit.Height != state.LastBlockHeight { + commit = cs.blockStore.LoadBlockCommit(state.LastBlockHeight) + } + if commit == nil { + return nil, fmt.Errorf("commit for height %v not found", state.LastBlockHeight) } - lastPrecommits := extCommit.ToVoteSet(state.ChainID, state.LastValidators) - if !lastPrecommits.HasTwoThirdsMajority() { - panic("failed to reconstruct last commit; does not have +2/3 maj") + vs := commit.ToVoteSet(state.ChainID, state.LastValidators) + if !vs.HasTwoThirdsMajority() { + return nil, errors.New("commit does not have +2/3 majority") } - - cs.LastCommit = lastPrecommits + return vs, nil } // Updates State and increments height to match that of state. diff --git a/types/block.go b/types/block.go index d5d99bd44..1a067fc3a 100644 --- a/types/block.go +++ b/types/block.go @@ -1038,6 +1038,27 @@ func (ec *ExtendedCommit) ToVoteSet(chainID string, vals *ValidatorSet) *VoteSet return voteSet } +// ToVoteSet constructs a VoteSet from the Commit and validator set. +// Panics if signatures from the commit can't be added to the voteset. +// Inverse of VoteSet.MakeCommit(). +func (c *Commit) ToVoteSet(chainID string, vals *ValidatorSet) *VoteSet { + voteSet := NewVoteSet(chainID, c.Height, c.Round, tmproto.PrecommitType, vals) + for idx, cs := range c.Signatures { + if cs.BlockIDFlag == BlockIDFlagAbsent { + continue // OK, some precommits can be missing. + } + vote := c.GetVote(int32(idx)) + if err := vote.ValidateBasic(); err != nil { + panic(fmt.Errorf("failed to validate vote reconstructed from commit: %w", err)) + } + added, err := voteSet.AddVote(vote) + if !added || err != nil { + panic(fmt.Errorf("failed to reconstruct vote set from commit: %w", err)) + } + } + return voteSet +} + // TODO Comment // this should probably also verify the signature // probably want to change to just verify when present.