Skip to content

Commit

Permalink
Modify builtin actor API so that message source is available
Browse files Browse the repository at this point in the history
  • Loading branch information
ekovalev committed Jan 10, 2024
1 parent 88972ad commit 7b1af6e
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 45 deletions.
14 changes: 9 additions & 5 deletions pallets/gear-builtin-actor/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ macro_rules! impl_builtin_actor {
($name: ident, $id: literal) => {
pub struct $name {}

impl BuiltinActor<Vec<u8>, u64> for $name {
impl BuiltinActor<SimpleBuiltinMessage, u64> for $name {
fn handle(
_builtin_id: BuiltinId,
_payload: Vec<u8>,
_message: &SimpleBuiltinMessage,
) -> (Result<Vec<u8>, BuiltinActorError>, u64) {
(Ok(Default::default()), Default::default())
}
Expand All @@ -43,7 +42,7 @@ macro_rules! impl_builtin_actor {
Default::default()
}
}
impl RegisteredBuiltinActor<Vec<u8>, u64> for $name {
impl RegisteredBuiltinActor<SimpleBuiltinMessage, u64> for $name {
const ID: BuiltinId = BuiltinId($id as u64);
}
};
Expand Down Expand Up @@ -115,8 +114,13 @@ benchmarks! {
None,
);
let gas_limit = 10_000_000_000_u64;
let builtin_message = SimpleBuiltinMessage {
source,
destination: builtin_id,
payload,
};
}: {
let _ = <T as Config>::BuiltinActor::handle(builtin_id, payload);
let _ = <T as Config>::BuiltinActor::handle(&builtin_message);
} verify {
// No changes in runtime are expected since the actual dispatch doesn't take place.
}
Expand Down
76 changes: 56 additions & 20 deletions pallets/gear-builtin-actor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@ pub use pallet::*;
#[allow(dead_code)]
const LOG_TARGET: &str = "gear::builtin_actor";

pub trait Dispatchable {
type Payload: AsRef<[u8]>;

fn source(&self) -> ProgramId;
fn destination(&self) -> BuiltinId;
fn payload_bytes(&self) -> &[u8];
}

#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
pub struct SimpleBuiltinMessage {
source: ProgramId,
destination: BuiltinId,
payload: Vec<u8>,
}

impl Dispatchable for SimpleBuiltinMessage {
type Payload = Vec<u8>;

fn source(&self) -> ProgramId {
self.source
}

fn destination(&self) -> BuiltinId {
self.destination
}

fn payload_bytes(&self) -> &[u8] {
self.payload.as_ref()
}
}

/// Built-in actor error type
#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, derive_more::Display)]
pub enum BuiltinActorError {
Expand Down Expand Up @@ -95,39 +126,39 @@ pub type BuiltinResult<P> = Result<P, BuiltinActorError>;

/// A trait representing an interface of a builtin actor that can receive a message
/// and produce a set of outputs that can then be converted into a reply message.
pub trait BuiltinActor<Payload, Gas: Zero> {
pub trait BuiltinActor<Message: Dispatchable, Gas: Zero> {
/// Handles a message and returns a result and the actual gas spent.
fn handle(builtin_id: BuiltinId, payload: Payload) -> (BuiltinResult<Payload>, Gas);
fn handle(message: &Message) -> (BuiltinResult<Message::Payload>, Gas);

/// Returns the maximum gas cost that can be incurred by handling a message.
fn max_gas_cost(builtin_id: BuiltinId) -> Gas;
/// Returns the maximum gas cost that can be incurred by a builtin actor.
fn max_gas_cost(actor_id: BuiltinId) -> Gas;
}

pub trait RegisteredBuiltinActor<P, G: Zero>: BuiltinActor<P, G> {
pub trait RegisteredBuiltinActor<M: Dispatchable, G: Zero>: BuiltinActor<M, G> {
/// The global unique ID of the trait implementer type.
const ID: BuiltinId;
}

// Assuming as many as 16 builtin actors for the meantime
#[impl_for_tuples(16)]
#[tuple_types_custom_trait_bound(RegisteredBuiltinActor<P, G>)]
impl<P, G: Zero> BuiltinActor<P, G> for Tuple {
fn handle(builtin_id: BuiltinId, payload: P) -> (BuiltinResult<P>, G) {
#[tuple_types_custom_trait_bound(RegisteredBuiltinActor<M, G>)]
impl<M: Dispatchable, G: Zero> BuiltinActor<M, G> for Tuple {
fn handle(message: &M) -> (BuiltinResult<M::Payload>, G) {
for_tuples!(
#(
if (Tuple::ID == builtin_id) {
return Tuple::handle(builtin_id, payload);
if (Tuple::ID == message.destination()) {
return Tuple::handle(message);
}
)*
);
(Err(BuiltinActorError::UnknownBuiltinId), Zero::zero())
}

fn max_gas_cost(builtin_id: BuiltinId) -> G {
fn max_gas_cost(actor_id: BuiltinId) -> G {
for_tuples!(
#(
if (Tuple::ID == builtin_id) {
return Tuple::max_gas_cost(builtin_id);
if (Tuple::ID == actor_id) {
return Tuple::max_gas_cost(actor_id);
}
)*
);
Expand All @@ -152,7 +183,7 @@ pub mod pallet {
#[pallet::config]
pub trait Config: frame_system::Config {
/// The builtin actor type.
type BuiltinActor: BuiltinActor<Vec<u8>, u64>;
type BuiltinActor: BuiltinActor<SimpleBuiltinMessage, u64>;

/// Weight cost incurred by builtin actors calls.
type WeightInfo: WeightInfo;
Expand Down Expand Up @@ -213,12 +244,13 @@ pub mod pallet {
/// This function is supposed to be called during the Runtime upgrade to update the
/// builtin actors cache (if new actors are being added).
#[allow(unused)]
pub(crate) fn register_actor<B, P, G>() -> DispatchResult
pub(crate) fn register_actor<B, M, G>() -> DispatchResult
where
B: RegisteredBuiltinActor<P, G>,
B: RegisteredBuiltinActor<M, G>,
M: Dispatchable,
G: Zero,
{
let builtin_id = <B as RegisteredBuiltinActor<P, G>>::ID;
let builtin_id = <B as RegisteredBuiltinActor<M, G>>::ID;
let actor_id = Self::generate_actor_id(builtin_id);
ensure!(
!Actors::<T>::contains_key(actor_id),
Expand Down Expand Up @@ -338,17 +370,21 @@ impl<T: Config> BuiltinRouter<ProgramId> for Pallet<T> {
dispatch: StoredDispatch,
gas_limit: u64,
) -> Vec<Self::Output> {
// Re-package the dispatch into a `SimpleBuiltinMessage` for the builtin actor
let builtin_message = SimpleBuiltinMessage {
source: dispatch.source(),
destination: builtin_id,
payload: dispatch.payload_bytes().to_vec(),
};
// Estimate maximum gas that can be spent during message processing.
// The exact gas cost may depend on the payload and be only available postfactum.
let max_gas = <T as Config>::BuiltinActor::max_gas_cost(builtin_id);
if max_gas > gas_limit {
return Self::process_error(&dispatch, BuiltinActorError::InsufficientGas);
}

let payload = dispatch.payload_bytes().to_vec();

// Do message processing
let (res, gas_spent) = <T as Config>::BuiltinActor::handle(builtin_id, payload);
let (res, gas_spent) = <T as Config>::BuiltinActor::handle(&builtin_message);
res.map_or_else(
|err| {
log::debug!(target: LOG_TARGET, "Builtin actor error: {:?}", err);
Expand Down
39 changes: 19 additions & 20 deletions pallets/gear-builtin-actor/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate as pallet_gear_builtin_actor;
use crate::{BuiltinActor, BuiltinActorError, RegisteredBuiltinActor};
use crate::{
self as pallet_gear_builtin_actor, BuiltinActor, BuiltinActorError, Dispatchable,
RegisteredBuiltinActor, SimpleBuiltinMessage,
};
use core::cell::RefCell;
use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight};
use frame_support::{
Expand All @@ -27,7 +29,7 @@ use frame_support::{
};
use frame_support_test::TestRandomness;
use frame_system::{self as system, pallet_prelude::BlockNumberFor, EnsureRoot};
use gear_core::ids::BuiltinId;
use gear_core::ids::{BuiltinId, ProgramId};
use pallet_session::historical::{self as pallet_session_historical};
use sp_core::{crypto::key_types, H256};
use sp_runtime::{
Expand Down Expand Up @@ -65,7 +67,8 @@ pub(crate) const SESSION_DURATION: u64 = 250;

#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct ExecutionTraceFrame {
pub builtin_id: BuiltinId,
pub destination: BuiltinId,
pub source: ProgramId,
pub input: Vec<u8>,
pub is_success: bool,
}
Expand Down Expand Up @@ -289,16 +292,14 @@ pallet_gear::impl_config!(
);

pub struct SuccessBuiltinActor {}
impl BuiltinActor<Vec<u8>, u64> for SuccessBuiltinActor {
fn handle(
_builtin_id: BuiltinId,
payload: Vec<u8>,
) -> (Result<Vec<u8>, BuiltinActorError>, u64) {
impl BuiltinActor<SimpleBuiltinMessage, u64> for SuccessBuiltinActor {
fn handle(message: &SimpleBuiltinMessage) -> (Result<Vec<u8>, BuiltinActorError>, u64) {
if !in_transaction() {
DEBUG_EXECUTION_TRACE.with(|d| {
d.borrow_mut().push(ExecutionTraceFrame {
builtin_id: <Self as RegisteredBuiltinActor<_, _>>::ID,
input: payload,
destination: <Self as RegisteredBuiltinActor<_, _>>::ID,
source: message.source(),
input: message.payload_bytes().to_vec(),
is_success: true,
})
});
Expand All @@ -314,21 +315,19 @@ impl BuiltinActor<Vec<u8>, u64> for SuccessBuiltinActor {
1_000_000_u64
}
}
impl RegisteredBuiltinActor<Vec<u8>, u64> for SuccessBuiltinActor {
impl RegisteredBuiltinActor<SimpleBuiltinMessage, u64> for SuccessBuiltinActor {
const ID: BuiltinId = BuiltinId(u64::from_le_bytes(*b"bltn/suc"));
}

pub struct ErrorBuiltinActor {}
impl BuiltinActor<Vec<u8>, u64> for ErrorBuiltinActor {
fn handle(
_builtin_id: BuiltinId,
payload: Vec<u8>,
) -> (Result<Vec<u8>, BuiltinActorError>, u64) {
impl BuiltinActor<SimpleBuiltinMessage, u64> for ErrorBuiltinActor {
fn handle(message: &SimpleBuiltinMessage) -> (Result<Vec<u8>, BuiltinActorError>, u64) {
if !in_transaction() {
DEBUG_EXECUTION_TRACE.with(|d| {
d.borrow_mut().push(ExecutionTraceFrame {
builtin_id: <Self as RegisteredBuiltinActor<_, _>>::ID,
input: payload,
destination: <Self as RegisteredBuiltinActor<_, _>>::ID,
source: message.source(),
input: message.payload_bytes().to_vec(),
is_success: false,
})
});
Expand All @@ -340,7 +339,7 @@ impl BuiltinActor<Vec<u8>, u64> for ErrorBuiltinActor {
1_000_000_u64
}
}
impl RegisteredBuiltinActor<Vec<u8>, u64> for ErrorBuiltinActor {
impl RegisteredBuiltinActor<SimpleBuiltinMessage, u64> for ErrorBuiltinActor {
const ID: BuiltinId = BuiltinId(u64::from_le_bytes(*b"bltn/err"));
}

Expand Down

0 comments on commit 7b1af6e

Please sign in to comment.