Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,25 @@ jobs:
components: rustfmt, clippy

- name: Run Tests
run: cargo +${{ matrix.toolchain }} test --all-features
run: |
cargo +${{ matrix.toolchain }} test --no-default-features
cargo +${{ matrix.toolchain }} test --all-features
no-std:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install MSRV Rust toolchain
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561
with:
toolchain: "1.74.0"
# Common bare-metal Cortex-M target (no_std: `core` + `alloc`).
targets: thumbv7em-none-eabi

- name: Build for no_std target
shell: bash
run: |
cargo +1.74.0 build --lib --target thumbv7em-none-eabi --no-default-features
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ rust-version = "1.74.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitcoin_hashes = "0.19"
hex-conservative = "1.0.0"
bitcoin_hashes = { version = "0.19", default-features = false }
hex-conservative = { version = "1.0.0", default-features = false }
serde = { version = "1.0", features = ["derive"], optional = true }
hashbrown = "0.16.1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure: you can't disable a dependency if a feature is on, right? You can only enable them. Because now we have at least one non rust-bitcoin dependency that's not feature-gated. I guess it's ok, since hashbrown is super small and is the basis for std's hashmap implementation

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sort of ugly. Otherwise we would have to create a no-std or hashbrown feature required for no-std.

But as you say, std::collections::HashMap is wrapping the hashbrown one: https://doc.rust-lang.org/src/std/collections/hash/map.rs.html

bitcoin-io = { version = "0.3.0", default-features = false, features = ["alloc"] }

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -23,8 +25,9 @@ criterion = { version = ">=0.5.1", features = ["html_reports"] }
rand = "0.9.2"

[features]
default = ["std"]
std = ["bitcoin_hashes/std", "hex-conservative/std"]
with-serde = ["serde"]
default = []

[lints.clippy]
use_self = "warn"
Expand All @@ -40,6 +43,8 @@ name = "proof-update"

[[example]]
name = "custom-hash-type"
# Compile AccumulatorHash with the `std::io::{Read, Write}` trait bounds
required-features = ["std"]

[[bench]]
name = "accumulator_benchmarks"
Expand Down
9 changes: 7 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ build:
# Check code formatting, compilation and linting
check:
cargo +nightly fmt --all --check
cargo +nightly check --all-features --all-targets --tests --benches
cargo +nightly clippy --all-features --all-targets --tests --benches -- -D warnings

cargo +nightly check --no-default-features --all-targets
cargo +nightly check --all-features --all-targets
cargo +nightly clippy --no-default-features --all-targets -- -D warnings
cargo +nightly clippy --all-features --all-targets -- -D warnings

RUSTDOCFLAGS="-D warnings" cargo +nightly doc --no-deps --all-features

# Format code
Expand All @@ -22,6 +26,7 @@ fmt:

# Run all tests
test:
cargo test --no-default-features
cargo test --all-features

# Run all tests on MSRV (1.74.0)
Expand Down
70 changes: 31 additions & 39 deletions src/accumulator/mem_forest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@
//! assert_eq!(p.get_roots()[0].get_data(), BitcoinNodeHash::default());
//! ```
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io::Read;
use std::io::Write;
use std::rc::Rc;
use std::rc::Weak;
use alloc::rc::Rc;
use alloc::rc::Weak;
use core::cell::Cell;
use core::cell::RefCell;
use core::fmt;
use core::fmt::Debug;
use core::fmt::Display;
use core::fmt::Formatter;

