From 79f5e32631893bbb914f05ef101200801c20a463 Mon Sep 17 00:00:00 2001 From: benluelo Date: Mon, 5 Aug 2024 16:01:07 -0400 Subject: [PATCH 1/2] feat(hubble): scroll consensus height indexer --- Cargo.lock | 1 + hubble/Cargo.toml | 1 + hubble/hubble.nix | 6 ++ hubble/src/chain_id_query.rs | 1 + hubble/src/cli.rs | 11 +++ hubble/src/main.rs | 1 + hubble/src/scroll.rs | 160 +++++++++++++++++++++++++++++++++++ 7 files changed, 181 insertions(+) create mode 100644 hubble/src/scroll.rs diff --git a/Cargo.lock b/Cargo.lock index 1da07f2f39..2bd585fe60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4501,6 +4501,7 @@ dependencies = [ "protos", "regex", "reqwest 0.11.27", + "scroll-api", "serde", "serde-aux", "serde_json", diff --git a/hubble/Cargo.toml b/hubble/Cargo.toml index 203c4e744f..c0bb641b1f 100644 --- a/hubble/Cargo.toml +++ b/hubble/Cargo.toml @@ -38,6 +38,7 @@ prost.workspace = true protos = { workspace = true, features = ["client"] } regex = "1.10.5" reqwest = { workspace = true, features = ["json", "blocking"] } +scroll-api.workspace = true serde = { workspace = true, features = ["derive"] } serde-aux = "4.5.0" serde_json = { workspace = true } diff --git a/hubble/hubble.nix b/hubble/hubble.nix index cbfc61891c..8f9150f7a5 100644 --- a/hubble/hubble.nix +++ b/hubble/hubble.nix @@ -89,6 +89,12 @@ default = null; }; + # scroll + options.scroll_api_client = mkOption { + type = types.nullOr types.str; + default = null; + }; + options.chain_id = mkOption { type = types.nullOr types.str; example = "union-testnet-8"; default = null; }; options.grpc_url = mkOption { type = types.nullOr types.str; example = "https://grpc.example.com"; default = null; }; options.type = mkOption { type = types.enum [ "tendermint" "ethereum" "beacon" "bera" "ethereum-fork" "arb" ]; }; diff --git a/hubble/src/chain_id_query.rs b/hubble/src/chain_id_query.rs index ca3e28b078..b6e39efe0f 100644 --- a/hubble/src/chain_id_query.rs +++ b/hubble/src/chain_id_query.rs @@ -29,6 +29,7 @@ pub async fn tx(db: PgPool, indexers: Indexers) { for indexer in indexers { match indexer { + IndexerConfig::Scroll(_) => {} IndexerConfig::Arb(_) => {} IndexerConfig::Beacon(_) => {} IndexerConfig::Bera(_) => {} diff --git a/hubble/src/cli.rs b/hubble/src/cli.rs index c065f51d49..81ba0b93ce 100644 --- a/hubble/src/cli.rs +++ b/hubble/src/cli.rs @@ -76,6 +76,8 @@ pub enum IndexerConfig { Bera(crate::bera::Config), #[serde(rename = "arb")] Arb(crate::arb::Config), + #[serde(rename = "scroll")] + Scroll(crate::scroll::Config), } impl IndexerConfig { @@ -87,6 +89,7 @@ impl IndexerConfig { Self::Bera(cfg) => &cfg.label, Self::EthFork(cfg) => &cfg.label, Self::Arb(cfg) => &cfg.label, + Self::Scroll(cfg) => &cfg.label, } } } @@ -140,6 +143,14 @@ impl IndexerConfig { .instrument(indexer_span) .await } + Self::Scroll(cfg) => { + cfg.indexer(db) + .instrument(initializer_span) + .await? + .index() + .instrument(indexer_span) + .await + } } } } diff --git a/hubble/src/main.rs b/hubble/src/main.rs index 51500a4056..3c014e9e72 100644 --- a/hubble/src/main.rs +++ b/hubble/src/main.rs @@ -24,6 +24,7 @@ mod logging; mod metrics; mod postgres; mod race_client; +mod scroll; mod tm; #[cfg(not(target_env = "msvc"))] diff --git a/hubble/src/scroll.rs b/hubble/src/scroll.rs new file mode 100644 index 0000000000..4c8c40abd0 --- /dev/null +++ b/hubble/src/scroll.rs @@ -0,0 +1,160 @@ +use std::time::Duration; + +use backon::{ConstantBuilder, ExponentialBuilder, Retryable}; +use color_eyre::eyre::{ContextCompat, Result, WrapErr}; +use ethers::providers::{Http, Middleware, Provider}; +use scroll_api::ScrollClient; +use tracing::{debug, info}; +use unionlabs::{ + hash::{H160, H256}, + uint::U256, +}; + +use crate::{ + beacon::Beacon, + consensus::{Indexer, Querier}, +}; + +pub struct Scroll { + pub l1_client: Provider, + #[allow(unused)] + pub l2_client: Provider, + + pub beacon: Beacon, + + pub scroll_api_client: ScrollClient, + + pub rollup_finalization_config: RollupFinalizationConfig, +} + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct Config { + pub label: String, + + pub l1_url: url::Url, + pub l2_url: url::Url, + + pub beacon_url: url::Url, + + pub scroll_api_url: url::Url, + + pub rollup_finalization_config: RollupFinalizationConfig, + + pub start_height: Option, +} + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct RollupFinalizationConfig { + pub rollup_contract_address: H160, + pub rollup_last_finalized_batch_index_slot: U256, +} + +impl Config { + pub async fn indexer(self, db: sqlx::PgPool) -> Result> { + let l2_client = Provider::new(Http::new(self.l2_url)); + + let l2_chain_id = U256::from( + l2_client + .get_chainid() + .await + .wrap_err("unable to fetch chain id from l2")?, + ) + .to_string(); + + info!("fetching db chain_id for chain {}", l2_chain_id); + + let chain_id = (|| async { + let chain_id = crate::postgres::get_chain_id(&db, l2_chain_id.clone()) + .await? + // This can reasonably fail because the other indexer is creating the chain_id. Otherwise + // this should always succeed. + .wrap_err("chain not found")?; + Ok::<_, color_eyre::Report>(chain_id) + }) + .retry(&ExponentialBuilder::default()) + .await?; + + let querier = Scroll { + l1_client: Provider::new(Http::new(self.l1_url)), + l2_client, + + beacon: Beacon::new(self.beacon_url, reqwest::Client::new()), + + scroll_api_client: ScrollClient::new(self.scroll_api_url), + + rollup_finalization_config: self.rollup_finalization_config, + }; + + Ok(Indexer::new(chain_id, db, querier, self.start_height)) + } +} + +impl Scroll { + // NOTE: Copied from chain_utils + async fn execution_height_of_beacon_slot(&self, slot: u64) -> Result { + Ok(self + .scroll_height_of_batch_index(self.batch_index_of_beacon_slot(slot).await?) + .await) + } + + pub async fn batch_index_of_beacon_slot(&self, slot: u64) -> Result { + let l1_height = self + .beacon + .get_height_at_skip_missing(slot.try_into().expect("negative slot?")) + .await? + .data + .message + .body + .execution_payload + .block_number; + + let storage = self + .l1_client + .get_storage_at( + ethers::types::H160(self.rollup_finalization_config.rollup_contract_address.0), + H256::from( + self.rollup_finalization_config + .rollup_last_finalized_batch_index_slot + .to_be_bytes(), + ) + .into(), + Some(ethers::types::BlockId::Number( + ethers::types::BlockNumber::Number((l1_height as u64).into()), + )), + ) + .await + .wrap_err("error fetching l1 rollup contract storage")?; + + let batch_index = U256::from_be_bytes(storage.to_fixed_bytes()) + .try_into() + .expect("value is a u64 in the contract; qed;"); + + debug!("execution height {l1_height} is batch index {batch_index}"); + + Ok(batch_index) + } + + pub async fn scroll_height_of_batch_index(&self, batch_index: u64) -> u64 { + let batch = self.scroll_api_client.batch(batch_index).await.batch; + + debug!( + "batch index {batch_index} is scroll height range {}..={}", + batch.start_block_number, batch.end_block_number + ); + + batch.end_block_number + } +} + +impl Querier for Scroll { + async fn get_execution_height(&self, slot: i64) -> Result<(i64, i64)> { + let height = (|| self.execution_height_of_beacon_slot(slot as u64)) + .retry( + &ConstantBuilder::default() + .with_delay(Duration::from_millis(500)) + .with_max_times(60), + ) + .await?; + Ok((slot, height as i64)) + } +} From bdd8cfac4bc898badddab0f579277e5c3b86d027 Mon Sep 17 00:00:00 2001 From: jurriaan Date: Tue, 6 Aug 2024 17:01:51 +0200 Subject: [PATCH 2/2] feat(hubble): scroll consensus height indexer (fmt) --- hubble/Cargo.toml | 70 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/hubble/Cargo.toml b/hubble/Cargo.toml index c0bb641b1f..f2ef51200b 100644 --- a/hubble/Cargo.toml +++ b/hubble/Cargo.toml @@ -18,42 +18,42 @@ name = "hubble" path = "src/main.rs" [dependencies] -axum = { workspace = true, features = ["macros", "tokio"] } -backon = "0.4.4" -base64 = { workspace = true } -chain-utils = { workspace = true } -clap = { workspace = true, features = ["derive", "env", "error-context"] } -color-eyre = { workspace = true, features = ["default"] } -cometbft-rpc = { workspace = true } -const-hex = "1.12.0" -contracts.workspace = true -ethers = { workspace = true, features = ["default", "std"] } -futures = { workspace = true, features = ["async-await"] } -hex.workspace = true -itertools = "0.13.0" -lazy_static = { workspace = true } -num-traits = "0.2.19" -prometheus = { version = "0.13.3", features = ["process"] } -prost.workspace = true -protos = { workspace = true, features = ["client"] } -regex = "1.10.5" -reqwest = { workspace = true, features = ["json", "blocking"] } +axum = { workspace = true, features = ["macros", "tokio"] } +backon = "0.4.4" +base64 = { workspace = true } +chain-utils = { workspace = true } +clap = { workspace = true, features = ["derive", "env", "error-context"] } +color-eyre = { workspace = true, features = ["default"] } +cometbft-rpc = { workspace = true } +const-hex = "1.12.0" +contracts.workspace = true +ethers = { workspace = true, features = ["default", "std"] } +futures = { workspace = true, features = ["async-await"] } +hex.workspace = true +itertools = "0.13.0" +lazy_static = { workspace = true } +num-traits = "0.2.19" +prometheus = { version = "0.13.3", features = ["process"] } +prost.workspace = true +protos = { workspace = true, features = ["client"] } +regex = "1.10.5" +reqwest = { workspace = true, features = ["json", "blocking"] } scroll-api.workspace = true -serde = { workspace = true, features = ["derive"] } -serde-aux = "4.5.0" -serde_json = { workspace = true } -sqlx = { workspace = true, features = ["postgres", "runtime-tokio", "tls-rustls", "time", "macros", "json", "bigdecimal"] } -tendermint = { workspace = true, features = ["std"] } -tendermint-rpc = { workspace = true, features = ["http-client", "tokio"] } -thiserror = { workspace = true } -time = { workspace = true, features = ["serde"] } -tokio = { workspace = true, features = ["full"] } -tonic = { workspace = true, features = ["transport", "tls", "tls-roots", "tls-webpki-roots"] } -tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter", "json", "tracing-log"] } -unionlabs = { workspace = true, features = ["ethabi"] } -url = { version = "2.4.1", features = ["serde"] } -valuable = { version = "0.1.0", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } +serde-aux = "4.5.0" +serde_json = { workspace = true } +sqlx = { workspace = true, features = ["postgres", "runtime-tokio", "tls-rustls", "time", "macros", "json", "bigdecimal"] } +tendermint = { workspace = true, features = ["std"] } +tendermint-rpc = { workspace = true, features = ["http-client", "tokio"] } +thiserror = { workspace = true } +time = { workspace = true, features = ["serde"] } +tokio = { workspace = true, features = ["full"] } +tonic = { workspace = true, features = ["transport", "tls", "tls-roots", "tls-webpki-roots"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter", "json", "tracing-log"] } +unionlabs = { workspace = true, features = ["ethabi"] } +url = { version = "2.4.1", features = ["serde"] } +valuable = { version = "0.1.0", features = ["derive"] } [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = "0.5"