Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ pg-data/
/torii-data/image-cache/
HANDOFF.md
.cargo/
certs/*.pem
47 changes: 47 additions & 0 deletions Cargo.lock

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

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ prost-types = "0.13"
# HTTP server and routing
axum = { version = "0.7", features = ["ws"] }
tower = "0.5"
tower-http = { version = "0.5", features = ["cors"] }
tower-http = { version = "0.5", features = ["cors", "fs"] }
hyper = "1.0"

# Serialization
Expand Down Expand Up @@ -187,20 +187,25 @@ axum.workspace = true
chrono.workspace = true
futures-util.workspace = true
futures.workspace = true
hyper.workspace = true
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "service"] }
metrics.workspace = true
metrics-exporter-prometheus.workspace = true
prost-types.workspace = true
rustls-pemfile = "2.0"
serde_json.workspace = true
serde.workspace = true
sqlx.workspace = true
starknet.workspace = true
tokio-stream.workspace = true
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"] }
tokio-util.workspace = true
tokio.workspace = true
tonic-reflection.workspace = true
tonic-web.workspace = true
tonic.workspace = true
torii-common.workspace = true
tower.workspace = true
tower-http.workspace = true
tracing.workspace = true
prost.workspace = true
Expand Down
33 changes: 33 additions & 0 deletions bins/torii-arcade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,39 @@ cargo run --bin torii-arcade -- \
--from-block 0
```

## Local TLS + ALPN

For browser-compatible local HTTPS, use `mkcert` instead of a raw self-signed certificate.
Modern browsers require a trusted local CA and `subjectAltName` entries for `localhost`.

Install and trust the local development CA:

```bash
brew install mkcert nss
mkcert -install
mkdir -p certs
mkcert -cert-file certs/dev-cert.pem -key-file certs/dev-key.pem localhost 127.0.0.1 ::1
```

Start `torii-arcade` with TLS enabled:

```bash
cargo run --bin torii-arcade -- \
--from-block 0 \
--tls-cert ./certs/dev-cert.pem \
--tls-key ./certs/dev-key.pem
```

The local listener advertises ALPN for `h2` and `http/1.1`. Native gRPC clients will negotiate
HTTP/2 automatically; HTTPS health and metrics endpoints remain available on the same port.

Verify the local HTTPS listener:

```bash
curl https://localhost:3000/health
grpcurl -insecure localhost:3000 list
```

## Operational validation

The mixed backend can be checked end-to-end from the repository root:
Expand Down
50 changes: 49 additions & 1 deletion bins/torii-arcade/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{bail, Result};
use clap::{Parser, ValueEnum};
use starknet::core::types::Felt;
use std::path::Path;
use std::path::{Path, PathBuf};

const DEFAULT_RPC_URL: &str = "https://api.cartridge.gg/x/starknet/mainnet";
const DEFAULT_WORLD_ADDRESS: &str =
Expand Down Expand Up @@ -139,6 +139,14 @@ pub struct Config {
#[arg(long, default_value = "3000")]
pub port: u16,

/// PEM-encoded TLS certificate for the local HTTP/gRPC listener.
#[arg(long, env = "TORII_TLS_CERT")]
pub tls_cert: Option<PathBuf>,

/// PEM-encoded TLS private key for the local HTTP/gRPC listener.
#[arg(long, env = "TORII_TLS_KEY")]
pub tls_key: Option<PathBuf>,

#[arg(long)]
pub observability: bool,

Expand All @@ -151,6 +159,10 @@ pub struct Config {
#[arg(long, default_value = "2")]
pub max_prefetch_batches: usize,

/// Delay between ETL idle/retry cycles in seconds.
#[arg(long, default_value = "3")]
pub cycle_interval: u64,

#[arg(long, default_value = "0")]
pub rpc_parallelism: usize,

Expand Down Expand Up @@ -336,6 +348,16 @@ impl Config {
.to_string(),
))
}

pub fn tls_config(&self) -> Result<Option<torii::ToriiTlsConfig>> {
match (&self.tls_cert, &self.tls_key) {
(Some(cert), Some(key)) => {
Ok(Some(torii::ToriiTlsConfig::new(cert.clone(), key.clone())))
}
(None, None) => Ok(None),
_ => bail!("--tls-cert and --tls-key must be provided together"),
}
}
}

fn is_postgres_url(url: &str) -> bool {
Expand Down Expand Up @@ -416,6 +438,32 @@ mod tests {
)));
}

#[test]
fn tls_flags_parse_when_both_paths_are_present() {
let cfg = Config::parse_from([
"torii-arcade",
"--tls-cert",
"./certs/dev-cert.pem",
"--tls-key",
"./certs/dev-key.pem",
]);

let tls = cfg.tls_config().unwrap().unwrap();
assert_eq!(tls.cert_path, PathBuf::from("./certs/dev-cert.pem"));
assert_eq!(tls.key_path, PathBuf::from("./certs/dev-key.pem"));
assert_eq!(
tls.alpn_protocols,
vec![b"h2".to_vec(), b"http/1.1".to_vec()]
);
}

#[test]
fn tls_flags_require_both_cert_and_key() {
let cfg = Config::parse_from(["torii-arcade", "--tls-cert", "./certs/dev-cert.pem"]);

assert!(cfg.tls_config().is_err());
}

#[test]
fn postgres_storage_defaults_to_shared_postgres_for_tokens() {
let cfg = Config::parse_from([
Expand Down
Loading
Loading