Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions fendermint/testing/contract-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
Binary file not shown.
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 @@ -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<UserActorManifest>,
store: DB,
multi_engine: Arc<MultiEngine>,
stage: Stage<DB>,
Expand Down Expand Up @@ -105,6 +109,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 @@ -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)),
Expand Down Expand Up @@ -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(())
}
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 @@ -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,
Expand Down Expand Up @@ -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<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 })
}
}
10 changes: 9 additions & 1 deletion fendermint/vm/interpreter/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -170,13 +171,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 @@ -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")
Expand Down Expand Up @@ -496,6 +500,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 @@ -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<MemoryBlockstore>, GenesisOutput)> {
let builder = GenesisBuilder::new(
builtin_actors_bundle,
custom_actors_bundle,
user_actors_bundle,
ipc_path,
genesis_params,
);
Expand Down
1 change: 1 addition & 0 deletions fendermint/vm/snapshot/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
Loading