Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cairo version update + improvements #20

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0430d63
Add helper function for mmr append
fmkra Sep 29, 2023
a143f6f
Check peaks length in mmr append
fmkra Sep 29, 2023
9cb7250
Add test for mmr forge peaks
fmkra Sep 29, 2023
6c130bd
Change utils function from u256 to u32
fmkra Sep 29, 2023
49fde6d
Add natspec comments
fmkra Sep 29, 2023
9b64079
Run scarb fmt
fmkra Sep 29, 2023
b309c9f
Implement trailing_ones helper function
fmkra Sep 29, 2023
a6989df
Improve mmr append
fmkra Sep 29, 2023
e98ef12
Remove unnecessary import in test_mmr
fmkra Sep 29, 2023
ce3e1f2
Run scarb fmt
fmkra Sep 29, 2023
d9e3148
fix words64 to u256 empty words
tiagofneto Oct 3, 2023
3ced64e
Implement get_peak_info function
fmkra Oct 12, 2023
2460417
Tests for get_peak_info function
fmkra Oct 12, 2023
c5af976
Fix verify_proof logic
fmkra Oct 12, 2023
a8581d4
Merge branch 'fixes' into improvement/append
fmkra Oct 12, 2023
01e7d3a
Format
fmkra Oct 12, 2023
fa1d030
use div_rem instead of operators
feltroidprime Mar 14, 2024
d36d653
as_u256_le optimisation
feltroidprime Mar 14, 2024
5c00047
use div_rem in extract_nibbles instead of bitwise ops
feltroidprime Mar 14, 2024
2d8b0ea
slice_le optimisation
feltroidprime Mar 15, 2024
89b5a39
Update scarb version
fmkra Mar 21, 2024
0b17101
Remove unused variable
fmkra Mar 21, 2024
11cfdc6
Refactor tests
fmkra Mar 21, 2024
3aaed82
Fmt
fmkra Mar 21, 2024
f41cbc5
Merge branch 'update-cairo' into improvement/append
fmkra Mar 21, 2024
b5ef6ee
Merge branch 'update-cairo' into safe-perf
fmkra Mar 22, 2024
fcf48af
Fix compatibility with 2.6.4
fmkra Mar 22, 2024
5add5cc
Merge pull request #19 from feltroidprime/safe-perf
fmkra Mar 22, 2024
1e2bc3f
Merge branch 'update-cairo' into improvement/append
fmkra Mar 22, 2024
c65b8e3
MMR append change loops to pop from span
fmkra Mar 22, 2024
f1419ee
Remove unused function
fmkra Mar 22, 2024
f1a5e9a
Use divrem in trailing_ones
fmkra Mar 22, 2024
c00222c
Change u32 to usize
fmkra Mar 22, 2024
f94a861
Benchmarking
fmkra Apr 22, 2024
895ca89
Use const spans in pow2
fmkra Apr 22, 2024
d8af0bd
Fmt with new formatter
fmkra Apr 22, 2024
f31c1ec
Minor optimization of append + comment
fmkra May 6, 2024
14604c0
Fmt
fmkra May 6, 2024
8b117d7
Remove formatting check from CI
fmkra May 6, 2024
52b8b2e
Add rlp decode test which fails
fmkra May 9, 2024
b0f93a1
Fix slice_le bug
fmkra May 9, 2024
bc453f9
Change MMR Size type to u128
fmkra Jun 3, 2024
0db686d
Change Mmr Size to u256
fmkra Jun 7, 2024
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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
- run: scarb fmt --check
- run: scarb test
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scarb 0.7.0
scarb nightly-2024-04-20
6 changes: 6 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "cairo_lib"
version = "0.2.0"
80 changes: 80 additions & 0 deletions benchmark/benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import argparse
import subprocess
import re
import os
import pandas as pd

def bench(name, force):
try:
with open(os.path.join("results", name), "w" if force else "x") as f:
test_out = subprocess.check_output(["scarb", "test"], cwd="..")
results = []
for line in test_out.decode("utf-8").split("\n"):
m = re.match(r"^test ([a-zA-Z0-9_:]+) ... ok \(gas usage est\.: (\d+)\)$", line)
if m:
results.append(f"{m.group(1)} {m.group(2)}")
f.write("\n".join(results))
except FileExistsError:
print(f"Bench {name} already exists. If you want to overwrite it, pass the --force flag.")


