[][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:

StubBehavior
TxStubGenerates unsigned transactions
KeyPairGenerates signed transactions (preferred)
(PublicKey, SecretKey)Generates signed transactions
BroadcasterBroadcasts transactions signed by the service keys of the node
ExecutionContextCalls methods of another service during transaction execution (1)
  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 after_commit handler.

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 RustRuntime.

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 ServiceFactory.

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 GenericCall by taking self by the mutable reference.

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.