Skip to content

Commit

Permalink
Add no-std support (#88)
Browse files Browse the repository at this point in the history
It is desirable to have no-std support so the consumers will have more
flexibility of use with this library, provided an appropriate storage
backend.

This functionality will support the usage of contracts in fuel-tx, since
its id is calculated with a binary merkle tree, according to fuel specs.
  • Loading branch information
vlopes11 authored May 21, 2022
1 parent 88e4d8d commit 59f9755
Show file tree
Hide file tree
Showing 18 changed files with 178 additions and 119 deletions.
17 changes: 17 additions & 0 deletions fuel-merkle/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ jobs:
toolchain: stable
override: true

# Using thumbv6m-none-eabi as ARMv6-M arbitrary common choice for a bare-minimum target.
# More info: https://docs.rs/cortex-m-rt/latest/cortex_m_rt/
#
# Can be replaced by other targets that guarantee bare-minimum no-std
- name: Install toolchain no-std
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: thumbv6m-none-eabi
override: true

- name: Install rustfmt
run: rustup component add rustfmt

Expand All @@ -41,6 +52,12 @@ jobs:
command: build
args: --verbose

- name: Build no-std
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose --target thumbv6m-none-eabi --no-default-features

- name: Run tests
uses: actions-rs/cargo@v1
with:
Expand Down
21 changes: 11 additions & 10 deletions fuel-merkle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,19 @@
name = "fuel-merkle"
version = "0.1.1"
authors = ["Fuel Labs <contact@fuel.sh>"]
edition = "2018"
edition = "2021"
homepage = "https://fuel.network/"
keywords = ["blockchain", "cryptocurrencies", "fuel-vm", "vm"]
license = "Apache-2.0"
repository = "https://github.com/FuelLabs/fuel-merkle"
description = "Fuel Merkle tree libraries."

[dependencies]
anyhow = "1.0"
bytes = "1.0"
digest = "0.9"
fuel-storage = "0.1"
generic-array = "0.14"
hex = "0.4"
lazy_static = "1.4"
sha2 = "0.9"
thiserror = "1.0"
digest = { version = "0.10", default-features = false }
fuel-storage = "0.2"
hex = { version = "0.4", default-features = false, features = ["alloc"] }
sha2 = { version = "0.10", default-features = false }
thiserror = { version = "1.0", optional = true }

[dev-dependencies]
datatest-stable = "0.1"
Expand All @@ -28,7 +24,12 @@ serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
thiserror = "1.0"

[features]
default = ["std"]
std = ["dep:thiserror", "digest/default", "hex/default", "sha2/default"]

[[test]]
name = "tests-data"
path = "./tests-data/tests-data.rs"
harness = false
required-features = ["std"]
6 changes: 4 additions & 2 deletions fuel-merkle/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ mod hash;
mod merkle_tree;
mod node;

pub(crate) use hash::{empty_sum, leaf_sum, node_sum};
pub(crate) use hash::{leaf_sum, node_sum};
pub(crate) use node::Node;

pub use hash::empty_sum;
pub use merkle_tree::MerkleTree;
pub use merkle_tree::MerkleTreeError;
pub(crate) use node::Node;
22 changes: 10 additions & 12 deletions fuel-merkle/src/binary/hash.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
use digest::Digest;
use crate::common::{self, Bytes32, LEAF, NODE};

use crate::common::{Bytes32, LEAF, NODE};
use lazy_static::lazy_static;
use digest::Digest;
use sha2::Sha256;
use std::convert::TryInto;

type Hash = Sha256;

lazy_static! {
static ref EMPTY_SUM: Bytes32 = Hash::new().finalize().try_into().unwrap();
}

// Merkle Tree hash of an empty list
// MTH({}) = Hash()
pub fn empty_sum() -> &'static Bytes32 {
&*EMPTY_SUM
pub const fn empty_sum() -> &'static Bytes32 {
common::empty_sum_sha256()
}

// Merkle tree hash of an n-element list D[n]
// MTH(D[n]) = Hash(0x01 || MTH(D[0:k]) || MTH(D[k:n])
pub fn node_sum(lhs_data: &[u8], rhs_data: &[u8]) -> Bytes32 {
let mut hash = Hash::new();

hash.update(&[NODE]);
hash.update(&lhs_data);
hash.update(&rhs_data);
hash.finalize().try_into().unwrap()

hash.finalize().into()
}

