From 3b9fa5f9babf6e0a5d036811fcd47cbbba3a93ca Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Fri, 29 May 2026 12:11:49 +0530 Subject: [PATCH 01/11] docs: add Chain Events category to sidebar Introduces a new category for Chain Events (ETR Webhook) in the sidebar, including links to various how-to guides related to chain events, such as overview, quick start, configuration, and more. --- docs/how-tos/chain-events/configuration.md | 151 ++ docs/how-tos/chain-events/opentelemetry.md | 172 +++ docs/how-tos/chain-events/overview.md | 61 + docs/how-tos/chain-events/quick-start.md | 175 +++ docs/how-tos/chain-events/rate-limits.md | 152 ++ docs/how-tos/chain-events/registry-api.md | 158 ++ docs/how-tos/chain-events/scaling.md | 163 ++ docs/how-tos/chain-events/webhook-payload.md | 175 +++ sidebars.json | 14 + static/docs/chain-events/config.example.json | 117 ++ .../chain-events/grafana-fleet-health.json | 320 ++++ .../chain-events/grafana-webhook-events.json | 1344 +++++++++++++++++ static/docs/etr-listener/how-it-works.png | Bin 0 -> 118357 bytes 13 files changed, 3002 insertions(+) create mode 100644 docs/how-tos/chain-events/configuration.md create mode 100644 docs/how-tos/chain-events/opentelemetry.md create mode 100644 docs/how-tos/chain-events/overview.md create mode 100644 docs/how-tos/chain-events/quick-start.md create mode 100644 docs/how-tos/chain-events/rate-limits.md create mode 100644 docs/how-tos/chain-events/registry-api.md create mode 100644 docs/how-tos/chain-events/scaling.md create mode 100644 docs/how-tos/chain-events/webhook-payload.md create mode 100644 static/docs/chain-events/config.example.json create mode 100644 static/docs/chain-events/grafana-fleet-health.json create mode 100644 static/docs/chain-events/grafana-webhook-events.json create mode 100644 static/docs/etr-listener/how-it-works.png diff --git a/docs/how-tos/chain-events/configuration.md b/docs/how-tos/chain-events/configuration.md new file mode 100644 index 0000000..5de71ea --- /dev/null +++ b/docs/how-tos/chain-events/configuration.md @@ -0,0 +1,151 @@ +--- +id: configuration +title: Configuration Reference +sidebar_label: Configuration +sidebar_position: 3 +--- + +# Configuration Reference + +The container is configured via two sources: + +| Source | Purpose | +|---|---| +| `config.json` (volume-mounted) | Chain definitions, webhook URL, server settings | +| Environment variables (`.env` or `-e` flags) | Secrets and deployment-specific overrides | + +`${ENV_VAR}` placeholders inside `config.json` values are interpolated from the process environment at startup — use this to keep secrets out of the file. + +```json +{ + "chains": [...], + "webhook": { + "url": "https://my-system.example.com/events", + "headers": { + "Authorization": "Bearer ${WEBHOOK_API_TOKEN}" + } + } +} +``` + +--- + +## Chain Fields + +Each entry in the `chains` array configures one blockchain connection. + +| Field | Required | Default | Description | +|---|---|---|---| +| `chainKey` | **Yes** | — | Identifies the chain — see [Supported Chains](/docs/how-tos/chain-events/overview#supported-chains) | +| `rpcUrl` | **Yes** | — | WebSocket (`wss://`, `ws://`) or HTTP (`https://`, `http://`) RPC endpoint | +| `registryAddresses` | No | `[]` | EVM addresses of Token Registries to watch; can be managed at runtime via the [Registry API](./registry-api) | +| `replayFromBlock` | No | `0` | Block number where your registry was deployed — replay starts here on first run | +| `replayBatchSize` | No | `2000` | Max blocks per `eth_getLogs` batch — lower this (e.g. `500`) on free-tier RPCs with rate limits | +| `replayDelayMs` | No | `0` | Delay between replay batches in ms — add `500`–`1000` on free-tier RPCs | +| `confirmations` | No | `1` | Blocks to wait before delivery (max `12`) — increase to reduce reorg risk on faster chains | +| `pollIntervalMs` | No | chain default | Polling interval for HTTP-transport chains (`stability`, `astron`) — omit for WebSocket chains | + +:::tip WebSocket vs HTTP RPCs +WebSocket URLs (`wss://`) are used for event subscriptions on Ethereum, Polygon, and XDC — they receive new blocks in real time. Stability and Astron use HTTP polling (`https://`) because they do not support WebSocket subscriptions. +::: + +--- + +## Webhook Fields + +| Field | Required | Default | Description | +|---|---|---|---| +| `url` | **Yes** | — | Your downstream HTTP endpoint (must accept POST) | +| `timeoutMs` | No | `10000` | Per-attempt timeout in ms | +| `retryAttempts` | No | `3` | Retries on delivery failure (max `10`) | +| `retryBackoffMs` | No | `1000` | Base backoff in ms — doubles each attempt | +| `headers` | No | none | Extra headers on every delivery (e.g. `Authorization`, `X-Api-Key`) | +| `maxConcurrentDeliveries` | No | `10` | Max in-flight POSTs at the same time | +| `maxQueueSize` | No | `10000` | In-memory event buffer — events beyond this are logged and dropped | + +--- + +## Server Fields + +| Field | Required | Default | Description | +|---|---|---|---| +| `port` | No | `8080` | Port for the health check and Registry API | +| `host` | No | `0.0.0.0` | Keep `0.0.0.0` when running inside Docker | +| `workerProcesses` | No | `true` | Spawn each chain in its own OS process for fault isolation — one chain crashing does not affect others | +| `logLevel` | No | `info` | `trace` / `debug` / `info` / `warn` / `error` / `fatal` | + +--- + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `SIGNING_PRIVATE_KEY` | **Yes** | Raw 32-byte Ed25519 seed, base64-encoded — see [Quick Start](./quick-start#step-2--generate-a-signing-key) | +| `CONFIG_PATH` | No | Path to config file inside the container (default: `/app/config.json`) | +| `DB_HOST` | No | Database hostname — enables state persistence and distributed leasing | +| `DB_DIALECT` | No | Database type: `postgres` (default), `mysql`, `mariadb`, or `mssql` | +| `DB_PORT` | No | Database port — defaults to `5432` (postgres), `3306` (mysql/mariadb), `1433` (mssql) | +| `DB_NAME` | No | Database name (default: `trustvc_events`) | +| `DB_USER` | No | Database username (default: `postgres`) | +| `DB_PASSWORD` | No | Database password | +| `DB_POOL_MAX` | No | Connection pool max (default: `5`) | +| `DB_LEASE_TTL_MS` | No | Distributed lease TTL in ms for HA deployments (default: `30000`) | +| `OTEL_ENABLED` | No | Set `true` to enable OpenTelemetry traces and metrics | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | No | OTLP collector endpoint (default: `http://localhost:4318`) | +| `OTEL_SERVICE_NAME` | No | Service name reported in telemetry (default: `trustvc-webhook-events`) | + +--- + +## Full `config.json` Example + +```json +{ + "chains": [ + { + "chainKey": "ethereum-sepolia", + "rpcUrl": "wss://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}", + "registryAddresses": [ + "0xe6b5ce7E3691a0927b2806CE6638b35237DFfAc4" + ], + "replayFromBlock": 10896377, + "replayBatchSize": 10000, + "replayDelayMs": 500, + "confirmations": 1 + }, + { + "chainKey": "stability", + "rpcUrl": "https://rpc.stabilityprotocol.com/zgt/${STABILITY_API_KEY}", + "registryAddresses": [ + "0xCB524ba5D1C39f86d87af20B180c01aeD4517DcB" + ], + "pollIntervalMs": 10000, + "replayFromBlock": 35000000, + "replayBatchSize": 5000, + "replayDelayMs": 5000, + "confirmations": 1 + }, + { + "chainKey": "polygon-amoy", + "rpcUrl": "wss://polygon-amoy-bor-rpc.publicnode.com", + "registryAddresses": [], + "replayFromBlock": 39173608, + "replayBatchSize": 5000 + } + ], + "webhook": { + "url": "https://your-system.example.com/trustvc/events", + "timeoutMs": 10000, + "retryAttempts": 3, + "retryBackoffMs": 1000, + "headers": { + "Authorization": "Bearer ${WEBHOOK_SECRET}" + } + }, + "server": { + "port": 8080, + "host": "0.0.0.0", + "workerProcesses": true, + "logLevel": "info" + } +} +``` diff --git a/docs/how-tos/chain-events/opentelemetry.md b/docs/how-tos/chain-events/opentelemetry.md new file mode 100644 index 0000000..079e768 --- /dev/null +++ b/docs/how-tos/chain-events/opentelemetry.md @@ -0,0 +1,172 @@ +--- +id: opentelemetry +title: OpenTelemetry +sidebar_label: OpenTelemetry +sidebar_position: 7 +--- + +# OpenTelemetry + +`trustvc-chain-events` can export traces and metrics to any [OpenTelemetry](https://opentelemetry.io/)-compatible backend. Point it at your existing OTLP endpoint using environment variables — no changes to `config.json` are required. + +When `OTEL_ENABLED` is not set, all telemetry operations are no-ops with zero overhead. + +--- + +## Configuration + +Add these to your `.env`: + +```bash +OTEL_ENABLED=true +OTEL_SERVICE_NAME=trustvc-chain-events +OTEL_EXPORTER_OTLP_ENDPOINT=https://your-otlp-endpoint +OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogram +``` + +| Variable | Default | Description | +|---|---|---| +| `OTEL_ENABLED` | `false` | Set `true` to enable telemetry export | +| `OTEL_SERVICE_NAME` | `trustvc-webhook-events` | Service name shown in your observability backend | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318` | OTLP HTTP endpoint of your collector | +| `OTEL_EXPORTER_OTLP_HEADERS` | — | Auth headers required by your backend (see examples below) | +| `OTEL_INSTANCE_ID` | `-` | Custom instance identifier shown in metrics labels | +| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | — | Set to `explicit_bucket_histogram` for Prometheus-compatible histograms | + +--- + +## Backend Examples + +### Grafana Cloud + +```bash +OTEL_ENABLED=true +OTEL_SERVICE_NAME=trustvc-chain-events +OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp +OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic +OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogram +``` + +### Datadog + +```bash +OTEL_ENABLED=true +OTEL_SERVICE_NAME=trustvc-chain-events +OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com/api/intake/otlp/v1/traces +OTEL_EXPORTER_OTLP_HEADERS=DD-API-KEY= +``` + +### New Relic + +```bash +OTEL_ENABLED=true +OTEL_SERVICE_NAME=trustvc-chain-events +OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net +OTEL_EXPORTER_OTLP_HEADERS=api-key= +OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogram +``` + +### Self-hosted OTLP Collector + +```bash +OTEL_ENABLED=true +OTEL_SERVICE_NAME=trustvc-chain-events +OTEL_EXPORTER_OTLP_ENDPOINT=http://your-collector-host:4318 +``` + +--- + +## Emitted Metrics + +Metrics are exported on a 15-second interval. Prometheus-compatible backends receive them with dots replaced by underscores (e.g. `trustvc.instance.health` → `trustvc_instance_health`). + +### Instance Metrics + +| Metric | Type | Description | +|---|---|---| +| `trustvc.instance.health` | Gauge | `1` = ok/starting, `0` = degraded (at least one chain permanently failed) | +| `trustvc.instance.uptime_seconds` | Gauge | Process uptime in seconds | +| `trustvc.instance.active_chains` | Gauge | Number of chains currently running | +| `trustvc.instance.active_workers` | Gauge | Active child worker processes (`0` when `workerProcesses: false`) | +| `trustvc.instance.total_escrows` | Gauge | Total active TitleEscrow subscriptions across all chains | + +### Chain Metrics + +Labels: `chain`, `transport` + +| Metric | Type | Description | +|---|---|---| +| `trustvc.chain.connected` | Gauge | `1` = RPC connected, `0` = not connected | +| `trustvc.chain.last_seen_block` | Gauge | Latest block number observed | +| `trustvc.chain.active_escrows` | Gauge | Active TitleEscrow subscriptions on this chain | +| `trustvc.chain.reconnect_attempts` | Gauge | Cumulative RPC reconnection attempts | +| `trustvc.chain.events_received` | Counter | On-chain ETR events detected — labels: `chain`, `event_type` | +| `trustvc.chain.state_changes` | Counter | RPC provider state transitions — labels: `chain`, `from_status`, `to_status` | +| `trustvc.rpc.connects` | Counter | Successful RPC connections — labels: `chain`, `transport` | +| `trustvc.rpc.disconnects` | Counter | RPC disconnections — labels: `chain` | + +### Webhook Metrics + +| Metric | Type | Description | +|---|---|---| +| `trustvc.webhook.delivered` | Counter | Successful deliveries — label: `event_type` | +| `trustvc.webhook.failed` | Counter | Deliveries failed after all retries or dropped (queue full) — label: `event_type` | +| `trustvc.webhook.delivery_duration_ms` | Histogram | End-to-end delivery duration including retries — labels: `event_type`, `success` | +| `trustvc.webhook.queue_depth` | Gauge | Events currently waiting in the delivery queue | + +--- + +## Emitted Traces + +| Span | Description | Attributes | +|---|---|---| +| `deliver {event.type}` | Top-level span for a webhook delivery attempt | `event.id`, `event.type`, `event.source`, `webhook.url`, `delivery.attempts` | +| `webhook attempt {n}` | Child span for each individual retry | `http.attempt`, `http.url`, `http.status_code` | +| `chain.status_changed` | Emitted when the RPC provider state changes | `chain`, `transport`, `from_status`, `to_status`, `instance` | + +--- + +## Grafana Dashboards + +Two pre-built Grafana dashboards are available. Both use a Prometheus data source and can be imported directly into your Grafana instance. + +**To import either dashboard:** + +1. In Grafana, go to **Dashboards → Import** +2. Upload the downloaded JSON file +3. Select your Prometheus data source when prompted +4. Click **Import** + +--- + +### Dashboard 1 — Webhook Events + +Focused on day-to-day operational health: is my webhook delivering events? How fast? Are chains connected and tracking escrows? + +Download Webhook Events Dashboard + +

+ +| Section | Panels | +|---|---| +| **Overview** | Uptime, chains connected, active escrows, total delivered, total failed, queue depth | +| **Webhook Delivery** | Delivery rate per minute, p50/p95/p99 delivery duration, queue depth over time | +| **Chain Status** | Chain status table, active escrows per chain, latest block per chain, escrow replay duration (from traces) | +| **On-Chain Events** | Events detected by type per minute | + +--- + +### Dashboard 2 — Fleet & Chain Health + +Focused on infrastructure health across multiple instances: useful when running more than one container in a high-availability setup. Shows which instances are healthy, which chains are connected, and how state is distributed across the fleet. + +Download Fleet & Chain Health Dashboard + +

