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

(WIP) feat: sequencer #179

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open

Conversation

borispovod
Copy link
Contributor

Hello everyone,

This PR includes changes we've made for running the sequencer on top of Magi. It also impacts types, configurations (additional parameters and refactoring), networking (broadcast and configuration), and RPC (implementation of missing endpoints).

I would appreciate a review, particularly if you notice something functioning incorrectly, and I'd like to hear your opinions on the changes, etc. In the future, I believe the sequencer should be moved into its own module, launched by a timer, etc. This is planned as part of future updates.

Additionally, I have primarily tested it on our devnet, so the tests should probably also be run on top of existing networks.

Thank you.

P.S. Sorry for the single commit. We have a monorepo, and it would be difficult to separate all the changes into individual commits. I hope we can improve this process in the future.

@LEAFERx
Copy link
Contributor

LEAFERx commented Nov 27, 2023

Hi I think run_sequencer_step implementation src/driver/mod.rs:347 is incorrect (https://github.com/a16z/magi/pull/179/files#diff-c97e92b44dfb27733ada13c547be940cddfd184bc1a87dd32fa5046916f117caR347)
The current build_payload implementation will call get_payload right after the forkchoice_update call, which gives the execution client no time to build the attributes with transactions in the txpool.

see opnode implementation: https://github.com/ethereum-optimism/optimism/blob/develop/op-node/rollup/driver/sequencer.go#L125
it essentially waits blocktime - sealingtime before you call get_payload.

also see Engine API spec:

(Payload Building)
Client software SHOULD start the process of updating the payload. The strategy of this process is implementation dependent. The default strategy is to keep the transaction set up-to-date with the state of local mempool.

Client software SHOULD stop the updating process when either a call to engine_getPayload with the build process's payloadId is made or SECONDS_PER_SLOT (12s in the Mainnet configuration) have passed since the point in time identified by the timestamp parameter.

(engine_getPayload)
Given the payloadId client software MUST return the most recent version of the payload that is available in the corresponding build process at the time of receiving the call.

@borispovod
Copy link
Contributor Author

borispovod commented Nov 27, 2023

Hey @LEAFERx,

Thank you for your catch.
We tested it on devnet with Geth and later with Reth; however, we have our own Reth fork, so the behavior might be slightly different.

I will look into it shortly. However, block generations, along with transactions, have worked and continue to work. Still, I agree that we should implement sealingtime according to the protocol.

Copy link
Contributor

@ncitron ncitron left a comment

Choose a reason for hiding this comment

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

This is awesome! I just gave it a first pass and left some comments, and will probably make another pass through it to make sure I didn't miss anything since this PR is so large.

I'll also work on getting this synced to some of the op stack chains to make sure there are no regressions.

bin/magi.rs Outdated
#[clap(long = "sequencer-enabled")]
sequencer_enabled: bool,
#[clap(long = "sequencer-max-safe-lag", default_value = "0")]
sequencer_max_safe_lag: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a string? Could we use an unsigned integer here instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

bin/magi.rs Show resolved Hide resolved
bin/network.rs Outdated
@@ -1,28 +1,91 @@
use std::str::FromStr;
#![allow(unused_imports)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this and delete any unused imports or is there another reason this is here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, as I understood, the idea behind bin/network.rs is just for testing purposes, so we've added code to generate its own Enr and commented it out, and kept imports, will fix that part.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

bin/network.rs Outdated
// let bootnodes = vec![enr];

let bootnodes: Vec<discv5::enr::Enr<CombinedKey>> =
vec![Enr::from_str("enr:-Je4QKqISnjZwcUSRQqLTbOoqFvmQX8sVlPIud5sWPrUp_8hPJXnzSyY-fqXhzqWGKDHjNSLJRbBGjC9VILm_HGuhHkBgmlkgnY0gmlwhH8AAAGHb3BzdGFja4OFBwCJc2VjcDI1NmsxoQMqv564GlblO4zWKiGSn0-lcr70dYrzwiieFETLNEy8xoN0Y3CCJvyDdWRwgib8").map_err(|e| eyre::eyre!("err: {}", e))?];
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this node coming from? Can we make sure that this uses the optimism mainnet ones like as we specify in discovery::bootnodes?

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

bin/network.rs Outdated
let (_, recv) = watch::channel(Address::from_str(
"0x715b7219d986641df9efd9c7ef01218d528e19ec",
"0xF64c29538cAE4E69eac62c50CDfebAC22b378044",
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also keep this as the op mainnet value?

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

prev_randao,
suggested_fee_recipient,
transactions,
no_tx_pool: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same concern as earlier with this not matching the spec, or is it supposed to add new transactions to the block since it is only run during the sequencer step?

Copy link
Contributor Author

@borispovod borispovod Nov 30, 2023

Choose a reason for hiding this comment

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

Well, i think i need to rework that part as i already told.
Yet would need some time.


pub async fn get_finalized<P: InnerProvider>(p: &P, chain: &ChainConfig) -> HeadInfo {
let block_number = BlockNumber::Finalized;
Self::get_head_info(p, block_number)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can we refactor these three to use an internal function to dedupe the unwrap_or_else handling? Maybe we can call it get_head_info_or_genesis or something similar?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good idea. Also, we noticed that if we relaunch the L2 node and, at the same time, launch Magi, it can roll back heads to the genesis ones. So, I think maybe we should add additional checks there.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

/// Produces a block if the conditions are met.
/// If successful the block would be signed by sequencer and shared by P2P.
async fn run_sequencer_step(&mut self) -> Result<()> {
if let Some(seq_config) = self.sequencer_config.clone() {
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 can avoid cloning the entire config here by doing an as_ref or something.,

Copy link
Contributor

Choose a reason for hiding this comment

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

Although I guess since its just a uint in there it doesn't really matter. But if more stuff ends up being added to that config it could become a big clone.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

async fn run_sequencer_step(&mut self) -> Result<()> {
if let Some(seq_config) = self.sequencer_config.clone() {
// Get unsafe head to build a new block on top of it.
let head = self.engine_driver.unsafe_info.head;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to unsafe_head to match with the next line and avoid any confusion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

let head = self.engine_driver.unsafe_info.head;
let unsafe_epoch = self.engine_driver.unsafe_info.epoch;

if seq_config.max_safe_lag() > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if this extra check here really matters in terms of efficiency. Might be better to just remove it for readability reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you want to remove the parameter at all?

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 think that we need to remove the parameter, but doesn't the check on L294 cover this case, making it redundant, or am I misunderstanding something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we remove the zero check, the code will always return max safe lag reached... when max_safe_lag == 0, because safe block + 0 <= unsafe will always be true. As a result, no new blocks would be generated, among other issues.

We could use an Option for max_safe_lag, but I still think there would be issues if someone passes 0 as the value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see that makes sense!

@0x677261706562616261
Copy link

what is the progress of this PR?

@dk-pontem
Copy link
Contributor

dk-pontem commented Jan 21, 2024

@0x677261706562616261

what is the progress of this PR?

I'd like @borispovod to look into it before review of a16z team.

@borispovod
Copy link
Contributor Author

I think most of issues fixed by me and @dk-pontem , we are going to do review, unit tests, manual tests

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.

None yet

5 participants