penumbra_sdk_transaction/view/
transaction_perspective.rs1use anyhow::anyhow;
2use pbjson_types::Any;
3use penumbra_sdk_asset::{asset, EstimatedPrice, Value, ValueView};
4use penumbra_sdk_dex::BatchSwapOutputData;
5use penumbra_sdk_keys::{Address, AddressView, PayloadKey};
6use penumbra_sdk_proto::core::transaction::v1::{
7 self as pb, NullifierWithNote, PayloadKeyWithCommitment,
8};
9use penumbra_sdk_sct::Nullifier;
10use penumbra_sdk_shielded_pool::{note, Note, NoteView};
11use penumbra_sdk_txhash::TransactionId;
12
13use std::collections::BTreeMap;
14
15#[derive(Debug, Clone, Default)]
18pub struct TransactionPerspective {
19 pub payload_keys: BTreeMap<note::StateCommitment, PayloadKey>,
34 pub spend_nullifiers: BTreeMap<Nullifier, Note>,
36 pub advice_notes: BTreeMap<note::StateCommitment, Note>,
38 pub address_views: Vec<AddressView>,
40 pub denoms: asset::Cache,
42 pub transaction_id: TransactionId,
44 pub prices: Vec<EstimatedPrice>,
46 pub extended_metadata: BTreeMap<asset::Id, Any>,
48 pub creation_transaction_ids_by_nullifier: BTreeMap<Nullifier, TransactionId>,
52 pub nullification_transaction_ids_by_commitment: BTreeMap<note::StateCommitment, TransactionId>,
56 pub batch_swap_output_data: Vec<BatchSwapOutputData>,
60}
61
62impl TransactionPerspective {
63 pub fn view_value(&self, value: Value) -> ValueView {
64 value
65 .view_with_cache(&self.denoms)
66 .with_prices(&self.prices, &self.denoms)
67 .with_extended_metadata(self.extended_metadata.get(&value.asset_id).cloned())
68 }
69
70 pub fn view_note(&self, note: Note) -> NoteView {
71 NoteView {
72 address: self.view_address(note.address()),
73 value: self.view_value(note.value()),
74 rseed: note.rseed(),
75 }
76 }
77
78 pub fn view_address(&self, address: Address) -> AddressView {
79 match self.address_views.iter().find(|av| av.address() == address) {
80 Some(av) => av.clone(),
81 None => AddressView::Opaque { address },
82 }
83 }
84
85 pub fn get_and_view_advice_note(&self, commitment: ¬e::StateCommitment) -> Option<NoteView> {
86 self.advice_notes
87 .get(commitment)
88 .cloned()
89 .map(|note| self.view_note(note))
90 }
91}
92
93impl TransactionPerspective {}
94
95impl From<TransactionPerspective> for pb::TransactionPerspective {
96 fn from(msg: TransactionPerspective) -> Self {
97 let mut payload_keys = Vec::new();
98 let mut spend_nullifiers = Vec::new();
99 let mut advice_notes = Vec::new();
100 let mut address_views = Vec::new();
101 let mut denoms = Vec::new();
102
103 for (commitment, payload_key) in msg.payload_keys {
104 payload_keys.push(PayloadKeyWithCommitment {
105 payload_key: Some(payload_key.to_owned().into()),
106 commitment: Some(commitment.to_owned().into()),
107 });
108 }
109
110 for (nullifier, note) in msg.spend_nullifiers {
111 spend_nullifiers.push(NullifierWithNote {
112 nullifier: Some(nullifier.into()),
113 note: Some(note.into()),
114 })
115 }
116 for note in msg.advice_notes.into_values() {
117 advice_notes.push(note.into());
118 }
119 for address_view in msg.address_views {
120 address_views.push(address_view.into());
121 }
122 for denom in msg.denoms.values() {
123 denoms.push(denom.clone().into());
124 }
125
126 Self {
127 payload_keys,
128 spend_nullifiers,
129 advice_notes,
130 address_views,
131 denoms,
132 transaction_id: Some(msg.transaction_id.into()),
133 prices: msg.prices.into_iter().map(Into::into).collect(),
134 extended_metadata: msg
135 .extended_metadata
136 .into_iter()
137 .map(|(k, v)| pb::transaction_perspective::ExtendedMetadataById {
138 asset_id: Some(k.into()),
139 extended_metadata: Some(v),
140 })
141 .collect(),
142 creation_transaction_ids_by_nullifier: msg
143 .creation_transaction_ids_by_nullifier
144 .into_iter()
145 .map(
146 |(k, v)| pb::transaction_perspective::CreationTransactionIdByNullifier {
147 nullifier: Some(k.into()),
148 transaction_id: Some(v.into()),
149 },
150 )
151 .collect(),
152 nullification_transaction_ids_by_commitment: msg
153 .nullification_transaction_ids_by_commitment
154 .into_iter()
155 .map(
156 |(k, v)| pb::transaction_perspective::NullificationTransactionIdByCommitment {
157 commitment: Some(k.into()),
158 transaction_id: Some(v.into()),
159 },
160 )
161 .collect(),
162 batch_swap_output_data: msg
163 .batch_swap_output_data
164 .into_iter()
165 .map(Into::into)
166 .collect(),
167 }
168 }
169}
170
171impl TryFrom<pb::TransactionPerspective> for TransactionPerspective {
172 type Error = anyhow::Error;
173
174 fn try_from(msg: pb::TransactionPerspective) -> Result<Self, Self::Error> {
175 let mut payload_keys = BTreeMap::new();
176 let mut spend_nullifiers = BTreeMap::new();
177 let mut advice_notes = BTreeMap::new();
178 let mut address_views = Vec::new();
179 let mut denoms = BTreeMap::new();
180
181 for pk in msg.payload_keys {
182 if pk.commitment.is_some() {
183 payload_keys.insert(
184 pk.commitment
185 .ok_or_else(|| anyhow!("missing commitment in payload key"))?
186 .try_into()?,
187 pk.payload_key
188 .ok_or_else(|| anyhow!("missing payload key"))?
189 .try_into()?,
190 );
191 };
192 }
193
194 for nwn in msg.spend_nullifiers {
195 spend_nullifiers.insert(
196 nwn.nullifier
197 .ok_or_else(|| anyhow!("missing nullifier in spend nullifier"))?
198 .try_into()?,
199 nwn.note
200 .ok_or_else(|| anyhow!("missing note in spend nullifier"))?
201 .try_into()?,
202 );
203 }
204
205 for note in msg.advice_notes {
206 let note: Note = note.try_into()?;
207 advice_notes.insert(note.commit(), note);
208 }
209
210 for address_view in msg.address_views {
211 address_views.push(address_view.try_into()?);
212 }
213
214 for denom in msg.denoms {
215 denoms.insert(
216 denom
217 .penumbra_asset_id
218 .clone()
219 .ok_or_else(|| anyhow!("missing penumbra asset ID in denom"))?
220 .try_into()?,
221 denom.try_into()?,
222 );
223 }
224
225 let transaction_id: TransactionId = match msg.transaction_id {
226 Some(tx_id) => tx_id.try_into()?,
227 None => TransactionId::default(),
228 };
229
230 Ok(Self {
231 payload_keys,
232 spend_nullifiers,
233 advice_notes,
234 address_views,
235 denoms: denoms.try_into()?,
236 transaction_id,
237 prices: msg
238 .prices
239 .into_iter()
240 .map(TryInto::try_into)
241 .collect::<Result<_, _>>()?,
242 extended_metadata: msg
243 .extended_metadata
244 .into_iter()
245 .map(|em| {
246 Ok((
247 em.asset_id
248 .ok_or_else(|| anyhow!("missing asset ID in extended metadata"))?
249 .try_into()?,
250 em.extended_metadata
251 .ok_or_else(|| anyhow!("missing extended metadata"))?,
252 ))
253 })
254 .collect::<Result<_, anyhow::Error>>()?,
255 creation_transaction_ids_by_nullifier: msg
256 .creation_transaction_ids_by_nullifier
257 .into_iter()
258 .map(|ct| {
259 Ok((
260 ct.nullifier
261 .ok_or_else(|| anyhow!("missing nullifier in creation transaction ID"))?
262 .try_into()?,
263 ct.transaction_id
264 .ok_or_else(|| {
265 anyhow!("missing transaction ID in creation transaction ID")
266 })?
267 .try_into()?,
268 ))
269 })
270 .collect::<Result<_, anyhow::Error>>()?,
271 nullification_transaction_ids_by_commitment: msg
272 .nullification_transaction_ids_by_commitment
273 .into_iter()
274 .map(|nt| {
275 Ok((
276 nt.commitment
277 .ok_or_else(|| {
278 anyhow!("missing commitment in nullification transaction ID")
279 })?
280 .try_into()?,
281 nt.transaction_id
282 .ok_or_else(|| {
283 anyhow!("missing transaction ID in nullification transaction ID")
284 })?
285 .try_into()?,
286 ))
287 })
288 .collect::<Result<_, anyhow::Error>>()?,
289 batch_swap_output_data: msg
290 .batch_swap_output_data
291 .into_iter()
292 .map(TryInto::try_into)
293 .collect::<Result<_, _>>()?,
294 })
295 }
296}