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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Common pattern across crates:
- ALWAYS run clippy with both `--all-features` and `--no-default-features`.
- ALWAYS run `cargo +nightly fmt`.
- Run tests per-crate (`-p <crate>`) before running repo-wide.
- For `signet-cold-sql`: ALWAYS run `./scripts/test-postgres.sh` to verify
the PostgreSQL backend against the conformance suite.

## Research

Expand Down
16 changes: 9 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.1.0"
version = "0.2.0"
edition = "2024"
rust-version = "1.92"
authors = ["init4"]
Expand Down Expand Up @@ -35,12 +35,13 @@ incremental = false

[workspace.dependencies]
# internal
signet-hot = { version = "0.1.0", path = "./crates/hot" }
signet-hot-mdbx = { version = "0.1.0", path = "./crates/hot-mdbx" }
signet-cold = { version = "0.1.0", path = "./crates/cold" }
signet-cold-mdbx = { version = "0.1.0", path = "./crates/cold-mdbx" }
signet-storage = { version = "0.1.0", path = "./crates/storage" }
signet-storage-types = { version = "0.1.0", path = "./crates/types" }
signet-hot = { version = "0.2.0", path = "./crates/hot" }
signet-hot-mdbx = { version = "0.2.0", path = "./crates/hot-mdbx" }
signet-cold = { version = "0.2.0", path = "./crates/cold" }
signet-cold-mdbx = { version = "0.2.0", path = "./crates/cold-mdbx" }
signet-cold-sql = { version = "0.2.0", path = "./crates/cold-sql" }
signet-storage = { version = "0.2.0", path = "./crates/storage" }
signet-storage-types = { version = "0.2.0", path = "./crates/types" }

