penumbra_sdk_shielded_pool/component/
note_manager.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use penumbra_sdk_asset::Value;
5use penumbra_sdk_keys::Address;
6use penumbra_sdk_sct::component::tree::{SctManager, SctRead};
7use penumbra_sdk_sct::CommitmentSource;
8use penumbra_sdk_tct as tct;
9use tct::StateCommitment;
10use tracing::instrument;
11
12use crate::state_key;
13use crate::{Note, NotePayload, Rseed};
14
15/// Manages the addition of new notes to the chain state.
16#[async_trait]
17pub trait NoteManager: StateWrite {
18    /// Mint a new (public) note into the shielded pool.
19    ///
20    /// Most notes in the shielded pool are created by client transactions.
21    /// This method allows the chain to inject new value into the shielded pool
22    /// on its own.
23    #[instrument(skip(self, value, address, source))]
24    async fn mint_note(
25        &mut self,
26        value: Value,
27        address: &Address,
28        source: CommitmentSource,
29    ) -> Result<()> {
30        tracing::debug!(?value, ?address, "minting tokens");
31
32        // These notes are public, so we don't need a blinding factor for
33        // privacy, but since the note commitments are determined by the note
34        // contents, we need to have unique (deterministic) blinding factors for
35        // each note, so they cannot collide.
36        //
37        // Hashing the current SCT root would be sufficient, since it will
38        // change every time we insert a new note.  But computing the SCT root
39        // is very slow, so instead we hash the current position.
40        let position: u64 = self
41            .get_sct()
42            .await
43            .position()
44            .expect("state commitment tree is not full")
45            .into();
46
47        let rseed_bytes: [u8; 32] = blake2b_simd::Params::default()
48            .personal(b"PenumbraMint")
49            .to_state()
50            .update(&position.to_le_bytes())
51            .finalize()
52            .as_bytes()[0..32]
53            .try_into()?;
54
55        let note = Note::from_parts(address.clone(), value, Rseed(rseed_bytes))?;
56        self.add_note_payload(note.payload(), source).await;
57
58        Ok(())
59    }
60
61    #[instrument(skip(self, note_payload, source), fields(commitment = ?note_payload.note_commitment))]
62    async fn add_note_payload(&mut self, note_payload: NotePayload, source: CommitmentSource) {
63        tracing::debug!(source = ?source);
64
65        // 0. Record an ABCI event for transaction indexing.
66        //self.record(event::state_payload(&payload));
67
68        // 1. Insert it into the SCT, recording its note source:
69        let position = self.add_sct_commitment(note_payload.note_commitment, source.clone())
70            .await
71            // TODO: why? can't we exceed the number of state commitments in a block?
72            .expect("inserting into the state commitment tree should not fail because we should budget commitments per block (currently unimplemented)");
73
74        // 2. Finally, record it to be inserted into the compact block:
75        let mut payloads = self.pending_note_payloads();
76        payloads.push_back((position, note_payload, source));
77        self.object_put(state_key::pending_notes(), payloads);
78    }
79
80    #[instrument(skip(self, note_commitment))]
81    async fn add_rolled_up_payload(
82        &mut self,
83        note_commitment: StateCommitment,
84        source: CommitmentSource,
85    ) {
86        tracing::debug!(?note_commitment);
87
88        // 0. Record an ABCI event for transaction indexing.
89        //self.record(event::state_payload(&payload));
90
91        // 1. Insert it into the SCT:
92        let position = self.add_sct_commitment(note_commitment, source)
93            .await
94            // TODO: why? can't we exceed the number of state commitments in a block?
95            .expect("inserting into the state commitment tree should not fail because we should budget commitments per block (currently unimplemented)");
96
97        // 2. Finally, record it to be inserted into the compact block:
98        let mut payloads = self.pending_rolled_up_payloads();
99        payloads.push_back((position, note_commitment));
100        self.object_put(state_key::pending_rolled_up_payloads(), payloads);
101    }
102
103    fn pending_note_payloads(&self) -> im::Vector<(tct::Position, NotePayload, CommitmentSource)> {
104        self.object_get(state_key::pending_notes())
105            .unwrap_or_default()
106    }
107
108    fn pending_rolled_up_payloads(&self) -> im::Vector<(tct::Position, StateCommitment)> {
109        self.object_get(state_key::pending_rolled_up_payloads())
110            .unwrap_or_default()
111    }
112}
113
114impl<T: StateWrite + ?Sized> NoteManager for T {}