def results():
bench_names = []
for file in os.listdir("results"):
if file.startswith("."):
continue
bench_names.append(file)

results = {}
test_names = set()
for bench in bench_names:
with open(os.path.join("results", bench), "r") as f:
for line in f.read().split("\n"):
test_name, gas = line.split(" ")
test_names.add(test_name)
if not test_name in results:
results[test_name] = {}
results[test_name][bench] = gas
test_names = list(test_names)
test_names.sort()

table = [[None for _ in bench_names] for _ in test_names]
for i, test in enumerate(test_names):
for j, bench in enumerate(bench_names):
try:
table[i][j] = results[test][bench]
except KeyError:
pass
df = pd.DataFrame(table, index=test_names, columns=bench_names)
df.to_excel("results.xlsx")



def main():
parser = argparse.ArgumentParser(description="Process some commands.")

# Create a subparser object
subparsers = parser.add_subparsers(dest="command", help='sub-command help')

# Create the parser for the "bench" command
parser_bench = subparsers.add_parser('bench', help='Run a benchmark')
parser_bench.add_argument('benchmark_name', type=str, help='The name of the benchmark to run')
parser_bench.add_argument('--force', action='store_true', help='Overwrite the benchmark if it already exists')

# Create the parser for the "results" command
parser_results = subparsers.add_parser('results', help='Show results')

# Parse the arguments
args = parser.parse_args()

# Decide what to do based on the command
if args.command == 'bench':
bench(args.benchmark_name, args.force)
elif args.command == 'results':
results()
else:
parser.print_help()