# External, in-house
signet-libmdbx = { version = "0.8.0" }
Expand Down Expand Up @@ -69,4 +70,5 @@ tokio = { version = "1.45.0", features = ["full"] }
tokio-util = { version = "0.7", features = ["rt"] }
itertools = "0.14"
lru = "0.16"
sqlx = { version = "0.8", default-features = false }
tracing = "0.1.44"
34 changes: 34 additions & 0 deletions crates/cold-sql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "signet-cold-sql"
description = "SQL backend for signet-cold storage"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
keywords = ["sql", "storage", "cold-storage", "blockchain"]
categories = ["database-implementations"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
alloy = { workspace = true, optional = true }
signet-cold.workspace = true
signet-storage-types = { workspace = true, optional = true }
signet-zenith = { workspace = true, optional = true }
sqlx = { workspace = true }
thiserror.workspace = true

[dev-dependencies]
signet-cold = { workspace = true, features = ["test-utils"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }

[features]
default = []
postgres = ["sqlx/postgres", "sqlx/any", "sqlx/runtime-tokio-rustls", "dep:alloy", "dep:signet-storage-types", "dep:signet-zenith"]
sqlite = ["sqlx/sqlite", "sqlx/any", "sqlx/runtime-tokio-rustls", "dep:alloy", "dep:signet-storage-types", "dep:signet-zenith"]
test-utils = ["signet-cold/test-utils", "sqlite", "postgres"]
38 changes: 38 additions & 0 deletions crates/cold-sql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# signet-cold-sql

SQL backend for signet-cold storage.

## Testing

### SQLite (no external dependencies)

```sh
cargo t -p signet-cold-sql --features test-utils
```

### PostgreSQL (requires Docker)

A script is provided to start a Postgres container and run the full test
suite (SQLite + PostgreSQL):

```sh
./scripts/test-postgres.sh
```

This starts a Postgres 16 container via `docker compose`, runs the
conformance tests with `DATABASE_URL` set, and tears down the container
on exit.

**Manual steps** (if you prefer to manage Postgres yourself):

```sh
# 1. Start Postgres (any method)
docker compose up -d --wait postgres

# 2. Run tests with the connection string
DATABASE_URL="postgres://signet:signet@localhost:5432/signet_test" \
cargo t -p signet-cold-sql --features test-utils

# 3. Tear down
docker compose down
```
115 changes: 115 additions & 0 deletions crates/cold-sql/migrations/001_initial.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
-- Cold storage schema with fully decomposed columns.
--
-- Compatible with both PostgreSQL and SQLite.
-- BLOB is used instead of BYTEA for cross-dialect compatibility
-- (sqlx maps both transparently to Vec<u8> / &[u8]).

CREATE TABLE IF NOT EXISTS headers (
block_number INTEGER PRIMARY KEY NOT NULL,
block_hash BLOB NOT NULL,
parent_hash BLOB NOT NULL,
ommers_hash BLOB NOT NULL,
beneficiary BLOB NOT NULL,
state_root BLOB NOT NULL,
transactions_root BLOB NOT NULL,
receipts_root BLOB NOT NULL,
logs_bloom BLOB NOT NULL,
difficulty BLOB NOT NULL,
gas_limit INTEGER NOT NULL,
gas_used INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
extra_data BLOB NOT NULL,
mix_hash BLOB NOT NULL,
nonce BLOB NOT NULL,
base_fee_per_gas INTEGER,
withdrawals_root BLOB,
blob_gas_used INTEGER,
excess_blob_gas INTEGER,
parent_beacon_block_root BLOB,
requests_hash BLOB
);

CREATE INDEX IF NOT EXISTS idx_headers_hash ON headers (block_hash);

CREATE TABLE IF NOT EXISTS transactions (
block_number INTEGER NOT NULL,
tx_index INTEGER NOT NULL,
tx_hash BLOB NOT NULL,
tx_type INTEGER NOT NULL,
sig_y_parity INTEGER NOT NULL,
sig_r BLOB NOT NULL,
sig_s BLOB NOT NULL,
chain_id INTEGER,
nonce INTEGER NOT NULL,
gas_limit INTEGER NOT NULL,
to_address BLOB,
value BLOB NOT NULL,
input BLOB NOT NULL,
gas_price BLOB,
max_fee_per_gas BLOB,
max_priority_fee_per_gas BLOB,
max_fee_per_blob_gas BLOB,
blob_versioned_hashes BLOB,
access_list BLOB,
authorization_list BLOB,
PRIMARY KEY (block_number, tx_index)
);

CREATE INDEX IF NOT EXISTS idx_tx_hash ON transactions (tx_hash);

CREATE TABLE IF NOT EXISTS receipts (
block_number INTEGER NOT NULL,
tx_index INTEGER NOT NULL,
tx_type INTEGER NOT NULL,
success INTEGER NOT NULL,
cumulative_gas_used INTEGER NOT NULL,
PRIMARY KEY (block_number, tx_index)
);

CREATE TABLE IF NOT EXISTS logs (
block_number INTEGER NOT NULL,
tx_index INTEGER NOT NULL,
log_index INTEGER NOT NULL,
address BLOB NOT NULL,
topic0 BLOB,
topic1 BLOB,
topic2 BLOB,
topic3 BLOB,
data BLOB NOT NULL,
PRIMARY KEY (block_number, tx_index, log_index)
);

CREATE INDEX IF NOT EXISTS idx_logs_address ON logs (address);
CREATE INDEX IF NOT EXISTS idx_logs_topic0 ON logs (topic0);

CREATE TABLE IF NOT EXISTS signet_events (
block_number INTEGER NOT NULL,
event_index INTEGER NOT NULL,
event_type INTEGER NOT NULL,
order_index INTEGER NOT NULL,
rollup_chain_id BLOB NOT NULL,
sender BLOB,
to_address BLOB,
value BLOB,
gas BLOB,
max_fee_per_gas BLOB,
data BLOB,
rollup_recipient BLOB,
amount BLOB,
token BLOB,
PRIMARY KEY (block_number, event_index)
);

CREATE TABLE IF NOT EXISTS zenith_headers (
block_number INTEGER PRIMARY KEY NOT NULL,
host_block_number BLOB NOT NULL,
rollup_chain_id BLOB NOT NULL,
gas_limit BLOB NOT NULL,
reward_address BLOB NOT NULL,
block_data_hash BLOB NOT NULL
);

CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY NOT NULL,
block_number INTEGER NOT NULL
);
113 changes: 113 additions & 0 deletions crates/cold-sql/migrations/001_initial_pg.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
-- Cold storage schema with fully decomposed columns (PostgreSQL).
--
-- Uses BYTEA and BIGINT for correct PostgreSQL types.

CREATE TABLE IF NOT EXISTS headers (
block_number BIGINT PRIMARY KEY NOT NULL,
block_hash BYTEA NOT NULL,
parent_hash BYTEA NOT NULL,
ommers_hash BYTEA NOT NULL,
beneficiary BYTEA NOT NULL,
state_root BYTEA NOT NULL,
transactions_root BYTEA NOT NULL,
receipts_root BYTEA NOT NULL,
logs_bloom BYTEA NOT NULL,
difficulty BYTEA NOT NULL,
gas_limit BIGINT NOT NULL,
gas_used BIGINT NOT NULL,
timestamp BIGINT NOT NULL,
extra_data BYTEA NOT NULL,
mix_hash BYTEA NOT NULL,
nonce BYTEA NOT NULL,
base_fee_per_gas BIGINT,
withdrawals_root BYTEA,
blob_gas_used BIGINT,
excess_blob_gas BIGINT,
parent_beacon_block_root BYTEA,
requests_hash BYTEA
);

CREATE INDEX IF NOT EXISTS idx_headers_hash ON headers (block_hash);

CREATE TABLE IF NOT EXISTS transactions (
block_number BIGINT NOT NULL,
tx_index BIGINT NOT NULL,
tx_hash BYTEA NOT NULL,
tx_type INTEGER NOT NULL,
sig_y_parity INTEGER NOT NULL,
sig_r BYTEA NOT NULL,
sig_s BYTEA NOT NULL,
chain_id BIGINT,
nonce BIGINT NOT NULL,
gas_limit BIGINT NOT NULL,
to_address BYTEA,
value BYTEA NOT NULL,
input BYTEA NOT NULL,
gas_price BYTEA,
max_fee_per_gas BYTEA,
max_priority_fee_per_gas BYTEA,
max_fee_per_blob_gas BYTEA,
blob_versioned_hashes BYTEA,
access_list BYTEA,
authorization_list BYTEA,
PRIMARY KEY (block_number, tx_index)
);

CREATE INDEX IF NOT EXISTS idx_tx_hash ON transactions (tx_hash);

CREATE TABLE IF NOT EXISTS receipts (
block_number BIGINT NOT NULL,
tx_index BIGINT NOT NULL,
tx_type INTEGER NOT NULL,
success INTEGER NOT NULL,
cumulative_gas_used BIGINT NOT NULL,
PRIMARY KEY (block_number, tx_index)
);

CREATE TABLE IF NOT EXISTS logs (
block_number BIGINT NOT NULL,
tx_index BIGINT NOT NULL,
log_index BIGINT NOT NULL,
address BYTEA NOT NULL,
topic0 BYTEA,
topic1 BYTEA,
topic2 BYTEA,
topic3 BYTEA,
data BYTEA NOT NULL,
PRIMARY KEY (block_number, tx_index, log_index)
);

CREATE INDEX IF NOT EXISTS idx_logs_address ON logs (address);
CREATE INDEX IF NOT EXISTS idx_logs_topic0 ON logs (topic0);

CREATE TABLE IF NOT EXISTS signet_events (
block_number BIGINT NOT NULL,
event_index BIGINT NOT NULL,
event_type INTEGER NOT NULL,
order_index BIGINT NOT NULL,
rollup_chain_id BYTEA NOT NULL,
sender BYTEA,
to_address BYTEA,
value BYTEA,
gas BYTEA,
max_fee_per_gas BYTEA,
data BYTEA,
rollup_recipient BYTEA,
amount BYTEA,
token BYTEA,
PRIMARY KEY (block_number, event_index)
);

CREATE TABLE IF NOT EXISTS zenith_headers (
block_number BIGINT PRIMARY KEY NOT NULL,
host_block_number BYTEA NOT NULL,
rollup_chain_id BYTEA NOT NULL,
gas_limit BYTEA NOT NULL,
reward_address BYTEA NOT NULL,
block_data_hash BYTEA NOT NULL
);

CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY NOT NULL,
block_number BIGINT NOT NULL
);
Loading