-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
work in progress: test that gov proposals can be made
- Loading branch information
Showing
1 changed file
with
296 additions
and
0 deletions.
There are no files selected for viewing
296 changes: 296 additions & 0 deletions
296
crates/core/app/tests/app_can_propose_community_pool_spends.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
use { | ||
anyhow::anyhow, | ||
cnidarium::TempStorage, | ||
decaf377_rdsa::VerificationKey, | ||
penumbra_app::{ | ||
genesis::{AppState, Content}, | ||
server::consensus::Consensus, | ||
}, | ||
penumbra_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend}, | ||
penumbra_governance::{ | ||
Proposal, ProposalSubmit, ValidatorVote, ValidatorVoteBody, ValidatorVoteReason, | ||
}, | ||
penumbra_keys::{ | ||
keys::{SpendKey, SpendKeyBytes}, | ||
test_keys, | ||
}, | ||
penumbra_mock_client::MockClient, | ||
penumbra_mock_consensus::TestNode, | ||
penumbra_num::Amount, | ||
penumbra_proto::{ | ||
core::keys::v1::{GovernanceKey, IdentityKey}, | ||
penumbra::core::component::stake::v1::Validator as PenumbraValidator, | ||
DomainType, | ||
}, | ||
penumbra_shielded_pool::{genesis::Allocation, Note, SpendPlan}, | ||
penumbra_stake::{component::validator_handler::ValidatorDataRead, DelegationToken}, | ||
penumbra_tct::StateCommitment, | ||
penumbra_transaction::{ActionPlan, TransactionParameters, TransactionPlan}, | ||
rand::Rng, | ||
rand_core::OsRng, | ||
std::collections::BTreeMap, | ||
tap::{Tap, TapFallible}, | ||
tracing::{error_span, info, Instrument}, | ||
}; | ||
|
||
mod common; | ||
|
||
/// Exercises that the app can make proposals to spend community pool funds. | ||
#[tokio::test] | ||
async fn app_can_propose_community_pool_spends() -> anyhow::Result<()> { | ||
// Install a test logger, and acquire some temporary storage. | ||
let guard = common::set_tracing_subscriber(); | ||
let storage = TempStorage::new().await?; | ||
|
||
// Generate a set of consensus keys. | ||
let consensus_sk = ed25519_consensus::SigningKey::new(OsRng); | ||
let consensus_vk = consensus_sk.verification_key(); | ||
|
||
// Generate a set of identity keys. | ||
let spend_key: SpendKey = SpendKeyBytes(OsRng.gen()).into(); | ||
let (identity_sk, identity_vk) = { | ||
let sk = spend_key.spend_auth_key(); | ||
let vk = VerificationKey::from(sk); | ||
(sk, vk) | ||
}; | ||
let (governance_sk, governance_vk) = (identity_sk, identity_vk); | ||
|
||
// Define a validator and an associated genesis allocation. | ||
let (validator, allocation) = { | ||
let v = PenumbraValidator { | ||
identity_key: Some(IdentityKey { | ||
ik: identity_vk.to_bytes().to_vec(), | ||
}), | ||
// NB: for now, we will use the same key for governance. See the documentation of | ||
// `GovernanceKey` for more information about cold storage of validator keys. | ||
governance_key: Some(GovernanceKey { | ||
gk: identity_vk.to_bytes().to_vec(), | ||
}), | ||
consensus_key: consensus_vk.as_bytes().to_vec(), | ||
enabled: true, | ||
sequence_number: 0, | ||
name: String::default(), | ||
website: String::default(), | ||
description: String::default(), | ||
funding_streams: Vec::default(), | ||
}; | ||
|
||
let (address, _) = spend_key | ||
.full_viewing_key() | ||
.incoming() | ||
.payment_address(0u32.into()); | ||
|
||
let ik = penumbra_stake::IdentityKey(identity_vk.into()); | ||
let delegation_denom = DelegationToken::from(ik).denom(); | ||
|
||
let allocation = Allocation { | ||
raw_amount: 1000u128.into(), | ||
raw_denom: delegation_denom.to_string(), | ||
address, | ||
}; | ||
|
||
(v, allocation) | ||
}; | ||
|
||
// Define our application state, and start the test node. | ||
let mut test_node = { | ||
let mut content = Content { | ||
governance_content: penumbra_governance::genesis::Content { | ||
governance_params: penumbra_governance::params::GovernanceParameters { | ||
proposal_deposit_amount: 0_u32.into(), | ||
..Default::default() | ||
}, | ||
}, | ||
..Default::default() | ||
}; | ||
content.stake_content.validators.push(validator); | ||
content.shielded_pool_content.allocations.push(allocation); | ||
let app_state = AppState::Content(content); | ||
let app_state = serde_json::to_vec(&app_state).unwrap(); | ||
let consensus = Consensus::new(storage.as_ref().clone()); | ||
TestNode::builder() | ||
.single_validator() | ||
.app_state(app_state) | ||
.init_chain(consensus) | ||
.await | ||
.tap_ok(|e| tracing::info!(hash = %e.last_app_hash_hex(), "finished init chain"))? | ||
}; | ||
let original_snapshot = storage.latest_snapshot(); | ||
let [_validator] = storage | ||
.latest_snapshot() | ||
.validator_definitions() | ||
.await? | ||
.try_into() | ||
.map_err(|validator| anyhow::anyhow!("expected one validator, got: {validator:?}"))?; | ||
|
||
// Sync the mock client, using the test wallet's spend key, to the latest snapshot. | ||
let mut client = MockClient::new(test_keys::SPEND_KEY.clone()) | ||
.with_sync_to_storage(&storage) | ||
.await? | ||
.tap(|c| info!(client.notes = %c.notes.len(), "mock client synced to test storage")); | ||
|
||
// Take one of the test wallet's notes, and prepare to deposit it in the community pool. | ||
let note = client | ||
.notes | ||
.values() | ||
.cloned() | ||
.next() | ||
.ok_or_else(|| anyhow!("mock client had no note"))?; | ||
|
||
// Create a community pool transaction. | ||
let mut plan = { | ||
let value = note.value(); | ||
let spend = SpendPlan::new( | ||
&mut OsRng, | ||
note.clone(), | ||
client | ||
.position(note.commit()) | ||
.ok_or_else(|| anyhow!("input note commitment was unknown to mock client"))?, | ||
) | ||
.into(); | ||
let deposit = CommunityPoolDeposit { value }.into(); | ||
TransactionPlan { | ||
actions: vec![spend, deposit], | ||
// Now fill out the remaining parts of the transaction needed for verification: | ||
memo: None, | ||
detection_data: None, // We'll set this automatically below | ||
transaction_parameters: TransactionParameters { | ||
chain_id: TestNode::<()>::CHAIN_ID.to_string(), | ||
..Default::default() | ||
}, | ||
} | ||
}; | ||
plan.populate_detection_data(OsRng, 0); | ||
let tx = client.witness_auth_build(&plan).await?; | ||
|
||
// Execute the transaction, applying it to the chain state. | ||
test_node | ||
.block() | ||
.with_data(vec![tx.encode_to_vec()]) | ||
.execute() | ||
.instrument(error_span!("executing block with community pool deposit")) | ||
.await?; | ||
let post_deposit_snapshot = storage.latest_snapshot(); | ||
client.sync_to_latest(post_deposit_snapshot).await?; | ||
|
||
// Now, make a governance proposal that we should spend community pool funds, to return | ||
// the note back to the test wallet. | ||
let mut plan = { | ||
// XXX The submission should include a proposed transaction plan to spend pool funds. | ||
// TODO: per the spec, the asset id and address are specified in base64. fix that! | ||
// let value = note.value(); | ||
// let proposed_tx_plan = TransactionPlan { | ||
// actions: vec![ | ||
// CommunityPoolSpend { value }.into(), | ||
// CommunityPoolOutput { | ||
// value, | ||
// address: *test_keys::ADDRESS_0, | ||
// } | ||
// .into(), | ||
// ], | ||
// memo: None, | ||
// detection_data: None, | ||
// transaction_parameters: TransactionParameters { | ||
// chain_id: TestNode::<()>::CHAIN_ID.to_string(), | ||
// ..Default::default() | ||
// }, | ||
// }; | ||
let proposal = ActionPlan::ProposalSubmit(ProposalSubmit { | ||
proposal: Proposal { | ||
id: 1_u64, | ||
title: "return test deposit".to_owned(), | ||
description: "a proposal to return the community pool deposit".to_owned(), | ||
payload: penumbra_governance::ProposalPayload::CommunityPoolSpend { | ||
// XXX transaction_plan: proposed_tx_plan.encode_to_vec(), | ||
transaction_plan: TransactionPlan::default().encode_to_vec(), | ||
}, | ||
}, | ||
deposit_amount: 0_u32.into(), | ||
}); | ||
TransactionPlan { | ||
actions: vec![proposal], | ||
memo: None, | ||
detection_data: None, | ||
transaction_parameters: TransactionParameters { | ||
chain_id: TestNode::<()>::CHAIN_ID.to_string(), | ||
..Default::default() | ||
}, | ||
} | ||
}; | ||
plan.populate_detection_data(OsRng, 0); | ||
let tx = client.witness_auth_build(&plan).await?; | ||
|
||
// Execute the transaction, applying it to the chain state. | ||
test_node | ||
.block() | ||
.with_data(vec![tx.encode_to_vec()]) | ||
.execute() | ||
.instrument(error_span!("executing block with governance proposal")) | ||
.await?; | ||
let post_proposal_snapshot = storage.latest_snapshot(); | ||
panic!("stop here"); // XXX this transaction does not execute successfuly, panic here for now. | ||
|
||
// Now make another transaction that will contain a validator vote upon our transaction. | ||
let mut plan = { | ||
let body = ValidatorVoteBody { | ||
proposal: 1_u64, | ||
vote: penumbra_governance::Vote::Yes, | ||
identity_key: penumbra_stake::IdentityKey(identity_vk.to_bytes().into()), | ||
governance_key: penumbra_stake::GovernanceKey(governance_vk), | ||
reason: ValidatorVoteReason("test reason".to_owned()), | ||
}; | ||
let auth_sig = governance_sk.sign(OsRng, body.encode_to_vec().as_slice()); | ||
let vote = ValidatorVote { body, auth_sig }.into(); | ||
TransactionPlan { | ||
actions: vec![vote], | ||
memo: None, | ||
detection_data: None, | ||
transaction_parameters: TransactionParameters { | ||
chain_id: TestNode::<()>::CHAIN_ID.to_string(), | ||
..Default::default() | ||
}, | ||
} | ||
}; | ||
plan.populate_detection_data(OsRng, 0); | ||
let tx = client.witness_auth_build(&plan).await?; | ||
|
||
// Execute the transaction, applying it to the chain state. | ||
test_node | ||
.block() | ||
.with_data(vec![tx.encode_to_vec()]) | ||
.execute() | ||
.instrument(error_span!("executing block with validator vote")) | ||
.await?; | ||
let post_vote_snapshot = storage.latest_snapshot(); | ||
|
||
// Define a helper to sum the client's notes of the originally deposited asset type. | ||
type Notes = BTreeMap<StateCommitment, Note>; | ||
let sum_notes = |notes: &Notes| { | ||
notes | ||
.iter() | ||
.filter(|(_, n)| n.asset_id() == note.asset_id()) | ||
.map(|(_, n)| n.amount()) | ||
.sum::<Amount>() | ||
}; | ||
|
||
// Find the sums before and after our transactions. | ||
let original_sum = sum_notes(&client.notes); | ||
|
||
let post_deposit_sum = sum_notes(&client.notes); | ||
|
||
client.sync_to_latest(post_proposal_snapshot).await?; | ||
let post_proposal_sum = sum_notes(&client.notes); | ||
|
||
client.sync_to_latest(post_vote_snapshot).await?; | ||
let post_vote_sum = sum_notes(&client.notes); | ||
|
||
assert_eq!(original_sum, post_deposit_sum); | ||
assert_eq!(post_deposit_sum, post_proposal_sum); | ||
assert_eq!(post_proposal_sum, post_vote_sum); | ||
|
||
// Free our temporary storage. | ||
Ok(()) | ||
.tap(|_| drop(test_node)) | ||
.tap(|_| drop(storage)) | ||
.tap(|_| drop(guard)) | ||
} |