[][src]Trait exonum::runtime::Runtime

pub trait Runtime: Send + Debug + 'static {
    fn deploy_artifact(
        &mut self,
        artifact: ArtifactId,
        deploy_spec: Vec<u8>
    ) -> Box<dyn Future<Item = (), Error = ExecutionError>>;
fn is_artifact_deployed(&self, id: &ArtifactId) -> bool;
fn start_adding_service(
        &self,
        context: ExecutionContext,
        spec: &InstanceSpec,
        parameters: Vec<u8>
    ) -> Result<(), ExecutionError>;
fn commit_service(
        &mut self,
        snapshot: &dyn Snapshot,
        spec: &InstanceSpec
    ) -> Result<(), ExecutionError>;
fn execute(
        &self,
        context: ExecutionContext,
        call_info: &CallInfo,
        arguments: &[u8]
    ) -> Result<(), ExecutionError>;
fn state_hashes(&self, snapshot: &dyn Snapshot) -> StateHashAggregator;
fn before_commit(
        &self,
        context: ExecutionContext,
        instance_id: InstanceId
    ) -> Result<(), ExecutionError>;
fn after_commit(&mut self, snapshot: &dyn Snapshot, mailbox: &mut Mailbox); fn initialize(&mut self, blockchain: &Blockchain) { ... }
fn on_resume(&mut self) { ... }
fn shutdown(&mut self) { ... } }

Runtime environment for Exonum services.

You can read more about the life cycle of services and transactions above.

Using this trait, you can extend the Exonum blockchain with the services written in different languages. It assumes that the deployment procedure of a new service may be complex and long and even may fail; therefore, an additional entity is introduced - artifacts. Each artifact has a unique identifier and, depending on the runtime, may have an additional specification needed for its deployment; e.g., files to be compiled. An artifact creates corresponding service instances similar to classes in object-oriented programming.

Call Ordering

Within the lifetime of a Runtime, calls to its methods have the following order:

LIFE ::= initialize (GENESIS | RESUME) BLOCK* shutdown
GENESIS ::= (deploy_artifact | start_adding_service commit_service)* after_commit
RESUME ::= (deploy_artifact | commit_service)* on_resume
BLOCK* ::= PROPOSAL+ COMMIT
PROPOSAL ::= (execute | start_adding_service)* before_commit*
COMMIT ::= deploy_artifact* commit_service* after_commit

The ordering for the "readonly" methods is_artifact_deployed and state_hashes in relation to the lifecycle above is not specified.

Consensus and Local Methods

The following methods should return the same result given the same arguments for all nodes in the blockchain network:

  • execute
  • before_commit
  • start_adding_service
  • state_hashes

All these methods except for state_hashes should also produce the same changes to the storage via provided ExecutionContext. Discrepancy in node behavior within these methods may lead to a consensus failure.

The other Runtime methods may execute logic specific to the node.

Handling Panics

Unless specified in the method docs, a panic in the Runtime methods will not be caught and will cause node termination. You may use catch_panic method to catch panics according to panic policy.

Required methods

fn deploy_artifact(
    &mut self,
    artifact: ArtifactId,
    deploy_spec: Vec<u8>
) -> Box<dyn Future<Item = (), Error = ExecutionError>>

Request to deploy artifact with the given identifier and additional deploy specification.

This method is called once for a specific artifact during Runtime lifetime:

  • For newly added artifacts, the method is called as the decision to deploy the artifact is made by the supervisor service.
  • After a node restart, the method is called for all previously deployed artifacts.

fn is_artifact_deployed(&self, id: &ArtifactId) -> bool

Return true if the specified artifact is deployed in this runtime.

fn start_adding_service(
    &self,
    context: ExecutionContext,
    spec: &InstanceSpec,
    parameters: Vec<u8>
) -> Result<(), ExecutionError>

Runs the constructor of a new service instance with the given specification and initial arguments. The constructor can initialize the storage of the service, check for dependencies, etc.

The constructor is run exactly once during blockchain lifetime for each successfully initialized service instance. That is to say, it is not called on a node restart. The caveat here is "successfully initialized"; at the point start_adding_service is called, the service is not guaranteed to eventually be added to the blockchain via commit_service. Indeed, committing the service will not follow if the alternative block proposal without the service instantiation was accepted. If the commit_service call is performed, it is guaranteed to be performed in the closest committed block, i.e., before the nearest Runtime::after_commit().

The dispatcher does not route transactions and before_commit events to the service until after commit_service() is called with the same instance spec.

The runtime should discard the instantiated service instance after completing this method, unless there are compelling reasons to retain it (e.g., creating an instance takes very long time). Alternatively, "garbage" services may be removed from Runtime in after_commit because of the time dependence between commit_service and after_commit described above. The runtime should commit long-term resources for the service after a commit_service() call. Since discarded instances persist their state in a discarded fork, no further action is required to remove this state.

Return value

The Runtime should catch all panics except for FatalErrors and convert them into an ExecutionError.

