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: L3 support #437

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

feat: L3 support #437

wants to merge 27 commits into from

Conversation

ocdbytes
Copy link
Member

@ocdbytes ocdbytes commented Dec 20, 2024

Added support for L3s. Starknet client is added in this PR and tests are added for several client functions. for now we are assuming that the l3 gas prices will be zero only.

Pull Request type

Please add the labels corresponding to the type of changes your PR introduces:

  • Feature
  • Testing

What is the current behavior?

Currently we didn't support the L3 appchain spec.

What is the new behavior?

Included the starknet client and updated the cli arguments for running madara in appchain mode. For now for all the gas prices and metrics we are still using the l1 modules. We can cover this refactoring in another PR.

Key Changes

  1. Initially we had only one client for settlement (crates/madara/client/eth) but now the folder structure is little bit changed in order to add two clients. crates/madara/client/eth is renamed to crates/madara/client/settlement_client :
├── Cargo.toml
├── README.md
└── src /
    ├── client.rs
    ├── error.rs
    ├── eth /
    │   ├── event.rs
    │   ├── mod.rs
    │   └── starknet_core.json
    ├── gas_price.rs
    ├── lib.rs
    ├── messaging.rs
    ├── starknet /
    │   ├── event.rs
    │   ├── mod.rs
    │   └── utils.rs
    ├── state_update.rs
    ├── sync.rs
    └── utils.rs
  1. Added automock for client testing. automock in client
  2. Also Added seperate tests based on mocking the client for messaging and state update listeners.
  3. In this PR wrappers for event stream is added for ETH which basically takes the alloy eth event stream and puts a stream wrapper implemented in order to make event processing easier. Stream Wrapper.Here the stream wrapper is not generalised and is hard coded to return the Messaging stream in another PR we should make the event stream generalsie din order to be used with any event listener from alloy or any other lib we use in future.
  4. We have implemented separate event streams for eth and starknet, as starknet does not have .watch() function for listening to events we have added a custom stream implementation for starknet (Starknet Stream Implementation)
  5. We have also changes the input type of eth_core_contract_address and eth_gps_statement_verifier in chain config in order to be more accomodating to other types we might use in future and we are currently using.

Important

For now as the PR is itself very long and connot be breaked into components because of inter component and e2e messaging tests. All the variables which are related to l1 are not renamed rn as it will be more time consuming and has no impact on functionality as of now.

Simple Diagram for messaging compoenents

image

Does this introduce a breaking change?

No

