pmonitor/
config.rs

1//! Logic for reading and writing config files for `pmonitor`, in the TOML format.
2use anyhow::Result;
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use url::Url;
6use uuid::Uuid;
7
8use penumbra_sdk_keys::FullViewingKey;
9use penumbra_sdk_num::Amount;
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct FvkEntry {
13    pub fvk: FullViewingKey,
14    pub wallet_id: Uuid,
15}
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18/// Representation of a single Penumbra wallet to track.
19pub struct AccountConfig {
20    /// The initial [FullViewingKey] has specified during `pmonitor init`.
21    ///
22    /// Distinct because the tool understands account migrations.
23    original: FvkEntry,
24    /// The amount held by the account at the time of genesis.
25    genesis_balance: Amount,
26    /// List of account migrations, performed via `pcli migrate balance`, if any.
27    migrations: Vec<FvkEntry>,
28}
29
30impl AccountConfig {
31    pub fn new(original: FvkEntry, genesis_balance: Amount) -> Self {
32        Self {
33            original,
34            genesis_balance,
35            migrations: vec![],
36        }
37    }
38
39    /// Get original/genesis FVK.
40    pub fn original_fvk(&self) -> FullViewingKey {
41        self.original.fvk.clone()
42    }
43
44    /// Get genesis balance.
45    pub fn genesis_balance(&self) -> Amount {
46        self.genesis_balance
47    }
48
49    /// Add migration to the account config.
50    pub fn add_migration(&mut self, fvk_entry: FvkEntry) {
51        self.migrations.push(fvk_entry);
52    }
53
54    /// Get the active wallet, which is the last migration or the original FVK if no migrations have occurred.
55    pub fn active_wallet(&self) -> FvkEntry {
56        if self.migrations.is_empty() {
57            self.original.clone()
58        } else {
59            self.migrations
60                .last()
61                .expect("migrations must not be empty")
62                .clone()
63        }
64    }
65
66    pub fn active_fvk(&self) -> FullViewingKey {
67        self.active_wallet().fvk
68    }
69
70    pub fn active_uuid(&self) -> Uuid {
71        self.active_wallet().wallet_id
72    }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
76/// The primary TOML file for configuring `pmonitor`, containing all its account info.
77///
78/// During `pmonitor audit` runs, the config will be automatically updated
79/// if tracked FVKs were detected to migrate, via `pcli migrate balance`, to save time
80/// on future syncs.
81pub struct PmonitorConfig {
82    /// The gRPC URL for a Penumbra node's `pd` endpoint, used for retrieving account activity.
83    grpc_url: Url,
84    /// The list of Penumbra wallets to track.
85    accounts: Vec<AccountConfig>,
86}
87
88impl PmonitorConfig {
89    pub fn new(grpc_url: Url, accounts: Vec<AccountConfig>) -> Self {
90        Self { grpc_url, accounts }
91    }
92
93    pub fn grpc_url(&self) -> Url {
94        self.grpc_url.clone()
95    }
96
97    pub fn accounts(&self) -> &Vec<AccountConfig> {
98        &self.accounts
99    }
100
101    pub fn set_account(&mut self, index: usize, account: AccountConfig) {
102        self.accounts[index] = account;
103    }
104}
105
106/// Get the destination FVK from a migration memo.
107pub fn parse_dest_fvk_from_memo(memo: &str) -> Result<FullViewingKey> {
108    let re = Regex::new(r"Migrating balance from .+ to (.+)")?;
109    if let Some(captures) = re.captures(memo) {
110        if let Some(dest_fvk_str) = captures.get(1) {
111            return dest_fvk_str
112                .as_str()
113                .parse::<FullViewingKey>()
114                .map_err(|_| anyhow::anyhow!("Invalid destination FVK in memo"));
115        }
116    }
117    Err(anyhow::anyhow!("Could not parse destination FVK from memo"))
118}