Skip to content

Commit

Permalink
Add message verification with unit tests (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
zivkovicmilos authored Feb 28, 2024
1 parent 94efb32 commit 868624f
Show file tree
Hide file tree
Showing 10 changed files with 918 additions and 58 deletions.
2 changes: 0 additions & 2 deletions .github/golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ run:
modules-download-mode: readonly
allow-parallel-runners: false
go: ""
skip-dirs: # Temporary
- core

output:
uniq-by-line: false
Expand Down
4 changes: 4 additions & 0 deletions core/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (t *Tendermint) buildPrevoteMessage(id []byte) *types.PrevoteMessage {
}

// buildPrecommitMessage builds a precommit message using the given precommit identifier
//
//nolint:unused // Temporarily unused
func (t *Tendermint) buildPrecommitMessage(id []byte) *types.PrecommitMessage {
// TODO make thread safe
var (
Expand Down Expand Up @@ -94,6 +96,8 @@ func (t *Tendermint) broadcastPrevote(prevote *types.PrevoteMessage) {
}

// broadcastPrecommit signs and broadcasts the given precommit message
//
//nolint:unused // Temporarily unused
func (t *Tendermint) broadcastPrecommit(precommit *types.PrecommitMessage) {
message := &types.Message{
Type: types.MessageType_PRECOMMIT,
Expand Down
115 changes: 115 additions & 0 deletions core/messages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package core

import (
"errors"
"fmt"

"github.com/gnolang/go-tendermint/messages/types"
)

var (
ErrMessageNotSet = errors.New("message not set")
ErrMessagePayloadNotSet = errors.New("message payload not set")
ErrInvalidMessageSignature = errors.New("invalid message signature")
ErrMessageFromNonValidator = errors.New("message is from a non-validator")
ErrEarlierHeightMessage = errors.New("message is for an earlier height")
ErrEarlierRoundMessage = errors.New("message is for an earlier round")
)

// AddMessage verifies and adds a new message to the consensus engine
func (t *Tendermint) AddMessage(message *types.Message) error {
// Verify the incoming message
if err := t.verifyMessage(message); err != nil {
return fmt.Errorf("unable to verify message, %w", err)
}

// Add the message to the store
t.store.AddMessage(message)

return nil
}

// verifyMessage verifies the incoming consensus message (base verification)
func (t *Tendermint) verifyMessage(message *types.Message) error {
// Make sure the message is present
if message == nil {
return ErrMessageNotSet
}

// Make sure the message payload is present
if message.Payload == nil {
return ErrMessagePayloadNotSet
}

// Get the signature payload
signPayload, err := message.GetSignaturePayload()
if err != nil {
return fmt.Errorf("unable to get message signature payload, %w", err)
}

// Make sure the signature is valid
if !t.signer.IsValidSignature(signPayload, message.Signature) {
return ErrInvalidMessageSignature
}

// Extract individual message data
var (
sender []byte
view *types.View
)

switch message.Type {
case types.MessageType_PROPOSAL:
// Get the proposal message
payload := message.GetProposalMessage()
if payload == nil || !payload.IsValid() {
return types.ErrInvalidMessagePayload
}

sender = payload.GetFrom()
view = payload.GetView()
case types.MessageType_PREVOTE:
// Get the prevote message
payload := message.GetPrevoteMessage()
if payload == nil || !payload.IsValid() {
return types.ErrInvalidMessagePayload
}

sender = payload.GetFrom()
view = payload.GetView()
case types.MessageType_PRECOMMIT:
// Get the precommit message
payload := message.GetPrecommitMessage()
if payload == nil || !payload.IsValid() {
return types.ErrInvalidMessagePayload
}

sender = payload.GetFrom()
view = payload.GetView()
}

// Make sure the message sender is a validator
if !t.verifier.IsValidator(sender) {
return ErrMessageFromNonValidator
}

// Make sure the message view is valid
var (
currentHeight = t.state.view.GetHeight() // TODO make thread safe
currentRound = t.state.view.GetRound() // TODO make thread safe
)

// Make sure the height is valid.
// The message height needs to be the current state height, or greater
if currentHeight > view.GetHeight() {
return ErrEarlierHeightMessage
}

// Make sure the round is valid.
// The message rounds needs to be >= the current round
if currentRound > view.GetRound() {
return ErrEarlierRoundMessage
}

return nil
}
42 changes: 40 additions & 2 deletions core/mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ func (m *mockNode) BuildProposal(height uint64) []byte {
return nil
}

type signDelegate func([]byte) []byte
type (
signDelegate func([]byte) []byte
isValidSignatureDelegate func([]byte, []byte) bool
)

type mockSigner struct {
signFn signDelegate
signFn signDelegate
isValidSignatureFn isValidSignatureDelegate
}

func (m *mockSigner) Sign(data []byte) []byte {
Expand All @@ -63,3 +67,37 @@ func (m *mockSigner) Sign(data []byte) []byte {

return nil
}

func (m *mockSigner) IsValidSignature(data, signature []byte) bool {
if m.isValidSignatureFn != nil {
return m.isValidSignatureFn(data, signature)
}

return false
}

type (
isProposerDelegate func([]byte, uint64, uint64) bool
isValidator func([]byte) bool
)

type mockVerifier struct {
isProposerFn isProposerDelegate
isValidatorFn isValidator
}

func (m *mockVerifier) IsProposer(id []byte, height, round uint64) bool {
if m.isProposerFn != nil {
return m.isProposerFn(id, height, round)
}

return false
}

func (m *mockVerifier) IsValidator(from []byte) bool {
if m.isValidatorFn != nil {
return m.isValidatorFn(from)
}

return false
}
7 changes: 4 additions & 3 deletions core/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ func (n step) String() string {
// TODO make thread safe
type state struct {
view *types.View
step step

acceptedProposal *types.ProposalMessage
acceptedProposalID []byte

lockedValue []byte
validValue []byte

lockedRound int64
validRound int64

validValue []byte
validRound int64
step step
}

// newState creates a fresh state using the given view
Expand Down
54 changes: 11 additions & 43 deletions core/tendermint.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ type Tendermint struct {
broadcast Broadcast
signer Signer

// wg is the barrier for keeping all
// parallel consensus processes synced
wg sync.WaitGroup

// state is the current Tendermint consensus state
state *state

Expand All @@ -31,6 +27,10 @@ type Tendermint struct {

// timeouts hold state timeout information (constant)
timeouts map[step]timeout

// wg is the barrier for keeping all
// parallel consensus processes synced
wg sync.WaitGroup
}

// RunSequence runs the Tendermint consensus sequence for a given height,
Expand All @@ -56,7 +56,7 @@ func (t *Tendermint) RunSequence(ctx context.Context, h uint64) []byte {
teardown()

return proposal
case _ = <-t.watchForRoundJumps(ctxRound):
case _ = <-t.watchForRoundJumps(ctxRound): //nolint:gosimple // Temporarily unassigned
teardown()

// TODO start NEW round (that was received)
Expand Down Expand Up @@ -224,9 +224,7 @@ func (t *Tendermint) runStates(ctx context.Context) []byte {
// waits for a valid PROPOSE message
func (t *Tendermint) runPropose(ctx context.Context) {
// TODO make thread safe
var (
_ = t.state.view
)
_ = t.state.view

ch, unsubscribeFn := t.store.SubscribeToPropose()
defer unsubscribeFn()
Expand All @@ -237,9 +235,8 @@ func (t *Tendermint) runPropose(ctx context.Context) {
return
case getMessagesFn := <-ch:
// TODO filter and verify messages
_ = getMessagesFn()

// TODO move to prevote if the proposal is valid
_ = getMessagesFn()
}
}
}
Expand All @@ -248,9 +245,7 @@ func (t *Tendermint) runPropose(ctx context.Context) {
// waits for a valid PREVOTE messages
func (t *Tendermint) runPrevote(ctx context.Context) {
// TODO make thread safe
var (
_ = t.state.view
)
_ = t.state.view

ch, unsubscribeFn := t.store.SubscribeToPrevote()
defer unsubscribeFn()
Expand All @@ -261,9 +256,8 @@ func (t *Tendermint) runPrevote(ctx context.Context) {
return
case getMessagesFn := <-ch:
// TODO filter and verify messages
_ = getMessagesFn()

// TODO move to precommit if the proposal is valid
_ = getMessagesFn()
}
}
}
Expand All @@ -272,9 +266,7 @@ func (t *Tendermint) runPrevote(ctx context.Context) {
// waits for a valid PRECOMMIT messages
func (t *Tendermint) runPrecommit(ctx context.Context) []byte {
// TODO make thread safe
var (
_ = t.state.view
)
_ = t.state.view

ch, unsubscribeFn := t.store.SubscribeToPrecommit()
defer unsubscribeFn()
Expand All @@ -285,32 +277,8 @@ func (t *Tendermint) runPrecommit(ctx context.Context) []byte {
return nil
case getMessagesFn := <-ch:
// TODO filter and verify messages
_ = getMessagesFn()

// TODO move to precommit if the proposal is valid
_ = getMessagesFn()
}
}
}

// AddMessage verifies and adds a new message to the consensus engine
func (t *Tendermint) AddMessage(message *types.Message) {
// Make sure the message is present
if message == nil {
return
}

// Make sure the message payload is present
if message.Payload == nil {
return
}

// TODO verify the message sender

// TODO verify the message signature

// TODO verify the message height

// TODO verify the message round

// TODO verify the message content (fields set)
}
Loading

0 comments on commit 868624f

Please sign in to comment.