use crate::lotus_json::HasLotusJson;
use crate::networks::ACTOR_BUNDLES_METADATA;
use crate::shim::actors::{
AccountActorStateLoad, CronActorStateLoad, DataCapActorStateLoad, EVMActorStateLoad,
InitActorStateLoad, MarketActorStateLoad, MinerActorStateLoad, MultisigActorStateLoad,
PaymentchannelActorStateLoad, PowerActorStateLoad, RewardActorStateLoad, SystemActorStateLoad,
VerifregActorStateLoad, account, cron, datacap, evm, init, market, miner, multisig,
paymentchannel, power, reward, system, verifreg,
};
use crate::shim::machine::BuiltinActor;
use ahash::{HashMap, HashMapExt};
use anyhow::{Context, Result, anyhow};
use cid::Cid;
use fil_actors_shared::actor_versions::ActorVersion;
use fvm_ipld_blockstore::Blockstore;
use serde_json::Value;
use std::sync::LazyLock;
#[derive(Debug)]
pub struct ActorRegistry {
map: HashMap<Cid, (BuiltinActor, ActorVersion)>,
}
impl ActorRegistry {
fn new() -> Self {
let mut map = HashMap::new();
for ((_, _), metadata) in ACTOR_BUNDLES_METADATA.iter() {
if let Ok(version_u64) = metadata.actor_major_version()
&& let Some(version) = ActorVersion::from_repr(version_u64 as u8)
{
for (actor_type, cid) in metadata.manifest.builtin_actors() {
map.insert(cid, (actor_type, version));
}
}
}
Self { map }
}
pub fn get_actor_details_from_code(code_cid: &Cid) -> Result<(BuiltinActor, ActorVersion)> {
ACTOR_REGISTRY
.map
.get(code_cid)
.copied()
.ok_or_else(|| anyhow!("Unknown actor code CID: {}", code_cid))
}
pub fn iter(&self) -> impl Iterator<Item = (&Cid, &(BuiltinActor, ActorVersion))> {
self.map.iter()
}
}
pub(crate) static ACTOR_REGISTRY: LazyLock<ActorRegistry> = LazyLock::new(ActorRegistry::new);
macro_rules! load_and_serialize_state {
($store:expr, $code_cid:expr, $state_cid:expr, $actor_type:expr, $state_type:ty) => {{
let state = <$state_type>::load($store, *$code_cid, *$state_cid).context(format!(
"Failed to load {:?} actor state",
$actor_type.name()
))?;
serde_json::to_value(state.into_lotus_json()).context(format!(
"Failed to serialize {:?} state to JSON",
$actor_type.name()
))
}};
}
pub fn load_and_serialize_actor_state<BS>(
store: &BS,
code_cid: &Cid,
state_cid: &Cid,
) -> Result<Value>
where
BS: Blockstore,
{
let (actor_type, _) = ActorRegistry::get_actor_details_from_code(code_cid)?;
match actor_type {
BuiltinActor::Account => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, account::State)
}
BuiltinActor::Cron => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, cron::State)
}
BuiltinActor::Miner => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, miner::State)
}
BuiltinActor::Market => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, market::State)
}
BuiltinActor::EVM => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, evm::State)
}
BuiltinActor::System => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, system::State)
}
BuiltinActor::Init => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, init::State)
}
BuiltinActor::Power => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, power::State)
}
BuiltinActor::Multisig => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, multisig::State)
}
BuiltinActor::Reward => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, reward::State)
}
BuiltinActor::VerifiedRegistry => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, verifreg::State)
}
BuiltinActor::PaymentChannel => {
load_and_serialize_state!(
store,
code_cid,
state_cid,
actor_type,
paymentchannel::State
)
}
BuiltinActor::DataCap => {
load_and_serialize_state!(store, code_cid, state_cid, actor_type, datacap::State)
}
BuiltinActor::EAM | BuiltinActor::EthAccount | BuiltinActor::Placeholder => Ok(Value::Null),
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::db::MemoryDB;
use crate::utils::multihash::MultihashCode;
use fvm_ipld_encoding::{DAG_CBOR, to_vec};
use multihash_derive::MultihashDigest;
use std::sync::Arc;
fn get_real_actor_cid(target_actor: BuiltinActor) -> Option<Cid> {
ACTOR_BUNDLES_METADATA
.values()
.flat_map(|metadata| metadata.manifest.builtin_actors())
.find(|(actor_type, _)| *actor_type == target_actor)
.map(|(_, cid)| cid)
}
#[test]
fn test_get_actor_details_from_code_success() {
let account_cid = get_real_actor_cid(BuiltinActor::Account)
.expect("Should have Account actor in metadata");
let result = ActorRegistry::get_actor_details_from_code(&account_cid);
assert!(result.is_ok());
let (builtin_actor_type, _) = result.unwrap();
assert_eq!(builtin_actor_type, BuiltinActor::Account);
}
#[test]
fn test_get_actor_details_from_code_multiple_actors() {
let test_cases = vec![
(BuiltinActor::Account, "Account"),
(BuiltinActor::System, "System"),
(BuiltinActor::Cron, "Cron"),
(BuiltinActor::Miner, "Miner"),
];
for (expected_actor, actor_name) in test_cases {
if let Some(cid) = get_real_actor_cid(expected_actor) {
let result = ActorRegistry::get_actor_details_from_code(&cid);
assert!(
result.is_ok(),
"Failed to get details for {actor_name} actor"
);
let (builtin_actor_type, _) = result.unwrap();
assert_eq!(
builtin_actor_type, expected_actor,
"Wrong actor type returned for {actor_name} actor"
);
}
}
}
#[test]
fn test_details_get_actor_details_from_code_unknown_cid() {
let unknown_cid = Cid::new_v1(
DAG_CBOR,
MultihashCode::Blake2b256.digest(b"unknown_actor_code"),
);
let result = ActorRegistry::get_actor_details_from_code(&unknown_cid);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Unknown actor code CID"));
assert!(error_msg.contains(&unknown_cid.to_string()));
}
#[test]
fn test_basic_load_and_serialize_actor_state_all_supported_actors() {
let db = Arc::new(MemoryDB::default());
let supported_actors = vec![
(BuiltinActor::Account, "Account"),
(BuiltinActor::Cron, "Cron"),
(BuiltinActor::Miner, "Miner"),
(BuiltinActor::Market, "Market"),
(BuiltinActor::EVM, "EVM"),
(BuiltinActor::System, "System"),
];
for (actor_type, actor_name) in supported_actors {
if let Some(code_cid) = get_real_actor_cid(actor_type) {
let state_data = match actor_type {
BuiltinActor::Account => {
let state = account::State::V16(fil_actor_account_state::v16::State {
address: crate::shim::address::Address::new_id(1).into(),
});
to_vec(&state).unwrap()
}
BuiltinActor::System => {
let state = system::State::V16(fil_actor_system_state::v16::State {
builtin_actors: Cid::new_v1(
DAG_CBOR,
MultihashCode::Blake2b256.digest(b"test"),
),
});
to_vec(&state).unwrap()
}
BuiltinActor::Cron => {
let state = cron::State::V16(fil_actor_cron_state::v16::State {
entries: Vec::new(),
});
to_vec(&state).unwrap()
}
_ => format!("minimal_{}_state", actor_name.to_lowercase()).into_bytes(),
};
let state_cid =
Cid::new_v1(DAG_CBOR, MultihashCode::Blake2b256.digest(&state_data));
db.put_keyed(&state_cid, &state_data).unwrap();
let result = load_and_serialize_actor_state(db.as_ref(), &code_cid, &state_cid);
if let Err(e) = result {
let error_msg = e.to_string();
assert!(
!error_msg.contains("Unknown actor code CID")
&& !error_msg.contains("No serializer implemented"),
"Unexpected error for {actor_name} actor: {error_msg}"
);
}
}
}
}
}