Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(torii): configutation file for all torii cli options #2646

Merged
merged 8 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bin/torii/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ torii-grpc = { workspace = true, features = [ "server" ] }
torii-relay.workspace = true
torii-server.workspace = true
tower.workspace = true
toml.workspace = true

tower-http.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
url.workspace = true
webbrowser = "0.8"
tempfile.workspace = true
clap_config = "0.1.1"
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Consider using clap directly instead of clap_config, sensei!

The verification reveals:

  1. clap_config is only used in one place (bin/torii/src/main.rs) for a simple skip attribute
  2. The package is relatively new (last updated July 2024) with low adoption (~1.8K total downloads)
  3. The codebase already extensively uses clap directly, as seen in multiple files

The functionality can be replaced with clap's built-in attributes, making the dependency unnecessary:

  • Replace #[clap_config(skip)] with #[arg(skip)]
🔗 Analysis chain

Consider workspace version management and package stability, sensei!

The clap_config dependency has a few concerns:

  1. It's not using workspace version management (.workspace = true) like other dependencies
  2. Version 0.1.1 is quite early and might be unstable
  3. Being a small community package, we should verify its maintenance status

Let's check the package's health:

Consider:

  1. Moving the version to workspace-level management
  2. Evaluating more mature alternatives or waiting for a stable release
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify clap_config package health
# Get package info from crates.io API
curl -s https://crates.io/api/v1/crates/clap_config | jq -r '.crate | {max_version, updated_at, downloads, recent_downloads}'

Length of output: 257


Script:

#!/bin/bash
# Check if clap_config is used in workspace dependencies
rg -U "clap_config.*workspace\s*=\s*true" Cargo.toml

# Check workspace root Cargo.toml for any clap_config version specification
rg "clap_config.*version" Cargo.toml

# Find all usages of clap_config in the codebase
rg "clap_config" -A 2 -B 2

# Check for alternative crates that might be used
rg "clap::" -A 2 -B 2

Length of output: 15413


[dev-dependencies]
camino.workspace = true
Expand Down
78 changes: 29 additions & 49 deletions bin/torii/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
//! for more info.

use std::cmp;
use std::collections::VecDeque;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use anyhow::Context;
use clap::{ArgAction, Parser};
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser};
use clap_config::ClapConfig;
use dojo_metrics::exporters::prometheus::PrometheusRecorder;
use dojo_utils::parse::{parse_socket_address, parse_url};
use dojo_world::contracts::world::WorldContractReader;
Expand All @@ -39,7 +39,7 @@ use torii_core::executor::Executor;
use torii_core::processors::store_transaction::StoreTransactionProcessor;
use torii_core::simple_broker::SimpleBroker;
use torii_core::sql::Sql;
use torii_core::types::{Contract, ContractType, Model, ToriiConfig};
use torii_core::types::{Contract, ContractType, Model};
use torii_server::proxy::Proxy;
use tracing::{error, info};
use tracing_subscriber::{fmt, EnvFilter};
Expand All @@ -48,7 +48,7 @@ use url::{form_urlencoded, Url};
pub(crate) const LOG_TARGET: &str = "torii::cli";