// Merkle tree hash of a list with one entry
// MTH({d(0)}) = Hash(0x00 || d(0))
pub fn leaf_sum(data: &[u8]) -> Bytes32 {
let mut hash = Hash::new();

hash.update(&[LEAF]);
hash.update(&data);
hash.finalize().try_into().unwrap()

hash.finalize().into()
}
41 changes: 23 additions & 18 deletions fuel-merkle/src/binary/merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
use std::marker::{Send, Sync};
use crate::binary::{self, Node};
use crate::common::{Bytes32, Position, Subtree};

use anyhow::{anyhow, Result};
use fuel_storage::Storage;

use crate::binary::{empty_sum, Node};
use crate::common::{Bytes32, Position, Subtree};
use alloc::boxed::Box;
use alloc::vec::Vec;

#[derive(Debug, thiserror::Error)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum MerkleTreeError {
#[error("proof index {0} is not valid")]
#[cfg_attr(feature = "std", error("proof index {0} is not valid"))]
InvalidProofIndex(u64),

#[error("cannot load node with key {0}; the key is not found in storage")]
#[cfg_attr(
feature = "std",
error("cannot load node with key {0}; the key is not found in storage")
)]
LoadError(u64),
}

Expand All @@ -26,7 +30,7 @@ pub struct MerkleTree<'storage, StorageError> {

