diff --git a/consensus/quorum/quorom_test.go b/consensus/quorum/quorom_test.go index a4e320029..80f55b2e1 100644 --- a/consensus/quorum/quorom_test.go +++ b/consensus/quorum/quorom_test.go @@ -1,11 +1,18 @@ package quorum import ( + "math/big" + "strings" "testing" + bls_core "github.com/harmony-one/bls/ffi/go/bls" harmony_bls "github.com/harmony-one/harmony/crypto/bls" + "github.com/harmony-one/harmony/internal/configs/sharding" "github.com/harmony-one/harmony/shard" "github.com/stretchr/testify/assert" + + "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/harmony/crypto/bls" ) func TestPhaseStrings(t *testing.T) { @@ -59,3 +66,416 @@ func TestAddingQuoromParticipants(t *testing.T) { decider.UpdateParticipants(blsKeys) assert.Equal(t, keyCount, decider.ParticipantsCount()) } + +func TestSubmitVote(test *testing.T) { + blockHash := [32]byte{} + copy(blockHash[:], []byte("random")) + blockNum := uint64(1000) + viewID := uint64(2) + + decider := NewDecider( + SuperMajorityStake, shard.BeaconChainShardID, + ) + + message := "test string" + blsPriKey1 := bls.RandPrivateKey() + pubKeyWrapper1 := bls.PublicKeyWrapper{Object: blsPriKey1.GetPublicKey()} + pubKeyWrapper1.Bytes.FromLibBLSPublicKey(pubKeyWrapper1.Object) + + blsPriKey2 := bls.RandPrivateKey() + pubKeyWrapper2 := bls.PublicKeyWrapper{Object: blsPriKey2.GetPublicKey()} + pubKeyWrapper2.Bytes.FromLibBLSPublicKey(pubKeyWrapper2.Object) + + decider.UpdateParticipants([]bls.PublicKeyWrapper{pubKeyWrapper1, pubKeyWrapper2}) + + if _, err := decider.SubmitVote( + Prepare, + []bls.SerializedPublicKey{pubKeyWrapper1.Bytes}, + blsPriKey1.Sign(message), + common.BytesToHash(blockHash[:]), + blockNum, + viewID, + ); err != nil { + test.Log(err) + } + + if _, err := decider.SubmitVote( + Prepare, + []bls.SerializedPublicKey{pubKeyWrapper2.Bytes}, + blsPriKey2.Sign(message), + common.BytesToHash(blockHash[:]), + blockNum, + viewID, + ); err != nil { + test.Log(err) + } + if decider.SignersCount(Prepare) != 2 { + test.Fatal("SubmitVote failed") + } + + aggSig := &bls_core.Sign{} + aggSig.Add(blsPriKey1.Sign(message)) + aggSig.Add(blsPriKey2.Sign(message)) + if decider.AggregateVotes(Prepare).SerializeToHexStr() != aggSig.SerializeToHexStr() { + test.Fatal("AggregateVotes failed") + } +} + +func TestSubmitVoteAggregateSig(test *testing.T) { + blockHash := [32]byte{} + copy(blockHash[:], []byte("random")) + blockNum := uint64(1000) + viewID := uint64(2) + + decider := NewDecider( + SuperMajorityStake, shard.BeaconChainShardID, + ) + + blsPriKey1 := bls.RandPrivateKey() + pubKeyWrapper1 := bls.PublicKeyWrapper{Object: blsPriKey1.GetPublicKey()} + pubKeyWrapper1.Bytes.FromLibBLSPublicKey(pubKeyWrapper1.Object) + + blsPriKey2 := bls.RandPrivateKey() + pubKeyWrapper2 := bls.PublicKeyWrapper{Object: blsPriKey2.GetPublicKey()} + pubKeyWrapper2.Bytes.FromLibBLSPublicKey(pubKeyWrapper2.Object) + + blsPriKey3 := bls.RandPrivateKey() + pubKeyWrapper3 := bls.PublicKeyWrapper{Object: blsPriKey3.GetPublicKey()} + pubKeyWrapper3.Bytes.FromLibBLSPublicKey(pubKeyWrapper3.Object) + + decider.UpdateParticipants([]bls.PublicKeyWrapper{pubKeyWrapper1, pubKeyWrapper2}) + + decider.SubmitVote( + Prepare, + []bls.SerializedPublicKey{pubKeyWrapper1.Bytes}, + blsPriKey1.SignHash(blockHash[:]), + common.BytesToHash(blockHash[:]), + blockNum, + viewID, + ) + + aggSig := &bls_core.Sign{} + for _, priKey := range []*bls_core.SecretKey{blsPriKey2, blsPriKey3} { + if s := priKey.SignHash(blockHash[:]); s != nil { + aggSig.Add(s) + } + } + if _, err := decider.SubmitVote( + Prepare, + []bls.SerializedPublicKey{pubKeyWrapper2.Bytes, pubKeyWrapper3.Bytes}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID, + ); err != nil { + test.Log(err) + } + + if decider.SignersCount(Prepare) != 3 { + test.Fatal("SubmitVote failed") + } + + aggSig.Add(blsPriKey1.SignHash(blockHash[:])) + if decider.AggregateVotes(Prepare).SerializeToHexStr() != aggSig.SerializeToHexStr() { + test.Fatal("AggregateVotes failed") + } + + if _, err := decider.SubmitVote( + Prepare, + []bls.SerializedPublicKey{pubKeyWrapper2.Bytes}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID, + ); err == nil { + test.Fatal("Expect error for duplicate votes from the same key") + } +} + +func TestAddNewVote(test *testing.T) { + shard.Schedule = shardingconfig.LocalnetSchedule + blockHash := [32]byte{} + copy(blockHash[:], []byte("random")) + blockNum := uint64(1000) + viewID := uint64(2) + + decider := NewDecider( + SuperMajorityStake, shard.BeaconChainShardID, + ) + + slotList := shard.SlotList{} + sKeys := []bls_core.SecretKey{} + pubKeys := []bls.PublicKeyWrapper{} + + quorumNodes := 10 + + for i := 0; i < quorumNodes; i++ { + newSlot, sKey := generateRandomSlot() + if i < 3 { + newSlot.EffectiveStake = nil + } + sKeys = append(sKeys, sKey) + slotList = append(slotList, newSlot) + wrapper := bls.PublicKeyWrapper{Object: sKey.GetPublicKey()} + wrapper.Bytes.FromLibBLSPublicKey(wrapper.Object) + pubKeys = append(pubKeys, wrapper) + } + + decider.UpdateParticipants(pubKeys) + decider.SetVoters(&shard.Committee{ + shard.BeaconChainShardID, slotList, + }, big.NewInt(3)) + + aggSig := &bls_core.Sign{} + for _, priKey := range []*bls_core.SecretKey{&sKeys[0], &sKeys[1], &sKeys[2]} { + if s := priKey.SignHash(blockHash[:]); s != nil { + aggSig.Add(s) + } + } + + // aggregate sig from all of 3 harmony nodes + decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[0], &pubKeys[1], &pubKeys[2]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if !decider.IsQuorumAchieved(Prepare) { + test.Error("quorum should have been achieved with harmony nodes") + } + if decider.SignersCount(Prepare) != 3 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 3) + } + + decider.ResetPrepareAndCommitVotes() + + // aggregate sig from 3 external nodes, expect error + aggSig = &bls_core.Sign{} + for _, priKey := range []*bls_core.SecretKey{&sKeys[3], &sKeys[4], &sKeys[5]} { + if s := priKey.SignHash(blockHash[:]); s != nil { + aggSig.Add(s) + } + } + _, err := decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[3], &pubKeys[4], &pubKeys[5]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if err == nil { + test.Error("Should have error due to aggregate sig from multiple accounts") + } + if decider.IsQuorumAchieved(Prepare) { + test.Fatal("quorum shouldn't have been achieved with external nodes") + } + if decider.SignersCount(Prepare) != 0 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 0) + } + + decider.ResetPrepareAndCommitVotes() + + // one sig from external node + _, err = decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[3]}, + sKeys[3].SignHash(blockHash[:]), + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + if err != nil { + test.Error(err) + } + if decider.IsQuorumAchieved(Prepare) { + test.Fatal("quorum shouldn't have been achieved with only one key signing") + } + if decider.SignersCount(Prepare) != 1 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 1) + } +} + +func TestAddNewVoteAggregateSig(test *testing.T) { + shard.Schedule = shardingconfig.LocalnetSchedule + blockHash := [32]byte{} + copy(blockHash[:], []byte("random")) + blockNum := uint64(1000) + viewID := uint64(2) + + decider := NewDecider( + SuperMajorityStake, shard.BeaconChainShardID, + ) + + slotList := shard.SlotList{} + sKeys := []bls_core.SecretKey{} + pubKeys := []bls.PublicKeyWrapper{} + + quorumNodes := 5 + + for i := 0; i < quorumNodes; i++ { + newSlot, sKey := generateRandomSlot() + if i < 3 { + newSlot.EffectiveStake = nil + } + sKeys = append(sKeys, sKey) + slotList = append(slotList, newSlot) + wrapper := bls.PublicKeyWrapper{Object: sKey.GetPublicKey()} + wrapper.Bytes.FromLibBLSPublicKey(wrapper.Object) + pubKeys = append(pubKeys, wrapper) + } + + // make all external keys belong to same account + slotList[3].EcdsaAddress = slotList[4].EcdsaAddress + + decider.UpdateParticipants(pubKeys) + decider.SetVoters(&shard.Committee{ + shard.BeaconChainShardID, slotList, + }, big.NewInt(3)) + + aggSig := &bls_core.Sign{} + for _, priKey := range []*bls_core.SecretKey{&sKeys[0], &sKeys[1]} { + if s := priKey.SignHash(blockHash[:]); s != nil { + aggSig.Add(s) + } + } + + // aggregate sig from all of 2 harmony nodes + decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[0], &pubKeys[1]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if decider.IsQuorumAchieved(Prepare) { + test.Error("quorum should not have been achieved with 2 harmony nodes") + } + if decider.SignersCount(Prepare) != 2 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 2) + } + // aggregate sig from all of 2 external nodes + + aggSig = &bls_core.Sign{} + for _, priKey := range []*bls_core.SecretKey{&sKeys[3], &sKeys[4]} { + if s := priKey.SignHash(blockHash[:]); s != nil { + aggSig.Add(s) + } + } + decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[3], &pubKeys[4]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if !decider.IsQuorumAchieved(Prepare) { + test.Error("quorum should have been achieved with 2 harmony nodes") + } + if decider.SignersCount(Prepare) != 4 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 4) + } +} + +func TestAddNewVoteInvalidAggregateSig(test *testing.T) { + shard.Schedule = shardingconfig.LocalnetSchedule + blockHash := [32]byte{} + copy(blockHash[:], []byte("random")) + blockNum := uint64(1000) + viewID := uint64(2) + + decider := NewDecider( + SuperMajorityStake, shard.BeaconChainShardID, + ) + + slotList := shard.SlotList{} + sKeys := []bls_core.SecretKey{} + pubKeys := []bls.PublicKeyWrapper{} + + quorumNodes := 8 + + for i := 0; i < quorumNodes; i++ { + newSlot, sKey := generateRandomSlot() + if i < 3 { + newSlot.EffectiveStake = nil + } + sKeys = append(sKeys, sKey) + slotList = append(slotList, newSlot) + wrapper := bls.PublicKeyWrapper{Object: sKey.GetPublicKey()} + wrapper.Bytes.FromLibBLSPublicKey(wrapper.Object) + pubKeys = append(pubKeys, wrapper) + } + + // make all external keys belong to same account + slotList[3].EcdsaAddress = slotList[7].EcdsaAddress + slotList[4].EcdsaAddress = slotList[7].EcdsaAddress + slotList[5].EcdsaAddress = slotList[7].EcdsaAddress + slotList[6].EcdsaAddress = slotList[7].EcdsaAddress + + decider.UpdateParticipants(pubKeys) + decider.SetVoters(&shard.Committee{ + shard.BeaconChainShardID, slotList, + }, big.NewInt(3)) + + aggSig := &bls_core.Sign{} + for _, priKey := range []*bls_core.SecretKey{&sKeys[0], &sKeys[1]} { + if s := priKey.SignHash(blockHash[:]); s != nil { + aggSig.Add(s) + } + } + + // aggregate sig from all of 2 harmony nodes + decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[0], &pubKeys[1]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if decider.IsQuorumAchieved(Prepare) { + test.Error("quorum should not have been achieved with 2 harmony nodes") + } + if decider.SignersCount(Prepare) != 2 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 2) + } + // aggregate sig from all of 2 external nodes + _, err := decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[3], &pubKeys[4]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if err != nil { + test.Error(err, "expect no error") + } + if decider.SignersCount(Prepare) != 4 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 4) + } + + _, err = decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[3], &pubKeys[7]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if !strings.Contains(err.Error(), "vote is already submitted") { + test.Error(err, "expect error due to already submitted votes") + } + if decider.SignersCount(Prepare) != 4 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 4) + } + + _, err = decider.AddNewVote(Prepare, + []*bls.PublicKeyWrapper{&pubKeys[6], &pubKeys[5], &pubKeys[6]}, + aggSig, + common.BytesToHash(blockHash[:]), + blockNum, + viewID) + + if !strings.Contains(err.Error(), "duplicate key found in votes") { + test.Error(err, "expect error due to duplicate keys in aggregated votes") + } + if decider.SignersCount(Prepare) != 4 { + test.Errorf("signers are incorrect for harmony nodes signing with aggregate sig: have %d, expect %d", decider.SignersCount(Prepare), 4) + } +} diff --git a/consensus/quorum/quorum.go b/consensus/quorum/quorum.go index a3f87e1e8..6b9b4e0ab 100644 --- a/consensus/quorum/quorum.go +++ b/consensus/quorum/quorum.go @@ -16,7 +16,7 @@ import ( "github.com/pkg/errors" ) -// MessageType is a phase that needs quorum to proceed +// Phase is a phase that needs quorum to proceed type Phase byte const ( @@ -253,7 +253,13 @@ func (s *cIdentities) SubmitVote( sig *bls_core.Sign, headerHash common.Hash, height, viewID uint64, ) (*votepower.Ballot, error) { + seenKeys := map[bls.SerializedPublicKey]struct{}{} for _, pubKey := range pubkeys { + if _, ok := seenKeys[pubKey]; ok { + return nil, errors.Errorf("duplicate key found in votes %x", pubKey) + } + seenKeys[pubKey] = struct{}{} + if ballet := s.ReadBallot(p, pubKey); ballet != nil { return nil, errors.Errorf("vote is already submitted %x", pubKey) }