diff --git a/examples/cryptocurrency-advanced/backend/Cargo.toml b/examples/cryptocurrency-advanced/backend/Cargo.toml index 8215237974..4e86918b02 100644 --- a/examples/cryptocurrency-advanced/backend/Cargo.toml +++ b/examples/cryptocurrency-advanced/backend/Cargo.toml @@ -16,10 +16,12 @@ circle-ci = { repository = "exonum/exonum" } [dependencies] exonum = { version = "0.9.0", path = "../../../exonum" } +exonum_derive = { path = "../../../exonum_derive" } exonum-configuration = { version = "0.9.0", path = "../../../services/configuration" } serde = "1.0.0" serde_derive = "1.0.0" failure = "0.1.2" +protobuf = "=2.2.0" [dev-dependencies] exonum-testkit = { version = "0.9.0", path = "../../../testkit" } @@ -27,3 +29,6 @@ serde_json = "1.0.0" pretty_assertions = "=0.5.1" assert_matches = "1.2.0" hex = "=0.3.2" + +[build-dependencies] +exonum_build = { version = "0.9.0", path = "../../../exonum_build" } diff --git a/examples/cryptocurrency-advanced/backend/build.rs b/examples/cryptocurrency-advanced/backend/build.rs new file mode 100644 index 0000000000..70c628570f --- /dev/null +++ b/examples/cryptocurrency-advanced/backend/build.rs @@ -0,0 +1,13 @@ +extern crate exonum_build; + +use exonum_build::protobuf_generate; +use std::env; + +fn main() { + let exonum_protos = env::var("DEP_EXONUM_PROTOBUF_PROTOS").unwrap(); + protobuf_generate( + "src/proto", + &["src/proto", &exonum_protos], + "protobuf_mod.rs", + ); +} diff --git a/examples/cryptocurrency-advanced/backend/src/lib.rs b/examples/cryptocurrency-advanced/backend/src/lib.rs index 87d9e78339..e86d78b709 100644 --- a/examples/cryptocurrency-advanced/backend/src/lib.rs +++ b/examples/cryptocurrency-advanced/backend/src/lib.rs @@ -21,9 +21,11 @@ bare_trait_objects )] -#[macro_use] extern crate exonum; #[macro_use] +extern crate exonum_derive; +extern crate protobuf; +#[macro_use] extern crate failure; extern crate serde; #[macro_use] @@ -32,6 +34,7 @@ extern crate serde_derive; pub use schema::Schema; pub mod api; +pub mod proto; pub mod schema; pub mod transactions; pub mod wallet; diff --git a/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto b/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto new file mode 100644 index 0000000000..134cf680aa --- /dev/null +++ b/examples/cryptocurrency-advanced/backend/src/proto/cryptocurrency.proto @@ -0,0 +1,57 @@ +// Copyright 2018 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. + +syntax = "proto3"; + +package exonum.examples.cryptocurrency_advanced; + +import "helpers.proto"; + +/// Transfer `amount` of the currency from one wallet to another. +message Transfer { + // `PublicKey` of receiver's wallet. + exonum.PublicKey to = 1; + // Amount of currency to transfer. + uint64 amount = 2; + // Auxiliary number to guarantee non-idempotence of transactions. + uint64 seed = 3; +} + +// Issue `amount` of the currency to the `wallet`. +message Issue { + // Issued amount of currency. + uint64 amount = 1; + // Auxiliary number to guarantee non-idempotence of transactions. + uint64 seed = 2; +} + +// Create wallet with the given `name`. +message CreateWallet { + // Name of the new wallet. + string name = 1; +} + +// Wallet information stored in the database. +message Wallet { + // `PublicKey` of the wallet. + exonum.PublicKey pub_key = 1; + // Name of the wallet. + string name = 2; + // Current balance of the wallet. + uint64 balance = 3; + // Length of the transactions history. + uint64 history_len = 4; + // `Hash` of the transactions history. + exonum.Hash history_hash = 5; +} diff --git a/examples/cryptocurrency-advanced/backend/src/proto/mod.rs b/examples/cryptocurrency-advanced/backend/src/proto/mod.rs new file mode 100644 index 0000000000..ed9fe2cce2 --- /dev/null +++ b/examples/cryptocurrency-advanced/backend/src/proto/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2018 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. + +//! Module of the rust-protobuf generated files. + +#![allow(bare_trait_objects)] +#![allow(renamed_and_removed_lints)] + +pub use self::cryptocurrency::{CreateWallet, Issue, Transfer, Wallet}; + +include!(concat!(env!("OUT_DIR"), "/protobuf_mod.rs")); + +use exonum::encoding::protobuf::*; diff --git a/examples/cryptocurrency-advanced/backend/src/schema.rs b/examples/cryptocurrency-advanced/backend/src/schema.rs index 882bf43c5c..e3557be1d5 100644 --- a/examples/cryptocurrency-advanced/backend/src/schema.rs +++ b/examples/cryptocurrency-advanced/backend/src/schema.rs @@ -84,13 +84,13 @@ impl<'a> Schema<&'a mut Fork> { /// Panics if there is no wallet with given public key. pub fn increase_wallet_balance(&mut self, wallet: Wallet, amount: u64, transaction: &Hash) { let wallet = { - let mut history = self.wallet_history_mut(wallet.pub_key()); + let mut history = self.wallet_history_mut(&wallet.pub_key); history.push(*transaction); let history_hash = history.merkle_root(); - let balance = wallet.balance(); + let balance = wallet.balance; wallet.set_balance(balance + amount, &history_hash) }; - self.wallets_mut().put(wallet.pub_key(), wallet.clone()); + self.wallets_mut().put(&wallet.pub_key, wallet.clone()); } /// Decrease balance of the wallet and append new record to its history. @@ -98,13 +98,13 @@ impl<'a> Schema<&'a mut Fork> { /// Panics if there is no wallet with given public key. pub fn decrease_wallet_balance(&mut self, wallet: Wallet, amount: u64, transaction: &Hash) { let wallet = { - let mut history = self.wallet_history_mut(wallet.pub_key()); + let mut history = self.wallet_history_mut(&wallet.pub_key); history.push(*transaction); let history_hash = history.merkle_root(); - let balance = wallet.balance(); + let balance = wallet.balance; wallet.set_balance(balance - amount, &history_hash) }; - self.wallets_mut().put(wallet.pub_key(), wallet.clone()); + self.wallets_mut().put(&wallet.pub_key, wallet.clone()); } /// Create new wallet and append first record to its history. diff --git a/examples/cryptocurrency-advanced/backend/src/transactions.rs b/examples/cryptocurrency-advanced/backend/src/transactions.rs index 560f3b1b32..0fede26979 100644 --- a/examples/cryptocurrency-advanced/backend/src/transactions.rs +++ b/examples/cryptocurrency-advanced/backend/src/transactions.rs @@ -24,6 +24,7 @@ use exonum::{ messages::{Message, RawTransaction, Signed}, }; +use super::proto; use schema::Schema; use CRYPTOCURRENCY_SERVICE_ID; @@ -65,44 +66,62 @@ impl From for ExecutionError { } } -transactions! { - /// Transaction group. - pub WalletTransactions { - - /// Transfer `amount` of the currency from one wallet to another. - struct Transfer { - /// `PublicKey` of receiver's wallet. - to: &PublicKey, - /// Amount of currency to transfer. - amount: u64, - /// Auxiliary number to guarantee [non-idempotence][idempotence] of transactions. - /// - /// [idempotence]: https://en.wikipedia.org/wiki/Idempotence - seed: u64, - } +/// Transfer `amount` of the currency from one wallet to another. +#[derive(Serialize, Deserialize, Clone, Debug, ProtobufConvert)] +#[exonum(pb = "proto::Transfer")] +pub struct Transfer { + /// `PublicKey` of receiver's wallet. + pub to: PublicKey, + /// Amount of currency to transfer. + pub amount: u64, + /// Auxiliary number to guarantee [non-idempotence][idempotence] of transactions. + /// + /// [idempotence]: https://en.wikipedia.org/wiki/Idempotence + pub seed: u64, +} - /// Issue `amount` of the currency to the `wallet`. - struct Issue { - /// Issued amount of currency. - amount: u64, - /// Auxiliary number to guarantee [non-idempotence][idempotence] of transactions. - /// - /// [idempotence]: https://en.wikipedia.org/wiki/Idempotence - seed: u64, - } +/// Issue `amount` of the currency to the `wallet`. +#[derive(Serialize, Deserialize, Clone, Debug, ProtobufConvert)] +#[exonum(pb = "proto::Issue")] +pub struct Issue { + /// Issued amount of currency. + pub amount: u64, + /// Auxiliary number to guarantee [non-idempotence][idempotence] of transactions. + /// + /// [idempotence]: https://en.wikipedia.org/wiki/Idempotence + pub seed: u64, +} - /// Create wallet with the given `name`. - struct CreateWallet { - /// Name of the new wallet. - name: &str, - } - } +/// Create wallet with the given `name`. +#[derive(Serialize, Deserialize, Clone, Debug, ProtobufConvert)] +#[exonum(pb = "proto::CreateWallet")] +pub struct CreateWallet { + /// Name of the new wallet. + pub name: String, +} + +/// Transaction group. +#[derive(Serialize, Deserialize, Clone, Debug, TransactionSet)] +pub enum WalletTransactions { + /// Transfer tx. + Transfer(Transfer), + /// Issue tx. + Issue(Issue), + /// CreateWallet tx. + CreateWallet(CreateWallet), } impl CreateWallet { #[doc(hidden)] pub fn sign(name: &str, pk: &PublicKey, sk: &SecretKey) -> Signed { - Message::sign_transaction(CreateWallet::new(name), CRYPTOCURRENCY_SERVICE_ID, *pk, sk) + Message::sign_transaction( + Self { + name: name.to_owned(), + }, + CRYPTOCURRENCY_SERVICE_ID, + *pk, + sk, + ) } } @@ -110,13 +129,13 @@ impl Transfer { #[doc(hidden)] pub fn sign( pk: &PublicKey, - to: &PublicKey, + &to: &PublicKey, amount: u64, seed: u64, sk: &SecretKey, ) -> Signed { Message::sign_transaction( - Transfer::new(to, amount, seed), + Self { to, amount, seed }, CRYPTOCURRENCY_SERVICE_ID, *pk, sk, @@ -131,8 +150,8 @@ impl Transaction for Transfer { let mut schema = Schema::new(context.fork()); - let to = self.to(); - let amount = self.amount(); + let to = &self.to; + let amount = self.amount; if from == to { return Err(ExecutionError::new(ERROR_SENDER_SAME_AS_RECEIVER)); @@ -142,7 +161,7 @@ impl Transaction for Transfer { let receiver = schema.wallet(to).ok_or(Error::ReceiverNotFound)?; - if sender.balance() < amount { + if sender.balance < amount { Err(Error::InsufficientCurrencyAmount)? } @@ -161,7 +180,7 @@ impl Transaction for Issue { let mut schema = Schema::new(context.fork()); if let Some(wallet) = schema.wallet(pub_key) { - let amount = self.amount(); + let amount = self.amount; schema.increase_wallet_balance(wallet, amount, &hash); Ok(()) } else { @@ -178,7 +197,7 @@ impl Transaction for CreateWallet { let mut schema = Schema::new(context.fork()); if schema.wallet(pub_key).is_none() { - let name = self.name(); + let name = &self.name; schema.create_wallet(pub_key, name, &hash); Ok(()) } else { diff --git a/examples/cryptocurrency-advanced/backend/src/wallet.rs b/examples/cryptocurrency-advanced/backend/src/wallet.rs index fd041cad08..45fcf9bba9 100644 --- a/examples/cryptocurrency-advanced/backend/src/wallet.rs +++ b/examples/cryptocurrency-advanced/backend/src/wallet.rs @@ -16,30 +16,48 @@ use exonum::crypto::{Hash, PublicKey}; -encoding_struct! { - /// Wallet information stored in the database. - struct Wallet { - /// `PublicKey` of the wallet. - pub_key: &PublicKey, - /// Name of the wallet. - name: &str, - /// Current balance of the wallet. - balance: u64, - /// Length of the transactions history. - history_len: u64, - /// `Hash` of the transactions history. - history_hash: &Hash, - } +use super::proto; + +/// Wallet information stored in the database. +#[derive(Serialize, Deserialize, Clone, Debug, ProtobufConvert)] +#[exonum(pb = "proto::Wallet")] +pub struct Wallet { + /// `PublicKey` of the wallet. + pub pub_key: PublicKey, + /// Name of the wallet. + pub name: String, + /// Current balance of the wallet. + pub balance: u64, + /// Length of the transactions history. + pub history_len: u64, + /// `Hash` of the transactions history. + pub history_hash: Hash, } impl Wallet { + /// Create new Wallet. + pub fn new( + &pub_key: &PublicKey, + name: &str, + balance: u64, + history_len: u64, + &history_hash: &Hash, + ) -> Self { + Self { + pub_key, + name: name.to_owned(), + balance, + history_len, + history_hash, + } + } /// Returns a copy of this wallet with updated balance. pub fn set_balance(self, balance: u64, history_hash: &Hash) -> Self { Self::new( - self.pub_key(), - self.name(), + &self.pub_key, + &self.name, balance, - self.history_len() + 1, + self.history_len + 1, history_hash, ) } diff --git a/examples/cryptocurrency-advanced/backend/tests/api.rs b/examples/cryptocurrency-advanced/backend/tests/api.rs index 7ffffef972..fce1d5476d 100644 --- a/examples/cryptocurrency-advanced/backend/tests/api.rs +++ b/examples/cryptocurrency-advanced/backend/tests/api.rs @@ -55,9 +55,9 @@ fn test_create_wallet() { // Check that the user indeed is persisted by the service. let wallet = api.get_wallet(tx.author()).unwrap(); - assert_eq!(wallet.pub_key(), &tx.author()); - assert_eq!(wallet.name(), ALICE_NAME); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.pub_key, tx.author()); + assert_eq!(wallet.name, ALICE_NAME); + assert_eq!(wallet.balance, 100); } /// Check that the transfer transaction works as intended. @@ -73,9 +73,9 @@ fn test_transfer() { // Check that the initial Alice's and Bob's balances persisted by the service. let wallet = api.get_wallet(tx_alice.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); let wallet = api.get_wallet(tx_bob.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); // Transfer funds by invoking the corresponding API method. let tx = Transfer::sign( @@ -92,9 +92,9 @@ fn test_transfer() { // After the transfer transaction is included into a block, we may check new wallet // balances. let wallet = api.get_wallet(tx_alice.author()).unwrap(); - assert_eq!(wallet.balance(), 90); + assert_eq!(wallet.balance, 90); let wallet = api.get_wallet(tx_bob.author()).unwrap(); - assert_eq!(wallet.balance(), 110); + assert_eq!(wallet.balance, 110); } /// Check that a transfer from a non-existing wallet fails as expected. @@ -110,7 +110,7 @@ fn test_transfer_from_nonexisting_wallet() { api.assert_no_wallet(tx_alice.author()); let wallet = api.get_wallet(tx_bob.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); let tx = Transfer::sign( &tx_alice.author(), @@ -128,7 +128,7 @@ fn test_transfer_from_nonexisting_wallet() { // Check that Bob's balance doesn't change. let wallet = api.get_wallet(tx_bob.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); } /// Check that a transfer to a non-existing wallet fails as expected. @@ -143,7 +143,7 @@ fn test_transfer_to_nonexisting_wallet() { testkit.create_block_with_tx_hashes(&[tx_alice.hash()]); let wallet = api.get_wallet(tx_alice.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); api.assert_no_wallet(tx_bob.author()); let tx = Transfer::sign( @@ -162,7 +162,7 @@ fn test_transfer_to_nonexisting_wallet() { // Check that Alice's balance doesn't change. let wallet = api.get_wallet(tx_alice.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); } /// Check that an overcharge does not lead to changes in sender's and receiver's balances. @@ -190,9 +190,9 @@ fn test_transfer_overcharge() { ); let wallet = api.get_wallet(tx_alice.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); let wallet = api.get_wallet(tx_bob.author()).unwrap(); - assert_eq!(wallet.balance(), 100); + assert_eq!(wallet.balance, 100); } #[test] diff --git a/exonum-dictionary.txt b/exonum-dictionary.txt index 5f2d317bd7..7530cb1c8b 100644 --- a/exonum-dictionary.txt +++ b/exonum-dictionary.txt @@ -100,6 +100,7 @@ proptest protobuf proto protoc +protos pubkey pubkeys pwbox diff --git a/exonum/Cargo.toml b/exonum/Cargo.toml index 3b9f12ae34..4980426855 100644 --- a/exonum/Cargo.toml +++ b/exonum/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["database", "distributed", "blockchain", "framework", "exonum"] categories = ["cryptography", "database-implementations"] description = "An extensible framework for blockchain software projects." autobenches = true +links = "exonum_protobuf" [badges] travis-ci = { repository = "exonum/exonum" } diff --git a/exonum/build.rs b/exonum/build.rs index 823e787998..2f1c50843d 100644 --- a/exonum/build.rs +++ b/exonum/build.rs @@ -8,7 +8,17 @@ use std::{env, fs::File, io::Write, path::Path, process::Command}; static USER_AGENT_FILE_NAME: &str = "user_agent"; -fn main() { +fn create_path_to_protobuf_schema_env() { + // Workaround for https://github.com/rust-lang/cargo/issues/3544 + // We "link" exonum with exonum_protobuf library + // and dependents in their `build.rs` will have access to `$DEP_EXONUM_PROTOBUF_PROTOS`. + let path = env::current_dir() + .expect("Failed to get current dir.") + .join("src/encoding/protobuf/proto"); + println!("cargo:protos={}", path.to_str().unwrap()); +} + +fn write_user_agent_file() { let package_name = option_env!("CARGO_PKG_NAME").unwrap_or("exonum"); let package_version = option_env!("CARGO_PKG_VERSION").unwrap_or("?"); let rust_version = rust_version().unwrap_or("rust ?".to_string()); @@ -19,6 +29,12 @@ fn main() { let mut file = File::create(dest_path).expect("Unable to create output file"); file.write_all(user_agent.as_bytes()) .expect("Unable to write data to file"); +} + +fn main() { + write_user_agent_file(); + + create_path_to_protobuf_schema_env(); protobuf_generate( "src/encoding/protobuf/proto/",