Skip to content

Commit

Permalink
refactor(consensus): print err from SignAndCheckVote (#2346)
Browse files Browse the repository at this point in the history
Refs #2357

---

#### PR checklist

- [x] Tests written/updated
- [x] Changelog entry added in `.changelog` (we use
[unclog](https://github.com/informalsystems/unclog) to manage our
changelog)
- [x] Updated relevant documentation (`docs/` or `spec/`) and code
comments
- [x] Title follows the [Conventional
Commits](https://www.conventionalcommits.org/en/v1.0.0/) spec
  • Loading branch information
melekes committed Feb 26, 2024
1 parent cdcc98d commit e623c44
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 11 deletions.
2 changes: 1 addition & 1 deletion internal/consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2443,7 +2443,7 @@ func (cs *State) signVote(

recoverable, err := types.SignAndCheckVote(vote, cs.privValidator, cs.state.ChainID, extEnabled && (msgType == types.PrecommitType))
if err != nil && !recoverable {
panic(fmt.Sprintf("non-recoverable error when signing vote (%d/%d)", vote.Height, vote.Round))
panic(fmt.Sprintf("non-recoverable error when signing vote %v: %v", vote, err))
}

return vote, err
Expand Down
27 changes: 18 additions & 9 deletions types/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ func NewConflictingVoteError(vote1, vote2 *Vote) *ErrVoteConflictingVotes {
}
}

// The vote extension is only valid for non-nil precommits.
type ErrVoteExtensionInvalid struct {
ExtSignature []byte
}

func (err *ErrVoteExtensionInvalid) Error() string {
return fmt.Sprintf("extensions must be present IFF vote is a non-nil Precommit; extension signature: %X", err.ExtSignature)
}

// Address is hex bytes.
type Address = crypto.Address

Expand Down Expand Up @@ -401,6 +410,9 @@ func VotesToProto(votes []*Vote) []*cmtproto.Vote {
return res
}

// SignAndCheckVote signs the vote with the given privVal and checks the vote.
// It returns an error if the vote is invalid and a boolean indicating if the
// error is recoverable or not.
func SignAndCheckVote(
vote *Vote,
privVal PrivValidator,
Expand All @@ -409,33 +421,30 @@ func SignAndCheckVote(
) (bool, error) {
v := vote.ToProto()
if err := privVal.SignVote(chainID, v); err != nil {
// Failing to sign a vote has always been a recoverable error, this function keeps it that way
return true, err // true = recoverable
// Failing to sign a vote has always been a recoverable error, this
// function keeps it that way.
return true, err
}
vote.Signature = v.Signature

isPrecommit := vote.Type == PrecommitType
if !isPrecommit && extensionsEnabled {
// Non-recoverable because the caller passed parameters that don't make sense
return false, fmt.Errorf("only Precommit votes may have extensions enabled; vote type: %d", vote.Type)
return false, &ErrVoteExtensionInvalid{ExtSignature: v.ExtensionSignature}
}

isNil := vote.BlockID.IsNil()
extSignature := (len(v.ExtensionSignature) > 0)
if extSignature == (!isPrecommit || isNil) {
// Non-recoverable because the vote is malformed
return false, fmt.Errorf(
"extensions must be present IFF vote is a non-nil Precommit; present %t, vote type %d, is nil %t",
extSignature,
vote.Type,
isNil,
)
return false, &ErrVoteExtensionInvalid{ExtSignature: v.ExtensionSignature}
}

vote.ExtensionSignature = nil
if extensionsEnabled {
vote.ExtensionSignature = v.ExtensionSignature
}

vote.Timestamp = v.Timestamp

return true, nil
Expand Down
111 changes: 110 additions & 1 deletion types/vote_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"fmt"
"testing"
"time"

Expand All @@ -22,6 +23,7 @@ func examplePrevote() *Vote {

func examplePrecommit() *Vote {
vote := exampleVote(byte(PrecommitType))
vote.Extension = []byte("extension")
vote.ExtensionSignature = []byte("signature")
return vote
}
Expand Down Expand Up @@ -312,7 +314,7 @@ func TestVoteVerify(t *testing.T) {

func TestVoteString(t *testing.T) {
str := examplePrecommit().String()
expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 657874656E73 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
if str != expected {
t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str)
}
Expand Down Expand Up @@ -490,3 +492,110 @@ func TestVoteProtobuf(t *testing.T) {
}
}
}

func TestSignAndCheckVote(t *testing.T) {
privVal := NewMockPV()

testCases := []struct {
name string
extensionsEnabled bool
vote *Vote
expectError bool
}{
{
name: "precommit with extension signature",
extensionsEnabled: true,
vote: examplePrecommit(),
expectError: false,
},
{
name: "precommit with extension signature",
extensionsEnabled: false,
vote: examplePrecommit(),
expectError: false,
},
{
name: "precommit with extension signature for a nil block",
extensionsEnabled: true,
vote: func() *Vote {
v := examplePrecommit()
v.BlockID = BlockID{make([]byte, 0), PartSetHeader{0, make([]byte, 0)}}
return v
}(),
expectError: true,
},
{
name: "precommit with extension signature for a nil block",
extensionsEnabled: false,
vote: func() *Vote {
v := examplePrecommit()
v.BlockID = BlockID{make([]byte, 0), PartSetHeader{0, make([]byte, 0)}}
return v
}(),
expectError: true,
},
{
name: "precommit without extension",
extensionsEnabled: true,
vote: func() *Vote {
v := examplePrecommit()
v.Extension = make([]byte, 0)
return v
}(),
expectError: false,
},
{
name: "precommit without extension",
extensionsEnabled: false,
vote: func() *Vote {
v := examplePrecommit()
v.Extension = make([]byte, 0)
return v
}(),
expectError: false,
},
{
name: "prevote",
extensionsEnabled: true,
vote: examplePrevote(),
expectError: true,
},
{
name: "prevote",
extensionsEnabled: false,
vote: examplePrevote(),
expectError: false,
},
{
name: "prevote with extension",
extensionsEnabled: true,
vote: func() *Vote {
v := examplePrevote()
v.Extension = []byte("extension")
return v
}(),
expectError: true,
},
{
name: "prevote with extension",
extensionsEnabled: false,
vote: func() *Vote {
v := examplePrevote()
v.Extension = []byte("extension")
return v
}(),
expectError: true,
},
}

for _, tc := range testCases {
t.Run(fmt.Sprintf("%s (extensionsEnabled: %t) ", tc.name, tc.extensionsEnabled), func(t *testing.T) {
_, err := SignAndCheckVote(tc.vote, privVal, "test_chain_id", tc.extensionsEnabled)
if tc.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}

0 comments on commit e623c44

Please sign in to comment.