mod resolve;
use std::{
fmt::Write as FmtWrite,
io::{Write, stdout},
sync::Arc,
};
use crate::shim::actors::{
account::State as AccountState, cron::State as CronState, datacap::State as DatacapState,
evm::State as EvmState, init::State as InitState, market::State as MarketState,
miner::State as MinerState, multisig::State as MultiSigState, power::State as PowerState,
reward::State as RewardState, system::State as SystemState,
};
use crate::{
lotus_json::HasLotusJson as _,
shim::{
actors::state_load::*,
address::Address,
state_tree::{ActorState, StateTree},
},
};
use ahash::HashMap;
use cid::Cid;
use colored::*;
use fvm_ipld_blockstore::Blockstore;
use ipld_core::ipld::Ipld;
use itertools::Itertools as _;
use resolve::resolve_cids_recursive;
use serde::{Deserialize, Serialize};
use similar::{ChangeTag, TextDiff};
#[derive(Serialize, Deserialize)]
struct ActorStateResolved {
#[serde(with = "crate::lotus_json")]
code: Cid,
sequence: u64,
balance: String,
#[serde(with = "crate::lotus_json")]
state: Ipld,
}
fn actor_to_resolved(
bs: &impl Blockstore,
actor: &ActorState,
depth: Option<u64>,
) -> ActorStateResolved {
let resolved =
resolve_cids_recursive(bs, &actor.state, depth).unwrap_or(Ipld::Link(actor.state));
ActorStateResolved {
state: resolved,
code: actor.code,
balance: actor.balance.to_string(),
sequence: actor.sequence,
}
}
fn root_to_state_map<BS: Blockstore>(
bs: &Arc<BS>,
root: &Cid,
) -> anyhow::Result<HashMap<Address, ActorState>> {
let mut actors = HashMap::default();
let state_tree = StateTree::new_from_root(bs.clone(), root)?;
state_tree.for_each(|addr: Address, actor: &ActorState| {
actors.insert(addr, actor.clone());
Ok(())
})?;
Ok(actors)
}
fn try_print_actor_states<BS: Blockstore>(
bs: &Arc<BS>,
root: &Cid,
expected_root: &Cid,
depth: Option<u64>,
) -> anyhow::Result<()> {
let mut e_state = root_to_state_map(bs, expected_root)?;
let state_tree = StateTree::new_from_root(bs.clone(), root)?;
state_tree.for_each(|addr: Address, actor| {
if let Some(other) = e_state.remove(&addr) {
if &other != actor {
const COMMA: &str = ",";
let calc_pp = pp_actor_state(bs, actor, depth)?;
let expected_pp = pp_actor_state(bs, &other, depth)?;
let expected = expected_pp
.split(COMMA)
.map(|s| s.trim_start_matches('\n'))
.collect_vec();
let calculated = calc_pp
.split(COMMA)
.map(|s| s.trim_start_matches('\n'))
.collect_vec();
let diffs = TextDiff::from_slices(&expected, &calculated);
let stdout = stdout();
let mut handle = stdout.lock();
writeln!(handle, "Address {addr} changed: ")?;
print_diffs(&mut handle, diffs)?;
}
} else {
let calc_pp = pp_actor_state(bs, actor, depth)?;
println!("{}", format!("+ Address {addr}:\n{calc_pp}").green());
}
Ok(())
})?;
for (addr, state) in e_state.into_iter() {
let expected_json = serde_json::to_string_pretty(&actor_to_resolved(bs, &state, depth))?;
println!("{}", format!("- Address {addr}:\n{expected_json}").red())
}
Ok(())
}
fn pp_actor_state(
bs: &impl Blockstore,
actor_state: &ActorState,
depth: Option<u64>,
) -> anyhow::Result<String> {
let mut buffer = String::new();
writeln!(&mut buffer, "{actor_state:?}")?;
if let Ok(miner_state) = MinerState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{miner_state:?}")?;
return Ok(buffer);
}
if let Ok(cron_state) = CronState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{cron_state:?}")?;
return Ok(buffer);
}
if let Ok(account_state) = AccountState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{account_state:?}")?;
return Ok(buffer);
}
if let Ok(power_state) = PowerState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{power_state:?}")?;
return Ok(buffer);
}
if let Ok(init_state) = InitState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{init_state:?}")?;
return Ok(buffer);
}
if let Ok(reward_state) = RewardState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{reward_state:?}")?;
return Ok(buffer);
}
if let Ok(system_state) = SystemState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{system_state:?}")?;
return Ok(buffer);
}
if let Ok(multi_sig_state) = MultiSigState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{multi_sig_state:?}")?;
return Ok(buffer);
}
if let Ok(market_state) = MarketState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{market_state:?}")?;
return Ok(buffer);
}
if let Ok(datacap_state) = DatacapState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{datacap_state:?}")?;
return Ok(buffer);
}
if let Ok(evm_state) = EvmState::load(bs, actor_state.code, actor_state.state) {
write!(&mut buffer, "{evm_state:?}")?;
return Ok(buffer);
}
let resolved = actor_to_resolved(bs, actor_state, depth);
buffer = serde_json::to_string_pretty(&resolved)?;
Ok(buffer)
}
fn print_diffs(handle: &mut impl Write, diffs: TextDiff<str>) -> std::io::Result<()> {
for op in diffs.ops() {
for change in diffs.iter_changes(op) {
match change.tag() {
ChangeTag::Delete => writeln!(handle, "{}", format!("-{}", change.value()).red())?,
ChangeTag::Insert => {
writeln!(handle, "{}", format!("+{}", change.value()).green())?
}
ChangeTag::Equal => writeln!(handle, " {}", change.value())?,
};
}
}
Ok(())
}
pub fn print_state_diff<BS>(
bs: &Arc<BS>,
root: &Cid,
expected_root: &Cid,
depth: Option<u64>,
) -> anyhow::Result<()>
where
BS: Blockstore,
{
if let Err(e) = try_print_actor_states(bs, root, expected_root, depth) {
println!("Could not resolve actor states: {e:#}\nUsing default resolution:");
let expected = resolve_cids_recursive(bs, expected_root, depth)?;
let actual = resolve_cids_recursive(bs, root, depth)?;
let expected_json = expected.into_lotus_json_string_pretty()?;
let actual_json = actual.into_lotus_json_string_pretty()?;
let diffs = TextDiff::from_lines(&expected_json, &actual_json);
let stdout = stdout();
let mut handle = stdout.lock();
print_diffs(&mut handle, diffs)?
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::db::MemoryDB;
use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
use crate::utils::db::CborStoreExt;
use cid::Cid;
use fil_actor_account_state::v10::State as AccountState;
use fvm_ipld_blockstore::Blockstore;
use super::pp_actor_state;
fn mk_account_v10(db: &impl Blockstore, account: &AccountState) -> ActorState {
let account_cid =
Cid::try_from("bafk2bzaceampw4romta75hyz5p4cqriypmpbgnkxncgxgqn6zptv5lsp2w2bo")
.unwrap();
let actor_state_cid = db.put_cbor_default(&account).unwrap();
ActorState::new(
account_cid,
actor_state_cid,
TokenAmount::from_atto(0),
0,
None,
)
}
#[test]
fn correctly_pretty_print_account_actor_state() {
let db = MemoryDB::default();
let account_state = AccountState {
address: Address::new_id(0xdeadbeef).into(),
};
let state = mk_account_v10(&db, &account_state);
let pretty = pp_actor_state(&db, &state, None).unwrap();
assert_eq!(
pretty,
"ActorState(\
ActorState { \
code: Cid(bafk2bzaceampw4romta75hyz5p4cqriypmpbgnkxncgxgqn6zptv5lsp2w2bo), \
state: Cid(bafy2bzaceaiws3hdhmfyxyfjzmbaxv5aw6eywwbipeae4n5jjg5smmfxsaeic), \
sequence: 0, balance: TokenAmount(0.0), delegated_address: None })\n\
V10(State { address: Address { payload: ID(3735928559) } })"
);
}
#[test]
fn check_json_fallback_if_unknown_actor() {
let db = MemoryDB::default();
let account_state = AccountState {
address: Address::new_id(0xdeadbeef).into(),
};
let mut state = mk_account_v10(&db, &account_state);
state.code = Cid::default();
let pretty = pp_actor_state(&db, &state, None).unwrap();
assert_eq!(
pretty,
"{
\"code\": {
\"/\": \"baeaaaaa\"
},
\"sequence\": 0,
\"balance\": \"0.0\",
\"state\": [
{
\"/\": {
\"bytes\": \"mAO/9tvUN\"
}
}
]
}"
);
}
}