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
7173func 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
523609func loadValidatorsInfo (db cometdbm.DB , height int64 ) (* cmtstate.ValidatorsInfo , error ) {
0 commit comments