From f01c6f1c41f15fce55d1719b8cef53fd5a040859 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Tue, 10 Oct 2023 13:10:23 +0900 Subject: [PATCH 1/5] lazy rlp decoding --- src/data_structures/eth_mpt.cairo | 2 +- src/encoding/rlp.cairo | 71 +++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 6976766..64ddca7 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -205,7 +205,7 @@ impl MPTImpl of MPTTrait { // @param rlp RLP encoded node // @return Result with the decoded node fn decode_rlp_node(rlp: Words64) -> Result { - let (item, _) = rlp_decode(rlp)?; + let (item, _) = rlp_decode(rlp, Option::None(()))?; match item { RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'), RLPItem::List(l) => { diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index d47df7c..e25ee80 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -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/ @@ -46,8 +47,9 @@ enum RLPItem { // @notice RLP decodes a rlp encoded byte array // For more info: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ // @param input RLP encoded input, in little endian 64 bits words +// @param lazy If Some, will only decode the specified indexes. If it is not a list, it will always be decoded fully // @return Result with RLPItem and size of the decoded item -fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { +fn rlp_decode(input: Words64, lazy: Option>) -> Result<(RLPItem, usize), felt252> { // It's guaranteed to fid in 32 bits, as we are masking with 0xff let prefix: u32 = (*input.at(0) & 0xff).try_into().unwrap(); @@ -116,7 +118,7 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt break Result::Ok(output.span()); } - let (decoded, decoded_len) = match rlp_decode(input) { + let (decoded, decoded_len) = match rlp_decode(input, Option::None(())) { Result::Ok((d, dl)) => (d, dl), Result::Err(e) => { break Result::Err(e); @@ -141,6 +143,69 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt } } +fn rlp_decode_list_lazy(ref input: Words64, len: usize, lazy: Span) -> Result, felt252> { + let mut output = ArrayTrait::new(); + let mut current_input_index = 0; + let mut lazy_index = 0; + + loop { + if output.len() == lazy.len() { + break Result::Ok(output.span()); + } + + 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) + }, + RLPType::StringShort(()) => { + let len = prefix - 0x80; + (1, len) + }, + RLPType::StringLong(()) => { + let len_len = prefix - 0xb7; + let len_span = input.slice_le(6, 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 start = current_input_index / 8 + (7 - (current_input_index % 8)); + let decoded = input.slice_le(start, item_len.try_into().unwrap()); + output.append(decoded); + } + + current_input_index += item_len.try_into().unwrap(); + + lazy_index += 1; + } +} + impl RLPItemPartialEq of PartialEq { fn eq(lhs: @RLPItem, rhs: @RLPItem) -> bool { match lhs { From 8799090f078eb39659d8e82e8c0dc89da087ad60 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Tue, 10 Oct 2023 13:28:57 +0900 Subject: [PATCH 2/5] fixes --- src/data_structures/eth_mpt.cairo | 2 +- src/encoding/rlp.cairo | 33 ++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 64ddca7..6976766 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -205,7 +205,7 @@ impl MPTImpl of MPTTrait { // @param rlp RLP encoded node // @return Result with the decoded node fn decode_rlp_node(rlp: Words64) -> Result { - let (item, _) = rlp_decode(rlp, Option::None(()))?; + let (item, _) = rlp_decode(rlp)?; match item { RLPItem::Bytes(_) => Result::Err('Invalid RLP for node'), RLPItem::List(l) => { diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index e25ee80..0d99e5c 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -49,7 +49,7 @@ enum RLPItem { // @param input RLP encoded input, in little endian 64 bits words // @param lazy If Some, will only decode the specified indexes. If it is not a list, it will always be decoded fully // @return Result with RLPItem and size of the decoded item -fn rlp_decode(input: Words64, lazy: Option>) -> Result<(RLPItem, usize), felt252> { +fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { // It's guaranteed to fid in 32 bits, as we are masking with 0xff let prefix: u32 = (*input.at(0) & 0xff).try_into().unwrap(); @@ -118,7 +118,7 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt break Result::Ok(output.span()); } - let (decoded, decoded_len) = match rlp_decode(input, Option::None(())) { + let (decoded, decoded_len) = match rlp_decode(input) { Result::Ok((d, dl)) => (d, dl), Result::Err(e) => { break Result::Err(e); @@ -143,11 +143,34 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt } } -fn rlp_decode_list_lazy(ref input: Words64, len: usize, lazy: Span) -> Result, felt252> { +fn rlp_decode_list_lazy(ref input: Words64, lazy: Span) -> Result, felt252> { let mut output = ArrayTrait::new(); - let mut current_input_index = 0; 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 mut current_input_index = 0; + loop { if output.len() == lazy.len() { break Result::Ok(output.span()); @@ -174,7 +197,7 @@ fn rlp_decode_list_lazy(ref input: Words64, len: usize, lazy: Span) -> Re }, RLPType::StringLong(()) => { let len_len = prefix - 0xb7; - let len_span = input.slice_le(6, len_len.try_into().unwrap()); + let len_span = input.slice_le(current_word + 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'); From f9a85b2691139afa80fcb89f8752e47bbd06109b Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 12 Oct 2023 12:59:08 +0900 Subject: [PATCH 3/5] test lazy decode short list and long list --- src/encoding/rlp.cairo | 12 +-- src/encoding/tests/test_rlp.cairo | 167 +++++++++++++++++++++++++++++- 2 files changed, 171 insertions(+), 8 deletions(-) diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 0d99e5c..624da79 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -47,7 +47,6 @@ enum RLPItem { // @notice RLP decodes a rlp encoded byte array // For more info: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ // @param input RLP encoded input, in little endian 64 bits words -// @param lazy If Some, will only decode the specified indexes. If it is not a list, it will always be decoded fully // @return Result with RLPItem and size of the decoded item fn rlp_decode(input: Words64) -> Result<(RLPItem, usize), felt252> { // It's guaranteed to fid in 32 bits, as we are masking with 0xff @@ -143,7 +142,7 @@ fn rlp_decode_list(ref input: Words64, len: usize) -> Result, felt } } -fn rlp_decode_list_lazy(ref input: Words64, lazy: Span) -> Result, felt252> { +fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result, felt252> { let mut output = ArrayTrait::new(); let mut lazy_index = 0; @@ -168,9 +167,6 @@ fn rlp_decode_list_lazy(ref input: Words64, lazy: Span) -> Result) -> Result { let len_len = prefix - 0xb7; - let len_span = input.slice_le(current_word + current_word_offset, len_len.try_into().unwrap()); + 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'); @@ -218,7 +214,9 @@ fn rlp_decode_list_lazy(ref input: Words64, lazy: Span) -> Result Date: Thu, 12 Oct 2023 14:27:32 +0900 Subject: [PATCH 4/5] test decode list long string --- src/encoding/rlp.cairo | 4 ++ src/encoding/tests/test_rlp.cairo | 100 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/encoding/rlp.cairo b/src/encoding/rlp.cairo index 624da79..0b9940f 100644 --- a/src/encoding/rlp.cairo +++ b/src/encoding/rlp.cairo @@ -193,6 +193,10 @@ fn rlp_decode_list_lazy(input: Words64, lazy: Span) -> Result { 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'); diff --git a/src/encoding/tests/test_rlp.cairo b/src/encoding/tests/test_rlp.cairo index c3060c5..5d24f2d 100644 --- a/src/encoding/tests/test_rlp.cairo +++ b/src/encoding/tests/test_rlp.cairo @@ -442,3 +442,103 @@ fn test_rlp_lazy_decode_long_list() { ].span(); assert(res == expected_res, 'Wrong value indexes: 5, 9, 15'); } + +#[test] +#[available_gas(99999999)] +fn test_rlp_decode_list_long_string() { + let arr = array![ + 0x7235e356aca05bf8, + 0x7f0b03476f57b94f, + 0x4760f75aaf1d2720, + 0xa9c2173ae53aab1f, + 0xf338d438b8ed276f, + 0x27777eada3968dad, + 0x53189e661865fe38, + 0xc101f7b5d6dffd52, + 0x65454695474abcbb, + 0x4567644756674547, + 0x5663776535476567, + 0xfa77645733, + ]; + + let (res, len) = rlp_decode(arr.span()).unwrap(); + assert(len == 1 + (0xf8 - 0xf7) + 0x5b, 'Wrong len'); + + let expected_res = array![ + array![ + 0x57b94f7235e356ac, + 0x1d27207f0b03476f, + 0x3aab1f4760f75aaf, + 0xed276fa9c2173ae5, + ].span(), + array![ + 0xada3968dadf338d4, + 0x661865fe3827777e, + 0xb5d6dffd5253189e, + 0x95474abcbbc101f7, + 0x4756674547654546, + 0x6535476567456764, + 0xfa77645733566377, + ].span(), + ]; + let expected_item = RLPItem::List(expected_res.span()); + assert(res == expected_item, 'Wrong value'); +} + +#[test] +#[available_gas(99999999)] +fn test_rlp_lazy_decode_list_long_string() { + let arr = array![ + 0x7235e356aca05bf8, + 0x7f0b03476f57b94f, + 0x4760f75aaf1d2720, + 0xa9c2173ae53aab1f, + 0xf338d438b8ed276f, + 0x27777eada3968dad, + 0x53189e661865fe38, + 0xc101f7b5d6dffd52, + 0x65454695474abcbb, + 0x4567644756674547, + 0x5663776535476567, + 0xfa77645733, + ]; + + let expected_res_full = array![ + array![ + 0x57b94f7235e356ac, + 0x1d27207f0b03476f, + 0x3aab1f4760f75aaf, + 0xed276fa9c2173ae5, + ].span(), + array![ + 0xada3968dadf338d4, + 0x661865fe3827777e, + 0xb5d6dffd5253189e, + 0x95474abcbbc101f7, + 0x4756674547654546, + 0x6535476567456764, + 0xfa77645733566377, + ].span(), + ]; + + let res = rlp_decode_list_lazy(arr.span(), array![].span()).unwrap(); + assert(res.is_empty(), 'Wrong value indexes: empty'); + + let res = rlp_decode_list_lazy(arr.span(), array![0].span()).unwrap(); + let expected_res = array![ + *expected_res_full.at(0) + ].span(); + assert(res == expected_res, 'Wrong value indexes: 0'); + + let res = rlp_decode_list_lazy(arr.span(), array![1].span()).unwrap(); + let expected_res = array![ + *expected_res_full.at(1) + ].span(); + assert(res == expected_res, 'Wrong value indexes: 1'); + + let res = rlp_decode_list_lazy(arr.span(), array![0, 1].span()).unwrap(); + let expected_res = array![ + *expected_res_full.at(0), *expected_res_full.at(1) + ].span(); + assert(res == expected_res, 'Wrong value indexes: 0, 1'); +} From 1c2a5d358711a60e29a000863f3041dc57220053 Mon Sep 17 00:00:00 2001 From: tiagofneto Date: Thu, 12 Oct 2023 16:10:51 +0900 Subject: [PATCH 5/5] lazy decode rlp branch node and tests --- src/data_structures/eth_mpt.cairo | 109 ++++++------------- src/data_structures/tests/test_eth_mpt.cairo | 83 ++++++++++++++ 2 files changed, 119 insertions(+), 73 deletions(-) diff --git a/src/data_structures/eth_mpt.cairo b/src/data_structures/eth_mpt.cairo index 6976766..68ccecb 100644 --- a/src/data_structures/eth_mpt.cairo +++ b/src/data_structures/eth_mpt.cairo @@ -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}; @@ -19,11 +19,13 @@ impl MPTDefault of Default { } // @notice Represents a node in the MPT -#[derive(Drop)] +#[derive(Drop, PartialEq)] enum MPTNode { // @param 16 hashes of children // @param Value of the node Branch: (Span, Words64), + // @param hash of the next node + LazyBranch: u256, // @param shared_nibbles // @param next_node // @param nibbles_skip Number of nibbles to skip in shared nibbles @@ -55,8 +57,10 @@ 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'); } @@ -65,10 +69,22 @@ impl MPTImpl of MPTTrait { let hash = MPTTrait::hash_rlp_node(node); assert(hash == current_hash, 'Element not matching'); - let decoded = match MPTTrait::decode_rlp_node(node) { - Result::Ok(decoded) => decoded, - 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 = 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(decoded) => decoded, + Result::Err(e) => { + break Result::Err(e); + } + } + } else { + match MPTTrait::decode_rlp_node(node) { + Result::Ok(decoded) => decoded, + Result::Err(e) => { + break Result::Err(e); + } } }; match decoded { @@ -96,6 +112,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 )) => { @@ -252,6 +272,15 @@ impl MPTImpl of MPTTrait { } } + + fn lazy_rlp_decode_branch_node(rlp: Words64, current_nibble: u8) -> Result { + let hash_words = rlp_decode_list_lazy(rlp, array![current_nibble.into()].span())?; + match (*hash_words.at(0)).try_into() { + Option::Some(h) => Result::Ok(MPTNode::LazyBranch(h)), + Option::None(_) => Result::Err('Invalid hash') + } + } + // @notice keccak256 hashes an RLP encoded node // @param rlp RLP encoded node // @return keccak256 hash of the node @@ -259,69 +288,3 @@ impl MPTImpl of MPTTrait { keccak_cairo_words64(rlp) } } - -impl MPTNodePartialEq of PartialEq { - fn eq(lhs: @MPTNode, rhs: @MPTNode) -> bool { - match lhs { - MPTNode::Branch(( - lhs_nibbles, lhs_value - )) => { - match rhs { - MPTNode::Branch(( - rhs_nibbles, rhs_value - )) => { - if (*lhs_nibbles).len() != (*rhs_nibbles).len() { - return false; - } - let mut i: usize = 0; - loop { - if i >= (*lhs_nibbles).len() { - break lhs_value == rhs_value; - } - if (*lhs_nibbles).at(i) != (*rhs_nibbles).at(i) { - break false; - } - i += 1; - } - }, - MPTNode::Extension(_) => false, - MPTNode::Leaf(_) => false - } - }, - MPTNode::Extension(( - lhs_shared_nibbles, lhs_next_node, lhs_nibbles_skip - )) => { - match rhs { - MPTNode::Branch(_) => false, - MPTNode::Extension(( - rhs_shared_nibbles, rhs_next_node, rhs_nibbles_skip - )) => { - lhs_shared_nibbles == rhs_shared_nibbles - && lhs_next_node == rhs_next_node - && lhs_nibbles_skip == rhs_nibbles_skip - }, - MPTNode::Leaf(_) => false - } - }, - MPTNode::Leaf(( - lhs_key_end, lhs_value, lhs_nibbles_skip - )) => { - match rhs { - MPTNode::Branch(_) => false, - MPTNode::Extension(_) => false, - MPTNode::Leaf(( - rhs_key_end, rhs_value, rhs_nibbles_skip - )) => { - lhs_key_end == rhs_key_end - && lhs_value == rhs_value - && lhs_nibbles_skip == rhs_nibbles_skip - } - } - } - } - } - - fn ne(lhs: @MPTNode, rhs: @MPTNode) -> bool { - !(lhs == rhs) - } -} diff --git a/src/data_structures/tests/test_eth_mpt.cairo b/src/data_structures/tests/test_eth_mpt.cairo index 8544bf2..f0d5109 100644 --- a/src/data_structures/tests/test_eth_mpt.cairo +++ b/src/data_structures/tests/test_eth_mpt.cairo @@ -110,6 +110,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'); }, @@ -119,6 +122,86 @@ 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 = MPTTrait::lazy_rlp_decode_branch_node(rlp_node.span(), 0xa).unwrap(); + 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() {