penumbra_sdk_view/
note_record.rs
1use penumbra_sdk_asset::Value;
2use penumbra_sdk_keys::{keys::AddressIndex, Address, AddressView};
3use penumbra_sdk_proto::{view::v1 as pb, DomainType};
4use penumbra_sdk_sct::{CommitmentSource, Nullifier};
5use penumbra_sdk_shielded_pool::{note, Note, Rseed};
6use penumbra_sdk_tct::Position;
7
8use r2d2_sqlite::rusqlite::Row;
9use serde::{Deserialize, Serialize};
10
11#[derive(Serialize, Deserialize, Debug, Clone)]
13#[serde(try_from = "pb::SpendableNoteRecord", into = "pb::SpendableNoteRecord")]
14pub struct SpendableNoteRecord {
15 pub note_commitment: note::StateCommitment,
16 pub note: Note,
17 pub address_index: AddressIndex,
18 pub nullifier: Nullifier,
19 pub height_created: u64,
20 pub height_spent: Option<u64>,
21 pub position: Position,
22 pub source: CommitmentSource,
23 pub return_address: Option<AddressView>,
24}
25
26impl DomainType for SpendableNoteRecord {
27 type Proto = pb::SpendableNoteRecord;
28}
29
30impl From<SpendableNoteRecord> for pb::SpendableNoteRecord {
31 fn from(v: SpendableNoteRecord) -> Self {
32 pb::SpendableNoteRecord {
33 note_commitment: Some(v.note_commitment.into()),
34 note: Some(v.note.into()),
35 address_index: Some(v.address_index.into()),
36 nullifier: Some(v.nullifier.into()),
37 height_created: v.height_created,
38 height_spent: v.height_spent.unwrap_or(0),
39 position: v.position.into(),
40 source: Some(v.source.into()),
41 return_address: v.return_address.map(Into::into),
42 }
43 }
44}
45
46impl TryFrom<pb::SpendableNoteRecord> for SpendableNoteRecord {
47 type Error = anyhow::Error;
48 fn try_from(v: pb::SpendableNoteRecord) -> Result<Self, Self::Error> {
49 Ok(SpendableNoteRecord {
50 note_commitment: v
51 .note_commitment
52 .ok_or_else(|| anyhow::anyhow!("missing note commitment"))?
53 .try_into()?,
54 note: v
55 .note
56 .ok_or_else(|| anyhow::anyhow!("missing note"))?
57 .try_into()?,
58 address_index: v
59 .address_index
60 .ok_or_else(|| anyhow::anyhow!("missing address index"))?
61 .try_into()?,
62 nullifier: v
63 .nullifier
64 .ok_or_else(|| anyhow::anyhow!("missing nullifier"))?
65 .try_into()?,
66 height_created: v.height_created,
67 height_spent: if v.height_spent > 0 {
68 Some(v.height_spent)
69 } else {
70 None
71 },
72 position: v.position.into(),
73 source: v
74 .source
75 .ok_or_else(|| anyhow::anyhow!("missing note source"))?
76 .try_into()?,
77 return_address: v.return_address.map(TryInto::try_into).transpose()?,
78 })
79 }
80}
81
82impl TryFrom<&Row<'_>> for SpendableNoteRecord {
83 type Error = anyhow::Error;
84
85 fn try_from(row: &Row<'_>) -> Result<Self, Self::Error> {
86 let return_address_bytes = row
87 .get::<_, Option<Vec<u8>>>("return_address")
88 .ok()
90 .flatten();
91 let return_address = return_address_bytes
92 .map(|b| {
93 Address::try_from(b)
95 })
96 .transpose()?
97 .map(|a| AddressView::Opaque { address: a });
98 Ok(SpendableNoteRecord {
99 address_index: row.get::<_, Vec<u8>>("address_index")?[..].try_into()?,
100 nullifier: row.get::<_, Vec<u8>>("nullifier")?[..].try_into()?,
101 height_created: row.get("height_created")?,
102 height_spent: row.get("height_spent")?,
103 position: row.get::<_, u64>("position")?.into(),
104 note_commitment: row.get::<_, Vec<u8>>("note_commitment")?[..].try_into()?,
105 note: Note::from_parts(
106 row.get::<_, Vec<u8>>("address")?[..].try_into()?,
107 Value {
108 amount: u128::from_be_bytes(row.get::<_, [u8; 16]>("amount")?).into(),
109 asset_id: row.get::<_, Vec<u8>>("asset_id")?[..].try_into()?,
110 },
111 Rseed(row.get::<_, [u8; 32]>("rseed")?),
112 )?,
113 source: CommitmentSource::decode(&row.get::<_, Vec<u8>>("source")?[..])?,
114 return_address,
115 })
116 }
117}