Skip to content

Commit

Permalink
sct: extend commitment source with LQT variant (#5024)
Browse files Browse the repository at this point in the history
## Describe your changes

- new commitment source kind to track the providence of LQT reward notes

## Issue ticket number and link

references #5011

## Checklist before requesting a review

- [x] I have added guiding text to explain how a reviewer should test
these changes.

- [x] If this code contains consensus-breaking changes, I have added the
"consensus-breaking" label. Otherwise, I declare my belief that there
are not consensus-breaking changes, for the following reason:
  • Loading branch information
TalDerei authored Jan 30, 2025
1 parent 1b1edc2 commit ea20ade
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/bin/pcli/src/command/view/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ fn format_source(source: &CommitmentSource) -> String {
"ICS20 packet {} via {} from {}",
packet_seq, channel_id, sender
),
CommitmentSource::LiquidityTournamentReward { epoch, tx_hash } => {
format!(
"Liquidity tournament reward (Epoch {}, Tx {})",
epoch, tx_hash
)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/core/component/sct/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pbjson-types = {workspace = true}
penumbra-sdk-keys = {workspace = true, default-features = false}
penumbra-sdk-proto = {workspace = true, default-features = false}
penumbra-sdk-tct = {workspace = true, default-features = true}
penumbra-sdk-txhash = {workspace = true, default-features = false}
poseidon377 = {workspace = true, features = ["r1cs"]}
rand = {workspace = true}
rand_core = {workspace = true, features = ["getrandom"]}
Expand Down
22 changes: 22 additions & 0 deletions crates/core/component/sct/src/source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::anyhow;
use penumbra_sdk_proto::{core::component::sct::v1 as pb, DomainType};
use penumbra_sdk_txhash::TransactionId;
use serde::{Deserialize, Serialize};

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -28,6 +29,13 @@ pub enum CommitmentSource {
/// The sender address on the counterparty chain.
sender: String,
},
/// The commitment was created through the participation in the liquidity tournament.
LiquidityTournamentReward {
/// The epoch in which the reward occured.
epoch: u64,
/// Transaction hash of the transaction that did the voting.
tx_hash: TransactionId,
},
}

impl DomainType for CommitmentSource {
Expand Down Expand Up @@ -82,6 +90,12 @@ impl From<CommitmentSource> for pb::CommitmentSource {
channel_id,
sender,
}),
CommitmentSource::LiquidityTournamentReward { epoch, tx_hash } => {
Source::Lqt(pbcs::LiquidityTournamentReward {
epoch,
tx_hash: Some(tx_hash.into()),
})
}
}),
}
}
Expand Down Expand Up @@ -116,6 +130,14 @@ impl TryFrom<pb::CommitmentSource> for CommitmentSource {
channel_id: x.channel_id,
sender: x.sender,
},
Source::Lqt(x) => Self::LiquidityTournamentReward {
epoch: x.epoch,
tx_hash: x
.tx_hash
.map(|x| x.try_into())
.transpose()?
.ok_or_else(|| anyhow!("missing LQT transaction hash"))?,
},
})
}
}
28 changes: 27 additions & 1 deletion crates/proto/src/gen/penumbra.core.component.sct.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl ::prost::Name for Epoch {
/// decide whether or not to download block data.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CommitmentSource {
#[prost(oneof = "commitment_source::Source", tags = "1, 2, 20, 30, 40")]
#[prost(oneof = "commitment_source::Source", tags = "1, 2, 20, 30, 40, 50")]
pub source: ::core::option::Option<commitment_source::Source>,
}
/// Nested message and enum types in `CommitmentSource`.
Expand Down Expand Up @@ -154,6 +154,30 @@ pub mod commitment_source {
"/penumbra.core.component.sct.v1.CommitmentSource.Ics20Transfer".into()
}
}
/// The commitment was created by the LQT mechanism and tracks LQT reward notes.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LiquidityTournamentReward {
/// The epoch in which the reward occured.
#[prost(uint64, tag = "1")]
pub epoch: u64,
/// Transaction hash of the transaction that did the voting.
#[prost(message, optional, tag = "2")]
pub tx_hash: ::core::option::Option<
super::super::super::super::txhash::v1::TransactionId,
>,
}
impl ::prost::Name for LiquidityTournamentReward {
const NAME: &'static str = "LiquidityTournamentReward";
const PACKAGE: &'static str = "penumbra.core.component.sct.v1";
fn full_name() -> ::prost::alloc::string::String {
"penumbra.core.component.sct.v1.CommitmentSource.LiquidityTournamentReward"
.into()
}
fn type_url() -> ::prost::alloc::string::String {
"/penumbra.core.component.sct.v1.CommitmentSource.LiquidityTournamentReward"
.into()
}
}
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Source {
#[prost(message, tag = "1")]
Expand All @@ -166,6 +190,8 @@ pub mod commitment_source {
CommunityPoolOutput(CommunityPoolOutput),
#[prost(message, tag = "40")]
Genesis(Genesis),
#[prost(message, tag = "50")]
Lqt(LiquidityTournamentReward),
}
}
impl ::prost::Name for CommitmentSource {
Expand Down
130 changes: 130 additions & 0 deletions crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ impl serde::Serialize for CommitmentSource {
commitment_source::Source::Genesis(v) => {
struct_ser.serialize_field("genesis", v)?;
}
commitment_source::Source::Lqt(v) => {
struct_ser.serialize_field("lqt", v)?;
}
}
}
struct_ser.end()
Expand All @@ -241,6 +244,7 @@ impl<'de> serde::Deserialize<'de> for CommitmentSource {
"community_pool_output",
"communityPoolOutput",
"genesis",
"lqt",
];

