Skip to content

Commit

Permalink
feat(proto)!: remove optional fields in protos
Browse files Browse the repository at this point in the history
The cosmos fork of gogoproto, used to generate golang code from BSR,
doesn't support "optional" fields in proto3 syntax. Rather than sort
that out, we're removing use of optional to unblock integration work on
interchaintest.

In order to preserve the representation of optionality, we use
submessages in the proto types, so we can include or omit the named
field. A notable exception is for encrypted memos: since the encrypted memo
payload must be a specific length, we can check for whether it's empty,
and map an empty encrypted memo to None.

H/t to @plaidfinch for describing the submessage pattern to me,
and implementing it on the governance changes.

Closes #2933.
  • Loading branch information
conorsch committed Aug 18, 2023
1 parent 828f33b commit 843da8d
Show file tree
Hide file tree
Showing 26 changed files with 3,456 additions and 2,207 deletions.
8 changes: 6 additions & 2 deletions crates/core/component/stake/src/validator/bonding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ impl From<State> for pb::BondingState {
}
},
unbonding_epoch: match v {
State::Unbonding { unbonding_epoch } => Some(unbonding_epoch),
State::Unbonding { unbonding_epoch } => Some(pb::bonding_state::UnbondingEpoch {
epoch: unbonding_epoch,
}),
_ => None,
},
}
Expand All @@ -74,7 +76,9 @@ impl TryFrom<pb::BondingState> for State {
let Some(unbonding_epoch) = v.unbonding_epoch else {
anyhow::bail!("unbonding epoch should be set for unbonding state")
};
Ok(State::Unbonding { unbonding_epoch })
Ok(State::Unbonding {
unbonding_epoch: unbonding_epoch.epoch,
})
}
pb::bonding_state::BondingStateEnum::Unspecified => {
Err(anyhow::anyhow!("unspecified bonding state!"))
Expand Down
69 changes: 53 additions & 16 deletions crates/core/transaction/src/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,16 @@ impl From<Proposal> for pb::Proposal {
..Default::default() // We're about to fill in precisely one of the fields for the payload
};
match inner.payload {
ProposalPayload::Signaling { commit } => {
proposal.signaling = Some(pb::proposal::Signaling { commit });
}
ProposalPayload::Signaling { commit } => match commit {
Some(c) => {
proposal.signaling = Some(pb::proposal::Signaling {
commit: Some(pb::proposal::Commit { commit: c }),
});
}
None => {
proposal.signaling = Some(pb::proposal::Signaling { commit: None });
}
},
ProposalPayload::Emergency { halt_chain } => {
proposal.emergency = Some(pb::proposal::Emergency { halt_chain });
}
Expand Down Expand Up @@ -106,7 +113,11 @@ impl TryFrom<pb::Proposal> for Proposal {
description: inner.description,
payload: if let Some(signaling) = inner.signaling {
ProposalPayload::Signaling {
commit: signaling.commit,
commit: if let Some(c) = signaling.commit {
Some(c.commit)
} else {
None
},
}
} else if let Some(emergency) = inner.emergency {
ProposalPayload::Emergency {
Expand Down Expand Up @@ -581,12 +592,22 @@ impl From<Outcome<String>> for pb::ProposalOutcome {
}
Outcome::Failed { withdrawn } => {
pb::proposal_outcome::Outcome::Failed(pb::proposal_outcome::Failed {
withdrawn_with_reason: withdrawn.into(),
withdrawn: match withdrawn {
Withdrawn::No => None,
Withdrawn::WithReason { reason } => {
Some(pb::proposal_outcome::Withdrawn { reason })
}
},
})
}
Outcome::Slashed { withdrawn } => {
pb::proposal_outcome::Outcome::Slashed(pb::proposal_outcome::Slashed {
withdrawn_with_reason: withdrawn.into(),
withdrawn: match withdrawn {
Withdrawn::No => None,
Withdrawn::WithReason { reason } => {
Some(pb::proposal_outcome::Withdrawn { reason })
}
},
})
}
};
Expand All @@ -609,14 +630,22 @@ impl TryFrom<pb::ProposalOutcome> for Outcome<String> {
Outcome::Passed
}
pb::proposal_outcome::Outcome::Failed(pb::proposal_outcome::Failed {
withdrawn_with_reason,
withdrawn,
}) => Outcome::Failed {
withdrawn: withdrawn_with_reason.into(),
withdrawn: if let Some(pb::proposal_outcome::Withdrawn { reason }) = withdrawn {
Withdrawn::WithReason { reason }
} else {
Withdrawn::No
},
},
pb::proposal_outcome::Outcome::Slashed(pb::proposal_outcome::Slashed {
withdrawn_with_reason,
withdrawn,
}) => Outcome::Slashed {
withdrawn: withdrawn_with_reason.into(),
withdrawn: if let Some(pb::proposal_outcome::Withdrawn { reason }) = withdrawn {
Withdrawn::WithReason { reason }
} else {
Withdrawn::No
},
},
},
)
Expand All @@ -639,12 +668,20 @@ impl From<Outcome<()>> for pb::ProposalOutcome {
}
Outcome::Failed { withdrawn } => {
pb::proposal_outcome::Outcome::Failed(pb::proposal_outcome::Failed {
withdrawn_with_reason: <Option<()>>::from(withdrawn).map(|()| "".to_string()),
withdrawn: <Option<()>>::from(withdrawn).map(|()| {
pb::proposal_outcome::Withdrawn {
reason: "".to_string(),
}
}),
})
}
Outcome::Slashed { withdrawn } => {
pb::proposal_outcome::Outcome::Slashed(pb::proposal_outcome::Slashed {
withdrawn_with_reason: <Option<()>>::from(withdrawn).map(|()| "".to_string()),
withdrawn: <Option<()>>::from(withdrawn).map(|()| {
pb::proposal_outcome::Withdrawn {
reason: "".to_string(),
}
}),
})
}
};
Expand All @@ -667,14 +704,14 @@ impl TryFrom<pb::ProposalOutcome> for Outcome<()> {
Outcome::Passed
}
pb::proposal_outcome::Outcome::Failed(pb::proposal_outcome::Failed {
withdrawn_with_reason,
withdrawn,
}) => Outcome::Failed {
withdrawn: <Withdrawn<String>>::from(withdrawn_with_reason).try_into()?,
withdrawn: <Withdrawn<String>>::from(withdrawn.map(|w| w.reason)).try_into()?,
},
pb::proposal_outcome::Outcome::Slashed(pb::proposal_outcome::Slashed {
withdrawn_with_reason,
withdrawn,
}) => Outcome::Slashed {
withdrawn: <Withdrawn<String>>::from(withdrawn_with_reason).try_into()?,
withdrawn: <Withdrawn<String>>::from(withdrawn.map(|w| w.reason)).try_into()?,
},
},
)
Expand Down
20 changes: 11 additions & 9 deletions crates/core/transaction/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,10 @@ impl From<TransactionBody> for pbt::TransactionBody {
fn from(msg: TransactionBody) -> Self {
let encrypted_memo: pbt::MemoData = match msg.memo {
Some(memo) => pbt::MemoData {
encrypted_memo: Some(bytes::Bytes::copy_from_slice(&memo.0)),
encrypted_memo: bytes::Bytes::copy_from_slice(&memo.0),
},
None => pbt::MemoData {
encrypted_memo: None,
encrypted_memo: Bytes::default(),
},
};

Expand Down Expand Up @@ -578,17 +578,19 @@ impl TryFrom<pbt::TransactionBody> for TransactionBody {
.try_into()
.context("fee malformed")?;

let memo = match proto
let encrypted_memo = proto
.memo_data
.ok_or_else(|| anyhow::anyhow!("transaction body missing memo data field"))?
.encrypted_memo
{
Some(bytes) => Some(
bytes[..]
.encrypted_memo;

let memo: Option<MemoCiphertext> = if encrypted_memo.is_empty() {
None
} else {
Some(
encrypted_memo[..]
.try_into()
.context("encrypted memo malformed while parsing transaction body")?,
),
None => None,
)
};

let detection_data = match proto.detection_data {
Expand Down
35 changes: 23 additions & 12 deletions crates/proto/src/gen/penumbra.core.governance.v1alpha1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ pub struct ProposalOutcome {
}
/// Nested message and enum types in `ProposalOutcome`.
pub mod proposal_outcome {
/// Whether or not the proposal was withdrawn.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Withdrawn {
/// The reason for withdrawing the proposal during the voting period.
#[prost(string, tag = "1")]
pub reason: ::prost::alloc::string::String,
}
/// The proposal was passed.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
Expand All @@ -281,21 +289,17 @@ pub mod proposal_outcome {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Failed {
/// The proposal was withdrawn during the voting period.
#[prost(string, optional, tag = "1")]
pub withdrawn_with_reason: ::core::option::Option<
::prost::alloc::string::String,
>,
/// Present if the proposal was withdrawn during the voting period.
#[prost(message, optional, tag = "1")]
pub withdrawn: ::core::option::Option<Withdrawn>,
}
/// The proposal did not pass, and was slashed.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Slashed {
/// The proposal was withdrawn during the voting period.
#[prost(string, optional, tag = "1")]
pub withdrawn_with_reason: ::core::option::Option<
::prost::alloc::string::String,
>,
/// Present if the proposal was withdrawn during the voting period.
#[prost(message, optional, tag = "1")]
pub withdrawn: ::core::option::Option<Withdrawn>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
Expand Down Expand Up @@ -355,8 +359,15 @@ pub mod proposal {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Signaling {
/// The commit to be voted upon, if any is relevant.
#[prost(string, optional, tag = "1")]
pub commit: ::core::option::Option<::prost::alloc::string::String>,
#[prost(message, optional, tag = "1")]
pub commit: ::core::option::Option<Commit>,
}
/// The sha1 hash of a git commit, used to indicate a specific version of the software.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Commit {
#[prost(string, tag = "1")]
pub commit: ::prost::alloc::string::String,
}
/// An emergency proposal can be passed instantaneously by a 2/3 majority of validators, without
/// waiting for the voting period to expire.
Expand Down
Loading

0 comments on commit 843da8d

Please sign in to comment.