Skip to content

Commit

Permalink
Add a list-neurons subcommand (#34)
Browse files Browse the repository at this point in the history
* Begin implementing a list-neurons subcommand

* fix queries

* implements query response parsing

* reduce the scope to the list of controller's neurons

* version bump

* clippy

Co-authored-by: Christian Müller <[email protected]>
  • Loading branch information
jwiegley and Christian Müller authored Aug 23, 2021
1 parent f9762ef commit 7b2222e
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "quill"
version = "0.2.3"
version = "0.2.4"
authors = ["DFINITY Team"]
edition = "2018"

Expand Down
22 changes: 22 additions & 0 deletions src/commands/list_neurons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::{
commands::sign::sign_ingress,
lib::{governance_canister_id, sign::signed_message::Ingress, AnyhowResult},
};
use candid::{CandidType, Encode};

#[derive(CandidType)]
pub struct ListNeurons {
pub neuron_ids: Vec<u64>,
pub include_neurons_readable_by_caller: bool,
}

// We currently only support a subset of the functionality.
pub async fn exec(pem: &Option<String>) -> AnyhowResult<Vec<Ingress>> {
let args = Encode!(&ListNeurons {
neuron_ids: Vec::new(),
include_neurons_readable_by_caller: true,
})?;
Ok(vec![
sign_ingress(pem, governance_canister_id(), "list_neurons", args).await?,
])
}
6 changes: 6 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::Clap;
use std::io::{self, Write};
use tokio::runtime::Runtime;

mod list_neurons;
mod neuron_manage;
mod neuron_stake;
mod public;
Expand All @@ -23,6 +24,8 @@ pub enum Command {
Transfer(transfer::TransferOpts),
NeuronStake(neuron_stake::StakeOpts),
NeuronManage(neuron_manage::ManageOpts),
/// Signs the query for all neurons belonging to the signin principal.
ListNeurons,
}

pub fn exec(pem: &Option<String>, cmd: Command) -> AnyhowResult {
Expand All @@ -43,6 +46,9 @@ pub fn exec(pem: &Option<String>, cmd: Command) -> AnyhowResult {
.and_then(|out| print(&out))
}),
Command::Send(opts) => runtime.block_on(async { send::exec(pem, opts).await }),
Command::ListNeurons => {
runtime.block_on(async { list_neurons::exec(pem).await.and_then(|out| print(&out)) })
}
}
}

Expand Down
15 changes: 11 additions & 4 deletions src/commands/send.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::commands::request_status;
use crate::lib::{
read_from_file,
sign::signed_message::{Ingress, IngressWithRequestId},
sign::signed_message::{parse_query_response, Ingress, IngressWithRequestId},
AnyhowResult, IC_URL,
};
use anyhow::anyhow;
Expand Down Expand Up @@ -58,6 +58,10 @@ pub async fn exec(pem: &Option<String>, opts: SendOpts) -> AnyhowResult {
let json = read_from_file(&opts.file_name)?;
if let Ok(val) = serde_json::from_str::<Ingress>(&json) {
send(&val, &opts).await?;
} else if let Ok(vals) = serde_json::from_str::<Vec<Ingress>>(&json) {
for msg in vals {
send(&msg, &opts).await?;
}
} else if let Ok(vals) = serde_json::from_str::<Vec<IngressWithRequestId>>(&json) {
for tx in vals {
submit_ingress_and_check_status(pem, &tx, &opts).await?;
Expand Down Expand Up @@ -114,9 +118,12 @@ async fn send(message: &Ingress, opts: &SendOpts) -> AnyhowResult {

match message.call_type.as_str() {
"query" => {
let response = transport.query(canister_id, content).await?;
print!("Response: ");
println!("{}", hex::encode(response));
let response = parse_query_response(
transport.query(canister_id, content).await?,
canister_id,
&method_name,
)?;
println!("Response: {}", response);
}
"update" => {
let request_id = RequestId::from_str(
Expand Down
13 changes: 12 additions & 1 deletion src/commands/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::commands::request_status;
use crate::lib::{
get_agent, get_candid_type, get_local_candid,
sign::sign_transport::{SignReplicaV2Transport, SignedMessageWithRequestId},
sign::signed_message::IngressWithRequestId,
sign::signed_message::{Ingress, IngressWithRequestId},
AnyhowResult,
};
use anyhow::anyhow;
Expand Down Expand Up @@ -81,3 +81,14 @@ pub async fn sign_ingress_with_request_status_query(
};
Ok(message)
}

/// Generates a signed ingress message.
pub async fn sign_ingress(
pem: &Option<String>,
canister_id: Principal,
method_name: &str,
args: Vec<u8>,
) -> AnyhowResult<Ingress> {
let msg = sign(pem, canister_id, method_name, args).await?;
Ok(msg.message.try_into()?)
}
33 changes: 33 additions & 0 deletions src/lib/sign/signed_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,36 @@ impl Ingress {
Err(anyhow!("Invalid cbor content"))
}
}

pub fn parse_query_response(
response: Vec<u8>,
canister_id: Principal,
method_name: &str,
) -> AnyhowResult<String> {
let cbor: Value = serde_cbor::from_slice(&response)
.map_err(|_| anyhow!("Invalid cbor data in the content of the message."))?;
if let Value::Map(m) = cbor {
// Try to decode a rejected response.
if let (_, Some(Value::Integer(reject_code)), Some(Value::Text(reject_message))) = (
m.get(&Value::Text("status".to_string())),
m.get(&Value::Text("reject_code".to_string())),
m.get(&Value::Text("reject_message".to_string())),
) {
return Ok(format!(
"Rejected (code {}): {}",
reject_code, reject_message
));
}

// Try to decode a successful response.
if let (_, Some(Value::Map(m))) = (
m.get(&Value::Text("status".to_string())),
m.get(&Value::Text("reply".to_string())),
) {
if let Some(Value::Bytes(reply)) = m.get(&Value::Text("arg".to_string())) {
return get_idl_string(reply, canister_id, method_name, "rets");
}
}
}
Err(anyhow!("Invalid cbor content"))
}
1 change: 1 addition & 0 deletions tests/commands/list-neurons.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
../target/debug/quill --pem-file - list-neurons | ../target/debug/quill send --dry-run -
2 changes: 2 additions & 0 deletions tests/fix_outputs.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/bin/bash

PEM=`cat ./identity.pem`

for f in `ls -1 ./commands/`; do
Expand Down
7 changes: 7 additions & 0 deletions tests/outputs/list-neurons.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Sending message with

Call type: query
Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae
Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai
Method name: list_neurons
Arguments: (record { neuron_ids = vec {}; include_neurons_readable_by_caller = true })

0 comments on commit 7b2222e

Please sign in to comment.