diff --git a/based/Cargo.lock b/based/Cargo.lock index 5059b2947..ebf1c73c1 100644 --- a/based/Cargo.lock +++ b/based/Cargo.lock @@ -2305,6 +2305,37 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.101", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -2798,6 +2829,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "font8x8" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e" + [[package]] name = "foreign-types" version = "0.3.2" @@ -3743,6 +3780,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -5087,6 +5133,8 @@ dependencies = [ "tabwriter", "tokio", "tracing", + "tui-big-text", + "tui-scrollview", "uuid", ] @@ -5869,6 +5917,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "reqwest" version = "0.12.15" @@ -8757,6 +8811,36 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" +[[package]] +name = "rstest" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.1", +] + +[[package]] +name = "rstest_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.1", + "syn 2.0.101", + "unicode-ident", +] + [[package]] name = "ruint" version = "1.14.0" @@ -10173,6 +10257,29 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui-big-text" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97cefa9f1425ab6146db2961241cec86845d11105b5dd6bb504294b0cdd21af" +dependencies = [ + "derive_builder", + "font8x8", + "itertools 0.14.0", + "ratatui 0.29.0", +] + +[[package]] +name = "tui-scrollview" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6e1d736488ba64c2e74637089a6b9ca666ccd2eaade3ab83854f415f1d260b" +dependencies = [ + "indoc", + "ratatui 0.29.0", + "rstest", +] + [[package]] name = "tungstenite" version = "0.24.0" diff --git a/based/bin/overseer/Cargo.toml b/based/bin/overseer/Cargo.toml index 2a394b79a..0c9371368 100644 --- a/based/bin/overseer/Cargo.toml +++ b/based/bin/overseer/Cargo.toml @@ -19,7 +19,9 @@ reqwest.workspace = true serde.workspace = true strum.workspace = true strum_macros.workspace = true +tabwriter.workspace = true tokio.workspace = true tracing.workspace = true +tui-big-text = "0.7.1" +tui-scrollview = "0.5.1" uuid.workspace = true -tabwriter.workspace = true diff --git a/based/bin/overseer/src/data.rs b/based/bin/overseer/src/data.rs index b3ab91ffc..afbd49f8f 100644 --- a/based/bin/overseer/src/data.rs +++ b/based/bin/overseer/src/data.rs @@ -1,8 +1,9 @@ use std::io::Write; +use alloy_primitives::Address; use block::BlockData; use bop_common::{ - api::{RollupConfig, SyncStatus}, + api::{OpGethPeer, OpPeerInfo, RollupConfig, SyncStatus}, communication::{Consumer, Queue, queues_dir}, telemetry::{ Telemetry, @@ -13,7 +14,10 @@ use bop_common::{ time::{Duration, TimingMessage}, }; use frag::FragData; +use ratatui::{buffer::Buffer, layout::Size, widgets::Wrap}; +use reqwest::Url; use transaction::TransactionData; +use tui_scrollview::ScrollViewState; use crate::{ OverseerConsumers, @@ -35,6 +39,7 @@ pub struct UIData { pub table_frags: TableState, pub table_pool: TableState, pub table_system: TableState, + pub scroll_view_state: ScrollViewState, } impl UIData { @@ -49,6 +54,10 @@ impl UIData { Layout::horizontal([Constraint::Percentage(35), Constraint::Percentage(35), Constraint::Fill(1)]) .areas(bottom); + let [middle_top, middle_bottom] = Layout::vertical([Constraint::Length(8), Constraint::Fill(1)]).areas(middle); + + self.render_keybindings_and_branding(data, middle_top, frame); + self.render_system_messages(data, right, frame); let [left, bottom_left] = Layout::vertical([Constraint::Percentage(50), Constraint::Fill(1)]).areas(left); @@ -71,12 +80,65 @@ impl UIData { TransactionData::pool_header(), data.transactions.iter().map(|b| b.to_pool_row()).rev(), frame, - middle, + middle_bottom, ) } - pub fn render_chain_info(&mut self, data: &Data, area: Rect, frame: &mut Frame) { - frame.render_widget(Paragraph::new(format!("{:#?}", data.rollup_config)), area); + pub fn render_chain_info(&mut self, data: &Data, area: Rect, buf: &mut Buffer) { + let rollup_config = format!("{:#?}", data.rollup_config); + let rollup_len = rollup_config.lines().count() as u16 + 2; + let rollup_paragraph = Paragraph::new(rollup_config).block(Block::new().borders(Borders::all())); + + let peers = format!("{:#?}", data.peers_local_op_node); + let peers_len = peers.lines().count() as u16 + 2; + let peers_paragraph = Paragraph::new(peers) + .wrap(Wrap { trim: false }) + .block(Block::new().title("Op Node Peers").borders(Borders::all())); + + let peers_geth = format!("{:#?}", data.peers_local_op_geth); + let peers_len_geth = peers_geth.lines().count() as u16 + 2; + let peers_paragraph_geth = Paragraph::new(peers_geth) + .wrap(Wrap { trim: false }) + .block(Block::new().title("Op Geth Peers").borders(Borders::all())); + + let width = if area.height < peers_len + rollup_len + peers_len_geth { area.width - 1 } else { area.width }; + + let mut scroll_view = + tui_scrollview::ScrollView::new(Size::new(width, rollup_len + peers_len + peers_len_geth)); + let [top, mid, bottom] = + Layout::vertical([Constraint::Length(rollup_len), Constraint::Length(peers_len), Constraint::Fill(1)]) + .areas(scroll_view.area()); + + scroll_view.render_widget(rollup_paragraph, top); + scroll_view.render_widget(peers_paragraph, mid); + scroll_view.render_widget(peers_paragraph_geth, bottom); + + ::render( + scroll_view, + area, + buf, + &mut self.scroll_view_state, + ); + } + + fn render_keybindings_and_branding(&mut self, _data: &Data, area: Rect, frame: &mut Frame) { + let mut tw = tabwriter::TabWriter::new(vec![]); + let _ = writeln!(&mut tw, "Q:\tQuit"); + let _ = writeln!(&mut tw, "◄ ►:\tChange Tab"); + + tw.flush().unwrap(); + let txt = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + let info = Paragraph::new(txt) + .block(Block::new().title("Key Bindings").borders(Borders::all()).fg(Color::from_u32(0x800080))); + let [left, right] = Layout::horizontal([Constraint::Length(20), Constraint::Fill(1)]).areas(area); + let big_text = tui_big_text::BigText::builder() + .pixel_size(tui_big_text::PixelSize::Full) + .style(Style::new().blue()) + .centered() + .lines(vec!["Based Op".fg(Color::from_u32(0x800080)).into()]) + .build(); + frame.render_widget(big_text, right); + frame.render_widget(info, left); } fn render_system_overview(&mut self, data: &Data, area: Rect, frame: &mut Frame) { @@ -88,60 +150,81 @@ impl UIData { ); let (t, cur_state) = data.current_state().unwrap_or_default(); + if let Some((url, address)) = data.current_gateway.as_ref() { + let _ = writeln!(&mut tw, "Current Gateway:\t{url}\t signing wallet: {address}"); + } else { + let _ = writeln!(&mut tw, "Current Gateway:"); + } let _ = writeln!(&mut tw, "Current Block:\t{}", data.block_number); let _ = writeln!(&mut tw, "Current State:\t{}", cur_state.as_ref()); let (_, last_state) = data.last_state().unwrap_or_default(); let _ = writeln!(&mut tw, "Prev State:\t{}", empty_if_default(last_state)); let _ = writeln!(&mut tw, "Last State Transition:\t{}", empty_if_default(t)); + let local_op_bn = data.sync_status_local.1.unsafe_l2.number; + if local_op_bn == 0 { + let _ = writeln!(&mut tw, "Based Op Node sync:\tUnreachable/Is Down"); + } else { + let _ = writeln!(&mut tw, "Based Op Node sync:\t{local_op_bn}"); + } + let _ = writeln!(&mut tw, "Based Op Node peers:\t{}", data.peers_local_op_node.len()); + let _ = writeln!(&mut tw, "Based Op Geth peers:\t{}", data.peers_local_op_geth.len()); + tw.flush().unwrap(); let txt = String::from_utf8(tw.into_inner().unwrap()).unwrap(); - let info = Paragraph::new(txt).block(Block::new().title("Local Gateway Status").borders(Borders::all())); + let info = Paragraph::new(txt).block(Block::new().title("Based-Gateway Status").borders(Borders::all())); frame.render_widget(info, area); } fn render_sync_status(&mut self, data: &Data, area: Rect, frame: &mut Frame) { let mut tw = tabwriter::TabWriter::new(vec![]); - let _ = writeln!(&mut tw, "Last Update:\t{}", data.sync_status.0.with_fmt("%d %H:%M:%S%.3f")); + let _ = writeln!(&mut tw, "Last Update:\t{}", data.sync_status_global.0.with_fmt("%d %H:%M:%S%.3f")); let _ = writeln!( &mut tw, "Unsafe L2:\t{}\t{}", - data.sync_status.1.unsafe_l2.number, data.sync_status.1.unsafe_l2.hash + data.sync_status_global.1.unsafe_l2.number, data.sync_status_global.1.unsafe_l2.hash + ); + let _ = writeln!( + &mut tw, + "Safe L2:\t{}\t{}", + data.sync_status_global.1.safe_l2.number, data.sync_status_global.1.safe_l2.hash ); - let _ = - writeln!(&mut tw, "Safe L2:\t{}\t{}", data.sync_status.1.safe_l2.number, data.sync_status.1.safe_l2.hash); let _ = writeln!( &mut tw, "Finalized L2:\t{}\t{}", - data.sync_status.1.finalized_l2.number, data.sync_status.1.finalized_l2.hash + data.sync_status_global.1.finalized_l2.number, data.sync_status_global.1.finalized_l2.hash ); let _ = writeln!( &mut tw, "Pending Safe L2:\t{}\t{}", - data.sync_status.1.pending_safe_l2.number, data.sync_status.1.pending_safe_l2.hash + data.sync_status_global.1.pending_safe_l2.number, data.sync_status_global.1.pending_safe_l2.hash ); let _ = writeln!( &mut tw, - "Queued Un Safe L2:\t{}\t{}", - empty_if_default(data.sync_status.1.queued_unsafe_l2.as_ref().map(|t| t.number).unwrap_or_default()), - empty_if_default(data.sync_status.1.queued_unsafe_l2.as_ref().map(|t| t.hash).unwrap_or_default()) + "L1:\t{}\t{}", + data.sync_status_global.1.current_l1.number, data.sync_status_global.1.current_l1.hash + ); + let _ = writeln!( + &mut tw, + "Safe L1:\t{}\t{}", + data.sync_status_global.1.safe_l1.number, data.sync_status_global.1.safe_l1.hash ); - let _ = - writeln!(&mut tw, "L1:\t{}\t{}", data.sync_status.1.current_l1.number, data.sync_status.1.current_l1.hash); - let _ = - writeln!(&mut tw, "Safe L1:\t{}\t{}", data.sync_status.1.safe_l1.number, data.sync_status.1.safe_l1.hash); let _ = writeln!( &mut tw, "Finalized L1:\t{}\t{}", - data.sync_status.1.current_l1_finalized.number, data.sync_status.1.current_l1_finalized.hash + data.sync_status_global.1.current_l1_finalized.number, data.sync_status_global.1.current_l1_finalized.hash + ); + let _ = writeln!( + &mut tw, + "Head L1:\t{}\t{}", + data.sync_status_global.1.head_l1.number, data.sync_status_global.1.head_l1.hash ); - let _ = - writeln!(&mut tw, "Head L1:\t{}\t{}", data.sync_status.1.head_l1.number, data.sync_status.1.head_l1.hash); + tw.flush().unwrap(); let txt = String::from_utf8(tw.into_inner().unwrap()).unwrap(); - let info = Paragraph::new(txt).block(Block::new().title("Chain Sync Status").borders(Borders::all())); + let info = Paragraph::new(txt).block(Block::new().title("Global Chain Status").borders(Borders::all())); frame.render_widget(info, area); } @@ -228,7 +311,11 @@ pub struct Data { pub timekeeper: TimeKeeper, pub time_datas: TimeDatas, pub rollup_config: RollupConfig, - pub sync_status: (Nanos, SyncStatus), + pub sync_status_global: (Nanos, SyncStatus), + pub current_gateway: Option<(Url, Address)>, + pub sync_status_local: (Nanos, SyncStatus), + pub peers_local_op_node: Vec, + pub peers_local_op_geth: Vec, } impl Data { pub fn new(rollup_config: RollupConfig) -> Self { @@ -245,7 +332,11 @@ impl Data { syncstatus_poller: Repeater::every(Duration::from_secs(1)), flamegraph_resetter: Repeater::every(Duration::from_secs(60)), rollup_config, - sync_status: Default::default(), + sync_status_global: Default::default(), + current_gateway: Default::default(), + sync_status_local: Default::default(), + peers_local_op_node: Default::default(), + peers_local_op_geth: Default::default(), } } } @@ -385,10 +476,38 @@ impl Data { self.time_datas.reset() } if self.syncstatus_poller.fired() { - if let Ok(sync_status) = - consumers.sync_status().inspect_err(|e| tracing::warn!("couldn't get SyncStatus from portal: {e}")) + if let Ok(sync_status_global) = consumers + .sync_status_global() + .inspect_err(|e| tracing::warn!("couldn't get SyncStatus from portal: {e}")) + { + self.sync_status_global = (Nanos::now(), sync_status_global) + } + + if let Ok(sync_status_local) = consumers + .sync_status_local() + .inspect_err(|e| tracing::warn!("couldn't get SyncStatus from local based-op-node: {e}")) + { + self.sync_status_local = (Nanos::now(), sync_status_local) + } + + self.current_gateway = consumers + .current_gateway() + .inspect_err(|e| tracing::warn!("couldn't get current sequencing based-gateway from portal: {e}")) + .ok(); + + if let Ok(mut peers) = consumers + .peers_based_op_node() + .inspect_err(|e| tracing::warn!("couldn't get peers of local based-op-node: {e}")) + { + peers.sort_unstable_by(|d1, d2| d1.peer_id.cmp(&d2.peer_id)); + self.peers_local_op_node = peers; + } + if let Ok(mut peers) = consumers + .peers_based_op_geth() + .inspect_err(|e| tracing::warn!("couldn't get peers of local based-op-node: {e}")) { - self.sync_status = (Nanos::now(), sync_status) + peers.sort_unstable_by(|d1, d2| d1.id.cmp(&d2.id)); + self.peers_local_op_geth = peers; } } } diff --git a/based/bin/overseer/src/main.rs b/based/bin/overseer/src/main.rs index 1717877b3..1a19e7991 100644 --- a/based/bin/overseer/src/main.rs +++ b/based/bin/overseer/src/main.rs @@ -9,8 +9,11 @@ mod utils; use std::io::stdout; +use alloy_primitives::Address; use bop_common::{ - api::{OpNodeApiClient, RollupConfig, SyncStatus}, + api::{ + self, OpGethPeer, OpNodeApiClient, OpNodeP2PApiClient, OpPeerInfo, RegistryApiClient, RollupConfig, SyncStatus, + }, communication::Consumer, config::{LoggingConfig, LoggingFlags}, telemetry::{TelemetryUpdate, telemetry_queue}, @@ -41,10 +44,17 @@ use tracing::warn; use ui::plot::RenderFlags; #[derive(Parser, Debug, Clone)] -#[command(version, about, name = "based-portal")] +#[command(version, about, name = "based-overseer")] pub struct OverseerArgs { + /// The url of the portal that is connected to the main sequencer node #[arg(short, long)] pub portal_url: Url, + /// The url of the based-op-node running next to the based-gateway + #[arg(long, default_value = "http://0.0.0.0:8547")] + pub based_op_node_url: Url, + /// The url of the based-op-geth running next to the based-gateway + #[arg(long, default_value = "http://0.0.0.0:8645")] + pub based_op_geth_url: Url, } #[derive(Copy, Clone, Debug, Default, Display, FromRepr, EnumIter)] @@ -93,25 +103,51 @@ impl Mode { struct OverseerConsumers { telemetry: Consumer, runtime: tokio::runtime::Runtime, - portal_client: HttpClient, + client_portal: HttpClient, + client_based_op_node: HttpClient, + client_based_op_geth: HttpClient, } impl OverseerConsumers { - pub fn new(portal_url: Url) -> Self { + pub fn new(args: &OverseerArgs) -> Self { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("Couldn't initialize tokio runtime"); - let portal_client = HttpClient::builder().build(portal_url).expect("Couldn't initialize portal rpc client"); - Self { telemetry: telemetry_queue().into(), runtime, portal_client } + let client_portal = + HttpClient::builder().build(args.portal_url.clone()).expect("Couldn't initialize portal rpc client"); + let client_based_op_node = HttpClient::builder() + .build(args.based_op_node_url.clone()) + .expect("Couldn't initialize based-op-node rpc client"); + let client_based_op_geth = HttpClient::builder() + .build(args.based_op_geth_url.clone()) + .expect("Couldn't initialize based-op-geth rpc client"); + + Self { telemetry: telemetry_queue().into(), runtime, client_portal, client_based_op_node, client_based_op_geth } } pub fn rollup_config(&self) -> Result { - self.runtime.block_on(self.portal_client.rollup_config()) + self.runtime.block_on(self.client_portal.rollup_config()) + } + + pub fn sync_status_global(&self) -> Result { + self.runtime.block_on(self.client_portal.sync_status()) + } + + pub fn sync_status_local(&self) -> Result { + self.runtime.block_on(self.client_based_op_node.sync_status()) + } + + pub fn current_gateway(&self) -> Result<(Url, Address), ClientError> { + self.runtime.block_on(self.client_portal.current_gateway()).map(|(_, url, address, _)| (url, address)) + } + + pub fn peers_based_op_node(&self) -> Result, ClientError> { + self.runtime.block_on(self.client_based_op_node.peers(true)).map(|p| p.peers.into_values().collect()) } - pub fn sync_status(&self) -> Result { - self.runtime.block_on(self.portal_client.sync_status()) + pub fn peers_based_op_geth(&self) -> Result, ClientError> { + self.runtime.block_on(api::OpGethAdminApiClient::peers(&self.client_based_op_geth)) } } @@ -165,7 +201,7 @@ impl Overseer { TimeKeeper::render(&mut self.data, inner_area, frame) } Mode::ChainInfo => { - self.ui_data.render_chain_info(&self.data, inner_area, frame); + self.ui_data.render_chain_info(&self.data, inner_area, frame.buffer_mut()); } } } @@ -192,6 +228,10 @@ impl Overseer { match (code, modifiers, &self.mode) { (KeyCode::Right, _, _) => self.next_tab(), (KeyCode::Left, _, _) => self.previous_tab(), + (KeyCode::Up | KeyCode::Char('k'), _, Mode::ChainInfo) => self.ui_data.scroll_view_state.scroll_up(), + (KeyCode::Down | KeyCode::Char('j'), _, Mode::ChainInfo) => self.ui_data.scroll_view_state.scroll_down(), + (KeyCode::PageUp, _, Mode::ChainInfo) => self.ui_data.scroll_view_state.scroll_page_up(), + (KeyCode::PageDown, _, Mode::ChainInfo) => self.ui_data.scroll_view_state.scroll_page_down(), (KeyCode::Char('m'), _, Mode::TimekeeperRealtime) => { self.data.time_datas.toggle_render_options(RenderFlags::ShowMin) } @@ -234,7 +274,7 @@ fn main() { let _guard = init_tracing(logging_config); tracing::info!("Overseer starting"); let args = OverseerArgs::parse(); - let mut consumers = OverseerConsumers::new(args.portal_url); + let mut consumers = OverseerConsumers::new(&args); let mut overseer: Overseer = consumers.rollup_config().expect("couldn't connect to portal").into(); let genesis_time = overseer.genesis_time(); let block_duration = overseer.block_duration(); diff --git a/based/bin/portal/src/cli.rs b/based/bin/portal/src/cli.rs index a75c58dde..7d0e46cf8 100644 --- a/based/bin/portal/src/cli.rs +++ b/based/bin/portal/src/cli.rs @@ -30,7 +30,7 @@ pub struct PortalArgs { pub fallback_url: Url, /// Timeout for fallback requests in milliseconds - #[arg(long = "fallback.timeout_ms", default_value_t = 100)] + #[arg(long = "fallback.timeout_ms", default_value_t = 60_000)] pub fallback_timeout_ms: u64, /// The JWT token to use for the fallback diff --git a/based/bin/portal/src/middleware.rs b/based/bin/portal/src/middleware.rs index 87df4f4cd..ffca79984 100644 --- a/based/bin/portal/src/middleware.rs +++ b/based/bin/portal/src/middleware.rs @@ -135,6 +135,7 @@ where fallback_eth_client.clone().request("eth_getBlockByNumber", ("latest", false)).await; match r { Ok(r) => { + tracing::warn!("Serving latest instead of finalized or safe for eth_getBlockByNumber. This should only happen at genesis!"); let payload = ResponsePayload::success(r); MethodResponse::response(req.id, payload.into(), 4_000_000_000usize) } diff --git a/based/crates/common/src/api.rs b/based/crates/common/src/api.rs index f39525800..472792673 100644 --- a/based/crates/common/src/api.rs +++ b/based/crates/common/src/api.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, @@ -158,6 +160,35 @@ pub trait PortalApi { async fn op_geth_bootnode_enode(&self) -> RpcResult; } +#[rpc(client, server, namespace = "optimism")] +pub trait OpNodeApi { + /// The rollup config of the op-node + #[method(name = "rollupConfig")] + async fn rollup_config(&self) -> RpcResult; + + /// The syncstatus of the op-node + #[method(name = "syncStatus")] + async fn sync_status(&self) -> RpcResult; +} + +#[rpc(client, server, namespace = "opp2p")] +pub trait OpNodeP2PApi { + /// The rollup config of the op-node + #[method(name = "self")] + async fn peer_info(&self) -> RpcResult; + #[method(name = "peers")] + async fn peers(&self, _t: bool) -> RpcResult; +} + +#[rpc(client, server, namespace = "admin")] +pub trait OpGethAdminApi { + /// The rollup config of the op-node + #[method(name = "nodeInfo")] + async fn node_info(&self) -> RpcResult; + #[method(name = "peers")] + async fn peers(&self) -> RpcResult>; +} + #[derive(Clone, Serialize, Deserialize, Debug)] pub struct RollupConfig { pub genesis: Genesis, @@ -234,20 +265,23 @@ pub struct L2Block { pub sequence_number: u64, } -#[rpc(client, server, namespace = "optimism")] -pub trait OpNodeApi { - /// The rollup config of the op-node - #[method(name = "rollupConfig")] - async fn rollup_config(&self) -> RpcResult; - - /// The syncstatus of the op-node - #[method(name = "syncStatus")] - async fn sync_status(&self) -> RpcResult; +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct OpPeers { + pub total_connected: u64, + pub peers: HashMap, // map keyed by peer-id strings + + #[serde(rename = "bannedPeers")] + pub banned_peers: Vec, + #[serde(rename = "bannedIPS")] + pub banned_ips: Vec, + #[serde(rename = "bannedSubnets")] + pub banned_subnets: Vec, } #[derive(Clone, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct PeerInfo { +pub struct OpPeerInfo { #[serde(alias = "peerID")] pub peer_id: String, #[serde(alias = "nodeID")] @@ -263,7 +297,7 @@ pub struct PeerInfo { pub protected: bool, #[serde(alias = "chainID")] pub chain_id: u64, - pub latency: u64, + pub latency: f64, pub gossip_blocks: bool, pub scores: Scores, } @@ -278,35 +312,28 @@ pub struct Scores { #[derive(Clone, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct GossipScore { - pub total: u64, + pub total: f64, pub blocks: BlockScores, #[serde(rename = "IPColocationFactor")] - pub ip_colocation_factor: u64, - pub behavioral_penalty: u64, + pub ip_colocation_factor: f64, + pub behavioral_penalty: f64, } #[derive(Clone, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct BlockScores { - pub time_in_mesh: u64, - pub first_message_deliveries: u64, - pub mesh_message_deliveries: u64, - pub invalid_message_deliveries: u64, + pub time_in_mesh: f64, + pub first_message_deliveries: f64, + pub mesh_message_deliveries: f64, + pub invalid_message_deliveries: f64, } #[derive(Clone, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ReqRespScore { - pub valid_responses: u64, - pub error_responses: u64, - pub rejected_payloads: u64, -} - -#[rpc(client, server, namespace = "opp2p")] -pub trait OpNodeP2PApi { - /// The rollup config of the op-node - #[method(name = "self")] - async fn peer_info(&self) -> RpcResult; + pub valid_responses: f64, + pub error_responses: f64, + pub rejected_payloads: f64, } #[derive(Clone, Serialize, Deserialize, Debug)] @@ -386,23 +413,60 @@ pub struct OptimismConfig { pub eip1559_denominator_canyon: u64, } -#[rpc(client, server, namespace = "admin")] -pub trait OpGethAdminApi { - /// The rollup config of the op-node - #[method(name = "nodeInfo")] - async fn node_info(&self) -> RpcResult; +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct OpGethPeer { + pub enr: Option, + pub enode: String, + pub id: String, + pub name: String, + pub caps: Vec, + pub network: GethPeerNetwork, + pub protocols: PeerProtocols, +} + +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct GethPeerNetwork { + #[serde(alias = "localAddress")] + pub local_address: String, + #[serde(alias = "remoteAddress")] + pub remote_address: String, + pub inbound: bool, + pub trusted: bool, + #[serde(alias = "static")] + pub static_str: bool, // `static` is a reserved keyword in Rust, so we need to use `static_` +} + +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct PeerProtocols { + pub eth: EthVersion, + pub snap: SnapVersion, +} + +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct EthVersion { + pub version: u32, +} + +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct SnapVersion { + pub version: Option, } #[cfg(test)] pub mod test { use super::*; #[test] - fn parse_peerinfo() { - assert_ne!(serde_json::from_str::("{\"ENR\":\"enr:-J-4QKMgVCRicuzgRSXF--kcfNcSb3el3gnK0VTKH5IqfAnjY096UPHcnpeOkYf8Y6hdbhFbjIoRcdMxKgy1QOftlZGGAZavZyU2gmlkgnY0gmlwhKwfGmOHb3BzdGFja4Xkq4MBAIlzZWNwMjU2azGhA38YA_8AH2SrzzVprDUjXbyv88AJION0F_UdgJmIk7v2g3RjcIIjK4N1ZHCCIys\",\"addresses\":[\"/ip4/127.0.0.1/tcp/9003/p2p/16Uiu2HAmMD7NHS98BXCoNuDGuS1zueMF5LQdiexCKyjJ97frMu6M\",\"/ip4/172.31.26.99/tcp/9003/p2p/16Uiu2HAmMD7NHS98BXCoNuDGuS1zueMF5LQdiexCKyjJ97frMu6M\"],\"chainID\":0,\"connectedness\":0,\"direction\":0,\"gossipBlocks\":true,\"latency\":0,\"nodeID\":\"ca1451eb9482746566a92fbde4bcb7c646d23bfceb21f66d4d79e1e0f0819cfc\",\"peerID\":\"16Uiu2HAmMD7NHS98BXCoNuDGuS1zueMF5LQdiexCKyjJ97frMu6M\",\"protected\":false,\"protocolVersion\":\"\",\"protocols\":null,\"scores\":{\"gossip\":{\"IPColocationFactor\":0,\"behavioralPenalty\":0,\"blocks\":{\"firstMessageDeliveries\":0,\"invalidMessageDeliveries\":0,\"meshMessageDeliveries\":0,\"timeInMesh\":0},\"total\":0},\"reqResp\":{\"errorResponses\":0,\"rejectedPayloads\":0,\"validResponses\":0}},\"userAgent\":\"\"}").unwrap().peer_id, ""); + fn parse_op_node_peer_info() { + assert_ne!(serde_json::from_str::("{\"ENR\":\"enr:-J-4QKMgVCRicuzgRSXF--kcfNcSb3el3gnK0VTKH5IqfAnjY096UPHcnpeOkYf8Y6hdbhFbjIoRcdMxKgy1QOftlZGGAZavZyU2gmlkgnY0gmlwhKwfGmOHb3BzdGFja4Xkq4MBAIlzZWNwMjU2azGhA38YA_8AH2SrzzVprDUjXbyv88AJION0F_UdgJmIk7v2g3RjcIIjK4N1ZHCCIys\",\"addresses\":[\"/ip4/127.0.0.1/tcp/9003/p2p/16Uiu2HAmMD7NHS98BXCoNuDGuS1zueMF5LQdiexCKyjJ97frMu6M\",\"/ip4/172.31.26.99/tcp/9003/p2p/16Uiu2HAmMD7NHS98BXCoNuDGuS1zueMF5LQdiexCKyjJ97frMu6M\"],\"chainID\":0,\"connectedness\":0,\"direction\":0,\"gossipBlocks\":true,\"latency\":0,\"nodeID\":\"ca1451eb9482746566a92fbde4bcb7c646d23bfceb21f66d4d79e1e0f0819cfc\",\"peerID\":\"16Uiu2HAmMD7NHS98BXCoNuDGuS1zueMF5LQdiexCKyjJ97frMu6M\",\"protected\":false,\"protocolVersion\":\"\",\"protocols\":null,\"scores\":{\"gossip\":{\"IPColocationFactor\":0,\"behavioralPenalty\":0,\"blocks\":{\"firstMessageDeliveries\":0,\"invalidMessageDeliveries\":0,\"meshMessageDeliveries\":0,\"timeInMesh\":0},\"total\":0},\"reqResp\":{\"errorResponses\":0,\"rejectedPayloads\":0,\"validResponses\":0}},\"userAgent\":\"\"}").unwrap().peer_id, ""); } #[test] - fn parse_gethinfo() { + fn parse_geth_info() { assert_ne!(serde_json::from_str::("{\"enode\":\"enode://36c3170ea04471fb52a8c0d4f8f06da660e2e7388959089844269d5b790be4215ebc5052892cf40be08ffc19bcd7439db6fdf713c6fb6d0f7960906ce088de52@57.133.217.139:30303?discport=1089\",\"enr\":\"enr:-Ku4QA9W-NTtseU4M0OBXgdVBkpSEKP_D3l-TicPAyBACAdLPVjNRD2oMpBeK8_z-Y7Xl31iovo-O0eoJ4HII6d0CS6GAZbFKQKgg2V0aMfGhBXQ1LeAgmlkgnY0gmlwhDmF2YuJc2VjcDI1NmsxoQI2wxcOoERx-1KowNT48G2mYOLnOIlZCJhEJp1beQvkIYRzbmFwwIN0Y3CCdl-DdWRwggRBhHVkcDaCdl8\",\"id\":\"4b259315183c61074e998c46eec2689f2cc321e81226b4bea0c83ee57a00c96f\",\"ip\":\"57.133.217.139\",\"listenAddr\":\"[::]:30303\",\"name\":\"Geth/v1.101411.8-rc.1-374d61f9-20250211/linux-amd64/go1.23.6\",\"ports\":{\"discovery\":1089,\"listener\":30303},\"protocols\":{\"eth\":{\"config\":{\"arrowGlacierBlock\":0,\"bedrockBlock\":0,\"berlinBlock\":0,\"byzantiumBlock\":0,\"cancunTime\":0,\"canyonTime\":0,\"chainId\":2151908,\"constantinopleBlock\":0,\"depositContractAddress\":\"0x0000000000000000000000000000000000000000\",\"ecotoneTime\":0,\"eip150Block\":0,\"eip155Block\":0,\"eip158Block\":0,\"fjordTime\":0,\"graniteTime\":0,\"grayGlacierBlock\":0,\"holoceneTime\":0,\"homesteadBlock\":0,\"istanbulBlock\":0,\"londonBlock\":0,\"mergeNetsplitBlock\":0,\"muirGlacierBlock\":0,\"optimism\":{\"eip1559Denominator\":50,\"eip1559DenominatorCanyon\":250,\"eip1559Elasticity\":6},\"petersburgBlock\":0,\"regolithTime\":0,\"shanghaiTime\":0,\"terminalTotalDifficulty\":0},\"difficulty\":0,\"genesis\":\"0xf81cfade9797c41a311da5bb09fbc77fa481bfdaff40e9fcc99a4dc43453b1b3\",\"head\":\"0x4469e2e3a10785af7b05b69cbbeae7bd3c3c47b3df81dfb98dfee974918350b5\",\"network\":2151908},\"snap\":{}}}").unwrap().id, ""); } + + #[test] + fn parse_geth_peers() { + assert!(serde_json::from_str::("{\"enr\":\"enr:-KO4QPfDpnj33Qcejr3m7rk8mMA7nXzBrOvm5bpsMfuun09Nfgdv1O8qqGhl9v_e69b2wAogFjUKK8ZXReI8pgPS9ZiGAZbbGWtgg2V0aMfGhFmJ9hSAgmlkgnY0gmlwhBK5xzOJc2VjcDI1NmsxoQI-TZICg15Lqr3j5KRiqSVTVwuV9FQJpvB9GrAMB7w5pYRzbmFwwIN0Y3CCdl-DdWRwgnZf\",\"enode\":\"enode://3e4d9202835e4baabde3e4a462a92553570b95f45409a6f07d1ab00c07bc39a52dd710f098008ae5974aa625edeb46734e3634f0c9d3ab24bb35e37d12f1ac18@18.185.199.51:30303\",\"id\":\"1b83fb2f39f76c9926dd1690b3570e956f3a43e599acb89d28c739022db5d887\",\"name\":\"Geth/v1.101411.8-rc.1-374d61f9-20250211/linux-amd64/go1.23.6\",\"caps\":[\"eth/68\",\"snap/1\"],\"network\":{\"localAddress\":\"192.168.1.33:35502\",\"remoteAddress\":\"18.185.199.51:30303\",\"inbound\":false,\"trusted\":false,\"static\":false},\"protocols\":{\"eth\":{\"version\":68},\"snap\":{\"version\":1}}}").inspect_err(|e| panic!("{e}")).is_ok()); + } } diff --git a/main_node/compose.yml b/main_node/compose.yml index 61137c4c1..bf4dad51d 100644 --- a/main_node/compose.yml +++ b/main_node/compose.yml @@ -162,6 +162,7 @@ services: POSTGRES_DB: blockscout volumes: - ./data/blockscout-postgres:/var/lib/postgresql/data + shm_size: '50gb' ################################################################################ # Blockscout explorer