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

State sync from l1 #1296

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

jerrybaoo
Copy link

Add sync state from L1 feature

Pull Request type

  • Feature

What is the current behavior?

Currently, there is no functionality to synchronize data from L1.

What is the new behavior?

  • When the functionality to synchronize state from L1 is enabled, the node fetches a trusted state diff from L1.
  • Node will package the state diff into a substrate block, which includes a starknet block header in the block header digest, and then apply it locally.
  • Due to the absence of a transaction list on L1, this feature cannot construct a complete blockchain; it can only build a complete state.

Does this introduce a breaking change?

No

@jerrybaoo jerrybaoo mentioned this pull request Dec 5, 2023
@tdelabro
Copy link
Collaborator

tdelabro commented Dec 6, 2023

Hey thanks for the good work.
Can I ask why did you develop that?
In the context of a Madara appchain, what is the use you envision for this? What should the chain do with that l1 state once it get it?

I think it can be used as a way to finalize the l2 state, but it needs something build on top of it. Did you had other stuffs in mind?

@tdelabro
Copy link
Collaborator

tdelabro commented Dec 6, 2023

Also, are you aware of this PR: #1282
It's about getting the l1->l2 messaging, by reading l1.
We are going to merge it on main soon.
Can you give it a look and tell me if it is redundant with your PR?

@jerrybaoo
Copy link
Author

jerrybaoo commented Dec 7, 2023

Hey thanks for the good work. Can I ask why did you develop that? In the context of a Madara appchain, what is the use you envision for this? What should the chain do with that l1 state once it get it?

I think it can be used as a way to finalize the l2 state, but it needs something build on top of it. Did you had other stuffs in mind?

Based on this issue #1224, we developed the code in this pull request (PR). This feature can be used to rapidly rebuild the state of L2 from L1.
After understanding the synchronization mechanisms of other Starknet sequencer implementation approaches, we found that they currently heavily rely on centralized feedline gateways, which are going to be phased out. The alternative to feedline gateways is full-node RPC. However, this remains fundamentally centralized as we must trust the linked RPC. Therefore, by utilizing data synchronized from L1, as the data on L1 is proven, we can rapidly build the L2 state without the need for trust assumptions.

@jerrybaoo
Copy link
Author

jerrybaoo commented Dec 7, 2023

Also, are you aware of this PR: #1282 It's about getting the l1->l2 messaging, by reading l1. We are going to merge it on main soon. Can you give it a look and tell me if it is redundant with your PR?

I've read the code, and there are no redundant with my PR. This PR (#1282) is about retrieving and applying messages from L1 to L2, whereas mine is about fetching L2 state from L1 and applying it locally.


/// When enable, the node will sync state from l1,
#[clap(long)]
pub sync_from_l1: Option<String>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

