Skip to content

Commit 0be98c8

Browse files
authored
Merge pull request #294 from Jenola344/main
comprehensive documentation and operational runbooks for infrastructure management.
2 parents 63aa101 + 69a713c commit 0be98c8

5 files changed

Lines changed: 1782 additions & 0 deletions

File tree

Architecture.md

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
# 🏗️ Architecture & System Design
2+
3+
> **StellarEscrow** — Decentralized escrow on Stellar/Soroban Network
4+
> Version: 1.0 | Audience: Engineers, DevOps, Technical Leadership
5+
6+
---
7+
8+
## Table of Contents
9+
10+
- [System Overview](#1-system-overview)
11+
- [Blockchain Layer — Soroban Contract](#2-blockchain-layer--soroban-contract)
12+
- [Off-Chain Infrastructure](#3-off-chain-infrastructure)
13+
- [Networking & Security](#4-networking--security)
14+
- [Observability Stack](#5-observability-stack)
15+
- [Deployment Environments](#6-deployment-environments)
16+
- [Data Flow: Complete Trade Lifecycle](#7-data-flow-complete-trade-lifecycle)
17+
18+
---
19+
20+
## 1. System Overview
21+
22+
StellarEscrow is a decentralized escrow platform built on the Stellar blockchain using Soroban smart contracts. It enables trustless peer-to-peer trades with USDC settlement, dispute arbitration, and tiered fee structures — all enforced by immutable on-chain logic.
23+
24+
> **Key Design Principle:** All financial logic lives in the Soroban smart contract (immutable, on-chain). The off-chain stack (indexer, API, frontend) is stateless and reconstructible from blockchain event history at any time.
25+
26+
### 1.1 High-Level Architecture
27+
28+
The platform is organized into four logical tiers:
29+
30+
| Tier | Components | Role |
31+
|------|-----------|------|
32+
| **Blockchain** | Soroban WASM contract | Escrow state machine, USDC transfers, arbitration |
33+
| **Indexer** | Rust + Tokio service | Event polling, DB writes, WebSocket fanout |
34+
| **API** | Node.js gateway | REST endpoints, auth, analytics |
35+
| **Presentation** | SvelteKit + Nginx | Web UI, TLS termination, CDN headers |
36+
37+
### 1.2 Component Inventory
38+
39+
| Component | Technology | Language | Role | Port |
40+
|-----------|-----------|----------|------|------|
41+
| Smart Contract | Soroban / Stellar | Rust | Escrow state machine, USDC settlement | On-chain |
42+
| Indexer | Rust + Tokio | Rust | Event polling, DB writes, WebSocket fanout | 3000 (internal) |
43+
| API Gateway | Node.js | TypeScript | REST endpoints, auth, analytics | 4000 (internal) |
44+
| Client (SvelteKit) | SvelteKit + Vite | TypeScript | Web UI — trade lifecycle, funding | 3001 |
45+
| Nginx | nginx:alpine || TLS termination, reverse proxy, CDN headers | 80, 443 |
46+
| PostgreSQL 15 | postgres:15-alpine | SQL | Indexed event & trade storage | 5432 (internal) |
47+
| Redis 7 | redis:7-alpine || API response caching, rate-limit counters | 6379 (internal) |
48+
| Prometheus | prom/prometheus || Metrics scraping & time-series storage | 9090 (internal) |
49+
| Grafana | grafana/grafana || Dashboards & alert visualization | 3002 (internal) |
50+
| Loki + Promtail | grafana/loki || Centralized log aggregation | 3100 (internal) |
51+
| Alertmanager | prom/alertmanager || Alert routing (Slack, PagerDuty) | 9093 (internal) |
52+
| Certbot | certbot/certbot || Automated Let's Encrypt TLS renewal ||
53+
| Backup Service | postgres:15-alpine | Bash | Daily pg_dump to S3 with retention ||
54+
55+
---
56+
57+
## 2. Blockchain Layer — Soroban Contract
58+
59+
### 2.1 Contract Architecture
60+
61+
The escrow contract is the authoritative source of truth for all trade state. Deployed as a WASM binary to the Stellar Soroban environment, it is **immutable after deployment** — upgrades require migrating to a new contract address.
62+
63+
#### Core Contract Functions
64+
65+
| Function | Actor | Description |
66+
|----------|-------|-------------|
67+
| `create_trade(seller, amount, asset)` | Seller | Initializes escrow, sets terms, emits `created` event |
68+
| `get_funding_preview(trade_id, buyer)` | Buyer | Returns `FundingPreview`: balance, allowance, fee breakdown |
69+
| `execute_fund(trade_id, buyer, preview)` | Buyer | Transfers USDC into escrow, emits `funded` event |
70+
| `confirm_receipt(trade_id)` | Buyer | Releases escrowed funds to seller, emits `completed` event |
71+
| `raise_dispute(trade_id, reason)` | Buyer/Seller | Locks funds, assigns arbitrator, emits `disputed` event |
72+
| `resolve_dispute(trade_id, winner)` | Arbitrator | Releases funds to winner, emits `resolved` event |
73+
| `analytics_query(params)` | Read-only | Returns on-chain metrics: volume, success rate, unique addresses |
74+
75+
#### Trade State Machine
76+
77+
Every trade progresses through the following states. Transitions are enforced by the contract and cannot be bypassed:
78+
79+
```
80+
CREATED → FUNDED → COMPLETED
81+
82+
DISPUTED → RESOLVED
83+
```
84+
85+
| State | Description |
86+
|-------|-------------|
87+
| `CREATED` | Seller has initialized the trade; awaiting buyer funding |
88+
| `FUNDED` | Buyer has deposited USDC; funds held in escrow |
89+
| `COMPLETED` | Buyer confirmed receipt; funds released to seller |
90+
| `DISPUTED` | One party raised a dispute; funds locked pending arbitration |
91+
| `RESOLVED` | Arbitrator decision executed; trade closed |
92+
93+
### 2.2 Fee Structure
94+
95+
StellarEscrow uses tiered fees based on cumulative trade volume per address:
96+
97+
| Tier | Cumulative Volume | Fee Rate |
98+
|------|------------------|----------|
99+
| Standard | < $10,000 | 1.50% |
100+
| Silver | $10,000 – $100,000 | 1.00% |
101+
| Gold | $100,000 – $1,000,000 | 0.75% |
102+
| Platinum | > $1,000,000 | 0.50% |
103+
104+
---
105+
106+
## 3. Off-Chain Infrastructure
107+
108+
### 3.1 Indexer Service
109+
110+
The Rust-based indexer bridges the Stellar blockchain to PostgreSQL. It polls the Stellar Horizon API at configurable intervals (default **5 seconds**, matching ledger close time), processes contract events, and writes normalized records to the database. It also serves as a WebSocket server for real-time frontend subscriptions.
111+
112+
#### Indexer Configuration (`config.toml`)
113+
114+
```toml
115+
[database]
116+
max_connections = 10 # Pool ceiling; increase for high-concurrency
117+
min_connections = 2 # Warm connections always available
118+
119+
[stellar]
120+
poll_interval_seconds = 5 # Matches Stellar ledger close time
121+
network = "mainnet" # testnet | mainnet
122+
123+
[cache]
124+
redis_url = "redis://:${REDIS_PASSWORD}@redis:6379"
125+
```
126+
127+
#### Redis Caching Strategy
128+
129+
| Endpoint Pattern | TTL | Rationale |
130+
|-----------------|-----|-----------|
131+
| `GET /events*` | 10s | High-frequency reads; matches 5s Stellar ledger interval |
132+
| `GET /search*` | 30s | Search results change infrequently |
133+
| `GET /stats` | 60s | Aggregate queries are expensive; staleness acceptable |
134+
| `POST /events/replay` | No cache | Mutating operation — always hits database |
135+
136+
> **Fallback:** If Redis is unavailable, all requests fall through to PostgreSQL with no errors. Cache misses degrade performance gracefully rather than causing failures.
137+
138+
### 3.2 API Gateway
139+
140+
The Node.js API gateway exposes REST and WebSocket endpoints to the SvelteKit client and authenticated third-party consumers. It validates API keys (`API_KEYS` for standard access, `ADMIN_KEYS` for administrative endpoints), proxies analytics queries to the indexer, and serves trade history with CSV export.
141+
142+
#### Key Environment Variables
143+
144+
| Variable | Required | Description |
145+
|----------|----------|-------------|
146+
| `DATABASE_URL` | Yes | PostgreSQL connection string (DSN format) |
147+
| `INDEXER_URL` | Yes | Internal URL of the indexer (`http://indexer:3000`) |
148+
| `API_KEYS` | Yes | Comma-separated list of valid API keys |
149+
| `ADMIN_KEYS` | Yes | Admin-level API keys for privileged endpoints |
150+
| `NODE_ENV` | Yes | Must be `"production"` in production deployments |
151+
| `REDIS_URL` | No | Optional; enables response caching if set |
152+
153+
### 3.3 Data Layer
154+
155+
#### PostgreSQL Schema (Key Tables)
156+
157+
| Table | Description | Indexes |
158+
|-------|-------------|---------|
159+
| `trades` | One row per escrow trade; mirrors contract state | trade_id (PK), status, buyer, seller, created_at |
160+
| `events` | Raw contract events from Stellar ledger | event_type, trade_id, ledger_sequence, timestamp |
161+
| `analytics` | Pre-aggregated metrics snapshots | time_window, metric_name |
162+
| `arbitrators` | Registered dispute arbitrators | address (PK), active flag |
163+
164+
#### Connection Pool Sizing
165+
166+
> **Rule of thumb:** `max_connections = (2 × CPU cores) + effective_spindle_count`
167+
> For a 4-core host: set `max_connections = 10–15`
168+
169+
---
170+
171+
## 4. Networking & Security
172+
173+
### 4.1 Network Topology
174+
175+
Internal services (PostgreSQL, Redis, Prometheus, Grafana, Loki) are **bound to `127.0.0.1` only** — not reachable from outside the Docker network. Only Nginx (ports 80/443) and the client (port 3001) are publicly exposed.
176+
177+
```
178+
Internet
179+
180+
181+
[Nginx :80/:443] ← TLS termination
182+
183+
├──► [Client :3001] (SvelteKit UI)
184+
├──► [Indexer :3000] (internal only)
185+
└──► [API :4000] (internal only)
186+
187+
├──► [PostgreSQL :5432] (internal only)
188+
└──► [Redis :6379] (internal only)
189+
```
190+
191+
> ⚠️ **Security Rule:** Database and cache ports (5432, 6379) are **NEVER** published to external interfaces. The `docker-compose.yml` comments these out explicitly.
192+
193+
### 4.2 TLS / SSL
194+
195+
TLS is managed by Certbot (Let's Encrypt) with automatic renewal every 12 hours. Certificates are stored in the `letsencrypt` Docker volume and mounted read-only into Nginx.
196+
197+
```bash
198+
# Certificate renewal (runs every 12 hours via docker entrypoint)
199+
certbot renew --webroot -w /var/www/certbot --quiet
200+
201+
# Cron-based renewal fallback
202+
0 0,12 * * * /opt/stellarescrow/scripts/renew-certs.sh
203+
```
204+
205+
### 4.3 Container Security
206+
207+
| Security Control | Applied To | Effect |
208+
|-----------------|-----------|--------|
209+
| `no-new-privileges:true` | indexer, api | Prevents privilege escalation via setuid binaries |
210+
| `read_only: true` | indexer, api | Container filesystem is read-only; `/tmp` is tmpfs |
211+
| `requirepass` (Redis) | redis | `REDIS_PASSWORD` required for all connections |
212+
| Internal-only bind | postgres, redis | Not reachable from host network |
213+
| `POSTGRES_PASSWORD` env | postgres | Mandatory via `?`-syntax — startup fails without it |
214+
215+
---
216+
217+
## 5. Observability Stack
218+
219+
### 5.1 Metrics Pipeline
220+
221+
```
222+
Indexer ──► Prometheus ──► Grafana (dashboards)
223+
└────► Alertmanager ──► Slack / PagerDuty
224+
```
225+
226+
#### Key Performance Indicators
227+
228+
| Metric | Target | Alert Threshold |
229+
|--------|--------|----------------|
230+
| TTFB | < 200ms (good), < 800ms (acceptable) | > 1000ms for 5+ minutes |
231+
| Largest Contentful Paint | < 2.5s | > 4.0s |
232+
| DB query mean (hot paths) | < 10ms | > 50ms for 3+ minutes |
233+
| Redis cache hit rate | > 80% | < 60% for 10+ minutes |
234+
| Indexer memory usage | < 256MB | > 400MB |
235+
| API error rate (5xx) | < 0.1% | > 1% for 2+ minutes |
236+
237+
### 5.2 Log Aggregation
238+
239+
Promtail ships container and host logs to Loki. Logs are queryable via Grafana's Explore interface using LogQL. All services use structured JSON logging with `RUST_LOG=info` for Rust services.
240+
241+
### 5.3 Web Vitals
242+
243+
The frontend collects Core Web Vitals (TTFB, LCP, FID, CLS) and CDN performance metrics via `observeWebVitals()` and `observeCdnPerformance()`. These beacon to `POST /api/metrics` and appear in Grafana alongside infrastructure metrics.
244+
245+
---
246+
247+
## 6. Deployment Environments
248+
249+
| Environment | Network | Contract | Purpose |
250+
|-------------|---------|----------|---------|
251+
| Development | Stellar Testnet | Dev contract (redeployable) | Local dev with `pnpm dev` / `cargo test` |
252+
| Staging | Stellar Testnet | Staging contract | Pre-production validation; mirrors production config |
253+
| Production | Stellar Mainnet | Immutable mainnet contract | Live traffic; RTO ≤ 2h, RPO ≤ 24h |
254+
255+
Each environment has its own `.env` file (`.env.development`, `.env.staging`, `.env.production`) with environment-specific secrets and RPC endpoints.
256+
257+
---
258+
259+
## 7. Data Flow: Complete Trade Lifecycle
260+
261+
```
262+
1. Seller calls create_trade() on Soroban contract
263+
└─► Contract stores trade on-chain, emits `created` event
264+
265+
2. Indexer detects `created` event (within 5s poll)
266+
└─► Writes trade record to PostgreSQL `trades` table
267+
268+
3. Buyer's browser calls GET /trades/:id via API
269+
└─► Redis cache miss → API queries PostgreSQL → response cached (10s)
270+
271+
4. Buyer calls get_funding_preview() on contract (read-only)
272+
└─► Receives FundingPreview with balance, allowance, fee breakdown
273+
274+
5. Buyer approves USDC allowance → calls execute_fund()
275+
└─► Contract transfers USDC into escrow, emits `funded` event
276+
277+
6. Indexer picks up `funded` event
278+
└─► Updates DB status to FUNDED
279+
280+
7. Buyer confirms receipt → calls confirm_receipt()
281+
└─► Funds released to seller, emits `completed` event
282+
283+
8. Indexer updates trade to COMPLETED
284+
└─► Analytics aggregated → Grafana dashboard refreshed
285+
```
286+
287+
---
288+
289+
*© 2026 StellarEscrow*

0 commit comments

Comments
 (0)