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  }