diff --git a/fendermint/app/src/cmd/genesis.rs b/fendermint/app/src/cmd/genesis.rs index e53256d785..e23345a210 100644 --- a/fendermint/app/src/cmd/genesis.rs +++ b/fendermint/app/src/cmd/genesis.rs @@ -337,6 +337,7 @@ pub async fn seal_genesis(genesis_file: &PathBuf, args: &SealGenesisArgs) -> any let builder = GenesisBuilder::new( builtin_actors.as_ref(), custom_actors.as_ref(), + None, artifacts_path, genesis_params, ); diff --git a/fendermint/testing/contract-test/src/lib.rs b/fendermint/testing/contract-test/src/lib.rs index 9b5429aafc..6e6692786d 100644 --- a/fendermint/testing/contract-test/src/lib.rs +++ b/fendermint/testing/contract-test/src/lib.rs @@ -30,9 +30,12 @@ pub async fn create_test_exec_state( )> { let artifacts_path = contracts_path(); + let user_actor_car: &[u8] = include_bytes!("../user_actors_bundle.car"); + let (state, out) = create_test_genesis_state( actors_builtin_car::CAR, actors_custom_car::CAR, + Some(user_actor_car), artifacts_path, genesis, ) diff --git a/fendermint/testing/contract-test/user_actors_bundle.car b/fendermint/testing/contract-test/user_actors_bundle.car new file mode 100644 index 0000000000..5a26857670 Binary files /dev/null and b/fendermint/testing/contract-test/user_actors_bundle.car differ diff --git a/fendermint/vm/interpreter/src/fvm/state/genesis.rs b/fendermint/vm/interpreter/src/fvm/state/genesis.rs index 5adad8b116..dc03b167f6 100644 --- a/fendermint/vm/interpreter/src/fvm/state/genesis.rs +++ b/fendermint/vm/interpreter/src/fvm/state/genesis.rs @@ -1,6 +1,7 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT +use std::collections::HashMap; use std::sync::Arc; use actors_custom_car::Manifest as CustomActorManifest; @@ -64,7 +65,10 @@ where { pub manifest_data_cid: Cid, pub manifest: Manifest, + /// IPC specific actor manifest pub custom_actor_manifest: CustomActorManifest, + /// other dynamically loaded actor manifest + user_actor_manifest: Option, store: DB, multi_engine: Arc, stage: Stage, @@ -105,6 +109,7 @@ where multi_engine: Arc, bundle: &[u8], custom_actor_bundle: &[u8], + user_actor_bundle: Option<&[u8]>, ) -> anyhow::Result { // Load the builtin actor bundle. let (manifest_version, manifest_data_cid): (u32, Cid) = @@ -117,12 +122,21 @@ where let custom_actor_manifest = CustomActorManifest::load(&store, &custom_manifest_data_cid, custom_manifest_version)?; - let state_tree = empty_state_tree(store.clone())?; + let user_actor_manifest = match user_actor_bundle { + None => None, + Some(bundle) => { + let (version, data_cid): (u32, Cid) = parse_bundle(&store, bundle).await?; + let manifest = UserActorManifest::load(&store, &data_cid, version)?; + Some(manifest) + } + }; + let state_tree = empty_state_tree(store.clone())?; let state = Self { manifest_data_cid, manifest, custom_actor_manifest, + user_actor_manifest, store, multi_engine, stage: Stage::Tree(Box::new(state_tree)), @@ -284,6 +298,20 @@ where self.create_actor_internal(code_cid, id, state, balance, delegated_address) } + pub fn load_user_actor(&mut self, mut next_actor_id: ActorID) -> anyhow::Result<()> { + let Some(name_to_actor) = self.user_actor_manifest.take() else { + return Ok(()); + }; + + for (_, info) in name_to_actor.actor_to_data.into_iter() { + self.create_actor_with_state_cid(next_actor_id, info.code, info.init_state)?; + + next_actor_id += 1; + } + + Ok(()) + } + pub fn construct_custom_actor( &mut self, name: &str, @@ -337,6 +365,37 @@ where Ok(()) } + fn create_actor_with_state_cid( + &mut self, + id: ActorID, + code_cid: Cid, + state_cid: Cid, + ) -> anyhow::Result<()> { + let actor_state = ActorState { + code: code_cid, + state: state_cid, + sequence: 0, + balance: TokenAmount::zero(), + delegated_address: None, + }; + + self.with_state_tree( + |s| s.set_actor(id, actor_state.clone()), + |s| s.set_actor(id, actor_state.clone()), + ); + + { + let cid = self.with_state_tree(|s| s.flush(), |s| s.flush())?; + tracing::debug!( + state_root = cid.to_string(), + actor_id = id, + "interim state root after actor creation" + ); + } + + Ok(()) + } + pub fn create_account_actor( &mut self, acct: Account, @@ -572,3 +631,61 @@ where .ok_or_else(|| anyhow!("actor state by {actor_state_cid} not found")) } } + +struct UserActorStatePair { + init_state: Cid, + code: Cid, +} + +struct UserActorManifest { + actor_to_data: HashMap, +} + +impl UserActorManifest { + pub fn load(bs: &B, root_cid: &Cid, ver: u32) -> anyhow::Result { + if ver != 1 { + return Err(anyhow!("unsupported manifest version {}", ver)); + } + + let vec: Vec<(String, Cid)> = match bs.get_cbor(root_cid)? { + Some(vec) => vec, + None => { + return Err(anyhow!( + "cannot find user actor manifest root cid {}", + root_cid + )); + } + }; + + UserActorManifest::new(vec) + } + + /// Construct a new manifest from actor name/cid tuples. + fn new(data: Vec<(String, Cid)>) -> anyhow::Result { + if data.len() % 2 != 0 { + return Err(anyhow!("total number of cids should be multiples of 2")); + } + + let mut actor_to_data = HashMap::new(); + + for chunk in data.chunks(2) { + match chunk { + [(actor_name, code_cid), (state_name, state_cid)] => { + let expected_state_name = format!("{actor_name}-state"); + if *state_name != expected_state_name { + return Err(anyhow!("state and actor name not matching, invalid bundle")); + } + + let entry = UserActorStatePair { + init_state: *state_cid, + code: *code_cid, + }; + actor_to_data.insert(actor_name.clone(), entry); + } + _ => unreachable!(), + } + } + + Ok(Self { actor_to_data }) + } +} diff --git a/fendermint/vm/interpreter/src/genesis.rs b/fendermint/vm/interpreter/src/genesis.rs index 444a788348..5e7a1b196c 100644 --- a/fendermint/vm/interpreter/src/genesis.rs +++ b/fendermint/vm/interpreter/src/genesis.rs @@ -161,7 +161,8 @@ pub struct GenesisBuilder<'a> { builtin_actors: &'a [u8], /// The custom actors bundle custom_actors: &'a [u8], - + /// The end user actors bundle + user_actors: Option<&'a [u8]>, /// Genesis params genesis_params: Genesis, } @@ -170,6 +171,7 @@ impl<'a> GenesisBuilder<'a> { pub fn new( builtin_actors: &'a [u8], custom_actors: &'a [u8], + user_actors: Option<&'a [u8]>, artifacts_path: PathBuf, genesis_params: Genesis, ) -> Self { @@ -177,6 +179,7 @@ impl<'a> GenesisBuilder<'a> { hardhat: Hardhat::new(artifacts_path), builtin_actors, custom_actors, + user_actors, genesis_params, } } @@ -244,6 +247,7 @@ impl<'a> GenesisBuilder<'a> { Arc::new(MultiEngine::new(1)), self.builtin_actors, self.custom_actors, + self.user_actors, ) .await .context("failed to create genesis state") @@ -496,6 +500,8 @@ impl<'a> GenesisBuilder<'a> { config, )?; + state.load_user_actor(next_id)?; + Ok(out) } } @@ -736,12 +742,14 @@ fn circ_supply(g: &Genesis) -> TokenAmount { pub async fn create_test_genesis_state( builtin_actors_bundle: &[u8], custom_actors_bundle: &[u8], + user_actors_bundle: Option<&[u8]>, ipc_path: PathBuf, genesis_params: Genesis, ) -> anyhow::Result<(FvmGenesisState, GenesisOutput)> { let builder = GenesisBuilder::new( builtin_actors_bundle, custom_actors_bundle, + user_actors_bundle, ipc_path, genesis_params, ); diff --git a/fendermint/vm/snapshot/src/manager.rs b/fendermint/vm/snapshot/src/manager.rs index ee1ec20386..bd6f87e2b0 100644 --- a/fendermint/vm/snapshot/src/manager.rs +++ b/fendermint/vm/snapshot/src/manager.rs @@ -448,6 +448,7 @@ mod tests { let (state, out) = create_test_genesis_state( actors_builtin_car::CAR, actors_custom_car::CAR, + None, contracts_path(), genesis, )