[−][src]Trait exonum::runtime::Runtime
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>>
&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>
&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 FatalError
s 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>
&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 correspondingstart_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>
&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>
&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.
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.
Implementors
impl Runtime for RustRuntime
[src]
fn initialize(&mut self, blockchain: &Blockchain)
[src]
fn on_resume(&mut self)
[src]
fn deploy_artifact(
&mut self,
artifact: ArtifactId,
spec: Vec<u8>
) -> Box<dyn Future<Item = (), Error = ExecutionError>>
[src]
&mut self,
artifact: ArtifactId,
spec: Vec<u8>
) -> Box<dyn Future<Item = (), Error = ExecutionError>>
fn is_artifact_deployed(&self, id: &ArtifactId) -> bool
[src]
fn start_adding_service(
&self,
context: ExecutionContext,
spec: &InstanceSpec,
parameters: Vec<u8>
) -> Result<(), ExecutionError>
[src]
&self,
context: ExecutionContext,
spec: &InstanceSpec,
parameters: Vec<u8>
) -> Result<(), ExecutionError>
fn commit_service(
&mut self,
_snapshot: &dyn Snapshot,
spec: &InstanceSpec
) -> Result<(), ExecutionError>
[src]
&mut self,
_snapshot: &dyn Snapshot,
spec: &InstanceSpec
) -> Result<(), ExecutionError>
fn execute(
&self,
context: ExecutionContext,
call_info: &CallInfo,
payload: &[u8]
) -> Result<(), ExecutionError>
[src]
&self,
context: ExecutionContext,
call_info: &CallInfo,
payload: &[u8]
) -> Result<(), ExecutionError>
fn state_hashes(&self, snapshot: &dyn Snapshot) -> StateHashAggregator
[src]
fn before_commit(
&self,
context: ExecutionContext,
instance_id: InstanceId
) -> Result<(), ExecutionError>
[src]
&self,
context: ExecutionContext,
instance_id: InstanceId
) -> Result<(), ExecutionError>