diff --git a/Cargo.lock b/Cargo.lock index 10ab6cd5d..deb8bb31a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "ambassador" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b8741165d4c4a8e6e8dcf8a2d09a1b0f94d85722fb57caed8babdd421a9837" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "anes" version = "0.1.6" @@ -2278,6 +2290,7 @@ dependencies = [ name = "fvm" version = "4.0.0" dependencies = [ + "ambassador", "anyhow", "arbitrary", "blake2b_simd", diff --git a/fvm/Cargo.toml b/fvm/Cargo.toml index adcba3b50..e3365e4eb 100644 --- a/fvm/Cargo.toml +++ b/fvm/Cargo.toml @@ -41,6 +41,7 @@ minstant = "0.1.2" blake2b_simd = "1.0.0" byteorder = "1.4.3" static_assertions = "1.1.0" +ambassador = "0.3.5" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/fvm/src/engine/mod.rs b/fvm/src/engine/mod.rs index 9096cb8b8..60d6e7ff0 100644 --- a/fvm/src/engine/mod.rs +++ b/fvm/src/engine/mod.rs @@ -27,8 +27,7 @@ use crate::machine::limiter::MemoryLimiter; use crate::machine::{Machine, NetworkConfig}; use crate::syscalls::error::Abort; use crate::syscalls::{ - bind_syscalls, charge_for_exec, charge_for_init, record_init_time, update_gas_available, - InvocationData, + charge_for_exec, charge_for_init, record_init_time, update_gas_available, InvocationData, }; use crate::Kernel; @@ -514,10 +513,15 @@ impl Engine { .expect("invalid instance cache entry"), Vacant(e) => &mut *e .insert({ - let mut linker: Linker> = Linker::new(&self.inner.engine); + let mut linker = Linker::new(&self.inner.engine); linker.allow_shadowing(true); - bind_syscalls(&mut linker).map_err(Abort::Fatal)?; + store + .data() + .kernel + .bind_syscalls(&mut linker) + .map_err(Abort::Fatal)?; + Box::new(Cache { linker }) }) .downcast_mut() diff --git a/fvm/src/gas/price_list.rs b/fvm/src/gas/price_list.rs index 8083e38f2..47a375c05 100644 --- a/fvm/src/gas/price_list.rs +++ b/fvm/src/gas/price_list.rs @@ -901,7 +901,6 @@ impl PriceList { } /// Returns the gas required for installing an actor. - #[cfg(feature = "m2-native")] pub fn on_install_actor(&self, wasm_size: usize) -> GasCharge { GasCharge::new( "OnInstallActor", diff --git a/fvm/src/kernel/default.rs b/fvm/src/kernel/default.rs index 6e20873a0..7ba1f5d99 100644 --- a/fvm/src/kernel/default.rs +++ b/fvm/src/kernel/default.rs @@ -1,30 +1,22 @@ // Copyright 2021-2023 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use std::panic::{self, UnwindSafe}; use std::path::PathBuf; use anyhow::{anyhow, Context as _}; use cid::Cid; -use filecoin_proofs_api::{self as proofs, ProverId, PublicReplicaInfo, SectorId}; use fvm_ipld_blockstore::Blockstore; -use fvm_ipld_encoding::{bytes_32, IPLD_RAW}; +use fvm_ipld_encoding::IPLD_RAW; use fvm_shared::address::Payload; -use fvm_shared::consensus::ConsensusFault; use fvm_shared::crypto::signature; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ErrorNumber; use fvm_shared::event::{ActorEvent, Entry, Flags}; -use fvm_shared::piece::{zero_piece_commitment, PaddedPieceSize}; -use fvm_shared::sector::{RegisteredPoStProof, SectorInfo}; use fvm_shared::sys::out::vm::ContextFlags; use fvm_shared::upgrade::UpgradeInfo; -use fvm_shared::{commcid, ActorID}; -use lazy_static::lazy_static; +use fvm_shared::ActorID; use multihash::MultihashDigest; -use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use rayon::prelude::ParallelDrainRange; use super::blocks::{Block, BlockRegistry}; use super::error::Result; @@ -34,18 +26,13 @@ use crate::call_manager::{ CallManager, Entrypoint, InvocationResult, INVOKE_FUNC_NAME, NO_DATA_BLOCK_ID, UPGRADE_FUNC_NAME, }; -use crate::externs::{Chain, Consensus, Rand}; +use crate::externs::{Chain, Rand}; use crate::gas::GasTimer; use crate::init_actor::INIT_ACTOR_ID; use crate::machine::{MachineContext, NetworkConfig, BURNT_FUNDS_ACTOR_ID}; use crate::state_tree::ActorState; use crate::{ipld, syscall_error}; -lazy_static! { - static ref NUM_CPUS: usize = num_cpus::get(); - static ref INITIAL_RESERVE_BALANCE: TokenAmount = TokenAmount::from_whole(300_000_000); -} - const BLAKE2B_256: u64 = 0xb220; const ENV_ARTIFACT_DIR: &str = "FVM_STORE_ARTIFACT_DIR"; const MAX_ARTIFACT_NAME_LEN: usize = 256; @@ -57,20 +44,20 @@ const TEST_ACTOR_ALLOWED_TO_CALL_CREATE_ACTOR: ActorID = 98; pub struct DefaultKernel { // Fields extracted from the message, except parameters, which have been // preloaded into the block registry. - caller: ActorID, - actor_id: ActorID, - method: MethodNum, - value_received: TokenAmount, - read_only: bool, + pub caller: ActorID, + pub actor_id: ActorID, + pub method: MethodNum, + pub value_received: TokenAmount, + pub read_only: bool, /// The call manager for this call stack. If this kernel calls another actor, it will /// temporarily "give" the call manager to the other kernel before re-attaching it. - call_manager: C, + pub call_manager: C, /// Tracks block data and organizes it through index handles so it can be /// referred to. /// /// This does not yet reason about reachability. - blocks: BlockRegistry, + pub blocks: BlockRegistry, } // Even though all children traits are implemented, Rust needs to know that the @@ -185,6 +172,100 @@ where }, }) } + + fn upgrade_actor>( + &mut self, + new_code_cid: Cid, + params_id: BlockId, + ) -> Result { + if self.read_only { + return Err( + syscall_error!(ReadOnly, "upgrade_actor cannot be called while read-only").into(), + ); + } + + // check if this actor is already on the call stack + // + // We first find the first position of this actor on the call stack, and then make sure that + // no other actor appears on the call stack after 'position' (unless its a recursive upgrade + // call which is allowed) + let mut iter = self.call_manager.get_call_stack().iter(); + let position = iter.position(|&tuple| tuple == (self.actor_id, INVOKE_FUNC_NAME)); + if position.is_some() { + for tuple in iter { + if tuple.0 != self.actor_id || tuple.1 != UPGRADE_FUNC_NAME { + return Err(syscall_error!( + Forbidden, + "calling upgrade on actor already on call stack is forbidden" + ) + .into()); + } + } + } + + // Load parameters. + let params = if params_id == NO_DATA_BLOCK_ID { + None + } else { + Some(self.blocks.get(params_id)?.clone()) + }; + + // Make sure we can actually store the return block. + if self.blocks.is_full() { + return Err(syscall_error!(LimitExceeded; "cannot store return block").into()); + } + + let result = self.call_manager.with_transaction(|cm| { + let state = cm + .get_actor(self.actor_id)? + .ok_or_else(|| syscall_error!(IllegalOperation; "actor deleted"))?; + + // store the code cid of the calling actor before running the upgrade entrypoint + // in case it was changed (which could happen if the target upgrade entrypoint + // sent a message to this actor which in turn called upgrade) + let code = state.code; + + // update the code cid of the actor to new_code_cid + cm.set_actor( + self.actor_id, + ActorState::new( + new_code_cid, + state.state, + state.balance, + state.sequence, + None, + ), + )?; + + // run the upgrade entrypoint + let result = cm.call_actor::( + self.caller, + Address::new_id(self.actor_id), + Entrypoint::Upgrade(UpgradeInfo { old_code_cid: code }), + params, + &TokenAmount::from_whole(0), + None, + false, + )?; + + Ok(result) + }); + + match result { + Ok(InvocationResult { exit_code, value }) => { + let (block_stat, block_id) = match value { + None => (BlockStat { codec: 0, size: 0 }, NO_DATA_BLOCK_ID), + Some(block) => (block.stat(), self.blocks.put_reachable(block)?), + }; + Ok(CallResult { + block_id, + block_stat, + exit_code, + }) + } + Err(err) => Err(err), + } + } } impl DefaultKernel @@ -554,132 +635,6 @@ where t.record(Ok(hasher.digest(data))) } - - fn compute_unsealed_sector_cid( - &self, - proof_type: RegisteredSealProof, - pieces: &[PieceInfo], - ) -> Result { - let t = self.call_manager.charge_gas( - self.call_manager - .price_list() - .on_compute_unsealed_sector_cid(proof_type, pieces), - )?; - - t.record(catch_and_log_panic("computing unsealed sector CID", || { - compute_unsealed_sector_cid(proof_type, pieces) - })) - } - - fn verify_post(&self, verify_info: &WindowPoStVerifyInfo) -> Result { - let t = self - .call_manager - .charge_gas(self.call_manager.price_list().on_verify_post(verify_info))?; - - // This is especially important to catch as, otherwise, a bad "post" could be undisputable. - t.record(catch_and_log_panic("verifying post", || { - verify_post(verify_info) - })) - } - - fn verify_consensus_fault( - &self, - h1: &[u8], - h2: &[u8], - extra: &[u8], - ) -> Result> { - let t = self.call_manager.charge_gas( - self.call_manager.price_list().on_verify_consensus_fault( - h1.len(), - h2.len(), - extra.len(), - ), - )?; - - // This syscall cannot be resolved inside the FVM, so we need to traverse - // the node boundary through an extern. - let (fault, _) = t.record( - self.call_manager - .externs() - .verify_consensus_fault(h1, h2, extra) - .or_illegal_argument(), - )?; - - Ok(fault) - } - - fn batch_verify_seals(&self, vis: &[SealVerifyInfo]) -> Result> { - // NOTE: gas has already been charged by the power actor when the batch verify was enqueued. - // Lotus charges "virtual" gas here for tracing only. - let mut items = Vec::new(); - for vi in vis { - let t = self - .call_manager - .charge_gas(self.call_manager.price_list().on_verify_seal(vi))?; - items.push((vi, t)); - } - log::debug!("batch verify seals start"); - let out = items.par_drain(..) - .with_min_len(vis.len() / *NUM_CPUS) - .map(|(seal, timer)| { - let start = GasTimer::start(); - let verify_seal_result = std::panic::catch_unwind(|| verify_seal(seal)); - let ok = match verify_seal_result { - Ok(res) => { - match res { - Ok(correct) => { - if !correct { - log::debug!( - "seal verify in batch failed (miner: {}) (err: Invalid Seal proof)", - seal.sector_id.miner - ); - } - correct // all ok - } - Err(err) => { - log::debug!( - "seal verify in batch failed (miner: {}) (err: {})", - seal.sector_id.miner, - err - ); - false - } - } - } - Err(e) => { - log::error!("seal verify internal fail (miner: {}) (err: {:?})", seal.sector_id.miner, e); - false - } - }; - timer.stop_with(start); - ok - }) - .collect(); - log::debug!("batch verify seals end"); - Ok(out) - } - - fn verify_aggregate_seals(&self, aggregate: &AggregateSealVerifyProofAndInfos) -> Result { - let t = self.call_manager.charge_gas( - self.call_manager - .price_list() - .on_verify_aggregate_seals(aggregate), - )?; - t.record(catch_and_log_panic("verifying aggregate seals", || { - verify_aggregate_seals(aggregate) - })) - } - - fn verify_replica_update(&self, replica: &ReplicaUpdateInfo) -> Result { - let t = self.call_manager.charge_gas( - self.call_manager - .price_list() - .on_verify_replica_update(replica), - )?; - t.record(catch_and_log_panic("verifying replica update", || { - verify_replica_update(replica) - })) - } } impl GasOps for DefaultKernel @@ -876,98 +831,21 @@ where .create_actor(code_id, actor_id, delegated_address) } - fn upgrade_actor( - &mut self, - new_code_cid: Cid, - params_id: BlockId, - ) -> Result { - if self.read_only { - return Err( - syscall_error!(ReadOnly, "upgrade_actor cannot be called while read-only").into(), - ); - } - - // check if this actor is already on the call stack - // - // We first find the first position of this actor on the call stack, and then make sure that - // no other actor appears on the call stack after 'position' (unless its a recursive upgrade - // call which is allowed) - let mut iter = self.call_manager.get_call_stack().iter(); - let position = iter.position(|&tuple| tuple == (self.actor_id, INVOKE_FUNC_NAME)); - if position.is_some() { - for tuple in iter { - if tuple.0 != self.actor_id || tuple.1 != UPGRADE_FUNC_NAME { - return Err(syscall_error!( - Forbidden, - "calling upgrade on actor already on call stack is forbidden" - ) - .into()); - } - } - } - - // Load parameters. - let params = if params_id == NO_DATA_BLOCK_ID { - None - } else { - Some(self.blocks.get(params_id)?.clone()) - }; - - // Make sure we can actually store the return block. - if self.blocks.is_full() { - return Err(syscall_error!(LimitExceeded; "cannot store return block").into()); - } - - let result = self.call_manager.with_transaction(|cm| { - let state = cm - .get_actor(self.actor_id)? - .ok_or_else(|| syscall_error!(IllegalOperation; "actor deleted"))?; - - // store the code cid of the calling actor before running the upgrade entrypoint - // in case it was changed (which could happen if the target upgrade entrypoint - // sent a message to this actor which in turn called upgrade) - let code = state.code; - - // update the code cid of the actor to new_code_cid - cm.set_actor( - self.actor_id, - ActorState::new( - new_code_cid, - state.state, - state.balance, - state.sequence, - None, - ), - )?; - - // run the upgrade entrypoint - let result = cm.call_actor::( - self.caller, - Address::new_id(self.actor_id), - Entrypoint::Upgrade(UpgradeInfo { old_code_cid: code }), - params, - &TokenAmount::from_whole(0), - None, - false, - )?; + fn install_actor(&mut self, code_id: Cid) -> Result<()> { + let start = GasTimer::start(); + let size = self + .call_manager + .engine() + .preload(self.call_manager.blockstore(), &[code_id]) + .context("failed to install actor") + .or_illegal_argument()?; - Ok(result) - }); + let t = self + .call_manager + .charge_gas(self.call_manager.price_list().on_install_actor(size))?; + t.stop_with(start); - match result { - Ok(InvocationResult { exit_code, value }) => { - let (block_stat, block_id) = match value { - None => (BlockStat { codec: 0, size: 0 }, NO_DATA_BLOCK_ID), - Some(block) => (block.stat(), self.blocks.put_reachable(block)?), - }; - Ok(CallResult { - block_id, - block_stat, - exit_code, - }) - } - Err(err) => Err(err), - } + Ok(()) } fn get_builtin_actor_type(&self, code_cid: &Cid) -> Result { @@ -1001,24 +879,6 @@ where ) } - #[cfg(feature = "m2-native")] - fn install_actor(&mut self, code_id: Cid) -> Result<()> { - let start = GasTimer::start(); - let size = self - .call_manager - .engine() - .preload(self.call_manager.blockstore(), &[code_id]) - .context("failed to install actor") - .or_illegal_argument()?; - - let t = self - .call_manager - .charge_gas(self.call_manager.price_list().on_install_actor(size))?; - t.stop_with(start); - - Ok(()) - } - fn balance_of(&self, actor_id: ActorID) -> Result { let t = self .call_manager @@ -1257,262 +1117,3 @@ fn catch_and_log_panic Result + UnwindSafe, R>(context: &str, } } } - -fn prover_id_from_u64(id: u64) -> ProverId { - let mut prover_id = ProverId::default(); - let prover_bytes = Address::new_id(id).payload().to_raw_bytes(); - prover_id[..prover_bytes.len()].copy_from_slice(&prover_bytes); - prover_id -} - -fn get_required_padding( - old_length: PaddedPieceSize, - new_piece_length: PaddedPieceSize, -) -> (Vec, PaddedPieceSize) { - let mut sum = 0; - - let mut to_fill = 0u64.wrapping_sub(old_length.0) % new_piece_length.0; - let n = to_fill.count_ones(); - let mut pad_pieces = Vec::with_capacity(n as usize); - for _ in 0..n { - let next = to_fill.trailing_zeros(); - let p_size = 1 << next; - to_fill ^= p_size; - - let padded = PaddedPieceSize(p_size); - pad_pieces.push(padded); - sum += padded.0; - } - - (pad_pieces, PaddedPieceSize(sum)) -} - -fn to_fil_public_replica_infos( - src: &[SectorInfo], - typ: RegisteredPoStProof, -) -> Result> { - let replicas = src - .iter() - .map::, _>( - |sector_info: &SectorInfo| { - let commr = commcid::cid_to_replica_commitment_v1(§or_info.sealed_cid)?; - if !check_valid_proof_type(typ, sector_info.proof) { - return Err("invalid proof type".to_string()); - } - let replica = PublicReplicaInfo::new(typ.try_into()?, commr); - Ok((SectorId::from(sector_info.sector_number), replica)) - }, - ) - .collect::, _>>() - .or_illegal_argument()?; - Ok(replicas) -} - -fn check_valid_proof_type(post_type: RegisteredPoStProof, seal_type: RegisteredSealProof) -> bool { - if let Ok(proof_type_v1p1) = seal_type.registered_window_post_proof() { - proof_type_v1p1 == post_type - } else { - false - } -} - -fn verify_seal(vi: &SealVerifyInfo) -> Result { - let commr = commcid::cid_to_replica_commitment_v1(&vi.sealed_cid).or_illegal_argument()?; - let commd = commcid::cid_to_data_commitment_v1(&vi.unsealed_cid).or_illegal_argument()?; - let prover_id = prover_id_from_u64(vi.sector_id.miner); - - proofs::seal::verify_seal( - vi.registered_proof - .try_into() - .or_illegal_argument() - .context(format_args!("invalid proof type {:?}", vi.registered_proof))?, - commr, - commd, - prover_id, - SectorId::from(vi.sector_id.number), - bytes_32(&vi.randomness.0), - bytes_32(&vi.interactive_randomness.0), - &vi.proof, - ) - .or_illegal_argument() - // There are probably errors here that should be fatal, but it's hard to tell so I'm sticking - // with illegal argument for now. - // - // Worst case, _some_ node falls out of sync. Better than the network halting. - .context("failed to verify seal proof") -} - -fn verify_post(verify_info: &WindowPoStVerifyInfo) -> Result { - let WindowPoStVerifyInfo { - ref proofs, - ref challenged_sectors, - prover, - .. - } = verify_info; - - let Randomness(mut randomness) = verify_info.randomness.clone(); - - // Necessary to be valid bls12 381 element. - randomness[31] &= 0x3f; - - let proof_type = proofs[0].post_proof; - - for proof in proofs { - if proof.post_proof != proof_type { - return Err( - syscall_error!(IllegalArgument; "all proof types must be the same (found both {:?} and {:?})", proof_type, proof.post_proof) - .into(), - ); - } - } - // Convert sector info into public replica - let replicas = to_fil_public_replica_infos(challenged_sectors, proof_type)?; - - // Convert PoSt proofs into proofs-api format - let proofs: Vec<(proofs::RegisteredPoStProof, _)> = proofs - .iter() - .map(|p| Ok((p.post_proof.try_into()?, p.proof_bytes.as_ref()))) - .collect::>() - .or_illegal_argument()?; - - // Generate prover bytes from ID - let prover_id = prover_id_from_u64(*prover); - - // Verify Proof - proofs::post::verify_window_post(&bytes_32(&randomness), &proofs, &replicas, prover_id) - .or_illegal_argument() -} - -fn verify_aggregate_seals(aggregate: &AggregateSealVerifyProofAndInfos) -> Result { - if aggregate.infos.is_empty() { - return Err(syscall_error!(IllegalArgument; "no seal verify infos").into()); - } - let spt: proofs::RegisteredSealProof = aggregate.seal_proof.try_into().or_illegal_argument()?; - let prover_id = prover_id_from_u64(aggregate.miner); - struct AggregationInputs { - // replica - commr: [u8; 32], - // data - commd: [u8; 32], - sector_id: SectorId, - ticket: [u8; 32], - seed: [u8; 32], - } - let inputs: Vec = aggregate - .infos - .iter() - .map(|info| { - let commr = commcid::cid_to_replica_commitment_v1(&info.sealed_cid)?; - let commd = commcid::cid_to_data_commitment_v1(&info.unsealed_cid)?; - Ok(AggregationInputs { - commr, - commd, - ticket: bytes_32(&info.randomness.0), - seed: bytes_32(&info.interactive_randomness.0), - sector_id: SectorId::from(info.sector_number), - }) - }) - .collect::, &'static str>>() - .or_illegal_argument()?; - - let inp: Vec> = inputs - .par_iter() - .map(|input| { - proofs::seal::get_seal_inputs( - spt, - input.commr, - input.commd, - prover_id, - input.sector_id, - input.ticket, - input.seed, - ) - }) - .try_reduce(Vec::new, |mut acc, current| { - acc.extend(current); - Ok(acc) - }) - .or_illegal_argument()?; - - let commrs: Vec<[u8; 32]> = inputs.iter().map(|input| input.commr).collect(); - let seeds: Vec<[u8; 32]> = inputs.iter().map(|input| input.seed).collect(); - - proofs::seal::verify_aggregate_seal_commit_proofs( - spt, - aggregate.aggregate_proof.try_into().or_illegal_argument()?, - aggregate.proof.clone(), - &commrs, - &seeds, - inp, - ) - .or_illegal_argument() -} - -fn verify_replica_update(replica: &ReplicaUpdateInfo) -> Result { - let up: proofs::RegisteredUpdateProof = - replica.update_proof_type.try_into().or_illegal_argument()?; - - let commr_old = - commcid::cid_to_replica_commitment_v1(&replica.old_sealed_cid).or_illegal_argument()?; - let commr_new = - commcid::cid_to_replica_commitment_v1(&replica.new_sealed_cid).or_illegal_argument()?; - let commd = - commcid::cid_to_data_commitment_v1(&replica.new_unsealed_cid).or_illegal_argument()?; - - proofs::update::verify_empty_sector_update_proof( - up, - &replica.proof, - commr_old, - commr_new, - commd, - ) - .or_illegal_argument() -} - -fn compute_unsealed_sector_cid( - proof_type: RegisteredSealProof, - pieces: &[PieceInfo], -) -> Result { - let ssize = proof_type.sector_size().or_illegal_argument()? as u64; - - let mut all_pieces = Vec::::with_capacity(pieces.len()); - - let pssize = PaddedPieceSize(ssize); - if pieces.is_empty() { - all_pieces.push(proofs::PieceInfo { - size: pssize.unpadded().into(), - commitment: zero_piece_commitment(pssize), - }) - } else { - // pad remaining space with 0 piece commitments - let mut sum = PaddedPieceSize(0); - let pad_to = |pads: Vec, - all_pieces: &mut Vec, - sum: &mut PaddedPieceSize| { - for p in pads { - all_pieces.push(proofs::PieceInfo { - size: p.unpadded().into(), - commitment: zero_piece_commitment(p), - }); - - sum.0 += p.0; - } - }; - for p in pieces { - let (ps, _) = get_required_padding(sum, p.size); - pad_to(ps, &mut all_pieces, &mut sum); - - all_pieces.push(proofs::PieceInfo::try_from(p).or_illegal_argument()?); - sum.0 += p.size.0; - } - - let (ps, _) = get_required_padding(sum, pssize); - pad_to(ps, &mut all_pieces, &mut sum); - } - - let comm_d = - proofs::seal::compute_comm_d(proof_type.try_into().or_illegal_argument()?, &all_pieces) - .or_illegal_argument()?; - - commcid::data_commitment_v1_to_cid(&comm_d).or_illegal_argument() -} diff --git a/fvm/src/kernel/filecoin.rs b/fvm/src/kernel/filecoin.rs new file mode 100644 index 000000000..b4e998587 --- /dev/null +++ b/fvm/src/kernel/filecoin.rs @@ -0,0 +1,557 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::panic::{self, UnwindSafe}; + +use ambassador::Delegate; +use filecoin_proofs_api::{self as proofs, ProverId, PublicReplicaInfo, SectorId}; + +use fvm_ipld_encoding::bytes_32; +use fvm_shared::econ::TokenAmount; +use fvm_shared::piece::{zero_piece_commitment, PaddedPieceSize}; +use fvm_shared::sector::{RegisteredPoStProof, SectorInfo}; +use fvm_shared::{commcid, ActorID}; +use lazy_static::lazy_static; +use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefIterator, ParallelDrainRange, ParallelIterator, +}; + +use super::blocks::BlockRegistry; +use super::error::Result; +use super::*; +use crate::call_manager::CallManager; +use crate::externs::Consensus; +use crate::*; + +lazy_static! { + static ref NUM_CPUS: usize = num_cpus::get(); + static ref INITIAL_RESERVE_BALANCE: TokenAmount = TokenAmount::from_whole(300_000_000); +} + +pub trait FilecoinKernel: Kernel { + /// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes. + fn compute_unsealed_sector_cid( + &self, + proof_type: RegisteredSealProof, + pieces: &[PieceInfo], + ) -> Result; + + /// Verifies a window proof of spacetime. + fn verify_post(&self, verify_info: &WindowPoStVerifyInfo) -> Result; + + /// Verifies that two block headers provide proof of a consensus fault: + /// - both headers mined by the same actor + /// - headers are different + /// - first header is of the same or lower epoch as the second + /// - at least one of the headers appears in the current chain at or after epoch `earliest` + /// - the headers provide evidence of a fault (see the spec for the different fault types). + /// The parameters are all serialized block headers. The third "extra" parameter is consulted only for + /// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the + /// blocks in the parent of h2 (i.e. h2's grandparent). + /// Returns nil and an error if the headers don't prove a fault. + fn verify_consensus_fault( + &self, + h1: &[u8], + h2: &[u8], + extra: &[u8], + ) -> Result>; + + /// Verifies a batch of seals. This is a privledged syscall, may _only_ be called by the + /// power actor during cron. + /// + /// Gas: This syscall intentionally _does not_ charge any gas (as said gas would be charged to + /// cron). Instead, gas is pre-paid by the storage provider on pre-commit. + fn batch_verify_seals(&self, vis: &[SealVerifyInfo]) -> Result>; + + /// Verify aggregate seals verifies an aggregated batch of prove-commits. + fn verify_aggregate_seals(&self, aggregate: &AggregateSealVerifyProofAndInfos) -> Result; + + /// Verify replica update verifies a snap deal: an upgrade from a CC sector to a sector with + /// deals. + fn verify_replica_update(&self, replica: &ReplicaUpdateInfo) -> Result; +} + +#[derive(Delegate)] +#[delegate(IpldBlockOps)] +#[delegate(ActorOps)] +#[delegate(CircSupplyOps)] +#[delegate(CryptoOps)] +#[delegate(DebugOps)] +#[delegate(EventOps)] +#[delegate(GasOps)] +#[delegate(MessageOps)] +#[delegate(NetworkOps)] +#[delegate(RandomnessOps)] +#[delegate(SelfOps)] +#[delegate(LimiterOps)] +pub struct DefaultFilecoinKernel(pub K) +where + K: Kernel; + +impl FilecoinKernel for DefaultFilecoinKernel> +where + C: CallManager, + DefaultFilecoinKernel>: Kernel, +{ + fn compute_unsealed_sector_cid( + &self, + proof_type: RegisteredSealProof, + pieces: &[PieceInfo], + ) -> Result { + let t = self.0.call_manager.charge_gas( + self.0 + .call_manager + .price_list() + .on_compute_unsealed_sector_cid(proof_type, pieces), + )?; + + t.record(catch_and_log_panic("computing unsealed sector CID", || { + compute_unsealed_sector_cid(proof_type, pieces) + })) + } + + /// Verifies a window proof of spacetime. + fn verify_post(&self, verify_info: &WindowPoStVerifyInfo) -> Result { + let t = self + .0 + .call_manager + .charge_gas(self.0.call_manager.price_list().on_verify_post(verify_info))?; + + // This is especially important to catch as, otherwise, a bad "post" could be undisputable. + t.record(catch_and_log_panic("verifying post", || { + verify_post(verify_info) + })) + } + + fn verify_consensus_fault( + &self, + h1: &[u8], + h2: &[u8], + extra: &[u8], + ) -> Result> { + let t = self.0.call_manager.charge_gas( + self.0.call_manager.price_list().on_verify_consensus_fault( + h1.len(), + h2.len(), + extra.len(), + ), + )?; + + // This syscall cannot be resolved inside the FVM, so we need to traverse + // the node boundary through an extern. + let (fault, _) = t.record( + self.0 + .call_manager + .externs() + .verify_consensus_fault(h1, h2, extra) + .or_illegal_argument(), + )?; + + Ok(fault) + } + + fn batch_verify_seals(&self, vis: &[SealVerifyInfo]) -> Result> { + // NOTE: gas has already been charged by the power actor when the batch verify was enqueued. + // Lotus charges "virtual" gas here for tracing only. + let mut items = Vec::new(); + for vi in vis { + let t = self + .0 + .call_manager + .charge_gas(self.0.call_manager.price_list().on_verify_seal(vi))?; + items.push((vi, t)); + } + log::debug!("batch verify seals start"); + let out = items.par_drain(..) + .with_min_len(vis.len() / *NUM_CPUS) + .map(|(seal, timer)| { + let start = GasTimer::start(); + let verify_seal_result = std::panic::catch_unwind(|| verify_seal(seal)); + let ok = match verify_seal_result { + Ok(res) => { + match res { + Ok(correct) => { + if !correct { + log::debug!( + "seal verify in batch failed (miner: {}) (err: Invalid Seal proof)", + seal.sector_id.miner + ); + } + correct // all ok + } + Err(err) => { + log::debug!( + "seal verify in batch failed (miner: {}) (err: {})", + seal.sector_id.miner, + err + ); + false + } + } + } + Err(e) => { + log::error!("seal verify internal fail (miner: {}) (err: {:?})", seal.sector_id.miner, e); + false + } + }; + timer.stop_with(start); + ok + }) + .collect(); + log::debug!("batch verify seals end"); + Ok(out) + } + + fn verify_aggregate_seals(&self, aggregate: &AggregateSealVerifyProofAndInfos) -> Result { + let t = self.0.call_manager.charge_gas( + self.0 + .call_manager + .price_list() + .on_verify_aggregate_seals(aggregate), + )?; + t.record(catch_and_log_panic("verifying aggregate seals", || { + verify_aggregate_seals(aggregate) + })) + } + + fn verify_replica_update(&self, replica: &ReplicaUpdateInfo) -> Result { + let t = self.0.call_manager.charge_gas( + self.0 + .call_manager + .price_list() + .on_verify_replica_update(replica), + )?; + t.record(catch_and_log_panic("verifying replica update", || { + verify_replica_update(replica) + })) + } +} + +impl Kernel for DefaultFilecoinKernel> +where + C: CallManager, +{ + type CallManager = C; + + fn into_inner(self) -> (Self::CallManager, BlockRegistry) + where + Self: Sized, + { + self.0.into_inner() + } + + fn machine(&self) -> &::Machine { + self.0.machine() + } + + fn send>( + &mut self, + recipient: &Address, + method: u64, + params: BlockId, + value: &TokenAmount, + gas_limit: Option, + flags: SendFlags, + ) -> Result { + self.0 + .send::(recipient, method, params, value, gas_limit, flags) + } + + fn upgrade_actor>( + &mut self, + new_code_cid: Cid, + params_id: BlockId, + ) -> Result { + self.0.upgrade_actor::(new_code_cid, params_id) + } + + fn new( + mgr: C, + blocks: BlockRegistry, + caller: ActorID, + actor_id: ActorID, + method: MethodNum, + value_received: TokenAmount, + read_only: bool, + ) -> Self { + DefaultFilecoinKernel(DefaultKernel::new( + mgr, + blocks, + caller, + actor_id, + method, + value_received, + read_only, + )) + } +} + +fn catch_and_log_panic Result + UnwindSafe, R>(context: &str, f: F) -> Result { + match panic::catch_unwind(f) { + Ok(v) => v, + Err(e) => { + log::error!("caught panic when {}: {:?}", context, e); + Err(syscall_error!(IllegalArgument; "caught panic when {}: {:?}", context, e).into()) + } + } +} + +fn verify_post(verify_info: &WindowPoStVerifyInfo) -> Result { + let WindowPoStVerifyInfo { + ref proofs, + ref challenged_sectors, + prover, + .. + } = verify_info; + + let Randomness(mut randomness) = verify_info.randomness.clone(); + + // Necessary to be valid bls12 381 element. + randomness[31] &= 0x3f; + + let proof_type = proofs[0].post_proof; + + for proof in proofs { + if proof.post_proof != proof_type { + return Err( + syscall_error!(IllegalArgument; "all proof types must be the same (found both {:?} and {:?})", proof_type, proof.post_proof) + .into(), + ); + } + } + // Convert sector info into public replica + let replicas = to_fil_public_replica_infos(challenged_sectors, proof_type)?; + + // Convert PoSt proofs into proofs-api format + let proofs: Vec<(proofs::RegisteredPoStProof, _)> = proofs + .iter() + .map(|p| Ok((p.post_proof.try_into()?, p.proof_bytes.as_ref()))) + .collect::>() + .or_illegal_argument()?; + + // Generate prover bytes from ID + let prover_id = prover_id_from_u64(*prover); + + // Verify Proof + proofs::post::verify_window_post(&bytes_32(&randomness), &proofs, &replicas, prover_id) + .or_illegal_argument() +} + +fn to_fil_public_replica_infos( + src: &[SectorInfo], + typ: RegisteredPoStProof, +) -> Result> { + let replicas = src + .iter() + .map::, _>( + |sector_info: &SectorInfo| { + let commr = commcid::cid_to_replica_commitment_v1(§or_info.sealed_cid)?; + if !check_valid_proof_type(typ, sector_info.proof) { + return Err("invalid proof type".to_string()); + } + let replica = PublicReplicaInfo::new(typ.try_into()?, commr); + Ok((SectorId::from(sector_info.sector_number), replica)) + }, + ) + .collect::, _>>() + .or_illegal_argument()?; + Ok(replicas) +} + +fn check_valid_proof_type(post_type: RegisteredPoStProof, seal_type: RegisteredSealProof) -> bool { + if let Ok(proof_type_v1p1) = seal_type.registered_window_post_proof() { + proof_type_v1p1 == post_type + } else { + false + } +} + +fn prover_id_from_u64(id: u64) -> ProverId { + let mut prover_id = ProverId::default(); + let prover_bytes = Address::new_id(id).payload().to_raw_bytes(); + prover_id[..prover_bytes.len()].copy_from_slice(&prover_bytes); + prover_id +} + +fn get_required_padding( + old_length: PaddedPieceSize, + new_piece_length: PaddedPieceSize, +) -> (Vec, PaddedPieceSize) { + let mut sum = 0; + + let mut to_fill = 0u64.wrapping_sub(old_length.0) % new_piece_length.0; + let n = to_fill.count_ones(); + let mut pad_pieces = Vec::with_capacity(n as usize); + for _ in 0..n { + let next = to_fill.trailing_zeros(); + let p_size = 1 << next; + to_fill ^= p_size; + + let padded = PaddedPieceSize(p_size); + pad_pieces.push(padded); + sum += padded.0; + } + + (pad_pieces, PaddedPieceSize(sum)) +} + +fn verify_seal(vi: &SealVerifyInfo) -> Result { + let commr = commcid::cid_to_replica_commitment_v1(&vi.sealed_cid).or_illegal_argument()?; + let commd = commcid::cid_to_data_commitment_v1(&vi.unsealed_cid).or_illegal_argument()?; + let prover_id = prover_id_from_u64(vi.sector_id.miner); + + proofs::seal::verify_seal( + vi.registered_proof + .try_into() + .or_illegal_argument() + .context(format_args!("invalid proof type {:?}", vi.registered_proof))?, + commr, + commd, + prover_id, + SectorId::from(vi.sector_id.number), + bytes_32(&vi.randomness.0), + bytes_32(&vi.interactive_randomness.0), + &vi.proof, + ) + .or_illegal_argument() + // There are probably errors here that should be fatal, but it's hard to tell so I'm sticking + // with illegal argument for now. + // + // Worst case, _some_ node falls out of sync. Better than the network halting. + .context("failed to verify seal proof") +} + +fn verify_aggregate_seals(aggregate: &AggregateSealVerifyProofAndInfos) -> Result { + if aggregate.infos.is_empty() { + return Err(syscall_error!(IllegalArgument; "no seal verify infos").into()); + } + let spt: proofs::RegisteredSealProof = aggregate.seal_proof.try_into().or_illegal_argument()?; + let prover_id = prover_id_from_u64(aggregate.miner); + struct AggregationInputs { + // replica + commr: [u8; 32], + // data + commd: [u8; 32], + sector_id: SectorId, + ticket: [u8; 32], + seed: [u8; 32], + } + let inputs: Vec = aggregate + .infos + .iter() + .map(|info| { + let commr = commcid::cid_to_replica_commitment_v1(&info.sealed_cid)?; + let commd = commcid::cid_to_data_commitment_v1(&info.unsealed_cid)?; + Ok(AggregationInputs { + commr, + commd, + ticket: bytes_32(&info.randomness.0), + seed: bytes_32(&info.interactive_randomness.0), + sector_id: SectorId::from(info.sector_number), + }) + }) + .collect::, &'static str>>() + .or_illegal_argument()?; + + let inp: Vec> = inputs + .par_iter() + .map(|input| { + proofs::seal::get_seal_inputs( + spt, + input.commr, + input.commd, + prover_id, + input.sector_id, + input.ticket, + input.seed, + ) + }) + .try_reduce(Vec::new, |mut acc, current| { + acc.extend(current); + Ok(acc) + }) + .or_illegal_argument()?; + + let commrs: Vec<[u8; 32]> = inputs.iter().map(|input| input.commr).collect(); + let seeds: Vec<[u8; 32]> = inputs.iter().map(|input| input.seed).collect(); + + proofs::seal::verify_aggregate_seal_commit_proofs( + spt, + aggregate.aggregate_proof.try_into().or_illegal_argument()?, + aggregate.proof.clone(), + &commrs, + &seeds, + inp, + ) + .or_illegal_argument() +} + +fn verify_replica_update(replica: &ReplicaUpdateInfo) -> Result { + let up: proofs::RegisteredUpdateProof = + replica.update_proof_type.try_into().or_illegal_argument()?; + + let commr_old = + commcid::cid_to_replica_commitment_v1(&replica.old_sealed_cid).or_illegal_argument()?; + let commr_new = + commcid::cid_to_replica_commitment_v1(&replica.new_sealed_cid).or_illegal_argument()?; + let commd = + commcid::cid_to_data_commitment_v1(&replica.new_unsealed_cid).or_illegal_argument()?; + + proofs::update::verify_empty_sector_update_proof( + up, + &replica.proof, + commr_old, + commr_new, + commd, + ) + .or_illegal_argument() +} + +fn compute_unsealed_sector_cid( + proof_type: RegisteredSealProof, + pieces: &[PieceInfo], +) -> Result { + let ssize = proof_type.sector_size().or_illegal_argument()? as u64; + + let mut all_pieces = Vec::::with_capacity(pieces.len()); + + let pssize = PaddedPieceSize(ssize); + if pieces.is_empty() { + all_pieces.push(proofs::PieceInfo { + size: pssize.unpadded().into(), + commitment: zero_piece_commitment(pssize), + }) + } else { + // pad remaining space with 0 piece commitments + let mut sum = PaddedPieceSize(0); + let pad_to = |pads: Vec, + all_pieces: &mut Vec, + sum: &mut PaddedPieceSize| { + for p in pads { + all_pieces.push(proofs::PieceInfo { + size: p.unpadded().into(), + commitment: zero_piece_commitment(p), + }); + + sum.0 += p.0; + } + }; + for p in pieces { + let (ps, _) = get_required_padding(sum, p.size); + pad_to(ps, &mut all_pieces, &mut sum); + + all_pieces.push(proofs::PieceInfo::try_from(p).or_illegal_argument()?); + sum.0 += p.size.0; + } + + let (ps, _) = get_required_padding(sum, pssize); + pad_to(ps, &mut all_pieces, &mut sum); + } + + let comm_d = + proofs::seal::compute_comm_d(proof_type.try_into().or_illegal_argument()?, &all_pieces) + .or_illegal_argument()?; + + commcid::data_commitment_v1_to_cid(&comm_d).or_illegal_argument() +} diff --git a/fvm/src/kernel/mod.rs b/fvm/src/kernel/mod.rs index 02a5f38ef..cc082cb03 100644 --- a/fvm/src/kernel/mod.rs +++ b/fvm/src/kernel/mod.rs @@ -21,22 +21,26 @@ use fvm_shared::sys::out::vm::MessageContext; use fvm_shared::sys::SendFlags; use fvm_shared::{ActorID, MethodNum}; +mod blocks; mod hash; -mod blocks; pub mod default; +pub mod filecoin; pub(crate) mod error; +use ambassador::delegatable_trait; pub use error::{ClassifyResult, Context, ExecutionError, Result, SyscallError}; use fvm_shared::event::StampedEvent; pub use hash::SupportedHashes; use multihash::MultihashGeneric; +use wasmtime::Linker; use crate::call_manager::CallManager; use crate::gas::{Gas, GasTimer, PriceList}; use crate::machine::limiter::MemoryLimiter; use crate::machine::Machine; +use crate::syscalls::InvocationData; pub struct CallResult { pub block_id: BlockId, @@ -52,7 +56,8 @@ pub struct CallResult { /// Actors may call into the kernel via the syscalls defined in the [`syscalls`][crate::syscalls] /// module. pub trait Kernel: - ActorOps + SyscallHandler + + ActorOps + IpldBlockOps + CircSupplyOps + CryptoOps @@ -113,9 +118,20 @@ pub trait Kernel: gas_limit: Option, flags: SendFlags, ) -> Result; + + fn upgrade_actor>( + &mut self, + new_code_cid: Cid, + params_id: BlockId, + ) -> Result; +} + +pub trait SyscallHandler: Sized { + fn bind_syscalls(&self, linker: &mut Linker>) -> anyhow::Result<()>; } /// Network-related operations. +#[delegatable_trait] pub trait NetworkOps { /// Network information (epoch, version, etc.). fn network_context(&self) -> Result; @@ -125,12 +141,14 @@ pub trait NetworkOps { } /// Accessors to query attributes of the incoming message. +#[delegatable_trait] pub trait MessageOps { /// Message information. fn msg_context(&self) -> Result; } /// The IPLD subset of the kernel. +#[delegatable_trait] pub trait IpldBlockOps { /// Open a block. /// @@ -164,6 +182,7 @@ pub trait IpldBlockOps { /// Actor state access and manipulation. /// Depends on BlockOps to read and write blocks in the state tree. +#[delegatable_trait] pub trait SelfOps: IpldBlockOps { /// Get the state root. fn root(&mut self) -> Result; @@ -182,6 +201,7 @@ pub trait SelfOps: IpldBlockOps { /// Actors operations whose scope of action is actors other than the calling /// actor. The calling actor's state may be consulted to resolve some. +#[delegatable_trait] pub trait ActorOps { /// Resolves an address of any protocol to an ID address (via the Init actor's table). /// This allows resolution of externally-provided SECP, BLS, or actor addresses to the canonical form. @@ -209,14 +229,6 @@ pub trait ActorOps { delegated_address: Option
, ) -> Result<()>; - fn upgrade_actor( - &mut self, - new_code_cid: Cid, - params_id: BlockId, - ) -> Result; - - /// Installs actor code pointed by cid - #[cfg(feature = "m2-native")] fn install_actor(&mut self, code_cid: Cid) -> Result<()>; /// Returns the actor's "type" (if builitin) or 0 (if not). @@ -230,6 +242,7 @@ pub trait ActorOps { } /// Operations to query the circulating supply. +#[delegatable_trait] pub trait CircSupplyOps { /// Returns the total token supply in circulation at the beginning of the current epoch. /// The circulating supply is the sum of: @@ -243,6 +256,7 @@ pub trait CircSupplyOps { } /// Operations for explicit gas charging. +#[delegatable_trait] pub trait GasOps { /// Returns the gas used by the transaction so far. fn gas_used(&self) -> Gas; @@ -259,6 +273,7 @@ pub trait GasOps { } /// Cryptographic primitives provided by the kernel. +#[delegatable_trait] pub trait CryptoOps { /// Verifies that a signature is valid for an address and plaintext. fn verify_signature( @@ -281,50 +296,10 @@ pub trait CryptoOps { /// to small to fit the entire digest, it will be truncated. If too large, the leftover space /// will not be overwritten. fn hash(&self, code: u64, data: &[u8]) -> Result>; - - /// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes. - fn compute_unsealed_sector_cid( - &self, - proof_type: RegisteredSealProof, - pieces: &[PieceInfo], - ) -> Result; - - /// Verifies a window proof of spacetime. - fn verify_post(&self, verify_info: &WindowPoStVerifyInfo) -> Result; - - /// Verifies that two block headers provide proof of a consensus fault: - /// - both headers mined by the same actor - /// - headers are different - /// - first header is of the same or lower epoch as the second - /// - at least one of the headers appears in the current chain at or after epoch `earliest` - /// - the headers provide evidence of a fault (see the spec for the different fault types). - /// The parameters are all serialized block headers. The third "extra" parameter is consulted only for - /// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the - /// blocks in the parent of h2 (i.e. h2's grandparent). - /// Returns nil and an error if the headers don't prove a fault. - fn verify_consensus_fault( - &self, - h1: &[u8], - h2: &[u8], - extra: &[u8], - ) -> Result>; - - /// Verifies a batch of seals. This is a privledged syscall, may _only_ be called by the - /// power actor during cron. - /// - /// Gas: This syscall intentionally _does not_ charge any gas (as said gas would be charged to - /// cron). Instead, gas is pre-paid by the storage provider on pre-commit. - fn batch_verify_seals(&self, vis: &[SealVerifyInfo]) -> Result>; - - /// Verify aggregate seals verifies an aggregated batch of prove-commits. - fn verify_aggregate_seals(&self, aggregate: &AggregateSealVerifyProofAndInfos) -> Result; - - /// Verify replica update verifies a snap deal: an upgrade from a CC sector to a sector with - /// deals. - fn verify_replica_update(&self, replica: &ReplicaUpdateInfo) -> Result; } /// Randomness queries. +#[delegatable_trait] pub trait RandomnessOps { /// Randomness returns a (pseudo)random byte array drawing from the latest /// ticket chain from a given epoch. @@ -342,6 +317,7 @@ pub trait RandomnessOps { } /// Debugging APIs. +#[delegatable_trait] pub trait DebugOps { /// Log a message. fn log(&self, msg: String); @@ -359,6 +335,7 @@ pub trait DebugOps { /// This interface is not one of the operations the kernel provides to actors. /// It's only part of the kernel out of necessity to pass it through to the /// call manager which tracks the limits across the whole execution stack. +#[delegatable_trait] pub trait LimiterOps { type Limiter: MemoryLimiter; /// Give access to the limiter of the underlying call manager. @@ -366,6 +343,7 @@ pub trait LimiterOps { } /// Eventing APIs. +#[delegatable_trait] pub trait EventOps { /// Records an event emitted throughout execution. fn emit_event( diff --git a/fvm/src/lib.rs b/fvm/src/lib.rs index 697b9d015..65d0b624e 100644 --- a/fvm/src/lib.rs +++ b/fvm/src/lib.rs @@ -55,6 +55,7 @@ mod test { use crate::call_manager::DefaultCallManager; use crate::engine::EnginePool; use crate::externs::{Chain, Consensus, Externs, Rand}; + use crate::kernel::filecoin::DefaultFilecoinKernel; use crate::machine::{DefaultMachine, Manifest, NetworkConfig}; use crate::state_tree::StateTree; use crate::{executor, DefaultKernel}; @@ -124,9 +125,8 @@ mod test { let machine = DefaultMachine::new(&mc, bs, DummyExterns).unwrap(); let engine = EnginePool::new_default((&mc.network).into()).unwrap(); - let _ = executor::DefaultExecutor::>>::new( - engine, - Box::new(machine), - ); + let _ = executor::DefaultExecutor::< + DefaultFilecoinKernel>>, + >::new(engine, Box::new(machine)); } } diff --git a/fvm/src/syscalls/actor.rs b/fvm/src/syscalls/actor.rs index 4c790a0db..da9ef64d8 100644 --- a/fvm/src/syscalls/actor.rs +++ b/fvm/src/syscalls/actor.rs @@ -162,7 +162,6 @@ pub fn get_code_cid_for_type( context.memory.write_cid(&k, obuf_off, obuf_len) } -#[cfg(feature = "m2-native")] pub fn install_actor( context: Context<'_, impl Kernel>, typ_off: u32, // Cid diff --git a/fvm/src/syscalls/crypto.rs b/fvm/src/syscalls/crypto.rs index e389e8784..45b176e56 100644 --- a/fvm/src/syscalls/crypto.rs +++ b/fvm/src/syscalls/crypto.rs @@ -2,21 +2,15 @@ // SPDX-License-Identifier: Apache-2.0, MIT use std::cmp; -use anyhow::{anyhow, Context as _}; +use anyhow::Context as _; use fvm_shared::crypto::signature::{ SignatureType, SECP_PUB_LEN, SECP_SIG_LEN, SECP_SIG_MESSAGE_HASH_SIZE, }; -use fvm_shared::piece::PieceInfo; -use fvm_shared::sector::{ - AggregateSealVerifyProofAndInfos, RegisteredSealProof, ReplicaUpdateInfo, SealVerifyInfo, - WindowPoStVerifyInfo, -}; -use fvm_shared::sys; use num_traits::FromPrimitive; use super::Context; use crate::kernel::{ClassifyResult, Result}; -use crate::{syscall_error, Kernel}; +use crate::Kernel; /// Verifies that a signature is valid for an address and plaintext. /// @@ -94,168 +88,3 @@ pub fn hash( digest_out[..length].copy_from_slice(&digest.digest()[..length]); Ok(length as u32) } - -/// Computes an unsealed sector CID (CommD) from its constituent piece CIDs -/// (CommPs) and sizes. -/// -/// Writes the CID in the provided output buffer. -pub fn compute_unsealed_sector_cid( - context: Context<'_, impl Kernel>, - proof_type: i64, // RegisteredSealProof, - pieces_off: u32, // [PieceInfo] - pieces_len: u32, - cid_off: u32, - cid_len: u32, -) -> Result { - // Check/read all arguments. - let typ = RegisteredSealProof::from(proof_type); - if let RegisteredSealProof::Invalid(invalid) = typ { - return Err(syscall_error!(IllegalArgument; "invalid proof type {}", invalid).into()); - } - let pieces: Vec = context.memory.read_cbor(pieces_off, pieces_len)?; - context.memory.check_bounds(cid_off, cid_len)?; - - // Compute - let cid = context - .kernel - .compute_unsealed_sector_cid(typ, pieces.as_slice())?; - - // REturn - context.memory.write_cid(&cid, cid_off, cid_len) -} - -/// Verifies a window proof of spacetime. -/// -/// The return i32 indicates the status code of the verification: -/// - 0: verification ok. -/// - -1: verification failed. -pub fn verify_post( - context: Context<'_, impl Kernel>, - info_off: u32, // WindowPoStVerifyInfo, - info_len: u32, -) -> Result { - let info = context - .memory - .read_cbor::(info_off, info_len)?; - context - .kernel - .verify_post(&info) - .map(|v| if v { 0 } else { -1 }) -} - -/// Verifies that two block headers provide proof of a consensus fault: -/// - both headers mined by the same actor -/// - headers are different -/// - first header is of the same or lower epoch as the second -/// - at least one of the headers appears in the current chain at or after epoch `earliest` -/// - the headers provide evidence of a fault (see the spec for the different fault types). -/// The parameters are all serialized block headers. The third "extra" parameter is consulted only for -/// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the -/// blocks in the parent of h2 (i.e. h2's grandparent). -/// -pub fn verify_consensus_fault( - context: Context<'_, impl Kernel>, - h1_off: u32, - h1_len: u32, - h2_off: u32, - h2_len: u32, - extra_off: u32, - extra_len: u32, -) -> Result { - let h1 = context.memory.try_slice(h1_off, h1_len)?; - let h2 = context.memory.try_slice(h2_off, h2_len)?; - let extra = context.memory.try_slice(extra_off, extra_len)?; - - let ret = context.kernel.verify_consensus_fault(h1, h2, extra)?; - - match ret { - // Consensus fault detected - Some(fault) => Ok(sys::out::crypto::VerifyConsensusFault { - fault: fault.fault_type as u32, - epoch: fault.epoch, - target: fault - .target - .id() - .context("kernel returned non-id target address") - .or_fatal()?, - }), - // No consensus fault. - None => Ok(sys::out::crypto::VerifyConsensusFault { - fault: 0, - epoch: 0, - target: 0, - }), - } -} - -/// The return i32 indicates the status code of the verification: -/// - 0: verification ok. -/// - -1: verification failed. -pub fn verify_aggregate_seals( - context: Context<'_, impl Kernel>, - agg_off: u32, // AggregateSealVerifyProofAndInfos - agg_len: u32, -) -> Result { - let info = context - .memory - .read_cbor::(agg_off, agg_len)?; - context - .kernel - .verify_aggregate_seals(&info) - .map(|v| if v { 0 } else { -1 }) -} - -/// The return i32 indicates the status code of the verification: -/// - 0: verification ok. -/// - -1: verification failed. -pub fn verify_replica_update( - context: Context<'_, impl Kernel>, - rep_off: u32, // ReplicaUpdateInfo - rep_len: u32, -) -> Result { - let info = context - .memory - .read_cbor::(rep_off, rep_len)?; - context - .kernel - .verify_replica_update(&info) - .map(|v| if v { 0 } else { -1 }) -} - -/// Verify a batch of seals encoded as a CBOR array of `SealVerifyInfo`. -/// -/// When successful, this method will write a single byte back into the array at `result_off` for -/// each result: 0 for failed, 1 for success. -pub fn batch_verify_seals( - context: Context<'_, impl Kernel>, - batch_off: u32, - batch_len: u32, - result_off: u32, -) -> Result<()> { - // Check and decode params. - let batch = context - .memory - .read_cbor::>(batch_off, batch_len)?; - let output = context - .memory - .try_slice_mut(result_off, batch.len() as u32)?; - - // Execute. - let result = context.kernel.batch_verify_seals(&batch)?; - - // Sanity check that we got the correct number of results. - if result.len() != batch.len() { - return Err(anyhow!( - "expected one result per input: {} != {}", - batch.len(), - result.len() - )) - .or_fatal(); - } - - // Return. - unsafe { - output.copy_from_slice(&*(&*result as *const [bool] as *const [u8])); - } - Ok(()) -} diff --git a/fvm/src/syscalls/filecoin.rs b/fvm/src/syscalls/filecoin.rs new file mode 100644 index 000000000..757fe2bdb --- /dev/null +++ b/fvm/src/syscalls/filecoin.rs @@ -0,0 +1,180 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use fvm_shared::sector::WindowPoStVerifyInfo; + +use super::Context; +use crate::kernel::ClassifyResult; +use crate::kernel::{filecoin::FilecoinKernel, Result}; +use crate::syscall_error; +use anyhow::anyhow; +use anyhow::Context as _; +use fvm_shared::piece::PieceInfo; +use fvm_shared::sector::{ + AggregateSealVerifyProofAndInfos, RegisteredSealProof, ReplicaUpdateInfo, SealVerifyInfo, +}; +use fvm_shared::sys; + +/// Computes an unsealed sector CID (CommD) from its constituent piece CIDs +/// (CommPs) and sizes. +/// +/// Writes the CID in the provided output buffer. +pub fn compute_unsealed_sector_cid( + context: Context<'_, impl FilecoinKernel>, + proof_type: i64, // RegisteredSealProof, + pieces_off: u32, // [PieceInfo] + pieces_len: u32, + cid_off: u32, + cid_len: u32, +) -> Result { + // Check/read all arguments. + let typ = RegisteredSealProof::from(proof_type); + if let RegisteredSealProof::Invalid(invalid) = typ { + return Err(syscall_error!(IllegalArgument; "invalid proof type {}", invalid).into()); + } + let pieces: Vec = context.memory.read_cbor(pieces_off, pieces_len)?; + context.memory.check_bounds(cid_off, cid_len)?; + + // Compute + let cid = context + .kernel + .compute_unsealed_sector_cid(typ, pieces.as_slice())?; + + // REturn + context.memory.write_cid(&cid, cid_off, cid_len) +} + +/// Verifies a window proof of spacetime. +/// +/// The return i32 indicates the status code of the verification: +/// - 0: verification ok. +/// - -1: verification failed. +pub fn verify_post( + context: Context<'_, impl FilecoinKernel>, + info_off: u32, // WindowPoStVerifyInfo, + info_len: u32, +) -> Result { + let info = context + .memory + .read_cbor::(info_off, info_len)?; + context + .kernel + .verify_post(&info) + .map(|v| if v { 0 } else { -1 }) +} + +/// Verifies that two block headers provide proof of a consensus fault: +/// - both headers mined by the same actor +/// - headers are different +/// - first header is of the same or lower epoch as the second +/// - at least one of the headers appears in the current chain at or after epoch `earliest` +/// - the headers provide evidence of a fault (see the spec for the different fault types). +/// The parameters are all serialized block headers. The third "extra" parameter is consulted only for +/// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the +/// blocks in the parent of h2 (i.e. h2's grandparent). +/// +pub fn verify_consensus_fault( + context: Context<'_, impl FilecoinKernel>, + h1_off: u32, + h1_len: u32, + h2_off: u32, + h2_len: u32, + extra_off: u32, + extra_len: u32, +) -> Result { + let h1 = context.memory.try_slice(h1_off, h1_len)?; + let h2 = context.memory.try_slice(h2_off, h2_len)?; + let extra = context.memory.try_slice(extra_off, extra_len)?; + + let ret = context.kernel.verify_consensus_fault(h1, h2, extra)?; + + match ret { + // Consensus fault detected + Some(fault) => Ok(sys::out::crypto::VerifyConsensusFault { + fault: fault.fault_type as u32, + epoch: fault.epoch, + target: fault + .target + .id() + .context("kernel returned non-id target address") + .or_fatal()?, + }), + // No consensus fault. + None => Ok(sys::out::crypto::VerifyConsensusFault { + fault: 0, + epoch: 0, + target: 0, + }), + } +} + +/// The return i32 indicates the status code of the verification: +/// - 0: verification ok. +/// - -1: verification failed. +pub fn verify_aggregate_seals( + context: Context<'_, impl FilecoinKernel>, + agg_off: u32, // AggregateSealVerifyProofAndInfos + agg_len: u32, +) -> Result { + let info = context + .memory + .read_cbor::(agg_off, agg_len)?; + context + .kernel + .verify_aggregate_seals(&info) + .map(|v| if v { 0 } else { -1 }) +} + +/// The return i32 indicates the status code of the verification: +/// - 0: verification ok. +/// - -1: verification failed. +pub fn verify_replica_update( + context: Context<'_, impl FilecoinKernel>, + rep_off: u32, // ReplicaUpdateInfo + rep_len: u32, +) -> Result { + let info = context + .memory + .read_cbor::(rep_off, rep_len)?; + context + .kernel + .verify_replica_update(&info) + .map(|v| if v { 0 } else { -1 }) +} + +/// Verify a batch of seals encoded as a CBOR array of `SealVerifyInfo`. +/// +/// When successful, this method will write a single byte back into the array at `result_off` for +/// each result: 0 for failed, 1 for success. +pub fn batch_verify_seals( + context: Context<'_, impl FilecoinKernel>, + batch_off: u32, + batch_len: u32, + result_off: u32, +) -> Result<()> { + // Check and decode params. + let batch = context + .memory + .read_cbor::>(batch_off, batch_len)?; + let output = context + .memory + .try_slice_mut(result_off, batch.len() as u32)?; + + // Execute. + let result = context.kernel.batch_verify_seals(&batch)?; + + // Sanity check that we got the correct number of results. + if result.len() != batch.len() { + return Err(anyhow!( + "expected one result per input: {} != {}", + batch.len(), + result.len() + )) + .or_fatal(); + } + + // Return. + unsafe { + output.copy_from_slice(&*(&*result as *const [bool] as *const [u8])); + } + Ok(()) +} diff --git a/fvm/src/syscalls/mod.rs b/fvm/src/syscalls/mod.rs index 81490ccdf..b4d7575d2 100644 --- a/fvm/src/syscalls/mod.rs +++ b/fvm/src/syscalls/mod.rs @@ -4,11 +4,13 @@ use anyhow::{anyhow, Context as _}; use num_traits::Zero; use wasmtime::{AsContextMut, ExternType, Global, Linker, Memory, Module, Val}; -use crate::call_manager::backtrace; +use crate::call_manager::{backtrace, CallManager}; use crate::gas::{Gas, GasInstant, GasTimer}; -use crate::kernel::ExecutionError; +use crate::kernel::filecoin::DefaultFilecoinKernel; +use crate::kernel::{ExecutionError, SyscallHandler}; + use crate::machine::limiter::MemoryLimiter; -use crate::Kernel; +use crate::{DefaultKernel, Kernel}; pub(crate) mod error; @@ -18,6 +20,7 @@ mod context; mod crypto; mod debug; mod event; +mod filecoin; mod gas; mod ipld; mod network; @@ -233,107 +236,129 @@ fn min_table_elements(module: &Module) -> Option { use self::bind::BindSyscall; use self::error::Abort; -// Binds the syscall handlers so they can handle invocations -// from the actor code. -pub fn bind_syscalls( - linker: &mut Linker>, -) -> anyhow::Result<()> { - linker.bind("vm", "exit", vm::exit)?; - linker.bind("vm", "message_context", vm::message_context)?; - - linker.bind( - "network", - "total_fil_circ_supply", - network::total_fil_circ_supply, - )?; - linker.bind("network", "context", network::context)?; - linker.bind("network", "tipset_cid", network::tipset_cid)?; - - linker.bind("ipld", "block_open", ipld::block_open)?; - linker.bind("ipld", "block_create", ipld::block_create)?; - linker.bind("ipld", "block_read", ipld::block_read)?; - linker.bind("ipld", "block_stat", ipld::block_stat)?; - linker.bind("ipld", "block_link", ipld::block_link)?; - - linker.bind("self", "root", sself::root)?; - linker.bind("self", "set_root", sself::set_root)?; - linker.bind("self", "current_balance", sself::current_balance)?; - linker.bind("self", "self_destruct", sself::self_destruct)?; - - linker.bind("actor", "resolve_address", actor::resolve_address)?; - linker.bind( - "actor", - "lookup_delegated_address", - actor::lookup_delegated_address, - )?; - linker.bind("actor", "get_actor_code_cid", actor::get_actor_code_cid)?; - linker.bind("actor", "next_actor_address", actor::next_actor_address)?; - linker.bind("actor", "create_actor", actor::create_actor)?; - if cfg!(feature = "upgrade-actor") { - // We disable/enable with the feature, but we always compile this code to ensure we don't - // accidentally break it. - linker.bind("actor", "upgrade_actor", actor::upgrade_actor)?; +impl SyscallHandler for DefaultKernel +where + K: Kernel, +{ + fn bind_syscalls( + &self, + linker: &mut wasmtime::Linker>, + ) -> anyhow::Result<()> { + linker.bind("vm", "exit", vm::exit)?; + linker.bind("vm", "message_context", vm::message_context)?; + + linker.bind( + "network", + "total_fil_circ_supply", + network::total_fil_circ_supply, + )?; + linker.bind("network", "context", network::context)?; + linker.bind("network", "tipset_cid", network::tipset_cid)?; + + linker.bind("ipld", "block_open", ipld::block_open)?; + linker.bind("ipld", "block_create", ipld::block_create)?; + linker.bind("ipld", "block_read", ipld::block_read)?; + linker.bind("ipld", "block_stat", ipld::block_stat)?; + linker.bind("ipld", "block_link", ipld::block_link)?; + + linker.bind("self", "root", sself::root)?; + linker.bind("self", "set_root", sself::set_root)?; + linker.bind("self", "current_balance", sself::current_balance)?; + linker.bind("self", "self_destruct", sself::self_destruct)?; + + linker.bind("actor", "resolve_address", actor::resolve_address)?; + linker.bind( + "actor", + "lookup_delegated_address", + actor::lookup_delegated_address, + )?; + linker.bind("actor", "get_actor_code_cid", actor::get_actor_code_cid)?; + linker.bind("actor", "next_actor_address", actor::next_actor_address)?; + linker.bind("actor", "create_actor", actor::create_actor)?; + if cfg!(feature = "upgrade-actor") { + // We disable/enable with the feature, but we always compile this code to ensure we don't + // accidentally break it. + linker.bind("actor", "upgrade_actor", actor::upgrade_actor)?; + } + linker.bind( + "actor", + "get_builtin_actor_type", + actor::get_builtin_actor_type, + )?; + linker.bind( + "actor", + "get_code_cid_for_type", + actor::get_code_cid_for_type, + )?; + linker.bind("actor", "balance_of", actor::balance_of)?; + + // Only wire this syscall when M2 native is enabled. + if cfg!(feature = "m2-native") { + linker.bind("actor", "install_actor", actor::install_actor)?; + } + + linker.bind("crypto", "verify_signature", crypto::verify_signature)?; + linker.bind( + "crypto", + "recover_secp_public_key", + crypto::recover_secp_public_key, + )?; + linker.bind("crypto", "hash", crypto::hash)?; + + linker.bind("event", "emit_event", event::emit_event)?; + + linker.bind("rand", "get_chain_randomness", rand::get_chain_randomness)?; + linker.bind("rand", "get_beacon_randomness", rand::get_beacon_randomness)?; + + linker.bind("gas", "charge", gas::charge_gas)?; + linker.bind("gas", "available", gas::available)?; + + // Ok, this singled-out syscall should probably be in another category. + linker.bind("send", "send", send::send)?; + + linker.bind("debug", "log", debug::log)?; + linker.bind("debug", "enabled", debug::enabled)?; + linker.bind("debug", "store_artifact", debug::store_artifact)?; + + Ok(()) } - linker.bind( - "actor", - "get_builtin_actor_type", - actor::get_builtin_actor_type, - )?; - linker.bind( - "actor", - "get_code_cid_for_type", - actor::get_code_cid_for_type, - )?; - linker.bind("actor", "balance_of", actor::balance_of)?; - - // Only wire this syscall when M2 native is enabled. - #[cfg(feature = "m2-native")] - linker.bind("actor", "install_actor", actor::install_actor)?; - - linker.bind("crypto", "verify_signature", crypto::verify_signature)?; - linker.bind( - "crypto", - "recover_secp_public_key", - crypto::recover_secp_public_key, - )?; - linker.bind("crypto", "hash", crypto::hash)?; - linker.bind("crypto", "verify_post", crypto::verify_post)?; - linker.bind( - "crypto", - "compute_unsealed_sector_cid", - crypto::compute_unsealed_sector_cid, - )?; - linker.bind( - "crypto", - "verify_consensus_fault", - crypto::verify_consensus_fault, - )?; - linker.bind( - "crypto", - "verify_aggregate_seals", - crypto::verify_aggregate_seals, - )?; - linker.bind( - "crypto", - "verify_replica_update", - crypto::verify_replica_update, - )?; - linker.bind("crypto", "batch_verify_seals", crypto::batch_verify_seals)?; - - linker.bind("event", "emit_event", event::emit_event)?; - - linker.bind("rand", "get_chain_randomness", rand::get_chain_randomness)?; - linker.bind("rand", "get_beacon_randomness", rand::get_beacon_randomness)?; - - linker.bind("gas", "charge", gas::charge_gas)?; - linker.bind("gas", "available", gas::available)?; - - // Ok, this singled-out syscall should probably be in another category. - linker.bind("send", "send", send::send)?; - - linker.bind("debug", "log", debug::log)?; - linker.bind("debug", "enabled", debug::enabled)?; - linker.bind("debug", "store_artifact", debug::store_artifact)?; +} - Ok(()) +impl SyscallHandler>> + for DefaultFilecoinKernel> +where + C: CallManager, +{ + fn bind_syscalls( + &self, + linker: &mut Linker>>>, + ) -> anyhow::Result<()> { + self.0.bind_syscalls(linker)?; + + // Now bind the crypto syscalls. + linker.bind( + "crypto", + "compute_unsealed_sector_cid", + filecoin::compute_unsealed_sector_cid, + )?; + linker.bind("crypto", "verify_post", filecoin::verify_post)?; + linker.bind( + "crypto", + "verify_consensus_fault", + filecoin::verify_consensus_fault, + )?; + linker.bind( + "crypto", + "verify_aggregate_seals", + filecoin::verify_aggregate_seals, + )?; + linker.bind( + "crypto", + "verify_replica_update", + filecoin::verify_replica_update, + )?; + linker.bind("crypto", "batch_verify_seals", filecoin::batch_verify_seals)?; + + Ok(()) + } } diff --git a/sdk/src/actor.rs b/sdk/src/actor.rs index e2f96c272..2a6a709d0 100644 --- a/sdk/src/actor.rs +++ b/sdk/src/actor.rs @@ -124,7 +124,6 @@ pub fn upgrade_actor(new_code_cid: &Cid, params: Option) -> SyscallRe /// Installs or ensures an actor code CID is valid and loaded. /// Note: this is a privileged syscall, restricted to the init actor. -#[cfg(feature = "m2-native")] pub fn install_actor(code_cid: &Cid) -> SyscallResult<()> { let cid = code_cid.to_bytes(); unsafe { sys::actor::install_actor(cid.as_ptr()) } diff --git a/sdk/src/sys/actor.rs b/sdk/src/sys/actor.rs index f93c096dc..1d32bcb18 100644 --- a/sdk/src/sys/actor.rs +++ b/sdk/src/sys/actor.rs @@ -168,7 +168,6 @@ super::fvm_syscalls! { /// Installs and ensures actor code is valid and loaded. /// **Privileged:** May only be called by the init actor. - #[cfg(feature = "m2-native")] pub fn install_actor(cid_off: *const u8) -> Result<()>; /// Gets the balance of the specified actor. diff --git a/testing/conformance/src/vm.rs b/testing/conformance/src/vm.rs index 26ea2beb7..9e55c9903 100644 --- a/testing/conformance/src/vm.rs +++ b/testing/conformance/src/vm.rs @@ -5,6 +5,8 @@ use std::sync::{Arc, Mutex}; use anyhow::anyhow; use cid::Cid; +use fvm::kernel::filecoin::{DefaultFilecoinKernel, FilecoinKernel}; +use fvm::syscalls::InvocationData; use multihash::MultihashGeneric; use fvm::call_manager::{CallManager, DefaultCallManager}; @@ -26,11 +28,11 @@ use fvm_shared::piece::PieceInfo; use fvm_shared::randomness::RANDOMNESS_LENGTH; use fvm_shared::sector::{ AggregateSealVerifyProofAndInfos, RegisteredSealProof, ReplicaUpdateInfo, SealVerifyInfo, - WindowPoStVerifyInfo, }; use fvm_shared::sys::{EventEntry, SendFlags}; use fvm_shared::version::NetworkVersion; use fvm_shared::{ActorID, MethodNum, TOTAL_FILECOIN}; +use wasmtime::Linker; use crate::externs::TestExterns; use crate::vector::{MessageVector, Variant}; @@ -183,7 +185,10 @@ where /// A kernel for intercepting syscalls. // TestKernel is coupled to TestMachine because it needs to use that to plumb the // TestData through when it's destroyed into a CallManager then recreated by that CallManager. -pub struct TestKernel>>(pub K, pub TestData); +pub struct TestKernel>>>( + pub K, + pub TestData, +); impl Kernel for TestKernel where @@ -201,7 +206,7 @@ where } fn new( - mgr: Self::CallManager, + mgr: C, blocks: BlockRegistry, caller: ActorID, actor_id: ActorID, @@ -249,6 +254,24 @@ where self.0 .send::(recipient, method, params, value, gas_limit, flags) } + + fn upgrade_actor(&mut self, new_code_cid: Cid, params_id: BlockId) -> Result { + self.0.upgrade_actor::(new_code_cid, params_id) + } +} + +impl SyscallHandler> for TestKernel +where + M: Machine, + C: CallManager>, + K: Kernel, +{ + fn bind_syscalls( + &self, + _linker: &mut Linker>>, + ) -> anyhow::Result<()> { + Ok(()) + } } impl ActorOps for TestKernel @@ -278,6 +301,10 @@ where self.0.create_actor(code_id, actor_id, delegated_address) } + fn install_actor(&mut self, _code_id: Cid) -> Result<()> { + Ok(()) + } + fn get_builtin_actor_type(&self, code_cid: &Cid) -> Result { self.0.get_builtin_actor_type(code_cid) } @@ -286,11 +313,6 @@ where self.0.get_code_cid_for_type(typ) } - #[cfg(feature = "m2-native")] - fn install_actor(&mut self, _code_id: Cid) -> Result<()> { - Ok(()) - } - fn balance_of(&self, actor_id: ActorID) -> Result { self.0.balance_of(actor_id) } @@ -298,10 +320,6 @@ where fn lookup_delegated_address(&self, actor_id: ActorID) -> Result> { self.0.lookup_delegated_address(actor_id) } - - fn upgrade_actor(&mut self, new_code_cid: Cid, params_id: BlockId) -> Result { - self.0.upgrade_actor::(new_code_cid, params_id) - } } impl IpldBlockOps for TestKernel @@ -342,19 +360,12 @@ where Ok(self.1.circ_supply.clone()) } } - -impl CryptoOps for TestKernel +impl FilecoinKernel for TestKernel where M: Machine, C: CallManager>, - K: Kernel, + K: FilecoinKernel, { - // forwarded - fn hash(&self, code: u64, data: &[u8]) -> Result> { - self.0.hash(code, data) - } - - // forwarded fn compute_unsealed_sector_cid( &self, proof_type: RegisteredSealProof, @@ -363,25 +374,8 @@ where self.0.compute_unsealed_sector_cid(proof_type, pieces) } - // forwarded - fn verify_signature( - &self, - sig_type: SignatureType, - signature: &[u8], - signer: &Address, - plaintext: &[u8], - ) -> Result { - self.0 - .verify_signature(sig_type, signature, signer, plaintext) - } - - // forwarded - fn recover_secp_public_key( - &self, - hash: &[u8; SECP_SIG_MESSAGE_HASH_SIZE], - signature: &[u8; SECP_SIG_LEN], - ) -> Result<[u8; SECP_PUB_LEN]> { - self.0.recover_secp_public_key(hash, signature) + fn verify_post(&self, verify_info: &fvm_shared::sector::WindowPoStVerifyInfo) -> Result { + self.0.verify_post(verify_info) } // NOT forwarded @@ -389,13 +383,6 @@ where Ok(vec![true; vis.len()]) } - // NOT forwarded - fn verify_post(&self, vi: &WindowPoStVerifyInfo) -> Result { - let charge = self.1.price_list.on_verify_post(vi); - let _ = self.0.charge_gas(&charge.name, charge.total())?; - Ok(true) - } - // NOT forwarded fn verify_consensus_fault( &self, @@ -426,6 +413,39 @@ where } } +impl CryptoOps for TestKernel +where + M: Machine, + C: CallManager>, + K: Kernel, +{ + // forwarded + fn hash(&self, code: u64, data: &[u8]) -> Result> { + self.0.hash(code, data) + } + + // forwarded + fn verify_signature( + &self, + sig_type: SignatureType, + signature: &[u8], + signer: &Address, + plaintext: &[u8], + ) -> Result { + self.0 + .verify_signature(sig_type, signature, signer, plaintext) + } + + // forwarded + fn recover_secp_public_key( + &self, + hash: &[u8; SECP_SIG_MESSAGE_HASH_SIZE], + signature: &[u8; SECP_SIG_LEN], + ) -> Result<[u8; SECP_PUB_LEN]> { + self.0.recover_secp_public_key(hash, signature) + } +} + impl DebugOps for TestKernel where M: Machine, diff --git a/testing/integration/src/tester.rs b/testing/integration/src/tester.rs index a613598b6..6ba16b16d 100644 --- a/testing/integration/src/tester.rs +++ b/testing/integration/src/tester.rs @@ -6,6 +6,7 @@ use fvm::call_manager::DefaultCallManager; use fvm::engine::EnginePool; use fvm::executor::DefaultExecutor; use fvm::externs::Externs; +use fvm::kernel::filecoin::DefaultFilecoinKernel; use fvm::machine::{DefaultMachine, Machine, MachineContext, NetworkConfig}; use fvm::state_tree::{ActorState, StateTree}; use fvm::{init_actor, system_actor, DefaultKernel}; @@ -35,7 +36,7 @@ lazy_static! { pub trait Store: Blockstore + Sized + 'static {} pub type IntegrationExecutor = - DefaultExecutor>>>; + DefaultExecutor>>>>; pub type Account = (ActorID, Address); @@ -296,10 +297,9 @@ where let machine = DefaultMachine::new(&mc, blockstore, externs)?; - let executor = - DefaultExecutor::>>>::new( - engine, machine, - )?; + let executor = DefaultExecutor::< + DefaultFilecoinKernel>>>, + >::new(engine, machine)?; self.executor = Some(executor); self.ready = true; diff --git a/testing/test_actors/actors/fil-syscall-actor/src/actor.rs b/testing/test_actors/actors/fil-syscall-actor/src/actor.rs index 4b009e81a..927aa4ac8 100644 --- a/testing/test_actors/actors/fil-syscall-actor/src/actor.rs +++ b/testing/test_actors/actors/fil-syscall-actor/src/actor.rs @@ -40,7 +40,6 @@ pub fn invoke(_: u32) -> u32 { test_message_context(); test_balance(); test_unaligned(); - test_upgrade(); #[cfg(coverage)] sdk::debug::store_artifact("syscall_actor.profraw", minicov::capture_coverage()); @@ -378,15 +377,3 @@ fn test_unaligned() { assert_eq!(expected, actual); } } - -fn test_upgrade() { - // test that calling `upgrade_actor` on ourselves results in a SYS_INVALID_RECEIVER error - // since we don't have a upgrade endpoint - let code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10000)).unwrap(); - let res = sdk::actor::upgrade_actor(&code_cid, None).unwrap(); - - assert_eq!( - res.exit_code, - fvm_shared::error::ExitCode::SYS_INVALID_RECEIVER, - ); -}