Skip to content

Commit da2de9b

Browse files
first commit
0 parents  commit da2de9b

37 files changed

+17982
-0
lines changed

.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Tempo node image (validators + RPC followers)
2+
DOCKER_IMAGE=ghcr.io/tempoxyz/tempo:1.4.2
3+
4+
# Faucet configuration (used by RPC follower nodes)
5+
FAUCET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
6+
FAUCET_AMOUNT=1000000000000000
7+
8+
# Observability log verbosity sent via OTLP
9+
LOG_LEVEL=info,reth=debug
10+
RUST_LOG=debug
11+
TRACE_SAMPLE_RATIO=0.1
12+
13+
# Grafana admin credentials
14+
GF_SECURITY_ADMIN_USER=admin
15+
GF_SECURITY_ADMIN_PASSWORD=admin
16+
17+
# Path to the tempo repo (for genesis regeneration via Justfile)
18+
TEMPO_REPO=../tempo

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.env
2+
.envrc
3+
data/

Justfile

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
set dotenv-load
2+
3+
tempo_repo := env("TEMPO_REPO", "../tempo")
4+
5+
# Start consensus network (validators + RPCs + Traefik), no monitoring
6+
up:
7+
docker compose --profile consensus up -d
8+
9+
# Start everything: consensus network + full observability stack
10+
up-all:
11+
docker compose --profile consensus --profile monitoring up -d
12+
13+
up-restart:
14+
docker compose --profile consensus --profile monitoring up -d --force-recreate
15+
16+
# Stop all services
17+
down:
18+
docker compose --profile consensus --profile monitoring down
19+
20+
# Stop all services with volumes
21+
down-v:
22+
docker compose --profile consensus --profile monitoring down -v
23+
24+
# Tail logs for a service (default: validator-0)
25+
# Usage: just logs [validator-0|validator-1|rpc-0|rpc-1|traefik|alloy|...]
26+
logs service="validator-0":
27+
docker logs -f tempo-{{service}}
28+
29+
# Regenerate genesis and validator keys from the tempo repo
30+
generate-genesis:
31+
cd {{tempo_repo}} && CARGO_HOME=/tmp/cargo-home cargo xtask generate-genesis \
32+
--validators 10.0.0.1:8000,10.0.0.2:8000,10.0.0.3:8000,10.0.0.4:8000 \
33+
--seed 0 \
34+
--accounts 100 \
35+
--no-extra-tokens \
36+
--no-pairwise-liquidity \
37+
--output {{justfile_directory()}}/consensus/generated-tmp
38+
rm -rf {{justfile_directory()}}/consensus/validator-{0,1,2,3} {{justfile_directory()}}/consensus/genesis.json
39+
mv {{justfile_directory()}}/consensus/generated-tmp/genesis.json {{justfile_directory()}}/consensus/genesis.json
40+
cd {{justfile_directory()}}/consensus/generated-tmp && \
41+
for i in 0 1 2 3; do mv "10.0.0.$((i+1)):8000" {{justfile_directory()}}/consensus/validator-$i; done
42+
rm -rf {{justfile_directory()}}/consensus/generated-tmp
43+
# Fund faucet account (Hardhat #0) with 10M native ETH for gas
44+
python3 -c "import json; \
45+
f=open('{{justfile_directory()}}/consensus/genesis.json'); g=json.load(f); f.close(); \
46+
g['alloc']['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266']={'balance':hex(10_000_000*10**18)}; \
47+
f=open('{{justfile_directory()}}/consensus/genesis.json','w'); json.dump(g,f,indent=2); f.write('\n'); f.close()"
48+
49+
# Run health check against the RPC endpoint
50+
health rpc_url="http://localhost:8545":
51+
./scripts/health-check.sh {{rpc_url}}
52+
53+
# Show service status
54+
status:
55+
docker compose --profile consensus --profile monitoring ps
56+
57+
# Serve docs locally with MkDocs (http://localhost:8000)
58+
docs:
59+
docker run --rm -p 8000:8000 -v {{justfile_directory()}}:/docs squidfunk/mkdocs-material

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Tempo Monitor
2+
3+
A local [Tempo](https://tempo.xyz) consensus development network with full observability -- 4 validators, 2 RPC followers, a faucet node, Traefik load balancer, and an optional monitoring stack (Grafana, Prometheus, Loki, Tempo, Pyroscope).
4+
5+
## Prerequisites
6+
7+
- Docker >= 24.0 with Compose >= 2.20
8+
- [just](https://github.com/casey/just) task runner
9+
- 8 GB RAM minimum (16 GB recommended with monitoring)
10+
11+
## Quick Start
12+
13+
```bash
14+
git clone https://github.com/alessandrolomanto/tempo-monitor.git
15+
cd tempo-monitor
16+
cp .env.example .env
17+
just up # consensus only
18+
just up-all # consensus + monitoring
19+
```
20+
21+
See the full docs running `just docs` for local DNS setup, chain interaction guide, and dashboard reference.
22+
## Endpoints
23+
24+
| Service | URL |
25+
|---|---|
26+
| JSON-RPC (load-balanced) | `http://localhost` or `http://rpc.tempo.local` |
27+
| Faucet RPC | `http://localhost:8546` or `http://faucet.tempo.local` |
28+
| Traefik dashboard | `http://localhost:8081` |
29+
| Grafana | `http://localhost:3000` (admin/admin) |
30+
| Prometheus | `http://localhost:9090` |
31+
32+
## Documentation
33+
34+
See the [full docs](docs/index.md) for local DNS setup, chain interaction guide, and dashboard reference.

address-exporter/config.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
global:
2+
logging: "info"
3+
metricsAddr: ":9091"
4+
namespace: eth_address
5+
checkInterval: "15s"
6+
7+
execution:
8+
url: "http://10.0.0.15:8545"
9+
timeout: "10s"
10+
11+
# TIP-20 pathUSD contract address (genesis-deployed on Tempo).
12+
# eth_getBalance is NOT used — it always returns a fixed sentinel value on Tempo.
13+
# Balances are read via eth_call → balanceOf(address) on this contract.
14+
# https://docs.tempo.xyz/quickstart/evm-compatibility#handling-eth-native-token-balance-checks
15+
tip20_contract: "0x20c0000000000000000000000000000000000000"
16+
17+
addresses:
18+
account:
19+
- name: faucet
20+
address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
21+
labels:
22+
role: faucet

alloy/config.alloy

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
otelcol.receiver.otlp "default" {
2+
grpc {
3+
endpoint = "0.0.0.0:4317"
4+
}
5+
http {
6+
endpoint = "0.0.0.0:4318"
7+
}
8+
9+
output {
10+
logs = [otelcol.exporter.loki.default.input]
11+
traces = [otelcol.exporter.otlp.tempo.input]
12+
}
13+
}
14+
15+
prometheus.scrape "execution" {
16+
targets = [
17+
{__address__ = "tempo-validator-0:9001", instance = "validator-0"},
18+
{__address__ = "tempo-validator-1:9001", instance = "validator-1"},
19+
{__address__ = "tempo-validator-2:9001", instance = "validator-2"},
20+
{__address__ = "tempo-validator-3:9001", instance = "validator-3"},
21+
{__address__ = "tempo-rpc-0:9001", instance = "rpc-0"},
22+
{__address__ = "tempo-rpc-1:9001", instance = "rpc-1"},
23+
{__address__ = "tempo-faucet:9001", instance = "faucet"},
24+
]
25+
forward_to = [prometheus.remote_write.default.receiver]
26+
scrape_interval = "15s"
27+
job_name = "tempo-execution"
28+
}
29+
30+
prometheus.scrape "consensus" {
31+
targets = [
32+
{__address__ = "tempo-validator-0:8001", instance = "validator-0"},
33+
{__address__ = "tempo-validator-1:8001", instance = "validator-1"},
34+
{__address__ = "tempo-validator-2:8001", instance = "validator-2"},
35+
{__address__ = "tempo-validator-3:8001", instance = "validator-3"},
36+
]
37+
forward_to = [prometheus.remote_write.default.receiver]
38+
scrape_interval = "15s"
39+
job_name = "tempo-consensus"
40+
}
41+
42+
prometheus.scrape "traefik" {
43+
targets = [
44+
{__address__ = "traefik:8082", instance = "traefik"},
45+
]
46+
forward_to = [prometheus.remote_write.default.receiver]
47+
scrape_interval = "15s"
48+
job_name = "traefik"
49+
}
50+
51+
prometheus.scrape "address_exporter" {
52+
targets = [
53+
{__address__ = "address-exporter:9091", instance = "address-exporter"},
54+
]
55+
forward_to = [prometheus.remote_write.default.receiver]
56+
scrape_interval = "15s"
57+
job_name = "address-exporter"
58+
}
59+
60+
prometheus.remote_write "default" {
61+
endpoint {
62+
url = "http://prometheus:9090/api/v1/write"
63+
}
64+
}
65+
66+
otelcol.exporter.loki "default" {
67+
forward_to = [loki.write.default.receiver]
68+
}
69+
70+
loki.write "default" {
71+
endpoint {
72+
url = "http://loki:3100/loki/api/v1/push"
73+
}
74+
}
75+
76+
otelcol.exporter.otlp "tempo" {
77+
client {
78+
endpoint = "grafana-tempo:4317"
79+
tls {
80+
insecure = true
81+
}
82+
}
83+
}
84+
85+
pyroscope.scrape "heap" {
86+
targets = [
87+
{__address__ = "tempo-validator-0:9001", service_name = "validator-0"},
88+
{__address__ = "tempo-validator-1:9001", service_name = "validator-1"},
89+
{__address__ = "tempo-validator-2:9001", service_name = "validator-2"},
90+
{__address__ = "tempo-validator-3:9001", service_name = "validator-3"},
91+
{__address__ = "tempo-rpc-0:9001", service_name = "rpc-0"},
92+
{__address__ = "tempo-rpc-1:9001", service_name = "rpc-1"},
93+
{__address__ = "tempo-faucet:9001", service_name = "faucet"},
94+
]
95+
forward_to = [pyroscope.write.default.receiver]
96+
scrape_interval = "15s"
97+
98+
profiling_config {
99+
profile.memory {
100+
enabled = true
101+
path = "/debug/pprof/heap"
102+
delta = false
103+
}
104+
profile.process_cpu { enabled = false }
105+
profile.goroutine { enabled = false }
106+
profile.block { enabled = false }
107+
profile.mutex { enabled = false }
108+
profile.fgprof { enabled = false }
109+
}
110+
}
111+
112+
discovery.docker "containers" {
113+
host = "unix:///var/run/docker.sock"
114+
}
115+
116+
discovery.relabel "tempo_nodes" {
117+
targets = discovery.docker.containers.targets
118+
119+
rule {
120+
source_labels = ["__meta_docker_container_label_pyroscope_agent_enabled"]
121+
regex = "true"
122+
action = "keep"
123+
}
124+
125+
rule {
126+
source_labels = ["__meta_docker_container_label_pyroscope_service"]
127+
target_label = "service_name"
128+
}
129+
}
130+
131+
discovery.process "tempo_nodes" {
132+
join = discovery.relabel.tempo_nodes.output
133+
}
134+
135+
pyroscope.ebpf "tempo_nodes" {
136+
targets = discovery.process.tempo_nodes.targets
137+
forward_to = [pyroscope.write.default.receiver]
138+
demangle = "full"
139+
collect_interval = "15s"
140+
sample_rate = 97
141+
}
142+
143+
pyroscope.write "default" {
144+
endpoint {
145+
url = "http://pyroscope:4040"
146+
}
147+
}

catalog-info.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: backstage.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: tempo-monitor
5+
title: Tempo Monitor
6+
description: Local Tempo consensus network with full observability — 4 validators, 2 RPC followers, faucet, Traefik, Grafana/Prometheus/Loki/Tempo/Pyroscope.
7+
annotations:
8+
backstage.io/techdocs-ref: dir:.
9+
github.com/project-slug: alessandrolomanto/tempo-monitor
10+
tags:
11+
- blockchain
12+
- tempo
13+
- observability
14+
- docker-compose
15+
spec:
16+
type: service
17+
lifecycle: development
18+
owner: alessandrolomanto

consensus/genesis.json

Lines changed: 690 additions & 0 deletions
Large diffs are not rendered by default.

consensus/validator-0/signing.key

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0x117be1de549d1d4322c4711f11efa0c5137903124f85fc37c761ffc91ace30cb
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0x006b170a6a737021ce871c154f6681ea2297b65bfcce6eb2e6a43c5e8a40fefba4

0 commit comments

Comments
 (0)