Skip to content

Commit

Permalink
[operator-tool] Add VerifyValidatorState command
Browse files Browse the repository at this point in the history
Closes: #10098
  • Loading branch information
khiemngo authored and bors-libra committed Dec 24, 2021
1 parent 46bc24d commit 61a5eb9
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 1 deletion.
31 changes: 30 additions & 1 deletion config/management/operational/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

use crate::{
account_resource::SimplifiedAccountResource, validator_config::DecryptedValidatorConfig,
validator_set::DecryptedValidatorInfo, TransactionContext,
validator_set::DecryptedValidatorInfo, validator_state::VerifyValidatorStateResult,
TransactionContext,
};
use diem_config::config::Peer;
use diem_crypto::{ed25519::Ed25519PublicKey, x25519};
Expand Down Expand Up @@ -72,6 +73,10 @@ pub enum Command {
ValidatorConfig(crate::validator_config::ValidatorConfig),
#[structopt(about = "Displays the current validator set infos registered on the blockchain")]
ValidatorSet(crate::validator_set::ValidatorSet),
#[structopt(
about = "Compare a local validator state against the validator state held on-chain"
)]
VerifyValidatorState(crate::validator_state::VerifyValidatorState),
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -103,6 +108,7 @@ pub enum CommandName {
ValidateTransaction,
ValidatorConfig,
ValidatorSet,
VerifyValidatorState,
}

impl From<&Command> for CommandName {
Expand Down Expand Up @@ -135,6 +141,7 @@ impl From<&Command> for CommandName {
Command::ValidateTransaction(_) => CommandName::ValidateTransaction,
Command::ValidatorConfig(_) => CommandName::ValidatorConfig,
Command::ValidatorSet(_) => CommandName::ValidatorSet,
Command::VerifyValidatorState(_) => CommandName::VerifyValidatorState,
}
}
}
Expand Down Expand Up @@ -169,6 +176,7 @@ impl std::fmt::Display for CommandName {
CommandName::ValidateTransaction => "validate-transaction",
CommandName::ValidatorConfig => "validator-config",
CommandName::ValidatorSet => "validator-set",
CommandName::VerifyValidatorState => "verify-validator-state",
};
write!(f, "{}", name)
}
Expand Down Expand Up @@ -216,6 +224,9 @@ impl Command {
Command::ValidateTransaction(cmd) => Self::print_transaction_context(cmd.execute()),
Command::ValidatorConfig(cmd) => Self::pretty_print(cmd.execute()),
Command::ValidatorSet(cmd) => Self::pretty_print(cmd.execute()),
Command::VerifyValidatorState(cmd) => {
Self::print_verify_validator_state_result(cmd.execute())
}
}
}

Expand Down Expand Up @@ -243,6 +254,16 @@ impl Command {
}))
}

/// Show VerifyValidatorState result
fn print_verify_validator_state_result(
result: Result<VerifyValidatorStateResult, Error>,
) -> Result<String, Error> {
match &result {
Ok(verify_result) => Self::pretty_print(Ok(format!("{:?}", verify_result))),
Err(_) => Self::pretty_print(result),
}
}

/// Show success or the error result
fn print_success(result: Result<(), Error>) -> Result<String, Error> {
Self::pretty_print(result.map(|()| "Success"))
Expand Down Expand Up @@ -420,6 +441,14 @@ impl Command {
pub fn validator_set(self) -> Result<Vec<DecryptedValidatorInfo>, Error> {
execute_command!(self, Command::ValidatorSet, CommandName::ValidatorSet)
}

pub fn verify_validator_state(self) -> Result<VerifyValidatorStateResult, Error> {
execute_command!(
self,
Command::VerifyValidatorState,
CommandName::VerifyValidatorState
)
}
}

/// A result wrapper for displaying either a correct execution result or an error.
Expand Down
1 change: 1 addition & 0 deletions config/management/operational/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod print;
mod validate_transaction;
mod validator_config;
mod validator_set;
mod validator_state;

mod network_checker;
#[cfg(any(test, feature = "testing"))]
Expand Down
21 changes: 21 additions & 0 deletions config/management/operational/src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
keys::{load_key, EncodingType, KeyType},
validator_config::DecryptedValidatorConfig,
validator_set::DecryptedValidatorInfo,
validator_state::VerifyValidatorStateResult,
TransactionContext,
};
use diem_config::{config, config::Peer, network_id::NetworkId};
Expand Down Expand Up @@ -708,6 +709,26 @@ impl OperationalTool {
|cmd| cmd.remove_validator(),
)
}

