From bdcd75c24d722e365e87fce816c38895d21135ec Mon Sep 17 00:00:00 2001 From: "Victor \"Nate\" Graf" Date: Wed, 23 Oct 2019 21:24:55 -0700 Subject: [PATCH] Create shuffled round robin proposer selector and use it by default (#536) * create new shuffled round robin order policy and set as default * add Random function to contract_comm/random and utilize it to seed proposer selection * fixed typo * add todo note * change comments to be more vauge * use explicit state in call to Random * move randomness retrieval into Validators and ParentValidators functions * fix typo * log proposer policy * move round shuffled round robin up * remove overly verbose trace * add command line flag for proposer policy * fix type errors --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 9 + consensus/istanbul/backend/backend.go | 36 ++- consensus/istanbul/backend/handler.go | 1 + consensus/istanbul/config.go | 3 +- consensus/istanbul/validator.go | 28 +- consensus/istanbul/validator/default.go | 70 ++--- consensus/istanbul/validator/default_test.go | 55 ---- consensus/istanbul/validator/selectors.go | 79 ++++++ .../istanbul/validator/selectors_test.go | 239 ++++++++++++++++++ contract_comm/random/random.go | 24 ++ 12 files changed, 426 insertions(+), 120 deletions(-) create mode 100644 consensus/istanbul/validator/selectors.go create mode 100644 consensus/istanbul/validator/selectors_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b8d2f03f0754..c6ad39cec565 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -139,6 +139,7 @@ var ( configFileFlag, utils.IstanbulRequestTimeoutFlag, utils.IstanbulBlockPeriodFlag, + utils.IstanbulProposerPolicyFlag, utils.PingIPFromPacketFlag, utils.UseInMemoryDiscoverTable, utils.VersionCheckFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 9ca0cd396fd3..af9ac07b358a 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -253,6 +253,7 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ utils.IstanbulRequestTimeoutFlag, utils.IstanbulBlockPeriodFlag, + utils.IstanbulProposerPolicyFlag, }, }, } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1efae821b988..0a1d2af5c28c 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/istanbul" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" @@ -660,6 +661,11 @@ var ( Usage: "Default minimum difference between two consecutive block's timestamps in seconds", Value: eth.DefaultConfig.Istanbul.BlockPeriod, } + IstanbulProposerPolicyFlag = cli.Uint64Flag{ + Name: "istanbul.proposerpolicy", + Usage: "Default minimum difference between two consecutive block's timestamps in seconds", + Value: uint64(eth.DefaultConfig.Istanbul.ProposerPolicy), + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1166,6 +1172,9 @@ func setIstanbul(ctx *cli.Context, cfg *eth.Config) { if ctx.GlobalIsSet(IstanbulBlockPeriodFlag.Name) { cfg.Istanbul.BlockPeriod = ctx.GlobalUint64(IstanbulBlockPeriodFlag.Name) } + if ctx.GlobalIsSet(IstanbulProposerPolicyFlag.Name) { + cfg.Istanbul.ProposerPolicy = istanbul.ProposerPolicy(ctx.GlobalUint64(IstanbulProposerPolicyFlag.Name)) + } } // checkExclusive verifies that only a single isntance of the provided flags was diff --git a/consensus/istanbul/backend/backend.go b/consensus/istanbul/backend/backend.go index 1554b1c75171..a3d528ad5c9e 100644 --- a/consensus/istanbul/backend/backend.go +++ b/consensus/istanbul/backend/backend.go @@ -30,6 +30,7 @@ import ( istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core" "github.com/ethereum/go-ethereum/consensus/istanbul/validator" "github.com/ethereum/go-ethereum/contract_comm/election" + "github.com/ethereum/go-ethereum/contract_comm/random" "github.com/ethereum/go-ethereum/contract_comm/validators" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -50,6 +51,9 @@ const ( var ( // errInvalidSigningFn is returned when the consensus signing function is invalid. errInvalidSigningFn = errors.New("invalid signing function for istanbul messages") + + // errNoBlockHeader is returned when the requested block header could not be found. + errNoBlockHeader = errors.New("failed to retrieve block header") ) // Entries for the recent announce messages @@ -156,7 +160,15 @@ func (sb *Backend) Close() error { // Validators implements istanbul.Backend.Validators func (sb *Backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet { - return sb.getValidators(proposal.Number().Uint64(), proposal.Hash()) + valSet := sb.getValidators(proposal.Number().Uint64(), proposal.Hash()) + + seed, err := sb.validatorRandomnessAtBlockNumber(proposal.Number().Uint64(), proposal.Hash()) + if err != nil { + sb.logger.Error("Failed to set randomness for proposer selection", "number", proposal.Number().Uint64(), "hash", proposal.Hash(), "error", err) + } + valSet.SetRandomness(seed) + + return valSet } func (sb *Backend) GetValidators(blockNumber *big.Int, headerHash common.Hash) []istanbul.Validator { @@ -375,6 +387,18 @@ func (sb *Backend) getNewValidatorSet(header *types.Header, state *state.StateDB return newValSet, err } +func (sb *Backend) validatorRandomnessAtBlockNumber(number uint64, hash common.Hash) (common.Hash, error) { + header := sb.chain.GetHeader(hash, number) + if header == nil { + return common.Hash{}, errNoBlockHeader + } + state, err := sb.stateAt(header.Hash()) + if err != nil { + return common.Hash{}, err + } + return random.Random(header, state) +} + func (sb *Backend) verifyValSetDiff(proposal istanbul.Proposal, block *types.Block, state *state.StateDB) error { header := block.Header() @@ -471,7 +495,15 @@ func (sb *Backend) GetProposer(number uint64) common.Address { // ParentValidators implements istanbul.Backend.GetParentValidators func (sb *Backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet { if block, ok := proposal.(*types.Block); ok { - return sb.getValidators(block.Number().Uint64()-1, block.ParentHash()) + valSet := sb.getValidators(block.Number().Uint64()-1, block.ParentHash()) + + seed, err := sb.validatorRandomnessAtBlockNumber(proposal.Number().Uint64()-1, block.ParentHash()) + if err != nil { + sb.logger.Error("Failed to set randomness for proposer selection", "number", proposal.Number().Uint64()-1, "hash", block.ParentHash(), "error", err) + } + valSet.SetRandomness(seed) + + return valSet } return validator.NewSet(nil, sb.config.ProposerPolicy) } diff --git a/consensus/istanbul/backend/handler.go b/consensus/istanbul/backend/handler.go index d8516a615574..45213470de22 100644 --- a/consensus/istanbul/backend/handler.go +++ b/consensus/istanbul/backend/handler.go @@ -117,6 +117,7 @@ func (sb *Backend) NewChainHead() error { } else { sb.logger.Info("Validators Election Results: Node IN ValidatorSet") } + // Establish connections to new peers and tear down connections to old ones. go sb.RefreshValPeers(valset) } diff --git a/consensus/istanbul/config.go b/consensus/istanbul/config.go index b9d8d0d3dbde..dd10af039e21 100644 --- a/consensus/istanbul/config.go +++ b/consensus/istanbul/config.go @@ -21,6 +21,7 @@ type ProposerPolicy uint64 const ( RoundRobin ProposerPolicy = iota Sticky + ShuffledRoundRobin ) type Config struct { @@ -33,6 +34,6 @@ type Config struct { var DefaultConfig = &Config{ RequestTimeout: 3000, BlockPeriod: 1, - ProposerPolicy: RoundRobin, + ProposerPolicy: ShuffledRoundRobin, Epoch: 30000, } diff --git a/consensus/istanbul/validator.go b/consensus/istanbul/validator.go index 3d58986e8070..4be987a5400b 100644 --- a/consensus/istanbul/validator.go +++ b/consensus/istanbul/validator.go @@ -77,9 +77,23 @@ type Validators []Validator type ValidatorSet interface { // Calculate the proposer CalcProposer(lastProposer common.Address, round uint64) + // Get current proposer + GetProposer() Validator + // Check whether the validator with given address is the current proposer + IsProposer(address common.Address) bool + // Policy by which this selector chooses proposers + Policy() ProposerPolicy + // Sets the randomness for use in the proposer policy + SetRandomness(seed common.Hash) + // Return the validator size PaddedSize() int Size() int + // Get the maximum number of faulty nodes + F() int + // Get the minimum quorum size + MinQuorumSize() int + // Return the validator array List() []Validator // Return the validator array without holes @@ -90,24 +104,16 @@ type ValidatorSet interface { GetByIndex(i uint64) Validator // Get validator by given address GetByAddress(addr common.Address) (int, Validator) - // Get current proposer - GetProposer() Validator - // Check whether the validator with given address is a proposer - IsProposer(address common.Address) bool + // Add validators AddValidators(validators []ValidatorData) bool // Remove validators RemoveValidators(removedValidators *big.Int) bool // Copy validator set Copy() ValidatorSet - // Get the maximum number of faulty nodes - F() int - // Get proposer policy - Policy() ProposerPolicy - // Get the minimum quorum size - MinQuorumSize() int } // ---------------------------------------------------------------------------- -type ProposalSelector func(ValidatorSet, common.Address, uint64) Validator +// Returns the block proposer for a round given the last proposer, round number, and randomness. +type ProposerSelector func(ValidatorSet, common.Address, uint64, common.Hash) Validator diff --git a/consensus/istanbul/validator/default.go b/consensus/istanbul/validator/default.go index 8549c5c32459..938bbaabfc8c 100644 --- a/consensus/istanbul/validator/default.go +++ b/consensus/istanbul/validator/default.go @@ -17,6 +17,7 @@ package validator import ( + "fmt" "math" "math/big" "reflect" @@ -51,7 +52,8 @@ type defaultSet struct { proposer istanbul.Validator validatorMu sync.RWMutex - selector istanbul.ProposalSelector + selector istanbul.ProposerSelector + randomness common.Hash } func newDefaultSet(validators []istanbul.ValidatorData, policy istanbul.ProposerPolicy) *defaultSet { @@ -67,9 +69,17 @@ func newDefaultSet(validators []istanbul.ValidatorData, policy istanbul.Proposer if valSet.Size() > 0 { valSet.proposer = valSet.GetByIndex(0) } - valSet.selector = roundRobinProposer - if policy == istanbul.Sticky { - valSet.selector = stickyProposer + + switch policy { + case istanbul.Sticky: + valSet.selector = StickyProposer + case istanbul.RoundRobin: + valSet.selector = RoundRobinProposer + case istanbul.ShuffledRoundRobin: + valSet.selector = ShuffledRoundRobinProposer + default: + // Programming error. + panic(fmt.Sprintf("unknown proposer selection policy: %v", policy)) } return valSet @@ -154,51 +164,7 @@ func (valSet *defaultSet) IsProposer(address common.Address) bool { func (valSet *defaultSet) CalcProposer(lastProposer common.Address, round uint64) { valSet.validatorMu.RLock() defer valSet.validatorMu.RUnlock() - valSet.proposer = valSet.selector(valSet, lastProposer, round) -} - -func calcSeed(valSet istanbul.ValidatorSet, proposer common.Address, round uint64) uint64 { - offset := 0 - if idx := valSet.GetFilteredIndex(proposer); idx >= 0 { - offset = idx - } - return uint64(offset) + round -} - -func emptyAddress(addr common.Address) bool { - return addr == common.Address{} -} - -func roundRobinProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64) istanbul.Validator { - if valSet.Size() == 0 { - return nil - } - seed := uint64(0) - if emptyAddress(proposer) { - seed = round - } else { - seed = calcSeed(valSet, proposer, round) + 1 - } - - filteredList := valSet.FilteredList() - pick := seed % uint64(valSet.Size()) - return filteredList[pick] -} - -func stickyProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64) istanbul.Validator { - if valSet.Size() == 0 { - return nil - } - seed := uint64(0) - if emptyAddress(proposer) { - seed = round - } else { - seed = calcSeed(valSet, proposer, round) - } - - filteredList := valSet.FilteredList() - pick := seed % uint64(valSet.Size()) - return filteredList[pick] + valSet.proposer = valSet.selector(valSet, lastProposer, round, valSet.randomness) } func (valSet *defaultSet) AddValidators(validators []istanbul.ValidatorData) bool { @@ -278,8 +244,10 @@ func (valSet *defaultSet) Copy() istanbul.ValidatorSet { func (valSet *defaultSet) F() int { return int(math.Ceil(float64(valSet.Size())/3)) - 1 } -func (valSet *defaultSet) Policy() istanbul.ProposerPolicy { return valSet.policy } - func (valSet *defaultSet) MinQuorumSize() int { return int(math.Ceil(float64(2*valSet.Size()) / 3)) } + +func (valSet *defaultSet) Policy() istanbul.ProposerPolicy { return valSet.policy } + +func (valSet *defaultSet) SetRandomness(seed common.Hash) { valSet.randomness = seed } diff --git a/consensus/istanbul/validator/default_test.go b/consensus/istanbul/validator/default_test.go index 3cde8cd03d93..49652fb33dee 100644 --- a/consensus/istanbul/validator/default_test.go +++ b/consensus/istanbul/validator/default_test.go @@ -36,7 +36,6 @@ func TestValidatorSet(t *testing.T) { testNewValidatorSet(t) testNormalValSet(t) testEmptyValSet(t) - testStickyProposer(t) testAddAndRemoveValidator(t) testQuorumSizes(t) } @@ -102,26 +101,6 @@ func testNormalValSet(t *testing.T) { if _, val := valSet.GetByAddress(invalidAddr); val != nil { t.Errorf("validator mismatch: have %v, want nil", val) } - // test get proposer - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { - t.Errorf("proposer mismatch: have %v, want %v", val, val1) - } - // test calculate proposer - lastProposer := addr1 - valSet.CalcProposer(lastProposer, uint64(0)) - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { - t.Errorf("proposer mismatch: have %v, want %v", val, val2) - } - valSet.CalcProposer(lastProposer, uint64(3)) - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { - t.Errorf("proposer mismatch: have %v, want %v", val, val1) - } - // test empty last proposer - lastProposer = common.Address{} - valSet.CalcProposer(lastProposer, uint64(3)) - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { - t.Errorf("proposer mismatch: have %v, want %v", val, val2) - } } func testEmptyValSet(t *testing.T) { @@ -196,40 +175,6 @@ func testAddAndRemoveValidator(t *testing.T) { } } -func testStickyProposer(t *testing.T) { - b1 := common.Hex2Bytes(testAddress) - b2 := common.Hex2Bytes(testAddress2) - addr1 := common.BytesToAddress(b1) - addr2 := common.BytesToAddress(b2) - val1 := New(addr1, []byte{}) - val2 := New(addr2, []byte{}) - - validators, _ := istanbul.CombineIstanbulExtraToValidatorData([]common.Address{addr1, addr2}, [][]byte{{}, {}}) - valSet := newDefaultSet(validators, istanbul.Sticky) - - // test get proposer - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { - t.Errorf("proposer mismatch: have %v, want %v", val, val1) - } - // test calculate proposer - lastProposer := addr1 - valSet.CalcProposer(lastProposer, uint64(0)) - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val1) { - t.Errorf("proposer mismatch: have %v, want %v", val, val1) - } - - valSet.CalcProposer(lastProposer, uint64(1)) - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { - t.Errorf("proposer mismatch: have %v, want %v", val, val2) - } - // test empty last proposer - lastProposer = common.Address{} - valSet.CalcProposer(lastProposer, uint64(3)) - if val := valSet.GetProposer(); !reflect.DeepEqual(val, val2) { - t.Errorf("proposer mismatch: have %v, want %v", val, val2) - } -} - func generateValidators(n int) ([]istanbul.ValidatorData, [][]byte) { vals := make([]istanbul.ValidatorData, 0) keys := make([][]byte, 0) diff --git a/consensus/istanbul/validator/selectors.go b/consensus/istanbul/validator/selectors.go new file mode 100644 index 000000000000..e34a64608aa2 --- /dev/null +++ b/consensus/istanbul/validator/selectors.go @@ -0,0 +1,79 @@ +// Copyright 2019 The Celo Authors +// This file is part of the celo library. +// +// The celo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The celo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the celo library. If not, see . + +package validator + +import ( + "encoding/binary" + "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +func proposerIndex(valSet istanbul.ValidatorSet, proposer common.Address) uint64 { + if idx := valSet.GetFilteredIndex(proposer); idx >= 0 { + return uint64(idx) + } + return 0 +} + +// TODO: Pull ordering from smart contract and deprecate this function. +func randFromHash(hash common.Hash) *rand.Rand { + // Reduce the hash to 64 bits to use as the seed. + var seed uint64 + for i := 0; i < common.HashLength; i += 8 { + seed ^= binary.BigEndian.Uint64(hash[i : i+8]) + } + return rand.New(rand.NewSource(int64(seed))) +} + +// ShuffledRoundRobinProposer selects the next proposer with a round robin strategy according to a shuffled order. +func ShuffledRoundRobinProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64, seed common.Hash) istanbul.Validator { + if valSet.Size() == 0 { + return nil + } + shuffle := randFromHash(seed).Perm(valSet.Size()) + idx := round + if proposer != (common.Address{}) { + idx += proposerIndex(valSet, proposer) + 1 + } + return valSet.FilteredList()[shuffle[idx%uint64(valSet.Size())]] +} + +// RoundRobinProposer selects the next proposer with a round robin strategy according to storage order. +func RoundRobinProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64, _ common.Hash) istanbul.Validator { + if valSet.Size() == 0 { + return nil + } + idx := round + if proposer != (common.Address{}) { + idx += proposerIndex(valSet, proposer) + 1 + } + return valSet.FilteredList()[idx%uint64(valSet.Size())] +} + +// StickyProposer selects the next proposer with a sticky strategy, advancing on round change. +func StickyProposer(valSet istanbul.ValidatorSet, proposer common.Address, round uint64, _ common.Hash) istanbul.Validator { + if valSet.Size() == 0 { + return nil + } + idx := round + if proposer != (common.Address{}) { + idx += proposerIndex(valSet, proposer) + } + return valSet.FilteredList()[idx%uint64(valSet.Size())] +} diff --git a/consensus/istanbul/validator/selectors_test.go b/consensus/istanbul/validator/selectors_test.go new file mode 100644 index 000000000000..498e640a006c --- /dev/null +++ b/consensus/istanbul/validator/selectors_test.go @@ -0,0 +1,239 @@ +// Copyright 2019 The Celo Authors +// This file is part of the celo library. +// +// The celo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The celo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the celo library. If not, see . + +package validator + +import ( + "fmt" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/istanbul" +) + +var testAddresses = []string{ + "0000000000000000000000000000000000000001", + "0000000000000000000000000000000000000002", + "0000000000000000000000000000000000000003", + "0000000000000000000000000000000000000004", + "0000000000000000000000000000000000000005", +} + +func TestStickyProposer(t *testing.T) { + var addrs []common.Address + var validators []istanbul.Validator + for _, strAddr := range testAddresses { + addr := common.HexToAddress(strAddr) + addrs = append(addrs, addr) + validators = append(validators, New(addr, nil)) + } + + v, err := istanbul.CombineIstanbulExtraToValidatorData(addrs, make([][]byte, len(addrs))) + if err != nil { + t.Fatalf("CombineIstanbulExtraToValidatorData(...): %v", err) + } + valSet := newDefaultSet(v, istanbul.Sticky) + + cases := []struct { + lastProposer common.Address + round uint64 + want istanbul.Validator + }{{ + lastProposer: addrs[0], + round: 0, + want: validators[0], + }, { + lastProposer: addrs[0], + round: 1, + want: validators[1], + }, { + lastProposer: addrs[0], + round: 2, + want: validators[2], + }, { + lastProposer: addrs[2], + round: 2, + want: validators[4], + }, { + lastProposer: addrs[2], + round: 3, + want: validators[0], + }, { + lastProposer: common.Address{}, + round: 3, + want: validators[3], + }} + + t.Run("initial", func(t *testing.T) { + if val := valSet.GetProposer(); !reflect.DeepEqual(val, validators[0]) { + t.Errorf("proposer mismatch: got %v, want %v", val, validators[0]) + } + }) + + for i, c := range cases { + t.Run(fmt.Sprintf("case:%d", i), func(t *testing.T) { + t.Logf("CalcProposer(%s, %d)", c.lastProposer.String(), c.round) + valSet.CalcProposer(c.lastProposer, c.round) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, c.want) { + t.Errorf("proposer mismatch: have %v, want %v", val, c.want) + } + }) + } +} + +func TestRoundRobinProposer(t *testing.T) { + var addrs []common.Address + var validators []istanbul.Validator + for _, strAddr := range testAddresses { + addr := common.HexToAddress(strAddr) + addrs = append(addrs, addr) + validators = append(validators, New(addr, nil)) + } + + v, err := istanbul.CombineIstanbulExtraToValidatorData(addrs, make([][]byte, len(addrs))) + if err != nil { + t.Fatalf("CombineIstanbulExtraToValidatorData(...): %v", err) + } + valSet := newDefaultSet(v, istanbul.RoundRobin) + + cases := []struct { + lastProposer common.Address + round uint64 + want istanbul.Validator + }{{ + lastProposer: addrs[0], + round: 0, + want: validators[1], + }, { + lastProposer: addrs[0], + round: 1, + want: validators[2], + }, { + lastProposer: addrs[0], + round: 2, + want: validators[3], + }, { + lastProposer: addrs[2], + round: 2, + want: validators[0], + }, { + lastProposer: addrs[2], + round: 3, + want: validators[1], + }, { + lastProposer: common.Address{}, + round: 3, + want: validators[3], + }} + + t.Run("initial", func(t *testing.T) { + if val := valSet.GetProposer(); !reflect.DeepEqual(val, validators[0]) { + t.Errorf("proposer mismatch: got %v, want %v", val, validators[0]) + } + }) + + for i, c := range cases { + t.Run(fmt.Sprintf("case:%d", i), func(t *testing.T) { + t.Logf("CalcProposer(%s, %d)", c.lastProposer.String(), c.round) + valSet.CalcProposer(c.lastProposer, c.round) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, c.want) { + t.Errorf("proposer mismatch: have %v, want %v", val, c.want) + } + }) + } +} + +func TestShuffledRoundRobinProposer(t *testing.T) { + var addrs []common.Address + var validators []istanbul.Validator + for _, strAddr := range testAddresses { + addr := common.HexToAddress(strAddr) + addrs = append(addrs, addr) + validators = append(validators, New(addr, nil)) + } + + v, err := istanbul.CombineIstanbulExtraToValidatorData(addrs, make([][]byte, len(addrs))) + if err != nil { + t.Fatalf("CombineIstanbulExtraToValidatorData(...): %v", err) + } + valSet := newDefaultSet(v, istanbul.ShuffledRoundRobin) + + testSeed := common.HexToHash("f36aa9716b892ec8") + cases := []struct { + lastProposer common.Address + round uint64 + seed common.Hash + want istanbul.Validator + }{{ + lastProposer: addrs[0], + round: 0, + want: validators[2], + }, { + lastProposer: addrs[0], + round: 1, + want: validators[3], + }, { + lastProposer: addrs[0], + round: 2, + want: validators[0], + }, { + lastProposer: addrs[2], + round: 2, + want: validators[4], + }, { + lastProposer: addrs[2], + round: 3, + want: validators[2], + }, { + lastProposer: addrs[0], + round: 0, + seed: testSeed, + want: validators[0], + }, { + lastProposer: addrs[0], + round: 1, + seed: testSeed, + want: validators[4], + }, { + lastProposer: addrs[0], + round: 2, + seed: testSeed, + want: validators[2], + }, { + lastProposer: common.Address{}, + round: 3, + want: validators[0], + }} + + t.Run("initial", func(t *testing.T) { + if val := valSet.GetProposer(); !reflect.DeepEqual(val, validators[0]) { + t.Errorf("proposer mismatch: got %v, want %v", val, validators[0]) + } + }) + + for i, c := range cases { + t.Run(fmt.Sprintf("case:%d", i), func(t *testing.T) { + t.Logf("SetRandomness(%s)", c.seed.String()) + valSet.SetRandomness(c.seed) + t.Logf("CalcProposer(%s, %d)", c.lastProposer.String(), c.round) + valSet.CalcProposer(c.lastProposer, c.round) + if val := valSet.GetProposer(); !reflect.DeepEqual(val, c.want) { + t.Errorf("proposer mismatch: have %v, want %v", val, c.want) + } + }) + } +} diff --git a/contract_comm/random/random.go b/contract_comm/random/random.go index 5c03016c8ea6..235ac218c5aa 100644 --- a/contract_comm/random/random.go +++ b/contract_comm/random/random.go @@ -83,6 +83,22 @@ const ( "stateMutability": "view", "type": "function" } +]` + randomAbi = `[ + { + "constant": true, + "inputs": [], + "name": "random", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } ]` ) @@ -90,6 +106,7 @@ var ( revealAndCommitFuncABI, _ = abi.JSON(strings.NewReader(revealAndCommitABI)) commitmentsFuncABI, _ = abi.JSON(strings.NewReader(commitmentsAbi)) computeCommitmentFuncABI, _ = abi.JSON(strings.NewReader(computeCommitmentAbi)) + randomFuncABI, _ = abi.JSON(strings.NewReader(randomAbi)) zeroValue = common.Big0 dbRandomnessPrefix = []byte("db-randomness-prefix") ) @@ -169,3 +186,10 @@ func RevealAndCommit(randomness, newCommitment common.Hash, proposer common.Addr _, err := contract_comm.MakeCall(params.RandomRegistryId, revealAndCommitFuncABI, "revealAndCommit", args, nil, params.MaxGasForRevealAndCommit, zeroValue, header, state) return err } + +// Random performs an internal call to the EVM to retrieve the current randomness from the official Random contract. +func Random(header *types.Header, state vm.StateDB) (common.Hash, error) { + randomness := common.Hash{} + _, err := contract_comm.MakeStaticCall(params.RandomRegistryId, randomFuncABI, "random", []interface{}{}, &randomness, params.MaxGasForComputeCommitment, header, state) + return randomness, err +}