Skip to content
This repository has been archived by the owner on Nov 7, 2023. It is now read-only.

Commit

Permalink
messages/protobuf: Implement ViewChange message type
Browse files Browse the repository at this point in the history
Signed-off-by: Sergey Fedorov <sergey.fedorov@neclab.eu>
  • Loading branch information
Sergey Fedorov committed May 17, 2021
1 parent e4943bb commit b5ffd9a
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 1 deletion.
12 changes: 12 additions & 0 deletions messages/protobuf/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func typedMessageFromPb(pbMsg *pb.Message) (messages.Message, error) {
return newCommitFromPb(t.Commit)
case *pb.Message_ReqViewChange:
return newReqViewChangeFromPb(t.ReqViewChange)
case *pb.Message_ViewChange:
return newViewChangeFromPb(t.ViewChange)
default:
return nil, xerrors.New("unknown message type")
}
Expand All @@ -54,6 +56,8 @@ func pbMessageFromAPI(m messages.Message) proto.Message {
return pbCommitFromAPI(m)
case messages.ReqViewChange:
return pbReqViewChangeFromAPI(m)
case messages.ViewChange:
return pbViewChangeFromAPI(m)
default:
panic("unknown message type")
}
Expand Down Expand Up @@ -93,3 +97,11 @@ func pbReqViewChangeFromAPI(m messages.ReqViewChange) *pb.ReqViewChange {
}
return pb.ReqViewChangeFromAPI(m)
}

func pbViewChangeFromAPI(m messages.ViewChange) *pb.ViewChange {
if m, ok := m.(*viewChange); ok {
return m.pbMsg
}

return pb.ViewChangeFromAPI(m)
}
2 changes: 1 addition & 1 deletion messages/protobuf/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (*impl) NewReqViewChange(r uint32, nv uint64) messages.ReqViewChange {
}

func (*impl) NewViewChange(r uint32, nv uint64, log messages.MessageLog, vcCert messages.ViewChangeCert) messages.ViewChange {
panic("Not implemented")
return newViewChange(r, nv, log, vcCert)
}

func marshalMessage(m proto.Message) ([]byte, error) {
Expand Down
136 changes: 136 additions & 0 deletions messages/protobuf/view-change.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2021 NEC Laboratories Europe GmbH.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package protobuf

import (
"golang.org/x/xerrors"

"github.com/hyperledger-labs/minbft/messages"
"github.com/hyperledger-labs/minbft/messages/protobuf/pb"
"github.com/hyperledger-labs/minbft/usig"
)

type viewChange struct {
pbMsg *pb.ViewChange
log messages.MessageLog
vcCert messages.ViewChangeCert
ui *usig.UI
}

func newViewChange(r uint32, nv uint64, log messages.MessageLog, vcCert messages.ViewChangeCert) *viewChange {
return &viewChange{
pbMsg: &pb.ViewChange{
ReplicaId: r,
NewView: nv,
Log: pbMessageLogFromAPI(log),
VcCert: pbViewChangeCertFromAPI(vcCert),
},
log: log,
vcCert: vcCert,
}
}

func newViewChangeFromPb(pbMsg *pb.ViewChange) (*viewChange, error) {
ui := new(usig.UI)
if err := ui.UnmarshalBinary(pbMsg.GetUi()); err != nil {
return nil, xerrors.Errorf("cannot unmarshal UI: %w", err)
}
log, err := messageLogFromPb(pbMsg.GetLog())
if err != nil {
return nil, xerrors.Errorf("cannot unmarshal message log: %w", err)
}
vcCert, err := viewChangeCertFromPb(pbMsg.GetVcCert())
if err != nil {
return nil, xerrors.Errorf("cannot unmarshal view-change certificate: %w", err)
}
return &viewChange{pbMsg: pbMsg, log: log, vcCert: vcCert, ui: ui}, nil
}

func (m *viewChange) MarshalBinary() ([]byte, error) {
return marshalMessage(m.pbMsg)
}

func (m *viewChange) ReplicaID() uint32 {
return m.pbMsg.GetReplicaId()
}

func (m *viewChange) NewView() uint64 {
return m.pbMsg.GetNewView()
}

func (m *viewChange) MessageLog() messages.MessageLog {
return m.log
}

func (m *viewChange) ViewChangeCert() messages.ViewChangeCert {
return m.vcCert
}

func (m *viewChange) UI() *usig.UI {
return m.ui
}

func (m *viewChange) SetUI(ui *usig.UI) {
m.ui = ui
m.pbMsg.Ui = usig.MustMarshalUI(ui)
}

func pbMessageLogFromAPI(log messages.MessageLog) []*pb.Message {
pbLog := make([]*pb.Message, 0, len(log))
for _, m := range log {
pbLog = append(pbLog, pb.WrapMessage(pbMessageFromAPI(m)))
}
return pbLog
}

func messageLogFromPb(pbLog []*pb.Message) (messages.MessageLog, error) {
log := make(messages.MessageLog, 0, len(pbLog))
for _, pbMsg := range pbLog {
typedMsg, err := typedMessageFromPb(pbMsg)
if err != nil {
return nil, err
}
m, ok := typedMsg.(messages.CertifiedMessage)
if !ok {
return nil, xerrors.New("unexpected message type")
}
log = append(log, m)
}
return log, nil
}

func pbViewChangeCertFromAPI(cert messages.ViewChangeCert) []*pb.ReqViewChange {
pbCert := make([]*pb.ReqViewChange, 0, len(cert))
for _, m := range cert {
pbCert = append(pbCert, pbReqViewChangeFromAPI(m))
}
return pbCert
}

func viewChangeCertFromPb(pbCert []*pb.ReqViewChange) (messages.ViewChangeCert, error) {
cert := make(messages.ViewChangeCert, 0, len(pbCert))
for _, m := range pbCert {
rvc, err := newReqViewChangeFromPb(m)
if err != nil {
return nil, err
}
cert = append(cert, rvc)
}
return cert, nil
}

func (viewChange) ImplementsReplicaMessage() {}
func (viewChange) ImplementsPeerMessage() {}
func (viewChange) ImplementsViewChange() {}
160 changes: 160 additions & 0 deletions messages/protobuf/view-change_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) 2021 NEC Laboratories Europe GmbH.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package protobuf

