1//! Displays Zebra's scanning results:
2//!
3//! 1. Opens Zebra's scanning storage and reads the results containing scanning keys and TXIDs.
4//! 2. Fetches the transactions by their TXIDs from Zebra using the `getrawtransaction` RPC.
5//! 3. Decrypts the tx outputs using the corresponding scanning key.
6//! 4. Prints the memos in the outputs.
78use std::collections::HashMap;
910use hex::ToHex;
11use jsonrpc::simple_http::SimpleHttpTransport;
12use jsonrpc::Client;
1314use zcash_client_backend::decrypt_transaction;
15use zcash_primitives::consensus::{BlockHeight, BranchId};
16use zcash_primitives::transaction::Transaction;
17use zip32::AccountId;
1819use zebra_scan::scan::{dfvk_to_ufvk, sapling_key_to_dfvk};
20use zebra_scan::{storage::Storage, Config};
2122/// Prints the memos of transactions from Zebra's scanning results storage.
23///
24/// Reads the results storage, iterates through all decrypted memos, and prints the them to standard
25/// output. Filters out some frequent and uninteresting memos typically associated with ZECPages.
26///
27/// Notes:
28///
29/// - `#[allow(clippy::print_stdout)]` is set to allow usage of `println!` for displaying the memos.
30/// - This function expects Zebra's RPC server to be available.
31///
32/// # Panics
33///
34/// When:
35///
36/// - The Sapling key from the storage is not valid.
37/// - There is no diversifiable full viewing key (dfvk) available.
38/// - The RPC response cannot be decoded from a hex string to bytes.
39/// - The transaction fetched via RPC cannot be deserialized from raw bytes.
40#[allow(clippy::print_stdout)]
41pub fn main() {
42let network = zebra_chain::parameters::Network::Mainnet;
43let zp_network = zebra_scan::scan::zp_network(&network);
44let storage = Storage::new(&Config::default(), &network, true);
45// If the first memo is empty, it doesn't get printed. But we never print empty memos anyway.
46let mut prev_memo = "".to_owned();
4748for (key, _) in storage.sapling_keys_last_heights().iter() {
49let ufvks = HashMap::from([(
50 AccountId::ZERO,
51 dfvk_to_ufvk(&sapling_key_to_dfvk(key, &network).expect("dfvk")).expect("ufvk"),
52 )]);
5354for (height, txids) in storage.sapling_results(key) {
55let height = BlockHeight::from(height.0);
5657for txid in txids.iter() {
58let tx = Transaction::read(
59&hex::decode(fetch_tx_via_rpc(txid.encode_hex()))
60 .expect("RPC response should be decodable from hex string to bytes")[..],
61 BranchId::for_height(&zp_network, height),
62 )
63 .expect("TX fetched via RPC should be deserializable from raw bytes");
6465for output in decrypt_transaction(&zp_network, Some(height), None, &tx, &ufvks)
66 .sapling_outputs()
67 {
68let memo = memo_bytes_to_string(output.memo().as_array());
6970if !memo.is_empty()
71// Filter out some uninteresting and repeating memos from ZECPages.
72&& !memo.contains("LIKE:")
73 && !memo.contains("VOTE:")
74 && memo != prev_memo
75 {
76println!("{memo}\n");
77 prev_memo = memo;
78 }
79 }
80 }
81 }
82 }
83}
8485/// Trims trailing zeroes from a memo, and returns the memo as a [`String`].
86fn memo_bytes_to_string(memo: &[u8; 512]) -> String {
87match memo.iter().rposition(|&byte| byte != 0) {
88Some(i) => String::from_utf8_lossy(&memo[..=i]).into_owned(),
89None => "".to_owned(),
90 }
91}
9293/// Uses the `getrawtransaction` RPC to retrieve a transaction by its TXID.
94fn fetch_tx_via_rpc(txid: String) -> String {
95let client = Client::with_transport(
96 SimpleHttpTransport::builder()
97 .url("127.0.0.1:8232")
98 .expect("Zebra's URL should be valid")
99 .build(),
100 );
101102 client
103 .send_request(client.build_request("getrawtransaction", Some(&jsonrpc::arg([txid]))))
104 .expect("Sending the `getrawtransaction` request should succeed")
105 .result()
106 .expect("Zebra's RPC response should contain a valid result")
107}