Skip to content

Commit

Permalink
feat(dre): Add a proposals subcommand to get a single proposal by id (#…
Browse files Browse the repository at this point in the history
…412)

This allows fetching full proposal information, including the payload.
  • Loading branch information
sasa-tomic committed May 23, 2024
1 parent 524bd45 commit 5bfb885
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 13 deletions.
6 changes: 6 additions & 0 deletions rs/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,12 @@ pub mod proposals {
#[clap(long, aliases = ["topic"], short = 't')]
topics: Vec<Topic>,
},

/// Get a proposal by ID
Get {
/// Proposal ID
proposal_id: u64,
},
}

#[derive(ValueEnum, Clone, Debug)]
Expand Down
8 changes: 8 additions & 0 deletions rs/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ async fn async_main() -> Result<(), anyhow::Error> {
cli::proposals::Commands::Filter { limit, statuses, topics } => {
filter_proposals(target_network, limit, statuses.iter().map(|s| s.clone().into()).collect(), topics.iter().map(|t| t.clone().into()).collect()).await
}
cli::proposals::Commands::Get { proposal_id } => {
let nns_url = target_network.get_nns_urls().first().expect("Should have at least one NNS URL");
let client = GovernanceCanisterWrapper::from(CanisterClient::from_anonymous(nns_url)?);
let proposal = client.get_proposal(*proposal_id).await?;
let proposal = serde_json::to_string_pretty(&proposal).map_err(|e| anyhow::anyhow!("Couldn't serialize to string: {:?}", e))?;
println!("{}", proposal);
Ok(())
}
},
}
})
Expand Down
85 changes: 72 additions & 13 deletions rs/ic-canisters/src/governance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use candid::{Decode, Encode};
use candid::Decode;
use ic_agent::Agent;
use ic_nns_common::pb::v1::NeuronId;
use ic_nns_common::pb::v1::ProposalId;
Expand All @@ -9,11 +9,13 @@ use ic_nns_governance::pb::v1::ListProposalInfoResponse;
use ic_nns_governance::pb::v1::ManageNeuron;
use ic_nns_governance::pb::v1::ManageNeuronResponse;
use ic_nns_governance::pb::v1::ProposalInfo;
use log::warn;
use serde::{self, Serialize};
use std::str::FromStr;
use url::Url;

use crate::CanisterClient;
const MAX_RETRIES: usize = 5;

#[derive(Clone, Serialize)]
pub struct GovernanceCanisterVersion {
Expand Down Expand Up @@ -61,9 +63,14 @@ impl From<CanisterClient> for GovernanceCanisterWrapper {

impl GovernanceCanisterWrapper {
pub async fn get_pending_proposals(&self) -> anyhow::Result<Vec<ProposalInfo>> {
let vec: Vec<u8> = vec![];
backoff::future::retry(backoff::ExponentialBackoff::default(), || async {
let empty_args = Encode! { &vec }.map_err(|err| backoff::Error::Permanent(anyhow::format_err!(err)))?;
let mut retries = 0;
backoff::future::retry(backoff::ExponentialBackoff::default(), || async move {
retries += 1;
if retries >= MAX_RETRIES {
return Err(backoff::Error::Permanent(anyhow::anyhow!("Max retries exceeded")));
}
let empty_args =
candid::encode_one(()).map_err(|err| backoff::Error::Permanent(anyhow::format_err!(err)))?;
match self
.client
.agent
Expand All @@ -72,18 +79,72 @@ impl GovernanceCanisterWrapper {
{
Ok(Some(response)) => match Decode!(response.as_slice(), Vec<ProposalInfo>) {
Ok(response) => Ok(response),
Err(err) => Err(anyhow::anyhow!("Error decoding response: {}", err)),
Err(err) => Err(backoff::Error::Permanent(anyhow::anyhow!(
"Error decoding response: {}",
err
))),
},
Ok(None) => Ok(vec![]),
Err(err) => Err(anyhow::anyhow!("Error executing query: {}", err)),
Err(err) => {
warn!("Error executing query, retrying: {}", err);
Err(backoff::Error::Transient {
err: anyhow::anyhow!("Error executing query: {}", err),
retry_after: None,
})
}
}
})
.await
}

pub async fn get_proposal(&self, proposal_id: u64) -> anyhow::Result<ProposalInfo> {
let mut retries = 0;
backoff::future::retry(backoff::ExponentialBackoff::default(), || async move {
retries += 1;
if retries >= MAX_RETRIES {
return Err(backoff::Error::Permanent(anyhow::anyhow!("Max retries exceeded")));
}
let args =
candid::encode_one(proposal_id).map_err(|err| backoff::Error::Permanent(anyhow::format_err!(err)))?;
match self
.client
.agent
.execute_query(&GOVERNANCE_CANISTER_ID, "get_proposal_info", args)
.await
{
Ok(Some(response)) => match Decode!(response.as_slice(), Option<ProposalInfo>) {
Ok(response) => match response {
Some(proposal) => Ok(proposal),
None => Err(backoff::Error::Permanent(anyhow::anyhow!(
"Proposal with id {} not found",
proposal_id
))),
},
Err(err) => Err(backoff::Error::Permanent(anyhow::anyhow!(
"Error decoding response: {}",
err
))),
},
Ok(None) => Err(backoff::Error::Permanent(anyhow::anyhow!("Got an empty reponse"))),
Err(err) => {
warn!("Error executing query, retrying: {}", err);
Err(backoff::Error::Transient {
err: anyhow::anyhow!("Error executing query: {}", err),
retry_after: None,
})
}
}
.map_err(|err| backoff::Error::Transient { err, retry_after: None })
})
.await
}

pub async fn register_vote(&self, neuron_id: u64, proposal_id: u64) -> anyhow::Result<String> {
let response = backoff::future::retry(backoff::ExponentialBackoff::default(), || async {
let mut retries = 0;
let response = backoff::future::retry(backoff::ExponentialBackoff::default(), || async move {
retries += 1;
if retries >= MAX_RETRIES {
return Err(backoff::Error::Permanent(anyhow::anyhow!("Max retries exceeded")));
}
self.manage_neuron(&ManageNeuron {
id: Some(NeuronId { id: neuron_id }),
neuron_id_or_subaccount: None,
Expand Down Expand Up @@ -121,17 +182,15 @@ impl GovernanceCanisterWrapper {
}

async fn manage_neuron(&self, manage_neuron: &ManageNeuron) -> anyhow::Result<ManageNeuronResponse> {
let vec: Vec<u8> = vec![];

match self
.client
.agent
.execute_update(
&GOVERNANCE_CANISTER_ID,
&GOVERNANCE_CANISTER_ID,
"manage_neuron",
Encode! { manage_neuron }?,
Encode! { &vec }?,
candid::encode_one(manage_neuron)?,
candid::encode_one(())?,
)
.await
{
Expand All @@ -145,7 +204,7 @@ impl GovernanceCanisterWrapper {
}

pub async fn list_proposals(&self, contract: ListProposalInfo) -> anyhow::Result<Vec<ProposalInfo>> {
let args = Encode! { &contract }?;
let args = candid::encode_one(&contract)?;
match self
.client
.agent
Expand Down

0 comments on commit 5bfb885

Please sign in to comment.