Skip to content

Commit

Permalink
8.1(herodotus-on-starknet) Close to Ressource Limit in Starknet
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagofneto committed Oct 27, 2023
1 parent 6717057 commit 0af6115
Show file tree
Hide file tree
Showing 4 changed files with 516 additions and 47 deletions.
61 changes: 52 additions & 9 deletions src/data_structures/eth_mpt.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cairo_lib::hashing::keccak::keccak_cairo_words64;
use cairo_lib::encoding::rlp::{RLPItem, rlp_decode};
use cairo_lib::encoding::rlp::{RLPItem, rlp_decode, rlp_decode_list_lazy};
use cairo_lib::utils::types::byte::{Byte, ByteTrait};
use cairo_lib::utils::bitwise::{right_shift, left_shift};
use cairo_lib::utils::types::words64::{Words64, Words64Trait, Words64TryIntoU256LE};
Expand All @@ -24,14 +24,16 @@ enum MPTNode {
// @param hashes 16 hashes of children
// @param value value of the node
Branch: (Span<Words64>, Words64),
// @param hash hash of the next node
LazyBranch: u256,
// @param shared_nibbles
// @param next_node
// @param nibbles_skip number of nibbles to skip in shared nibbles
// @param n_nibbles number of shared nibbles
Extension: (Words64, u256, usize, usize),
// @param key_end
// @param value of the node
// @param nibbles_skip Number of nibbles to skip in the key end
// @param value value of the node
// @param nibbles_skip number of nibbles to skip in the key end
// @param n_nibbles number of nibbles in key_end
Leaf: (Words64, Words64, usize, usize)
}
Expand All @@ -57,16 +59,33 @@ impl MPTImpl of MPTTrait {
let mut proof_index: usize = 0;
let mut key_pow2: u256 = pow(2, (key_len.into() - 1) * 4);

let proof_len = proof.len();

loop {
if proof_index == proof.len() {
if proof_index == proof_len {
break Result::Err('Proof reached end');
}

let node = *proof.at(proof_index);

let (decoded, rlp_byte_len) = match MPTTrait::decode_rlp_node(node) {
Result::Ok(d) => d,
Result::Err(e) => {
break Result::Err(e);
// 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
match MPTTrait::lazy_rlp_decode_branch_node(
node, current_nibble.try_into().unwrap()
) {
Result::Ok(d) => d,
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);
}
}
};

Expand Down Expand Up @@ -103,6 +122,10 @@ impl MPTImpl of MPTTrait {
};
key_pow2 = key_pow2 / 16;
},
MPTNode::LazyBranch(next_node) => {
current_hash = next_node;
key_pow2 = key_pow2 / 16;
},
MPTNode::Extension((
shared_nibbles, next_node, nibbles_skip, n_nibbles
)) => {
Expand Down Expand Up @@ -235,7 +258,7 @@ impl MPTImpl of MPTTrait {
);
}

let (current_hash, _) = *l.at(i);
let (current_hash, _) = (*l.at(i));
nibble_hashes.append(current_hash);
i += 1;
}
Expand Down Expand Up @@ -275,6 +298,26 @@ impl MPTImpl of MPTTrait {
}
}
}


fn lazy_rlp_decode_branch_node(
rlp: Words64, current_nibble: u8
) -> Result<(MPTNode, usize), felt252> {
let (lazy_item, rlp_byte_len) = rlp_decode_list_lazy(
rlp, array![current_nibble.into()].span()
)?;
match lazy_item {
RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'),
RLPItem::List(l) => {
let (hash_words, _) = *l.at(0);
match (hash_words).try_into() {
Option::Some(h) => Result::Ok((MPTNode::LazyBranch(h), rlp_byte_len)),
Option::None(_) => Result::Err('Invalid hash')
}
}
}
}