pub fn verify_validator_state(
&self,
backend: &config::SecureBackend,
) -> Result<VerifyValidatorStateResult, Error> {
let args = format!(
"
{command}
--json-server {host}
--chain-id {chain_id}
--validator-backend {backend_args}
",
command = command(TOOL_NAME, CommandName::VerifyValidatorState),
host = self.host,
chain_id = self.chain_id.id(),
backend_args = backend_args(backend)?,
);
let command = Command::from_iter(args.split_whitespace());
command.verify_validator_state()
}
}

fn command(tool_name: &'static str, command: CommandName) -> String {
Expand Down
68 changes: 68 additions & 0 deletions config/management/operational/src/validator_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{json_rpc::JsonRpcClientWrapper, validator_config::DecryptedValidatorConfig};
use diem_global_constants::{CONSENSUS_KEY, OWNER_ACCOUNT};
use diem_management::error::Error;
use serde::Serialize;
use structopt::StructOpt;

#[derive(Debug, Default, Serialize)]
pub struct VerifyValidatorStateResult {
/// Check if a validator is in the latest validator set on-chain.
pub in_validator_set: Option<bool>,
/// Check if the consensus key held in secure storage matches
/// that registered on-chain for the validator.
pub consensus_key_match: Option<bool>,
}

impl VerifyValidatorStateResult {
pub fn is_state_consistent(&self) -> bool {
self.in_validator_set == Some(true) && self.consensus_key_match == Some(true)
}
}

#[derive(Debug, StructOpt)]
pub struct VerifyValidatorState {
#[structopt(long, required_unless = "config")]
json_server: Option<String>,
#[structopt(flatten)]
validator_config: diem_management::validator_config::ValidatorConfig,
}

impl VerifyValidatorState {
pub fn execute(self) -> Result<VerifyValidatorStateResult, Error> {
// Load the config, storage backend and create a json rpc client.
let config = self
.validator_config
.config()?
.override_json_server(&self.json_server);
let storage = config.validator_backend();
let client = JsonRpcClientWrapper::new(config.json_server);
let owner_account = storage.account_address(OWNER_ACCOUNT)?;

// Verify if the validator is in the set
let in_validator_set = client
.validator_set(None)?
.iter()
.any(|vi| vi.account_address() == &owner_account);

// TODO(khiemngo): consider return early if the validator is not in the set

// Fetch the current on-chain config for this operator's owner
let validator_config = client.validator_config(owner_account).and_then(|vc| {
DecryptedValidatorConfig::from_validator_config_resource(&vc, owner_account)
})?;

let storage_key = storage.ed25519_public_from_private(CONSENSUS_KEY)?;
let consensus_key_match = storage_key == validator_config.consensus_public_key;

// TODO(khiemngo): add checks for validator/fullnode network addresses
// TODO(khiemngo): add check for key uniqueness

Ok(VerifyValidatorStateResult {
in_validator_set: Some(in_validator_set),
consensus_key_match: Some(consensus_key_match),
})
}
}
19 changes: 19 additions & 0 deletions testsuite/smoke-test/src/operational_tooling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,25 @@ async fn test_validator_set() {
);
}

#[tokio::test]
async fn test_verify_validator_state() {
let (_env, op_tool, backend, mut storage) = launch_swarm_with_op_tool_and_backend(4).await;

let result = op_tool.verify_validator_state(&backend).unwrap();
assert_eq!(result.in_validator_set, Some(true));
assert_eq!(result.consensus_key_match, Some(true));

// Rotate consensus key locally, but we do not update it on-chain
// Verify the local validator state again.
// The local consensus key is no longer mached with that registered on-chain
let _ = storage.rotate_key(CONSENSUS_KEY).unwrap();
let result = op_tool.verify_validator_state(&backend).unwrap();
assert_eq!(result.in_validator_set, Some(true));
assert_eq!(result.consensus_key_match, Some(false));

// TODO(khiemngo): consider adding test where the validator is no longer in set
}

/// Creates a new account address and key for testing.
fn create_new_test_account() -> (Ed25519PrivateKey, AccountAddress) {
let mut rng = OsRng;
Expand Down

0 comments on commit 61a5eb9

Please sign in to comment.