Skip to content

Commit

Permalink
Full object history dump, better output (MystenLabs#3272)
Browse files Browse the repository at this point in the history
* Full object history dump, better output

* cargo hakari generate

* lint

* Use futures::future::join_all
  • Loading branch information
mystenmark authored Jul 21, 2022
1 parent 0b28e93 commit 54258b5
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 57 deletions.
24 changes: 24 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/sui-tool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ tracing = "0.1.35"
clap = { version = "3.1.17", features = ["derive"] }
telemetry-subscribers = { git = "https://github.com/MystenLabs/mysten-infra", rev = "123c9e40b529315e1c1d91a54fb717111c3e349c" }
mysten-network = { git = "https://github.com/MystenLabs/mysten-infra", rev = "123c9e40b529315e1c1d91a54fb717111c3e349c" }
textwrap = "0.15"
futures = "0.3.21"

sui-core = { path = "../sui-core" }
sui-config = { path = "../sui-config" }
Expand Down
262 changes: 208 additions & 54 deletions crates/sui-tool/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use futures::future::join_all;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::Duration;
Expand Down Expand Up @@ -38,6 +39,26 @@ pub enum ToolCommand {

#[clap(long = "genesis")]
genesis: PathBuf,

#[clap(long = "history", help = "show full history of object")]
history: bool,

/// Concise mode prints tabular output suitable for processing with unix tools. For
/// instance, to quickly check that all validators agree on the history of an object:
///
/// $ sui-tool fetch-object --id 0x260efde76ebccf57f4c5e951157f5c361cde822c \
/// --genesis $HOME/.sui/sui_config/genesis.blob \
/// --history --concise --no-header \
/// | sort -k2 -k1 \
/// | uniq -f1 -c
///
/// (Prints full history in concise mode, suppresses header, sorts by version and then
/// validator name, uniqs and counts lines ignoring validator name.)
#[clap(long = "concise", help = "show concise output")]
concise: bool,

#[clap(long = "no-header", help = "don't show header in concise output")]
no_header: bool,
},
}

Expand All @@ -61,48 +82,178 @@ fn make_clients(genesis: PathBuf) -> Result<BTreeMap<AuthorityName, NetworkAutho
Ok(authority_clients)
}

struct ObjectOutput {
type ObjectVersionResponses = Vec<(Option<SequenceNumber>, Result<ObjectInfoResponse>)>;
struct ObjectData {
requested_id: ObjectID,
responses: Vec<(AuthorityName, ObjectInfoResponse)>,
responses: Vec<(AuthorityName, ObjectVersionResponses)>,
}

trait OptionDebug<T> {
fn opt_debug(&self, def_str: &str) -> String;
}
trait OptionDisplay<T> {
fn opt_display(&self, def_str: &str) -> String;
}

impl<T> OptionDebug<T> for Option<T>
where
T: std::fmt::Debug,
{
fn opt_debug(&self, def_str: &str) -> String {
match self {
None => def_str.to_string(),
Some(t) => format!("{:?}", t),
}
}
}

impl<T> OptionDisplay<T> for Option<T>
where
T: std::fmt::Display,
{
fn opt_display(&self, def_str: &str) -> String {
match self {
None => def_str.to_string(),
Some(t) => format!("{}", t),
}
}
}

struct ConciseObjectOutput(ObjectData);

impl ConciseObjectOutput {
fn header() -> String {
format!(
"{:<66} {:<8} {:<66} {:<45}",
"validator", "version", "digest", "parent_cert"
)
}
}

impl std::fmt::Display for ConciseObjectOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (name, versions) in &self.0.responses {
for (version, resp) in versions {
write!(
f,
"{:<66} {:<8}",
format!("{:?}", name),
version.map(|s| s.value()).opt_debug("-")
)?;
match resp {
Err(_) => writeln!(
f,
"{:<66} {:<45}",
"object-fetch-failed", "no-cert-available"
)?,
Ok(resp) => {
let objref = resp
.requested_object_reference
.map(|(_, _, digest)| digest)
.opt_debug("objref-not-available");
let cert = resp
.parent_certificate
.as_ref()
.map(|c| *c.digest())
.opt_debug("<genesis>");
write!(f, " {:<66} {:<45}", objref, cert)?;
}
}
writeln!(f)?;
}
}
Ok(())
}
}