// @notice keccak256 hashes an RLP encoded node
// @param rlp RLP encoded node
// @param last_word_bytes number of bytes in the last worf of the RLP encoded node
Expand Down
87 changes: 86 additions & 1 deletion src/data_structures/tests/test_eth_mpt.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ fn test_decode_rlp_node_branch() {

let (decoded, rlp_byte_len) = MPTTrait::decode_rlp_node(rlp_node.span()).unwrap();
assert(rlp_byte_len == 66 * 8 + 4, 'Wrong RLP len');

match decoded {
MPTNode::Branch((
hashes, value
Expand All @@ -112,6 +111,9 @@ fn test_decode_rlp_node_branch() {
i += 1;
};
},
MPTNode::LazyBranch(_) => {
panic_with_felt252('Branch node differs');
},
MPTNode::Extension(_) => {
panic_with_felt252('Branch node differs');
},
Expand All @@ -121,6 +123,89 @@ fn test_decode_rlp_node_branch() {
}
}

#[test]
#[available_gas(9999999999)]
fn test_lazy_rlp_node_branch() {
let rlp_node = array![
0x09cf7077a01102f9,
0xa962df351b7a06b5,
0xadecaece75818924,
0x0c4044a8b4cd681f,
0x85a31ea0f44ac173,
0x045c6d4661b25ad0,
0x1a9fc1344568fe87,
0x35361adc184b5c4b,
0x4c2ca0b471500260,
0x1846d1d34035ce04,
0x8366e5a5533c3072,
0x0c80a8368d4f30c1,
0xa9a0eecd3ffaf56a,
0xc4d37d4bc58d77dc,
0xb0fe61d139e72282,
0x3717d5dcb2ceeec0,
0xa05138a6378e5bf0,
0xdd62df56554d5fa9,
0x9b56ae97049962c2,
0x9307207bdafd8ecd,
0xd71897db4cded3f8,
0x2238146d06d439a0,
0x74a843e9c94aaf6e,
0xb91dd8b05fc2a9a9,
0x03e2b336138c1d86,
0x6ab4637ccc7aa04c,
0x25a141a0c9b318a4,
0x7a396b316173cb6b,
0x13bb1b4967885ada,
0x25818a3515a03001,
0xc736fe137193c42e,
0x3497a1fb11b74680,
0x5f78007a1829bb91,
0xd3429168a0ae52f8,
0xdfce8b1ca7faab16,
0x254e10b2db1d2049,
0x1f2256e8c490dc0a,
0x5036dca058964a53,
0xa714a3a8fd342599,
0xb59dc7a83baeb0db,
0xc060242ace690c55,
0xb020a0a3c1c4ad07,
0xe19e05b055663b68,
0xc1cb6b504b4ed003,
0x11b1dab792630039,
0x8ea0e7420366c278,
0xd91c0f63fb45ebed,
0xcb17225718eb3697,
0x03e21bb715f3d5c6,
0xa014269bd9e83cb0,
0x6f985af63da32379,
0x69b9c2e4e6f9e7d5,
0x3999be4e94086b73,
0xf309e62f6114864a,
0x71201ad0d73465a0,
0xce46b9552afba44a,
0xa22aadff2d22c364,
0xb12ac97334928ad1,
0xb8fe8bc2f9bfa0fd,
0xb0c3c818b6a92dbf,
0x4714bdc0b10ce86f,
0xe229ff6121c4f738,
0x3c6961147fa02f50,
0x5ea3bb1b02a54e70,
0x8f459e43f602c572,
0x8fea4837d02e2498,
0x805fb3e2
];

let expected = 0xE7420366C27811B1DAB792630039C1CB6B504B4ED003E19E05B055663B68B020;

let (decoded, rlp_byte_len) = MPTTrait::lazy_rlp_decode_branch_node(rlp_node.span(), 0xa)
.unwrap();
assert(rlp_byte_len == 66 * 8 + 4, 'Wrong RLP len');

let expected_node = MPTNode::LazyBranch(expected);
assert(decoded == expected_node, 'Lazy branch node diffes');
}

#[test]
#[available_gas(9999999999)]
fn test_decode_rlp_node_leaf_odd() {
Expand Down
134 changes: 98 additions & 36 deletions src/encoding/rlp.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use cairo_lib::utils::types::words64::{Words64, Words64Trait, reverse_endianness_u64};
use cairo_lib::utils::types::words64::{Words64, Words64Trait, reverse_endianness_u64, pow2};
use cairo_lib::utils::types::byte::Byte;
use cairo_lib::utils::array::span_contains;

// @notice Enum with all possible RLP types
// For more info: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
Expand Down Expand Up @@ -36,7 +37,7 @@ impl RLPTypeImpl of RLPTypeTrait {
}

// @notice Represent a RLP item
#[derive(Drop)]
#[derive(Drop, PartialEq)]
enum RLPItem {
Bytes: (Words64, usize),
// Should be Span<RLPItem> to allow for any depth/recursion, not yet supported by the compiler
Expand Down Expand Up @@ -141,43 +142,104 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result<Span<(Words64, usiz
}
}

impl RLPItemPartialEq of PartialEq<RLPItem> {
fn eq(lhs: @RLPItem, rhs: @RLPItem) -> bool {
match lhs {
RLPItem::Bytes(b) => {
match rhs {
RLPItem::Bytes(b2) => {
b == b2
},
RLPItem::List(_) => false
}
fn rlp_decode_list_lazy(input: Words64, lazy: Span<usize>) -> Result<(RLPItem, usize), felt252> {
let mut output = ArrayTrait::new();
let mut lazy_index = 0;

let list_prefix: u32 = (*input.at(0) & 0xff).try_into().unwrap();
let list_type = RLPTypeTrait::from_byte(list_prefix.try_into().unwrap()).unwrap();
let (mut current_input_index, len) = match list_type {
RLPType::String(()) => {
return Result::Err('Not a list');
},
RLPType::StringShort(()) => {
return Result::Err('Not a list');
},
RLPType::StringLong(()) => {
return Result::Err('Not a list');
},
RLPType::ListShort(()) => (1, list_prefix - 0xc0),
RLPType::ListLong(()) => {
let len_len = list_prefix - 0xf7;
let len_span = input.slice_le(6, len_len);
// Enough to store 4.29 GB (fits in u32)
assert(len_span.len() == 1 && *len_span.at(0) <= 0xffffffff, 'Len of len too big');

// len fits in 32 bits, confirmed by previous assertion
let len = reverse_endianness_u64(*len_span.at(0), Option::Some(len_len.into()))
.try_into()
.unwrap();
(1 + len_len, len)
}
};

let rlp_byte_len = current_input_index + len;

loop {
if output.len() == lazy.len() {
break Result::Ok((RLPItem::List(output.span()), rlp_byte_len));
}

if current_input_index >= len {
break Result::Err('Too many items to decode');
}

let current_word = current_input_index / 8;
let current_word_offset = 7 - (current_input_index % 8);

let pow2_shift = pow2((7 - current_word_offset) * 8);
let prefix = (*input.at(current_word) / pow2_shift) & 0xff;

let rlp_type = RLPTypeTrait::from_byte(prefix.try_into().unwrap()).unwrap();
let (item_start_skip, item_len) = match rlp_type {
RLPType::String(()) => {
(0, 1)
},
RLPItem::List(l) => {
match rhs {
RLPItem::Bytes(_) => false,
RLPItem::List(l2) => {
let len_l = (*l).len();
if len_l != (*l2).len() {
return false;
}
let mut i: usize = 0;
loop {
if i >= len_l {
break true;
}
if (*l).at(i) != (*l2).at(i) {
break false;
}
i += 1;
}
}
}
RLPType::StringShort(()) => {
let len = prefix - 0x80;
(1, len)
},
RLPType::StringLong(()) => {
let len_len = prefix - 0xb7;

let current_word = (current_input_index + 1) / 8;
let current_word_offset = 7 - ((current_input_index + 1) % 8);

let len_span = input
.slice_le(current_word * 8 + current_word_offset, len_len.try_into().unwrap());
// Enough to store 4.29 GB (fits in u32)
assert(len_span.len() == 1 && *len_span.at(0) <= 0xffffffff, 'Len of len too big');

// len fits in 32 bits, confirmed by previous assertion
let len: u32 = reverse_endianness_u64(
*len_span.at(0), Option::Some(len_len.try_into().unwrap())
)
.try_into()
.unwrap();

(1 + len_len, len.into())
},
RLPType::ListShort(()) => {
panic_with_felt252('Recursive list not supported')
},
RLPType::ListLong(()) => {
panic_with_felt252('Recursive list not supported')
}
};

current_input_index += item_start_skip.try_into().unwrap();
if span_contains(lazy, lazy_index) {
let current_word = current_input_index / 8;
let current_word_offset = 7 - (current_input_index % 8);
let start = current_word * 8 + current_word_offset;

let item_len = item_len.try_into().unwrap();
let decoded = input.slice_le(start, item_len);
output.append((decoded, item_len));
}
}

fn ne(lhs: @RLPItem, rhs: @RLPItem) -> bool {
!(lhs == rhs)
current_input_index += item_len.try_into().unwrap();

lazy_index += 1;
}
}

Loading

0 comments on commit 0af6115

Please sign in to comment.