Skip to content

Commit

Permalink
Merge branch 'main' into sc-ssv-runner-consensus-sanity-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
GalRogozinski authored May 28, 2023
2 parents 75343f7 + af449bf commit 5138c0f
Show file tree
Hide file tree
Showing 53 changed files with 532 additions and 166 deletions.
2 changes: 1 addition & 1 deletion qbft/messages_encoding.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions qbft/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (i *Instance) uponPrepare(
return nil
}

// getRoundChangeJustification returns the round change justification for the current round.
// The justification is a quorum of signed prepare messages that agree on state.LastPreparedValue
func getRoundChangeJustification(state *State, config IConfig, prepareMsgContainer *MsgContainer) ([]*SignedMessage, error) {
if state.LastPreparedValue == nil {
return nil, nil
Expand All @@ -70,6 +72,10 @@ func getRoundChangeJustification(state *State, config IConfig, prepareMsgContain
ret = append(ret, msg)
}
}

if !HasQuorum(state.Share, ret) {
return nil, nil
}
return ret, nil
}

Expand Down
4 changes: 4 additions & 0 deletions qbft/spectest/all_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ var AllTests = []tests.TestF{
messages.CreateCommit,
messages.CreateRoundChange,
messages.CreateRoundChangePreviouslyPrepared,
messages.CreateRoundChangeNoJustificationQuorum,
messages.RoundChangeDataEncoding,
messages.SignedMessageSigner0,
messages.MarshalJustificationsWithoutFullData,
Expand Down Expand Up @@ -163,6 +164,8 @@ var AllTests = []tests.TestF{
prepare.WrongHeight,
prepare.WrongSignature,
prepare.UnknownSigner,
prepare.PrepareQuorumTriggeredTwice,
prepare.PrepareQuorumTriggeredTwiceLateCommit,

commit.CurrentRound,
commit.FutureRound,
Expand All @@ -181,6 +184,7 @@ var AllTests = []tests.TestF{
commit.UnknownSigner,
commit.InvalidValCheck,
commit.NoPrepareQuorum,
commit.NoCommitQuorum,

roundchange.HappyFlow,
roundchange.WrongHeight,
Expand Down
2 changes: 1 addition & 1 deletion qbft/spectest/generate/tests.json

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions qbft/spectest/tests/commit/no_commit_quorum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package commit

import (
"github.com/bloxapp/ssv-spec/qbft"
"github.com/bloxapp/ssv-spec/qbft/spectest/tests"
"github.com/bloxapp/ssv-spec/types"
"github.com/bloxapp/ssv-spec/types/testingutils"
"github.com/bloxapp/ssv-spec/types/testingutils/comparable"
)

// NoCommitQuorum tests the state of the QBFT instance when received commit messages don't create a quorum
func NoCommitQuorum() tests.SpecTest {
pre := testingutils.BaseInstance()
ks := testingutils.Testing4SharesSet()
sc := NoCommitQuorumStateComparison()

msgs := []*qbft.SignedMessage{
testingutils.TestingProposalMessage(ks.Shares[1], 1),

testingutils.TestingPrepareMessage(ks.Shares[1], 1),
testingutils.TestingPrepareMessage(ks.Shares[2], 2),
testingutils.TestingPrepareMessage(ks.Shares[3], 3),

testingutils.TestingCommitMessage(ks.Shares[1], 1),
testingutils.TestingCommitMessage(ks.Shares[2], 2),
}
return &tests.MsgProcessingSpecTest{
Name: "no commit quorum",
Pre: pre,
PostRoot: sc.Root(),
PostState: sc.ExpectedState,
InputMessages: msgs,
OutputMessages: []*qbft.SignedMessage{
testingutils.TestingPrepareMessage(ks.Shares[1], 1),
testingutils.TestingCommitMessage(ks.Shares[1], 1),
},
}
}

func NoCommitQuorumStateComparison() *comparable.StateComparison {
ks := testingutils.Testing4SharesSet()

state := testingutils.BaseInstance().State
state.ProposalAcceptedForCurrentRound = testingutils.TestingProposalMessage(ks.Shares[1], types.OperatorID(1))

state.LastPreparedRound = 1
state.LastPreparedValue = testingutils.TestingQBFTFullData
state.Decided = false

state.ProposeContainer = &qbft.MsgContainer{Msgs: map[qbft.Round][]*qbft.SignedMessage{
qbft.FirstRound: {
testingutils.TestingProposalMessage(ks.Shares[1], types.OperatorID(1)),
},
}}

state.PrepareContainer = &qbft.MsgContainer{Msgs: map[qbft.Round][]*qbft.SignedMessage{
qbft.FirstRound: {
testingutils.TestingPrepareMessage(ks.Shares[1], types.OperatorID(1)),
testingutils.TestingPrepareMessage(ks.Shares[2], types.OperatorID(2)),
testingutils.TestingPrepareMessage(ks.Shares[3], types.OperatorID(3)),
},
}}

state.CommitContainer = &qbft.MsgContainer{Msgs: map[qbft.Round][]*qbft.SignedMessage{
qbft.FirstRound: {
testingutils.TestingCommitMessage(ks.Shares[1], types.OperatorID(1)),
testingutils.TestingCommitMessage(ks.Shares[2], types.OperatorID(2)),
},
}}

return &comparable.StateComparison{ExpectedState: state}
}
45 changes: 29 additions & 16 deletions qbft/spectest/tests/create_message_spectest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package tests
import (
"encoding/hex"
"github.com/bloxapp/ssv-spec/qbft"
"github.com/bloxapp/ssv-spec/types"
"github.com/bloxapp/ssv-spec/types/testingutils"
typescomparable "github.com/bloxapp/ssv-spec/types/testingutils/comparable"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"testing"
Expand All @@ -17,42 +19,52 @@ const (
)

type CreateMsgSpecTest struct {
Name string
Value [32]byte
Name string
// ISSUE 217: rename to root
Value [32]byte
// ISSUE 217: rename to value
StateValue []byte
Round qbft.Round
RoundChangeJustifications, PrepareJustifications []*qbft.SignedMessage
CreateType string
ExpectedRoot string
ExpectedState types.Root `json:"-"` // Field is ignored by encoding/json"
ExpectedError string
}

func (test *CreateMsgSpecTest) Run(t *testing.T) {
var msg *qbft.SignedMessage
var lastErr error
var err error
switch test.CreateType {
case CreateProposal:
msg, lastErr = test.createProposal()
msg, err = test.createProposal()
case CreatePrepare:
msg, lastErr = test.createPrepare()
msg, err = test.createPrepare()
case CreateCommit:
msg, lastErr = test.createCommit()
msg, err = test.createCommit()
case CreateRoundChange:
msg, lastErr = test.createRoundChange()
msg, err = test.createRoundChange()
default:
t.Fail()
}

r, err := msg.GetRoot()
if err != nil {
lastErr = err
if err != nil && len(test.ExpectedError) != 0 {
require.EqualError(t, err, test.ExpectedError)
return
}
require.NoError(t, err)

r, err2 := msg.GetRoot()
if len(test.ExpectedError) != 0 {
require.EqualError(t, lastErr, test.ExpectedError)
} else {
require.NoError(t, lastErr)
require.EqualError(t, err2, test.ExpectedError)
return
}
require.NoError(t, err2)

if test.ExpectedRoot != hex.EncodeToString(r[:]) {
diff := typescomparable.PrintDiff(test.ExpectedState, msg)
require.Fail(t, "post state not equal", diff)
}
require.EqualValues(t, test.ExpectedRoot, hex.EncodeToString(r[:]))
}

Expand Down Expand Up @@ -92,14 +104,15 @@ func (test *CreateMsgSpecTest) createProposal() (*qbft.SignedMessage, error) {
func (test *CreateMsgSpecTest) createRoundChange() (*qbft.SignedMessage, error) {
ks := testingutils.Testing4SharesSet()
state := &qbft.State{
Share: testingutils.TestingShare(ks),
ID: []byte{1, 2, 3, 4},
Share: testingutils.TestingShare(ks),
ID: []byte{1, 2, 3, 4},
PrepareContainer: qbft.NewMsgContainer(),
}
config := testingutils.TestingConfig(ks)

if len(test.PrepareJustifications) > 0 {
state.LastPreparedRound = test.PrepareJustifications[0].Message.Round
state.LastPreparedValue = test.Value[:]
state.LastPreparedValue = test.StateValue

for _, msg := range test.PrepareJustifications {
_, err := state.PrepareContainer.AddFirstMsgForSignerAndRound(msg)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package messages

import (
"github.com/bloxapp/ssv-spec/qbft"
"github.com/bloxapp/ssv-spec/qbft/spectest/tests"
"github.com/bloxapp/ssv-spec/types"
"github.com/bloxapp/ssv-spec/types/testingutils"
"github.com/bloxapp/ssv-spec/types/testingutils/comparable"
"github.com/pkg/errors"
)

// CreateRoundChangeNoJustificationQuorum tests creating a round change msg that was previouly prepared
// but failed to extract a justification quorum (shouldn't happen).
// The result should be an unjustified round change.
func CreateRoundChangeNoJustificationQuorum() tests.SpecTest {
ks := testingutils.Testing4SharesSet()
sc := CreateRoundChangeNoJustificationQuorumSC()
return &tests.CreateMsgSpecTest{
CreateType: tests.CreateRoundChange,
Name: "create round change no justification quorum",
StateValue: testingutils.TestingQBFTFullData,
ExpectedState: sc.ExpectedState,
PrepareJustifications: []*qbft.SignedMessage{
testingutils.TestingPrepareMessage(ks.Shares[1], types.OperatorID(1)),
testingutils.TestingPrepareMessage(ks.Shares[2], types.OperatorID(2)),
},
ExpectedRoot: sc.Root(),
}
}

func CreateRoundChangeNoJustificationQuorumSC() *comparable.StateComparison {
expectedMsg := qbft.Message{
MsgType: qbft.RoundChangeMsgType,
Height: 0,
Round: 1,
Identifier: []byte{1, 2, 3, 4},
Root: testingutils.TestingQBFTRootData,
DataRound: 1,
RoundChangeJustification: [][]byte{},
PrepareJustification: nil,
}

ks := testingutils.Testing4SharesSet()
config := testingutils.TestingConfig(ks)
sig, err := config.GetSigner().SignRoot(&expectedMsg, types.QBFTSignatureType, config.GetSigningPubKey())
if err != nil {
panic(errors.Wrap(err, "unable to sign root for create_round_change_no_justification_quorum"))
}
signedMsg := &qbft.SignedMessage{
Signature: sig,
Signers: []types.OperatorID{1},
Message: expectedMsg,

FullData: testingutils.TestingQBFTFullData,
}
return &comparable.StateComparison{ExpectedState: signedMsg}
}
71 changes: 71 additions & 0 deletions qbft/spectest/tests/prepare/prepare_quorum_triggered_twice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package prepare

import (
"github.com/bloxapp/ssv-spec/qbft"
"github.com/bloxapp/ssv-spec/qbft/spectest/tests"
"github.com/bloxapp/ssv-spec/types"
"github.com/bloxapp/ssv-spec/types/testingutils"
"github.com/bloxapp/ssv-spec/types/testingutils/comparable"
)

// PrepareQuorumTriggeredTwice tests triggering prepare quorum twice by sending > 2f+1 prepare messages
func PrepareQuorumTriggeredTwice() tests.SpecTest {
ks := testingutils.Testing4SharesSet()
pre := testingutils.BaseInstance()
sc := prepareQuorumTriggeredTwiceStateComparison()
msgs := []*qbft.SignedMessage{
testingutils.TestingProposalMessage(ks.Shares[1], 1),

testingutils.TestingPrepareMessage(ks.Shares[1], 1),
testingutils.TestingPrepareMessage(ks.Shares[2], 2),
testingutils.TestingPrepareMessage(ks.Shares[3], 3),

testingutils.TestingCommitMessage(ks.Shares[1], 1),

testingutils.TestingPrepareMessage(ks.Shares[4], 4),
}
return &tests.MsgProcessingSpecTest{
Name: "prepared quorum committed twice",
Pre: pre,
PostRoot: sc.Root(),
PostState: sc.ExpectedState,
InputMessages: msgs,
OutputMessages: []*qbft.SignedMessage{
testingutils.TestingPrepareMessage(ks.Shares[1], 1),
testingutils.TestingCommitMessage(ks.Shares[1], 1),
},
}
}

func prepareQuorumTriggeredTwiceStateComparison() *comparable.StateComparison {
ks := testingutils.Testing4SharesSet()

state := testingutils.BaseInstance().State
state.ProposalAcceptedForCurrentRound = testingutils.TestingProposalMessage(ks.Shares[1], types.OperatorID(1))

state.LastPreparedRound = 1
state.LastPreparedValue = testingutils.TestingQBFTFullData

state.ProposeContainer = &qbft.MsgContainer{Msgs: map[qbft.Round][]*qbft.SignedMessage{
qbft.FirstRound: {
testingutils.TestingProposalMessage(ks.Shares[1], types.OperatorID(1)),
},
}}

state.PrepareContainer = &qbft.MsgContainer{Msgs: map[qbft.Round][]*qbft.SignedMessage{
qbft.FirstRound: {
testingutils.TestingPrepareMessage(ks.Shares[1], types.OperatorID(1)),
testingutils.TestingPrepareMessage(ks.Shares[2], types.OperatorID(2)),
testingutils.TestingPrepareMessage(ks.Shares[3], types.OperatorID(3)),
testingutils.TestingPrepareMessage(ks.Shares[4], types.OperatorID(4)),
},
}}

state.CommitContainer = &qbft.MsgContainer{Msgs: map[qbft.Round][]*qbft.SignedMessage{
qbft.FirstRound: {
testingutils.TestingCommitMessage(ks.Shares[1], types.OperatorID(1)),
},
}}

return &comparable.StateComparison{ExpectedState: state}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package prepare

import (
"github.com/bloxapp/ssv-spec/qbft"
"github.com/bloxapp/ssv-spec/qbft/spectest/tests"
"github.com/bloxapp/ssv-spec/types/testingutils"
)

// PrepareQuorumTriggeredTwiceLateCommit tests triggering prepare quorum twice by sending > 2f+1 prepare messages.
// The commit message is processed after the second prepare quorum is triggered.
func PrepareQuorumTriggeredTwiceLateCommit() tests.SpecTest {
ks := testingutils.Testing4SharesSet()
pre := testingutils.BaseInstance()
sc := prepareQuorumTriggeredTwiceStateComparison()

msgs := []*qbft.SignedMessage{
testingutils.TestingProposalMessage(ks.Shares[1], 1),

testingutils.TestingPrepareMessage(ks.Shares[1], 1),
testingutils.TestingPrepareMessage(ks.Shares[2], 2),
testingutils.TestingPrepareMessage(ks.Shares[3], 3),

testingutils.TestingPrepareMessage(ks.Shares[4], 4),
testingutils.TestingCommitMessage(ks.Shares[1], 1),
}
return &tests.MsgProcessingSpecTest{
Name: "prepared quorum committed twice late commit",
Pre: pre,
PostRoot: sc.Root(),
PostState: sc.ExpectedState,
InputMessages: msgs,
OutputMessages: []*qbft.SignedMessage{
testingutils.TestingPrepareMessage(ks.Shares[1], 1),
testingutils.TestingCommitMessage(ks.Shares[1], 1),
// ISSUE 214: we should have only commit broadcasted
testingutils.TestingCommitMessage(ks.Shares[1], 1),
},
}
}
Loading

0 comments on commit 5138c0f

Please sign in to comment.