if __name__ == '__main__':
main()
Empty file added benchmark/results/.gitkeep
Empty file.
33 changes: 14 additions & 19 deletions src/data_structures/eth_mpt.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,18 @@ impl MPTImpl of MPTTrait {
// If it's not the last node and more than 9 words, it must be a branch node
let (decoded, rlp_byte_len) = if proof_index != proof_len - 1 && node.len() > 9 {
let current_nibble = (key / key_pow2) & 0xf;
// Unwrap impossible to fail, as we are masking with 0xf, meaning the result is always a nibble
// Unwrap impossible to fail, as we are masking with 0xf, meaning the result is
// always a nibble
match MPTTrait::lazy_rlp_decode_branch_node(
node, current_nibble.try_into().unwrap()
) {
Result::Ok(d) => d,
Result::Err(e) => {
break Result::Err(e);
}
Result::Err(e) => { break Result::Err(e); }
}
} else {
match MPTTrait::decode_rlp_node(node) {
Result::Ok(d) => d,
Result::Err(e) => {
break Result::Err(e);
}
Result::Err(e) => { break Result::Err(e); }
}
};

Expand All @@ -111,17 +108,16 @@ impl MPTImpl of MPTTrait {
}

let current_nibble = (key / key_pow2) & 0xf;
// Unwrap impossible to fail, as we are masking with 0xf, meaning the result is always a nibble
// Unwrap impossible to fail, as we are masking with 0xf, meaning the result is
// always a nibble
let current_hash_words = *nibbles.at(current_nibble.try_into().unwrap());
current_hash =
if current_hash_words.len() == 0 {
break Result::Ok(array![].span());
} else {
match current_hash_words.as_u256_le(32) {
match current_hash_words.as_u256_le() {
Result::Ok(h) => h,
Result::Err(_) => {
break Result::Err('Invalid hash');
}
Result::Err(_) => { break Result::Err('Invalid hash'); }
}
};
key_pow2 = key_pow2 / 16;
Expand Down Expand Up @@ -192,9 +188,7 @@ impl MPTImpl of MPTTrait {
}
current_hash = next_hash;
},
Result::Err(e) => {
break Result::Err(e);
}
Result::Err(e) => { break Result::Err(e); }
}
},
MPTNode::Leaf((
Expand Down Expand Up @@ -277,21 +271,22 @@ impl MPTImpl of MPTTrait {
} else if len == 2 {
let (first, first_len) = *l.at(0);
let (second, _) = *l.at(1);
// Unwrap impossible to fail, as we are making with 0xff, meaning the result always fits in a byte
// Unwrap impossible to fail, as we are making with 0xff, meaning the result
// always fits in a byte
let prefix_byte: Byte = (*first.at(0) & 0xff).try_into().unwrap();
let (prefix, _) = prefix_byte.extract_nibbles();

let n_nibbles = (first_len * 2) - 1;

if prefix == 0 {
match second.as_u256_le(32) {
match second.as_u256_le() {
Result::Ok(n) => Result::Ok(
(MPTNode::Extension((first, n, 2, n_nibbles - 1)), rlp_byte_len)
),
Result::Err(_) => Result::Err('Invalid next node')
}
} else if prefix == 1 {
match second.as_u256_le(32) {
match second.as_u256_le() {
Result::Ok(n) => Result::Ok(
(MPTNode::Extension((first, n, 1, n_nibbles)), rlp_byte_len)
),
Expand Down Expand Up @@ -322,7 +317,7 @@ impl MPTImpl of MPTTrait {
RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'),
RLPItem::List(l) => {
let (hash_words, _) = *l.at(0);
match hash_words.as_u256_le(32) {
match hash_words.as_u256_le() {
Result::Ok(h) => Result::Ok((MPTNode::LazyBranch(h), rlp_byte_len)),
Result::Err(_) => Result::Err('Invalid hash')
}
Expand Down
83 changes: 35 additions & 48 deletions src/data_structures/mmr/mmr.cairo
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use cairo_lib::data_structures::mmr::peaks::{Peaks, PeaksTrait};
use cairo_lib::data_structures::mmr::proof::{Proof, ProofTrait};
use cairo_lib::data_structures::mmr::utils::{
compute_root, get_height, mmr_size_to_leaf_count, leaf_count_to_peaks_count, get_peak_info
compute_root, get_height, mmr_size_to_leaf_count, leaf_count_to_peaks_count, trailing_ones,
get_peak_info
};
use cairo_lib::hashing::poseidon::PoseidonHasher;

type MmrElement = felt252;
type MmrSize = u256;

// @notice Merkle Mountatin Range struct
#[derive(Drop, Clone, Serde, starknet::Store)]
struct MMR {
root: felt252,
last_pos: usize
root: MmrElement,
last_pos: MmrSize
}

impl MMRDefault of Default<MMR> {
Expand All @@ -27,17 +31,19 @@ impl MMRImpl of MMRTrait {
// @param last_pos The last position in the MMR
// @return MMR with the given root and last_pos
#[inline(always)]
fn new(root: felt252, last_pos: usize) -> MMR {
fn new(root: MmrElement, last_pos: MmrSize) -> MMR {
MMR { root, last_pos }
}

// @notice Appends an element to the MMR
// @param hash The hashed element to append
// @param peaks The peaks of the MMR
// @return Result with the new root and new peaks of the MMR
fn append(ref self: MMR, hash: felt252, peaks: Peaks) -> Result<(felt252, Peaks), felt252> {
let leaf_count = mmr_size_to_leaf_count(self.last_pos.into());
if leaf_count_to_peaks_count(leaf_count) != peaks.len().into() {
fn append(ref self: MMR, hash: MmrElement, peaks: Peaks) -> Result<(MmrElement, Peaks), felt252> {
let leaf_count = mmr_size_to_leaf_count(self.last_pos);
let peaks_count= peaks.len();

if leaf_count_to_peaks_count(leaf_count) != peaks_count.into() {
return Result::Err('Invalid peaks count');
}
if !peaks.valid(self.last_pos, self.root) {
Expand All @@ -46,54 +52,35 @@ impl MMRImpl of MMRTrait {

self.last_pos += 1;

let mut peaks_arr = ArrayTrait::new();
let mut i: usize = 0;
loop {
if i == peaks.len() {
break ();
}

peaks_arr.append(*peaks.at(i));

i += 1;
};
peaks_arr.append(hash);
// number of new nodes = trailing_ones(leaf_count)
// explanation: https://mmr.herodotus.dev/append
let no_merged_peaks = trailing_ones(leaf_count);
let no_preserved_peaks = peaks_count - no_merged_peaks;
let mut preserved_peaks = peaks.slice(0, no_preserved_peaks);
let mut merged_peaks = peaks.slice(no_preserved_peaks, no_merged_peaks);

let mut height = 0;
let mut last_peak = hash;
loop {
if get_height(self.last_pos + 1) <= height {
break ();
}
match merged_peaks.pop_back() {
Option::Some(x) => { last_peak = PoseidonHasher::hash_double(*x, last_peak); },
Option::None => { break; }
};
self.last_pos += 1;
};

let mut peaks_span = peaks_arr.span();
// As the above condition verifies that a merge is happening, we have at least 2 peaks (that are about to be merged)
let right = peaks_span.pop_back().unwrap();
let left = peaks_span.pop_back().unwrap();

let mut new_peaks = ArrayTrait::new();
i = 0;
loop {
if i == peaks_arr.len() - 2 {
break ();
}

new_peaks.append(*peaks_arr.at(i));

i += 1;
let mut new_peaks = ArrayTrait::new();
loop {
match preserved_peaks.pop_front() {
Option::Some(x) => { new_peaks.append(*x); },
Option::None => { break; }
};

let hash = PoseidonHasher::hash_double(*left, *right);
new_peaks.append(hash);
peaks_arr = new_peaks;

height += 1;
};
new_peaks.append(last_peak);

let new_root = compute_root(self.last_pos.into(), peaks_arr.span());
let new_root = compute_root(self.last_pos, new_peaks.span());
self.root = new_root;

Result::Ok((new_root, peaks_arr.span()))
Result::Ok((new_root, new_peaks.span()))
}

// @notice Verifies a proof for an element in the MMR
Expand All @@ -103,9 +90,9 @@ impl MMRImpl of MMRTrait {
// @param proof The proof for the element
// @return Result with true if the proof is valid, false otherwise
fn verify_proof(
self: @MMR, index: usize, hash: felt252, peaks: Peaks, proof: Proof
self: @MMR, index: MmrSize, hash: MmrElement, peaks: Peaks, proof: Proof
) -> Result<bool, felt252> {
let leaf_count = mmr_size_to_leaf_count((*self.last_pos).into());
let leaf_count = mmr_size_to_leaf_count(*self.last_pos);
if leaf_count_to_peaks_count(leaf_count) != peaks.len().into() {
return Result::Err('Invalid peaks count');
}
Expand Down
17 changes: 5 additions & 12 deletions src/data_structures/mmr/peaks.cairo
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use cairo_lib::hashing::poseidon::PoseidonHasher;
use cairo_lib::data_structures::mmr::utils::compute_root;
use cairo_lib::data_structures::mmr::mmr::{MmrSize, MmrElement};
use cairo_lib::utils::array::span_contains;

// @notice Represents the peaks of the MMR
type Peaks = Span<felt252>;
type Peaks = Span<MmrElement>;

#[generate_trait]
impl PeaksImpl of PeaksTrait {
// @notice Bags the peaks (hashing them together)
// @return The bagged peaks
fn bag(self: Peaks) -> felt252 {
fn bag(self: Peaks) -> MmrElement {
if self.is_empty() {
return 0;
}
Expand All @@ -35,16 +36,8 @@ impl PeaksImpl of PeaksTrait {
// @param last_pos The last position in the MMR
// @param root The root of the MMR
// @return True if the peaks are valid
fn valid(self: Peaks, last_pos: usize, root: felt252) -> bool {
let computed_root = compute_root(last_pos.into(), self);
fn valid(self: Peaks, last_pos: MmrSize, root: MmrElement) -> bool {
let computed_root = compute_root(last_pos, self);
computed_root == root
}

// @notice Checks if the peaks contain a peak
// @param peak The peak to check inclusion
// @return True if the peaks contain the peak
#[inline(always)]
fn contains_peak(self: Peaks, peak: felt252) -> bool {
span_contains(self, peak)
}
}
Loading
Loading