impl<'storage, StorageError> MerkleTree<'storage, StorageError>
where
StorageError: std::error::Error + Send + Sync + 'static,
StorageError: From<MerkleTreeError> + Send + Sync + 'static,
{
pub fn new(storage: &'storage mut StorageType<StorageError>) -> Self {
Self {
Expand All @@ -39,7 +43,7 @@ where
pub fn load(
storage: &'storage mut StorageType<StorageError>,
leaves_count: u64,
) -> Result<Self> {
) -> Result<Self, StorageError> {
let mut tree = Self {
storage,
head: None,
Expand All @@ -51,19 +55,19 @@ where
Ok(tree)
}

pub fn root(&mut self) -> Result<Bytes32> {
pub fn root(&mut self) -> Result<Bytes32, StorageError> {
let root_node = self.root_node()?;
let root = match root_node {
None => *empty_sum(),
None => *binary::empty_sum(),
Some(ref node) => *node.hash(),
};

Ok(root)
}

pub fn prove(&mut self, proof_index: u64) -> Result<(Bytes32, ProofSet)> {
pub fn prove(&mut self, proof_index: u64) -> Result<(Bytes32, ProofSet), StorageError> {
if proof_index + 1 > self.leaves_count {
return Err(anyhow!(MerkleTreeError::InvalidProofIndex(proof_index)));
return Err(MerkleTreeError::InvalidProofIndex(proof_index).into());
}

let mut proof_set = ProofSet::new();
Expand Down Expand Up @@ -91,7 +95,7 @@ where
Ok((root, proof_set))
}

pub fn push(&mut self, data: &[u8]) -> Result<()> {
pub fn push(&mut self, data: &[u8]) -> Result<(), StorageError> {
let node = Node::create_leaf(self.leaves_count, data);
self.storage.insert(&node.key(), &node)?;
let next = self.head.take();
Expand All @@ -108,7 +112,7 @@ where
// PRIVATE
//

fn build(&mut self) -> Result<()> {
fn build(&mut self) -> Result<(), StorageError> {
let keys = (0..self.leaves_count).map(|i| Position::from_leaf_index(i).in_order_index());
for key in keys {
let node = self
Expand All @@ -125,7 +129,7 @@ where
Ok(())
}

fn root_node(&mut self) -> Result<Option<Node>> {
fn root_node(&mut self) -> Result<Option<Node>, StorageError> {
let root_node = match self.head {
None => None,
Some(ref initial) => {
Expand All @@ -142,7 +146,7 @@ where
Ok(root_node)
}

fn join_all_subtrees(&mut self) -> Result<()> {
fn join_all_subtrees(&mut self) -> Result<(), StorageError> {
loop {
let current = self.head.as_ref().unwrap();
if !(current.next().is_some()
Expand All @@ -168,14 +172,15 @@ where
&mut self,
lhs: &mut Subtree<Node>,
rhs: &mut Subtree<Node>,
) -> Result<Box<Subtree<Node>>> {
) -> Result<Box<Subtree<Node>>, StorageError> {
let joined_node = Node::create_node(lhs.node(), rhs.node());
self.storage.insert(&joined_node.key(), &joined_node)?;
let joined_head = Subtree::new(joined_node, lhs.take_next());
Ok(Box::new(joined_head))
}
}

#[cfg(feature = "std")]
#[cfg(test)]
mod test {
use super::{MerkleTree, Storage};
Expand Down
4 changes: 2 additions & 2 deletions fuel-merkle/src/binary/node.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::binary::{leaf_sum, node_sum};
use std::fmt::Debug;

use crate::common::{Bytes32, Position};

use core::fmt::Debug;

#[derive(Clone, PartialEq, Debug)]
pub struct Node {
position: Position,
Expand Down
31 changes: 29 additions & 2 deletions fuel-merkle/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ mod node;
mod path_iterator;
mod position;
mod position_path;
mod storage_map;
mod subtree;

#[cfg(feature = "std")]
mod storage_map;

pub use msb::Msb;
pub use node::{Node, ParentNode};
pub use path_iterator::AsPathIterator;
pub use position::Position;
pub use storage_map::{StorageError, StorageMap};
pub use subtree::Subtree;

#[cfg(feature = "std")]
pub use storage_map::{StorageError, StorageMap};

pub(crate) use position_path::PositionPath;

pub const NODE: u8 = 0x01;
Expand All @@ -24,3 +28,26 @@ pub type Bytes4 = [u8; 4];
pub type Bytes8 = [u8; 8];
pub type Bytes16 = [u8; 16];
pub type Bytes32 = [u8; 32];

// Merkle Tree hash of an empty list
// MTH({}) = Hash()
pub const fn empty_sum_sha256() -> &'static Bytes32 {
const EMPTY_SUM: Bytes32 = [
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9,
0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52,
0xb8, 0x55,
];

&EMPTY_SUM
}

#[test]
fn empty_sum_sha256_is_empty_hash() {
use digest::Digest;
use sha2::Sha256;

let sum = empty_sum_sha256();
let empty = Bytes32::from(Sha256::new().finalize());

assert_eq!(&empty, sum);
}
4 changes: 2 additions & 2 deletions fuel-merkle/src/common/node.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::mem::size_of;
use core::mem;

pub trait Node {
type Key;

fn key_size_in_bits() -> usize {
size_of::<Self::Key>() * 8
mem::size_of::<Self::Key>() * 8
}

fn height(&self) -> u32;
Expand Down
17 changes: 14 additions & 3 deletions fuel-merkle/src/common/storage_map.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
use crate::{binary, sparse, sum};

use fuel_storage::Storage;
use thiserror::Error;

use std::borrow::Cow;
use std::collections::HashMap;
use thiserror::Error;

#[derive(Clone, Debug, Error)]
pub enum StorageError {}
#[derive(Debug, Clone, Error)]
pub enum StorageError {
#[error("A binary merkle tree error was thrown: {0}")]
BinaryMerkleTree(#[from] binary::MerkleTreeError),

#[error("A sparse merkle tree error was thrown: {0}")]
SparseMerkleTree(#[from] sparse::MerkleTreeError),

#[error("A sum merkle tree error was thrown: {0}")]
SumMerkleTree(#[from] sum::MerkleTreeError),
}

#[derive(Debug)]
pub struct StorageMap<Key, Value> {
Expand Down
2 changes: 2 additions & 0 deletions fuel-merkle/src/common/subtree.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use alloc::boxed::Box;

#[derive(Debug, Clone)]
pub struct Subtree<T> {
node: T,
Expand Down
4 changes: 4 additions & 0 deletions fuel-merkle/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

pub mod binary;
pub mod common;
pub mod sparse;
Expand Down
5 changes: 3 additions & 2 deletions fuel-merkle/src/sparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod hash;
mod merkle_tree;
mod node;

pub(crate) use hash::{empty_sum, zero_sum};
pub use merkle_tree::MerkleTree;
pub use merkle_tree::{MerkleTree, MerkleTreeError};

pub(crate) use hash::zero_sum;
pub(crate) use node::{Buffer, Node, StorageNode};
Loading

0 comments on commit 59f9755

Please sign in to comment.