Skip to content

Commit

Permalink
Encapsulate field parsing (#4)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan <[email protected]>
  • Loading branch information
tuommaki and zeapoz authored Sep 29, 2023
1 parent f6b4671 commit 3ba7ecc
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 249 deletions.
261 changes: 13 additions & 248 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#![feature(array_chunks)]

use std::collections::HashMap;
use std::fs;
use thiserror::Error;

mod state;
use crate::state::CommitBlockInfoV1;
Expand All @@ -19,21 +17,6 @@ pub const ZK_SYNC_ADDR: &str = "0x32400084C286CF3E17e7B677ea9583e60a000324";
pub const GENESIS_BLOCK: u64 = 16_627_460;
pub const BLOCK_STEP: u64 = 128;

#[derive(Error, Debug)]
pub enum ParseError {
#[error("invalid Calldata: {0}")]
InvalidCalldata(String),

#[error("invalid StoredBlockInfo: {0}")]
InvalidStoredBlockInfo(String),

#[error("invalid CommitBlockInfo: {0}")]
InvalidCommitBlockInfo(String),

#[error("invalid compressed bytecode: {0}")]
InvalidCompressedByteCode(String),
}

pub fn create_initial_state() {
let _input = fs::read_to_string(INITAL_STATE_PATH).unwrap();
todo!();
Expand All @@ -52,10 +35,10 @@ pub async fn init_eth_adapter(http_url: &str) -> (Provider<Http>, Contract) {
fn parse_calldata(commit_blocks_fn: &Function, calldata: &[u8]) -> Result<Vec<CommitBlockInfoV1>> {
let mut parsed_input = commit_blocks_fn
.decode_input(&calldata[4..])
.map_err(|e| ParseError::InvalidCalldata(e.to_string()))?;
.map_err(|e| state::ParseError::InvalidCalldata(e.to_string()))?;

if parsed_input.len() != 2 {
return Err(ParseError::InvalidCalldata(format!(
return Err(state::ParseError::InvalidCalldata(format!(
"invalid number of parameters (got {}, expected 2) for commitBlocks function",
parsed_input.len()
))
Expand All @@ -64,24 +47,26 @@ fn parse_calldata(commit_blocks_fn: &Function, calldata: &[u8]) -> Result<Vec<Co

let new_blocks_data = parsed_input
.pop()
.ok_or_else(|| ParseError::InvalidCalldata("new blocks data".to_string()))?;
.ok_or_else(|| state::ParseError::InvalidCalldata("new blocks data".to_string()))?;
let stored_block_info = parsed_input
.pop()
.ok_or_else(|| ParseError::InvalidCalldata("stored block info".to_string()))?;
.ok_or_else(|| state::ParseError::InvalidCalldata("stored block info".to_string()))?;

let abi::Token::Tuple(stored_block_info) = stored_block_info else {
return Err(ParseError::InvalidCalldata("invalid StoredBlockInfo".to_string()).into());
return Err(
state::ParseError::InvalidCalldata("invalid StoredBlockInfo".to_string()).into(),
);
};

let abi::Token::Uint(previous_l2_block_number) = stored_block_info[0].clone() else {
return Err(ParseError::InvalidStoredBlockInfo(
return Err(state::ParseError::InvalidStoredBlockInfo(
"cannot parse previous L2 block number".to_string(),
)
.into());
};

let abi::Token::Uint(_previous_enumeration_index) = stored_block_info[2].clone() else {
return Err(ParseError::InvalidStoredBlockInfo(
return Err(state::ParseError::InvalidStoredBlockInfo(
"cannot parse previous enumeration index".to_string(),
)
.into());
Expand All @@ -98,242 +83,22 @@ fn parse_commit_block_info(data: &abi::Token) -> Result<Vec<CommitBlockInfoV1>>
let mut res = vec![];

let abi::Token::Array(data) = data else {
return Err(ParseError::InvalidCommitBlockInfo(
return Err(state::ParseError::InvalidCommitBlockInfo(
"cannot convert newBlocksData to array".to_string(),
)
.into());
};

for data in data.iter() {
let abi::Token::Tuple(block_elems) = data else {
return Err(ParseError::InvalidCommitBlockInfo("struct elements".to_string()).into());
};
let abi::Token::Uint(new_l2_block_number) = block_elems[0].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("blockNumber".to_string()).into());
};

/* TODO(tuommaki): Fix the check below.
if new_l2_block_number <= latest_l2_block_number {
println!("skipping before we even get started");
continue;
match CommitBlockInfoV1::try_from(data) {
Ok(blk) => res.push(blk),
Err(e) => println!("failed to parse commit block info: {}", e),
}
*/

let abi::Token::Uint(timestamp) = block_elems[1].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("timestamp".to_string()).into());
};

let abi::Token::Uint(new_enumeration_index) = block_elems[2].clone() else {
return Err(ParseError::InvalidCommitBlockInfo(
"indexRepeatedStorageChanges".to_string(),
)
.into());
};
let new_enumeration_index = new_enumeration_index.0[0];

let abi::Token::FixedBytes(state_root) = block_elems[3].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("newStateRoot".to_string()).into());
};

let abi::Token::Uint(number_of_l1_txs) = block_elems[4].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("numberOfLayer1Txs".to_string()).into());
};

let abi::Token::FixedBytes(l2_logs_tree_root) = block_elems[5].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("l2LogsTreeRoot".to_string()).into());
};

let abi::Token::FixedBytes(priority_operations_hash) = block_elems[6].clone() else {
return Err(
ParseError::InvalidCommitBlockInfo("priorityOperationsHash".to_string()).into(),
);
};

let abi::Token::Bytes(initial_changes_calldata) = block_elems[7].clone() else {
return Err(
ParseError::InvalidCommitBlockInfo("initialStorageChanges".to_string()).into(),
);
};

if initial_changes_calldata.len() % 64 != 4 {
return Err(
ParseError::InvalidCommitBlockInfo("initialStorageChanges".to_string()).into(),
);
}
let abi::Token::Bytes(repeated_changes_calldata) = block_elems[8].clone() else {
return Err(
ParseError::InvalidCommitBlockInfo("repeatedStorageChanges".to_string()).into(),
);
};

let abi::Token::Bytes(l2_logs) = block_elems[9].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("l2Logs".to_string()).into());
};

// TODO(tuommaki): Are these useful at all?
/*
let abi::Token::Bytes(_l2_arbitrary_length_msgs) = block_elems[10].clone() else {
return Err(ParseError::InvalidCommitBlockInfo(
"l2ArbitraryLengthMessages".to_string(),
)
.into());
};
*/

// TODO(tuommaki): Parse factory deps
let abi::Token::Array(factory_deps) = block_elems[11].clone() else {
return Err(ParseError::InvalidCommitBlockInfo("factoryDeps".to_string()).into());
};

let mut smartcontracts = vec![];
for bytecode in factory_deps.into_iter() {
let abi::Token::Bytes(bytecode) = bytecode else {
return Err(ParseError::InvalidCommitBlockInfo("factoryDeps".to_string()).into());
};

match decompress_bytecode(bytecode) {
Ok(bytecode) => smartcontracts.push(bytecode),
Err(e) => println!("failed to decompress bytecode: {}", e),
};
}

assert_eq!(repeated_changes_calldata.len() % 40, 4);

println!(
"Have {} new keys",
(initial_changes_calldata.len() - 4) / 64
);
println!(
"Have {} repeated keys",
(repeated_changes_calldata.len() - 4) / 40
);

let mut blk = CommitBlockInfoV1 {
block_number: new_l2_block_number.as_u64(),
timestamp: timestamp.as_u64(),
index_repeated_storage_changes: new_enumeration_index,
new_state_root: state_root,
number_of_l1_txs,
l2_logs_tree_root,
priority_operations_hash,
initial_storage_changes: HashMap::default(),
repeated_storage_changes: HashMap::default(),
l2_logs: l2_logs.to_vec(),
factory_deps: smartcontracts,
};

for initial_calldata in initial_changes_calldata[4..].chunks(64) {
let mut t = initial_calldata.array_chunks::<32>();
let key = *t.next().ok_or_else(|| {
ParseError::InvalidCommitBlockInfo("initialStorageChanges".to_string())
})?;
let value = *t.next().ok_or_else(|| {
ParseError::InvalidCommitBlockInfo("initialStorageChanges".to_string())
})?;

if t.next().is_some() {
return Err(ParseError::InvalidCommitBlockInfo(
"initialStorageChanges".to_string(),
)
.into());
}

let _ = blk.initial_storage_changes.insert(key, value);
}

for repeated_calldata in repeated_changes_calldata[4..].chunks(40) {
let index = u64::from_be_bytes([
repeated_calldata[0],
repeated_calldata[1],
repeated_calldata[2],
repeated_calldata[3],
repeated_calldata[4],
repeated_calldata[5],
repeated_calldata[6],
repeated_calldata[7],
]);
let mut t = repeated_calldata[8..].array_chunks::<32>();
let value = *t.next().ok_or_else(|| {
ParseError::InvalidCommitBlockInfo("repeatedStorageChanges".to_string())
})?;

if t.next().is_some() {
return Err(ParseError::InvalidCommitBlockInfo(
"repeatedStorageChanges".to_string(),
)
.into());
}

blk.repeated_storage_changes.insert(index, value);
}

// --------------------------------------------------------------
// TODO(tuommaki): Handle rest of the fields:
// - factoryDeps -> compressed bytecode
// --------------------------------------------------------------

res.push(blk);
}

Ok(res)
}

fn decompress_bytecode(data: Vec<u8>) -> Result<Vec<u8>> {
/*
let num_entries = u32::from_be_bytes([data[3], data[2], data[1], data[0]]);
*/
let mut offset = 0;

let dict_len = u16::from_be_bytes([data[offset + 1], data[offset]]);

offset += 2;

let end = 2 + dict_len as usize;
let dict = data[offset..end].to_vec();
offset += end;
let encoded_data = data[offset..].to_vec();

// Each dictionary element should be 8 bytes. Verify alignment.
if dict.len() % 8 != 0 {
return Err(ParseError::InvalidCompressedByteCode(format!(
"invalid dict length: {}",
dict.len()
))
.into());
}

let dict: Vec<&[u8]> = dict.chunks(8).collect();

// Verify that dictionary size is below maximum.
if dict.len() > (1 << 16)
/* 2^16 */
{
return Err(ParseError::InvalidCompressedByteCode(format!(
"too many elements in dictionary: {}",
dict.len()
))
.into());
}

let mut bytecode = vec![];
for idx in encoded_data.chunks(2) {
let idx = u16::from_be_bytes([idx[0], idx[1]]) as usize;
if dict.len() <= idx {
return Err(ParseError::InvalidCompressedByteCode(format!(
"encoded data index ({}) exceeds dictionary size ({})",
idx,
dict.len()
))
.into());
}

bytecode.append(&mut dict[idx].to_vec());
}

Ok(bytecode)
}

#[cfg(test)]
mod tests {
use ethers::{
Expand Down
Loading

0 comments on commit 3ba7ecc

Please sign in to comment.