@apoorvsadana apoorvsadana marked this pull request as draft December 23, 2024 03:40
.gitignore Show resolved Hide resolved
crates/client/settlement_client/Cargo.toml Outdated Show resolved Hide resolved
crates/client/settlement_client/src/client.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/client.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/eth/mod.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/starknet/utils.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/starknet/mod.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/starknet/mod.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/starknet/mod.rs Outdated Show resolved Hide resolved
crates/node/src/service/l1.rs Outdated Show resolved Hide resolved
@Trantorian1 Trantorian1 added the research Research and exploration required before implementation label Dec 31, 2024
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/client.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved
Comment on lines 54 to 74
while !page_indicator {
let events = provider
.get_events(
EventFilter {
from_block: filter.from_block,
to_block: filter.to_block,
address: filter.address,
keys: filter.keys.clone(),
},
continuation_token.clone(),
1000,
)
.await?;

event_vec.extend(events.events);
if let Some(token) = events.continuation_token {
continuation_token = Some(token);
} else {
page_indicator = true;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

this code exists in get_events in mod.rs as well, can we use that?

Copy link
Member Author

Choose a reason for hiding this comment

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

That cannot be used unfortunately as there are some constraints in stream implementation.
So when I used the function it kills the stream when no event is returned. So there are no new calls to the stream.
I am checking more on this behaviour on why this is happening.

crates/client/settlement_client/src/starknet/event.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@Mohiiit Mohiiit left a comment

Choose a reason for hiding this comment

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

I need some more context in order to understand the event structure a bit better, by structure I mean, how we are fetching for both cases and how is cancelling of messages is working.

crates/client/settlement_client/Cargo.toml Outdated Show resolved Hide resolved
crates/client/settlement_client/src/client.rs Outdated Show resolved Hide resolved
let block_number = self.provider.get_block_number().await?.as_u64();
Ok(block_number)
}

/// Get the block number of the last occurrence of a given event.
pub async fn get_last_event_block_number<T: SolEvent>(&self) -> anyhow::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 have we removed this? ideally this function should be able to filter out on any event right? if not let's update it's name and comment accordingly?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is not removed get_last_event_block_number function still exists although it is not used anywhere, but it is still there in the client trait implementation.

Copy link
Contributor

@Mohiiit Mohiiit Jan 10, 2025

Choose a reason for hiding this comment

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

my bad for wrong comment, I meant why we removed <T: SolEvent>?

Copy link
Member Author

Choose a reason for hiding this comment

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

So basically It was removed to make the ClientTrait more generalised.

Copy link
Contributor

Choose a reason for hiding this comment

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

ahh I see, so can we change the name of the function here? get_last_state_update_event_block_number, given it was generic earlier?

crates/client/settlement_client/src/eth/mod.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved

// Initialize database service
let db = Arc::new(
DatabaseService::new(&base_path, backup_dir, false, chain_info.clone(), Default::default())
Copy link
Contributor

Choose a reason for hiding this comment

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

open_for_testing can be used instead of new

crates/client/settlement_client/src/state_update.rs Outdated Show resolved Hide resolved
crates/node/src/cli/chain_config_overrides.rs Outdated Show resolved Hide resolved
crates/node/src/main.rs Outdated Show resolved Hide resolved
test-artifacts/devnet.yaml Outdated Show resolved Hide resolved
crates/node/src/service/l1.rs Outdated Show resolved Hide resolved
devnet: bool,
mempool: Arc<Mempool>,
) -> anyhow::Result<Box<dyn Service>> {
match config.settlement_layer {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we add the if !config.l1_sync_disabled && (config.l1_endpoint.is_some() || !devnet) else {} part that is common in eth and starknet here? we can have this match statement inside the if part and it will return the client

Copy link
Contributor

Choose a reason for hiding this comment

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

as discussed this common logic should be in the create function

crates/client/settlement_client/src/eth/mod.rs Outdated Show resolved Hide resolved
// Get the latest block number
async fn get_latest_block_number(&self) -> anyhow::Result<u64>;

// Get the block number of the last occurrence of a specific event
Copy link
Contributor

Choose a reason for hiding this comment

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

let's change the comment, given it's not generic now

Suggested change
// Get the block number of the last occurrence of a specific event
// Get the block number of the last occurrence of the state update event

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Ok(event_data) => {
assert_eq!(event_data.block_number, 100);
assert_eq!(event_data.event_index, Some(0u64));
// Add more assertions as needed
Copy link
Contributor

Choose a reason for hiding this comment

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

is this WIP?

Copy link
Member Author

Choose a reason for hiding this comment

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

nop removed missed it used claude for generating the tests.

async fn process_message(
backend: &MadaraBackend,
event: &CommonMessagingEventData,
_chain_id: &ChainId,
Copy link
Contributor

Choose a reason for hiding this comment

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

let's remove this? given it's not required

Copy link
Member Author

Choose a reason for hiding this comment

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

So this param was there in the eth implementation also so I thought maybe it is left for some future functionality. Should I remove this ?

// Block number management in case of pending block number events.
match event.block_number {
Some(block_number) => Ok(block_number),
None => Ok(self.get_latest_block_number().await? + 1),
Copy link
Contributor

Choose a reason for hiding this comment

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

in starknet, we get events as soon as the transactions are executed? and that's why we might get last event from the pending block?

@@ -1,37 +1,32 @@
use std::sync::Arc;

<<<<<<<< HEAD:crates/madara/client/settlement_client/src/state_update.rs
Copy link
Contributor

Choose a reason for hiding this comment

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

missed these

Copy link
Member Author

Choose a reason for hiding this comment

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

removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

we have this in primitives/utils/src/hash.rs

Copy link
Member Author

Choose a reason for hiding this comment

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

removed maybe left during the merge

Comment on lines 154 to 160
while let Some(events) = ctx
.run_until_cancelled(self.get_events(
BlockId::Number(self.get_latest_block_number().await?),
BlockId::Number(self.get_latest_block_number().await?),
self.l2_core_contract,
vec![get_selector_from_name("LogStateUpdate")?],
))
Copy link
Contributor

Choose a reason for hiding this comment

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

can move this to a closure for better readability as discussed

crates/madara/client/settlement_client/src/state_update.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/client.rs Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

ideally we should create an issue for a test case/inform MSL where if a block has 2 events and madara processes one and shuts down, then on restart the next message is picked up of that same block

crates/madara/client/settlement_client/src/eth/mod.rs Outdated Show resolved Hide resolved
}
Poll::Pending => Poll::Pending,
},
None => Poll::Ready(Some(None)), // Handle the case where future is None
Copy link
Contributor

Choose a reason for hiding this comment

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

i think we should throw an error here to inform users something went wrong

crates/madara/client/settlement_client/src/eth/mod.rs Outdated Show resolved Hide resolved
crates/client/settlement_client/src/messaging/sync.rs Outdated Show resolved Hide resolved
@Trantorian1 Trantorian1 added sequencer Related to the sequencing logic and implementation node Related to the full node implementation labels Jan 23, 2025
@Mohiiit Mohiiit marked this pull request as ready for review January 31, 2025 13:45
Copy link
Collaborator

@Trantorian1 Trantorian1 left a comment

Choose a reason for hiding this comment

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

I glossed over the last few files, so there could be some stuff I didn't catch. Overall this seems interesting but needs as few changes here and there, nothing major.

Comment on lines +42 to +50
# TODO : For now madara binary is stored in aws s3 bucket :
# After the proper release binaries are implemented
# We can directly use that and we can remove this
# temporary AWS implementation
- name: Download madara binary for l2 client testing
run: |
curl -L https://madara-test-binary.s3.us-west-1.amazonaws.com/madara-linux -o ./test-artifacts/madara
chmod +x ./test-artifacts/madara

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we downloading a madara binary for testing instead of running against a build of the code in the pr?

Comment on lines +37 to +40
- name: Download madara binary for l2 client testing
run: |
curl -L https://madara-test-binary.s3.us-west-1.amazonaws.com/madara-linux -o ./test-artifacts/madara
chmod +x ./test-artifacts/madara
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment on lines +23 to +32
- name: Launch Anvil
run: anvil --fork-url $ANVIL_FORK_URL --fork-block-number $ANVIL_BLOCK_NUMBER &
env:
ANVIL_FORK_URL: "https://eth.merkle.io"
ANVIL_BLOCK_NUMBER: 20395662
- name: Wait for Anvil to be ready
run: |
while ! nc -z localhost 8545; do
sleep 1
done
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we sure this is necessary? If I remember the rust unit tests should start anvil anyway no?

Comment on lines +8035 to +8058
[[package]]
name = "serial_test"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
dependencies = [
"futures",
"log",
"once_cell",
"parking_lot 0.12.3",
"scc",
"serial_test_derive",
]

[[package]]
name = "serial_test_derive"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should not be here. I believe we removed serial-test from madara so the CI can run in parallel. Consider implementing a locking mechanism by hand otherwise, I am not convinced we need an external crate for this kind of simple functionality.

@@ -222,6 +220,7 @@ dotenv = "0.15.0"
httpmock = "0.7.0"
tempfile = "3.10.1"
mockall = "0.13.0"
serial_test = "3.1.1"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto

}

#[rstest]
#[traced_test]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto

#[tokio::test]
// This test is redundant now as the event poller will not return the same
// event twice with same nonce that's why added ignore here.
#[ignore]
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is redundant then consider removing it

}

#[rstest]
#[traced_test]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto

use tempfile::TempDir;
use url::Url;

#[rstest]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we using rstest if there is no fixture?

Comment on lines +15 to +23
backend: Arc<MadaraBackend>,
settlement_client: Arc<Box<dyn ClientTrait<Config = C, StreamType = S>>>,
chain_id: ChainId,
l1_gas_provider: GasPriceProvider,
gas_price_sync_disabled: bool,
gas_price_poll_ms: Duration,
mempool: Arc<Mempool>,
ctx: ServiceContext,
l1_block_metrics: Arc<L1BlockMetrics>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider refactoring some of these arguments behind a struct.

@@ -3,6 +3,7 @@
## Next release

- fix(primitives): limit legacy class sizes
- feat : l3 support
Copy link
Member

Choose a reason for hiding this comment

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

you can remove it

Copy link
Member

Choose a reason for hiding this comment

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

Please update the dependencies to use workspace instead of fixed versions to ensure consistency, avoid version conflicts, and simplify dependency management

Comment on lines +19 to +26
#[derive(Debug, Default, PartialEq)]
pub struct DummyConfig;
pub type DummyStream = BoxStream<'static, Option<anyhow::Result<CommonMessagingEventData>>>;

#[automock(
type Config = DummyConfig;
type StreamType = DummyStream;
)]
Copy link
Member

