Skip to content

Commit 1b69da2

Browse files
codilionMontague di HartVale Fitz PianoGianna McDiminuendoIsa di Hind
authored andcommitted
[cli] improve ux and info on vouch and cw reauth txs (#411)
Co-authored-by: Montague di Hart <[email protected]> Co-authored-by: Vale Fitz Piano <[email protected]> Co-authored-by: Gianna McDiminuendo <[email protected]> Co-authored-by: Isa di Hind <[email protected]> Co-authored-by: Peregrine Saint Hart <[email protected]> Co-authored-by: Peregrine Saint Forte <[email protected]> Co-authored-by: Reginald Andante <[email protected]> Co-authored-by: Peregrine Von Urchin <[email protected]> Co-authored-by: Gianna Accelerando <[email protected]> Co-authored-by: Cholmondeley Hobby <[email protected]> Co-authored-by: Franci Leveret <[email protected]> Co-authored-by: 0o-de-lally <[email protected]>
1 parent 806061e commit 1b69da2

File tree

8 files changed

+361
-30
lines changed

8 files changed

+361
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

framework/libra-framework/sources/ol_sources/vouch_lib/vouch_limits.move

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,11 @@ module ol_framework::vouch_limits {
183183
#[view]
184184
/// Calculate the maximum number of vouches a user should be able to give based on their trust score
185185
public fun calculate_score_limit(grantor_acc: address): u64 {
186-
// Calculate the quality using the social distance method
186+
// Get the user's vouching limit using the social distance method
187187
// This avoids dependency on page_rank_lazy
188-
let trust_score = page_rank_lazy::get_trust_score(grantor_acc);
188+
189+
// commit note: this was previously an impure function which would fail
190+
let trust_score = page_rank_lazy::get_cached_score(grantor_acc);
189191
let max_score = page_rank_lazy::get_max_single_score();
190192

191193
// For accounts with low quality vouchers,

tools/query/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ diem-debugger = { workspace = true }
1919
diem-sdk = { workspace = true }
2020
indoc = { workspace = true }
2121
libra-types = { workspace = true }
22+
serde = { workspace = true }
2223
serde_json = { workspace = true }
2324
tokio = { workspace = true }
2425
url = { workspace = true }

tools/query/src/account_queries.rs

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,22 @@ use libra_types::{
1111
move_resource::{gas_coin::SlowWalletBalance, txschedule::TxSchedule},
1212
type_extensions::client_ext::{entry_function_id, ClientExt},
1313
};
14+
use serde::{Deserialize, Serialize};
1415
use serde_json::{json, Value};
1516

17+
/// Structured data for account vouch report
18+
#[derive(Debug, Serialize, Deserialize)]
19+
pub struct AccountVouchReportData {
20+
pub account: String,
21+
pub cached_score: Option<u64>,
22+
pub fresh_score: Option<u64>,
23+
pub max_depth_reached: Option<u64>,
24+
pub accounts_processed: Option<u64>,
25+
pub max_vouches_by_score: Option<u64>,
26+
pub remaining_vouches_available: Option<u64>,
27+
pub errors: Vec<String>,
28+
}
29+
1630
/// helper to get libra balance at a SlowWalletBalance type which shows
1731
/// total balance and the unlocked balance.
1832
pub async fn get_account_balance_libra(
@@ -134,3 +148,271 @@ pub async fn multi_auth_ballots(
134148

135149
Ok(r.data)
136150
}
151+
152+
/// Calculates a fresh page rank trust score for an account without updating the cache.
153+
/// Returns (score, max_depth_reached, accounts_processed) as a tuple.
154+
pub async fn page_rank_calculate_score(
155+
client: &Client,
156+
account: AccountAddress,
157+
) -> anyhow::Result<(u64, u64, u64)> {
158+
let calculate_score_id = entry_function_id("page_rank_lazy", "calculate_score")?;
159+
let request = ViewRequest {
160+
function: calculate_score_id,
161+
type_arguments: vec![],
162+
arguments: vec![account.to_string().into()],
163+
};
164+
165+
let res = client.view(&request, None).await?.into_inner();
166+
167+
// Parse the tuple response (score, max_depth_reached, accounts_processed)
168+
if res.len() != 3 {
169+
return Err(anyhow::anyhow!(
170+
"Expected 3 values from calculate_score, got {}",
171+
res.len()
172+
));
173+
}
174+
175+
// Handle string or numeric responses from the Move VM
176+
let score: u64 = match &res[0] {
177+
serde_json::Value::String(s) => s.parse()?,
178+
serde_json::Value::Number(n) => n
179+
.as_u64()
180+
.ok_or_else(|| anyhow::anyhow!("Invalid number format for score"))?,
181+
_ => return Err(anyhow::anyhow!("Unexpected response type for score")),
182+
};
183+
184+
let max_depth_reached: u64 = match &res[1] {
185+
serde_json::Value::String(s) => s.parse()?,
186+
serde_json::Value::Number(n) => n
187+
.as_u64()
188+
.ok_or_else(|| anyhow::anyhow!("Invalid number format for max_depth"))?,
189+
_ => return Err(anyhow::anyhow!("Unexpected response type for max_depth")),
190+
};
191+
192+
let accounts_processed: u64 = match &res[2] {
193+
serde_json::Value::String(s) => s.parse()?,
194+
serde_json::Value::Number(n) => n
195+
.as_u64()
196+
.ok_or_else(|| anyhow::anyhow!("Invalid number format for accounts_processed"))?,
197+
_ => {
198+
return Err(anyhow::anyhow!(
199+
"Unexpected response type for accounts_processed"
200+
))
201+
}
202+
};
203+
204+
Ok((score, max_depth_reached, accounts_processed))
205+
}
206+
207+
/// Retrieves the cached page rank trust score for an account.
208+
/// This returns the previously computed score without recalculation.
209+
pub async fn page_rank_get_cached_score(
210+
client: &Client,
211+
account: AccountAddress,
212+
) -> anyhow::Result<u64> {
213+
let get_cached_score_id = entry_function_id("page_rank_lazy", "get_cached_score")?;
214+
let request = ViewRequest {
215+
function: get_cached_score_id,
216+
type_arguments: vec![],
217+
arguments: vec![account.to_string().into()],
218+
};
219+
220+
let res = client.view(&request, None).await?.into_inner();
221+
222+
// Parse the single u64 response
223+
if res.is_empty() {
224+
return Err(anyhow::anyhow!("No values returned from get_cached_score"));
225+
}
226+
227+
// Handle both string and numeric responses from the Move VM
228+
let score: u64 = match &res[0] {
229+
serde_json::Value::String(s) => s.parse()?,
230+
serde_json::Value::Number(n) => n
231+
.as_u64()
232+
.ok_or_else(|| anyhow::anyhow!("Invalid number format"))?,
233+
_ => return Err(anyhow::anyhow!("Unexpected response type for cached score")),
234+
};
235+
Ok(score)
236+
}
237+
238+
/// Calculates the maximum number of vouches a user should be able to give based on their trust score.
239+
/// This is determined by the user's page rank trust score relative to the maximum score in the system.
240+
pub async fn vouch_limits_calculate_score_limit(
241+
client: &Client,
242+
account: AccountAddress,
243+
) -> anyhow::Result<u64> {
244+
let calculate_score_limit_id = entry_function_id("vouch_limits", "calculate_score_limit")?;
245+
let request = ViewRequest {
246+
function: calculate_score_limit_id,
247+
type_arguments: vec![],
248+
arguments: vec![account.to_string().into()],
249+
};
250+
251+
let res = client.view(&request, None).await?.into_inner();
252+
253+
// Parse the single u64 response
254+
if res.is_empty() {
255+
return Err(anyhow::anyhow!(
256+
"No values returned from calculate_score_limit"
257+
));
258+
}
259+
260+
// Handle both string and numeric responses from the Move VM
261+
let limit: u64 = match &res[0] {
262+
serde_json::Value::String(s) => s.parse()?,
263+
serde_json::Value::Number(n) => n
264+
.as_u64()
265+
.ok_or_else(|| anyhow::anyhow!("Invalid number format"))?,
266+
_ => return Err(anyhow::anyhow!("Unexpected response type for score limit")),
267+
};
268+
Ok(limit)
269+
}
270+
271+
/// Returns the number of vouches a user can still give based on system limits.
272+
/// This takes into account all constraints: base maximum limit, score-based limit,
273+
/// received vouches + 1 limit, and per-epoch limit.
274+
pub async fn vouch_limits_get_vouch_limit(
275+
client: &Client,
276+
account: AccountAddress,
277+
) -> anyhow::Result<u64> {
278+
let get_vouch_limit_id = entry_function_id("vouch_limits", "get_vouch_limit")?;
279+
let request = ViewRequest {
280+
function: get_vouch_limit_id,
281+
type_arguments: vec![],
282+
arguments: vec![account.to_string().into()],
283+
};
284+
285+
let res = client.view(&request, None).await?.into_inner();
286+
287+
// Parse the single u64 response
288+
if res.is_empty() {
289+
return Err(anyhow::anyhow!("No values returned from get_vouch_limit"));
290+
}
291+
292+
// Handle both string and numeric responses from the Move VM
293+
let limit: u64 = match &res[0] {
294+
serde_json::Value::String(s) => s.parse()?,
295+
serde_json::Value::Number(n) => n
296+
.as_u64()
297+
.ok_or_else(|| anyhow::anyhow!("Invalid number format"))?,
298+
_ => return Err(anyhow::anyhow!("Unexpected response type for vouch limit")),
299+
};
300+
Ok(limit)
301+
}
302+
303+
/// Creates a comprehensive vouch report for an account, combining page rank scores and vouch limits.
304+
/// This function returns structured data that can be used for JSON output or further processing.
305+
pub async fn account_vouch_report(
306+
client: &Client,
307+
account: AccountAddress,
308+
) -> anyhow::Result<AccountVouchReportData> {
309+
let mut errors = Vec::new();
310+
311+
// Get page rank scores
312+
let cached_score = match page_rank_get_cached_score(client, account).await {
313+
Ok(score) => Some(score),
314+
Err(e) => {
315+
errors.push(format!("cached_score: {}", e));
316+
None
317+
}
318+
};
319+
320+
let (fresh_score, max_depth_reached, accounts_processed) =
321+
match page_rank_calculate_score(client, account).await {
322+
Ok((score, max_depth_reached, accounts_processed)) => (
323+
Some(score),
324+
Some(max_depth_reached),
325+
Some(accounts_processed),
326+
),
327+
Err(e) => {
328+
errors.push(format!("fresh_score: {}", e));
329+
(None, None, None)
330+
}
331+
};
332+
333+
// Get vouch limits
334+
let max_vouches_by_score = match vouch_limits_calculate_score_limit(client, account).await {
335+
Ok(limit) => Some(limit),
336+
Err(e) => {
337+
errors.push(format!("max_vouches_by_score: {}", e));
338+
None
339+
}
340+
};
341+
342+
let remaining_vouches_available = match vouch_limits_get_vouch_limit(client, account).await {
343+
Ok(limit) => Some(limit),
344+
Err(e) => {
345+
errors.push(format!("remaining_vouches_available: {}", e));
346+
None
347+
}
348+
};
349+
350+
Ok(AccountVouchReportData {
351+
account: account.to_string(),
352+
cached_score,
353+
fresh_score,
354+
max_depth_reached,
355+
accounts_processed,
356+
max_vouches_by_score,
357+
remaining_vouches_available,
358+
errors,
359+
})
360+
}
361+
362+
/// Prints a comprehensive vouch report for an account to the console.
363+
/// This function provides a readable summary of an account's trust metrics and vouching capabilities.
364+
pub async fn account_vouch_report_console(
365+
client: &Client,
366+
account: AccountAddress,
367+
) -> anyhow::Result<()> {
368+
let report = account_vouch_report(client, account).await?;
369+
370+
println!("=== Account Vouch Report for {} ===\n", account);
371+
372+
// Page rank scores
373+
println!("Page Rank Trust Scores:");
374+
375+
match report.cached_score {
376+
Some(score) => println!(" • Cached Trust Score: {}", score),
377+
None => println!(" • Cached Trust Score: Not available"),
378+
}
379+
380+
match (
381+
report.fresh_score,
382+
report.max_depth_reached,
383+
report.accounts_processed,
384+
) {
385+
(Some(score), Some(max_depth), Some(accounts_processed)) => {
386+
println!(" • Fresh Trust Score: {}", score);
387+
println!(" • Max Depth Reached: {}", max_depth);
388+
println!(" • Accounts Processed: {}", accounts_processed);
389+
}
390+
_ => println!(" • Fresh Trust Score: Not available"),
391+
}
392+
393+
println!();
394+
395+
// Vouch limits
396+
println!("Vouching Limits:");
397+
398+
match report.max_vouches_by_score {
399+
Some(limit) => println!(" • Max Vouches (based on trust score): {}", limit),
400+
None => println!(" • Max Vouches (based on trust score): Not available"),
401+
}
402+
403+
match report.remaining_vouches_available {
404+
Some(limit) => println!(" • Remaining Vouches Available: {}", limit),
405+
None => println!(" • Remaining Vouches Available: Not available"),
406+
}
407+
408+
// Show errors if any
409+
if !report.errors.is_empty() {
410+
println!("\nErrors encountered:");
411+
for error in &report.errors {
412+
println!(" • {}", error);
413+
}
414+
}
415+
416+
println!("\n=== End of Report ===");
417+
Ok(())
418+
}

tools/query/src/query_type.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
account_queries::{
3-
community_wallet_scheduled_transactions, community_wallet_signers,
3+
account_vouch_report, community_wallet_scheduled_transactions, community_wallet_signers,
44
get_account_balance_libra, get_events, get_transactions, get_val_config,
55
is_community_wallet_migrated,
66
},
@@ -125,6 +125,11 @@ pub enum QueryType {
125125
},
126126
/// Display all account structs
127127
Annotate { account: AccountAddress },
128+
/// Generate a comprehensive vouch report showing page rank scores and vouch limits
129+
VouchReport {
130+
/// account to generate vouch report for
131+
account: AccountAddress,
132+
},
128133
}
129134

130135
impl QueryType {
@@ -247,6 +252,12 @@ impl QueryType {
247252
let pretty = format!("{:#}", blob.unwrap().to_string());
248253
Ok(json!(pretty))
249254
}
255+
QueryType::VouchReport { account } => {
256+
// Get the structured vouch report data
257+
let report_data = account_vouch_report(client, *account).await?;
258+
// Return the data as JSON
259+
Ok(json!(report_data))
260+
}
250261
_ => {
251262
bail!(
252263
"Not implemented for type: {:?}\n Ground control to Major Tom.",

tools/txs/src/txs_cli_community.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ impl ReauthVoteTx {
486486
pub async fn run(&self, sender: &mut Sender) -> anyhow::Result<()> {
487487
// Submit the reauthorization vote
488488
let payload = libra_stdlib::donor_voice_txs_vote_reauth_tx(self.community_wallet);
489-
sender.sign_submit_wait(payload).await.ok();
489+
sender.sign_submit_wait(payload).await?;
490490

491491
// First, check if we have pending reauthorization ballots
492492
let ballot_id =

0 commit comments

Comments
 (0)