From f09af5615f1fc99583e49cc22ac71fa548d5c727 Mon Sep 17 00:00:00 2001 From: Renan Date: Thu, 20 Jul 2023 19:49:56 -0300 Subject: [PATCH] feat(offchain): adds the authority-claimer --- offchain/Cargo.lock | 20 +++ offchain/Cargo.toml | 2 + offchain/authority-claimer/Cargo.toml | 28 ++++ offchain/authority-claimer/src/claimer.rs | 63 +++++++++ offchain/authority-claimer/src/config/cli.rs | 133 ++++++++++++++++++ .../authority-claimer/src/config/error.rs | 52 +++++++ offchain/authority-claimer/src/config/json.rs | 29 ++++ offchain/authority-claimer/src/config/mod.rs | 56 ++++++++ offchain/authority-claimer/src/lib.rs | 8 ++ offchain/authority-claimer/src/listener.rs | 62 ++++++++ offchain/authority-claimer/src/main.rs | 67 +++++++++ offchain/authority-claimer/src/metrics.rs | 34 +++++ offchain/authority-claimer/src/sender.rs | 58 ++++++++ offchain/http-server/src/config.rs | 5 +- offchain/http-server/src/lib.rs | 3 + offchain/indexer/Cargo.toml | 1 + 16 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 offchain/authority-claimer/Cargo.toml create mode 100644 offchain/authority-claimer/src/claimer.rs create mode 100644 offchain/authority-claimer/src/config/cli.rs create mode 100644 offchain/authority-claimer/src/config/error.rs create mode 100644 offchain/authority-claimer/src/config/json.rs create mode 100644 offchain/authority-claimer/src/config/mod.rs create mode 100644 offchain/authority-claimer/src/lib.rs create mode 100644 offchain/authority-claimer/src/listener.rs create mode 100644 offchain/authority-claimer/src/main.rs create mode 100644 offchain/authority-claimer/src/metrics.rs create mode 100644 offchain/authority-claimer/src/sender.rs diff --git a/offchain/Cargo.lock b/offchain/Cargo.lock index d0298bc93..97f64c0a3 100644 --- a/offchain/Cargo.lock +++ b/offchain/Cargo.lock @@ -476,6 +476,26 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "authority-claimer" +version = "1.0.0" +dependencies = [ + "async-trait", + "clap", + "eth-tx-manager", + "ethers", + "http-server", + "rollups-events", + "rusoto_core", + "serde", + "serde_json", + "snafu", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "auto_impl" version = "0.5.0" diff --git a/offchain/Cargo.toml b/offchain/Cargo.toml index d580f90b3..8b659bd3a 100644 --- a/offchain/Cargo.toml +++ b/offchain/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "advance-runner", + "authority-claimer", "contracts", "data", "dispatcher", @@ -38,6 +39,7 @@ diesel_migrations = "2.0" env_logger = "0.10" eth-tx-manager = "0.10" ethabi = "18.0" +ethers = "1.0" ethers-signers = "1.0" futures = "0.3" futures-util = "0.3" diff --git a/offchain/authority-claimer/Cargo.toml b/offchain/authority-claimer/Cargo.toml new file mode 100644 index 000000000..38bfc1ab7 --- /dev/null +++ b/offchain/authority-claimer/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "authority-claimer" +license = "Apache-2.0" +version = "1.0.0" +edition = "2021" + +[[bin]] +name = "cartesi-rollups-authority-claimer" +path = "src/main.rs" +test = false + +[dependencies] +http-server = { path = "../http-server" } +rollups-events = { path = "../rollups-events" } + +thiserror = "1.0" + +async-trait.workspace = true +clap = { workspace = true, features = ["derive"] } +eth-tx-manager.workspace = true +ethers.workspace = true +rusoto_core.workspace = true +serde.workspace = true +serde_json.workspace = true +snafu.workspace = true +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } +tracing.workspace = true diff --git a/offchain/authority-claimer/src/claimer.rs b/offchain/authority-claimer/src/claimer.rs new file mode 100644 index 000000000..6e5a95bd1 --- /dev/null +++ b/offchain/authority-claimer/src/claimer.rs @@ -0,0 +1,63 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use async_trait::async_trait; +use tracing::{trace, warn}; + +use crate::{listener::BrokerListener, sender::ClaimSender}; + +/// The `AuthorityClaimer` starts an event loop that waits for claim messages +/// from the broker, and then sends the claims to the blockchain. +/// +/// It uses a `BrokerListener` for listening for messages from the broker. +/// +/// It also uses a `ClaimSender` that interacts with the blockchain and +/// effectively submits the claims. +#[async_trait] +pub trait AuthorityClaimer { + async fn start( + &self, + broker_listener: L, + claim_sender: S, + ) -> Result<(), AuthorityClaimerError> { + trace!("Starting the authority claimer loop"); + let mut claim_sender = claim_sender; + loop { + match broker_listener.listen().await { + Ok(rollups_claim) => { + trace!("Got a claim from the broker: {:?}", rollups_claim); + claim_sender = claim_sender + .send_claim(rollups_claim) + .await + .map_err(AuthorityClaimerError::ClaimSenderError)?; + } + Err(e) => { + warn!("Broker error `{}`", e); + } + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AuthorityClaimerError { + #[error("claim sender error: {0}")] + ClaimSenderError(S::Error), + + #[error("broker listener error: {0}")] + BrokerListenerError(L::Error), +} + +// ------------------------------------------------------------------------------------------------ +// DefaultAuthorityClaimer +// ------------------------------------------------------------------------------------------------ + +pub struct DefaultAuthorityClaimer; + +impl DefaultAuthorityClaimer { + pub fn new() -> Self { + Self + } +} + +impl AuthorityClaimer for DefaultAuthorityClaimer {} diff --git a/offchain/authority-claimer/src/config/cli.rs b/offchain/authority-claimer/src/config/cli.rs new file mode 100644 index 000000000..02d3667bc --- /dev/null +++ b/offchain/authority-claimer/src/config/cli.rs @@ -0,0 +1,133 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use clap::{command, Parser}; +use eth_tx_manager::{ + config::{TxEnvCLIConfig as TxManagerCLIConfig, TxManagerConfig}, + Priority, +}; +use rollups_events::{BrokerCLIConfig, BrokerConfig}; +use rusoto_core::Region; +use snafu::ResultExt; +use std::{fs, path::PathBuf, str::FromStr}; + +use crate::config::{ + error::{ + AuthConfigError, AuthSnafu, AuthorityClaimerConfigError, + InvalidRegionSnafu, MnemonicFileSnafu, TxManagerSnafu, + }, + json::{read_json_file, DappDeployment}, + AuthConfig, AuthorityClaimerConfig, +}; + +// ------------------------------------------------------------------------------------------------ +// AuthorityClaimerCLI +// ------------------------------------------------------------------------------------------------ + +#[derive(Clone, Parser)] +#[command(name = "rd_config")] +#[command(about = "Configuration for rollups authority claimer")] +pub(crate) struct AuthorityClaimerCLI { + #[command(flatten)] + txm_config: TxManagerCLIConfig, + + #[command(flatten)] + auth_config: AuthCLIConfig, + + #[command(flatten)] + broker_config: BrokerCLIConfig, + + /// Path to file with deployment json of dapp + #[arg(long, env, default_value = "./dapp_deployment.json")] + dapp_deployment_file: PathBuf, +} + +impl TryFrom for AuthorityClaimerConfig { + type Error = AuthorityClaimerConfigError; + + fn try_from(cli_config: AuthorityClaimerCLI) -> Result { + let txm_config = TxManagerConfig::initialize(cli_config.txm_config) + .context(TxManagerSnafu)?; + + let auth_config = + AuthConfig::try_from(cli_config.auth_config).context(AuthSnafu)?; + + let broker_config = BrokerConfig::from(cli_config.broker_config); + + let dapp_deployment = + read_json_file::(cli_config.dapp_deployment_file)?; + let dapp_address = dapp_deployment.dapp_address; + let dapp_deploy_block_hash = dapp_deployment.dapp_deploy_block_hash; + + Ok(AuthorityClaimerConfig { + txm_config, + auth_config, + broker_config, + dapp_address, + dapp_deploy_block_hash, + txm_priority: Priority::Normal, + }) + } +} + +// ------------------------------------------------------------------------------------------------ +// AuthConfig +// ------------------------------------------------------------------------------------------------ + +#[derive(Debug, Clone, Parser)] +#[command(name = "auth_config")] +#[command(about = "Configuration for signing authentication")] +pub(crate) struct AuthCLIConfig { + /// Signer mnemonic, overrides `auth_mnemonic_file` and `auth_aws_kms_*` + #[arg(long, env)] + auth_mnemonic: Option, + + /// Signer mnemonic file path, overrides `auth_aws_kms_*` + #[arg(long, env)] + auth_mnemonic_file: Option, + + /// Mnemonic account index + #[arg(long, env)] + auth_mnemonic_account_index: Option, + + /// AWS KMS signer key-id + #[arg(long, env)] + auth_aws_kms_key_id: Option, + + /// AWS KMS signer region + #[arg(long, env)] + auth_aws_kms_region: Option, +} + +impl TryFrom for AuthConfig { + type Error = AuthConfigError; + + fn try_from(cli: AuthCLIConfig) -> Result { + let account_index = cli.auth_mnemonic_account_index; + if let Some(mnemonic) = cli.auth_mnemonic { + Ok(AuthConfig::Mnemonic { + mnemonic, + account_index, + }) + } else if let Some(path) = cli.auth_mnemonic_file { + let mnemonic = fs::read_to_string(path.clone()) + .context(MnemonicFileSnafu { path })? + .trim() + .to_string(); + Ok(AuthConfig::Mnemonic { + mnemonic, + account_index, + }) + } else { + match (cli.auth_aws_kms_key_id, cli.auth_aws_kms_region) { + (None, _) => Err(AuthConfigError::MissingConfiguration), + (Some(_), None) => Err(AuthConfigError::MissingRegion), + (Some(key_id), Some(region)) => { + let region = Region::from_str(®ion) + .context(InvalidRegionSnafu)?; + Ok(AuthConfig::Aws { key_id, region }) + } + } + } + } +} diff --git a/offchain/authority-claimer/src/config/error.rs b/offchain/authority-claimer/src/config/error.rs new file mode 100644 index 000000000..e669fa408 --- /dev/null +++ b/offchain/authority-claimer/src/config/error.rs @@ -0,0 +1,52 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use eth_tx_manager::config::Error as TxManagerConfigError; +use rusoto_core::region::ParseRegionError; +use snafu::Snafu; +use std::path::PathBuf; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub(crate)))] +pub enum AuthorityClaimerConfigError { + #[snafu(display("TxManager configuration error: {}", source))] + TxManagerError { source: TxManagerConfigError }, + + #[snafu(display("Auth configuration error: {}", source))] + AuthError { source: AuthConfigError }, + + #[snafu(display("Read file error ({})", path.display()))] + ReadFileError { + path: PathBuf, + source: std::io::Error, + }, + + #[snafu(display("Json parse error ({})", path.display()))] + JsonParseError { + path: PathBuf, + source: serde_json::Error, + }, +} + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub(crate)))] +pub enum AuthConfigError { + #[snafu(display("Missing auth configuration"))] + MissingConfiguration, + + #[snafu(display( + "Could not read mnemonic file at path `{}`: {}", + path, + source + ))] + MnemonicFileError { + path: String, + source: std::io::Error, + }, + + #[snafu(display("Missing AWS region"))] + MissingRegion, + + #[snafu(display("Invalid AWS region"))] + InvalidRegion { source: ParseRegionError }, +} diff --git a/offchain/authority-claimer/src/config/json.rs b/offchain/authority-claimer/src/config/json.rs new file mode 100644 index 000000000..d13d6a253 --- /dev/null +++ b/offchain/authority-claimer/src/config/json.rs @@ -0,0 +1,29 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use ethers::types::{Address, H256}; +use serde::{de::DeserializeOwned, Deserialize}; +use snafu::ResultExt; +use std::{fs::File, io::BufReader, path::PathBuf}; + +use crate::config::error::{ + AuthorityClaimerConfigError, JsonParseSnafu, ReadFileSnafu, +}; + +#[derive(Clone, Debug, Deserialize)] +pub(crate) struct DappDeployment { + #[serde(rename = "address")] + pub dapp_address: Address, // TODO: can I use rollups_events types? + + #[serde(rename = "blockHash")] + pub dapp_deploy_block_hash: H256, // TODO: can I use rollups_events types? +} + +pub(crate) fn read_json_file( + path: PathBuf, +) -> Result { + let file = + File::open(&path).context(ReadFileSnafu { path: path.clone() })?; + let reader = BufReader::new(file); + serde_json::from_reader(reader).context(JsonParseSnafu { path }) +} diff --git a/offchain/authority-claimer/src/config/mod.rs b/offchain/authority-claimer/src/config/mod.rs new file mode 100644 index 000000000..acc4c7724 --- /dev/null +++ b/offchain/authority-claimer/src/config/mod.rs @@ -0,0 +1,56 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +mod cli; +mod error; +mod json; + +pub use error::{AuthConfigError, AuthorityClaimerConfigError}; + +use cli::AuthorityClaimerCLI; +use eth_tx_manager::{config::TxManagerConfig, Priority}; +use ethers::types::{Address, H256}; +use http_server::HttpServerConfig; +use rollups_events::BrokerConfig; +use rusoto_core::Region; + +#[derive(Debug, Clone)] +pub struct Config { + pub authority_claimer_config: AuthorityClaimerConfig, + pub http_server_config: HttpServerConfig, +} + +#[derive(Debug, Clone)] +pub struct AuthorityClaimerConfig { + pub txm_config: TxManagerConfig, + pub auth_config: AuthConfig, + pub broker_config: BrokerConfig, + pub dapp_address: Address, // TODO: can I use rollups_events types? + pub dapp_deploy_block_hash: H256, // TODO: can I use rollups_events types? + pub txm_priority: Priority, +} + +#[derive(Debug, Clone)] +pub enum AuthConfig { + Mnemonic { + mnemonic: String, + account_index: Option, + }, + + Aws { + key_id: String, + region: Region, + }, +} + +impl Config { + pub fn new() -> Result { + let (http_server_config, authority_claimer_cli) = + HttpServerConfig::parse::("authority_claimer"); + let authority_claimer_config = authority_claimer_cli.try_into()?; + Ok(Self { + authority_claimer_config, + http_server_config, + }) + } +} diff --git a/offchain/authority-claimer/src/lib.rs b/offchain/authority-claimer/src/lib.rs new file mode 100644 index 000000000..31ff21ff1 --- /dev/null +++ b/offchain/authority-claimer/src/lib.rs @@ -0,0 +1,8 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +pub mod claimer; +pub mod config; +pub mod listener; +pub mod metrics; +pub mod sender; diff --git a/offchain/authority-claimer/src/listener.rs b/offchain/authority-claimer/src/listener.rs new file mode 100644 index 000000000..362a0d696 --- /dev/null +++ b/offchain/authority-claimer/src/listener.rs @@ -0,0 +1,62 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use async_trait::async_trait; +use rollups_events::{BrokerConfig, DAppMetadata, RollupsClaim}; +use snafu::Snafu; +use std::fmt::Debug; + +use crate::metrics::AuthorityClaimerMetrics; + +/// The `BrokerListener` listens for new claims from the broker. +/// +/// The `listen` function should preferably yield to other processes while +/// waiting for new messages (instead of busy-waiting). +#[async_trait] +pub trait BrokerListener: Sized + Send + Debug { + type Error: snafu::Error + Send; + + async fn listen(&self) -> Result; +} + +// ------------------------------------------------------------------------------------------------ +// DefaultBrokerListener +// ------------------------------------------------------------------------------------------------ + +#[derive(Debug)] +pub struct DefaultBrokerListener; + +#[derive(Debug, Snafu)] +pub enum DefaultBrokerListenerError { + Todo, +} + +impl DefaultBrokerListener { + pub fn new( + _broker_config: BrokerConfig, + _dapp_metadata: DAppMetadata, + _metrics: AuthorityClaimerMetrics, + ) -> Result { + todo!() + } +} + +#[async_trait] +impl BrokerListener for DefaultBrokerListener { + type Error = DefaultBrokerListenerError; + + async fn listen(&self) -> Result { + todo!() + } +} + +// impl Stream for BrokerListener { +// type Item = u32; +// +// fn poll_next( +// self: Pin<&mut Self>, +// cx: &mut Context<'_>, +// ) -> Poll> { +// todo!() +// } +// } diff --git a/offchain/authority-claimer/src/main.rs b/offchain/authority-claimer/src/main.rs new file mode 100644 index 000000000..a8bbf8674 --- /dev/null +++ b/offchain/authority-claimer/src/main.rs @@ -0,0 +1,67 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use rollups_events::DAppMetadata; +use std::error::Error; +use tracing::trace; +use tracing_subscriber::filter::{EnvFilter, LevelFilter}; + +use authority_claimer::{ + claimer::{AuthorityClaimer, DefaultAuthorityClaimer}, + config::Config, + listener::DefaultBrokerListener, + metrics::AuthorityClaimerMetrics, + sender::TxManagerClaimSender, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Getting the configuration. + let config = Config::new().map_err(Box::new)?; + + tracing::info!(?config, "starting authority-claimer"); + + // Settin up the logging environment. + let env_filter = EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(); + tracing_subscriber::fmt().with_env_filter(env_filter).init(); + + // Creating the metrics and health server. + let metrics = AuthorityClaimerMetrics::new(); + let http_server_handle = + http_server::start(config.http_server_config, metrics.clone().into()); + + let dapp_address = config.authority_claimer_config.dapp_address; + let dapp_metadata = DAppMetadata { + chain_id: config.authority_claimer_config.txm_config.chain_id, + dapp_address: rollups_events::Address::new(dapp_address.into()), + }; + + // Creating the default broker listener. + trace!("Creating the broker listener"); + let default_broker_listener = DefaultBrokerListener::new( + config.authority_claimer_config.broker_config.clone(), + dapp_metadata.clone(), + metrics.clone(), + ) + .map_err(Box::new)?; + + // Creating the transaction manager claim sender. + trace!("Creating the claim sender"); + let tx_manager_claim_sender = + TxManagerClaimSender::new(dapp_metadata, metrics).map_err(Box::new)?; + + // Creating the claimer loop. + let authority_claimer = DefaultAuthorityClaimer::new(); + let claimer_handle = authority_claimer + .start(default_broker_listener, tx_manager_claim_sender); + + // Starting the HTTP server and the claimer loop. + tokio::select! { + ret = http_server_handle => { ret.map_err(Box::new)? } + ret = claimer_handle => { ret.map_err(Box::new)? } + }; + + unreachable!() +} diff --git a/offchain/authority-claimer/src/metrics.rs b/offchain/authority-claimer/src/metrics.rs new file mode 100644 index 000000000..3ffd67a66 --- /dev/null +++ b/offchain/authority-claimer/src/metrics.rs @@ -0,0 +1,34 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use http_server::{CounterRef, FamilyRef, Registry}; +use rollups_events::DAppMetadata; + +const METRICS_PREFIX: &str = "cartesi_rollups_authority_claimer"; + +fn prefixed_metrics(name: &str) -> String { + format!("{}_{}", METRICS_PREFIX, name) +} + +#[derive(Debug, Clone, Default)] +pub struct AuthorityClaimerMetrics { + pub claims_sent: FamilyRef, +} + +impl AuthorityClaimerMetrics { + pub fn new() -> Self { + Self::default() + } +} + +impl From for Registry { + fn from(metrics: AuthorityClaimerMetrics) -> Self { + let mut registry = Registry::default(); + registry.register( + prefixed_metrics("claims_sent"), + "Counts the number of claims sent", + metrics.claims_sent, + ); + registry + } +} diff --git a/offchain/authority-claimer/src/sender.rs b/offchain/authority-claimer/src/sender.rs new file mode 100644 index 000000000..5eddc5ece --- /dev/null +++ b/offchain/authority-claimer/src/sender.rs @@ -0,0 +1,58 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +use async_trait::async_trait; +use rollups_events::{DAppMetadata, RollupsClaim}; +use snafu::Snafu; +use std::fmt::Debug; + +use crate::metrics::AuthorityClaimerMetrics; + +/// The `ClaimSender` sends claims to the blockchain. +/// +/// It should wait for N blockchain confirmations. +#[async_trait] +pub trait ClaimSender: Sized + Send + Debug { + type Error: snafu::Error + Send; + + /// The `send_claim` function consumes the `ClaimSender` object + /// and then returns it to avoid that processes use the claim sender + /// concurrently. + async fn send_claim( + self, + rollups_claim: RollupsClaim, + ) -> Result; +} + +// ------------------------------------------------------------------------------------------------ +// TxManagerClaimSender +// ------------------------------------------------------------------------------------------------ + +#[derive(Debug, Clone)] +pub struct TxManagerClaimSender; + +#[derive(Debug, Snafu)] +pub enum TxManagerClaimSenderError { + Todo, +} + +impl TxManagerClaimSender { + pub fn new( + _dapp_metadata: DAppMetadata, + _metrics: AuthorityClaimerMetrics, + ) -> Result { + todo!() + } +} + +#[async_trait] +impl ClaimSender for TxManagerClaimSender { + type Error = TxManagerClaimSenderError; + + async fn send_claim( + self, + _rollups_claim: RollupsClaim, + ) -> Result { + todo!() + } +} diff --git a/offchain/http-server/src/config.rs b/offchain/http-server/src/config.rs index c32595753..7a32ac82d 100644 --- a/offchain/http-server/src/config.rs +++ b/offchain/http-server/src/config.rs @@ -5,7 +5,7 @@ use clap::{ value_parser, Arg, Command, CommandFactory, FromArgMatches, Parser, }; -#[derive(Debug, Parser)] +#[derive(Debug, Clone, Parser)] pub struct HttpServerConfig { pub(crate) port: u16, } @@ -14,6 +14,9 @@ impl HttpServerConfig { /// Returns the HTTP server config and the app's config after parsing /// it from the command line and/or environment variables. /// + /// The parameter `service` must be a lowercase string that + /// uses underlines as spaces. + /// /// The parametric type `C` must be a struct that derives `Parser`. pub fn parse( service: &'static str, diff --git a/offchain/http-server/src/lib.rs b/offchain/http-server/src/lib.rs index edca9f547..f6c36f36c 100644 --- a/offchain/http-server/src/lib.rs +++ b/offchain/http-server/src/lib.rs @@ -13,6 +13,9 @@ pub use prometheus_client::metrics::counter::Counter as CounterRef; pub use prometheus_client::metrics::family::Family as FamilyRef; // End of metrics to re-export. +// Re-exporting hyper error. +pub use hyper::Error as HttpServerError; + use axum::{routing::get, Router}; use prometheus_client::encoding::text::encode; use std::{ diff --git a/offchain/indexer/Cargo.toml b/offchain/indexer/Cargo.toml index 8c1adb295..3b37c567c 100644 --- a/offchain/indexer/Cargo.toml +++ b/offchain/indexer/Cargo.toml @@ -30,3 +30,4 @@ rand.workspace = true serial_test.workspace = true test-log = { workspace = true, features = ["trace"] } testcontainers.workspace = true +