[][src]Module exonum::runtime::versioning

Versioning tools for Exonum artifacts.

Versioning Problem

The problem solved by versioning is as follows. Exonum services have clients, both internal (other services on the same blockchain) and external (e.g., light clients and other software capable of submitting transactions). For a multitude of reasons, the clients may have different idea as to the service capabilities than the reality at hand.

Here's hypothetical manifestations of the problem:

  • The client thinks service with a certain ID is a crypto-token service, but in reality it is a time oracle.
  • The client correctly thinks that a service with a certain ID is a crypto-token service, but is unaware that the format of the transfer transaction has changed.
  • The client (another service) attempts to get the consolidated time from the schema of a time oracle, but in reality it's not a time oracle. (Or it is a newer time oracle with changed schema layout.)

In all these cases, the lack of knowledge on the client side may lead to unpredictable consequences. In the best case, a transaction constructed by such a client will turn out to be garbage from the service perspective, so it will just return a deserialization error. In the worst case, the transaction may be interpreted arbitrarily. The same reasoning is true for the service schema; in the best case, accessing the bogus schema will lead to an error due to the mismatch of expected an actual index types. In the worst case, the indexes will be accessed, but will return garbage data or lead to undefined behavior of the node.

Artifact versioning

For any reasonable solution to the problem above to work, Exonum artifacts must be semantically versioned. Indeed, semantic versioning allows to reason about client / service compatibility in terms other than "Any specific version of a service artifact is absolutely incompatible with any other version."

Correct versioning is the responsibility of the service developers; the framework does not (and cannot) check versioning automatically.

The general guidelines to maximize service longevity are:

  • Versioning concerns all public interfaces of the service. As of Exonum 1.0, these interfaces are transactions and the (public part of) service schema.
  • Transaction methods can be evolved much like Protobuf messages (in fact, transaction payloads should be Protobuf messages for this reason). Semantics of a method with the given ID must never change; in particular, the method ID must never be reused.
  • Removing a method or disabling processing for certain payloads should be considered a breaking change (with a possible exclusion of bug fixes).
  • Public service schema should expose the minimum possible number of indexes, since the changes in these indexes will be breaking. See the example below.
  • Having non-public indexes in the public part of the schema does not solve the problem of compatibility. The client code will construct these indexes anyway, and if the indexes are gone or have been modified in a newer service version, this will lead to an error or undefined behavior.

Transactions versioning

To be able to process transactions, service must have a static mapping between numeric identifier of transaction and logic of transaction processing. Logic of transaction processing may include deserializing input parameters from byte array, processing the input and reporting the execution result (which can be either successful or unsuccessful).

Important: Transaction numeric identifier is considered a constant during all the time of service existence. It means that if transaction was declared with certain ID, its logic can be updated (e.g., to fix a bug) or be removed, but it never should be replaced with other transaction.

If transaction was removed from service, attempt to invoke it should always result in returning an ExecutionError.

You should use CommonError::MethodRemoved to report the error in case a method was removed.

At the same time, Exonum core does not provide a tool for marking transaction as deprecated. It is expected that service authors will notify users about transaction deprecation via documentation update or in any other applicable way.

Versioning for clients

To defend against these scenarios, Exonum provides following defences.

Manual Artifact Verification

The client may check the name and version of the artifact for a specific service using builtin APIs:

Version Tooling

  • For service schemas, BlockchainData and SnapshotExt expose the service_schema method. This allows to run versioning checks automatically.
  • For transactions, clients may use the middleware service.

Examples

Demonstrates how to define a service schema in a forward-compatible way.

/// Full schema which embeds the public part.
#[derive(Debug, FromAccess)]
pub(crate) struct SchemaImpl<T: Access> {
    /// Public part of the schema.
    #[from_access(flatten)]
    pub public: Schema<T>,

    // Private fields (public within the crate). These fields may arbitrarily change
    // without breaking compatibility.
    pub private_entry: Entry<T::Base, String>,
    pub private_group: Group<T, str, ListIndex<T::Base, u64>>,
}

/// Public part of the schema.
#[derive(Debug, FromAccess, RequireArtifact)]
#[require_artifact(name = "some.Token", version = "^1")]
pub struct Schema<T: Access> {
    /// Public index. Note that changing key or value type will be a breaking change.
    /// To extend interface longevity, it makes sense to make key / value types
    /// Protobuf messages.
    pub wallets: ProofMapIndex<T::Base, str, u64>,
}

// Then, the `Schema` may be used like this:
use exonum::runtime::SnapshotExt;

let snapshot: Box<dyn Snapshot> = // ...
let schema: Schema<_> = snapshot.service_schema("my-service")?;
let balance = schema.wallets.get("Alice").unwrap_or(0);

Structs

ArtifactReq

Requirement on an artifact. Can be matched against artifact identifiers.

Version

Represents a version number conforming to the semantic versioning scheme.

VersionReq

A VersionReq is a struct containing a list of predicates that can apply to ranges of version numbers. Matching operations can then be done with the VersionReq against a particular version to see if it satisfies some or all of the constraints.

Enums

ArtifactReqError

Artifact requirement error.

Traits

RequireArtifact

Versioned object that checks compatibility with the artifact of a service.