Skip to content

Commit 37f0b9d

Browse files
authored
add auto-find-target option (#3978)
1 parent 774ac0b commit 37f0b9d

File tree

1 file changed

+160
-74
lines changed

1 file changed

+160
-74
lines changed

cmd/gaiad/cmd/testnet_set_local_validator.go

Lines changed: 160 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var (
5353
flagValidatorPrivKey = "validator-privkey"
5454
flagAccountsToFund = "accounts-to-fund"
5555
flagReplaceValidator = "replace-validator"
56+
flagAutoFindTarget = "auto-find-target"
5657
flagReplacedOperatorAddress = "replaced-operator-address"
5758
flagReplacedConsensusAddress = "replaced-consensus-address"
5859
)
@@ -64,8 +65,9 @@ type valArgs struct {
6465
accountsToFund []sdk.AccAddress // list of accounts to fund and use for testing later on
6566
homeDir string
6667
replaceValidator bool // if set, replaces a validator with new keys
68+
autoFindTarget bool // if set, automatically finds the first validator with an available vote to replace
6769
replacedOperatorAddress string // operator address of the validator to be replaced
68-
replacedConsensusAddress string // consensus address of the validator to be replaced
70+
replacedConsensusAddress string // consensus address of the validator to be replaced (hex)
6971
}
7072

7173
func init() {
@@ -86,14 +88,17 @@ Example:
8688
simd testnet unsafe-start-local-validator --validator-operator="cosmosvaloper17fjdcqy7g80pn0seexcch5pg0dtvs45p57t97r" --validator-pubkey="SLpHEfzQHuuNO9J1BB/hXyiH6c1NmpoIVQ2pMWmyctE=" --validator-privkey="AiayvI2px5CZVl/uOGmacfFjcIBoyk3Oa2JPBO6zEcdIukcR/NAe64070nUEH+FfKIfpzU2amghVDakxabJy0Q==" --accounts-to-fund="cosmos1ju6tlfclulxumtt2kglvnxduj5d93a64r5czge,cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl" [other_server_start_flags]
8789
# Replaces the specified validator with the new keys
8890
simd testnet unsafe-start-local-validator --validator-operator="cosmosvaloper17fjdcqy7g80pn0seexcch5pg0dtvs45p57t97r" --validator-pubkey="SLpHEfzQHuuNO9J1BB/hXyiH6c1NmpoIVQ2pMWmyctE=" --validator-privkey="AiayvI2px5CZVl/uOGmacfFjcIBoyk3Oa2JPBO6zEcdIukcR/NAe64070nUEH+FfKIfpzU2amghVDakxabJy0Q==" --accounts-to-fund="cosmos1ju6tlfclulxumtt2kglvnxduj5d93a64r5czge,cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl" --replace-validator -- replaced-operator-address="cosmosvaloper1r5v5srda7xfth3hn2s26txvrcrntldju7lnwmv" --replaced-consensus-address="04B333F6E43751948C2D56B273DC41C3E13E5932" [other_server_start_flags]
91+
# Replaces the first validator with an available vote in the last commit (useful when the target validator's vote is absent)
92+
simd testnet unsafe-start-local-validator --validator-operator="cosmosvaloper17fjdcqy7g80pn0seexcch5pg0dtvs45p57t97r" --validator-pubkey="SLpHEfzQHuuNO9J1BB/hXyiH6c1NmpoIVQ2pMWmyctE=" --validator-privkey="AiayvI2px5CZVl/uOGmacfFjcIBoyk3Oa2JPBO6zEcdIukcR/NAe64070nUEH+FfKIfpzU2amghVDakxabJy0Q==" --accounts-to-fund="cosmos1ju6tlfclulxumtt2kglvnxduj5d93a64r5czge,cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl" --replace-validator --auto-find-target [other_server_start_flags]
8993
`
9094
cmd.Flags().String(flagValidatorOperatorAddress, "", "Validator operator address e.g. cosmosvaloper17fjdcqy7g80pn0seexcch5pg0dtvs45p57t97r")
9195
cmd.Flags().String(flagValidatorPubKey, "", "Validator tendermint/PubKeyEd25519 consensus public key from the priv_validator_key.json file")
9296
cmd.Flags().String(flagValidatorPrivKey, "", "Validator tendermint/PrivKeyEd25519 consensus private key from the priv_validator_key.json file")
9397
cmd.Flags().String(flagAccountsToFund, "", "Comma-separated list of account addresses that will be funded for testing purposes")
9498
cmd.Flags().Bool(flagReplaceValidator, false, "Replaces a specified validator with the new keys")
95-
cmd.Flags().String(flagReplacedOperatorAddress, "", "Operator address of the validator to be replaced")
96-
cmd.Flags().String(flagReplacedConsensusAddress, "", "Consensus address of the validator to be replaced")
99+
cmd.Flags().Bool(flagAutoFindTarget, false, "Automatically find the first validator with an available vote in the last commit and replace it (implies --replace-validator)")
100+
cmd.Flags().String(flagReplacedOperatorAddress, "", "Operator address of the validator to be replaced (not required when --auto-find-target is set)")
101+
cmd.Flags().String(flagReplacedConsensusAddress, "", "Consensus address (uppercase hex) of the validator to be replaced (not required when --auto-find-target is set)")
97102

98103
return cmd
99104
}
@@ -148,8 +153,15 @@ func getCommandArgs(appOpts servertypes.AppOptions) (valArgs, error) {
148153
}
149154

150155
replaceValidator := cast.ToBool(appOpts.Get(flagReplaceValidator))
156+
autoFindTarget := cast.ToBool(appOpts.Get(flagAutoFindTarget))
157+
// --auto-find-target implies --replace-validator
158+
if autoFindTarget {
159+
replaceValidator = true
160+
}
151161
args.replaceValidator = replaceValidator
152-
if replaceValidator {
162+
args.autoFindTarget = autoFindTarget
163+
164+
if replaceValidator && !autoFindTarget {
153165
replacedOperatorAddress := cast.ToString(appOpts.Get(flagReplacedOperatorAddress))
154166
if replacedOperatorAddress == "" {
155167
return args, errors.New("invalid replaced validator operator address string")
@@ -197,15 +209,22 @@ func (a appCreator) newTestingApp(
197209
}
198210

199211
if args.replaceValidator {
200-
logger.Info("Replacing validator", "operator_address", args.replacedOperatorAddress, "consensus_address", args.replacedConsensusAddress)
212+
if args.autoFindTarget {
213+
logger.Info("Replacing validator", "mode", "auto-find-target")
214+
} else {
215+
logger.Info("Replacing validator", "operator_address", args.replacedOperatorAddress, "consensus_address", args.replacedConsensusAddress)
216+
}
201217
} else {
202218
logger.Info("Clearing the validator set")
203219
}
204220

205-
err = updateConsensusState(logger, appOpts, gaiaApp.CommitMultiStore().LatestVersion(), args)
221+
discoveredConsAddr, err := updateConsensusState(logger, appOpts, gaiaApp.CommitMultiStore().LatestVersion(), args)
206222
if err != nil {
207223
panic(err)
208224
}
225+
if discoveredConsAddr != "" {
226+
args.replacedConsensusAddress = discoveredConsAddr
227+
}
209228
err = updateApplicationState(gaiaApp, args)
210229
if err != nil {
211230
panic(err)
@@ -258,32 +277,57 @@ func updateApplicationState(app *gaia.GaiaApp, args valArgs) error {
258277

259278
foundTargetValidator := false
260279
for _, v := range validators {
261-
if (args.replaceValidator && v.GetOperator() == args.replacedOperatorAddress) || !args.replaceValidator {
262-
valConsAddr, err := v.GetConsAddr()
263-
if err != nil {
264-
return err
280+
matches := !args.replaceValidator
281+
if args.replaceValidator {
282+
valConsAddr, cerr := v.GetConsAddr()
283+
if cerr != nil {
284+
return cerr
265285
}
266-
267-
// delete the old validator record
268-
valAddr, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(v.GetOperator())
269-
if err != nil {
270-
return err
271-
}
272-
store.Delete(stakingtypes.GetValidatorKey(valAddr))
273-
store.Delete(stakingtypes.GetValidatorByConsAddrKey(valConsAddr))
274-
store.Delete(stakingtypes.GetValidatorsByPowerIndexKey(v, app.StakingKeeper.PowerReduction(appCtx), app.StakingKeeper.ValidatorAddressCodec()))
275-
store.Delete(stakingtypes.GetLastValidatorPowerKey(valAddr))
276-
if v.IsUnbonding() {
277-
app.StakingKeeper.DeleteValidatorQueueTimeSlice(appCtx, v.UnbondingTime, v.UnbondingHeight)
278-
}
279-
if args.replaceValidator {
280-
foundTargetValidator = true
281-
appCtx.Logger().Info("Found validator to replace", "operator_address", v.GetOperator())
282-
break
286+
if args.autoFindTarget {
287+
matches = fmt.Sprintf("%X", valConsAddr) == args.replacedConsensusAddress
288+
} else {
289+
matches = v.GetOperator() == args.replacedOperatorAddress
283290
}
284291
}
292+
if !matches {
293+
continue
294+
}
295+
296+
valConsAddr, err := v.GetConsAddr()
297+
if err != nil {
298+
return err
299+
}
300+
301+
if args.replaceValidator {
302+
appCtx.Logger().Info("Replacing validator",
303+
"moniker", v.GetMoniker(),
304+
"operator_address", v.GetOperator(),
305+
"cons_addr_bech32", sdk.ConsAddress(valConsAddr).String(),
306+
"cons_addr_hex", fmt.Sprintf("%X", valConsAddr),
307+
)
308+
}
309+
310+
// delete the old validator record
311+
valAddr, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(v.GetOperator())
312+
if err != nil {
313+
return err
314+
}
315+
store.Delete(stakingtypes.GetValidatorKey(valAddr))
316+
store.Delete(stakingtypes.GetValidatorByConsAddrKey(valConsAddr))
317+
store.Delete(stakingtypes.GetValidatorsByPowerIndexKey(v, app.StakingKeeper.PowerReduction(appCtx), app.StakingKeeper.ValidatorAddressCodec()))
318+
store.Delete(stakingtypes.GetLastValidatorPowerKey(valAddr))
319+
if v.IsUnbonding() {
320+
app.StakingKeeper.DeleteValidatorQueueTimeSlice(appCtx, v.UnbondingTime, v.UnbondingHeight)
321+
}
322+
if args.replaceValidator {
323+
foundTargetValidator = true
324+
break
325+
}
285326
}
286327
if args.replaceValidator && !foundTargetValidator {
328+
if args.autoFindTarget {
329+
return fmt.Errorf("validator with consensus address %s not found in application state", args.replacedConsensusAddress)
330+
}
287331
return fmt.Errorf("validator with operator address %s not found", args.replacedOperatorAddress)
288332
}
289333

@@ -356,15 +400,15 @@ func updateApplicationState(app *gaia.GaiaApp, args valArgs) error {
356400
return nil
357401
}
358402

359-
func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, appHeight int64, args valArgs) error {
403+
func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, appHeight int64, args valArgs) (string, error) {
360404
// create validator set from the local validator
361405
newTmVal := tmtypes.NewValidator(tmd25519.PubKey(args.validatorConsPubKeyByte), valVotingPower)
362406
logger.Info("Creating new validator:", "validator", newTmVal)
363407

364408
// CHANGE STATE CONSENSUS STORE
365409
stateDB, err := openDB(args.homeDir, "state", cometdbm.BackendType(server.GetAppDBBackend(appOpts)))
366410
if err != nil {
367-
return err
411+
return "", err
368412
}
369413

370414
stateStore := sm.NewStore(stateDB, sm.StoreOptions{
@@ -374,7 +418,7 @@ func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, app
374418
// load state in order to change validator set info
375419
state, err := stateStore.Load()
376420
if err != nil {
377-
return err
421+
return "", err
378422
}
379423
defer func() {
380424
if derr := stateStore.Close(); derr != nil {
@@ -387,15 +431,15 @@ func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, app
387431
// and the voting validator address has to be present in the staking module, which is not the case for old validator
388432
valInfo, err := loadValidatorsInfo(stateDB, state.LastBlockHeight)
389433
if err != nil {
390-
return err
434+
return "", err
391435
}
392436

393437
// CHANGE BLOCK CONSENSUS STORE
394438
// we need to change the last commit data by updating the signature's info. Consensus will match the validator's set length
395439
// and size of the lastCommit signatures when building the last commit info and they have to match
396440
blockStoreDB, err := openDB(args.homeDir, "blockstore", cometdbm.BackendType(server.GetAppDBBackend(appOpts)))
397441
if err != nil {
398-
return err
442+
return "", err
399443
}
400444
defer func() {
401445
if derr := blockStoreDB.Close(); derr != nil {
@@ -408,50 +452,92 @@ func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, app
408452
lastCommit := blockStore.LoadSeenCommit(state.LastBlockHeight)
409453
var vote *tmtypes.Vote
410454
currentValidators := state.Validators.Copy()
455+
discoveredConsAddr := ""
411456
if args.replaceValidator {
412-
// Replace the target validator
413-
replaced := false
414-
for i, v := range currentValidators.Validators {
415-
if v.Address.String() == args.replacedConsensusAddress { // oldAddress = old consensus address
416-
currentValidators.Validators[i] = newTmVal // newTmVal = new tmtypes.Validator with new pubkey
417-
replaced = true
418-
break
419-
}
420-
}
421-
if !replaced {
422-
return fmt.Errorf("validator to replace not found in consensus set")
423-
}
424-
currentValidators.Proposer = newTmVal
425-
426-
var sigIndex int
427-
for idx, commitSig := range lastCommit.Signatures {
428-
if commitSig.BlockIDFlag == tmtypes.BlockIDFlagAbsent {
429-
continue
430-
}
431-
validatorAddress := commitSig.ValidatorAddress
432-
// logger.Info("Commit signature", "idx", idx, "address", fmt.Sprintf("%X", validatorAddress), "commitSig", commitSig)
433-
if validatorAddress.String() == args.replacedConsensusAddress {
434-
logger.Info("Found validator to replace", "idx", idx, "consensus_address", fmt.Sprintf("%X", validatorAddress))
457+
if args.autoFindTarget {
458+
// Auto-find: use the first validator with an available vote in the last commit
459+
var sigIndex int
460+
for idx, commitSig := range lastCommit.Signatures {
461+
if commitSig.BlockIDFlag == tmtypes.BlockIDFlagAbsent {
462+
continue
463+
}
464+
discoveredConsAddr = commitSig.ValidatorAddress.String()
465+
logger.Info("Auto-selected validator to replace", "idx", idx, "cons_addr_hex", fmt.Sprintf("%X", commitSig.ValidatorAddress))
435466
vote = lastCommit.GetVote(int32(idx))
436467
sigIndex = idx
437468
break
438469
}
439-
}
440-
if vote == nil {
441-
return errors.New("cannot get the vote from the last commit")
442-
}
470+
if vote == nil {
471+
return "", errors.New("no validator with an available vote found in the last commit")
472+
}
473+
// Replace the matching validator in the current consensus validator set
474+
replaced := false
475+
for i, v := range currentValidators.Validators {
476+
if v.Address.String() == discoveredConsAddr {
477+
currentValidators.Validators[i] = newTmVal
478+
replaced = true
479+
break
480+
}
481+
}
482+
if !replaced {
483+
return "", fmt.Errorf("auto-selected validator %s not found in consensus validator set", discoveredConsAddr)
484+
}
485+
currentValidators.Proposer = newTmVal
443486

444-
voteSignBytes := tmtypes.VoteSignBytes(state.ChainID, vote.ToProto())
445-
signatureBytes, err := args.validatorConsPrivKey.Sign(voteSignBytes)
446-
if err != nil {
447-
return err
448-
}
487+
voteSignBytes := tmtypes.VoteSignBytes(state.ChainID, vote.ToProto())
488+
signatureBytes, err := args.validatorConsPrivKey.Sign(voteSignBytes)
489+
if err != nil {
490+
return "", err
491+
}
492+
lastCommit.Signatures[sigIndex] = tmtypes.CommitSig{
493+
BlockIDFlag: tmtypes.BlockIDFlagCommit,
494+
ValidatorAddress: newTmVal.Address,
495+
Timestamp: vote.Timestamp,
496+
Signature: []byte(signatureBytes),
497+
}
498+
} else {
499+
// Replace the target validator by explicit consensus address
500+
replaced := false
501+
for i, v := range currentValidators.Validators {
502+
if v.Address.String() == args.replacedConsensusAddress {
503+
currentValidators.Validators[i] = newTmVal
504+
replaced = true
505+
break
506+
}
507+
}
508+
if !replaced {
509+
return "", fmt.Errorf("validator to replace not found in consensus set")
510+
}
511+
currentValidators.Proposer = newTmVal
512+
513+
var sigIndex int
514+
for idx, commitSig := range lastCommit.Signatures {
515+
if commitSig.BlockIDFlag == tmtypes.BlockIDFlagAbsent {
516+
continue
517+
}
518+
validatorAddress := commitSig.ValidatorAddress
519+
if validatorAddress.String() == args.replacedConsensusAddress {
520+
logger.Info("Found validator to replace", "idx", idx, "consensus_address", fmt.Sprintf("%X", validatorAddress))
521+
vote = lastCommit.GetVote(int32(idx))
522+
sigIndex = idx
523+
break
524+
}
525+
}
526+
if vote == nil {
527+
return "", errors.New("cannot get the vote from the last commit")
528+
}
449529

450-
lastCommit.Signatures[sigIndex] = tmtypes.CommitSig{
451-
BlockIDFlag: tmtypes.BlockIDFlagCommit,
452-
ValidatorAddress: newTmVal.Address,
453-
Timestamp: vote.Timestamp,
454-
Signature: []byte(signatureBytes),
530+
voteSignBytes := tmtypes.VoteSignBytes(state.ChainID, vote.ToProto())
531+
signatureBytes, err := args.validatorConsPrivKey.Sign(voteSignBytes)
532+
if err != nil {
533+
return "", err
534+
}
535+
lastCommit.Signatures[sigIndex] = tmtypes.CommitSig{
536+
BlockIDFlag: tmtypes.BlockIDFlagCommit,
537+
ValidatorAddress: newTmVal.Address,
538+
Timestamp: vote.Timestamp,
539+
Signature: []byte(signatureBytes),
540+
}
455541
}
456542
} else {
457543
// Clear the validator set
@@ -466,13 +552,13 @@ func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, app
466552
break
467553
}
468554
if vote == nil {
469-
return errors.New("cannot get the vote from the last commit")
555+
return "", errors.New("cannot get the vote from the last commit")
470556
}
471557

472558
voteSignBytes := tmtypes.VoteSignBytes(state.ChainID, vote.ToProto())
473559
signatureBytes, err := args.validatorConsPrivKey.Sign(voteSignBytes)
474560
if err != nil {
475-
return err
561+
return "", err
476562
}
477563

478564
lastCommit.Signatures = []tmtypes.CommitSig{{
@@ -489,12 +575,12 @@ func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, app
489575
state.NextValidators = currentValidators
490576
// save state store
491577
if err = stateStore.Save(state); err != nil {
492-
return err
578+
return "", err
493579
}
494580

495581
protoValSet, err := currentValidators.ToProto()
496582
if err != nil {
497-
return err
583+
return "", err
498584
}
499585
valInfo.ValidatorSet = protoValSet
500586
valInfo.LastHeightChanged = state.LastBlockHeight
@@ -517,7 +603,7 @@ func updateConsensusState(logger log.Logger, appOpts servertypes.AppOptions, app
517603
blockStore.DeleteLatestBlock()
518604
}
519605

520-
return blockStore.SaveSeenCommit(state.LastBlockHeight, lastCommit)
606+
return discoveredConsAddr, blockStore.SaveSeenCommit(state.LastBlockHeight, lastCommit)
521607
}
522608

523609
func loadValidatorsInfo(db cometdbm.DB, height int64) (*cmtstate.ValidatorsInfo, error) {

0 commit comments

Comments
 (0)