From d05094388ecef53f374f1eb17693a43317f01cfa Mon Sep 17 00:00:00 2001 From: Conor Schaefer Date: Mon, 13 Jan 2025 16:19:26 -0800 Subject: [PATCH] test(pmonitor): add testnet integration --- crates/bin/pmonitor/Cargo.toml | 3 +- crates/bin/pmonitor/tests/testnet.rs | 119 +++++++++++++++++++++++++++ justfile | 2 +- 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 crates/bin/pmonitor/tests/testnet.rs diff --git a/crates/bin/pmonitor/Cargo.toml b/crates/bin/pmonitor/Cargo.toml index 71612426ba..f9799f9f91 100644 --- a/crates/bin/pmonitor/Cargo.toml +++ b/crates/bin/pmonitor/Cargo.toml @@ -8,7 +8,8 @@ homepage = { workspace = true } license = { workspace = true } publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +integration-testnet = [] [dependencies] anyhow = {workspace = true} diff --git a/crates/bin/pmonitor/tests/testnet.rs b/crates/bin/pmonitor/tests/testnet.rs new file mode 100644 index 0000000000..53a069a135 --- /dev/null +++ b/crates/bin/pmonitor/tests/testnet.rs @@ -0,0 +1,119 @@ +#![cfg(feature = "integration-testnet")] +//! Integration tests for pmonitor against the public Penumbra testnet. +//! Mostly useful for verifying that HTTPS connections for gRPC +//! are well supported. +use anyhow::Result; +use assert_cmd::Command as AssertCommand; +use pcli::config::PcliConfig; + +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::{Path, PathBuf}; +use tempfile::{tempdir, NamedTempFile, TempDir}; + +const NODE_URL: &str = "https://testnet.plinfra.net"; + +/// Initialize a new pcli wallet at the target directory. +/// Discards the generated seed phrase. +fn pcli_init_softkms(pcli_home: &TempDir) -> Result<()> { + let mut cmd = AssertCommand::cargo_bin("pcli")?; + cmd.args([ + "--home", + pcli_home + .path() + .to_str() + .expect("can convert wallet path to string"), + "init", + "--grpc-url", + NODE_URL, + "soft-kms", + "generate", + ]) + // send empty string to accept the interstitial seed phrase display + .write_stdin(""); + cmd.assert().success(); + Ok(()) +} + +/// Retrieve a FullViewingKey from a pcli home dir. +fn get_fvk_from_wallet_dir(pcli_home: &TempDir) -> Result { + let pcli_config_path = pcli_home.path().join("config.toml"); + let pcli_config = PcliConfig::load( + pcli_config_path + .to_str() + .expect("failed to convert pcli wallet path to str"), + )?; + Ok(pcli_config.full_viewing_key.to_string()) +} + +/// Given a list of FVKs, formatted as Strings, write a JSON file +/// containing those FVKs, for use with pmonitor via the `--fvks` CLI flag. +fn write_fvks_json(fvks: Vec, dest_filepath: &File) -> Result<()> { + let mut w = BufWriter::new(dest_filepath); + serde_json::to_writer(&mut w, &fvks)?; + w.flush()?; + Ok(()) +} + +#[test] +// Initialize an empty (i.e. random) wallet. We don't care about prior balances, +// because we're not exercising misbehavior: all we care about is that pmonitor +// can talk to an HTTPS endpoint and understand the blocks it pulls. +pub fn pmonitor_passes_with_empty_wallet_on_testnet() -> Result<()> { + tracing_subscriber::fmt::try_init().ok(); + let pcli_home = tempdir().unwrap(); + pcli_init_softkms(&pcli_home)?; + + let fvks = vec![get_fvk_from_wallet_dir(&pcli_home)?]; + let fvks_json = NamedTempFile::new()?; + write_fvks_json(fvks, fvks_json.as_file())?; + let pmonitor_pardir = tempfile::tempdir()?; + let pmonitor_home = initialize_pmonitor(&pmonitor_pardir, fvks_json.path())?; + + // Run `pmonitor audit` based on the pcli wallets and associated FVKs. + let cmd = AssertCommand::cargo_bin("pmonitor")? + .args([ + "--home", + pmonitor_home + .as_path() + .to_str() + .expect("failed to parse pmonitor tempdir as directory"), + "audit", + ]) + .ok(); + + if cmd.is_ok() { + Ok(()) + } else { + anyhow::bail!("failed during 'pmonitor audit'") + } +} + +/// Generate a config directory for `pmonitor`, based on input FVKs. +fn initialize_pmonitor(tmpdir: &TempDir, fvks_json: &Path) -> anyhow::Result { + // pmonitor doesn't like pre-existing homedirs so we'll nest this one. + let pmonitor_home = tmpdir.path().join("pmonitor"); + + let cmd = AssertCommand::cargo_bin("pmonitor")? + .args([ + "--home", + pmonitor_home + .as_path() + .to_str() + .expect("failed to parse pmonitor tempdir as dir"), + "init", + "--grpc-url", + NODE_URL, + "--fvks", + fvks_json + .to_str() + .expect("failed to parse fvk json tempfile as filepath"), + ]) + .output(); + + assert!( + cmd.unwrap().status.success(), + "failed to initialize pmonitor" + ); + Ok(pmonitor_home) +} diff --git a/justfile b/justfile index 1d86198fbe..91093ddb55 100644 --- a/justfile +++ b/justfile @@ -5,7 +5,7 @@ default: # Run integration tests for pmonitor tool test-pmonitor: # prebuild cargo binaries required for integration tests - cargo -q build --package pcli --package pd --package pmonitor + cargo -q build --release --package pcli --package pd --package pmonitor cargo -q run --release --bin pd -- network unsafe-reset-all rm -rf /tmp/pmonitor-integration-test cargo nextest run -p pmonitor --run-ignored=ignored-only --test-threads 1