Skip to content
This repository was archived by the owner on Jun 5, 2026. It is now read-only.

Commit ca6d0cc

Browse files
add list of validators, propagate selection mode (#1530)
* add list of validators, propagate selection mode * Check that genesis only has a single selection mode * Override blockheader valdiators at a particular height * remove invalid validation - needs to be done at startup * change validators list of blockheader on specific transition Co-authored-by: Antony Denyer <git@antonydenyer.co.uk>
1 parent 416e63a commit ca6d0cc

8 files changed

Lines changed: 92 additions & 50 deletions

File tree

cmd/utils/flags.go

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,23 +2513,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, useExist bool) (chain *core.B
25132513
}
25142514
if config.Clique != nil {
25152515
engine = clique.New(config.Clique, chainDb)
2516-
} else if config.QBFT != nil {
2517-
log.Debug("setBFTConfig", "proto", "qbft")
2518-
qbftConfig := setBFTConfig(config.QBFT.BFTConfig)
2519-
qbftConfig.TestQBFTBlock = big.NewInt(0)
2520-
qbftConfig.Transitions = config.Transitions
2521-
if config.QBFT.ValidatorContractAddress != (common.Address{}) {
2522-
qbftConfig.ValidatorContract = config.QBFT.ValidatorContractAddress
2523-
}
2524-
qbftConfig.Client = ethclient.NewClient(client)
2525-
engine = istanbulBackend.New(qbftConfig, stack.GetNodeKey(), chainDb)
2526-
} else if config.IBFT != nil {
2527-
log.Debug("setBFTConfig", "proto", "ibft")
2528-
ibftConfig := setBFTConfig(config.IBFT.BFTConfig)
2529-
ibftConfig.TestQBFTBlock = nil
2530-
ibftConfig.Transitions = config.Transitions
2531-
ibftConfig.Client = ethclient.NewClient(client)
2532-
engine = istanbulBackend.New(ibftConfig, stack.GetNodeKey(), chainDb)
25332516
} else if config.Istanbul != nil {
25342517
log.Warn("WARNING: The attribute config.istanbul is deprecated and will be removed in the future, please use config.ibft on genesis file")
25352518
// for IBFT
@@ -2546,9 +2529,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, useExist bool) (chain *core.B
25462529
} else if config.IBFT != nil {
25472530
ibftConfig := setBFTConfig(config.IBFT.BFTConfig)
25482531
ibftConfig.TestQBFTBlock = nil
2549-
if config.Transitions != nil && len(config.Transitions) != 0 {
2550-
ibftConfig.Transitions = config.Transitions
2551-
}
2532+
ibftConfig.Transitions = config.Transitions
25522533
ibftConfig.Client = ethclient.NewClient(client)
25532534
engine = istanbulBackend.New(ibftConfig, stack.GetNodeKey(), chainDb)
25542535
} else if config.QBFT != nil {
@@ -2558,12 +2539,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node, useExist bool) (chain *core.B
25582539
qbftConfig.BeneficiaryList = config.QBFT.BeneficiaryList // list mode
25592540
qbftConfig.MiningBeneficiary = config.QBFT.MiningBeneficiary // auto (undefined mode) and besu mode
25602541
qbftConfig.TestQBFTBlock = big.NewInt(0)
2561-
if config.Transitions != nil && len(config.Transitions) != 0 {
2562-
qbftConfig.Transitions = config.Transitions
2563-
}
2564-
if config.QBFT.ValidatorContractAddress != (common.Address{}) {
2565-
qbftConfig.ValidatorContract = config.QBFT.ValidatorContractAddress
2566-
}
2542+
qbftConfig.Transitions = config.Transitions
2543+
qbftConfig.ValidatorContract = config.QBFT.ValidatorContractAddress
2544+
qbftConfig.ValidatorSelectionMode = config.QBFT.ValidatorSelectionMode
2545+
qbftConfig.Validators = config.QBFT.Validators
25672546
qbftConfig.Client = ethclient.NewClient(client)
25682547
engine = istanbulBackend.New(qbftConfig, stack.GetNodeKey(), chainDb)
25692548
} else if config.IsQuorum {

consensus/istanbul/backend/engine.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,10 @@ func (sb *Backend) snapshot(chain consensus.ChainHeaderReader, number uint64, ha
367367
}
368368

369369
var validators []common.Address
370-
targetBlockHeight := new(big.Int).SetUint64(number)
371-
validatorContract := sb.config.GetValidatorContractAddress(targetBlockHeight)
372-
if validatorContract != (common.Address{}) && sb.config.GetValidatorSelectionMode(targetBlockHeight) == params.ContractMode {
373-
sb.logger.Info("Initialising snap with contract validators", "address", validatorContract, "client", sb.config.Client)
370+
validatorContract := sb.config.GetValidatorContractAddress(big.NewInt(0))
371+
if validatorContract != (common.Address{}) && sb.config.GetValidatorSelectionMode(big.NewInt(0)) == params.ContractMode {
374372

375-
validatorContractCaller, err := contract.NewValidatorContractInterfaceCaller(sb.config.GetValidatorContractAddress(targetBlockHeight), sb.config.Client)
373+
validatorContractCaller, err := contract.NewValidatorContractInterfaceCaller(validatorContract, sb.config.Client)
376374

377375
if err != nil {
378376
return nil, fmt.Errorf("invalid smart contract in genesis alloc: %w", err)
@@ -383,16 +381,24 @@ func (sb *Backend) snapshot(chain consensus.ChainHeaderReader, number uint64, ha
383381
BlockNumber: big.NewInt(0),
384382
}
385383
validators, err = validatorContractCaller.GetValidators(&opts)
384+
log.Trace("BFT: Initialising snap with contract validators", "address", validatorContract, "validators", validators)
386385
if err != nil {
387386
log.Error("BFT: invalid smart contract in genesis alloc", "err", err)
388387
return nil, err
389388
}
390389
} else {
391-
var err error
392-
validators, err = sb.EngineForBlockNumber(big.NewInt(0)).ExtractGenesisValidators(genesis)
393-
if err != nil {
394-
sb.logger.Error("BFT: invalid genesis block", "err", err)
395-
return nil, err
390+
validatorsFromConfig := sb.config.GetValidatorsAt(big.NewInt(0))
391+
if len(validatorsFromConfig) > 0 {
392+
validators = validatorsFromConfig
393+
log.Info("BFT: Initialising snap with config validators", "validators", validators)
394+
} else {
395+
var err error
396+
validators, err = sb.EngineForBlockNumber(big.NewInt(0)).ExtractGenesisValidators(genesis)
397+
log.Info("BFT: Initialising snap with extradata", "validators", validators)
398+
if err != nil {
399+
log.Error("BFT: invalid genesis block", "err", err)
400+
return nil, err
401+
}
396402
}
397403
}
398404

@@ -459,6 +465,12 @@ func (sb *Backend) snapshot(chain consensus.ChainHeaderReader, number uint64, ha
459465
sb.logger.Trace("Fetched validators from smart contract", "validators", validators)
460466
valSet := validator.NewSet(validators, sb.config.ProposerPolicy)
461467
snap.ValSet = valSet
468+
} else if validatorsFromTransitions := sb.config.GetValidatorsAt(targetBlockHeight); len(validatorsFromTransitions) > 0 && sb.config.GetValidatorSelectionMode(targetBlockHeight) == params.BlockHeaderMode {
469+
//Note! we only want to set this once at this block height. Subsequent blocks will be propagated with the same
470+
// validator as they are copied into the block header on the next block. Then normal voting can take place
471+
// again.
472+
valSet := validator.NewSet(validatorsFromTransitions, sb.config.ProposerPolicy)
473+
snap.ValSet = valSet
462474
}
463475

464476
// If we've generated a new checkpoint snapshot, save to disk

consensus/istanbul/common/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,6 @@ var (
9292
// ErrFailedDecodeMessageSet = errors.New("failed to decode message set")
9393
// ErrInvalidSigner is returned when the message is signed by a validator different than message sender
9494
ErrInvalidSigner = errors.New("message not signed by the sender")
95+
96+
ErrInvalidGenesis = errors.New("genesis must only specify single validator mode for block zero")
9597
)

consensus/istanbul/config.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ type Config struct {
136136
BeneficiaryList []common.Address `toml:",omitempty"` // List of wallet addresses that have benefit at every new block (list mode)
137137
MiningBeneficiary *common.Address `toml:",omitempty"` // Wallet address that benefits at every new block (besu mode)
138138
Transitions []params.Transition
139-
ValidatorContract common.Address
139+
ValidatorContract common.Address `toml:",omitempty"`
140+
Validators []common.Address `toml:",omitempty"`
141+
ValidatorSelectionMode *string `toml:",omitempty"`
140142
Client bind.ContractCaller `toml:",omitempty"`
141143
}
142144

@@ -211,6 +213,15 @@ func (c Config) GetConfig(blockNumber *big.Int) Config {
211213
if transition.MiningBeneficiary != nil {
212214
newConfig.MiningBeneficiary = transition.MiningBeneficiary
213215
}
216+
if transition.ValidatorSelectionMode != "" {
217+
newConfig.ValidatorSelectionMode = &transition.ValidatorSelectionMode
218+
}
219+
if transition.ValidatorContractAddress != (common.Address{}) {
220+
newConfig.ValidatorContract = transition.ValidatorContractAddress
221+
}
222+
if len(transition.Validators) > 0 {
223+
newConfig.Validators = transition.Validators
224+
}
214225
})
215226

216227
return newConfig
@@ -236,6 +247,21 @@ func (c Config) GetValidatorSelectionMode(blockNumber *big.Int) string {
236247
return mode
237248
}
238249

250+
func (c Config) GetValidatorsAt(blockNumber *big.Int) []common.Address {
251+
if blockNumber.Cmp(big.NewInt(0)) == 0 && len(c.Validators) > 0 {
252+
return c.Validators
253+
}
254+
255+
if blockNumber != nil && c.Transitions != nil {
256+
for i := 0; i < len(c.Transitions) && c.Transitions[i].Block.Cmp(blockNumber) == 0; i++ {
257+
return c.Transitions[i].Validators
258+
}
259+
}
260+
261+
//Note! empty means we will get the valset from previous block header which contains votes, validators etc
262+
return []common.Address{}
263+
}
264+
239265
func (c Config) Get2FPlus1Enabled(blockNumber *big.Int) bool {
240266
twoFPlusOneEnabled := false
241267
c.getTransitionValue(blockNumber, func(transition params.Transition) {

consensus/istanbul/qbft/engine/engine.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,17 +336,35 @@ func (e *Engine) Prepare(chain consensus.ChainHeaderReader, header *types.Header
336336
header.Time = uint64(time.Now().Unix())
337337
}
338338

339-
validatorContract := e.cfg.GetValidatorContractAddress(big.NewInt(0).SetUint64(number - 1))
340-
if validatorContract != (common.Address{}) && e.cfg.GetValidatorSelectionMode(big.NewInt(0).SetUint64(number-1)) == params.ContractMode {
339+
currentBlockNumber := big.NewInt(0).SetUint64(number - 1)
340+
validatorContract := e.cfg.GetValidatorContractAddress(currentBlockNumber)
341+
if validatorContract != (common.Address{}) && e.cfg.GetValidatorSelectionMode(currentBlockNumber) == params.ContractMode {
341342
return ApplyHeaderQBFTExtra(
342343
header,
343344
WriteValidators([]common.Address{}),
344345
)
345346
} else {
347+
for _, transition := range e.cfg.Transitions {
348+
if transition.Block.Cmp(currentBlockNumber) == 0 && len(transition.Validators) > 0 {
349+
toRemove := make([]istanbul.Validator, 0, validators.Size())
350+
l := validators.List()
351+
for i := range l {
352+
toRemove = append(toRemove, l[i])
353+
}
354+
for i := range toRemove {
355+
validators.RemoveValidator(toRemove[i].Address())
356+
}
357+
for i := range transition.Validators {
358+
validators.AddValidator(transition.Validators[i])
359+
}
360+
break
361+
}
362+
}
363+
validatorsList := validator.SortedAddresses(validators.List())
346364
// add validators in snapshot to extraData's validators section
347365
return ApplyHeaderQBFTExtra(
348366
header,
349-
WriteValidators(validator.SortedAddresses(validators.List())),
367+
WriteValidators(validatorsList),
350368
)
351369
}
352370
}

eth/ethconfig/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co
296296
if chainConfig.QBFT.ValidatorContractAddress != (common.Address{}) {
297297
config.Istanbul.ValidatorContract = chainConfig.QBFT.ValidatorContractAddress
298298
}
299+
config.Istanbul.Validators = chainConfig.QBFT.Validators
300+
config.Istanbul.ValidatorSelectionMode = chainConfig.QBFT.ValidatorSelectionMode
301+
299302
return istanbulBackend.New(&config.Istanbul, stack.GetNodeKey(), db)
300303
}
301304
// For Quorum, Raft run as a separate service, so

params/config.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,12 @@ func (c IBFTConfig) String() string {
429429

430430
type QBFTConfig struct {
431431
*BFTConfig
432-
BlockReward *math.HexOrDecimal256 `json:"blockReward,omitempty"` // Reward from start, works only on QBFT consensus protocol
433-
BeneficiaryMode *string `json:"beneficiaryMode,omitempty"` // Mode for setting the beneficiary, either: list, besu, validators (beneficiary list is the list of validators)
434-
BeneficiaryList []common.Address `json:"beneficiaryList,omitempty"` // List of wallet addresses that have benefit at every new block (list mode)
435-
MiningBeneficiary *common.Address `json:"miningBeneficiary,omitempty"` // Wallet address that benefits at every new block (besu mode)
436-
432+
BlockReward *math.HexOrDecimal256 `json:"blockReward,omitempty"` // Reward from start, works only on QBFT consensus protocol
433+
BeneficiaryMode *string `json:"beneficiaryMode,omitempty"` // Mode for setting the beneficiary, either: list, besu, validators (beneficiary list is the list of validators)
434+
BeneficiaryList []common.Address `json:"beneficiaryList,omitempty"` // List of wallet addresses that have benefit at every new block (list mode)
435+
MiningBeneficiary *common.Address `json:"miningBeneficiary,omitempty"` // Wallet address that benefits at every new block (besu mode)
436+
ValidatorSelectionMode *string `json:"validatorselectionmode,omitempty"` // Select model for validators
437+
Validators []common.Address `json:"validators"` // Validators list
437438
}
438439

439440
func (c QBFTConfig) String() string {
@@ -457,6 +458,7 @@ type Transition struct {
457458
RequestTimeoutSeconds uint64 `json:"requesttimeoutseconds,omitempty"` // Minimum request timeout for each IBFT or QBFT round in milliseconds
458459
ContractSizeLimit uint64 `json:"contractsizelimit,omitempty"` // Maximum smart contract code size
459460
ValidatorContractAddress common.Address `json:"validatorcontractaddress"` // Smart contract address for list of validators
461+
Validators []common.Address `json:"validators"` // List of validators
460462
ValidatorSelectionMode string `json:"validatorselectionmode,omitempty"` // Validator selection mode to switch to
461463
EnhancedPermissioningEnabled *bool `json:"enhancedPermissioningEnabled,omitempty"` // aka QIP714Block
462464
PrivacyEnhancementsEnabled *bool `json:"privacyEnhancementsEnabled,omitempty"` // privacy enhancements (mandatory party, private state validation)

params/config_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,10 @@ func TestCheckTransitionsData(t *testing.T) {
332332
var ibftTransitionsConfig, qbftTransitionsConfig, invalidTransition, invalidBlockOrder []Transition
333333
var emptyBlockPeriodSeconds uint64 = 10
334334

335-
tranI0 := Transition{big.NewInt(0), IBFT, 30000, 5, nil, 10, 50, common.Address{}, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
336-
tranQ5 := Transition{big.NewInt(5), QBFT, 30000, 5, &emptyBlockPeriodSeconds, 10, 50, common.Address{}, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
337-
tranI10 := Transition{big.NewInt(10), IBFT, 30000, 5, nil, 10, 50, common.Address{}, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
338-
tranQ8 := Transition{big.NewInt(8), QBFT, 30000, 5, &emptyBlockPeriodSeconds, 10, 50, common.Address{}, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
335+
tranI0 := Transition{big.NewInt(0), IBFT, 30000, 5, nil, 10, 50, common.Address{}, nil, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
336+
tranQ5 := Transition{big.NewInt(5), QBFT, 30000, 5, &emptyBlockPeriodSeconds, 10, 50, common.Address{}, nil, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
337+
tranI10 := Transition{big.NewInt(10), IBFT, 30000, 5, nil, 10, 50, common.Address{}, nil, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
338+
tranQ8 := Transition{big.NewInt(8), QBFT, 30000, 5, &emptyBlockPeriodSeconds, 10, 50, common.Address{}, nil, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}
339339

340340
ibftTransitionsConfig = append(ibftTransitionsConfig, tranI0, tranI10)
341341
qbftTransitionsConfig = append(qbftTransitionsConfig, tranQ5, tranQ8)
@@ -395,7 +395,7 @@ func TestCheckTransitionsData(t *testing.T) {
395395
wantErr: ErrBlockOrder,
396396
},
397397
{
398-
stored: &ChainConfig{Transitions: []Transition{{nil, IBFT, 30000, 5, &emptyBlockPeriodSeconds, 10, 50, common.Address{}, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}}},
398+
stored: &ChainConfig{Transitions: []Transition{{nil, IBFT, 30000, 5, &emptyBlockPeriodSeconds, 10, 50, common.Address{}, nil, "", nil, nil, nil, nil, 0, nil, 0, nil, nil, nil, nil}}},
399399
wantErr: ErrBlockNumberMissing,
400400
},
401401
{

0 commit comments

Comments
 (0)