#[allow(clippy::enum_variant_names)]
Expand All @@ -250,6 +254,7 @@ impl<'de> serde::Deserialize<'de> for CommitmentSource {
FundingStreamReward,
CommunityPoolOutput,
Genesis,
Lqt,
__SkipField__,
}
impl<'de> serde::Deserialize<'de> for GeneratedField {
Expand Down Expand Up @@ -277,6 +282,7 @@ impl<'de> serde::Deserialize<'de> for CommitmentSource {
"fundingStreamReward" | "funding_stream_reward" => Ok(GeneratedField::FundingStreamReward),
"communityPoolOutput" | "community_pool_output" => Ok(GeneratedField::CommunityPoolOutput),
"genesis" => Ok(GeneratedField::Genesis),
"lqt" => Ok(GeneratedField::Lqt),
_ => Ok(GeneratedField::__SkipField__),
}
}
Expand Down Expand Up @@ -332,6 +338,13 @@ impl<'de> serde::Deserialize<'de> for CommitmentSource {
return Err(serde::de::Error::duplicate_field("genesis"));
}
source__ = map_.next_value::<::std::option::Option<_>>()?.map(commitment_source::Source::Genesis)
;
}
GeneratedField::Lqt => {
if source__.is_some() {
return Err(serde::de::Error::duplicate_field("lqt"));
}
source__ = map_.next_value::<::std::option::Option<_>>()?.map(commitment_source::Source::Lqt)
;
}
GeneratedField::__SkipField__ => {
Expand Down Expand Up @@ -726,6 +739,123 @@ impl<'de> serde::Deserialize<'de> for commitment_source::Ics20Transfer {
deserializer.deserialize_struct("penumbra.core.component.sct.v1.CommitmentSource.Ics20Transfer", FIELDS, GeneratedVisitor)
}
}
impl serde::Serialize for commitment_source::LiquidityTournamentReward {
#[allow(deprecated)]
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut len = 0;
if self.epoch != 0 {
len += 1;
}
if self.tx_hash.is_some() {
len += 1;
}
let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.CommitmentSource.LiquidityTournamentReward", len)?;
if self.epoch != 0 {
#[allow(clippy::needless_borrow)]
#[allow(clippy::needless_borrows_for_generic_args)]
struct_ser.serialize_field("epoch", ToString::to_string(&self.epoch).as_str())?;
}
if let Some(v) = self.tx_hash.as_ref() {
struct_ser.serialize_field("txHash", v)?;
}
struct_ser.end()
}
}
impl<'de> serde::Deserialize<'de> for commitment_source::LiquidityTournamentReward {
#[allow(deprecated)]
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
const FIELDS: &[&str] = &[
"epoch",
"tx_hash",
"txHash",
];

#[allow(clippy::enum_variant_names)]
enum GeneratedField {
Epoch,
TxHash,
__SkipField__,
}
impl<'de> serde::Deserialize<'de> for GeneratedField {
fn deserialize<D>(deserializer: D) -> std::result::Result<GeneratedField, D::Error>
where
D: serde::Deserializer<'de>,
{
struct GeneratedVisitor;

impl<'de> serde::de::Visitor<'de> for GeneratedVisitor {
type Value = GeneratedField;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "expected one of: {:?}", &FIELDS)
}

#[allow(unused_variables)]
fn visit_str<E>(self, value: &str) -> std::result::Result<GeneratedField, E>
where
E: serde::de::Error,
{
match value {
"epoch" => Ok(GeneratedField::Epoch),
"txHash" | "tx_hash" => Ok(GeneratedField::TxHash),
_ => Ok(GeneratedField::__SkipField__),
}
}
}
deserializer.deserialize_identifier(GeneratedVisitor)
}
}
struct GeneratedVisitor;
impl<'de> serde::de::Visitor<'de> for GeneratedVisitor {
type Value = commitment_source::LiquidityTournamentReward;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("struct penumbra.core.component.sct.v1.CommitmentSource.LiquidityTournamentReward")
}

