use crate::{
blockchain::Schema as CoreSchema,
crypto::{Hash, PublicKey},
helpers::{Height, ValidateInput},
merkledb::{access::Prefixed, BinaryValue, Fork},
runtime::{
migrations::MigrationType, ArtifactId, BlockchainData, CallSite, CallType, Caller,
CoreError, Dispatcher, DispatcherSchema, ExecutionError, ExecutionFail, InstanceDescriptor,
InstanceId, InstanceQuery, InstanceSpec, MethodId, RuntimeFeature, SUPERVISOR_INSTANCE_ID,
},
};
const ACCESS_ERROR_STR: &str = "An attempt to access blockchain data after execution error.";
#[derive(Debug)]
pub struct ExecutionContext<'a> {
pub(crate) fork: &'a mut Fork,
caller: Caller,
interface_name: &'a str,
instance: InstanceDescriptor,
transaction_hash: Option<Hash>,
dispatcher: &'a Dispatcher,
call_stack_depth: u64,
has_child_call_error: &'a mut bool,
}
impl<'a> ExecutionContext<'a> {
pub const MAX_CALL_STACK_DEPTH: u64 = 128;
pub(crate) fn for_transaction(
dispatcher: &'a Dispatcher,
fork: &'a mut Fork,
has_child_call_error: &'a mut bool,
instance: InstanceDescriptor,
author: PublicKey,
transaction_hash: Hash,
) -> Self {
Self::new(
dispatcher,
fork,
has_child_call_error,
instance,
Caller::Transaction { author },
Some(transaction_hash),
)
}
pub(crate) fn for_block_call(
dispatcher: &'a Dispatcher,
fork: &'a mut Fork,
has_child_call_error: &'a mut bool,
instance: InstanceDescriptor,
) -> Self {
Self::new(
dispatcher,
fork,
has_child_call_error,
instance,
Caller::Blockchain,
None,
)
}
fn new(
dispatcher: &'a Dispatcher,
fork: &'a mut Fork,
has_child_call_error: &'a mut bool,
instance: InstanceDescriptor,
caller: Caller,
transaction_hash: Option<Hash>,
) -> Self {
Self {
dispatcher,
fork,
instance,
caller,
transaction_hash,
interface_name: "",
call_stack_depth: 0,
has_child_call_error,
}
}
pub fn transaction_hash(&self) -> Option<Hash> {
self.transaction_hash
}
pub fn data(&self) -> BlockchainData<&Fork> {
if *self.has_child_call_error {
panic!(ACCESS_ERROR_STR);
}
BlockchainData::new(self.fork, &self.instance.name)
}
pub fn service_data(&self) -> Prefixed<&Fork> {
self.data().for_executing_service()
}
pub fn caller(&self) -> &Caller {
&self.caller
}
pub fn instance(&self) -> &InstanceDescriptor {
&self.instance
}
pub fn in_genesis_block(&self) -> bool {
let core_schema = self.data().for_core();
core_schema.next_height() == Height(0)
}
pub fn interface_name(&self) -> &str {
self.interface_name
}
#[doc(hidden)]
pub fn supervisor_extensions(&mut self) -> SupervisorExtensions<'_> {
if self.instance.id != SUPERVISOR_INSTANCE_ID {
panic!("`supervisor_extensions` called within a non-supervisor service");
}
SupervisorExtensions(self.reborrow(self.instance.clone()))
}
pub(crate) fn initiate_adding_service(
&mut self,
spec: InstanceSpec,
constructor: impl BinaryValue,
) -> Result<(), ExecutionError> {
debug_assert!(spec.validate().is_ok(), "{:?}", spec.validate());
let runtime = self
.dispatcher
.runtime_by_id(spec.artifact.runtime_id)
.ok_or(CoreError::IncorrectRuntime)?;
let context = self.reborrow(spec.as_descriptor());
runtime
.initiate_adding_service(context, &spec.artifact, constructor.into_bytes())
.map_err(|mut err| {
self.should_rollback();
err.set_runtime_id(spec.artifact.runtime_id)
.set_call_site(|| CallSite::new(spec.id, CallType::Constructor));
err
})?;
DispatcherSchema::new(&*self.fork)
.initiate_adding_service(spec)
.map_err(From::from)
}
fn reborrow(&mut self, instance: InstanceDescriptor) -> ExecutionContext<'_> {
if *self.has_child_call_error {
panic!(ACCESS_ERROR_STR);
}
ExecutionContext {
fork: &mut *self.fork,
caller: self.caller.clone(),
transaction_hash: self.transaction_hash,
instance,
interface_name: self.interface_name,
dispatcher: self.dispatcher,
call_stack_depth: self.call_stack_depth,
has_child_call_error: self.has_child_call_error,
}
}
fn child_context<'s>(
&'s mut self,
interface_name: &'s str,
instance: InstanceDescriptor,
fallthrough_auth: bool,
) -> ExecutionContext<'s> {
if *self.has_child_call_error {
panic!(ACCESS_ERROR_STR);
}
let caller = if fallthrough_auth {
self.caller.clone()
} else {
Caller::Service {
instance_id: self.instance.id,
}
};
ExecutionContext {
caller,
transaction_hash: self.transaction_hash,
dispatcher: self.dispatcher,
instance,
fork: &mut *self.fork,
interface_name,
call_stack_depth: self.call_stack_depth + 1,
has_child_call_error: self.has_child_call_error,
}
}
pub(crate) fn should_rollback(&mut self) {
*self.has_child_call_error = true;
}
}
#[doc(hidden)]
pub trait ExecutionContextUnstable {
fn make_child_call<'q>(
&mut self,
called_instance: impl Into<InstanceQuery<'q>>,
interface_name: &str,
method_id: MethodId,
arguments: &[u8],
fallthrough_auth: bool,
) -> Result<(), ExecutionError>;
}
impl ExecutionContextUnstable for ExecutionContext<'_> {
fn make_child_call<'q>(
&mut self,
called_instance: impl Into<InstanceQuery<'q>>,
interface_name: &str,
method_id: MethodId,
arguments: &[u8],
fallthrough_auth: bool,
) -> Result<(), ExecutionError> {
if self.call_stack_depth + 1 >= Self::MAX_CALL_STACK_DEPTH {
let err = CoreError::stack_overflow(Self::MAX_CALL_STACK_DEPTH);
return Err(err);
}
let descriptor = self
.dispatcher
.get_service(called_instance)
.ok_or(CoreError::IncorrectInstanceId)?;
let instance_id = descriptor.id;
let (runtime_id, runtime) = self
.dispatcher
.runtime_for_service(instance_id)
.ok_or(CoreError::IncorrectRuntime)?;
let context = self.child_context(interface_name, descriptor, fallthrough_auth);
runtime
.execute(context, method_id, arguments)
.map_err(|mut err| {
self.should_rollback();
err.set_runtime_id(runtime_id).set_call_site(|| {
CallSite::new(
instance_id,
CallType::Method {
interface: interface_name.to_owned(),
id: method_id,
},
)
});
err
})
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct SupervisorExtensions<'a>(pub(super) ExecutionContext<'a>);
impl SupervisorExtensions<'_> {
pub fn start_artifact_registration(&self, artifact: &ArtifactId, spec: Vec<u8>) {
Dispatcher::commit_artifact(self.0.fork, artifact, spec);
}
pub fn unload_artifact(&self, artifact: &ArtifactId) -> Result<(), ExecutionError> {
Dispatcher::unload_artifact(self.0.fork, artifact)
}
pub fn initiate_adding_service(
&mut self,
instance_spec: InstanceSpec,
constructor: impl BinaryValue,
) -> Result<(), ExecutionError> {
self.0
.child_context("", self.0.instance.clone(), false)
.initiate_adding_service(instance_spec, constructor)
}
pub fn initiate_stopping_service(&self, instance_id: InstanceId) -> Result<(), ExecutionError> {
Dispatcher::initiate_stopping_service(self.0.fork, instance_id)
}
pub fn initiate_freezing_service(&self, instance_id: InstanceId) -> Result<(), ExecutionError> {
self.0
.dispatcher
.initiate_freezing_service(self.0.fork, instance_id)
}
pub fn initiate_resuming_service(
&mut self,
instance_id: InstanceId,
params: impl BinaryValue,
) -> Result<(), ExecutionError> {
let state = DispatcherSchema::new(&*self.0.fork)
.get_instance(instance_id)
.ok_or(CoreError::IncorrectInstanceId)?;
if let Some(data_version) = state.data_version {
let msg = format!(
"Cannot resume service `{}` because its data version ({}) does not match \
the associated artifact `{}`. To solve, associate the service with the newer \
artifact revision, for example, via fast-forward migration.",
state.spec.as_descriptor(),
data_version,
state.spec.artifact
);
return Err(CoreError::CannotResumeService.with_description(msg));
}
let spec = state.spec;
DispatcherSchema::new(&*self.0.fork).initiate_resuming_service(instance_id)?;
let runtime = self
.0
.dispatcher
.runtime_by_id(spec.artifact.runtime_id)
.ok_or(CoreError::IncorrectRuntime)?;
runtime
.initiate_resuming_service(
self.0.child_context("", spec.as_descriptor(), false),
&spec.artifact,
params.into_bytes(),
)
.map_err(|mut err| {
self.0.should_rollback();
err.set_runtime_id(spec.artifact.runtime_id)
.set_call_site(|| CallSite::new(instance_id, CallType::Resume));
err
})
}
pub fn writeable_core_schema(&self) -> CoreSchema<&Fork> {
CoreSchema::new(self.0.fork)
}
pub fn initiate_migration(
&self,
new_artifact: ArtifactId,
old_service: &str,
) -> Result<MigrationType, ExecutionError> {
self.0
.dispatcher
.initiate_migration(self.0.fork, new_artifact, old_service)
}
pub fn rollback_migration(&self, service_name: &str) -> Result<(), ExecutionError> {
Dispatcher::rollback_migration(self.0.fork, service_name)
}
pub fn commit_migration(
&self,
service_name: &str,
migration_hash: Hash,
) -> Result<(), ExecutionError> {
Dispatcher::commit_migration(self.0.fork, service_name, migration_hash)
}
pub fn flush_migration(&mut self, service_name: &str) -> Result<(), ExecutionError> {
Dispatcher::flush_migration(self.0.fork, service_name)
}
pub fn check_feature(&self, runtime_id: u32, feature: &RuntimeFeature) -> bool {
self.0
.dispatcher
.runtime_by_id(runtime_id)
.unwrap_or_else(|| {
panic!("Runtime with ID {} does not exist", runtime_id);
})
.is_supported(feature)
}
}