diff --git a/Cargo.lock b/Cargo.lock index 6ec848814..ebb18c8e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1929,6 +1929,30 @@ dependencies = [ "multihash 0.18.1", ] +[[package]] +name = "fil_upgrade_actor" +version = "0.1.0" +dependencies = [ + "cid 0.10.1", + "fvm_ipld_encoding 0.4.0", + "fvm_sdk 4.0.0-alpha.4", + "fvm_shared 4.0.0-alpha.4", + "serde", + "serde_tuple", +] + +[[package]] +name = "fil_upgrade_receive_actor" +version = "0.1.0" +dependencies = [ + "cid 0.10.1", + "fvm_ipld_encoding 0.4.0", + "fvm_sdk 4.0.0-alpha.4", + "fvm_shared 4.0.0-alpha.4", + "serde", + "serde_tuple", +] + [[package]] name = "filecoin-hashers" version = "11.0.0" diff --git a/fvm/Cargo.toml b/fvm/Cargo.toml index 9275f8671..0bd945747 100644 --- a/fvm/Cargo.toml +++ b/fvm/Cargo.toml @@ -66,4 +66,5 @@ cuda-supraseal = ["filecoin-proofs-api/cuda-supraseal"] testing = [] arb = ["arbitrary", "quickcheck", "fvm_shared/arb", "cid/arb"] m2-native = [] +upgrade-actor = [] gas_calibration = [] diff --git a/fvm/src/call_manager/backtrace.rs b/fvm/src/call_manager/backtrace.rs index 12bf3c425..2f59a2419 100644 --- a/fvm/src/call_manager/backtrace.rs +++ b/fvm/src/call_manager/backtrace.rs @@ -4,10 +4,12 @@ use std::fmt::Display; use fvm_shared::address::Address; use fvm_shared::error::{ErrorNumber, ExitCode}; -use fvm_shared::{ActorID, MethodNum}; +use fvm_shared::ActorID; use crate::kernel::SyscallError; +use super::Entrypoint; + /// A call backtrace records the actors an error was propagated through, from /// the moment it was emitted. The original error is the _cause_. Backtraces are /// useful for identifying the root cause of an error. @@ -76,8 +78,8 @@ impl Backtrace { pub struct Frame { /// The actor that exited with this code. pub source: ActorID, - /// The method that was invoked. - pub method: MethodNum, + /// The entrypoint that was invoked. + pub entrypoint: Entrypoint, /// The exit code. pub code: ExitCode, /// The abort message. @@ -90,7 +92,7 @@ impl Display for Frame { f, "{} (method {}) -- {} ({})", Address::new_id(self.source), - self.method, + self.entrypoint, &self.message, self.code, ) diff --git a/fvm/src/call_manager/default.rs b/fvm/src/call_manager/default.rs index f92c573c6..c6ce6b98a 100644 --- a/fvm/src/call_manager/default.rs +++ b/fvm/src/call_manager/default.rs @@ -12,11 +12,11 @@ use fvm_shared::econ::TokenAmount; use fvm_shared::error::{ErrorNumber, ExitCode}; use fvm_shared::event::StampedEvent; use fvm_shared::sys::BlockId; -use fvm_shared::{ActorID, MethodNum, METHOD_SEND}; +use fvm_shared::{ActorID, METHOD_SEND}; use num_traits::Zero; use super::state_access_tracker::{ActorAccessState, StateAccessTracker}; -use super::{Backtrace, CallManager, InvocationResult, NO_DATA_BLOCK_ID}; +use super::{Backtrace, CallManager, Entrypoint, InvocationResult, NO_DATA_BLOCK_ID}; use crate::blockstore::DiscardBlockstore; use crate::call_manager::backtrace::Frame; use crate::call_manager::FinishRet; @@ -75,6 +75,8 @@ pub struct InnerDefaultCallManager { limits: M::Limiter, /// Accumulator for events emitted in this call stack. events: EventsAccumulator, + /// The actor call stack (ActorID and entrypoint name tuple). + actor_call_stack: Vec<(ActorID, &'static str)>, } #[doc(hidden)] @@ -138,7 +140,7 @@ where // different matter. // // NOTE: Technically, we should be _caching_ the existence of the receiver, so we can skip - // this step on `send` and create the target actor immediately. By not doing that, we're not + // this step on `call_actor` and create the target actor immediately. By not doing that, we're not // being perfectly efficient and are technically under-charging gas. HOWEVER, this behavior // cannot be triggered by an actor on-chain, so it's not a concern (for now). state_access_tracker.record_lookup_address(&receiver_address); @@ -159,6 +161,7 @@ where limits, events: Default::default(), state_access_tracker, + actor_call_stack: vec![], }))) } @@ -166,11 +169,11 @@ where &mut self.limits } - fn send( + fn call_actor( &mut self, from: ActorID, to: Address, - method: MethodNum, + entrypoint: Entrypoint, params: Option, value: &TokenAmount, gas_limit: Option, @@ -183,7 +186,7 @@ where self.trace(ExecutionEvent::Call { from, to, - method, + entrypoint, params: params.as_ref().map(Into::into), value: value.clone(), gas_limit: std::cmp::min( @@ -199,44 +202,13 @@ where self.gas_tracker.push_limit(limit); } - if self.call_stack_depth >= self.machine.context().max_call_depth { - let sys_err = syscall_error!(LimitExceeded, "message execution exceeds call depth"); - if self.machine.context().tracing { - self.trace(ExecutionEvent::CallError(sys_err.clone())); - } - return Err(sys_err.into()); - } - - self.state_tree_mut().begin_transaction(); - self.events.begin_transaction(); - self.state_access_tracker.begin_transaction(); - self.call_stack_depth += 1; - - let (revert, mut result) = match <::Limiter>::with_stack_frame( - self, - |s| s.limiter_mut(), - |s| s.send_unchecked::(from, to, method, params, value, read_only), - ) { - Ok(v) => (!v.exit_code.is_success(), Ok(v)), - Err(e) => (true, Err(e)), - }; + let mut result = self.with_stack_frame(|s| { + s.call_actor_unchecked::(from, to, entrypoint, params, value, read_only) + }); - self.call_stack_depth -= 1; - // Return the _first_ error (if any). We don't expect any errors here anyways as all error - // cases are fatal. - if let Some(err) = [ - // End all transactions - self.state_access_tracker.end_transaction(revert).err(), - self.events.end_transaction(revert).err(), - self.state_tree_mut().end_transaction(revert).err(), - // If we pushed a gas limit, pop it. - gas_limit.and_then(|_| self.gas_tracker.pop_limit().err()), - ] - .into_iter() - .flatten() // Iterator> -> Iterator - .next() - { - return Err(err); + // If we pushed a limit, pop it. + if gas_limit.is_some() { + self.gas_tracker.pop_limit()?; } // If we're not out of gas but the error is "out of gas" (e.g., due to a gas limit), replace @@ -268,6 +240,26 @@ where result } + fn with_transaction( + &mut self, + f: impl FnOnce(&mut Self) -> Result, + ) -> Result { + self.state_tree_mut().begin_transaction(); + self.events.begin_transaction(); + self.state_access_tracker.begin_transaction(); + + let (revert, res) = match f(self) { + Ok(v) => (!v.exit_code.is_success(), Ok(v)), + Err(e) => (true, Err(e)), + }; + + self.state_tree_mut().end_transaction(revert)?; + self.events.end_transaction(revert)?; + self.state_access_tracker.end_transaction(revert)?; + + res + } + fn finish(mut self) -> (Result, Self::Machine) { let InnerDefaultCallManager { machine, @@ -338,6 +330,10 @@ where self.nonce } + fn get_call_stack(&self) -> &[(ActorID, &'static str)] { + &self.actor_call_stack + } + fn next_actor_address(&self) -> Address { // Base the next address on the address specified as the message origin. This lets us use, // e.g., an f2 address even if we can't look it up anywhere. @@ -578,10 +574,10 @@ where syscall_error!(IllegalArgument; "failed to serialize params: {}", e) })?; - self.send_resolved::( + self.call_actor_resolved::( system_actor::SYSTEM_ACTOR_ID, id, - fvm_shared::METHOD_CONSTRUCTOR, + Entrypoint::Invoke(fvm_shared::METHOD_CONSTRUCTOR), Some(Block::new(CBOR, params, Vec::new())), &TokenAmount::zero(), false, @@ -598,13 +594,13 @@ where self.create_actor_from_send(addr, state) } - /// Send without checking the call depth and/or dealing with transactions. This must _only_ be - /// called from `send`. - fn send_unchecked( + /// Call actor without checking the call depth and/or dealing with transactions. This must _only_ be + /// called from `call_actor`. + fn call_actor_unchecked( &mut self, from: ActorID, to: Address, - method: MethodNum, + entrypoint: Entrypoint, params: Option, value: &TokenAmount, read_only: bool, @@ -638,15 +634,19 @@ where }, }; - self.send_resolved::(from, to, method, params, value, read_only) + self.actor_call_stack.push((to, entrypoint.func_name())); + let res = self.call_actor_resolved::(from, to, entrypoint, params, value, read_only); + self.actor_call_stack.pop(); + + res } - /// Send with resolved addresses. - fn send_resolved( + /// Call actor with resolved addresses. + fn call_actor_resolved( &mut self, from: ActorID, to: ActorID, - method: MethodNum, + entrypoint: Entrypoint, params: Option, value: &TokenAmount, read_only: bool, @@ -671,7 +671,7 @@ where } // Abort early if we have a send. - if method == METHOD_SEND { + if entrypoint.invokes(METHOD_SEND) { log::trace!("sent {} -> {}: {}", from, to, &value); return Ok(InvocationResult::default()); } @@ -694,6 +694,10 @@ where NO_DATA_BLOCK_ID }; + // additional_params takes care of adding entrypoint specific params to the block + // registry and passing them to wasmtime + let additional_params = entrypoint.into_params(&mut block_registry)?; + // Increment invocation count self.invocation_count += 1; @@ -707,7 +711,7 @@ where |_| syscall_error!(NotFound; "actor code cid does not exist {}", &state.code), )?; - log::trace!("calling {} -> {}::{}", from, to, method); + log::trace!("calling {} -> {}::{}", from, to, entrypoint); self.map_mut(|cm| { let engine = cm.engine.clone(); // reference the RC. @@ -717,7 +721,7 @@ where block_registry, from, to, - method, + entrypoint.method_num(), value.clone(), read_only, ); @@ -727,9 +731,10 @@ where // From this point on, there are no more syscall errors, only aborts. let result: std::result::Result = (|| { + let code = &state.code; // Instantiate the module. let instance = engine - .instantiate(&mut store, &state.code)? + .instantiate(&mut store, code)? .context("actor not found") .map_err(Abort::Fatal)?; @@ -741,18 +746,26 @@ where store.data_mut().memory = memory; - // Lookup the invoke method. - let invoke: wasmtime::TypedFunc<(u32,), u32> = instance - .get_typed_func(&mut store, "invoke") - // All actors will have an invoke method. - .map_err(Abort::Fatal)?; + let func = match instance.get_func(&mut store, entrypoint.func_name()) { + Some(func) => func, + None => { + return Err(Abort::Exit( + ExitCode::SYS_INVALID_RECEIVER, + format!("cannot upgrade to {code}"), + 0, + )); + } + }; + + let mut params = vec![wasmtime::Val::I32(params_id as i32)]; + params.extend_from_slice(additional_params.as_slice()); // Set the available gas. update_gas_available(&mut store)?; - // Invoke it. + let mut out = [wasmtime::Val::I32(0)]; let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - invoke.call(&mut store, (params_id,)) + func.call(&mut store, params.as_slice(), &mut out) })) .map_err(|panic| Abort::Fatal(anyhow!("panic within actor: {:?}", panic)))?; @@ -764,7 +777,10 @@ where // detected it and returned OutOfGas above. Any other invocation failure is returned // here as an Abort - Ok(res?) + match res { + Ok(_) => Ok(out[0].unwrap_i32() as u32), + Err(e) => Err(e.into()), + } })(); let invocation_data = store.into_data(); @@ -837,7 +853,7 @@ where cm.backtrace.push_frame(Frame { source: to, - method, + entrypoint, message, code, }); @@ -872,11 +888,11 @@ where Ok(val) => log::trace!( "returning {}::{} -> {} ({})", to, - method, + entrypoint, from, val.exit_code ), - Err(e) => log::trace!("failing {}::{} -> {} (err:{})", to, method, from, e), + Err(e) => log::trace!("failing {}::{} -> {} (err:{})", to, entrypoint, from, e), } } @@ -894,6 +910,31 @@ where { replace_with::replace_with_and_return(self, || DefaultCallManager(None), f) } + + /// Check that we're not violating the call stack depth, then envelope a call + /// with an increase/decrease of the depth to make sure none of them are missed. + fn with_stack_frame(&mut self, f: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + { + if self.call_stack_depth >= self.machine.context().max_call_depth { + let sys_err = syscall_error!(LimitExceeded, "message execution exceeds call depth"); + if self.machine.context().tracing { + self.trace(ExecutionEvent::CallError(sys_err.clone())); + } + return Err(sys_err.into()); + } + + self.call_stack_depth += 1; + let res = + << as CallManager>::Machine as Machine>::Limiter>::with_stack_frame( + self, + |s| s.limiter_mut(), + f, + ); + self.call_stack_depth -= 1; + res + } } /// Stores events in layers as they are emitted by actors. As the call stack progresses, when an diff --git a/fvm/src/call_manager/mod.rs b/fvm/src/call_manager/mod.rs index 03f4ec638..680c8ecda 100644 --- a/fvm/src/call_manager/mod.rs +++ b/fvm/src/call_manager/mod.rs @@ -1,14 +1,16 @@ // Copyright 2021-2023 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT use cid::Cid; +use fvm_ipld_encoding::{to_vec, CBOR}; use fvm_shared::address::Address; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; +use fvm_shared::upgrade::UpgradeInfo; use fvm_shared::{ActorID, MethodNum}; use crate::engine::Engine; use crate::gas::{Gas, GasCharge, GasTimer, GasTracker, PriceList}; -use crate::kernel::{self, Result}; +use crate::kernel::{self, BlockRegistry, ClassifyResult, Context, Result}; use crate::machine::{Machine, MachineContext}; use crate::state_tree::ActorState; use crate::Kernel; @@ -33,13 +35,13 @@ pub const NO_DATA_BLOCK_ID: u32 = 0; /// /// 1. The [`crate::executor::Executor`] creates a [`CallManager`] for that message, giving itself /// to the [`CallManager`]. -/// 2. The [`crate::executor::Executor`] calls the specified actor/method using -/// [`CallManager::send()`]. +/// 2. The [`crate::executor::Executor`] calls the specified actor/entrypoint using +/// [`CallManager::call_actor()`]. /// 3. The [`CallManager`] then constructs a [`Kernel`] and executes the actual actor code on that /// kernel. /// 4. If an actor calls another actor, the [`Kernel`] will: /// 1. Detach the [`CallManager`] from itself. -/// 2. Call [`CallManager::send()`] to execute the new message. +/// 2. Call [`CallManager::call_actor()`] to execute the new message. /// 3. Re-attach the [`CallManager`]. /// 4. Return. pub trait CallManager: 'static { @@ -60,20 +62,26 @@ pub trait CallManager: 'static { gas_premium: TokenAmount, ) -> Self; - /// Send a message. The type parameter `K` specifies the the _kernel_ on top of which the target + /// Calls an actor at the given address and entrypoint. The type parameter `K` specifies the the _kernel_ on top of which the target /// actor should execute. #[allow(clippy::too_many_arguments)] - fn send>( + fn call_actor>( &mut self, from: ActorID, to: Address, - method: MethodNum, + entrypoint: Entrypoint, params: Option, value: &TokenAmount, gas_limit: Option, read_only: bool, ) -> Result; + /// Execute some operation (usually a call_actor) within a transaction. + fn with_transaction( + &mut self, + f: impl FnOnce(&mut Self) -> Result, + ) -> Result; + /// Finishes execution, returning the gas used, machine, and exec trace if requested. fn finish(self) -> (Result, Self::Machine); @@ -111,6 +119,9 @@ pub trait CallManager: 'static { delegated_address: Option
, ) -> Result<()>; + // returns the actor call stack + fn get_call_stack(&self) -> &[(ActorID, &'static str)]; + /// Resolve an address into an actor ID, charging gas as appropriate. fn resolve_address(&self, address: &Address) -> Result>; @@ -165,7 +176,7 @@ pub trait CallManager: 'static { fn append_event(&mut self, evt: StampedEvent); } -/// The result of a method invocation. +/// The result of calling actor's entrypoint #[derive(Clone, Debug)] pub struct InvocationResult { /// The exit code (0 for success). @@ -191,3 +202,60 @@ pub struct FinishRet { pub events: Vec, pub events_root: Option, } + +#[derive(Clone, Debug, Copy)] +pub enum Entrypoint { + Invoke(MethodNum), + Upgrade(UpgradeInfo), +} + +pub static INVOKE_FUNC_NAME: &str = "invoke"; +pub static UPGRADE_FUNC_NAME: &str = "upgrade"; + +const METHOD_UPGRADE: MethodNum = 932083; + +impl std::fmt::Display for Entrypoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Entrypoint::Invoke(method) => write!(f, "invoke({})", method), + Entrypoint::Upgrade(_) => write!(f, "upgrade"), + } + } +} + +impl Entrypoint { + fn method_num(&self) -> MethodNum { + match self { + Entrypoint::Invoke(num) => *num, + Entrypoint::Upgrade(_) => METHOD_UPGRADE, + } + } + + fn func_name(&self) -> &'static str { + match self { + Entrypoint::Invoke(_) => INVOKE_FUNC_NAME, + Entrypoint::Upgrade(_) => UPGRADE_FUNC_NAME, + } + } + + fn invokes(&self, method: MethodNum) -> bool { + match self { + Entrypoint::Invoke(num) => *num == method, + Entrypoint::Upgrade(_) => false, + } + } + + fn into_params(self, br: &mut BlockRegistry) -> Result> { + match self { + Entrypoint::Invoke(_) => Ok(Vec::new()), + Entrypoint::Upgrade(ui) => { + let ui_params = to_vec(&ui) + .or_fatal() + .context("failed to serialize upgrade params")?; + // This is CBOR instead of DAG_CBOR because these params are not reachable + let block_id = br.put_reachable(kernel::Block::new(CBOR, ui_params, Vec::new()))?; + Ok(vec![wasmtime::Val::I32(block_id as i32)]) + } + } + } +} diff --git a/fvm/src/executor/default.rs b/fvm/src/executor/default.rs index 0bc982140..69628221c 100644 --- a/fvm/src/executor/default.rs +++ b/fvm/src/executor/default.rs @@ -16,7 +16,7 @@ use fvm_shared::{ActorID, IPLD_RAW, METHOD_SEND}; use num_traits::Zero; use super::{ApplyFailure, ApplyKind, ApplyRet, Executor}; -use crate::call_manager::{backtrace, Backtrace, CallManager, InvocationResult}; +use crate::call_manager::{backtrace, Backtrace, CallManager, Entrypoint, InvocationResult}; use crate::eam_actor::EAM_ACTOR_ID; use crate::engine::EnginePool; use crate::gas::{Gas, GasCharge, GasOutputs}; @@ -141,17 +141,19 @@ where ) }); - // Invoke the message. We charge for the return value internally if the call-stack depth - // is 1. - let result = cm.send::( - sender_id, - msg.to, - msg.method_num, - params, - &msg.value, - None, - false, - ); + let result = cm.with_transaction(|cm| { + // Invoke the message. We charge for the return value internally if the call-stack depth + // is 1. + cm.call_actor::( + sender_id, + msg.to, + Entrypoint::Invoke(msg.method_num), + params, + &msg.value, + None, + false, + ) + }); let (res, machine) = match cm.finish() { (Ok(res), machine) => (res, machine), diff --git a/fvm/src/kernel/default.rs b/fvm/src/kernel/default.rs index 283e7c2d2..6e20873a0 100644 --- a/fvm/src/kernel/default.rs +++ b/fvm/src/kernel/default.rs @@ -19,6 +19,7 @@ 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 multihash::MultihashDigest; @@ -29,7 +30,10 @@ use super::blocks::{Block, BlockRegistry}; use super::error::Result; use super::hash::SupportedHashes; use super::*; -use crate::call_manager::{CallManager, InvocationResult, NO_DATA_BLOCK_ID}; +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::gas::GasTimer; use crate::init_actor::INIT_ACTOR_ID; @@ -116,7 +120,7 @@ where value: &TokenAmount, gas_limit: Option, flags: SendFlags, - ) -> Result { + ) -> Result { let from = self.actor_id; let read_only = self.read_only || flags.read_only(); @@ -137,9 +141,17 @@ where } // Send. - let result = self.call_manager.send::( - from, *recipient, method, params, value, gas_limit, read_only, - )?; + let result = self.call_manager.with_transaction(|cm| { + cm.call_actor::( + from, + *recipient, + Entrypoint::Invoke(method), + params, + value, + gas_limit, + read_only, + ) + })?; // Store result and return. Ok(match result { @@ -157,7 +169,7 @@ where .put_reachable(blk) .or_fatal() .context("failed to store a valid return value")?; - SendResult { + CallResult { block_id, block_stat, exit_code, @@ -166,7 +178,7 @@ where InvocationResult { exit_code, value: None, - } => SendResult { + } => CallResult { block_id: NO_DATA_BLOCK_ID, block_stat: BlockStat { codec: 0, size: 0 }, exit_code, @@ -864,6 +876,100 @@ 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, + )?; + + 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), + } + } + fn get_builtin_actor_type(&self, code_cid: &Cid) -> Result { let t = self .call_manager diff --git a/fvm/src/kernel/error.rs b/fvm/src/kernel/error.rs index c909a6a87..257b5dfbf 100644 --- a/fvm/src/kernel/error.rs +++ b/fvm/src/kernel/error.rs @@ -54,16 +54,6 @@ impl ExecutionError { OutOfGas | Syscall(_) => false, } } - - /// Returns true if an actor can catch the error. All errors except fatal and out of gas errors - /// are recoverable. - pub fn is_recoverable(&self) -> bool { - use ExecutionError::*; - match self { - OutOfGas | Fatal(_) => false, - Syscall(_) => true, - } - } } // NOTE: this is the _only_ from impl we provide. Otherwise, we expect the user to explicitly diff --git a/fvm/src/kernel/mod.rs b/fvm/src/kernel/mod.rs index dcf5c547f..02a5f38ef 100644 --- a/fvm/src/kernel/mod.rs +++ b/fvm/src/kernel/mod.rs @@ -38,7 +38,7 @@ use crate::gas::{Gas, GasTimer, PriceList}; use crate::machine::limiter::MemoryLimiter; use crate::machine::Machine; -pub struct SendResult { +pub struct CallResult { pub block_id: BlockId, pub block_stat: BlockStat, pub exit_code: ExitCode, @@ -112,7 +112,7 @@ pub trait Kernel: value: &TokenAmount, gas_limit: Option, flags: SendFlags, - ) -> Result; + ) -> Result; } /// Network-related operations. @@ -209,6 +209,12 @@ 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<()>; diff --git a/fvm/src/syscalls/actor.rs b/fvm/src/syscalls/actor.rs index 7376631e9..4c790a0db 100644 --- a/fvm/src/syscalls/actor.rs +++ b/fvm/src/syscalls/actor.rs @@ -3,8 +3,10 @@ use anyhow::{anyhow, Context as _}; use fvm_shared::{sys, ActorID}; +use super::bind::ControlFlow; +use super::error::Abort; use super::Context; -use crate::kernel::{ClassifyResult, Result}; +use crate::kernel::{CallResult, ClassifyResult, Result}; use crate::{syscall_error, Kernel}; pub fn resolve_address( @@ -109,6 +111,37 @@ pub fn create_actor( context.kernel.create_actor(typ, actor_id, addr) } +pub fn upgrade_actor( + context: Context<'_, K>, + new_code_cid_off: u32, + params_id: u32, +) -> ControlFlow { + let cid = match context.memory.read_cid(new_code_cid_off) { + Ok(cid) => cid, + Err(err) => return err.into(), + }; + + match context.kernel.upgrade_actor::(cid, params_id) { + Ok(CallResult { + block_id, + block_stat, + exit_code, + }) => { + if exit_code.is_success() { + ControlFlow::Abort(Abort::Exit(exit_code, String::new(), block_id)) + } else { + ControlFlow::Return(sys::out::send::Send { + exit_code: exit_code.value(), + return_id: block_id, + return_codec: block_stat.codec, + return_size: block_stat.size, + }) + } + } + Err(err) => err.into(), + } +} + pub fn get_builtin_actor_type( context: Context<'_, impl Kernel>, code_cid_off: u32, // Cid diff --git a/fvm/src/syscalls/bind.rs b/fvm/src/syscalls/bind.rs index 5ebb2d765..5ab2598b6 100644 --- a/fvm/src/syscalls/bind.rs +++ b/fvm/src/syscalls/bind.rs @@ -49,38 +49,71 @@ pub(super) trait BindSyscall { ) -> anyhow::Result<&mut Self>; } +/// ControlFlow is a general-purpose enum allowing us to pass syscall error up the +/// stack to the actor and treat error handling there (decide when to abort, etc). +pub enum ControlFlow { + Return(T), + Error(SyscallError), + Abort(Abort), +} + +impl From for ControlFlow { + fn from(value: ExecutionError) -> Self { + match value { + ExecutionError::Syscall(err) => ControlFlow::Error(err), + ExecutionError::Fatal(err) => ControlFlow::Abort(Abort::Fatal(err)), + ExecutionError::OutOfGas => ControlFlow::Abort(Abort::OutOfGas), + } + } +} + /// The helper trait used by `BindSyscall` to convert kernel results with execution errors into /// results that can be handled by wasmtime. See the documentation on `BindSyscall` for details. #[doc(hidden)] -pub trait IntoSyscallResult: Sized { +pub trait IntoControlFlow: Sized { type Value: SyscallSafe; - fn into(self) -> Result, Abort>; + fn into_control_flow(self) -> ControlFlow; +} + +/// An uninhabited type. We use this in `abort` to make sure there's no way to return without +/// returning an error. +#[derive(Copy, Clone)] +#[doc(hidden)] +pub enum Never {} +unsafe impl SyscallSafe for Never {} + +// Implementations for syscalls that always abort. +impl IntoControlFlow for Abort { + type Value = Never; + fn into_control_flow(self) -> ControlFlow { + ControlFlow::Abort(self) + } } -// Implementations for syscalls that abort on error. -impl IntoSyscallResult for Result +// Implementations for syscalls that can abort. +impl IntoControlFlow for ControlFlow where T: SyscallSafe, { type Value = T; - fn into(self) -> Result, Abort> { - Ok(Ok(self?)) + fn into_control_flow(self) -> ControlFlow { + self } } // Implementations for normal syscalls. -impl IntoSyscallResult for kernel::Result +impl IntoControlFlow for kernel::Result where T: SyscallSafe, { type Value = T; - fn into(self) -> Result, Abort> { + fn into_control_flow(self) -> ControlFlow { match self { - Ok(value) => Ok(Ok(value)), + Ok(value) => ControlFlow::Return(value), Err(e) => match e { - ExecutionError::Syscall(err) => Ok(Err(err)), - ExecutionError::OutOfGas => Err(Abort::OutOfGas), - ExecutionError::Fatal(err) => Err(Abort::Fatal(err)), + ExecutionError::Syscall(err) => ControlFlow::Error(err), + ExecutionError::OutOfGas => ControlFlow::Abort(Abort::OutOfGas), + ExecutionError::Fatal(err) => ControlFlow::Abort(Abort::Fatal(err)), }, } } @@ -111,7 +144,7 @@ macro_rules! impl_bind_syscalls { where K: Kernel, Func: Fn(Context<'_, K> $(, $t)*) -> Ret + Send + Sync + 'static, - Ret: IntoSyscallResult, + Ret: IntoControlFlow, $($t: WasmTy+SyscallSafe,)* { fn bind( @@ -129,21 +162,21 @@ macro_rules! impl_bind_syscalls { charge_syscall_gas!(data.kernel); let ctx = Context{kernel: &mut data.kernel, memory: &mut memory}; - let out = syscall(ctx $(, $t)*).into(); + let out = syscall(ctx $(, $t)*).into_control_flow(); let result = match out { - Ok(Ok(_)) => { + ControlFlow::Return(_) => { log::trace!("syscall {}::{}: ok", module, name); data.last_error = None; Ok(0) }, - Ok(Err(err)) => { + ControlFlow::Error(err) => { let code = err.1; log::trace!("syscall {}::{}: fail ({})", module, name, code as u32); data.last_error = Some(backtrace::Cause::from_syscall(module, name, err)); Ok(code as u32) }, - Err(e) => Err(e.into()), + ControlFlow::Abort(abort) => Err(abort.into()), }; update_gas_available(&mut caller)?; @@ -167,8 +200,8 @@ macro_rules! impl_bind_syscalls { } let ctx = Context{kernel: &mut data.kernel, memory: &mut memory}; - let result = match syscall(ctx $(, $t)*).into() { - Ok(Ok(value)) => { + let result = match syscall(ctx $(, $t)*).into_control_flow() { + ControlFlow::Return(value) => { log::trace!("syscall {}::{}: ok", module, name); unsafe { // We're writing into a user-specified pointer, so avoid @@ -178,13 +211,13 @@ macro_rules! impl_bind_syscalls { data.last_error = None; Ok(0) }, - Ok(Err(err)) => { + ControlFlow::Error(err) => { let code = err.1; log::trace!("syscall {}::{}: fail ({})", module, name, code as u32); data.last_error = Some(backtrace::Cause::from_syscall(module, name, err)); Ok(code as u32) }, - Err(e) => Err(e.into()), + ControlFlow::Abort(abort) => Err(abort.into()), }; update_gas_available(&mut caller)?; diff --git a/fvm/src/syscalls/error.rs b/fvm/src/syscalls/error.rs index b7193e543..1dbd81aaa 100644 --- a/fvm/src/syscalls/error.rs +++ b/fvm/src/syscalls/error.rs @@ -78,6 +78,7 @@ impl From for Abort { _ => Abort::Fatal(anyhow!("unexpected wasmtime trap: {}", trap)), }; }; + match e.downcast::() { Ok(abort) => abort, Err(e) => Abort::Fatal(e), diff --git a/fvm/src/syscalls/mod.rs b/fvm/src/syscalls/mod.rs index 26cbf242a..81490ccdf 100644 --- a/fvm/src/syscalls/mod.rs +++ b/fvm/src/syscalls/mod.rs @@ -269,6 +269,11 @@ pub fn bind_syscalls( 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", diff --git a/fvm/src/syscalls/send.rs b/fvm/src/syscalls/send.rs index edf51cb1f..fbb450d42 100644 --- a/fvm/src/syscalls/send.rs +++ b/fvm/src/syscalls/send.rs @@ -7,7 +7,7 @@ use fvm_shared::sys::{self, SendFlags}; use super::Context; use crate::gas::Gas; -use crate::kernel::{ClassifyResult, Result, SendResult}; +use crate::kernel::{CallResult, ClassifyResult, Result}; use crate::Kernel; /// Send a message to another actor. The result is placed as a CBOR-encoded @@ -37,7 +37,7 @@ pub fn send( // An execution error here means that something went wrong in the FVM. // Actor errors are communicated in the receipt. - let SendResult { + let CallResult { block_id, block_stat, exit_code, diff --git a/fvm/src/syscalls/vm.rs b/fvm/src/syscalls/vm.rs index 8b8834997..1e5ad4218 100644 --- a/fvm/src/syscalls/vm.rs +++ b/fvm/src/syscalls/vm.rs @@ -2,19 +2,11 @@ // SPDX-License-Identifier: Apache-2.0, MIT use fvm_shared::error::ExitCode; use fvm_shared::sys::out::vm::MessageContext; -use fvm_shared::sys::SyscallSafe; use super::error::Abort; use super::Context; use crate::kernel::Kernel; -/// An uninhabited type. We use this in `abort` to make sure there's no way to return without -/// returning an error. -#[derive(Copy, Clone)] -pub enum Never {} - -unsafe impl SyscallSafe for Never {} - /// The maximum message length included in the backtrace. Given 1024 levels, this gives us a total /// maximum of around 1MiB for debugging. const MAX_MESSAGE_LEN: usize = 1024; @@ -26,14 +18,14 @@ pub fn exit( blk: u32, message_off: u32, message_len: u32, -) -> Result { +) -> Abort { let code = ExitCode::new(code); if !code.is_success() && code.is_system_error() { - return Err(Abort::Exit( + return Abort::Exit( ExitCode::SYS_ILLEGAL_EXIT_CODE, format!("actor aborted with code {}", code), blk, - )); + ); } let message = if message_len == 0 { @@ -57,7 +49,7 @@ pub fn exit( Err(e) => format!("failed to extract error message: {e}"), } }; - Err(Abort::Exit(code, message, blk)) + Abort::Exit(code, message, blk) } pub fn message_context(context: Context<'_, impl Kernel>) -> crate::kernel::Result { diff --git a/fvm/src/trace/mod.rs b/fvm/src/trace/mod.rs index 745410773..796123f43 100644 --- a/fvm/src/trace/mod.rs +++ b/fvm/src/trace/mod.rs @@ -4,8 +4,9 @@ use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::address::Address; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; -use fvm_shared::{ActorID, MethodNum}; +use fvm_shared::ActorID; +use crate::call_manager::Entrypoint; use crate::gas::GasCharge; use crate::kernel::SyscallError; use crate::Cid; @@ -25,7 +26,7 @@ pub enum ExecutionEvent { Call { from: ActorID, to: Address, - method: MethodNum, + entrypoint: Entrypoint, params: Option, value: TokenAmount, gas_limit: u64, diff --git a/fvm/tests/dummy.rs b/fvm/tests/dummy.rs index 8fd2ea9ad..1faf483c0 100644 --- a/fvm/tests/dummy.rs +++ b/fvm/tests/dummy.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use anyhow::Context; use cid::Cid; -use fvm::call_manager::{Backtrace, CallManager, FinishRet, InvocationResult}; +use fvm::call_manager::{Backtrace, CallManager, Entrypoint, FinishRet, InvocationResult}; use fvm::engine::Engine; use fvm::externs::{Chain, Consensus, Externs, Rand}; use fvm::gas::{Gas, GasCharge, GasTimer, GasTracker}; @@ -274,11 +274,11 @@ impl CallManager for DummyCallManager { } } - fn send>( + fn call_actor>( &mut self, _from: fvm_shared::ActorID, _to: Address, - _method: fvm_shared::MethodNum, + _entrypoint: Entrypoint, _params: Option, _value: &fvm_shared::econ::TokenAmount, _gas_limit: Option, @@ -288,6 +288,14 @@ impl CallManager for DummyCallManager { todo!() } + fn with_transaction( + &mut self, + _f: impl FnOnce(&mut Self) -> kernel::Result, + ) -> kernel::Result { + // Ok(InvocationResult::Return(None)) + todo!() + } + fn finish(self) -> (kernel::Result, Self::Machine) { ( Ok(FinishRet { @@ -350,6 +358,10 @@ impl CallManager for DummyCallManager { todo!() } + fn get_call_stack(&self) -> &[(ActorID, &'static str)] { + todo!() + } + fn invocation_count(&self) -> u64 { todo!() } diff --git a/sdk/src/actor.rs b/sdk/src/actor.rs index 3aa6c283f..e2f96c272 100644 --- a/sdk/src/actor.rs +++ b/sdk/src/actor.rs @@ -1,16 +1,16 @@ // Copyright 2021-2023 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use core::option::Option; use std::ptr; // no_std use cid::Cid; +use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::address::{Address, Payload, MAX_ADDRESS_LEN}; use fvm_shared::econ::TokenAmount; use fvm_shared::error::ErrorNumber; -use fvm_shared::{ActorID, MAX_CID_LEN}; +use fvm_shared::{ActorID, Response, MAX_CID_LEN}; use log::error; -use crate::{sys, SyscallResult}; +use crate::{build_response, sys, SyscallResult, NO_DATA_BLOCK_ID}; /// Resolves the ID address of an actor. Returns `None` if the address cannot be resolved. /// Successfully resolving an address doesn't necessarily mean the actor exists (e.g., if the @@ -107,6 +107,21 @@ pub fn create_actor( } } +/// Upgrades an actor using the given block which includes the old code cid and the upgrade params +pub fn upgrade_actor(new_code_cid: &Cid, params: Option) -> SyscallResult { + unsafe { + let cid = new_code_cid.to_bytes(); + + let params_id = match params { + Some(p) => sys::ipld::block_create(p.codec, p.data.as_ptr(), p.data.len() as u32)?, + None => NO_DATA_BLOCK_ID, + }; + + let send = sys::actor::upgrade_actor(cid.as_ptr(), params_id)?; + build_response(send) + } +} + /// 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")] diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 5ce05a36f..f1a362905 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -46,3 +46,30 @@ pub fn initialize() { debug::init_logging(); vm::set_panic_handler(); } + +fn build_response(send: fvm_shared::sys::out::send::Send) -> SyscallResult { + let exit_code = fvm_shared::error::ExitCode::new(send.exit_code); + let return_data = if send.return_id == NO_DATA_BLOCK_ID { + None + } else { + // Allocate a buffer to read the return data. + let mut bytes = vec![0; send.return_size as usize]; + + unsafe { + // Now read the return data. + let unread = + sys::ipld::block_read(send.return_id, 0, bytes.as_mut_ptr(), send.return_size)?; + assert_eq!(0, unread); + } + + Some(fvm_ipld_encoding::ipld_block::IpldBlock { + codec: send.return_codec, + data: bytes.to_vec(), + }) + }; + + Ok(fvm_shared::Response { + exit_code, + return_data, + }) +} diff --git a/sdk/src/send.rs b/sdk/src/send.rs index dc02f81df..5d54785ca 100644 --- a/sdk/src/send.rs +++ b/sdk/src/send.rs @@ -5,11 +5,11 @@ use std::convert::TryInto; use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::address::Address; use fvm_shared::econ::TokenAmount; -use fvm_shared::error::{ErrorNumber, ExitCode}; +use fvm_shared::error::ErrorNumber; use fvm_shared::sys::SendFlags; use fvm_shared::{MethodNum, Response}; -use crate::{sys, SyscallResult, NO_DATA_BLOCK_ID}; +use crate::{build_response, sys, SyscallResult, NO_DATA_BLOCK_ID}; /// Sends a message to another actor. pub fn send( @@ -33,12 +33,7 @@ pub fn send( }; // Perform the syscall to send the message. - let fvm_shared::sys::out::send::Send { - exit_code, - return_id, - return_codec, - return_size, - } = sys::send::send( + let send = sys::send::send( recipient.as_ptr(), recipient.len() as u32, method, @@ -49,26 +44,6 @@ pub fn send( flags, )?; - // Process the result. - let exit_code = ExitCode::new(exit_code); - let return_data = if return_id == NO_DATA_BLOCK_ID { - None - } else { - // Allocate a buffer to read the return data. - let mut bytes = vec![0; return_size as usize]; - - // Now read the return data. - let unread = sys::ipld::block_read(return_id, 0, bytes.as_mut_ptr(), return_size)?; - assert_eq!(0, unread); - Some(IpldBlock { - codec: return_codec, - data: bytes.to_vec(), - }) - }; - - Ok(Response { - exit_code, - return_data, - }) + build_response(send) } } diff --git a/sdk/src/sys/actor.rs b/sdk/src/sys/actor.rs index af8024729..f93c096dc 100644 --- a/sdk/src/sys/actor.rs +++ b/sdk/src/sys/actor.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0, MIT //! Syscalls for creating and resolving actors. +#[doc(inline)] +pub use fvm_shared::sys::out::send::*; + // for documentation links #[cfg(doc)] use crate::sys::ErrorNumber::*; @@ -128,6 +131,41 @@ super::fvm_syscalls! { delegated_addr_len: u32, ) -> Result<()>; + + /// Atomically transition to the new actor code. On success, this syscall does not return to the + /// current actor. Instead, the target actor "replaces" the invocation. + /// + /// # Parameters + /// + /// - `new_code_cid_off` is the offset (in wasm memory) of the code CID to upgrade _to_. + /// - `params` is the IPLD block handle passed to the new code's `upgrade` wasm endpoint. + /// + /// # Returns + /// + /// On successful upgrade, this syscall will not return. Instead, the current invocation will + /// "complete" and the return value will be the block returned by the new code's `upgrade` endpoint. + /// + /// If the new code rejects the upgrade (aborts) or performs an illegal operation, this syscall will + /// return the exit code plus the error returned by the upgrade endpoint. + /// + /// Finally, the syscall will return an error if it fails to call the upgrade endpoint entirely. + /// + /// # Errors + /// + /// | Error | Reason | + /// |-----------------------|-----------------------------------------------------------------| + /// | [`NotFound`] | no code with the specified CID has been deployed. | + /// | [`IllegalOperation`] | the actor has been deleted. | + /// | [`InvalidHandle`] | parameters block not found. | + /// | [`LimitExceeded`] | recursion limit reached. | + /// | [`IllegalArgument`] | invalid code cid buffer. | + /// | [`Forbidden`] | the actor is not allowed to upgrade (e.g., due to re-entrency). | + /// | [`ReadOnly`] | the actor is executing in read-only mode. | + pub fn upgrade_actor( + new_code_cid_off: *const u8, + params: u32, + ) -> Result; + /// Installs and ensures actor code is valid and loaded. /// **Privileged:** May only be called by the init actor. #[cfg(feature = "m2-native")] diff --git a/shared/src/error/mod.rs b/shared/src/error/mod.rs index fb4a06a9a..3f4712304 100644 --- a/shared/src/error/mod.rs +++ b/shared/src/error/mod.rs @@ -64,7 +64,8 @@ impl ExitCode { //pub const SYS_RESERVED_3 ExitCode = ExitCode::new(3); /// The message receiver trapped (panicked). pub const SYS_ILLEGAL_INSTRUCTION: ExitCode = ExitCode::new(4); - /// The message receiver doesn't exist and can't be automatically created + /// The message receiver either doesn't exist and can't be automatically created or it doesn't + /// implement the required entrypoint. pub const SYS_INVALID_RECEIVER: ExitCode = ExitCode::new(5); /// The message sender didn't have the requisite funds. pub const SYS_INSUFFICIENT_FUNDS: ExitCode = ExitCode::new(6); diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 765281597..1b31f717a 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -29,6 +29,7 @@ pub mod sector; pub mod smooth; pub mod state; pub mod sys; +pub mod upgrade; pub mod version; use econ::TokenAmount; diff --git a/shared/src/upgrade/mod.rs b/shared/src/upgrade/mod.rs new file mode 100644 index 000000000..557f45abd --- /dev/null +++ b/shared/src/upgrade/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use cid::Cid; +use fvm_ipld_encoding::tuple::*; + +#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct UpgradeInfo { + // the old code cid we are upgrading from + pub old_code_cid: Cid, +} diff --git a/testing/conformance/src/vm.rs b/testing/conformance/src/vm.rs index 4ee681b49..26ea2beb7 100644 --- a/testing/conformance/src/vm.rs +++ b/testing/conformance/src/vm.rs @@ -241,7 +241,7 @@ where value: &TokenAmount, gas_limit: Option, flags: SendFlags, - ) -> Result { + ) -> Result { // Note that KK, the type of the kernel to crate for the receiving actor, is ignored, // and Self is passed as the type parameter for the nested call. // If we could find the correct bound to specify KK for the call, we would. @@ -298,6 +298,10 @@ 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 diff --git a/testing/integration/Cargo.toml b/testing/integration/Cargo.toml index b5660f33c..11de6a4a5 100644 --- a/testing/integration/Cargo.toml +++ b/testing/integration/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Protocol Labs", "Filecoin Core Devs", "Polyphene"] repository = "https://github.com/filecoin-project/ref-fvm" [dependencies] -fvm = { version = "4.0.0-alpha.4", path = "../../fvm", default-features = false, features = ["testing"] } +fvm = { version = "4.0.0-alpha.4", path = "../../fvm", default-features = false, features = ["testing", "upgrade-actor"] } fvm_shared = { version = "4.0.0-alpha.4", path = "../../shared", features = ["testing"] } fvm_ipld_car = { version = "0.7.1", path = "../../ipld/car" } fvm_ipld_blockstore = { version = "0.2.0", path = "../../ipld/blockstore" } diff --git a/testing/integration/tests/address_test.rs b/testing/integration/tests/address_test.rs deleted file mode 100644 index da9b061a9..000000000 --- a/testing/integration/tests/address_test.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2021-2023 Protocol Labs -// SPDX-License-Identifier: Apache-2.0, MIT -mod bundles; -use bundles::*; -use fvm::executor::{ApplyKind, Executor}; -use fvm_integration_tests::dummy::DummyExterns; -use fvm_ipld_blockstore::MemoryBlockstore; -use fvm_shared::address::Address; -use fvm_shared::econ::TokenAmount; -use fvm_shared::message::Message; -use fvm_shared::state::StateTreeVersion; -use fvm_shared::version::NetworkVersion; -use fvm_test_actors::wasm_bin::ADDRESS_ACTOR_BINARY; -use num_traits::Zero; - -#[test] -fn basic_address_tests() { - // Instantiate tester - let mut tester = new_tester( - NetworkVersion::V21, - StateTreeVersion::V5, - MemoryBlockstore::default(), - ) - .unwrap(); - - let [(_sender_id, sender_address)] = tester.create_accounts().unwrap(); - - let wasm_bin = ADDRESS_ACTOR_BINARY; - - // Set actor state - let actor_state = [(); 0]; - let state_cid = tester.set_state(&actor_state).unwrap(); - - // Set actor - let actor_address = Address::new_id(10000); - - tester - .set_actor_from_bin(wasm_bin, state_cid, actor_address, TokenAmount::zero()) - .unwrap(); - - // Instantiate machine - tester.instantiate_machine(DummyExterns).unwrap(); - - let executor = tester.executor.as_mut().unwrap(); - - // Test all methods. - for (seq, method) in (2..=5).enumerate() { - let message = Message { - from: sender_address, - to: actor_address, - gas_limit: 1000000000, - method_num: method, - sequence: seq as u64, - ..Message::default() - }; - - let res = executor - .execute_message(message, ApplyKind::Explicit, 100) - .unwrap(); - assert!( - res.msg_receipt.exit_code.is_success(), - "{:?}", - res.failure_info - ); - } -} diff --git a/testing/integration/tests/main.rs b/testing/integration/tests/main.rs index 8f4b65be6..d2345f5ec 100644 --- a/testing/integration/tests/main.rs +++ b/testing/integration/tests/main.rs @@ -7,8 +7,9 @@ use std::rc::Rc; use anyhow::anyhow; use cid::Cid; use fvm::executor::{ApplyKind, Executor, ThreadedExecutor}; +use fvm::machine::Machine; use fvm_integration_tests::dummy::DummyExterns; -use fvm_integration_tests::tester::{Account, IntegrationExecutor}; +use fvm_integration_tests::tester::{Account, IntegrationExecutor, Tester}; use fvm_ipld_blockstore::{Blockstore, MemoryBlockstore}; use fvm_ipld_encoding::tuple::*; use fvm_ipld_encoding::RawBytes; @@ -19,8 +20,10 @@ use fvm_shared::message::Message; use fvm_shared::state::StateTreeVersion; use fvm_shared::version::NetworkVersion; use fvm_test_actors::wasm_bin::{ - CREATE_ACTOR_BINARY, EXIT_DATA_ACTOR_BINARY, HELLO_WORLD_ACTOR_BINARY, IPLD_ACTOR_BINARY, - OOM_ACTOR_BINARY, SSELF_ACTOR_BINARY, STACK_OVERFLOW_ACTOR_BINARY, SYSCALL_ACTOR_BINARY, + ADDRESS_ACTOR_BINARY, CREATE_ACTOR_BINARY, EXIT_DATA_ACTOR_BINARY, HELLO_WORLD_ACTOR_BINARY, + IPLD_ACTOR_BINARY, OOM_ACTOR_BINARY, READONLY_ACTOR_BINARY, SSELF_ACTOR_BINARY, + STACK_OVERFLOW_ACTOR_BINARY, SYSCALL_ACTOR_BINARY, UPGRADE_ACTOR_BINARY, + UPGRADE_RECEIVE_ACTOR_BINARY, }; use num_traits::Zero; @@ -1002,6 +1005,261 @@ fn test_oom4() { assert_eq!(res.msg_receipt.exit_code, ExitCode::SYS_ILLEGAL_INSTRUCTION); } +#[test] +fn basic_address_tests() { + // Instantiate tester + let mut tester = new_tester( + NetworkVersion::V21, + StateTreeVersion::V5, + MemoryBlockstore::default(), + ) + .unwrap(); + + let [(_sender_id, sender_address)] = tester.create_accounts().unwrap(); + + let wasm_bin = ADDRESS_ACTOR_BINARY; + + // Set actor state + let actor_state = [(); 0]; + let state_cid = tester.set_state(&actor_state).unwrap(); + + // Set actor + let actor_address = Address::new_id(10000); + + tester + .set_actor_from_bin(wasm_bin, state_cid, actor_address, TokenAmount::zero()) + .unwrap(); + + // Instantiate machine + tester.instantiate_machine(DummyExterns).unwrap(); + + let executor = tester.executor.as_mut().unwrap(); + + // Test all methods. + for (seq, method) in (2..=5).enumerate() { + let message = Message { + from: sender_address, + to: actor_address, + gas_limit: 1000000000, + method_num: method, + sequence: seq as u64, + ..Message::default() + }; + + let res = executor + .execute_message(message, ApplyKind::Explicit, 100) + .unwrap(); + assert!( + res.msg_receipt.exit_code.is_success(), + "{:?}", + res.failure_info + ); + } +} + +#[test] +fn readonly_actor_tests() { + // Instantiate tester + let mut tester = new_tester( + NetworkVersion::V21, + StateTreeVersion::V5, + MemoryBlockstore::default(), + ) + .unwrap(); + + let [(_sender_id, sender_address)] = tester.create_accounts().unwrap(); + + let wasm_bin = READONLY_ACTOR_BINARY; + + // Set actor state + let actor_state = [(); 0]; + let state_cid = tester.set_state(&actor_state).unwrap(); + + // Set actor + let actor_address = Address::new_id(10000); + + tester + .set_actor_from_bin(wasm_bin, state_cid, actor_address, TokenAmount::zero()) + .unwrap(); + + // Instantiate machine + tester.instantiate_machine(DummyExterns).unwrap(); + + let executor = tester.executor.as_mut().unwrap(); + + let message = Message { + from: sender_address, + to: actor_address, + gas_limit: 1000000000, + method_num: 2, + sequence: 0, + value: TokenAmount::from_atto(100), + ..Message::default() + }; + + let res = executor + .execute_message(message, ApplyKind::Explicit, 100) + .unwrap(); + assert!( + res.msg_receipt.exit_code.is_success(), + "{:?}", + res.failure_info + ); + assert!(res.msg_receipt.events_root.is_none()); +} + +#[test] +fn upgrade_actor_test() { + // inline function to calculate cid from address + let calc_cid_func = |bytes: &[u8]| -> Cid { + fvm_ipld_blockstore::Block { + codec: fvm_shared::IPLD_RAW, + data: bytes, + } + .cid(multihash::Code::Blake2b256) + }; + + let receiver = Address::new_id(10000); + let receiver2 = Address::new_id(10001); + let receiver3 = Address::new_id(10002); + + // inline function to reset the tester framework so we can have clean slate between test cases + let init_tester = || -> (Tester, [Account; 1]) { + let mut tester = new_tester( + NetworkVersion::V21, + StateTreeVersion::V5, + MemoryBlockstore::default(), + ) + .unwrap(); + + let sender: [Account; 1] = tester.create_accounts().unwrap(); + + let state_cid = tester.set_state(&[(); 0]).unwrap(); + + // UPGRADE_ACTOR_BINARY is our main actor where we will do the upgrade tests + tester + .set_actor_from_bin( + UPGRADE_ACTOR_BINARY, + state_cid, + receiver, + TokenAmount::zero(), + ) + .unwrap(); + + // UPGRADE_RECEIVE_ACTOR_BINARY is another actor to test recursive upgrade calls + tester + .set_actor_from_bin( + UPGRADE_RECEIVE_ACTOR_BINARY, + state_cid, + receiver2, + TokenAmount::zero(), + ) + .unwrap(); + + // lets have a third actor that will reject the upgrade (since it does not have + // an upgrade entrypoint) + tester + .set_actor_from_bin( + HELLO_WORLD_ACTOR_BINARY, + state_cid, + receiver3, + TokenAmount::zero(), + ) + .unwrap(); + + tester.instantiate_machine(DummyExterns).unwrap(); + + (tester, sender) + }; + + struct Case { + // the method inside invoke + method_num: u64, + // if set, this is the expected receipt data + return_data: Option, + // if set, this is the expected code cid after the upgrade + expected_cid: Option, + } + + let cases = { + [ + // test that successful calls to `upgrade_actor` does not return and that the + // code cid is updated to the UPGRADE_RECEIVE_ACTOR_BINARY) + Case { + method_num: 1, + return_data: Some(666), + expected_cid: Some(calc_cid_func(UPGRADE_RECEIVE_ACTOR_BINARY)), + }, + // test that when `upgrade` endpoint rejects upgrade that we get the returned exit code + Case { + method_num: 2, + return_data: None, + expected_cid: None, + }, + // test recursive update + Case { + method_num: 3, + return_data: Some(444), + expected_cid: Some(calc_cid_func(UPGRADE_RECEIVE_ACTOR_BINARY)), + }, + // test sending a message to ourself (putting us on the call stack) + Case { + method_num: 4, + return_data: None, + expected_cid: None, + }, + // test that calling an upgrade after self destruct fails with IllegalOperation + Case { + method_num: 5, + return_data: None, + expected_cid: None, + }, + ] + }; + + for case in cases.into_iter() { + let (mut tester, sender) = init_tester(); + let executor = tester.executor.as_mut().unwrap(); + + let message = Message { + from: sender[0].1, + to: receiver, + gas_limit: 1000000000, + method_num: case.method_num, + sequence: 0_u64, + value: TokenAmount::from_atto(100), + ..Message::default() + }; + + let res = executor + .execute_message(message, ApplyKind::Explicit, 100) + .unwrap(); + + assert!( + res.msg_receipt.exit_code.is_success(), + "{:?}", + res.failure_info + ); + + // if this test case should return some data, check that it did + if let Some(return_data) = case.return_data { + let val: i64 = res.msg_receipt.return_data.deserialize().unwrap(); + assert_eq!(val, return_data); + } + + // if this test case should have changed the code cid, check that it did + if let Some(expected_cid) = case.expected_cid { + let code = executor + .state_tree() + .get_actor(receiver.id().unwrap()) + .unwrap() + .unwrap() + .code; + assert_eq!(code, expected_cid); + } + } +} + #[derive(Default)] pub struct FailingBlockstore { fail_for: RefCell>, diff --git a/testing/integration/tests/readonly_test.rs b/testing/integration/tests/readonly_test.rs deleted file mode 100644 index eb489119e..000000000 --- a/testing/integration/tests/readonly_test.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2021-2023 Protocol Labs -// SPDX-License-Identifier: Apache-2.0, MIT -mod bundles; -use bundles::*; -use fvm::executor::{ApplyKind, Executor}; -use fvm_integration_tests::dummy::DummyExterns; -use fvm_ipld_blockstore::MemoryBlockstore; -use fvm_shared::address::Address; -use fvm_shared::econ::TokenAmount; -use fvm_shared::message::Message; -use fvm_shared::state::StateTreeVersion; -use fvm_shared::version::NetworkVersion; -use fvm_test_actors::wasm_bin::READONLY_ACTOR_BINARY; -use num_traits::Zero; - -#[test] -fn readonly_actor_tests() { - // Instantiate tester - let mut tester = new_tester( - NetworkVersion::V21, - StateTreeVersion::V5, - MemoryBlockstore::default(), - ) - .unwrap(); - - let [(_sender_id, sender_address)] = tester.create_accounts().unwrap(); - - let wasm_bin = READONLY_ACTOR_BINARY; - - // Set actor state - let actor_state = [(); 0]; - let state_cid = tester.set_state(&actor_state).unwrap(); - - // Set actor - let actor_address = Address::new_id(10000); - - tester - .set_actor_from_bin(wasm_bin, state_cid, actor_address, TokenAmount::zero()) - .unwrap(); - - // Instantiate machine - tester.instantiate_machine(DummyExterns).unwrap(); - - let executor = tester.executor.as_mut().unwrap(); - - let message = Message { - from: sender_address, - to: actor_address, - gas_limit: 1000000000, - method_num: 2, - sequence: 0, - value: TokenAmount::from_atto(100), - ..Message::default() - }; - - let res = executor - .execute_message(message, ApplyKind::Explicit, 100) - .unwrap(); - assert!( - res.msg_receipt.exit_code.is_success(), - "{:?}", - res.failure_info - ); - assert!(res.msg_receipt.events_root.is_none()); -} 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 927aa4ac8..4b009e81a 100644 --- a/testing/test_actors/actors/fil-syscall-actor/src/actor.rs +++ b/testing/test_actors/actors/fil-syscall-actor/src/actor.rs @@ -40,6 +40,7 @@ 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()); @@ -377,3 +378,15 @@ 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, + ); +} diff --git a/testing/test_actors/actors/fil-upgrade-actor/Cargo.toml b/testing/test_actors/actors/fil-upgrade-actor/Cargo.toml new file mode 100644 index 000000000..328f3d26f --- /dev/null +++ b/testing/test_actors/actors/fil-upgrade-actor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fil_upgrade_actor" +version = "0.1.0" +edition = "2021" +publish = false + +[target.'cfg(target_arch = "wasm32")'.dependencies] +fvm_sdk = { version = "4.0.0-alpha.4", path = "../../../../sdk" } +fvm_shared = { version = "4.0.0-alpha.4", path = "../../../../shared" } +fvm_ipld_encoding = { version = "0.4.0", path = "../../../../ipld/encoding" } +cid = { workspace = true } +serde = { version = "1.0.164", features = ["derive"] } +serde_tuple = "0.5.0" + +[lib] +crate-type = ["cdylib"] ## cdylib is necessary for Wasm build diff --git a/testing/test_actors/actors/fil-upgrade-actor/src/actor.rs b/testing/test_actors/actors/fil-upgrade-actor/src/actor.rs new file mode 100644 index 000000000..716b4c63d --- /dev/null +++ b/testing/test_actors/actors/fil-upgrade-actor/src/actor.rs @@ -0,0 +1,135 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use fvm_ipld_encoding::ipld_block::IpldBlock; +use fvm_ipld_encoding::{to_vec, CBOR}; +use fvm_sdk as sdk; +use fvm_shared::address::Address; +use fvm_shared::econ::TokenAmount; +use fvm_shared::error::ErrorNumber; +use fvm_shared::upgrade::UpgradeInfo; +use serde_tuple::*; +#[derive(Serialize_tuple, Deserialize_tuple, PartialEq, Eq, Clone, Debug)] +struct SomeStruct { + value: u64, +} + +const UPGRADE_FAILED_EXIT_CODE: u32 = 19; + +#[no_mangle] +pub fn upgrade(params_id: u32, upgrade_info_id: u32) -> u32 { + sdk::initialize(); + + let params = sdk::message::params_raw(params_id).unwrap().unwrap(); + let ui_params = sdk::message::params_raw(upgrade_info_id).unwrap().unwrap(); + + assert_eq!(params.codec, fvm_ipld_encoding::CBOR); + assert_eq!(ui_params.codec, fvm_ipld_encoding::CBOR); + + let p = params.deserialize::().unwrap(); + let ui = ui_params.deserialize::().unwrap(); + + sdk::debug::log(format!( + "[upgrade] value: {}, old_code_cid: {}", + p.value, ui.old_code_cid + )); + + match p.value { + 1 => { + let block_id = sdk::ipld::put_block(CBOR, &to_vec(&666).unwrap()).unwrap(); + sdk::debug::log(format!( + "[upgrade] params:1, returning block_id {}", + block_id + )); + block_id + } + 2 => { + sdk::debug::log("[upgrade] params:2, calling sdk::vm::exit()".to_string()); + sdk::vm::exit(UPGRADE_FAILED_EXIT_CODE, None, None) + } + 3 => { + sdk::debug::log("[upgrade] params:3, calling upgrade within an upgrade".to_string()); + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10001)).unwrap(); + let params = IpldBlock::serialize_cbor(&SomeStruct { value: 2 }).unwrap(); + let _ = sdk::actor::upgrade_actor(&new_code_cid, params); + unreachable!("we should never return from a successful upgrade"); + } + 4 => { + let block_id = sdk::ipld::put_block(CBOR, &to_vec(&444).unwrap()).unwrap(); + sdk::debug::log(format!( + "[upgrade] params:4, inside upgrade within an upgrade, returning block_id {}", + block_id + )); + block_id + } + other => { + panic!("unexpected value: {}", other); + } + } +} + +#[no_mangle] +pub fn invoke(_: u32) -> u32 { + sdk::initialize(); + + match sdk::message::method_number() { + // test that successful calls to `upgrade_actor` does not return + 1 => { + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10001)).unwrap(); + let params = IpldBlock::serialize_cbor(&SomeStruct { value: 1 }).unwrap(); + let _ = sdk::actor::upgrade_actor(&new_code_cid, params); + unreachable!("we should never return from a successful upgrade"); + } + // test that when `upgrade` endpoint rejects upgrade that we get the returned exit code + 2 => { + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10000)).unwrap(); + let params = IpldBlock::serialize_cbor(&SomeStruct { value: 2 }).unwrap(); + let res = sdk::actor::upgrade_actor(&new_code_cid, params).unwrap(); + assert_eq!( + UPGRADE_FAILED_EXIT_CODE, + res.exit_code.value(), + "invalid exit code returned from upgrade_actor" + ); + } + // test recursive update + 3 => { + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10000)).unwrap(); + let params = IpldBlock::serialize_cbor(&SomeStruct { value: 3 }).unwrap(); + let _ = sdk::actor::upgrade_actor(&new_code_cid, params); + unreachable!("we should never return from a successful upgrade"); + } + // test sending a message to ourself (putting us on the call stack) + 4 => { + sdk::send::send( + &Address::new_id(10000), + 99, + Default::default(), + TokenAmount::from_atto(100), + None, + Default::default(), + ) + .unwrap(); + } + // test that calling an upgrade after self destruct fails with IllegalOperation + 5 => { + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10000)).unwrap(); + sdk::sself::self_destruct(true).unwrap(); + let res = sdk::actor::upgrade_actor(&new_code_cid, None); + assert_eq!(res, Err(ErrorNumber::IllegalOperation)); + } + // test that calling an upgrade with actor already on the call stack fails + 99 => { + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10000)).unwrap(); + let res = sdk::actor::upgrade_actor(&new_code_cid, None); + assert_eq!(res, Err(ErrorNumber::Forbidden)); + } + + other => { + sdk::vm::abort( + fvm_shared::error::ExitCode::FIRST_USER_EXIT_CODE, + Some(format!("unexpected method {}", other).as_str()), + ); + } + } + + 0 +} diff --git a/testing/test_actors/actors/fil-upgrade-actor/src/lib.rs b/testing/test_actors/actors/fil-upgrade-actor/src/lib.rs new file mode 100644 index 000000000..1d4daedef --- /dev/null +++ b/testing/test_actors/actors/fil-upgrade-actor/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +#[cfg(target_arch = "wasm32")] +mod actor; diff --git a/testing/test_actors/actors/fil-upgrade-receive-actor/Cargo.toml b/testing/test_actors/actors/fil-upgrade-receive-actor/Cargo.toml new file mode 100644 index 000000000..b8bfd01f4 --- /dev/null +++ b/testing/test_actors/actors/fil-upgrade-receive-actor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fil_upgrade_receive_actor" +version = "0.1.0" +edition = "2021" +publish = false + +[target.'cfg(target_arch = "wasm32")'.dependencies] +fvm_sdk = { version = "4.0.0-alpha.4", path = "../../../../sdk" } +fvm_shared = { version = "4.0.0-alpha.4", path = "../../../../shared" } +fvm_ipld_encoding = { version = "0.4.0", path = "../../../../ipld/encoding" } +cid = { workspace = true } +serde = { version = "1.0.164", features = ["derive"] } +serde_tuple = "0.5.0" + +[lib] +crate-type = ["cdylib"] ## cdylib is necessary for Wasm build diff --git a/testing/test_actors/actors/fil-upgrade-receive-actor/src/actor.rs b/testing/test_actors/actors/fil-upgrade-receive-actor/src/actor.rs new file mode 100644 index 000000000..e15271a18 --- /dev/null +++ b/testing/test_actors/actors/fil-upgrade-receive-actor/src/actor.rs @@ -0,0 +1,74 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +use fvm_ipld_encoding::ipld_block::IpldBlock; +use fvm_ipld_encoding::{to_vec, CBOR}; +use fvm_sdk as sdk; +use fvm_shared::address::Address; +use fvm_shared::error::ExitCode; +use fvm_shared::upgrade::UpgradeInfo; +use serde_tuple::*; + +#[derive(Serialize_tuple, Deserialize_tuple, PartialEq, Eq, Clone, Debug)] +struct SomeStruct { + value: u64, +} + +#[no_mangle] +pub fn upgrade(params_id: u32, upgrade_info_id: u32) -> u32 { + sdk::initialize(); + + let params = sdk::message::params_raw(params_id).unwrap().unwrap(); + let ui_params = sdk::message::params_raw(upgrade_info_id).unwrap().unwrap(); + + assert_eq!(params.codec, fvm_ipld_encoding::CBOR); + assert_eq!(ui_params.codec, fvm_ipld_encoding::CBOR); + + let p = params.deserialize::().unwrap(); + let ui = ui_params.deserialize::().unwrap(); + + sdk::debug::log(format!( + "[upgrade-receive-actor] value: {}, old_code_cid: {}", + p.value, ui.old_code_cid + )); + + match p.value { + 1 => { + // try upgrade to a cid which should fail since it does not implement the upgrade endpoint + let new_code_cid = sdk::actor::get_actor_code_cid(&Address::new_id(10002)).unwrap(); + let params = IpldBlock::serialize_cbor(&SomeStruct { value: 4 }).unwrap(); + let res = sdk::actor::upgrade_actor(&new_code_cid, params).unwrap(); + sdk::debug::log(format!("[upgrade-receive-actor] res: {:?}", res)); + assert_eq!( + res.exit_code, + ExitCode::SYS_INVALID_RECEIVER, + "expected invalid receiver error" + ); + + let block_id = sdk::ipld::put_block(CBOR, &to_vec(&666).unwrap()).unwrap(); + sdk::debug::log(format!( + "[upgrade] params:1, returning block_id {}", + block_id + )); + block_id + } + 2 => { + sdk::debug::log("[upgrade-receive-actor] params:2".to_string()); + let block_id = sdk::ipld::put_block(CBOR, &to_vec(&444).unwrap()).unwrap(); + sdk::debug::log(format!( + "[upgrade] params:2, returning block_id {}", + block_id + )); + block_id + } + other => { + panic!("unexpected value: {}", other); + } + } +} + +#[no_mangle] +pub fn invoke(_: u32) -> u32 { + sdk::initialize(); + sdk::debug::log("[upgrade-receive-actor] calling vm::exit()".to_string()); + sdk::vm::exit(1, None, None) +} diff --git a/testing/test_actors/actors/fil-upgrade-receive-actor/src/lib.rs b/testing/test_actors/actors/fil-upgrade-receive-actor/src/lib.rs new file mode 100644 index 000000000..1d4daedef --- /dev/null +++ b/testing/test_actors/actors/fil-upgrade-receive-actor/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2021-2023 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +#[cfg(target_arch = "wasm32")] +mod actor; diff --git a/testing/test_actors/build.rs b/testing/test_actors/build.rs index a1865c6f5..a3f118cb7 100644 --- a/testing/test_actors/build.rs +++ b/testing/test_actors/build.rs @@ -31,6 +31,8 @@ const ACTORS: &[(&str, &str)] = &[ ("CREATE_ACTOR_BINARY", "fil_create_actor"), ("OOM_ACTOR_BINARY", "fil_oom_actor"), ("SSELF_ACTOR_BINARY", "fil_sself_actor"), + ("UPGRADE_ACTOR_BINARY", "fil_upgrade_actor"), + ("UPGRADE_RECEIVE_ACTOR_BINARY", "fil_upgrade_receive_actor"), ]; const WASM_TARGET: &str = "wasm32-unknown-unknown";