Files
tendermint/consensus/msgs.go
Sergio Mena 78c643718e abci++: Vote extension cleanup (#9848)
* [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>
2022-12-07 23:11:18 +01:00

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
}