/// Dojo World Indexer
#[derive(Parser, Debug)]
#[derive(ClapConfig, Parser, Debug)]
#[command(name = "torii", author, version, about, long_about = None)]
struct Args {
/// The world to index
Expand Down Expand Up @@ -91,8 +91,7 @@ struct Args {

/// Specify allowed origins for api endpoints (comma-separated list of allowed origins, or "*"
/// for all)
#[arg(long)]
#[arg(value_delimiter = ',')]
#[arg(long, value_delimiter = ',')]
allowed_origins: Option<Vec<String>>,

/// The external url of the server, used for configuring the GraphQL Playground in a hosted
Expand Down Expand Up @@ -139,32 +138,33 @@ struct Args {
index_raw_events: bool,

/// ERC contract addresses to index
#[arg(long, value_parser = parse_erc_contracts)]
#[arg(conflicts_with = "config")]
contracts: Option<std::vec::Vec<Contract>>,
#[arg(long, default_value = "")]
contracts: String,

/// Configuration file
#[arg(long)]
#[clap_config(skip)]
config: Option<PathBuf>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();

let mut config = if let Some(path) = args.config {
ToriiConfig::load_from_path(&path)?
let matches = <Args as CommandFactory>::command().get_matches();
let args = if let Some(path) = matches.get_one::<PathBuf>("config") {
let config: ArgsConfig = toml::from_str(&std::fs::read_to_string(path)?)?;
Args::from_merged(matches, Some(config))
} else {
let mut config = ToriiConfig::default();

if let Some(contracts) = args.contracts {
config.contracts = VecDeque::from(contracts);
}
Args::from_arg_matches(&matches)?
};

config
let world_address = if let Some(world_address) = args.world_address {
world_address
} else {
return Err(anyhow::anyhow!("Please specify a world address."));
};

let world_address = verify_single_world_address(args.world_address, &mut config)?;
let mut contracts = parse_erc_contracts(&args.contracts)?;
contracts.push(Contract { address: world_address, r#type: ContractType::WORLD });

let filter_layer = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info,hyper_reverse_proxy=off"));
Expand Down Expand Up @@ -210,8 +210,6 @@ async fn main() -> anyhow::Result<()> {
// Get world address
let world = WorldContractReader::new(world_address, provider.clone());

let contracts =
config.contracts.iter().map(|contract| (contract.address, contract.r#type)).collect();

let (mut executor, sender) = Executor::new(pool.clone(), shutdown_tx.clone()).await?;
tokio::spawn(async move {
Expand Down Expand Up @@ -251,7 +249,7 @@ async fn main() -> anyhow::Result<()> {
},
shutdown_tx.clone(),
Some(block_tx),
Arc::new(contracts),
&contracts,
);

let shutdown_rx = shutdown_tx.subscribe();
Expand Down Expand Up @@ -321,26 +319,6 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}

// Verifies that the world address is defined at most once
// and returns the world address
fn verify_single_world_address(
world_address: Option<Felt>,
config: &mut ToriiConfig,
) -> anyhow::Result<Felt> {
let world_from_config =
config.contracts.iter().find(|c| c.r#type == ContractType::WORLD).map(|c| c.address);

match (world_address, world_from_config) {
(Some(_), Some(_)) => Err(anyhow::anyhow!("World address specified multiple times")),
(Some(addr), _) => {
config.contracts.push_front(Contract { address: addr, r#type: ContractType::WORLD });
Ok(addr)
}
(_, Some(addr)) => Ok(addr),
(None, None) => Err(anyhow::anyhow!("World address not specified")),
}
}

async fn spawn_rebuilding_graphql_server(
shutdown_tx: Sender<()>,
pool: Arc<SqlitePool>,
Expand Down Expand Up @@ -369,18 +347,20 @@ async fn spawn_rebuilding_graphql_server(
// - erc_type:address:start_block
// - address:start_block (erc_type defaults to ERC20)
fn parse_erc_contracts(s: &str) -> anyhow::Result<Vec<Contract>> {
if s.is_empty() {
return Ok(Vec::new());
}

let parts: Vec<&str> = s.split(',').collect();
let mut contracts = Vec::new();
for part in parts {
match part.split(':').collect::<Vec<&str>>().as_slice() {
[r#type, address] => {
let r#type = r#type.parse::<ContractType>()?;
let address = Felt::from_str(address)
.with_context(|| format!("Expected address, found {}", address))?;
contracts.push(Contract { address, r#type });
}
[address] => {
let r#type = ContractType::WORLD;
if r#type == ContractType::WORLD {
return Err(anyhow::anyhow!("World address cannot be specified as an ERC contract"));
}

let address = Felt::from_str(address)
.with_context(|| format!("Expected address, found {}", address))?;
contracts.push(Contract { address, r#type });
Expand Down
6 changes: 4 additions & 2 deletions crates/torii/core/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::processors::store_update_member::StoreUpdateMemberProcessor;
use crate::processors::store_update_record::StoreUpdateRecordProcessor;
use crate::processors::{BlockProcessor, EventProcessor, TransactionProcessor};
use crate::sql::{Cursors, Sql};
use crate::types::ContractType;
use crate::types::{Contract, ContractType};

type EventProcessorMap<P> = HashMap<Felt, Vec<Box<dyn EventProcessor<P>>>>;

Expand Down Expand Up @@ -217,8 +217,10 @@ impl<P: Provider + Send + Sync + std::fmt::Debug + 'static> Engine<P> {
config: EngineConfig,
shutdown_tx: Sender<()>,
block_tx: Option<BoundedSender<u64>>,
contracts: Arc<HashMap<Felt, ContractType>>,
contracts: &Vec<Contract>,
) -> Self {
let contracts = Arc::new(contracts.iter().map(|contract| (contract.address, contract.r#type)).collect());

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider adding contract validation.

Ohayo, sensei! The contract initialization lacks validation for duplicate addresses.

Add validation to ensure no duplicate contract addresses exist in the input vector. This could lead to undefined behavior if multiple contracts share the same address but have different types.

+let mut seen_addresses = HashSet::new();
+for contract in contracts {
+    if !seen_addresses.insert(contract.address) {
+        return Err(anyhow::anyhow!("Duplicate contract address: {:#x}", contract.address));
+    }
+}
 let contracts = Arc::new(contracts
     .iter()
     .map(|contract| (contract.address, contract.r#type))
     .collect::<HashMap<_, _>>());

Also applies to: 32-38

Self {
world: Arc::new(world),
db,
Expand Down
10 changes: 5 additions & 5 deletions crates/torii/core/src/sql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::executor::{
Argument, DeleteEntityQuery, EventMessageQuery, QueryMessage, QueryType, ResetCursorsQuery,
SetHeadQuery, UpdateCursorsQuery,
};
use crate::types::ContractType;
use crate::types::Contract;
use crate::utils::utc_dt_string_from_timestamp;

type IsEventMessage = bool;
Expand Down Expand Up @@ -59,17 +59,17 @@ impl Sql {
pub async fn new(
pool: Pool<Sqlite>,
executor: UnboundedSender<QueryMessage>,
contracts: &HashMap<Felt, ContractType>,
contracts: &Vec<Contract>,
) -> Result<Self> {
for contract in contracts {
executor.send(QueryMessage::other(
"INSERT OR IGNORE INTO contracts (id, contract_address, contract_type) VALUES (?, \
?, ?)"
.to_string(),
vec![
Argument::FieldElement(*contract.0),
Argument::FieldElement(*contract.0),
Argument::String(contract.1.to_string()),
Argument::FieldElement(contract.address),
Argument::FieldElement(contract.address),
Argument::String(contract.r#type.to_string()),
],
))?;
}
Expand Down
10 changes: 5 additions & 5 deletions crates/torii/core/src/sql/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use tokio::sync::broadcast;
use crate::engine::{Engine, EngineConfig, Processors};
use crate::executor::Executor;
use crate::sql::Sql;
use crate::types::ContractType;
use crate::types::{Contract, ContractType};

pub async fn bootstrap_engine<P>(
world: WorldContractReader<P>,
Expand All @@ -45,7 +45,7 @@ where
EngineConfig::default(),
shutdown_tx,
None,
Arc::new(HashMap::from([(world_address, ContractType::WORLD)])),
&vec![Contract { address: world_address, r#type: ContractType::WORLD }],
);

let data = engine.fetch_range(0, to, &HashMap::new()).await.unwrap();
Expand Down Expand Up @@ -127,7 +127,7 @@ async fn test_load_from_remote(sequencer: &RunnerCtx) {
let db = Sql::new(
pool.clone(),
sender.clone(),
&HashMap::from([(world_reader.address, ContractType::WORLD)]),
&vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }],
)
.await
.unwrap();
Expand Down Expand Up @@ -285,7 +285,7 @@ async fn test_load_from_remote_del(sequencer: &RunnerCtx) {
let db = Sql::new(
pool.clone(),
sender.clone(),
&HashMap::from([(world_reader.address, ContractType::WORLD)]),
&vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }],
)
.await
.unwrap();
Expand Down Expand Up @@ -371,7 +371,7 @@ async fn test_update_with_set_record(sequencer: &RunnerCtx) {
let db = Sql::new(
pool.clone(),
sender.clone(),
&HashMap::from([(world_reader.address, ContractType::WORLD)]),
&vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }],
)
.await
.unwrap();
Expand Down
21 changes: 2 additions & 19 deletions crates/torii/core/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use core::fmt;
use std::collections::VecDeque;
use std::path::PathBuf;
use std::str::FromStr;

use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -123,28 +121,13 @@ pub struct Event {
pub executed_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}

#[derive(Default, Deserialize, Debug, Clone)]
pub struct ToriiConfig {
/// contract addresses to index
pub contracts: VecDeque<Contract>,
}

impl ToriiConfig {
pub fn load_from_path(path: &PathBuf) -> Result<Self, anyhow::Error> {
let config = std::fs::read_to_string(path)?;
let config: Self = toml::from_str(&config)?;
Ok(config)
}
}

#[derive(Deserialize, Debug, Clone, Copy)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct Contract {
pub address: Felt,
pub r#type: ContractType,
}

#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ContractType {
WORLD,
ERC20,
Expand Down
Loading
Loading