Skip to content

Commit 345e74b

Browse files
authored
test: demos for amaru as relay node (#708)
* test: add a demo script for several nodes Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: show the ledger log messages Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: switch panes order Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: add a demo with a more complex topology Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: adjust scripts Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: export otel traces Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: add diagrams Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: set the service name for otel traces Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> * test: fix the log dir for the relay-1 demo Signed-off-by: Eric Torreborre <etorreborre@yahoo.com> --------- Signed-off-by: Eric Torreborre <etorreborre@yahoo.com>
1 parent 92247bc commit 345e74b

File tree

8 files changed

+977
-1
lines changed

8 files changed

+977
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ data/**/blocks/**
5454

5555
# Test arguments, generated data and traces created during the simulation
5656
simulation/amaru-sim/test-data/
57+
58+
# Relay demos data
59+
scripts/relay*/run/**

crates/amaru/src/observability.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ pub fn setup_open_telemetry(subscriber: &mut TracingSubscriber<Registry>) -> (Op
276276
use opentelemetry::KeyValue;
277277
use opentelemetry_sdk::{Resource, metrics::Temporality};
278278

279-
let resource = Resource::builder().with_attribute(KeyValue::new(SERVICE_NAME, DEFAULT_OTLP_SERVICE_NAME)).build();
279+
let service_name = std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| DEFAULT_OTLP_SERVICE_NAME.to_string());
280+
let resource = Resource::builder().with_attribute(KeyValue::new(SERVICE_NAME, service_name)).build();
280281

281282
// Traces & span
282283
let opentelemetry_provider = SdkTracerProvider::builder()

scripts/relay-1/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Relay demo: Haskell -> Amaru -> Amaru
2+
3+
This demo shows the use of an Amaru node between a Haskell node (upstream) and another Amaru node (downstream):
4+
5+
```
6+
cardano-node ──────→ amaru ──────→ amaru-downstream
7+
(port: 3001) (peer: 3001, (peer: 4001,
8+
listen: 4001) listen: 4002)
9+
```
10+
11+
3 nodes total: 1 cardano-node source, 2 Amaru relays.
12+
13+
## Prerequisites
14+
15+
- A `cardano-node` executable (installed via package manager, built from source, etc.)
16+
- A directory with cardano-node configuration files (`config.json`, `topology.json`)
17+
- The amaru node checked-out from https://github.com/pragma-org/amaru and bootstrapped with
18+
`make AMARU_NETWORK=preprod bootstrap`
19+
20+
## Configuration
21+
22+
The following environment variables configure the demo:
23+
24+
| Variable | Required | Default | Description |
25+
|---------------------------|----------|-------------------|-------------------------------------------------------|
26+
| `CARDANO_NODE` | **Yes** | - | Path to the cardano-node executable |
27+
| `CARDANO_NODE_CONFIG_DIR` | **Yes** | - | Directory containing config.json, topology.json, etc. |
28+
| `AMARU_DIR` | No | Current directory | Path to the amaru project directory |
29+
| `UPSTREAM_PORT` | No | 3001 | Port for cardano-node listener |
30+
| `LISTEN_PORT` | No | 4001 | Port for amaru listener (for downstream) |
31+
| `DOWNSTREAM_LISTEN_PORT` | No | 4002 | Port for amaru downstream listener |
32+
33+
The `CARDANO_NODE_CONFIG_DIR` should contain:
34+
35+
- `config.json` - node configuration
36+
- `topology.json` - network topology
37+
- `db/` - database directory (will be created if it doesn't exist)
38+
39+
## Usage
40+
41+
```bash
42+
export CARDANO_NODE=/path/to/cardano-node
43+
export CARDANO_NODE_CONFIG_DIR=/path/to/config-dir
44+
./demo.sh # start the demo
45+
./demo.sh stop # stop the demo
46+
./demo.sh status # check status
47+
./demo.sh restart amaru # restart the amaru node
48+
```
49+
50+
Running `./demo.sh` will open a tmux session starting the 3 nodes and we should see new headers eventually being
51+
received by the downstream amaru node.

scripts/relay-1/demo.sh

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ---------- config ----------
5+
SESSION="${SESSION:-amaru-demo}"
6+
7+
# Derive AMARU_DIR from script location (scripts/relay-1/demo.sh -> amaru root)
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
AMARU_DIR="${AMARU_DIR:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
10+
11+
LOGDIR="${LOGDIR:-/tmp/amaru-relay-1}"
12+
RUNDIR="${RUNDIR:-$AMARU_DIR/scripts/relay-1/run}"
13+
14+
# Cardano node configuration
15+
CARDANO_NODE="${CARDANO_NODE:-}" # path to cardano-node executable
16+
CARDANO_NODE_CONFIG_DIR="${CARDANO_NODE_CONFIG_DIR:-}" # directory with config.json, topology.json, etc.
17+
18+
# Deterministic ports (can be overridden with env vars)
19+
UPSTREAM_PORT="${UPSTREAM_PORT:-3001}" # cardano-node listener
20+
LISTEN_PORT="${LISTEN_PORT:-4001}" # amaru listener (for downstream)
21+
DOWNSTREAM_LISTEN_PORT="${DOWNSTREAM_LISTEN_PORT:-4002}" # amaru downstream listener
22+
23+
# ---------- helpers ----------
24+
ensure_dirs() {
25+
mkdir -p "$LOGDIR" "$RUNDIR"
26+
}
27+
28+
die() { echo "error: $*" >&2; exit 1; }
29+
30+
have() { command -v "$1" >/dev/null 2>&1; }
31+
32+
tmux_new_session() {
33+
tmux new-session -d -s "$SESSION" -c "$AMARU_DIR" -n nodes
34+
tmux set-option -t "$SESSION" remain-on-exit on
35+
# Disable automatic renaming so pane titles stay fixed
36+
tmux set-option -t "$SESSION" allow-rename off
37+
tmux set-option -t "$SESSION" automatic-rename off
38+
# Show pane titles in the border with colors
39+
tmux set-option -t "$SESSION" pane-border-status top
40+
tmux set-option -t "$SESSION" pane-border-format " #[fg=white,bg=blue,bold] #{pane_title} #[default] "
41+
tmux set-option -t "$SESSION" pane-border-style "fg=brightblack"
42+
tmux set-option -t "$SESSION" pane-active-border-style "fg=blue,bold"
43+
# Enable mouse support and clipboard integration
44+
tmux set-option -t "$SESSION" mouse on
45+
tmux set-option -t "$SESSION" set-clipboard on
46+
}
47+
48+
tmux_kill_session() {
49+
tmux kill-session -t "$SESSION" 2>/dev/null || true
50+
}
51+
52+
# Run a command in a pane, with nice bash defaults
53+
# Usage: pane_run <target-pane> <command>
54+
pane_run() {
55+
local target="$1"; shift
56+
local cmd="$*"
57+
# Join multi-line commands into a single line to avoid multiple prompts
58+
local oneline
59+
oneline=$(printf '%s' "$cmd" | tr '\n' ';' | sed 's/;;*/;/g; s/^;//; s/;$//')
60+
tmux send-keys -t "$target" "set -eo pipefail; cd '$AMARU_DIR'; $oneline" C-m
61+
}
62+
63+
# ---------- commands you must fill ----------
64+
cmd_upstream() {
65+
cat <<EOF
66+
printf '\033]2;upstream\033\\\\'
67+
echo "[upstream] starting..."
68+
$CARDANO_NODE run --config $CARDANO_NODE_CONFIG_DIR/config.json --topology $CARDANO_NODE_CONFIG_DIR/topology.json --database-path $CARDANO_NODE_CONFIG_DIR/db --socket-path $CARDANO_NODE_CONFIG_DIR/node.socket --port $UPSTREAM_PORT 2>&1 | tee '$LOGDIR/upstream.log'
69+
sleep 999999
70+
EOF
71+
}
72+
73+
cmd_amaru() {
74+
local delay="${UPSTREAM_INIT_DELAY:-10}"
75+
cat <<EOF
76+
printf '\033]2;amaru\033\\\\'
77+
echo "[amaru] waiting ${delay}s for cardano-node to initialize..."
78+
sleep $delay
79+
echo "[amaru] starting..."
80+
cd $AMARU_DIR
81+
export AMARU_TRACE=warn,amaru_consensus=debug,amaru::ledger=info
82+
ulimit -n 65536
83+
cargo run --profile dev -- --with-json-traces run --peer-address 127.0.0.1:$UPSTREAM_PORT --listen-address 0.0.0.0:$LISTEN_PORT --chain-dir $RUNDIR/amaru/chain.preprod.db --ledger-dir $RUNDIR/amaru/ledger.preprod.db 2>&1 | tee '$LOGDIR/amaru.log'
84+
sleep 999999
85+
EOF
86+
}
87+
88+
cmd_amaru_downstream() {
89+
local delay="${DOWNSTREAM_INIT_DELAY:-15}"
90+
cat <<EOF
91+
printf '\033]2;amaru-downstream\033\\\\'
92+
echo "[amaru-downstream] waiting ${delay}s for amaru to initialize..."
93+
sleep $delay
94+
echo "[amaru-downstream] starting..."
95+
cd $AMARU_DIR
96+
export AMARU_TRACE=warn,amaru_consensus=debug,amaru::ledger=info
97+
ulimit -n 65536
98+
cargo run --profile dev -- --with-json-traces run --peer-address 127.0.0.1:$LISTEN_PORT --listen-address 0.0.0.0:$DOWNSTREAM_LISTEN_PORT --chain-dir $RUNDIR/amaru-downstream/chain.preprod.db --ledger-dir $RUNDIR/amaru-downstream/ledger.preprod.db 2>&1 | tee '$LOGDIR/amaru-downstream.log'
99+
sleep 999999
100+
EOF
101+
}
102+
103+
cmd_watch() {
104+
# Curated view: tweak grep patterns to your log messages (handshake, ChainSync, BlockFetch…)
105+
cat <<EOF
106+
printf '\033]2;watch\033\\\\'
107+
echo "[watch] tailing logs (Ctrl-c in this pane won't stop the session; use ./demo.sh stop)"
108+
( tail -n +1 -F '$LOGDIR/upstream.log' '$LOGDIR/amaru.log' '$LOGDIR/amaru-downstream.log' | sed -E -e 's|^|[log] |' -e 's|\\[upstream\\]|[upstream]|g' ) | grep -E --line-buffered -i 'connected|handshake|chainsync|blockfetch|tip|accepted|peer|error|warn|TODO|\\[log\\]' || true
109+
EOF
110+
}
111+
112+
# ---------- main ----------
113+
start() {
114+
have tmux || die "tmux not found"
115+
[[ -n "$CARDANO_NODE" ]] || die "CARDANO_NODE must be set (path to cardano-node executable)"
116+
[[ -x "$CARDANO_NODE" ]] || die "CARDANO_NODE is not executable: $CARDANO_NODE"
117+
[[ -n "$CARDANO_NODE_CONFIG_DIR" ]] || die "CARDANO_NODE_CONFIG_DIR must be set (directory with config.json, topology.json, etc.)"
118+
[[ -d "$CARDANO_NODE_CONFIG_DIR" ]] || die "CARDANO_NODE_CONFIG_DIR does not exist: $CARDANO_NODE_CONFIG_DIR"
119+
[[ -f "$CARDANO_NODE_CONFIG_DIR/config.json" ]] || die "config.json not found in $CARDANO_NODE_CONFIG_DIR"
120+
[[ -f "$CARDANO_NODE_CONFIG_DIR/topology.json" ]] || die "topology.json not found in $CARDANO_NODE_CONFIG_DIR"
121+
[[ -d "$AMARU_DIR" ]] || die "AMARU_DIR does not exist: $AMARU_DIR"
122+
123+
ensure_dirs
124+
rm -f "$LOGDIR"/*.log 2>/dev/null || true
125+
126+
# Copy databases into isolated run directories
127+
rm -rf "$RUNDIR/amaru" "$RUNDIR/amaru-downstream"
128+
mkdir -p "$RUNDIR/amaru" "$RUNDIR/amaru-downstream"
129+
cp -r "$AMARU_DIR/chain.preprod.db" "$RUNDIR/amaru/chain.preprod.db"
130+
cp -r "$AMARU_DIR/ledger.preprod.db" "$RUNDIR/amaru/ledger.preprod.db"
131+
cp -r "$AMARU_DIR/chain.preprod.db" "$RUNDIR/amaru-downstream/chain.preprod.db"
132+
cp -r "$AMARU_DIR/ledger.preprod.db" "$RUNDIR/amaru-downstream/ledger.preprod.db"
133+
134+
# reset session
135+
tmux_kill_session
136+
tmux_new_session
137+
138+
# Layout:
139+
# nodes window: left split top/bottom = upstream/downstream, right = amaru
140+
# Pane numbers after splits: 0=top-left, 1=bottom-left, 2=right
141+
tmux split-window -t "$SESSION:nodes" -h -c "$AMARU_DIR" # create right pane
142+
tmux split-window -t "$SESSION:nodes.0" -v -c "$AMARU_DIR" # split left into upstream/downstream
143+
144+
tmux select-pane -t "$SESSION:nodes.0" \; select-pane -t "$SESSION:nodes.1"
145+
146+
# Name panes
147+
tmux select-pane -t "$SESSION:nodes.0" -T "upstream"
148+
tmux select-pane -t "$SESSION:nodes.1" -T "amaru-downstream"
149+
tmux select-pane -t "$SESSION:nodes.2" -T "amaru"
150+
151+
# Add watch window
152+
tmux new-window -t "$SESSION" -n watch -c "$AMARU_DIR"
153+
tmux select-pane -t "$SESSION:watch.0" -T "watch"
154+
155+
# Run commands
156+
pane_run "$SESSION:nodes.0" "$(cmd_upstream)"
157+
pane_run "$SESSION:nodes.1" "$(cmd_amaru_downstream)"
158+
pane_run "$SESSION:nodes.2" "$(cmd_amaru)"
159+
pane_run "$SESSION:watch.0" "$(cmd_watch)"
160+
161+
# Attach
162+
tmux select-window -t "$SESSION:nodes"
163+
exec tmux attach -t "$SESSION"
164+
}
165+
166+
restart_pane() {
167+
local target="$1"; shift
168+
local cmd="$*"
169+
tmux send-keys -t "$target" C-c 2>/dev/null || true
170+
sleep 1
171+
pane_run "$target" "$cmd"
172+
}
173+
174+
restart() {
175+
local pane="${1:?usage: $0 restart <upstream|amaru|amaru-downstream>}"
176+
[[ -n "$CARDANO_NODE" ]] || die "CARDANO_NODE must be set (path to cardano-node executable)"
177+
[[ -x "$CARDANO_NODE" ]] || die "CARDANO_NODE is not executable: $CARDANO_NODE"
178+
[[ -n "$CARDANO_NODE_CONFIG_DIR" ]] || die "CARDANO_NODE_CONFIG_DIR must be set"
179+
[[ -d "$CARDANO_NODE_CONFIG_DIR" ]] || die "CARDANO_NODE_CONFIG_DIR does not exist: $CARDANO_NODE_CONFIG_DIR"
180+
[[ -d "$AMARU_DIR" ]] || die "AMARU_DIR does not exist: $AMARU_DIR"
181+
case "$pane" in
182+
upstream) restart_pane "$SESSION:nodes.0" "$(cmd_upstream)" ;;
183+
amaru-downstream) restart_pane "$SESSION:nodes.1" "$(cmd_amaru_downstream)" ;;
184+
amaru) restart_pane "$SESSION:nodes.2" "$(cmd_amaru)" ;;
185+
*) die "unknown pane: $pane (choose upstream, amaru, or amaru-downstream)" ;;
186+
esac
187+
}
188+
189+
stop() {
190+
# Kill processes running in the tmux panes before killing the session
191+
if tmux has-session -t "$SESSION" 2>/dev/null; then
192+
# Send SIGTERM to all processes in each pane
193+
for pane in 0 1 2; do
194+
tmux send-keys -t "$SESSION:nodes.$pane" C-c 2>/dev/null || true
195+
done
196+
tmux send-keys -t "$SESSION:watch.0" C-c 2>/dev/null || true
197+
sleep 1
198+
fi
199+
200+
# Also kill any lingering processes (scoped to this demo only)
201+
pkill -f -- "--chain-dir $RUNDIR/amaru/chain.preprod.db" 2>/dev/null || true
202+
pkill -f -- "--chain-dir $RUNDIR/amaru-downstream/chain.preprod.db" 2>/dev/null || true
203+
pkill -f -- "--socket-path $CARDANO_NODE_CONFIG_DIR/node.socket" 2>/dev/null || true
204+
205+
tmux_kill_session
206+
echo "stopped tmux session: $SESSION"
207+
}
208+
209+
status() {
210+
if tmux has-session -t "$SESSION" 2>/dev/null; then
211+
echo "running: $SESSION"
212+
tmux list-windows -t "$SESSION"
213+
else
214+
echo "not running: $SESSION"
215+
fi
216+
}
217+
218+
case "${1:-start}" in
219+
start) start ;;
220+
stop) stop ;;
221+
restart) restart "${2:-}" ;;
222+
status) status ;;
223+
*) die "usage: $0 {start|stop|restart <pane>|status}" ;;
224+
esac

scripts/relay-2/README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Relay demo: Haskell -> Amaru -> Haskell
2+
3+
This demo shows the use of an Amaru node acting as a relay between two Haskell cardano-nodes:
4+
5+
```text
6+
cardano-node ──────→ amaru ──────→ cardano-node
7+
(port: 3001) (peer: 3001, (port: 3002,
8+
listen: 4001) topology→4001)
9+
```
10+
11+
3 nodes total: 1 cardano-node source, 1 Amaru relay, 1 cardano-node downstream.
12+
13+
This differs from relay-1 which has `cardano-node -> Amaru -> Amaru`.
14+
15+
## Prerequisites
16+
17+
- A `cardano-node` executable (installed via package manager, built from source, etc.)
18+
- **Two separate** directories with cardano-node configuration files:
19+
- Upstream: `config.json`, `topology.json` pointing to public network peers
20+
- Downstream: `config.json`, `topology.json` pointing to Amaru (127.0.0.1:4001)
21+
- The amaru node checked-out from https://github.com/pragma-org/amaru and bootstrapped with
22+
`make AMARU_NETWORK=preprod bootstrap`
23+
24+
## Configuration
25+
26+
The following environment variables configure the demo:
27+
28+
| Variable | Required | Default | Description |
29+
|--------------------------------------|----------|----------------|------------------------------------------------------------------------|
30+
| `CARDANO_NODE` | **Yes** | - | Path to the cardano-node executable |
31+
| `CARDANO_NODE_CONFIG_DIR` | **Yes** | - | Directory for upstream cardano-node (config.json, topology.json, etc.) |
32+
| `CARDANO_NODE_DOWNSTREAM_CONFIG_DIR` | **Yes** | - | Directory for downstream cardano-node |
33+
| `AMARU_DIR` | No | Script-derived | Path to the amaru project directory |
34+
| `UPSTREAM_PORT` | No | 3001 | Port for upstream cardano-node listener |
35+
| `LISTEN_PORT` | No | 4001 | Port for amaru listener (for downstream) |
36+
| `DOWNSTREAM_PORT` | No | 3002 | Port for downstream cardano-node listener |
37+
38+
## Downstream Topology Configuration
39+
40+
The downstream cardano-node's `topology.json` must point to amaru's listen address:
41+
42+
```json
43+
{
44+
"bootstrapPeers": null,
45+
"localRoots": [
46+
{
47+
"accessPoints": [
48+
{
49+
"address": "127.0.0.1",
50+
"port": 4001
51+
}
52+
],
53+
"advertise": false,
54+
"trustable": true,
55+
"valency": 1
56+
}
57+
],
58+
"publicRoots": [],
59+
"useLedgerAfterSlot": -1
60+
}
61+
```
62+
63+
## Usage
64+
65+
```bash
66+
export CARDANO_NODE=/path/to/cardano-node
67+
export CARDANO_NODE_CONFIG_DIR=/path/to/upstream-config
68+
export CARDANO_NODE_DOWNSTREAM_CONFIG_DIR=/path/to/downstream-config
69+
./demo.sh # start the demo
70+
./demo.sh stop # stop the demo
71+
./demo.sh status # check status
72+
./demo.sh restart amaru # restart the amaru node
73+
./demo.sh restart downstream # restart the downstream cardano-node
74+
```
75+
76+
Running `./demo.sh` will open a tmux session starting the 3 nodes. The data flow is:
77+
78+
1. Upstream cardano-node starts and syncs from the network
79+
2. Amaru connects to upstream and begins syncing blocks
80+
3. Downstream cardano-node connects to Amaru and receives blocks through it
81+
82+
We should see blocks flowing: upstream -> amaru -> downstream.

0 commit comments

Comments
 (0)