From 4642db1e9cfbcb80b780271d2a8a851d0bbb19f8 Mon Sep 17 00:00:00 2001 From: Noam Berg Date: Tue, 28 Jan 2020 17:03:56 +0200 Subject: [PATCH 1/2] new reputation calculation with a weighted non-repeated draw --- .../call_contract_ordered_committee.go | 12 +- .../native/repository/Committee/committee.go | 72 +++--- .../repository/Committee/committee_test.go | 206 ++++++++++-------- .../native/repository/Committee/reputation.go | 18 +- .../repository/Committee/reputation_test.go | 8 +- 5 files changed, 175 insertions(+), 141 deletions(-) diff --git a/services/consensuscontext/call_contract_ordered_committee.go b/services/consensuscontext/call_contract_ordered_committee.go index 799458f8f..d84b51aa1 100644 --- a/services/consensuscontext/call_contract_ordered_committee.go +++ b/services/consensuscontext/call_contract_ordered_committee.go @@ -7,6 +7,7 @@ package consensuscontext import ( + "bytes" "context" "github.com/orbs-network/orbs-network-go/config" "github.com/orbs-network/orbs-network-go/crypto/digest" @@ -18,6 +19,7 @@ import ( "github.com/orbs-network/orbs-spec/types/go/services" "github.com/orbs-network/scribe/log" "github.com/pkg/errors" + "sort" "time" ) @@ -115,9 +117,15 @@ func (s *service) generateGenesisCommittee(ctx context.Context, currentBlockHeig } func generateGenesisCommitteeAddresses(nodes map[string]config.ValidatorNode) []byte { - res := make([]byte, 0, len(nodes)*digest.NODE_ADDRESS_SIZE_BYTES) + listOfAddresses := make([][]byte, 0, len(nodes)) for _, value := range nodes { - res = append(res, value.NodeAddress()...) + listOfAddresses = append(listOfAddresses, value.NodeAddress()) + } + // committee contract expects same order for input on all nodes so a sort is a must here. + sort.Slice(listOfAddresses, func(i, j int) bool { return bytes.Compare(listOfAddresses[i], listOfAddresses[j]) > 0}) + res := make([]byte, 0, len(nodes)*digest.NODE_ADDRESS_SIZE_BYTES) + for _, value := range listOfAddresses { + res = append(res, value...) } return res } diff --git a/services/processor/native/repository/Committee/committee.go b/services/processor/native/repository/Committee/committee.go index e69e2f633..aff3af85b 100644 --- a/services/processor/native/repository/Committee/committee.go +++ b/services/processor/native/repository/Committee/committee.go @@ -7,14 +7,14 @@ package committee_systemcontract import ( - "bytes" "encoding/binary" "github.com/orbs-network/orbs-contract-sdk/go/sdk/v1/env" "github.com/orbs-network/orbs-network-go/crypto/hash" - "math" - "sort" ) +/** + * This function is meant ot be used via the callsystemcontract func ... it will not give same result when used with RunQuery + */ func getOrderedCommittee() []byte { return getOrderedCommitteeForAddresses(_getElectedValidators()) } @@ -38,44 +38,50 @@ func _generateSeed() []byte { return seedBytes } -func _orderList(addrs [][]byte, seed []byte) [][]byte { - addrsToSort := addrsAndScores{addrs, make([]float64, len(addrs))} - for i, addr := range addrs { - addrsToSort.scores[i] = _calculateScoreWithReputation(addr, seed) +func _orderList(addresses [][]byte, seed []byte) [][]byte { + committeeSize := len(addresses) + accumulatedWeights := make([]int, committeeSize) + totalWeight := 0 + for i, address := range addresses { + weight := _absoluteWeight(address) + totalWeight += weight + accumulatedWeights[i] = totalWeight } - sort.Sort(addrsToSort) - return addrsToSort.addresses -} - -func _calculateScoreWithReputation(addr []byte, seed []byte) float64 { - rep := getReputation(addr) - return float64(_calculateScore(addr, seed)) / _reputationAsFactor(rep) -} -func _calculateScore(addr []byte, seed []byte) uint32 { - random := hash.CalcSha256(addr, seed) - return binary.LittleEndian.Uint32(random[hash.SHA256_HASH_SIZE_BYTES-4:]) -} + orderedCommitteeAddresses := make([][]byte, 0, committeeSize) + random := seed -func _reputationAsFactor(reputation uint32) float64 { - return math.Pow(2, float64(reputation)) -} + for j := 0; j < committeeSize; j++ { + random = _nextRandom(random) + curr := _getRandomWeight(random, totalWeight) -type addrsAndScores struct { - addresses [][]byte - scores []float64 + for i := 0; i < committeeSize; i++ { + if curr > accumulatedWeights[i] || accumulatedWeights[i] == 0 { + continue + } + orderedCommitteeAddresses = append(orderedCommitteeAddresses, addresses[i]) + currWeight := _absoluteWeight(addresses[i]) + totalWeight -= currWeight + accumulatedWeights[i] = 0 + for k := i + 1;k < committeeSize;k++ { + if accumulatedWeights[k] != 0 { + accumulatedWeights[k] -= currWeight + } + } + break + } + } + return orderedCommitteeAddresses } -func (s addrsAndScores) Len() int { - return len(s.addresses) +func _absoluteWeight(address []byte) int { + return 1 << (_getMaxReputation() - getReputation(address)) } -func (s addrsAndScores) Swap(i, j int) { - s.addresses[i], s.addresses[j] = s.addresses[j], s.addresses[i] - s.scores[i], s.scores[j] = s.scores[j], s.scores[i] +func _nextRandom(random []byte) []byte { + return hash.CalcSha256(random) } -// descending order -func (s addrsAndScores) Less(i, j int) bool { - return s.scores[i] > s.scores[j] || (s.scores[i] == s.scores[j] && bytes.Compare(s.addresses[i], s.addresses[j]) > 0) +func _getRandomWeight(random []byte, maxWeight int) int { + return int(binary.LittleEndian.Uint32(random[hash.SHA256_HASH_SIZE_BYTES-4:]) % uint32(maxWeight)) + 1 } diff --git a/services/processor/native/repository/Committee/committee_test.go b/services/processor/native/repository/Committee/committee_test.go index f680a7b0f..eb022268b 100644 --- a/services/processor/native/repository/Committee/committee_test.go +++ b/services/processor/native/repository/Committee/committee_test.go @@ -7,142 +7,158 @@ package committee_systemcontract import ( - "encoding/binary" + "bytes" "github.com/orbs-network/orbs-contract-sdk/go/sdk/v1/state" . "github.com/orbs-network/orbs-contract-sdk/go/testing/unit" - "github.com/orbs-network/orbs-network-go/crypto/hash" "github.com/stretchr/testify/require" "testing" ) -func appendAndOrder(newAddr []byte, addrs [][]byte) [][]byte { - return appendAndOrderWithSeed(newAddr, addrs, []byte{}) -} - -func appendAndOrderWithSeed(newAddr []byte, addrs [][]byte, seed []byte) [][]byte { - currScore := _calculateScoreWithReputation(newAddr, seed) - addrs = append(addrs, newAddr) - for i := len(addrs) - 1; i > 0; i-- { - if currScore > _calculateScoreWithReputation(addrs[i-1], seed) { - addrs[i], addrs[i-1] = addrs[i-1], addrs[i] - } else { - break +func TestOrbsCommitteeContract_check_random(t *testing.T) { + totalWeight := 1280 + random := []byte{0x02, 0x1, 0x2, 0x3, 0x5, 0x5, 0x6, 0x4} + maxRuns := 1000000 + res := make([]int, totalWeight+1) + for i := 0; i < maxRuns; i++ { + random = _nextRandom(random) + ind := _getRandomWeight(random, totalWeight) + res[ind] += 1 + } + expected := maxRuns / totalWeight + expectedOver := expected * 98 / 100 + expectedUnder := expected * 102 / 100 + tooFar := 0 + for i := 1; i <= totalWeight; i++ { + if res[i] < expectedOver || res[i] > expectedUnder { + tooFar++ } } - return addrs + require.LessOrEqual(t, float64(tooFar)/float64(maxRuns), 0.001, "misses of+-2 percent should be under 0.1 percent") } -func TestOrbsCommitteeContract_getOrderedCommittee_withoutReputation(t *testing.T) { - addrs := makeNodeAddressArray(10) - blockHeight := 155 +func TestOrbsCommitteeContract_check_OrderWithDiffSeedAndRep(t *testing.T) { + committeeSize := 5 + addressArray := makeNodeAddressArray(committeeSize) InServiceScope(nil, nil, func(m Mockery) { _init() - - // prepare - m.MockEnvBlockHeight(blockHeight) - - // add each to the correct place - expectedOrder := make([][]byte, 0, len(addrs)) - for _, addr := range addrs { - expectedOrder = appendAndOrderWithSeed(addr, expectedOrder, _generateSeed()) - } - - // run - ordered := _getOrderedCommitteeArray(addrs) - - //assert - require.EqualValues(t, expectedOrder, ordered) + ordered := _orderList(addressArray, []byte{7}) + orderedDiffSeed := _orderList(addressArray, []byte{8}) + require.NotEqual(t, ordered, orderedDiffSeed) + require.ElementsMatch(t, ordered, orderedDiffSeed, "must actually be same list in diff order") + orderedSameSeed := _orderList(addressArray, []byte{7}) + require.EqualValues(t, ordered, orderedSameSeed) + state.WriteUint32(_formatMisses(addressArray[0]), ReputationBottomCap) + orderedSameSeedDiffProb := _orderList(addressArray, []byte{7}) + require.NotEqual(t, ordered, orderedSameSeedDiffProb) + require.ElementsMatch(t, ordered, orderedSameSeedDiffProb, "must actually be same list in diff order") }) } -func TestOrbsCommitteeContract_getOrderedCommittee_SimpleReputation(t *testing.T) { - addrs := makeNodeAddressArray(3) - blockHeight := 155 +func TestOrbsCommitteeContract_orderList_AllRepIs0(t *testing.T) { + max := 10000 + committeeSize := 20 + addresses := makeNodeAddressArray(committeeSize) + checkAddress := addresses[0] + var foundAtPos0, foundAtPosMid, foundAtPosLast int InServiceScope(nil, nil, func(m Mockery) { _init() - - // prepare - m.MockEnvBlockHeight(blockHeight) - state.WriteUint32(_formatMisses(addrs[0]), 10) - - // sort with simplified calculation - expectedOrder := make([][]byte, 0, len(addrs)) - for _, addr := range addrs { - expectedOrder = appendAndOrderWithSeed(addr, expectedOrder, _generateSeed()) - } + // no setup // run - ordered := _getOrderedCommitteeArray(addrs) + midLocation := committeeSize / 2 + endLocation := committeeSize - 1 + for i := 1 ; i <= max ; i++ { + m.MockEnvBlockHeight(i) // to generate random seed + outputArr := _orderList(addresses, _generateSeed()) + if bytes.Equal(outputArr[0], checkAddress) { + foundAtPos0++ + } else if bytes.Equal(outputArr[midLocation], checkAddress){ + foundAtPosMid++ + } else if bytes.Equal(outputArr[endLocation], checkAddress){ + foundAtPosLast++ + } + } - //assert - require.EqualValues(t, expectedOrder, ordered) + // assert + expected := max / committeeSize // equal chance + requireCountToBeInRange(t, foundAtPos0, expected) + requireCountToBeInRange(t, foundAtPosMid, expected) + requireCountToBeInRange(t, foundAtPosLast, expected) }) } -func TestOrbsCommitteeContract_orderList_noReputation_noSeed(t *testing.T) { - addrs := makeNodeAddressArray(10) +func TestOrbsCommitteeContract_orderList_OneRepIsWorst(t *testing.T) { + max := 100000 + committeeSize := 4 // to allow a good spread in a small amount of runs we need a small committee size. + addresses := makeNodeAddressArray(committeeSize) + badAddress := addresses[0] + goodAddress := addresses[1] + foundBadAddressInFirstPosition := 0 + foundGoodAddressInFirstPosition := 0 InServiceScope(nil, nil, func(m Mockery) { _init() - // Prepare do calculation in similar way - expectedOrder := make([][]byte, 0, len(addrs)) - for _, addr := range addrs { - expectedOrder = appendAndOrder(addr, expectedOrder) - } + // prepare + state.WriteUint32(_formatMisses(badAddress), ReputationBottomCap) - // run with empty seed - ordered := _orderList(addrs, []byte{}) + // run + for i := 1 ; i <= max ; i++ { + m.MockEnvBlockHeight(i) // to generate random seed + outputArr := _orderList(addresses, _generateSeed()) + if bytes.Equal(outputArr[0], badAddress) { + foundBadAddressInFirstPosition++ + } + if bytes.Equal(outputArr[0], goodAddress) { + foundGoodAddressInFirstPosition++ + } + } - //assert - require.EqualValues(t, expectedOrder, ordered) + // assert + requireCountToBeInRange(t, foundGoodAddressInFirstPosition, (max * 64) / ((committeeSize-1) * 64 + 1)) + requireCountToBeInRange(t, foundBadAddressInFirstPosition, max / ((committeeSize-1) * 64 + 1)) }) } -func TestOrbsCommitteeContract_calculateScoreWithReputation(t *testing.T) { - addr := makeNodeAddress(25) - blockHeight := 777744444 +func TestOrbsCommitteeContract_orderList_QuarterAreALittleBad(t *testing.T) { + max := 10000 + committeeSize := 20 + addresses := makeNodeAddressArray(committeeSize) + badAddress := addresses[0] + goodAddress := addresses[5] + foundBadAddressInFirstPosition := 0 + foundGoodAddressInFirstPosition := 0 InServiceScope(nil, nil, func(m Mockery) { _init() - // Prepare - m.MockEnvBlockHeight(blockHeight) - scoreWithOutRep := _calculateScore(addr, _generateSeed()) - - // rep below cap - state.WriteUint32(_formatMisses(addr), 2) - score := _calculateScoreWithReputation(addr, _generateSeed()) - require.EqualValues(t, scoreWithOutRep, score) + // prepare + state.WriteUint32(_formatMisses(addresses[0]), ToleranceLevel+2) + state.WriteUint32(_formatMisses(addresses[1]), ToleranceLevel+2) + state.WriteUint32(_formatMisses(addresses[2]), ToleranceLevel+2) + state.WriteUint32(_formatMisses(addresses[3]), ToleranceLevel+2) + state.WriteUint32(_formatMisses(addresses[4]), ToleranceLevel+2) - // rep with factor (2^5) - state.WriteUint32(_formatMisses(addr), 5) - score = _calculateScoreWithReputation(addr, _generateSeed()) - require.EqualValues(t, float64(scoreWithOutRep)/float64(32), score) + // run + for i := 1 ; i <= max ; i++ { + m.MockEnvBlockHeight(i) // to generate random seed + outputArr := _orderList(addresses, _generateSeed()) + if bytes.Equal(outputArr[0], badAddress) { + foundBadAddressInFirstPosition++ + } + if bytes.Equal(outputArr[0], goodAddress) { + foundGoodAddressInFirstPosition++ + } + } - // rep with factor (2^10) miss is above cap - state.WriteUint32(_formatMisses(addr), 11) - score = _calculateScoreWithReputation(addr, _generateSeed()) - require.EqualValues(t, float64(scoreWithOutRep)/float64(1024), score) + // assert + requireCountToBeInRange(t, foundGoodAddressInFirstPosition, (max * 64) / ((committeeSize-5) * 64 + 5 * 16)) + requireCountToBeInRange(t, foundBadAddressInFirstPosition, (max * 16) / ((committeeSize-5) * 64 + 5 * 16)) }) } -func TestOrbsCommitteeContract_calculateScore(t *testing.T) { - addr := []byte{0xa1, 0x33} - var emptySeed = []byte{} - nonEmptySeed := []byte{0x44} - nonEmptySeedOneBitDiff := []byte{0x43} - - scoreWithEmpty := _calculateScore(addr, emptySeed) - scoreWithNonEmpty := _calculateScore(addr, nonEmptySeed) - scoreWithNonEmptyOneBitDiff := _calculateScore(addr, nonEmptySeedOneBitDiff) - - shaOfAddrWithNoSeed := hash.CalcSha256(addr) - expectedScoreWithEmpty := binary.LittleEndian.Uint32(shaOfAddrWithNoSeed[hash.SHA256_HASH_SIZE_BYTES-4:]) - - require.Equal(t, expectedScoreWithEmpty, scoreWithEmpty, "for score with empty seed doesn't match expected") - require.NotEqual(t, scoreWithNonEmpty, scoreWithEmpty, "for score with and without seed must not match") - require.NotEqual(t, scoreWithNonEmpty, scoreWithNonEmptyOneBitDiff, "score is diff even with one bit difference in seed") +func requireCountToBeInRange(t testing.TB, actual, expected int) { + require.InDelta(t, expected, actual, 0.05 * float64(expected), "expect (%d) to be five precent delta to (%d)", actual, expected) } diff --git a/services/processor/native/repository/Committee/reputation.go b/services/processor/native/repository/Committee/reputation.go index d2e197323..413de6de8 100644 --- a/services/processor/native/repository/Committee/reputation.go +++ b/services/processor/native/repository/Committee/reputation.go @@ -13,8 +13,8 @@ import ( "github.com/orbs-network/orbs-contract-sdk/go/sdk/v1/state" ) -const ToleranceLevel = uint32(4) -const ReputationBottomCap = uint32(10) +const ToleranceLevel = uint32(2) +const ReputationBottomCap = uint32(8) func _formatMisses(addr []byte) []byte { return []byte(fmt.Sprintf("Address_%s_Misses", hex.EncodeToString(addr))) @@ -31,13 +31,17 @@ func _addMiss(addr []byte) { func getReputation(addr []byte) uint32 { currMiss := getMisses(addr) - if currMiss < ToleranceLevel { + if currMiss <= ToleranceLevel { return 0 } if currMiss < ReputationBottomCap { - return currMiss + return currMiss - ToleranceLevel } - return ReputationBottomCap + return _getMaxReputation() +} + +func _getMaxReputation() uint32 { + return ReputationBottomCap - ToleranceLevel } func _clearMiss(addr []byte) { @@ -46,7 +50,7 @@ func _clearMiss(addr []byte) { // Function for external monitoring of reputation via absolute number of misses func getAllCommitteeMisses() (committeeAddresses [][20]byte, committeeMisses []uint32) { - addressesArray := _getOrderedCommitteeForAddresses(_getElectedValidators()) + addressesArray := _split(_getElectedValidators()) committeeAddresses = make([][20]byte, len(addressesArray)) committeeMisses = make([]uint32, len(addressesArray)) for i, address := range addressesArray { @@ -58,7 +62,7 @@ func getAllCommitteeMisses() (committeeAddresses [][20]byte, committeeMisses []u // Function for external monitoring of reputation func getAllCommitteeReputations() (committeeAddresses [][20]byte, committeeReputations []uint32) { - addressesArray := _getOrderedCommitteeForAddresses(_getElectedValidators()) + addressesArray := _split(_getElectedValidators()) committeeAddresses = make([][20]byte, len(addressesArray)) committeeReputations = make([]uint32, len(addressesArray)) for i, address := range addressesArray { diff --git a/services/processor/native/repository/Committee/reputation_test.go b/services/processor/native/repository/Committee/reputation_test.go index 141cc3efc..5d9b9544c 100644 --- a/services/processor/native/repository/Committee/reputation_test.go +++ b/services/processor/native/repository/Committee/reputation_test.go @@ -59,18 +59,18 @@ func TestOrbsCommitteeContract_getReputation(t *testing.T) { _init() // Prepare - moreThanRepCap := int(ReputationBottomCap) + 2 + moreThanRepCap := int(ReputationBottomCap) + 3 // assert for i := 0; i < moreThanRepCap; i++ { rep := getReputation(addr) miss := getMisses(addr) - if i < int(ToleranceLevel) { + if i <= int(ToleranceLevel) { require.EqualValues(t, 0, rep, "upto tolerance should be 0") } else if i < int(ReputationBottomCap) { - require.EqualValues(t, miss, rep, "upto cap should be miss") + require.EqualValues(t, miss-ToleranceLevel, rep, "upto cap should be miss") } else { - require.EqualValues(t, ReputationBottomCap, rep, "cannot go over cap of reputation") + require.EqualValues(t, ReputationBottomCap-ToleranceLevel, rep, "cannot go over cap of reputation") } _addMiss(addr) } From 3d3690312463df56ebac6b822179180448ac9e44 Mon Sep 17 00:00:00 2001 From: Noam Berg Date: Wed, 29 Jan 2020 14:41:59 +0200 Subject: [PATCH 2/2] added "getNextCommittee" func for monitoring. --- .../native/repository/Committee/committee.go | 36 ++++++++++++------- .../repository/Committee/committee_test.go | 36 +++++++++++++++---- .../native/repository/Committee/index.go | 2 +- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/services/processor/native/repository/Committee/committee.go b/services/processor/native/repository/Committee/committee.go index aff3af85b..f73a4a7a0 100644 --- a/services/processor/native/repository/Committee/committee.go +++ b/services/processor/native/repository/Committee/committee.go @@ -28,42 +28,41 @@ func _getOrderedCommitteeForAddresses(addresses []byte) [][]byte { return _getOrderedCommitteeArray(addressArray) } +func getNextOrderedCommittee() [][]byte { + addresses := _split(_getElectedValidators()) + return _orderList(addresses, _generateSeed(env.GetBlockHeight()+1)) +} + func _getOrderedCommitteeArray(addresses [][]byte) [][]byte { - return _orderList(addresses, _generateSeed()) + return _orderList(addresses, _generateSeed(env.GetBlockHeight())) } -func _generateSeed() []byte { +func _generateSeed(blockHeight uint64) []byte { seedBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(seedBytes, env.GetBlockHeight()) + binary.LittleEndian.PutUint64(seedBytes, blockHeight) return seedBytes } func _orderList(addresses [][]byte, seed []byte) [][]byte { - committeeSize := len(addresses) - accumulatedWeights := make([]int, committeeSize) - totalWeight := 0 - for i, address := range addresses { - weight := _absoluteWeight(address) - totalWeight += weight - accumulatedWeights[i] = totalWeight - } + committeeSize := len(addresses) orderedCommitteeAddresses := make([][]byte, 0, committeeSize) random := seed + accumulatedWeights, totalWeight := _calculateAccumulatedWeightArray(addresses) for j := 0; j < committeeSize; j++ { random = _nextRandom(random) curr := _getRandomWeight(random, totalWeight) for i := 0; i < committeeSize; i++ { - if curr > accumulatedWeights[i] || accumulatedWeights[i] == 0 { + if curr > accumulatedWeights[i] { continue } orderedCommitteeAddresses = append(orderedCommitteeAddresses, addresses[i]) currWeight := _absoluteWeight(addresses[i]) totalWeight -= currWeight accumulatedWeights[i] = 0 - for k := i + 1;k < committeeSize;k++ { + for k := i + 1; k < committeeSize; k++ { if accumulatedWeights[k] != 0 { accumulatedWeights[k] -= currWeight } @@ -74,6 +73,17 @@ func _orderList(addresses [][]byte, seed []byte) [][]byte { return orderedCommitteeAddresses } +func _calculateAccumulatedWeightArray(addresses [][]byte) ([]int, int) { + accumulatedWeights := make([]int, len(addresses)) + totalWeight := 0 + for i, address := range addresses { + weight := _absoluteWeight(address) + totalWeight += weight + accumulatedWeights[i] = totalWeight + } + return accumulatedWeights, totalWeight +} + func _absoluteWeight(address []byte) int { return 1 << (_getMaxReputation() - getReputation(address)) } diff --git a/services/processor/native/repository/Committee/committee_test.go b/services/processor/native/repository/Committee/committee_test.go index eb022268b..f66c854b6 100644 --- a/services/processor/native/repository/Committee/committee_test.go +++ b/services/processor/native/repository/Committee/committee_test.go @@ -10,6 +10,7 @@ import ( "bytes" "github.com/orbs-network/orbs-contract-sdk/go/sdk/v1/state" . "github.com/orbs-network/orbs-contract-sdk/go/testing/unit" + elections_systemcontract "github.com/orbs-network/orbs-network-go/services/processor/native/repository/_Elections" "github.com/stretchr/testify/require" "testing" ) @@ -70,8 +71,7 @@ func TestOrbsCommitteeContract_orderList_AllRepIs0(t *testing.T) { midLocation := committeeSize / 2 endLocation := committeeSize - 1 for i := 1 ; i <= max ; i++ { - m.MockEnvBlockHeight(i) // to generate random seed - outputArr := _orderList(addresses, _generateSeed()) + outputArr := _orderList(addresses, _generateSeed(uint64(i))) if bytes.Equal(outputArr[0], checkAddress) { foundAtPos0++ } else if bytes.Equal(outputArr[midLocation], checkAddress){ @@ -106,8 +106,7 @@ func TestOrbsCommitteeContract_orderList_OneRepIsWorst(t *testing.T) { // run for i := 1 ; i <= max ; i++ { - m.MockEnvBlockHeight(i) // to generate random seed - outputArr := _orderList(addresses, _generateSeed()) + outputArr := _orderList(addresses, _generateSeed(uint64(i))) if bytes.Equal(outputArr[0], badAddress) { foundBadAddressInFirstPosition++ } @@ -143,8 +142,7 @@ func TestOrbsCommitteeContract_orderList_QuarterAreALittleBad(t *testing.T) { // run for i := 1 ; i <= max ; i++ { - m.MockEnvBlockHeight(i) // to generate random seed - outputArr := _orderList(addresses, _generateSeed()) + outputArr := _orderList(addresses, _generateSeed(uint64(i))) if bytes.Equal(outputArr[0], badAddress) { foundBadAddressInFirstPosition++ } @@ -162,3 +160,29 @@ func TestOrbsCommitteeContract_orderList_QuarterAreALittleBad(t *testing.T) { func requireCountToBeInRange(t testing.TB, actual, expected int) { require.InDelta(t, expected, actual, 0.05 * float64(expected), "expect (%d) to be five precent delta to (%d)", actual, expected) } + +func TestOrbsCommitteeContract_getNextOrderedCommittee(t *testing.T) { + committeeSize := 20 + addresses := makeNodeAddressArray(committeeSize) + + InServiceScope(nil, nil, func(m Mockery) { + _init() + blockHeight := 10 + + // prepare + m.MockEnvBlockHeight(blockHeight) + m.MockServiceCallMethod(elections_systemcontract.CONTRACT_NAME, elections_systemcontract.METHOD_GET_ELECTED_VALIDATORS_BY_BLOCK_HEIGHT, []interface{}{_concat(addresses)}, uint64(blockHeight)) + m.MockServiceCallMethod(elections_systemcontract.CONTRACT_NAME, elections_systemcontract.METHOD_GET_ELECTED_VALIDATORS_BY_BLOCK_HEIGHT, []interface{}{_concat(addresses)}, uint64(blockHeight+1)) + + // run + currentCommittee := _split(getOrderedCommittee()) + nextCommittee := getNextOrderedCommittee() + m.MockEnvBlockHeight(blockHeight+1) + nextCurrentCommittee := _split(getOrderedCommittee()) + + //assert + require.NotEqual(t, currentCommittee, nextCommittee) + require.ElementsMatch(t, currentCommittee, nextCommittee, "must actually be same list in diff order") + require.Equal(t, nextCommittee, nextCurrentCommittee) + }) +} diff --git a/services/processor/native/repository/Committee/index.go b/services/processor/native/repository/Committee/index.go index 9e9e9f913..0346526ed 100644 --- a/services/processor/native/repository/Committee/index.go +++ b/services/processor/native/repository/Committee/index.go @@ -20,7 +20,7 @@ const METHOD_GET_ORDERED_COMMITTEE = "getOrderedCommittee" // used with election const METHOD_GET_ORDERED_COMMITTEE_FOR_ADDRESSES = "getOrderedCommitteeForAddresses" // used before election or for testing const METHOD_UPDATE_MISSES = "updateMisses" -var PUBLIC = sdk.Export(getOrderedCommittee, getOrderedCommitteeForAddresses, getReputation, getAllCommitteeReputations, getMisses, getAllCommitteeMisses, updateMisses) +var PUBLIC = sdk.Export(getOrderedCommittee, getNextOrderedCommittee, getOrderedCommitteeForAddresses, getReputation, getAllCommitteeReputations, getMisses, getAllCommitteeMisses, updateMisses) var SYSTEM = sdk.Export(_init) var EVENTS = sdk.Export(CommitteeMemberMissed, CommitteeMemberClosedBlock)