Choose a reason for hiding this comment

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

Consider moving this behind a "test" feature flag to avoid including them in production builds.

/// - 0 if the message has not been cancelled
/// - timestamp of the cancellation if it has been cancelled
/// - An Error if the call fail
async fn get_l1_to_l2_message_cancellations(&self, msg_hash: Vec<u8>) -> anyhow::Result<Felt>;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
async fn get_l1_to_l2_message_cancellations(&self, msg_hash: Vec<u8>) -> anyhow::Result<Felt>;
async fn get_l1_to_l2_message_cancellations(&self, msg_hash: &[u8]) -> anyhow::Result<Felt>;

Comment on lines +35 to +38
// Create a new instance of the client
async fn new(config: Self::Config) -> anyhow::Result<Self>
where
Self: Sized;
Copy link
Member

Choose a reason for hiding this comment

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

Consider removing new from the trait and implementing it as an associated function in the struct to avoid issues with Self: Sized, async trait limitations, and the inability to use it with dyn ClientTrait

let current_processed = self.processed_update_state_block.load(Ordering::Relaxed);
if current_processed < block_number {
let formatted_event =
StateUpdate { block_number, global_root: data.data[0], block_hash: data.data[2] };
Copy link
Member

Choose a reason for hiding this comment

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

ditto (direct access)

let events_fetched = events?;
if let Some(event) = events_fetched.last() {
let data = event;
let block_number = data.data[1].to_u64().ok_or(anyhow!("Block number conversion failed"))?;
Copy link
Member

Choose a reason for hiding this comment

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

ditto (direct access)

Comment on lines +275 to +283
fn event_to_felt_array(&self, event: &CommonMessagingEventData) -> Vec<Felt> {
let mut felt_vec = vec![event.from, event.to, event.selector, event.nonce];
felt_vec.push(Felt::from(event.payload.len()));
event.payload.clone().into_iter().for_each(|felt| {
felt_vec.push(felt);
});

felt_vec
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
fn event_to_felt_array(&self, event: &CommonMessagingEventData) -> Vec<Felt> {
let mut felt_vec = vec![event.from, event.to, event.selector, event.nonce];
felt_vec.push(Felt::from(event.payload.len()));
event.payload.clone().into_iter().for_each(|felt| {
felt_vec.push(felt);
});
felt_vec
}
fn event_to_felt_array(&self, event: &CommonMessagingEventData) -> Vec<Felt> {
std::iter::once(event.from)
.chain(std::iter::once(event.to))
.chain(std::iter::once(event.selector))
.chain(std::iter::once(event.nonce))
.chain(std::iter::once(Felt::from(event.payload.len())))
.chain(event.payload.iter().cloned())
.collect()
}

}
}

pub fn starknet_account() -> anyhow::Result<StarknetAccount> {
Copy link
Member

Choose a reason for hiding this comment

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

Is it only for testing?

db_backend: Arc<MadaraBackend>,
eth_client: Option<Arc<EthereumClient>>,
settlement_client: Option<Arc<Box<dyn ClientTrait<Config = C, StreamType = S>>>>,
Copy link
Member

Choose a reason for hiding this comment

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

Is Box really necessary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
node Related to the full node implementation research Research and exploration required before implementation sequencer Related to the sequencing logic and implementation
Projects
Status: In progress
Development

Successfully merging this pull request may close these issues.

5 participants