parent
e5199939f5
commit
41c0b3d06b
@ -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]) |
||||
} |
Loading…
Reference in new issue