From 98e7397e4c8ec77b087fa25f5a830aa2e44acba9 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Thu, 28 Mar 2019 15:03:30 +0800 Subject: [PATCH] Add ethereum-types (#5) * Add ethereum-types; Modify features config of primitive-types Signed-off-by: koushiro * Modify doc of primitive-types Signed-off-by: koushiro * Update ci config Signed-off-by: koushiro --- .circleci/config.yml | 8 +- Cargo.toml | 1 + ethereum-types/Cargo.toml | 33 ++++ ethereum-types/benches/bigint.rs | 151 ++++++++++++++++ ethereum-types/benches/bloom.rs | 127 +++++++++++++ ethereum-types/benches/unrolling.rs | 70 ++++++++ ethereum-types/src/bloom.rs | 264 ++++++++++++++++++++++++++++ ethereum-types/src/lib.rs | 13 ++ primitive-types/Cargo.toml | 3 - primitive-types/src/lib.rs | 7 +- primitive-types/src/tests.rs | 1 + 11 files changed, 668 insertions(+), 10 deletions(-) create mode 100644 ethereum-types/Cargo.toml create mode 100644 ethereum-types/benches/bigint.rs create mode 100644 ethereum-types/benches/bloom.rs create mode 100644 ethereum-types/benches/unrolling.rs create mode 100644 ethereum-types/src/bloom.rs create mode 100644 ethereum-types/src/lib.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c391e9..e8c657a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 jobs: build: docker: - - image: ubuntu:16.04 + - image: ubuntu:18.04 working_directory: ~/chainx-common-ci @@ -12,8 +12,8 @@ jobs: - run: name: Setup build environment command: | - apt-get update - apt-get install -y curl wget build-essential zlib1g-dev python libcurl4-openssl-dev libelf-dev libdw-dev cmake binutils-dev libiberty-dev + apt update + apt install -y curl wget build-essential zlib1g-dev python libcurl4-openssl-dev libelf-dev libdw-dev cmake binutils-dev libiberty-dev curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path --default-toolchain none -y; source $HOME/.cargo/env no_output_timeout: 1800s @@ -41,6 +41,8 @@ jobs: cd base58 && cargo test && cargo test --no-default-features && cd .. cd rlp && cargo test && cargo test --no-default-features && cd .. cd primitive-types && cargo test --features 'serde,codec,rlp' && cargo test --no-default-features --features 'codec,rlp' && cd .. + cd ethereum-types && cargo test --features 'serde,codec,rlp' && cargo test --no-default-features --features 'codec,rlp' && cd .. + - run: name: Coverage diff --git a/Cargo.toml b/Cargo.toml index 2e39bb9..2b5b0a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "primitive-types/impls/serde", "primitive-types/impls/codec", "primitive-types/impls/rlp", + "ethereum-types", ] diff --git a/ethereum-types/Cargo.toml b/ethereum-types/Cargo.toml new file mode 100644 index 0000000..e906dc7 --- /dev/null +++ b/ethereum-types/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "ethereum-types" +version = "0.5.0" +authors = ["Parity Technologies and koushiro "] +license = "MIT" +homepage = "https://github.com/paritytech/parity-common" +description = "Ethereum types" +edition = "2018" + +[dependencies] +ustd = { path = "../ustd", default-features = false } +primitive-types = { path = "../primitive-types", default-features = false } +impl-serde = { path = "../primitive-types/impls/serde", default-features = false, optional = true } +impl-codec = { path = "../primitive-types/impls/codec", default-features = false, optional = true } +impl-rlp = { path = "../primitive-types/impls/rlp", default-features = false, optional = true } +fixed-hash = { version = "0.3", default-features = false } +tiny-keccak = "1.4" # support `no_std` +crunchy = { version = "0.2.1", default-features = false, features = ["limit_256"] } + +[dev-dependencies] +rustc-hex = { version = "2.0", default-features = false } +rand = { version = "0.4" } + +[features] +default = ["std"] +std = [ + "ustd/std", + "fixed-hash/std", + "primitive-types/std", +] +serde = ["std", "impl-serde", "primitive-types/serde"] +codec = ["impl-codec", "primitive-types/codec"] +rlp = ["impl-rlp", "primitive-types/rlp"] diff --git a/ethereum-types/benches/bigint.rs b/ethereum-types/benches/bigint.rs new file mode 100644 index 0000000..adfd0fe --- /dev/null +++ b/ethereum-types/benches/bigint.rs @@ -0,0 +1,151 @@ +// Copyright 2015-2017 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! benchmarking for bigint +//! should be started with: +//! ```bash +//! rustup run nightly cargo bench +//! ``` + +#![feature(test)] +#![feature(asm)] + +extern crate test; + +use test::{black_box, Bencher}; + +use ethereum_types::{U128, U256, U512}; + +#[bench] +fn u128_mul(b: &mut Bencher) { + b.iter(|| { + let n = black_box(10000); + (1..n).fold(U128([12345u64, 0u64]), |old, new| { + old.overflowing_mul(U128::from(new | 1)).0 + }) + }); +} + +#[bench] +fn u256_add(b: &mut Bencher) { + b.iter(|| { + let n = black_box(10000); + let zero = black_box(U256::zero()); + (0..n).fold(zero, |old, new| { + old.overflowing_add(U256::from(black_box(new))).0 + }) + }); +} + +#[bench] +fn u256_sub(b: &mut Bencher) { + b.iter(|| { + let n = black_box(10000); + let max = black_box(U256::max_value()); + (0..n).fold(max, |old, new| { + old.overflowing_sub(U256::from(black_box(new))).0 + }) + }); +} + +#[bench] +fn u256_mul(b: &mut Bencher) { + b.iter(|| { + (1..10000).fold(black_box(U256::one()), |old, new| { + old.overflowing_mul(U256::from(black_box(new | 1))).0 + }) + }); +} + +#[bench] +fn u256_mul_small(b: &mut Bencher) { + b.iter(|| { + (1..77).fold(black_box(U256::one()), |old, _| { + old.overflowing_mul(U256::from(black_box(10))).0 + }) + }); +} + +#[bench] +fn u256_full_mul(b: &mut Bencher) { + b.iter(|| { + let n = black_box(10000); + let one = black_box(U256::one()); + (1..n).map(|n| n | 1).fold(one, |old, new| { + let new = black_box(new); + let U512(ref u512words) = old.full_mul(U256([new, new, new, new])); + U256([u512words[0], u512words[2], u512words[2], u512words[3]]) + }) + }); +} + +#[bench] +fn u256_from_le(b: &mut Bencher) { + b.iter(|| { + let raw = black_box([ + 1u8, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, + 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, + ]); + let _ = U256::from_little_endian(&raw[..]); + }); +} + +#[bench] +fn u256_from_be(b: &mut Bencher) { + b.iter(|| { + let raw = black_box([ + 1u8, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, + 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, + ]); + let _ = U256::from_big_endian(&raw[..]); + }); +} + +#[bench] +fn u512_add(b: &mut Bencher) { + b.iter(|| { + let n = black_box(10000); + let zero = black_box(U512::zero()); + (0..n).fold(zero, |old, new| { + let new = black_box(new); + old.overflowing_add(U512([new, new, new, new, new, new, new, new])) + .0 + }) + }); +} + +#[bench] +fn u512_sub(b: &mut Bencher) { + b.iter(|| { + let n = black_box(10000); + let max = black_box(U512::max_value()); + (0..n).fold(max, |old, new| { + let new = black_box(new); + let p = new % 2; + old.overflowing_sub(U512([p, p, p, p, p, p, p, new])).0 + }) + }); +} + +#[bench] +fn u512_mul(b: &mut Bencher) { + b.iter(|| { + (1..10000).fold(black_box(U512::one()), |old, new| { + old.overflowing_mul(U512::from(black_box(new | 1))).0 + }) + }); +} + +#[bench] +fn u512_mul_small(b: &mut Bencher) { + b.iter(|| { + (1..153).fold(black_box(U512::one()), |old, _| { + old.overflowing_mul(U512::from(black_box(10))).0 + }) + }); +} diff --git a/ethereum-types/benches/bloom.rs b/ethereum-types/benches/bloom.rs new file mode 100644 index 0000000..95eb8b9 --- /dev/null +++ b/ethereum-types/benches/bloom.rs @@ -0,0 +1,127 @@ +#![feature(test)] + +extern crate test; + +use test::Bencher; + +use ethereum_types::{Bloom, BloomInput}; +use tiny_keccak::keccak256; + +fn from_hex_str(s: &str) -> Vec { + rustc_hex::FromHex::from_hex(s).unwrap() +} + +fn test_bloom() -> Bloom { + let hex = from_hex_str( + "00000000000000000000000000000000\ + 00000000100000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000002020000000000000000000000\ + 00000000000000000000000800000000\ + 10000000000000000000000000000000\ + 00000000000000000000001000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000", + ); + Bloom::from_slice(&hex) +} + +fn test_topic() -> Vec { + from_hex_str("02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc") +} + +fn test_address() -> Vec { + from_hex_str("ef2d6d194084c2de36e0dabfce45d046b37d1106") +} + +fn test_dummy() -> Vec { + b"123456".to_vec() +} + +fn test_dummy2() -> Vec { + b"654321".to_vec() +} + +#[bench] +fn accrue_raw(b: &mut Bencher) { + let mut bloom = Bloom::default(); + let topic = test_topic(); + let address = test_address(); + b.iter(|| { + bloom.accrue(BloomInput::Raw(&topic)); + bloom.accrue(BloomInput::Raw(&address)); + }); +} + +#[bench] +fn accrue_hash(b: &mut Bencher) { + let mut bloom = Bloom::default(); + let topic = keccak256(&test_topic()); + let address = keccak256(&test_address()); + b.iter(|| { + bloom.accrue(BloomInput::Hash(&topic)); + bloom.accrue(BloomInput::Hash(&address)); + }); +} + +#[bench] +fn contains_input_raw(b: &mut Bencher) { + let bloom = test_bloom(); + let topic = test_topic(); + let address = test_address(); + b.iter(|| { + assert!(bloom.contains_input(BloomInput::Raw(&topic))); + assert!(bloom.contains_input(BloomInput::Raw(&address))); + }); +} + +#[bench] +fn does_not_contain_raw(b: &mut Bencher) { + let bloom = test_bloom(); + let dummy = test_dummy(); + let dummy2 = test_dummy2(); + b.iter(|| { + assert!(!bloom.contains_input(BloomInput::Raw(&dummy))); + assert!(!bloom.contains_input(BloomInput::Raw(&dummy2))); + }); +} + +#[bench] +fn contains_input_hash(b: &mut Bencher) { + let bloom = test_bloom(); + let topic = keccak256(&test_topic()); + let address = keccak256(&test_address()); + b.iter(|| { + assert!(bloom.contains_input(BloomInput::Hash(&topic))); + assert!(bloom.contains_input(BloomInput::Hash(&address))); + }); +} + +#[bench] +fn does_not_contain_hash(b: &mut Bencher) { + let bloom = test_bloom(); + let dummy = keccak256(&test_dummy()); + let dummy2 = keccak256(&test_dummy2()); + b.iter(|| { + assert!(!bloom.contains_input(BloomInput::Hash(&dummy))); + assert!(!bloom.contains_input(BloomInput::Hash(&dummy2))); + }); +} + +#[bench] +fn does_not_contain_random_hash(b: &mut Bencher) { + let bloom = test_bloom(); + let dummy: Vec<_> = (0..255u8).into_iter().map(|i| keccak256(&[i])).collect(); + b.iter(|| { + for d in &dummy { + assert!(!bloom.contains_input(BloomInput::Hash(d))); + } + }); +} diff --git a/ethereum-types/benches/unrolling.rs b/ethereum-types/benches/unrolling.rs new file mode 100644 index 0000000..061ae6b --- /dev/null +++ b/ethereum-types/benches/unrolling.rs @@ -0,0 +1,70 @@ +#![feature(test)] + +extern crate test; + +use test::{black_box, Bencher}; + +use crunchy::unroll; +use rand::Rng; + +fn random_data() -> [u8; 256] { + let mut res = [0u8; 256]; + rand::thread_rng().fill_bytes(&mut res); + res +} + +#[bench] +fn forwards_with_crunchy(b: &mut Bencher) { + let mut data = random_data(); + b.iter(|| { + let other_data = random_data(); + unroll! { + for i in 0..255 { + data[i] |= other_data[i]; + } + } + }); + + black_box(data); +} + +#[bench] +fn backwards_with_crunchy(b: &mut Bencher) { + let mut data = random_data(); + b.iter(|| { + let other_data = random_data(); + unroll! { + for i in 0..255 { + data[255-i] |= other_data[255-i]; + } + } + }); + + black_box(data); +} + +#[bench] +fn forwards_without_crunchy(b: &mut Bencher) { + let mut data = random_data(); + b.iter(|| { + let other_data = random_data(); + for i in 0..255 { + data[i] |= other_data[i]; + } + }); + + black_box(data); +} + +#[bench] +fn backwards_without_crunchy(b: &mut Bencher) { + let mut data = random_data(); + b.iter(|| { + let other_data = random_data(); + for i in 0..255 { + data[255 - i] |= other_data[255 - i]; + } + }); + + black_box(data); +} diff --git a/ethereum-types/src/bloom.rs b/ethereum-types/src/bloom.rs new file mode 100644 index 0000000..a16be9c --- /dev/null +++ b/ethereum-types/src/bloom.rs @@ -0,0 +1,264 @@ +use ustd::{mem, ops}; + +use crunchy::unroll; +use fixed_hash::construct_fixed_hash; +use tiny_keccak::keccak256; + +// 3 according to yellowpaper +const BLOOM_BITS: u32 = 3; +const BLOOM_SIZE: usize = 256; + +construct_fixed_hash! { + /// Bloom hash type with 256 bytes (2048 bits) size. + pub struct Bloom(BLOOM_SIZE); +} + +impl<'a> PartialEq> for Bloom { + fn eq(&self, other: &BloomRef<'a>) -> bool { + let s_ref: &[u8] = &self.0; + let o_ref: &[u8] = other.0; + s_ref.eq(o_ref) + } +} + +impl<'a> From> for Bloom { + fn from(input: BloomInput<'a>) -> Bloom { + let mut bloom = Bloom::default(); + bloom.accrue(input); + bloom + } +} + +impl From for Bloom { + fn from(hash: primitive_types::H2048) -> Self { + Bloom(hash.to_fixed_bytes()) + } +} + +impl Bloom { + pub fn is_empty(&self) -> bool { + self.0.iter().all(|x| *x == 0) + } + + pub fn contains_input(&self, input: BloomInput) -> bool { + let bloom: Bloom = input.into(); + self.contains_bloom(&bloom) + } + + pub fn contains_bloom<'a, B>(&self, bloom: B) -> bool + where + BloomRef<'a>: From, + { + let bloom_ref: BloomRef = bloom.into(); + // workaround for https://github.com/rust-lang/rust/issues/43644 + self.contains_bloom_ref(bloom_ref) + } + + fn contains_bloom_ref(&self, bloom: BloomRef) -> bool { + let self_ref: BloomRef = self.into(); + self_ref.contains_bloom(bloom) + } + + pub fn accrue(&mut self, input: BloomInput) { + let p = BLOOM_BITS; + + let m = self.0.len(); + let bloom_bits = m * 8; + let mask = bloom_bits - 1; + let bloom_bytes = (log2(bloom_bits) + 7) / 8; + + let hash: Hash = input.into(); + + // must be a power of 2 + assert_eq!(m & (m - 1), 0); + // out of range + assert!(p * bloom_bytes <= hash.len() as u32); + + let mut ptr = 0; + + assert_eq!(BLOOM_BITS, 3); + unroll! { + for i in 0..3 { + let _ = i; + let mut index = 0 as usize; + for _ in 0..bloom_bytes { + index = (index << 8) | hash[ptr] as usize; + ptr += 1; + } + index &= mask; + self.0[m - 1 - index / 8] |= 1 << (index % 8); + } + } + } + + pub fn accrue_bloom<'a, B>(&mut self, bloom: B) + where + BloomRef<'a>: From, + { + let bloom_ref: BloomRef = bloom.into(); + assert_eq!(self.0.len(), BLOOM_SIZE); + assert_eq!(bloom_ref.0.len(), BLOOM_SIZE); + for i in 0..BLOOM_SIZE { + self.0[i] |= bloom_ref.0[i]; + } + } + + pub fn data(&self) -> &[u8; BLOOM_SIZE] { + &self.0 + } +} + +#[derive(Clone)] +pub struct BloomRef<'a>(&'a [u8; BLOOM_SIZE]); + +impl<'a> From<&'a [u8; BLOOM_SIZE]> for BloomRef<'a> { + fn from(data: &'a [u8; BLOOM_SIZE]) -> Self { + BloomRef(data) + } +} + +impl<'a> From<&'a Bloom> for BloomRef<'a> { + fn from(bloom: &'a Bloom) -> Self { + BloomRef(&bloom.0) + } +} + +impl<'a> BloomRef<'a> { + pub fn is_empty(&self) -> bool { + self.0.iter().all(|x| *x == 0) + } + + pub fn contains_input(&self, input: BloomInput) -> bool { + let bloom: Bloom = input.into(); + self.contains_bloom(&bloom) + } + + pub fn contains_bloom<'b, B>(&self, bloom: B) -> bool + where + BloomRef<'b>: From, + { + let bloom_ref: BloomRef = bloom.into(); + assert_eq!(self.0.len(), BLOOM_SIZE); + assert_eq!(bloom_ref.0.len(), BLOOM_SIZE); + for i in 0..BLOOM_SIZE { + let a = self.0[i]; + let b = bloom_ref.0[i]; + if (a & b) != b { + return false; + } + } + true + } + + pub fn data(&self) -> &'a [u8; BLOOM_SIZE] { + self.0 + } +} + +#[cfg(feature = "serde")] +impl_serde::impl_fixed_hash_serde!(Bloom, BLOOM_SIZE); + +#[cfg(feature = "codec")] +impl_codec::impl_fixed_hash_codec_ext!(Bloom); + +#[cfg(feature = "rlp")] +impl_rlp::impl_fixed_hash_rlp!(Bloom, BLOOM_SIZE); + +/// Returns log2. +fn log2(x: usize) -> u32 { + if x <= 1 { + return 0; + } + + let n = x.leading_zeros(); + mem::size_of::() as u32 * 8 - n +} + +pub enum BloomInput<'a> { + Raw(&'a [u8]), + Hash(&'a [u8; 32]), +} + +enum Hash<'a> { + Ref(&'a [u8; 32]), + Owned([u8; 32]), +} + +impl<'a> From> for Hash<'a> { + fn from(input: BloomInput<'a>) -> Self { + match input { + BloomInput::Raw(raw) => Hash::Owned(keccak256(raw)), + BloomInput::Hash(hash) => Hash::Ref(hash), + } + } +} + +impl<'a> ops::Index for Hash<'a> { + type Output = u8; + + fn index(&self, index: usize) -> &u8 { + match *self { + Hash::Ref(r) => &r[index], + Hash::Owned(ref hash) => &hash[index], + } + } +} + +impl<'a> Hash<'a> { + fn len(&self) -> usize { + match *self { + Hash::Ref(r) => r.len(), + Hash::Owned(ref hash) => hash.len(), + } + } +} + +#[cfg(test)] +mod tests { + use ustd::vec::Vec; + + use super::{Bloom, BloomInput}; + + fn from_hex_str(s: &str) -> Vec { + rustc_hex::FromHex::from_hex(s).unwrap() + } + + #[test] + fn test_bloom() { + let hex = from_hex_str( + "00000000000000000000000000000000\ + 00000000100000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000002020000000000000000000000\ + 00000000000000000000000800000000\ + 10000000000000000000000000000000\ + 00000000000000000000001000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000\ + 00000000000000000000000000000000", + ); + let bloom = Bloom::from_slice(&hex); + let address = from_hex_str("ef2d6d194084c2de36e0dabfce45d046b37d1106"); + let topic = + from_hex_str("02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"); + + let mut my_bloom = Bloom::default(); + assert!(!my_bloom.contains_input(BloomInput::Raw(&address))); + assert!(!my_bloom.contains_input(BloomInput::Raw(&topic))); + + my_bloom.accrue(BloomInput::Raw(&address)); + assert!(my_bloom.contains_input(BloomInput::Raw(&address))); + assert!(!my_bloom.contains_input(BloomInput::Raw(&topic))); + + my_bloom.accrue(BloomInput::Raw(&topic)); + assert!(my_bloom.contains_input(BloomInput::Raw(&address))); + assert!(my_bloom.contains_input(BloomInput::Raw(&topic))); + assert_eq!(my_bloom, bloom); + } +} diff --git a/ethereum-types/src/lib.rs b/ethereum-types/src/lib.rs new file mode 100644 index 0000000..ce32343 --- /dev/null +++ b/ethereum-types/src/lib.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod bloom; + +pub use self::bloom::{Bloom, BloomInput, BloomRef}; +pub use primitive_types::{ + BigEndianHash, H128, H160, H256, H264, H32, H512, H520, H64, U128, U256, U512, U64, +}; + +pub type Address = H160; +pub type Secret = H256; +pub type Public = H512; +pub type Signature = H520; diff --git a/primitive-types/Cargo.toml b/primitive-types/Cargo.toml index 0f43b95..fd0e821 100644 --- a/primitive-types/Cargo.toml +++ b/primitive-types/Cargo.toml @@ -26,9 +26,6 @@ std = [ "ustd/std", "uint/std", "fixed-hash/std", - "impl-serde", - "impl-codec/std", - "impl-rlp/std", ] serde = ["std", "impl-serde"] codec = ["impl-codec"] diff --git a/primitive-types/src/lib.rs b/primitive-types/src/lib.rs index b89a268..d057043 100644 --- a/primitive-types/src/lib.rs +++ b/primitive-types/src/lib.rs @@ -6,10 +6,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Primitive types shared by Substrate and Parity Ethereum. -//! -//! Those are uint types `U128`, `U256` and `U512`, and fixed hash types `H160`, -//! `H256` and `H512`, with optional serde serialization, parity-codec and +//! Those are uint types `U64`, `U128`, `U256` and `U512`, and fixed hash types +//! `H32`, `H48`, `H64`, `H128`, `H160`, `H256`, `H264`, `H512`, `H520`, `H1024`, `H2048`. +//! All uint and fixed hash types, with optional serde serialization, parity-codec and //! rlp encoding. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/primitive-types/src/tests.rs b/primitive-types/src/tests.rs index fa9e102..384416b 100644 --- a/primitive-types/src/tests.rs +++ b/primitive-types/src/tests.rs @@ -1,3 +1,4 @@ +#[cfg(any(feature = "serde", feature = "codec"))] macro_rules! from_low_u64_be { ($hash: ident, $val: expr) => {{ use byteorder::ByteOrder;