From ae371e67c736e4927cfb532ccb77dfe90bf04408 Mon Sep 17 00:00:00 2001 From: Alex North <445306+anorth@users.noreply.github.com> Date: Mon, 29 May 2023 13:56:16 +1200 Subject: [PATCH] Add Kernel::send type parameter specifying kernel type for receiver --- fvm/src/kernel/default.rs | 133 +++++++++--------- fvm/src/kernel/mod.rs | 31 +++-- fvm/src/syscalls/send.rs | 6 +- testing/conformance/src/vm.rs | 251 ++++++---------------------------- 4 files changed, 125 insertions(+), 296 deletions(-) diff --git a/fvm/src/kernel/default.rs b/fvm/src/kernel/default.rs index e62193ace..997334948 100644 --- a/fvm/src/kernel/default.rs +++ b/fvm/src/kernel/default.rs @@ -111,6 +111,70 @@ where fn machine(&self) -> &::Machine { self.call_manager.machine() } + + fn send>( + &mut self, + recipient: &Address, + method: MethodNum, + params_id: BlockId, + value: &TokenAmount, + gas_limit: Option, + flags: SendFlags, + ) -> Result { + let from = self.actor_id; + let read_only = self.read_only || flags.read_only(); + + if read_only && !value.is_zero() { + return Err(syscall_error!(ReadOnly; "cannot transfer value when read-only").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()); + } + + // Send. + let result = self.call_manager.with_transaction(|cm| { + cm.send::( + from, *recipient, method, params, value, gas_limit, read_only, + ) + })?; + + // Store result and return. + Ok(match result { + InvocationResult { + exit_code, + value: Some(blk), + } => { + let block_stat = blk.stat(); + let block_id = self + .blocks + .put(blk) + .or_fatal() + .context("failed to store a valid return value")?; + SendResult { + block_id, + block_stat, + exit_code, + } + } + InvocationResult { + exit_code, + value: None, + } => SendResult { + block_id: NO_DATA_BLOCK_ID, + block_stat: BlockStat { codec: 0, size: 0 }, + exit_code, + }, + }) + } } impl DefaultKernel @@ -358,75 +422,6 @@ where } } -impl SendOps for DefaultKernel -where - C: CallManager, -{ - fn send( - &mut self, - recipient: &Address, - method: MethodNum, - params_id: BlockId, - value: &TokenAmount, - gas_limit: Option, - flags: SendFlags, - ) -> Result { - let from = self.actor_id; - let read_only = self.read_only || flags.read_only(); - - if read_only && !value.is_zero() { - return Err(syscall_error!(ReadOnly; "cannot transfer value when read-only").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()); - } - - // Send. - let result = self.call_manager.with_transaction(|cm| { - cm.send::( - from, *recipient, method, params, value, gas_limit, read_only, - ) - })?; - - // Store result and return. - Ok(match result { - InvocationResult { - exit_code, - value: Some(blk), - } => { - let block_stat = blk.stat(); - let block_id = self - .blocks - .put(blk) - .or_fatal() - .context("failed to store a valid return value")?; - SendResult { - block_id, - block_stat, - exit_code, - } - } - InvocationResult { - exit_code, - value: None, - } => SendResult { - block_id: NO_DATA_BLOCK_ID, - block_stat: BlockStat { codec: 0, size: 0 }, - exit_code, - }, - }) - } -} - impl CircSupplyOps for DefaultKernel where C: CallManager, diff --git a/fvm/src/kernel/mod.rs b/fvm/src/kernel/mod.rs index 7a893c903..b094c3dc5 100644 --- a/fvm/src/kernel/mod.rs +++ b/fvm/src/kernel/mod.rs @@ -63,7 +63,6 @@ pub trait Kernel: + NetworkOps + RandomnessOps + SelfOps - + SendOps + LimiterOps + 'static { @@ -97,6 +96,23 @@ pub trait Kernel: /// The kernel's underlying "machine". fn machine(&self) -> &::Machine; + + /// Sends a message to another actor. + /// The method type parameter K is the type of the kernel to instantiate for + /// the receiving actor. This is necessary to support wrapping a kernel, so the outer + /// kernel can specify its Self as the receiver's kernel type, rather than the wrapped + /// kernel specifying its Self. + /// This method is part of the Kernel trait so it can refer to the Self::CallManager + /// associated type necessary to constrain K. + fn send>( + &mut self, + recipient: &Address, + method: u64, + params: BlockId, + value: &TokenAmount, + gas_limit: Option, + flags: SendFlags, + ) -> Result; } /// Network-related operations. @@ -209,19 +225,6 @@ pub trait ActorOps { fn balance_of(&self, actor_id: ActorID) -> Result; } -/// Operations to send messages to other actors. -pub trait SendOps { - fn send( - &mut self, - recipient: &Address, - method: u64, - params: BlockId, - value: &TokenAmount, - gas_limit: Option, - flags: SendFlags, - ) -> Result; -} - /// Operations to query the circulating supply. pub trait CircSupplyOps { /// Returns the total token supply in circulation at the beginning of the current epoch. diff --git a/fvm/src/syscalls/send.rs b/fvm/src/syscalls/send.rs index f9810c20b..edf51cb1f 100644 --- a/fvm/src/syscalls/send.rs +++ b/fvm/src/syscalls/send.rs @@ -13,8 +13,8 @@ use crate::Kernel; /// Send a message to another actor. The result is placed as a CBOR-encoded /// receipt in the block registry, and can be retrieved by the returned BlockId. #[allow(clippy::too_many_arguments)] -pub fn send( - context: Context<'_, impl Kernel>, +pub fn send( + context: Context<'_, K>, recipient_off: u32, recipient_len: u32, method: u64, @@ -43,7 +43,7 @@ pub fn send( exit_code, } = context .kernel - .send(&recipient, method, params_id, &value, gas_limit, flags)?; + .send::(&recipient, method, params_id, &value, gas_limit, flags)?; Ok(sys::out::send::Send { exit_code: exit_code.value(), diff --git a/testing/conformance/src/vm.rs b/testing/conformance/src/vm.rs index b0b7a72ba..51a9bbc53 100644 --- a/testing/conformance/src/vm.rs +++ b/testing/conformance/src/vm.rs @@ -5,13 +5,14 @@ use std::sync::{Arc, Mutex}; use anyhow::anyhow; use cid::Cid; -use fvm::call_manager::{CallManager, DefaultCallManager, FinishRet, InvocationResult}; -use fvm::engine::Engine; -use fvm::gas::{price_list_by_network_version, Gas, GasTimer, GasTracker, PriceList}; +use multihash::MultihashGeneric; + +use fvm::call_manager::{CallManager, DefaultCallManager}; +use fvm::gas::{price_list_by_network_version, Gas, GasTimer, PriceList}; use fvm::kernel::*; use fvm::machine::limiter::MemoryLimiter; use fvm::machine::{DefaultMachine, Machine, MachineContext, Manifest, NetworkConfig}; -use fvm::state_tree::{ActorState, StateTree}; +use fvm::state_tree::StateTree; use fvm::DefaultKernel; use fvm_ipld_blockstore::MemoryBlockstore; use fvm_shared::address::Address; @@ -21,7 +22,6 @@ use fvm_shared::crypto::signature::{ SignatureType, SECP_PUB_LEN, SECP_SIG_LEN, SECP_SIG_MESSAGE_HASH_SIZE, }; use fvm_shared::econ::TokenAmount; -use fvm_shared::event::StampedEvent; use fvm_shared::piece::PieceInfo; use fvm_shared::randomness::RANDOMNESS_LENGTH; use fvm_shared::sector::{ @@ -31,7 +31,6 @@ use fvm_shared::sector::{ use fvm_shared::sys::SendFlags; use fvm_shared::version::NetworkVersion; use fvm_shared::{ActorID, MethodNum, TOTAL_FILECOIN}; -use multihash::MultihashGeneric; use crate::externs::TestExterns; use crate::vector::{MessageVector, Variant}; @@ -181,189 +180,24 @@ where } } -/// A CallManager that wraps kernels in an InterceptKernel. -// NOTE: For now, this _must_ be transparent because we transmute a pointer. -#[repr(transparent)] -pub struct TestCallManager>(pub C); - -impl CallManager for TestCallManager -where - M: Machine, - C: CallManager>, -{ - type Machine = C::Machine; - - fn new( - machine: Self::Machine, - engine: Engine, - gas_limit: u64, - origin: ActorID, - origin_address: Address, - receiver: Option, - receiver_address: Address, - nonce: u64, - gas_premium: TokenAmount, - ) -> Self { - TestCallManager(C::new( - machine, - engine, - gas_limit, - origin, - origin_address, - receiver, - receiver_address, - nonce, - gas_premium, - )) - } - - fn send>( - &mut self, - from: ActorID, - to: Address, - method: MethodNum, - params: Option, - value: &TokenAmount, - gas_limit: Option, - read_only: bool, - ) -> Result { - // K is the kernel specified by the non intercepted kernel. - // We wrap that here. - self.0 - .send::>(from, to, method, params, value, gas_limit, read_only) - } - - fn with_transaction( - &mut self, - f: impl FnOnce(&mut Self) -> Result, - ) -> Result { - // This transmute is _safe_ because this type is "repr transparent". - let inner_ptr = &mut self.0 as *mut C; - self.0.with_transaction(|inner: &mut C| unsafe { - // Make sure that we've got the right pointer. Otherwise, this cast definitely isn't - // safe. - assert_eq!(inner_ptr, inner as *mut C); - - // Ok, we got the pointer we expected, casting back to the interceptor is safe. - f(&mut *(inner as *mut C as *mut Self)) - }) - } - - fn finish(self) -> (Result, Self::Machine) { - self.0.finish() - } - - fn machine(&self) -> &Self::Machine { - self.0.machine() - } - - fn machine_mut(&mut self) -> &mut Self::Machine { - self.0.machine_mut() - } - - fn engine(&self) -> &Engine { - self.0.engine() - } - - fn gas_tracker(&self) -> &GasTracker { - self.0.gas_tracker() - } - - fn gas_premium(&self) -> &TokenAmount { - self.0.gas_premium() - } - - fn origin(&self) -> ActorID { - self.0.origin() - } - - fn nonce(&self) -> u64 { - self.0.nonce() - } - - fn next_actor_address(&self) -> Address { - self.0.next_actor_address() - } - - fn create_actor( - &mut self, - code_id: Cid, - actor_id: ActorID, - delegated_address: Option
, - ) -> Result<()> { - self.0.create_actor(code_id, actor_id, delegated_address) - } - - fn price_list(&self) -> &fvm::gas::PriceList { - self.0.price_list() - } - - fn context(&self) -> &MachineContext { - self.0.context() - } - - fn blockstore(&self) -> &::Blockstore { - self.0.blockstore() - } - - fn externs(&self) -> &::Externs { - self.0.externs() - } - - fn charge_gas(&self, charge: fvm::gas::GasCharge) -> Result { - self.0.charge_gas(charge) - } - - fn invocation_count(&self) -> u64 { - self.0.invocation_count() - } - - fn limiter_mut(&mut self) -> &mut ::Limiter { - self.0.limiter_mut() - } - - fn append_event(&mut self, evt: StampedEvent) { - self.0.append_event(evt) - } - - fn resolve_address(&self, address: &Address) -> Result> { - self.0.resolve_address(address) - } - - fn get_actor(&self, id: ActorID) -> Result> { - self.0.get_actor(id) - } - - fn set_actor(&mut self, id: ActorID, state: ActorState) -> Result<()> { - self.0.set_actor(id, state) - } - - fn delete_actor(&mut self, id: ActorID) -> Result<()> { - self.0.delete_actor(id) - } - - fn transfer(&mut self, from: ActorID, to: ActorID, value: &TokenAmount) -> Result<()> { - self.0.transfer(from, to, value) - } -} - /// A kernel for intercepting syscalls. -pub struct TestKernel>(pub K, pub TestData); +// 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); impl Kernel for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { - type CallManager = C; + type CallManager = K::CallManager; fn into_inner(self) -> (Self::CallManager, BlockRegistry) where Self: Sized, { - let (cm, br) = self.0.into_inner(); - (cm.0, br) + self.0.into_inner() } fn new( @@ -383,7 +217,7 @@ where TestKernel( K::new( - TestCallManager(mgr), + mgr, blocks, caller, actor_id, @@ -398,13 +232,30 @@ where 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 { + // 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. + // This restricts the ability for the TestKernel to itself be wrapped by another kernel type. + self.0 + .send::(recipient, method, params, value, gas_limit, flags) + } } impl ActorOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn resolve_address(&self, address: &Address) -> Result { self.0.resolve_address(address) @@ -453,7 +304,7 @@ impl IpldBlockOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn block_open(&mut self, cid: &Cid) -> Result<(BlockId, BlockStat)> { self.0.block_open(cid) @@ -480,7 +331,7 @@ impl CircSupplyOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { // Not forwarded. Circulating supply is taken from the TestData. fn total_fil_circ_supply(&self) -> Result { @@ -492,7 +343,7 @@ impl CryptoOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { // forwarded fn hash(&self, code: u64, data: &[u8]) -> Result> { @@ -582,7 +433,7 @@ impl DebugOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn log(&self, msg: String) { self.0.log(msg) @@ -601,7 +452,7 @@ impl GasOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn gas_used(&self) -> Gas { self.0.gas_used() @@ -624,7 +475,7 @@ impl MessageOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn msg_context(&self) -> Result { self.0.msg_context() @@ -635,7 +486,7 @@ impl NetworkOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn network_context(&self) -> Result { self.0.network_context() @@ -650,7 +501,7 @@ impl RandomnessOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn get_randomness_from_tickets( &self, @@ -677,7 +528,7 @@ impl SelfOps for TestKernel where M: Machine, C: CallManager>, - K: Kernel>, + K: Kernel, { fn root(&self) -> Result { self.0.root() @@ -696,26 +547,6 @@ where } } -impl SendOps for TestKernel -where - M: Machine, - C: CallManager>, - K: Kernel>, -{ - 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) - } -} - impl LimiterOps for TestKernel where K: LimiterOps, @@ -729,9 +560,9 @@ where impl EventOps for TestKernel where - C: CallManager>, - K: Kernel>, M: Machine, + C: CallManager>, + K: Kernel, { fn emit_event(&mut self, raw_evt: &[u8]) -> Result<()> { self.0.emit_event(raw_evt)