Skip to content

Commit

Permalink
WIP: new approach to declaring parameter changes in governance
Browse files Browse the repository at this point in the history
  • Loading branch information
hdevalence committed May 4, 2024
1 parent db773d8 commit 313a8eb
Show file tree
Hide file tree
Showing 7 changed files with 428 additions and 4 deletions.
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.

1 change: 1 addition & 0 deletions crates/core/component/governance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ rand_chacha = {workspace = true}
rand_core = {workspace = true, features = ["getrandom"]}
regex = {workspace = true}
serde = {workspace = true, features = ["derive"]}
serde_json = {workspace = true}
tap = {workspace = true}
tendermint = {workspace = true}
thiserror = {workspace = true}
Expand Down
248 changes: 248 additions & 0 deletions crates/core/component/governance/src/change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use std::str::FromStr;

use anyhow::Context;
use penumbra_proto::{core::component::governance::v1 as pb, DomainType};
use serde::{Deserialize, Serialize};

/// An encoded parameter.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "pb::EncodedParameter", into = "pb::EncodedParameter")]
pub struct EncodedParameter {
pub component: String,
pub key: String,
pub value: String,
}

impl DomainType for EncodedParameter {
type Proto = pb::EncodedParameter;
}

impl TryFrom<pb::EncodedParameter> for EncodedParameter {
type Error = anyhow::Error;
fn try_from(value: pb::EncodedParameter) -> Result<Self, Self::Error> {
Ok(EncodedParameter {
component: value.component,
key: value.key,
value: value.value,
})
}
}

impl From<EncodedParameter> for pb::EncodedParameter {
fn from(value: EncodedParameter) -> Self {
pb::EncodedParameter {
component: value.component,
key: value.key,
value: value.value,
}
}
}

/// Generates a set of encoded parameters for the given object.
///
/// This is useful for generating template changes.
pub fn encode_parameters(parameters: serde_json::Value) -> Vec<EncodedParameter> {
let mut encoded_parameters = Vec::new();
for (component, value) in parameters.as_object().into_iter().flatten() {
for (key, value) in value.as_object().into_iter().flatten() {
encoded_parameters.push(EncodedParameter {
component: component.to_string(),
key: key.to_string(),
value: value.to_string(),
});
}
}
encoded_parameters
}

/// Applies a set of changes to the app parameters.
///
/// The app parameters are input as a [`serde_json::Value`] object, so that the
/// parameter change code does not need to know about the structure of the entire
/// application.
///
/// If the changes can be successfully applied, the new app parameters are returned.
/// By taking ownership of the input `app_parameters`, we ensure that the caller cannot
/// access any partially-mutated app parameters.
pub fn apply_changes(
mut app_parameters: serde_json::Value,
changes: &[EncodedParameter],
) -> Result<serde_json::Value, anyhow::Error> {
for change in changes {
let component = app_parameters
.get_mut(&change.component)
.ok_or_else(|| {
anyhow::anyhow!("component {} not found in app parameters", change.component)
})?
.as_object_mut()
.ok_or_else(|| {
anyhow::anyhow!(
"expected component {} to be an object in app parameters",
change.component
)
})?;

dbg!(&change);

let new_value = serde_json::Value::from_str(&change.value)
.context("could not decode new value as JSON value")?;

dbg!(&new_value);
dbg!(component.get(&change.key));

// We want to insert into the map to handle the case where the existing value
// is missing (e.g., it had a default value and so was not encoded)
component.insert(change.key.clone(), new_value);
}
Ok(app_parameters)
}

