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/// Corresponds to the SpendableNoteRecord proto
12#[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            // If there's no return_address column, fill in None
89            .ok()
90            .flatten();
91        let return_address = return_address_bytes
92            .map(|b| {
93                // Address is not proto-encoded
94                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}