From 20c5c89b15a185d43cafea49cb8f4e3003704721 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sat, 15 Feb 2020 17:39:36 +0100 Subject: [PATCH 01/10] Use inline storage for small hashes --- Cargo.toml | 1 - src/lib.rs | 94 +++++++++++++++++++++++++++++++++++++++----------- src/storage.rs | 43 +++++++++++++++++++++++ 3 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 src/storage.rs diff --git a/Cargo.toml b/Cargo.toml index 14443099..202c42ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ edition = "2018" [dependencies] blake2b_simd = { version = "0.5.9", default-features = false } blake2s_simd = { version = "0.5.9", default-features = false } -bytes = "0.5" sha1 = "0.5" sha2 = { version = "0.7", default-features = false } tiny-keccak = "1.4" diff --git a/src/lib.rs b/src/lib.rs index 829878db..60a64569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,18 +8,22 @@ mod errors; mod hashes; +mod storage; use std::convert::TryFrom; +use std::fmt::Debug; +use std::hash; use blake2b_simd::{blake2b, Params as Blake2bVariable}; use blake2s_simd::{blake2s, Params as Blake2sVariable}; -use bytes::{BufMut, Bytes, BytesMut}; use sha2::Digest; use tiny_keccak::Keccak; use unsigned_varint::{decode, encode}; pub use errors::{DecodeError, DecodeOwnedError, EncodeError}; pub use hashes::Hash; +use std::fmt; +use storage::Storage; // Helper macro for encoding input into output using sha1, sha2, tiny_keccak, or blake2 macro_rules! encode { @@ -107,12 +111,12 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result { let total_len = code.len() + size.len() + input.len(); - let mut output = BytesMut::with_capacity(total_len); - output.put_slice(code); - output.put_slice(size); - output.put_slice(input); + let mut output = Vec::with_capacity(total_len); + output.extend_from_slice(code); + output.extend_from_slice(size); + output.extend_from_slice(input); Ok(Multihash { - bytes: output.freeze(), + storage: Storage::copy_from_slice(&output), }) } else { let (offset, mut output) = encode_hash(hash); @@ -135,31 +139,51 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result { }); Ok(Multihash { - bytes: output.freeze(), + storage: Storage::copy_from_slice(&output), }) } } -// Encode the given [`Hash`] value and ensure the returned [`BytesMut`] +// Encode the given [`Hash`] value and ensure the returned [`Vec`] // has enough capacity to hold the actual digest. -fn encode_hash(hash: Hash) -> (usize, BytesMut) { +fn encode_hash(hash: Hash) -> (usize, Vec) { let mut buf = encode::u16_buffer(); let code = encode::u16(hash.code(), &mut buf); let len = code.len() + 1 + usize::from(hash.size()); - let mut output = BytesMut::with_capacity(len); - output.put_slice(code); - output.put_u8(hash.size()); + let mut output = Vec::with_capacity(len); + output.extend_from_slice(code); + output.push(hash.size()); output.resize(len, 0); (code.len() + 1, output) } /// Represents a valid multihash. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Clone)] pub struct Multihash { - bytes: Bytes, + storage: Storage, +} + +impl Debug for Multihash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Multihash") + } +} + +impl PartialEq for Multihash { + fn eq(&self, other: &Self) -> bool { + self.storage.bytes() == other.storage.bytes() + } +} + +impl Eq for Multihash {} + +impl hash::Hash for Multihash { + fn hash(&self, state: &mut H) { + self.storage.bytes().hash(state); + } } impl Multihash { @@ -172,7 +196,7 @@ impl Multihash { }); } Ok(Multihash { - bytes: Bytes::from(bytes), + storage: Storage::copy_from_slice(&bytes), }) } @@ -183,17 +207,21 @@ impl Multihash { /// Returns the bytes representation of the multihash. pub fn to_vec(&self) -> Vec { - Vec::from(&self.bytes[..]) + Vec::from(self.as_bytes()) } /// Returns the bytes representation of this multihash. pub fn as_bytes(&self) -> &[u8] { - &self.bytes + let bytes = self.storage.bytes(); + let size = multihash_size(bytes).expect("storage contains a valid multihash"); + &bytes[..size] } /// Builds a `MultihashRef` corresponding to this `Multihash`. pub fn as_ref(&self) -> MultihashRef { - MultihashRef { bytes: &self.bytes } + MultihashRef { + bytes: self.as_bytes(), + } } /// Returns which hashing algorithm is used in this multihash. @@ -215,7 +243,7 @@ impl AsRef<[u8]> for Multihash { impl<'a> PartialEq> for Multihash { fn eq(&self, other: &MultihashRef<'a>) -> bool { - &*self.bytes == other.bytes + &*self.as_bytes() == other.as_bytes() } } @@ -233,6 +261,30 @@ pub struct MultihashRef<'a> { bytes: &'a [u8], } +/// Given a buffer starting with a valid multihash, returns the size of the multihash +fn multihash_size(input: &[u8]) -> Result { + if input.is_empty() { + return Err(DecodeError::BadInputLength); + } + let mut res = 0usize; + + // Ensure `Hash::code` returns a `u16` so that our `decode::u16` here is correct. + std::convert::identity:: u16>(Hash::code); + let (code, bytes) = decode::u16(&input).map_err(|_| DecodeError::BadInputLength)?; + + // Very convoluted way to get the size of the code + let mut tmp = [0u8; 3]; + res += unsigned_varint::encode::u16(code, &mut tmp).len(); + + let (hash_len, _) = decode::u32(&bytes).map_err(|_| DecodeError::BadInputLength)?; + + // Very convoluted way to get the size of the hash_len + let mut tmp = [0u8; 5]; + res += unsigned_varint::encode::u32(hash_len, &mut tmp).len(); + res += hash_len as usize; + Ok(res) +} + impl<'a> MultihashRef<'a> { /// Creates a `MultihashRef` from the given `input`. pub fn from_slice(input: &'a [u8]) -> Result { @@ -290,7 +342,7 @@ impl<'a> MultihashRef<'a> { /// This operation allocates. pub fn to_owned(&self) -> Multihash { Multihash { - bytes: Bytes::copy_from_slice(self.bytes), + storage: Storage::copy_from_slice(self.bytes), } } @@ -302,7 +354,7 @@ impl<'a> MultihashRef<'a> { impl<'a> PartialEq for MultihashRef<'a> { fn eq(&self, other: &Multihash) -> bool { - self.bytes == &*other.bytes + self.as_bytes() == &*other.as_bytes() } } diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 00000000..8c54906a --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +const MAX_INLINE: usize = 39; + +#[derive(Clone)] +pub enum Storage { + /// hash is stored inline. if it is smaller than 39 bytes it should be padded with 0u8 + Inline([u8; MAX_INLINE]), + /// hash is stored on the heap. this must be only used if the hash is actually larger than + /// 39 bytes to ensure an unique representation. + Heap(Arc<[u8]>), +} + +impl Storage { + /// The raw bytes. Note that this can be longer than the data this storage has been created from. + pub fn bytes(&self) -> &[u8] { + match self { + Storage::Inline(bytes) => bytes, + Storage::Heap(data) => &data, + } + } + + /// creates storage from a vec. Note that this will not preserve the size. + pub fn copy_from_slice(slice: &[u8]) -> Self { + if slice.len() <= MAX_INLINE { + let mut data: [u8; MAX_INLINE] = [0; MAX_INLINE]; + &data[..slice.len()].copy_from_slice(slice); + Storage::Inline(data) + } else { + Storage::Heap(slice.into()) + } + } +} + +#[cfg(test)] +mod tests { + use super::Storage; + + #[test] + fn test_size() { + assert_eq!(std::mem::size_of::(), 40); + } +} From 4e0632b6f203810b73cfd8f36d01fc83f18d5aed Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sat, 15 Feb 2020 18:17:49 +0100 Subject: [PATCH 02/10] Clippy --- src/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage.rs b/src/storage.rs index 8c54906a..975dc684 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -24,7 +24,7 @@ impl Storage { pub fn copy_from_slice(slice: &[u8]) -> Self { if slice.len() <= MAX_INLINE { let mut data: [u8; MAX_INLINE] = [0; MAX_INLINE]; - &data[..slice.len()].copy_from_slice(slice); + data[..slice.len()].copy_from_slice(slice); Storage::Inline(data) } else { Storage::Heap(slice.into()) From 0559bedb7bfc82807d7d30c616716959f29c3a63 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Sat, 15 Feb 2020 18:34:42 +0100 Subject: [PATCH 03/10] Rename copy_from_slice to just from_slice --- src/lib.rs | 8 ++++---- src/storage.rs | 2 +- tests/lib.rs | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 60a64569..ac63e6e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result { output.extend_from_slice(size); output.extend_from_slice(input); Ok(Multihash { - storage: Storage::copy_from_slice(&output), + storage: Storage::from_slice(&output), }) } else { let (offset, mut output) = encode_hash(hash); @@ -139,7 +139,7 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result { }); Ok(Multihash { - storage: Storage::copy_from_slice(&output), + storage: Storage::from_slice(&output), }) } } @@ -196,7 +196,7 @@ impl Multihash { }); } Ok(Multihash { - storage: Storage::copy_from_slice(&bytes), + storage: Storage::from_slice(&bytes), }) } @@ -342,7 +342,7 @@ impl<'a> MultihashRef<'a> { /// This operation allocates. pub fn to_owned(&self) -> Multihash { Multihash { - storage: Storage::copy_from_slice(self.bytes), + storage: Storage::from_slice(self.bytes), } } diff --git a/src/storage.rs b/src/storage.rs index 975dc684..8c14b419 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -21,7 +21,7 @@ impl Storage { } /// creates storage from a vec. Note that this will not preserve the size. - pub fn copy_from_slice(slice: &[u8]) -> Self { + pub fn from_slice(slice: &[u8]) -> Self { if slice.len() <= MAX_INLINE { let mut data: [u8; MAX_INLINE] = [0; MAX_INLINE]; data[..slice.len()].copy_from_slice(slice); diff --git a/tests/lib.rs b/tests/lib.rs index 400b4099..9c009929 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -276,3 +276,8 @@ fn multihash_ref_errors() { "Should error on wrong hash length" ); } + +#[test] +fn multihash_size() { + assert_eq!(std::mem::size_of::(), 40); +} From 02cf1140f752201c3c6b6255cf5800e86a340f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Mon, 17 Feb 2020 15:08:47 +0100 Subject: [PATCH 04/10] Explicity store the bytes size We can afford it since the average hash is 34 bytes, and we want this thing to be a multiple of 8 large. --- src/lib.rs | 28 +--------------------------- src/storage.rs | 36 ++++++++++++++++++++++++------------ tests/lib.rs | 5 ----- 3 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ac63e6e3..e2757e66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,9 +212,7 @@ impl Multihash { /// Returns the bytes representation of this multihash. pub fn as_bytes(&self) -> &[u8] { - let bytes = self.storage.bytes(); - let size = multihash_size(bytes).expect("storage contains a valid multihash"); - &bytes[..size] + self.storage.bytes() } /// Builds a `MultihashRef` corresponding to this `Multihash`. @@ -261,30 +259,6 @@ pub struct MultihashRef<'a> { bytes: &'a [u8], } -/// Given a buffer starting with a valid multihash, returns the size of the multihash -fn multihash_size(input: &[u8]) -> Result { - if input.is_empty() { - return Err(DecodeError::BadInputLength); - } - let mut res = 0usize; - - // Ensure `Hash::code` returns a `u16` so that our `decode::u16` here is correct. - std::convert::identity:: u16>(Hash::code); - let (code, bytes) = decode::u16(&input).map_err(|_| DecodeError::BadInputLength)?; - - // Very convoluted way to get the size of the code - let mut tmp = [0u8; 3]; - res += unsigned_varint::encode::u16(code, &mut tmp).len(); - - let (hash_len, _) = decode::u32(&bytes).map_err(|_| DecodeError::BadInputLength)?; - - // Very convoluted way to get the size of the hash_len - let mut tmp = [0u8; 5]; - res += unsigned_varint::encode::u32(hash_len, &mut tmp).len(); - res += hash_len as usize; - Ok(res) -} - impl<'a> MultihashRef<'a> { /// Creates a `MultihashRef` from the given `input`. pub fn from_slice(input: &'a [u8]) -> Result { diff --git a/src/storage.rs b/src/storage.rs index 8c14b419..02068339 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,31 +1,32 @@ use std::sync::Arc; -const MAX_INLINE: usize = 39; +const MAX_INLINE: usize = 38; #[derive(Clone)] pub enum Storage { - /// hash is stored inline. if it is smaller than 39 bytes it should be padded with 0u8 - Inline([u8; MAX_INLINE]), + /// hash is stored inline if it is smaller than MAX_INLINE + Inline(u8, [u8; MAX_INLINE]), /// hash is stored on the heap. this must be only used if the hash is actually larger than - /// 39 bytes to ensure an unique representation. + /// MAX_INLINE bytes to ensure an unique representation. Heap(Arc<[u8]>), } impl Storage { - /// The raw bytes. Note that this can be longer than the data this storage has been created from. + /// The raw bytes. pub fn bytes(&self) -> &[u8] { match self { - Storage::Inline(bytes) => bytes, + Storage::Inline(len, bytes) => &bytes[..(*len as usize)], Storage::Heap(data) => &data, } } - /// creates storage from a vec. Note that this will not preserve the size. + /// creates storage from a vec. For a size up to MAX_INLINE, this will not allocate. pub fn from_slice(slice: &[u8]) -> Self { - if slice.len() <= MAX_INLINE { + let len = slice.len(); + if len <= MAX_INLINE { let mut data: [u8; MAX_INLINE] = [0; MAX_INLINE]; - data[..slice.len()].copy_from_slice(slice); - Storage::Inline(data) + data[..len].copy_from_slice(slice); + Storage::Inline(len as u8, data) } else { Storage::Heap(slice.into()) } @@ -34,10 +35,21 @@ impl Storage { #[cfg(test)] mod tests { - use super::Storage; + use super::{Storage, MAX_INLINE}; #[test] - fn test_size() { + fn struct_size() { + // this should be true for both 32 and 64 bit archs assert_eq!(std::mem::size_of::(), 40); } + + #[test] + fn roundtrip() { + // check that .bytes() returns whatever the storage was created with + for i in 0..((MAX_INLINE + 10) as u8) { + let data = (0..i).collect::>(); + let storage = Storage::from_slice(&data); + assert_eq!(data, storage.bytes()); + } + } } diff --git a/tests/lib.rs b/tests/lib.rs index 9c009929..400b4099 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -276,8 +276,3 @@ fn multihash_ref_errors() { "Should error on wrong hash length" ); } - -#[test] -fn multihash_size() { - assert_eq!(std::mem::size_of::(), 40); -} From 9ab33d9d5c8b46a35f65f170091ddcb115dfff6b Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 17 Feb 2020 20:20:51 +0100 Subject: [PATCH 05/10] Add comment about the rationale for the 38 byte limit. The next useful limit would be 62, which would make the whole thing 64 bytes large and fit any 384 bit hash, but I don't think those are very common yet. --- src/storage.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/storage.rs b/src/storage.rs index 02068339..92a4639c 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,5 +1,11 @@ use std::sync::Arc; +/// MAX_INLINE is the maximum size of a multihash that can be stored inline +/// +/// We want the currently most common multihashes using 256bit hashes to be stored inline. These +/// hashes are 34 bytes long. An overall size of 38 seems like a good compromise. It allows storing +/// any 256bit hash with some room to spare and gives an overall size for Storage of 40 bytes, which +/// is a multiple of 8. We need 2 extra bytes, one for the size and one for the enum discriminator. const MAX_INLINE: usize = 38; #[derive(Clone)] From 5a91f486685b6e8f30ed9666961498bb14627ef4 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Feb 2020 18:56:37 +0100 Subject: [PATCH 06/10] PR feedback - use pub(crate) for Storage - use Storage::from_slices to prevent allocations for small identity multihashes --- src/lib.rs | 9 +-------- src/storage.rs | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e2757e66..724b30af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,15 +108,8 @@ pub fn encode(hash: Hash, input: &[u8]) -> Result { let code = encode::u16(hash.code(), &mut buf); let mut len_buf = encode::u32_buffer(); let size = encode::u32(input.len() as u32, &mut len_buf); - - let total_len = code.len() + size.len() + input.len(); - - let mut output = Vec::with_capacity(total_len); - output.extend_from_slice(code); - output.extend_from_slice(size); - output.extend_from_slice(input); Ok(Multihash { - storage: Storage::from_slice(&output), + storage: Storage::from_slices(&[&code, &size, &input]), }) } else { let (offset, mut output) = encode_hash(hash); diff --git a/src/storage.rs b/src/storage.rs index 92a4639c..9ea5a8d7 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -9,7 +9,7 @@ use std::sync::Arc; const MAX_INLINE: usize = 38; #[derive(Clone)] -pub enum Storage { +pub(crate) enum Storage { /// hash is stored inline if it is smaller than MAX_INLINE Inline(u8, [u8; MAX_INLINE]), /// hash is stored on the heap. this must be only used if the hash is actually larger than @@ -37,6 +37,26 @@ impl Storage { Storage::Heap(slice.into()) } } + + /// creates storage from multiple slices. For a size up to MAX_INLINE, this will not allocate. + pub fn from_slices(slices: &[&[u8]]) -> Self { + let n = slices.iter().fold(0usize, |a, s| a.saturating_add(s.len())); + if n <= MAX_INLINE { + let s = slices + .iter() + .fold(([0; MAX_INLINE], 0), |(mut array, i), s| { + array[i..i + s.len()].copy_from_slice(s); + (array, i + s.len()) + }); + Storage::Inline(n as u8, s.0) + } else { + let mut v = Vec::with_capacity(n); + for s in slices { + v.extend_from_slice(s) + } + Storage::Heap(v.into()) + } + } } #[cfg(test)] From ec44135f1f6399dcdc21053468490eb98c6f0c58 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Feb 2020 19:15:08 +0100 Subject: [PATCH 07/10] Add quickcheck tests for from_slices ...and also a property based test for the normal roundtrip, now that we have the dependency anyway. --- Cargo.toml | 3 +++ src/lib.rs | 4 ++++ src/storage.rs | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 202c42ee..4bae39e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,6 @@ sha1 = "0.5" sha2 = { version = "0.7", default-features = false } tiny-keccak = "1.4" unsigned-varint = "0.3" + +[dev-dependencies] +quickcheck = "0.9.2" diff --git a/src/lib.rs b/src/lib.rs index 724b30af..243cf98f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,10 @@ pub use hashes::Hash; use std::fmt; use storage::Storage; +#[cfg(test)] +#[macro_use] +extern crate quickcheck; + // Helper macro for encoding input into output using sha1, sha2, tiny_keccak, or blake2 macro_rules! encode { (sha1, Sha1, $input:expr, $output:expr) => {{ diff --git a/src/storage.rs b/src/storage.rs index 9ea5a8d7..bfcbe7e2 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -62,6 +62,7 @@ impl Storage { #[cfg(test)] mod tests { use super::{Storage, MAX_INLINE}; + use quickcheck; #[test] fn struct_size() { @@ -78,4 +79,22 @@ mod tests { assert_eq!(data, storage.bytes()); } } + + quickcheck! { + fn roundtrip_check(data: Vec) -> bool { + let storage = Storage::from_slice(&data); + storage.bytes() == data.as_slice() + } + + fn from_slices_roundtrip_check(data: Vec>) -> bool { + let mut slices = Vec::new(); + let mut expected = Vec::new(); + for v in data.iter() { + slices.push(v.as_slice()); + expected.extend_from_slice(&v); + } + let storage = Storage::from_slices(&slices); + storage.bytes() == expected.as_slice() + } + } } From b72968570305262fd8ba995bc595c78efcc9cf56 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 18 Feb 2020 19:22:22 +0100 Subject: [PATCH 08/10] Add check_invariants to make sure we don't create heap storage for small vecs --- src/storage.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index bfcbe7e2..260e87d5 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -80,10 +80,17 @@ mod tests { } } + fn check_invariants(storage: Storage) -> bool { + match storage { + Storage::Inline(len, _) => len as usize <= MAX_INLINE, + Storage::Heap(arc) => arc.len() > MAX_INLINE, + } + } + quickcheck! { fn roundtrip_check(data: Vec) -> bool { let storage = Storage::from_slice(&data); - storage.bytes() == data.as_slice() + storage.bytes() == data.as_slice() && check_invariants(storage) } fn from_slices_roundtrip_check(data: Vec>) -> bool { @@ -94,7 +101,7 @@ mod tests { expected.extend_from_slice(&v); } let storage = Storage::from_slices(&slices); - storage.bytes() == expected.as_slice() + storage.bytes() == expected.as_slice() && check_invariants(storage) } } } From 3456e0714818aa2d93845b933c0ea864174c6395 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Wed, 19 Feb 2020 21:33:42 +0100 Subject: [PATCH 09/10] PR feedback no more extern crate --- src/lib.rs | 4 ---- src/storage.rs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 243cf98f..724b30af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,10 +25,6 @@ pub use hashes::Hash; use std::fmt; use storage::Storage; -#[cfg(test)] -#[macro_use] -extern crate quickcheck; - // Helper macro for encoding input into output using sha1, sha2, tiny_keccak, or blake2 macro_rules! encode { (sha1, Sha1, $input:expr, $output:expr) => {{ diff --git a/src/storage.rs b/src/storage.rs index 260e87d5..835d7a07 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -62,7 +62,7 @@ impl Storage { #[cfg(test)] mod tests { use super::{Storage, MAX_INLINE}; - use quickcheck; + use quickcheck::quickcheck; #[test] fn struct_size() { From 62692e2dfe76bf802376719b5d765edaa00ca4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Klaehn?= Date: Mon, 17 Feb 2020 15:26:22 +0100 Subject: [PATCH 10/10] Make debug instance useful --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 724b30af..9b2fc43c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,7 +161,7 @@ pub struct Multihash { impl Debug for Multihash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Multihash") + f.debug_tuple("Multihash").field(&self.as_bytes()).finish() } }