Skip to content

Commit 0d964ab

Browse files
authored
New explorer (#4292)
## Motivation Linera already has a block explorer but that's based on the node service and GraphQL subscriptions. Since we are doubling down on the block exporter and have an indexer destination (that pushes data to an SQL database) we should build an indexer on top of that. ## Proposal Add a simple block explorer that connects to the SQLite database and displays blocks. To test: 1. Build docker images (in `/docker` directory run `./make-all-build.sh`). 2. Run the setup (in `/docker` dir run `./make-all-up.sh`). You have a fully-functional setup now consisting of: - linera storage service - linera network (with 1 validator) and a faucet - linera exporter - linera indexer (SQLite db) - linera explorer To test further, you can deploy apps to it as usual to the local network (faucet is on 8080 port by default). <img width="1156" height="716" alt="Screenshot 2025-08-05 at 15 18 51" src="https://github.com/user-attachments/assets/f7688196-0f90-4deb-8a86-83c56dc887e9" /> <img width="1142" height="655" alt="Screenshot 2025-08-05 at 15 19 11" src="https://github.com/user-attachments/assets/8ed2a0a8-1b50-4af9-8277-72940e7b6aac" /> <img width="1192" height="644" alt="Screenshot 2025-08-05 at 15 19 24" src="https://github.com/user-attachments/assets/76ff4e4d-7fd4-420b-80cf-4f4f09319a80" /> (This PR was ~~almost entirely~~ (not anymore – a lot of back-and-forth and watching over his shoulder) written by Claude Code) ## Test Plan Manual. ## Release Plan - Nothing to do ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 1737cdd commit 0d964ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+11323
-21
lines changed

docker/.env.indexer-test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Storage service configuration
2+
LINERA_STORAGE_SERVICE_PORT=1235
3+
4+
# Indexer configuration
5+
INDEXER_PORT=8081
6+
INDEXER_DATABASE_PATH=/data/indexer.db
7+
8+
# Block exporter configuration
9+
BLOCK_EXPORTER_PORT=8882
10+
METRICS_PORT=9091
11+
12+
# Network configuration
13+
FAUCET_PORT=8080
14+
15+
# Docker image
16+
LINERA_INDEXER_IMAGE=linera-all-test

docker/.env.local

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Storage service configuration
2+
LINERA_STORAGE_SERVICE_PORT=1235
3+
4+
# Indexer configuration
5+
INDEXER_PORT=8081
6+
INDEXER_DATABASE_PATH=/data/indexer.db
7+
8+
# Block exporter configuration
9+
BLOCK_EXPORTER_PORT=8882
10+
METRICS_PORT=9091
11+
12+
# Network configuration
13+
FAUCET_PORT=8080
14+
15+
# Docker image
16+
LINERA_INDEXER_IMAGE=linera-all-test

docker/Dockerfile.explorer

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Multi-stage build for Linera Explorer
2+
# Stage 1: Build the frontend
3+
FROM node:18-alpine AS frontend-builder
4+
5+
WORKDIR /app
6+
7+
# Copy package files
8+
COPY linera-explorer-new/package*.json ./
9+
COPY linera-explorer-new/tsconfig*.json ./
10+
COPY linera-explorer-new/vite.config.ts ./
11+
COPY linera-explorer-new/tailwind.config.js ./
12+
COPY linera-explorer-new/postcss.config.js ./
13+
COPY linera-explorer-new/index.html ./
14+
15+
# Install all dependencies (including dev dependencies for build)
16+
RUN npm ci
17+
18+
# Copy source code
19+
COPY linera-explorer-new/src ./src
20+
21+
# Build frontend
22+
RUN npm run build
23+
24+
# Stage 2: Setup backend dependencies
25+
FROM node:18-alpine AS backend-builder
26+
27+
WORKDIR /app
28+
29+
# Copy backend files
30+
COPY linera-explorer-new/server ./server
31+
COPY linera-explorer-new/package*.json ./
32+
33+
# Install backend dependencies (including better-sqlite3)
34+
RUN npm ci --only=production
35+
36+
# Stage 3: Production runtime
37+
FROM node:18-alpine
38+
39+
# Install sqlite3 for database operations
40+
RUN apk add --no-cache sqlite
41+
42+
WORKDIR /app
43+
44+
# Copy built frontend from stage 1
45+
COPY --from=frontend-builder /app/dist ./dist
46+
47+
# Copy backend and dependencies from stage 2
48+
COPY --from=backend-builder /app/server ./server
49+
COPY --from=backend-builder /app/node_modules ./node_modules
50+
COPY --from=backend-builder /app/package.json ./package.json
51+
52+
# Create data directory for database
53+
RUN mkdir -p /data
54+
55+
# Copy startup script
56+
COPY docker/explorer-entrypoint.sh ./entrypoint.sh
57+
RUN chmod +x ./entrypoint.sh
58+
59+
# Expose ports
60+
EXPOSE 3001 3002
61+
62+
# Set environment variables
63+
ENV NODE_ENV=production
64+
ENV DB_PATH=/data/indexer.db
65+
66+
# Health check
67+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
68+
CMD wget --no-verbose --tries=1 --spider http://localhost:3002/api/health || exit 1
69+
70+
# Start both frontend and backend
71+
CMD ["sh", "./entrypoint.sh"]

docker/Dockerfile.indexer-test

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,22 @@ COPY \
120120
FROM builder$copy AS binaries
121121

122122
# Setup running environment for container
123-
FROM debian:latest
123+
FROM debian:bookworm-slim
124124

125125
ARG git_commit
126126
LABEL git_commit=$git_commit
127127

128128
ARG build_date
129129
LABEL build_date=$build_date
130130

131-
RUN apt-get update && apt-get install -y \
131+
RUN apt-get update && apt-get install --no-install-recommends -y \
132132
ca-certificates \
133133
openssl \
134134
pkg-config \
135135
protobuf-compiler \
136136
clang \
137137
netcat-openbsd
138+
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
138139
RUN update-ca-certificates
139140

140141
ARG target

docker/README.indexer-test.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ The setup includes the following services in startup order:
1919

2020
```bash
2121
cd docker
22-
docker build -f Dockerfile.indexer-test -t linera-all-test ..
22+
./make-all-build.sh
2323
```
2424

2525
### 2. Start the Services
2626

2727
```bash
2828
# in /docker directory
29-
docker-compose -f docker-compose.indexer-test.yml --env-file ../.env.indexer-test up
29+
./make-all-up.sh
3030
```
3131

3232
### 3. Verify Services are Running
@@ -37,6 +37,13 @@ docker-compose -f docker-compose.indexer-test.yml --env-file ../.env.indexer-tes
3737
- Metrics `curl http://localhost:9091/metrics`
3838
- Faucet: `curl http://localhost:8080`
3939

40+
41+
### 4. Stop the services
42+
```bash
43+
# in /docker directory
44+
./make-all-down.sh
45+
```
46+
4047
## Configuration
4148

4249
### Environment Variables

docker/docker-compose.indexer-test.yml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ services:
7474
networks:
7575
- linera-network
7676

77-
# 4. Network (Validator + Faucet) - starts last
77+
# 4. Network (Validator + Faucet) - starts after block exporter
7878
linera-network:
7979
image: "${LINERA_INDEXER_IMAGE:-linera-all-test}"
8080
container_name: linera-network
@@ -91,9 +91,16 @@ services:
9191
--exporter-address linera-block-exporter
9292
--exporter-port ${BLOCK_EXPORTER_PORT:-8882}"
9393
environment:
94+
- RUST_LOG=linera=info
9495
- LINERA_STORAGE_SERVICE_PORT=${LINERA_STORAGE_SERVICE_PORT:-1235}
9596
- FAUCET_PORT=${FAUCET_PORT:-8080}
9697
- BLOCK_EXPORTER_PORT=${BLOCK_EXPORTER_PORT:-8882}
98+
healthcheck:
99+
test: ["CMD-SHELL", "nc -z localhost ${FAUCET_PORT:-8080}"]
100+
interval: 5s
101+
timeout: 3s
102+
retries: 10
103+
start_period: 20s
97104
volumes:
98105
- network-data:/data
99106
depends_on:
@@ -102,6 +109,32 @@ services:
102109
networks:
103110
- linera-network
104111

112+
# 5. Explorer - starts last after all services are ready
113+
linera-explorer:
114+
image: "${LINERA_EXPLORER_IMAGE:-linera-explorer-new}"
115+
container_name: linera-explorer
116+
ports:
117+
- "${EXPLORER_FRONTEND_PORT:-3001}:3001"
118+
- "${EXPLORER_API_PORT:-3002}:3002"
119+
environment:
120+
- NODE_ENV=production
121+
- DB_PATH=/data/indexer.db
122+
- EXPLORER_FRONTEND_PORT=${EXPLORER_FRONTEND_PORT:-3001}
123+
- EXPLORER_API_PORT=${EXPLORER_API_PORT:-3002}
124+
volumes:
125+
- ./indexer-data:/data:ro
126+
depends_on:
127+
linera-indexer:
128+
condition: service_healthy
129+
healthcheck:
130+
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:${EXPLORER_API_PORT:-3002}/api/health || exit 1"]
131+
interval: 30s
132+
timeout: 10s
133+
retries: 3
134+
start_period: 30s
135+
networks:
136+
- linera-network
137+
105138
volumes:
106139
indexer-data:
107140
driver: local

docker/explorer-entrypoint.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/sh
2+
set -e
3+
4+
echo "Starting Linera Explorer..."
5+
6+
# Wait for database file to exist (created by indexer)
7+
echo "Waiting for indexer database..."
8+
while [ ! -f "${DB_PATH:-/data/indexer.db}" ]; do
9+
echo "Database not found at ${DB_PATH:-/data/indexer.db}, waiting..."
10+
sleep 2
11+
done
12+
13+
echo "Database found, starting services..."
14+
15+
# Start the API server in the background
16+
echo "Starting API server on port 3002..."
17+
cd /app && node server/index.js &
18+
API_PID=$!
19+
20+
# Wait a moment for API to start
21+
sleep 3
22+
23+
# Start frontend server (serve static files)
24+
echo "Starting frontend server on port 3001..."
25+
npx serve -s dist -l 3001 &
26+
FRONTEND_PID=$!
27+
28+
# Function to handle shutdown
29+
shutdown() {
30+
echo "Shutting down services..."
31+
kill $API_PID $FRONTEND_PID 2>/dev/null || true
32+
wait $API_PID $FRONTEND_PID 2>/dev/null || true
33+
exit 0
34+
}
35+
36+
# Handle signals
37+
trap shutdown SIGTERM SIGINT
38+
39+
# Wait for either process to exit
40+
wait $API_PID $FRONTEND_PID

docker/make-all-build.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
# NOTE: This script assumes to be called from within the `/docker` directory.
3+
echo "Building linera images..."
4+
docker build -f ./Dockerfile.indexer-test -t linera-all-test ..
5+
6+
echo "Building linera explorer image..."
7+
docker build -f ./Dockerfile.explorer -t linera-explorer-new ..
8+
9+
10+
echo "Done building images"

docker/make-all-down.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
3+
docker-compose -f ./docker-compose.indexer-test.yml down --volumes --remove-orphans
4+
5+
# Removing data directories so that the next run starts with a clean state
6+
echo "Removing exporter data..."
7+
rm -rf ./exporter-data/
8+
echo "Removing indexer data..."
9+
rm -rf ./indexer-data/
10+
echo "All data removed"

docker/make-all-up.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# Removing data directories so that the next run starts with a clean state
4+
echo "Removing exporter data..."
5+
rm -rf ./exporter-data/
6+
echo "Removing indexer data..."
7+
rm -rf ./indexer-data/
8+
echo "All data removed"
9+
docker-compose -f ./docker-compose.indexer-test.yml up --force-recreate --remove-orphans

0 commit comments

Comments
 (0)