Merge pull request #3205 from SebastianJ/coverage-consensus

[coverage] [consensus] Additional unit tests / coverage
pull/3209/head
Leo Chen 4 years ago committed by GitHub
commit af38bc655d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 69
      consensus/consensus_msg_sender_test.go
  2. 125
      consensus/consensus_test.go
  3. 45
      consensus/enums_test.go
  4. 60
      consensus/quorum/quorom_test.go
  5. 148
      consensus/view_change_test.go
  6. 48
      p2p/tests/address_test.go
  7. 83
      p2p/tests/host_test.go
  8. 87
      test/helpers/p2p.go

@ -0,0 +1,69 @@
package consensus
import (
"sync/atomic"
"testing"
msg_pb "github.com/harmony-one/harmony/api/proto/message"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/test/helpers"
"github.com/stretchr/testify/assert"
)
func TestMessageSenderInitialization(t *testing.T) {
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
messageSender := NewMessageSender(host)
expectedMessageSender := &MessageSender{blockNum: 0, host: host, retryTimes: int(phaseDuration.Seconds()) / RetryIntervalInSec}
assert.Equal(t, expectedMessageSender.host, messageSender.host)
assert.Equal(t, expectedMessageSender.retryTimes, messageSender.retryTimes)
assert.Equal(t, uint64(0), messageSender.blockNum)
assert.Equal(t, 0, numberOfMessagesToRetry(messageSender))
}
func TestMessageSenderReset(t *testing.T) {
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
messageSender := NewMessageSender(host)
assert.Equal(t, uint64(0), messageSender.blockNum)
assert.Equal(t, 0, numberOfMessagesToRetry(messageSender))
groups := []nodeconfig.GroupID{
"hmy/testnet/0.0.1/client/beacon",
"hmy/testnet/0.0.1/node/beacon",
}
p2pMsg := []byte{0}
msgType := msg_pb.MessageType_ANNOUNCE
msgRetry := MessageRetry{blockNum: 1, groups: groups, p2pMsg: p2pMsg, msgType: msgType, retryCount: 0}
atomic.StoreUint32(&msgRetry.isActive, 1)
messageSender.messagesToRetry.Store(msgType, &msgRetry)
assert.Equal(t, 1, numberOfMessagesToRetry(messageSender))
messageSender.Reset(1)
assert.Equal(t, uint64(1), messageSender.blockNum)
assert.Equal(t, 0, numberOfMessagesToRetry(messageSender))
msgType = msg_pb.MessageType_COMMITTED
msgRetry = MessageRetry{blockNum: 2, groups: groups, p2pMsg: p2pMsg, msgType: msgType, retryCount: 0}
atomic.StoreUint32(&msgRetry.isActive, 1)
messageSender.messagesToRetry.Store(msgType, &msgRetry)
messageSender.Reset(2)
assert.Equal(t, uint64(2), messageSender.blockNum)
assert.Equal(t, 1, numberOfMessagesToRetry(messageSender))
}
func numberOfMessagesToRetry(messageSender *MessageSender) int {
messagesToRetryCount := 0
messageSender.messagesToRetry.Range(func(_, _ interface{}) bool {
messagesToRetryCount++
return true
})
return messagesToRetryCount
}

@ -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.GetPrivateKeys(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.GetPublicKeys(), consensus.GetPublicKeys())
// 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.PrivateKeys, *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.GetPrivateKeys(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,148 @@
package consensus
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 TestBasicViewChanging(t *testing.T) {
_, _, consensus, _, err := GenerateConsensusForTesting()
assert.NoError(t, err)
state := State{mode: Normal}
// Change Mode
assert.Equal(t, state.mode, consensus.current.mode)
assert.Equal(t, state.Mode(), consensus.current.Mode())
consensus.current.SetMode(ViewChanging)
assert.Equal(t, ViewChanging, consensus.current.mode)
assert.Equal(t, ViewChanging, consensus.current.Mode())
// Change ViewID
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?
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)
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{}
wrappedBLSKeys := []*shard.BLSPublicKeyWrapper{}
keyCount := int64(5)
for i := int64(0); i < keyCount; i++ {
blsKey := harmony_bls.RandPrivateKey()
blsPubKey := blsKey.GetPublicKey()
bytes := shard.BLSPublicKey{}
bytes.FromLibBLSPublicKey(blsPubKey)
wrapped := &shard.BLSPublicKeyWrapper{Object: blsPubKey, Bytes: bytes}
blsKeys = append(blsKeys, blsPubKey)
wrappedBLSKeys = append(wrappedBLSKeys, wrapped)
}
consensus.Decider.UpdateParticipants(blsKeys)
assert.Equal(t, keyCount, consensus.Decider.ParticipantsCount())
consensus.LeaderPubKey = wrappedBLSKeys[0]
nextKey := consensus.GetNextLeaderKey()
assert.Equal(t, nextKey, wrappedBLSKeys[1])
}

