mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 05:46:32 +00:00
* [cherry-picked] abci++: Vote extension cleanup (#8402) * Split vote verification/validation based on vote extensions Some parts of the code need vote extensions to be verified and validated (mostly in consensus), and other parts of the code don't because its possible that, in some cases (as per RFC 017), we won't have vote extensions. This explicitly facilitates that split. Signed-off-by: Thane Thomson <connect@thanethomson.com> * Only sign extensions in precommits, not prevotes Signed-off-by: Thane Thomson <connect@thanethomson.com> * Update privval/file.go Co-authored-by: M. J. Fromberger <fromberger@interchain.io> * Apply suggestions from code review Co-authored-by: M. J. Fromberger <fromberger@interchain.io> * Temporarily disable extension requirement again for E2E testing Signed-off-by: Thane Thomson <connect@thanethomson.com> * Reorganize comment for clarity Signed-off-by: Thane Thomson <connect@thanethomson.com> * Leave vote validation and pre-call nil check up to caller of VoteToProto Signed-off-by: Thane Thomson <connect@thanethomson.com> * Split complex vote validation test into multiple tests Signed-off-by: Thane Thomson <connect@thanethomson.com> * Universally enforce no vote extensions on any vote type but precommits Signed-off-by: Thane Thomson <connect@thanethomson.com> * Make error messages more generic Signed-off-by: Thane Thomson <connect@thanethomson.com> * Verify with vote extensions when constructing a VoteSet Signed-off-by: Thane Thomson <connect@thanethomson.com> * Expand comment for clarity Signed-off-by: Thane Thomson <connect@thanethomson.com> * Add extension check for prevotes prior to signing votes Signed-off-by: Thane Thomson <connect@thanethomson.com> * Fix supporting test code to only inject extensions into precommits Signed-off-by: Thane Thomson <connect@thanethomson.com> * Separate vote malleation from signing in vote tests for clarity Signed-off-by: Thane Thomson <connect@thanethomson.com> * Add extension signature length check and corresponding test Signed-off-by: Thane Thomson <connect@thanethomson.com> * Perform basic vote validation in CommitToVoteSet Signed-off-by: Thane Thomson <connect@thanethomson.com> Co-authored-by: M. J. Fromberger <fromberger@interchain.io> * Appease TestMempoolProgressAfterCreateEmptyBlocksInterval Co-authored-by: Thane Thomson <connect@thanethomson.com> Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
348 lines
8.5 KiB
Go
348 lines
8.5 KiB
Go
package consensus
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/cosmos/gogoproto/proto"
|
|
|
|
cstypes "github.com/tendermint/tendermint/consensus/types"
|
|
"github.com/tendermint/tendermint/libs/bits"
|
|
tmmath "github.com/tendermint/tendermint/libs/math"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus"
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
// MsgToProto takes a consensus message type and returns the proto defined consensus message.
|
|
//
|
|
// TODO: This needs to be removed, but WALToProto depends on this.
|
|
func MsgToProto(msg Message) (proto.Message, error) {
|
|
if msg == nil {
|
|
return nil, errors.New("consensus: message is nil")
|
|
}
|
|
var pb proto.Message
|
|
|
|
switch msg := msg.(type) {
|
|
case *NewRoundStepMessage:
|
|
pb = &tmcons.NewRoundStep{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Step: uint32(msg.Step),
|
|
SecondsSinceStartTime: msg.SecondsSinceStartTime,
|
|
LastCommitRound: msg.LastCommitRound,
|
|
}
|
|
|
|
case *NewValidBlockMessage:
|
|
pbPartSetHeader := msg.BlockPartSetHeader.ToProto()
|
|
pbBits := msg.BlockParts.ToProto()
|
|
pb = &tmcons.NewValidBlock{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
BlockPartSetHeader: pbPartSetHeader,
|
|
BlockParts: pbBits,
|
|
IsCommit: msg.IsCommit,
|
|
}
|
|
|
|
case *ProposalMessage:
|
|
pbP := msg.Proposal.ToProto()
|
|
pb = &tmcons.Proposal{
|
|
Proposal: *pbP,
|
|
}
|
|
|
|
case *ProposalPOLMessage:
|
|
pbBits := msg.ProposalPOL.ToProto()
|
|
pb = &tmcons.ProposalPOL{
|
|
Height: msg.Height,
|
|
ProposalPolRound: msg.ProposalPOLRound,
|
|
ProposalPol: *pbBits,
|
|
}
|
|
|
|
case *BlockPartMessage:
|
|
parts, err := msg.Part.ToProto()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("msg to proto error: %w", err)
|
|
}
|
|
pb = &tmcons.BlockPart{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Part: *parts,
|
|
}
|
|
|
|
case *VoteMessage:
|
|
vote := msg.Vote.ToProto()
|
|
pb = &tmcons.Vote{
|
|
Vote: vote,
|
|
}
|
|
|
|
case *HasVoteMessage:
|
|
pb = &tmcons.HasVote{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Type: msg.Type,
|
|
Index: msg.Index,
|
|
}
|
|
|
|
case *VoteSetMaj23Message:
|
|
bi := msg.BlockID.ToProto()
|
|
pb = &tmcons.VoteSetMaj23{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Type: msg.Type,
|
|
BlockID: bi,
|
|
}
|
|
|
|
case *VoteSetBitsMessage:
|
|
bi := msg.BlockID.ToProto()
|
|
bits := msg.Votes.ToProto()
|
|
|
|
vsb := &tmcons.VoteSetBits{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Type: msg.Type,
|
|
BlockID: bi,
|
|
}
|
|
|
|
if bits != nil {
|
|
vsb.Votes = *bits
|
|
}
|
|
|
|
pb = vsb
|
|
|
|
default:
|
|
return nil, fmt.Errorf("consensus: message not recognized: %T", msg)
|
|
}
|
|
|
|
return pb, nil
|
|
}
|
|
|
|
// MsgFromProto takes a consensus proto message and returns the native go type
|
|
func MsgFromProto(p proto.Message) (Message, error) {
|
|
if p == nil {
|
|
return nil, errors.New("consensus: nil message")
|
|
}
|
|
var pb Message
|
|
|
|
switch msg := p.(type) {
|
|
case *tmcons.NewRoundStep:
|
|
rs, err := tmmath.SafeConvertUint8(int64(msg.Step))
|
|
// deny message based on possible overflow
|
|
if err != nil {
|
|
return nil, fmt.Errorf("denying message due to possible overflow: %w", err)
|
|
}
|
|
pb = &NewRoundStepMessage{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Step: cstypes.RoundStepType(rs),
|
|
SecondsSinceStartTime: msg.SecondsSinceStartTime,
|
|
LastCommitRound: msg.LastCommitRound,
|
|
}
|
|
case *tmcons.NewValidBlock:
|
|
pbPartSetHeader, err := types.PartSetHeaderFromProto(&msg.BlockPartSetHeader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parts to proto error: %w", err)
|
|
}
|
|
|
|
pbBits := new(bits.BitArray)
|
|
pbBits.FromProto(msg.BlockParts)
|
|
|
|
pb = &NewValidBlockMessage{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
BlockPartSetHeader: *pbPartSetHeader,
|
|
BlockParts: pbBits,
|
|
IsCommit: msg.IsCommit,
|
|
}
|
|
case *tmcons.Proposal:
|
|
pbP, err := types.ProposalFromProto(&msg.Proposal)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proposal msg to proto error: %w", err)
|
|
}
|
|
|
|
pb = &ProposalMessage{
|
|
Proposal: pbP,
|
|
}
|
|
case *tmcons.ProposalPOL:
|
|
pbBits := new(bits.BitArray)
|
|
pbBits.FromProto(&msg.ProposalPol)
|
|
pb = &ProposalPOLMessage{
|
|
Height: msg.Height,
|
|
ProposalPOLRound: msg.ProposalPolRound,
|
|
ProposalPOL: pbBits,
|
|
}
|
|
case *tmcons.BlockPart:
|
|
parts, err := types.PartFromProto(&msg.Part)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("blockpart msg to proto error: %w", err)
|
|
}
|
|
pb = &BlockPartMessage{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Part: parts,
|
|
}
|
|
case *tmcons.Vote:
|
|
// Vote validation will be handled in the vote message ValidateBasic
|
|
// call below.
|
|
vote, err := types.VoteFromProto(msg.Vote)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("vote msg to proto error: %w", err)
|
|
}
|
|
|
|
pb = &VoteMessage{
|
|
Vote: vote,
|
|
}
|
|
case *tmcons.HasVote:
|
|
pb = &HasVoteMessage{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Type: msg.Type,
|
|
Index: msg.Index,
|
|
}
|
|
case *tmcons.VoteSetMaj23:
|
|
bi, err := types.BlockIDFromProto(&msg.BlockID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("voteSetMaj23 msg to proto error: %w", err)
|
|
}
|
|
pb = &VoteSetMaj23Message{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Type: msg.Type,
|
|
BlockID: *bi,
|
|
}
|
|
case *tmcons.VoteSetBits:
|
|
bi, err := types.BlockIDFromProto(&msg.BlockID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("voteSetBits msg to proto error: %w", err)
|
|
}
|
|
bits := new(bits.BitArray)
|
|
bits.FromProto(&msg.Votes)
|
|
|
|
pb = &VoteSetBitsMessage{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Type: msg.Type,
|
|
BlockID: *bi,
|
|
Votes: bits,
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("consensus: message not recognized: %T", msg)
|
|
}
|
|
|
|
if err := pb.ValidateBasic(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pb, nil
|
|
}
|
|
|
|
// WALToProto takes a WAL message and return a proto walMessage and error
|
|
func WALToProto(msg WALMessage) (*tmcons.WALMessage, error) {
|
|
var pb tmcons.WALMessage
|
|
|
|
switch msg := msg.(type) {
|
|
case types.EventDataRoundState:
|
|
pb = tmcons.WALMessage{
|
|
Sum: &tmcons.WALMessage_EventDataRoundState{
|
|
EventDataRoundState: &tmproto.EventDataRoundState{
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Step: msg.Step,
|
|
},
|
|
},
|
|
}
|
|
case msgInfo:
|
|
consMsg, err := MsgToProto(msg.Msg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if w, ok := consMsg.(p2p.Wrapper); ok {
|
|
consMsg = w.Wrap()
|
|
}
|
|
cm := consMsg.(*tmcons.Message)
|
|
pb = tmcons.WALMessage{
|
|
Sum: &tmcons.WALMessage_MsgInfo{
|
|
MsgInfo: &tmcons.MsgInfo{
|
|
Msg: *cm,
|
|
PeerID: string(msg.PeerID),
|
|
},
|
|
},
|
|
}
|
|
case timeoutInfo:
|
|
pb = tmcons.WALMessage{
|
|
Sum: &tmcons.WALMessage_TimeoutInfo{
|
|
TimeoutInfo: &tmcons.TimeoutInfo{
|
|
Duration: msg.Duration,
|
|
Height: msg.Height,
|
|
Round: msg.Round,
|
|
Step: uint32(msg.Step),
|
|
},
|
|
},
|
|
}
|
|
case EndHeightMessage:
|
|
pb = tmcons.WALMessage{
|
|
Sum: &tmcons.WALMessage_EndHeight{
|
|
EndHeight: &tmcons.EndHeight{
|
|
Height: msg.Height,
|
|
},
|
|
},
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("to proto: wal message not recognized: %T", msg)
|
|
}
|
|
|
|
return &pb, nil
|
|
}
|
|
|
|
// WALFromProto takes a proto wal message and return a consensus walMessage and error
|
|
func WALFromProto(msg *tmcons.WALMessage) (WALMessage, error) {
|
|
if msg == nil {
|
|
return nil, errors.New("nil WAL message")
|
|
}
|
|
var pb WALMessage
|
|
|
|
switch msg := msg.Sum.(type) {
|
|
case *tmcons.WALMessage_EventDataRoundState:
|
|
pb = types.EventDataRoundState{
|
|
Height: msg.EventDataRoundState.Height,
|
|
Round: msg.EventDataRoundState.Round,
|
|
Step: msg.EventDataRoundState.Step,
|
|
}
|
|
case *tmcons.WALMessage_MsgInfo:
|
|
um, err := msg.MsgInfo.Msg.Unwrap()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unwrap message: %w", err)
|
|
}
|
|
walMsg, err := MsgFromProto(um)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("msgInfo from proto error: %w", err)
|
|
}
|
|
pb = msgInfo{
|
|
Msg: walMsg,
|
|
PeerID: p2p.ID(msg.MsgInfo.PeerID),
|
|
}
|
|
|
|
case *tmcons.WALMessage_TimeoutInfo:
|
|
tis, err := tmmath.SafeConvertUint8(int64(msg.TimeoutInfo.Step))
|
|
// deny message based on possible overflow
|
|
if err != nil {
|
|
return nil, fmt.Errorf("denying message due to possible overflow: %w", err)
|
|
}
|
|
pb = timeoutInfo{
|
|
Duration: msg.TimeoutInfo.Duration,
|
|
Height: msg.TimeoutInfo.Height,
|
|
Round: msg.TimeoutInfo.Round,
|
|
Step: cstypes.RoundStepType(tis),
|
|
}
|
|
return pb, nil
|
|
case *tmcons.WALMessage_EndHeight:
|
|
pb := EndHeightMessage{
|
|
Height: msg.EndHeight.Height,
|
|
}
|
|
return pb, nil
|
|
default:
|
|
return nil, fmt.Errorf("from proto: wal message not recognized: %T", msg)
|
|
}
|
|
return pb, nil
|
|
}
|