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

feat(l1-recover): DO NOT MERGE, work in progress #3174

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft

Conversation

tomg10
Copy link
Contributor

@tomg10 tomg10 commented Oct 25, 2024

No description provided.

fn commit_functions() -> Result<Vec<Function>> {
let contract = Self::hyperchain_contract();
Ok(vec![
contract.functions_by_name("commitBatches").unwrap()[0].clone(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function(...)

RollupEventType::Execute => "BlockExecution",
RollupEventType::NewPriorityTx => "NewPriorityRequest",
};
Ok(Self::hyperchain_contract().events_by_name(event_name)?[0].clone())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

event(...)

RollupEventType::Commit => 1,
RollupEventType::Prove => 2,
RollupEventType::Execute => 1,
_ => panic!("{event_type:?} event doesn't have l1_batch_number"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return error

event_type: RollupEventType,
log: &Log,
) -> PriorityOpId {
assert_eq!(event_type, RollupEventType::NewPriorityTx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need the event_type argument in the first place, if you are just asserting the value?

eth_client
}
/// Get the last published L1 block.
async fn get_last_l1_block_number(eth_client: &Box<DynClient<L1>>) -> Result<U64> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why it doesn't use &self instead?

.await?;

last_block
.expect("last block must be present")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return error

@@ -0,0 +1,1083 @@
use std::{cmp, cmp::PartialEq, collections::HashMap, future::Future, sync::Arc};

use anyhow::{anyhow, Result};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use "format_err" (alias of anyhow) instead. It is a more meaningful name

@@ -0,0 +1,1083 @@
use std::{cmp, cmp::PartialEq, collections::HashMap, future::Future, sync::Arc};

use anyhow::{anyhow, Result};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use qualified anyhow::Result instead, so as not to shadow the Result from prelude, especially since you define custom errors in this file as well.


fn hyperchain_contract() -> Contract {
hyperchain_contract()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not very useful method, isn't it :P

log: &Log,
) -> PriorityOpId {
assert_eq!(event_type, RollupEventType::NewPriorityTx);
L1Tx::try_from(log.clone()).unwrap().common_data.serial_id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return error

.await
.unwrap();
let mut end_block = last_l1_block_number;
// we use step override as such big step value guarantees that we don't miss
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment. Can you elaborate?

end_block = last_l1_block_number;
let mut current_block = start_block;
loop {
let filter_to_block = cmp::min(current_block + step - 1, end_block);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi, instead of cmp::min you can use min method (no imports required), for example
let filter_to_block = end_block.min(...).

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitBlock {
/// ZKSync batch number.
pub l1_batch_number: u64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not L1BatchNumber?

{
let commit_block_info = F::try_from(value)?;
Ok(Self::from_commit_block(commit_block_info.to_enum_variant()))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
.await;
let tx =
L1Fetcher::get_transaction_by_hash(&self.eth_client, logs[0].transaction_hash.unwrap())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you don't want to make these methods of L1Fetcher, then consider wrapping eth_client into yet another type instead and making functions like get_transaction_by_hash methods of that new type instead.

hyperchain_contract()
}

fn commit_functions() -> Result<Vec<Function>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any specific reason why those functions are members of L1Fetcher class?

) -> Vec<CommitBlock> {
let end_block = L1Fetcher::get_last_l1_block_number(&self.eth_client)
.await
.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return error

RollupEventsFilter::L1BatchNumber(l1_batch_number + 1),
)
.await
.unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return an error. In general in places that you can statically guarantee that an error will never occur (and therefore unwrap call is legitimate), document in a comment why it is the case.

// This code is compatible with both Infura and Alchemy API providers.
// Note: we don't handle rate-limits here - assumption is that we're never going to hit them.
if let Err(err) = &result {
tracing::warn!("Provider returned error message: {err}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general, by default choose "{err:#}" for error printing, as "{err}" tends to not display the cause.

};

// check whether the error is related to having too many results
if err_message.contains(TOO_MANY_RESULTS_INFURA)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to confirm: there is no error code for these?

// safety check to prevent infinite recursion (quite unlikely)
if from_number >= mid {
tracing::warn!("Infinite recursion detected while getting events: from_number={from_number:?}, mid={mid:?}");
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if to_number == from_number+1, then this case would trigger, while afaict it would be perfectly fine to split the range further into two 1-element ranges. Also the message is misleading, because the real problem is that a single block has too many events, not that our binary search has infinite recursion.

} else if should_retry(err_code, err_message) && retries_left > 0 {
tracing::warn!("Retrying. Retries left: {retries_left}");
result = self
.get_events_inner(from, to, topics1, topics2, addresses, retries_left - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it seems wasteful to use recursion for retries instead of a loop, no?
Also the layout/block nesting of this function could be improved, by inverting success and failure branches, i.e.:

let err = match result {
  Ok(res) => return res;
  Err(err) => err
};
// error handling.


async fn get_logs(
&self,
start_block: U64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it would be nice to have a dedicated type for L1 block numbers (or at least an alias), instead of plain U64

E: std::fmt::Display,
{
sleep(Duration::from_millis(100)).await;
for attempt in 1..=100 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a lot of retries. Why 100?

Fut: Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
sleep(Duration::from_millis(100)).await;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an artificial delay. Why is it needed?

.ok_or_else(|| anyhow!("found latest block, but it contained no block number"))
}

async fn retry_call<T, E, Fut>(callback: impl Fn() -> Fut, err: L1FetchError) -> Result<T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not map_err() at the call site?

match callback().await {
Ok(x) => return Ok(x),
Err(e) => {
tracing::error!("attempt {attempt}: failed to fetch from L1: {e}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto "{e:#}"

protocol_versioning: &ProtocolVersioning,
l1_block_number: u64,
commit_candidates: &[Function],
calldata: &[u8],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this parsing looks very manual. Can't we make it more robust by using ethabi library somehow? Also l1_contracts_interface could be a better place for this logic.

/// `MAX_RETRIES` is the maximum number of retries on failed blob retrieval.
const MAX_RETRIES: u8 = 5;
/// The interval in seconds to wait before retrying to fetch a blob.
const FAILED_FETCH_RETRY_INTERVAL_S: u64 = 10;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use time::Duration for this constant directly

},
Err(e) => {
tracing::error!("attempt {}: GET {} failed: {:?}", attempt, full_url, e);
sleep(Duration::from_secs(FAILED_FETCH_RETRY_INTERVAL_S)).await;
Copy link
Contributor

@pompon0 pompon0 Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invert control flow to avoid spurious indentation, deduplicate sleeps

if l1_batch_number == log_batch_number {
return Some(log);
} else {
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"else { continue }" is not needed

}

fn query_client(eth_client: &Box<DynClient<L1>>) -> &dyn EthInterface {
eth_client
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function does explicit casting to an interface. Does rust compiler have problems without it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants