[−][src]Crate exonum_rust_runtime
The current runtime is for running native services written in Rust.
In the Rust runtime a set of service artifacts that you may want to deploy is static. The set is defined at the time of compilation. Once the set is created, you can change it only by the node binary recompilation.
Beware of removing artifacts from the Rust runtime. An attempt to remove an artifact from an instance that is already running can cause the blockchain to break. It is only safe to add new artifacts.
The Rust runtime does not provide any level of service isolation from the operation system. Therefore, the security audit of the artifacts that should be deployed is up to the node administrators.
The artifact interface in the Rust runtime is represented by the
ServiceFactory
trait. The trait creates service instances and provides
information about the artifact.
Examples
Minimal complete example
use exonum::runtime::{BlockchainData, ExecutionError}; use exonum_rust_runtime::{ExecutionContext, Service}; use exonum_derive::*; use serde_derive::*; // Determine the types of data that will be used in service transactions. #[derive(Debug, PartialEq, Serialize, Deserialize, BinaryValue)] #[binary_value(codec = "bincode")] pub struct CreateWallet { pub name: String, } // You may create service-specific error types. #[derive(Debug, ExecutionFail)] pub enum Error { /// Wallet with the specified owner key already exists. WalletAlreadyExists = 1, } // Define a transaction interface for your service by creating a `Transactions` trait with // the following attribute and method signatures. #[exonum_interface] pub trait Transactions<Ctx> { type Output; // Each method in service should have an `interface_method` attribute specifying its ID. // Alternative is to use `#[exonum_interface(auto_ids)]` to assign IDs automatically, but // this is not a good idea for production code, since the method IDs assigned automatically // can change (e.g. because of reordering methods in trait). #[interface_method(id = 0)] // Each method of the trait should have a signature of the following format. // The argument should implement the `BinaryValue` trait. fn create_wallet(&self, context: Ctx, arg: CreateWallet) -> Self::Output; } // In order a service could process transactions, you have to implement the // `ServiceDispatcher` trait, which can be derived using the corresponding macro. // To explain to the runtime how to create instances of this service, you have // to implement the `ServiceFactory` trait by using the `ServiceFactory` derive macro. #[derive(Debug, ServiceDispatcher, ServiceFactory)] // Declare that the service implements the `Transactions` interface // that was presented above. #[service_dispatcher(implements("Transactions"))] // By default the macro uses the crate name and version to provide an artifact ID // for this service factory. You should only provide a path to the generated // Protobuf schema. #[service_factory(proto_sources = "exonum::proto::schema")] pub struct WalletService; // Do not forget to implement the `Transactions` and `Service` traits // for the service. impl Transactions<ExecutionContext<'_>> for WalletService { type Output = Result<(), ExecutionError>; fn create_wallet( &self, context: ExecutionContext<'_>, arg: CreateWallet, ) -> Result<(), ExecutionError> { // Some business logic... Ok(()) } } impl Service for WalletService {}
Stateful Service Definition
Beware of stateful services in production, use this functionality only for debugging and prototyping.
#[exonum_interface] pub trait Transactions<Ctx> { // service methods... } // If your service has a state, for example, for debugging purposes, then you can // use a separate structure for the service. #[derive(Debug, Default, ServiceDispatcher)] #[service_dispatcher(implements("Transactions"))] pub struct StatefulService { state: u64, } #[derive(Debug, ServiceFactory)] #[service_factory( // In this case you have to specify the service constructor explicitly. service_constructor = "Self::new_instance", proto_sources = "exonum::proto::schema", // To specify the artifact name and/or version explicitly you have to use the // following attributes. artifact_name = "stateful", artifact_version = "1.0.0", )] pub struct StatefulServiceFactory; impl StatefulServiceFactory { fn new_instance(&self) -> Box<dyn Service> { Box::new(StatefulService::default()) } }
Removing Transactions
If transaction became obsolete, it can be removed from the service.
Removed transaction will remain its ID, but attempt to invoke it will result
in returning CommonError::NoSuchMethod
.
Note to service authors: when removing transaction from interface, leave a comment why this method was removed and which ID it has. It is not required, but seeing the service history may be helpful, and it's easier to see than ID in the macro above the trait.
Example:
#[exonum_interface(removed_method_ids(0, 2))] pub trait Transactions<Ctx> { type Output; // Method with ID 0 is removed because it was buggy. #[interface_method(id = 1)] fn actual_method(&self, context: Ctx, arg: u64) -> Self::Output; // Method with ID 2 is removed because it wasn't used by anybody. } #[derive(Debug, ServiceDispatcher, ServiceFactory)] #[service_dispatcher(implements("Transactions"))] #[service_factory(proto_sources = "exonum::proto::schema")] pub struct SampleService; impl Transactions<ExecutionContext<'_>> for SampleService { type Output = Result<(), ExecutionError>; // Implement only existing methods in trait. fn actual_method( &self, context: ExecutionContext<'_>, arg: u64, ) -> Result<(), ExecutionError> { // Some business logic... Ok(()) } } impl Service for SampleService {}
Interfaces
By bringing an interface trait into scope, you can use its methods with any stub type. Stub here means a type that can process calls from any interface. (What exactly is meant by processing depends on the stub and on the provided context argument.) For example, the following stubs are defined in this crate:
Stub | Behavior |
---|---|
TxStub | Generates unsigned transactions |
KeyPair | Generates signed transactions (preferred) |
(PublicKey, SecretKey) | Generates signed transactions |
Broadcaster | Broadcasts transactions signed by the service keys of the node |
ExecutionContext | Calls methods of another service during transaction execution (1) |
- Beware that this is experimental functionality which is subject to change in next releases.
More stub types can be defined in other crates. To define a stub type, you need to implement
one of GenericCall
or GenericCallMut
traits.
Mutable interfaces
#[exonum_interface]
macro produces a mutable version of the interface trait,
which differs from the original trait in the following ways:
- Name is the original trait name appended with
Mut
(e.g.,TransactionsMut
) - All methods consume
&mut self
instead of&self
Otherwise, the mutable trait is a carbon copy of the original trait.
The mutable trait is necessary for some stub types (e.g., ExecutionContext
) because they need
to mutate their state when processing the calls. Hence, the mutable trait should be
exported from the crate along with the original "immutable" trait.
Interface usage
#[exonum_interface] pub trait Transactions<Ctx> { type Output; #[interface_method(id = 0)] fn create_wallet(&self, context: Ctx, arg: CreateWallet) -> Self::Output; #[interface_method(id = 1)] fn transfer(&self, context: Ctx, arg: Transfer) -> Self::Output; } // Create a signed transaction. let keypair = KeyPair::random(); let create_wallet: CreateWallet = // ... // The context in this case is the numerical instance ID. let instance_id = 100; let tx = keypair.create_wallet(instance_id, create_wallet); let transfer: Transfer = // ... let other_tx = keypair.transfer(instance_id, transfer); // The same call with the explicit method attribution: let other_tx = Transactions::transfer(&keypair, instance_id, transfer); // Using the trait within another service implementation: fn batch_transfers( &self, mut ctx: ExecutionContext<'_>, wallet_count: u64, ) -> Result<(), ExecutionError> { let receiver_service = "token"; // ^-- `ExecutionContext` allows to use any of service IDs as the context. for _ in 0..wallet_count { let transfer: Transfer = // ... ctx.transfer(receiver_service, transfer)?; // The same call with the explicit attribution: TransactionsMut::transfer(&mut ctx, receiver_service, transfer)?; } Ok(()) }
Modules
api | Building blocks for creating HTTP API of Rust services. |
spec | Specifications of Rust and non-Rust artifacts for use in deployment. |
Structs
AfterCommitContext | Provide context for the |
ArtifactProtobufSpec | Artifact Protobuf specification for the Exonum clients. |
Broadcaster | Transaction broadcaster. |
ExecutionContext | Provides the current state of the blockchain and the caller information for the call which is being executed. |
MethodDescriptor | Descriptor of a method declared as a part of the service interface. |
ProtoSourceFile | Artifact Protobuf file sources. |
RustRuntime | Rust runtime entity. |
RustRuntimeBuilder | Builder of the |
TxStub | Stub that creates unsigned transactions. |
Enums
Error | List of possible Rust runtime errors. |
ProtoSourcesQuery | Protobuf sources query parameters. |
Traits
DefaultInstance | Provides default instance configuration parameters for |
GenericCall | Generic / low-level stub implementation which is defined for any method in any interface. |
GenericCallMut | Generic / low-level stub implementation which is defined for any method in any interface.
Differs from |
Interface | A service interface specification. |
Service | Describes an Exonum service instance. |
ServiceDispatcher | Describes how the service instance should dispatch specific method calls with consideration of the interface where the method belongs. |
ServiceFactory | Describes a service instance factory for the specific Rust artifact. |