#[cfg(test)]
mod tests {
use penumbra_num::Amount;

use crate::params::GovernanceParameters;

const SAMPLE_JSON_PARAMETERS: &'static str = r#"
{
"chainId": "penumbra-testnet-deimos-6-b295771a",
"sctParams": {
"epochDuration": "719"
},
"communityPoolParams": {
"communityPoolSpendProposalsEnabled": true
},
"governanceParams": {
"proposalVotingBlocks": "17280",
"proposalDepositAmount": {
"lo": "10000000"
},
"proposalValidQuorum": "40/100",
"proposalPassThreshold": "50/100",
"proposalSlashThreshold": "80/100"
},
"ibcParams": {
"ibcEnabled": true,
"inboundIcs20TransfersEnabled": true,
"outboundIcs20TransfersEnabled": true
},
"stakeParams": {
"activeValidatorLimit": "80",
"baseRewardRate": "30000",
"slashingPenaltyMisbehavior": "10000000",
"slashingPenaltyDowntime": "10000",
"signedBlocksWindowLen": "10000",
"missedBlocksMaximum": "9500",
"minValidatorStake": {
"lo": "1000000"
},
"unbondingDelay": "2158"
},
"feeParams": {
"fixedGasPrices": {}
},
"distributionsParams": {
"stakingIssuancePerBlock": "1"
},
"fundingParams": {},
"shieldedPoolParams": {
"fixedFmdParams": {
"asOfBlockHeight": "1"
}
},
"dexParams": {
"isEnabled": true,
"fixedCandidates": [
{
"inner": "KeqcLzNx9qSH5+lcJHBB9KNW+YPrBk5dKzvPMiypahA="
},
{
"inner": "reum7wQmk/owgvGMWMZn/6RFPV24zIKq3W6In/WwZgg="
},
{
"inner": "HW2Eq3UZVSBttoUwUi/MUtE7rr2UU7/UH500byp7OAc="
},
{
"inner": "nwPDkQq3OvLnBwGTD+nmv1Ifb2GEmFCgNHrU++9BsRE="
},
{
"inner": "ypUT1AOtjfwMOKMATACoD9RSvi8jY/YnYGi46CZ/6Q8="
},
{
"inner": "pmpygqUf4DL+z849rGPpudpdK/+FAv8qQ01U2C73kAw="
},
{
"inner": "o2gZdbhCH70Ry+7iBhkSeHC/PB1LZhgkn7LHC2kEhQc="
}
],
"maxHops": 4,
"maxPositionsPerPair": 10
},
"auctionParams": {}
}
"#;

#[test]
fn dump_encoded_parameters() {
let parameters = serde_json::from_str(SAMPLE_JSON_PARAMETERS).unwrap();
dbg!(&parameters);
let encoded_parameters = super::encode_parameters(parameters);
for encoded_parameter in encoded_parameters.iter() {
println!("{}", serde_json::to_string(&encoded_parameter).unwrap());
}
}

#[test]
fn apply_changes_to_gov_params() {
let old_parameters_raw: serde_json::Value =
serde_json::from_str(SAMPLE_JSON_PARAMETERS).unwrap();

// Make changes to the gov parameters specifically since they're
// local to this crate so we can also inspect the decoded parameters.
let changes = vec![
super::EncodedParameter {
component: "governanceParams".to_string(),
key: "proposalVotingBlocks".to_string(),
value: r#""17281""#.to_string(),
},
super::EncodedParameter {
component: "governanceParams".to_string(),
key: "proposalDepositAmount".to_string(),
value: r#"{"lo":"10000001"}"#.to_string(),
},
];
let new_parameters_raw =
super::apply_changes(old_parameters_raw.clone(), &changes).unwrap();

println!(
"{}",
serde_json::to_string_pretty(&old_parameters_raw).unwrap()
);
println!(
"{}",
serde_json::to_string_pretty(&new_parameters_raw).unwrap()
);

let old_gov_parameters_raw = old_parameters_raw["governanceParams"].clone();
let new_gov_parameters_raw = new_parameters_raw["governanceParams"].clone();

let old_gov_parameters: GovernanceParameters =
serde_json::value::from_value(old_gov_parameters_raw).unwrap();
let new_gov_parameters: GovernanceParameters =
serde_json::value::from_value(new_gov_parameters_raw).unwrap();

dbg!(&old_gov_parameters);
dbg!(&new_gov_parameters);

assert_eq!(old_gov_parameters.proposal_voting_blocks, 17280);
assert_eq!(
old_gov_parameters.proposal_deposit_amount,
Amount::from(10_000_000u64)
);
assert_eq!(new_gov_parameters.proposal_voting_blocks, 17281);
assert_eq!(
new_gov_parameters.proposal_deposit_amount,
Amount::from(10_000_001u64)
);
}
}
2 changes: 2 additions & 0 deletions crates/core/component/governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ pub use vote::Vote;

pub mod genesis;
pub mod params;

pub mod change;
30 changes: 28 additions & 2 deletions crates/proto/src/gen/penumbra.core.component.governance.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,8 +1003,33 @@ impl ::prost::Name for GenesisContent {
::prost::alloc::format!("penumbra.core.component.governance.v1.{}", Self::NAME)
}
}
/// Note: must be kept in sync with AppParameters.
/// Each field here is optional.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EncodedParameter {
/// The component name in the `AppParameters`.
///
/// This is the ProtoJSON-produced key in the `AppParameters` structure.
#[prost(string, tag = "1")]
pub component: ::prost::alloc::string::String,
/// The parameter key in the component parameters.
///
/// This is the ProtoJSON-produced field name in the component's substructure.
#[prost(string, tag = "2")]
pub key: ::prost::alloc::string::String,
/// The parameter value.
///
/// This is the ProtoJSON-encoded value of the parameter.
#[prost(string, tag = "3")]
pub value: ::prost::alloc::string::String,
}
impl ::prost::Name for EncodedParameter {
const NAME: &'static str = "EncodedParameter";
const PACKAGE: &'static str = "penumbra.core.component.governance.v1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!("penumbra.core.component.governance.v1.{}", Self::NAME)
}
}
/// DEPRECATED
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ChangedAppParameters {
Expand Down Expand Up @@ -1059,6 +1084,7 @@ impl ::prost::Name for ChangedAppParameters {
::prost::alloc::format!("penumbra.core.component.governance.v1.{}", Self::NAME)
}
}
/// DEPRECATED
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ChangedAppParametersSet {
Expand Down
Loading

0 comments on commit 313a8eb

Please sign in to comment.