// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see . package istanbul import ( "math/big" "strings" "sync" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" "github.com/naoina/toml" ) type ProposerPolicyId uint64 const ( RoundRobin ProposerPolicyId = iota Sticky ) // ProposerPolicy represents the Validator Proposer Policy type ProposerPolicy struct { Id ProposerPolicyId // Could be RoundRobin or Sticky By ValidatorSortByFunc // func that defines how the ValidatorSet should be sorted registry []ValidatorSet // Holds the ValidatorSet for a given block height registryMU *sync.Mutex // Mutex to lock access to changes to Registry } // NewRoundRobinProposerPolicy returns a RoundRobin ProposerPolicy with ValidatorSortByString as default sort function func NewRoundRobinProposerPolicy() *ProposerPolicy { return NewProposerPolicy(RoundRobin) } // NewStickyProposerPolicy return a Sticky ProposerPolicy with ValidatorSortByString as default sort function func NewStickyProposerPolicy() *ProposerPolicy { return NewProposerPolicy(Sticky) } func NewProposerPolicy(id ProposerPolicyId) *ProposerPolicy { return NewProposerPolicyByIdAndSortFunc(id, ValidatorSortByString()) } func NewProposerPolicyByIdAndSortFunc(id ProposerPolicyId, by ValidatorSortByFunc) *ProposerPolicy { return &ProposerPolicy{Id: id, By: by, registryMU: new(sync.Mutex)} } type proposerPolicyToml struct { Id ProposerPolicyId } func (p *ProposerPolicy) MarshalTOML() (interface{}, error) { if p == nil { return nil, nil } pp := &proposerPolicyToml{Id: p.Id} data, err := toml.Marshal(pp) if err != nil { return nil, err } return string(data), nil } func (p *ProposerPolicy) UnmarshalTOML(decode func(interface{}) error) error { var innerToml string err := decode(&innerToml) if err != nil { return err } var pp proposerPolicyToml err = toml.Unmarshal([]byte(innerToml), &pp) if err != nil { return err } p.Id = pp.Id p.By = ValidatorSortByString() return nil } // Use sets the ValidatorSortByFunc for the given ProposerPolicy and sorts the validatorSets according to it func (p *ProposerPolicy) Use(v ValidatorSortByFunc) { p.By = v for _, validatorSet := range p.registry { validatorSet.SortValidators() } } // RegisterValidatorSet stores the given ValidatorSet in the policy registry func (p *ProposerPolicy) RegisterValidatorSet(valSet ValidatorSet) { p.registryMU.Lock() defer p.registryMU.Unlock() if len(p.registry) == 0 { p.registry = []ValidatorSet{valSet} } else { p.registry = append(p.registry, valSet) } } // ClearRegistry removes any ValidatorSet from the ProposerPolicy registry func (p *ProposerPolicy) ClearRegistry() { p.registryMU.Lock() defer p.registryMU.Unlock() p.registry = nil } type Config struct { RequestTimeout uint64 `toml:",omitempty"` // The timeout for each Istanbul round in milliseconds. BlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between two consecutive block's timestamps in second EmptyBlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between a block and empty block's timestamps in second ProposerPolicy *ProposerPolicy `toml:",omitempty"` // The policy for proposer selection Epoch uint64 `toml:",omitempty"` // The number of blocks after which to checkpoint and reset the pending votes Ceil2Nby3Block *big.Int `toml:",omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)] AllowedFutureBlockTime uint64 `toml:",omitempty"` // Max time (in seconds) from current time allowed for blocks, before they're considered future blocks TestQBFTBlock *big.Int `toml:",omitempty"` // Fork block at which block confirmations are done using qbft consensus instead of ibft BeneficiaryMode *string `toml:",omitempty"` // Mode for setting the beneficiary, either: list, besu, validators (beneficiary list is the list of validators) BlockReward *math.HexOrDecimal256 `toml:",omitempty"` // Reward MiningBeneficiary *common.Address `toml:",omitempty"` // Wallet address that benefits at every new block (besu mode) ValidatorContract common.Address `toml:",omitempty"` Validators []common.Address `toml:",omitempty"` ValidatorSelectionMode *string `toml:",omitempty"` Client bind.ContractCaller `toml:",omitempty"` Transitions []params.Transition } var DefaultConfig = &Config{ RequestTimeout: 10000, BlockPeriod: 5, EmptyBlockPeriod: 0, ProposerPolicy: NewRoundRobinProposerPolicy(), Epoch: 30000, Ceil2Nby3Block: big.NewInt(0), AllowedFutureBlockTime: 0, TestQBFTBlock: big.NewInt(0), } // QBFTBlockNumber returns the qbftBlock fork block number, returns -1 if qbftBlock is not defined func (c Config) QBFTBlockNumber() int64 { if c.TestQBFTBlock == nil { return -1 } return c.TestQBFTBlock.Int64() } // IsQBFTConsensusAt checks if qbft consensus is enabled for the block height identified by the given header func (c *Config) IsQBFTConsensusAt(blockNumber *big.Int) bool { if c.TestQBFTBlock != nil { if c.TestQBFTBlock.Uint64() == 0 { return true } if blockNumber.Cmp(c.TestQBFTBlock) >= 0 { return true } } result := false if blockNumber == nil { blockNumber = big.NewInt(0) } c.getTransitionValue(blockNumber, func(t params.Transition) { if strings.EqualFold(t.Algorithm, params.QBFT) { result = true } }) return result } func (c Config) GetConfig(blockNumber *big.Int) Config { newConfig := c c.getTransitionValue(blockNumber, func(transition params.Transition) { if transition.RequestTimeoutSeconds != 0 { newConfig.RequestTimeout = transition.RequestTimeoutSeconds } if transition.EpochLength != 0 { newConfig.Epoch = transition.EpochLength } if transition.BlockPeriodSeconds != 0 { newConfig.BlockPeriod = transition.BlockPeriodSeconds } if transition.EmptyBlockPeriodSeconds != nil { newConfig.EmptyBlockPeriod = *transition.EmptyBlockPeriodSeconds } if transition.BeneficiaryMode != nil { newConfig.BeneficiaryMode = transition.BeneficiaryMode } if transition.BlockReward != nil { newConfig.BlockReward = transition.BlockReward } if transition.MiningBeneficiary != nil { newConfig.MiningBeneficiary = transition.MiningBeneficiary } if transition.ValidatorSelectionMode != "" { newConfig.ValidatorSelectionMode = &transition.ValidatorSelectionMode } if transition.ValidatorContractAddress != (common.Address{}) { newConfig.ValidatorContract = transition.ValidatorContractAddress } if len(transition.Validators) > 0 { newConfig.Validators = transition.Validators } }) return newConfig } func (c Config) GetValidatorContractAddress(blockNumber *big.Int) common.Address { validatorContractAddress := c.ValidatorContract c.getTransitionValue(blockNumber, func(transition params.Transition) { if (transition.ValidatorContractAddress != common.Address{}) { validatorContractAddress = transition.ValidatorContractAddress } }) return validatorContractAddress } func (c Config) GetValidatorSelectionMode(blockNumber *big.Int) string { mode := params.BlockHeaderMode if c.ValidatorSelectionMode != nil { mode = *c.ValidatorSelectionMode } c.getTransitionValue(blockNumber, func(transition params.Transition) { if transition.ValidatorSelectionMode != "" { mode = transition.ValidatorSelectionMode } }) return mode } func (c Config) GetValidatorsAt(blockNumber *big.Int) []common.Address { if blockNumber.Cmp(big.NewInt(0)) == 0 && len(c.Validators) > 0 { return c.Validators } if blockNumber != nil && c.Transitions != nil { for i := 0; i < len(c.Transitions) && c.Transitions[i].Block.Cmp(blockNumber) == 0; i++ { return c.Transitions[i].Validators } } //Note! empty means we will get the valset from previous block header which contains votes, validators etc return []common.Address{} } func (c Config) Get2FPlus1Enabled(blockNumber *big.Int) bool { twoFPlusOneEnabled := false c.getTransitionValue(blockNumber, func(transition params.Transition) { if transition.TwoFPlusOneEnabled != nil { twoFPlusOneEnabled = *transition.TwoFPlusOneEnabled } }) return twoFPlusOneEnabled } func (c *Config) getTransitionValue(num *big.Int, callback func(transition params.Transition)) { if c != nil && num != nil && c.Transitions != nil { for i := 0; i < len(c.Transitions) && c.Transitions[i].Block.Cmp(num) <= 0; i++ { callback(c.Transitions[i]) } } }