From b80cb9c74fafcb2a1b964522c3f058ad940b5b88 Mon Sep 17 00:00:00 2001 From: Marcello Date: Tue, 17 Oct 2023 15:43:46 +0200 Subject: [PATCH] cli command to verify if it is safe to update (#4092) * cli command to verify if it is safe to update * address comments * revert to unwrap_or_else * updated command to return general informations * address comments * cargo fmt * added test --- Cargo.lock | 1 + api/bin/chainflip-cli/src/main.rs | 13 +++ api/bin/chainflip-cli/src/settings.rs | 2 + api/lib/Cargo.toml | 3 +- api/lib/src/queries.rs | 111 +++++++++++++++++++++++++- 5 files changed, 128 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 582789ff1b..e6a68643d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1359,6 +1359,7 @@ dependencies = [ "chainflip-node", "custom-rpc", "ed25519-dalek", + "frame-support", "frame-system", "futures", "hex", diff --git a/api/bin/chainflip-cli/src/main.rs b/api/bin/chainflip-cli/src/main.rs index 47de1818c7..bc8a236948 100644 --- a/api/bin/chainflip-cli/src/main.rs +++ b/api/bin/chainflip-cli/src/main.rs @@ -124,6 +124,7 @@ async fn run_cli() -> Result<()> { VanityName { name } => { api.operator_api().set_vanity_name(name).await?; }, + PreUpdateCheck {} => pre_update_check(api.query_api()).await?, ForceRotation {} => { api.governance_api().force_rotation().await?; }, @@ -291,6 +292,18 @@ async fn get_bound_executor_address(api: QueryApi) -> Result<()> { Ok(()) } +async fn pre_update_check(api: QueryApi) -> Result<()> { + let can_update = api.pre_update_check(None, None).await?; + + println!("Your node is an authority: {}", can_update.is_authority); + println!("A rotation is occurring: {}", can_update.rotation); + if let Some(blocks) = can_update.next_block_in { + println!("Your validator will produce a block in {} blocks", blocks); + } + + Ok(()) +} + fn confirm_submit() -> bool { use std::{io, io::*}; diff --git a/api/bin/chainflip-cli/src/settings.rs b/api/bin/chainflip-cli/src/settings.rs index 7e8e628d47..2da3fc84dc 100644 --- a/api/bin/chainflip-cli/src/settings.rs +++ b/api/bin/chainflip-cli/src/settings.rs @@ -150,6 +150,8 @@ pub enum CliCommand { #[clap(help = "Name in UTF-8 (max length 64)")] name: String, }, + #[clap(about = "Check if it is safe to update your node/engine")] + PreUpdateCheck {}, #[clap( // This is only useful for testing. No need to show to the end user. hide = true, diff --git a/api/lib/Cargo.toml b/api/lib/Cargo.toml index 204b76fb97..983cd64ccb 100644 --- a/api/lib/Cargo.toml +++ b/api/lib/Cargo.toml @@ -42,9 +42,10 @@ pallet-cf-validator = { path = "../../state-chain/pallets/cf-validator" } state-chain-runtime = { path = "../../state-chain/runtime" } frame-system = { git = 'https://github.com/chainflip-io/substrate.git', tag = 'chainflip-monthly-2023-08+2' } cf-amm = { path = "../../state-chain/amm" } +frame-support = { git = "https://github.com/chainflip-io/substrate.git", tag = "chainflip-monthly-2023-08+2", default-features = false } # Substrate key types sp-consensus-aura = { git = 'https://github.com/chainflip-io/substrate.git', tag = 'chainflip-monthly-2023-08+2' } sp-core = { git = 'https://github.com/chainflip-io/substrate.git', tag = 'chainflip-monthly-2023-08+2' } sp-consensus-grandpa = { git = 'https://github.com/chainflip-io/substrate.git', tag = 'chainflip-monthly-2023-08+2' } -codec = { package = "parity-scale-codec", version = "3.6.1" } +codec = { package = "parity-scale-codec", version = "3.6.1" } \ No newline at end of file diff --git a/api/lib/src/queries.rs b/api/lib/src/queries.rs index 2da798e592..e4723c74bf 100644 --- a/api/lib/src/queries.rs +++ b/api/lib/src/queries.rs @@ -4,10 +4,14 @@ use cf_primitives::{chains::assets::any, AssetAmount}; use chainflip_engine::state_chain_observer::client::{ chain_api::ChainApi, storage_api::StorageApi, }; +use codec::Decode; +use frame_support::sp_runtime::DigestItem; use pallet_cf_ingress_egress::DepositChannelDetails; +use pallet_cf_validator::RotationPhase; use serde::Deserialize; +use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; use state_chain_runtime::PalletInstanceAlias; -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::BTreeMap, ops::Deref, sync::Arc}; use tracing::log; use utilities::task_scope; @@ -18,6 +22,12 @@ pub struct SwapChannelInfo { destination_asset: any::Asset, } +pub struct PreUpdateStatus { + pub rotation: bool, + pub is_authority: bool, + pub next_block_in: Option, +} + pub struct QueryApi { pub(crate) state_chain_client: Arc, } @@ -143,4 +153,103 @@ impl QueryApi { ) .await?) } + + pub async fn pre_update_check( + &self, + block_hash: Option, + account_id: Option, + ) -> Result { + let block_hash = + block_hash.unwrap_or_else(|| self.state_chain_client.latest_finalized_hash()); + let account_id = account_id.unwrap_or_else(|| self.state_chain_client.account_id()); + + let mut result = + PreUpdateStatus { rotation: false, is_authority: false, next_block_in: None }; + + if self + .state_chain_client + .storage_value::>( + block_hash, + ) + .await? != RotationPhase::Idle + { + result.rotation = true; + } + + let current_validators = self + .state_chain_client + .storage_value::>( + block_hash, + ) + .await?; + + if current_validators.contains(&account_id) { + result.is_authority = true; + } else { + return Ok(result) + } + + let header = self.state_chain_client.base_rpc_client.block_header(block_hash).await?; + + let slot: usize = + *extract_slot_from_digest_item(&header.digest.logs[0]).unwrap().deref() as usize; + + let validator_len = current_validators.len(); + let current_relative_slot = slot % validator_len; + let index = current_validators.iter().position(|account| account == &account_id).unwrap(); + + result.next_block_in = Some(compute_distance(index, current_relative_slot, validator_len)); + Ok(result) + } +} + +// https://github.com/chainflip-io/substrate/blob/c172d0f683fab3792b90d876fd6ca27056af9fe9/frame/aura/src/lib.rs#L179 +fn extract_slot_from_digest_item(item: &DigestItem) -> Option { + item.as_pre_runtime().and_then(|(id, mut data)| { + if id == AURA_ENGINE_ID { + Slot::decode(&mut data).ok() + } else { + None + } + }) +} + +fn compute_distance(index: usize, slot: usize, len: usize) -> usize { + if index >= slot { + index - slot + } else { + len - slot + index + } +} + +#[test] +fn test_slot_extraction() { + let slot = Slot::from(42); + assert_eq!( + Some(slot), + extract_slot_from_digest_item(&DigestItem::PreRuntime( + AURA_ENGINE_ID, + Encode::encode(&slot) + )) + ); + assert_eq!( + None, + extract_slot_from_digest_item(&DigestItem::PreRuntime(*b"BORA", Encode::encode(&slot))) + ); + assert_eq!(None, extract_slot_from_digest_item(&DigestItem::Other(b"SomethingElse".to_vec()))); +} + +#[test] +fn test_compute_distance() { + let index: usize = 5; + let slot: usize = 7; + let len: usize = 15; + + assert_eq!(compute_distance(index, slot, len), 13); + + let index: usize = 18; + let slot: usize = 7; + let len: usize = 24; + + assert_eq!(compute_distance(index, slot, len), 11); }