devtools: Add `--lookup` argument to `zcash-inspect`

This queries `lightwalletd` for things that might be txids, and could be
extended to other queryable formats in future.
This commit is contained in:
Jack Grigg 2024-07-24 18:53:18 +00:00
parent 104c0c2aed
commit 86409235c2
6 changed files with 261 additions and 32 deletions

88
Cargo.lock generated
View File

@ -276,7 +276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecfa55659849ace733f86ccd219da40abd8bc14124e40b312433e85a5a266e77"
dependencies = [
"futures-io",
"rustls",
"rustls 0.21.12",
]
[[package]]
@ -1229,6 +1229,7 @@ dependencies = [
name = "devtools"
version = "0.0.0"
dependencies = [
"anyhow",
"bech32",
"bellman",
"bip0039",
@ -1247,8 +1248,11 @@ dependencies = [
"serde",
"serde_json",
"sha2 0.10.8",
"tokio",
"tonic",
"uint",
"zcash_address",
"zcash_client_backend",
"zcash_encoding",
"zcash_keys",
"zcash_note_encryption",
@ -2422,9 +2426,9 @@ checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0"
dependencies = [
"log",
"once_cell",
"rustls",
"rustls-webpki",
"webpki-roots",
"rustls 0.21.12",
"rustls-webpki 0.101.7",
"webpki-roots 0.25.4",
]
[[package]]
@ -3523,10 +3527,41 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring 0.17.8",
"rustls-webpki",
"rustls-webpki 0.101.7",
"sct",
]
[[package]]
name = "rustls"
version = "0.23.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
dependencies = [
"log",
"once_cell",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.6",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
dependencies = [
"base64 0.22.1",
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
@ -3537,6 +3572,17 @@ dependencies = [
"untrusted 0.9.0",
]
[[package]]
name = "rustls-webpki"
version = "0.102.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
dependencies = [
"ring 0.17.8",
"rustls-pki-types",
"untrusted 0.9.0",
]
[[package]]
name = "rustversion"
version = "1.0.17"
@ -4280,7 +4326,18 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
dependencies = [
"rustls",
"rustls 0.21.12",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.12",
"rustls-pki-types",
"tokio",
]
@ -4363,6 +4420,7 @@ dependencies = [
"axum",
"base64 0.22.1",
"bytes",
"flate2",
"h2",
"http 1.1.0",
"http-body",
@ -4373,13 +4431,16 @@ dependencies = [
"percent-encoding",
"pin-project",
"prost",
"rustls-pemfile",
"socket2",
"tokio",
"tokio-rustls 0.26.0",
"tokio-stream",
"tower",
"tower-layer",
"tower-service",
"tracing",
"webpki-roots 0.26.3",
]
[[package]]
@ -5006,7 +5067,7 @@ dependencies = [
"educe",
"futures",
"pin-project",
"rustls",
"rustls 0.21.12",
"thiserror",
"tokio",
"tokio-util",
@ -5462,6 +5523,15 @@ version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "webpki-roots"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "which"
version = "4.4.2"
@ -5771,12 +5841,12 @@ dependencies = [
"subtle",
"time",
"tokio",
"tokio-rustls",
"tokio-rustls 0.24.0",
"tonic",
"tonic-build",
"tor-rtcompat",
"tracing",
"webpki-roots",
"webpki-roots 0.25.4",
"which",
"zcash_address",
"zcash_encoding",

View File

@ -17,11 +17,12 @@ equihash.workspace = true
group.workspace = true
sha2.workspace = true
zcash_address.workspace = true
zcash_client_backend ={ workspace = true, features = ["lightwalletd-tonic-transport"] }
zcash_encoding.workspace = true
zcash_keys.workspace = true
zcash_note_encryption.workspace = true
zcash_primitives = { workspace = true, features = ["transparent-inputs"] }
zcash_proofs.workspace = true
zcash_proofs = { workspace = true, features = ["directories"] }
zcash_protocol.workspace = true
# Transparent
@ -39,11 +40,14 @@ sapling.workspace = true
orchard.workspace = true
# zcash-inspect tool
anyhow = "1"
hex.workspace = true
lazy_static.workspace = true
secrecy.workspace = true
serde.workspace = true
serde_json.workspace = true
tonic = { workspace = true, features = ["gzip", "tls-webpki-roots"] }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
uint = "0.9"
[[bin]]

View File

@ -0,0 +1,85 @@
use tonic::transport::{Channel, ClientTlsConfig};
use zcash_client_backend::proto::service::{
compact_tx_streamer_client::CompactTxStreamerClient, TxFilter,
};
use zcash_primitives::transaction::Transaction;
use zcash_protocol::consensus::{BlockHeight, BranchId, Network};
const MAINNET: Server = Server {
host: "zec.rocks",
port: 443,
};
const TESTNET: Server = Server {
host: "testnet.zec.rocks",
port: 443,
};
struct Server {
host: &'static str,
port: u16,
}
impl Server {
fn endpoint(&self) -> String {
format!("https://{}:{}", self.host, self.port)
}
}
async fn connect(server: &Server) -> anyhow::Result<CompactTxStreamerClient<Channel>> {
let channel = Channel::from_shared(server.endpoint())?;
let tls = ClientTlsConfig::new()
.domain_name(server.host.to_string())
.with_webpki_roots();
let channel = channel.tls_config(tls)?;
Ok(CompactTxStreamerClient::new(channel.connect().await?))
}
#[derive(Debug)]
pub(crate) struct Lightwalletd {
inner: CompactTxStreamerClient<Channel>,
parameters: Network,
}
impl Lightwalletd {
pub(crate) async fn mainnet() -> anyhow::Result<Self> {
Ok(Self {
inner: connect(&MAINNET).await?,
parameters: Network::MainNetwork,
})
}
pub(crate) async fn testnet() -> anyhow::Result<Self> {
Ok(Self {
inner: connect(&TESTNET).await?,
parameters: Network::TestNetwork,
})
}
pub(crate) async fn lookup_txid(
&mut self,
candidate: [u8; 32],
) -> Option<(Transaction, Option<BlockHeight>)> {
let request = TxFilter {
hash: candidate.into(),
..Default::default()
};
let response = self.inner.get_transaction(request).await.ok()?.into_inner();
// `RawTransaction.height` has type u64 in the protobuf format, but is documented
// as using -1 for the "not mined" sentinel. Given that we only support u32 block
// heights, -1 in two's complement will fall outside that range.
let mined_height = response.height.try_into().ok();
Transaction::read(
&response.data[..],
mined_height
.map(|height| BranchId::for_height(&self.parameters, height))
.unwrap_or(BranchId::Nu5),
)
.ok()
.map(|tx| (tx, mined_height))
}
}

View File

@ -6,6 +6,7 @@ use std::process;
use gumdrop::{Options, ParsingStyle};
use lazy_static::lazy_static;
use secrecy::Zeroize;
use tokio::runtime::Runtime;
use zcash_address::ZcashAddress;
use zcash_primitives::{block::BlockHeader, consensus::BranchId, transaction::Transaction};
use zcash_proofs::{default_params_folder, load_parameters, ZcashParameters};
@ -16,6 +17,7 @@ use context::{Context, ZUint256};
mod address;
mod block;
mod keys;
mod lookup;
mod transaction;
lazy_static! {
@ -35,6 +37,9 @@ struct CliOptions {
#[options(help = "Print this help output")]
help: bool,
#[options(help = "Query information from the chain to help determine what the data is")]
lookup: bool,
#[options(free, required, help = "String or hex-encoded bytes to inspect")]
data: String,
@ -64,7 +69,7 @@ fn main() {
opts.data.zeroize();
keys::inspect_mnemonic(mnemonic, opts.context);
} else if let Ok(bytes) = hex::decode(&opts.data) {
inspect_bytes(bytes, opts.context);
inspect_bytes(bytes, opts.context, opts.lookup);
} else if let Ok(addr) = ZcashAddress::try_from_encoded(&opts.data) {
address::inspect(addr);
} else {
@ -90,7 +95,7 @@ where
})
}
fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>) {
fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>, lookup: bool) {
if let Some(block) = complete(&bytes, |r| block::Block::read(r)) {
block::inspect(&block, context);
} else if let Some(header) = complete(&bytes, |r| BlockHeader::read(r)) {
@ -98,30 +103,64 @@ fn inspect_bytes(bytes: Vec<u8>, context: Option<Context>) {
} else if let Some(tx) = complete(&bytes, |r| Transaction::read(r, BranchId::Nu5)) {
// TODO: Take the branch ID used above from the context if present.
// https://github.com/zcash/zcash/issues/6831
transaction::inspect(tx, context);
transaction::inspect(tx, context, None);
} else {
// It's not a known variable-length format. check fixed-length data formats.
inspect_fixed_length_bytes(bytes);
match bytes.len() {
32 => inspect_possible_hash(bytes.try_into().unwrap(), context, lookup),
64 => {
// Could be a signature
eprintln!("This is most likely a signature.");
}
_ => {
eprintln!("Binary data does not match known Zcash data formats.");
process::exit(2);
}
}
}
}
fn inspect_fixed_length_bytes(bytes: Vec<u8>) {
match bytes.len() {
32 => {
eprintln!(
"This is most likely a hash of some sort, or maybe a commitment or nullifier."
);
if bytes.iter().take(4).all(|c| c == &0) {
eprintln!("- It could be a mainnet block hash.");
}
}
64 => {
// Could be a signature
eprintln!("This is most likely a signature.");
}
_ => {
eprintln!("Binary data does not match known Zcash data formats.");
process::exit(2);
fn inspect_possible_hash(bytes: [u8; 32], context: Option<Context>, lookup: bool) {
let maybe_mainnet_block_hash = bytes.iter().take(4).all(|c| c == &0);
if lookup {
// Block hashes and txids are byte-reversed; we didn't do this when parsing the
// original hex because other hex byte encodings are not byte-reversed.
let mut candidate = bytes;
candidate.reverse();
let rt = Runtime::new().unwrap();
let found = rt.block_on(async {
match lookup::Lightwalletd::mainnet().await {
Err(e) => eprintln!("Error: Failed to connect to mainnet lightwalletd: {:?}", e),
Ok(mut mainnet) => {
if let Some((tx, mined_height)) = mainnet.lookup_txid(candidate).await {
transaction::inspect(tx, context, mined_height);
return true;
}
}
};
match lookup::Lightwalletd::testnet().await {
Err(e) => eprintln!("Error: Failed to connect to testnet lightwalletd: {:?}", e),
Ok(mut testnet) => {
if let Some((tx, mined_height)) = testnet.lookup_txid(candidate).await {
transaction::inspect(tx, context, mined_height);
return true;
}
}
};
false
});
if found {
return;
}
}
eprintln!("This is most likely a hash of some sort, or maybe a commitment or nullifier.");
if maybe_mainnet_block_hash {
eprintln!("- It could be a mainnet block hash.");
}
}

View File

@ -152,9 +152,16 @@ impl Authorization for PrecomputedAuth {
type TzeAuth = tze::Authorized;
}
pub(crate) fn inspect(tx: Transaction, context: Option<Context>) {
pub(crate) fn inspect(
tx: Transaction,
context: Option<Context>,
mined_height: Option<BlockHeight>,
) {
eprintln!("Zcash transaction");
eprintln!(" - ID: {}", tx.txid());
if let Some(height) = mined_height {
eprintln!(" - Mined in block {}", height);
}
eprintln!(" - Version: {:?}", tx.version());
match tx.version() {
// TODO: If pre-v5 and no branch ID provided in context, disable signature checks.

View File

@ -1023,10 +1023,26 @@ criteria = "safe-to-deploy"
version = "0.21.8"
criteria = "safe-to-deploy"
[[exemptions.rustls]]
version = "0.23.12"
criteria = "safe-to-deploy"
[[exemptions.rustls-pemfile]]
version = "2.1.2"
criteria = "safe-to-deploy"
[[exemptions.rustls-pki-types]]
version = "1.7.0"
criteria = "safe-to-deploy"
[[exemptions.rustls-webpki]]
version = "0.101.7"
criteria = "safe-to-deploy"
[[exemptions.rustls-webpki]]
version = "0.102.6"
criteria = "safe-to-deploy"
[[exemptions.rusty-fork]]
version = "0.3.0"
criteria = "safe-to-deploy"
@ -1211,6 +1227,10 @@ criteria = "safe-to-deploy"
version = "2.2.0"
criteria = "safe-to-deploy"
[[exemptions.tokio-rustls]]
version = "0.26.0"
criteria = "safe-to-deploy"
[[exemptions.tokio-util]]
version = "0.7.10"
criteria = "safe-to-deploy"
@ -1439,6 +1459,10 @@ criteria = "safe-to-deploy"
version = "0.3.65"
criteria = "safe-to-deploy"
[[exemptions.webpki-roots]]
version = "0.26.3"
criteria = "safe-to-deploy"
[[exemptions.which]]
version = "4.4.2"
criteria = "safe-to-deploy"