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(event::epoch_root(index, epoch_root, block_timestamp));
104 }
105
106 self.write_sct_cache(sct);
107 self.persist_sct_cache();
108 }
109
110 async fn add_sct_commitment(
113 &mut self,
114 commitment: tct::StateCommitment,
115 source: CommitmentSource,
116 ) -> Result<tct::Position> {
117 let mut tree = self.get_sct().await;
119 let position = tree.insert(tct::Witness::Forget, commitment)?;
120 self.write_sct_cache(tree);
121
122 self.record_proto(event::commitment(commitment, position, source));
124
125 Ok(position)
126 }
127
128 #[instrument(skip(self, source))]
129 async fn nullify(&mut self, nullifier: Nullifier, source: CommitmentSource) {
131 tracing::debug!("marking as spent");
132
133 self.put(
137 state_key::nullifier_set::spent_nullifier_lookup(&nullifier),
138 NullificationInfo {
141 id: source
142 .id()
143 .expect("nullifiers are only consumed by transactions"),
144 spend_height: self.get_block_height().await.expect("block height is set"),
145 },
146 );
147
148 let mut nullifiers = self.pending_nullifiers();
150 nullifiers.push_back(nullifier);
151 self.object_put(state_key::nullifier_set::pending_nullifiers(), nullifiers);
152 }
153
154 async fn end_sct_block(
160 &mut self,
161 end_epoch: bool,
162 ) -> Result<(block::Root, Option<epoch::Root>)> {
163 let height = self.get_block_height().await?;
164
165 let mut tree = self.get_sct().await;
166
167 let block_root = tree
169 .end_block()
170 .expect("ending a block in the state commitment tree can never fail");
171
172 let epoch_root = if end_epoch {
174 let epoch_root = tree
175 .end_epoch()
176 .expect("ending an epoch in the state commitment tree can never fail");
177 Some(epoch_root)
178 } else {
179 None
180 };
181
182 self.write_sct(height, tree, block_root, epoch_root).await;
183
184 Ok((block_root, epoch_root))
185 }
186
187 fn write_sct_cache(&mut self, tree: tct::Tree) {
190 self.object_put(state_key::cache::cached_state_commitment_tree(), tree);
191 }
192
193 fn persist_sct_cache(&mut self) {
200 if let Some(tree) =
202 self.object_get::<tct::Tree>(state_key::cache::cached_state_commitment_tree())
203 {
204 let bytes = bincode::serialize(&tree)
205 .expect("able to serialize state commitment tree to bincode");
206 self.nonverifiable_put_raw(
207 state_key::tree::state_commitment_tree().as_bytes().to_vec(),
208 bytes,
209 );
210 }
211 }
212}
213
214impl<T: StateWrite + ?Sized> SctManager for T {}
215
216#[async_trait]
217pub trait VerificationExt: StateRead {
218 async fn check_claimed_anchor(&self, anchor: tct::Root) -> Result<()> {
219 if anchor.is_empty() {
220 return Ok(());
221 }
222
223 if let Some(anchor_height) = self
224 .get_proto::<u64>(&state_key::tree::anchor_lookup(anchor))
225 .await?
226 {
227 tracing::debug!(?anchor, ?anchor_height, "anchor is valid");
228 Ok(())
229 } else {
230 Err(anyhow!(
231 "provided anchor {} is not a valid SCT root",
232 anchor
233 ))
234 }
235 }
236
237 async fn check_nullifier_unspent(&self, nullifier: Nullifier) -> Result<()> {
238 if let Some(info) = self
239 .get::<NullificationInfo>(&state_key::nullifier_set::spent_nullifier_lookup(
240 &nullifier,
241 ))
242 .await?
243 {
244 anyhow::bail!(
245 "nullifier {} was already spent in {:?}",
246 nullifier,
247 hex::encode(info.id),
248 );
249 }
250 Ok(())
251 }
252}
253
254impl<T: StateRead + ?Sized> VerificationExt for T {}