In ExtendedRunCommand pub base: RunCmd, pub network_params: NetworkParams, pub sync: SyncMode,` we find this, provided by substrate:

/// Syncing mode.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
#[value(rename_all = "kebab-case")]
pub enum SyncMode {
	/// Full sync. Download end verify all blocks.
	Full,
	/// Download blocks without executing them. Download latest state with proofs.
	Fast,
	/// Download blocks without executing them. Download latest state without proofs.
	FastUnsafe,
	/// Prove finality and download the latest state.
	Warp,
}

Do you think we can reuse those already defined modes? They look a bit similar to what we offer.

What is the list of the different sync mode we aim to support? I think we should define them.

Copy link
Author

@jerrybaoo jerrybaoo Dec 8, 2023

Choose a reason for hiding this comment

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

What you said makes a lot of sense. I think we can expand this enumeration. Currently, pub sync_from_l1: Option<String>, actually refers to a configuration file path, and the content of this configuration file at present is like this:

{
    "l1_start": 5854324,
    "core_contract": "0xde29d060D45901Fb19ED6C6e959EB22d8626708e",
    "verifier_contract": "0x5EF3C980Bf970FcE5BbC217835743ea9f0388f4F",
    "memory_page_contract": "0x743789ff2fF82Bfb907009C9911a7dA636D34FA7",
    "l2_start": 0,
    "l1_url_list": ["https://eth-goerli.g.alchemy.com/v2/nMMxqPTld6cj0DUO-4Qj2cg88Dd1MUhH","https://eth-goerli.g.alchemy.com/v2/AktNdFZZplqKaD2NrEfowmeYAwmEn4db"],
    "v011_diff_format_height": 28566,
    "constructor_args_diff_height": 4873
}

So, we can extend this enumeration to:

/// Syncing mode.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
#[value(rename_all = "kebab-case")]
pub enum SyncMode {
	/// Full sync. Download end verify all blocks.
	Full,
	/// Download blocks without executing them. Download latest state with proofs.
	Fast,
	/// Download blocks without executing them. Download latest state without proofs.
	FastUnsafe,
	/// Prove finality and download the latest state.
	Warp,
       /// Sync state from L1
       FromL1(String),
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Has this made it into the codebase in the end?

Copy link
Author

@jerrybaoo jerrybaoo Jan 17, 2024

Choose a reason for hiding this comment

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

The SyncMode defined in substrate specifies the synchronization method between nodes within the same network. However, sync from l1 is another type of synchronization that only exists within the rollup chain. Therefore, we cannot reuse the substrate SyncMode. Additionally, extending Substrate SyncMode is not quite suitable since substrate does not provide rollup functionality. Therefore, it is more appropriate to continue placing this command parameter in the ExtendedRunCommand.

@antiyro
Copy link
Contributor

antiyro commented Dec 7, 2023

thanks for this impl! finishing some stuff on the rpc side and I'll deep dive here 👍

@antiyro
Copy link
Contributor

antiyro commented Jan 6, 2024

Reviewing it now, my apologies for the delays I've been busy bumping madara specs.

@antiyro
Copy link
Contributor

antiyro commented Jan 6, 2024

@jerrybaoo could you please sync with upstream when you have time so I can test it locally?

@jerrybaoo
Copy link
Author

@jerrybaoo could you please sync with upstream when you have time so I can test it locally?

Okay, I'll start syncing with upstream now.

@antiyro
Copy link
Contributor

antiyro commented Jan 7, 2024

Thanks!

@jerrybaoo
Copy link
Author

jerrybaoo commented Jan 7, 2024

Thanks!

Sync with upstream has been completed. Additionally, we have a quick start guide that we hope will be helpful to you. Alternatively, we can include it in the Readme.

pub struct L1L2BlockMapping {
pub l1_block_hash: H256,
pub l1_block_number: u64,
pub l2_block_hash: U256,
Copy link
Collaborator

Choose a reason for hiding this comment

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

We are talking starknet hash here, not substrate hash.
Can we add a comment to make it explicit?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is it a U256 and not a H256? We are talking about a hash

Copy link
Author

Choose a reason for hiding this comment

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

Why is it a U256 and not a H256? We are talking about a hash

You are right, this is a mistake.

Copy link
Author

Choose a reason for hiding this comment

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

We are talking starknet hash here, not substrate hash. Can we add a comment to make it explicit?

Indeed, it is necessary to provide detailed comments explaining the mapping between L1 block hash and starknet block hash.

pub struct EthOrigin {
block_hash: H256,
block_number: u64,
_transaction_hash: H256,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is it here if it's never used?

Copy link
Author

Choose a reason for hiding this comment

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

Indeed, it is not being used and has already been removed.

Comment on lines 37 to 38
eth_origin: EthOrigin,
update: LogStateUpdate,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Both field contains block_hash and block_number. It looks like duplicated data, is it? Or is there a good reason to this?

Copy link
Author

Choose a reason for hiding this comment

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

There is no duplicate data; they have different meanings. eth_origin represents the L2 rollup data to L1, including the block and transaction information for this transaction. On the other hand, update conveys information about the L2 state update.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It is misleading for something called EthOrigin to be about l2, which is not eth.
Can you figure out a way to make it more understandable (changing names or adding more doc)?

Copy link
Author

Choose a reason for hiding this comment

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

I have indeed renamed it to avoid confusion and added comments for clarity.

pub block_hash: U256,
}

/// Ethereum contract event representing a log state update in old contract.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add a bit more background on that, please? When was the update from old to new done (at which block) and link the code of both

Copy link
Author

@jerrybaoo jerrybaoo Jan 17, 2024

Choose a reason for hiding this comment

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

In the testnet, we noticed an upgrade in the Starknet Core contract, leading to an additional field in the LogStateUpdate event. This adjustment is made solely for forward compatibility with the older events. If there is no need to maintain consistency with the Starknet mainnet and testnet, you may safely ignore LogStateUpdateOld.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Add it to the doc. I think it's interesting for people to understand that

Copy link
Author

Choose a reason for hiding this comment

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

Some comment documentation has been added.

return Ok(LogStateUpdate {
global_root: update.global_root,
block_number: update.block_number,
block_hash: Default::default(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this safe?
Wouldnt' it be better to have an enum

pub enum LogStateUpdate {
  Old(..),
  New(..)
}

Copy link
Author

@jerrybaoo jerrybaoo Jan 17, 2024

Choose a reason for hiding this comment

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

In the current processing logic, if the L2 block hash cannot be obtained from the chain, a local l2 block hash will be computed. culcualte l2 block hash.

During the calculation of the local block hash, we are unable to retrieve all the information of the L2 block header. Therefore, this block hash cannot be computed correctly. To ensure security, it is crucial to find a method to obtain the correct block hash.

Do you have any good ideas about this? Is it possible for us not to consider compatibility with such changes in Starknet? In fact, we haven't found any documentation explaining these changes; we've only observed the changes in contract events from the chain explorer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@antiyro how did you manage that? When getting data from l1, before some contract update the l2 block hash wasn't part of the payload. How to you manage to still retrieve the blockhash?

crates/client/state-sync/src/ethereum.rs Outdated Show resolved Hide resolved

let state_updates = self.query_state_update(l1_from, l2_start).await?;
let tasks = state_updates.iter().map(|updates| {
debug!(target: LOG_TARGET, "crate task fro update l1:{} l2: {}", updates.eth_origin.block_number, updates.update.block_number);
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo fro

Copy link
Author

Choose a reason for hiding this comment

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

fixed

crates/client/state-sync/src/ethereum.rs Outdated Show resolved Hide resolved
Comment on lines 652 to 658
let mut states_res = Vec::new();
for fetched_state in fetched_states {
match fetched_state {
Ok(state) => states_res.push(state),
Err(e) => return Err(e),
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

let states_res: Result<Vec<_>> = fetched_states.into_iter().collect();

https://stackoverflow.com/a/63798748/9967008

Copy link
Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 432 to 445
/// Error occurring during transaction construction with a specific message.
ConstructTransaction(String),
/// Error while committing data to storage with a specific message.
CommitStorage(String),
/// Error related to connection issues with L1 chain with a specific message.
L1Connection(String),
/// Error decoding an event from L1.
L1EventDecode,
/// Error related to state handling on L1 with a specific message.
L1StateError(String),
/// Error due to a type mismatch or inconsistency with a specific message.
TypeError(String),
/// Any other unspecified error with a specific message.
Other(String),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't use a String as a way to represent the original error.
Use the error type itself.
You can achieve this easily with thiserror and their #[from] macro

Copy link
Author

@jerrybaoo jerrybaoo Jan 18, 2024

Choose a reason for hiding this comment

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

I have rewritten the Error enum using thiserror.

Copy link

There hasn't been any activity on this pull request recently, and in order to prioritize active work, it has been marked as stale.
This PR will be closed and locked in 7 days if no further activity occurs.
Thank you for your contributions!

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

Successfully merging this pull request may close these issues.

None yet

5 participants