pmonitor/genesis.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
//! Logic for inspecting the [CompactBlock] at genesis of the target chain.
//! Used to compute balances for tracked FVKs at genesis time. The initial genesis balance is
//! stored in the `pmonitor` config file, so that audit actions can reference it.
use std::{collections::BTreeMap, str::FromStr};
use penumbra_asset::STAKING_TOKEN_ASSET_ID;
use penumbra_compact_block::{CompactBlock, StatePayload};
use penumbra_keys::FullViewingKey;
use penumbra_num::Amount;
use penumbra_shielded_pool::{Note, NotePayload};
use penumbra_stake::{
rate::{BaseRateData, RateData},
DelegationToken,
};
use penumbra_tct::StateCommitment;
#[derive(Debug, Clone)]
pub struct FilteredGenesisBlock {
// Notes per FVK
#[allow(dead_code)]
pub notes: BTreeMap<String, BTreeMap<StateCommitment, Note>>,
// UM-equivalent balances per FVK
pub balances: BTreeMap<String, Amount>,
}
/// Scanning of the genesis `CompactBlock` with a list of FVKs to determine the
/// initial balances of the relevant addresses.
///
/// Assumption: There are no swaps or nullifiers in the genesis block.
pub async fn scan_genesis_block(
CompactBlock {
height,
state_payloads,
..
}: CompactBlock,
fvks: Vec<FullViewingKey>,
) -> anyhow::Result<FilteredGenesisBlock> {
assert_eq!(height, 0);
let mut notes = BTreeMap::new();
let mut balances = BTreeMap::new();
// Calculate the rate data for each validator in the initial validator set.
let base_rate = BaseRateData {
epoch_index: 0,
base_reward_rate: 0u128.into(),
base_exchange_rate: 1_0000_0000u128.into(),
};
// We proceed one FVK at a time.
for fvk in fvks {
// Trial-decrypt a note with our a specific viewing key
let trial_decrypt_note =
|note_payload: NotePayload| -> tokio::task::JoinHandle<Option<Note>> {
let fvk2 = fvk.clone();
tokio::spawn(async move { note_payload.trial_decrypt(&fvk2) })
};
// Trial-decrypt the notes in this block, keeping track of the ones that were meant for the FVK
// we're monitoring.
let mut note_decryptions = Vec::new();
// We only care about notes, so we're ignoring swaps and rolled-up commitments.
for payload in state_payloads.iter() {
if let StatePayload::Note { note, .. } = payload {
note_decryptions.push(trial_decrypt_note((**note).clone()));
}
}
let mut notes_for_this_fvk = BTreeMap::new();
for decryption in note_decryptions {
if let Some(note) = decryption
.await
.expect("able to join tokio note decryption handle")
{
notes_for_this_fvk.insert(note.commit(), note.clone());
// Balance is expected to be in the staking or delegation token
let note_value = note.value();
if note_value.asset_id == *STAKING_TOKEN_ASSET_ID {
balances
.entry(fvk.to_string())
.and_modify(|existing_amount| *existing_amount += note.amount())
.or_insert(note.amount());
} else if let Ok(delegation_token) =
DelegationToken::from_str(¬e_value.asset_id.to_string())
{
// We need to convert the amount to the UM-equivalent amount
let rate_data = RateData {
identity_key: delegation_token.validator(),
validator_reward_rate: 0u128.into(),
validator_exchange_rate: base_rate.base_exchange_rate,
};
let um_equivalent_balance = rate_data.unbonded_amount(note.amount());
balances
.entry(fvk.to_string())
.and_modify(|existing_amount| *existing_amount += um_equivalent_balance)
.or_insert(um_equivalent_balance);
} else {
tracing::warn!(
"ignoring note with unknown asset id: {}",
note_value.asset_id
);
}
}
}
// Save all the notes for this FVK, and continue.
notes.insert(fvk.to_string(), notes_for_this_fvk);
}
// Construct filtered genesis block with allocations
let result = FilteredGenesisBlock { notes, balances };
Ok(result)
}