impl std::fmt::Display for ObjectOutput {
struct VerboseObjectOutput(ObjectData);

impl std::fmt::Display for VerboseObjectOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Object: {}", self.requested_id)?;

for (name, resp) in &self.responses {
writeln!(f, "-- validator: {:?}", name)?;

let objref = resp
.requested_object_reference
.as_ref()
.map(|o| format!("{:?}", o))
.unwrap_or_else(|| "<no object>".into());
writeln!(f, "---- ref: {}", objref)?;

let cert = resp
.parent_certificate
.as_ref()
.map(|c| format!("{:?}", c.digest()))
.unwrap_or_else(|| "<genesis>".into());
writeln!(f, "---- cert: {}", cert)?;

if let Some(ObjectResponse { lock, .. }) = &resp.object_and_lock {
// TODO maybe show object contents if we can do so meaningfully.
writeln!(f, "---- object: <data>")?;
writeln!(f, "Object: {}", self.0.requested_id)?;

for (name, versions) in &self.0.responses {
writeln!(f, "validator: {:?}", name)?;

for (version, resp) in versions {
writeln!(
f,
"---- locked by : {}",
lock.as_ref()
.map(|l| format!("{:?}", l.digest()))
.unwrap_or_else(|| "<not locked>".into())
"-- version: {}",
version.opt_debug("<version not available>")
)?;

match resp {
Err(e) => writeln!(f, "Error fetching object: {}", e)?,
Ok(resp) => {
let objref = resp.requested_object_reference.opt_debug("<no object>");
writeln!(f, " -- ref: {}", objref)?;

write!(f, " -- cert:")?;
match &resp.parent_certificate {
None => writeln!(f, " <genesis>")?,
Some(cert) => {
let cert = format!("{}", cert);
let cert = textwrap::indent(&cert, " | ");
write!(f, "\n{}", cert)?;
}
}

if let Some(ObjectResponse { lock, .. }) = &resp.object_and_lock {
// TODO maybe show object contents if we can do so meaningfully.
writeln!(f, " -- object: <data>")?;
writeln!(f, " -- locked by: {}", lock.opt_debug("<not locked>"))?;
}
}
}
}
}
Ok(())
}
}

async fn get_object(
client: &NetworkAuthorityClient,
id: ObjectID,
start_version: Option<u64>,
full_history: bool,
) -> Vec<(Option<SequenceNumber>, Result<ObjectInfoResponse>)> {
let mut ret = Vec::new();
let mut version = start_version;

loop {
let resp = client
.handle_object_info_request(ObjectInfoRequest {
object_id: id,
request_kind: match version {
None => ObjectInfoRequestKind::LatestObjectInfo(None),
Some(v) => ObjectInfoRequestKind::PastObjectInfo(SequenceNumber::from_u64(v)),
},
})
.await
.map_err(anyhow::Error::from);

let resp_version = resp
.as_ref()
.ok()
.and_then(|r| r.requested_object_reference)
.map(|(_, v, _)| v.value());
ret.push((resp_version.map(SequenceNumber::from), resp));

version = match (version, resp_version) {
(Some(v), _) | (None, Some(v)) => {
if v == 0 || !full_history {
break;
} else {
Some(v - 1)
}
}
_ => break,
};
}

ret
}

impl ToolCommand {
pub async fn execute(self) -> Result<(), anyhow::Error> {
match self {
Expand All @@ -111,39 +262,42 @@ impl ToolCommand {
validator,
genesis,
version,
history,
concise,
no_header,
} => {
let clients = make_clients(genesis)?;

let clients = clients.iter().filter(|(name, _)| {
if let Some(v) = validator {
v == **name
} else {
true
}
});
let responses = join_all(
clients
.iter()
.filter(|(name, _)| {
if let Some(v) = validator {
v == **name
} else {
true
}
})
.map(|(name, client)| async {
let object_versions = get_object(client, id, version, history).await;
(*name, object_versions)
}),
)
.await;

let mut output = ObjectOutput {
let output = ObjectData {
requested_id: id,
responses: Vec::new(),
responses,
};

for (name, client) in clients {
let resp = client
.handle_object_info_request(ObjectInfoRequest {
object_id: id,
request_kind: match version {
None => ObjectInfoRequestKind::LatestObjectInfo(None),
Some(v) => ObjectInfoRequestKind::PastObjectInfo(
SequenceNumber::from_u64(v),
),
},
})
.await?;

output.responses.push((*name, resp));
if concise {
if !no_header {
println!("{}", ConciseObjectOutput::header());
}
print!("{}", ConciseObjectOutput(output));
} else {
println!("{}", VerboseObjectOutput(output));
}

println!("{}", output);
}
}

Expand Down
Loading

0 comments on commit 54258b5

Please sign in to comment.