Skip to content

Commit

Permalink
Optimize gas usage of node and leaf hashes (#104)
Browse files Browse the repository at this point in the history
## Type of change

<!--Delete points that do not apply-->

- Improvement (refactoring, restructuring repository, cleaning tech
debt, ...)

## Changes

The following changes have been made:

- Refactor leaf and node hashing to improve gas usage
- Added test for a complete but not full tree

## Notes

- This **DOES NOT** implement a bitmask described in #95, but completes
the motivation. After some thorough testing, I don't think this is
necessary. Current optimizations in conjunction with changes within the
compiler and the stdlib have already brought gas down SIGNIFICANTLY.
Million leaf merkle trees are no longer expensive. If this is something
we would like to pursue in the future we can reopen the issue.
- Some testing using the Rust SDK shows that a bitmask only provides a
0.2% decrease in gas costs.
- The recent bump to forc v0.35.3 decrease gas usage from 5,802,095 ->
26,076 for 1 million leaf merkle tree
- Current changes reduce gas usage by another 84% from master from
26,076 -> 4,017 for 1 million leaf merkle tree
- This also drop the bytecode size from 13548 bytes to 5668 bytes

## Related Issues

<!--Delete everything after the "#" symbol and replace it with a number.
No spaces between hash and number-->

Closes #95

---------

Co-authored-by: bitzoic <[email protected]>
  • Loading branch information
bitzoic and bitzoic authored Feb 24, 2023
1 parent d22dd19 commit 5700990
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 22 deletions.
24 changes: 13 additions & 11 deletions libs/merkle_proof/src/binary_merkle_proof.sw
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ pub const NODE = 1u8;
///
/// * 'data' - The hash of the leaf data.
pub fn leaf_digest(data: b256) -> b256 {
let mut bytes = Bytes::new();
let mut b256_as_bytes = Bytes::from(data);
let mut bytes = Bytes::with_capacity(33);
let new_ptr = bytes.buf.ptr().add_uint_offset(1);

bytes.push(LEAF);
bytes.append(b256_as_bytes);
bytes.buf.ptr().write_byte(LEAF);
__addr_of(data).copy_bytes_to(new_ptr, 32);
bytes.len = 33;

bytes.sha256()
}
Expand All @@ -36,13 +37,14 @@ pub fn leaf_digest(data: b256) -> b256 {
/// * 'left' - The hash of the left node.
/// * 'right' - The hash of the right node.
pub fn node_digest(left: b256, right: b256) -> b256 {
let mut bytes = Bytes::new();
let mut left_as_bytes = Bytes::from(left);
let mut right_as_bytes = Bytes::from(right);

bytes.push(NODE);
bytes.append(left_as_bytes);
bytes.append(right_as_bytes);
let mut bytes = Bytes::with_capacity(65);
let new_ptr_left = bytes.buf.ptr().add_uint_offset(1);
let new_ptr_right = bytes.buf.ptr().add_uint_offset(33);

bytes.buf.ptr().write_byte(NODE);
__addr_of(left).copy_bytes_to(new_ptr_left, 32);
__addr_of(right).copy_bytes_to(new_ptr_right, 32);
bytes.len = 65;

bytes.sha256()
}
Expand Down
23 changes: 20 additions & 3 deletions tests/src/merkle_proof/tests/functions/process_proof.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// TODO: More extensive testing using different proof lengths should be added when https://github.com/FuelLabs/fuels-rs/issues/353 is revolved.
// TODO: Using the fuel-merkle repository will currently fail all tests due to https://github.com/FuelLabs/sway/issues/2594
use crate::merkle_proof::tests::utils::{
abi_calls::process_proof,
test_helpers::{build_tree, build_tree_manual, leaves_with_depth, merkle_proof_instance},
Expand Down Expand Up @@ -29,7 +27,7 @@ mod success {
async fn processes_merkle_proof() {
let instance = merkle_proof_instance().await;

let depth = 8;
let depth = 16;
let leaves = leaves_with_depth(depth).await;
let key = 0;

Expand All @@ -41,6 +39,25 @@ mod success {
);
}

#[tokio::test]
async fn processes_merkle_proof_complete_tree() {
let instance = merkle_proof_instance().await;

let depth = 16;
let mut leaves = leaves_with_depth(depth).await;
let key = 0;
let length = leaves.len() / 3;

leaves.truncate(length);

let (_tree, root, leaf, proof) = build_tree(leaves.clone(), key).await;

assert_eq!(
process_proof(&instance, key, leaf, leaves.len() as u64, proof).await,
root
);
}

#[tokio::test]
async fn processes_merkle_proof_key_is_num_leaves() {
let instance = merkle_proof_instance().await;
Expand Down
8 changes: 0 additions & 8 deletions tests/src/merkle_proof/tests/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ pub mod abi_calls {
use super::*;

pub async fn leaf_digest(contract: &TestMerkleProofLib, data: Bits256) -> Bits256 {
let tx_params = TxParameters::new(None, Some(10_000_000), None);
contract
.methods()
.leaf_digest(data)
.tx_params(tx_params)
.call()
.await
.unwrap()
Expand All @@ -36,11 +34,9 @@ pub mod abi_calls {
left: Bits256,
right: Bits256,
) -> Bits256 {
let tx_params = TxParameters::new(None, Some(10_000_000), None);
contract
.methods()
.node_digest(left, right)
.tx_params(tx_params)
.call()
.await
.unwrap()
Expand All @@ -54,11 +50,9 @@ pub mod abi_calls {
num_leaves: u64,
proof: Vec<Bits256>,
) -> Bits256 {
let tx_params = TxParameters::new(None, Some(10_000_000), None);
contract
.methods()
.process_proof(key, leaf, num_leaves, proof)
.tx_params(tx_params)
.call()
.await
.unwrap()
Expand All @@ -73,11 +67,9 @@ pub mod abi_calls {
num_leaves: u64,
proof: Vec<Bits256>,
) -> bool {
let tx_params = TxParameters::new(None, Some(10_000_000), None);
contract
.methods()
.verify_proof(key, leaf, root, num_leaves, proof)
.tx_params(tx_params)
.call()
.await
.unwrap()
Expand Down

0 comments on commit 5700990

Please sign in to comment.