Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fendermint/app/src/cmd/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
119 changes: 118 additions & 1 deletion fendermint/vm/interpreter/src/fvm/state/genesis.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -61,7 +62,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<UserActorManifest>,
store: DB,
multi_engine: Arc<MultiEngine>,
stage: Stage<DB>,
Expand Down Expand Up @@ -101,6 +105,7 @@ where
multi_engine: Arc<MultiEngine>,
bundle: &[u8],
custom_actor_bundle: &[u8],
user_actor_bundle: Option<&[u8]>,
) -> anyhow::Result<Self> {
// Load the builtin actor bundle.
let (manifest_version, manifest_data_cid): (u32, Cid) =
Expand All @@ -113,12 +118,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)),
Expand Down Expand Up @@ -280,6 +294,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(())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Non-deterministic Actor ID Assignment Breaks Consensus

The load_user_actor method introduces non-deterministic actor ID assignment during genesis due to HashMap iteration order, which can break consensus. It also incorrectly manages the next_actor_id counter by taking it by value, leading to inconsistent ID allocation and potential conflicts for subsequent actor creations.

Fix in Cursor Fix in Web


pub fn construct_custom_actor(
&mut self,
name: &str,
Expand Down Expand Up @@ -333,6 +361,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,
Expand Down Expand Up @@ -568,3 +627,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<String, UserActorStatePair>,
}

impl UserActorManifest {
pub fn load<B: Blockstore>(bs: &B, root_cid: &Cid, ver: u32) -> anyhow::Result<Self> {
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<Self> {
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 })
}
}
9 changes: 8 additions & 1 deletion fendermint/vm/interpreter/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,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,
}
Expand All @@ -173,13 +174,15 @@ 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 {
Self {
hardhat: Hardhat::new(artifacts_path),
builtin_actors,
custom_actors,
user_actors,
genesis_params,
}
}
Expand Down Expand Up @@ -234,6 +237,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")
Expand Down Expand Up @@ -486,6 +490,8 @@ impl<'a> GenesisBuilder<'a> {
config,
)?;

state.load_user_actor(next_id)?;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Actor ID Propagation Issue

The next_id variable is passed by value to deploy_contracts, so actor IDs consumed there don't propagate back. This causes load_user_actor to use the original next_id, potentially leading to actor ID collisions where user actors overwrite previously deployed contract actors.

Fix in Cursor Fix in Web

Ok(out)
}
}
Expand Down Expand Up @@ -732,6 +738,7 @@ pub async fn create_test_genesis_state(
let builder = GenesisBuilder::new(
builtin_actors_bundle,
custom_actors_bundle,
None,
ipc_path,
genesis_params,
);
Expand Down
Loading