penumbra_sdk_sct/
source.rs

1use anyhow::anyhow;
2use penumbra_sdk_proto::{core::component::sct::v1 as pb, DomainType};
3use penumbra_sdk_txhash::TransactionId;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
7#[serde(try_from = "pb::CommitmentSource", into = "pb::CommitmentSource")]
8pub enum CommitmentSource {
9    /// The state commitment was included in the genesis state.
10    Genesis,
11    /// The commitment was created by a transaction.
12    Transaction {
13        /// The transaction ID, if specified.
14        ///
15        /// Making this optional allows the `CompactBlock` to have "stripped" transaction sources,
16        /// indicating to the client that they should download and inspect the block's transactions.
17        id: Option<[u8; 32]>,
18    },
19    /// The commitment was created through a validator's funding stream.
20    FundingStreamReward { epoch_index: u64 },
21    /// The commitment was created through a `CommunityPoolOutput` in a governance-initated transaction.
22    CommunityPoolOutput,
23    /// The commitment was created by an inbound ICS20 transfer.
24    Ics20Transfer {
25        /// The sequence number of the transfer packet.
26        packet_seq: u64,
27        /// The channel the packet was sent on.
28        channel_id: String,
29        /// The sender address on the counterparty chain.
30        sender: String,
31    },
32    /// The commitment was created through the participation in the liquidity tournament.
33    LiquidityTournamentReward {
34        /// The epoch in which the reward occured.
35        epoch: u64,
36        /// Transaction hash of the transaction that did the voting.
37        tx_hash: TransactionId,
38    },
39}
40
41impl DomainType for CommitmentSource {
42    type Proto = pb::CommitmentSource;
43}
44
45impl CommitmentSource {
46    /// Convenience method for constructing a "stripped" transaction source.
47    pub fn transaction() -> Self {
48        CommitmentSource::Transaction { id: None }
49    }
50
51    /// Convenience method for stripping transaction hashes out of the source.
52    pub fn stripped(&self) -> Self {
53        match self {
54            CommitmentSource::Transaction { .. } => CommitmentSource::Transaction { id: None },
55            x => x.clone(),
56        }
57    }
58
59    /// Get the transaction ID, if this source is a hydrated transaction source.
60    pub fn id(&self) -> Option<[u8; 32]> {
61        match self {
62            CommitmentSource::Transaction { id: Some(id) } => Some(*id),
63            _ => None,
64        }
65    }
66}
67
68impl From<CommitmentSource> for pb::CommitmentSource {
69    fn from(value: CommitmentSource) -> Self {
70        use pb::commitment_source::{self as pbcs, Source};
71
72        Self {
73            source: Some(match value {
74                CommitmentSource::Genesis => Source::Genesis(pbcs::Genesis {}),
75                CommitmentSource::Transaction { id } => Source::Transaction(pbcs::Transaction {
76                    id: id.map(|bytes| bytes.to_vec()).unwrap_or_default(),
77                }),
78                CommitmentSource::FundingStreamReward { epoch_index } => {
79                    Source::FundingStreamReward(pbcs::FundingStreamReward { epoch_index })
80                }
81                CommitmentSource::CommunityPoolOutput => {
82                    Source::CommunityPoolOutput(pbcs::CommunityPoolOutput {})
83                }
84                CommitmentSource::Ics20Transfer {
85                    packet_seq,
86                    channel_id,
87                    sender,
88                } => Source::Ics20Transfer(pbcs::Ics20Transfer {
89                    packet_seq,
90                    channel_id,
91                    sender,
92                }),
93                CommitmentSource::LiquidityTournamentReward { epoch, tx_hash } => {
94                    Source::Lqt(pbcs::LiquidityTournamentReward {
95                        epoch,
96                        tx_hash: Some(tx_hash.into()),
97                    })
98                }
99            }),
100        }
101    }
102}
103
104impl TryFrom<pb::CommitmentSource> for CommitmentSource {
105    type Error = anyhow::Error;
106
107    fn try_from(value: pb::CommitmentSource) -> Result<Self, Self::Error> {
108        use pb::commitment_source::Source;
109        let source = value.source.ok_or_else(|| anyhow!("missing source"))?;
110
111        Ok(match source {
112            Source::Genesis(_) => Self::Genesis,
113            Source::CommunityPoolOutput(_) => Self::CommunityPoolOutput,
114            Source::FundingStreamReward(x) => Self::FundingStreamReward {
115                epoch_index: x.epoch_index,
116            },
117            Source::Transaction(x) => {
118                if x.id.is_empty() {
119                    Self::Transaction { id: None }
120                } else {
121                    Self::Transaction {
122                        id: Some(x.id.try_into().map_err(|id: Vec<u8>| {
123                            anyhow!("expected 32-byte id array, got {} bytes", id.len())
124                        })?),
125                    }
126                }
127            }
128            Source::Ics20Transfer(x) => Self::Ics20Transfer {
129                packet_seq: x.packet_seq,
130                channel_id: x.channel_id,
131                sender: x.sender,
132            },
133            Source::Lqt(x) => Self::LiquidityTournamentReward {
134                epoch: x.epoch,
135                tx_hash: x
136                    .tx_hash
137                    .map(|x| x.try_into())
138                    .transpose()?
139                    .ok_or_else(|| anyhow!("missing LQT transaction hash"))?,
140            },
141        })
142    }
143}
144
145impl From<TransactionId> for CommitmentSource {
146    fn from(id: TransactionId) -> Self {
147        Self::Transaction { id: Some(id.0) }
148    }
149}