Skip to content

Commit

Permalink
feat(mirror): add a show-keys subcommand (#11847)
Browse files Browse the repository at this point in the history
This adds a subcommand that helps to view the secret keys associated
with an account in the source chain. We allow mapping a key given
explicitly on the command line, as well as finding and mapping the keys
associated with an account either by RPC or by reading from a NEAR home
directory
  • Loading branch information
marcelo-gonzalez authored Jul 31, 2024
1 parent e43e06a commit a797ef4
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 21 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions tools/mirror/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ near-chain-primitives.workspace = true
near-client.workspace = true
near-client-primitives.workspace = true
near-epoch-manager.workspace = true
near-jsonrpc-client.workspace = true
near-jsonrpc-primitives.workspace = true
near-indexer-primitives.workspace = true
near-indexer.workspace = true
near-network.workspace = true
Expand All @@ -60,6 +62,8 @@ nightly = [
"near-epoch-manager/nightly",
"near-indexer-primitives/nightly",
"near-indexer/nightly",
"near-jsonrpc-client/nightly",
"near-jsonrpc-primitives/nightly",
"near-network/nightly",
"near-o11y/nightly",
"near-primitives-core/nightly",
Expand All @@ -77,6 +81,8 @@ nightly_protocol = [
"near-epoch-manager/nightly_protocol",
"near-indexer-primitives/nightly_protocol",
"near-indexer/nightly_protocol",
"near-jsonrpc-client/nightly_protocol",
"near-jsonrpc-primitives/nightly_protocol",
"near-network/nightly_protocol",
"near-o11y/nightly_protocol",
"near-primitives-core/nightly_protocol",
Expand Down
179 changes: 158 additions & 21 deletions tools/mirror/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use anyhow::Context;
use near_primitives::types::BlockHeight;
use std::cell::Cell;
use std::path::PathBuf;

use near_primitives::types::BlockHeight;
use near_primitives::views::AccessKeyPermissionView;

#[derive(clap::Parser)]
pub struct MirrorCommand {
#[clap(subcommand)]
Expand All @@ -13,6 +15,7 @@ pub struct MirrorCommand {
enum SubCommand {
Prepare(PrepareCmd),
Run(RunCmd),
ShowKeys(ShowKeysCmd),
}

/// initialize a target chain with genesis records from the source chain, and
Expand Down Expand Up @@ -50,7 +53,6 @@ struct RunCmd {
impl RunCmd {
fn run(self) -> anyhow::Result<()> {
openssl_probe::init_ssl_cert_env_vars();
let runtime = tokio::runtime::Runtime::new().context("failed to start tokio runtime")?;

let secret = if let Some(secret_file) = &self.secret_file {
let secret = crate::secret::load(secret_file)
Expand All @@ -68,25 +70,14 @@ impl RunCmd {
None
};

let system = new_actix_system(runtime);
system
.block_on(async move {
let _subscriber_guard = near_o11y::default_subscriber(
near_o11y::EnvFilterBuilder::from_env().finish().unwrap(),
&near_o11y::Options::default(),
)
.global();
actix::spawn(crate::run(
self.source_home,
self.target_home,
secret,
self.stop_height,
self.online_source,
self.config_path,
))
.await
})
.unwrap()
run_async(crate::run(
self.source_home,
self.target_home,
secret,
self.stop_height,
self.online_source,
self.config_path,
))
}
}

Expand Down Expand Up @@ -131,6 +122,136 @@ impl PrepareCmd {
}
}

/// Given a source chain NEAR home dir, read and map access keys corresponding to
/// a given account ID and optional block height.
#[derive(clap::Parser)]
struct ShowKeysFromSourceDBCmd {
#[clap(long)]
home: PathBuf,
#[clap(long)]
account_id: String,
#[clap(long)]
block_height: Option<BlockHeight>,
}

/// Given an RPC URL, request and map access keys corresponding to
/// a given account ID and optional block height.
#[derive(clap::Parser)]
struct ShowKeysFromRPCCmd {
#[clap(long)]
rpc_url: String,
#[clap(long)]
account_id: String,
#[clap(long)]
block_height: Option<BlockHeight>,
}

/// Map the given public key
#[derive(clap::Parser)]
struct ShowKeyFromKeyCmd {
#[clap(long)]
public_key: String,
}

/// Show the default extra key. This key should exist for any account that does not have
/// any full access keys in the source chain (e.g. validators with staking pools)
#[derive(clap::Parser)]
struct ShowDefaultExtraKeyCmd;

#[derive(clap::Parser)]
enum ShowKeysSubCommand {
FromSourceDB(ShowKeysFromSourceDBCmd),
FromRPC(ShowKeysFromRPCCmd),
FromPubKey(ShowKeyFromKeyCmd),
DefaultExtraKey(ShowDefaultExtraKeyCmd),
}

/// Print the secret keys that correspond to source chain public keys
#[derive(clap::Parser)]
struct ShowKeysCmd {
/// file containing an optional secret as generated by the
/// `prepare` command.
#[clap(long)]
secret_file: Option<PathBuf>,
#[clap(subcommand)]
subcmd: ShowKeysSubCommand,
}

impl ShowKeysCmd {
fn run(self) -> anyhow::Result<()> {
let secret = if let Some(secret_file) = &self.secret_file {
let secret = crate::secret::load(secret_file)
.with_context(|| format!("Failed to load secret from {:?}", secret_file))?;
secret
} else {
None
};
let mut probably_extra_key = false;
let keys = match self.subcmd {
ShowKeysSubCommand::FromSourceDB(c) => {
let keys = crate::key_util::keys_from_source_db(
&c.home,
&c.account_id,
c.block_height,
secret.as_ref(),
)?;
probably_extra_key = keys.iter().all(|key| {
key.permission
.as_ref()
.map_or(true, |p| *p != AccessKeyPermissionView::FullAccess)
});
keys
}
ShowKeysSubCommand::FromRPC(c) => {
let keys = run_async(async move {
crate::key_util::keys_from_rpc(
&c.rpc_url,
&c.account_id,
c.block_height,
secret.as_ref(),
)
.await
})?;
probably_extra_key = keys.iter().all(|key| {
key.permission
.as_ref()
.map_or(true, |p| *p != AccessKeyPermissionView::FullAccess)
});
keys
}
ShowKeysSubCommand::FromPubKey(c) => {
vec![crate::key_util::map_pub_key(&c.public_key, secret.as_ref())?]
}
ShowKeysSubCommand::DefaultExtraKey(_c) => {
vec![crate::key_util::default_extra_key(secret.as_ref())]
}
};
for key in keys.iter() {
if let Some(k) = &key.original_key {
println!("original pub key: {}", k);
}
println!(
"mapped secret key: {}\nmapped public key: {}",
&key.mapped_key,
key.mapped_key.public_key()
);
if let Some(a) = &key.permission {
println!("access: {:?}", a);
}
println!("------------")
}
if probably_extra_key {
let extra_key = crate::key_mapping::default_extra_key(secret.as_ref());
println!(
"{} account probably has an extra full access key added:\nmapped secret key: {}\npublic key: {}",
if keys.is_empty() { "If it exists, this" } else { "This" },
&extra_key, extra_key.public_key(),
);
}
Ok(())
}
}

// copied from neard/src/cli.rs
fn new_actix_system(runtime: tokio::runtime::Runtime) -> actix::SystemRunner {
// `with_tokio_rt()` accepts an `Fn()->Runtime`, however we know that this function is called exactly once.
Expand All @@ -144,13 +265,29 @@ fn new_actix_system(runtime: tokio::runtime::Runtime) -> actix::SystemRunner {
})
}

fn run_async<F: std::future::Future + 'static>(f: F) -> F::Output {
let runtime = tokio::runtime::Runtime::new().unwrap();
let system = new_actix_system(runtime);
system
.block_on(async move {
let _subscriber_guard = near_o11y::default_subscriber(
near_o11y::EnvFilterBuilder::from_env().finish().unwrap(),
&near_o11y::Options::default(),
)
.global();
actix::spawn(f).await
})
.unwrap()
}

impl MirrorCommand {
pub fn run(self) -> anyhow::Result<()> {
tracing::warn!(target: "mirror", "the mirror command is not stable, and may be removed or changed arbitrarily at any time");

match self.subcmd {
SubCommand::Prepare(r) => r.run(),
SubCommand::Run(r) => r.run(),
SubCommand::ShowKeys(r) => r.run(),
}
}
}
Loading

0 comments on commit a797ef4

Please sign in to comment.