fn visit_map<V>(self, mut map_: V) -> std::result::Result<commitment_source::LiquidityTournamentReward, V::Error>
where
V: serde::de::MapAccess<'de>,
{
let mut epoch__ = None;
let mut tx_hash__ = None;
while let Some(k) = map_.next_key()? {
match k {
GeneratedField::Epoch => {
if epoch__.is_some() {
return Err(serde::de::Error::duplicate_field("epoch"));
}
epoch__ =
Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0)
;
}
GeneratedField::TxHash => {
if tx_hash__.is_some() {
return Err(serde::de::Error::duplicate_field("txHash"));
}
tx_hash__ = map_.next_value()?;
}
GeneratedField::__SkipField__ => {
let _ = map_.next_value::<serde::de::IgnoredAny>()?;
}
}
}
Ok(commitment_source::LiquidityTournamentReward {
epoch: epoch__.unwrap_or_default(),
tx_hash: tx_hash__,
})
}
}
deserializer.deserialize_struct("penumbra.core.component.sct.v1.CommitmentSource.LiquidityTournamentReward", FIELDS, GeneratedVisitor)
}
}
impl serde::Serialize for commitment_source::Transaction {
#[allow(deprecated)]
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
Expand Down
9 changes: 9 additions & 0 deletions proto/penumbra/penumbra/core/component/sct/v1/sct.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";
package penumbra.core.component.sct.v1;

import "penumbra/crypto/tct/v1/tct.proto";
import "penumbra/core/txhash/v1/txhash.proto";
import "google/protobuf/timestamp.proto";

// Configuration data for the SCT component.
Expand Down Expand Up @@ -59,12 +60,20 @@ message CommitmentSource {
// The sender address on the counterparty chain
string sender = 3;
}
// The commitment was created by the LQT mechanism and tracks LQT reward notes.
message LiquidityTournamentReward {
// The epoch in which the reward occured.
uint64 epoch = 1;
// Transaction hash of the transaction that did the voting.
txhash.v1.TransactionId tx_hash = 2;
}
oneof source {
Transaction transaction = 1;
Ics20Transfer ics_20_transfer = 2;
FundingStreamReward funding_stream_reward = 20;
CommunityPoolOutput community_pool_output = 30;
Genesis genesis = 40;
LiquidityTournamentReward lqt = 50;
}
}

Expand Down

0 comments on commit ea20ade

Please sign in to comment.