quorum/consensus/istanbul/core/backlog_test.go

365 lines
9.0 KiB
Go

// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"math/big"
"reflect"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/istanbul"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
)
func TestCheckMessage(t *testing.T) {
c := &core{
state: StateAcceptRequest,
current: newRoundState(&istanbul.View{
Sequence: big.NewInt(1),
Round: big.NewInt(0),
}, newTestValidatorSet(4), common.Hash{}, nil, nil, nil),
}
// invalid view format
err := c.checkMessage(msgPreprepare, nil)
if err != errInvalidMessage {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidMessage)
}
testStates := []State{StateAcceptRequest, StatePreprepared, StatePrepared, StateCommitted}
testCode := []uint64{msgPreprepare, msgPrepare, msgCommit, msgRoundChange}
// future sequence
v := &istanbul.View{
Sequence: big.NewInt(2),
Round: big.NewInt(0),
}
for i := 0; i < len(testStates); i++ {
c.state = testStates[i]
for j := 0; j < len(testCode); j++ {
err := c.checkMessage(testCode[j], v)
if err != errFutureMessage {
t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage)
}
}
}
// future round
v = &istanbul.View{
Sequence: big.NewInt(1),
Round: big.NewInt(1),
}
for i := 0; i < len(testStates); i++ {
c.state = testStates[i]
for j := 0; j < len(testCode); j++ {
err := c.checkMessage(testCode[j], v)
if testCode[j] == msgRoundChange {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else if err != errFutureMessage {
t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage)
}
}
}
// current view but waiting for round change
v = &istanbul.View{
Sequence: big.NewInt(1),
Round: big.NewInt(0),
}
c.waitingForRoundChange = true
for i := 0; i < len(testStates); i++ {
c.state = testStates[i]
for j := 0; j < len(testCode); j++ {
err := c.checkMessage(testCode[j], v)
if testCode[j] == msgRoundChange {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else if err != errFutureMessage {
t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage)
}
}
}
c.waitingForRoundChange = false
v = c.currentView()
// current view, state = StateAcceptRequest
c.state = StateAcceptRequest
for i := 0; i < len(testCode); i++ {
err = c.checkMessage(testCode[i], v)
if testCode[i] == msgRoundChange {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else if testCode[i] == msgPreprepare {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else {
if err != errFutureMessage {
t.Errorf("error mismatch: have %v, want %v", err, errFutureMessage)
}
}
}
// current view, state = StatePreprepared
c.state = StatePreprepared
for i := 0; i < len(testCode); i++ {
err = c.checkMessage(testCode[i], v)
if testCode[i] == msgRoundChange {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
}
// current view, state = StatePrepared
c.state = StatePrepared
for i := 0; i < len(testCode); i++ {
err = c.checkMessage(testCode[i], v)
if testCode[i] == msgRoundChange {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
}
// current view, state = StateCommitted
c.state = StateCommitted
for i := 0; i < len(testCode); i++ {
err = c.checkMessage(testCode[i], v)
if testCode[i] == msgRoundChange {
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
} else if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
}
}
func TestStoreBacklog(t *testing.T) {
c := &core{
logger: log.New("backend", "test", "id", 0),
valSet: newTestValidatorSet(1),
backlogs: make(map[common.Address]*prque.Prque),
backlogsMu: new(sync.Mutex),
}
v := &istanbul.View{
Round: big.NewInt(10),
Sequence: big.NewInt(10),
}
p := c.valSet.GetByIndex(0)
// push preprepare msg
preprepare := &istanbul.Preprepare{
View: v,
Proposal: makeBlock(1),
}
prepreparePayload, _ := Encode(preprepare)
m := &message{
Code: msgPreprepare,
Msg: prepreparePayload,
}
c.storeBacklog(m, p)
msg := c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
// push prepare msg
subject := &istanbul.Subject{
View: v,
Digest: common.StringToHash("1234567890"),
}
subjectPayload, _ := Encode(subject)
m = &message{
Code: msgPrepare,
Msg: subjectPayload,
}
c.storeBacklog(m, p)
msg = c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
// push commit msg
m = &message{
Code: msgCommit,
Msg: subjectPayload,
}
c.storeBacklog(m, p)
msg = c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
// push roundChange msg
m = &message{
Code: msgRoundChange,
Msg: subjectPayload,
}
c.storeBacklog(m, p)
msg = c.backlogs[p.Address()].PopItem()
if !reflect.DeepEqual(msg, m) {
t.Errorf("message mismatch: have %v, want %v", msg, m)
}
}
func TestProcessFutureBacklog(t *testing.T) {
backend := &testSystemBackend{
events: new(event.TypeMux),
}
c := &core{
logger: log.New("backend", "test", "id", 0),
valSet: newTestValidatorSet(1),
backlogs: make(map[common.Address]*prque.Prque),
backlogsMu: new(sync.Mutex),
backend: backend,
current: newRoundState(&istanbul.View{
Sequence: big.NewInt(1),
Round: big.NewInt(0),
}, newTestValidatorSet(4), common.Hash{}, nil, nil, nil),
state: StateAcceptRequest,
}
c.subscribeEvents()
defer c.unsubscribeEvents()
v := &istanbul.View{
Round: big.NewInt(10),
Sequence: big.NewInt(10),
}
p := c.valSet.GetByIndex(0)
// push a future msg
subject := &istanbul.Subject{
View: v,
Digest: common.StringToHash("1234567890"),
}
subjectPayload, _ := Encode(subject)
m := &message{
Code: msgCommit,
Msg: subjectPayload,
}
c.storeBacklog(m, p)
c.processBacklog()
const timeoutDura = 2 * time.Second
timeout := time.NewTimer(timeoutDura)
select {
case e, ok := <-c.events.Chan():
if !ok {
return
}
t.Errorf("unexpected events comes: %v", e)
case <-timeout.C:
// success
}
}
func TestProcessBacklog(t *testing.T) {
v := &istanbul.View{
Round: big.NewInt(0),
Sequence: big.NewInt(1),
}
preprepare := &istanbul.Preprepare{
View: v,
Proposal: makeBlock(1),
}
prepreparePayload, _ := Encode(preprepare)
subject := &istanbul.Subject{
View: v,
Digest: common.StringToHash("1234567890"),
}
subjectPayload, _ := Encode(subject)
msgs := []*message{
{
Code: msgPreprepare,
Msg: prepreparePayload,
},
{
Code: msgPrepare,
Msg: subjectPayload,
},
{
Code: msgCommit,
Msg: subjectPayload,
},
{
Code: msgRoundChange,
Msg: subjectPayload,
},
}
for i := 0; i < len(msgs); i++ {
testProcessBacklog(t, msgs[i])
}
}
func testProcessBacklog(t *testing.T, msg *message) {
vset := newTestValidatorSet(1)
backend := &testSystemBackend{
events: new(event.TypeMux),
peers: vset,
}
c := &core{
logger: log.New("backend", "test", "id", 0),
backlogs: make(map[common.Address]*prque.Prque),
backlogsMu: new(sync.Mutex),
valSet: vset,
backend: backend,
state: State(msg.Code),
current: newRoundState(&istanbul.View{
Sequence: big.NewInt(1),
Round: big.NewInt(0),
}, newTestValidatorSet(4), common.Hash{}, nil, nil, nil),
}
c.subscribeEvents()
defer c.unsubscribeEvents()
c.storeBacklog(msg, vset.GetByIndex(0))
c.processBacklog()
const timeoutDura = 2 * time.Second
timeout := time.NewTimer(timeoutDura)
select {
case ev := <-c.events.Chan():
e, ok := ev.Data.(backlogEvent)
if !ok {
t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data))
}
if e.msg.Code != msg.Code {
t.Errorf("message code mismatch: have %v, want %v", e.msg.Code, msg.Code)
}
// success
case <-timeout.C:
t.Error("unexpected timeout occurs")
}
}