VAL23 — Relay Bandwidth Management Validation¶
Audience: engineers and reviewers who want a reproducible local lab proving
the relay bandwidth controls (relay config set-bandwidth) against a live
edged daemon, including rate-limit enforcement, daily-quota enforcement,
counter accuracy, and hot-reload behavior.
1. Scope¶
VAL23 validates five operational goals:
Unlimited baseline — with no bandwidth config, all segments deliver and
relay statusreportsunlimited=true, with peer evidence matching the exact seeded segment ID set.Rate-limit enforcement —
bytes_per_secondthrottles delivery to at most one segment per token-refill cycle;ThrottleCountaccumulates,QuotaDropCountstays zero.Daily-quota enforcement —
daily_quota_bytescaps total bytes delivered per 24-hour window; segments beyond the quota are rejected and eventually deadletter;QuotaDropCountaccumulates; the terminal delivered IDs plus deadletter IDs reconcile exactly to the seeded set.Hot-reload —
relay config set-bandwidthapplied to a running daemon takes effect on the next executor cycle without a restart;applied=trueis returned and subsequent peer evidence matches the exact seeded segment ID set.Daily quota reset — the 24-hour rolling window resets correctly, verified via the
TestBandwidthLimiter_DailyQuota_ResetsAfter24hunit test with an injected clock (live 24h advance is not practical in a lab).
Branch rule: coverage by existing runner¶
The existing run_edge_deadletter_lab.sh --with-bandwidth mode covers
relay config set-bandwidth command execution and the relay.bandwidth.configured
audit event, but has three gaps that VAL23 addresses:
Gap |
Existing lab |
VAL23 |
|---|---|---|
Rate-only vs quota-only isolation |
No — always sets both simultaneously |
Yes — S-B (rate only), S-C (quota only) |
ThrottleCount / QuotaDropCount counter accuracy |
No |
Yes — S-B verifies ThrottleCount >= 4, QuotaDropCount = 0; S-C verifies QuotaDropCount >= 4 |
Hot-reload (set-bandwidth mid-run) |
No |
Yes — S-D verifies applied=true + delivery resumes |
New standalone runner is justified because:
The existing lab hardcodes a combined rate+quota scenario that cannot isolate which limit is responsible for throttling
Port isolation (19054–19055) prevents interference with VAL19–VAL22
No impairment proxy is required: bandwidth throttling is enforced inside edged before the outbound TCP dial, so a direct peer address is sufficient.
Out of scope¶
Bandwidth interaction with deadletter retry (covered by PR-16 evidence)
Per-peer bandwidth configuration (not implemented as of VAL23)
Quota persistence across edged restarts (quota window is in-memory; restart resets daily usage — documented behavior)
Live 24-hour quota reset window (covered by unit test in S-E)
2. Architecture¶
edged (relay executor, bandwidth limiter)
→ edge_deadletter_lab_peer:19055 ← mTLS receive + JSON evidence
edged listen addr: 127.0.0.1:19054
edged control socket: $WORKDIR/ctl.sock
No impairment proxy. Bandwidth throttling is an in-process gate that runs
before transport.Dial().
Port assignments (isolated from VAL19–VAL22)¶
Component |
Address |
|---|---|
edged |
|
peer server |
|
3. Scenario Matrix¶
Scenario |
Bandwidth config |
Segments seeded |
Expected delivery |
Key counters |
|---|---|---|---|---|
S-A Unlimited baseline |
none |
4 × 64B |
4/4 |
unlimited=true |
S-B Rate only |
bytes_per_second=64, no quota |
5 × 64B |
5/5 (throttled then delivered) |
throttle_count >= 4, quota_drop_count = 0 |
S-C Quota only |
daily_quota=128, no rate |
6 × 64B |
2/6 (128B / 64B = 2) |
quota_drop_count >= 4, daily_used_bytes <= 128 |
S-D Hot-reload |
start: bytes_per_second=8 → live update to unlimited |
6 × 64B |
6/6 (blocked then released) |
applied=true |
S-E Daily reset |
unit test (injected clock) |
— |
PASS |
— |
Segment naming¶
Scenario |
Segment IDs |
|---|---|
S-A |
|
S-B |
|
S-C |
|
S-D |
|
All segments are 64 bytes and target peer-val23.
4. Token-Bucket Behaviour (S-B Timing Reference)¶
With bytes_per_second=64 and segment size 64B:
Time |
Tokens |
Event |
|---|---|---|
t=0 |
64 |
First scheduler tick. Segment 1: Allow(64) → OK (tokens → 0). Segments 2–5: Allow(64) → FAIL (throttle_count += 4). |
t=1s |
64 |
Segment 2 retry (backoff=1s). Allow(64) → OK. Segments 3–5 retry: FAIL (throttle_count += 3). |
t=2s |
64 |
Segment 3: OK. Segments 4–5: FAIL. |
t=3s |
64 |
Segment 4: OK. Segment 5: FAIL. |
t=4s |
64 |
Segment 5: OK. |
Total: throttle_count >= 4; all 5 delivered by t=5s. Runner waits 20s.
5. Daily-Quota Drop Path (S-C)¶
With daily_quota=128 and segment size 64B:
Segment 1:
daily_used + 64 <= 128→ OK.daily_used = 64.Segment 2:
64 + 64 <= 128→ OK.daily_used = 128.Segments 3–6:
128 + 64 > 128→ FAIL.quota_drop_count += 1each attempt.
Each quota-rejected segment records a failed attempt and backs off per the
retry schedule. With max_retry_count=3 and backoff_base_seconds=1:
Attempt 1: fail (quota drop)
Backoff 1s → attempt 2: fail (quota drop)
Backoff 2s → attempt 3: fail (quota drop) → deadletter
Total time to deadletter all 4 over-quota segments: ~4–7s. Runner waits 20s.
6. Hot-Reload Path (S-D)¶
With bytes_per_second=8 and segment size 64B (initial tokens=8 < 64):
t=1s: attempt 1, all 6 fail (tokens=8 < 64),
throttle_count += 6.t=2s: attempt 2 (backoff=1s), tokens=16 < 64, fail again.
t=3s: unlimited config applied (
bytes_per_second=0, daily_quota=0).t=4s: attempt 3 (backoff=2s from t=2) fires. Bandwidth is now unlimited →
Allow(64)returns true → all 6 segments deliver.
max_retry_count=5 ensures segments are not deadlettered before the
unlimited config is applied at t=3s.
7. 10-Check Matrix¶
ID |
Scenario |
Description |
Pass criterion |
|---|---|---|---|
VAL23-01 |
S-A |
Unlimited baseline: exact 4 seeded segments delivered |
|
VAL23-02 |
S-A |
relay status: unlimited=true when no bandwidth configured |
|
VAL23-03 |
S-B |
set-bandwidth audit event emitted |
|
VAL23-04 |
S-B |
Rate throttle: ThrottleCount >= 4, QuotaDropCount = 0 |
relay status after S-B shows |
VAL23-05 |
S-B |
Rate-limited delivery: exact 5 seeded segments delivered |
|
VAL23-06 |
S-C |
Quota enforcement: first 2 deliver, remaining 4 deadletter |
peer has exact |
VAL23-07 |
S-C |
QuotaDropCount >= 4 and daily_used_bytes <= daily_quota_bytes |
relay status after S-C: |
VAL23-08 |
S-D |
Hot-reload: applied=true returned immediately |
|
VAL23-09 |
S-D |
Post-hot-reload delivery: exact 6 seeded segments delivered |
|
VAL23-10 |
S-E |
Daily quota reset via deterministic clock |
|
Operator-visible output checks¶
Evidence file |
What to verify |
|---|---|
|
exact |
|
|
|
|
|
contains |
|
|
|
exact |
|
|
|
|
|
exact |
|
exact |
|
|
|
exact |
|
|
8. Run the Lab¶
export GOROOT=/home/ubuntu/.local/go1.25.7
export PATH="$GOROOT/bin:$PATH"
export GOTOOLCHAIN=local
bash scripts/labs/run_relay_bandwidth_val23_lab.sh
Optional custom evidence directory:
bash scripts/labs/run_relay_bandwidth_val23_lab.sh \
"$PWD/evidence/val23-relay-bandwidth-local-$(date +%F)"
Expected runtime: 2–3 minutes.
S-A: ~15s (4-segment delivery with no throttle)
S-B: ~25s (rate-throttled delivery, 5 segments at 64B/s)
S-C: ~25s (quota exhaustion + deadletter wait)
S-D: ~15s (block + hot-reload + delivery)
S-E: ~5s (unit test)
9. Final Report Format¶
VAL23 — Relay Bandwidth Management Validation
Date: <YYYY-MM-DD>
Environment: <OS, Go version>
Evidence dir: <path>
Scenario summary:
S-A unlimited baseline: 4/4 delivered, unlimited=true
S-B rate only (64B/s): 5/5 delivered, throttle_count=<N>, quota_drop_count=0
S-C quota only (128B): 2/6 delivered, quota_drop_count=<N>, daily_used=<N>B
S-D hot-reload (8→∞ B/s): 6/6 delivered after unlimited applied
S-E daily reset unit test: PASS
10-check matrix:
VAL23-01 PASS/FAIL unlimited baseline: <count>/4 delivered
VAL23-02 PASS/FAIL unlimited status: unlimited=<value>
VAL23-03 PASS/FAIL set-bandwidth audit: <found/not found>
VAL23-04 PASS/FAIL rate throttle counters: throttle=<N>, quota_drops=<N>
VAL23-05 PASS/FAIL rate-limited delivery: <count>/5 delivered
VAL23-06 PASS/FAIL quota enforcement: <count>/6 delivered
VAL23-07 PASS/FAIL quota drop counters: quota_drops=<N>, daily_used=<N>B
VAL23-08 PASS/FAIL hot-reload applied: <true/false>
VAL23-09 PASS/FAIL post-hotreload delivery: <count>/6
VAL23-10 PASS/FAIL daily quota reset: <PASS/FAIL>
Overall: PASS=<N> FAIL=<N>
10. Tooling¶
File |
Role |
|---|---|
|
Lab runner (entry point) |
|
Setup binary — seeds PENDING segments, generates TLS + edge.toml |
|
Reused from PR-14 — mTLS peer server |
|
Reused — final ledger state dump |