@ -0,0 +1,48 @@
package p2ptests
import (
"strings"
"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(helpers.Bootnodes)
assert.NoError(t, err)
assert.Equal(t, len(helpers.Bootnodes), len(multiAddresses))
for index, multiAddress := range multiAddresses {
assert.Equal(t, multiAddress.String(), helpers.Bootnodes[index])
}
}
func TestAddressListConversionToString(t *testing.T) {
t.Parallel()
multiAddresses, err := p2p.StringsToAddrs(helpers.Bootnodes)
assert.NoError(t, err)
assert.Equal(t, len(helpers.Bootnodes), len(multiAddresses))
expected := strings.Join(helpers.Bootnodes[:], ",")
var addressList p2p.AddrList = multiAddresses
assert.Equal(t, expected, addressList.String())
}
func TestAddressListConversionFromString(t *testing.T) {
t.Parallel()
multiAddresses, err := p2p.StringsToAddrs(helpers.Bootnodes)
assert.NoError(t, err)
assert.Equal(t, len(helpers.Bootnodes), len(multiAddresses))
addressString := strings.Join(helpers.Bootnodes[:], ",")
var addressList p2p.AddrList = multiAddresses
addressList.Set(addressString)
assert.Equal(t, len(addressList), len(multiAddresses))
assert.Equal(t, addressList[0], multiAddresses[0])
}

@ -0,0 +1,83 @@
package p2ptests
import (
"testing"
"github.com/harmony-one/harmony/test/helpers"
"github.com/stretchr/testify/assert"
)
func TestHostSetup(t *testing.T) {
t.Parallel()
hostData := helpers.Hosts[0]
host, pubKey, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
peer := host.GetSelfPeer()
assert.Equal(t, hostData.IP, peer.IP)
assert.Equal(t, hostData.Port, peer.Port)
assert.Equal(t, pubKey, peer.ConsensusPubKey)
assert.NotEmpty(t, peer.PeerID)
assert.Equal(t, peer.PeerID, host.GetID())
assert.Empty(t, peer.Addrs)
}
func TestAddPeer(t *testing.T) {
t.Parallel()
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, host.GetID())
discoveredHostData := helpers.Hosts[1]
discoveredHost, _, err := helpers.GenerateHost(discoveredHostData.IP, discoveredHostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, discoveredHost.GetID())
discoveredPeer := discoveredHost.GetSelfPeer()
assert.Empty(t, host.GetP2PHost().Peerstore().Addrs(discoveredHost.GetSelfPeer().PeerID))
err = host.AddPeer(&discoveredPeer)
assert.NoError(t, err)
assert.NotEmpty(t, host.GetP2PHost().Peerstore().Addrs(discoveredHost.GetSelfPeer().PeerID))
assert.Equal(t, 2, host.GetPeerCount())
}
/*func TestTopicJoining(t *testing.T) {
t.Parallel()
hostData := hosts[0]
host, _, err := createNode(hostData.IP, hostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, host.GetID())
for _, topicName := range topics {
topic, err := host.GetOrJoin(topicName)
assert.NoError(t, err)
assert.NotNil(t, topic)
}
}*/
func TestConnectionToInvalidPeer(t *testing.T) {
t.Parallel()
hostData := helpers.Hosts[0]
host, _, err := helpers.GenerateHost(hostData.IP, hostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, host.GetID())
discoveredHostData := helpers.Hosts[1]
discoveredHost, _, err := helpers.GenerateHost(discoveredHostData.IP, discoveredHostData.Port)
assert.NoError(t, err)
assert.NotEmpty(t, discoveredHost.GetID())
discoveredPeer := discoveredHost.GetSelfPeer()
err = host.ConnectHostPeer(discoveredPeer)
assert.Error(t, err)
}

@ -0,0 +1,87 @@
package helpers
import (
"github.com/harmony-one/bls/ffi/go/bls"
harmony_bls "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/p2p"
libp2p_crypto "github.com/libp2p/go-libp2p-crypto"
"github.com/pkg/errors"
)
// Host - struct for representing a host (IP / Port)
type Host struct {
IP string
Port string
}
var (
// 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: "9000"},
{IP: "8.8.8.8", Port: "9001"},
}
Topics = []string{
"hmy/testnet/0.0.1/client/beacon",
"hmy/testnet/0.0.1/node/beacon",
"hmy/testnet/0.0.1/node/shard/1",
"hmy/testnet/0.0.1/node/shard/2",
"hmy/testnet/0.0.1/node/shard/3",
}
Bootnodes = []string{
"/ip4/54.86.126.90/tcp/9850/p2p/Qmdfjtk6hPoyrH1zVD9PEH4zfWLo38dP2mDvvKXfh3tnEv",
"/ip4/52.40.84.2/tcp/9850/p2p/QmbPVwrqWsTYXq1RxGWcxx9SWaTUCfoo1wA6wmdbduWe29",
}
}
// 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)
if err != nil {
return nil, nil, err
}
host, err := p2p.NewHost(&peer, nodePrivateKey)
if err != nil {
return nil, nil, err
}
return host, peer.ConsensusPubKey, nil
}
// 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 {
return p2p.Peer{}, errors.New("failed to generate bls key for peer")
}
peer := p2p.Peer{IP: address, Port: port, ConsensusPubKey: peerPublicKey}
return peer, nil
}
// 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
}
return privateKey, publicKey, nil
}
Loading…
Cancel
Save