use super::node_hash::AccumulatorHash;
use super::node_hash::BitcoinNodeHash;
Expand All @@ -48,6 +46,7 @@ use super::util::max_position_at_row;
use super::util::right_child;
use super::util::root_position;
use super::util::tree_rows;
use crate::prelude::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NodeType {
Expand Down Expand Up @@ -84,7 +83,7 @@ impl<Hash: AccumulatorHash> Node<Hash> {
/// Writes one node to the writer, this method will recursively write all children.
/// The primary use of this method is to serialize the accumulator. In this case,
/// you should call this method on each root in the forest.
pub fn write_one<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
pub fn write_one<W: Write>(&self, writer: &mut W) -> io::Result<()> {
match self.ty {
NodeType::Branch => writer.write_all(&0_u64.to_le_bytes())?,
NodeType::Leaf => writer.write_all(&1_u64.to_le_bytes())?,
Expand Down Expand Up @@ -124,14 +123,12 @@ impl<Hash: AccumulatorHash> Node<Hash> {
/// you should call this method on each root in the forest, assuming you know how
/// many roots there are.
#[allow(clippy::type_complexity)]
pub fn read_one<R: std::io::Read>(
reader: &mut R,
) -> std::io::Result<(Rc<Self>, HashMap<Hash, Weak<Self>>)> {
fn _read_one<Hash: AccumulatorHash, R: std::io::Read>(
pub fn read_one<R: Read>(reader: &mut R) -> io::Result<(Rc<Self>, HashMap<Hash, Weak<Self>>)> {
fn _read_one<Hash: AccumulatorHash, R: Read>(
ancestor: Option<Rc<Node<Hash>>>,
reader: &mut R,
index: &mut HashMap<Hash, Weak<Node<Hash>>>,
) -> std::io::Result<Rc<Node<Hash>>> {
) -> io::Result<Rc<Node<Hash>>> {
let mut ty = [0u8; 8];
reader.read_exact(&mut ty)?;
let data = Hash::read(reader)?;
Expand Down Expand Up @@ -253,7 +250,7 @@ impl<Hash: AccumulatorHash> MemForest<Hash> {
/// vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
/// );
/// ```
pub fn serialize<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
pub fn serialize<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.leaves.to_le_bytes())?;
writer.write_all(&(self.roots.len() as u64).to_le_bytes())?;

Expand All @@ -267,17 +264,16 @@ impl<Hash: AccumulatorHash> MemForest<Hash> {
/// Deserializes a MemForest from a reader.
/// # Example
/// ```
/// use std::io::Cursor;
///
/// use rustreexo::accumulator::mem_forest::MemForest;
/// use rustreexo::accumulator::node_hash::BitcoinNodeHash;
/// use rustreexo::prelude::io::Cursor;
/// let mut serialized = Cursor::new(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// let MemForest = MemForest::<BitcoinNodeHash>::deserialize(&mut serialized).unwrap();
/// assert_eq!(MemForest.leaves, 0);
/// assert_eq!(MemForest.get_roots().len(), 0);
/// ```
pub fn deserialize<R: Read>(mut reader: R) -> std::io::Result<Self> {
fn read_u64<R: Read>(reader: &mut R) -> std::io::Result<u64> {
pub fn deserialize<R: Read>(mut reader: R) -> io::Result<Self> {
fn read_u64<R: Read>(reader: &mut R) -> io::Result<u64> {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
Ok(u64::from_le_bytes(buf))
Expand Down Expand Up @@ -675,29 +671,29 @@ impl<Hash: AccumulatorHash> MemForest<Hash> {
}

impl Debug for MemForest {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.string())
}
}

impl Display for MemForest {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.string())
}
}

#[cfg(test)]
mod test {
use std::convert::TryFrom;
use std::rc::Rc;
use std::str::FromStr;
use std::vec;
use core::convert::TryFrom;
use core::str::FromStr;

use bitcoin_hashes::sha256::Hash as Data;
use bitcoin_hashes::HashEngine;
use io::Cursor;
use serde::Deserialize;

use super::MemForest;
use super::*;
use crate::accumulator::mem_forest::Node;
use crate::accumulator::node_hash::AccumulatorHash;
use crate::accumulator::node_hash::BitcoinNodeHash;
Expand Down Expand Up @@ -926,11 +922,10 @@ mod test {
deletion_tests: Vec<TestCase>,
}

let contents = std::fs::read_to_string("test_values/test_cases.json")
.expect("Something went wrong reading the file");
let contents = include_str!("../../test_values/test_cases.json");

let tests = serde_json::from_str::<TestsJSON>(contents.as_str())
.expect("JSON deserialization error");
let tests =
serde_json::from_str::<TestsJSON>(contents).expect("JSON deserialization error");

for i in tests.insertion_tests {
run_single_addition_case(i);
Expand Down Expand Up @@ -999,11 +994,10 @@ mod test {
let mut p = MemForest::new();
p.modify(&hashes, &[]).expect("Test mem_forests are valid");
p.modify(&[], &[hashes[0]]).expect("can remove 0");
let mut writer = std::io::Cursor::new(Vec::new());
let mut writer = Vec::new();
p.get_roots()[0].write_one(&mut writer).unwrap();
let (deserialized, _) =
Node::<BitcoinNodeHash>::read_one(&mut std::io::Cursor::new(writer.into_inner()))
.unwrap();
Node::<BitcoinNodeHash>::read_one(&mut Cursor::new(writer)).unwrap();
assert_eq!(deserialized.get_data(), p.get_roots()[0].get_data());
}

Expand All @@ -1013,12 +1007,10 @@ mod test {
let mut p = MemForest::new();
p.modify(&hashes, &[]).expect("Test mem_forests are valid");
p.modify(&[], &[hashes[0]]).expect("can remove 0");
let mut writer = std::io::Cursor::new(Vec::new());
let mut writer = Vec::new();
p.serialize(&mut writer).unwrap();
let deserialized = MemForest::<BitcoinNodeHash>::deserialize(&mut std::io::Cursor::new(
writer.into_inner(),
))
.unwrap();
let deserialized =
MemForest::<BitcoinNodeHash>::deserialize(&mut Cursor::new(writer)).unwrap();
assert_eq!(
deserialized.get_roots()[0].get_data(),
p.get_roots()[0].get_data()
Expand Down
46 changes: 25 additions & 21 deletions src/accumulator/node_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@
//! .unwrap();
//! assert_eq!(parent, expected_parent);
//! ```
use std::convert::TryFrom;
use std::fmt::Debug;
use std::fmt::Display;
use std::ops::Deref;
use std::str::FromStr;
use core::convert::TryFrom;
use core::fmt;
use core::fmt::Debug;
use core::fmt::Display;
use core::hash::Hash;
use core::ops::Deref;
use core::str::FromStr;

use bitcoin_hashes::sha256;
use bitcoin_hashes::sha512_256;
Expand All @@ -61,20 +64,20 @@ use serde::Deserialize;
#[cfg(feature = "with-serde")]
use serde::Serialize;

pub trait AccumulatorHash:
Copy + Clone + Ord + Debug + Display + std::hash::Hash + Default + 'static
{
use crate::prelude::*;

pub trait AccumulatorHash: Copy + Clone + Ord + Debug + Display + Hash + Default + 'static {
fn is_empty(&self) -> bool;
fn empty() -> Self;
fn is_placeholder(&self) -> bool;
fn placeholder() -> Self;
fn parent_hash(left: &Self, right: &Self) -> Self;
fn write<W>(&self, writer: &mut W) -> std::io::Result<()>
fn write<W>(&self, writer: &mut W) -> io::Result<()>
where
W: std::io::Write;
fn read<R>(reader: &mut R) -> std::io::Result<Self>
W: Write;
fn read<R>(reader: &mut R) -> io::Result<Self>
where
R: std::io::Read;
R: Read;
}

#[derive(Eq, PartialEq, Copy, Clone, Hash, PartialOrd, Ord)]
Expand Down Expand Up @@ -112,7 +115,7 @@ impl Deref for BitcoinNodeHash {
}

impl Display for BitcoinNodeHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
if let Self::Some(ref inner) = self {
let mut s = String::new();
for byte in inner.iter() {
Expand All @@ -126,7 +129,7 @@ impl Display for BitcoinNodeHash {
}

impl Debug for BitcoinNodeHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
Self::Empty => write!(f, "empty"),
Self::Placeholder => write!(f, "placeholder"),
Expand Down Expand Up @@ -278,9 +281,9 @@ impl AccumulatorHash for BitcoinNodeHash {
}

/// write to buffer
fn write<W>(&self, writer: &mut W) -> std::io::Result<()>
fn write<W>(&self, writer: &mut W) -> io::Result<()>
where
W: std::io::Write,
W: Write,
{
match self {
Self::Empty => writer.write_all(&[0]),
Expand All @@ -293,9 +296,9 @@ impl AccumulatorHash for BitcoinNodeHash {
}

/// Read from buffer
fn read<R>(reader: &mut R) -> std::io::Result<Self>
fn read<R>(reader: &mut R) -> io::Result<Self>
where
R: std::io::Read,
R: Read,
{
let mut tag = [0];
reader.read_exact(&mut tag)?;
Expand All @@ -308,8 +311,8 @@ impl AccumulatorHash for BitcoinNodeHash {
Ok(Self::Some(hash))
}
[_] => {
let err = std::io::Error::new(
std::io::ErrorKind::InvalidData,
let err = io::Error::new(
io::ErrorKind::InvalidData,
"unexpected tag for AccumulatorHash",
);
Err(err)
Expand All @@ -320,7 +323,8 @@ impl AccumulatorHash for BitcoinNodeHash {

#[cfg(test)]
mod test {
use std::str::FromStr;
use alloc::string::ToString;
use core::str::FromStr;

use super::AccumulatorHash;
use crate::accumulator::node_hash::BitcoinNodeHash;
Expand Down
Loading
Loading