From a7645d00dd34a7d3a2b322eb4b87d4daf66ff47a Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 23 Jan 2020 10:09:28 +0200 Subject: [PATCH] Brush up main crate (#1724) --- cli/src/config.rs | 2 +- cli/src/lib.rs | 2 +- components/explorer/examples/explorer.rs | 4 +- components/explorer/tests/blockchain/mod.rs | 2 +- exonum-node/src/consensus.rs | 14 +- exonum-node/src/sandbox/mod.rs | 2 +- .../src/sandbox/sandbox_tests_helper.rs | 2 +- exonum-node/src/sandbox/tests/old.rs | 2 +- .../src/sandbox/tests/round_details.rs | 2 +- exonum-node/src/sandbox/tests/transactions.rs | 2 +- .../sandbox/tests/unsynchronized_message.rs | 2 +- exonum-node/src/state.rs | 8 +- exonum/src/blockchain/api_sender.rs | 18 +- exonum/src/blockchain/block.rs | 90 ++++------ exonum/src/blockchain/config.rs | 19 +-- exonum/src/blockchain/mod.rs | 18 +- exonum/src/blockchain/schema.rs | 32 ++-- exonum/src/blockchain/tests.rs | 4 +- exonum/src/helpers/mod.rs | 6 +- exonum/src/helpers/ordered_map.rs | 8 +- exonum/src/helpers/types.rs | 159 ++++++++---------- exonum/src/lib.rs | 105 +++++++++--- exonum/src/messages/mod.rs | 18 +- exonum/src/messages/signed.rs | 32 ++-- exonum/src/messages/types.rs | 4 +- exonum/src/proto/macros.rs | 36 ---- exonum/src/proto/mod.rs | 4 +- .../schema/exonum/key_value_sequence.proto | 2 +- exonum/src/proto/schema/mod.rs | 2 +- .../src/runtime/dispatcher/migration_tests.rs | 2 +- exonum/src/runtime/dispatcher/mod.rs | 40 +++-- exonum/src/runtime/dispatcher/tests.rs | 10 +- exonum/src/runtime/error/error_match.rs | 13 +- exonum/src/runtime/error/execution_error.rs | 3 +- exonum/src/runtime/error/mod.rs | 33 +++- exonum/src/runtime/error/tests.rs | 114 +++++-------- exonum/src/runtime/execution_context.rs | 34 ++-- exonum/src/runtime/migrations.rs | 7 + exonum/src/runtime/mod.rs | 67 +++++--- exonum/src/runtime/types.rs | 30 ++-- exonum/src/runtime/versioning.rs | 8 + runtimes/rust/benches/criterion/block.rs | 7 +- runtimes/rust/src/api.rs | 50 +++--- runtimes/rust/tests/blockchain_data.rs | 2 +- runtimes/rust/tests/inspected/mod.rs | 2 +- services/explorer/tests/api.rs | 2 +- test-suite/testkit/src/lib.rs | 2 +- 47 files changed, 514 insertions(+), 513 deletions(-) delete mode 100644 exonum/src/proto/macros.rs diff --git a/cli/src/config.rs b/cli/src/config.rs index bf28e70027..85312b9cd0 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -16,8 +16,8 @@ use exonum::{ blockchain::{ConsensusConfig, ValidatorKeys}, - exonum_merkledb::DbOptions, keys::{read_keys_from_file, Keys}, + merkledb::DbOptions, }; use exonum_node::{ ConnectListConfig, MemoryPoolConfig, NetworkConfiguration, NodeApiConfig, diff --git a/cli/src/lib.rs b/cli/src/lib.rs index fd6c4e00f3..f2340c2f05 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -92,7 +92,7 @@ pub use structopt; use exonum::{ blockchain::{config::GenesisConfigBuilder, config::InstanceInitParams}, - exonum_merkledb::RocksDB, + merkledb::RocksDB, runtime::{RuntimeInstance, WellKnownRuntime}, }; use exonum_explorer_service::ExplorerFactory; diff --git a/components/explorer/examples/explorer.rs b/components/explorer/examples/explorer.rs index 1c4444420a..7265851b05 100644 --- a/components/explorer/examples/explorer.rs +++ b/components/explorer/examples/explorer.rs @@ -257,9 +257,7 @@ fn main() { // Determine the number of blocks proposed by a specific validator let block_count = explorer .blocks(Height(1)..) // skip genesis block - .filter(|block| { - block.header().get_header::().unwrap().unwrap() == ValidatorId(0).into() - }) + .filter(|block| block.header().get_header::().unwrap() == Some(ValidatorId(0))) .count(); assert_eq!(block_count, 1); } diff --git a/components/explorer/tests/blockchain/mod.rs b/components/explorer/tests/blockchain/mod.rs index 589295c884..3a82c91741 100644 --- a/components/explorer/tests/blockchain/mod.rs +++ b/components/explorer/tests/blockchain/mod.rs @@ -147,7 +147,7 @@ pub fn create_block(blockchain: &mut BlockchainMut, transactions: Vec (Hash, Patch) { @@ -1041,17 +1041,13 @@ impl NodeHandler { .into_payload(); let (block_hash, patch) = self.create_block( - propose.validator.into(), + propose.validator, propose.height, propose.transactions.as_slice(), ); // Save patch - self.state.add_block( - block_hash, - patch, - propose.transactions, - propose.validator.into(), - ); + self.state + .add_block(block_hash, patch, propose.transactions, propose.validator); self.state .propose_mut(propose_hash) .unwrap() diff --git a/exonum-node/src/sandbox/mod.rs b/exonum-node/src/sandbox/mod.rs index 058097191c..97de2ac9c0 100644 --- a/exonum-node/src/sandbox/mod.rs +++ b/exonum-node/src/sandbox/mod.rs @@ -706,7 +706,7 @@ impl Sandbox { blockchain.merge(fork.into_patch()).unwrap(); let (_, patch) = - blockchain.create_patch(ValidatorId(0).into(), height, &hashes, &mut BTreeMap::new()); + blockchain.create_patch(ValidatorId(0), height, &hashes, &mut BTreeMap::new()); let fork = blockchain.fork(); let mut schema = Schema::new(&fork); diff --git a/exonum-node/src/sandbox/sandbox_tests_helper.rs b/exonum-node/src/sandbox/sandbox_tests_helper.rs index 22d523b36c..629ffa0cab 100644 --- a/exonum-node/src/sandbox/sandbox_tests_helper.rs +++ b/exonum-node/src/sandbox/sandbox_tests_helper.rs @@ -105,7 +105,7 @@ impl<'a> BlockBuilder<'a> { .unwrap_or_else(|| self.sandbox.current_leader()); let mut additional_headers = self.entries.clone().unwrap_or_else(AdditionalHeaders::new); - additional_headers.insert::(proposer_id.into()); + additional_headers.insert::(proposer_id); Block { height: self.height.unwrap_or_else(|| self.sandbox.current_height()), diff --git a/exonum-node/src/sandbox/tests/old.rs b/exonum-node/src/sandbox/tests/old.rs index 5f50318d68..a95a726568 100644 --- a/exonum-node/src/sandbox/tests/old.rs +++ b/exonum-node/src/sandbox/tests/old.rs @@ -75,7 +75,7 @@ fn create_propose(sandbox: &Sandbox) -> Verified { fn create_block(sandbox: &Sandbox) -> Block { let mut additional_headers = AdditionalHeaders::new(); - additional_headers.insert::(ValidatorId(2).into()); + additional_headers.insert::(ValidatorId(2)); Block { height: Height(1), diff --git a/exonum-node/src/sandbox/tests/round_details.rs b/exonum-node/src/sandbox/tests/round_details.rs index 4699c1e6c3..6afc46e372 100644 --- a/exonum-node/src/sandbox/tests/round_details.rs +++ b/exonum-node/src/sandbox/tests/round_details.rs @@ -1461,7 +1461,7 @@ fn handle_precommit_positive_scenario_commit_with_queued_precommit() { // Precommits with this block will be received during get 1st height in // fn add_one_height_with_transaction() let mut first_block = sandbox.create_block(&[tx.clone()]); - first_block.add_header::(ValidatorId(0).into()); + first_block.add_header::(ValidatorId(0)); // this propose will be used during second commit let height_one_propose = ProposeBuilder::new(&sandbox) diff --git a/exonum-node/src/sandbox/tests/transactions.rs b/exonum-node/src/sandbox/tests/transactions.rs index c2f52736c3..6d657458cf 100644 --- a/exonum-node/src/sandbox/tests/transactions.rs +++ b/exonum-node/src/sandbox/tests/transactions.rs @@ -128,7 +128,7 @@ fn tx_pool_size_overflow() { ); let mut block = sandbox.create_block(&[tx1.clone()]); - block.add_header::(ValidatorId(2).into()); + block.add_header::(ValidatorId(2)); block.height = Height(1); sandbox.recv(&propose); diff --git a/exonum-node/src/sandbox/tests/unsynchronized_message.rs b/exonum-node/src/sandbox/tests/unsynchronized_message.rs index bd13c3d06e..30a019be34 100644 --- a/exonum-node/src/sandbox/tests/unsynchronized_message.rs +++ b/exonum-node/src/sandbox/tests/unsynchronized_message.rs @@ -96,7 +96,7 @@ fn test_queue_propose_message_from_next_height() { let sandbox_state = SandboxState::new(); let tx = gen_timestamping_tx(); let mut block_at_first_height = sandbox.create_block(&[tx.clone()]); - block_at_first_height.add_header::(ValidatorId(0).into()); + block_at_first_height.add_header::(ValidatorId(0)); let future_propose = sandbox.create_propose( ValidatorId(0), diff --git a/exonum-node/src/state.rs b/exonum-node/src/state.rs index f3a0359952..51603bd154 100644 --- a/exonum-node/src/state.rs +++ b/exonum-node/src/state.rs @@ -16,7 +16,7 @@ use bit_vec::BitVec; use exonum::{ - blockchain::{contains_transaction, ConsensusConfig, ProposerId, ValidatorKeys}, + blockchain::{contains_transaction, ConsensusConfig, ValidatorKeys}, crypto::{Hash, PublicKey}, helpers::{byzantine_quorum, Height, Milliseconds, Round, ValidatorId}, keys::Keys, @@ -161,7 +161,7 @@ pub struct BlockState { // Changes that should be made for block committing. patch: Option, txs: Vec, - proposer_id: ProposerId, + proposer_id: ValidatorId, } /// Incomplete block. @@ -363,7 +363,7 @@ impl BlockState { } /// Returns id of the validator that proposed the block. - pub fn proposer_id(&self) -> ProposerId { + pub fn proposer_id(&self) -> ValidatorId { self.proposer_id } } @@ -1023,7 +1023,7 @@ impl State { block_hash: Hash, patch: Patch, txs: Vec, - proposer_id: ProposerId, + proposer_id: ValidatorId, ) -> Option<&BlockState> { match self.blocks.entry(block_hash) { Entry::Occupied(..) => None, diff --git a/exonum/src/blockchain/api_sender.rs b/exonum/src/blockchain/api_sender.rs index 85a4e46979..24ec788791 100644 --- a/exonum/src/blockchain/api_sender.rs +++ b/exonum/src/blockchain/api_sender.rs @@ -19,7 +19,8 @@ use std::fmt; use crate::messages::{AnyTx, Verified}; -/// Transactions sender. +/// Asynchronous sender of messages (transactions by default). The receiver of messages is +/// usually an Exonum node, which then processes them with the consensus algorithm. pub struct ApiSender>(mpsc::Sender); impl Clone for ApiSender { @@ -29,17 +30,17 @@ impl Clone for ApiSender { } impl ApiSender { - /// Creates new `ApiSender` with given channel. + /// Creates new `ApiSender` with the given channel. pub fn new(inner: mpsc::Sender) -> Self { ApiSender(inner) } - /// Creates a dummy sender which is not connected to the node and thus cannot send messages. + /// Creates a dummy sender which is not connected to anything and thus cannot send messages. pub fn closed() -> Self { ApiSender(mpsc::channel(0).0) } - /// Sends an arbitrary `ExternalMessage` to the node. + /// Sends a message to the node. /// /// # Return value /// @@ -54,8 +55,11 @@ impl ApiSender { } impl ApiSender { - /// Broadcasts transaction to other nodes in the blockchain network. This is an asynchronous - /// operation that can take some time if the node is overloaded with requests. + /// Sends a transaction over the channel. If this sender is connected to a node, + /// this will broadcast the transaction to all nodes in the blockchain network. + /// + /// This is an asynchronous operation that can take some time if the node is overloaded + /// with requests. /// /// # Return value /// @@ -74,7 +78,7 @@ impl fmt::Debug for ApiSender { } } -/// Errors that can occur during sending a message to the node via `ApiSender` or `ShutdownHandle`. +/// Errors that can occur during sending a message to the node via `ApiSender`. #[derive(Debug, Fail)] #[fail(display = "Failed to send API request to the node: the node is being shut down")] pub struct SendError(()); diff --git a/exonum/src/blockchain/block.rs b/exonum/src/blockchain/block.rs index 1b062af378..436fafb619 100644 --- a/exonum/src/blockchain/block.rs +++ b/exonum/src/blockchain/block.rs @@ -15,9 +15,8 @@ use exonum_derive::{BinaryValue, ObjectHash}; use exonum_merkledb::{BinaryValue, MapProof}; use exonum_proto::ProtobufConvert; -use failure::Error; -use std::{borrow::Cow, fmt}; +use std::borrow::Cow; use crate::{ crypto::Hash, @@ -26,12 +25,12 @@ use crate::{ proto, }; -/// Trait that represents key in block header entry map. Provide -/// mapping between `NAME` of the entry and its value. +/// Trait that represents a key in block header entry map. Provides +/// a mapping between `NAME` of the entry and its value. /// -/// # Usage +/// # Examples /// -/// see [`Block::get_entry()`]. +/// See [`Block::get_entry()`]. /// /// [`Block::get_entry()`]: struct.Block.html#method.get_entry pub trait BlockHeaderKey { @@ -41,35 +40,13 @@ pub trait BlockHeaderKey { type Value: BinaryValue; } -/// Proposer identifier. +/// Identifier of a proposer of the block. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct ProposerId(pub ValidatorId); - -impl fmt::Display for ProposerId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl BinaryValue for ProposerId { - fn to_bytes(&self) -> Vec { - (self.0).0.to_bytes() - } - - fn from_bytes(bytes: Cow<'_, [u8]>) -> Result { - Ok(Self(ValidatorId(u16::from_bytes(bytes)?))) - } -} - -impl From for ProposerId { - fn from(validator_id: ValidatorId) -> Self { - ProposerId(validator_id) - } -} +pub struct ProposerId(()); impl BlockHeaderKey for ProposerId { const NAME: &'static str = "proposer_id"; - type Value = Self; + type Value = ValidatorId; } /// Expandable set of headers allowed to be added to the block. @@ -95,27 +72,26 @@ impl AdditionalHeaders { /// Insert new header to the map. pub fn insert(&mut self, value: K::Value) { - self.headers.0.insert(K::NAME.into(), value.to_bytes()); + self.headers.0.insert(K::NAME.into(), value.into_bytes()); } /// Get header from the map. pub fn get(&self) -> Option<&[u8]> { - self.headers.0.get(K::NAME).map(|v| v.as_slice()) + self.headers.0.get(K::NAME).map(Vec::as_slice) } } -/// Exonum block header data structure. +/// Header of a block. /// -/// A block is essentially a list of transactions, which is +/// A block is essentially a list of transactions. Blocks are produced as /// a result of the consensus algorithm (thus authenticated by the supermajority of validators) -/// and is applied atomically to the blockchain state. -/// -/// The header only contains the amount of transactions and the transactions root hash as well as -/// other information, but not the transactions themselves. +/// and are applied atomically to the blockchain state. The header contains a block summary, +/// such as the number of transactions and the transactions root hash, but not +/// the transactions themselves. /// /// Note that this structure is export-only, meaning that one can rely on the serialization format -/// provided by corresponding `protobuf` definitions, but cannot expect `exonum` to accept -/// and process `Block` structure created outside of the `exonum` core. +/// provided by corresponding Protobuf definitions, but cannot expect Exonum nodes +/// or the `exonum` crate to accept and process `Block`s created externally. #[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] #[derive(Serialize, Deserialize)] #[derive(ProtobufConvert, BinaryValue, ObjectHash)] @@ -147,10 +123,10 @@ impl Block { self.additional_headers.insert::(value); } - /// Get block additional header value for specified key type. Key type is specified via - /// type parameter. + /// Gets the value of an additional header for the specified key type, which is specified via + /// the type parameter. /// - /// # Usage + /// # Examples /// /// ``` /// # use exonum::crypto::Hash; @@ -159,18 +135,18 @@ impl Block { /// # use exonum::merkledb::BinaryValue; /// # use failure::Error; /// # use std::borrow::Cow; - /// /// // Suppose we store a list of active service IDs in a block. /// // We can do this by defining a corresponding BlockHeaderKey implementation. /// struct ActiveServices { - /// service_id: u32, + /// service_ids: Vec, /// } /// /// # impl BinaryValue for ActiveServices { - /// # fn to_bytes(&self) -> Vec { vec![] } - /// # fn from_bytes(bytes: Cow<'_, [u8]>) -> Result { Ok(Self { service_id: 0 }) } + /// # fn to_bytes(&self) -> Vec { vec![] } + /// # fn from_bytes(bytes: Cow<'_, [u8]>) -> Result { + /// # Ok(Self { service_ids: vec![] }) + /// # } /// # } - /// /// // To implement `BlockHeaderKey` we need to provide the key name and a corresponding /// // value type. In this case it's `Self`. /// impl BlockHeaderKey for ActiveServices { @@ -179,23 +155,21 @@ impl Block { /// } /// /// // Create an empty block. - /// let mut block = Block { + /// let block = Block { /// # height: Height(0), /// # tx_count: 0, /// # prev_hash: Hash::zero(), /// # tx_hash: Hash::zero(), /// # state_hash: Hash::zero(), /// # error_hash: Hash::zero(), + /// // other fields skipped... /// additional_headers: AdditionalHeaders::new(), /// }; /// - /// let services = block.get_header::().expect("Entry deserialization error"); - /// assert!(services.is_none()) + /// let services = block.get_header::().unwrap(); + /// assert!(services.is_none()); /// ``` - pub fn get_header(&self) -> Result, failure::Error> - where - K::Value: BinaryValue, - { + pub fn get_header(&self) -> Result, failure::Error> { self.additional_headers .get::() .map(|bytes: &[u8]| K::Value::from_bytes(Cow::Borrowed(bytes))) @@ -282,7 +256,7 @@ mod tests { #[test] fn block() { let mut additional_headers = AdditionalHeaders::new(); - additional_headers.insert::(hash(&[0u8; 10])); + additional_headers.insert::(hash(&[0_u8; 10])); let txs = [4, 5, 6]; let height = Height(123_345); @@ -338,7 +312,7 @@ mod tests { let hash_without_entries = block_without_entries.object_hash(); let mut entries = AdditionalHeaders::new(); - entries.insert::(hash(&[0u8; 10])); + entries.insert::(hash(&[0_u8; 10])); let block_with_entries = create_block(entries); let hash_with_entries = block_with_entries.object_hash(); diff --git a/exonum/src/blockchain/config.rs b/exonum/src/blockchain/config.rs index 39742e245d..eaefcf3436 100644 --- a/exonum/src/blockchain/config.rs +++ b/exonum/src/blockchain/config.rs @@ -12,14 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Exonum global variables which are stored in the blockchain as UTF-8 encoded -//! JSON. +//! Exonum blockchain configuration. //! -//! This module includes all the elements of the `StoredConfiguration` which is -//! used as the global configuration of the blockchain and should be the same for -//! all validators in the network. The configuration includes the public keys of -//! validators, consensus related parameters, hash of the previous configuration, -//! etc. +//! This module includes the components of the global configuration of the blockchain +//! The configuration includes the public keys of validators, consensus related parameters, +//! and built-in services (services deployed at the blockchain start). use exonum_derive::{BinaryValue, ObjectHash}; use exonum_proto::ProtobufConvert; @@ -60,8 +57,10 @@ pub struct ValidatorKeys { impl ValidatorKeys { /// Creates a new `ValidatorKeys` object. /// - /// Since the inner structure `ValidatorKeys` can change, this method is considered unstable - /// and may break in the future. + /// # Stability + /// + /// Since more keys may be added to `ValidatorKeys` in the future, this method is considered + /// unstable. pub fn new(consensus_key: PublicKey, service_key: PublicKey) -> Self { Self { consensus_key, @@ -540,7 +539,7 @@ impl InstanceInitParams { pub fn with_constructor(self, constructor: impl BinaryValue) -> InstanceInitParams { InstanceInitParams { instance_spec: self.instance_spec, - constructor: constructor.to_bytes(), + constructor: constructor.into_bytes(), non_exhaustive: (), } } diff --git a/exonum/src/blockchain/mod.rs b/exonum/src/blockchain/mod.rs index d27a4a0092..3d09e01611 100644 --- a/exonum/src/blockchain/mod.rs +++ b/exonum/src/blockchain/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! The module containing building blocks for creating blockchains powered by the Exonum framework. +//! Building blocks for creating blockchains powered by the Exonum framework. pub use self::{ api_sender::{ApiSender, SendError}, @@ -112,7 +112,7 @@ impl Blockchain { } /// Returns the transactions pool size. - #[doc(hidden)] // FIXME: move to separate schema + #[doc(hidden)] pub fn pool_size(&self) -> u64 { Schema::new(&self.snapshot()).transactions_pool_len() } @@ -198,8 +198,7 @@ impl BlockchainMut { self.inner.db.fork() } - /// Commits changes from the patch to the blockchain storage. - /// See [`Fork`](../../exonum_merkledb/struct.Fork.html) for details. + /// Commits changes from the `patch` to the blockchain storage. pub fn merge(&mut self, patch: Patch) -> StorageResult<()> { self.inner.db.merge(patch) } @@ -260,7 +259,7 @@ impl BlockchainMut { self.merge(patch).unwrap(); let (_, patch) = self.create_patch( - ValidatorId::zero().into(), + ValidatorId::zero(), Height::zero(), &[], &mut BTreeMap::new(), @@ -280,7 +279,7 @@ impl BlockchainMut { /// from the current storage state and returns them with the hash of the resulting block. pub fn create_patch( &self, - proposer_id: ProposerId, + proposer_id: ValidatorId, height: Height, tx_hashes: &[Hash], tx_cache: &mut BTreeMap>, @@ -292,7 +291,7 @@ impl BlockchainMut { pub(crate) fn create_patch_inner( &self, mut fork: Fork, - proposer_id: ProposerId, + proposer_id: ValidatorId, height: Height, tx_hashes: &[Hash], tx_cache: &mut BTreeMap>, @@ -337,7 +336,7 @@ impl BlockchainMut { fn create_block_header( &self, fork: Fork, - proposer_id: ProposerId, + proposer_id: ValidatorId, height: Height, tx_hashes: &[Hash], ) -> (Patch, Block) { @@ -459,7 +458,8 @@ impl BlockchainMut { .expect("Cannot update transaction pool"); } - /// Shuts down the dispatcher. This should be the last operation performed on this instance. + /// Shuts down the service dispatcher enclosed by this blockchain. This must be + /// the last operation performed on the blockchain. pub fn shutdown(&mut self) { self.dispatcher.shutdown(); } diff --git a/exonum/src/blockchain/schema.rs b/exonum/src/blockchain/schema.rs index b5fa525a0c..2f33b044ea 100644 --- a/exonum/src/blockchain/schema.rs +++ b/exonum/src/blockchain/schema.rs @@ -57,9 +57,8 @@ define_names!( CONSENSUS_CONFIG => "consensus_config"; ); -/// Transaction location in a block. -/// The given entity defines the block where the transaction was -/// included and the position of this transaction in that block. +/// Transaction location in a block. Defines the block where the transaction was +/// included and the position of this transaction in the block. #[derive(Debug, Clone, Copy, PartialEq)] #[derive(Serialize, Deserialize)] #[derive(ProtobufConvert, BinaryValue, ObjectHash)] @@ -122,16 +121,20 @@ impl Schema { /// This method can be used to build a proof that execution of a certain transaction /// ended up with a particular status. /// For an execution that resulted in an error, this will be an usual proof of existence. - /// If transaction was executed successfully, such a proof will be a proof of absence. + /// If the transaction was executed successfully, such a proof will be a proof of absence. /// Since the number of transactions in a block is mentioned in the block header, the user /// will be able to distinguish absence of error (meaning successful execution) from - /// absence of transaction with such an index: if index is less than amount of transactions - /// in block, proof denotes successful execution, otherwise transaction with such an index - /// does not exist in the block. + /// the absence of a transaction with such an index. Indeed, if the index is less + /// than amount of transactions in block, the proof denotes successful execution; + /// otherwise, the transaction with the given index does not exist in the block. /// - /// Similarly, execution errors of the `before_transactions` / `after_transactions` hooks can be proven - /// to external clients. Discerning successful execution from a non-existing service requires prior knowledge - /// though. + /// Similarly, execution errors of the `before_transactions` / `after_transactions` + /// hooks can be proven to external clients. Discerning successful execution + /// from a non-existing service requires prior knowledge though. + /// + /// Proofs obtained with this method should not be mixed up with a proof of transaction + /// commitment. To verify that a certain transaction was committed, use a proof from + /// the `block_transactions` index. // TODO: Retain historic information about services [ECR-3922] pub fn call_errors( &self, @@ -160,11 +163,11 @@ impl Schema { } /// Returns an entry that represents a count of committed transactions in the blockchain. - pub(crate) fn transactions_len_index(&self) -> Entry { + fn transactions_len_index(&self) -> Entry { self.access.clone().get_entry(TRANSACTIONS_LEN) } - /// Returns the number of transactions in the blockchain. + /// Returns the number of committed transactions in the blockchain. pub fn transactions_len(&self) -> u64 { // TODO: Change a count of tx logic after replacement storage to MerkleDB. ECR-3087 let pool = self.transactions_len_index(); @@ -218,6 +221,7 @@ impl Schema { } /// Returns an actual consensus configuration entry. + #[doc(hidden)] pub fn consensus_config_entry(&self) -> ProofEntry { self.access.clone().get_proof_entry(CONSENSUS_CONFIG) } @@ -239,7 +243,7 @@ impl Schema { /// /// # Panics /// - /// Panics if the "genesis block" was not created. + /// Panics if the genesis block was not created. pub fn last_block(&self) -> Block { let hash = self .block_hashes_by_height() @@ -275,7 +279,7 @@ impl Schema { /// /// # Panics /// - /// Panics if the "genesis block" was not created. + /// Panics if the genesis block was not created. pub fn consensus_config(&self) -> ConsensusConfig { self.consensus_config_entry() .get() diff --git a/exonum/src/blockchain/tests.rs b/exonum/src/blockchain/tests.rs index ed726b8f2d..0e229e26dd 100644 --- a/exonum/src/blockchain/tests.rs +++ b/exonum/src/blockchain/tests.rs @@ -370,7 +370,7 @@ fn execute_transaction( }; let (block_hash, patch) = blockchain.create_patch( - ValidatorId::zero().into(), + ValidatorId::zero(), height, &[tx_hash], &mut BTreeMap::new(), @@ -772,7 +772,7 @@ fn blockchain_height() { // Create one block. let (_, patch) = blockchain.create_patch( - ValidatorId::zero().into(), + ValidatorId::zero(), Height::zero(), &[], &mut BTreeMap::new(), diff --git a/exonum/src/helpers/mod.rs b/exonum/src/helpers/mod.rs index 4dff29bb2a..719083c29a 100644 --- a/exonum/src/helpers/mod.rs +++ b/exonum/src/helpers/mod.rs @@ -32,7 +32,11 @@ use log::SetLoggerError; mod types; mod user_agent; -/// Performs the logger initialization. +/// Initializes the logger. +/// +/// See [`env_logger`] crate for details how to configure the logger output. +/// +/// [`env_logger`]: https://docs.rs/env_logger/ pub fn init_logger() -> Result<(), SetLoggerError> { Builder::from_default_env() .default_format_timestamp_nanos(true) diff --git a/exonum/src/helpers/ordered_map.rs b/exonum/src/helpers/ordered_map.rs index b580df390d..72762cd6e9 100644 --- a/exonum/src/helpers/ordered_map.rs +++ b/exonum/src/helpers/ordered_map.rs @@ -75,7 +75,7 @@ where fn to_pb(&self) -> Self::ProtoStruct { let mut proto_struct = Self::ProtoStruct::new(); - proto_struct.entry = self + proto_struct.entries = self .0 .iter() .map(pair_to_key_value_pb) @@ -87,7 +87,7 @@ where fn from_pb(proto_struct: Self::ProtoStruct) -> Result { let values = proto_struct - .entry + .entries .into_iter() .map(key_value_pb_to_pair) .collect::, failure::Error>>()?; @@ -166,7 +166,7 @@ mod tests { // Unordered keys. let mut map = PbKeyValueSequence::new(); - map.set_entry(RepeatedField::from_vec(vec![kv.clone(), kv2.clone()])); + map.set_entries(RepeatedField::from_vec(vec![kv.clone(), kv2.clone()])); let res: Result>, failure::Error> = ProtobufConvert::from_pb(map); @@ -176,7 +176,7 @@ mod tests { // Duplicate keys. let mut map = PbKeyValueSequence::new(); - map.set_entry(RepeatedField::from_vec(vec![kv2.clone(), kv, kv2])); + map.set_entries(RepeatedField::from_vec(vec![kv2.clone(), kv, kv2])); let res: Result>, failure::Error> = ProtobufConvert::from_pb(map); diff --git a/exonum/src/helpers/types.rs b/exonum/src/helpers/types.rs index e0328313f4..6be70db4d8 100644 --- a/exonum/src/helpers/types.rs +++ b/exonum/src/helpers/types.rs @@ -14,19 +14,18 @@ //! Common widely used type definitions. -use exonum_merkledb::{ - impl_binary_key_for_binary_value, impl_object_hash_for_binary_value, BinaryValue, ObjectHash, -}; +use exonum_derive::ObjectHash; +use exonum_merkledb::{impl_binary_key_for_binary_value, BinaryValue}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{borrow::Cow, fmt, num::ParseIntError, str::FromStr}; -use crate::crypto::Hash; +use std::{borrow::Cow, fmt, num::ParseIntError, str::FromStr}; /// Number of milliseconds. pub type Milliseconds = u64; -/// Blockchain height (number of blocks). +/// Blockchain height, that is, the number of committed blocks in it. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(ObjectHash)] pub struct Height(pub u64); impl Height { @@ -127,11 +126,50 @@ impl BinaryValue for Height { } impl_binary_key_for_binary_value! { Height } -impl_object_hash_for_binary_value! { Height } + +impl fmt::Display for Height { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for u64 { + fn from(val: Height) -> Self { + val.0 + } +} + +// Serialization/deserialization is implemented manually because TOML round-trip for the tuple +// structs is broken currently. See https://github.com/alexcrichton/toml-rs/issues/194 for details. +impl Serialize for Height { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Height { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Height(u64::deserialize(deserializer)?)) + } +} + +impl FromStr for Height { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + u64::from_str(s).map(Height) + } +} /// Consensus round index. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ObjectHash)] pub struct Round(pub u32); impl Round { @@ -246,11 +284,8 @@ impl Round { /// assert_eq!(Some(Round(1)), iter.next()); /// assert_eq!(None, iter.next()); /// ``` - pub fn iter_to(self, to: Self) -> RoundRangeIter { - RoundRangeIter { - next: self, - last: to, - } + pub fn iter_to(self, to: Self) -> impl Iterator { + (self.0..to.0).map(Round) } } @@ -265,7 +300,23 @@ impl BinaryValue for Round { } } -impl_object_hash_for_binary_value! { Round } +impl fmt::Display for Round { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for u32 { + fn from(val: Round) -> Self { + val.0 + } +} + +impl From for u64 { + fn from(val: Round) -> Self { + u64::from(val.0) + } +} /// Validators identifier. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -288,33 +339,13 @@ impl ValidatorId { } } -impl fmt::Display for Height { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for u64 { - fn from(val: Height) -> Self { - val.0 - } -} - -impl fmt::Display for Round { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) +impl BinaryValue for ValidatorId { + fn to_bytes(&self) -> Vec { + self.0.to_bytes() } -} -impl From for u32 { - fn from(val: Round) -> Self { - val.0 - } -} - -impl From for u64 { - fn from(val: Round) -> Self { - u64::from(val.0) + fn from_bytes(bytes: Cow<'_, [u8]>) -> Result { + u16::from_bytes(bytes).map(ValidatorId) } } @@ -335,53 +366,3 @@ impl From for usize { val.0 as usize } } - -// Serialization/deserialization is implemented manually because TOML round-trip for the tuple -// structs is broken currently. See https://github.com/alexcrichton/toml-rs/issues/194 for details. -impl Serialize for Height { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.0.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Height { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(Height(u64::deserialize(deserializer)?)) - } -} - -impl FromStr for Height { - type Err = ParseIntError; - - fn from_str(s: &str) -> Result { - u64::from_str(s).map(Height) - } -} - -/// Iterator over rounds range. -#[derive(Debug)] -pub struct RoundRangeIter { - next: Round, - last: Round, -} - -// TODO: Add (or replace by) `Step` implementation. (ECR-165) -impl Iterator for RoundRangeIter { - type Item = Round; - - fn next(&mut self) -> Option { - if self.next < self.last { - let res = Some(self.next); - self.next.increment(); - res - } else { - None - } - } -} diff --git a/exonum/src/lib.rs b/exonum/src/lib.rs index 7999200b34..54de135cfe 100644 --- a/exonum/src/lib.rs +++ b/exonum/src/lib.rs @@ -12,9 +12,68 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Exonum blockchain framework. +//! Core library for the Exonum blockchain framework. //! -//! For more information see the project readme. +//! Exonum is an extensible open-source framework for +//! creating blockchain applications. Exonum can be used to create cryptographically +//! powered distributed ledgers in virtually any problem domain, including finance, +//! governance, and legal. The Exonum framework is oriented towards creating +//! permissioned blockchains, that is, blockchains with the known set of blockchain +//! infrastructure providers. +//! +//! For more information about the framework see the [readme] and the [Exonum website]. +//! +//! [readme]: https://github.com/exonum/exonum#readme +//! [Exonum website]: https://exonum.com/ +//! +//! # Crate Overview +//! +//! This crate provides the fundamentals for Exonum nodes, such as tools to store and access +//! data in the blockchain, means for handling transactions and evolve the blockchain with time +//! (e.g., add new business logic or perform data migrations). +//! +//! ## Re-exports +//! +//! The crate re-exports the following crates: +//! +//! | Crate | Exported name | Description | +//! |-------|---------------|-------------| +//! | [`exonum-crypto`] | `crypto` | Cryptographic utils used by Exonum | +//! | [`exonum-merkledb`] | `merkledb` | Storage engine with Merkelized data | +//! | [`exonum-keys`] | `keys` | Key tools for Exonum nodes | +//! +//! [`exonum-crypto`]: https://docs.rs/exonum-crypto/ +//! [`exonum-merkledb`]: https://docs.rs/exonum-merkledb/ +//! [`exonum-keys`]: https://docs.rs/exonum-keys/ +//! +//! ## Blockchain Management +//! +//! The crate provides basic tools to build Exonum nodes ([`blockchain`] and [`messages`] modules), +//! although the bulk of the node logic is placed [in a dedicated crate][`exonum-node`]. +//! +//! [`blockchain`]: blockchain/index.html +//! [`messages`]: messages/index.html +//! [`exonum-node`]: https://docs.rs/exonum-node/ +//! +//! ## Runtimes +//! +//! [Runtimes] are a way to attach user-provided business logic to an Exonum blockchain. This +//! logic, bundled in *services*, allows to process user transactions and interact with +//! the blockchain in other ways (e.g., via HTTP API). +//! +//! Exonum provides a [generic interface][`Runtime`] for runtimes, which allows to implement +//! services in different programming languages, for example [Rust][rust-rt] and [Java][java-rt]. +//! +//! [Runtimes]: runtime/index.html +//! [`Runtime`]: runtime/trait.Runtime.html +//! [rust-rt]: https://docs.rs/exonum-rust-runtime/ +//! [java-rt]: https://github.com/exonum/exonum-java-binding +//! +//! # Examples +//! +//! See the [GitHub repository][examples] for examples. +//! +//! [examples]: https://github.com/exonum/exonum/tree/master/examples #![warn( missing_debug_implementations, @@ -22,42 +81,34 @@ unsafe_code, bare_trait_objects )] -#![allow(clippy::use_self)] -// #![warn(clippy::pedantic)] -// #![allow( -// // The following `cast_*` lints do not give alternatives: -// clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss, -// // `filter(..).map(..)` often looks shorter and more readable. -// clippy::filter_map, -// // The following lints produce too much noise/false positives: -// clippy::module_name_repetitions, clippy::similar_names, -// // Variant name ends with the enum name. Demonstrates similar behavior to `similar_names`. -// clippy::pub_enum_variant_names, -// // '... may panic' lints. -// clippy::indexing_slicing, -// // Suggestions for improvement that look inadequate in respect of the code that uses a lot of generics. -// clippy::default_trait_access, -// )] +#![warn(clippy::pedantic)] +#![allow( + // Next `cast_*` lints don't give alternatives. + clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss, + // `filter(..).map(..)` often looks more shorter and readable. + clippy::filter_map, + // Next lints produce too much noise/false positives. + clippy::module_name_repetitions, clippy::similar_names, + // Variant name ends with the enum name. Similar behavior to similar_names. + clippy::pub_enum_variant_names, + // '... may panic' lints. + clippy::indexing_slicing, + clippy::use_self, + clippy::default_trait_access, +)] -pub use exonum_merkledb; -#[macro_use] +#[macro_use] // Code generated by Protobuf requires `serde_derive` macros to be globally available. extern crate serde_derive; -// Test dependencies. -#[cfg(all(test, feature = "long_benchmarks"))] -extern crate test; - pub use exonum_crypto as crypto; pub use exonum_keys as keys; pub use exonum_merkledb as merkledb; #[macro_use] pub mod messages; -#[macro_use] -pub mod helpers; pub mod blockchain; +pub mod helpers; pub mod runtime; -#[macro_use] #[doc(hidden)] pub mod proto; diff --git a/exonum/src/messages/mod.rs b/exonum/src/messages/mod.rs index 8ecb86b145..12c3081f79 100644 --- a/exonum/src/messages/mod.rs +++ b/exonum/src/messages/mod.rs @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Handling messages received from P2P node network. +//! Tools for messages authenticated with the Ed25519 public-key crypto system. +//! These messages are used by the P2P networking and for transaction authentication by external +//! clients. //! //! Every message passes through three phases: //! -//! - `Vec`: raw bytes as received from the network -//! - `SignedMessage`: integrity and signature of the message has been verified -//! - `impl IntoMessage`: the message has been completely parsed and has correct structure +//! 1. `Vec`: raw bytes as received from the network +//! 2. `SignedMessage`: integrity and the signature of the message has been verified +//! 3. `impl IntoMessage`: the message has been completely parsed //! //! Graphical representation of the message processing flow: //! @@ -34,7 +36,7 @@ //! //! # Examples //! -//! The procedure of creating a new signed message is as follows. +//! A new signed message can be created as follows. //! //! ``` //! # use chrono::Utc; @@ -45,7 +47,7 @@ //! # }; //! # fn send(_: T) {} //! let keypair = crypto::gen_keypair(); -//! // For example, get some `Status` message. +//! // For example, let's create a `Precommit` message. //! let payload = Precommit::new( //! ValidatorId(0), //! Height(15), @@ -62,7 +64,7 @@ //! send(raw_signed_message); //! ``` //! -//! The procedure of verification of a signed message is as follows: +//! A signed message can be verified as follows: //! //! ``` //! # use assert_matches::assert_matches; @@ -107,7 +109,7 @@ mod signed; mod types; /// Lower bound on the size of the correct `SignedMessage`. -/// This is the size of message fields + protobuf overhead. +/// This is the size of message fields + Protobuf overhead. #[doc(hidden)] pub const SIGNED_MESSAGE_MIN_SIZE: usize = PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH + 8; diff --git a/exonum/src/messages/signed.rs b/exonum/src/messages/signed.rs index 912b8c79cd..f5e99ab557 100644 --- a/exonum/src/messages/signed.rs +++ b/exonum/src/messages/signed.rs @@ -52,20 +52,25 @@ impl SignedMessage { impl_serde_hex_for_binary_value! { SignedMessage } -/// Wraps a `Payload` together with the corresponding `SignedMessage`. +/// Wraps a payload together with the corresponding `SignedMessage`. /// -/// Usually one wants to work with fully parsed and verified messages (i.e., `Payload`). -/// However, occasionally we have to retransmit the message into the network or +/// Usually one wants to work with fully parsed and verified messages (i.e., the type param +/// from the `Verified` definition). +/// However, occasionally we need to retransmit the message into the network or /// save its serialized form. We could serialize the `Payload` back, /// but Protobuf does not have a canonical form so the resulting payload may -/// have different binary representation (thus invalidating the message signature). +/// have a different binary representation (thus invalidating the message signature). /// /// So we use `Verified` to keep the original byte buffer around with the parsed `Payload`. /// -/// Be careful with `BinaryValue::from_bytes` method! -/// It for performance reasons skips signature verification. +/// Be careful with `BinaryValue::from_bytes` method! It skips signature verification +/// for performance reasons. This is OK in a typical use case (a previously verified message +/// is loaded from the blockchain storage), but should not be used to deserialize messages +/// from an untrusted source. /// -/// See module [documentation](index.html#examples) for examples. +/// # Examples +/// +/// See [module documentation](index.html#examples) for examples. #[derive(Clone, Debug)] pub struct Verified { raw: SignedMessage, @@ -89,7 +94,7 @@ impl Verified { self.raw } - /// Returns message author key. + /// Returns the public key of the message author. pub fn author(&self) -> PublicKey { self.raw.author } @@ -111,7 +116,7 @@ impl Verified where T: TryFrom, { - /// Returns reference to the underlying message payload. + /// Returns a reference to the underlying message payload. pub fn payload(&self) -> &T { &self.inner } @@ -122,10 +127,11 @@ where } } -/// Message that can be converted into a unambiguous presentation for signing. "Unambiguous" -/// means that any sequence of bytes produced by serializing `Container` obtained by converting -/// this message can be interpreted in a single way. In other words, messages of different types -/// have separated representation domains. +/// Message that can be converted into a unambiguous presentation for signing. +/// +/// "Unambiguous" above means that any sequence of bytes produced by serializing `Container` +/// obtained by converting this message can be interpreted in a single way. +/// In other words, messages of different types have separated representation domains. pub trait IntoMessage: Sized { /// Container for the message. type Container: BinaryValue + From + TryInto; diff --git a/exonum/src/messages/types.rs b/exonum/src/messages/types.rs index 929f9dd4ad..85532946b0 100644 --- a/exonum/src/messages/types.rs +++ b/exonum/src/messages/types.rs @@ -27,7 +27,7 @@ use crate::{ proto::schema::messages, }; -/// Protobuf based container for any signed messages. +/// Protobuf-based container for an arbitrary signed message. /// /// See module [documentation](index.html#examples) for examples. #[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] @@ -38,7 +38,7 @@ pub struct SignedMessage { pub payload: Vec, /// `PublicKey` of the author of the message. pub author: PublicKey, - /// Digital signature over `payload` created with `SecretKey` of the author of the message. + /// Digital signature over `payload` created with the secret key of the author of the message. pub signature: Signature, } diff --git a/exonum/src/proto/macros.rs b/exonum/src/proto/macros.rs deleted file mode 100644 index 52a52df050..0000000000 --- a/exonum/src/proto/macros.rs +++ /dev/null @@ -1,36 +0,0 @@ -// limitations under the License. - -// 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 - -/// Implement `BinaryValue` trait for type that implements `Protobuf::Message`. -#[macro_export] -macro_rules! impl_binary_value_for_pb_message { - ($( $type:ty ),*) => { - $( - impl BinaryValue for $type { - fn to_bytes(&self) -> Vec { - use protobuf::Message; - self.write_to_bytes().expect("Error while serializing value") - } - - fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Result { - use protobuf::Message; - let mut pb = Self::new(); - pb.merge_from_bytes(bytes.as_ref())?; - Ok(pb) - } - } - )* - }; -} diff --git a/exonum/src/proto/mod.rs b/exonum/src/proto/mod.rs index a130efab5d..c4e254cb83 100644 --- a/exonum/src/proto/mod.rs +++ b/exonum/src/proto/mod.rs @@ -24,6 +24,8 @@ pub use self::schema::{ use exonum_proto::ProtobufConvert; use failure::{ensure, Error}; +use std::convert::TryFrom; + use crate::helpers::{Height, Round, ValidatorId}; pub mod schema; @@ -64,7 +66,7 @@ impl ProtobufConvert for ValidatorId { fn from_pb(pb: Self::ProtoStruct) -> Result { ensure!( - pb <= u32::from(u16::max_value()), + u16::try_from(pb).is_ok(), "{} is out of range for valid ValidatorId", pb ); diff --git a/exonum/src/proto/schema/exonum/key_value_sequence.proto b/exonum/src/proto/schema/exonum/key_value_sequence.proto index 8b276d5004..acf21ae165 100644 --- a/exonum/src/proto/schema/exonum/key_value_sequence.proto +++ b/exonum/src/proto/schema/exonum/key_value_sequence.proto @@ -28,5 +28,5 @@ message KeyValue { // A sequence of key-value pairs. message KeyValueSequence { - repeated KeyValue entry = 1; + repeated KeyValue entries = 1; } diff --git a/exonum/src/proto/schema/mod.rs b/exonum/src/proto/schema/mod.rs index f69ae828df..db3abdc7c9 100644 --- a/exonum/src/proto/schema/mod.rs +++ b/exonum/src/proto/schema/mod.rs @@ -15,7 +15,7 @@ //! Module with protobuf generated files from `schema/exonum`. // For rust-protobuf generated files. -#![allow(bare_trait_objects)] +#![allow(bare_trait_objects, clippy::pedantic)] use crate::crypto::proto::*; use crate::merkledb::proto::*; diff --git a/exonum/src/runtime/dispatcher/migration_tests.rs b/exonum/src/runtime/dispatcher/migration_tests.rs index 4775613917..6d1ad0ee87 100644 --- a/exonum/src/runtime/dispatcher/migration_tests.rs +++ b/exonum/src/runtime/dispatcher/migration_tests.rs @@ -257,7 +257,7 @@ impl Rig { let height = CoreSchema::new(&fork).next_height(); let (block_hash, patch) = self.blockchain.create_patch_inner( fork, - ValidatorId(0).into(), + ValidatorId(0), height, &[], &mut BTreeMap::new(), diff --git a/exonum/src/runtime/dispatcher/mod.rs b/exonum/src/runtime/dispatcher/mod.rs index 296693fc4c..fcd32c01cc 100644 --- a/exonum/src/runtime/dispatcher/mod.rs +++ b/exonum/src/runtime/dispatcher/mod.rs @@ -97,8 +97,8 @@ impl CommittedServices { InstanceQuery::Id(id) => (id, self.instances.get(&id)?), InstanceQuery::Name(name) => { - let id = *self.instance_names.get(name)?; - (id, self.instances.get(&id)?) + let resolved_id = *self.instance_names.get(name)?; + (resolved_id, self.instances.get(&resolved_id)?) } InstanceQuery::__NonExhaustive => unreachable!("Never actually constructed"), @@ -530,12 +530,14 @@ impl Dispatcher { if let Err(ref mut err) = res { fork.rollback(); - err.set_runtime_id(runtime_id).set_call_site(|| CallSite { - instance_id: call_info.instance_id, - call_type: CallType::Method { - interface: String::new(), - id: call_info.method_id, - }, + err.set_runtime_id(runtime_id).set_call_site(|| { + CallSite::new( + call_info.instance_id, + CallType::Method { + interface: String::new(), + id: call_info.method_id, + }, + ) }); Self::report_error(err, fork, CallInBlock::transaction(tx_index)); } else { @@ -548,7 +550,7 @@ impl Dispatcher { fn call_service_hooks( &self, fork: &mut Fork, - call_type: CallType, + call_type: &CallType, ) -> Vec<(CallInBlock, ExecutionError)> { self.service_infos .active_instances() @@ -563,10 +565,8 @@ impl Dispatcher { let res = call_fn(self.runtimes[&runtime_id].as_ref(), context); if let Err(mut err) = res { fork.rollback(); - err.set_runtime_id(runtime_id).set_call_site(|| CallSite { - instance_id: instance.id, - call_type: call_type.clone(), - }); + err.set_runtime_id(runtime_id) + .set_call_site(|| CallSite::new(instance.id, call_type.clone())); let call = match &call_type { CallType::BeforeTransactions => { @@ -590,7 +590,7 @@ impl Dispatcher { &self, fork: &mut Fork, ) -> Vec<(CallInBlock, ExecutionError)> { - self.call_service_hooks(fork, CallType::BeforeTransactions) + self.call_service_hooks(fork, &CallType::BeforeTransactions) } /// Calls `after_transactions` for all currently active services, isolating each call. @@ -599,7 +599,7 @@ impl Dispatcher { /// indexes of the dispatcher information scheme. Thus, these statuses will be equally /// calculated for precommit and actually committed block. pub(crate) fn after_transactions(&self, fork: &mut Fork) -> Vec<(CallInBlock, ExecutionError)> { - let errors = self.call_service_hooks(fork, CallType::AfterTransactions); + let errors = self.call_service_hooks(fork, &CallType::AfterTransactions); self.activate_pending(fork); errors } @@ -895,6 +895,9 @@ impl Mailbox { type ExecutionFuture = Box + Send>; /// Action to be performed by the dispatcher. +/// +/// This type is not intended to be exhaustively matched. It can be extended in the future +/// without breaking the semver compatibility. pub enum Action { /// Start artifact deployment. StartDeploy { @@ -906,6 +909,10 @@ pub enum Action { /// For example, this closure may create a transaction with the deployment confirmation. then: Box) -> ExecutionFuture + Send>, }, + + /// Never actually generated. + #[doc(hidden)] + __NonExhaustive, } impl fmt::Debug for Action { @@ -916,6 +923,7 @@ impl fmt::Debug for Action { .field("artifact", artifact) .field("spec", spec) .finish(), + Action::__NonExhaustive => unreachable!(), } } } @@ -936,6 +944,8 @@ impl Action { error!("Deploying artifact {:?} failed: {}", artifact, e); }); } + + Action::__NonExhaustive => unreachable!(), } } } diff --git a/exonum/src/runtime/dispatcher/tests.rs b/exonum/src/runtime/dispatcher/tests.rs index f2d9fc4b37..e73c63fa83 100644 --- a/exonum/src/runtime/dispatcher/tests.rs +++ b/exonum/src/runtime/dispatcher/tests.rs @@ -292,6 +292,7 @@ fn test_builder() { } #[test] +#[allow(clippy::too_many_lines)] // Adequate for a test fn test_dispatcher_simple() { const RUST_SERVICE_ID: InstanceId = 2; const JAVA_SERVICE_ID: InstanceId = 3; @@ -764,7 +765,7 @@ fn delayed_deployment() { assert_eq!(runtime.deploy_attempts(&artifact), 1); } -fn test_failed_deployment(db: Arc, runtime: DeploymentRuntime, artifact_name: &str) { +fn test_failed_deployment(db: &Arc, runtime: &DeploymentRuntime, artifact_name: &str) { let blockchain = Blockchain::new( Arc::clone(&db) as Arc, gen_keypair(), @@ -794,16 +795,14 @@ fn test_failed_deployment(db: Arc, runtime: DeploymentRuntime, arti fn failed_deployment() { let db = Arc::new(TemporaryDB::new()); let runtime = DeploymentRuntime::default(); - test_failed_deployment(db, runtime, "bad"); + test_failed_deployment(&db, &runtime, "bad"); } #[test] fn failed_deployment_with_node_restart() { let db = Arc::new(TemporaryDB::new()); let runtime = DeploymentRuntime::default(); - let db_ = Arc::clone(&db); - let runtime_ = runtime.clone(); - panic::catch_unwind(|| test_failed_deployment(db_, runtime_, "recoverable_after_restart")) + panic::catch_unwind(|| test_failed_deployment(&db, &runtime, "recoverable_after_restart")) .expect_err("Node didn't stop after unsuccessful sync deployment"); let snapshot = db.snapshot(); @@ -872,6 +871,7 @@ fn recoverable_error_during_deployment() { } #[test] +#[allow(clippy::too_many_lines)] // Adequate for a test fn stopped_service_workflow() { let instance_id = 0; let instance_name = "supervisor"; diff --git a/exonum/src/runtime/error/error_match.rs b/exonum/src/runtime/error/error_match.rs index b0a35d2767..24a96201e5 100644 --- a/exonum/src/runtime/error/error_match.rs +++ b/exonum/src/runtime/error/error_match.rs @@ -234,10 +234,7 @@ mod tests { assert_eq!(error, matcher); // Check `instance_id` matching. - error.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Constructor, - }); + error.call_site = Some(CallSite::new(100, CallType::Constructor)); assert_eq!(error, matcher); matcher.instance_id = Some(99); assert_ne!(error, matcher); @@ -250,13 +247,13 @@ mod tests { matcher.call_type = Some(CallType::Constructor); assert_eq!(error, matcher); - error.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Method { + error.call_site = Some(CallSite::new( + 100, + CallType::Method { interface: "exonum.Configure".to_owned(), id: 1, }, - }); + )); matcher.call_type = None; assert_eq!(error, matcher); matcher.call_type = Some(CallType::Method { diff --git a/exonum/src/runtime/error/execution_error.rs b/exonum/src/runtime/error/execution_error.rs index b31e340228..aa01c76cfc 100644 --- a/exonum/src/runtime/error/execution_error.rs +++ b/exonum/src/runtime/error/execution_error.rs @@ -41,6 +41,7 @@ pub struct ExecutionErrorSerde; impl ExecutionError { /// Creates a new execution error instance with the specified error kind /// and an optional description. + #[doc(hidden)] // used by `derive(ExecutionFail)` pub fn new(kind: ErrorKind, description: impl Into) -> Self { Self { kind, @@ -96,7 +97,7 @@ impl ExecutionError { } /// Returns the ID of a runtime in which this error has occurred. If the runtime is not known - /// (e.g. error originates in the core code), returns `None`. + /// (e.g., the error originates in the core code), returns `None`. pub fn runtime_id(&self) -> Option { self.runtime_id } diff --git a/exonum/src/runtime/error/mod.rs b/exonum/src/runtime/error/mod.rs index b3abc3cfc7..102bf18f22 100644 --- a/exonum/src/runtime/error/mod.rs +++ b/exonum/src/runtime/error/mod.rs @@ -107,8 +107,7 @@ pub trait ExecutionFail { /// though. /// /// The error kind and call info affect the blockchain state hash, while the description does not. -/// Therefore descriptions are mostly used for developer purposes, not for interaction of -/// the system with users. +/// Therefore descriptions are mostly used for developer purposes, not for interaction with users. /// /// [`ErrorKind`]: enum.ErrorKind.html /// [`CallSite`]: struct.CallSite.html @@ -157,11 +156,24 @@ where /// or in the service code, depending on the `kind` of the error. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, BinaryValue)] pub struct CallSite { - /// ID of the service instance that has generated an error. + /// ID of the service instance handling the call. pub instance_id: InstanceId, /// Type of a call. #[serde(flatten)] pub call_type: CallType, + + #[serde(default, skip)] + non_exhaustive: (), +} + +impl CallSite { + pub(crate) fn new(instance_id: InstanceId, call_type: CallType) -> Self { + Self { + instance_id, + call_type, + non_exhaustive: (), + } + } } impl fmt::Display for CallSite { @@ -192,6 +204,7 @@ impl ProtobufConvert for CallSite { } CallType::BeforeTransactions => pb.set_call_type(BEFORE_TRANSACTIONS), CallType::AfterTransactions => pb.set_call_type(AFTER_TRANSACTIONS), + CallType::__NonExhaustive => unreachable!(), } pb } @@ -209,14 +222,14 @@ impl ProtobufConvert for CallSite { id: pb.get_method_id(), }, }; - Ok(Self { - instance_id: pb.get_instance_id(), - call_type, - }) + Ok(Self::new(pb.get_instance_id(), call_type)) } } /// Type of a call to a service. +/// +/// This type is not intended to be exhaustively matched. It can be extended in the future +/// without breaking the semver compatibility. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "call_type", rename_all = "snake_case")] pub enum CallType { @@ -238,6 +251,11 @@ pub enum CallType { BeforeTransactions, /// Hook executing after processing transactions in a block. AfterTransactions, + + /// Never actually generated. + #[doc(hidden)] + #[serde(skip)] + __NonExhaustive, } impl fmt::Display for CallType { @@ -253,6 +271,7 @@ impl fmt::Display for CallType { } CallType::BeforeTransactions => formatter.write_str("before_transactions hook"), CallType::AfterTransactions => formatter.write_str("after_transactions hook"), + CallType::__NonExhaustive => unreachable!(), } } } diff --git a/exonum/src/runtime/error/tests.rs b/exonum/src/runtime/error/tests.rs index 37d20c4492..46779e4c69 100644 --- a/exonum/src/runtime/error/tests.rs +++ b/exonum/src/runtime/error/tests.rs @@ -47,18 +47,12 @@ fn execution_error_binary_value_round_trip() { let err2 = ExecutionError::from_bytes(bytes.into()).unwrap(); assert_eq!(err, err2); - err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Constructor, - }); + err.call_site = Some(CallSite::new(100, CallType::Constructor)); let bytes = err.to_bytes(); let err2 = ExecutionError::from_bytes(bytes.into()).unwrap(); assert_eq!(err, err2); - err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Resume, - }); + err.call_site = Some(CallSite::new(100, CallType::Resume)); let bytes = err.to_bytes(); let err2 = ExecutionError::from_bytes(bytes.into()).unwrap(); assert_eq!(err, err2); @@ -108,64 +102,52 @@ fn execution_error_object_hash_description() { assert_ne!(first_err.object_hash(), second_err.object_hash()); let mut second_err = first_err.clone(); - second_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Constructor, - }); + second_err.call_site = Some(CallSite::new(100, CallType::Constructor)); assert_ne!(first_err.object_hash(), second_err.object_hash()); - first_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Constructor, - }); + first_err.call_site = Some(CallSite::new(100, CallType::Constructor)); assert_eq!(first_err.object_hash(), second_err.object_hash()); - second_err.call_site = Some(CallSite { - instance_id: 101, - call_type: CallType::Constructor, - }); + second_err.call_site = Some(CallSite::new(101, CallType::Constructor)); assert_ne!(first_err.object_hash(), second_err.object_hash()); - second_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::AfterTransactions, - }); + second_err.call_site = Some(CallSite::new(100, CallType::AfterTransactions)); assert_ne!(first_err.object_hash(), second_err.object_hash()); - second_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Method { + second_err.call_site = Some(CallSite::new( + 100, + CallType::Method { interface: String::new(), id: 0, }, - }); + )); assert_ne!(first_err.object_hash(), second_err.object_hash()); - first_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Method { + first_err.call_site = Some(CallSite::new( + 100, + CallType::Method { interface: String::new(), id: 0, }, - }); + )); assert_eq!(first_err.object_hash(), second_err.object_hash()); - second_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Method { + second_err.call_site = Some(CallSite::new( + 100, + CallType::Method { interface: String::new(), id: 1, }, - }); + )); assert_ne!(first_err.object_hash(), second_err.object_hash()); - second_err.call_site = Some(CallSite { - instance_id: 100, - call_type: CallType::Method { + second_err.call_site = Some(CallSite::new( + 100, + CallType::Method { interface: "foo".to_owned(), id: 0, }, - }); + )); assert_ne!(first_err.object_hash(), second_err.object_hash()); } @@ -175,10 +157,7 @@ fn execution_error_display() { kind: ErrorKind::Service { code: 3 }, description: String::new(), runtime_id: Some(1), - call_site: Some(CallSite { - instance_id: 100, - call_type: CallType::Constructor, - }), + call_site: Some(CallSite::new(100, CallType::Constructor)), }; let err_string = err.to_string(); assert!(err_string.contains("Execution error with code `service:3`")); @@ -188,24 +167,24 @@ fn execution_error_display() { err.description = "Error description!".to_owned(); assert!(err.to_string().ends_with(": Error description!")); - err.call_site = Some(CallSite { - instance_id: 200, - call_type: CallType::Method { + err.call_site = Some(CallSite::new( + 200, + CallType::Method { interface: "exonum.Configure".to_owned(), id: 0, }, - }); + )); assert!(err .to_string() .contains("in exonum.Configure::(method 0) of service 200")); - err.call_site = Some(CallSite { - instance_id: 300, - call_type: CallType::Method { + err.call_site = Some(CallSite::new( + 300, + CallType::Method { interface: String::new(), id: 2, }, - }); + )); assert!(err.to_string().contains("in method 2 of service 300")); err.call_site = None; @@ -238,10 +217,7 @@ fn execution_result_serde_presentation() { kind: ErrorKind::Service { code: 3 }, description: String::new(), runtime_id: Some(1), - call_site: Some(CallSite { - instance_id: 100, - call_type: CallType::Constructor, - }), + call_site: Some(CallSite::new(100, CallType::Constructor)), })); assert_eq!( serde_json::to_value(result).unwrap(), @@ -260,10 +236,7 @@ fn execution_result_serde_presentation() { kind: ErrorKind::Service { code: 3 }, description: String::new(), runtime_id: Some(1), - call_site: Some(CallSite { - instance_id: 100, - call_type: CallType::Resume, - }), + call_site: Some(CallSite::new(100, CallType::Resume)), })); assert_eq!( serde_json::to_value(result).unwrap(), @@ -282,13 +255,13 @@ fn execution_result_serde_presentation() { kind: ErrorKind::Core { code: 8 }, description: "!".to_owned(), runtime_id: Some(0), - call_site: Some(CallSite { - instance_id: 100, - call_type: CallType::Method { + call_site: Some(CallSite::new( + 100, + CallType::Method { interface: "exonum.Configure".to_owned(), id: 1, }, - }), + )), })); assert_eq!( serde_json::to_value(result).unwrap(), @@ -334,23 +307,20 @@ fn execution_result_serde_roundtrip() { } if let Err(err) = res.0.as_mut() { - err.call_site = Some(CallSite { - instance_id: 1_000, - call_type: CallType::AfterTransactions, - }); + err.call_site = Some(CallSite::new(1_000, CallType::AfterTransactions)); let json = serde_json::to_string_pretty(&res).unwrap(); let res2 = serde_json::from_str(&json).unwrap(); assert_eq!(res, res2); } if let Err(err) = res.0.as_mut() { - err.call_site = Some(CallSite { - instance_id: 1_000, - call_type: CallType::Method { + err.call_site = Some(CallSite::new( + 1_000, + CallType::Method { interface: "exonum.Configure".to_owned(), id: 1, }, - }); + )); let json = serde_json::to_string_pretty(&res).unwrap(); let res2 = serde_json::from_str(&json).unwrap(); assert_eq!(res, res2); diff --git a/exonum/src/runtime/execution_context.rs b/exonum/src/runtime/execution_context.rs index 1c320490ed..22f83beb72 100644 --- a/exonum/src/runtime/execution_context.rs +++ b/exonum/src/runtime/execution_context.rs @@ -126,11 +126,13 @@ impl<'a> ExecutionContext<'a> { core_schema.next_height() == Height(0) } - /// Returns identifier of the service interface required for the call. + /// Returns an identifier of the service interface required for the call. + /// This identifier is always empty for the primary service interface. /// - /// Keep in mind that this getter, in fact, is a part of an unfinished "interfaces" feature. - /// It will be replaced in future releases. At the moment this field is always empty for - /// the primary service interface. + /// # Stability + /// + /// This getter is a part of an unfinished "interfaces" feature. It is exempt + /// from semantic versioning and will be replaced in the future releases. pub fn interface_name(&self) -> &str { self.interface_name } @@ -171,10 +173,7 @@ impl<'a> ExecutionContext<'a> { .initiate_adding_service(context, &spec.artifact, constructor.into_bytes()) .map_err(|mut err| { err.set_runtime_id(spec.artifact.runtime_id) - .set_call_site(|| CallSite { - instance_id: spec.id, - call_type: CallType::Constructor, - }); + .set_call_site(|| CallSite::new(spec.id, CallType::Constructor)); err })?; @@ -275,12 +274,14 @@ impl ExecutionContextUnstable for ExecutionContext<'_> { runtime .execute(context, method_id, arguments) .map_err(|mut err| { - err.set_runtime_id(runtime_id).set_call_site(|| CallSite { - instance_id: descriptor.id, - call_type: CallType::Method { - interface: interface_name.to_owned(), - id: method_id, - }, + err.set_runtime_id(runtime_id).set_call_site(|| { + CallSite::new( + descriptor.id, + CallType::Method { + interface: interface_name.to_owned(), + id: method_id, + }, + ) }); err }) @@ -367,10 +368,7 @@ impl<'a> SupervisorExtensions<'a> { ) .map_err(|mut err| { err.set_runtime_id(spec.artifact.runtime_id) - .set_call_site(|| CallSite { - instance_id, - call_type: CallType::Constructor, - }); + .set_call_site(|| CallSite::new(instance_id, CallType::Constructor)); err })?; diff --git a/exonum/src/runtime/migrations.rs b/exonum/src/runtime/migrations.rs index 3ff7811600..ed4093d5ff 100644 --- a/exonum/src/runtime/migrations.rs +++ b/exonum/src/runtime/migrations.rs @@ -300,6 +300,9 @@ pub trait MigrateData { /// Errors that can occur when initiating a data migration. This error indicates that the migration /// cannot be started. +/// +/// This type is not intended to be exhaustively matched. It can be extended in the future +/// without breaking the semver compatibility. #[derive(Debug, Fail)] pub enum InitMigrationError { /// The start version is too far in the past. @@ -331,6 +334,10 @@ pub enum InitMigrationError { /// Data migrations are not supported by the artifact. #[fail(display = "Data migrations are not supported by the artifact")] NotSupported, + + #[doc(hidden)] + #[fail(display = "")] // Never actually generated. + __NonExhaustive, } impl From for ExecutionError { diff --git a/exonum/src/runtime/mod.rs b/exonum/src/runtime/mod.rs index 33a4e95b28..0000c2cdc4 100644 --- a/exonum/src/runtime/mod.rs +++ b/exonum/src/runtime/mod.rs @@ -15,12 +15,21 @@ //! 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 +//! 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 @@ -84,14 +93,26 @@ //! 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 by a corresponding request to [`Dispatcher`]. +//! 4. Active service instances can be stopped by a corresponding request to the dispatcher. //! A stopped service no longer participates in business logic, i.e. it does not process //! transactions, events, 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. //! -//! The [`Dispatcher`] is responsible for persisting artifacts and services across node restarts. +//! The dispatcher is responsible for persisting artifacts and services across node restarts. +//! +//! ## 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 //! @@ -121,7 +142,7 @@ //! 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. +//! 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, @@ -153,7 +174,6 @@ //! //! [`AnyTx`]: struct.AnyTx.html //! [`CallInfo`]: struct.CallInfo.html -//! [`Dispatcher`]: struct.Dispatcher.html //! [`instance_id`]: struct.CallInfo.html#structfield.instance_id //! [`Runtime`]: trait.Runtime.html //! [execution]: trait.Runtime.html#execute @@ -165,9 +185,10 @@ //! [`ExecutionError`]: struct.ExecutionError.html //! [`instance_id`]: struct.CallInfo.html#structfield.method_id +pub(crate) use self::dispatcher::Dispatcher; pub use self::{ blockchain_data::{BlockchainData, SnapshotExt}, - dispatcher::{Action as DispatcherAction, Dispatcher, Mailbox, Schema as DispatcherSchema}, + dispatcher::{Action as DispatcherAction, Mailbox, Schema as DispatcherSchema}, error::{ catch_panic, CallSite, CallType, CommonError, CoreError, ErrorKind, ErrorMatch, ExecutionError, ExecutionFail, ExecutionStatus, @@ -250,18 +271,19 @@ impl fmt::Display for RuntimeIdentifier { } } -/// Runtime environment for the Exonum services. +/// Runtime environment for Exonum services. /// /// You can read more about the life cycle of services and transactions -/// [above](index.html#service-life-cycle). +/// [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. 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. Depending on the runtime, an artifact may have an -/// additional specification required for its deployment; e.g., files to be compiled. -/// An artifact creates corresponding service instances similar to classes in object-oriented -/// programming. +/// 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 /// @@ -288,6 +310,7 @@ impl fmt::Display for RuntimeIdentifier { /// - `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 @@ -659,19 +682,7 @@ impl<'a> InstanceDescriptor<'a> { } impl fmt::Display for InstanceDescriptor<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.id, self.name) - } -} - -impl From> for (InstanceId, String) { - fn from(descriptor: InstanceDescriptor<'_>) -> Self { - (descriptor.id, descriptor.name.to_owned()) - } -} - -impl<'a> From<(InstanceId, &'a str)> for InstanceDescriptor<'a> { - fn from((id, name): (InstanceId, &'a str)) -> Self { - InstanceDescriptor::new(id, name) + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "{}:{}", self.id, self.name) } } diff --git a/exonum/src/runtime/types.rs b/exonum/src/runtime/types.rs index 56d33037fc..4b11c27099 100644 --- a/exonum/src/runtime/types.rs +++ b/exonum/src/runtime/types.rs @@ -47,14 +47,14 @@ pub type InstanceId = u32; /// Identifier of the method in the service interface required for the call. pub type MethodId = u32; -/// Information for calling the service method. +/// Information sufficient to route a transaction to a service. #[derive(Default, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] #[derive(Serialize, Deserialize)] #[derive(ProtobufConvert)] #[protobuf_convert(source = "schema::runtime::CallInfo")] pub struct CallInfo { /// Unique service instance identifier. The dispatcher uses this identifier to find the - /// corresponding runtime to execute a transaction. + /// runtime to execute a transaction. pub instance_id: InstanceId, /// Identifier of the method in the service interface required for the call. pub method_id: MethodId, @@ -66,7 +66,7 @@ pub struct CallInfo { } impl CallInfo { - /// Create an ordinary `CallInfo` instance. + /// Creates a `CallInfo` instance. pub fn new(instance_id: u32, method_id: u32) -> Self { Self { instance_id, @@ -76,7 +76,7 @@ impl CallInfo { } } -/// Transaction with the information required for the call. +/// Transaction with the information required to dispatch it to a service. /// /// # Examples /// @@ -143,11 +143,11 @@ impl AnyTx { } } -/// The artifact identifier is required by the runtime to construct service instances. +/// The artifact identifier is required to construct service instances. /// In other words, an artifact identifier is similar to a class name, and a specific service /// instance is similar to a class instance. /// -/// In string representation the artifact identifier is written as follows: +/// An artifact ID has the following string representation: /// /// ```text /// {runtime_id}:{artifact_name}:{version} @@ -197,6 +197,7 @@ pub struct ArtifactId { mod pb_version { use super::*; + #[allow(clippy::needless_pass_by_value)] // required for work with `protobuf_convert(with)` pub fn from_pb(pb: String) -> Result { pb.parse().map_err(From::from) } @@ -221,10 +222,12 @@ impl ArtifactId { /// Creates a new artifact identifier from prepared parts without any checks. /// + /// Use this method only if you don't need an artifact verification (e.g. in tests). + /// + /// # Stability + /// /// Since the internal structure of `ArtifactId` can change, this method is considered /// unstable and can break in the future. - /// - /// Use this method only if you don't need an artifact verification (e.g. in tests). pub fn from_raw_parts(runtime_id: u32, name: String, version: Version) -> Self { Self { runtime_id, @@ -239,7 +242,7 @@ impl ArtifactId { self.name == other.name && self.version > other.version } - /// Converts into `InstanceInitParams` with given id, name and empty constructor. + /// Converts into `InstanceInitParams` with the given IDs and an empty constructor. pub fn into_default_instance( self, id: InstanceId, @@ -306,7 +309,7 @@ impl FromStr for ArtifactId { } } -/// Exhaustive artifact specification. +/// Exhaustive artifact specification. This information is enough to deploy an artifact. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize)] #[derive(ProtobufConvert, BinaryValue, ObjectHash)] @@ -684,7 +687,7 @@ impl ProtobufConvert for InstanceStatus { } } -/// Current state of artifact in dispatcher. +/// Current state of an artifact. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize)] #[derive(ProtobufConvert, BinaryValue, ObjectHash)] @@ -702,8 +705,8 @@ pub struct ArtifactState { } impl ArtifactState { - /// Create a new artifact state with the given specification and status. - pub fn new(deploy_spec: Vec, status: ArtifactStatus) -> Self { + /// Creates an artifact state with the given specification and status. + pub(super) fn new(deploy_spec: Vec, status: ArtifactStatus) -> Self { Self { deploy_spec, status, @@ -748,6 +751,7 @@ pub struct InstanceState { mod pb_optional_version { use super::*; + #[allow(clippy::needless_pass_by_value)] // required for work with `protobuf_convert(with)` pub fn from_pb(pb: String) -> Result, failure::Error> { if pb.is_empty() { Ok(None) diff --git a/exonum/src/runtime/versioning.rs b/exonum/src/runtime/versioning.rs index 11a3e82b61..6ea65de9bc 100644 --- a/exonum/src/runtime/versioning.rs +++ b/exonum/src/runtime/versioning.rs @@ -167,6 +167,7 @@ pub use semver::{Version, VersionReq}; use failure::{format_err, Fail}; + use std::{fmt, str::FromStr}; use crate::runtime::{ArtifactId, CoreError, ExecutionError, ExecutionFail}; @@ -322,6 +323,9 @@ pub trait RequireArtifact { } /// Artifact requirement error. +/// +/// This type is not intended to be exhaustively matched. It can be extended in the future +/// without breaking the semver compatibility. #[derive(Debug, Fail)] pub enum ArtifactReqError { /// No service with the specified identifier exists. @@ -346,6 +350,10 @@ pub enum ArtifactReqError { /// Actual artifact version. actual: Version, }, + + #[doc(hidden)] + #[fail(display = "")] // Never actually generated. + __NonExhaustive, } impl From for ExecutionError { diff --git a/runtimes/rust/benches/criterion/block.rs b/runtimes/rust/benches/criterion/block.rs index c16fd67338..fa1422c06d 100644 --- a/runtimes/rust/benches/criterion/block.rs +++ b/runtimes/rust/benches/criterion/block.rs @@ -121,7 +121,7 @@ fn create_consensus_config_and_blockchain_base( fn execute_block(blockchain: &BlockchainMut, height: u64, txs: &[Hash]) -> (Hash, Patch) { blockchain.create_patch( - ValidatorId::zero().into(), + ValidatorId::zero(), Height(height), txs, &mut BTreeMap::new(), @@ -192,7 +192,7 @@ mod cryptocurrency { crypto::PublicKey, merkledb::access::AccessExt, messages::Verified, - runtime::{AnyTx, ErrorKind, ExecutionContext, ExecutionError, InstanceId}, + runtime::{AnyTx, ExecutionContext, ExecutionError, InstanceId}, }; use exonum_derive::{ exonum_interface, BinaryValue, ObjectHash, ServiceDispatcher, ServiceFactory, @@ -270,8 +270,7 @@ mod cryptocurrency { if arg.seed % 2 == 0 { Ok(()) } else { - let error_kind = ErrorKind::Service { code: 15 }; - Err(ExecutionError::new(error_kind, "")) + Err(ExecutionError::service(15, "")) } } } diff --git a/runtimes/rust/src/api.rs b/runtimes/rust/src/api.rs index 5a0baed7d6..2759dc070b 100644 --- a/runtimes/rust/src/api.rs +++ b/runtimes/rust/src/api.rs @@ -137,7 +137,7 @@ impl ServiceApiScope { Self { inner: ApiScope::new(), blockchain, - descriptor: instance.into(), + descriptor: (instance.id, instance.name.to_owned()), } } @@ -152,16 +152,14 @@ impl ServiceApiScope { R: IntoFuture + 'static, { let blockchain = self.blockchain.clone(); - let descriptor = self.descriptor.clone(); + let (instance_id, instance_name) = self.descriptor.clone(); self.inner .endpoint(name, move |query: Q| -> FutureResult { - let (instance_id, instance_name) = descriptor.clone(); - let state = ServiceApiState::from_api_context( - &blockchain, - (instance_id, instance_name.as_ref()).into(), - name, - ); + let descriptor = InstanceDescriptor::new(instance_id, &instance_name); + let state = ServiceApiState::from_api_context(&blockchain, descriptor, name); let result = handler(&state, query); + + let instance_name = instance_name.clone(); let future = result .into_future() .map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name))); @@ -181,16 +179,14 @@ impl ServiceApiScope { R: IntoFuture + 'static, { let blockchain = self.blockchain.clone(); - let descriptor = self.descriptor.clone(); + let (instance_id, instance_name) = self.descriptor.clone(); self.inner .endpoint_mut(name, move |query: Q| -> FutureResult { - let (instance_id, instance_name) = descriptor.clone(); - let state = ServiceApiState::from_api_context( - &blockchain, - (instance_id, instance_name.as_ref()).into(), - name, - ); + let descriptor = InstanceDescriptor::new(instance_id, &instance_name); + let state = ServiceApiState::from_api_context(&blockchain, descriptor, name); let result = handler(&state, query); + + let instance_name = instance_name.clone(); let future = result .into_future() .map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name))); @@ -214,16 +210,14 @@ impl ServiceApiScope { R: IntoFuture + 'static, { let blockchain = self.blockchain.clone(); - let descriptor = self.descriptor.clone(); + let (instance_id, instance_name) = self.descriptor.clone(); let inner = deprecated.handler.clone(); let handler = move |query: Q| -> FutureResult { - let (instance_id, instance_name) = descriptor.clone(); - let state = ServiceApiState::from_api_context( - &blockchain, - (instance_id, instance_name.as_ref()).into(), - name, - ); + let descriptor = InstanceDescriptor::new(instance_id, &instance_name); + let state = ServiceApiState::from_api_context(&blockchain, descriptor, name); let result = inner(&state, query); + + let instance_name = instance_name.clone(); let future = result .into_future() .map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name))); @@ -250,16 +244,14 @@ impl ServiceApiScope { R: IntoFuture + 'static, { let blockchain = self.blockchain.clone(); - let descriptor = self.descriptor.clone(); + let (instance_id, instance_name) = self.descriptor.clone(); let inner = deprecated.handler.clone(); let handler = move |query: Q| -> FutureResult { - let (instance_id, instance_name) = descriptor.clone(); - let state = ServiceApiState::from_api_context( - &blockchain, - (instance_id, instance_name.as_ref()).into(), - name, - ); + let descriptor = InstanceDescriptor::new(instance_id, &instance_name); + let state = ServiceApiState::from_api_context(&blockchain, descriptor, name); let result = inner(&state, query); + + let instance_name = instance_name.clone(); let future = result .into_future() .map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name))); diff --git a/runtimes/rust/tests/blockchain_data.rs b/runtimes/rust/tests/blockchain_data.rs index ce557cbd1b..c3631bc84a 100644 --- a/runtimes/rust/tests/blockchain_data.rs +++ b/runtimes/rust/tests/blockchain_data.rs @@ -119,7 +119,7 @@ fn setup_blockchain_for_index_proofs() -> Box { blockchain.merge(fork.into_patch()).unwrap(); let (block_hash, patch) = - blockchain.create_patch(ValidatorId(0).into(), Height(1), &[], &mut BTreeMap::new()); + blockchain.create_patch(ValidatorId(0), Height(1), &[], &mut BTreeMap::new()); blockchain .commit(patch, block_hash, vec![], &mut BTreeMap::new()) .unwrap(); diff --git a/runtimes/rust/tests/inspected/mod.rs b/runtimes/rust/tests/inspected/mod.rs index 4f29e08e18..ecc8afc015 100644 --- a/runtimes/rust/tests/inspected/mod.rs +++ b/runtimes/rust/tests/inspected/mod.rs @@ -70,7 +70,7 @@ pub fn create_block_with_transactions( }; blockchain.create_patch( - ValidatorId::zero().into(), + ValidatorId::zero(), height, &tx_hashes, &mut BTreeMap::new(), diff --git a/services/explorer/tests/api.rs b/services/explorer/tests/api.rs index 143b90f525..cc021f9600 100644 --- a/services/explorer/tests/api.rs +++ b/services/explorer/tests/api.rs @@ -83,7 +83,7 @@ fn test_explorer_blocks_basic() { .unwrap(); let mut headers = AdditionalHeaders::new(); - headers.insert::(ValidatorId(0).into()); + headers.insert::(ValidatorId(0)); assert_eq!(blocks.len(), 2); assert_eq!(blocks[0].block.height, Height(1)); diff --git a/test-suite/testkit/src/lib.rs b/test-suite/testkit/src/lib.rs index bcad9b3f5f..4ad2705b59 100644 --- a/test-suite/testkit/src/lib.rs +++ b/test-suite/testkit/src/lib.rs @@ -412,7 +412,7 @@ impl TestKit { let guard = self.processing_lock.lock().unwrap(); let (block_hash, patch) = self.blockchain.create_patch( - validator_id.into(), + validator_id, new_block_height, tx_hashes, &mut BTreeMap::new(),