Skip to content

Commit

Permalink
Add --all option to light rm command to remove all peers (#432)
Browse files Browse the repository at this point in the history
* Add --all option to `light rm` command to remove all peers

* Add --yes option to skip confirmation

* Update changelog
  • Loading branch information
romac authored Dec 8, 2020
1 parent 15bac8c commit 1bd058d
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 56 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ We also consolidated our TLA+ specs into an "IBC Core TLA+ specification," and a

Special thanks to external contributors for this release: @CharlyCst ([#347], [#419]).

- [relayer-cli]
- Add `--all` option to `light rm` command to remove all peers for a given chain ([#431])

[#431]: https://github.com/informalsystems/ibc-rs/issues/431

### FEATURES

- Update to tendermint-rs version `0.17-RC3` ([#403])
Expand Down
159 changes: 104 additions & 55 deletions relayer-cli/src/commands/light/rm.rs
Original file line number Diff line number Diff line change
@@ -1,119 +1,168 @@
use std::ops::Deref;
use std::{io, io::Write, ops::Deref};

use crate::prelude::*;

use abscissa_core::{application::fatal_error, error::BoxError, Command, Options, Runnable};

use ibc::ics24_host::identifier::ChainId;
use relayer::config::Config;
use relayer::config::PeersConfig;
use tendermint_light_client::types::PeerId;

#[derive(Command, Debug, Options)]
pub struct RmCmd {
/// peer id for this client
/// identifiers of peers to remove
#[options(free)]
peer_id: Option<PeerId>,
peer_id: Vec<PeerId>,

/// identifier of the chain to remove peers from
#[options(short = "c")]
/// identifier of the chain
chain_id: Option<ChainId>,

/// force removal of primary peer
#[options(short = "f")]
force: bool,

/// remove all peers, implies --force
#[options(no_short)]
all: bool,

/// skip confirmation
yes: bool,
}

#[derive(Clone, Debug)]
struct RmOptions {
/// identifier of the chain
/// identifier of the chain to remove peers from
chain_id: ChainId,

/// peer id for this client
peer_id: PeerId,
/// identifiers of peers to remove
peer_ids: Vec<PeerId>,

/// remove all peers, implies --force
all: bool,

/// force removal of primary peer
force: bool,

/// skip confirmation
yes: bool,
}

impl RmOptions {
fn from_cmd(cmd: &RmCmd) -> Result<RmOptions, BoxError> {
let chain_id = cmd.chain_id.clone().ok_or("missing chain identifier")?;
let peer_id = cmd.peer_id.ok_or("missing peer identifier")?;
let force = cmd.force;
let peer_ids = if !cmd.all && cmd.peer_id.is_empty() {
return Err("missing peer identifier".into());
} else {
cmd.peer_id.clone()
};

Ok(RmOptions {
chain_id,
peer_id,
force,
peer_ids,
all: cmd.all,
yes: cmd.yes,
force: cmd.force,
})
}
}

impl Runnable for RmCmd {
fn run(&self) {
self.cmd()
.unwrap_or_else(|e| fatal_error(app_reader().deref(), &*e))
}
}

impl RmCmd {
fn update_config(options: RmOptions, config: &mut Config) -> Result<PeerId, BoxError> {
fn cmd(&self) -> Result<(), BoxError> {
let options = RmOptions::from_cmd(self).map_err(|e| format!("invalid options: {}", e))?;
let mut config = (*app_config()).clone();

let chain_config = config
.chains
.iter_mut()
.find(|c| c.id == options.chain_id)
.ok_or_else(|| format!("could not find config for chain: {}", options.chain_id))?;

let peers_config = chain_config
let mut peers_config = chain_config
.peers
.as_mut()
.ok_or_else(|| format!("no peers configured for chain: {}", options.chain_id))?;

// Check if the given peer actually exists already, if not throw an error.
let peer_exists = peers_config.light_client(options.peer_id).is_some();
if !peer_exists {
return Err(format!("cannot find peer: {}", options.peer_id).into());
}
if options.all && (options.yes || confirm(&options.chain_id)?) {
let removed_peers = get_all_peer_ids(&peers_config);
chain_config.peers = None;
status_ok!("Removed", "light client peers {:?}", removed_peers);
} else {
for peer_id in options.peer_ids {
let removed_peer = remove_peer(&mut peers_config, peer_id, options.force)?;
status_ok!("Removed", "light client peer '{}'", removed_peer);
}
};

// Only allow remove the primary peer if the --force option is set
let is_primary = peers_config.primary == options.peer_id;
if is_primary && !options.force {
return Err("cannot remove primary peer, pass --force flag to force removal".into());
}
let config_path = crate::config::config_path()?;
relayer::config::store(&config, config_path)?;

// Filter out the light client config with the specified peer id
peers_config
.light_clients
.retain(|p| p.peer_id != options.peer_id);

// Disallow removing the last remaining peer
if peers_config.light_clients.is_empty() {
return Err(
"cannot remove last remaining peer, add other peers before removing this one"
.into(),
);
}
Ok(())
}
}

// If the peer we removed was the primary peer, use the next available peer as the primary
if is_primary {
let new_primary = peers_config.light_clients.first().unwrap(); // SAFETY: safe because of check above
fn remove_peer(
peers_config: &mut PeersConfig,
peer_id: PeerId,
force: bool,
) -> Result<PeerId, BoxError> {
// Check if the given peer actually exists already, if not throw an error.
let peer_exists = peers_config.light_client(peer_id).is_some();
if !peer_exists {
return Err(format!("cannot find peer: {}", peer_id).into());
}

// Only allow remove the primary peer if the --force option is set
let removed_primary = peers_config.primary == peer_id;
if removed_primary && !force {
return Err("cannot remove primary peer, pass --force flag to force removal".into());
}

// Filter out the light client config with the specified peer id
peers_config.light_clients.retain(|p| p.peer_id != peer_id);

// If the peer we removed was the primary peer, use the next available peer as the primary,
// if any.
if removed_primary {
if let Some(new_primary) = peers_config.light_clients.first() {
peers_config.primary = new_primary.peer_id;
}

Ok(options.peer_id)
}

fn cmd(&self) -> Result<(), BoxError> {
let options = RmOptions::from_cmd(self).map_err(|e| format!("invalid options: {}", e))?;
let mut config = (*app_config()).clone();
Ok(peer_id)
}

let removed_peer = Self::update_config(options, &mut config)?;
fn get_all_peer_ids(peers_config: &PeersConfig) -> Vec<String> {
peers_config
.light_clients
.iter()
.map(|c| c.peer_id.to_string())
.collect()
}

let config_path = crate::config::config_path()?;
relayer::config::store(&config, config_path)?;
fn confirm(chain_id: &ChainId) -> Result<bool, BoxError> {
loop {
print!(
"\n? Do you really want to remove all peers for chain '{}'? (y/n) > ",
chain_id
);

status_ok!("Removed", "light client peer '{}'", removed_peer);
io::stdout().flush()?; // need to flush stdout since stdout is often line-buffered

Ok(())
}
}
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;

impl Runnable for RmCmd {
fn run(&self) {
self.cmd()
.unwrap_or_else(|e| fatal_error(app_reader().deref(), &*e))
match choice.trim_end() {
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
_ => continue,
}
}
}
2 changes: 1 addition & 1 deletion relayer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ pub fn parse(path: impl AsRef<Path>) -> Result<Config, error::Error> {
/// Serialize the given `Config` as TOML to the given config file.
pub fn store(config: &Config, path: impl AsRef<Path>) -> Result<(), error::Error> {
let mut file = if path.as_ref().exists() {
fs::OpenOptions::new().write(true).open(path)
fs::OpenOptions::new().write(true).truncate(true).open(path)
} else {
File::create(path)
}
Expand Down

0 comments on commit 1bd058d

Please sign in to comment.