diff --git a/Cargo.lock b/Cargo.lock index a12943fb1..e9d9b2f72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,6 +726,7 @@ dependencies = [ "alpen-vertex-state", "anyhow", "arbitrary", + "bincode", "bitcoin", "borsh", "parking_lot 0.12.3", @@ -749,9 +750,12 @@ dependencies = [ name = "alpen-vertex-evmexec" version = "0.1.0" dependencies = [ + "alpen-test-utils", + "alpen-vertex-db", "alpen-vertex-evmctl", "alpen-vertex-primitives", "alpen-vertex-state", + "anyhow", "arbitrary", "borsh", "futures", @@ -766,9 +770,12 @@ dependencies = [ "reth-rpc-api", "reth-rpc-layer", "reth-rpc-types", + "reth-rpc-types-compat", "serde", + "thiserror", "tokio", "tower", + "tracing", ] [[package]] @@ -804,6 +811,23 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "alpen-vertex-reth" +version = "0.1.0" +dependencies = [ + "alloy-genesis", + "clap", + "eyre", + "reth", + "reth-chainspec", + "reth-db", + "reth-node-ethereum", + "serde_json", + "tokio", + "toml 0.5.11", + "tracing", +] + [[package]] name = "alpen-vertex-rpc-api" version = "0.1.0" @@ -825,6 +849,7 @@ dependencies = [ "alpen-vertex-consensus-logic", "alpen-vertex-db", "alpen-vertex-evmctl", + "alpen-vertex-evmexec", "alpen-vertex-primitives", "alpen-vertex-rpc-api", "alpen-vertex-state", @@ -1850,6 +1875,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata 0.4.7", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1954,6 +1990,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.1.5" @@ -2102,6 +2153,31 @@ dependencies = [ "memchr", ] +[[package]] +name = "comfy-table" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +dependencies = [ + "crossterm", + "strum", + "strum_macros", + "unicode-width", +] + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "concat-kdf" version = "0.1.0" @@ -2123,6 +2199,18 @@ dependencies = [ "toml 0.8.14", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "const-hex" version = "1.12.0" @@ -2307,6 +2395,31 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot 0.12.3", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -2819,6 +2932,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -3561,6 +3680,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "human_bytes" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" + [[package]] name = "humantime" version = "2.1.0" @@ -4889,6 +5014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -5982,6 +6108,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "ratatui" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.13.0", + "lru", + "paste", + "stability", + "strum", + "strum_macros", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "raw-cpuid" version = "11.0.2" @@ -6208,6 +6355,85 @@ dependencies = [ "quick-error", ] +[[package]] +name = "reth" +version = "1.0.1" +source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.0.1#d599393771f9d7d137ea4abf271e1bd118184c73" +dependencies = [ + "alloy-rlp", + "aquamarine", + "backon", + "clap", + "confy", + "discv5", + "eyre", + "fdlimit", + "futures", + "itertools 0.13.0", + "libc", + "metrics-process", + "reth-basic-payload-builder", + "reth-beacon-consensus", + "reth-blockchain-tree", + "reth-chainspec", + "reth-cli-commands", + "reth-cli-runner", + "reth-cli-util", + "reth-config", + "reth-consensus", + "reth-consensus-common", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-downloaders", + "reth-engine-util", + "reth-errors", + "reth-ethereum-payload-builder", + "reth-evm", + "reth-execution-types", + "reth-exex", + "reth-fs-util", + "reth-network", + "reth-network-api", + "reth-network-p2p", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-node-events", + "reth-optimism-primitives", + "reth-payload-builder", + "reth-payload-primitives", + "reth-payload-validator", + "reth-primitives", + "reth-provider", + "reth-prune", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-rpc-types", + "reth-rpc-types-compat", + "reth-stages", + "reth-stages-api", + "reth-static-file", + "reth-static-file-types", + "reth-tasks", + "reth-tracing", + "reth-transaction-pool", + "reth-trie", + "serde", + "serde_json", + "similar-asserts", + "tempfile", + "tikv-jemallocator", + "tokio", + "toml 0.8.14", + "tracing", +] + [[package]] name = "reth-auto-seal-consensus" version = "1.0.1" @@ -6353,6 +6579,63 @@ dependencies = [ "serde_json", ] +[[package]] +name = "reth-cli-commands" +version = "1.0.1" +source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.0.1#d599393771f9d7d137ea4abf271e1bd118184c73" +dependencies = [ + "ahash", + "backon", + "clap", + "comfy-table", + "confy", + "crossterm", + "eyre", + "fdlimit", + "human_bytes", + "itertools 0.13.0", + "metrics-process", + "ratatui", + "reth-beacon-consensus", + "reth-chainspec", + "reth-cli-runner", + "reth-cli-util", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-downloaders", + "reth-evm", + "reth-exex", + "reth-fs-util", + "reth-network", + "reth-network-p2p", + "reth-node-core", + "reth-primitives", + "reth-provider", + "reth-prune", + "reth-stages", + "reth-static-file", + "reth-static-file-types", + "reth-trie", + "serde", + "serde_json", + "tokio", + "toml 0.8.14", + "tracing", +] + +[[package]] +name = "reth-cli-runner" +version = "1.0.1" +source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.0.1#d599393771f9d7d137ea4abf271e1bd118184c73" +dependencies = [ + "reth-tasks", + "tokio", + "tracing", +] + [[package]] name = "reth-cli-util" version = "1.0.1" @@ -7260,6 +7543,7 @@ dependencies = [ "secp256k1", "serde_json", "shellexpand", + "tikv-jemalloc-ctl", "tokio", "tower", "tracing", @@ -7310,6 +7594,11 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-optimism-primitives" +version = "1.0.1" +source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.0.1#d599393771f9d7d137ea4abf271e1bd118184c73" + [[package]] name = "reth-payload-builder" version = "1.0.1" @@ -7855,6 +8144,7 @@ version = "1.0.1" source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.0.1#d599393771f9d7d137ea4abf271e1bd118184c73" dependencies = [ "alloy-primitives", + "clap", "derive_more", "serde", "strum", @@ -9038,6 +9328,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -9063,6 +9374,26 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e041bb827d1bfca18f213411d51b665309f1afb37a04a5d1464530e13779fc0f" +dependencies = [ + "console", + "similar", +] + [[package]] name = "simple_asn1" version = "0.6.2" @@ -9169,6 +9500,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.71", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -9431,6 +9772,37 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tikv-jemalloc-ctl" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619bfed27d807b54f7f776b9430d4f8060e66ee138a28632ca898584d462c31c" +dependencies = [ + "libc", + "paste", + "tikv-jemalloc-sys", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.3.36" @@ -10028,6 +10400,23 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "unicode-xid" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index c7a5254c1..b0fb159fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,10 @@ members = [ "crates/prover/zkvm", "crates/prover/adapters/risc0", "sequencer", + "reth", ] -default-members = ["sequencer"] +default-members = ["sequencer", "reth"] resolver = "2" @@ -28,12 +29,14 @@ alpen-vertex-common = { path = "crates/common" } alpen-vertex-consensus-logic = { path = "crates/consensus-logic" } alpen-vertex-db = { path = "crates/db" } alpen-vertex-evmctl = { path = "crates/evmctl" } +alpen-vertex-evmexec = { path = "crates/evmexec" } alpen-vertex-mmr = { path = "crates/util/mmr" } alpen-vertex-primitives = { path = "crates/primitives" } alpen-vertex-rpc-api = { path = "crates/rpc/api" } alpen-vertex-state = { path = "crates/state" } zkvm = { path = "crates/prover/zkvm" } +alloy-genesis = { version = "0.1", default-features = false } anyhow = "1.0.86" arbitrary = { version = "1.3.2", features = ["derive"] } argh = "0.1" @@ -57,6 +60,8 @@ num_enum = "0.7" parking_lot = "0.12.3" rand = "0.8.5" reqwest = { version = "0.12.4", features = ["json"] } +reth = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } reth-db = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } reth-ipc = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } @@ -66,6 +71,7 @@ reth-rpc = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } reth-rpc-api = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } reth-rpc-types = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } +reth-rpc-types-compat = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.1" } rockbound = { git = "https://github.com/sovereign-Labs/rockbound", tag = "v2.0.1" } rocksdb = "0.21" secp256k1 = "0.29.0" diff --git a/README.md b/README.md index cc7d09ce7..fd7554a8c 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,56 @@ These exist in `crates/`. * `util/` - independent utility libraries * `mmr` - "merkle mountain range" util * `vtxjmt` - extensions to JMT crate for our purposes + +### How to run + +Prerequisite: + * bitcoin regtest instance with json-rpc access + * host:port for bitcoind rpc `BITCOIND_HOST`, + * auth for bitcoind rpc: `BITCOIND_USER`:`BITCOIND_PASSWORD` + * 32 byte sequencer key saved to file `SEQUENCER_KEY_PATH` + * 32 byte EL client jwt secret saved as **hex** to file `JWT_SECRET_PATH` + +Create `config.toml` for rollup (Or use `example_config.toml` as template) + +```toml +[bitcoind_rpc] +rpc_url = "{BITCOIND_HOST}" +rpc_user = "{BITCOIND_USER}" +rpc_password = "{BITCOIND_PASSWORD}" +network = "regtest" + +[client] +rpc_port = 8432 +datadir = ".data/rollup" +sequencer_key = "{SEQUENCER_KEY_PATH}" + +[sync] +l1_follow_distance = 6 +max_reorg_depth = 4 +client_poll_dur_ms = 2000 + +[exec.reth] +rpc_url = "http://localhost:8551" +secret = "{JWT_SECRET_PATH}" +``` + +Ensure bitcoin has some blocks + +in `sequencer/src/main.rs`, adjust rollup configs: + * `horizon_l1_height` + * `genesis_l1_height` + +ensure `horizon_l1_height` <= `genesis_l1_height` < bitcoin_block_height + +Start EL Client: + +```sh +cargo run --bin alpen-vertex-reth -- --datadir .data/reth --http -vvvv +``` + +Start CL Client/Sequencer + +```sh +cargo run --bin alpen-vertex-sequencer -- --config config.toml +``` diff --git a/crates/consensus-logic/src/duty_executor.rs b/crates/consensus-logic/src/duty_executor.rs index 9d3eff287..d9713e9fc 100644 --- a/crates/consensus-logic/src/duty_executor.rs +++ b/crates/consensus-logic/src/duty_executor.rs @@ -2,9 +2,10 @@ use std::collections::HashMap; use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; use std::{thread, time}; -use alpen_vertex_state::exec_update::{ExecUpdate, UpdateInput, UpdateOutput}; use borsh::{BorshDeserialize, BorshSerialize}; use tokio::sync::broadcast; use tracing::*; @@ -14,8 +15,10 @@ use alpen_vertex_evmctl::engine::{ExecEngineCtl, PayloadStatus}; use alpen_vertex_evmctl::errors::EngineError; use alpen_vertex_evmctl::messages::{ExecPayloadData, PayloadEnv}; use alpen_vertex_primitives::buf::{Buf32, Buf64}; -use alpen_vertex_state::block::{ExecSegment, L1Segment}; +use alpen_vertex_primitives::params::RollupParams; +use alpen_vertex_state::block::{ExecSegment, L1Segment, L2BlockAccessory, L2BlockBundle}; use alpen_vertex_state::client_state::ClientState; +use alpen_vertex_state::exec_update::{ExecUpdate, UpdateOutput}; use alpen_vertex_state::header::L2BlockHeader; use alpen_vertex_state::prelude::*; @@ -46,7 +49,7 @@ impl IdentityData { } } -pub fn duty_tracker_task( +pub fn duty_tracker_task( mut cupdate_rx: broadcast::Receiver>, batch_queue: broadcast::Sender, ident: Identity, @@ -130,6 +133,7 @@ pub fn duty_dispatch_task< database: Arc, engine: Arc, pool: Arc, + params: &RollupParams, ) { // TODO make this actually work let mut pending_duties: HashMap = HashMap::new(); @@ -166,7 +170,8 @@ pub fn duty_dispatch_task< let sm = sync_man.clone(); let db = database.clone(); let e = engine.clone(); - pool.execute(move || duty_exec_task(d, ik, sm, db, e)); + let params = params.clone(); + pool.execute(move || duty_exec_task(d, ik, sm, db, e, params)); trace!(%id, "dispatched duty exec task"); pending_duties.insert(id, ()); } @@ -184,8 +189,16 @@ fn duty_exec_task( sync_man: Arc, database: Arc, engine: Arc, + params: RollupParams, ) { - if let Err(e) = perform_duty(&duty, &ik, &sync_man, database.as_ref(), engine.as_ref()) { + if let Err(e) = perform_duty( + &duty, + &ik, + &sync_man, + database.as_ref(), + engine.as_ref(), + params, + ) { error!(err = %e, "error performing duty"); } else { debug!("completed duty successfully"); @@ -198,11 +211,13 @@ fn perform_duty( sync_man: &SyncManager, database: &D, engine: &E, + params: RollupParams, ) -> Result<(), Error> { match duty { Duty::SignBlock(data) => { let target = data.target_slot(); - let Some((blkid, _block)) = sign_and_store_block(target, ik, database, engine)? else { + let Some((blkid, _block)) = sign_and_store_block(target, ik, database, engine, params)? + else { return Ok(()); }; @@ -227,6 +242,7 @@ fn sign_and_store_block( ik: &IdentityKey, database: &D, engine: &E, + params: RollupParams, ) -> Result, Error> { debug!(%slot, "prepating to publish block"); @@ -257,11 +273,24 @@ fn sign_and_store_block( let prev_block_id = *last_ss.chain_tip_blkid(); + let prev_block = database + .l2_provider() + .get_block_data(prev_block_id)? + .expect("prev block must exist"); + + // TODO: get from rollup config + let block_time = params.block_time; + let target_ts = prev_block.block().header().timestamp() + block_time; + let current_ts = now_millis(); + + if current_ts < target_ts { + sleep(Duration::from_millis(target_ts - current_ts)); + } + // Start preparing the EL payload. let ts = now_millis(); - let prev_global_sr = Buf32::zero(); // TODO let safe_l1_block = Buf32::zero(); // TODO - let payload_env = PayloadEnv::new(ts, prev_global_sr, safe_l1_block, Vec::new()); + let payload_env = PayloadEnv::new(ts, prev_block_id, safe_l1_block, Vec::new()); let key = engine.prepare_payload(payload_env)?; trace!(%slot, "submitted EL payload job, waiting for completion"); @@ -282,7 +311,7 @@ fn sign_and_store_block( // TODO correctly assemble the exec segment, since this is bodging out how // the inputs/outputs should be structured - let eui = UpdateInput::new(slot, Buf32::zero(), payload_data.el_payload().to_vec()); + let eui = payload_data.update_input().clone(); let exec_update = ExecUpdate::new(eui, UpdateOutput::new_from_state(Buf32::zero())); let exec_seg = ExecSegment::new(exec_update); @@ -293,12 +322,14 @@ fn sign_and_store_block( let header_sig = sign_header(&header, ik); let signed_header = SignedL2BlockHeader::new(header, header_sig); let blkid = signed_header.get_blockid(); + let block_accessory = L2BlockAccessory::new(payload_data.accessory_data().to_vec()); let final_block = L2Block::new(signed_header, body); + let final_block_bundle = L2BlockBundle::new(final_block.clone(), block_accessory); info!(%slot, ?blkid, "finished building new block"); // Store the block in the database. let l2store = database.l2_store(); - l2store.put_block_data(final_block.clone())?; + l2store.put_block_data(final_block_bundle)?; debug!(?blkid, "wrote block to datastore"); Ok(Some((blkid, final_block))) diff --git a/crates/consensus-logic/src/fork_choice_manager.rs b/crates/consensus-logic/src/fork_choice_manager.rs index b315c06da..d167baf45 100644 --- a/crates/consensus-logic/src/fork_choice_manager.rs +++ b/crates/consensus-logic/src/fork_choice_manager.rs @@ -360,14 +360,14 @@ fn process_ct_msg( ForkChoiceMessage::NewBlock(blkid) => { let l2prov = state.database.l2_provider(); - let block = l2prov + let block_bundle = l2prov .get_block_data(blkid)? .ok_or(Error::MissingL2Block(blkid))?; // First, decide if the block seems correctly signed and we haven't // already marked it as invalid. let cstate = state.cur_csm_state.clone(); - let correctly_signed = check_new_block(&blkid, &block, &cstate, state)?; + let correctly_signed = check_new_block(&blkid, &block_bundle, &cstate, state)?; if !correctly_signed { // It's invalid, write that and return. state.set_block_status(&blkid, BlockStatus::Invalid)?; @@ -376,11 +376,8 @@ fn process_ct_msg( // Try to execute the payload, seeing if *that's* valid. // TODO take implicit input produced by the CL STF and include that in the payload data - // TODO include the full exec update input from the CL block - let exec_hash = block.header().exec_payload_hash(); - let exec_seg = block.exec_segment(); - let dummy_payload = exec_seg.update().input().extra_payload(); - let eng_payload = ExecPayloadData::new_simple(dummy_payload.to_vec()); + let exec_hash = block_bundle.header().exec_payload_hash(); + let eng_payload = ExecPayloadData::from_l2_block_bundle(&block_bundle); debug!(?blkid, ?exec_hash, "submitting execution payload"); let res = engine.submit_payload(eng_payload)?; @@ -398,7 +395,9 @@ fn process_ct_msg( // should switch to it as a potential head. This returns if we // created a new tip instead of advancing an existing tip. let cur_tip = state.cur_best_block; - let new_tip = state.chain_tracker.attach_block(blkid, block.header())?; + let new_tip = state + .chain_tracker + .attach_block(blkid, block_bundle.header())?; if new_tip { debug!(?blkid, "created new pending tip"); } diff --git a/crates/consensus-logic/src/genesis.rs b/crates/consensus-logic/src/genesis.rs index bc82934c3..8b05c1834 100644 --- a/crates/consensus-logic/src/genesis.rs +++ b/crates/consensus-logic/src/genesis.rs @@ -3,11 +3,12 @@ use tracing::*; use alpen_vertex_db::{errors::DbError, traits::*}; use alpen_vertex_primitives::{ buf::{Buf32, Buf64}, + evm_exec::create_evm_extra_payload, l1::L1BlockManifest, params::Params, }; use alpen_vertex_state::{ - block::{ExecSegment, L1Segment}, + block::{ExecSegment, L1Segment, L2BlockAccessory, L2BlockBundle}, chain_state::ChainState, client_state::ClientState, exec_env::ExecEnvState, @@ -60,10 +61,14 @@ pub fn init_genesis_chainstate( // Create a dummy exec state that we can build the rest of the genesis block // around and insert into the genesis state. // TODO this might need to talk to the EL to do the genesus setup *properly* - let geui = UpdateInput::new(0, Buf32::zero(), Vec::new()); - let gees = ExecEnvState::from_base_input(geui.clone(), Buf32::zero()); - let genesis_ee_state = Buf32::zero(); - let geu = ExecUpdate::new(geui.clone(), UpdateOutput::new_from_state(genesis_ee_state)); + let extra_payload = create_evm_extra_payload(params.rollup.evm_genesis_block_hash); + let geui = UpdateInput::new(0, Buf32::zero(), extra_payload); + let gees = + ExecEnvState::from_base_input(geui.clone(), params.rollup.evm_genesis_block_state_root); + let geu = ExecUpdate::new( + geui.clone(), + UpdateOutput::new_from_state(params.rollup.evm_genesis_block_state_root), + ); // Build the genesis block and genesis consensus states. let gblock = make_genesis_block(params, geu); @@ -106,7 +111,7 @@ fn load_pre_genesis_l1_manifests( Ok(manifests) } -fn make_genesis_block(params: &Params, genesis_update: ExecUpdate) -> L2Block { +fn make_genesis_block(params: &Params, genesis_update: ExecUpdate) -> L2BlockBundle { // This has to be empty since everyone should have an unambiguous view of the genesis block. let l1_seg = L1Segment::new_empty(); @@ -115,7 +120,11 @@ fn make_genesis_block(params: &Params, genesis_update: ExecUpdate) -> L2Block { let body = L2BlockBody::new(l1_seg, exec_seg); - // Assemble the genesis header core, pulling in data from whatever + // TODO stub + let exec_payload = vec![]; + let accessory = L2BlockAccessory::new(exec_payload); + + // Assemble the genesis header template, pulling in data from whatever // sources we need. // FIXME this isn't the right timestamp to start the blockchain, this should // definitely be pulled from the database or the rollup parameters maybe @@ -124,7 +133,8 @@ fn make_genesis_block(params: &Params, genesis_update: ExecUpdate) -> L2Block { let genesis_sr = Buf32::zero(); let header = L2BlockHeader::new(0, genesis_ts, zero_blkid, &body, genesis_sr); let signed_genesis_header = SignedL2BlockHeader::new(header, Buf64::zero()); - L2Block::new(signed_genesis_header, body) + let block = L2Block::new(signed_genesis_header, body); + L2BlockBundle::new(block, accessory) } /// Check if the database needs to have client init done to it. diff --git a/crates/consensus-logic/src/unfinalized_tracker.rs b/crates/consensus-logic/src/unfinalized_tracker.rs index d09fc7904..f269c829e 100644 --- a/crates/consensus-logic/src/unfinalized_tracker.rs +++ b/crates/consensus-logic/src/unfinalized_tracker.rs @@ -317,17 +317,18 @@ mod tests { use alpen_test_utils::ArbitraryGenerator; use alpen_vertex_db::traits::{Database, L2DataProvider, L2DataStore}; use alpen_vertex_state::{ - block::{L2Block, L2BlockBody}, + block::{L2Block, L2BlockAccessory, L2BlockBody, L2BlockBundle}, header::{L2BlockHeader, L2Header, SignedL2BlockHeader}, id::L2BlockId, }; use crate::unfinalized_tracker; - fn get_genesis_block() -> L2Block { + fn get_genesis_block() -> L2BlockBundle { let arb = ArbitraryGenerator::new(); let gen_header: SignedL2BlockHeader = arb.generate(); let body: L2BlockBody = arb.generate(); + let accessory: L2BlockAccessory = arb.generate(); let empty_hash = L2BlockId::default(); let header = L2BlockHeader::new( @@ -338,13 +339,14 @@ mod tests { *gen_header.state_root(), ); let signed_header = SignedL2BlockHeader::new(header, *gen_header.sig()); - L2Block::new(signed_header, body) + L2BlockBundle::new(L2Block::new(signed_header, body), accessory) } - fn get_mock_block_with_parent(parent: &SignedL2BlockHeader) -> L2Block { + fn get_mock_block_with_parent(parent: &SignedL2BlockHeader) -> L2BlockBundle { let arb = ArbitraryGenerator::new(); let gen_header: SignedL2BlockHeader = arb.generate(); let body: L2BlockBody = arb.generate(); + let accessory: L2BlockAccessory = arb.generate(); let header = L2BlockHeader::new( parent.blockidx() + 1, @@ -354,7 +356,7 @@ mod tests { *gen_header.state_root(), ); let signed_header = SignedL2BlockHeader::new(header, *gen_header.sig()); - L2Block::new(signed_header, body) + L2BlockBundle::new(L2Block::new(signed_header, body), accessory) } fn setup_test_chain(l2_prov: &impl L2DataStore) -> [L2BlockId; 7] { diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 32c12cc6f..6ebd21600 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -10,6 +10,7 @@ alpen-vertex-state = { workspace = true } anyhow = { workspace = true } arbitrary = { workspace = true } +bincode = { workspace = true } bitcoin = { workspace = true } borsh = { workspace = true } parking_lot = { workspace = true } diff --git a/crates/db/src/chain_state/mod.rs b/crates/db/src/chain_state/mod.rs index 3047cc192..29bfe2f8a 100644 --- a/crates/db/src/chain_state/mod.rs +++ b/crates/db/src/chain_state/mod.rs @@ -1,2 +1,2 @@ -mod db; +pub mod db; pub mod schemas; diff --git a/crates/db/src/chain_state/schemas.rs b/crates/db/src/chain_state/schemas.rs index d2997ecf6..39ff9c4aa 100644 --- a/crates/db/src/chain_state/schemas.rs +++ b/crates/db/src/chain_state/schemas.rs @@ -1,18 +1,18 @@ use alpen_vertex_state::chain_state::ChainState; use alpen_vertex_state::state_op::WriteBatch; -use crate::define_table_with_default_codec; +use crate::define_table_with_seek_key_codec; use crate::define_table_without_codec; use crate::impl_borsh_value_codec; // Consensus Output Schema and corresponding codecs implementation -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// Table to store client state updates. (ChainStateSchema) u64 => ChainState ); // Consensus State Schema and corresponding codecs implementation -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// Table to store client states. (WriteBatchSchema) u64 => WriteBatch ); diff --git a/crates/db/src/client_state/schemas.rs b/crates/db/src/client_state/schemas.rs index 3841bdc3d..3817a68f6 100644 --- a/crates/db/src/client_state/schemas.rs +++ b/crates/db/src/client_state/schemas.rs @@ -1,18 +1,18 @@ use alpen_vertex_state::client_state::ClientState; use alpen_vertex_state::operation::ClientUpdateOutput; -use crate::define_table_with_default_codec; +use crate::define_table_with_seek_key_codec; use crate::define_table_without_codec; use crate::impl_borsh_value_codec; // Consensus Output Schema and corresponding codecs implementation -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// Table to store client state updates. (ClientUpdateOutputSchema) u64 => ClientUpdateOutput ); // Consensus State Schema and corresponding codecs implementation -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// Table to store client states. (ClientStateSchema) u64 => ClientState ); diff --git a/crates/db/src/l2/db.rs b/crates/db/src/l2/db.rs index 450cd6eaa..54072c13f 100644 --- a/crates/db/src/l2/db.rs +++ b/crates/db/src/l2/db.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use rockbound::{SchemaBatch, DB}; -use alpen_vertex_state::prelude::*; +use alpen_vertex_state::{block::L2BlockBundle, prelude::*}; use crate::{ l2::schemas::L2BlockHeightSchema, @@ -23,18 +23,18 @@ impl L2Db { } impl L2DataStore for L2Db { - fn put_block_data(&self, block: L2Block) -> DbResult<()> { - let block_id = block.header().get_blockid(); + fn put_block_data(&self, bundle: L2BlockBundle) -> DbResult<()> { + let block_id = bundle.block().header().get_blockid(); // append to previous block height data - let block_height = block.header().blockidx(); + let block_height = bundle.block().header().blockidx(); let mut block_height_data = self.get_blocks_at_height(block_height)?; if !block_height_data.contains(&block_id) { block_height_data.push(block_id); } let mut batch = SchemaBatch::new(); - batch.put::(&block_id, &block)?; + batch.put::(&block_id, &bundle)?; batch.put::(&block_id, &BlockStatus::Unchecked)?; batch.put::(&block_height, &block_height_data)?; self.db.write_schemas(batch)?; @@ -43,13 +43,13 @@ impl L2DataStore for L2Db { } fn del_block_data(&self, id: L2BlockId) -> DbResult { - let block = match self.get_block_data(id)? { + let bundle = match self.get_block_data(id)? { Some(block) => block, None => return Ok(false), }; // update to previous block height data - let block_height = block.header().blockidx(); + let block_height = bundle.block().header().blockidx(); let mut block_height_data = self.get_blocks_at_height(block_height)?; block_height_data.retain(|&block_id| block_id != id); @@ -76,7 +76,7 @@ impl L2DataStore for L2Db { } impl L2DataProvider for L2Db { - fn get_block_data(&self, id: L2BlockId) -> DbResult> { + fn get_block_data(&self, id: L2BlockId) -> DbResult> { Ok(self.db.get::(&id)?) } @@ -97,11 +97,11 @@ mod tests { use super::*; use alpen_test_utils::{get_rocksdb_tmp_instance, ArbitraryGenerator}; - fn get_mock_data() -> L2Block { + fn get_mock_data() -> L2BlockBundle { let arb = ArbitraryGenerator::new(); - let l2_lock: L2Block = arb.generate(); + let l2_block: L2BlockBundle = arb.generate(); - l2_lock + l2_block } fn setup_db() -> L2Db { @@ -113,12 +113,12 @@ mod tests { fn set_and_get_block_data() { let l2_db = setup_db(); - let block = get_mock_data(); - let block_hash = block.header().get_blockid(); - let block_height = block.header().blockidx(); + let bundle = get_mock_data(); + let block_hash = bundle.block().header().get_blockid(); + let block_height = bundle.block().header().blockidx(); l2_db - .put_block_data(block.clone()) + .put_block_data(bundle.clone()) .expect("failed to put block data"); // assert block was stored @@ -126,7 +126,7 @@ mod tests { .get_block_data(block_hash) .expect("failed to retrieve block data") .unwrap(); - assert_eq!(received_block, block); + assert_eq!(received_block, bundle); // assert block status was set to `BlockStatus::Unchecked`` let block_status = l2_db @@ -145,9 +145,9 @@ mod tests { #[test] fn del_and_get_block_data() { let l2_db = setup_db(); - let block = get_mock_data(); - let block_hash = block.header().get_blockid(); - let block_height = block.header().blockidx(); + let bundle = get_mock_data(); + let block_hash = bundle.block().header().get_blockid(); + let block_height = bundle.block().header().blockidx(); // deleting non existing block should return false let res = l2_db @@ -157,7 +157,7 @@ mod tests { // deleting existing block should return true l2_db - .put_block_data(block.clone()) + .put_block_data(bundle.clone()) .expect("failed to put block data"); let res = l2_db .del_block_data(block_hash) @@ -186,11 +186,11 @@ mod tests { #[test] fn set_and_get_block_status() { let l2_db = setup_db(); - let block = get_mock_data(); - let block_hash = block.header().get_blockid(); + let bundle = get_mock_data(); + let block_hash = bundle.block().header().get_blockid(); l2_db - .put_block_data(block.clone()) + .put_block_data(bundle.clone()) .expect("failed to put block data"); // assert block status was set to `BlockStatus::Valid`` diff --git a/crates/db/src/l2/schemas.rs b/crates/db/src/l2/schemas.rs index e8824a954..2342bf818 100644 --- a/crates/db/src/l2/schemas.rs +++ b/crates/db/src/l2/schemas.rs @@ -1,8 +1,9 @@ use crate::define_table_with_default_codec; +use crate::define_table_with_seek_key_codec; use crate::define_table_without_codec; use crate::impl_borsh_value_codec; use crate::traits::BlockStatus; -use alpen_vertex_state::block::L2Block; +use alpen_vertex_state::block::L2BlockBundle; use alpen_vertex_state::id::L2BlockId; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -14,7 +15,7 @@ pub struct SomeBlock { define_table_with_default_codec!( /// A table to store L2 Block data. Maps block id to Block - (L2BlockSchema) L2BlockId => L2Block + (L2BlockSchema) L2BlockId => L2BlockBundle ); define_table_with_default_codec!( @@ -22,7 +23,7 @@ define_table_with_default_codec!( (L2BlockStatusSchema) L2BlockId => BlockStatus ); -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// A table to store L2 Block data. Maps block id to BlockId (L2BlockHeightSchema) u64 => Vec ); diff --git a/crates/db/src/lib.rs b/crates/db/src/lib.rs index 322794f5e..d3d43097b 100644 --- a/crates/db/src/lib.rs +++ b/crates/db/src/lib.rs @@ -56,6 +56,7 @@ pub const STORE_COLUMN_FAMILIES: &[ColumnFamilyName] = &[ ]; // Re-exports +pub use chain_state::db::ChainStateDb; pub use client_state::db::ClientStateDb; pub use l1::db::L1Db; pub use sequencer::db::SeqDb; diff --git a/crates/db/src/macros.rs b/crates/db/src/macros.rs index 382a2c721..a80154d50 100644 --- a/crates/db/src/macros.rs +++ b/crates/db/src/macros.rs @@ -66,3 +66,43 @@ macro_rules! define_table_with_default_codec { impl_borsh_value_codec!($table_name, $value); }; } + +/// Macro similar to [`define_table_with_default_codec`], but to be used when +/// your key type should be [`SeekKeyEncoder`]. Borsh serializes integers as +/// little-endian, but RocksDB uses lexicographic ordering which is only +/// compatible with big-endian, so we use [`bincode`] with the big-endian option +/// here. +#[macro_export] +macro_rules! define_table_with_seek_key_codec { + ($(#[$docs:meta])+ ($table_name:ident) $key:ty => $value:ty) => { + define_table_without_codec!($(#[$docs])+ ( $table_name ) $key => $value); + + impl ::rockbound::schema::KeyEncoder<$table_name> for $key { + fn encode_key(&self) -> ::std::result::Result<::std::vec::Vec, ::rockbound::CodecError> { + use ::anyhow::Context as _; + use ::bincode::Options as _; + + let bincode_options = ::bincode::options() + .with_fixint_encoding() + .with_big_endian(); + + bincode_options.serialize(self).context("Failed to serialize key").map_err(Into::into) + } + } + + impl ::rockbound::schema::KeyDecoder<$table_name> for $key { + fn decode_key(data: &[u8]) -> ::std::result::Result { + use ::anyhow::Context as _; + use ::bincode::Options as _; + + let bincode_options = ::bincode::options() + .with_fixint_encoding() + .with_big_endian(); + + bincode_options.deserialize_from(&mut &data[..]).context("Failed to deserialize key").map_err(Into::into) + } + } + + impl_borsh_value_codec!($table_name, $value); + }; +} diff --git a/crates/db/src/sequencer/schemas.rs b/crates/db/src/sequencer/schemas.rs index a0b71d104..7f8f5ba6c 100644 --- a/crates/db/src/sequencer/schemas.rs +++ b/crates/db/src/sequencer/schemas.rs @@ -1,21 +1,22 @@ use alpen_vertex_primitives::buf::Buf32; use crate::define_table_with_default_codec; +use crate::define_table_with_seek_key_codec; use crate::define_table_without_codec; use crate::impl_borsh_value_codec; use crate::types::TxnStatusEntry; -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// A table to store L1 txns (SeqL1TxnSchema) u64 => TxnStatusEntry ); -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// A table to store mapping of idx to L1 tx (SeqL1TxIdSchema) u64 => Buf32 ); -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// A table to store idx-> blobid mapping (SeqBlobIdSchema) u64 => Buf32 ); diff --git a/crates/db/src/stubs/l2.rs b/crates/db/src/stubs/l2.rs index e2b06701a..a9a831d69 100644 --- a/crates/db/src/stubs/l2.rs +++ b/crates/db/src/stubs/l2.rs @@ -1,5 +1,6 @@ use std::collections::*; +use alpen_vertex_state::block::L2BlockBundle; use parking_lot::Mutex; use alpen_vertex_state::prelude::*; @@ -10,7 +11,7 @@ use crate::traits::*; /// Dummy implementation that isn't really compliant with the spec, but we don't /// care because we just want to get something running. :sunglasses:. pub struct StubL2Db { - blocks: Mutex>, + blocks: Mutex>, statuses: Mutex>, heights: Mutex>>, } @@ -32,13 +33,13 @@ impl StubL2Db { } impl L2DataStore for StubL2Db { - fn put_block_data(&self, block: L2Block) -> DbResult<()> { - let blkid = block.header().get_blockid(); - let idx = block.header().blockidx(); + fn put_block_data(&self, bundle: L2BlockBundle) -> DbResult<()> { + let blkid = bundle.block().header().get_blockid(); + let idx = bundle.block().header().blockidx(); { let mut tbl = self.blocks.lock(); - tbl.insert(blkid, block); + tbl.insert(blkid, bundle); } { @@ -62,7 +63,7 @@ impl L2DataStore for StubL2Db { } impl L2DataProvider for StubL2Db { - fn get_block_data(&self, id: L2BlockId) -> DbResult> { + fn get_block_data(&self, id: L2BlockId) -> DbResult> { let tbl = self.blocks.lock(); Ok(tbl.get(&id).cloned()) } diff --git a/crates/db/src/sync_event/schemas.rs b/crates/db/src/sync_event/schemas.rs index 79ebc55b4..4762abc51 100644 --- a/crates/db/src/sync_event/schemas.rs +++ b/crates/db/src/sync_event/schemas.rs @@ -2,7 +2,7 @@ use alpen_vertex_state::sync_event::SyncEvent; use borsh::{BorshDeserialize, BorshSerialize}; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::define_table_with_default_codec; +use crate::define_table_with_seek_key_codec; use crate::define_table_without_codec; use crate::impl_borsh_value_codec; @@ -31,7 +31,7 @@ impl SyncEventWithTimestamp { } // Sync Event Schema and corresponding codecs implementation -define_table_with_default_codec!( +define_table_with_seek_key_codec!( /// A table to store Sync Events. Maps event index to event (SyncEventSchema) u64 => SyncEventWithTimestamp ); diff --git a/crates/db/src/traits.rs b/crates/db/src/traits.rs index 63e47d053..f0d61a629 100644 --- a/crates/db/src/traits.rs +++ b/crates/db/src/traits.rs @@ -3,6 +3,7 @@ use std::sync::Arc; +use alpen_vertex_state::block::L2BlockBundle; use borsh::{BorshDeserialize, BorshSerialize}; use alpen_vertex_mmr::CompactMmr; @@ -157,7 +158,7 @@ pub trait ClientStateProvider { pub trait L2DataStore { /// Stores an L2 block, does not care about the block height of the L2 /// block. Also sets the block's status to "unchecked". - fn put_block_data(&self, block: L2Block) -> DbResult<()>; + fn put_block_data(&self, block: L2BlockBundle) -> DbResult<()>; /// Tries to delete an L2 block from the store, returning if it really /// existed or not. This should only be used for blocks well before some @@ -171,7 +172,7 @@ pub trait L2DataStore { /// Data provider for L2 blocks. pub trait L2DataProvider { /// Gets the L2 block by its ID, if we have it. - fn get_block_data(&self, id: L2BlockId) -> DbResult>; + fn get_block_data(&self, id: L2BlockId) -> DbResult>; /// Gets the L2 block IDs that we have at some height, in case there's more /// than one on competing forks. diff --git a/crates/evmctl/src/engine.rs b/crates/evmctl/src/engine.rs index b8ea32a7f..d0d5608d7 100644 --- a/crates/evmctl/src/engine.rs +++ b/crates/evmctl/src/engine.rs @@ -61,6 +61,7 @@ pub enum BlockStatus { Syncing, } +#[derive(Debug)] pub enum PayloadStatus { /// Still building the payload. Working, diff --git a/crates/evmctl/src/messages.rs b/crates/evmctl/src/messages.rs index fd20b7651..c062ec90d 100644 --- a/crates/evmctl/src/messages.rs +++ b/crates/evmctl/src/messages.rs @@ -1,4 +1,5 @@ use alpen_vertex_primitives::prelude::*; +use alpen_vertex_state::{block::L2BlockBundle, exec_update::UpdateInput, id::L2BlockId}; /// Succinct commitment to relevant EL block data. // This ended up being the same as the EL payload types in the state crate, @@ -9,7 +10,9 @@ pub struct ExecPayloadData { /// /// This is the "explicit" input from the CL block. // TODO replace this with the `UpdateInput` from the exec update, maybe some other stuff - el_payload: Vec, + update_input: UpdateInput, + + accessory_data: Vec, /// CL operations pushed into the EL, such as deposits from L1. This /// corresponds to the "withdrawals" field in the `ExecutionPayloadVX` @@ -20,17 +23,29 @@ pub struct ExecPayloadData { } impl ExecPayloadData { - pub fn new(el_payload: Vec, ops: Vec) -> Self { - Self { el_payload, ops } + pub fn new(update_input: UpdateInput, accessory_data: Vec, ops: Vec) -> Self { + Self { + update_input, + accessory_data, + ops, + } } - /// Creates a new instance with some specific payload no ops. - pub fn new_simple(el_payload: Vec) -> Self { - Self::new(el_payload, Vec::new()) + pub fn from_l2_block_bundle(l2block: &L2BlockBundle) -> Self { + Self { + update_input: l2block.block().exec_segment().update().input().clone(), + accessory_data: l2block.accessory().exec_payload().to_vec(), + // TODO: extract ops from block + ops: vec![], + } } - pub fn el_payload(&self) -> &[u8] { - &self.el_payload + pub fn update_input(&self) -> &UpdateInput { + &self.update_input + } + + pub fn accessory_data(&self) -> &[u8] { + &self.accessory_data } pub fn ops(&self) -> &[Op] { @@ -55,8 +70,8 @@ pub struct PayloadEnv { /// Timestamp we're attesting this block was created on. timestamp: u64, - /// State root of the previous CL block. - _prev_global_state_root: Buf32, + /// BlockId of previous CL block + prev_l2_block_id: L2BlockId, /// Safe L1 block we're exposing into the EL that's not likely to reorg. _safe_l1_block: Buf32, @@ -68,13 +83,13 @@ pub struct PayloadEnv { impl PayloadEnv { pub fn new( timestamp: u64, - _prev_global_state_root: Buf32, + prev_l2_block_id: L2BlockId, _safe_l1_block: Buf32, el_ops: Vec, ) -> Self { Self { timestamp, - _prev_global_state_root, + prev_l2_block_id, _safe_l1_block, el_ops, } @@ -87,6 +102,10 @@ impl PayloadEnv { pub fn el_ops(&self) -> &[Op] { &self.el_ops } + + pub fn prev_l2_block_id(&self) -> &L2BlockId { + &self.prev_l2_block_id + } } /// Operation the CL pushes into the EL to perform as part of the block it's diff --git a/crates/evmctl/src/stub.rs b/crates/evmctl/src/stub.rs index 03d147fb4..3bf4f7257 100644 --- a/crates/evmctl/src/stub.rs +++ b/crates/evmctl/src/stub.rs @@ -9,6 +9,8 @@ use std::collections::*; use std::sync::Mutex; use std::time; +use alpen_vertex_primitives::buf::Buf32; +use alpen_vertex_state::exec_update::UpdateInput; use alpen_vertex_state::prelude::*; use crate::engine::*; @@ -69,7 +71,11 @@ impl ExecEngineCtl for StubController { Ok(PayloadStatus::Working) } else { // TODO make up a more plausible payload - let exec = ExecPayloadData::new_simple(Vec::new()); + let exec = ExecPayloadData::new( + UpdateInput::new(0, Buf32::zero(), Vec::new()), + Vec::new(), + Vec::new(), + ); Ok(PayloadStatus::Ready(exec)) } } diff --git a/crates/evmexec/Cargo.toml b/crates/evmexec/Cargo.toml index 204979364..d07dae3ef 100644 --- a/crates/evmexec/Cargo.toml +++ b/crates/evmexec/Cargo.toml @@ -4,9 +4,11 @@ name = "alpen-vertex-evmexec" version = "0.1.0" [dependencies] +alpen-vertex-db = { workspace = true } alpen-vertex-evmctl = { workspace = true } alpen-vertex-primitives = { workspace = true } alpen-vertex-state = { workspace = true } +anyhow = { workspace = true } arbitrary = { workspace = true } borsh = { workspace = true } futures = { workspace = true } @@ -19,10 +21,14 @@ reth-rpc = { workspace = true } reth-rpc-api = { workspace = true } reth-rpc-layer = { workspace = true } reth-rpc-types = { workspace = true } +reth-rpc-types-compat = { workspace = true } serde = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } tower = { workspace = true } +tracing = { workspace = true } [dev-dependencies] mockall = { workspace = true } rand = { workspace = true } +alpen-test-utils = { workspace = true } diff --git a/crates/evmexec/src/block.rs b/crates/evmexec/src/block.rs new file mode 100644 index 000000000..50c8e4363 --- /dev/null +++ b/crates/evmexec/src/block.rs @@ -0,0 +1,39 @@ +use borsh::BorshDeserialize; +use reth_primitives::B256; +use thiserror::Error; + +use alpen_vertex_primitives::evm_exec::EVMExtraPayload; +use alpen_vertex_state::block::{L2Block, L2BlockBundle}; + +pub(crate) struct EVML2Block { + #[allow(dead_code)] + l2_block: L2Block, + extra_payload: EVMExtraPayload, +} + +impl EVML2Block { + pub fn block_hash(&self) -> B256 { + self.extra_payload.block_hash().into() + } +} + +impl TryFrom for EVML2Block { + type Error = ConversionError; + + fn try_from(value: L2BlockBundle) -> Result { + let extra_payload_slice = value.exec_segment().update().input().extra_payload(); + let extra_payload = EVMExtraPayload::try_from_slice(extra_payload_slice) + .or(Err(ConversionError::Invalid))?; + + Ok(Self { + l2_block: value.block().to_owned(), + extra_payload, + }) + } +} + +#[derive(Debug, Error)] +pub(crate) enum ConversionError { + #[error("Invalid EVM L2 Block")] + Invalid, +} diff --git a/crates/evmexec/src/el_payload.rs b/crates/evmexec/src/el_payload.rs index c28c7d64c..b947629f2 100644 --- a/crates/evmexec/src/el_payload.rs +++ b/crates/evmexec/src/el_payload.rs @@ -1,8 +1,15 @@ -use alpen_vertex_primitives::buf::{Buf20, Buf32}; use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use reth_primitives::B256; use reth_rpc_types::ExecutionPayloadV1; +use reth_rpc_types_compat::engine::try_payload_v1_to_block; +use thiserror::Error; + +use alpen_vertex_primitives::{ + buf::{Buf20, Buf32}, + evm_exec::create_evm_extra_payload, +}; +use alpen_vertex_state::exec_update::UpdateInput; #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, Arbitrary)] pub(crate) struct ElPayload { @@ -36,6 +43,29 @@ pub(crate) struct ElPayload { pub transactions: Vec>, } +#[derive(Debug, Error)] +pub enum ElPayloadError { + #[error("Failed to extract evm block from payload: {0}")] + BlockConversionError(String), +} + +impl TryFrom for UpdateInput { + type Error = ElPayloadError; + + fn try_from(el_payload: ElPayload) -> Result { + let extra_payload = create_evm_extra_payload(el_payload.block_hash); + let v1_payload = ExecutionPayloadV1::from(el_payload); + let evm_block = try_payload_v1_to_block(v1_payload) + .map_err(|err| ElPayloadError::BlockConversionError(err.to_string()))?; + + Ok(Self::new( + evm_block.number, + Buf32(evm_block.transactions_root), + extra_payload, + )) + } +} + impl From for ElPayload { fn from(val: ExecutionPayloadV1) -> Self { ElPayload { diff --git a/crates/evmexec/src/engine.rs b/crates/evmexec/src/engine.rs index 91838a6df..86cc7b04a 100644 --- a/crates/evmexec/src/engine.rs +++ b/crates/evmexec/src/engine.rs @@ -1,130 +1,75 @@ -use std::future::Future; +use std::sync::Arc; use futures::future::TryFutureExt; -use jsonrpsee::http_client::HttpClient; -use jsonrpsee::http_client::{transport::HttpBackend, HttpClientBuilder}; -use reth_node_ethereum::EthEngineTypes; use reth_primitives::{Address, B256}; -use reth_rpc_api::EngineApiClient; -use reth_rpc_layer::{AuthClientLayer, AuthClientService}; use reth_rpc_types::engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadFieldV2, ExecutionPayloadInputV2, ForkchoiceState, - ForkchoiceUpdated, JwtSecret, PayloadAttributes, PayloadId, PayloadStatusEnum, + ExecutionPayloadFieldV2, ExecutionPayloadInputV2, ForkchoiceState, PayloadAttributes, + PayloadId, PayloadStatusEnum, }; use reth_rpc_types::Withdrawal; use tokio::runtime::Handle; use tokio::sync::Mutex; +use alpen_vertex_db::traits::L2DataProvider; use alpen_vertex_evmctl::engine::{BlockStatus, ExecEngineCtl, PayloadStatus}; use alpen_vertex_evmctl::errors::{EngineError, EngineResult}; use alpen_vertex_evmctl::messages::{ELDepositData, ExecPayloadData, Op, PayloadEnv}; -use alpen_vertex_primitives::buf::Buf32; +use alpen_vertex_state::block::L2BlockBundle; +use alpen_vertex_state::exec_update::UpdateInput; use alpen_vertex_state::id::L2BlockId; +use crate::block::EVML2Block; use crate::el_payload::ElPayload; +use crate::http_client::EngineRpc; fn address_from_slice(slice: &[u8]) -> Option
{ let slice: Option<[u8; 20]> = slice.try_into().ok(); slice.map(Address::from) } -fn http_client(http_url: &str, secret_hex: &str) -> HttpClient> { - let secret = JwtSecret::from_hex(secret_hex).unwrap(); - let middleware = tower::ServiceBuilder::new().layer(AuthClientLayer::new(secret)); +/// (head, safe, finalized) +pub type FCS = (L2BlockId, Option, Option); - HttpClientBuilder::default() - .set_http_middleware(middleware) - .build(http_url) - .expect("Failed to create http client") +pub struct RpcExecEngineCtl { + client: T, + fork_choice_state: Mutex, + tokio_handle: Handle, + l2_provider: Arc

, } -pub trait ELHttpClient { - fn fork_choice_updated_v2( - &self, +impl RpcExecEngineCtl { + pub fn new( + client: T, fork_choice_state: ForkchoiceState, - payload_attributes: Option, - ) -> impl Future>; - - fn get_payload_v2( - &self, - payload_id: PayloadId, - ) -> impl Future>; - - fn new_payload_v2( - &self, - payload: ExecutionPayloadInputV2, - ) -> impl Future>; -} - -pub struct ELHttpClientImpl { - client: HttpClient>, -} - -impl ELHttpClientImpl { - pub fn from_url_secret(http_url: &str, secret_hex: &str) -> Self { - ELHttpClientImpl { - client: http_client(http_url, secret_hex), + handle: Handle, + l2_provider: Arc

, + ) -> Self { + Self { + client, + fork_choice_state: Mutex::new(fork_choice_state), + tokio_handle: handle, + l2_provider, } } - - pub fn inner(&self) -> &HttpClient> { - &self.client - } } -impl ELHttpClient for ELHttpClientImpl { - fn fork_choice_updated_v2( - &self, - fork_choice_state: ForkchoiceState, - payload_attributes: Option, - ) -> impl Future> { - > as EngineApiClient>::fork_choice_updated_v2::<'_, '_>(&self.client, fork_choice_state, payload_attributes) - } - - fn get_payload_v2( - &self, - payload_id: PayloadId, - ) -> impl Future> - { - > as EngineApiClient>::get_payload_v2::<'_, '_>(&self.client, payload_id) +impl RpcExecEngineCtl { + fn get_l2block(&self, l2_block_id: &L2BlockId) -> anyhow::Result { + self.l2_provider + .get_block_data(*l2_block_id)? + .ok_or(anyhow::anyhow!("missing L2Block")) } - fn new_payload_v2( - &self, - payload: ExecutionPayloadInputV2, - ) -> impl Future> - { - > as EngineApiClient>::new_payload_v2::<'_, '_>(&self.client, payload) + fn get_evm_block_hash(&self, l2_block_id: &L2BlockId) -> anyhow::Result { + self.get_l2block(l2_block_id) + .and_then(|l2block| self.get_block_info(l2block)) + .map(|evm_block| evm_block.block_hash()) } -} - -#[derive(Debug, Default)] -pub struct ForkchoiceStatePartial { - /// Hash of the head block. - pub head_block_hash: Option, - /// Hash of the safe block. - pub safe_block_hash: Option, - /// Hash of finalized block. - pub finalized_block_hash: Option, -} - -pub struct RpcExecEngineCtl { - client: T, - fork_choice_state: Mutex, - handle: Handle, -} -impl RpcExecEngineCtl { - pub fn new(client: T, fork_choice_state: ForkchoiceState, handle: Handle) -> Self { - Self { - client, - fork_choice_state: Mutex::new(fork_choice_state), - handle, - } + fn get_block_info(&self, l2block: L2BlockBundle) -> anyhow::Result { + EVML2Block::try_from(l2block).map_err(anyhow::Error::msg) } -} -impl RpcExecEngineCtl { async fn update_block_state( &self, fcs_partial: ForkchoiceStatePartial, @@ -163,7 +108,11 @@ impl RpcExecEngineCtl { } } - async fn build_block_from_mempool(&self, payload_env: PayloadEnv) -> EngineResult { + async fn build_block_from_mempool( + &self, + payload_env: PayloadEnv, + prev_block: EVML2Block, + ) -> EngineResult { // TODO: pass other fields from payload_env let withdrawals: Vec = payload_env .el_ops() @@ -178,23 +127,20 @@ impl RpcExecEngineCtl { .collect(); let payload_attributes = PayloadAttributes { - timestamp: payload_env.timestamp(), + // evm expects timestamp in seconds + timestamp: payload_env.timestamp() / 1000, prev_randao: B256::ZERO, - withdrawals: if withdrawals.is_empty() { - None - } else { - Some(withdrawals) - }, + withdrawals: Some(withdrawals), parent_beacon_block_root: None, suggested_fee_recipient: Address::ZERO, }; + let mut fcs = *self.fork_choice_state.lock().await; + fcs.head_block_hash = prev_block.block_hash(); + let forkchoice_result = self .client - .fork_choice_updated_v2( - *self.fork_choice_state.lock().await, - Some(payload_attributes), - ) + .fork_choice_updated_v2(fcs, Some(payload_attributes)) .await; // TODO: correct error type @@ -204,8 +150,8 @@ impl RpcExecEngineCtl { .payload_id .ok_or(EngineError::Other("payload_id missing".into()))?; // should never happen - // XXX fix this hack, it's pub now, this is just to make it compile - let raw_id: [u8; 8] = unsafe { std::mem::transmute(payload_id) }; + let raw_id: [u8; 8] = payload_id.0.into(); + Ok(u64::from_be_bytes(raw_id)) } @@ -220,7 +166,11 @@ impl RpcExecEngineCtl { let execution_payload_data = match payload.execution_payload { ExecutionPayloadFieldV2::V1(payload) => { let el_payload: ElPayload = payload.into(); - ExecPayloadData::new_simple(borsh::to_vec(&el_payload).unwrap()) + let accessory_data = borsh::to_vec(&el_payload).unwrap(); + let update_input = UpdateInput::try_from(el_payload) + .map_err(|err| EngineError::Other(err.to_string()))?; + + ExecPayloadData::new(update_input, accessory_data, vec![]) } ExecutionPayloadFieldV2::V2(payload) => { let ops = payload @@ -235,7 +185,11 @@ impl RpcExecEngineCtl { .collect(); let el_payload: ElPayload = payload.payload_inner.into(); - ExecPayloadData::new(borsh::to_vec(&el_payload).unwrap(), ops) + let accessory_data = borsh::to_vec(&el_payload).unwrap(); + let update_input = UpdateInput::try_from(el_payload) + .map_err(|err| EngineError::Other(err.to_string()))?; + + ExecPayloadData::new(update_input, accessory_data, ops) } }; @@ -243,7 +197,7 @@ impl RpcExecEngineCtl { } async fn submit_new_payload(&self, payload: ExecPayloadData) -> EngineResult { - let el_payload = borsh::from_slice::(payload.el_payload()) + let el_payload = borsh::from_slice::(payload.accessory_data()) .map_err(|_| EngineError::Other("Invalid payload".to_string()))?; let withdrawals: Vec = payload @@ -260,11 +214,7 @@ impl RpcExecEngineCtl { let v2_payload = ExecutionPayloadInputV2 { execution_payload: el_payload.into(), - withdrawals: if withdrawals.is_empty() { - None - } else { - Some(withdrawals) - }, + withdrawals: Some(withdrawals), }; let payload_status_result = self.client.new_payload_v2(v2_payload).await; @@ -281,23 +231,33 @@ impl RpcExecEngineCtl { } } -impl ExecEngineCtl for RpcExecEngineCtl { +impl ExecEngineCtl for RpcExecEngineCtl { fn submit_payload(&self, payload: ExecPayloadData) -> EngineResult { - self.handle.block_on(self.submit_new_payload(payload)) + self.tokio_handle.block_on(self.submit_new_payload(payload)) } fn prepare_payload(&self, env: PayloadEnv) -> EngineResult { - self.handle.block_on(self.build_block_from_mempool(env)) + let prev_l2block = self + .get_l2block(env.prev_l2_block_id()) + .map_err(|err| EngineError::Other(err.to_string()))?; + let prev_block = EVML2Block::try_from(prev_l2block) + .map_err(|err| EngineError::Other(err.to_string()))?; + self.tokio_handle + .block_on(self.build_block_from_mempool(env, prev_block)) } fn get_payload_status(&self, id: u64) -> EngineResult { - self.handle.block_on(self.get_payload_status(id)) + self.tokio_handle.block_on(self.get_payload_status(id)) } fn update_head_block(&self, id: L2BlockId) -> EngineResult<()> { - self.handle.block_on(async { + let block_hash = self + .get_evm_block_hash(&id) + .map_err(|err| EngineError::Other(err.to_string()))?; + + self.tokio_handle.block_on(async { let fork_choice_state = ForkchoiceStatePartial { - head_block_hash: Some(Buf32::from(id).into()), + head_block_hash: Some(block_hash), ..Default::default() }; self.update_block_state(fork_choice_state).await.map(|_| ()) @@ -305,9 +265,16 @@ impl ExecEngineCtl for RpcExecEngineCtl { } fn update_safe_block(&self, id: L2BlockId) -> EngineResult<()> { - self.handle.block_on(async { + let block_hash = self + .get_evm_block_hash(&id) + .map_err(|err| EngineError::Other(err.to_string()))?; + + self.tokio_handle.block_on(async { let fork_choice_state = ForkchoiceStatePartial { - safe_block_hash: Some(Buf32::from(id).into()), + // NOTE: update_head_block is not called currently; so update head and safe block + // together + head_block_hash: Some(block_hash), + safe_block_hash: Some(block_hash), ..Default::default() }; self.update_block_state(fork_choice_state).await.map(|_| ()) @@ -315,9 +282,13 @@ impl ExecEngineCtl for RpcExecEngineCtl { } fn update_finalized_block(&self, id: L2BlockId) -> EngineResult<()> { - self.handle.block_on(async { + let block_hash = self + .get_evm_block_hash(&id) + .map_err(|err| EngineError::Other(err.to_string()))?; + + self.tokio_handle.block_on(async { let fork_choice_state = ForkchoiceStatePartial { - finalized_block_hash: Some(Buf32::from(id).into()), + finalized_block_hash: Some(block_hash), ..Default::default() }; self.update_block_state(fork_choice_state).await.map(|_| ()) @@ -325,47 +296,37 @@ impl ExecEngineCtl for RpcExecEngineCtl { } } +#[derive(Debug, Default)] +struct ForkchoiceStatePartial { + /// Hash of the head block. + pub head_block_hash: Option, + /// Hash of the safe block. + pub safe_block_hash: Option, + /// Hash of finalized block. + pub finalized_block_hash: Option, +} + #[cfg(test)] mod tests { - use arbitrary::{Arbitrary, Unstructured}; - use mockall::{mock, predicate::*}; - use rand::{Rng, RngCore}; + use alpen_vertex_db::stubs::l2::StubL2Db; + use alpen_vertex_db::traits::L2DataStore; + use alpen_vertex_state::block::{L2Block, L2BlockAccessory}; + use rand::Rng; use reth_primitives::revm_primitives::FixedBytes; use reth_primitives::{Bloom, Bytes, U256}; + use reth_rpc_types::engine::{ExecutionPayloadEnvelopeV2, ForkchoiceUpdated}; use reth_rpc_types::ExecutionPayloadV1; use alpen_vertex_evmctl::errors::EngineResult; use alpen_vertex_evmctl::messages::PayloadEnv; use alpen_vertex_primitives::buf::Buf32; - use super::*; + use crate::http_client::MockEngineRpc; - mock! { - HttpClient {} - impl ELHttpClient for HttpClient { - fn fork_choice_updated_v2( - &self, - fork_choice_state: ForkchoiceState, - payload_attributes: Option, - ) -> impl Future>; - - fn get_payload_v2( - &self, - payload_id: PayloadId, - ) -> impl Future>; - - fn new_payload_v2( - &self, - payload: ExecutionPayloadInputV2, - ) -> impl Future>; - } - } + use super::*; fn random_el_payload() -> ElPayload { - let mut rand_data = vec![0u8; 1024]; - rand::thread_rng().fill_bytes(&mut rand_data); - let mut unstructured = Unstructured::new(&rand_data); - ElPayload::arbitrary(&mut unstructured).unwrap() + random_execution_payload_v1().into() } fn random_execution_payload_v1() -> ExecutionPayloadV1 { @@ -391,7 +352,7 @@ mod tests { #[tokio::test] async fn test_update_block_state() { - let mut mock_client = MockHttpClient::new(); + let mut mock_client = MockEngineRpc::new(); let fcs_partial = ForkchoiceStatePartial { head_block_hash: Some(B256::random()), @@ -407,11 +368,14 @@ mod tests { mock_client .expect_fork_choice_updated_v2() - .returning(move |_, _| { - Box::pin(async { Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Valid)) }) - }); + .returning(move |_, _| Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Valid))); - let rpc_exec_engine_ctl = RpcExecEngineCtl::new(mock_client, fcs, Handle::current()); + let rpc_exec_engine_ctl = RpcExecEngineCtl::new( + mock_client, + fcs, + Handle::current(), + Arc::new(StubL2Db::new()), + ); let result = rpc_exec_engine_ctl.update_block_state(fcs_partial).await; @@ -420,29 +384,41 @@ mod tests { #[tokio::test] async fn test_build_block_from_mempool() { - let mut mock_client = MockHttpClient::new(); + let mut mock_client = MockEngineRpc::new(); let fcs = ForkchoiceState::default(); mock_client .expect_fork_choice_updated_v2() .returning(move |_, _| { - Box::pin(async { - Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Valid) - .with_payload_id(PayloadId::new([1u8; 8]))) - }) + Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Valid) + .with_payload_id(PayloadId::new([1u8; 8]))) }); - let rpc_exec_engine_ctl = RpcExecEngineCtl::new(mock_client, fcs, Handle::current()); + let l2db = StubL2Db::new(); + + let el_payload = random_el_payload(); + + let arb = alpen_test_utils::ArbitraryGenerator::new(); + let l2block: L2Block = arb.generate(); + let accessory = L2BlockAccessory::new(borsh::to_vec(&el_payload).unwrap()); + let l2block_bundle = L2BlockBundle::new(l2block, accessory); + + let evm_l2_block = EVML2Block::try_from(l2block_bundle.clone()).unwrap(); + + l2db.put_block_data(l2block_bundle).unwrap(); + + let rpc_exec_engine_ctl = + RpcExecEngineCtl::new(mock_client, fcs, Handle::current(), Arc::new(l2db)); let timestamp = 0; let el_ops = vec![]; let safe_l1_block = Buf32(FixedBytes::<32>::random()); - let prev_global_state_root = Buf32(FixedBytes::<32>::random()); + let prev_l2_block = Buf32(FixedBytes::<32>::random()).into(); - let payload_env = PayloadEnv::new(timestamp, prev_global_state_root, safe_l1_block, el_ops); + let payload_env = PayloadEnv::new(timestamp, prev_l2_block, safe_l1_block, el_ops); let result = rpc_exec_engine_ctl - .build_block_from_mempool(payload_env) + .build_block_from_mempool(payload_env, evm_l2_block) .await; assert!(result.is_ok()); @@ -450,19 +426,22 @@ mod tests { #[tokio::test] async fn test_get_payload_status() { - let mut mock_client = MockHttpClient::new(); + let mut mock_client = MockEngineRpc::new(); let fcs = ForkchoiceState::default(); mock_client.expect_get_payload_v2().returning(move |_| { - Box::pin(async { - Ok(ExecutionPayloadEnvelopeV2 { - execution_payload: ExecutionPayloadFieldV2::V1(random_execution_payload_v1()), - block_value: U256::from(100), - }) + Ok(ExecutionPayloadEnvelopeV2 { + execution_payload: ExecutionPayloadFieldV2::V1(random_execution_payload_v1()), + block_value: U256::from(100), }) }); - let rpc_exec_engine_ctl = RpcExecEngineCtl::new(mock_client, fcs, Handle::current()); + let rpc_exec_engine_ctl = RpcExecEngineCtl::new( + mock_client, + fcs, + Handle::current(), + Arc::new(StubL2Db::new()), + ); let result = rpc_exec_engine_ctl.get_payload_status(0).await; @@ -471,24 +450,43 @@ mod tests { #[tokio::test] async fn test_submit_new_payload() { - let mut mock_client = MockHttpClient::new(); + let mut mock_client = MockEngineRpc::new(); let fcs = ForkchoiceState::default(); - let el_payload = random_el_payload(); - let ops = vec![Op::Deposit(ELDepositData::new(10000, [1u8; 20].into()))]; + let el_payload = ElPayload { + base_fee_per_gas: Buf32(U256::from(10).into()), + parent_hash: Default::default(), + fee_recipient: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + logs_bloom: [0u8; 256], + prev_randao: Default::default(), + block_number: Default::default(), + gas_limit: Default::default(), + gas_used: Default::default(), + timestamp: Default::default(), + extra_data: Default::default(), + block_hash: Default::default(), + transactions: Default::default(), + }; + let accessory_data = borsh::to_vec(&el_payload).unwrap(); + let update_input = UpdateInput::try_from(el_payload).unwrap(); - let payload_data = ExecPayloadData::new(borsh::to_vec(&el_payload).unwrap(), ops); + let payload_data = ExecPayloadData::new(update_input, accessory_data, vec![]); mock_client.expect_new_payload_v2().returning(move |_| { - Box::pin(async { - Ok(reth_rpc_types::engine::PayloadStatus { - status: PayloadStatusEnum::Valid, - latest_valid_hash: None, - }) + Ok(reth_rpc_types::engine::PayloadStatus { + status: PayloadStatusEnum::Valid, + latest_valid_hash: None, }) }); - let rpc_exec_engine_ctl = RpcExecEngineCtl::new(mock_client, fcs, Handle::current()); + let rpc_exec_engine_ctl = RpcExecEngineCtl::new( + mock_client, + fcs, + Handle::current(), + Arc::new(StubL2Db::new()), + ); let result = rpc_exec_engine_ctl.submit_new_payload(payload_data).await; diff --git a/crates/evmexec/src/fork_choice_state.rs b/crates/evmexec/src/fork_choice_state.rs new file mode 100644 index 000000000..a2ddf78a0 --- /dev/null +++ b/crates/evmexec/src/fork_choice_state.rs @@ -0,0 +1,76 @@ +use std::sync::Arc; + +use anyhow::{Context, Result}; +use reth_primitives::B256; +use reth_rpc_types::engine::ForkchoiceState; + +use alpen_vertex_db::{ + errors::DbError, + traits::{ClientStateProvider, Database, L2DataProvider}, +}; +use alpen_vertex_primitives::params::RollupParams; +use alpen_vertex_state::{block::L2BlockBundle, client_state::ClientState, id::L2BlockId}; + +use crate::block::EVML2Block; + +pub fn fork_choice_state_initial( + db: Arc, + config: &RollupParams, +) -> Result { + let last_cstate = get_last_checkpoint_state(db.as_ref())?; + + let latest_block_hash = get_block_hash_by_id( + db.as_ref(), + last_cstate + .as_ref() + .and_then(|state| state.sync()) + .map(|sync_state| sync_state.chain_tip_blkid()), + )? + .unwrap_or(config.evm_genesis_block_hash.into()); + + let finalized_block_hash = get_block_hash_by_id( + db.as_ref(), + last_cstate + .as_ref() + .and_then(|state| state.sync()) + .map(|sync_state| sync_state.finalized_blkid()), + )? + .unwrap_or(B256::ZERO); + + Ok(ForkchoiceState { + head_block_hash: latest_block_hash, + safe_block_hash: latest_block_hash, + finalized_block_hash, + }) +} + +fn get_block_hash(l2_block: L2BlockBundle) -> Result { + EVML2Block::try_from(l2_block) + .map(|block| block.block_hash()) + .context("Failed to convert L2Block to EVML2Block") +} + +fn get_last_checkpoint_state(db: &D) -> Result> { + let last_checkpoint_idx = db.client_state_provider().get_last_checkpoint_idx(); + + if let Err(DbError::NotBootstrapped) = last_checkpoint_idx { + // before genesis block ready; use hardcoded genesis state + return Ok(None); + } + + last_checkpoint_idx + .and_then(|ckpt_idx| db.client_state_provider().get_state_checkpoint(ckpt_idx)) + .context("Failed to get last checkpoint state") +} + +fn get_block_hash_by_id( + db: &D, + block_id: Option<&L2BlockId>, +) -> anyhow::Result> { + block_id + .and_then(|id| db.l2_provider().get_block_data(*id).transpose()) + .transpose() + .context("Failed to get block data")? + .map(get_block_hash) + .transpose() +} diff --git a/crates/evmexec/src/http_client.rs b/crates/evmexec/src/http_client.rs new file mode 100644 index 000000000..c587f7dd4 --- /dev/null +++ b/crates/evmexec/src/http_client.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; + +use jsonrpsee::http_client::HttpClient; +use jsonrpsee::http_client::{transport::HttpBackend, HttpClientBuilder}; +use reth_node_ethereum::EthEngineTypes; +use reth_primitives::{Block, BlockHash}; +use reth_rpc_api::{EngineApiClient, EthApiClient}; +use reth_rpc_layer::{AuthClientLayer, AuthClientService}; +use reth_rpc_types::engine::{ + ExecutionPayloadBodiesV1, ExecutionPayloadEnvelopeV2, ExecutionPayloadInputV2, ForkchoiceState, + ForkchoiceUpdated, JwtSecret, PayloadAttributes, PayloadId, +}; + +#[cfg(test)] +use mockall::automock; + +fn http_client(http_url: &str, secret: JwtSecret) -> HttpClient> { + let middleware = tower::ServiceBuilder::new().layer(AuthClientLayer::new(secret)); + + HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(http_url) + .expect("Failed to create http client") +} + +type RpcResult = Result; + +#[allow(async_fn_in_trait)] +#[cfg_attr(test, automock)] +pub trait EngineRpc { + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + async fn get_payload_v2(&self, payload_id: PayloadId) -> RpcResult; + + async fn new_payload_v2( + &self, + payload: ExecutionPayloadInputV2, + ) -> RpcResult; + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> RpcResult; + + async fn block_by_hash(&self, block_hash: BlockHash) -> RpcResult>; +} + +#[derive(Debug, Clone)] +pub struct EngineRpcClient { + client: Arc>>, +} + +impl EngineRpcClient { + pub fn from_url_secret(http_url: &str, secret: JwtSecret) -> Self { + EngineRpcClient { + client: Arc::new(http_client(http_url, secret)), + } + } + + pub fn inner(&self) -> &HttpClient> { + &self.client + } +} + +impl EngineRpc for EngineRpcClient { + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + > as EngineApiClient>::fork_choice_updated_v2(&self.client, fork_choice_state, payload_attributes).await + } + + async fn get_payload_v2(&self, payload_id: PayloadId) -> RpcResult { + > as EngineApiClient>::get_payload_v2(&self.client, payload_id).await + } + + async fn new_payload_v2( + &self, + payload: ExecutionPayloadInputV2, + ) -> RpcResult { + > as EngineApiClient>::new_payload_v2(&self.client, payload).await + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> RpcResult { + > as EngineApiClient>::get_payload_bodies_by_hash_v1(&self.client, block_hashes).await + } + + async fn block_by_hash(&self, block_hash: BlockHash) -> RpcResult> { + let block = self.client.block_by_hash(block_hash, true).await?; + + Ok(block.map(|rich_block| rich_block.inner.try_into().unwrap())) + } +} diff --git a/crates/evmexec/src/lib.rs b/crates/evmexec/src/lib.rs index 5568d3814..8fbc976b2 100644 --- a/crates/evmexec/src/lib.rs +++ b/crates/evmexec/src/lib.rs @@ -1,3 +1,10 @@ +mod block; mod el_payload; +mod fork_choice_state; +mod http_client; + pub mod engine; pub mod preloaded_storage; + +pub use fork_choice_state::fork_choice_state_initial; +pub use http_client::EngineRpcClient; diff --git a/crates/primitives/src/buf.rs b/crates/primitives/src/buf.rs index 7a283f1e9..ecdf238ac 100644 --- a/crates/primitives/src/buf.rs +++ b/crates/primitives/src/buf.rs @@ -9,7 +9,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use reth_primitives::alloy_primitives::FixedBytes; // 20-byte buf -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] pub struct Buf20(pub FixedBytes<20>); impl Buf20 { diff --git a/crates/primitives/src/evm_exec.rs b/crates/primitives/src/evm_exec.rs new file mode 100644 index 000000000..41dfd808f --- /dev/null +++ b/crates/primitives/src/evm_exec.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::buf::Buf32; + +/// Structure for `ExecUpdate.input.extra_payload` for EVM EL +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct EVMExtraPayload { + block_hash: [u8; 32], +} + +impl EVMExtraPayload { + pub fn new(block_hash: [u8; 32]) -> Self { + Self { block_hash } + } + + pub fn block_hash(&self) -> Buf32 { + self.block_hash.into() + } +} + +/// Generate extra_payload for evm el +pub fn create_evm_extra_payload(block_hash: Buf32) -> Vec { + let extra_payload = EVMExtraPayload { + block_hash: *block_hash.as_ref(), + }; + borsh::to_vec(&extra_payload).expect("extra_payload vec") +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 27d90c294..09c4669de 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -6,6 +6,7 @@ pub mod block_credential; pub mod buf; +pub mod evm_exec; pub mod hash; pub mod l1; pub mod params; diff --git a/crates/primitives/src/params.rs b/crates/primitives/src/params.rs index d9964a090..dc9a56ae9 100644 --- a/crates/primitives/src/params.rs +++ b/crates/primitives/src/params.rs @@ -1,6 +1,6 @@ //! Global consensus parameters for the rollup. -use crate::block_credential::CredRule; +use crate::{block_credential::CredRule, prelude::Buf32}; /// Consensus parameters that don't change for the lifetime of the network /// (unless there's some weird hard fork). @@ -17,6 +17,11 @@ pub struct RollupParams { /// Block height we'll construct the L2 genesis block from. pub genesis_l1_height: u64, + + /// Hardcoded EL genesis info + /// TODO: move elsewhere + pub evm_genesis_block_hash: Buf32, + pub evm_genesis_block_state_root: Buf32, } /// Client sync parameters that are used to make the network work but don't diff --git a/crates/state/src/block.rs b/crates/state/src/block.rs index eea5f45b1..04e8ff8a3 100644 --- a/crates/state/src/block.rs +++ b/crates/state/src/block.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; @@ -120,3 +122,52 @@ impl ExecSegment { &self.update } } + +#[derive(Clone, Debug, Eq, PartialEq, Arbitrary, BorshSerialize, BorshDeserialize)] +pub struct L2BlockAccessory { + exec_payload: Vec, +} + +impl L2BlockAccessory { + pub fn new(exec_payload: Vec) -> Self { + Self { exec_payload } + } + + pub fn exec_payload(&self) -> &[u8] { + &self.exec_payload + } +} + +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize, Arbitrary)] +pub struct L2BlockBundle { + block: L2Block, + accessory: L2BlockAccessory, +} + +impl L2BlockBundle { + pub fn new(block: L2Block, accessory: L2BlockAccessory) -> Self { + Self { block, accessory } + } + + pub fn block(&self) -> &L2Block { + &self.block + } + + pub fn accessory(&self) -> &L2BlockAccessory { + &self.accessory + } +} + +impl From for L2Block { + fn from(value: L2BlockBundle) -> Self { + value.block + } +} + +impl Deref for L2BlockBundle { + type Target = L2Block; + + fn deref(&self) -> &Self::Target { + &self.block + } +} diff --git a/crates/state/src/chain_state.rs b/crates/state/src/chain_state.rs index d9291d655..e991ea7e5 100644 --- a/crates/state/src/chain_state.rs +++ b/crates/state/src/chain_state.rs @@ -103,8 +103,8 @@ mod tests { let root = state.state_root(); let expected = Buf32::from([ - 204, 67, 212, 125, 147, 105, 49, 245, 74, 231, 31, 227, 7, 182, 25, 145, 169, 240, 161, - 198, 228, 211, 168, 197, 252, 140, 251, 190, 127, 139, 180, 201, + 151, 170, 71, 78, 222, 173, 105, 242, 232, 9, 47, 21, 45, 160, 207, 234, 161, 29, 114, + 237, 237, 94, 26, 177, 140, 238, 193, 81, 63, 80, 88, 181, ]); assert_eq!(root, expected); diff --git a/crates/state/src/exec_update.rs b/crates/state/src/exec_update.rs index 21f3387e1..61c3539a6 100644 --- a/crates/state/src/exec_update.rs +++ b/crates/state/src/exec_update.rs @@ -4,7 +4,7 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; -use alpen_vertex_primitives::buf::Buf32; +use alpen_vertex_primitives::{buf::Buf32, evm_exec::create_evm_extra_payload}; use crate::{bridge_ops, da_blob}; @@ -36,7 +36,7 @@ impl ExecUpdate { /// Contains the explicit inputs to the STF. Implicit inputs are determined /// from the CL's exec env state. -#[derive(Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize)] +#[derive(Clone, Debug, Eq, PartialEq, BorshDeserialize, BorshSerialize)] pub struct UpdateInput { /// Update index. This is incremented exactly 1. This is to handle the /// future possible cases where we skip CL blocks and provide a monotonic @@ -52,6 +52,21 @@ pub struct UpdateInput { extra_payload: Vec, } +impl<'a> Arbitrary<'a> for UpdateInput { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let update_idx = u64::arbitrary(u)?; + let entries_root = Buf32::arbitrary(u)?; + let block_hash = Buf32::arbitrary(u)?; + let extra_payload = create_evm_extra_payload(block_hash); + + Ok(Self { + update_idx, + entries_root, + extra_payload, + }) + } +} + impl UpdateInput { pub fn new(update_idx: u64, entries_root: Buf32, extra_payload: Vec) -> Self { Self { diff --git a/example_config.toml b/example_config.toml index 19a95cc1f..083b79199 100644 --- a/example_config.toml +++ b/example_config.toml @@ -1,19 +1,21 @@ -[reader_config] -max_reorg_depth = 4 -client_poll_dur_ms = 200 +[client] +rpc_port = 8432 +datadir = "/path/to/data/directory" +# sequencer_key = "/path/to/data/directory/" [bitcoind_rpc] -rpc_url = "http://localhost:18332" +rpc_url = "localhost:18332" rpc_user = "alpen" rpc_password = "alpen" network = "regtest" -[client] -rpc_port = 8432 -datadir = "/path/to/data/directory" -# sequencer_key = "/path/to/data/directory/" - [sync] l1_follow_distance = 6 max_reorg_depth = 4 client_poll_dur_ms = 200 + +[exec.reth] +# reth {authrpc.address}:{authrpc.port} +rpc_url = "localhost:8551" +# reth authrpc.jwtsecret path +secret = "/path/to/jwt.hex" diff --git a/functional-tests/entry.py b/functional-tests/entry.py index 49a94815e..16620b11c 100755 --- a/functional-tests/entry.py +++ b/functional-tests/entry.py @@ -18,6 +18,8 @@ def generate_seqkey() -> bytes: assert len(buf) == 32, "bad seqkey len" return buf +def generate_jwt_secret() -> str: + return os.urandom(32).hex() def generate_blocks( bitcoin_rpc: BitcoindClient, @@ -94,7 +96,7 @@ def __init__(self, port_range: list[int]): @flexitest.with_ectx("ctx") def create_sequencer( - self, bitcoind_sock: str, bitcoind_user: str, bitcoind_pass: str, ctx: flexitest.EnvContext + self, bitcoind_sock: str, bitcoind_user: str, bitcoind_pass: str, reth_socket: str, reth_secret_path: str, ctx: flexitest.EnvContext ) -> flexitest.Service: datadir = ctx.make_service_dir("sequencer") rpc_port = self.next_port() @@ -115,8 +117,9 @@ def create_sequencer( "--bitcoind-host", bitcoind_sock, "--bitcoind-user", bitcoind_user, "--bitcoind-password", bitcoind_pass, - "--network", - "regtest", + "--reth-authrpc", reth_socket, + "--reth-jwtsecret", reth_secret_path, + "--network", "regtest", "--sequencer-key", keyfile, ] # fmt: on @@ -134,6 +137,34 @@ def _create_rpc(): return svc +class RethFactory(flexitest.Factory): + def __init__(self, port_range: list[int]): + super().__init__(port_range) + + @flexitest.with_ectx("ctx") + def create_exec_client( + self, reth_secret_path: str, ctx: flexitest.EnvContext + ) -> flexitest.Service: + datadir = ctx.make_service_dir("reth") + rpc_port = self.next_port() + logfile = os.path.join(datadir, "service.log") + + # fmt: off + cmd = [ + "alpen-vertex-reth", + "--datadir", datadir, + "--authrpc.port", str(rpc_port), + "--authrpc.jwtsecret", reth_secret_path, + "-vvvv" + ] + # fmt: on + props = {"rpc_port": rpc_port} + + with open(logfile, "w") as f: + svc = flexitest.service.ProcService(props, cmd, stdout=f) + + return svc + class BasicEnvConfig(flexitest.EnvConfig): def __init__(self): @@ -142,6 +173,7 @@ def __init__(self): def init(self, ctx: flexitest.EnvContext) -> flexitest.LiveEnv: btc_fac = ctx.get_factory("bitcoin") seq_fac = ctx.get_factory("sequencer") + reth_fac = ctx.get_factory("reth") bitcoind = btc_fac.create_regtest_bitcoin() time.sleep(BLOCK_GENERATION_INTERVAL_SECS) @@ -149,16 +181,28 @@ def init(self, ctx: flexitest.EnvContext) -> flexitest.LiveEnv: brpc = bitcoind.create_rpc() brpc.proxy.createwallet("dummy") + secret_dir = ctx.make_service_dir("secret") + reth_secret_path = os.path.join(secret_dir, "jwt.hex") + + with open(reth_secret_path, 'w') as file: + file.write(generate_jwt_secret()) + + reth = reth_fac.create_exec_client(reth_secret_path) + + reth_port = reth.get_prop("rpc_port") + reth_socket = f"localhost:{reth_port}" + # generate blocks every 500 millis generate_blocks(brpc, BLOCK_GENERATION_INTERVAL_SECS) rpc_port = bitcoind.get_prop("rpc_port") rpc_user = bitcoind.get_prop("rpc_user") rpc_pass = bitcoind.get_prop("rpc_password") rpc_sock = f"localhost:{rpc_port}" - sequencer = seq_fac.create_sequencer(rpc_sock, rpc_user, rpc_pass) - time.sleep(BLOCK_GENERATION_INTERVAL_SECS) + sequencer = seq_fac.create_sequencer(rpc_sock, rpc_user, rpc_pass, reth_socket, reth_secret_path) + # need to wait for at least `genesis_l1_height` blocks to be generated. sleeping some more for safety + time.sleep(BLOCK_GENERATION_INTERVAL_SECS * 10) - svcs = {"bitcoin": bitcoind, "sequencer": sequencer} + svcs = {"bitcoin": bitcoind, "sequencer": sequencer, "reth": reth} return flexitest.LiveEnv(svcs) @@ -173,8 +217,9 @@ def main(argv): btc_fac = BitcoinFactory([12300 + i for i in range(20)]) seq_fac = VertexFactory([12400 + i for i in range(20)]) + reth_fac = RethFactory([12500 + i for i in range(20)]) - factories = {"bitcoin": btc_fac, "sequencer": seq_fac} + factories = {"bitcoin": btc_fac, "sequencer": seq_fac, "reth": reth_fac} global_envs = {"basic": BasicEnvConfig()} rt = flexitest.TestRuntime(global_envs, datadir_root, factories) diff --git a/reth/Cargo.toml b/reth/Cargo.toml new file mode 100644 index 000000000..858680d97 --- /dev/null +++ b/reth/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "alpen-vertex-reth" +version = "0.1.0" + +[[bin]] +name = "alpen-vertex-reth" +path = "src/main.rs" + +[dependencies] +alloy-genesis = { workspace = true } +clap = "4" +eyre = "0.6" +reth = { workspace = true } +reth-chainspec = { workspace = true } +reth-db = { workspace = true } +reth-node-ethereum = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } +toml = { workspace = true } +tracing = { workspace = true } diff --git a/reth/res/alpen-dev-chain.json b/reth/res/alpen-dev-chain.json new file mode 100644 index 000000000..b8514e0ca --- /dev/null +++ b/reth/res/alpen-dev-chain.json @@ -0,0 +1,34 @@ +{ + "nonce": "0x42", + "timestamp": "0x0", + "extraData": "0x5343", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { + "balance": "0xD3C21BCECCEDA1000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "config": { + "ethash": {}, + "chainId": 12345, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "shanghaiTime": 0 + } +} \ No newline at end of file diff --git a/reth/src/main.rs b/reth/src/main.rs new file mode 100644 index 000000000..2353fa5ea --- /dev/null +++ b/reth/src/main.rs @@ -0,0 +1,75 @@ +use std::{future::Future, sync::Arc}; + +use alloy_genesis::Genesis; +use clap::Parser; +use reth::{ + args::LogArgs, + builder::{NodeBuilder, WithLaunchContext}, + commands::node::NodeCommand, + CliRunner, +}; +use reth_chainspec::ChainSpec; +use reth_node_ethereum::EthereumNode; +use tracing::info; + +const ALPEN_CHAIN_SPEC: &str = include_str!("../res/alpen-dev-chain.json"); + +fn main() { + reth::sigsegv_handler::install(); + + // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + let mut command = NodeCommand::::parse(); + // use provided alpen chain spec + command.chain = parse_chain_spec(ALPEN_CHAIN_SPEC).expect("valid chainspec"); + // disable peer discovery + command.network.discovery.disable_discovery = true; + + if let Err(err) = run(command, |builder, _| async { + let handle = builder.launch_node(EthereumNode::default()).await?; + handle.node_exit_future.await + }) { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} + +/// Our custom cli args extension that adds one flag to reth default CLI. +#[derive(Debug, clap::Parser)] +pub struct AdditionalConfig { + #[command(flatten)] + pub logs: LogArgs, +} + +fn parse_chain_spec(chain_json: &str) -> eyre::Result> { + // both serialized Genesis and ChainSpec structs supported + let genesis: Genesis = serde_json::from_str(chain_json)?; + + Ok(Arc::new(genesis.into())) +} + +/// Run node with logging +/// based on reth::cli::Cli::run +fn run(mut command: NodeCommand, launcher: L) -> eyre::Result<()> +where + L: FnOnce(WithLaunchContext>>, AdditionalConfig) -> Fut, + Fut: Future>, +{ + command.ext.logs.log_file_directory = command + .ext + .logs + .log_file_directory + .join(command.chain.chain.to_string()); + + let _guard = command.ext.logs.init_tracing()?; + info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", + command.ext.logs.log_file_directory); + + let runner = CliRunner::default(); + runner.run_command_until_exit(|ctx| command.execute(ctx, launcher))?; + + Ok(()) +} diff --git a/sequencer/Cargo.toml b/sequencer/Cargo.toml index e4e8b4d51..e8e04f705 100644 --- a/sequencer/Cargo.toml +++ b/sequencer/Cargo.toml @@ -13,6 +13,7 @@ alpen-vertex-common = { workspace = true } alpen-vertex-consensus-logic = { workspace = true } alpen-vertex-db = { workspace = true } alpen-vertex-evmctl = { workspace = true } +alpen-vertex-evmexec = { workspace = true } alpen-vertex-primitives = { workspace = true } alpen-vertex-rpc-api = { workspace = true } alpen-vertex-state = { workspace = true } diff --git a/sequencer/src/args.rs b/sequencer/src/args.rs index 9bbd15fa8..e6a994943 100644 --- a/sequencer/src/args.rs +++ b/sequencer/src/args.rs @@ -1,41 +1,43 @@ use std::path::PathBuf; use argh::FromArgs; +use bitcoin::Network; -#[derive(Clone, FromArgs)] +#[derive(Debug, Clone, FromArgs)] #[argh(description = "Alpen Vertex sequencer")] pub struct Args { - // TODO: add a rollup_config file arg + // TODO: default config location + #[argh(option, short = 'c', description = "path to configuration")] + pub config: Option, + #[argh( option, short = 'd', description = "datadir path that will contain databases" )] - pub datadir: PathBuf, + pub datadir: Option, #[argh(option, short = 'r', description = "JSON-RPC port")] - pub rpc_port: u16, + pub rpc_port: Option, #[argh(option, description = "bitcoind RPC host")] - pub bitcoind_host: String, + pub bitcoind_host: Option, #[argh(option, description = "bitcoind RPC user")] - pub bitcoind_user: String, + pub bitcoind_user: Option, #[argh(option, description = "bitcoind RPC password")] - pub bitcoind_password: String, + pub bitcoind_password: Option, - #[argh(option, short = 'c', description = "path to configuration")] - pub config: Option, - - #[argh( - option, - short = 'n', - description = "L1 network to run on", - default = "\"regtest\".to_owned()" - )] - pub network: String, + #[argh(option, short = 'n', description = "L1 network to run on")] + pub network: Option, #[argh(option, short = 'k', description = "path to sequencer root key")] pub sequencer_key: Option, + + #[argh(option, description = "reth authrpc host:port")] + pub reth_authrpc: Option, + + #[argh(option, description = "path to reth authrpc jwtsecret")] + pub reth_jwtsecret: Option, } diff --git a/sequencer/src/config.rs b/sequencer/src/config.rs index 3f1445655..4e69e54bb 100644 --- a/sequencer/src/config.rs +++ b/sequencer/src/config.rs @@ -27,11 +27,23 @@ pub struct BitcoindParams { pub network: Network, } +#[derive(Deserialize, Debug)] +pub struct RethELParams { + pub rpc_url: String, + pub secret: PathBuf, +} + +#[derive(Deserialize, Debug)] +pub struct ExecParams { + pub reth: RethELParams, +} + #[derive(Deserialize, Debug)] pub struct Config { pub client: ClientParams, pub bitcoind_rpc: BitcoindParams, pub sync: SyncParams, + pub exec: ExecParams, } impl Config { @@ -53,16 +65,40 @@ impl Config { max_reorg_depth: 4, client_poll_dur_ms: 200, }, + exec: ExecParams { + reth: RethELParams { + rpc_url: String::new(), + secret: PathBuf::new(), + }, + }, } } pub fn update_from_args(&mut self, args: &Args) { let args = args.clone(); - self.bitcoind_rpc.rpc_user = args.bitcoind_user; - self.bitcoind_rpc.rpc_url = args.bitcoind_host; - self.client.rpc_port = args.rpc_port; - self.bitcoind_rpc.rpc_password = args.bitcoind_password; - self.client.datadir = args.datadir; - self.client.sequencer_key = args.sequencer_key; + if let Some(rpc_user) = args.bitcoind_user { + self.bitcoind_rpc.rpc_user = rpc_user; + } + if let Some(rpc_url) = args.bitcoind_host { + self.bitcoind_rpc.rpc_url = rpc_url; + } + if let Some(rpc_password) = args.bitcoind_password { + self.bitcoind_rpc.rpc_password = rpc_password; + } + if let Some(rpc_port) = args.rpc_port { + self.client.rpc_port = rpc_port; + } + if let Some(datadir) = args.datadir { + self.client.datadir = datadir; + } + if args.sequencer_key.is_some() { + self.client.sequencer_key = args.sequencer_key; + } + if let Some(rpc_url) = args.reth_authrpc { + self.exec.reth.rpc_url = rpc_url; + } + if let Some(jwtsecret) = args.reth_jwtsecret { + self.exec.reth.secret = jwtsecret; + } } } @@ -87,6 +123,10 @@ mod test { l1_follow_distance = 6 max_reorg_depth = 4 client_poll_dur_ms = 200 + + [exec.reth] + rpc_url = "http://localhost:8551" + secret = "1234567890abcdef" "#; assert!(toml::from_str::(config_string).is_ok()); diff --git a/sequencer/src/main.rs b/sequencer/src/main.rs index 5f9c05628..b7aa1decf 100644 --- a/sequencer/src/main.rs +++ b/sequencer/src/main.rs @@ -6,13 +6,14 @@ use std::path::PathBuf; use std::process; use std::sync::Arc; use std::thread; -use std::time; use alpen_vertex_primitives::l1::L1Status; use anyhow::Context; use bitcoin::Network; use config::Config; use format_serde_error::SerdeError; +use reth_rpc_types::engine::JwtError; +use reth_rpc_types::engine::JwtSecret; use thiserror::Error; use tokio::sync::{broadcast, oneshot, RwLock}; use tracing::*; @@ -24,6 +25,7 @@ use alpen_vertex_consensus_logic::duty_executor::{self, IdentityData, IdentityKe use alpen_vertex_consensus_logic::sync_manager; use alpen_vertex_consensus_logic::sync_manager::SyncManager; use alpen_vertex_db::traits::Database; +use alpen_vertex_evmexec::{fork_choice_state_initial, EngineRpcClient}; use alpen_vertex_primitives::buf::Buf32; use alpen_vertex_primitives::{block_credential, params::*}; use alpen_vertex_rpc_api::AlpenApiServer; @@ -43,6 +45,9 @@ pub enum InitError { #[error("config: {0}")] MalformedConfig(#[from] SerdeError), + #[error("jwt: {0}")] + MalformedSecret(#[from] JwtError), + #[error("{0}")] Other(String), } @@ -54,6 +59,13 @@ fn load_configuration(path: &Path) -> Result { Ok(conf) } +fn load_jwtsecret(path: &Path) -> Result { + let secret = fs::read_to_string(path)?; + let jwt_secret = JwtSecret::from_hex(secret)?; + + Ok(jwt_secret) +} + fn main() { let args: Args = argh::from_env(); if let Err(e) = main_inner(args) { @@ -84,6 +96,16 @@ fn main_inner(args: Args) -> anyhow::Result<()> { cred_rule: block_credential::CredRule::Unchecked, horizon_l1_height: 3, genesis_l1_height: 5, + evm_genesis_block_hash: Buf32( + "0x37ad61cff1367467a98cf7c54c4ac99e989f1fbb1bc1e646235e90c065c565ba" + .parse() + .unwrap(), + ), + evm_genesis_block_state_root: Buf32( + "0x351714af72d74259f45cd7eab0b04527cd40e74836a45abcae50f92d919d988f" + .parse() + .unwrap(), + ), }, run: RunParams { l1_follow_distance: config.sync.l1_follow_distance, @@ -108,10 +130,10 @@ fn main_inner(args: Args) -> anyhow::Result<()> { // Initialize databases. let l1_db = Arc::new(alpen_vertex_db::L1Db::new(rbdb.clone())); - let l2_db = Arc::new(alpen_vertex_db::stubs::l2::StubL2Db::new()); // FIXME stub + let l2_db = Arc::new(alpen_vertex_db::l2::db::L2Db::new(rbdb.clone())); let sync_ev_db = Arc::new(alpen_vertex_db::SyncEventDb::new(rbdb.clone())); let cs_db = Arc::new(alpen_vertex_db::ClientStateDb::new(rbdb.clone())); - let chst_db = Arc::new(alpen_vertex_db::stubs::chain_state::StubChainstateDb::new()); + let chst_db = Arc::new(alpen_vertex_db::ChainStateDb::new(rbdb.clone())); let database = Arc::new(alpen_vertex_db::database::CommonDatabase::new( l1_db, l2_db, sync_ev_db, cs_db, chst_db, )); @@ -134,7 +156,19 @@ fn main_inner(args: Args) -> anyhow::Result<()> { } // Init engine controller. - let eng_ctl = alpen_vertex_evmctl::stub::StubController::new(time::Duration::from_millis(100)); + let reth_jwtsecret = load_jwtsecret(&config.exec.reth.secret)?; + let client = EngineRpcClient::from_url_secret( + &format!("http://{}", &config.exec.reth.rpc_url), + reth_jwtsecret, + ); + + let initial_fcs = fork_choice_state_initial(database.clone(), params.rollup())?; + let eng_ctl = alpen_vertex_evmexec::engine::RpcExecEngineCtl::new( + client, + initial_fcs, + rt.handle().clone(), + database.l2_provider().clone(), + ); let eng_ctl = Arc::new(eng_ctl); // Start the sync manager. @@ -147,7 +181,7 @@ fn main_inner(args: Args) -> anyhow::Result<()> { let sync_man = Arc::new(sync_man); // If the sequencer key is set, start the sequencer duties task. - if let Some(seqkey_path) = &args.sequencer_key { + if let Some(seqkey_path) = &config.client.sequencer_key { info!(?seqkey_path, "initing sequencer duties task"); let idata = load_seqkey(seqkey_path)?; @@ -163,15 +197,18 @@ fn main_inner(args: Args) -> anyhow::Result<()> { // Spawn the two tasks. thread::spawn(move || { // FIXME figure out why this can't infer the type, it's like *right there* - duty_executor::duty_tracker_task::<_, alpen_vertex_evmctl::stub::StubController>( - cu_rx, - duties_tx, - idata.ident, - db, - ) + duty_executor::duty_tracker_task::<_>(cu_rx, duties_tx, idata.ident, db) }); thread::spawn(move || { - duty_executor::duty_dispatch_task(duties_rx, idata.key, sm, db2, eng_ctl_de, pool) + duty_executor::duty_dispatch_task( + duties_rx, + idata.key, + sm, + db2, + eng_ctl_de, + pool, + params.rollup(), + ) }); }