-
Notifications
You must be signed in to change notification settings - Fork 5
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
Encapsulate field parsing #4
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||||||||||||||||
|
@@ -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!(); | ||||||||||||||||
|
@@ -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() | ||||||||||||||||
)) | ||||||||||||||||
|
@@ -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()); | ||||||||||||||||
|
@@ -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), | ||||||||||||||||
} | ||||||||||||||||
Comment on lines
+93
to
96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Don't think there's any need to print out the errors, I think we'd rather implement some tracing later down the line and log that error or even There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather keep this. In my experience these can easily save hours of debugging during development. Often times, some very minor unrelated mistake happens and if there's no observability, one really has to debug weird behaviour until the root cause is found. Having this kind of debug print shortens time of diagnostics a lot. Referring to earlier error handling, I'd leave |
||||||||||||||||
*/ | ||||||||||||||||
|
||||||||||||||||
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::{ | ||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These shared error types here are ugly as hell and don't feel right, but let's fix these separately, if that's ok?