[coverage] [consensus] Improved coverage for the consensus package

pull/3205/head
Sebastian Johnsson 4 years ago
parent e5199939f5
commit 41c0b3d06b
  1. 125
      consensus/consensus_test.go
  2. 45
      consensus/enums_test.go
  3. 60
      consensus/quorum/quorom_test.go
  4. 139
      consensus/view_change_test.go
  5. 19
      p2p/tests/address_test.go
  6. 21
      p2p/tests/host_test.go
  7. 37
      test/helpers/p2p.go

@ -1,37 +1,126 @@
package consensus
import (
"sync"
"testing"
"time"
"github.com/harmony-one/abool"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/multibls"
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/slash"
"github.com/harmony-one/harmony/test/helpers"
"github.com/stretchr/testify/assert"
)
func TestNew(test *testing.T) {
leader := p2p.Peer{IP: "127.0.0.1", Port: "9902"}
priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902")
host, err := p2p.NewHost(&leader, priKey)
if err != nil {
test.Fatalf("newhost failure: %v", err)
func TestConsensusInitialization(t *testing.T) {
host, multiBLSPrivateKey, consensus, decider, err := GenerateConsensusForTesting()
assert.NoError(t, err)
peer := host.GetSelfPeer()
messageSender := &MessageSender{host: host, retryTimes: int(phaseDuration.Seconds()) / RetryIntervalInSec}
fbtLog := NewFBFTLog()
state := State{mode: Normal}
timeouts := createTimeout()
expectedTimeouts := make(map[TimeoutType]time.Duration)
expectedTimeouts[timeoutConsensus] = phaseDuration
expectedTimeouts[timeoutViewChange] = viewChangeDuration
expectedTimeouts[timeoutBootstrap] = bootstrapDuration
assert.Equal(t, decider, consensus.Decider)
assert.Equal(t, host, consensus.host)
assert.Equal(t, messageSender, consensus.msgSender)
assert.IsType(t, make(chan struct{}), consensus.BlockNumLowChan)
// FBFTLog
assert.Equal(t, fbtLog, consensus.FBFTLog)
assert.Equal(t, maxLogSize, consensus.FBFTLog.maxLogSize)
assert.Equal(t, FBFTAnnounce, consensus.phase)
// State / consensus.current
assert.Equal(t, state.mode, consensus.current.mode)
assert.Equal(t, state.viewID, consensus.current.viewID)
// FBFT timeout
assert.IsType(t, make(map[TimeoutType]*utils.Timeout), consensus.consensusTimeout)
for timeoutType, timeout := range expectedTimeouts {
duration := consensus.consensusTimeout[timeoutType].Duration()
assert.Equal(t, timeouts[timeoutType].Duration().Nanoseconds(), duration.Nanoseconds())
assert.Equal(t, timeout.Nanoseconds(), duration.Nanoseconds())
}
decider := quorum.NewDecider(
quorum.SuperMajorityVote, shard.BeaconChainShardID,
)
consensus, err := New(
host, shard.BeaconChainShardID, leader, multibls.GetPrivateKey(bls.RandPrivateKey()), decider,
)
// Validators sync.Map
assert.IsType(t, sync.Map{}, consensus.validators)
assert.NotEmpty(t, consensus.validators)
validatorLength := 0
consensus.validators.Range(func(_, _ interface{}) bool {
validatorLength++
return true
})
assert.Equal(t, 1, validatorLength)
retrievedPeer, loadedOk := consensus.validators.Load(peer.ConsensusPubKey.SerializeToHexStr())
assert.Equal(t, true, loadedOk)
assert.Equal(t, retrievedPeer, peer)
// MultiBLS
assert.Equal(t, multiBLSPrivateKey, consensus.priKey)
assert.Equal(t, multiBLSPrivateKey.GetPublicKey(), consensus.PubKey)
// Misc
assert.Equal(t, uint64(0), consensus.viewID)
assert.Equal(t, uint32(shard.BeaconChainShardID), consensus.ShardID)
assert.IsType(t, make(chan []byte), consensus.MsgChan)
assert.NotNil(t, consensus.MsgChan)
assert.IsType(t, make(chan struct{}), consensus.syncReadyChan)
assert.NotNil(t, consensus.syncReadyChan)
assert.IsType(t, make(chan struct{}), consensus.syncNotReadyChan)
assert.NotNil(t, consensus.syncNotReadyChan)
assert.IsType(t, make(chan slash.Record), consensus.SlashChan)
assert.NotNil(t, consensus.SlashChan)
assert.IsType(t, make(chan uint64), consensus.commitFinishChan)
assert.NotNil(t, consensus.commitFinishChan)
assert.IsType(t, make(chan struct{}), consensus.ReadySignal)
assert.NotNil(t, consensus.ReadySignal)
assert.IsType(t, make(chan [vdfAndSeedSize]byte), consensus.RndChannel)
assert.NotNil(t, consensus.RndChannel)
assert.IsType(t, abool.NewBool(false), consensus.IgnoreViewIDCheck)
assert.NotNil(t, consensus.IgnoreViewIDCheck)
}
// GenerateConsensusForTesting - helper method to generate a basic consensus
func GenerateConsensusForTesting() (p2p.Host, *multibls.PrivateKey, *Consensus, quorum.Decider, error) {
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
if err != nil {
test.Fatalf("Cannot craeate consensus: %v", err)
}
if consensus.viewID != 0 {
test.Errorf("Consensus Id is initialized to the wrong value: %d", consensus.viewID)
return nil, nil, nil, nil, err
}
if consensus.ReadySignal == nil {
test.Error("Consensus ReadySignal should be initialized")
peer := host.GetSelfPeer()
decider := quorum.NewDecider(quorum.SuperMajorityVote, shard.BeaconChainShardID)
multiBLSPrivateKey := multibls.GetPrivateKey(bls.RandPrivateKey())
consensus, err := New(host, shard.BeaconChainShardID, peer, multiBLSPrivateKey, decider)
if err != nil {
return nil, nil, nil, nil, err
}
return host, multiBLSPrivateKey, consensus, decider, nil
}

@ -0,0 +1,45 @@
package consensus
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestModeStrings(t *testing.T) {
modes := []Mode{
Normal,
ViewChanging,
Syncing,
Listening,
}
expectations := make(map[Mode]string)
expectations[Normal] = "Normal"
expectations[ViewChanging] = "ViewChanging"
expectations[Syncing] = "Syncing"
expectations[Listening] = "Listening"
for _, mode := range modes {
expected := expectations[mode]
assert.Equal(t, expected, mode.String())
}
}
func TestPhaseStrings(t *testing.T) {
phases := []FBFTPhase{
FBFTAnnounce,
FBFTPrepare,
FBFTCommit,
}
expectations := make(map[FBFTPhase]string)
expectations[FBFTAnnounce] = "Announce"
expectations[FBFTPrepare] = "Prepare"
expectations[FBFTCommit] = "Commit"
for _, phase := range phases {
expected := expectations[phase]
assert.Equal(t, expected, phase.String())
}
}

@ -0,0 +1,60 @@
package quorum
import (
"testing"
"github.com/harmony-one/bls/ffi/go/bls"
harmony_bls "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/shard"
"github.com/stretchr/testify/assert"
)
func TestPhaseStrings(t *testing.T) {
phases := []Phase{
Prepare,
Commit,
ViewChange,
}
expectations := make(map[Phase]string)
expectations[Prepare] = "Prepare"
expectations[Commit] = "Commit"
expectations[ViewChange] = "viewChange"
for _, phase := range phases {
expected := expectations[phase]
assert.Equal(t, expected, phase.String())
}
}
func TestPolicyStrings(t *testing.T) {
policies := []Policy{
SuperMajorityVote,
SuperMajorityStake,
}
expectations := make(map[Policy]string)
expectations[SuperMajorityVote] = "SuperMajorityVote"
expectations[SuperMajorityStake] = "SuperMajorityStake"
for _, policy := range policies {
expected := expectations[policy]
assert.Equal(t, expected, policy.String())
}
}
func TestAddingQuoromParticipants(t *testing.T) {
decider := NewDecider(SuperMajorityVote, shard.BeaconChainShardID)
assert.Equal(t, int64(0), decider.ParticipantsCount())
blsKeys := []*bls.PublicKey{}
keyCount := int64(5)
for i := int64(0); i < keyCount; i++ {
blsKey := harmony_bls.RandPrivateKey()
blsKeys = append(blsKeys, blsKey.GetPublicKey())
}
decider.UpdateParticipants(blsKeys)
assert.Equal(t, keyCount, decider.ParticipantsCount())
}

@ -0,0 +1,139 @@
package consensus
import (
"testing"
"github.com/harmony-one/bls/ffi/go/bls"
harmony_bls "github.com/harmony-one/harmony/crypto/bls"
"github.com/stretchr/testify/assert"
)
func TestBasicViewChanging(t *testing.T) {
_, _, consensus, _, err := GenerateConsensusForTesting()
assert.NoError(t, err)
state := State{mode: Normal}
assert.Equal(t, state.mode, consensus.current.mode)
assert.Equal(t, state.Mode(), consensus.current.Mode())
assert.Equal(t, state.viewID, consensus.current.viewID)
assert.Equal(t, state.ViewID(), consensus.current.ViewID())
assert.Equal(t, state.GetViewID(), consensus.current.GetViewID()) // Why are there two methods to retrieve the ViewID?
// Set new mode
consensus.current.SetMode(ViewChanging)
assert.Equal(t, ViewChanging, consensus.current.mode)
assert.Equal(t, ViewChanging, consensus.current.Mode())
// Set new view ID
newViewID := consensus.current.ViewID() + 1
consensus.current.SetViewID(newViewID)
assert.Equal(t, newViewID, consensus.current.viewID)
assert.Equal(t, newViewID, consensus.current.ViewID())
assert.Equal(t, newViewID, consensus.current.GetViewID())
}
func TestPhaseSwitching(t *testing.T) {
type phaseSwitch struct {
start FBFTPhase
end FBFTPhase
}
phases := []FBFTPhase{FBFTAnnounce, FBFTPrepare, FBFTCommit}
_, _, consensus, _, err := GenerateConsensusForTesting()
assert.NoError(t, err)
assert.Equal(t, FBFTAnnounce, consensus.phase) // It's a new consensus, we should be at the FBFTAnnounce phase
override := false
switches := []phaseSwitch{
{start: FBFTAnnounce, end: FBFTPrepare},
{start: FBFTPrepare, end: FBFTCommit},
{start: FBFTCommit, end: FBFTAnnounce},
}
for _, sw := range switches {
testPhaseGroupSwitching(t, consensus, phases, sw.start, sw.end, override)
}
override = true
for _, sw := range switches {
testPhaseGroupSwitching(t, consensus, phases, sw.start, sw.end, override)
}
switches = []phaseSwitch{
{start: FBFTAnnounce, end: FBFTCommit},
{start: FBFTPrepare, end: FBFTAnnounce},
{start: FBFTCommit, end: FBFTPrepare},
}
for _, sw := range switches {
testPhaseGroupSwitching(t, consensus, phases, sw.start, sw.end, override)
}
}
func testPhaseGroupSwitching(t *testing.T, consensus *Consensus, phases []FBFTPhase, startPhase FBFTPhase, desiredPhase FBFTPhase, override bool) {
phaseMapping := make(map[FBFTPhase]bool)
if override {
for range phases {
consensus.switchPhase(desiredPhase, override)
assert.Equal(t, desiredPhase, consensus.phase)
}
assert.Equal(t, desiredPhase, consensus.phase)
return
}
phaseMapping[FBFTAnnounce] = false
phaseMapping[FBFTPrepare] = false
phaseMapping[FBFTCommit] = false
phaseMapping[startPhase] = false
phaseMapping[desiredPhase] = true
assert.Equal(t, startPhase, consensus.phase)
for _, phase := range phases {
consensus.switchPhase(desiredPhase, override)
if override {
assert.Equal(t, desiredPhase, consensus.phase)
} else {
expected := phaseMapping[phase]
assert.Equal(t, expected, (phase == consensus.phase))
}
}
assert.Equal(t, desiredPhase, consensus.phase)
}
func TestGetNextLeaderKeyShouldFailForStandardGeneratedConsensus(t *testing.T) {
_, _, consensus, _, err := GenerateConsensusForTesting()
assert.NoError(t, err)
// The below results in: "panic: runtime error: integer divide by zero"
// This happens because there's no check for if there are any participants or not in https://github.com/harmony-one/harmony/blob/main/consensus/quorum/quorum.go#L188-L197
assert.Panics(t, func() { consensus.GetNextLeaderKey() })
}
func TestGetNextLeaderKeyShouldSucceed(t *testing.T) {
_, _, consensus, _, err := GenerateConsensusForTesting()
assert.NoError(t, err)
assert.Equal(t, int64(0), consensus.Decider.ParticipantsCount())
blsKeys := []*bls.PublicKey{}
keyCount := int64(5)
for i := int64(0); i < keyCount; i++ {
blsKey := harmony_bls.RandPrivateKey()
blsKeys = append(blsKeys, blsKey.GetPublicKey())
}
consensus.Decider.UpdateParticipants(blsKeys)
assert.Equal(t, keyCount, consensus.Decider.ParticipantsCount())
assert.Equal(t, consensus.GetNextLeaderKey(), blsKeys[0])
}

@ -5,29 +5,30 @@ import (
"testing"
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/test/helpers"
"github.com/stretchr/testify/assert"
)
func TestMultiAddressParsing(t *testing.T) {
t.Parallel()
multiAddresses, err := p2p.StringsToAddrs(bootnodes)
multiAddresses, err := p2p.StringsToAddrs(helpers.Bootnodes)
assert.NoError(t, err)
assert.Equal(t, len(bootnodes), len(multiAddresses))
assert.Equal(t, len(helpers.Bootnodes), len(multiAddresses))
for index, multiAddress := range multiAddresses {
assert.Equal(t, multiAddress.String(), bootnodes[index])
assert.Equal(t, multiAddress.String(), helpers.Bootnodes[index])
}
}
func TestAddressListConversionToString(t *testing.T) {
t.Parallel()
multiAddresses, err := p2p.StringsToAddrs(bootnodes)
multiAddresses, err := p2p.StringsToAddrs(helpers.Bootnodes)
assert.NoError(t, err)
assert.Equal(t, len(bootnodes), len(multiAddresses))
assert.Equal(t, len(helpers.Bootnodes), len(multiAddresses))
expected := strings.Join(bootnodes[:], ",")
expected := strings.Join(helpers.Bootnodes[:], ",")
var addressList p2p.AddrList = multiAddresses
assert.Equal(t, expected, addressList.String())
}
@ -35,11 +36,11 @@ func TestAddressListConversionToString(t *testing.T) {
func TestAddressListConversionFromString(t *testing.T) {
t.Parallel()
multiAddresses, err := p2p.StringsToAddrs(bootnodes)
multiAddresses, err := p2p.StringsToAddrs(helpers.Bootnodes)
assert.NoError(t, err)
assert.Equal(t, len(bootnodes), len(multiAddresses))
assert.Equal(t, len(helpers.Bootnodes), len(multiAddresses))
addressString := strings.Join(bootnodes[:], ",")
addressString := strings.Join(helpers.Bootnodes[:], ",")
var addressList p2p.AddrList = multiAddresses
addressList.Set(addressString)
assert.Equal(t, len(addressList), len(multiAddresses))

@ -3,14 +3,15 @@ package p2ptests
import (
"testing"
"github.com/harmony-one/harmony/test/helpers"
"github.com/stretchr/testify/assert"
)
func TestHostSetup(t *testing.T) {
t.Parallel()
hostData := hosts[0]
host, pubKey, err := createNode(hostData.IP, hostData.Port)
hostData := helpers.Hosts[0]
host, pubKey, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
peer := host.GetSelfPeer()
@ -26,13 +27,13 @@ func TestHostSetup(t *testing.T) {
func TestAddPeer(t *testing.T) {
t.Parallel()
hostData := hosts[0]
host, _, err := createNode(hostData.IP, hostData.Port)
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, host.GetID())
discoveredHostData := hosts[1]
discoveredHost, _, err := createNode(discoveredHostData.IP, discoveredHostData.Port)
discoveredHostData := helpers.Hosts[1]
discoveredHost, _, err := helpers.GenerateHost(discoveredHostData.IP, discoveredHostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, discoveredHost.GetID())
@ -65,13 +66,13 @@ func TestAddPeer(t *testing.T) {
func TestConnectionToInvalidPeer(t *testing.T) {
t.Parallel()
hostData := hosts[0]
host, _, err := createNode(hostData.IP, hostData.Port)
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, host.GetID())
discoveredHostData := hosts[1]
discoveredHost, _, err := createNode(discoveredHostData.IP, discoveredHostData.Port)
discoveredHostData := helpers.Hosts[1]
discoveredHost, _, err := helpers.GenerateHost(discoveredHostData.IP, discoveredHostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, discoveredHost.GetID())

@ -1,4 +1,4 @@
package p2ptests
package helpers
import (
"github.com/harmony-one/bls/ffi/go/bls"
@ -8,24 +8,28 @@ import (
"github.com/pkg/errors"
)
type host struct {
// Host - struct for representing a host (IP / Port)
type Host struct {
IP string
Port string
}
var (
hosts []host
topics []string
bootnodes []string
// Hosts - host combinations
Hosts []Host
// Topics - p2p topics
Topics []string
// Bootnodes - p2p bootnodes
Bootnodes []string
)
func init() {
hosts = []host{
{IP: "127.0.0.1", Port: "5000"},
{IP: "8.8.8.8", Port: "5000"},
Hosts = []Host{
{IP: "127.0.0.1", Port: "9000"},
{IP: "8.8.8.8", Port: "9001"},
}
topics = []string{
Topics = []string{
"hmy/testnet/0.0.1/client/beacon",
"hmy/testnet/0.0.1/node/beacon",
"hmy/testnet/0.0.1/node/shard/1",
@ -33,19 +37,20 @@ func init() {
"hmy/testnet/0.0.1/node/shard/3",
}
bootnodes = []string{
Bootnodes = []string{
"/ip4/54.86.126.90/tcp/9850/p2p/Qmdfjtk6hPoyrH1zVD9PEH4zfWLo38dP2mDvvKXfh3tnEv",
"/ip4/52.40.84.2/tcp/9850/p2p/QmbPVwrqWsTYXq1RxGWcxx9SWaTUCfoo1wA6wmdbduWe29",
}
}
func createNode(address string, port string) (p2p.Host, *bls.PublicKey, error) {
nodePrivateKey, _, err := generatePrivateKey()
// GenerateHost - test helper to generate a new host
func GenerateHost(address string, port string) (p2p.Host, *bls.PublicKey, error) {
nodePrivateKey, _, err := GeneratePrivateKey()
if err != nil {
return nil, nil, errors.New("failed to generate private key for node")
}
peer, err := generatePeer(address, port)
peer, err := GeneratePeer(address, port)
if err != nil {
return nil, nil, err
}
@ -58,7 +63,8 @@ func createNode(address string, port string) (p2p.Host, *bls.PublicKey, error) {
return host, peer.ConsensusPubKey, nil
}
func generatePeer(address string, port string) (p2p.Peer, error) {
// GeneratePeer - test helper to generate a new peer
func GeneratePeer(address string, port string) (p2p.Peer, error) {
peerPrivateKey := harmony_bls.RandPrivateKey()
peerPublicKey := peerPrivateKey.GetPublicKey()
if peerPrivateKey == nil || peerPublicKey == nil {
@ -70,7 +76,8 @@ func generatePeer(address string, port string) (p2p.Peer, error) {
return peer, nil
}
func generatePrivateKey() (privateKey libp2p_crypto.PrivKey, publicKey libp2p_crypto.PubKey, err error) {
// GeneratePrivateKey - test helper to generate a new private key to be used for p2p
func GeneratePrivateKey() (privateKey libp2p_crypto.PrivKey, publicKey libp2p_crypto.PubKey, err error) {
privateKey, publicKey, err = libp2p_crypto.GenerateKeyPair(libp2p_crypto.RSA, 2048)
if err != nil {
return nil, nil, err
Loading…
Cancel
Save