Skip to content
Draft
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,266 changes: 449 additions & 817 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = ["Your Name <your.email@example.com>"]
license = "MIT OR Apache-2.0"

[dependencies]
celestia-types = "0.11.0"
celestia-types = "0.15.0"
tendermint = "0.40.3"
ed25519-consensus = "2.1.0"
rand = "0.8.5"
Expand All @@ -29,4 +29,4 @@ tower = "0.4"

[[bin]]
name = "localestia"
path = "src/main.rs"
path = "src/main.rs"
34 changes: 16 additions & 18 deletions DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ This guide explains how to build and run your Localestia application using Docke

## Quick Start

1. Build and start the services:
1. Build a new image for localestia (pulls in any source changes):

```bash
docker build -t localestia:latest .
```

2. Start the services:

```bash
docker-compose up -d
Expand All @@ -18,10 +24,10 @@ docker-compose up -d
This will:

- Start a Redis instance
- Build and start your Localestia application
- Start your Localestia application
- Make the API available at <http://localhost:26658>

2. Check the logs:
3. Check the logs:

```bash
docker-compose logs -f localestia
Expand All @@ -39,9 +45,9 @@ You can configure the application through environment variables in the `docker-c

```yaml
environment:
- REDIS_URL=redis://redis:6379
- LISTEN_ADDR=0.0.0.0:26658
- CLEAR_REDIS=true
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0:26658}
CLEAR_REDIS: ${CLEAR_REDIS:-false}
```

### Environment Variables
Expand All @@ -52,22 +58,14 @@ environment:

## Data Persistence

Redis data is stored in a Docker volume `redis-data`. To completely reset the data:
Redis data is _not_ persisted by default.
If you uncomment to enable that in [docker-compose.yaml](./docker-compose.yml), data is persisted in a `redis-data` Docker volume.
To completely reset the data:

```bash
docker-compose down -v
```

## Building for Production

For production deployments, you can build a Docker image:

```bash
docker build -t localestia:latest .
```

This creates an optimized image that you can deploy to any Docker-compatible environment.

## Customization

### Using External Redis
Expand All @@ -88,5 +86,5 @@ To expose your API on a different port:

```yaml
ports:
- "8080:26658" # Maps host port 8080 to container port 26658
- "8080:26658" # Maps host port 8080 to container port 26658
```
65 changes: 46 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
FROM rust:1.85 AS builder
FROM rust:latest AS chef
# cargo-chef is a small Rust binary; installing here avoids repeating in later stages
RUN cargo install cargo-chef
WORKDIR /usr/src/app

# Copy only manifests first to maximize dependency caching
COPY Cargo.toml Cargo.lock ./
# If using a workspace, also copy member Cargo.toml files:
# COPY crates/*/Cargo.toml ./crates/*/

# Prepare dependency graph
RUN cargo chef prepare --recipe-path recipe.json

############################
FROM rust:latest AS builder
ARG BIN=localestia
WORKDIR /usr/src/app

# Reuse cargo-chef from the planner to avoid reinstall
COPY --from=chef /usr/local/cargo/bin/cargo-chef /usr/local/cargo/bin/cargo-chef
COPY --from=chef /usr/src/app/recipe.json ./recipe.json

# Cook deps (this compiles all dependencies, but not your code)
# BuildKit caches registry, git, and target to speed up subsequent builds
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/usr/src/app/target \
cargo chef cook --release --recipe-path recipe.json

# Now bring in the actual source
COPY . .

# Build with release optimizations
RUN cargo build --release
# Compile your binary (reuses the cached deps + target artifacts)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/usr/src/app/target \
cargo build --release --bin $BIN

# Create a smaller runtime image
FROM debian:bookworm-slim
############################
FROM debian:bookworm-slim AS runtime
ARG BIN=localestia

RUN apt-get update && apt-get install -y \
ca-certificates \
libssl-dev \
# Minimal runtime deps (OpenSSL 3 on Bookworm)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates libssl3 \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy the binary from the builder stage
COPY --from=builder /usr/src/app/target/release/localestia /app/localestia
# Copy only the compiled binary
COPY --from=builder /usr/src/app/target/release/${BIN} /app/${BIN}

# Set environment variables (these can be overridden at runtime)
ENV REDIS_URL=redis://redis:6379
ENV LISTEN_ADDR=0.0.0.0:26658
ENV CLEAR_REDIS=true
# Defaults (override at runtime)
ENV REDIS_URL=redis://redis:6379 \
LISTEN_ADDR=0.0.0.0:26658 \
CLEAR_REDIS=true

# Expose the port
EXPOSE 26658

# Run the binary
CMD ["./localestia"]
ENTRYPOINT ["/app/localestia"]
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ Localestia implements the following JSON-RPC methods:

### Blob Methods

| Method | Description | Parameters |
| ------ | ----------- | ---------- |
| `blob.Get` | Retrieves a blob by height, namespace, and commitment | `height: u64`, `namespace: Namespace`, `commitment: Commitment` |
| `blob.Submit` | Submits one or more blobs and returns the height | `blobs: Vec<Blob>`, `opts: TxConfig` |
| `blob.Included` | Checks if a blob is included at a specified height | `height: u64`, `namespace: Namespace`, `proof: NamespaceProof`, `commitment: Commitment` |
| Method | Description | Parameters |
| --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `blob.Get` | Retrieves a blob by height, namespace, and commitment | `height: u64`, `namespace: Namespace`, `commitment: Commitment` |
| `blob.Submit` | Submits one or more blobs and returns the height | `blobs: Vec<Blob>`, `opts: TxConfig` |
| `blob.Included` | Checks if a blob is included at a specified height | `height: u64`, `namespace: Namespace`, `proof: NamespaceProof`, `commitment: Commitment` |

### Header Methods

| Method | Description | Parameters |
| ------ | ----------- | ---------- |
| `header.GetByHash` | Gets a header by its hash | `hash: Hash` |
| `header.GetByHeight` | Gets a header at a specific height | `height: u64` |
| `header.GetRangeByHeight` | Gets a range of headers | `from: u64`, `to: u64` |
| `header.WaitForHeight` | Waits for a header at a specific height | `height: u64` |
| Method | Description | Parameters |
| ------------------------- | --------------------------------------- | ---------------------- |
| `header.GetByHash` | Gets a header by its hash | `hash: Hash` |
| `header.GetByHeight` | Gets a header at a specific height | `height: u64` |
| `header.GetRangeByHeight` | Gets a range of headers | `from: u64`, `to: u64` |
| `header.WaitForHeight` | Waits for a header at a specific height | `height: u64` |

### Share Methods

| Method | Description | Parameters |
| ------ | ----------- | ---------- |
| `share.GetEDS` | Gets the Extended Data Square at a height | `height: u64` |
| `share.GetRange` | Gets a range of shares | `height: u64`, `start: u64`, `end: u64` |
| Method | Description | Parameters |
| ---------------- | ----------------------------------------- | --------------------------------------- |
| `share.GetEDS` | Gets the Extended Data Square at a height | `height: u64` |
| `share.GetRange` | Gets a range of shares | `height: u64`, `start: u64`, `end: u64` |

## Usage Examples

Expand Down
20 changes: 20 additions & 0 deletions dev.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Compose / services
COMPOSE_FILE=docker-compose.yml
REDIS_SERVICE_NAME=redis
APP_SERVICE_NAME=localestia

# Images / tags / Dockerfile
DOCKER_CONTAINER_NAME=localestia
LOCAL_IMAGE=localestia
LOCAL_TAG=latest
DOCKERFILE=Dockerfile

# Buildx / cache
DOCKER_BUILDER_NAME=devbuilder
BUILD_CACHE_DIR=.docker/cache
BUILD_CACHE_GIT=.docker/cache/git
BUILD_CACHE_REG=.docker/cache/registry
BUILD_CACHE_TGT=.docker/cache/target

# Health / timeouts (seconds)
REDIS_HEALTH_TIMEOUT=15
33 changes: 33 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
services:
redis:
# https://hub.docker.com/_/redis
image: redis:8.2.1-alpine
# By default, runs with in-memory storage only. Data will be lost if the container restarts.
# Uncomment the following lines to enable persistence (and `volumes:` at EOF):
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
# volumes:
# - redis-data:/data
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 1s
retries: 5
restart: unless-stopped

localestia:
image: localestia:latest
depends_on:
redis:
condition: service_healthy
environment:
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0:26658}
CLEAR_REDIS: ${CLEAR_REDIS:-true}
ports:
- "26658:26658"
restart: unless-stopped
# Uncomment if enabling persistence above
# volumes:
# redis-data:
118 changes: 118 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Use bash with strict flags
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
set quiet := true
set dotenv-path := "dev.env"

# Read everything from .env (no inline defaults)
compose_file := env("COMPOSE_FILE")
docker_container_name := env("DOCKER_CONTAINER_NAME")
local_image := env("LOCAL_IMAGE")
local_tag := env("LOCAL_TAG")
dockerfile := env("DOCKERFILE")
build_cache_dir := env("BUILD_CACHE_DIR")
build_cache_git := env("BUILD_CACHE_GIT")
build_cache_reg := env("BUILD_CACHE_REG")
build_cache_tgt := env("BUILD_CACHE_TGT")
docker_builder_name := env("DOCKER_BUILDER_NAME")
redis_service := env("REDIS_SERVICE_NAME")
app_service := env("APP_SERVICE_NAME")
redis_health_timeout := env("REDIS_HEALTH_TIMEOUT")

default:
@just --list

# Start only Redis from compose, in the background.
redis-up:
@echo "Starting Redis (from {{ compose_file }})..."
docker compose -f {{ compose_file }} up -d {{ redis_service }}

# Stop only the Redis service (container remains, not removed).
redis-down:
@echo "Stopping Redis (from {{ compose_file }})..."
docker compose -f {{ compose_file }} stop {{ redis_service }}

# Wait on Redis to be healthy: returns 0 when healthy; non-zero if timeout.
_redis-wait:
@echo "Waiting up to {{ redis_health_timeout }}s for Redis to become healthy..."
# Try redis-cli ping inside the service until it replies PONG or we time out.
end=$$((SECONDS + {{ redis_health_timeout }})); \
while [ $$SECONDS -lt $$end ]; do \
if docker compose -f {{ compose_file }} exec -T {{ redis_service }} sh -lc 'redis-cli -h 127.0.0.1 -p 6379 ping 2>/dev/null | grep -q PONG'; then \
echo "Redis is healthy."; \
exit 0; \
fi; \
sleep 1; \
done; \
echo "Timed out waiting for Redis health."; \
exit 1

# View Redis logs (Ctrl+C to exit).
redis-logs:
@echo "Tailing Redis logs..."
docker compose -f {{ compose_file }} logs -f --tail=200 {{ redis_service }}

# Buildx bootstrap and cache dirs (so Rust layers persist)
_buildx-bootstrap:
mkdir -p {{ build_cache_dir }}
if ! docker buildx inspect {{ docker_builder_name }} >/dev/null 2>&1; then \
docker buildx create --name {{ docker_builder_name }} --driver docker-container --use; \
else \
docker buildx use {{ docker_builder_name }}; \
fi
docker buildx inspect --bootstrap >/dev/null

# Build docker image & tag (with BuildKit cache so Rust won't rebuild from scratch)
docker-build: _buildx-bootstrap
DOCKER_BUILDKIT=1 docker buildx build \
--builder {{ docker_builder_name }} \
--file "{{ dockerfile }}" \
--tag "{{ docker_container_name }}:{{ local_tag }}" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from type=local,src={{ build_cache_dir }} \
--cache-to type=local,dest={{ build_cache_dir }},mode=max \
--progress=plain \
.

# Tag an existing local image with a new tag: `just tag from=latest to=v0.1.1`
tag from="{{local_tag}}" to="{{local_tag}}":
@echo "Tagging {{ local_image }}:{{ from }} -> {{ local_image }}:{{ to }} ..."
docker tag {{ local_image }}:{{ from }} {{ local_image }}:{{ to }}

# Build & start docker: (re)build image, start redis, wait healthy, then start app
docker-up:
just docker-build
just redis-up
just _redis-wait
@echo "Starting {{ app_service }} (from {{ compose_file }})..."
docker compose -f {{ compose_file }} up -d {{ app_service }}

# Stop the whole stack (convenience): stops both services without removing them.
docker-down:
@echo "Stopping services ({{ redis_service }}, {{ app_service }}) ..."
docker compose -f {{ compose_file }} stop {{ redis_service }} {{ app_service }}

# Remove containers (but keep images & volumes).
docker-clean:
@echo "Removing service containers ..."
docker compose -f {{ compose_file }} rm -fsv {{ redis_service }} {{ app_service }} || true

# Show current compose status
docker-ps:
docker compose -f {{ compose_file }} ps

# Local dev build/run via Cargo
build:
cargo build

run:
just build
just redis-up
just _redis-wait
@echo "Starting {{ app_service }} (local build)..."
cargo run

# Format source code (Rust, Justfile, and TOMLs)
fmt:
cargo fmt # *.rs
just --quiet --unstable --fmt > /dev/null # justfile
taplo format > /dev/null 2>&1 # *.toml
Loading