+ +| Section | Panels | +|---|---| +| **Fleet Overview** | Active instances, healthy instances, degraded instances, total active chains, total escrows, active worker processes | +| **Instance Health** | Instance status per host, instance uptime | +| **Chain Connectivity** | RPC connection status per chain, reconnect attempts, state transition rate, state transition counts | +| **Chain Activity** | Active escrows per chain, last seen block per chain | diff --git a/docs/how-tos/chain-events/overview.md b/docs/how-tos/chain-events/overview.md new file mode 100644 index 0000000..e070608 --- /dev/null +++ b/docs/how-tos/chain-events/overview.md @@ -0,0 +1,61 @@ +--- +id: overview +title: Chain Events — ETR Webhook Sidecar +sidebar_label: Overview +sidebar_position: 1 +--- + +# Chain Events — ETR Webhook Sidecar + +`trustvc-chain-events` is a self-hosted Docker container that watches your Token Registry contracts on-chain and delivers every ETR lifecycle event — mint, transfer, surrender, burn — to your system as a signed HTTP webhook within seconds of chain finality. + +## How It Works + +![TrustVC Secure Webhook Event Flow](/docs/etr-listener/how-it-works.png) + +Each event arrives as a **[CloudEvents 1.0](https://cloudevents.io/)** JSON payload with an `X-TrustVC-Signature` header you can verify independently. + +## Why Self-Hosted + +| Concern | Sidecar approach | +|---|---| +| Data sovereignty | Events never leave your network | +| Provider flexibility | Use your own Alchemy / QuickNode RPC | +| Compliance | Runs in a private VPC — no outbound except to your RPC and webhook | +| Isolation | Each deployment is fully independent | +| Availability | Decoupled from TrustVC infrastructure | + +## Supported Chains + +| `chainKey` | Network | Transport | +|---|---|---| +| `ethereum` | Ethereum Mainnet | WebSocket | +| `ethereum-sepolia` | Ethereum Sepolia | WebSocket | +| `polygon` | Polygon Mainnet | WebSocket | +| `polygon-amoy` | Polygon Amoy | WebSocket | +| `xdc` | XDC Network | WebSocket | +| `xdc-apothem` | XDC Apothem | WebSocket | +| `stability` | Stability Mainnet | HTTP polling | +| `stability-testnet` | Stability Testnet | HTTP polling | +| `astron` | Astron Mainnet | HTTP polling | +| `astron-testnet` | Astron Testnet | HTTP polling | + +Actual delivery timing depends on your `confirmations` setting in `config.json` — events are held until that many blocks have passed after the transaction. For example, with `"confirmations": 3` on Ethereum, delivery takes roughly 36 seconds (3 × 12 sec). + +## Prerequisites + +- A container runtime or managed service — Docker, AWS ECS, EC2, or any environment that can run a container image +- Access to an RPC endpoint for each chain you want to watch (WebSocket for EVM chains, HTTP for Stability/Astron) +- A database — PostgreSQL, MySQL, MariaDB, or MSSQL (optional but recommended — enables state persistence and hot restarts) +- An HTTP endpoint on your system that can receive POST requests + +:::tip No database? +The container runs without a database using in-memory state. If the container restarts it will replay missed events from the last known block. For production use, connect a database so state survives restarts. +::: + +## Next Steps + +1. [Quick Start](./quick-start) — get running in under 5 minutes +2. [Configuration Reference](./configuration) — all `config.json` and environment variable options +3. [Webhook Payload & Verification](./webhook-payload) — event schema and signature verification +4. [Registry API](./registry-api) — add and remove registries at runtime diff --git a/docs/how-tos/chain-events/quick-start.md b/docs/how-tos/chain-events/quick-start.md new file mode 100644 index 0000000..3a44b3c --- /dev/null +++ b/docs/how-tos/chain-events/quick-start.md @@ -0,0 +1,175 @@ +--- +id: quick-start +title: Quick Start +sidebar_label: Quick Start +sidebar_position: 2 +--- + +# Quick Start + +Get `trustvc-chain-events` running locally in under 5 minutes. + +--- + +## Step 1 — Pull the Image + +```bash +docker pull ghcr.io/trustvc/trustvc-chain-events:latest +``` + +--- + +## Step 2 — Generate a Signing Key + +The container signs every webhook delivery with an **Ed25519** key. You keep the private key; your receiver uses the public key to verify payloads. + +**Option A — random seed (simplest)** + +```bash +openssl rand -base64 32 +# example output: cZejchTTcxHUk8N+sbcOyVHZ3MVxzYQGYDCn+hFa4S4= +# Paste this value as SIGNING_PRIVATE_KEY in your .env +``` + +**Option B — PEM key pair (keep the public key for verification)** + +```bash +openssl genpkey -algorithm ed25519 -out private.pem +openssl pkey -in private.pem -pubout -out public.pem + +# Extract the 32-byte seed the container expects: +openssl pkey -in private.pem -outform DER | tail -c 32 | base64 +# Paste this output as SIGNING_PRIVATE_KEY in your .env +``` + +:::important +`SIGNING_PRIVATE_KEY` must be the **raw 32-byte Ed25519 seed encoded as base64** — not the PEM file itself. Use the extraction command above if you generated a PEM key. +::: + +--- + +## Step 3 — Create `config.json` + +Create a `config.json` in your working directory. At a minimum you need a chain, an RPC URL, and a webhook URL. + +Download config.json example + +

