github.com/klaytn/klaytn@v1.12.1/consensus/istanbul/validator/validator.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2017 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from quorum/consensus/istanbul/validator/validator.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package validator 22 23 import ( 24 "encoding/binary" 25 "math/rand" 26 "strconv" 27 "strings" 28 29 "github.com/klaytn/klaytn/common" 30 "github.com/klaytn/klaytn/consensus" 31 "github.com/klaytn/klaytn/consensus/istanbul" 32 "github.com/klaytn/klaytn/log" 33 ) 34 35 var logger = log.NewModuleLogger(log.ConsensusIstanbulValidator) 36 37 func New(addr common.Address) istanbul.Validator { 38 return &defaultValidator{ 39 address: addr, 40 } 41 } 42 43 func NewValidatorSet(addrs, demotedAddrs []common.Address, proposerPolicy istanbul.ProposerPolicy, subGroupSize uint64, chain consensus.ChainReader) istanbul.ValidatorSet { 44 var valSet istanbul.ValidatorSet 45 if proposerPolicy == istanbul.WeightedRandom { 46 valSet = NewWeightedCouncil(addrs, demotedAddrs, nil, nil, nil, proposerPolicy, subGroupSize, 0, 0, chain) 47 } else { 48 valSet = NewSubSet(addrs, proposerPolicy, subGroupSize) 49 } 50 51 return valSet 52 } 53 54 func NewSet(addrs []common.Address, policy istanbul.ProposerPolicy) istanbul.ValidatorSet { 55 return newDefaultSet(addrs, policy) 56 } 57 58 func NewSubSet(addrs []common.Address, policy istanbul.ProposerPolicy, subSize uint64) istanbul.ValidatorSet { 59 return newDefaultSubSet(addrs, policy, subSize) 60 } 61 62 func ExtractValidators(extraData []byte) []common.Address { 63 // get the validator addresses 64 addrs := make([]common.Address, (len(extraData) / common.AddressLength)) 65 for i := 0; i < len(addrs); i++ { 66 copy(addrs[i][:], extraData[i*common.AddressLength:]) 67 } 68 69 return addrs 70 } 71 72 // ConvertHashToSeed returns a random seed used to calculate proposer. 73 // It converts first 7.5 bytes of the given hash to int64. 74 func ConvertHashToSeed(hash common.Hash) (int64, error) { 75 // TODO-Klaytn-Istanbul: convert hash.Hex() to int64 directly without string conversion 76 hashstring := strings.TrimPrefix(hash.Hex(), "0x") 77 if len(hashstring) > 15 { 78 hashstring = hashstring[:15] 79 } 80 81 seed, err := strconv.ParseInt(hashstring, 16, 64) 82 if err != nil { 83 logger.Error("fail to make sub-list of validators", "hash", hash.Hex(), "seed", seed, "err", err) 84 return 0, err 85 } 86 return seed, nil 87 } 88 89 // SelectRandomCommittee composes a committee selecting validators randomly based on the seed value. 90 // It returns nil if the given committeeSize is bigger than validatorSize or proposer indexes are invalid. 91 func SelectRandomCommittee(validators []istanbul.Validator, committeeSize uint64, seed int64, proposerIdx int, nextProposerIdx int) []istanbul.Validator { 92 // ensure validator indexes are valid 93 if proposerIdx < 0 || nextProposerIdx < 0 || proposerIdx == nextProposerIdx { 94 logger.Error("invalid indexes of validators", "proposerIdx", proposerIdx, "nextProposerIdx", nextProposerIdx) 95 return nil 96 } 97 98 // ensure committeeSize and proposer indexes are valid 99 validatorSize := len(validators) 100 if validatorSize < int(committeeSize) || validatorSize <= proposerIdx || validatorSize <= nextProposerIdx { 101 logger.Error("invalid committee size or validator indexes", "validatorSize", validatorSize, 102 "committeeSize", committeeSize, "proposerIdx", proposerIdx, "nextProposerIdx", nextProposerIdx) 103 return nil 104 } 105 106 // it cannot be happened. just to make sure 107 if committeeSize < 2 { 108 if committeeSize == 0 { 109 logger.Error("committee size has an invalid value", "committeeSize", committeeSize) 110 return nil 111 } 112 return []istanbul.Validator{validators[proposerIdx]} 113 } 114 115 // first committee is the proposer and the second committee is the next proposer 116 committee := make([]istanbul.Validator, committeeSize) 117 committee[0] = validators[proposerIdx] 118 committee[1] = validators[nextProposerIdx] 119 120 // select the reset of committee members randomly 121 picker := rand.New(rand.NewSource(seed)) 122 pickSize := validatorSize - 2 123 indexs := make([]int, pickSize) 124 idx := 0 125 for i := 0; i < validatorSize; i++ { 126 if i != proposerIdx && i != nextProposerIdx { 127 indexs[idx] = i 128 idx++ 129 } 130 } 131 132 for i := 0; i < pickSize; i++ { 133 randIndex := picker.Intn(pickSize) 134 indexs[i], indexs[randIndex] = indexs[randIndex], indexs[i] 135 } 136 137 for i := uint64(0); i < committeeSize-2; i++ { 138 committee[i+2] = validators[indexs[i]] 139 } 140 141 return committee 142 } 143 144 // SelectRandaoCommittee composes a committee selecting validators randomly based on the mixHash. 145 // It returns nil if the given committeeSize is bigger than validatorSize. 146 func SelectRandaoCommittee(validators []istanbul.Validator, committeeSize uint64, mixHash []byte) []istanbul.Validator { 147 // it cannot be happened. just to make sure 148 if committeeSize < 2 { 149 if committeeSize == 0 { 150 logger.Error("invalid committee size", "committeeSize", committeeSize) 151 return nil 152 } 153 return validators 154 } 155 156 seed := int64(binary.BigEndian.Uint64(mixHash[:8])) 157 size := committeeSize 158 if committeeSize > uint64(len(validators)) { 159 size = uint64(len(validators)) 160 } 161 return shuffleValidators(validators, seed)[:size] 162 } 163 164 func shuffleValidators(validators istanbul.Validators, seed int64) []istanbul.Validator { 165 ret := make([]istanbul.Validator, len(validators)) 166 copy(ret, validators) 167 swap := func(x, y int) { 168 ret[x], ret[y] = ret[y], ret[x] 169 } 170 171 r := rand.New(rand.NewSource(seed)) 172 // The Fisher-Yates algorithm used in this shuffle is deterministic for a given seed. 173 r.Shuffle(len(ret), swap) 174 175 return ret 176 }