1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
// Copyright 2020 The Exonum Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Common building blocks that compose runtimes for the Exonum blockchain. //! //! Each runtime contains specific services that execute transactions, process events, //! provide user APIs, etc. A unified *dispatcher* redirects all the calls //! and requests to an appropriate runtime environment. Thus, a blockchain interacts with the //! dispatcher, and not with specific runtime instances. //! //! # Artifacts //! //! An artifact creates service instances similar to classes in object-oriented programming. //! Artifacts reflect the assumption that deploying business logic onto the blockchain //! may take a long time, may fail, end up with differing results on different nodes, etc. //! Thus, artifacts decouple the complex *deployment* of the business logic from its instantiation //! (which we assume is simple / non-fallible). //! //! Depending on the runtime, an artifact may have an additional specification required //! for its deployment; e.g., files to be compiled. //! //! Each runtime has its own [artifacts] registry. Users can create services from the stored //! artifacts. An artifact identifier is required by the runtime to construct service instances. //! In other words, an artifact identifier is similar to a class name, and a specific //! service instance - to a class instance. A single artifact may be used to instantiate //! zero or more services. //! //! The format of the artifact ID is uniform across runtimes - it is essentially a string. //! But the runtime may customize artifact deployment via runtime-specific deployment arguments. //! //! # Artifact Lifecycle //! //! 1. An artifact is assembled in a way specific to the runtime. For example, an artifact may //! be compiled from sources and packaged using an automated build system. //! //! 2. The artifact with the service is deployed on the blockchain. The decision to deploy the //! artifact and the deployment spec are usually performed by the blockchain administrators. //! The corresponding logic is customizable via the [supervisor service](#supervisor-service). //! What deployment entails depends on the runtime; e.g., the artifact may be downloaded //! by each Exonum node, verified for integrity and then added into the execution environment. //! //! 3. For each node, an artifact may be deployed either asynchronously or synchronously, that is //! in a blocking manner. The supervisor usually first commands a node to deploy the artifact //! asynchronously via [`Mailbox`], once the decision to start deployment is reached //! by the blockchain administrators. Asynchronous deployment speed and outcome may differ among //! nodes. //! //! 4. The supervisor translates the local deployment outcomes into a consensus-agreed result. //! For example, the supervisor may collect confirmations from the validator nodes that have //! successfully deployed the artifact. Once all the validator nodes have sent //! their confirmations, the artifact is *committed*. As a part of the service logic, //! artifact commitment is completely deterministic, agreed via consensus, //! and occurs at the same blockchain height for all nodes in the network. //! //! 5. Once the artifact is committed, every node in the network must have it deployed //! in order to continue functioning. //! If a node has not deployed the artifact previously, deployment becomes blocking. The node //! does not participate in consensus or block processing until the deployment is completed //! successfully. If the deployment is unsuccessful, the node stops indefinitely. //! The deployment confirmation mechanics is built into the supervisor. Thus, it is reasonable //! to assume that a deployment failure at this stage is local to the node and //! could be fixed by the node admin. //! //! 6. If the artifact is not associated with any services, it can be *unloaded*. Unloading //! the artifact may free resources associated with it in the corresponding runtime. //! Like other lifecycle events, unloading an artifact is controlled by the supervisor service. //! //! # Service Lifecycle //! //! 1. Once the artifact is committed, it is possible to instantiate the corresponding service. //! Each instantiation request contains an ID of the previously deployed artifact, //! a string instance ID, and instantiation arguments in a binary encoding //! (by convention, Protobuf). As with the artifacts, the logic that controls instantiation //! is encapsulated in the supervisor service. //! //! 2. During instantiation the service gets a numeric ID, which is used to reference //! the service in transactions. The runtime can execute initialization logic defined //! in the service artifact; e.g., the service may store some initial data in the storage, //! check service dependencies, etc. If the service (or the enclosing runtime) signals that //! the initialization failed, the service is considered not instantiated. //! //! 3. Once the service is instantiated, it can process transactions and interact with the //! external users in other ways. Different services instantiated from the same artifact //! are independent and have separate blockchain storages. Users can distinguish services //! by their IDs; both numeric and string IDs are unique within a blockchain. (Note that //! the transition to the "active" state is not immediate; //! see [*Service State Transitions*](#service-state-transitions) section below.) //! //! 4. Active service instances can be stopped or frozen by a corresponding request to the dispatcher. //! //! The dispatcher is responsible for persisting artifacts and services across node restarts. //! //! A **stopped** service no longer participates in business logic, i.e., //! it does not process transactions or hooks, and does not interact with the users //! in any way. Service data becomes unavailable for the other services, //! but still exists. The service name and identifier remain reserved //! for the stopped service and can't be used again for adding new services. //! //! **Frozen** service state is similar to the stopped one, except the service //! state can be read both by internal readers (other services) and external ones //! (HTTP API handlers). //! //! ## Service Hooks //! //! Each active service is called before any transactions in the block are processed; //! we call this `before_transactions` hook. The service may modify the blockchain state in this hook. //! Likewise, each active service is called after all transactions in the block have been processed //! (we call this `after_transactions` hook). These calls are quite similar to transactions: //! //! - Each call is isolated //! - Service logic may return an error, meaning that all state changes made within the hook //! are rolled back //! - The service may call other services within the hook //! //! ## Service State Transitions //! //! Transitions between service states (including service creation) occur once the block //! with the transition is committed; the effect of a transition is not immediate. This means //! that, for example, an instantiated service cannot process transactions or internal calls //! in the block with instantiation, but can in the following block. Likewise, the service hooks //! (`before_transactions` / `after_transactions`) are *not* called in the block with service //! instantiation. //! //! When the service is stopped or frozen, the reverse is true: //! //! - The service continues processing transactions until the end of the block containing //! the stop command //! - The service hooks *are* called for the service in this block //! //! # Transaction Lifecycle //! //! 1. An Exonum client creates a transaction message which includes two parts. The first part is //! the [`CallInfo`] - information about a method to call. The second part is the //! serialized method parameters as a payload. //! The client then signs the message using the Ed25519 signature system. //! //! 2. The client transmits the message to one of the Exonum nodes in the network. //! //! 3. The node verifies correctness of the transaction signature and retransmits it to //! the other network nodes if it is correct. //! //! 4. When the consensus algorithm finds a feasible candidate for the next block //! of transactions, transactions in this block are passed to the dispatcher for execution. //! //! 5. The dispatcher uses a lookup table to find the corresponding [`Runtime`] for each transaction //! by the [`instance_id`] recorded in the transaction message. If the corresponding runtime exists, //! the dispatcher passes the transaction into this runtime for immediate [execution]. //! //! 6. After execution the transaction [execution status] is written into the blockchain. //! //! # Data Migration Lifecycle //! //! Service data can be migrated to a newer version of the service artifact. //! See [`migrations` module docs] for details. //! //! # Supervisor Service //! //! A supervisor service is a service that has additional privileges. This service //! allows deploying artifacts and instantiating new services after the blockchain is launched //! and running. Moreover the Supervisor service allows update the configuration or stop the //! active service instances. //! Other than that, it looks like an ordinary service. //! //! To enable adding new artifacts / services to the blockchain after its start, the supervisor //! must be one of the builtin service instances. //! //! The supervisor service is distinguished by its numerical ID, which must be set //! to [`SUPERVISOR_INSTANCE_ID`]. Services may assume that transactions originating from //! the supervisor service are authorized by the blockchain administrators. This can be used //! in services: if a certain transaction originates from a service with `SUPERVISOR_INSTANCE_ID`, //! it is authorized by the administrators. //! //! # See Also //! //! - [Article on service lifecycle in general docs][docs:lifecycle] //! - [Blog article on service lifecycle][blog:lifecycle] //! //! [`AnyTx`]: struct.AnyTx.html //! [`CallInfo`]: struct.CallInfo.html //! [`instance_id`]: struct.CallInfo.html#structfield.instance_id //! [`Runtime`]: trait.Runtime.html //! [execution]: trait.Runtime.html#execute //! [execution status]: struct.ExecutionStatus.html //! [artifacts]: struct.ArtifactId.html //! [`migrations` module docs]: migrations/index.html //! [`SUPERVISOR_INSTANCE_ID`]: constant.SUPERVISOR_INSTANCE_ID.html //! [`Mailbox`]: struct.Mailbox.html //! [`ExecutionError`]: struct.ExecutionError.html //! [`instance_id`]: struct.CallInfo.html#structfield.method_id //! [docs:lifecycle]: https://exonum.com/doc/version/latest/architecture/service-lifecycle/ //! [blog:lifecycle]: https://medium.com/meetbitfury/about-service-lifecycles-in-exonum-58c67678c6bb pub use self::{ blockchain_data::{BlockchainData, SnapshotExt}, dispatcher::{ remove_local_migration_result, Action as DispatcherAction, Mailbox, Schema as DispatcherSchema, }, error::{ catch_panic, CallSite, CallType, CommonError, CoreError, ErrorKind, ErrorMatch, ExecutionError, ExecutionFail, ExecutionStatus, }, execution_context::{ExecutionContext, ExecutionContextUnstable, SupervisorExtensions}, types::{ AnyTx, ArtifactId, ArtifactSpec, ArtifactState, ArtifactStatus, CallInfo, Caller, CallerAddress, InstanceId, InstanceQuery, InstanceSpec, InstanceState, InstanceStatus, MethodId, MigrationStatus, }, }; pub(crate) use self::{dispatcher::Dispatcher, error::ExecutionErrorAux}; pub mod migrations; pub mod oneshot; pub mod versioning; use exonum_merkledb::Snapshot; use semver::Version; use std::fmt; use self::migrations::{InitMigrationError, MigrationScript}; use crate::blockchain::Blockchain; mod blockchain_data; mod dispatcher; pub(crate) mod error; mod execution_context; mod types; /// Persistent identifier of a supervisor service instance. /// /// Only a service with this ID can perform actions with the dispatcher. pub const SUPERVISOR_INSTANCE_ID: InstanceId = 0; /// List of predefined runtimes. #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[repr(u32)] #[non_exhaustive] pub enum RuntimeIdentifier { /// Built-in Rust runtime. Rust = 0, /// Exonum Java Binding runtime. Java = 1, } impl From<RuntimeIdentifier> for u32 { fn from(id: RuntimeIdentifier) -> Self { id as Self } } impl RuntimeIdentifier { fn transform(id: u32) -> Result<Self, ()> { match id { 0 => Ok(Self::Rust), 1 => Ok(Self::Java), _ => Err(()), } } } impl fmt::Display for RuntimeIdentifier { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Rust => formatter.write_str("Rust runtime"), Self::Java => formatter.write_str("Java runtime"), } } } /// Optional features that may or may not be supported by a particular `Runtime`. #[derive(Debug)] #[non_exhaustive] pub enum RuntimeFeature { /// Freezing services: disabling APIs mutating service state (e.g., transactions) /// while leaving read-only APIs switched on. FreezingServices, } impl fmt::Display for RuntimeFeature { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::FreezingServices => formatter.write_str("freezing services"), } } } /// Runtime environment for Exonum services. /// /// You can read more about the life cycle of services and transactions /// [in the module docs](index.html#service-life-cycle). /// /// Using this trait, you can extend the Exonum blockchain with the services written in /// different languages. /// /// # Stability /// /// This trait is considered unstable; breaking changes may be introduced to it within /// semantically non-breaking releases. However, it is guaranteed that such changes /// will require reasonable amount of updates from the `Runtime` implementations. /// /// # Call Ordering /// /// Within the lifetime of a `Runtime`, calls to its methods have the following order: /// /// ```text /// LIFE ::= initialize (GENESIS | RESUME) BLOCK* shutdown /// GENESIS ::= /// deploy_artifact* /// (initiate_adding_service update_service_status)* /// after_commit /// RESUME ::= (deploy_artifact | update_service_status | migrate)* on_resume /// BLOCK* ::= PROPOSAL+ COMMIT /// PROPOSAL ::= /// (before_transactions CALL*)* /// (execute CALL*)* /// (after_transactions CALL*)* /// CALL ::= execute | initiate_adding_service | initiate_resuming_service | migrate /// COMMIT ::= /// (deploy_artifact | unload_artifact)* /// (update_service_status | migrate)* /// after_commit /// ``` /// /// `before_transactions`, `execute` and `after_transactions` handlers may spawn /// child calls among services; this is denoted as `CALL*` in the excerpt above. The child calls /// are executed synchronously. See the [*Service Interaction*] article for more details. /// /// The ordering for the "read-only" methods `is_artifact_deployed` and `is_supported` in relation /// to the lifecycle above is not specified. /// /// # Consensus and Local Methods /// /// The following methods should return the same result if provided arguments are the same for all /// the nodes in the blockchain network: /// /// - `before_transactions` /// - `execute` /// - `after_transactions` /// - `initiate_adding_service` /// - `initiate_resuming_service` /// /// All these methods should also produce the same changes to the storage via /// the provided `ExecutionContext`. Discrepancy in node behavior within these methods may lead /// to a consensus failure. /// /// Other `Runtime` methods may execute logic specific to the node. /// /// # Handling Panics /// /// Panics in the `Runtime` methods are **not** caught. A panic in the runtime method will cause /// the node termination. To catch panics in the Rust code and convert them to unchecked execution /// errors, use the [`catch_panic`](fn.catch_panic.html) method. /// /// [*Service Interaction*]: https://exonum.com/doc/version/latest/advanced/service-interaction/ #[allow(unused_variables)] pub trait Runtime: Send + fmt::Debug + 'static { /// Initializes the runtime, providing a `Blockchain` instance for further use. /// /// Calling this method always takes place before calling any other `Runtime` methods. /// The `initialize` method is called *exactly once* during the `Runtime` lifetime. /// /// The default implementation does nothing. fn initialize(&mut self, blockchain: &Blockchain) {} /// Checks if the runtime supports an optional feature. /// /// This method can be called by the core before performing operations that might not /// be implemented in a runtime, or by the supervisor service in order to check that a potential /// service / artifact state transition can be handled by the runtime. /// /// An implementation should return `false` for all features the runtime does not recognize. /// The default implementation always returns `false`, i.e., signals that the runtime supports /// no optional features. fn is_supported(&self, feature: &RuntimeFeature) -> bool { false } /// Notifies the runtime that the dispatcher has completed re-initialization after the /// node restart. Re-initialization includes restoring the deployed artifacts / started service /// instances for all the runtimes. /// /// This method is called *maximum once* during the `Runtime` lifetime. It is called iff /// the genesis block was already created before the node start (e.g. after node relaunch). /// The blockchain state will remain the same between the `initialize` and `on_resume` calls. /// /// The default implementation does nothing. fn on_resume(&mut self) {} /// Requests to deploy an artifact with the given identifier and an additional deploy /// specification. /// /// This method is called *once* for a specific artifact during the `Runtime` lifetime: /// /// - For newly added artifacts, the method is called as the supervisor service decides to deploy /// the artifact. /// - After the node restart, the method is called for all the previously deployed artifacts. /// /// Core guarantees that there will be no request to deploy an artifact which is already deployed, /// thus runtime should not report an attempt to do so as `ExecutionError`, but should consider it /// a bug in core. fn deploy_artifact(&mut self, artifact: ArtifactId, deploy_spec: Vec<u8>) -> oneshot::Receiver; /// Returns `true` if the specified artifact is deployed in this runtime. fn is_artifact_deployed(&self, artifact: &ArtifactId) -> bool; /// Requests to unload an artifact with the given identifier. Unloading may free resources /// (e.g., RAM) associated with the artifact. /// /// The following invariants are guaranteed to hold when this call is performed: /// /// - The artifact is deployed /// - There are no services with any status associated with the artifact, either as /// an artifact [responsible for service logic][assoc-artifact] or as a [migration target] /// of the data migration in a service. /// /// The default implementation does nothing. While this may be inefficient, this implementation /// is logically sound. Indeed, the runtime retains resources associated with the artifact /// (until the node is restarted), but on the blockchain level, the artifact is considered /// unloaded. /// /// [assoc-artifact]: struct.InstanceState.html#method.associated_artifact /// [migration target]: migrations/struct.InstanceMigration.html#structfield.target fn unload_artifact(&mut self, artifact: &ArtifactId) { // The default implementation does nothing. } /// 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 runs *exactly once* during the blockchain lifetime for each successfully /// initialized service instance. That is to say, the constructor is *not* called on a node /// restart. /// /// At the same time, when `initiate_adding_service` is called, /// there is no guarantee that the service will eventually get to the blockchain via /// `update_service_status`. The consensus may accept an alternative block proposal, in which /// the service is not instantiated or instantiated with different parameters. /// /// The `update_service_status` call always takes place /// in the closest committed block, i.e., before the nearest `Runtime::after_commit()`. /// The dispatcher routes transactions and `before_transactions` / `after_transactions` /// events to the service only after `update_service_status()` is called with the same instance /// specification. /// /// The runtime should discard the instantiated service instance after completing this method. /// Otherwise, if the service is successfully committed in the block, it will duplicate the one /// instantiated in the runtime. There may be compelling reasons for the runtime to retain /// the instantiated service. For example, if creating an instance takes very long time. /// In this case, the "garbage" services may be removed from the runtime in `after_commit` /// because of the time dependence between `update_service_status` and `after_commit` described above. /// /// The runtime should commit long-term resources for the service only after the /// `update_service_status()` call. In other words, the runtime must be sure that the service /// has been committed to the blockchain. /// /// # Return Value /// /// Returning an error is a signal of `Runtime` that the /// service instantiation has failed. As a rule of a thumb, changes made by the /// `initiate_adding_service` method will be rolled back after such a signal. The exact logic of /// the rollback is determined by the supervisor. /// /// An error is one of the expected / handled outcomes of the service instantiation procedure. /// Thus, verifying prerequisites /// for instantiation and reporting corresponding failures should be performed at this stage /// rather than in `update_service_status`. /// /// Core guarantees that there will be no request to start a service instance which is already running, /// thus runtime should not report an attempt to do so as `ExecutionError`, but should consider it /// a bug in core. fn initiate_adding_service( &self, context: ExecutionContext<'_>, artifact: &ArtifactId, parameters: Vec<u8>, ) -> Result<(), ExecutionError>; /// Resumes previously stopped service instance with the given specification and arguments. /// As an example, arguments can be used to update the service configuration. /// /// The dispatcher ensures that a service instance with the given specification has been /// previously stopped and has the proper artifact version and name. /// /// This method has the same workflow as [`initiate_adding_service`] method. /// The main difference is that `initiate_adding_service` should call the service /// `initialize` method and `initiate_resuming_service` should call the service `resume` method. /// /// [`initiate_adding_service`]: #tymethod.initiate_adding_service fn initiate_resuming_service( &self, context: ExecutionContext<'_>, artifact: &ArtifactId, parameters: Vec<u8>, ) -> Result<(), ExecutionError>; /// Notifies runtime about changes of the service instance state. /// /// This method notifies runtime about a specific service instance state changes in the /// dispatcher. Runtime should perform corresponding actions in according to changes in /// the service instance state. /// /// This method is called for a specific service instance during the `Runtime` lifetime in the /// following cases: /// /// - For newly added instances, or modified existing this method is called when the fork /// with the corresponding changes is committed. /// - After a node restart, the method is called for all existing service instances regardless /// of their statuses. /// /// For newly added instances invocation of this method guarantees that /// `initiate_adding_service()` has been called with the same `spec` already and returned /// `Ok(())`. The results of the call (i.e., changes to the blockchain state) will be /// persisted from the call. /// /// # Arguments /// /// `snapshot` is a storage snapshot at the latest height when the method is called: /// /// - Suppose the service is committed during the node operation. Then `snapshot` is taken at the /// moment the fork applies for which the corresponding `initiate_adding_service` /// has been performed. /// - Suppose the service is stopped during the node operation. `Then `snapshot` is taken at /// the moment the fork applies for which the corresponding request has been performed. /// - Suppose the service resumes after the node restart. Then `snapshot` is the storage state /// at the node start. /// /// For the built-in services, on the first node start `snapshot` will not contain information /// on the genesis block. Thus, using some core APIs, like requesting the current /// blockchain height, will result in a panic. /// /// `status` is the resulting status of the service instance. /// /// # Return value /// /// This method does not return a value, meaning that any error occurred during this method execution /// is considered critical and should lead to the node stopping. /// /// It is assumed that if `initiate_adding_service` didn't return an error previously, /// the runtime is able to update service status and within normal conditions no error is /// expected to happen. fn update_service_status(&mut self, snapshot: &dyn Snapshot, state: &InstanceState); /// Gets the migration script to migrate the data of the service to the state usable /// by a newer version of the artifact. /// /// An implementation of this method should be idempotent, i.e., return the same script or error /// for the same input. /// /// # Invariants Ensured by the Caller /// /// - `new_artifact` is deployed in the runtime /// - `data_version < new_artifact.version` /// /// # Return Value /// /// - An error signals that the runtime does not know how to migrate the service /// to a newer version. /// - `Ok(Some(_))` provides a script to execute against service data. After the script /// is executed, [`data_version`] of the service will be updated to `end_version` /// from the script. `end_version` does not need to correspond to the version of `new_artifact`, /// or to a version of an artifact deployed on the blockchain in general. /// - `Ok(None)` means that the service does not require data migration. `data_version` /// of the service will be updated to the version of `new_artifact` once the block /// with the migration command is committed; see [*Service State Transitions*] for details. /// /// [`data_version`]: struct.InstanceState.html#field.data_version /// [*Service State Transitions*]: index.html#service-state-transitions fn migrate( &self, new_artifact: &ArtifactId, data_version: &Version, ) -> Result<Option<MigrationScript>, InitMigrationError>; /// Dispatches payload to the method of a specific service instance. /// /// The call is dispatched iff the service is considered active at the moment. /// See [*Service State Transitions*] for more details. /// /// # Arguments /// /// The service instance name and method ID are provided in the `call_info` argument and /// the interface name is provided as the corresponding field of the `context` argument. /// /// A 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. /// /// A 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 the 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 the service does not implement an interface, returns a `NoSuchInterface` error. /// - If the interface does not have a method, returns a `NoSuchMethod` error. /// /// An error returned from this method will lead to the rollback of all changes /// in the fork enclosed in the `context`. /// /// [*Service State Transitions*]: index.html#service-state-transitions fn execute( &self, context: ExecutionContext<'_>, method_id: MethodId, arguments: &[u8], ) -> Result<(), ExecutionError>; /// Notifies a service stored in the present runtime about the beginning of the block. Allows /// the service to modify the blockchain state before any transaction in the block is processed. /// /// `before_transactions` is called for every service active at the beginning of the block /// exactly once for each block. Services that will be instantiated within the block do **not** /// receive a call. The method is not called for the genesis block. /// /// # Return Value /// /// An error returned from this method will lead to the rollback of all changes /// in the fork enclosed in the `context`. fn before_transactions(&self, context: ExecutionContext<'_>) -> Result<(), ExecutionError>; /// Notifies a service stored in this runtime about the end of the block. Allows the method /// to modify the blockchain state after all transactions in the block are processed. /// /// `after_transactions` is called for every service active at the beginning of the block /// exactly once for each block. Services instantiated within the block do **not** receive a call. /// Services instantiated within genesis block are activated **immediately** and /// thus `after_transactions` is invoked for them in the genesis block. /// /// # Return value /// /// An error returned from this method will lead to the rollback of all changes /// in the fork enclosed in the `context`. fn after_transactions(&self, context: ExecutionContext<'_>) -> Result<(), ExecutionError>; /// Notifies the runtime about commit of a new block. /// /// This method is called *after* all `update_service_status` 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 `snapshot` provides an up-to-date /// block information. It corresponds exactly to the information /// eventually persisted. /// /// `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. fn after_commit(&mut self, snapshot: &dyn Snapshot, mailbox: &mut Mailbox); } #[allow(clippy::use_self)] // false positive impl<T: Runtime> From<T> for Box<dyn Runtime> { fn from(value: T) -> Self { Box::new(value) } } /// A subset of [`Runtime`]s with a well-known runtime identifier. /// /// [`Runtime`]: trait.Runtime.html pub trait WellKnownRuntime: Runtime { /// Identifier of the present runtime. const ID: u32; } // TODO: Rethink visibility [ECR-3913] /// Instance of [`Runtime`] with the corresponding ID. /// /// [`Runtime`]: trait.Runtime.html #[derive(Debug)] #[non_exhaustive] pub struct RuntimeInstance { /// Identifier of the enclosed runtime. pub id: u32, /// Enclosed `Runtime` object. pub instance: Box<dyn Runtime>, } impl RuntimeInstance { /// Constructs a new `RuntimeInstance` object. pub fn new(id: u32, instance: Box<dyn Runtime>) -> Self { Self { id, instance } } } impl<T: WellKnownRuntime> From<T> for RuntimeInstance { fn from(runtime: T) -> Self { Self::new(T::ID, runtime.into()) } } /// Instance descriptor contains information to access the running service instance. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct InstanceDescriptor { /// A unique numeric ID of the service instance. /// [Read more.](struct.InstanceSpec.html#structfield.id) pub id: InstanceId, /// A unique name of the service instance. /// [Read more.](struct.InstanceSpec.html#structfield.name) pub name: String, } impl InstanceDescriptor { /// Creates a new `InstanceDescriptor` object. pub fn new(id: InstanceId, name: impl Into<String>) -> Self { Self { id, name: name.into(), } } } impl fmt::Display for InstanceDescriptor { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "{}:{}", self.id, self.name) } }