+ +```json +{ + "chains": [ + { + "chainKey": "ethereum-sepolia", + "rpcUrl": "wss://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY", + "registryAddresses": ["0xYourTokenRegistryAddress"], + "replayFromBlock": 6000000 + } + ], + "webhook": { + "url": "https://your-system.example.com/trustvc/events" + } +} +``` + +:::tip Finding your `replayFromBlock` +Set this to the block number when your Token Registry was deployed. The container replays all events from that block on first start to catch up. Using `0` works but will scan the entire chain history. +::: + +You can leave `registryAddresses` as an empty array `[]` and add them later via the [Registry API](./registry-api) — useful when you do not know addresses at deploy time. + +--- + +## Step 4 — Create `.env` + +```bash +# .env +SIGNING_PRIVATE_KEY="cZejchTTcxHUk8N+sbcOyVHZ3MVxzYQGYDCn+hFa4S4=" + +# Optional — PostgreSQL for state persistence +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=trustvcevents +DB_USER=postgres +DB_PASSWORD=secret +``` + +--- + +## Step 5 — Run + +**With an env file (recommended)** + +```bash +docker run -d \ + -v $(pwd)/config.json:/app/config.json:ro \ + --env-file .env \ + -p 8080:8080 \ + --name trustvc-events \ + ghcr.io/trustvc/trustvc-chain-events:latest +``` + +**With Docker Compose** + +```yaml title="docker-compose.yml" +services: + trustvc-events: + image: ghcr.io/trustvc/trustvc-chain-events:latest + ports: + - "8080:8080" + volumes: + - ./config.json:/app/config.json:ro + env_file: + - .env + restart: unless-stopped +``` + +```bash +docker compose up -d +``` + +--- + +## Step 6 — Verify + +```bash +curl http://localhost:8080/health +``` + +```json +{"status":"ok"} +``` + +Check the logs to confirm chains are connected and escrows are loaded: + +```bash +docker logs trustvc-events +``` + +You should see output similar to: + +``` +INFO [startup]: trustvc-webhook-events starting version: "0.1.0" +INFO [startup]: Database connected +INFO [startup]: Chain worker ready chain: "ethereum-sepolia" escrows: 22 +INFO [startup]: ✓ Server ready — listening for on-chain events + chains: "ethereum-sepolia (22 escrows)" + webhook: "https://your-system.example.com/trustvc/events" +``` + +:::note Chain worker shows 0 escrows? +If you see `escrows: 0` in the "Chain worker ready" line but the final "Server ready" line shows the correct count, this is a display-only race condition in startup logging — the container is working correctly. The definitive count is always in the final summary. +::: + +--- + +## What Happens Next + +Once running, the container: + +1. **Replays history** — scans from `replayFromBlock` to the current block to catch any events you missed +2. **Subscribes to live blocks** — uses WebSocket subscriptions (or polling for HTTP-transport chains) to receive new events in real time +3. **Signs and delivers** — each event is signed with your Ed25519 key and POSTed to your webhook URL +4. **Retries on failure** — uses exponential backoff (configurable via `retryAttempts` and `retryBackoffMs`) + +See [Webhook Payload & Verification](./webhook-payload) for the full event schema and how to verify signatures on your receiver. diff --git a/docs/how-tos/chain-events/rate-limits.md b/docs/how-tos/chain-events/rate-limits.md new file mode 100644 index 0000000..d572dd9 --- /dev/null +++ b/docs/how-tos/chain-events/rate-limits.md @@ -0,0 +1,152 @@ +--- +id: rate-limits +title: Avoiding RPC Rate Limits +sidebar_label: Rate Limits +sidebar_position: 6 +--- + +# Avoiding RPC Rate Limits + +When the container first starts it replays historical events by scanning blocks in batches using `eth_getLogs`. Free-tier and public RPC endpoints enforce strict rate limits — too many requests too fast will result in `429 Too Many Requests` errors and missed events. + +The following config fields let you tune replay speed to stay within your RPC's limits. + +--- + +## Key Fields + +| Field | Default | What it controls | +|---|---|---| +| `replayBatchSize` | `2000` | Max number of blocks scanned per `eth_getLogs` call | +| `replayDelayMs` | `0` | Pause between each batch in milliseconds | +| `confirmations` | `1` | Blocks to wait after a transaction before delivering the event | +| `pollIntervalMs` | chain default | How often HTTP-polling chains (Stability, Astron) check for new blocks | + +--- + +## How Replay Works + +On startup the container scans from `replayFromBlock` to the current block in chunks of `replayBatchSize`: + +``` +Block 6,000,000 ──► [batch 1: 6,000,000 – 6,002,000] ──wait replayDelayMs──► + [batch 2: 6,002,000 – 6,004,000] ──wait replayDelayMs──► + ... + [current block] ──► switch to live subscription +``` + +Reducing `replayBatchSize` means more requests, each covering fewer blocks. Adding `replayDelayMs` spaces them out so you don't burst past the RPC's per-second limit. + +--- + +## Recommended Settings by RPC Tier + +### Public / free-tier RPCs + +Free public endpoints (e.g. `publicnode.com`, Infura free) typically allow 5–10 requests/second and cap `eth_getLogs` at 2,000 blocks per call. + +```json +{ + "chainKey": "ethereum-sepolia", + "rpcUrl": "wss://ethereum-sepolia-rpc.publicnode.com", + "replayFromBlock": 6000000, + "replayBatchSize": 500, + "replayDelayMs": 1000, + "confirmations": 1 +} +``` + +### Paid RPC (e.g. Alchemy Growth / QuickNode) + +Paid plans support much larger batch sizes and higher throughput. + +```json +{ + "chainKey": "ethereum-sepolia", + "rpcUrl": "wss://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}", + "replayFromBlock": 6000000, + "replayBatchSize": 10000, + "replayDelayMs": 100, + "confirmations": 1 +} +``` + +### HTTP-polling chains (Stability, Astron) + +These chains use polling instead of WebSocket subscriptions. `pollIntervalMs` controls how often new blocks are checked — set it no lower than your RPC's minimum polling window. + +```json +{ + "chainKey": "stability", + "rpcUrl": "https://rpc.stabilityprotocol.com/zgt/${STABILITY_API_KEY}", + "replayFromBlock": 35000000, + "replayBatchSize": 5000, + "replayDelayMs": 5000, + "pollIntervalMs": 10000, + "confirmations": 1 +} +``` + +--- + +## Diagnosing Rate Limit Errors + +Check logs for these patterns: + +| Log message | Cause | Fix | +|---|---|---| +| `missing response` / `timeout` | Batch too large, RPC dropped the connection | Lower `replayBatchSize` | +| `rate limit exceeded` / `429` | Too many requests per second | Increase `replayDelayMs` | +| `could not decode result data` | Wrong registry address or RPC returned empty | Verify `registryAddresses` and `rpcUrl` | + +Enable debug logging to see each batch request: + +```bash +# In config.json +{ "logLevel": "debug" } +``` + +--- + +## Full Example — Multi-Chain with Conservative Rate Limits + +```json +{ + "chains": [ + { + "chainKey": "ethereum-sepolia", + "rpcUrl": "wss://ethereum-sepolia-rpc.publicnode.com", + "registryAddresses": ["0xYourRegistryAddress"], + "replayFromBlock": 6000000, + "replayBatchSize": 500, + "replayDelayMs": 1000, + "confirmations": 1 + }, + { + "chainKey": "polygon-amoy", + "rpcUrl": "wss://polygon-amoy-bor-rpc.publicnode.com", + "registryAddresses": [], + "replayFromBlock": 39000000, + "replayBatchSize": 1000, + "replayDelayMs": 500, + "confirmations": 1 + }, + { + "chainKey": "stability", + "rpcUrl": "https://rpc.stabilityprotocol.com/zgt/${STABILITY_API_KEY}", + "registryAddresses": [], + "replayFromBlock": 35000000, + "replayBatchSize": 5000, + "replayDelayMs": 5000, + "pollIntervalMs": 10000, + "confirmations": 1 + } + ], + "webhook": { + "url": "https://your-system.example.com/trustvc/events" + }, + "server": { + "logLevel": "info" + } +} +``` diff --git a/docs/how-tos/chain-events/registry-api.md b/docs/how-tos/chain-events/registry-api.md new file mode 100644 index 0000000..f282933 --- /dev/null +++ b/docs/how-tos/chain-events/registry-api.md @@ -0,0 +1,158 @@ +--- +id: registry-api +title: Registry API +sidebar_label: Registry API +sidebar_position: 5 +--- + +# Registry API + +The container exposes a REST API on port `8080` that lets you add and remove Token Registry contracts at runtime — without restarting the container or editing `config.json`. + +:::note Database required +All registry management endpoints require `DB_HOST` to be configured. They return `503 Service Unavailable` if no database is connected. Registries added via the API are persisted to the database and survive container restarts. +::: + +--- + +## Health Check + +```bash +GET /health +``` + +```bash +curl http://localhost:8080/health +``` + +```json +{"status":"ok"} +``` + +| `status` | Meaning | HTTP | +|---|---|---| +| `ok` | All chains connected and ready | `200` | +| `starting` | At least one chain still connecting | `200` | +| `degraded` | At least one chain permanently failed | `503` | + +--- + +## Add a Registry + +```bash +POST /registry +Content-Type: application/json +``` + +```bash +curl -X POST http://localhost:8080/registry \ + -H 'Content-Type: application/json' \ + -d '{ + "chainKey": "ethereum-sepolia", + "address": "0xYourTokenRegistryAddress", + "fromBlock": 6000000 + }' +``` + +### Request Body + +| Field | Required | Description | +|---|---|---| +| `chainKey` | **Yes** | Must match a `chainKey` in your running `config.json` | +| `address` | **Yes** | EVM address of the Token Registry contract | +| `fromBlock` | No | Block to replay history from (default: `0`) — set to your registry's deployment block | + +### Responses + +| HTTP | Meaning | +|---|---| +| `200` | Registry added and syncing — historical events are replaying in the background | +| `400` | Missing or invalid fields | +| `422` | Address is not a deployed TrustVC registry on that chain | +| `503` | Database not configured | + +:::tip Set `fromBlock` accurately +If you omit `fromBlock` or pass `0`, the container scans from the chain genesis — this can take a long time. Always pass the block number when your registry was deployed. +::: + +--- + +## List Registries + +```bash +GET /registries +``` + +```bash +curl http://localhost:8080/registries +``` + +```json +[ + { + "chainKey": "ethereum-sepolia", + "address": "0xe6b5ce7e3691a0927b2806ce6638b35237dffac4", + "fromBlock": 6000000, + "addedAt": "2024-01-15T10:00:00.000Z" + } +] +``` + +--- + +## Remove a Registry + +```bash +DELETE /registry/:chainKey/:address +``` + +```bash +curl -X DELETE \ + http://localhost:8080/registry/ethereum-sepolia/0xe6b5ce7e3691a0927b2806ce6638b35237dffac4 +``` + +| HTTP | Meaning | +|---|---| +| `200` | Registry removed — no further events will be delivered for this registry | +| `404` | Registry not found | +| `503` | Database not configured | + +--- + +## Workflow Example + +A common pattern is to start the container with an empty `registryAddresses: []` in `config.json`, then add registries as they are deployed. + +**Step 1 — Start the container** + +```bash +docker run -d \ + -v $(pwd)/config.json:/app/config.json:ro \ + --env-file .env \ + -p 8080:8080 \ + ghcr.io/trustvc/trustvc-chain-events:latest +``` + +**Step 2 — Deploy your Token Registry** + +Deploy your Token Registry contract and note the contract address and deployment block number. + +**Step 3 — Register it** + +```bash +curl -X POST http://localhost:8080/registry \ + -H 'Content-Type: application/json' \ + -d '{ + "chainKey": "ethereum-sepolia", + "address": "0xYourRegistryAddress", + "fromBlock": 6000000 + }' +``` + +Events start flowing immediately once the registry is added. + +**Step 4 — Confirm it is active** + +```bash +curl http://localhost:8080/registries +``` diff --git a/docs/how-tos/chain-events/scaling.md b/docs/how-tos/chain-events/scaling.md new file mode 100644 index 0000000..2e77d05 --- /dev/null +++ b/docs/how-tos/chain-events/scaling.md @@ -0,0 +1,163 @@ +--- +id: scaling +title: Scaling & High Availability +sidebar_label: Scaling & HA +sidebar_position: 8 +--- + +# Scaling & High Availability + +--- + +## Single Instance (Default) + +By default, each chain runs in its own child process (`workerProcesses: true` in config). This means one chain crashing or losing its RPC connection does not affect the others. For most deployments a single container is sufficient. + +```json +{ + "server": { + "workerProcesses": true + } +} +``` + +--- + +## Horizontal Scaling + +To run multiple instances of the container in parallel — for redundancy or higher throughput — you must connect a database. The container uses a **distributed lease** mechanism (one active worker per chain at a time) to prevent duplicate event delivery when multiple instances are running. + +```bash +# Required for horizontal scaling +DB_HOST=your-postgres-or-mysql-host +DB_LEASE_TTL_MS=30000 # how long a lease is held before another instance can take over +``` + +If the active instance crashes or loses its lease, another instance picks it up within `DB_LEASE_TTL_MS` milliseconds. + +--- + +## Docker Compose — 2 Replicas + +```yaml +services: + trustvc-events: + image: ghcr.io/trustvc/trustvc-chain-events:latest + deploy: + replicas: 2 + ports: + - "8080-8081:8080" + volumes: + - ./config.json:/app/config.json:ro + env_file: + - .env + restart: unless-stopped + + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: trustvcevents + POSTGRES_USER: trustvc + POSTGRES_PASSWORD: secret + volumes: + - pgdata:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + pgdata: +``` + +**`.env`** + +```bash +SIGNING_PRIVATE_KEY="your-base64-seed" + +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=trustvcevents +DB_USER=trustvc +DB_PASSWORD=secret +DB_LEASE_TTL_MS=30000 +``` + +--- + +## AWS ECS — Fargate + +For production deployments on AWS, run the container as a Fargate service. The task definition below mirrors the Docker run command. + +**Task definition (excerpt)** + +```json +{ + "family": "trustvc-chain-events", + "containerDefinitions": [ + { + "name": "trustvc-chain-events", + "image": "ghcr.io/trustvc/trustvc-chain-events:latest", + "portMappings": [ + { "containerPort": 8080, "protocol": "tcp" } + ], + "mountPoints": [ + { + "sourceVolume": "config", + "containerPath": "/app/config.json", + "readOnly": true + } + ], + "environment": [ + { "name": "DB_HOST", "value": "your-rds-endpoint" }, + { "name": "DB_NAME", "value": "trustvcevents" }, + { "name": "DB_USER", "value": "trustvc" }, + { "name": "DB_LEASE_TTL_MS", "value": "30000" } + ], + "secrets": [ + { + "name": "SIGNING_PRIVATE_KEY", + "valueFrom": "arn:aws:secretsmanager:region:account:secret:trustvc/signing-key" + }, + { + "name": "DB_PASSWORD", + "valueFrom": "arn:aws:secretsmanager:region:account:secret:trustvc/db-password" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/trustvc-chain-events", + "awslogs-region": "ap-southeast-1", + "awslogs-stream-prefix": "ecs" + } + } + } + ] +} +``` + +:::tip `config.json` on ECS +Mount `config.json` from an S3 object using an init container, or bake a non-secret config into a custom image layer. Keep secrets in AWS Secrets Manager and inject them as environment variables as shown above. +::: + +--- + +## Health Check Integration + +All orchestrators (ECS, Kubernetes, Docker Compose) can use the `/health` endpoint to determine instance readiness: + +```json +{ + "healthCheck": { + "command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 15 + } +} +``` + +| `/health` response | Meaning | +|---|---| +| `{"status":"ok"}` | All chains connected | +| `{"status":"starting"}` | Container is still connecting to one or more chains | +| `{"status":"degraded"}` | At least one chain has permanently failed — restart the container | diff --git a/docs/how-tos/chain-events/webhook-payload.md b/docs/how-tos/chain-events/webhook-payload.md new file mode 100644 index 0000000..da537f5 --- /dev/null +++ b/docs/how-tos/chain-events/webhook-payload.md @@ -0,0 +1,175 @@ +--- +id: webhook-payload +title: Webhook Payload & Verification +sidebar_label: Webhook Payload +sidebar_position: 4 +--- + +# Webhook Payload & Verification + +Every event is delivered as an HTTP `POST` to your configured `webhook.url`. + +--- + +## Request Format + +``` +POST /your-endpoint +Content-Type: application/json +X-TrustVC-Signature: ed25519= +``` + +The body follows the [CloudEvents 1.0](https://cloudevents.io/) specification: + +```json +{ + "specversion": "1.0", + "id": "550e8400-e29b-41d4-a716-446655440000", + "source": "urn:trustvc:11155111:0xe6b5ce7e3691a0927b2806ce6638b35237dffac4", + "type": "com.trustvc.etr.holder_transfer", + "datacontenttype": "application/json", + "time": "2024-01-15T10:31:00.000Z", + "subject": "1", + "data": { + "chainKey": "ethereum-sepolia", + "chainId": 11155111, + "registryAddress": "0xe6b5ce7e3691a0927b2806ce6638b35237dffac4", + "tokenId": "1", + "blockNumber": 6123456, + "transactionHash": "0xabcdef...", + "logIndex": 0, + "payload": { + "fromHolder": "0xSenderAddress", + "toHolder": "0xReceiverAddress" + } + } +} +``` + +### Top-Level Fields + +| Field | Description | +|---|---| +| `specversion` | Always `"1.0"` | +| `id` | UUID — globally unique event identifier | +| `source` | `urn:trustvc::` | +| `type` | Event type — see [Event Types](#event-types) below | +| `time` | ISO-8601 block timestamp | +| `subject` | Token ID as a string | +| `data` | Event-specific payload — see below | + +### `data` Fields + +| Field | Description | +|---|---| +| `chainKey` | Chain identifier (e.g. `ethereum-sepolia`) | +| `chainId` | Numeric EIP-155 chain ID | +| `registryAddress` | Token Registry contract address (lowercase) | +| `tokenId` | ETR token ID as a string | +| `blockNumber` | Block the event was confirmed in | +| `transactionHash` | Transaction that emitted the event | +| `logIndex` | Log index within the transaction | +| `payload` | Event-specific data (addresses, amounts, etc.) | + +:::tip Idempotency +Use `data.transactionHash + data.logIndex` as your idempotency key — this combination uniquely identifies any on-chain event. +::: + +--- + +## Event Types + +| `type` | Trigger | Key `payload` fields | +|---|---|---| +| `com.trustvc.etr.minted` | Token minted | `tokenId`, `owner` | +| `com.trustvc.etr.burned` | Token burned | `tokenId` | +| `com.trustvc.etr.surrendered` | Token surrendered to registry | `tokenId` | +| `com.trustvc.etr.restored` | Token restored from registry | `tokenId` | +| `com.trustvc.etr.registry_paused` | Registry paused | — | +| `com.trustvc.etr.registry_unpaused` | Registry unpaused | — | +| `com.trustvc.etr.escrow_created` | New TitleEscrow deployed | `escrowAddress` | +| `com.trustvc.etr.token_received` | Escrow took custody | `escrowAddress` | +| `com.trustvc.etr.nomination` | Beneficiary nominee set | `nominee` | +| `com.trustvc.etr.beneficiary_transfer` | Beneficiary transferred | `fromBeneficiary`, `toBeneficiary` | +| `com.trustvc.etr.holder_transfer` | Holder transferred | `fromHolder`, `toHolder` | +| `com.trustvc.etr.return_to_issuer` | Token returned to issuer | — | +| `com.trustvc.etr.shred` | Token permanently destroyed | — | +| `com.trustvc.etr.reject_transfer_beneficiary` | Beneficiary transfer rejected | — | +| `com.trustvc.etr.reject_transfer_holder` | Holder transfer rejected | — | +| `com.trustvc.etr.reject_transfer_owners` | Both roles rejected simultaneously | — | + +--- + +## Signature Verification + +Every request includes an `X-TrustVC-Signature` header: + +``` +X-TrustVC-Signature: ed25519= +``` + +The signature is computed over the **raw request body bytes** using the Ed25519 private key configured in `SIGNING_PRIVATE_KEY`. Your receiver verifies it with the corresponding public key — the public key cannot forge payloads even if your receiver is compromised. + +:::important Verify on raw bytes +Parse the signature header before parsing the JSON body. Always verify the signature against the raw, unparsed body bytes — not a re-serialized object. +::: + +### Node.js / TypeScript + +```typescript +import crypto from 'node:crypto'; +import fs from 'node:fs'; + +const publicKey = crypto.createPublicKey(fs.readFileSync('public.pem')); + +function verifyTrustVCWebhook(rawBody: Buffer, signatureHeader: string): boolean { + const b64url = signatureHeader.replace('ed25519=', ''); + const signature = Buffer.from(b64url, 'base64url'); + return crypto.verify(null, rawBody, publicKey, signature); +} + +// Express example +app.post('/trustvc/events', express.raw({ type: 'application/json' }), (req, res) => { + if (!verifyTrustVCWebhook(req.body, req.headers['x-trustvc-signature'] as string)) { + return res.status(401).send('Invalid signature'); + } + const event = JSON.parse(req.body.toString()); + // handle event... + res.status(200).send('ok'); +}); +``` + +### Python + +```python +import base64 +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.exceptions import InvalidSignature + +with open('public.pem', 'rb') as f: + public_key = load_pem_public_key(f.read()) + +def verify_trustvc_webhook(raw_body: bytes, signature_header: str) -> bool: + b64url = signature_header.replace('ed25519=', '') + # base64url → standard base64 + padding = '=' * (-len(b64url) % 4) + signature = base64.b64decode(b64url.replace('-', '+').replace('_', '/') + padding) + try: + public_key.verify(signature, raw_body) + return True + except InvalidSignature: + return False +``` + +### Getting the Public Key + +If you generated the key with `openssl rand -base64 32` (a raw seed), derive the public key: + +```bash +# Convert raw seed to PEM private key, then extract public key +echo "YOUR_BASE64_SEED" | base64 -d > seed.bin +openssl pkey -inform DER -in <(printf '\x30\x2e\x02\x01\x00\x30\x05\x06\x03\x2b\x65\x70\x04\x22\x04\x20'; cat seed.bin) -pubout -out public.pem +``` + +If you generated with `openssl genpkey`, you already have `public.pem` from [Quick Start Step 2](./quick-start#step-2--generate-a-signing-key). diff --git a/sidebars.json b/sidebars.json index deb27ce..a0708c1 100644 --- a/sidebars.json +++ b/sidebars.json @@ -52,6 +52,20 @@ "how-tos/decentralized-renderer/template-advanced-features" ] }, + { + "label": "Chain Events (ETR Webhook)", + "type": "category", + "items": [ + "how-tos/chain-events/overview", + "how-tos/chain-events/quick-start", + "how-tos/chain-events/configuration", + "how-tos/chain-events/webhook-payload", + "how-tos/chain-events/registry-api", + "how-tos/chain-events/rate-limits", + "how-tos/chain-events/opentelemetry", + "how-tos/chain-events/scaling" + ] + }, "how-tos/implementing-qr-codes", { "label": "OpenAttestation (Legacy)", diff --git a/static/docs/chain-events/config.example.json b/static/docs/chain-events/config.example.json new file mode 100644 index 0000000..ec66ece --- /dev/null +++ b/static/docs/chain-events/config.example.json @@ -0,0 +1,117 @@ +{ + "chains": [ + { + "chainKey": "ethereum-sepolia", + "rpcUrl": "wss://eth-sepolia.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY", + "registryAddresses": [ + "0xYourTokenRegistryAddress" + ], + "replayFromBlock": 6000000, + "replayBatchSize": 10000, + "replayDelayMs": 500, + "confirmations": 1 + }, + { + "chainKey": "ethereum", + "rpcUrl": "wss://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY", + "registryAddresses": [], + "replayFromBlock": 0, + "replayBatchSize": 10000, + "replayDelayMs": 500, + "confirmations": 3 + }, + { + "chainKey": "polygon", + "rpcUrl": "wss://polygon-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY", + "registryAddresses": [], + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 500, + "confirmations": 2 + }, + { + "chainKey": "polygon-amoy", + "rpcUrl": "wss://polygon-amoy-bor-rpc.publicnode.com", + "registryAddresses": [], + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 500, + "confirmations": 1 + }, + { + "chainKey": "xdc", + "rpcUrl": "wss://rpc.xinfin.network/ws", + "registryAddresses": [], + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 0, + "confirmations": 1 + }, + { + "chainKey": "xdc-apothem", + "rpcUrl": "wss://rpc.apothem.network/ws", + "registryAddresses": [], + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 0, + "confirmations": 1 + }, + { + "chainKey": "stability", + "rpcUrl": "https://rpc.stabilityprotocol.com/zgt/YOUR_STABILITY_API_KEY", + "registryAddresses": [], + "pollIntervalMs": 10000, + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 5000, + "confirmations": 1 + }, + { + "chainKey": "stability-testnet", + "rpcUrl": "https://rpc.testnet.stabilityprotocol.com/zgt/YOUR_STABILITY_API_KEY", + "registryAddresses": [], + "pollIntervalMs": 10000, + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 5000, + "confirmations": 1 + }, + { + "chainKey": "astron", + "rpcUrl": "https://rpc.astron.network", + "registryAddresses": [], + "pollIntervalMs": 10000, + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 0, + "confirmations": 1 + }, + { + "chainKey": "astron-testnet", + "rpcUrl": "https://rpc.testnet.astron.network", + "registryAddresses": [], + "pollIntervalMs": 10000, + "replayFromBlock": 0, + "replayBatchSize": 5000, + "replayDelayMs": 0, + "confirmations": 1 + } + ], + "webhook": { + "url": "https://your-system.example.com/trustvc/events", + "timeoutMs": 10000, + "retryAttempts": 3, + "retryBackoffMs": 1000, + "headers": { + "Authorization": "Bearer ${WEBHOOK_SECRET}" + }, + "maxConcurrentDeliveries": 10, + "maxQueueSize": 10000 + }, + "server": { + "port": 8080, + "host": "0.0.0.0", + "workerProcesses": true, + "logLevel": "info" + } +} diff --git a/static/docs/chain-events/grafana-fleet-health.json b/static/docs/chain-events/grafana-fleet-health.json new file mode 100644 index 0000000..6731ddd --- /dev/null +++ b/static/docs/chain-events/grafana-fleet-health.json @@ -0,0 +1,320 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "Prometheus / Mimir datasource connected to your OTel collector", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { "type": "grafana", "id": "grafana", "name": "Grafana", "version": "10.0.0" }, + { "type": "datasource", "id": "prometheus", "name": "Prometheus", "version": "1.0.0" }, + { "type": "panel", "id": "stat", "name": "Stat", "version": "" }, + { "type": "panel", "id": "timeseries", "name": "Time series", "version": "" }, + { "type": "panel", "id": "table", "name": "Table", "version": "" } + ], + "uid": "trustvc-fleet-health", + "title": "TrustVC — Fleet & Chain Health", + "description": "Fleet health, per-instance status, chain connection, state-change events, and escrow subscriptions", + "schemaVersion": 39, + "version": 1, + "editable": true, + "graphTooltip": 1, + "time": { "from": "now-1h", "to": "now" }, + "refresh": "15s", + "tags": ["trustvc", "fleet"], + "templating": { + "list": [ + { + "name": "instance", + "label": "Instance", + "type": "query", + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "query": "label_values(trustvc_instance_health, instance)", + "includeAll": true, + "allValue": ".*", + "multi": true, + "refresh": 2, + "sort": 1, + "current": {} + } + ] + }, + "panels": [ + { + "id": 100, + "type": "row", + "title": "Fleet Overview", + "collapsed": false, + "gridPos": { "x": 0, "y": 0, "w": 24, "h": 1 } + }, + { + "id": 1, + "type": "stat", + "title": "Active Instances", + "description": "Running process replicas currently emitting metrics", + "gridPos": { "x": 0, "y": 1, "w": 4, "h": 4 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none", "justifyMode": "center", "orientation": "auto", "textMode": "auto" }, + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "count(trustvc_instance_health{instance=~\"$instance\"})", "legendFormat": "", "refId": "A", "instant": true }] + }, + { + "id": 2, + "type": "stat", + "title": "Healthy Instances", + "description": "Instances where no chain has permanently failed", + "gridPos": { "x": 4, "y": 1, "w": 4, "h": 4 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none", "justifyMode": "center" }, + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "sum(trustvc_instance_health{instance=~\"$instance\"} == 1) or vector(0)", "legendFormat": "", "refId": "A", "instant": true }] + }, + { + "id": 3, + "type": "stat", + "title": "Degraded Instances", + "description": "Instances with at least one permanently failed chain", + "gridPos": { "x": 8, "y": 1, "w": 4, "h": 4 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none", "justifyMode": "center" }, + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "sum(trustvc_instance_health{instance=~\"$instance\"} == 0) or vector(0)", "legendFormat": "", "refId": "A", "instant": true }] + }, + { + "id": 4, + "type": "stat", + "title": "Total Active Chains", + "description": "Sum of chains running across all instances", + "gridPos": { "x": 12, "y": 1, "w": 4, "h": 4 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none", "justifyMode": "center" }, + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "sum(trustvc_instance_active_chains{instance=~\"$instance\"}) or vector(0)", "legendFormat": "", "refId": "A", "instant": true }] + }, + { + "id": 5, + "type": "stat", + "title": "Total Active Escrows", + "description": "TitleEscrow subscriptions across all chains and instances", + "gridPos": { "x": 16, "y": 1, "w": 4, "h": 4 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none", "justifyMode": "center" }, + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { "mode": "absolute", "steps": [{ "color": "blue", "value": null }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "sum(trustvc_instance_total_escrows{instance=~\"$instance\"}) or vector(0)", "legendFormat": "", "refId": "A", "instant": true }] + }, + { + "id": 6, + "type": "stat", + "title": "Active Worker Processes", + "description": "Forked child worker processes across all instances", + "gridPos": { "x": 20, "y": 1, "w": 4, "h": 4 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none", "justifyMode": "center" }, + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { "mode": "absolute", "steps": [{ "color": "blue", "value": null }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "sum(trustvc_instance_active_workers{instance=~\"$instance\"}) or vector(0)", "legendFormat": "", "refId": "A", "instant": true }] + }, + { + "id": 101, + "type": "row", + "title": "Per-Instance Status", + "collapsed": false, + "gridPos": { "x": 0, "y": 5, "w": 24, "h": 1 } + }, + { + "id": 7, + "type": "table", + "title": "Instance Status", + "description": "One row per active process replica — health, uptime, chains, workers, escrows", + "gridPos": { "x": 0, "y": 6, "w": 16, "h": 7 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false }, "sortBy": [{ "desc": false, "displayName": "Instance" }] }, + "fieldConfig": { + "defaults": { "custom": { "align": "left", "displayMode": "auto" } }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Health" }, + "properties": [ + { "id": "mappings", "value": [{ "type": "value", "options": { "1": { "text": "ok", "color": "green" }, "0": { "text": "degraded", "color": "red" } } }] }, + { "id": "custom.displayMode", "value": "color-background" } + ] + }, + { "matcher": { "id": "byName", "options": "Uptime" }, "properties": [{ "id": "unit", "value": "s" }, { "id": "custom.width", "value": 110 }] } + ] + }, + "transformations": [ + { "id": "labelsToFields", "options": { "valueLabel": "instance" } }, + { "id": "merge", "options": {} }, + { "id": "organize", "options": { "renameByName": { "trustvc_instance_health": "Health", "Value #A": "Health", "trustvc_instance_uptime_seconds": "Uptime", "Value #B": "Uptime", "trustvc_instance_active_chains": "Chains", "Value #C": "Chains", "trustvc_instance_active_workers": "Workers", "Value #D": "Workers", "trustvc_instance_total_escrows": "Escrows", "Value #E": "Escrows", "instance": "Instance" }, "excludeByName": { "Time": true, "__name__": true } } } + ], + "targets": [ + { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_instance_health{instance=~\"$instance\"}", "refId": "A", "instant": true, "format": "table" }, + { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_instance_uptime_seconds{instance=~\"$instance\"}", "refId": "B", "instant": true, "format": "table" }, + { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_instance_active_chains{instance=~\"$instance\"}", "refId": "C", "instant": true, "format": "table" }, + { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_instance_active_workers{instance=~\"$instance\"}", "refId": "D", "instant": true, "format": "table" }, + { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_instance_total_escrows{instance=~\"$instance\"}", "refId": "E", "instant": true, "format": "table" } + ] + }, + { + "id": 8, + "type": "timeseries", + "title": "Instance Uptime", + "description": "Gaps indicate process restarts", + "gridPos": { "x": 16, "y": 6, "w": 8, "h": 7 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "tooltip": { "mode": "multi", "sort": "none" }, "legend": { "displayMode": "list", "placement": "bottom" } }, + "fieldConfig": { "defaults": { "unit": "s", "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_instance_uptime_seconds{instance=~\"$instance\"}", "legendFormat": "{{instance}}", "refId": "A" }] + }, + { + "id": 102, + "type": "row", + "title": "Chain Health", + "collapsed": false, + "gridPos": { "x": 0, "y": 13, "w": 24, "h": 1 } + }, + { + "id": 9, + "type": "timeseries", + "title": "Chain RPC Connection Status", + "description": "1 = connected, 0 = not connected. Drops indicate disconnections.", + "gridPos": { "x": 0, "y": 14, "w": 12, "h": 8 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "tooltip": { "mode": "multi", "sort": "none" }, "legend": { "displayMode": "list", "placement": "bottom" } }, + "fieldConfig": { + "defaults": { + "min": 0, "max": 1, + "custom": { "lineWidth": 2, "fillOpacity": 15, "drawStyle": "line", "spanNulls": false }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_chain_connected{instance=~\"$instance\"}", "legendFormat": "{{chain}} ({{instance}})", "refId": "A" }] + }, + { + "id": 10, + "type": "timeseries", + "title": "Chain Reconnect Attempts", + "description": "Rising line = persistent connection trouble", + "gridPos": { "x": 12, "y": 14, "w": 12, "h": 8 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "tooltip": { "mode": "multi", "sort": "none" }, "legend": { "displayMode": "list", "placement": "bottom" } }, + "fieldConfig": { + "defaults": { + "custom": { "lineWidth": 2, "fillOpacity": 10 }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 3 }, { "color": "red", "value": 10 }] } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_chain_reconnect_attempts{instance=~\"$instance\"}", "legendFormat": "{{chain}} ({{instance}})", "refId": "A" }] + }, + { + "id": 103, + "type": "row", + "title": "State-Change Events", + "collapsed": false, + "gridPos": { "x": 0, "y": 22, "w": 24, "h": 1 } + }, + { + "id": 11, + "type": "timeseries", + "title": "Chain State Transitions (rate)", + "description": "Spikes = flapping connection. Labels show from_status → to_status.", + "gridPos": { "x": 0, "y": 23, "w": 14, "h": 8 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "tooltip": { "mode": "multi", "sort": "none" }, "legend": { "displayMode": "list", "placement": "bottom" } }, + "fieldConfig": { + "defaults": { + "unit": "ops", + "custom": { "lineWidth": 2, "fillOpacity": 10, "drawStyle": "bars", "barAlignment": 0 } + }, + "overrides": [] + }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "rate(trustvc_chain_state_changes_total{instance=~\"$instance\"}[5m])", "legendFormat": "{{chain}}: {{from_status}} → {{to_status}}", "refId": "A" }] + }, + { + "id": 12, + "type": "table", + "title": "State Transition Counts", + "description": "Total transitions since process start", + "gridPos": { "x": 14, "y": 23, "w": 10, "h": 8 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, + "fieldConfig": { "defaults": { "custom": { "align": "left", "displayMode": "auto" } }, "overrides": [] }, + "transformations": [ + { "id": "organize", "options": { "renameByName": { "chain": "Chain", "from_status": "From", "to_status": "To", "Value": "Count" }, "excludeByName": { "Time": true, "__name__": true } } } + ], + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_chain_state_changes_total{instance=~\"$instance\"}", "refId": "A", "instant": true, "format": "table" }] + }, + { + "id": 104, + "type": "row", + "title": "Event Processing", + "collapsed": false, + "gridPos": { "x": 0, "y": 31, "w": 24, "h": 1 } + }, + { + "id": 13, + "type": "timeseries", + "title": "Active Escrows per Chain", + "description": "Grows as new tokens are minted", + "gridPos": { "x": 0, "y": 32, "w": 12, "h": 8 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "tooltip": { "mode": "multi", "sort": "none" }, "legend": { "displayMode": "list", "placement": "bottom" } }, + "fieldConfig": { "defaults": { "min": 0, "custom": { "lineWidth": 2, "fillOpacity": 15 } }, "overrides": [] }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_chain_active_escrows{instance=~\"$instance\"}", "legendFormat": "{{chain}} ({{instance}})", "refId": "A" }] + }, + { + "id": 14, + "type": "timeseries", + "title": "Last Seen Block per Chain", + "description": "Flat line = listener stalled", + "gridPos": { "x": 12, "y": 32, "w": 12, "h": 8 }, + "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "options": { "tooltip": { "mode": "multi", "sort": "none" }, "legend": { "displayMode": "list", "placement": "bottom" } }, + "fieldConfig": { "defaults": { "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [{ "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "expr": "trustvc_chain_last_seen_block{instance=~\"$instance\"}", "legendFormat": "{{chain}} ({{instance}})", "refId": "A" }] + } + ], + "annotations": { "list": [] }, + "links": [] +} diff --git a/static/docs/chain-events/grafana-webhook-events.json b/static/docs/chain-events/grafana-webhook-events.json new file mode 100644 index 0000000..6523921 --- /dev/null +++ b/static/docs/chain-events/grafana-webhook-events.json @@ -0,0 +1,1344 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + }, + { + "name": "DS_TEMPO", + "label": "Tempo", + "description": "", + "type": "datasource", + "pluginId": "tempo", + "pluginName": "Tempo" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.0.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "datasource", + "id": "tempo", + "name": "Tempo", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + } + ], + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "title": "Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "textMode": "auto" + }, + "title": "Uptime", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_instance_uptime_seconds", + "legendFormat": "uptime", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.5 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "textMode": "auto" + }, + "title": "Chains Connected", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(trustvc_chain_connected)", + "legendFormat": "connected", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "textMode": "auto" + }, + "title": "Active Escrows", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(trustvc_chain_active_escrows)", + "legendFormat": "escrows", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "textMode": "auto" + }, + "title": "Total Delivered", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(trustvc_webhook_delivered_total[24h]))", + "legendFormat": "delivered", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "textMode": "auto" + }, + "title": "Total Failed", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(trustvc_webhook_failed_total[24h]))", + "legendFormat": "failed", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 6, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ] + }, + "textMode": "auto" + }, + "title": "Queue Depth", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_webhook_queue_depth", + "legendFormat": "queue", + "refId": "A" + } + ] + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 102, + "title": "Webhook Delivery", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "failed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "title": "Delivery Rate (per minute)", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum by (event_type) (rate(trustvc_webhook_delivered_total[1m]) * 60)", + "legendFormat": "delivered • {{event_type}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum by (event_type) (rate(trustvc_webhook_failed_total[1m]) * 60)", + "legendFormat": "failed • {{event_type}}", + "refId": "B" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "p95" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "title": "Delivery Duration (p50 / p95 / p99)", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "histogram_quantile(0.50, sum by (le) (rate(trustvc_webhook_delivery_duration_ms_bucket[5m])))", + "legendFormat": "p50", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "histogram_quantile(0.95, sum by (le) (rate(trustvc_webhook_delivery_duration_ms_bucket[5m])))", + "legendFormat": "p95", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "histogram_quantile(0.99, sum by (le) (rate(trustvc_webhook_delivery_duration_ms_bucket[5m])))", + "legendFormat": "p99", + "refId": "C" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 200 + } + ] + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "title": "Queue Depth", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_webhook_queue_depth", + "legendFormat": "pending events", + "refId": "A" + } + ] + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 103, + "title": "Chain Status", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "index": 0, + "text": "Disconnected" + }, + "1": { + "color": "green", + "index": 1, + "text": "Connected" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 20, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Chain" + } + ] + }, + "title": "Chain Status Table", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true, + "job": true, + "instance": true + }, + "renameByName": { + "chain": "Chain", + "transport": "Transport", + "Value #connected": "Connected", + "Value #last_block": "Last Block", + "Value #escrows": "Active Escrows", + "Value #reconnects": "Reconnects" + } + } + } + ], + "type": "table", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_chain_connected", + "legendFormat": "{{chain}}", + "refId": "connected", + "instant": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_chain_last_seen_block", + "legendFormat": "{{chain}}", + "refId": "last_block", + "instant": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_chain_active_escrows", + "legendFormat": "{{chain}}", + "refId": "escrows", + "instant": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_chain_reconnect_attempts", + "legendFormat": "{{chain}}", + "refId": "reconnects", + "instant": true + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 29 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "title": "Active Escrows per Chain", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_chain_active_escrows", + "legendFormat": "{{chain}}", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 29 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "title": "Latest Block per Chain", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "trustvc_chain_last_seen_block", + "legendFormat": "{{chain}}", + "refId": "A" + } + ] + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 37 + }, + "id": 104, + "title": "Traces", + "type": "row" + }, + { + "datasource": { + "type": "tempo", + "uid": "${DS_TEMPO}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "title": "Escrow Replay Duration (from Traces)", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "${DS_TEMPO}" + }, + "filters": [ + { + "id": "service-name", + "operator": "=", + "scope": "resource", + "tag": "service.name", + "value": "trustvc-webhook-events", + "valueType": "string" + }, + { + "id": "span-name", + "operator": "=", + "scope": "span", + "tag": "name", + "value": "escrow.historical-replay", + "valueType": "string" + } + ], + "limit": 20, + "queryType": "traceql", + "query": "{resource.service.name=\"trustvc-webhook-events\" && name=\"escrow.historical-replay\"}", + "refId": "A", + "tableType": "spans" + } + ] + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 200, + "title": "Chain Events", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 47 + }, + "id": 201, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "On-chain Events Detected (by type, per minute)", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum by (chain, event_type) (rate(trustvc_chain_events_received_total[5m]) * 60)", + "legendFormat": "{{chain}} / {{event_type}}", + "refId": "A" + } + ] + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": [ + "trustvc", + "webhook", + "etr" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "TrustVC Webhook Events", + "uid": "trustvc-webhook-events", + "version": 1 +} diff --git a/static/docs/etr-listener/how-it-works.png b/static/docs/etr-listener/how-it-works.png new file mode 100644 index 0000000000000000000000000000000000000000..149f8264536c354454fa9e3027d4cd5499702b37 GIT binary patch literal 118357 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%0AY5-rv?BI#%iv z8B$YnQA>kCECs=0#>{+t?Ox*J`C>wmIYF465Ck%+Q*eMjvPBBgXF$!^eURpV7XGCF zlGX5&77s2dI5qnlzW56Xv<2_4e)36q9lRy0NyYot?M=5Wl{jqY*8iSe;Jc>n1E2ADYT^rutI(&ejje zy&QPWzX6xl8elP_qf=?fsCu!rbfrEvVoX z9i2fw_I^gnDVZ|`=9kZ|yf0}dp1i&qywpxs`&ths`_vQk4)y%8P6FrO?H>9rTj9mY zHz4bCQ_e4%+|vCo=+E?msfE2Iit|+U*C@IsONSds>J$9Hn4eQ}ayxOw*ge%ScJ!?d z%I@{wdd5@Qh2ja}7IwD)4|ko$Zb9{^T!0Q6B1K&I3E~|^oF@1_`FY&^i`2~GDk!9X z4{HI#@S7>oFkj32Pk15QsBLD`J^Ur;e2h{LaLK_y?G?2gmV`fpE{AQk8-^yeRL9k- zK!?@G*T?9jXI3xg6zSO1EUM_c1{P_aL1YS-RlLh1OI@C2NP%)`wEu2@tql)}4vj_n6SvlzE=@lWNa1xkow^Fz zp0_DQfu?OaEN(QQ4fl6A1{q2zsVFn?PQ9^{U_tGs0;*y@z;>gkIU)>SR{^=W0yh)1A zn?&*aXXqqw9!@l)%?b9j%x7C}wqGkg&%_^-R$z6_7}^B+d}83jf4gc0Jy$lbGFCX= zlGl)SK3DYAVM4U+(z|rM54pN=4?q*`PdpKzTXn#< zPiqR5y%J}@csXAL&x0aitseLy+Fo#^PS>R1YX0r++^e12E9_MdV4#b}?MnA=&oH0J zCx6GL&F7HT$y`7c*^v zqW(R(!V8NTA^~kkpZsf;M}7^bmET^Tv$77%zh|wh7&s?l8@ydK6tJN8>DtyLZ+XjQ z&=U-{%J6Dr!Z9kcZd-h1#eCiSvi-oxB5~Dv!C$~F3x5+iGRE>pIJGm{`T?nJt+~&O zF9}M2pWmkK8!OTNswz^-TPGwZs86Szil8ybzj*aL%Kd_F%D@oyCaufaoSH{bti1WR zk7gY@OB|@u(DG8*aKL@=`h|(Q$!DM1eJiroT?6g}e^jXyX-#zX2-0i}h9C zl1}%-zf`lF0cEgsU>A)ux$4h~V1bB*8xV`)_w8lvUq4lTq#BMSRge=i!Ne#pN@{$ekwTGEXR#jm*&+Q)Ze(3Z~i4+PoPfG zS*_adPa)k0AC-EC)0a}GdpcKNmr!|`06RKkDZE-FQ$;DC+zz8gi&9sJ;uMQ{5G*FN z5JLITI{Vg#rgGnM@5enncd%zgWo;sPE0|s5;X{eoqo*m4KV5aV$yYi7;ReY-ymjHB ztwLDatbwx|H;Z7Rvkf@%XZ4LjCsm6&%$^_bF|q8T`io`KC9H)QoE(M91cJYuLw@^$ z(2+>`X+(i+(1mT2C+otWUq&#$p6I@%wQx~k7%%+L^h9| zIlnb^C_-}cClA#KuE42|vee&(CYEaEe0K_?J^DhA`sHu0?@)3h3ch0WDt^jt#LC{) zW3ufY=_D&ycI!&7w4C2~K~yw4l>l>YR(`BUtAeu{d5Bdu11(y$eQWfX^OLXlZ4T#b zloeVjPcwdNHS|&>o}DdNJUO@4XUY@S>dUGQ`nHisdO}J9; zvG%g@0&f_RP4Sb;rI}BbjK>PKION@GRdphMT2)T}CH=5oSjee(|Owl>wmD zPV8Gtle-)lr-Oq%fETO$HSR@<{X_|eiJWMAste5}&_numd`79o$(2k2P`Fw8(KgRN zxW~*i^5(7FdKB!gG+%05up8*orXo@*of4T`gzCpjgC?hn-0pIb=aaZWDliHOTQ66C>kCim{E;)PE-9Wq5Ll<;w{iQ6 zKJu_8IKWVaciH|sXg8V{? z^)|=@bEC~(^gIrH0*r0r#m@^K$yitW+CI4wCgr+PUke*q-U{#%IW9bv?Dpe%I?cCC2fBBE2*6n#H$%Siv3>93!jzO$KpLr# z_{*VV^`n2y#Ayfm#^(`L(UP#fi&AFH+ARLE~79V1hMHjzfCLT7B4r|28xatOxm<+-MK)A zx{r=OP7G#KitpuOe8@9LN%pDaIz*w}#9%TGr&O4VwbH~U(ek3}>@5}7iyxjZp<1ucIeLWmQc zJnYtO;7)V?rYJb-3(pF;;b!oUg}Ae^RX6RdP#&`H?xJoCtpuT5Bz~;it{#&E#vvsf zGe33&>q1;e7QsgfF0kw1gpM?1OIe?nLl%-ezjvbgR)OuuGpyi%fcz3qV?$fH7iFhV zT3??~#kPkXy?^O9SLZ~Y^75sb=|1Uzi-$`lXjm@J#0(Bkd?Wke0BoxiaUUKKW=Orl zynM~sQa5b9d;0Rj3r$064phgew$v>p!$~@wQn6_R593mwy+Mn9TUnregmM$*q%-)o z{4{q)Q{h#}q_;Dj<>>lK{?&pDH%p}bhSirgA6kI~L*v)Wq)T#JKV_wzjnA;ISt~(K zE7ALd`bR>njOl-Ed7%x?(op`5-#QPAz-fk**tCr=@*5hw@*~#~3QR;XSr&;?e`B&x zAoCLlopz2n{gUDOMCX{oukp2$-Ql>iKrJ3Pg@3oWJaZw`g|v{vm(lZ<9uPZd@p}%1 zq4zsZR%`u$@-~58vR)0UP+$pJ+CWq%@DNopXocMK)*lfn?J}X!Y?!k+r}Hl7?jezA z(6{0NN4Ux*M5cKv8{O(5Om6Y@n!)?#1?D}E1E0BuR*V#oagQ+ApozQ??{i}u?to5< zWQ}w0E6VL>$*%wT@cQ$R=$*(4fjbmb#4aW2&to+-k{xt#$}o?Z3R70D`FoF6hJZp* zMNixkKQU4`m@mC`?;*9~r7_tsk62OV6nUE?^4GU>I^;&SQ|M^!Y6D|c@g7mv^~{Yb zQv3yckEEO*T@(=vDTQL6)$cie(;zG$fGqG+Na4q;%IE%&Yq4hM|ACK+)W3`p#4NNr zEILbDL1&5_r%vNT#(g3_L90&mJT{(cEmB9q8?7A0{1=ahGvUdYCd4^jGK2(6QSh@xxcc(JZ9@B$# zWODq(1)|O6JD7~ev?XY%=Dp^55RY)bXuc>Rn&XiMPG8IO2=69h;|pEjC(3S;tMn;h znF?I1nAR%y65_+#!8l%6oo8vHIeemN2HMkbFA#WcHmmjN8`3t0%X0HTPyq->N$Ev% z7C$);fA5vsOfEY_hdB*fu!wWPiZhLU#^2_k(#@++>K8kNUch3*v!C)(Za;ZpN^`qB z&37ybbQ}lO4jAe&t7iFlZajNSb%G=m0`^4MonjAGe^E)jNWv2p+_B@W!bQR{_g{8> zXZGIn1^Au{o&6pu&#D3AqO*$|BU5FmH|#zuvw6qnPu_h+`8S-A#{fB<5L^JVq)tB3 zi%X*k!brH%;E<_g^W#y$8JV_ip}S+b$+uP|PH%IJjgK|ejwc57I&4kUdj5H*fBnx2 zW9bJl`OG+?F8$0$a%LV~qeD&dabB!b$GWa&jv4e_^^Jrprhp7SsYanA-R4swNDW0g zt?9#^DS%Hn)rtDGtpt}xM%SZfpAsgbI*!B{cHauAFwPO^6qj(zq+64$mCSiKWOH0E z>u=>*o8cOspWj}`WpS!;9Dd8@Q z%o@2!5Am_g=IVH_OlUFK;V56#-|quRq{1cYdYv%@U+`rZLu@B|6+f{fKa%&1gYKsv z5BTzz@-)vRn|)P$!<)Dwmcm9s+L|lXIWbrCX}rU5!ub-i)e)Ob<*1^vySQF#Q*vEC znM-o39#ojV+4GvK?LQ8oIh=qvEPb_(usAnC^F6iluCalM=7%N&?w;C5GZv6j7{ddT zJJH*u`>@L-vrO)q2Ot&S9Jf*tfLl@+GCp4A*$RROF%5*l?`& zq?Lrn@W;ez=p~#mkJ$2Cx9?v&6SI=RuQm}(Clf#wog(=tJ*N?qWl$;zUdJtf^1$lxz!6K7_8 zxe|YV2chfN0kz775zPYs1bsjwV-(8&hq-=UBm@VF*iZl)>D0V9=I^X{IUR7+AB3ji zdE-&DHLaB6wy0-s6~(`=e@pvK66Aad*Nu%0CjE!LswL~|nUA#R1y~dgo2-QG`k58e zA<7eb)N*Fw4eR!RX9vNQ_2U70JgVuU;U)v7V?-5evSZaZC{`m6 zcSunqT<=mN;vus#o~{jzf=QiHnIyz*hN6iI-K)C3qwAMx`qj451EBFRio|`O7Zgx4 zpddquq6OQoKRpRc1p}v;IH145<41{Xa+*0j=`p z$jcg)!Ln2_5>rRO6;p{c>7xYK*SPalt$GMFj3PD@z|OK3x~#KcY8!kEccCN5%TU zFY?lE?6$@tp5i5f>=0wlw6aE<4_}Wl;rNR<6YqiSs`Lo1{^&r{@*Yh4a<55a0uYPt15?R zE{wYowBrv0s|>n@AuUi9(?8V17yqne;q$)wvlB=NPYFjcg(&?k!Jo`sJ=`y0)$$uX zuZt@~U|#Ove_jWWxI&k33o!Kvq$RFJbdf@n8C_ZOIX$N&SqC052nt?shv68dFmqQu zoDW661e(2J=Vrn*W&nNGtW%b5^C4ld4nilgEsiU=fb)2|hsKKyy*>8>FrY(An z=F=DBZ?Lp!LkT)+Rf#jbTWb9In4(cO(;k4OUIw^jP(l-?d=yzneLpQ}jnvk(0l>_f;y62%_7U2Urw+$FUlt=C)x(5dz zauJUD%woo-aV#R_EO`s93bSi6NQWdUHXkb|v7%WWP(Gd}QGKqgdw9?%K5r^B!iSLP$0$Bcy4%L!Q-E=ZF1Gouv` zQmr$flz`81UaukQL1Kw$K%AG%juEBFIVOZUg8qcMiRNX5)s`mx+|qzqqtw zEOzTruAQrj$mC9^Nl4PFgKZuRPbL^)%|k56i;dJVH*sZ%UBpE{kGb!fGjKnN-i~Y+ zYK$ZxFKW^@g1Sn;4o@UeI66wDxzGA~d5H%*lr$2ag?*FH(ocdGL&kUvOYRcfF14f7 z>(aAfbW!Fo;QxaVjAAEK-5j|xhJknF>*AUaxUKNz{EKTs%|5UIW;W`YU7>RUK43|Q zv4F7XJ2T+13lAz3hftN9yfTKHmdt_=WfKh<;?k;nw%}UvCQXvz8t$Syf|5Yh^U$%V z0o@YXg!CCcI|<@RF9k69l9;6Wk}vvVz-<&thv?XXo6>6tH7mcb*8}L;{}{oCY$Qz| zjqh}-0f6JsWU6u%O!34dfAvbpX(8hX>q7tj=0(eSK@>xS5L3+xd)X=!z1~SPfEtRB zHz$m=Uf8Fr%yc46bfK*8C>i-H7d`$^6dArQvD_#Q=4Yh&kcH3bfP|0+GuLR2tp+R0 zu7zbJ#F`uFA7I!QI|g~k!kLK?O$+DaY$;g2V2$DqYs!6#UnC^(5Lii*iMV3+3MB77 z$CbR@f2+L!5jR;FT{u750hx81s2p_Syk`$)EXebqCL8zX${drGf!!&oE_XnJBRW`< zeLV%Js^raX@-Ntdh$RdFmRvA3y!evX5%eZ9r`Vr10u9-U;}AMrR_R|EQI@cbwEjFA zuqWUE4295gxOCg<_;(YCnedY=LiHH6Yq``-G#ifP6pU66CHatL7)QcI-h@(vHqKBY zxF6w8dmeB!h2EMu(DHWJ6laYvlJiZw>pkV+*EbMG7gLhLLgSD5>d(ZOaG+OZeKtsx zSa9rMA`qVwOb;+LMi_>CfYP*|$SFZzDPKHk`!JEfMYu(H7=27h4$I`)9Sh#wxq=yo z%Mdd^%L*%r)nlmHCos^r5=NiULlM=&kf0W(1xw(yD>91v)Rf?odOR76MievcF_9ox z%VB3(`MnD$Vr@DjpR;nNELGg07+&OUqqx++=8K7pO$;#&SINz#{*4TW4&)%B=JX>a zPS`sYTHOF!@6md<1WpD&W!Jnu)z;MBKgQLbyi`PT>C?@Nnb(Nvc2EY8&dr1C0RKG% z+JM}#kLLd&W?X%YHsV4bGOkb<6=9LLlnz(Ck9~8`yxQAhijC;7DRS&}{hymlqN+K| z#EJhTJup&%cpSrMw8!jRR6mXVql-&LsJ;&?J`~#i>Kih_{xpN-_Rz_@Eqr(nxc9?> zzAG0IO&EFNhU*g^7&*ItT!xW`(Q>mW3?>b79Jt6%jIerzp&V&{b@>sd!=$Cp+i$K5 z?w!m+V*u9Y>V1XzjtDd_@d*Lk!=3G;iWnkL$!K=i9_j!sVli=yDUz#}KJ0%22TA%- zMBsTXpwyeYMmMq?YP8Q3lnkfU-qX&Y;elNPPAPU2AE2j;@`Hh$2BI0<2pok~Q|vS2 zI4r=4{=^@CJiS4oj$9WNMy4v%cj{t6er0epM;8U6QBI<`#RGmVxpQ|n<=Ly*C%I ztK0({k~TePjI+Z~NMBe%9TgeN%`sdfI?9b9aTxP!*h!~C^J1ZZ?<2BFqkWO1qspsV zXMO|eu{}Vy_daYMzzrpZ>nEu-yH6!&6`y<|H+p!AiP)OYOH^cP&lk|#Kp6^ z>B1EsTUhdxym+X=0zA5hQ3GQGrpnFxJ3rvCDr&HRg)(MyM*()^=zuv=7FfGY8DCbiSQKXfrLxH!!E+6j9l$v^>nWVBC1iH|bpEmb~xyQTeCQUiH2 zpoX&8Kb~CG54kXRlDo|!wk4|U2`r=(V@Z5t;V?0BR0^&(`&x0u3bv+*_X_PktStCS zHLg4HIAdU!9r&h{@M39kUu;1XwFUGO-?#EU@w6pr`vY?G5S37mN`)&_!LVP*8wc~@ zFjONN5r!n?Myd;i+c}A-i~-Yel5HvBPsN?@TzB}^) zeJm?rO{)YzG-(;2q;gMWV&R$vO=$AgP2+GaR0N7Ll!b=7xJ9ai^IiubJ!}^iU|WK% zW9qf1F?0cW0VWD|P&_bzz>==1t=;gWq|jSyAU|JltSRPvf#OYjeqqX-M-)5@PsPy* zcIgrYoDufaQrYQd9YtcOHAUR-Zn720#=*93xqUhTUn&KV(|944!!Z$L`#9jk-y@7d zdgRdf11*w=w4^<>qr8;re44uEMh|y>oS6Ghe4#EP!XGk5wMc?bfpWwlL3Z`1a3QiAMr1df z$QJ2Hx#^iA3QL}H5o115#o6Pj>{%k@ae-?NmDLS_^(b;SDi2u-$vsiL!)E)f*-<=X z#bUd2glwHedn6+_3Qa@I%9L4!=%E|sr&Jf9tQVl<5e&aHjKXUD9>}a#TEK?Vn13hr z#Xn_ZX$U()F-aCtedGI5l;o(rTB;-1R|#8g638F2i{--$Lu=EfSMo1&kq>DAM~SPe zMyO%J*AO?blp3#;^fkuAlM2}2Mo|wXqaGno$CEBHTs9o;$~=sgyr--c#~QKNfFmJQ zw#&7|ph-eHNG3@ms_Qr-j+tY8B$celiV0^G?kGbT5orIqa#EgUXKF39cKQIn$RvBD z0Qb3iWP~3XMYIu_2ZQ>v9#mF6>d6r*p670_*7ASG*Sp%;2V52}F6~?NuNjyPs{cA* z8&~JF&LVX=U1^K>%rK5A)BdDa$BisE!=V4rAlS3`@z=vVoODa}$Qr@}R`q4LA6f4q zed~{nQjG>Izv|R)P#zrZQ97Hs+`sXZkBz}-Sk|zqZd$=nB$nehZa(3-L&yRJE@T|= z8bi|fDP%4%$};^kfr;rP>9AE~HwMUVERfw`A=h95b4%j7T+otLTsBe1689vL79C-~ zDH(2$M|b>m9kUPlCvkxfCj)R^cUDM37CbYWXk*8k!8gNEN$Nt!k2IcVX*7PsP5p$T zXr0q%0KH=OGLI9S(ACTt^r6!)ChoUg(zgx-Jl;1{5KYZDwc+>mO)_Y$NhAN)Bpk|is}g!AxJqV|35a+kgdR9>Y*JjKp^2tAd_yaIAscQidnWD>*0ZQt-o9yV?g zhOXlFeIlwbaXp||QW$d980=cFgqGs>Txk6^R$g3cPqFa4+&y`L!_obp5CCsL(E)=s zL37RZvooTY_n!nJ0K4<&ydjns%G0<3P^N&AqS)ia9e%{h(A$0c2O-eYPXRa{>6CKr zvI)Ii{Qvn{+S90W@G%4jH`_toW8d(IVt~2cCQKc-4DA!O^5_HF7pj!3IOyLx5uQ2$ z`ONeM?=(#b}IjY z0%D9saGyffQ5U|@Vp==rgXrG>PM`(sEjobf$Mg+u|5*Z{iU|;}>>L{aUI)4H0QH+? zDXkDrA<;Rw#T{H1OaNHnrXN%3B>SF*gFhpAcK#}k1aAuvlA<|4P1q$myYBy)2e5g= zN*atxp&WH>db>Tn_W*MOtU~n&Wb$3i?hM_2E(|}Rm`VZj13Mwx8&vQsq0AIuDqzOp) zpf<^h5B&$oJ(yPo@eI6=?dBMIaRvwF;m-Fe3F3sfU1?|qVX*(M$jXk4fR=m^QLOTx zK>?cZmjNBr42FBkC2-^Zc_0o#yDP#MxB-iBN!wE%SQ0>jz_(%d^Pt33*Tsf;2-TIa z5bjgGgtW`}-$4n>Gop)=hmS3=bN?UP3Yad28a8)`@Cs1A1=i&E`9|u042NB!x1%{Y zDgby0lE16z+Q7kAA4@d5=aBmk9wC{)4g8r#k`)i^2z2|OnZ^^f98hq%fk`BH>2>;^ z&ouJ}Q2>EW|7SiR&=ck%r%H7uRdmNI^i;SM55ih zKy49ttjPkMO(1b;vf``I$QW)ItMsKL%6rvDX@gMU_1`DT7{*j5cP^l=`F~b==Z!q! zL~Of)9~43WE{mZ)Pz8bWK@{ihPkHwXD5(Kl7MH}Br0Pd+xAnh26V`2KH=hCyl>zz5 z(At9l74u*w>rHyDHmzIdd|4p#;uo6zH7rTi7A72ftd?VLd8MRshLcA3pwa<`5Qn_x z6#7gxJExl9t>BOl3VlzHC8D??M3LO*^lCsM@U;Nt#$e2{!tuHRSH~_Fbx$OdRZ*CT zyKDQv;ab~)*{xIC_AC^lRTX#l)fWP|T?W{&#YmaPU8Q>mdxvSUQRFwC=EoMC#vDh~ z^}!0idiUu$H##Y&o*^#?_@YI3JAS=767Haudg{pM_xkR(KX1M1-zbP}e^p%2JXr}l z0%@)O^1D%{%orkt9lo%Gc#O>mxZw72$3MWPu;&ZlLrTMtr|h-Pnn&l!hTDHEpeR9J z=KgR*pE;nWvM1dB#vfzxhh)wCd@fr0I@I-=@73{4R@WiaDNhG|SoIGH+SGK7({}H< zlTOJbxeyvf*}Jq8DA^yb3&N&MCn=;YBaab3*^vq0RL`geNyLM;&2W2b(N7BG+>E65 zCd?MxuV&N>5(QwE6jlBfj$dbkv;Y=qnh!O|1#O03nv)6+62(6%Ivs(f1lB5vp%T}@ zNM#RPZcPX1OmgQI%l19XfMTg&NEz-c>g*A%&uZ+jMC1)Zs5o`hO*`@t22yUuNi;Gd zx&J=ZMd{|}Y|S)GlEsUQ^tXISUtuYq#QX@ahV@A*(X8^}#o=<)`?Nj{^J+UA5<-_a3Q99}<)5{nsVp60`iFBV3HF`o6Zp8BI zy}AL>T%d49kjxE1%}ZQw8C&fVj`$E;*oMZXwxo+Z-aPUhl~Vw(u8OveS)KFh{7^dmn?Vhh6!yKcn4 zrvbkqpq+*r-aH2_Itcj<9EN^IP!L=4NX^_(rId+jWQbA|@T3}*qI{kxbpjCeg_H0_ z7ScMddZ~_-)_uhYlBMnRQE|jTgQuKv-6>5XNJXA>RU;lLO&y1;N61Xe&+#ST-;t?# zrL$$X@xw=@I<)B%xqm$(N3=*s)W-esFQ90` z*dx=QlSL&-PR%L>Ft8JI=_^LGx}Q<(yNND5Nu9vcdr*+Yf!DA(OX|TwY3&quH`OX6 z@UBJJIh_%T!0Chz+!XlORX8&$Wg5F-EL-ye7lUR|reH|;0e|PZ15B1a_7PBGkzgM{ z{@Vwz8AvGJz$0b);GB@}t!V1YEPgEb^Mi90_1g3a@!}%hBLnnJV|K+*S>XOIh)V># zt%4ldk^NPC#J)hiQ-$2lddcP_<8|AC$3|GPLG$w!JPmQ=0V%}IhkqRAfzYlaEZENG zhYy7p+`L%8cT=+UQzPe^J>qg}-}8j!IgA+yuSqH;5Tg12qCG%20h>bw>p}Y%#RBV7 zmnz*b7C&mPOKy*BNp_pV_R=jpoBLS^yMHXUS>aHQ6r~3gLEQgzLne;bR2Hvbs1dE* z4pD+LdXVWxag0q!V%{Q0*Fi=yybSv996V5s^Ay@FQ-5U-3G9a0!OVZi z2X0!)d;QHn!n4VcVRrM5QOi&g_7N12V9@P5plT3;9%bEJ8#^&5i0+f&zfGDJBq)fG zF^lR7Ye$=<;(=a+6&qop5wCx))VCV|)5a=5v&Yaus>LOq0jhmm(7h*9&6Ft=?z?&M z&3gmE@e2WFKg7b(*=>LS#(#fOZ{nD;_0OOWO#&tryR=P*J|x*s5)&jp$_hc5Eol}9 z0*}FA>K;`OF6a6p7oJ(EFR=cWLbUiju-86&`GD8K`7YvpjYf`_xat9p z++f(OqeXMbxyz-aw}3%&1(hcc8Hg%&oLNtt?kLeb02Xl$auk+HJxXk^0TpwAQKQi)v#B~YL}kf?HVlolFrT05ED zAMgqAIB_9V<6^Gwyj=VIae+NBC~Nu=@<#~6g|-UdRsfswqyaZ^>qK|oKFccJCbBY8 zb-F=8ULYJnLC&QgwPeK3UJ*-+b7ai6@Mg=lU$%IIVT@E`{f`K?!UDEpPUrI_gb6Y; zhyuVGBhSH3i*l-%psnX|tND-5C?$X~@0c^LZl`&o>u3LI2A792XxOH*kKa=7` zR@t#v`0t+AwyWTg5+kl4`;y=fjpD(duMS^Zi*6R_`^ga4$D4n(;sCYs z;mrZHakSqmPgd1L+Oh3`D6T^UI}(1(o1^|-qChdX9xs&T+WnH|6#Cj-gM=G9t_pcX zF*6_XC=h+xS+YH8xAX1`~=%@49k5QL=z#(Qzd&ZKx4;{F`{CtyM`JVa&;j$O8l2uI}m z`e&@BY>LE_QE=Gsmt*I$gEC1P1UurxIg54waUsz3plpRBS405@@qF+d4#n8Okg?0D zsmQL($~-Wia4uplm5^p+yoW)!?U{l-k)(sGCBt~rLgGd9!)42TTHsV8s!u;9>h`6T zx4AXWD0whM4=(BSDVP5lCt;} zT^MWqF{nyg?l4B&Zgagl^k9mE4o)6Y+qrXB6n<;b;9tw(X8DUcJlVe?Gpou}a4uQ^}{FV(vpB z5*HcIBKYk$>*IuB`=ym13j7S+cPowa*8_Qw?Zfs~70p;ieDUhfuY;$3+vKXAEZ91{ z?mfEEYVgkr12%9JminAz0#PeS-`*u++!mRtXmgux+{YGOtFr~xZ%C93?Wa6c@)zqk>l+d=Leh4Ouo^O z`->i63AL{E)tG1^6|o56lD-f)Bb$~oaitv_sxkhx_V6!@+4(l_O3FxTu3NGk{=zlE zV|N4w42U0-{BuO*Qp8l$y`q-pK!eO(KmHPr~rkp+O zXp%}|>Ag=P?~3b4_^y0>njgPykZBs=%Bkol^euS1Y=&mY^rh<;4XwocqWwkRG)t{s z1)9W9$XwNvxh!Vnu}!0irwd^d{DyTgAF7-KEI?8I z;p;bM6K#l|KPjPm?OV%quHu~1_uijoi_1ZUf`#?rUH1b@uXJiTtR4?#OvX6$Y%X<4k9WUy*be3O{9Eq;dUnRV{Q1FhLL+#1 zxgkEI(&78B^0t%S^I|I7%IdXtG!kEKydSKx2#D>zR6taCHt(+8&853#%;PKfYf1wr zPX2kb?GAXa-R`Ev@6J0-QM4L4r(`IaUxi$*`u5bQJXA09SJ4d3CR>qtxlQnz^HaJc z?%+KATc@eHj3+T;5kPob?(ze)|IS->X*#x6uleGudj5NrMBOes{LHlGZXgo zs`TCQJgzr);$0?nyCqIOJysC@^^I4N-}URd!zYg$vzrR=avnA(3tVdrL7c9AHR
Ehr*%^6{|Hcia(0V$FJWxNW|zq8-571GHtXJE-{s?4>4nNB`Za(rj* zXsjvUU8s7<4p*EFhJRdD9TQ((rwglrS<<;ioyz+Kk z+P_iaLur$suyS!{a1sygo-d%^)+f1{YF6i#7T3GPtxk2xUp8+7sTAsB%;8Ym`iv7l zjYT}aRBjnhb{>)Na!j~YfAbf&ym>~A-LuEt7mO{k)EQIRC>f;h>V0_mt_5*=w2>@# zd)2lrTO;^(#fy8og_(o5pJ`t8TE1t2eX;7H|D$!&eRy3=xY*%~cwN3LD8A$RR9HCb zdiC#J#dbO+bervmr)nHcSB9qRggJfeZpu*4{pju(q)4{b6S$?9ee7JxrZcYp*QtJ4 z;jdL542J0Yw6x8_V^L0#SN~a7peD)?X97oWXg6l=(-ckxl=1fdD9BG#L*3yLmH?Lq)$?KfN&F>Q!;8*9AExS7guTz=h-V|9JA3 zP<;F~-sYxz<}Ob)|K?;T8)LJu1LJ#Hj5YDM*6S1|bX7&I8G@0Qbh1s)kp1Nf+c5w>FtTVFdN2 zyE!G+&yz*OKk_@0PuXwR7w639^5j^4ybN>4nDJq#;wlM?VBJBIrZf;_$%@3TgdA>Y zqrxd6E_FI(<5-g)1#PXD)151cIpOu>_lGAA=UG)IY2<^QZh5*tSan!_g&S6xp64j8 zH}6giQdH?-wYOvvG;n=Bjg6Eqbxihn6k;YZ+D5rSmnBYF4f@g-x$#TLGnd0XkCw=UpGbP$1;_bHJ`yHrhsL|m6fT#Tao$q#^0qp zt$h>C(Z2ah9druQUevUvbx;l#xBAVvPWwsx%K7QOa$vX42R%lF(^;WBlT`WRYKGNJPM ztYY~L?MQ=Xfb?h~?V{fv*d-qHWPt;gCa5v>qXCt4a&5sa=bbL<=wwJ9i`F64OP zf_}gBI;1F46O}X7<=~qq^}D-7_+gxYYs1gtlZZ>_tIt`a!Ya)9sY3sbG-!tvg9hX# z!fCw~JS!`CPRTIgZM6P-+hV7i)grA<#YV&nWB7#_WQ>lmgQF% z#TnkredQdydc=kuG=Q8Q{&yNy6KHI*lmoBGKt4F7$kr7InLM~w^aF^JmeKUsTmyDL=&^kTEsPVLGPEBk!7 zKiloTW$ZlpW3SbpkL^nG+qO&RDJ)DzE2DL?g7r^cnktw|&>(F&{m-t#oq`O8>S%Q2 zzU2&+X!bnPPO~`QqSMZ#&v)UaQ$Es^eC(8m9F{Hel$YS?m~mYXr{0TAF-KkQbf&!W z$)~XKk7Ru1SNBI(HAF^4JYYzoSt#w$agTu!_Ty`jyo6*v7M7ky^Jeei=S z3(4Ef-}f1gX4+QgrpvfLvwE-jJ*1gB_4oIVd0Eo}E3OAO6IG;R2czJX7mhziu{^G$ zYA(Im``EKdTA0i9P{WHu)PYxw@7z4%c>h-ImhL^i647)ewR5?rL$8dl$6M0LT-LiQ z>-_kntQ(GwiB>WG$4TJ=l|T>UpdA7L!6$7(Y$#a+HGXY1x!15_pZ?+Xz(9uOdSAWS#NJxC|pE@J&i+NOFqkLxB zVY&3K=Wm?W_YG^B_8Hq3YlpY#8WSC#xv#ws<$7$XjcbB8zjkjqJ^0!!w6B?TLgM;r z#YBGAago1NMtoQA8K-E;X!aG@oE>e72ThlS??~jf*=Hhimf=+|(&c?#UaZA*sgyc> z<&*bu&ColdqW9iW$#&jqFqO+8VEsHQgmbVGoqDfk`S~Pb@`Z6U&z+U!w2hlL^S&0f z4$RJ5by-!>v38c}?^}A{3mZ_}zF_Nxplb*!Vkq@yK@s+f?^!%^!E^Y*`xFpHjs8LpPE*3()#x7$U;D%eY>js7$)5rF`RH#r%q|*?dH#CE! z%4~YMGqH`vb5Xi{9RCn~`gNnQebjAxr}}3xrb~Y`B@MiV-Lu%wy>ZorLsh+5YS>46 zj(@uMz!TbsTHxCAE`owA#M_eZ!DeNb_2`4+H9b2mq#cu%MI0%HF5sK4ENoDmpoAph zpR(;_)9fI+cS_!_OW!}_GF5%Yej9nKy~R0xp$6w&#mgf{|5LQAyOAcD!t+-KkgEn2)|~ zK(_N1JIn5egODu5%}~%%2xtMcF&Xjux+kc~9=^5+tmjGr)OK{wQgiO%f9Yyb=;R?YAFk7BgO(S$Xle%iJjDb^$Hw=R@9>+;53D zav{qRI5{B8u{wC`GFCCqAp6$5?Mk_`Mr307c=?I!Tr89MTaOILjyZX4+dyG`;al<} zL53R7)LGqLCeW2&;2o@wYx2^5q#lqEegLFo0Ln^0w7KrM;vm)R1zBb_rs+isb4uNo zLO=#=~IZ8t8keq6C z0hq4Sq_`B^^cGz4TF$AX5!&(j{#kk4|60V1->qwl)BWo1 zWn0BD!$$!lM`*bqCf_Obn1lVFE<@G~R0!-HDXN0JL!9=izl&^SavwUkF8}>gM1Iu| zn$j3^FS&HvjN}22J*SunWRj zytZ~BUex%gbAqAy>?uXM1SfgEmGD8KQ{IERrr5Nz(Ium;xA_ISCtG~bKIa=3YT(&L zTY>q#C6yS@6dhxjAcybFSLe~shZjz#?Dlx)w{{FSst-FEM#!(Wp~i(Z!uKp{yF0@) zn4MN;G3Jq@C1}mdDAzhoMW*7u%5@KTa8_G5KMRLoc#|VRP&8D+72A~q+)~_q? zK4zM*`K3hvr8$$Ka|2JFaO|Ax^01wwkh~pdwX?21ZN@DKFA1flgFInDi32C_kO6^@ zQL$isB?d9zYTG7}G1vwIij75ty1=2PRVyuuD#zn%+s$=zEB$;8gb~}f6LGE3w55Am zYPxfyuB)Jwk^`^ES}@)k&f=aH7Ltu^7@RhsjsKNx@4j8W7Z79~>fjzS?^u@EMwi$; z>rjdk&#jo+ijaAdzZ|4E#=`1nS=say!=RyX3Q@V#xcMc*2btoCs8m^%X0QK{P^8S~ z8)xVp65*nyDtZIy-kY{H^=rhX^OUq&y+(%uLRI%J>_EBql^o29!<4L3^~C~#!nN_< z*2?TcbngL|c4;3saWR`@StrDf`v?qupnULAnhg7mIyZwLY6ABZlxjj;rXxQO$bI=y zE>F-SYwv(L#k5Yi-rfwu1nRoO`_z6TyV!`OF=#)`qSA$OCAShSjQlw5Y_&F&H8}^H zjA5u@7P5az`CHpO{UL*03FhO*RcYTxB<# zXfKI`%k5pwj1JYCZG6WusGEnxBSLVBADN@pt3yd=WBbj;lfxy~KAU2a~vb;ubR zaA>e8FMh!mH#4S~Ei!)rZ(n!%DP@%XAq>jN4yIoTZFBe>51JxLv+c6Et3f#8#mC=k!Rb*7TTdgh zR_mx_-4DUctywOyH*}TR#u8cP5@DvKZ+!A5;{*WU(rf=+dM=R+%0-4L6Nh@63m+1l zTOPPgPKGM;{gblY+8xDNR5DVa(x|B-$OzBlxs`%LuEsuHmQU-bVDPFeOUyRSU-%Y^ zJggw1rrho|om6Q*JzQ(?F0di?<(QIY895AXZ5`Q5-JhUQ0teL?fM6C5riX5+hA8WB=bQWBAs707;=#pE3w zLYu|^J`>qq&`dK(I_e3()bgh!G`e)T($kts!&XxUbWn*4zBOZ|{mIX*t0!`C<4Zmq z=SSP6-Qse2!d%WVGBraZtSJ?OQYxU2yzjb%g z;*2`(UvTLV_0iQ~>UO`atrIyZGcC8<(>UB%7b(YqWn7C2V!uaU?BFoLP}r?1?iMyU z>$03y-FUr5I;me(qW9i%^NGIZc1;f4YV=*ZYUeBAp}w+Pk_ZJ^g;LB1-l?7SL5Uhz zy^c(-(W3ww4|~ZkN*FsHgyoQ$^4BBRK18}*Ow}iblng51EN^R962AFERH%z+I`kX4 z%}V-SXT{*A4i6i;HlMQa8BB`{7AO~AdfVD^czklWDJ!SqD#yq97u}ZkWIbCQJnl)h zZhc5_5}xk}tz0Ts1R=R}a~iSBzS@p^v)O*bD(>f_I2jKVQ+m?!s^*yQYI}s1Q_ZqA za$P{Y^MS%z-b-(GrrxsrHlQe?DB0|)`>?KEpL$lOs=K*~eigH35rcb@olxlq)1h<2 z4aXv1mv;Dk^dZvxS3uv%=OT{nP$tZ$V`XRXMtiBU_RG7e5MOh#3Av@PA;nt3`AAn? z&o_6M-_XFoFEq7e!8zlfwwd=zhQVD1dnXM#JVv}^LYyDbhrW5OzfsVd7jA6q>lHbP zWikrmW!!u4&{?)}JqwBmfg`qT9UB|sj802eD-F&TNqq;gbJ1h$f|dmvw*AR?QYk8A z7+seB?%`Q1cG@^B55DS?mbe-Uot#U2JpNV%d2V3!z=Bfv)}tQ1yi0fL&PH#p3Ksdc ziQC3+_IX6`>->~!w%Yw!RZ!j1(i-dbPT->x)3oq|p=!gC^K~Cm@EmuYqll%xRO9~? zIuZHi2r0XORL`CYc^h{SnW4 z`?0D5h2OTIdk1LZ%n!r7B3}WwZ&xeoPQQ~J5PNChH!9}V zC{WmTQW-uyFHxkacpordDrr_w=Esa78>7g3;F_+rWI^%1Im3fuhg;+oCH5s0_w+qf zLiF;hMH&lzJB?RJDKuW0ra#75@U@vI%D(HLh3$?5$1Bcgyzov}KG|=|DJ>?+)QHWw zYb?KeT|cg}X1Ot8^1wvNm~*rk%_C_k)330WXVgKc(%0T|nd2a|F=1c2CHwyTisVlJ z&?Fg;aNGR6{Lf-ZTOF`K&GN>ETS&NUp8;%A69>HbGbUqSX+{tXq-M*UlsBYbtE9;o zy82a?saIwYO%I>IoeHp2W7fI48bo%y+rZ6^CAO7@Wl?<%Ol+zF)OhL zHkPcvhiSp@L`;_d!59vqNvnxprcf z=a2cZ(Tu@h+rJJ6A%6sM+!-og?aR7dtpQtsLp8ET-Oll}D7Pyb+suEM7Y=uUyE{MJ zjvUzt8t@8Qb`p;}#1A6z{g3Y3%?eXHdF9*N5$3(&@s^2aIutjQp>@R*XLlScmkT!Q z(0+yrf92_=kM(1V@9gm2#@M|d!)3#P1&8@Z(1Y}?mh0F}W*V`KDWaZJP%b8oi8&T! zUN~g8hL62s!Yi)bYQvp;ho=h?znwuH?U-dfl->}0P^q0sS{|vX;w$$d-f}MFvc6q) z$mOoZ>*^t2Yd+7ceD)nx@5MO!o!A!EmD^RDtyuchL5EKlf&0)i0BeQ@&cA*zFuPMo z@<99{$Bq<58eJGV#}ghY3$uUtCHvqP#%G?op%m)eMXK| z?|@&36liz7BS5&{zxL+QBJi?jD?7dpv4G1jU{EK@I^O4<-kA2Sx&GQmqr)(~OR1nd zdk%p8{RjX%;+-}bZ26-Ca<<6>;F0M`1HLY?C~bFvBRv@;lzdKA%*MI-C7T?3NqN8I329c@G2Oh(2cd9p7re; z<_(R$YU4=pl_0c$5BLbsVtVQ%A!Xz0f(N{*6l1Vne9?IAaP}k}Llcc#HeaaA(U&Epwq)0WxLPzZL%aD>0?IV{yLKNz-Z$JR@ zEdc|7BlyI+Hki6hl!EdA{c{lx+LTao@&IFMda8FTTTn#f;ai8!JT}%yg=JL~tMkDC zx|2;-C9egH%2ov8th{^Vt_^XAGmu1n9~Ys#mK^DR|+|i$+-|x_E09 z{|R+YR_Vj`0KGXwu9RM1%T*>0>~VB-XTd{gHBG`US#NFWnZG=Rs%_|mkd@&ZWxTbW zWFAyS@jy7EZ_WM0?#zdX)xmZvDZIMs=|UFpO46kY)V2N6YqK*%{b$ikg{|HGI(ES( zQS07M) zR6%evv1d`=?a?t(>m|#ctgS!jnyy1PN}s7+5tUnCBGb%lLqR?9gPCH3EQ7Ucsnsu} z9MLggId0Ayfuopxp+J3)p`o|@+3M*w{Ow=QDaBILTHmLDg85RZfvzW*(EdZj?8v1& zh!3yL$c}?~$ixp$w04>KuGra>9NV{2(-+sZq5NT0%w#1o1nbjE?*VQ|YBAMyu#OK1 zqFBrfeBUfp za*9wrt{tggS^`lxhBbA^+Ov6V4K#T@<9()&i>Jk}I0N+{IN;E8@d-;rQZiZTpF#K{ zvN*%~6}%^%1sIU=%xyfT>hgE~)+Wa890qTI z#;3YdNC06R3RtJsx_>Vkd0LB)THGtq>80hk6hp9=fsIiB+r;*bD-q?_p{o&vO=Oq0 z!!WaRk$Pe*yoW6KONkJ^$gGeFcPDnqkxfi`UGH_M#;tK*#`Qt3Q2buej?It+VBoR8 z0s;GgD|n;lx!F#JDDbLDszb@fN*}Z)7LH%JC-tqz8pV$->ccO?+`K?CK~2C5-h-pGy9 zg*T81$mZwG69T+f&P@CNvzrXO$E_pBDO<}(n ziDiGt^;Vv+6PL+fYu_JSIq+31z+g-}>A76T&xb~_)UB!C9%UMS#XN^p%@}2sq}~wefl$CzX2h~q^{v9KY%y?S1DO!!&19&M~%bE zS&X4+@g1_$C$YKpxKwm}d{5hpZk~s4#Zk}NUr={ti#MfRc&(CfX)cjX9kJH-;>ri8 zx7A~*MVI5was@xV_M@49?VY0_9H!-#P}62yfe_-mH5FdpbRMo0@Q9wP3K%iE#iN5u z0pP^{)lTL7QZ>x-agm;wsa_5$dp=B5r7>~Y>Q<PsQ0X(omlu1t%gin0<98Z*Zb9`F$3%L$Jx~-15=xge4 zw#`p%_|yv@o|TCHs4MpQb(*)Kd|qKnlFGWGd9&uY49HLtPrEdxq_JoWJ#7B@=tAtz zM|?lJ#Z`)vB?1EJyL3sJN+#Ze*-9NGU`cgI1ENB;b+Jp~Oh6)amnxT4^LuNb^y|)i zDp`w`4UX{Wnq`?Ll>Z)(d$dUE=uYg^Upl+Z#Vu5pHHlWBMSP)Zy z>UkX%K8q~g=%VP0z4c4=ebE({I|0G-b2~1hX)f+$cSLn$O((S@FIpsbmExN`Q7~kq zJWf$f)P{C05F3xbJAa6H-sJqjA|i9dQ377lf$SdV>%AltsPB)PZO#*n9Ak{cj+ z2ZzfIWEqX6hku1|biGYHtFh5~(I`dgMtNxe1+cv#~Alp)-Fc4R)U zTDy;t2rtCt=skF!4t!nGD(3B3h{^kdBPoz-U~v7oBeR@%PQuC{H8z*Vle!3O;r9dV z&Qd7i60Al3At*PD@wv#7c>FOum!x!yj3XXhJ>^t4hQ=K6#9i52= zRHl^O+gF$jCW;+$=71OHFA&lXqwa-L9XYOA+q-{1iT>4N!JpK@74xnpuAwAxihQHg zQj@qZP}P;ugt@uVutUA3WAd!|TqKRJ$?ILCr3ydlGLoCJehBnci9;Sn)TX)%BEcKp z-*e~3%hSPh%jVVTmH7Bs7pTjkraKE9E?^r#_ zx4d~xvVqgjFZ& zD?P`5=$lQ&pOaUZ4>x)zH}S6DC}+g>liA(^BWtWX6SIV|O+P6?<+_6gmV;5aTGypm zN`!~|%&*94lS;rfQY3zm+aju)r~H66R^_onlhlk=bubJ}CX&G;5QVtoicK8EAC z$l;+K(W{awkEt};y4<$ITrgZ*0Odz2Jfw*UB>K#NKw999gH6BP0_vp&=>dPf1A7XU zU8*XjQbfzS#JRae?OorlD%1#(b*wq-w;Ro1p3v#{!TY0ml9P|$WNP(9pdB(69=Kb{ zFC5(IP;G<6&M7%L2qH!_p;M6&AvYA)qaK(oN8T7KO?O{;kUT%qKQ?6Ko!M~P=a|s1 z9|M+IY7=v<9g+8Sbl(gWM_?X}mbzqz`ab+jzi~-RgP~QZ+H-LCm+xvllF9JBy>M|o zQg$njwYuT8+e>}-W~sdq&W0V2T?NOT38#rQtr||wt**nXJv=H-{ccMh#cvW@9wK1w z;jKan=ln3C;%23yDUl0%%SbleMzQ&vs_}vbMcgNR@h@${PkYO4s99Ng!&tNJQg@iB zWqN1qmdBX0MWWk~sHi2fvtg}UEl%x5#AGMj+iY;mq`LTawOpK5{7;wVuQwPkPJaw7 z?t75d(|kc-<$+NOhHqVY=xbSL2P9@ z1}<3aG7@FXhLUsL6I!*|;O(eFhKZ|kBK`IfCUMB{enWcvR+p;m;^CEhk!k7ES$$|> z`O5>dCVmJhTsFfv4kdl*!z;6A5#rG-Xy1rc-9Z~%j_jO{WC_Z#b;V#(+)&<<6oN0k zfWR>Kzm(N9UUMRP`T(%Bu|fIa7RzvBhcEg}>^&Sm&)}`4A%EY%b@r5KdyiJ!&rXlX zt)b1Mu@?IJ`rG5v>5WFlBcjQ^EAdhraT8{Jx>gb9H69OcwP}BhQ7kTs4XHaZ+#fz@ znZV28y}8^}Khz;>ua%3g8gQFlH0#-3ZS3yc&SA?lORwbG|Ysqf48p z-NUN10QJxx6;9I+_Hd}ZMuag;Vk~PL-CjB-H0)&$3P#~;ugp6JWxOANr&2`Py)wn8 z3OlR0mMZt&Jb+hAOw6Hn#XclB4n>qyIQ2LiPWw$`92^{=OP%Tw2iAw>SD0uzw-?d* z!$K*MDal?2KjjKI6YV0hwU?7~Hd7w8(dDC!D(j!n$g^Piy!ht97GRO)IVSuF>$*rJ zD0H0E(vYz5O8$_|vk|d9bp<+i3)OycS-WATeA?*z@SfKW#&e0Y6A4G*oPJh2hgrRb z20G_fWN@UIZY@(!yEc1EC*9bvdc%matxW3g^;vUA?=p9aDlfVhhuxU=(x^ssuB->bUc=H%qu%aQ2NEH5bg&Xlg>M_q8;U}ZRKd>NS% zmUJQhUS18HW_ExF^QS5j8YzO< z=w!nNCH=hDv9Y4;r{cR>W!_yx9C-km5GGiI;ED=`lj>1gAM7jZjG(;|{YVha^oH86 zug1~H31lHL@Uk?|$&(zD5KhL?lz;SKxJRKI_ZK6f#ZhAmTQZEj?y`BSCnk#(^S$Dy z<uLk9r$siVsH4-v`F?K4PA9DIyp(n zZf)&BK2}c#J+7`xl$7`+H^B7dE2lA||n`Pca!tDVUD*mk5`ywUo`gXq`y%7!zDvD`L*X zt@p%rAZ^1XP)!bYcAM^;NTa_L7#mh? zerui7eeFJE^D*d9)zYp&&z$UY)(vEtH)3BtaL+HZ>`thuWph7Iy2My>?mi2~OqmyXza%Z(&c|J_eb+JE2HdI^u414@JLV zH>_jqb4^*FVHgNPJzKGNqPTIZJ0*n^-vRTp>YK-y_x;323#Bc>2aVu6;WG;Ay(KQA zDf3f%zvOtSgDTqHc~6uN>ROc3?NX1ehWmVxE_$&+oL;_H?X*eRazFrB(9pDX60&Cz zHp->1F_d{V>nUY(ujlu4E{2R3c9wG~Ai5PS6ux7W^60=m@*&0f?817`_QKGuA-RZa ziPhOrSgOG7W;052+VjPf0(*c>QM()Z+2Ez0=qMW5aKEi}M^1 z*s0A6k}@narsnULhOg;Vuzi&1Q+3QK8%l2xKEMIr4rwa=>@pOEPtjf)_JEkQD$dB%&pH`kb%H zK)1%8#D~|mEt@M>g5snfxp0iw4uVFT3DCSIz}UHsLFW&=4UL(lYzd$aJr!pxlKtC4q2o4AvcKegSE zhgkMbG{jm!)X~4b^7fr?o`|zVWf$)eR=E45J}m+we_9j?Zi z8`zY5P<$fFIEvzN1Rjm1L&8dKcRJUJV}sGExDM;?brb7nbjmT6oEGBjuz?=5jG4wEpFV9L!zSUd4^mk?B zALqwY_E=E;*)q>dR<}Z8qbkt#ThYjK^(@ul&69D73=mdoe?8U;PSufLXWD1Q4oeiHk`ETPC_VZ(wuU8Y$QF1&C_(q2I$E3d-L=EDnw zzREF8>s*_mbf<$U>@n-@s_#FY@R_wmQTVRLcWK8CDP2jiE?-2A_Wvi2^p zzk-$Mwr(j~C9-!Sewq1VzQfGIZ-0|YfBL{9sdwW2M8vbiV_2PuMmX0fjfUL2Dh#d; z4HwtewJCq#SDrTh^LByfnAgv$GDW*eDlhdp zSOIU5!^&P+-1xdXLZEcv#pDUJubmvyv2)ZT7{88=zTJbFt}u_l-NimHijKg!`#SHq zU8FAl3hlE&Hl7U?82E~=u^$0Fmr=LIgyA;*O|uuo0=(8EHNvn{xH~qMpASw&s&Y+l z4`F?CYvzvTZ`txI@nxbb(Vth2J%eow67qXZb;Ze; zaTr&7k;ZWqpU9AHkup#R*J)gNg=S|2omA7C*rzmSvL!lf_tpm-f(Fb>7r7Dcs|WaL zKr5Blw42|rhs7Q5`-*+x@jaMcm-rERd;;|2AI9M7nlf|gk^W^49W1YspP@xF`a~Mt zF+yGgc2=3_JP|-DI5qO_qtb)#gVVcry+3vTJsg9{%Bx}2cPzB~c{nXxexs;tBf@SX zF(vtAq~dbifNiC4BQO-(%#Aj2cxZbm3xo9N=dygO2K?%gJ_vcAzS(MLjVVn%$04>w zUSP$cyP3}}G|tGI*Keaz(fxmjWxe0P=bA6BDX+e6W9&}j(!BAYq)^RTi`4pNx9re- z&DWIqm7K|Tg-VxOEPI=4<`X>(AdG+%obAysx-=|Y!&ALQ{Y0pFq^zJ&<@nIRyBFVU zR$ub-caDTis&#}*4xiaw-aZ!9*>B*|uB#`rikxnc<9sbEB!ymk zJq*ix!XM!jtKY|BrQLyjwlRjb%irLTZ79|W3*Y{$Vq2k5cM{hii>^wz?=1*qHd`D; zeM(&lo9($GtbweFeecOvkCPcgB#T%rgGQ>;y~eN9theC&(7e&hO>F1IK9@E zZDR{+CQD?^Gbj7Qd;7+u`cRJ&9=J9ak7uAcj63c#6&JaE<<*H%Fo4&!_qMjkpsbVa zYIrBiRdVUDmNwD@Hg{Eqv6fbi9_{GeuGyAbPJd6re4e?ziOiul-dcIXp43trP}r$s z*E8Gcgd0V&<=yWeI$tvC*SR^uv>v=oTP0eFMn!GTTYu_S670wHpxO2kojgBB z=CB>}bxj-aofnO2{vtplw`pB|4O5ZeRQ+y2hpCrYJU$6&gq=p1;gS$1RRk*4eZ9Md z>mQ$rep2K7aAdExE`OvYC0ousAx3hfa48>|!Wb%8=q^yxCNmjF?X|FdOeo}TX!gnZ zY{U&OBloez*5%NArr7DZB_GbFi<8$lyNlDUegv?jV87idy&<@<6ba37A579~JUg$J zy**W6XBO3<6S^K=DrnrAl~W8I0XdT4$9{S%KgH&}!3EsgShPH=uXn-&)86%#jp%mS zMq$>)UAK6Qi@gk-rU@UbUO2c3A=rGNr&kpfm+v8eB<{%}{+=>?RGz_sZuL~$+YX&% zvS2dwzM<=J$^Hp>_wfj2^ogsiubIpI8A;m}EL8ZW=;dGBk;wZx#!S6lsy-D0(NFNL zmm_W)u*sbI$y6i4O4XXS#(OVdUr59MISV4_R23i9l%+4)VPRXY(rE&a|K zh}34Eeio}fwN6=dh4Ai5k9hfTo@VPtoS%e2`FWg9>qePIaUQF9hX`!@rxhaGV3LM{ z@&sRMB~bVbAF#7p!FiUQTyLYzxbgjizGLJ;rbI=d&4$xZ(0>@?72T;YzqwgoVb@Sv zXKD`Y*7;NYWa-LuB8+JneZC-N{w6%NH)qg$C&ffH38{^d3 z7%sVYVMTDs@)L6#vt^=bAim{xB2aORJ}EuybeoSgaXYyv+rL&8Bp$No?ZKLcIQOLr zh&uanv6HW)w(+!h{@SyYV0Mo|;oP#hjc^X_YwHMvxhQDl92eJLU>2_WrFpGUJ|>9+ zto~c%mMWK^CUP5*(?KiRYP}^UBn_#dR_!aDvn94!^f6f5+1XX6&MTV96VbcI*VQ*= zwfFpo?JK;N%^88pz*0~~ugrNPz;;hc?T}rzri?^;QTNN@gM?iWuC7GN-V{u4%1-;~ zw2UJ?q5giLB9({VLzWTSB9x`wxIaxh4l9HGF;(f_7|{sD*{NpNQs5BX~^$pcy8vRklY@|=_16k z3Lpd$Eq{Pq8EJ{!wcO_BBMf{8Uo%isy-4oMnY#}`5P>A}F5(Ghcw&{wOZkL9Rf2HK zeg%qD3F`nNowtX$lKTSjn*~#}v?TB#sdS*GzzXeIi^tQbqzPUjvoC%82S_(??HSA>5b26eD(%5l<4n&l=qA8<%z`f_SCc`rbRKqUl5J#&JH1X*Dd0APjTGbZ)# z{vV11H~=6%5a@pS)*r%sY({o}lz?4)EYs<513WPMQF!cIHOVGk8=G9`juGLw>nHiU z+J^_VkB;^NVU!Fwa0pwR+$5C-tyYk|f3d~`gr+9;{0lP+Ma~~(R00d_!dzD=sdmqC znQki{Bga?#u$SWet@lUSIuFXY$|AjZ6jC{;)&lUt+dm5kKN_%go0;au)1m()e@+EL z*jr9@#MhGCtzE-Nw|03_daIH=4qoo&pf)DCzBwx&@PqyldEbdV4MF-`JYn)wf6i*{ zO#$A*MU;dsXsF0-za>Z@>rM)#H@QKCbpFs?pcb^EhYK$_kd>QEKM{f>J$O-8hX?QO ztKFq1{>nYZ2i~y$ zhT*jq$n|6tF+*(fFxetAB@4Pkacy1KGcY_@Nd@#YUu99_(mr zydP&K2{;vca4qpqDtGW@Ny(&R)W=J~tDVVQk}xAc$;}VgDA-*hwUFXZL9&hnlX>m> z3#j7>k((8S5;#UM)#Kl^r0N2LIj8WVV_yz`4i2uJ!81u+$46E{h&Xld@79;UKr9e8UIV$ ztsen{JH%Q0W45O9{~_}FO=h2?zo))b>A(5=TMw`%1MV1Gz)OZQ1?<0T^(Inyi&uBp z_>WV24B+*1cwx+`IL@;iu( zz@EaMg78Q*(bsn&s()O3DF$y(C*nHj!S7Va|CgqMhJ_s9l}5bHh0OkiK3V0~@IHw< z^a)|e)Vl}Q68=TI4EEgXe9|iar<#HaJm~vaK0@O^Oz!(QQWWTa{x@8pZ|*6S zJM0B$p1adYxux{mTM=dg7TBb zzk?VNYimCWI(lYpz@+J6Zt@HLA4e|x`@mHp2EL_0EUmw zS04V?1gZwLr->A&I^uH{%k!?pCp9jm_y_+sRsdsaJ~+>6S2dSbx^?B>v>(AI6rh>& z3ZQ|J&GpQ0O{(zUBm)3+Q2^*_VJN=)*IJdLdcP%uY#GKTNAMPZ)j(~30HXM6j${zK zbCmQz{Cckp-l|4Cp-oFm?#(1n#BvpYcqZ`Hxp^H(=f9}O>m3LlAYT9$pOnu?Od5zY zj~-mupU@SYU;1K7(1X`EAZ$bp-iH%{I>;huI^G(ml9Wg{T+v5JtLKvm%8!#3@`R&KVR4s>xOZ)fefTtNX zASKB&)APmE1!DO@D*LRTs zKcK7vS4aO7;H@*?>G8x^-vA?Mv$=_q*vk>*^8S-Z)$0JHvm$>pSYM}r{kNuqp1eR! zaK(AI;(vAW4IS(u=YC;jz}|vs0{+G`1_6yk&-d{rd;swTJP6*{T>OWW|GZQXAKp+xgKf`sDd=W`U@TsB{6%fNU|Bs|hi)6Ck`Exsx^+e(O zpf7lB5HZR*&t>_qQ8MfRL=_y#jkAf#WMwALPs|p9wtgwYA|HSTMUrw*c{4mp-sckn zj7jJ?z8B0|n*#dZ@khiHC=yL_+ROdh2_I^|AGIbb=W(FS%OX!I+4oidI4L8 zFmJHDIr^JJ0*R2iV@C*p1}fu?DRFc^^-8s(Q2s-yGq?eWGAzdp&vY43zWrbEy`2w` zXwQ9-Xh$bBi*HUSVKx0z=^^3*QGrSK;mcnq)yU-MiAefy_xnv$hV zn54Grh&AznOh{ zo@EWAL}agv5QEG9)ovHM(w{Ex?HKEyj~08>vHFA5-w8G%{H=bsdnN}=!4HMyi>`y_ zPZsp)V3xfsX;V4u!{~qh=m{m8Jx_;%J z`95R#4|g8QM)apnh-f%U$|y@Zk`i cjRSwV4E@+?O?kBrj{J4&hWho~t0s^C5A@y9A^-pY literal 0 HcmV?d00001 From 252f43fe138f7618ca5e9452c473281d47bbfbd6 Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Fri, 29 May 2026 13:58:38 +0530 Subject: [PATCH 02/11] Update docs/how-tos/chain-events/opentelemetry.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/how-tos/chain-events/opentelemetry.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/how-tos/chain-events/opentelemetry.md b/docs/how-tos/chain-events/opentelemetry.md index 079e768..64640b1 100644 --- a/docs/how-tos/chain-events/opentelemetry.md +++ b/docs/how-tos/chain-events/opentelemetry.md @@ -22,13 +22,6 @@ OTEL_ENABLED=true OTEL_SERVICE_NAME=trustvc-chain-events OTEL_EXPORTER_OTLP_ENDPOINT=https://your-otlp-endpoint OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogram -``` - -| Variable | Default | Description | -|---|---|---| -| `OTEL_ENABLED` | `false` | Set `true` to enable telemetry export | -| `OTEL_SERVICE_NAME` | `trustvc-webhook-events` | Service name shown in your observability backend | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318` | OTLP HTTP endpoint of your collector | | `OTEL_EXPORTER_OTLP_HEADERS` | — | Auth headers required by your backend (see examples below) | | `OTEL_INSTANCE_ID` | `-` | Custom instance identifier shown in metrics labels | | `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | — | Set to `explicit_bucket_histogram` for Prometheus-compatible histograms | From c364aae5df2c41830b2698306a4ccfd8370c56cb Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Fri, 29 May 2026 14:08:44 +0530 Subject: [PATCH 03/11] fix: update code block formatting in chain events documentation Changed code block syntax from plain to text for better clarity in the quick start and webhook payload documentation sections. --- docs/how-tos/chain-events/quick-start.md | 2 +- docs/how-tos/chain-events/webhook-payload.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/how-tos/chain-events/quick-start.md b/docs/how-tos/chain-events/quick-start.md index 3a44b3c..39da51c 100644 --- a/docs/how-tos/chain-events/quick-start.md +++ b/docs/how-tos/chain-events/quick-start.md @@ -148,7 +148,7 @@ docker logs trustvc-events You should see output similar to: -``` +```text INFO [startup]: trustvc-webhook-events starting version: "0.1.0" INFO [startup]: Database connected INFO [startup]: Chain worker ready chain: "ethereum-sepolia" escrows: 22 diff --git a/docs/how-tos/chain-events/webhook-payload.md b/docs/how-tos/chain-events/webhook-payload.md index da537f5..081dc39 100644 --- a/docs/how-tos/chain-events/webhook-payload.md +++ b/docs/how-tos/chain-events/webhook-payload.md @@ -13,7 +13,7 @@ Every event is delivered as an HTTP `POST` to your configured `webhook.url`. ## Request Format -``` +```text POST /your-endpoint Content-Type: application/json X-TrustVC-Signature: ed25519= @@ -104,7 +104,7 @@ Use `data.transactionHash + data.logIndex` as your idempotency key — this comb Every request includes an `X-TrustVC-Signature` header: -``` +```text X-TrustVC-Signature: ed25519= ``` From a7041a30da257e22eae60eb78f71eba143f53300 Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Fri, 29 May 2026 14:09:26 +0530 Subject: [PATCH 04/11] Update docs/how-tos/chain-events/rate-limits.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/how-tos/chain-events/rate-limits.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/how-tos/chain-events/rate-limits.md b/docs/how-tos/chain-events/rate-limits.md index d572dd9..285c74d 100644 --- a/docs/how-tos/chain-events/rate-limits.md +++ b/docs/how-tos/chain-events/rate-limits.md @@ -101,10 +101,6 @@ Check logs for these patterns: Enable debug logging to see each batch request: -```bash -# In config.json -{ "logLevel": "debug" } -``` --- From 22757ceaa30657bccf1174f809db6c189289c28b Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Fri, 29 May 2026 14:13:14 +0530 Subject: [PATCH 05/11] fix: update code block formatting in rate limits documentation Changed code block syntax from plain to text for improved clarity in the rate limits section. --- docs/how-tos/chain-events/rate-limits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-tos/chain-events/rate-limits.md b/docs/how-tos/chain-events/rate-limits.md index 285c74d..93d39c0 100644 --- a/docs/how-tos/chain-events/rate-limits.md +++ b/docs/how-tos/chain-events/rate-limits.md @@ -28,7 +28,7 @@ The following config fields let you tune replay speed to stay within your RPC's On startup the container scans from `replayFromBlock` to the current block in chunks of `replayBatchSize`: -``` +```text Block 6,000,000 ──► [batch 1: 6,000,000 – 6,002,000] ──wait replayDelayMs──► [batch 2: 6,002,000 – 6,004,000] ──wait replayDelayMs──► ... From e7195a47d6b08b4c47a7a82852987dae60d7a05d Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Mon, 1 Jun 2026 12:52:47 +0530 Subject: [PATCH 06/11] docs: add optional variables section to OpenTelemetry configuration guide Introduced a new section detailing optional environment variables for OpenTelemetry configuration, enhancing clarity and usability for users. --- docs/how-tos/chain-events/opentelemetry.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/how-tos/chain-events/opentelemetry.md b/docs/how-tos/chain-events/opentelemetry.md index 64640b1..c19a318 100644 --- a/docs/how-tos/chain-events/opentelemetry.md +++ b/docs/how-tos/chain-events/opentelemetry.md @@ -22,6 +22,12 @@ OTEL_ENABLED=true OTEL_SERVICE_NAME=trustvc-chain-events OTEL_EXPORTER_OTLP_ENDPOINT=https://your-otlp-endpoint OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogram +``` + +### Optional Variables + +| Variable | Default | Description | +|---|---|---| | `OTEL_EXPORTER_OTLP_HEADERS` | — | Auth headers required by your backend (see examples below) | | `OTEL_INSTANCE_ID` | `-` | Custom instance identifier shown in metrics labels | | `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | — | Set to `explicit_bucket_histogram` for Prometheus-compatible histograms | From a02709d29d79de04f8b7189fb7b5c30e4486463f Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Mon, 1 Jun 2026 13:26:45 +0530 Subject: [PATCH 07/11] fix: remove outdated environment variable from OpenTelemetry documentation Removed the `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` entry to streamline the OpenTelemetry configuration guide and eliminate potential confusion for users. --- docs/how-tos/chain-events/opentelemetry.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/how-tos/chain-events/opentelemetry.md b/docs/how-tos/chain-events/opentelemetry.md index c19a318..6034ba8 100644 --- a/docs/how-tos/chain-events/opentelemetry.md +++ b/docs/how-tos/chain-events/opentelemetry.md @@ -30,7 +30,6 @@ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogr |---|---|---| | `OTEL_EXPORTER_OTLP_HEADERS` | — | Auth headers required by your backend (see examples below) | | `OTEL_INSTANCE_ID` | `-` | Custom instance identifier shown in metrics labels | -| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | — | Set to `explicit_bucket_histogram` for Prometheus-compatible histograms | --- From 9d30fa0ba742321ff85b6b477e44859ad0ad0242 Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Mon, 1 Jun 2026 13:28:35 +0530 Subject: [PATCH 08/11] docs: clarify optional environment variable for OpenTelemetry configuration Reintroduced the `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` variable in the optional variables section to provide clear guidance on its usage for Prometheus-compatible histograms. --- docs/how-tos/chain-events/opentelemetry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-tos/chain-events/opentelemetry.md b/docs/how-tos/chain-events/opentelemetry.md index 6034ba8..8b8165d 100644 --- a/docs/how-tos/chain-events/opentelemetry.md +++ b/docs/how-tos/chain-events/opentelemetry.md @@ -21,7 +21,6 @@ Add these to your `.env`: OTEL_ENABLED=true OTEL_SERVICE_NAME=trustvc-chain-events OTEL_EXPORTER_OTLP_ENDPOINT=https://your-otlp-endpoint -OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogram ``` ### Optional Variables @@ -30,6 +29,7 @@ OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=explicit_bucket_histogr |---|---|---| | `OTEL_EXPORTER_OTLP_HEADERS` | — | Auth headers required by your backend (see examples below) | | `OTEL_INSTANCE_ID` | `-` | Custom instance identifier shown in metrics labels | +| `OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION` | — | Set to `explicit_bucket_histogram` for Prometheus-compatible histograms | --- From ef59023ef1547b55f1cd731f5e258f6f0bec9afb Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Tue, 2 Jun 2026 11:07:46 +0530 Subject: [PATCH 09/11] docs: add warning about database requirement for horizontal scaling Included a warning in the scaling documentation to emphasize the necessity of a database when running multiple replicas to prevent duplicate events. Explained how the database facilitates lease management and progress persistence for replicas. --- docs/how-tos/chain-events/scaling.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/how-tos/chain-events/scaling.md b/docs/how-tos/chain-events/scaling.md index 2e77d05..b7926f3 100644 --- a/docs/how-tos/chain-events/scaling.md +++ b/docs/how-tos/chain-events/scaling.md @@ -25,6 +25,19 @@ By default, each chain runs in its own child process (`workerProcesses: true` in ## Horizontal Scaling +:::warning A database is required for scaling and HA + +Running more than one replica **without a database will cause duplicate events** — every replica polls the chain independently, so your webhook endpoint receives multiple copies of the same event. + +The database solves this in two ways: + +- **One replica owns each chain at a time** — replicas compete for a lease stored in the database. Only the winner polls; the others wait on standby. +- **Progress survives restarts** — the last processed block is persisted, so a restarting replica resumes exactly where it left off instead of replaying from scratch. + +**Set `DB_HOST` before running more than one container.** + +::: + To run multiple instances of the container in parallel — for redundancy or higher throughput — you must connect a database. The container uses a **distributed lease** mechanism (one active worker per chain at a time) to prevent duplicate event delivery when multiple instances are running. ```bash From b65ebf6fd7b0f290ec6205c92aba074fc62e93c2 Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Tue, 2 Jun 2026 14:47:38 +0530 Subject: [PATCH 10/11] docs: update container image references in chain events documentation Replaced instances of the GitHub Container Registry image with the Docker Hub image for consistency across the quick start, registry API, and scaling documentation. This change simplifies the instructions for users by standardizing the image source. --- docs/how-tos/chain-events/quick-start.md | 12 ++++++++++-- docs/how-tos/chain-events/registry-api.md | 2 +- docs/how-tos/chain-events/scaling.md | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/how-tos/chain-events/quick-start.md b/docs/how-tos/chain-events/quick-start.md index 39da51c..8947620 100644 --- a/docs/how-tos/chain-events/quick-start.md +++ b/docs/how-tos/chain-events/quick-start.md @@ -13,6 +13,14 @@ Get `trustvc-chain-events` running locally in under 5 minutes. ## Step 1 — Pull the Image +**Docker Hub (recommended)** + +```bash +docker pull trustvc/trustvc-chain-events:latest +``` + +**GitHub Container Registry (alternative)** + ```bash docker pull ghcr.io/trustvc/trustvc-chain-events:latest ``` @@ -106,7 +114,7 @@ docker run -d \ --env-file .env \ -p 8080:8080 \ --name trustvc-events \ - ghcr.io/trustvc/trustvc-chain-events:latest + trustvc/trustvc-chain-events:latest ``` **With Docker Compose** @@ -114,7 +122,7 @@ docker run -d \ ```yaml title="docker-compose.yml" services: trustvc-events: - image: ghcr.io/trustvc/trustvc-chain-events:latest + image: trustvc/trustvc-chain-events:latest ports: - "8080:8080" volumes: diff --git a/docs/how-tos/chain-events/registry-api.md b/docs/how-tos/chain-events/registry-api.md index f282933..b93b0dd 100644 --- a/docs/how-tos/chain-events/registry-api.md +++ b/docs/how-tos/chain-events/registry-api.md @@ -130,7 +130,7 @@ docker run -d \ -v $(pwd)/config.json:/app/config.json:ro \ --env-file .env \ -p 8080:8080 \ - ghcr.io/trustvc/trustvc-chain-events:latest + trustvc/trustvc-chain-events:latest ``` **Step 2 — Deploy your Token Registry** diff --git a/docs/how-tos/chain-events/scaling.md b/docs/how-tos/chain-events/scaling.md index b7926f3..7c93135 100644 --- a/docs/how-tos/chain-events/scaling.md +++ b/docs/how-tos/chain-events/scaling.md @@ -55,7 +55,7 @@ If the active instance crashes or loses its lease, another instance picks it up ```yaml services: trustvc-events: - image: ghcr.io/trustvc/trustvc-chain-events:latest + image: trustvc/trustvc-chain-events:latest deploy: replicas: 2 ports: @@ -107,7 +107,7 @@ For production deployments on AWS, run the container as a Fargate service. The t "containerDefinitions": [ { "name": "trustvc-chain-events", - "image": "ghcr.io/trustvc/trustvc-chain-events:latest", + "image": "trustvc/trustvc-chain-events:latest", "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ], From cac34a91257b33077309a9bfc3e9d8f634be24b9 Mon Sep 17 00:00:00 2001 From: manishdex25 Date: Wed, 3 Jun 2026 13:33:36 +0530 Subject: [PATCH 11/11] docs: update warning about database requirement in registry API documentation Changed the note regarding database requirements to a warning to better emphasize the necessity of configuring `DB_HOST` for registry management endpoints. This update enhances clarity for users regarding the implications of not having a connected database. --- docs/how-tos/chain-events/registry-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-tos/chain-events/registry-api.md b/docs/how-tos/chain-events/registry-api.md index b93b0dd..7efd6ff 100644 --- a/docs/how-tos/chain-events/registry-api.md +++ b/docs/how-tos/chain-events/registry-api.md @@ -9,7 +9,7 @@ sidebar_position: 5 The container exposes a REST API on port `8080` that lets you add and remove Token Registry contracts at runtime — without restarting the container or editing `config.json`. -:::note Database required +:::warning Database required All registry management endpoints require `DB_HOST` to be configured. They return `503 Service Unavailable` if no database is connected. Registries added via the API are persisted to the database and survive container restarts. :::