penumbra_sdk_sct/component/
tree.rs1use anyhow::{anyhow, Result};
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use penumbra_sdk_proto::{DomainType as _, StateReadProto, StateWriteProto};
5use penumbra_sdk_tct as tct;
6use tct::builder::{block, epoch};
7use tracing::instrument;
8
9use crate::{
10 component::clock::EpochRead, event, state_key, CommitmentSource, NullificationInfo, Nullifier,
11};
12
13#[async_trait]
14pub trait SctRead: StateRead {
16 async fn get_sct(&self) -> tct::Tree {
19 if let Some(tree) = self.object_get(state_key::cache::cached_state_commitment_tree()) {
21 return tree;
22 }
23
24 match self
25 .nonverifiable_get_raw(state_key::tree::state_commitment_tree().as_bytes())
26 .await
27 .expect("able to retrieve state commitment tree from nonverifiable storage")
28 {
29 Some(bytes) => bincode::deserialize(&bytes).expect(
30 "able to deserialize stored state commitment tree from nonverifiable storage",
31 ),
32 None => tct::Tree::new(),
33 }
34 }
35
36 async fn get_anchor_by_height(&self, height: u64) -> Result<Option<tct::Root>> {
39 self.get(&state_key::tree::anchor_by_height(height)).await
40 }
41
42 async fn spend_info(&self, nullifier: Nullifier) -> Result<Option<NullificationInfo>> {
44 self.get(&state_key::nullifier_set::spent_nullifier_lookup(
45 &nullifier,
46 ))
47 .await
48 }
49
50 fn pending_nullifiers(&self) -> im::Vector<Nullifier> {
52 self.object_get(state_key::nullifier_set::pending_nullifiers())
53 .unwrap_or_default()
54 }
55}
56
57impl<T: StateRead + ?Sized> SctRead for T {}
58
59#[async_trait]
60pub trait SctManager: StateWrite {
62 async fn write_sct(
68 &mut self,
69 height: u64,
70 sct: tct::Tree,
71 block_root: block::Root,
72 epoch_root: Option<epoch::Root>,
73 ) {
74 let sct_anchor = sct.root();
75 let block_timestamp = self
76 .get_current_block_timestamp()
77 .await
78 .map(|t| t.unix_timestamp())
79 .unwrap_or(0);
80
81 self.put_proto(state_key::tree::anchor_lookup(sct_anchor), height);
83 self.put(state_key::tree::anchor_by_height(height), sct_anchor);
86
87 self.record_proto(event::anchor(height, sct_anchor, block_timestamp));
88 self.record_proto(
89 event::EventBlockRoot {
90 height,
91 root: block_root,
92 timestamp_seconds: block_timestamp,
93 }
94 .to_proto(),
95 );
96 if let Some(epoch_root) = epoch_root {
98 let index = self
99 .get_current_epoch()
100 .await
101 .expect("epoch must be set")
102 .index;
103 self.record_proto(
104 event::EventEpochRoot {
105 index,
106 root: epoch_root,
107 timestamp_seconds: block_timestamp,
108 }
109 .to_proto(),
110 );
111 }
112
113 self.write_sct_cache(sct);
114 self.persist_sct_cache();
115 }
116
117 async fn add_sct_commitment(
120 &mut self,
121 commitment: tct::StateCommitment,
122 source: CommitmentSource,
123 ) -> Result<tct::Position> {
124 let mut tree = self.get_sct().await;
126 let position = tree.insert(tct::Witness::Forget, commitment)?;
127 self.write_sct_cache(tree);
128
129 self.record_proto(event::commitment(commitment, position, source));
131
132 Ok(position)
133 }
134
135 #[instrument(skip(self, source))]
136 async fn nullify(&mut self, nullifier: Nullifier, source: CommitmentSource) {
138 tracing::debug!("marking as spent");
139
140 self.put(
144 state_key::nullifier_set::spent_nullifier_lookup(&nullifier),
145 NullificationInfo {
148 id: source
149 .id()
150 .expect("nullifiers are only consumed by transactions"),
151 spend_height: self.get_block_height().await.expect("block height is set"),
152 },
153 );
154
155 let mut nullifiers = self.pending_nullifiers();
157 nullifiers.push_back(nullifier);
158 self.object_put(state_key::nullifier_set::pending_nullifiers(), nullifiers);
159 }
160
161 async fn end_sct_block(
167 &mut self,
168 end_epoch: bool,
169 ) -> Result<(block::Root, Option<epoch::Root>)> {
170 let height = self.get_block_height().await?;
171
172 let mut tree = self.get_sct().await;
173
174 let block_root = tree
176 .end_block()
177 .expect("ending a block in the state commitment tree can never fail");
178
179 let epoch_root = if end_epoch {
181 let epoch_root = tree
182 .end_epoch()
183 .expect("ending an epoch in the state commitment tree can never fail");
184 Some(epoch_root)
185 } else {
186 None
187 };
188
189 self.write_sct(height, tree, block_root, epoch_root).await;
190
191 Ok((block_root, epoch_root))
192 }
193
194 fn write_sct_cache(&mut self, tree: tct::Tree) {
197 self.object_put(state_key::cache::cached_state_commitment_tree(), tree);
198 }
199
200 fn persist_sct_cache(&mut self) {
207 if let Some(tree) =
209 self.object_get::<tct::Tree>(state_key::cache::cached_state_commitment_tree())
210 {
211 let bytes = bincode::serialize(&tree)
212 .expect("able to serialize state commitment tree to bincode");
213 self.nonverifiable_put_raw(
214 state_key::tree::state_commitment_tree().as_bytes().to_vec(),
215 bytes,
216 );
217 }
218 }
219}
220
221impl<T: StateWrite + ?Sized> SctManager for T {}
222
223#[async_trait]
224pub trait VerificationExt: StateRead {
225 async fn check_claimed_anchor(&self, anchor: tct::Root) -> Result<()> {
226 if anchor.is_empty() {
227 return Ok(());
228 }
229
230 if let Some(anchor_height) = self
231 .get_proto::<u64>(&state_key::tree::anchor_lookup(anchor))
232 .await?
233 {
234 tracing::debug!(?anchor, ?anchor_height, "anchor is valid");
235 Ok(())
236 } else {
237 Err(anyhow!(
238 "provided anchor {} is not a valid SCT root",
239 anchor
240 ))
241 }
242 }
243
244 async fn check_nullifier_unspent(&self, nullifier: Nullifier) -> Result<()> {
245 if let Some(info) = self
246 .get::<NullificationInfo>(&state_key::nullifier_set::spent_nullifier_lookup(
247 &nullifier,
248 ))
249 .await?
250 {
251 anyhow::bail!(
252 "nullifier {} was already spent in {:?}",
253 nullifier,
254 hex::encode(info.id),
255 );
256 }
257 Ok(())
258 }
259}
260
261impl<T: StateRead + ?Sized> VerificationExt for T {}