Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC: add PoS queries #570

Merged
merged 9 commits into from
Nov 30, 2022
Merged
1 change: 1 addition & 0 deletions .changelog/unreleased/improvements/570-rpc-sub-vp-pos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added PoS specific queries ([#570](https://github.com/anoma/namada/pull/570))
106 changes: 39 additions & 67 deletions apps/src/lib/client/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,10 +1072,7 @@ pub async fn is_validator(
ledger_address: TendermintAddress,
) -> bool {
let client = HttpClient::new(ledger_address).unwrap();
let key = pos::validator_state_key(address);
let state: Option<pos::ValidatorStates> =
query_storage_value(&client, &key).await;
state.is_some()
unwrap_client_response(RPC.vp().pos().is_validator(&client, address).await)
}

/// Check if a given address is a known delegator
Expand Down Expand Up @@ -1519,8 +1516,10 @@ pub async fn get_proposal_votes(
.expect("Vote key should contains the voting address.")
.clone();
if vote.is_yay() && validators.contains(&voter_address) {
let amount =
get_validator_stake(client, epoch, &voter_address).await;
let amount: VotePower =
get_validator_stake(client, epoch, &voter_address)
.await
.into();
yay_validators.insert(voter_address, amount);
} else if !validators.contains(&voter_address) {
let validator_address =
Expand Down Expand Up @@ -1594,12 +1593,13 @@ pub async fn get_proposal_offline_votes(
if proposal_vote.vote.is_yay()
&& validators.contains(&proposal_vote.address)
{
let amount = get_validator_stake(
let amount: VotePower = get_validator_stake(
client,
proposal.tally_epoch,
&proposal_vote.address,
)
.await;
.await
.into();
yay_validators.insert(proposal_vote.address, amount);
} else if is_delegator_at(
client,
Expand Down Expand Up @@ -1697,26 +1697,25 @@ pub async fn compute_tally(
epoch: Epoch,
votes: Votes,
) -> ProposalResult {
let validators = get_all_validators(client, epoch).await;
let total_stacked_tokens =
get_total_staked_tokes(client, epoch, &validators).await;
let total_staked_tokens: VotePower =
get_total_staked_tokens(client, epoch).await.into();

let Votes {
yay_validators,
yay_delegators,
nay_delegators,
} = votes;

let mut total_yay_stacked_tokens = VotePower::from(0_u64);
let mut total_yay_staked_tokens = VotePower::from(0_u64);
for (_, amount) in yay_validators.clone().into_iter() {
total_yay_stacked_tokens += amount;
total_yay_staked_tokens += amount;
}

// YAY: Add delegator amount whose validator didn't vote / voted nay
for (_, vote_map) in yay_delegators.iter() {
for (validator_address, vote_power) in vote_map.iter() {
if !yay_validators.contains_key(validator_address) {
total_yay_stacked_tokens += vote_power;
total_yay_staked_tokens += vote_power;
}
}
}
Expand All @@ -1725,23 +1724,23 @@ pub async fn compute_tally(
for (_, vote_map) in nay_delegators.iter() {
for (validator_address, vote_power) in vote_map.iter() {
if yay_validators.contains_key(validator_address) {
total_yay_stacked_tokens -= vote_power;
total_yay_staked_tokens -= vote_power;
}
}
}

if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 {
if total_yay_staked_tokens >= (total_staked_tokens / 3) * 2 {
ProposalResult {
result: TallyResult::Passed,
total_voting_power: total_stacked_tokens,
total_yay_power: total_yay_stacked_tokens,
total_voting_power: total_staked_tokens,
total_yay_power: total_yay_staked_tokens,
total_nay_power: 0,
}
} else {
ProposalResult {
result: TallyResult::Rejected,
total_voting_power: total_stacked_tokens,
total_yay_power: total_yay_stacked_tokens,
total_voting_power: total_staked_tokens,
total_yay_power: total_yay_staked_tokens,
total_nay_power: 0,
}
}
Expand Down Expand Up @@ -1803,69 +1802,42 @@ pub async fn get_bond_amount_at(
pub async fn get_all_validators(
client: &HttpClient,
epoch: Epoch,
) -> Vec<Address> {
let validator_set_key = pos::validator_set_key();
let validator_sets =
query_storage_value::<pos::ValidatorSets>(client, &validator_set_key)
.await
.expect("Validator set should always be set");
let validator_set = validator_sets
.get(epoch)
.expect("Validator set should be always set in the current epoch");
let all_validators = validator_set.active.union(&validator_set.inactive);
all_validators
.map(|validator| validator.address.clone())
.collect()
) -> HashSet<Address> {
unwrap_client_response(
RPC.vp()
.pos()
.validator_addresses(client, &Some(epoch))
.await,
)
}

pub async fn get_total_staked_tokes(
pub async fn get_total_staked_tokens(
client: &HttpClient,
epoch: Epoch,
validators: &[Address],
) -> VotePower {
let mut total = VotePower::from(0_u64);

for validator in validators {
total += get_validator_stake(client, epoch, validator).await;
}
total
) -> token::Amount {
unwrap_client_response(
RPC.vp().pos().total_stake(client, &Some(epoch)).await,
)
}

async fn get_validator_stake(
client: &HttpClient,
epoch: Epoch,
validator: &Address,
) -> VotePower {
let total_voting_power_key = pos::validator_total_deltas_key(validator);
let total_voting_power = query_storage_value::<pos::ValidatorTotalDeltas>(
client,
&total_voting_power_key,
) -> token::Amount {
unwrap_client_response(
RPC.vp()
.pos()
.validator_stake(client, validator, &Some(epoch))
.await,
)
.await
.expect("Total deltas should be defined");
let epoched_total_voting_power = total_voting_power.get(epoch);

VotePower::try_from(epoched_total_voting_power.unwrap_or_default())
.unwrap_or_default()
}

pub async fn get_delegators_delegation(
client: &HttpClient,
address: &Address,
_epoch: Epoch,
) -> Vec<Address> {
let key = pos::bonds_for_source_prefix(address);
let bonds_iter = query_storage_prefix::<pos::Bonds>(client, &key).await;

let mut delegation_addresses: Vec<Address> = Vec::new();
if let Some(bonds) = bonds_iter {
for (key, _epoched_amount) in bonds {
let validator_address = pos::get_validator_address_from_bond(&key)
.expect("Delegation key should contain validator address.");
delegation_addresses.push(validator_address);
}
}
delegation_addresses
) -> HashSet<Address> {
unwrap_client_response(RPC.vp().pos().delegations(client, address).await)
}

pub async fn get_governance_parameters(client: &HttpClient) -> GovParams {
Expand Down
70 changes: 35 additions & 35 deletions apps/src/lib/client/tx.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::env;
use std::fs::File;
Expand Down Expand Up @@ -682,12 +683,9 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) {
safe_exit(1)
}
}
let mut delegation_addresses = rpc::get_delegators_delegation(
&client,
&voter_address,
epoch,
)
.await;
let mut delegations =
rpc::get_delegators_delegation(&client, &voter_address)
.await;

// Optimize by quering if a vote from a validator
// is equal to ours. If so, we can avoid voting, but ONLY if we
Expand All @@ -704,22 +702,20 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) {
)
.await
{
delegation_addresses = filter_delegations(
delegations = filter_delegations(
&client,
delegation_addresses,
delegations,
proposal_id,
&args.vote,
)
.await;
}

println!("{:?}", delegation_addresses);

let tx_data = VoteProposalData {
id: proposal_id,
vote: args.vote,
voter: voter_address,
delegations: delegation_addresses,
delegations: delegations.into_iter().collect(),
};

let data = tx_data
Expand Down Expand Up @@ -779,33 +775,37 @@ async fn is_safe_voting_window(
/// vote)
async fn filter_delegations(
client: &HttpClient,
mut delegation_addresses: Vec<Address>,
delegations: HashSet<Address>,
proposal_id: u64,
delegator_vote: &ProposalVote,
) -> Vec<Address> {
let mut remove_indexes: Vec<usize> = vec![];

for (index, validator_address) in delegation_addresses.iter().enumerate() {
let vote_key = gov_storage::get_vote_proposal_key(
proposal_id,
validator_address.to_owned(),
validator_address.to_owned(),
);

if let Some(validator_vote) =
rpc::query_storage_value::<ProposalVote>(client, &vote_key).await
{
if &validator_vote == delegator_vote {
remove_indexes.push(index);
}
}
}

for index in remove_indexes {
delegation_addresses.swap_remove(index);
}
) -> HashSet<Address> {
// Filter delegations by their validator's vote concurrently
let delegations = futures::future::join_all(
delegations
.into_iter()
// we cannot use `filter/filter_map` directly because we want to
// return a future
.map(|validator_address| async {
let vote_key = gov_storage::get_vote_proposal_key(
proposal_id,
validator_address.to_owned(),
validator_address.to_owned(),
);

delegation_addresses
if let Some(validator_vote) =
rpc::query_storage_value::<ProposalVote>(client, &vote_key)
.await
{
if &validator_vote == delegator_vote {
return None;
}
}
Some(validator_address)
}),
)
.await;
// Take out the `None`s
delegations.into_iter().flatten().collect()
Comment on lines +781 to +808
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting pattern, to turn an iterator into a future. I'll try to use this whenever I can :D

}

pub async fn submit_bond(ctx: Context, args: args::Bond) {
Expand Down
28 changes: 23 additions & 5 deletions apps/src/lib/node/ledger/shell/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ where
})?;

let votes = get_proposal_votes(&shell.storage, proposal_end_epoch, id);
let tally_result =
compute_tally(&shell.storage, proposal_end_epoch, votes);
let is_accepted = votes.and_then(|votes| {
compute_tally(&shell.storage, proposal_end_epoch, votes)
});

let transfer_address = match tally_result {
TallyResult::Passed => {
let transfer_address = match is_accepted {
Ok(true) => {
let proposal_author_key = gov_storage::get_author_key(id);
let proposal_author = shell
.read_storage_key::<Address>(&proposal_author_key)
Expand Down Expand Up @@ -161,7 +162,7 @@ where
}
}
}
TallyResult::Rejected | TallyResult::Unknown => {
Ok(false) => {
let proposal_event: Event = ProposalEvent::new(
EventType::Proposal.to_string(),
TallyResult::Rejected,
Expand All @@ -173,6 +174,23 @@ where
response.events.push(proposal_event);
proposals_result.rejected.push(id);

slash_fund_address
}
Err(err) => {
tracing::error!(
"Unexpectedly failed to tally proposal ID {id} with error \
{err}"
);
let proposal_event: Event = ProposalEvent::new(
EventType::Proposal.to_string(),
TallyResult::Failed,
id,
false,
false,
)
.into();
response.events.push(proposal_event);

slash_fund_address
}
};
Expand Down
Loading