import (
"fmt"
"math/rand"
"testing"

"github.com/stretchr/testify/require"

"github.com/hyperledger-labs/minbft/messages"
)

func TestViewChange(t *testing.T) {
const f = 1
const n = 3
const maxView = 3
const maxRequests = 2

impl := NewImpl()

preps := make([]messages.Prepare, maxRequests)
for i := range preps {
c := uint64(i) + 1
preps[i] = newTestPrep(impl, 0, 0, randReq(impl), c)
}

var logs [][][]messages.MessageLog // r -> v -> i -> log
for r := uint32(0); r < n; r++ {
logs = append(logs, [][]messages.MessageLog{})
for v := uint64(0); v <= maxView; v++ {
logs[r] = append(logs[r], []messages.MessageLog{})
if v == 0 {
var log messages.MessageLog
for i, prep := range preps {
if r == uint32(v%n) {
log = append(log, prep)
} else {
log = append(log, newTestComm(impl, r, prep, uint64(i)+1))
}
}
for l := 0; l < len(log); l++ {
logs[r][v] = append(logs[r][v], log[:l])
}
} else {
// TODO: Test against logs with NewView messages
for _, log := range logs[r][v-1] {
lastCV := lastLogCV(log)
vc := newTestVC(impl, r, v, log, randVCCert(impl, f, n, v), lastCV+1)
logs[r][v] = append(logs[r][v], messages.MessageLog{vc})
}
}
}
}

for i, logs := range logs {
r := uint32(i)
for i, logs := range logs {
v := uint64(i)
for i, log := range logs {
t.Run(fmt.Sprintf("Replica=%d/View=%d/Log=%d", r, v, i), func(t *testing.T) {
testViewChange(t, impl, r, v, log, randVCCert(impl, f, n, v))
})
}
}
}
}

func testViewChange(t *testing.T, impl messages.MessageImpl, r uint32, v uint64, log messages.MessageLog, vcCert messages.ViewChangeCert) {
lastCV := lastLogCV(log)

t.Run("Fields", func(t *testing.T) {
vc := impl.NewViewChange(r, v, log, vcCert)
require.Equal(t, r, vc.ReplicaID())
require.Equal(t, v, vc.NewView())
requireMsgLogEqual(t, log, vc.MessageLog())
requireVCCertEqual(t, vcCert, vc.ViewChangeCert())
})
t.Run("SetUI", func(t *testing.T) {
vc := impl.NewViewChange(r, v, log, vcCert)
ui := newTestUI(lastCV+1, messages.AuthenBytes(vc))
vc.SetUI(ui)
require.Equal(t, ui, vc.UI())
})
t.Run("Marshaling", func(t *testing.T) {
vc := newTestVC(impl, r, v, log, vcCert, lastCV+1)
requireVCEqual(t, vc, remarshalMsg(impl, vc).(messages.ViewChange))
})
}

func newTestVC(impl messages.MessageImpl, r uint32, v uint64, log messages.MessageLog, vcCert messages.ViewChangeCert, cv uint64) messages.ViewChange {
vc := impl.NewViewChange(r, v, log, vcCert)
vc.SetUI(newTestUI(cv, messages.AuthenBytes(vc)))
return vc
}

func lastLogCV(log messages.MessageLog) uint64 {
var lastCV uint64
if len(log) != 0 {
lastCV = log[len(log)-1].UI().Counter
}
return lastCV
}

func randVCCert(impl messages.MessageImpl, f, n uint32, v uint64) messages.ViewChangeCert {
var cert messages.ViewChangeCert
for _, r := range rand.Perm(int(n))[:f+1] {
cert = append(cert, newTestReqViewChange(impl, uint32(r), v))
}
return cert
}

func requireVCEqual(t *testing.T, vc1, vc2 messages.ViewChange) {
require.Equal(t, vc1.ReplicaID(), vc2.ReplicaID())
require.Equal(t, vc1.NewView(), vc2.NewView())
requireMsgLogEqual(t, vc1.MessageLog(), vc2.MessageLog())
requireVCCertEqual(t, vc2.ViewChangeCert(), vc2.ViewChangeCert())
require.Equal(t, vc1.UI(), vc2.UI())
}

func requireMsgLogEqual(t *testing.T, log1, log2 messages.MessageLog) {
require.Equal(t, len(log1), len(log2))
for i, m1 := range log1 {
m2 := log2[i]
switch m1 := m1.(type) {
case messages.Prepare:
m2, ok := m2.(messages.Prepare)
require.True(t, ok)
requirePrepEqual(t, m1, m2)
case messages.Commit:
m2, ok := m2.(messages.Commit)
require.True(t, ok)
requireCommEqual(t, m1, m2)
case messages.ViewChange:
m2, ok := m2.(messages.ViewChange)
require.True(t, ok)
requireVCEqual(t, m1, m2)
}
}
}

func requireVCCertEqual(t *testing.T, c1, c2 messages.ViewChangeCert) {
require.Equal(t, len(c1), len(c2))
for i, rvc1 := range c1 {
requireReqViewChangeEqual(t, rvc1, c2[i])
}
}

0 comments on commit b5ffd9a

Please sign in to comment.