Skip to content

Commit

Permalink
Allow to derive BinaryValue from bincode serialization [ECR-4086] (
Browse files Browse the repository at this point in the history
  • Loading branch information
slowli authored and Oleksandr Anyshchenko committed Jan 10, 2020
1 parent 0e793fa commit 8a8f4ad
Show file tree
Hide file tree
Showing 84 changed files with 471 additions and 1,037 deletions.
33 changes: 17 additions & 16 deletions components/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,32 +188,33 @@ fn generate_mod_rs<P: AsRef<Path>, Q: AsRef<Path>>(
.expect("Unable to write data to file");
}

/// Generates .rs files from .proto files.
/// Generates Rust modules from Protobuf files.
///
/// `protoc` executable from protobuf should be in `$PATH`
/// The `protoc` executable (i.e., the Protobuf compiler) should be in `$PATH`.
///
/// # Examples
///
/// In `build.rs`
/// Specify in the build script (`build.rs`) of your crate:
///
/// ```no_run
///use exonum_build::ProtobufGenerator;
/// use exonum_build::ProtobufGenerator;
///
///ProtobufGenerator::with_mod_name("exonum_tests_proto_mod.rs")
/// .with_input_dir("src/proto")
/// .with_crypto()
/// .with_common()
/// .with_merkledb()
/// .generate();
/// ProtobufGenerator::with_mod_name("example_mod.rs")
/// .with_input_dir("src/proto")
/// .with_crypto()
/// .with_common()
/// .with_merkledb()
/// .generate();
/// ```
/// After successful run `$OUT_DIR` will contain \*.rs for each \*.proto file in
/// "src/proto/\*\*/" and example_mod.rs which will include all generated .rs files
///
/// After the successful run, `$OUT_DIR` will contain a module for each Protobuf file in
/// `src/proto` and `example_mod.rs` which will include all generated modules
/// as submodules.
///
/// To use generated protobuf structs.
/// To use the generated Rust types corresponding to Protobuf messages, specify
/// in `src/proto/mod.rs`:
///
/// In `src/proto/mod.rs`
/// ```ignore
///
/// include!(concat!(env!("OUT_DIR"), "/example_mod.rs"));
///
/// // If you use types from `exonum` .proto files.
Expand Down Expand Up @@ -275,7 +276,7 @@ impl<'a> ProtobufGenerator<'a> {
self
}

/// Proto files from `exonum-crypto` crate (`Hash`, `PublicKey`, etc..).
/// Proto files from `exonum-crypto` crate (`Hash`, `PublicKey`, etc.).
pub fn with_crypto(mut self) -> Self {
self.includes.push(ProtoSources::Crypto);
self
Expand Down
1 change: 0 additions & 1 deletion components/derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ proc-macro = true

[dependencies]
darling = "0.10.0"
heck = "0.3.1"
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0"
Expand Down
103 changes: 89 additions & 14 deletions components/derive/src/db_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,57 @@ use std::collections::HashSet;

use crate::find_meta_attrs;

#[derive(Debug, FromDeriveInput)]
#[derive(Debug)]
struct BinaryValueStruct {
ident: Ident,
attrs: BinaryValueAttrs,
}

impl FromDeriveInput for BinaryValueStruct {
fn from_derive_input(input: &DeriveInput) -> darling::Result<Self> {
let attrs = find_meta_attrs("binary_value", &input.attrs)
.map(|meta| BinaryValueAttrs::from_nested_meta(&meta))
.unwrap_or_else(|| Ok(BinaryValueAttrs::default()))?;

Ok(Self {
ident: input.ident.clone(),
attrs,
})
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum Codec {
Protobuf,
Bincode,
}

impl Default for Codec {
fn default() -> Self {
Codec::Protobuf
}
}

impl FromMeta for Codec {
fn from_string(value: &str) -> darling::Result<Self> {
match value {
"protobuf" => Ok(Codec::Protobuf),
"bincode" => Ok(Codec::Bincode),
_ => {
let msg = format!(
"Unknown codec ({}). Use one of `protobuf` or `bincode`",
value
);
Err(darling::Error::custom(msg))
}
}
}
}

#[derive(Debug, Default, FromMeta)]
struct BinaryValueAttrs {
#[darling(default)]
codec: Codec,
}

#[derive(Debug, FromDeriveInput)]
Expand All @@ -38,37 +86,68 @@ impl ObjectHashStruct {

quote! {
impl exonum_merkledb::ObjectHash for #name {
fn object_hash(&self) -> exonum_crypto::Hash {
use exonum_merkledb::BinaryValue;
let v = self.to_bytes();
exonum_crypto::hash(&v)
fn object_hash(&self) -> exonum_merkledb::_reexports::Hash {
let bytes = exonum_merkledb::BinaryValue::to_bytes(self);
exonum_merkledb::_reexports::hash(&bytes)
}
}
}
}
}

impl BinaryValueStruct {
pub fn implement_binary_value(&self) -> impl ToTokens {
fn implement_binary_value_from_pb(&self) -> proc_macro2::TokenStream {
let name = &self.ident;

quote! {
// This trait assumes that we work with trusted data so we can unwrap here.
impl exonum_merkledb::BinaryValue for #name {
fn to_bytes(&self) -> Vec<u8> {
self.to_pb().write_to_bytes().expect(
concat!("Failed to serialize in BinaryValue for ", stringify!(#name))
use protobuf::Message as _;
// This trait assumes that we work with trusted data so we can unwrap here.
exonum_proto::ProtobufConvert::to_pb(self).write_to_bytes().expect(
concat!("Failed to serialize `BinaryValue` for ", stringify!(#name))
)
}

fn from_bytes(value: std::borrow::Cow<[u8]>) -> Result<Self, failure::Error> {
fn from_bytes(
value: std::borrow::Cow<[u8]>,
) -> std::result::Result<Self, exonum_merkledb::_reexports::Error> {
use protobuf::Message as _;

let mut block = <Self as exonum_proto::ProtobufConvert>::ProtoStruct::new();
block.merge_from_bytes(value.as_ref())?;
exonum_proto::ProtobufConvert::from_pb(block)
}
}
}
}

fn implement_binary_value_from_bincode(&self) -> proc_macro2::TokenStream {
let name = &self.ident;

quote! {
impl exonum_merkledb::BinaryValue for #name {
fn to_bytes(&self) -> std::vec::Vec<u8> {
bincode::serialize(self).expect(
concat!("Failed to serialize `BinaryValue` for ", stringify!(#name))
)
}

fn from_bytes(
value: std::borrow::Cow<[u8]>,
) -> std::result::Result<Self, exonum_merkledb::_reexports::Error> {
bincode::deserialize(value.as_ref()).map_err(From::from)
}
}
}
}

fn implement_binary_value(&self) -> impl ToTokens {
match self.attrs.codec {
Codec::Protobuf => self.implement_binary_value_from_pb(),
Codec::Bincode => self.implement_binary_value_from_bincode(),
}
}
}

impl ToTokens for BinaryValueStruct {
Expand All @@ -82,10 +161,6 @@ impl ToTokens for BinaryValueStruct {
let expanded = quote! {
mod #mod_name {
use super::*;

use protobuf::Message as _ProtobufMessage;
use exonum_proto::ProtobufConvert;

#binary_value
}
};
Expand Down
43 changes: 40 additions & 3 deletions components/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,28 @@ use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{Attribute, NestedMeta};

/// Derives `BinaryValue` trait. The target type must implement `ProtobufConvert` trait.
/// Derives `BinaryValue` trait. The target type must implement (de)serialization logic,
/// which should be provided externally.
///
/// # Example
/// The trait currently supports two codecs:
///
/// - Protobuf serialization (used by default) via `exonum-proto` crate and its `ProtobufConvert`
/// trait.
/// - `bincode` serialization via the eponymous crate. Switched on by the
/// `#[binary_value(codec = "bincode")` attribute. Beware that `bincode` format is not as
/// forward / backward compatible as Protobuf; hence, this codec is better suited for tests
/// than for production code.
///
/// # Container Attributes
///
/// ## `codec`
///
/// Selects the serialization codec to use. Allowed values are `protobuf` (used by default)
/// and `bincode`.
///
/// # Examples
///
/// With Protobuf serialization:
///
/// ```ignore
/// #[derive(Clone, Debug, BinaryValue)]
Expand All @@ -49,7 +68,25 @@ use syn::{Attribute, NestedMeta};
/// let wallet = Wallet::new();
/// let bytes = wallet.to_bytes();
/// ```
#[proc_macro_derive(BinaryValue)]
///
/// With `bincode` serialization:
///
/// ```ignore
/// #[derive(Clone, Debug, Serialize, Deserialize, BinaryValue)]
/// #[binary_value(codec = "bincode")]
/// pub struct Wallet {
/// pub username: PublicKey,
/// /// Current balance of the wallet.
/// pub balance: u64,
/// }
///
/// let wallet = Wallet {
/// username: "Alice".to_owned(),
/// balance: 100,
/// };
/// let bytes = wallet.to_bytes();
/// ```
#[proc_macro_derive(BinaryValue, attributes(binary_value))]
pub fn binary_value(input: TokenStream) -> TokenStream {
db_traits::impl_binary_value(input)
}
Expand Down
3 changes: 1 addition & 2 deletions components/explorer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ serde = "1.0"
serde_derive = "1.0"

[dev-dependencies]
bincode = "1.2.1"
exonum-crypto = { version = "0.13.0-rc.2", path = "../crypto" }
exonum-derive = { version = "0.13.0-rc.2", path = "../derive" }
exonum-merkledb = { version = "0.13.0-rc.2", path = "../merkledb" }
exonum-rust-runtime = { version = "0.13.0-rc.2", path = "../../runtimes/rust" }

bincode = "1.2.1"
failure = "0.1.5"
futures = "0.1.29"
serde_json = "1.0"
30 changes: 6 additions & 24 deletions components/explorer/tests/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use exonum::{
blockchain::{config::GenesisConfigBuilder, Blockchain, BlockchainBuilder, BlockchainMut},
crypto::{self, PublicKey, SecretKey},
helpers::generate_testnet_config,
merkledb::{BinaryValue, ObjectHash, TemporaryDB},
merkledb::{ObjectHash, TemporaryDB},
messages::Verified,
node::ApiSender,
};
Expand All @@ -28,13 +28,14 @@ use exonum_rust_runtime::{
};
use serde_derive::*;

use std::{borrow::Cow, collections::BTreeMap};
use std::collections::BTreeMap;

pub const SERVICE_ID: InstanceId = 118;

#[derive(Clone, Debug)]
#[derive(Serialize, Deserialize)]
#[derive(ObjectHash)]
#[derive(BinaryValue, ObjectHash)]
#[binary_value(codec = "bincode")]
pub struct CreateWallet {
pub name: String,
}
Expand All @@ -45,19 +46,10 @@ impl CreateWallet {
}
}

impl BinaryValue for CreateWallet {
fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap()
}

fn from_bytes(bytes: Cow<'_, [u8]>) -> Result<Self, failure::Error> {
bincode::deserialize(bytes.as_ref()).map_err(Into::into)
}
}

#[derive(Clone, Debug)]
#[derive(Serialize, Deserialize)]
#[derive(ObjectHash)]
#[derive(BinaryValue, ObjectHash)]
#[binary_value(codec = "bincode")]
pub struct Transfer {
pub to: PublicKey,
pub amount: u64,
Expand All @@ -75,16 +67,6 @@ pub enum Error {
NotAllowed = 0,
}

impl BinaryValue for Transfer {
fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap()
}

fn from_bytes(bytes: Cow<'_, [u8]>) -> Result<Self, failure::Error> {
bincode::deserialize(bytes.as_ref()).map_err(Into::into)
}
}

#[exonum_interface]
pub trait ExplorerTransactions<Ctx> {
type Output;
Expand Down
22 changes: 5 additions & 17 deletions components/merkledb/benches/schema_patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@
// limitations under the License.

use criterion::{black_box, Bencher, Benchmark, Criterion, Throughput};
use exonum_derive::FromAccess;
use exonum_derive::{BinaryValue, FromAccess, ObjectHash};
use rand::{rngs::StdRng, Rng, SeedableRng};
use serde_derive::{Deserialize, Serialize};

use std::borrow::Cow;

use exonum_crypto::Hash;
use exonum_merkledb::{
access::{Access, AccessExt, FromAccess, Prefixed, RawAccessMut},
impl_object_hash_for_binary_value, BinaryValue, Database, Group, KeySetIndex, Lazy, MapIndex,
ObjectHash, ProofListIndex, ProofMapIndex, TemporaryDB,
Database, Group, KeySetIndex, Lazy, MapIndex, ObjectHash, ProofListIndex, ProofMapIndex,
TemporaryDB,
};

const SEED: [u8; 32] = [100; 32];
Expand All @@ -39,23 +37,13 @@ const COLD_DIVISOR: u64 = 13;
const COLD_CHANCE: u64 = 29;

#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[derive(BinaryValue, ObjectHash)]
#[binary_value(codec = "bincode")]
struct Transaction {
value: u64,
_payload: [u8; 32],
}

impl BinaryValue for Transaction {
fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap()
}

fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, failure::Error> {
bincode::deserialize(bytes.as_ref()).map_err(From::from)
}
}

impl_object_hash_for_binary_value! { Transaction }

trait ExecuteTransaction {
fn execute<T: Access>(fork: T, transaction: &Transaction)
where
Expand Down
Loading

0 comments on commit 8a8f4ad

Please sign in to comment.