Returning an error or panicking provides a way for the Runtime to signal that service instantiation has failed. As a rule of a thumb, changes made by the method will be rolled back after such a signal (the exact logic is determined by the supervisor). Because an error is one of expected / handled outcomes, verifying prerequisites for instantiation and reporting corresponding failures should be performed at this stage rather than in commit_service.

fn commit_service(
    &mut self,
    snapshot: &dyn Snapshot,
    spec: &InstanceSpec
) -> Result<(), ExecutionError>

Permanently adds a service to the runtime.

This method is called once for a specific service instance during Runtime lifetime:

  • For newly added instances, the method is called when the fork with the corresponding start_adding_service() call is committed.
  • After a node restart, the method is called for all existing service instances.

It is guaranteed that start_adding_service() was called with the same spec earlier and returned Ok(()). The results of the call (i.e., changes to the blockchain state) are guaranteed to be persisted from the call.

Arguments

snapshot is the storage snapshot at the latest height when the method is called:

  • If the service is committed during node operation, snapshot is taken at the moment after applying the fork for which the corresponding start_adding_service was performed.
  • If the service is resumed after node restart, snapshot is the storage state at the node start.

For builtin services on the first node start snapshot will not contain info on the genesis block. Thus, using some core APIs (like requesting the current blockchain height) will result in a panic.

Return value

An error or panic returned from this method will not be processed and will lead to the node stopping. A runtime should only return an error / panic if the error is local to the node with reasonable certainty, rather than common to all nodes in the network. (The latter kind of errors should be produced during the preceding start_adding_service call.) The error should contain a description allowing the node administrator to determine the root cause of the error and (ideally) recover the node by eliminating it.

fn execute(
    &self,
    context: ExecutionContext,
    call_info: &CallInfo,
    arguments: &[u8]
) -> Result<(), ExecutionError>

Dispatches payload to the method of a specific service instance.

Arguments

Service instance name and method ID are provided in the call_info argument and interface name is provided as the corresponding field of the context argument.

Blank interface name denotes the "default" interface; it should be supported by all services. The methods of the default interface are defined by the service artifact and thus may have different signatures for different services.

Non-empty interface name denotes an interface defined externally to the service instance. In this case, the name is a Protobuf flavor of a fully qualified name (e.g., exonum.Configure), and method signatures can be inferred from the name using an interface definition.

Note. Support of non-default interfaces is experimental; as such, an IDL for them is not stabilized yet.

Return value

  • If service does not implement an interface, return NoSuchInterface error.
  • If the interface does not have a method, return NoSuchMethod error.

An error or panic returned from this method will lead to the rollback of all changes in the fork enclosed in the context. Runtimes can, but are not required to convert panics into errors.

fn state_hashes(&self, snapshot: &dyn Snapshot) -> StateHashAggregator

Gets the state hashes of the every available service in the runtime.

fn before_commit(
    &self,
    context: ExecutionContext,
    instance_id: InstanceId
) -> Result<(), ExecutionError>

Notifies a service stored in this runtime about the end of the block, allowing it to modify the blockchain state after all transactions in the block are processed.

before_commit is called for every service active at the beginning of the block (i.e., services instantiated within the block do not receive a call) exactly once for each block.

Return value

An error or panic returned from this method will lead to the rollback of all changes in the fork enclosed in the context. Runtimes can, but are not required to convert panics into errors.

fn after_commit(&mut self, snapshot: &dyn Snapshot, mailbox: &mut Mailbox)

Notifies the runtime about commitment of a new block.

This method is guaranteed to be called after all commit_service calls related to the same block. The method is called exactly once for each block in the blockchain, including the genesis block.

A block is not yet persisted when this method is called; the up to date block information is provided in the snapshot. It corresponds exactly to the information eventually persisted; i.e., no modifying operations are performed on the block.

mailbox is used to send async commands to the dispatcher. This mechanism is used, e.g., by the supervisor service to enqueue artifact deployment. A runtime may ignore mailbox if its services (or the runtime itself) do not require privileged access to the dispatcher.

Loading content...

Provided methods

fn initialize(&mut self, blockchain: &Blockchain)

Initializes the runtime, providing a Blockchain instance for further use.

This method is guaranteed to be called before any other Runtime methods. It is called exactly once during Runtime lifetime.

The default implementation does nothing.

fn on_resume(&mut self)

Notifies the runtime that the dispatcher has completed re-initialization after node restart, including restoring the deployed artifacts / started service instances for all runtimes.

This method is called no more than once during Runtime lifetime. It is called iff the blockchain had genesis block when the node was started. The blockchain state is guaranteed to not change between initialize and on_resume calls.

The default implementation does nothing.

fn shutdown(&mut self)

Notify the runtime that it has to shutdown.

This callback is invoked sequentially for each runtime just before the node shutdown, so runtimes can stop themselves gracefully.

Invoking of this callback is guaranteed to be the last operation for the runtime. Since this method is a part of shutdown process, runtimes can perform blocking and heavy operations here if needed.

Loading content...

Implementors

impl Runtime for RustRuntime[src]

Loading content...