forked from lthn/blockchain
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6def43d69 | ||
|
|
cbc49e93fe | ||
|
|
56d7607754 | ||
|
|
a6773abaca | ||
|
|
1e565495bb | ||
|
|
3fd1af9824 | ||
|
|
e1292a6445 | ||
|
|
9e3c6c2223 | ||
|
|
2ff53183c3 | ||
|
|
96e79beef4 | ||
|
|
eebfb3f1f1 | ||
|
|
7af620e7f6 | ||
|
|
1261d0d94e | ||
|
|
87fbdbb08d | ||
|
|
21ebfa1d17 | ||
|
|
91efcd41d0 | ||
| b61d773a0f |
45 changed files with 3370 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@ Thumbs.db
|
|||
.vs/*
|
||||
CMakeUserPresets.json
|
||||
ConanPresets.json
|
||||
docker/dist/
|
||||
|
|
|
|||
209
PLAN-NAME-REGISTRATION.md
Normal file
209
PLAN-NAME-REGISTRATION.md
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# Mainnet Name Registration Plan
|
||||
|
||||
> **Status:** Validating on testnet
|
||||
> **Date:** 2026-04-03
|
||||
> **Authors:** Snider, Charon
|
||||
|
||||
## Problem
|
||||
|
||||
The Lethean chain merges two blockchains — a main chain (aliases) and an HNS sidechain (DNS). The sidechain blocks Alexa top 100K domains and ICANN TLDs from being registered by random users. But the main chain has no such protection — anyone could register `@google` as an alias before the sidechain has a chance to refuse it.
|
||||
|
||||
This creates a window for name squatting that corrupts the namespace before real users arrive.
|
||||
|
||||
## Numbers
|
||||
|
||||
| Category | Count |
|
||||
|----------|-------|
|
||||
| Reserved (Alexa top 100K) | 90,016 |
|
||||
| Locked (ICANN TLDs) | 11,554 |
|
||||
| **Total unique names** | **90,041** |
|
||||
| Long names (>=6 chars, public) | 73,977 |
|
||||
| Short names (<6 chars, need authority key) | 16,062 |
|
||||
|
||||
**Source:** `blockchain-network-dcore/lib/covenants/names.json` and `lockup.json`
|
||||
|
||||
## Strategy
|
||||
|
||||
### Phase 1: Genesis + HF Warmup
|
||||
|
||||
1. Mainnet launches with HF0 — 10M LTHN premine in SWAP wallet
|
||||
2. During HF warmup (HF0 → HF1), send **1,000+ LTHN** from genesis to dedicated namereg wallets
|
||||
3. Multiple namereg wallets (2-4) for parallel registration
|
||||
4. Destination for all reserved aliases: a **multi-sig wallet** controlled by the team
|
||||
|
||||
### Phase 2: Staking Funds the Protection
|
||||
|
||||
1. Network wallet stakes during warmup — earns ~720 LTHN/day from PoS
|
||||
2. Legacy users haven't reclaimed tokens yet (SWAP happens later)
|
||||
3. Staking rewards from the network's own balance fund all registrations
|
||||
4. The network literally pays for its own name protection
|
||||
|
||||
### Phase 3: Batch Registration
|
||||
|
||||
When aliases activate (HF that enables alias registration):
|
||||
|
||||
1. Namereg wallets already have coins and mature UTXOs ready
|
||||
2. Multiple wallets work in parallel, each tackling a chunk of the 90K list
|
||||
3. Priority order:
|
||||
- **Tier 1:** Top brands (google, facebook, amazon, etc) — ~120 names
|
||||
- **Tier 2:** Long names from Alexa list (>=6 chars) — ~73,977 names
|
||||
- **Tier 3:** Short names with authority key (<6 chars) — ~16,062 names
|
||||
4. Rate: ~1 alias per 3 seconds per wallet = ~28,800/day with 1 wallet, ~115,200/day with 4
|
||||
|
||||
### Phase 4: Ongoing Governance
|
||||
|
||||
1. All reserved names sit in a multi-sig wallet
|
||||
2. Legitimate brand owners can request transfer (proof of domain ownership)
|
||||
3. Community aliases (common words like "wallet", "exchange") remain locked
|
||||
4. Infrastructure names (@vpn, @proxy, @exit, etc) transferred to service operators
|
||||
|
||||
## Cost Model
|
||||
|
||||
| Item | Cost |
|
||||
|------|------|
|
||||
| Alias registration fee | 1 LTHN each |
|
||||
| Transaction fee | 0.01 LTHN each |
|
||||
| **Total for 90K names** | **~90,900 LTHN** |
|
||||
| PoS earnings (720/day) | Covered in ~126 days |
|
||||
| With 1000 LTHN seed | First ~1000 names immediate |
|
||||
|
||||
The network earns back the registration cost through staking before legacy users even begin the SWAP process.
|
||||
|
||||
## Technical Requirements
|
||||
|
||||
### Testnet Validation (current)
|
||||
|
||||
- [x] PoS staking working (first PoS block at height 12,382)
|
||||
- [x] Alias registration via wallet RPC confirmed
|
||||
- [x] Batch registration script built and tested
|
||||
- [x] Priority name list generated from HNS data (90,041 names)
|
||||
- [ ] Multi-wallet parallel registration tested
|
||||
- [ ] Authority key for short names (<6 chars) tested
|
||||
- [ ] Full 90K registration completed on testnet
|
||||
- [ ] Explorer confirms all names visible
|
||||
- [ ] LNS resolves registered names
|
||||
|
||||
### Mainnet Requirements
|
||||
|
||||
- [ ] Multi-sig wallet created for reserved names
|
||||
- [ ] Genesis transaction plan (SWAP wallet → namereg wallets)
|
||||
- [ ] Authority key provisioned for short-name registration
|
||||
- [ ] Ansible playbook for deploying namereg wallets on prod
|
||||
- [ ] Monitoring: alias count dashboard, registration rate, failures
|
||||
|
||||
## Alias Comment Format
|
||||
|
||||
All reserved names use this comment format for identification:
|
||||
|
||||
```
|
||||
v=lthn1;type=reserved;reason=hns-protected
|
||||
```
|
||||
|
||||
This allows:
|
||||
- LNS to identify reserved vs user-registered names
|
||||
- Future tools to query reserved names
|
||||
- Governance decisions (release to brand owners) to be automated
|
||||
|
||||
## Daemon Configuration
|
||||
|
||||
PoS staking requires `--rpc-ignore-offline` when the daemon has no peers:
|
||||
|
||||
```
|
||||
lethean-chain-node --rpc-ignore-offline --do-pos-mining
|
||||
```
|
||||
|
||||
Without this flag, the daemon refuses PoS mining requests when disconnected.
|
||||
|
||||
## Batch Registration Tool
|
||||
|
||||
```bash
|
||||
# Generate priority list from HNS data
|
||||
python3 tools/generate-name-list.py
|
||||
|
||||
# Register names (3s delay, checks balance every 10)
|
||||
bash tools/register-reserved.sh /tmp/protected-names-long.txt http://127.0.0.1:46944/json_rpc 10 3
|
||||
```
|
||||
|
||||
Tool location: `docker/tools/register-reserved.sh`
|
||||
|
||||
## Option B: TLD Registrar Model (Recommended)
|
||||
|
||||
Instead of racing to pre-register 90K names, change one constant to make
|
||||
ALL alias registration require the authority key:
|
||||
|
||||
```c
|
||||
// currency_config.h — one line change
|
||||
#define ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED 64 // was 6
|
||||
```
|
||||
|
||||
This makes Lethean a proper TLD registrar — every name under `.lthn`
|
||||
requires authority signature. No squatting possible at any length.
|
||||
|
||||
### Why This Is Better
|
||||
|
||||
| Pre-reg (Option A) | Registrar (Option B) |
|
||||
|---------------------|----------------------|
|
||||
| Race against squatters | No race — authority controls all |
|
||||
| 90K transactions needed | Zero pre-registration needed |
|
||||
| ~90K LTHN cost | Zero cost |
|
||||
| Only covers known names | Covers ALL names, forever |
|
||||
| New brands unprotected | New brands protected by default |
|
||||
|
||||
### Registrar Portal Flow
|
||||
|
||||
1. User requests `@myname` via website or CLI
|
||||
2. Portal checks: not taken, not reserved, not offensive
|
||||
3. User pays registration fee (1 LTHN + portal fee if desired)
|
||||
4. Authority key signs the registration transaction
|
||||
5. Name goes live on chain within 1 block
|
||||
|
||||
### Revenue Potential
|
||||
|
||||
| Name Type | Price |
|
||||
|-----------|-------|
|
||||
| Standard (6+ chars) | 1 LTHN (chain fee only) |
|
||||
| Short (3-5 chars) | 10-100 LTHN (premium) |
|
||||
| Ultra-short (1-2 chars) | Auction or reserved |
|
||||
|
||||
### Implementation
|
||||
|
||||
- **Hardfork:** Change `ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED` to 64
|
||||
- **Portal:** Web API that accepts requests, validates, signs with authority key
|
||||
- **Key security:** Authority private key in HSM or multi-sig, never on a hot server
|
||||
- **Delegation:** Portal signs, daemon validates — separation of concerns
|
||||
|
||||
### Why Lethean Should Be a Registrar
|
||||
|
||||
- Lethean owns `.lthn` on Handshake — it IS the TLD operator
|
||||
- Names route real traffic (VPN, DNS, service discovery)
|
||||
- A curated namespace is trustworthy; a squatted one is worthless
|
||||
- Every TLD in the world works this way — `.com`, `.co.uk`, `.eth`
|
||||
- Revenue stream from name sales funds network development
|
||||
|
||||
### Migration Path
|
||||
|
||||
1. Testnet: validate both approaches (pre-reg + registrar)
|
||||
2. HF proposal: include the constant change in next hardfork
|
||||
3. Build portal: simple web UI + authority signing service
|
||||
4. Mainnet launch: registrar model active from HF activation
|
||||
5. Pre-reg only needed for names registered BEFORE the HF
|
||||
|
||||
## Risk Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|-----------|
|
||||
| Tx pool congestion | Multiple wallets, 3s delay, batch checkpoints |
|
||||
| Insufficient funds | Staking covers ongoing costs, seed from genesis |
|
||||
| Authority key compromise | Multi-sig for the key, not single holder |
|
||||
| Name disputes post-launch | Governance process for brand transfers |
|
||||
| Testnet/mainnet drift | Same tooling, same scripts, validated on testnet first |
|
||||
|
||||
## Timeline
|
||||
|
||||
| Phase | When | Duration |
|
||||
|-------|------|----------|
|
||||
| Testnet validation | Now (Apr 2026) | 1-2 weeks |
|
||||
| Mainnet genesis | TBD (Darbs approval) | Day 0 |
|
||||
| HF warmup | Day 0-7 | 1 week |
|
||||
| Batch registration | Day 7+ | 1-4 days (with parallel wallets) |
|
||||
| Full coverage | Day 14 | All 90K protected |
|
||||
54
docker/.env
Normal file
54
docker/.env
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Lethean Testnet Configuration
|
||||
# Copy to .env and customise before running:
|
||||
# cp .env.example .env
|
||||
# docker compose -f docker-compose.pull.yml up -d
|
||||
|
||||
# Public hostname (used by explorer and trade frontend)
|
||||
PUBLIC_HOST=localhost
|
||||
|
||||
# Wallet password (empty = no password, change for production)
|
||||
WALLET_PASSWORD=
|
||||
|
||||
# Trade API JWT secret (CHANGE THIS for production)
|
||||
JWT_SECRET=change-me-before-production
|
||||
|
||||
# Explorer database
|
||||
EXPLORER_DB_USER=explorer
|
||||
EXPLORER_DB_PASS=explorer
|
||||
EXPLORER_DB_NAME=lethean_explorer
|
||||
|
||||
# Trade database
|
||||
TRADE_DB_USER=trade
|
||||
TRADE_DB_PASS=trade
|
||||
TRADE_DB_NAME=lethean_trade
|
||||
|
||||
# Daemon log level (0=minimal, 1=normal, 2=detailed, 3=trace)
|
||||
DAEMON_LOG_LEVEL=1
|
||||
|
||||
# Port overrides (defaults shown)
|
||||
# DAEMON_RPC_PORT=46941
|
||||
# DAEMON_P2P_PORT=46942
|
||||
# WALLET_RPC_PORT=46944
|
||||
# EXPLORER_PORT=3335
|
||||
# TRADE_API_PORT=3336
|
||||
# TRADE_FRONTEND_PORT=3338
|
||||
# POOL_STRATUM_PORT=5555
|
||||
# POOL_API_PORT=2117
|
||||
# LNS_HTTP_PORT=5553
|
||||
# LNS_DNS_PORT=5354
|
||||
|
||||
# === Exit Node Settings (docker-compose.exit.yml) ===
|
||||
|
||||
# Your public IP address (required for VPN exit node)
|
||||
# EXIT_PUBLIC_IP=auto
|
||||
|
||||
# Exit node name (registered as on-chain alias)
|
||||
# EXIT_NAME=my-exit-node
|
||||
|
||||
# Maximum VPN peers (WireGuard clients)
|
||||
# EXIT_MAX_PEERS=25
|
||||
|
||||
# Timezone
|
||||
# TZ=Europe/London
|
||||
EXIT_PUBLIC_IP=203.0.113.50
|
||||
EXIT_NAME=my-exit
|
||||
52
docker/.env.example
Normal file
52
docker/.env.example
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Lethean Testnet Configuration
|
||||
# Copy to .env and customise before running:
|
||||
# cp .env.example .env
|
||||
# docker compose -f docker-compose.pull.yml up -d
|
||||
|
||||
# Public hostname (used by explorer and trade frontend)
|
||||
PUBLIC_HOST=localhost
|
||||
|
||||
# Wallet password (empty = no password, change for production)
|
||||
WALLET_PASSWORD=
|
||||
|
||||
# Trade API JWT secret (CHANGE THIS for production)
|
||||
JWT_SECRET=change-me-before-production
|
||||
|
||||
# Explorer database
|
||||
EXPLORER_DB_USER=explorer
|
||||
EXPLORER_DB_PASS=explorer
|
||||
EXPLORER_DB_NAME=lethean_explorer
|
||||
|
||||
# Trade database
|
||||
TRADE_DB_USER=trade
|
||||
TRADE_DB_PASS=trade
|
||||
TRADE_DB_NAME=lethean_trade
|
||||
|
||||
# Daemon log level (0=minimal, 1=normal, 2=detailed, 3=trace)
|
||||
DAEMON_LOG_LEVEL=1
|
||||
|
||||
# Port overrides (defaults shown)
|
||||
# DAEMON_RPC_PORT=46941
|
||||
# DAEMON_P2P_PORT=46942
|
||||
# WALLET_RPC_PORT=46944
|
||||
# EXPLORER_PORT=3335
|
||||
# TRADE_API_PORT=3336
|
||||
# TRADE_FRONTEND_PORT=3338
|
||||
# POOL_STRATUM_PORT=5555
|
||||
# POOL_API_PORT=2117
|
||||
# LNS_HTTP_PORT=5553
|
||||
# LNS_DNS_PORT=5354
|
||||
|
||||
# === Exit Node Settings (docker-compose.exit.yml) ===
|
||||
|
||||
# Your public IP address (required for VPN exit node)
|
||||
# EXIT_PUBLIC_IP=auto
|
||||
|
||||
# Exit node name (registered as on-chain alias)
|
||||
# EXIT_NAME=my-exit-node
|
||||
|
||||
# Maximum VPN peers (WireGuard clients)
|
||||
# EXIT_MAX_PEERS=25
|
||||
|
||||
# Timezone
|
||||
# TZ=Europe/London
|
||||
38
docker/Dockerfile.cross-build
Normal file
38
docker/Dockerfile.cross-build
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Cross-platform build for Lethean blockchain binaries
|
||||
# Produces Linux x86_64 and ARM64 binaries
|
||||
# macOS and Windows need native build environments or cross-compilation toolchains
|
||||
#
|
||||
# Usage:
|
||||
# docker build -f Dockerfile.cross-build --target linux-x64 -o ./out .
|
||||
# docker build -f Dockerfile.cross-build --target linux-arm64 -o ./out .
|
||||
|
||||
# === Base builder ===
|
||||
FROM ubuntu:24.04 AS base-builder
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential cmake git python3 python3-pip \
|
||||
libboost-all-dev libssl-dev pkg-config curl \
|
||||
&& pip3 install conan --break-system-packages \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /build
|
||||
COPY . /build/
|
||||
|
||||
# === Linux x86_64 ===
|
||||
FROM base-builder AS linux-x64-build
|
||||
ARG TESTNET=1
|
||||
RUN make build CPU_CORES=$(nproc) TESTNET=${TESTNET} STATIC=1 || \
|
||||
(cd build/release && cmake --build . --parallel $(nproc))
|
||||
|
||||
FROM scratch AS linux-x64
|
||||
COPY --from=linux-x64-build /build/build/release/src/lethean-* /
|
||||
|
||||
# === Linux ARM64 (cross-compile) ===
|
||||
FROM base-builder AS linux-arm64-build
|
||||
RUN apt-get update && apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
ARG TESTNET=1
|
||||
# ARM64 cross-compilation requires CMAKE_TOOLCHAIN_FILE
|
||||
# This is a placeholder — actual cross-compile needs Conan profile for aarch64
|
||||
RUN echo "ARM64 cross-compilation requires additional setup. Use native ARM64 builder or QEMU."
|
||||
|
||||
FROM scratch AS linux-arm64
|
||||
# Placeholder — use buildx with --platform=linux/arm64 for native ARM builds
|
||||
183
docker/Dockerfile.staking-node
Normal file
183
docker/Dockerfile.staking-node
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# Lethean Staking Node — single container, no compose needed
|
||||
# Runs daemon + wallet with PoS staking in one container.
|
||||
#
|
||||
# Build:
|
||||
# docker build -f Dockerfile.staking-node -t lthn/staking-node .
|
||||
#
|
||||
# Run:
|
||||
# docker run -d --name lthn-staker \
|
||||
# -p 46941:36941 -p 46942:36942 -p 46944:36944 \
|
||||
# -v lthn-chain:/data -v lthn-wallet:/wallet \
|
||||
# lthn/staking-node
|
||||
#
|
||||
# Run with password:
|
||||
# docker run -d --name lthn-staker \
|
||||
# -e WALLET_PASSWORD=my-pass \
|
||||
# -p 46941:36941 -p 46942:36942 -p 46944:36944 \
|
||||
# -v lthn-chain:/data -v lthn-wallet:/wallet \
|
||||
# lthn/staking-node
|
||||
#
|
||||
# Check status:
|
||||
# docker exec lthn-staker /status.sh
|
||||
#
|
||||
# Get wallet address:
|
||||
# docker exec lthn-staker /wallet-address.sh
|
||||
#
|
||||
# Ports:
|
||||
# 36941 — Daemon RPC (chain queries)
|
||||
# 36942 — P2P (open on router for full node)
|
||||
# 36944 — Wallet RPC (balance, transfers)
|
||||
|
||||
FROM lthn/chain:testnet
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl supervisor bc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Supervisor manages both daemon and wallet in one container
|
||||
COPY <<'SUPERVISOR' /etc/supervisor/conf.d/lethean.conf
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
loglevel=info
|
||||
|
||||
[program:daemon]
|
||||
command=lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--rpc-enable-admin-api
|
||||
--allow-local-ip
|
||||
--log-level %(ENV_DAEMON_LOG_LEVEL)s
|
||||
--disable-upnp
|
||||
--rpc-ignore-offline
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:wallet]
|
||||
command=/start-wallet.sh
|
||||
autorestart=true
|
||||
startsecs=30
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
SUPERVISOR
|
||||
|
||||
# Wallet startup script — waits for daemon, creates wallet if needed
|
||||
COPY <<'WALLETSH' /start-wallet.sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
WALLET_FILE="/wallet/staker.wallet"
|
||||
WALLET_PASS="${WALLET_PASSWORD:-}"
|
||||
DAEMON="127.0.0.1:36941"
|
||||
|
||||
# Wait for daemon RPC
|
||||
echo "[wallet] Waiting for daemon..."
|
||||
until curl -sf "http://$DAEMON/json_rpc" \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' \
|
||||
-H 'Content-Type: application/json' > /dev/null 2>&1; do
|
||||
sleep 5
|
||||
done
|
||||
echo "[wallet] Daemon ready"
|
||||
|
||||
# Create wallet if it doesn't exist
|
||||
if [ ! -f "$WALLET_FILE" ]; then
|
||||
echo "[wallet] Creating new wallet..."
|
||||
echo "$WALLET_PASS" | lethean-wallet-cli \
|
||||
--generate-new-wallet "$WALLET_FILE" \
|
||||
--password "$WALLET_PASS" \
|
||||
--daemon-address "$DAEMON" \
|
||||
--command exit
|
||||
echo "[wallet] Wallet created"
|
||||
fi
|
||||
|
||||
# Start wallet with PoS staking
|
||||
exec lethean-wallet-cli \
|
||||
--wallet-file "$WALLET_FILE" \
|
||||
--password "$WALLET_PASS" \
|
||||
--daemon-address "$DAEMON" \
|
||||
--rpc-bind-port 36944 \
|
||||
--rpc-bind-ip 0.0.0.0 \
|
||||
--do-pos-mining
|
||||
WALLETSH
|
||||
|
||||
# Status helper — shows chain + wallet in one command
|
||||
COPY <<'STATUSSH' /status.sh
|
||||
#!/bin/bash
|
||||
echo "=== Lethean Staking Node ==="
|
||||
echo ""
|
||||
|
||||
# Chain
|
||||
INFO=$(curl -sf http://127.0.0.1:36941/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' \
|
||||
-H 'Content-Type: application/json' 2>/dev/null)
|
||||
|
||||
if [ -n "$INFO" ]; then
|
||||
echo "$INFO" | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
hf=d.get('is_hardfok_active',[])
|
||||
active=sum(1 for h in hf if h)
|
||||
print(f'Chain:')
|
||||
print(f' Height: {d[\"height\"]:,}')
|
||||
print(f' Hardforks: HF0-{active-1}')
|
||||
print(f' PoW diff: {d.get(\"pow_difficulty\",0):,}')
|
||||
print(f' Aliases: {d.get(\"alias_count\",0)}')
|
||||
print(f' Peers in: {d.get(\"incoming_connections_count\",0)}')
|
||||
print(f' Peers out: {d.get(\"outgoing_connections_count\",0)}')
|
||||
print(f' Status: {d[\"status\"]}')
|
||||
" 2>/dev/null
|
||||
else
|
||||
echo "Chain: starting..."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Wallet
|
||||
BAL=$(curl -sf http://127.0.0.1:36944/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' \
|
||||
-H 'Content-Type: application/json' 2>/dev/null)
|
||||
|
||||
if [ -n "$BAL" ]; then
|
||||
echo "$BAL" | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
print(f'Wallet:')
|
||||
print(f' Balance: {d[\"balance\"]/1e12:.4f} LTHN')
|
||||
print(f' Unlocked: {d[\"unlocked_balance\"]/1e12:.4f} LTHN')
|
||||
print(f' Staking: yes (PoS mining active)')
|
||||
" 2>/dev/null
|
||||
else
|
||||
echo "Wallet: syncing..."
|
||||
fi
|
||||
STATUSSH
|
||||
|
||||
# Wallet address helper
|
||||
COPY <<'ADDRSH' /wallet-address.sh
|
||||
#!/bin/bash
|
||||
curl -sf http://127.0.0.1:36944/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
print(d['address'])
|
||||
" 2>/dev/null || echo "Wallet not ready yet — daemon still syncing"
|
||||
ADDRSH
|
||||
|
||||
RUN chmod +x /start-wallet.sh /status.sh /wallet-address.sh
|
||||
|
||||
ENV DAEMON_LOG_LEVEL=1
|
||||
ENV WALLET_PASSWORD=
|
||||
|
||||
EXPOSE 36941 36942 36944
|
||||
|
||||
VOLUME ["/data", "/wallet"]
|
||||
|
||||
ENTRYPOINT ["supervisord", "-c", "/etc/supervisor/conf.d/lethean.conf"]
|
||||
152
docker/README.md
Normal file
152
docker/README.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Lethean Testnet — Docker Deployment
|
||||
|
||||
Run the full Lethean ecosystem in Docker. No compilation needed.
|
||||
|
||||
## Deployment Options
|
||||
|
||||
| Compose File | For | Services |
|
||||
|---|---|---|
|
||||
| `docker-compose.node.yml` | Home node operator | Daemon + wallet (PoS staking) |
|
||||
| `docker-compose.exit.yml` | VPN exit node operator | Daemon + wallet + WireGuard |
|
||||
| `docker-compose.pull.yml` | Full ecosystem | All 8 services + databases |
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
```bash
|
||||
bash demos/helpers/chain-info.sh # Chain height, difficulty, status
|
||||
bash demos/helpers/chain-aliases.sh # List on-chain aliases
|
||||
bash demos/helpers/wallet-address.sh # Show your wallet address
|
||||
bash demos/helpers/wallet-balance.sh # Check LTHN balance
|
||||
bash health.sh # Full ecosystem health check
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Configure
|
||||
cp .env.example .env
|
||||
# Edit .env — at minimum change JWT_SECRET and WALLET_PASSWORD
|
||||
|
||||
# 2. Start
|
||||
docker compose -f docker-compose.pull.yml up -d
|
||||
|
||||
# 3. Check
|
||||
docker compose -f docker-compose.pull.yml ps
|
||||
```
|
||||
|
||||
The daemon takes ~30 seconds to initialise the chain database on first run.
|
||||
Other services wait for the daemon health check before starting.
|
||||
|
||||
## Services
|
||||
|
||||
| Service | URL | Description |
|
||||
|---------|-----|-------------|
|
||||
| Block Explorer | http://localhost:3335 | Browse blocks, transactions, aliases |
|
||||
| Trade DEX | http://localhost:3338 | Decentralised exchange frontend |
|
||||
| Trade API | http://localhost:3336 | REST API for trade operations |
|
||||
| Mining Pool | http://localhost:2117 | Pool stats API |
|
||||
| Pool Stratum | localhost:5555 | Connect miners here |
|
||||
| LNS | http://localhost:5553 | Lethean Name Service HTTP API |
|
||||
| LNS DNS | localhost:5354 | DNS resolver for .lthn names |
|
||||
| Daemon RPC | http://localhost:46941 | Chain node JSON-RPC |
|
||||
| Wallet RPC | http://localhost:46944 | Wallet JSON-RPC |
|
||||
| Docs | http://localhost:8099 | Documentation site |
|
||||
|
||||
## Mining
|
||||
|
||||
Connect a ProgPoWZ miner to the pool stratum:
|
||||
|
||||
```
|
||||
stratum+tcp://YOUR_IP:5555
|
||||
```
|
||||
|
||||
Compatible miners:
|
||||
- **progminer** (recommended) — `progminer -P stratum+tcp://YOUR_WALLET_ADDRESS@localhost:5555`
|
||||
- **T-Rex** — ProgPoWZ algorithm
|
||||
- **TeamRedMiner** — ProgPoWZ algorithm
|
||||
|
||||
## Wallet
|
||||
|
||||
The wallet auto-creates on first start. To check your balance:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:46944/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' \
|
||||
-H 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
The wallet runs with PoS mining enabled (`--do-pos-mining`), so it will stake
|
||||
any balance automatically.
|
||||
|
||||
## Chain Status
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:46941/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' \
|
||||
-H 'Content-Type: application/json' | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Volumes
|
||||
|
||||
| Volume | Contains | Backup? |
|
||||
|--------|----------|---------|
|
||||
| `chain-data` | Blockchain database (LMDB) | Optional — can resync |
|
||||
| `wallet-data` | Wallet file + keys | **Critical — back up regularly** |
|
||||
| `explorer-db` | Explorer PostgreSQL | Can recreate from chain |
|
||||
| `trade-db` | Trade PostgreSQL | Important for trade history |
|
||||
|
||||
### Backup wallet
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.pull.yml stop wallet
|
||||
docker cp lthn-wallet:/wallet ./wallet-backup-$(date +%Y%m%d)
|
||||
docker compose -f docker-compose.pull.yml start wallet
|
||||
```
|
||||
|
||||
## Ports
|
||||
|
||||
All ports can be changed via `.env`. See `.env.example` for the full list.
|
||||
|
||||
If you're running behind a firewall and want external access:
|
||||
- **P2P:** Open port 46942 (TCP) for other nodes to connect
|
||||
- **Stratum:** Open port 5555 (TCP) for external miners
|
||||
- **Explorer:** Open port 3335 (TCP) for public block explorer
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Daemon crashes on start:**
|
||||
Check logs with `docker logs lthn-daemon`. Common causes:
|
||||
- Port already in use — change ports in `.env`
|
||||
- Corrupt chain data — remove volume: `docker volume rm docker_chain-data`
|
||||
|
||||
**Wallet shows 0 balance:**
|
||||
The wallet needs to sync with the daemon first. Check sync status in daemon logs.
|
||||
New wallets start empty — mine or receive LTHN to fund them.
|
||||
|
||||
**Pool shows "Daemon died":**
|
||||
The pool needs both the daemon and wallet running. Check both are healthy:
|
||||
```bash
|
||||
docker compose -f docker-compose.pull.yml ps daemon wallet
|
||||
```
|
||||
|
||||
**Explorer shows "offline":**
|
||||
The explorer connects to the daemon internally. If the daemon is still syncing,
|
||||
the explorer will show offline until sync completes.
|
||||
|
||||
## Stopping
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.pull.yml down # stop containers
|
||||
docker compose -f docker-compose.pull.yml down -v # stop + delete data
|
||||
```
|
||||
|
||||
## Building from Source
|
||||
|
||||
If you want to build the images yourself instead of pulling pre-built ones:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.ecosystem.yml build
|
||||
docker compose -f docker-compose.ecosystem.yml up -d
|
||||
```
|
||||
|
||||
This requires the full source tree — see the main repository README.
|
||||
77
docker/build-all-platforms.sh
Executable file
77
docker/build-all-platforms.sh
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/bin/bash
|
||||
# Build Lethean binaries for all platforms
|
||||
# Requires: Docker with buildx for multi-arch
|
||||
#
|
||||
# Usage:
|
||||
# ./build-all-platforms.sh testnet # testnet binaries
|
||||
# ./build-all-platforms.sh mainnet # mainnet binaries
|
||||
#
|
||||
# Outputs to: ../build/packages/
|
||||
|
||||
set -e
|
||||
|
||||
TARGET=${1:-testnet}
|
||||
TESTNET=$( [ "$TARGET" = "testnet" ] && echo 1 || echo 0 )
|
||||
VERSION=$(grep 'BUILD_VERSION:=' ../Makefile | cut -d= -f2)
|
||||
OUTDIR="../build/packages"
|
||||
|
||||
echo "Building Lethean $TARGET v$VERSION for all platforms"
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# === Linux x86_64 (Docker) ===
|
||||
echo ""
|
||||
echo "=== Linux x86_64 ==="
|
||||
docker build -f Dockerfile.cross-build \
|
||||
--build-arg TESTNET=$TESTNET \
|
||||
--target linux-x64 \
|
||||
-o "$OUTDIR/linux-x64" \
|
||||
.. 2>&1 | tail -5
|
||||
|
||||
if ls "$OUTDIR/linux-x64/lethean-"* > /dev/null 2>&1; then
|
||||
PKG="lethean-${TARGET}-linux-x86_64-v${VERSION}"
|
||||
mkdir -p "$OUTDIR/$PKG"
|
||||
cp "$OUTDIR/linux-x64/lethean-"* "$OUTDIR/$PKG/"
|
||||
cd "$OUTDIR" && tar czf "${PKG}.tar.gz" "$PKG/" && cd -
|
||||
echo " Package: $OUTDIR/${PKG}.tar.gz"
|
||||
else
|
||||
echo " Build failed — check Docker output"
|
||||
fi
|
||||
|
||||
# === Linux ARM64 (needs Docker buildx with QEMU) ===
|
||||
echo ""
|
||||
echo "=== Linux ARM64 ==="
|
||||
if docker buildx ls 2>/dev/null | grep -q "linux/arm64"; then
|
||||
echo " ARM64 builder available — building..."
|
||||
docker buildx build -f ../utils/docker/lthn-chain/Dockerfile \
|
||||
--platform linux/arm64 \
|
||||
--build-arg BUILD_TESTNET=$TESTNET \
|
||||
--build-arg BUILD_THREADS=4 \
|
||||
--target build-artifacts \
|
||||
-o "$OUTDIR/linux-arm64" \
|
||||
.. 2>&1 | tail -5
|
||||
else
|
||||
echo " SKIP: Docker buildx ARM64 emulation not configured"
|
||||
echo " To enable: docker run --privileged --rm tonistiigi/binfmt --install arm64"
|
||||
fi
|
||||
|
||||
# === macOS (needs native build or osxcross) ===
|
||||
echo ""
|
||||
echo "=== macOS ==="
|
||||
echo " SKIP: macOS builds require native macOS environment or osxcross toolchain"
|
||||
echo " Build on Cladius (M3 Ultra): cd blockchain && make $TARGET"
|
||||
echo " Or use GitHub Actions with macos-latest runner"
|
||||
|
||||
# === Windows (needs MSVC or mingw-w64) ===
|
||||
echo ""
|
||||
echo "=== Windows ==="
|
||||
echo " SKIP: Windows builds require MSVC or mingw-w64 cross-compiler"
|
||||
echo " Recommended: GitHub Actions with windows-latest runner"
|
||||
echo " Or: Docker with dockcross/windows-shared-x64 image"
|
||||
|
||||
# === Summary ===
|
||||
echo ""
|
||||
echo "=== Build Summary ==="
|
||||
ls -lh "$OUTDIR"/*.tar.gz 2>/dev/null || echo "No packages built yet"
|
||||
echo ""
|
||||
echo "For macOS/Windows, use CI/CD:"
|
||||
echo " .forgejo/workflows/build-release.yml (push a tag to trigger)"
|
||||
BIN
docker/demos/01-install.gif
Normal file
BIN
docker/demos/01-install.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
54
docker/demos/01-install.tape
Normal file
54
docker/demos/01-install.tape
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# 01 — Install Lethean from scratch
|
||||
# Shows a home user going from zero to running node
|
||||
|
||||
Output demos/01-install.gif
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1000
|
||||
Set Height 600
|
||||
Set Theme "Dracula"
|
||||
Set TypingSpeed 35ms
|
||||
Set Padding 12
|
||||
Set Shell bash
|
||||
|
||||
Type "# Install Lethean — from zero to running node"
|
||||
Enter
|
||||
Sleep 1s
|
||||
|
||||
Type "mkdir lethean && cd lethean"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "# Download the compose files"
|
||||
Enter
|
||||
Type "curl -sO https://forge.lthn.ai/lthn/blockchain/raw/branch/dev/docker/docker-compose.node.yml"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "curl -sO https://forge.lthn.ai/lthn/blockchain/raw/branch/dev/docker/.env.example"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "cp .env.example .env"
|
||||
Enter
|
||||
Sleep 300ms
|
||||
|
||||
Type "# Set a wallet password"
|
||||
Enter
|
||||
Type "sed -i 's/WALLET_PASSWORD=/WALLET_PASSWORD=my-secure-pass/' .env"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Start the node"
|
||||
Enter
|
||||
Type "docker compose -f docker-compose.node.yml up -d"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# That's it. Your node is syncing and staking."
|
||||
Enter
|
||||
Sleep 2s
|
||||
BIN
docker/demos/02-chain-status.gif
Normal file
BIN
docker/demos/02-chain-status.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 443 KiB |
39
docker/demos/02-chain-status.tape
Normal file
39
docker/demos/02-chain-status.tape
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# 02 — Chain Status (live data)
|
||||
|
||||
Output demos/02-chain-status.gif
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1000
|
||||
Set Height 600
|
||||
Set Theme "Dracula"
|
||||
Set TypingSpeed 35ms
|
||||
Set Padding 12
|
||||
Set Shell bash
|
||||
|
||||
Type "# Lethean Chain — Live Status"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "bash demos/helpers/chain-info.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "bash demos/helpers/last-block.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# On-chain service discovery via aliases"
|
||||
Enter
|
||||
Type "bash demos/helpers/chain-aliases.sh"
|
||||
Enter
|
||||
Sleep 4s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Blocks arrive every ~2 minutes — PoW and PoS alternating"
|
||||
Enter
|
||||
Sleep 2s
|
||||
BIN
docker/demos/03-ecosystem-tour.gif
Normal file
BIN
docker/demos/03-ecosystem-tour.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
87
docker/demos/03-ecosystem-tour.tape
Normal file
87
docker/demos/03-ecosystem-tour.tape
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# 03 — Ecosystem Tour (the teaser material)
|
||||
# Every service, live data, no fluff
|
||||
|
||||
Output demos/03-ecosystem-tour.gif
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1000
|
||||
Set Height 700
|
||||
Set Theme "Dracula"
|
||||
Set TypingSpeed 30ms
|
||||
Set Padding 12
|
||||
Set Shell bash
|
||||
|
||||
Type "# Lethean Ecosystem — Full Tour"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "bash health.sh"
|
||||
Enter
|
||||
Sleep 4s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Chain ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/chain-info.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Latest Block ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/last-block.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Wallet ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/wallet-address.sh"
|
||||
Enter
|
||||
Sleep 2s
|
||||
Type "bash demos/helpers/wallet-balance.sh"
|
||||
Enter
|
||||
Sleep 2s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Explorer ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/explorer-stats.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Mining Pool ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/pool-stats.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Name Service ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/lns-status.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# === Aliases — services advertise on-chain ==="
|
||||
Enter
|
||||
Type "bash demos/helpers/chain-aliases.sh"
|
||||
Enter
|
||||
Sleep 4s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Lethean — privacy by default, earn by participating"
|
||||
Enter
|
||||
Type "# https://lethean.io"
|
||||
Enter
|
||||
Sleep 3s
|
||||
BIN
docker/demos/04-mining.gif
Normal file
BIN
docker/demos/04-mining.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 957 KiB |
50
docker/demos/04-mining.tape
Normal file
50
docker/demos/04-mining.tape
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# 04 — Mining
|
||||
# Shows pool stats and how to connect a miner
|
||||
|
||||
Output demos/04-mining.gif
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1000
|
||||
Set Height 550
|
||||
Set Theme "Dracula"
|
||||
Set TypingSpeed 35ms
|
||||
Set Padding 12
|
||||
Set Shell bash
|
||||
|
||||
Type "# Lethean Mining — ProgPoWZ GPU Mining"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "bash demos/helpers/pool-stats.sh"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Get your wallet address for mining"
|
||||
Enter
|
||||
Type "bash demos/helpers/wallet-address.sh"
|
||||
Enter
|
||||
Sleep 2s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Connect a GPU miner:"
|
||||
Enter
|
||||
Type "echo 'progminer -P stratum+tcp://YOUR_WALLET@localhost:5555'"
|
||||
Enter
|
||||
Sleep 1s
|
||||
Type "echo 't-rex -a progpowz -o stratum+tcp://localhost:5555 -u YOUR_WALLET'"
|
||||
Enter
|
||||
Sleep 1s
|
||||
Type "echo 'teamredminer -a progpow -o stratum+tcp://localhost:5555 -u YOUR_WALLET'"
|
||||
Enter
|
||||
Sleep 2s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Pool dashboard: http://localhost:2117"
|
||||
Enter
|
||||
Type "# Block reward: 1 LTHN every ~2 minutes"
|
||||
Enter
|
||||
Sleep 2s
|
||||
BIN
docker/demos/05-exit-node.gif
Normal file
BIN
docker/demos/05-exit-node.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
68
docker/demos/05-exit-node.tape
Normal file
68
docker/demos/05-exit-node.tape
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# 05 — Exit Node Setup
|
||||
# Shows how to run a VPN exit node and earn LTHN
|
||||
|
||||
Output demos/05-exit-node.gif
|
||||
|
||||
Set FontSize 13
|
||||
Set Width 1000
|
||||
Set Height 600
|
||||
Set Theme "Dracula"
|
||||
Set TypingSpeed 35ms
|
||||
Set Padding 12
|
||||
Set Shell bash
|
||||
|
||||
Type "# Lethean VPN Exit Node — Earn LTHN"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type "# Configure your exit node"
|
||||
Enter
|
||||
Type "cp .env.example .env"
|
||||
Enter
|
||||
Sleep 300ms
|
||||
|
||||
Type "echo 'WALLET_PASSWORD=my-secure-pass' >> .env"
|
||||
Enter
|
||||
Type "echo 'EXIT_PUBLIC_IP=203.0.113.50' >> .env"
|
||||
Enter
|
||||
Type "echo 'EXIT_NAME=my-exit' >> .env"
|
||||
Enter
|
||||
Sleep 500ms
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Start the exit node stack"
|
||||
Enter
|
||||
Type "docker compose -f docker-compose.exit.yml up -d"
|
||||
Enter
|
||||
Sleep 3s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Your exit node runs:"
|
||||
Enter
|
||||
Type "echo ' Chain daemon — syncs blockchain'"
|
||||
Enter
|
||||
Type "echo ' Wallet — PoS staking + receives payments'"
|
||||
Enter
|
||||
Type "echo ' WireGuard VPN — encrypted tunnel for gateway traffic'"
|
||||
Enter
|
||||
Type "echo ' Controller — manages peering and on-chain registration'"
|
||||
Enter
|
||||
Sleep 2s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Gateways discover you via on-chain alias:"
|
||||
Enter
|
||||
Type "echo ' @my-exit v=lthn1;type=exit;cap=vpn,proxy;ip=203.0.113.50'"
|
||||
Enter
|
||||
Sleep 2s
|
||||
|
||||
Type ""
|
||||
Enter
|
||||
Type "# Open ports 46942/tcp + 51820/udp on your router"
|
||||
Enter
|
||||
Type "# Then sit back and earn LTHN"
|
||||
Enter
|
||||
Sleep 3s
|
||||
13
docker/demos/helpers/chain-aliases.sh
Executable file
13
docker/demos/helpers/chain-aliases.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:46941/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"get_all_alias_details"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
aliases=json.load(sys.stdin)['result']['aliases']
|
||||
print(f'{len(aliases)} aliases registered on-chain:')
|
||||
print()
|
||||
for a in aliases:
|
||||
name = a['alias']
|
||||
comment = a.get('comment','')
|
||||
print(f' @{name:12s} {comment[:50]}')
|
||||
"
|
||||
17
docker/demos/helpers/chain-info.sh
Executable file
17
docker/demos/helpers/chain-info.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:46941/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
hf = d.get('is_hardfok_active',[])
|
||||
active = sum(1 for h in hf if h)
|
||||
print(f'Height: {d[\"height\"]:,}')
|
||||
print(f'Hardforks: HF0-{active-1} active')
|
||||
print(f'PoW diff: {d.get(\"pow_difficulty\",0):,}')
|
||||
print(f'Aliases: {d.get(\"alias_count\",0)}')
|
||||
print(f'TX count: {d.get(\"tx_count\",0):,}')
|
||||
print(f'Peers in: {d.get(\"incoming_connections_count\",0)}')
|
||||
print(f'Peers out: {d.get(\"outgoing_connections_count\",0)}')
|
||||
print(f'Status: {d[\"status\"]}')
|
||||
"
|
||||
15
docker/demos/helpers/explorer-stats.sh
Executable file
15
docker/demos/helpers/explorer-stats.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:3335/api/get_info | python3 -c "
|
||||
import sys,json
|
||||
try:
|
||||
d=json.load(sys.stdin)
|
||||
print('Block Explorer')
|
||||
print(f' Height: {d.get(\"height\",0):,}')
|
||||
print(f' PoW diff: {d.get(\"pow_difficulty\",0):,}')
|
||||
print(f' Aliases: {d.get(\"alias_count\",0)}')
|
||||
print(f' TX count: {d.get(\"tx_count\",0):,}')
|
||||
print(f' Hashrate: {d.get(\"current_network_hashrate_350\",0):,} H/s')
|
||||
print(f' URL: http://localhost:3335')
|
||||
except:
|
||||
print('Explorer: connecting...')
|
||||
"
|
||||
13
docker/demos/helpers/last-block.sh
Executable file
13
docker/demos/helpers/last-block.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:46941/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getlastblockheader"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
b=json.load(sys.stdin)['result']['block_header']
|
||||
btype = 'PoW' if b.get('major_version',0)==3 else 'PoS'
|
||||
print(f'Latest Block #{b[\"height\"]:,}')
|
||||
print(f' Type: {btype}')
|
||||
print(f' Reward: {b[\"reward\"]/1e12:.4f} LTHN')
|
||||
print(f' Difficulty: {b[\"difficulty\"]:,}')
|
||||
print(f' Hash: {b[\"hash\"][:20]}...')
|
||||
"
|
||||
15
docker/demos/helpers/lns-status.sh
Executable file
15
docker/demos/helpers/lns-status.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
echo "Lethean Name Service (LNS)"
|
||||
curl -s localhost:5553/health | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)
|
||||
print(f' Mode: {d.get(\"mode\",\"?\")}')
|
||||
print(f' Names: {d.get(\"names\",0)} cached')
|
||||
print(f' Status: {d.get(\"status\",\"?\")}')
|
||||
"
|
||||
echo ""
|
||||
echo "Resolving .lthn names:"
|
||||
for name in charon gateway explorer trading; do
|
||||
result=$(curl -s "localhost:5553/resolve?name=$name.lthn" | python3 -c "import sys,json; d=json.load(sys.stdin); print('found' if d.get('found') else 'not cached')" 2>/dev/null)
|
||||
printf " %-16s %s\n" "$name.lthn" "$result"
|
||||
done
|
||||
17
docker/demos/helpers/pool-stats.sh
Executable file
17
docker/demos/helpers/pool-stats.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:2117/stats | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)
|
||||
pool = d.get('pool',{})
|
||||
net = d.get('network',{})
|
||||
cfg = d.get('config',{})
|
||||
print(f'Lethean Mining Pool')
|
||||
print(f' Coin: {cfg.get(\"coin\",\"?\")}')
|
||||
print(f' Algorithm: ProgPoWZ')
|
||||
print(f' Pool hash: {pool.get(\"hashrate\",0)} H/s')
|
||||
print(f' Miners: {pool.get(\"miners\",0)}')
|
||||
print(f' Blocks found: {pool.get(\"totalBlocks\",0)}')
|
||||
print(f' Net height: {net.get(\"height\",\"?\")}')
|
||||
print(f' Net diff: {net.get(\"difficulty\",\"?\")}')
|
||||
print(f' Stratum: stratum+tcp://localhost:5555')
|
||||
"
|
||||
8
docker/demos/helpers/wallet-address.sh
Executable file
8
docker/demos/helpers/wallet-address.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:46944/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
print('Address:', d['address'])
|
||||
"
|
||||
9
docker/demos/helpers/wallet-balance.sh
Executable file
9
docker/demos/helpers/wallet-balance.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
curl -s localhost:46944/json_rpc \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
print(f'Balance: {d[\"balance\"]/1e12:.4f} LTHN')
|
||||
print(f'Unlocked: {d[\"unlocked_balance\"]/1e12:.4f} LTHN')
|
||||
"
|
||||
78
docker/deploy.sh
Executable file
78
docker/deploy.sh
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/bash
|
||||
# Lethean Testnet — Production Deploy Script
|
||||
# Transfers images and config to a remote server and starts the stack.
|
||||
#
|
||||
# Usage:
|
||||
# bash deploy.sh user@server
|
||||
# bash deploy.sh user@server /opt/lethean/docker
|
||||
#
|
||||
# Prerequisites on the remote server:
|
||||
# - Docker + Docker Compose v2
|
||||
# - SSH access
|
||||
|
||||
set -e
|
||||
|
||||
REMOTE="${1:?Usage: deploy.sh user@server [install_dir]}"
|
||||
INSTALL_DIR="${2:-/opt/lethean/docker}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
IMAGES="$SCRIPT_DIR/dist/lethean-testnet-images.tar.gz"
|
||||
|
||||
if [ ! -f "$IMAGES" ]; then
|
||||
echo "Building image bundle..."
|
||||
mkdir -p "$SCRIPT_DIR/dist"
|
||||
docker save \
|
||||
lthn/chain:testnet \
|
||||
lthn/explorer:testnet \
|
||||
lthn/trade-api:testnet \
|
||||
lthn/trade-frontend:testnet \
|
||||
lthn/pool:testnet \
|
||||
lthn/lns:testnet \
|
||||
lthn/docs:testnet \
|
||||
| gzip > "$IMAGES"
|
||||
fi
|
||||
|
||||
echo "Image bundle: $(du -h "$IMAGES" | cut -f1)"
|
||||
echo "Deploying to $REMOTE:$INSTALL_DIR"
|
||||
echo ""
|
||||
|
||||
# Create remote directory
|
||||
ssh "$REMOTE" "mkdir -p $INSTALL_DIR"
|
||||
|
||||
# Transfer files
|
||||
echo "Transferring config files..."
|
||||
scp "$SCRIPT_DIR/docker-compose.pull.yml" "$REMOTE:$INSTALL_DIR/"
|
||||
scp "$SCRIPT_DIR/.env.example" "$REMOTE:$INSTALL_DIR/"
|
||||
scp "$SCRIPT_DIR/pool-config.json" "$REMOTE:$INSTALL_DIR/"
|
||||
scp "$SCRIPT_DIR/health.sh" "$REMOTE:$INSTALL_DIR/"
|
||||
scp "$SCRIPT_DIR/lethean-testnet.service" "$REMOTE:$INSTALL_DIR/"
|
||||
scp "$SCRIPT_DIR/docker-compose.node.yml" "$REMOTE:$INSTALL_DIR/"
|
||||
scp "$SCRIPT_DIR/docker-compose.exit.yml" "$REMOTE:$INSTALL_DIR/"
|
||||
scp -r "$SCRIPT_DIR/demos/helpers" "$REMOTE:$INSTALL_DIR/demos/"
|
||||
|
||||
# Create .env if it doesn't exist — generate unique JWT secret
|
||||
ssh "$REMOTE" "
|
||||
if [ ! -f $INSTALL_DIR/.env ]; then
|
||||
cp $INSTALL_DIR/.env.example $INSTALL_DIR/.env
|
||||
JWT=\$(openssl rand -hex 32 2>/dev/null || head -c 64 /dev/urandom | xxd -p | tr -d '\n' | head -c 64)
|
||||
sed -i \"s/change-me-before-production/\$JWT/\" $INSTALL_DIR/.env
|
||||
echo 'Generated unique JWT secret'
|
||||
else
|
||||
echo '.env already exists — skipping'
|
||||
fi
|
||||
"
|
||||
|
||||
echo "Transferring images (~$(du -h "$IMAGES" | cut -f1))... this may take a while"
|
||||
scp "$IMAGES" "$REMOTE:/tmp/lethean-testnet-images.tar.gz"
|
||||
|
||||
echo "Loading images on remote..."
|
||||
ssh "$REMOTE" "docker load < /tmp/lethean-testnet-images.tar.gz && rm /tmp/lethean-testnet-images.tar.gz"
|
||||
|
||||
echo "Starting stack..."
|
||||
ssh "$REMOTE" "cd $INSTALL_DIR && docker compose -f docker-compose.pull.yml up -d"
|
||||
|
||||
echo ""
|
||||
echo "Deploy complete. Run health check:"
|
||||
echo " ssh $REMOTE 'cd $INSTALL_DIR && bash health.sh'"
|
||||
echo ""
|
||||
echo "Install systemd service (optional):"
|
||||
echo " ssh $REMOTE 'sudo cp $INSTALL_DIR/lethean-testnet.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable lethean-testnet'"
|
||||
203
docker/docker-compose.ecosystem.yml
Normal file
203
docker/docker-compose.ecosystem.yml
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
# Lethean Full Ecosystem
|
||||
# Chain node + wallet + explorer + trade + pool + LNS + docs
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.ecosystem.yml up -d
|
||||
#
|
||||
# URLs after startup:
|
||||
# Explorer: http://localhost:3335
|
||||
# Trade: http://localhost:3337
|
||||
# Trade API: http://localhost:3336
|
||||
# Pool API: http://localhost:2117
|
||||
# Pool Web: http://localhost:8888
|
||||
# LNS HTTP: http://localhost:5553
|
||||
# LNS DNS: localhost:5354
|
||||
# Docs: http://localhost:8098
|
||||
# Daemon RPC: http://localhost:46941
|
||||
# Wallet RPC: http://localhost:46944
|
||||
|
||||
services:
|
||||
# --- Chain ---
|
||||
daemon:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-daemon
|
||||
ports:
|
||||
- "46941:36941"
|
||||
- "46942:36942"
|
||||
volumes:
|
||||
- daemon-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--rpc-enable-admin-api
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
|
||||
wallet:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-wallet
|
||||
ports:
|
||||
- "46944:36944"
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint: >
|
||||
sh -c "
|
||||
if [ ! -f /wallet/main.wallet ]; then
|
||||
echo '' | lethean-wallet-cli --generate-new-wallet /wallet/main.wallet --password '' --daemon-address daemon:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli
|
||||
--wallet-file /wallet/main.wallet
|
||||
--password ''
|
||||
--daemon-address daemon:36941
|
||||
--rpc-bind-port 36944
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--do-pos-mining
|
||||
"
|
||||
depends_on:
|
||||
- daemon
|
||||
|
||||
# --- Explorer ---
|
||||
explorer-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: lthn-explorer-db
|
||||
environment:
|
||||
POSTGRES_USER: explorer
|
||||
POSTGRES_PASSWORD: explorer
|
||||
POSTGRES_DB: lethean_explorer
|
||||
volumes:
|
||||
- explorer-db:/var/lib/postgresql/data
|
||||
|
||||
explorer:
|
||||
build:
|
||||
context: ../../zano-upstream/zano-explorer-zarcanum
|
||||
container_name: lthn-explorer
|
||||
ports:
|
||||
- "3335:3335"
|
||||
environment:
|
||||
API: http://daemon:36941
|
||||
FRONTEND_API: http://localhost:3335
|
||||
SERVER_PORT: "3335"
|
||||
AUDITABLE_WALLET_API: http://daemon:36941
|
||||
PGUSER: explorer
|
||||
PGPASSWORD: explorer
|
||||
PGDATABASE: lethean_explorer
|
||||
PGHOST: explorer-db
|
||||
PGPORT: "5432"
|
||||
MEXC_API_URL: ""
|
||||
depends_on:
|
||||
- daemon
|
||||
- explorer-db
|
||||
|
||||
# --- Trade ---
|
||||
trade-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: lthn-trade-db
|
||||
environment:
|
||||
POSTGRES_USER: trade
|
||||
POSTGRES_PASSWORD: trade
|
||||
POSTGRES_DB: lethean_trade
|
||||
volumes:
|
||||
- trade-db:/var/lib/postgresql/data
|
||||
|
||||
trade-api:
|
||||
build:
|
||||
context: ../../zano-upstream/zano_trade_backend
|
||||
container_name: lthn-trade-api
|
||||
ports:
|
||||
- "3336:3336"
|
||||
environment:
|
||||
PORT: "3336"
|
||||
PGUSER: trade
|
||||
PGPASSWORD: trade
|
||||
PGDATABASE: lethean_trade
|
||||
PGHOST: trade-db
|
||||
PGPORT: "5432"
|
||||
JWT_SECRET: testnet-dev-secret
|
||||
DAEMON_RPC_URL: http://daemon:36941/json_rpc
|
||||
depends_on:
|
||||
- daemon
|
||||
- trade-db
|
||||
|
||||
trade-frontend:
|
||||
build:
|
||||
context: ../../zano-upstream/zano_trade_frontend
|
||||
container_name: lthn-trade-frontend
|
||||
ports:
|
||||
- "3337:30289"
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: http://trade-api:3336
|
||||
depends_on:
|
||||
- trade-api
|
||||
|
||||
# --- Mining Pool ---
|
||||
pool-redis:
|
||||
image: redis:7-alpine
|
||||
container_name: lthn-pool-redis
|
||||
restart: unless-stopped
|
||||
|
||||
pool:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: zano-upstream/zano-pool/Dockerfile
|
||||
container_name: lthn-pool
|
||||
ports:
|
||||
- "5555:5555"
|
||||
- "7777:7777"
|
||||
- "2117:2117"
|
||||
- "8888:8888"
|
||||
volumes:
|
||||
- ./pool-config.json:/pool/config.json:ro
|
||||
depends_on:
|
||||
- daemon
|
||||
- wallet
|
||||
- pool-redis
|
||||
|
||||
# --- LNS (Lethean Name Service) ---
|
||||
lns:
|
||||
build:
|
||||
context: ../../lns
|
||||
container_name: lthn-lns
|
||||
ports:
|
||||
- "5553:5553" # HTTP API
|
||||
- "5354:5354/udp" # DNS
|
||||
- "5354:5354/tcp" # DNS (TCP)
|
||||
environment:
|
||||
DAEMON_URL: http://daemon:36941
|
||||
HSD_URL: http://host.docker.internal:14037
|
||||
LNS_MODE: light
|
||||
LNS_HTTP_PORT: "5553"
|
||||
LNS_DNS_PORT: "5354"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
- daemon
|
||||
|
||||
# --- Docs ---
|
||||
docs:
|
||||
build:
|
||||
context: ../../zano-upstream/zano-docs
|
||||
container_name: lthn-docs
|
||||
ports:
|
||||
- "8098:80"
|
||||
|
||||
volumes:
|
||||
daemon-data:
|
||||
wallet-data:
|
||||
explorer-db:
|
||||
trade-db:
|
||||
186
docker/docker-compose.exit.yml
Normal file
186
docker/docker-compose.exit.yml
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# Lethean Exit Node
|
||||
# Run a VPN exit node and earn LTHN by providing bandwidth to the network.
|
||||
# Peers with Lethean gateways which route encrypted VPN traffic through your node.
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.example .env
|
||||
# # Set WALLET_PASSWORD, EXIT_PUBLIC_IP, and EXIT_NAME
|
||||
# docker compose -f docker-compose.exit.yml up -d
|
||||
#
|
||||
# What it does:
|
||||
# - Runs a full chain node (syncs blockchain)
|
||||
# - Runs a wallet with PoS staking
|
||||
# - Runs a WireGuard VPN server
|
||||
# - Registers as an exit node on-chain via alias
|
||||
# - Peers with gateway nodes for traffic routing
|
||||
# - Earns LTHN for bandwidth provided
|
||||
#
|
||||
# Requirements:
|
||||
# - Public IP address (or port forwarding on router)
|
||||
# - Open ports: 46942 (P2P), 51820/udp (WireGuard)
|
||||
# - Linux with Docker (WireGuard kernel module)
|
||||
#
|
||||
# Ports:
|
||||
# 46942 — P2P (chain peering)
|
||||
# 51820 — WireGuard VPN (UDP)
|
||||
# 8124 — Exit node management API (local only)
|
||||
|
||||
services:
|
||||
# --- Chain Node ---
|
||||
daemon:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-exit-daemon
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DAEMON_P2P_PORT:-46942}:36942"
|
||||
volumes:
|
||||
- chain-data:/data
|
||||
entrypoint:
|
||||
- lethean-chain-node
|
||||
command:
|
||||
- --data-dir
|
||||
- /data
|
||||
- --rpc-bind-ip
|
||||
- "0.0.0.0"
|
||||
- --rpc-bind-port
|
||||
- "36941"
|
||||
- --p2p-bind-port
|
||||
- "36942"
|
||||
- --rpc-enable-admin-api
|
||||
- --allow-local-ip
|
||||
- --log-level
|
||||
- "${DAEMON_LOG_LEVEL:-1}"
|
||||
- --disable-upnp
|
||||
- --rpc-ignore-offline
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:36941/json_rpc -d '{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getinfo\"}' -H 'Content-Type: application/json' | grep -q OK"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
networks:
|
||||
exit-net:
|
||||
|
||||
# --- Wallet (PoS staking + payment receipt) ---
|
||||
wallet:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-exit-wallet
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint:
|
||||
- sh
|
||||
- -c
|
||||
command:
|
||||
- |
|
||||
if [ ! -f /wallet/exit.wallet ]; then
|
||||
echo '${WALLET_PASSWORD:-}' | lethean-wallet-cli --generate-new-wallet /wallet/exit.wallet --password '${WALLET_PASSWORD:-}' --daemon-address daemon:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli \
|
||||
--wallet-file /wallet/exit.wallet \
|
||||
--password '${WALLET_PASSWORD:-}' \
|
||||
--daemon-address daemon:36941 \
|
||||
--rpc-bind-port 36944 \
|
||||
--rpc-bind-ip 0.0.0.0 \
|
||||
--do-pos-mining
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
exit-net:
|
||||
|
||||
# --- WireGuard VPN Exit ---
|
||||
wireguard:
|
||||
image: lscr.io/linuxserver/wireguard:latest
|
||||
container_name: lthn-exit-wireguard
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
TZ: ${TZ:-Europe/London}
|
||||
SERVERURL: ${EXIT_PUBLIC_IP:-auto}
|
||||
SERVERPORT: 51820
|
||||
PEERS: ${EXIT_MAX_PEERS:-25}
|
||||
PEERDNS: 1.1.1.1,1.0.0.1
|
||||
INTERNAL_SUBNET: 10.13.13.0
|
||||
ALLOWEDIPS: 0.0.0.0/0,::/0
|
||||
LOG_CONFS: "false"
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
volumes:
|
||||
- wireguard-config:/config
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
- net.ipv4.ip_forward=1
|
||||
networks:
|
||||
exit-net:
|
||||
|
||||
# --- Exit Node Controller ---
|
||||
# Manages gateway peering, alias registration, and payment verification
|
||||
controller:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-exit-controller
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- controller-data:/data
|
||||
entrypoint:
|
||||
- sh
|
||||
- -c
|
||||
command:
|
||||
- |
|
||||
echo "Lethean Exit Node Controller"
|
||||
echo "Waiting for daemon and wallet..."
|
||||
|
||||
# Wait for daemon
|
||||
until curl -sf http://daemon:36941/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' -H 'Content-Type: application/json' > /dev/null 2>&1; do
|
||||
sleep 5
|
||||
done
|
||||
echo "Daemon ready"
|
||||
|
||||
# Wait for wallet
|
||||
until curl -sf http://wallet:36944/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json' > /dev/null 2>&1; do
|
||||
sleep 5
|
||||
done
|
||||
echo "Wallet ready"
|
||||
|
||||
# Get wallet address
|
||||
ADDR=$$(curl -sf http://wallet:36944/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' -H 'Content-Type: application/json' | grep -oP '"address":"[^"]+' | cut -d'"' -f4)
|
||||
echo "Exit node wallet: $$ADDR"
|
||||
|
||||
# Get chain height
|
||||
HEIGHT=$$(curl -sf http://daemon:36941/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' -H 'Content-Type: application/json' | grep -oP '"height":[0-9]+' | cut -d: -f2)
|
||||
echo "Chain height: $$HEIGHT"
|
||||
|
||||
# Register alias if EXIT_NAME is set and we have balance
|
||||
if [ -n "${EXIT_NAME:-}" ]; then
|
||||
echo "Exit node name: ${EXIT_NAME}"
|
||||
echo "To register on-chain: send 1 LTHN to this wallet, then alias will auto-register"
|
||||
echo "Alias comment: v=lthn1;type=exit;cap=vpn,proxy;ip=${EXIT_PUBLIC_IP:-auto}"
|
||||
fi
|
||||
|
||||
# Status loop
|
||||
while true; do
|
||||
BALANCE=$$(curl -sf http://wallet:36944/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' -H 'Content-Type: application/json' | grep -oP '"balance":[0-9]+' | cut -d: -f2)
|
||||
HEIGHT=$$(curl -sf http://daemon:36941/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' -H 'Content-Type: application/json' | grep -oP '"height":[0-9]+' | cut -d: -f2)
|
||||
WG_PEERS=$$(curl -sf http://wireguard:51820 2>/dev/null | wc -l || echo 0)
|
||||
echo "$$(date +%H:%M:%S) height=$$HEIGHT balance=$$(echo "scale=4; $$BALANCE/1000000000000" | bc 2>/dev/null || echo $$BALANCE) peers=$$WG_PEERS"
|
||||
sleep 60
|
||||
done
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
exit-net:
|
||||
|
||||
networks:
|
||||
exit-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
chain-data:
|
||||
wallet-data:
|
||||
wireguard-config:
|
||||
controller-data:
|
||||
7
docker/docker-compose.local.yml
Normal file
7
docker/docker-compose.local.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Local override — mounts existing testnet chain data
|
||||
# Usage: docker compose -f docker-compose.pull.yml -f docker-compose.local.yml up -d
|
||||
|
||||
services:
|
||||
daemon:
|
||||
volumes:
|
||||
- /opt/lethean/testnet-data:/data
|
||||
87
docker/docker-compose.node.yml
Normal file
87
docker/docker-compose.node.yml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Lethean Home Node
|
||||
# A self-contained chain node for home users who want to support the network.
|
||||
# Syncs the chain, runs a wallet with PoS staking, and exposes P2P for peering.
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.example .env # edit WALLET_PASSWORD at minimum
|
||||
# docker compose -f docker-compose.node.yml up -d
|
||||
#
|
||||
# What it does:
|
||||
# - Syncs and validates the Lethean blockchain
|
||||
# - Peers with other nodes (P2P port 46942)
|
||||
# - Runs a wallet that stakes automatically (PoS mining)
|
||||
# - Optionally mines with ProgPoWZ (connect external GPU miner)
|
||||
#
|
||||
# Earnings:
|
||||
# - PoS staking rewards (proportional to balance)
|
||||
# - PoW block rewards if mining
|
||||
#
|
||||
# Ports:
|
||||
# 46941 — Daemon RPC (local tools)
|
||||
# 46942 — P2P (open this on your router for full node)
|
||||
# 46944 — Wallet RPC (local tools)
|
||||
|
||||
services:
|
||||
daemon:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-node
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DAEMON_RPC_PORT:-46941}:36941"
|
||||
- "${DAEMON_P2P_PORT:-46942}:36942"
|
||||
volumes:
|
||||
- chain-data:/data
|
||||
entrypoint:
|
||||
- lethean-chain-node
|
||||
command:
|
||||
- --data-dir
|
||||
- /data
|
||||
- --rpc-bind-ip
|
||||
- "0.0.0.0"
|
||||
- --rpc-bind-port
|
||||
- "36941"
|
||||
- --p2p-bind-port
|
||||
- "36942"
|
||||
- --rpc-enable-admin-api
|
||||
- --allow-local-ip
|
||||
- --log-level
|
||||
- "${DAEMON_LOG_LEVEL:-1}"
|
||||
- --disable-upnp
|
||||
- --rpc-ignore-offline
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:36941/json_rpc -d '{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getinfo\"}' -H 'Content-Type: application/json' | grep -q OK"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
wallet:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-wallet
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WALLET_RPC_PORT:-46944}:36944"
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint:
|
||||
- sh
|
||||
- -c
|
||||
command:
|
||||
- |
|
||||
if [ ! -f /wallet/node.wallet ]; then
|
||||
echo '${WALLET_PASSWORD:-}' | lethean-wallet-cli --generate-new-wallet /wallet/node.wallet --password '${WALLET_PASSWORD:-}' --daemon-address daemon:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli \
|
||||
--wallet-file /wallet/node.wallet \
|
||||
--password '${WALLET_PASSWORD:-}' \
|
||||
--daemon-address daemon:36941 \
|
||||
--rpc-bind-port 36944 \
|
||||
--rpc-bind-ip 0.0.0.0 \
|
||||
--do-pos-mining
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
chain-data:
|
||||
wallet-data:
|
||||
114
docker/docker-compose.nodes.yml
Normal file
114
docker/docker-compose.nodes.yml
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Lethean Chain Nodes Only — minimal network simulation
|
||||
# 3 nodes + miner, no explorer/trade (for testing consensus, HF activation, reorgs)
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.nodes.yml up -d
|
||||
# # Start mining on node1:
|
||||
# docker exec lthn-node1 curl -X POST http://127.0.0.1:36941/start_mining \
|
||||
# -H 'Content-Type: application/json' \
|
||||
# -d '{"miner_address":"YOUR_iTHN_ADDRESS","threads_count":2}'
|
||||
# # Check sync status:
|
||||
# for n in 1 2 3; do
|
||||
# echo -n "node$n: "; docker exec lthn-node$n curl -s http://127.0.0.1:36941/json_rpc \
|
||||
# -H 'Content-Type: application/json' \
|
||||
# -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['height'])"
|
||||
# done
|
||||
|
||||
services:
|
||||
node1:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-node1
|
||||
ports:
|
||||
- "46941:36941"
|
||||
- "46942:36942"
|
||||
volumes:
|
||||
- node1-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--rpc-enable-admin-api
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
networks:
|
||||
lthn-net:
|
||||
ipv4_address: 172.29.0.10
|
||||
|
||||
node2:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-node2
|
||||
ports:
|
||||
- "46951:36941"
|
||||
volumes:
|
||||
- node2-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--add-exclusive-node 172.29.0.10:36942
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
depends_on:
|
||||
- node1
|
||||
networks:
|
||||
lthn-net:
|
||||
ipv4_address: 172.29.0.11
|
||||
|
||||
node3:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-node3
|
||||
ports:
|
||||
- "46961:36941"
|
||||
volumes:
|
||||
- node3-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--add-exclusive-node 172.29.0.10:36942
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
depends_on:
|
||||
- node1
|
||||
networks:
|
||||
lthn-net:
|
||||
ipv4_address: 172.29.0.12
|
||||
|
||||
networks:
|
||||
lthn-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.29.0.0/16
|
||||
|
||||
volumes:
|
||||
node1-data:
|
||||
node2-data:
|
||||
node3-data:
|
||||
235
docker/docker-compose.pull.yml
Normal file
235
docker/docker-compose.pull.yml
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
# Lethean Testnet Ecosystem
|
||||
# Full node + wallet + explorer + trade + pool + LNS
|
||||
#
|
||||
# Quick start:
|
||||
# cp .env.example .env # edit passwords/hostname
|
||||
# docker compose -f docker-compose.pull.yml up -d
|
||||
# docker compose -f docker-compose.pull.yml logs -f daemon
|
||||
#
|
||||
# Services:
|
||||
# Explorer: http://${PUBLIC_HOST:-localhost}:3335
|
||||
# Trade: http://${PUBLIC_HOST:-localhost}:3338
|
||||
# Trade API: http://${PUBLIC_HOST:-localhost}:3336
|
||||
# Pool API: http://${PUBLIC_HOST:-localhost}:2117
|
||||
# LNS HTTP: http://${PUBLIC_HOST:-localhost}:5553
|
||||
# Docs: http://${PUBLIC_HOST:-localhost}:8099
|
||||
# Daemon RPC: http://${PUBLIC_HOST:-localhost}:46941
|
||||
# Wallet RPC: http://${PUBLIC_HOST:-localhost}:46944
|
||||
|
||||
services:
|
||||
# --- Chain ---
|
||||
daemon:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-daemon
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DAEMON_RPC_PORT:-46941}:36941"
|
||||
- "${DAEMON_P2P_PORT:-46942}:36942"
|
||||
volumes:
|
||||
- chain-data:/data
|
||||
entrypoint:
|
||||
- lethean-chain-node
|
||||
command:
|
||||
- --data-dir
|
||||
- /data
|
||||
- --rpc-bind-ip
|
||||
- "0.0.0.0"
|
||||
- --rpc-bind-port
|
||||
- "36941"
|
||||
- --p2p-bind-port
|
||||
- "36942"
|
||||
- --rpc-enable-admin-api
|
||||
- --allow-local-ip
|
||||
- --log-level
|
||||
- "${DAEMON_LOG_LEVEL:-1}"
|
||||
- --disable-upnp
|
||||
- --rpc-ignore-offline
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:36941/json_rpc -d '{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getinfo\"}' -H 'Content-Type: application/json' | grep -q OK"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
wallet:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-wallet
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WALLET_RPC_PORT:-46944}:36944"
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint:
|
||||
- sh
|
||||
- -c
|
||||
command:
|
||||
- |
|
||||
if [ ! -f /wallet/main.wallet ]; then
|
||||
echo '${WALLET_PASSWORD:-}' | lethean-wallet-cli --generate-new-wallet /wallet/main.wallet --password '${WALLET_PASSWORD:-}' --daemon-address daemon:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli \
|
||||
--wallet-file /wallet/main.wallet \
|
||||
--password '${WALLET_PASSWORD:-}' \
|
||||
--daemon-address daemon:36941 \
|
||||
--rpc-bind-port 36944 \
|
||||
--rpc-bind-ip 0.0.0.0 \
|
||||
--do-pos-mining
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
|
||||
# --- Explorer ---
|
||||
explorer-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: lthn-explorer-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${EXPLORER_DB_USER:-explorer}
|
||||
POSTGRES_PASSWORD: ${EXPLORER_DB_PASS:-explorer}
|
||||
POSTGRES_DB: ${EXPLORER_DB_NAME:-lethean_explorer}
|
||||
volumes:
|
||||
- explorer-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${EXPLORER_DB_USER:-explorer}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
explorer:
|
||||
image: lthn/explorer:testnet
|
||||
container_name: lthn-explorer
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${EXPLORER_PORT:-3335}:3335"
|
||||
environment:
|
||||
API: http://daemon:36941
|
||||
FRONTEND_API: http://${PUBLIC_HOST:-localhost}:${EXPLORER_PORT:-3335}
|
||||
SERVER_PORT: "3335"
|
||||
AUDITABLE_WALLET_API: http://daemon:36941
|
||||
PGUSER: ${EXPLORER_DB_USER:-explorer}
|
||||
PGPASSWORD: ${EXPLORER_DB_PASS:-explorer}
|
||||
PGDATABASE: ${EXPLORER_DB_NAME:-lethean_explorer}
|
||||
PGHOST: explorer-db
|
||||
PGPORT: "5432"
|
||||
MEXC_API_URL: ""
|
||||
ASSETS_WHITELIST_URL: ""
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
explorer-db:
|
||||
condition: service_healthy
|
||||
|
||||
# --- Trade ---
|
||||
trade-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: lthn-trade-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${TRADE_DB_USER:-trade}
|
||||
POSTGRES_PASSWORD: ${TRADE_DB_PASS:-trade}
|
||||
POSTGRES_DB: ${TRADE_DB_NAME:-lethean_trade}
|
||||
volumes:
|
||||
- trade-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${TRADE_DB_USER:-trade}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
trade-api:
|
||||
image: lthn/trade-api:testnet
|
||||
container_name: lthn-trade-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${TRADE_API_PORT:-3336}:3336"
|
||||
environment:
|
||||
PORT: "3336"
|
||||
PGUSER: ${TRADE_DB_USER:-trade}
|
||||
PGPASSWORD: ${TRADE_DB_PASS:-trade}
|
||||
PGDATABASE: ${TRADE_DB_NAME:-lethean_trade}
|
||||
PGHOST: trade-db
|
||||
PGPORT: "5432"
|
||||
JWT_SECRET: ${JWT_SECRET:-change-me-before-production}
|
||||
DAEMON_RPC_URL: http://daemon:36941/json_rpc
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
trade-db:
|
||||
condition: service_healthy
|
||||
|
||||
trade-frontend:
|
||||
image: lthn/trade-frontend:testnet
|
||||
container_name: lthn-trade-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${TRADE_FRONTEND_PORT:-3338}:30289"
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: http://${PUBLIC_HOST:-localhost}:${TRADE_API_PORT:-3336}
|
||||
API_URL: http://trade-api:3336
|
||||
depends_on:
|
||||
- trade-api
|
||||
|
||||
# --- Mining Pool ---
|
||||
pool-redis:
|
||||
image: redis:7-alpine
|
||||
container_name: lthn-pool-redis
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
pool:
|
||||
image: lthn/pool:testnet
|
||||
container_name: lthn-pool
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${POOL_STRATUM_PORT:-5555}:5555"
|
||||
- "${POOL_API_PORT:-2117}:2117"
|
||||
- "7777:7777"
|
||||
- "8888:8888"
|
||||
volumes:
|
||||
- ./pool-config.json:/pool/config.json:ro
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
wallet:
|
||||
condition: service_started
|
||||
pool-redis:
|
||||
condition: service_healthy
|
||||
|
||||
# --- LNS (Lethean Name Service) ---
|
||||
lns:
|
||||
image: lthn/lns:testnet
|
||||
container_name: lthn-lns
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${LNS_HTTP_PORT:-5553}:5553"
|
||||
- "${LNS_DNS_PORT:-5354}:5354/udp"
|
||||
- "${LNS_DNS_PORT:-5354}:5354/tcp"
|
||||
environment:
|
||||
DAEMON_URL: http://daemon:36941
|
||||
HSD_URL: http://host.docker.internal:14037
|
||||
LNS_MODE: light
|
||||
LNS_HTTP_PORT: "5553"
|
||||
LNS_DNS_PORT: "5354"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
|
||||
# --- Docs ---
|
||||
docs:
|
||||
image: lthn/docs:testnet
|
||||
container_name: lthn-docs
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DOCS_PORT:-8099}:80"
|
||||
|
||||
volumes:
|
||||
chain-data:
|
||||
wallet-data:
|
||||
explorer-db:
|
||||
trade-db:
|
||||
221
docker/docker-compose.testnet.yml
Normal file
221
docker/docker-compose.testnet.yml
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
# Lethean Testnet Network Simulation
|
||||
# Spins up a 3-node testnet with explorer, wallet RPC, and trade services
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.testnet.yml up -d
|
||||
# docker compose -f docker-compose.testnet.yml logs -f node1
|
||||
# docker compose -f docker-compose.testnet.yml down -v
|
||||
#
|
||||
# Nodes discover each other via exclusive-node flags.
|
||||
# Node1 is the seed node, Node2 and Node3 connect to it.
|
||||
|
||||
services:
|
||||
# === Blockchain Nodes ===
|
||||
|
||||
node1:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-node1
|
||||
ports:
|
||||
- "46941:36941" # RPC
|
||||
- "46942:36942" # P2P
|
||||
volumes:
|
||||
- node1-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--rpc-enable-admin-api
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.10
|
||||
|
||||
node2:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-node2
|
||||
volumes:
|
||||
- node2-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--add-exclusive-node 172.28.0.10:36942
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
depends_on:
|
||||
- node1
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.11
|
||||
|
||||
node3:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-node3
|
||||
volumes:
|
||||
- node3-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--add-exclusive-node 172.28.0.10:36942
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
depends_on:
|
||||
- node1
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.12
|
||||
|
||||
# === Wallet RPC ===
|
||||
|
||||
wallet:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-wallet
|
||||
ports:
|
||||
- "46944:36944" # Wallet RPC
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint: >
|
||||
sh -c "
|
||||
if [ ! -f /wallet/testnet.wallet ]; then
|
||||
echo '' | lethean-wallet-cli --generate-new-wallet /wallet/testnet.wallet --password '' --daemon-address node1:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli
|
||||
--wallet-file /wallet/testnet.wallet
|
||||
--password ''
|
||||
--daemon-address node1:36941
|
||||
--rpc-bind-port 36944
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--do-pos-mining
|
||||
"
|
||||
depends_on:
|
||||
- node1
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.20
|
||||
|
||||
# === Explorer ===
|
||||
|
||||
explorer-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: lthn-explorer-db
|
||||
environment:
|
||||
POSTGRES_USER: explorer
|
||||
POSTGRES_PASSWORD: explorer
|
||||
POSTGRES_DB: lethean_explorer
|
||||
volumes:
|
||||
- explorer-db-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.30
|
||||
|
||||
explorer:
|
||||
build:
|
||||
context: ../../lthn/zano-upstream/zano-explorer-zarcanum
|
||||
dockerfile: Dockerfile
|
||||
container_name: lthn-explorer
|
||||
ports:
|
||||
- "3335:3335"
|
||||
environment:
|
||||
API: http://node1:36941
|
||||
FRONTEND_API: http://127.0.0.1:3335
|
||||
SERVER_PORT: "3335"
|
||||
AUDITABLE_WALLET_API: http://node1:36941
|
||||
PGUSER: explorer
|
||||
PGPASSWORD: explorer
|
||||
PGDATABASE: lethean_explorer
|
||||
PGHOST: explorer-db
|
||||
PGPORT: "5432"
|
||||
MEXC_API_URL: ""
|
||||
depends_on:
|
||||
- node1
|
||||
- explorer-db
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.31
|
||||
|
||||
# === Trade Backend ===
|
||||
|
||||
trade-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: lthn-trade-db
|
||||
environment:
|
||||
POSTGRES_USER: trade
|
||||
POSTGRES_PASSWORD: trade
|
||||
POSTGRES_DB: lethean_trade
|
||||
volumes:
|
||||
- trade-db-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.40
|
||||
|
||||
trade-api:
|
||||
build:
|
||||
context: ../../lthn/zano-upstream/zano_trade_backend
|
||||
container_name: lthn-trade-api
|
||||
ports:
|
||||
- "3336:3336"
|
||||
environment:
|
||||
PORT: "3336"
|
||||
PGUSER: trade
|
||||
PGPASSWORD: trade
|
||||
PGDATABASE: lethean_trade
|
||||
PGHOST: trade-db
|
||||
PGPORT: "5432"
|
||||
JWT_SECRET: testnet-dev-secret
|
||||
DAEMON_RPC_URL: http://node1:36941/json_rpc
|
||||
depends_on:
|
||||
- node1
|
||||
- trade-db
|
||||
networks:
|
||||
lthn-testnet:
|
||||
ipv4_address: 172.28.0.41
|
||||
|
||||
networks:
|
||||
lthn-testnet:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.28.0.0/16
|
||||
|
||||
volumes:
|
||||
node1-data:
|
||||
node2-data:
|
||||
node3-data:
|
||||
wallet-data:
|
||||
explorer-db-data:
|
||||
trade-db-data:
|
||||
126
docker/docker-compose.vpn.yml
Normal file
126
docker/docker-compose.vpn.yml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# Lethean VPN Stack — sandboxed legacy Python + WireGuard
|
||||
# Chain node + wallet + VPN dispatcher + WireGuard gateway
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.vpn.yml up -d
|
||||
#
|
||||
# This sandboxes the legacy Python VPN code inside containers
|
||||
# until the CoreGO replacement is ready.
|
||||
|
||||
services:
|
||||
# Chain daemon (testnet)
|
||||
daemon:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-vpn-daemon
|
||||
volumes:
|
||||
- daemon-data:/data
|
||||
command: >
|
||||
lethean-chain-node
|
||||
--data-dir /data
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
--rpc-bind-port 36941
|
||||
--p2p-bind-port 36942
|
||||
--rpc-enable-admin-api
|
||||
--allow-local-ip
|
||||
--log-level 1
|
||||
--disable-upnp
|
||||
networks:
|
||||
vpn-net:
|
||||
ipv4_address: 172.31.0.10
|
||||
|
||||
# Wallet RPC (for payment processing)
|
||||
wallet:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: utils/docker/lthn-chain/Dockerfile
|
||||
target: chain-service
|
||||
args:
|
||||
BUILD_TESTNET: 1
|
||||
BUILD_THREADS: 4
|
||||
container_name: lthn-vpn-wallet
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint: >
|
||||
sh -c "
|
||||
if [ ! -f /wallet/vpn.wallet ]; then
|
||||
echo '' | lethean-wallet-cli --generate-new-wallet /wallet/vpn.wallet --password '' --daemon-address daemon:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli
|
||||
--wallet-file /wallet/vpn.wallet
|
||||
--password ''
|
||||
--daemon-address daemon:36941
|
||||
--rpc-bind-port 36944
|
||||
--rpc-bind-ip 0.0.0.0
|
||||
"
|
||||
depends_on:
|
||||
- daemon
|
||||
networks:
|
||||
vpn-net:
|
||||
ipv4_address: 172.31.0.20
|
||||
|
||||
# VPN Dispatcher (legacy Python, sandboxed)
|
||||
dispatcher:
|
||||
build:
|
||||
context: ../../lthn/lthn-app-vpn
|
||||
container_name: lthn-vpn-dispatcher
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
DAEMON_HOST: daemon
|
||||
DAEMON_RPC_PORT: "36941"
|
||||
MODE: server
|
||||
ports:
|
||||
- "8124:8124" # Server management API
|
||||
depends_on:
|
||||
- daemon
|
||||
- wallet
|
||||
networks:
|
||||
vpn-net:
|
||||
ipv4_address: 172.31.0.30
|
||||
|
||||
# WireGuard Gateway
|
||||
wireguard:
|
||||
image: lscr.io/linuxserver/wireguard:latest
|
||||
container_name: lthn-vpn-wireguard
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
TZ: Europe/London
|
||||
SERVERURL: auto
|
||||
SERVERPORT: 51820
|
||||
PEERS: 10
|
||||
PEERDNS: 1.1.1.1
|
||||
INTERNAL_SUBNET: 10.13.13.0
|
||||
ALLOWEDIPS: 0.0.0.0/0,::/0
|
||||
LOG_CONFS: "false"
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
volumes:
|
||||
- wireguard-config:/config
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
- net.ipv4.ip_forward=1
|
||||
networks:
|
||||
vpn-net:
|
||||
ipv4_address: 172.31.0.40
|
||||
|
||||
networks:
|
||||
vpn-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.31.0.0/24
|
||||
|
||||
volumes:
|
||||
daemon-data:
|
||||
wallet-data:
|
||||
wireguard-config:
|
||||
58
docker/health.sh
Executable file
58
docker/health.sh
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
# Lethean Testnet Health Check
|
||||
# Run: bash health.sh
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
check() {
|
||||
local name="$1"
|
||||
local url="$2"
|
||||
local code
|
||||
code=$(curl -sf -o /dev/null -w "%{http_code}" "$url" 2>/dev/null)
|
||||
if [ "$code" = "200" ] || [ "$code" = "307" ] || [ "$code" = "404" ]; then
|
||||
printf " ${GREEN}%-16s${NC} %s ${YELLOW}(HTTP %s)${NC}\n" "$name" "$url" "$code"
|
||||
else
|
||||
printf " ${RED}%-16s${NC} %s ${RED}(DOWN)${NC}\n" "$name" "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
rpc() {
|
||||
local name="$1"
|
||||
local url="$2"
|
||||
local method="$3"
|
||||
local result
|
||||
result=$(curl -sf "$url" -d "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"$method\"}" -H 'Content-Type: application/json' 2>/dev/null)
|
||||
if [ -n "$result" ]; then
|
||||
printf " ${GREEN}%-16s${NC} %s\n" "$name" "$(echo "$result" | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin).get('result',{})
|
||||
if 'height' in d:
|
||||
hf = d.get('is_hardfok_active',[])
|
||||
active = sum(1 for h in hf if h)
|
||||
print(f'height={d[\"height\"]}, HF0-{active-1} active, status={d.get(\"status\",\"?\")}')
|
||||
elif 'balance' in d:
|
||||
print(f'{d[\"balance\"]/1e12:.4f} LTHN ({d[\"unlocked_balance\"]/1e12:.4f} unlocked)')
|
||||
else:
|
||||
print(json.dumps(d)[:80])
|
||||
" 2>/dev/null)"
|
||||
else
|
||||
printf " ${RED}%-16s${NC} %s ${RED}(DOWN)${NC}\n" "$name" "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo " Lethean Testnet Health Check"
|
||||
echo " $(date)"
|
||||
echo " ---"
|
||||
rpc "Daemon" "http://localhost:${DAEMON_RPC_PORT:-46941}/json_rpc" "getinfo"
|
||||
rpc "Wallet" "http://localhost:${WALLET_RPC_PORT:-46944}/json_rpc" "getbalance"
|
||||
check "Explorer" "http://localhost:${EXPLORER_PORT:-3335}/"
|
||||
check "Trade API" "http://localhost:${TRADE_API_PORT:-3336}/"
|
||||
check "Trade Web" "http://localhost:${TRADE_FRONTEND_PORT:-3338}/"
|
||||
check "Pool API" "http://localhost:${POOL_API_PORT:-2117}/stats"
|
||||
check "LNS" "http://localhost:${LNS_HTTP_PORT:-5553}/"
|
||||
check "Docs" "http://localhost:${DOCS_PORT:-8099}/"
|
||||
echo ""
|
||||
15
docker/lethean-testnet.service
Normal file
15
docker/lethean-testnet.service
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=Lethean Testnet Ecosystem
|
||||
Requires=docker.service
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=/opt/lethean/docker
|
||||
ExecStart=/usr/bin/docker compose -f docker-compose.pull.yml up -d
|
||||
ExecStop=/usr/bin/docker compose -f docker-compose.pull.yml down
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
52
docker/lethean/.env
Normal file
52
docker/lethean/.env
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Lethean Testnet Configuration
|
||||
# Copy to .env and customise before running:
|
||||
# cp .env.example .env
|
||||
# docker compose -f docker-compose.pull.yml up -d
|
||||
|
||||
# Public hostname (used by explorer and trade frontend)
|
||||
PUBLIC_HOST=localhost
|
||||
|
||||
# Wallet password (empty = no password, change for production)
|
||||
WALLET_PASSWORD=my-secure-pass
|
||||
|
||||
# Trade API JWT secret (CHANGE THIS for production)
|
||||
JWT_SECRET=change-me-before-production
|
||||
|
||||
# Explorer database
|
||||
EXPLORER_DB_USER=explorer
|
||||
EXPLORER_DB_PASS=explorer
|
||||
EXPLORER_DB_NAME=lethean_explorer
|
||||
|
||||
# Trade database
|
||||
TRADE_DB_USER=trade
|
||||
TRADE_DB_PASS=trade
|
||||
TRADE_DB_NAME=lethean_trade
|
||||
|
||||
# Daemon log level (0=minimal, 1=normal, 2=detailed, 3=trace)
|
||||
DAEMON_LOG_LEVEL=1
|
||||
|
||||
# Port overrides (defaults shown)
|
||||
# DAEMON_RPC_PORT=46941
|
||||
# DAEMON_P2P_PORT=46942
|
||||
# WALLET_RPC_PORT=46944
|
||||
# EXPLORER_PORT=3335
|
||||
# TRADE_API_PORT=3336
|
||||
# TRADE_FRONTEND_PORT=3338
|
||||
# POOL_STRATUM_PORT=5555
|
||||
# POOL_API_PORT=2117
|
||||
# LNS_HTTP_PORT=5553
|
||||
# LNS_DNS_PORT=5354
|
||||
|
||||
# === Exit Node Settings (docker-compose.exit.yml) ===
|
||||
|
||||
# Your public IP address (required for VPN exit node)
|
||||
# EXIT_PUBLIC_IP=auto
|
||||
|
||||
# Exit node name (registered as on-chain alias)
|
||||
# EXIT_NAME=my-exit-node
|
||||
|
||||
# Maximum VPN peers (WireGuard clients)
|
||||
# EXIT_MAX_PEERS=25
|
||||
|
||||
# Timezone
|
||||
# TZ=Europe/London
|
||||
52
docker/lethean/.env.example
Normal file
52
docker/lethean/.env.example
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Lethean Testnet Configuration
|
||||
# Copy to .env and customise before running:
|
||||
# cp .env.example .env
|
||||
# docker compose -f docker-compose.pull.yml up -d
|
||||
|
||||
# Public hostname (used by explorer and trade frontend)
|
||||
PUBLIC_HOST=localhost
|
||||
|
||||
# Wallet password (empty = no password, change for production)
|
||||
WALLET_PASSWORD=
|
||||
|
||||
# Trade API JWT secret (CHANGE THIS for production)
|
||||
JWT_SECRET=change-me-before-production
|
||||
|
||||
# Explorer database
|
||||
EXPLORER_DB_USER=explorer
|
||||
EXPLORER_DB_PASS=explorer
|
||||
EXPLORER_DB_NAME=lethean_explorer
|
||||
|
||||
# Trade database
|
||||
TRADE_DB_USER=trade
|
||||
TRADE_DB_PASS=trade
|
||||
TRADE_DB_NAME=lethean_trade
|
||||
|
||||
# Daemon log level (0=minimal, 1=normal, 2=detailed, 3=trace)
|
||||
DAEMON_LOG_LEVEL=1
|
||||
|
||||
# Port overrides (defaults shown)
|
||||
# DAEMON_RPC_PORT=46941
|
||||
# DAEMON_P2P_PORT=46942
|
||||
# WALLET_RPC_PORT=46944
|
||||
# EXPLORER_PORT=3335
|
||||
# TRADE_API_PORT=3336
|
||||
# TRADE_FRONTEND_PORT=3338
|
||||
# POOL_STRATUM_PORT=5555
|
||||
# POOL_API_PORT=2117
|
||||
# LNS_HTTP_PORT=5553
|
||||
# LNS_DNS_PORT=5354
|
||||
|
||||
# === Exit Node Settings (docker-compose.exit.yml) ===
|
||||
|
||||
# Your public IP address (required for VPN exit node)
|
||||
# EXIT_PUBLIC_IP=auto
|
||||
|
||||
# Exit node name (registered as on-chain alias)
|
||||
# EXIT_NAME=my-exit-node
|
||||
|
||||
# Maximum VPN peers (WireGuard clients)
|
||||
# EXIT_MAX_PEERS=25
|
||||
|
||||
# Timezone
|
||||
# TZ=Europe/London
|
||||
86
docker/lethean/docker-compose.node.yml
Normal file
86
docker/lethean/docker-compose.node.yml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Lethean Home Node
|
||||
# A self-contained chain node for home users who want to support the network.
|
||||
# Syncs the chain, runs a wallet with PoS staking, and exposes P2P for peering.
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.example .env # edit WALLET_PASSWORD at minimum
|
||||
# docker compose -f docker-compose.node.yml up -d
|
||||
#
|
||||
# What it does:
|
||||
# - Syncs and validates the Lethean blockchain
|
||||
# - Peers with other nodes (P2P port 46942)
|
||||
# - Runs a wallet that stakes automatically (PoS mining)
|
||||
# - Optionally mines with ProgPoWZ (connect external GPU miner)
|
||||
#
|
||||
# Earnings:
|
||||
# - PoS staking rewards (proportional to balance)
|
||||
# - PoW block rewards if mining
|
||||
#
|
||||
# Ports:
|
||||
# 46941 — Daemon RPC (local tools)
|
||||
# 46942 — P2P (open this on your router for full node)
|
||||
# 46944 — Wallet RPC (local tools)
|
||||
|
||||
services:
|
||||
daemon:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-node
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DAEMON_RPC_PORT:-46941}:36941"
|
||||
- "${DAEMON_P2P_PORT:-46942}:36942"
|
||||
volumes:
|
||||
- chain-data:/data
|
||||
entrypoint:
|
||||
- lethean-chain-node
|
||||
command:
|
||||
- --data-dir
|
||||
- /data
|
||||
- --rpc-bind-ip
|
||||
- "0.0.0.0"
|
||||
- --rpc-bind-port
|
||||
- "36941"
|
||||
- --p2p-bind-port
|
||||
- "36942"
|
||||
- --rpc-enable-admin-api
|
||||
- --allow-local-ip
|
||||
- --log-level
|
||||
- "${DAEMON_LOG_LEVEL:-1}"
|
||||
- --disable-upnp
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:36941/json_rpc -d '{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getinfo\"}' -H 'Content-Type: application/json' | grep -q OK"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
wallet:
|
||||
image: lthn/chain:testnet
|
||||
container_name: lthn-wallet
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WALLET_RPC_PORT:-46944}:36944"
|
||||
volumes:
|
||||
- wallet-data:/wallet
|
||||
entrypoint:
|
||||
- sh
|
||||
- -c
|
||||
command:
|
||||
- |
|
||||
if [ ! -f /wallet/node.wallet ]; then
|
||||
echo '${WALLET_PASSWORD:-}' | lethean-wallet-cli --generate-new-wallet /wallet/node.wallet --password '${WALLET_PASSWORD:-}' --daemon-address daemon:36941 --command exit;
|
||||
fi;
|
||||
lethean-wallet-cli \
|
||||
--wallet-file /wallet/node.wallet \
|
||||
--password '${WALLET_PASSWORD:-}' \
|
||||
--daemon-address daemon:36941 \
|
||||
--rpc-bind-port 36944 \
|
||||
--rpc-bind-ip 0.0.0.0 \
|
||||
--do-pos-mining
|
||||
depends_on:
|
||||
daemon:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
chain-data:
|
||||
wallet-data:
|
||||
301
docker/pool-config.json
Normal file
301
docker/pool-config.json
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
{
|
||||
"poolHost": "lethean.somewhere.com",
|
||||
"coin": "Lethean",
|
||||
"symbol": "LTHN",
|
||||
"coinUnits": 1000000000000,
|
||||
"coinDecimalPlaces": 12,
|
||||
"coinDifficultyTarget": 120,
|
||||
"blockchainExplorer": "http://localhost:3335/block/{id}",
|
||||
"transactionExplorer": "http://localhost:3335/transaction/{id}",
|
||||
"daemonType": "default",
|
||||
"cnAlgorithm": "progpowz",
|
||||
"cnVariant": 2,
|
||||
"cnBlobType": 0,
|
||||
"isRandomX": false,
|
||||
"includeHeight": false,
|
||||
"previousOffset": 7,
|
||||
"offset": 2,
|
||||
"isCryptonight": false,
|
||||
"reward": 1000000000000,
|
||||
"logging": {
|
||||
"files": {
|
||||
"level": "info",
|
||||
"directory": "logs",
|
||||
"flushInterval": 5,
|
||||
"prefix": "Lethean"
|
||||
},
|
||||
"console": {
|
||||
"level": "info",
|
||||
"colors": true
|
||||
}
|
||||
},
|
||||
"childPools": [],
|
||||
"poolServer": {
|
||||
"enabled": true,
|
||||
"mergedMining": false,
|
||||
"clusterForks": 3,
|
||||
"poolAddress": "iTHNUNiuu3VP1yy8xH2y5iQaABKXurdjqZmzFiBiyR4dKG3j6534e9jMriY6SM7PH8NibVwVWW1DWJfQEWnSjS8n3Wgx86pQpY",
|
||||
"intAddressPrefix": null,
|
||||
"blockRefreshInterval": 1000,
|
||||
"minerTimeout": 900,
|
||||
"sslCert": "cert.pem",
|
||||
"sslKey": "privkey.pem",
|
||||
"sslCA": "chain.pem",
|
||||
"ports": [
|
||||
{
|
||||
"port": 5555,
|
||||
"difficulty": 50000,
|
||||
"desc": "Low end hardware"
|
||||
},
|
||||
{
|
||||
"port": 7777,
|
||||
"difficulty": 500000,
|
||||
"desc": "Mid/high end hardware"
|
||||
},
|
||||
{
|
||||
"port": 8888,
|
||||
"difficulty": 5000000,
|
||||
"desc": "Nicehash, MRR"
|
||||
}
|
||||
],
|
||||
"varDiff": {
|
||||
"minDiff": 10000,
|
||||
"maxDiff": 5000000,
|
||||
"targetTime": 45,
|
||||
"retargetTime": 60,
|
||||
"variancePercent": 30,
|
||||
"maxJump": 100
|
||||
},
|
||||
"paymentId": {
|
||||
"addressSeparator": "+"
|
||||
},
|
||||
"fixedDiff": {
|
||||
"enabled": true,
|
||||
"addressSeparator": "."
|
||||
},
|
||||
"shareTrust": {
|
||||
"enabled": true,
|
||||
"min": 10,
|
||||
"stepDown": 3,
|
||||
"threshold": 10,
|
||||
"penalty": 30
|
||||
},
|
||||
"banning": {
|
||||
"enabled": true,
|
||||
"time": 600,
|
||||
"invalidPercent": 25,
|
||||
"checkThreshold": 30
|
||||
},
|
||||
"slushMining": {
|
||||
"enabled": false,
|
||||
"weight": 300,
|
||||
"blockTime": 60,
|
||||
"lastBlockCheckRate": 1
|
||||
}
|
||||
},
|
||||
"payments": {
|
||||
"enabled": true,
|
||||
"interval": 900,
|
||||
"maxAddresses": 5,
|
||||
"mixin": 10,
|
||||
"priority": 0,
|
||||
"transferFee": 10000000000,
|
||||
"dynamicTransferFee": true,
|
||||
"minerPayFee": true,
|
||||
"minPayment": 1000000000000,
|
||||
"maxPayment": 100000000000000,
|
||||
"maxTransactionAmount": 100000000000000,
|
||||
"denomination": 1000000000000
|
||||
},
|
||||
"blockUnlocker": {
|
||||
"enabled": true,
|
||||
"interval": 60,
|
||||
"depth": 10,
|
||||
"poolFee": 0.2,
|
||||
"soloFee": 0.2,
|
||||
"devDonation": 0.5,
|
||||
"networkFee": 0.0
|
||||
},
|
||||
"api": {
|
||||
"enabled": true,
|
||||
"hashrateWindow": 600,
|
||||
"updateInterval": 15,
|
||||
"bindIp": "0.0.0.0",
|
||||
"port": 2117,
|
||||
"blocks": 30,
|
||||
"payments": 30,
|
||||
"password": "password",
|
||||
"ssl": false,
|
||||
"sslPort": 2119,
|
||||
"sslCert": "cert.pem",
|
||||
"sslKey": "privkey.pem",
|
||||
"sslCA": "chain.pem",
|
||||
"trustProxyIP": true
|
||||
},
|
||||
"zmq": {
|
||||
"enabled": false,
|
||||
"host": "127.0.0.1",
|
||||
"port": 39995
|
||||
},
|
||||
"daemon": {
|
||||
"host": "daemon",
|
||||
"port": 36941
|
||||
},
|
||||
"wallet": {
|
||||
"host": "wallet",
|
||||
"port": 36944
|
||||
},
|
||||
"redis": {
|
||||
"host": "pool-redis",
|
||||
"port": 6379,
|
||||
"db": 11,
|
||||
"cleanupInterval": 15
|
||||
},
|
||||
"notifications": {
|
||||
"emailTemplate": "email_templates/default.txt",
|
||||
"emailSubject": {
|
||||
"emailAdded": "Your email was registered",
|
||||
"workerConnected": "Worker %WORKER_NAME% connected",
|
||||
"workerTimeout": "Worker %WORKER_NAME% stopped hashing",
|
||||
"workerBanned": "Worker %WORKER_NAME% banned",
|
||||
"blockFound": "Block %HEIGHT% found !",
|
||||
"blockUnlocked": "Block %HEIGHT% unlocked !",
|
||||
"blockOrphaned": "Block %HEIGHT% orphaned !",
|
||||
"payment": "We sent you a payment !"
|
||||
},
|
||||
"emailMessage": {
|
||||
"emailAdded": "Your email has been registered to receive pool notifications.",
|
||||
"workerConnected": "Your worker %WORKER_NAME% for address %MINER% is now connected from ip %IP%.",
|
||||
"workerTimeout": "Your worker %WORKER_NAME% for address %MINER% has stopped submitting hashes on %LAST_HASH%.",
|
||||
"workerBanned": "Your worker %WORKER_NAME% for address %MINER% has been banned.",
|
||||
"blockFound": "Block found at height %HEIGHT% by miner %MINER% on %TIME%. Waiting maturity.",
|
||||
"blockUnlocked": "Block mined at height %HEIGHT% with %REWARD% and %EFFORT% effort on %TIME%.",
|
||||
"blockOrphaned": "Block orphaned at height %HEIGHT% :(",
|
||||
"payment": "A payment of %AMOUNT% has been sent to %ADDRESS% wallet."
|
||||
},
|
||||
"telegramMessage": {
|
||||
"workerConnected": "Your worker _%WORKER_NAME%_ for address _%MINER%_ is now connected from ip _%IP%_.",
|
||||
"workerTimeout": "Your worker _%WORKER_NAME%_ for address _%MINER%_ has stopped submitting hashes on _%LAST_HASH%_.",
|
||||
"workerBanned": "Your worker _%WORKER_NAME%_ for address _%MINER%_ has been banned.",
|
||||
"blockFound": "*Block found at height* _%HEIGHT%_ *by miner* _%MINER%_*! Waiting maturity.*",
|
||||
"blockUnlocked": "*Block mined at height* _%HEIGHT%_ *with* _%REWARD%_ *and* _%EFFORT%_ *effort on* _%TIME%_*.*",
|
||||
"blockOrphaned": "*Block orphaned at height* _%HEIGHT%_ *:(*",
|
||||
"payment": "A payment of _%AMOUNT%_ has been sent."
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"enabled": false,
|
||||
"fromAddress": "your@email.com",
|
||||
"transport": "sendmail",
|
||||
"sendmail": {
|
||||
"path": "/usr/sbin/sendmail"
|
||||
},
|
||||
"smtp": {
|
||||
"host": "smtp.example.com",
|
||||
"port": 587,
|
||||
"secure": false,
|
||||
"auth": {
|
||||
"user": "username",
|
||||
"pass": "password"
|
||||
},
|
||||
"tls": {
|
||||
"rejectUnauthorized": false
|
||||
}
|
||||
},
|
||||
"mailgun": {
|
||||
"key": "your-private-key",
|
||||
"domain": "mg.yourdomain"
|
||||
}
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"botName": "",
|
||||
"token": "",
|
||||
"channel": "",
|
||||
"channelStats": {
|
||||
"enabled": false,
|
||||
"interval": 30
|
||||
},
|
||||
"botCommands": {
|
||||
"stats": "/stats",
|
||||
"report": "/report",
|
||||
"notify": "/notify",
|
||||
"blocks": "/blocks"
|
||||
}
|
||||
},
|
||||
"monitoring": {
|
||||
"daemon": {
|
||||
"checkInterval": 60,
|
||||
"rpcMethod": "getblockcount"
|
||||
},
|
||||
"wallet": {
|
||||
"checkInterval": 60,
|
||||
"rpcMethod": "getbalance"
|
||||
}
|
||||
},
|
||||
"prices": {
|
||||
"source": "tradeogre",
|
||||
"currency": "USD"
|
||||
},
|
||||
"charts": {
|
||||
"pool": {
|
||||
"hashrate": {
|
||||
"enabled": true,
|
||||
"updateInterval": 60,
|
||||
"stepInterval": 1800,
|
||||
"maximumPeriod": 86400
|
||||
},
|
||||
"miners": {
|
||||
"enabled": true,
|
||||
"updateInterval": 60,
|
||||
"stepInterval": 1800,
|
||||
"maximumPeriod": 86400
|
||||
},
|
||||
"workers": {
|
||||
"enabled": true,
|
||||
"updateInterval": 60,
|
||||
"stepInterval": 1800,
|
||||
"maximumPeriod": 86400
|
||||
},
|
||||
"difficulty": {
|
||||
"enabled": true,
|
||||
"updateInterval": 1800,
|
||||
"stepInterval": 10800,
|
||||
"maximumPeriod": 604800
|
||||
},
|
||||
"price": {
|
||||
"enabled": true,
|
||||
"updateInterval": 1800,
|
||||
"stepInterval": 10800,
|
||||
"maximumPeriod": 604800
|
||||
},
|
||||
"profit": {
|
||||
"enabled": true,
|
||||
"updateInterval": 1800,
|
||||
"stepInterval": 10800,
|
||||
"maximumPeriod": 604800
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"hashrate": {
|
||||
"enabled": true,
|
||||
"updateInterval": 180,
|
||||
"stepInterval": 1800,
|
||||
"maximumPeriod": 86400
|
||||
},
|
||||
"worker_hashrate": {
|
||||
"enabled": true,
|
||||
"updateInterval": 60,
|
||||
"stepInterval": 60,
|
||||
"maximumPeriod": 86400
|
||||
},
|
||||
"payments": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"blocks": {
|
||||
"enabled": true,
|
||||
"days": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
90
docker/test-network.sh
Executable file
90
docker/test-network.sh
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
#!/bin/bash
|
||||
# Lethean testnet network validation script
|
||||
# Tests: node sync, mining, wallet, block propagation
|
||||
#
|
||||
# Usage: ./test-network.sh [compose-file]
|
||||
# Default: docker-compose.nodes.yml
|
||||
|
||||
set -e
|
||||
COMPOSE=${1:-docker-compose.nodes.yml}
|
||||
|
||||
echo "=== Lethean Network Test ==="
|
||||
echo "Using: $COMPOSE"
|
||||
echo ""
|
||||
|
||||
# Check all nodes are running
|
||||
echo "1. Checking node health..."
|
||||
for n in 1 2 3; do
|
||||
port=$((46940 + (n-1)*10 + 1))
|
||||
height=$(curl -s -X POST http://127.0.0.1:$port/json_rpc \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin)['result']['height'])" 2>/dev/null)
|
||||
echo " node$n (:$port): height=$height"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "2. Checking P2P connectivity..."
|
||||
peers=$(curl -s -X POST http://127.0.0.1:46941/json_rpc \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' 2>/dev/null | \
|
||||
python3 -c "import sys,json; d=json.load(sys.stdin)['result']; print(f'out={d[\"outgoing_connections_count\"]} in={d[\"incoming_connections_count\"]}')" 2>/dev/null)
|
||||
echo " node1 peers: $peers"
|
||||
|
||||
echo ""
|
||||
echo "3. Testing getblocktemplate..."
|
||||
template=$(curl -s -X POST http://127.0.0.1:46941/json_rpc \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getblocktemplate","params":{"wallet_address":"iTHNUNiuu3VP1yy8xH2y5iQaABKXurdjqZmzFiBiyR4dKG3j6534e9jMriY6SM7PH8NibVwVWW1DWJfQEWnSjS8n3Wgx86pQpY"}}' 2>/dev/null | \
|
||||
python3 -c "import sys,json; r=json.load(sys.stdin)['result']; print(f'height={r[\"height\"]} diff={r[\"difficulty\"]}')" 2>/dev/null)
|
||||
echo " template: $template"
|
||||
|
||||
echo ""
|
||||
echo "4. Starting mining on node1..."
|
||||
curl -s -X POST http://127.0.0.1:46941/start_mining \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"miner_address":"iTHNUNiuu3VP1yy8xH2y5iQaABKXurdjqZmzFiBiyR4dKG3j6534e9jMriY6SM7PH8NibVwVWW1DWJfQEWnSjS8n3Wgx86pQpY","threads_count":4}' 2>/dev/null | python3 -m json.tool 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "5. Waiting 60s for blocks..."
|
||||
sleep 60
|
||||
|
||||
echo ""
|
||||
echo "6. Checking sync across nodes..."
|
||||
heights=""
|
||||
for n in 1 2 3; do
|
||||
port=$((46940 + (n-1)*10 + 1))
|
||||
h=$(curl -s -X POST http://127.0.0.1:$port/json_rpc \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin)['result']['height'])" 2>/dev/null)
|
||||
echo " node$n: height=$h"
|
||||
heights="$heights $h"
|
||||
done
|
||||
|
||||
# Check all nodes at same height
|
||||
unique=$(echo $heights | tr ' ' '\n' | sort -u | wc -l)
|
||||
if [ "$unique" -eq 1 ]; then
|
||||
echo ""
|
||||
echo " PASS: All nodes synced at same height"
|
||||
else
|
||||
echo ""
|
||||
echo " WARN: Nodes at different heights (propagation delay normal)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "7. Checking HF status..."
|
||||
curl -s -X POST http://127.0.0.1:46941/json_rpc \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' 2>/dev/null | \
|
||||
python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
hf=[i for i,v in enumerate(d['is_hardfok_active']) if v]
|
||||
print(f' Active HFs: {hf}')
|
||||
print(f' PoS allowed: {d[\"pos_allowed\"]}')
|
||||
print(f' Aliases: {d[\"alias_count\"]}')
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=== Test Complete ==="
|
||||
153
docker/tools/key-bridge-prototype.py
Normal file
153
docker/tools/key-bridge-prototype.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Lethean Key Bridge Prototype
|
||||
Derives an HNS sidechain secp256k1 keypair from a Lethean ed25519 spend key.
|
||||
|
||||
One seed phrase → both chains.
|
||||
|
||||
Lethean seed phrase
|
||||
↓
|
||||
ed25519 spend key (32 bytes)
|
||||
├── Main chain wallet (native CryptoNote)
|
||||
└── HKDF("lethean-hns-bridge", spend_key)
|
||||
↓
|
||||
secp256k1 private key (32 bytes)
|
||||
└── Sidechain wallet (derived)
|
||||
|
||||
Usage:
|
||||
python3 key-bridge-prototype.py <hex_spend_key>
|
||||
python3 key-bridge-prototype.py --from-wallet http://127.0.0.1:46944/json_rpc
|
||||
|
||||
Dependencies:
|
||||
pip install cryptography secp256k1 (or use hashlib for prototype)
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import sys
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
|
||||
def derive_sidechain_key(spend_key_hex: str) -> dict:
|
||||
"""
|
||||
Derive a secp256k1 private key from a Lethean ed25519 spend key.
|
||||
|
||||
Uses HKDF-like construction:
|
||||
prk = HMAC-SHA256(key="lethean-hns-bridge", msg=spend_key)
|
||||
secp256k1_key = HMAC-SHA256(key=prk, msg="hns-sidechain-v1" || 0x01)
|
||||
|
||||
The domain separation ensures:
|
||||
- Different derivation paths produce different keys
|
||||
- The ed25519 key cannot be recovered from the secp256k1 key
|
||||
- Future derivation paths (v2, v3) won't collide
|
||||
"""
|
||||
spend_key = bytes.fromhex(spend_key_hex)
|
||||
assert len(spend_key) == 32, f"Spend key must be 32 bytes, got {len(spend_key)}"
|
||||
|
||||
# Step 1: Extract — domain-separated PRK
|
||||
salt = b"lethean-hns-bridge"
|
||||
prk = hmac.new(salt, spend_key, hashlib.sha256).digest()
|
||||
|
||||
# Step 2: Expand — derive the secp256k1 key
|
||||
info = b"hns-sidechain-v1\x01"
|
||||
secp256k1_privkey = hmac.new(prk, info, hashlib.sha256).digest()
|
||||
|
||||
# secp256k1 requires the key to be in range [1, n-1]
|
||||
# n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
key_int = int.from_bytes(secp256k1_privkey, 'big')
|
||||
if key_int == 0 or key_int >= n:
|
||||
# Extremely unlikely but handle it — rehash
|
||||
secp256k1_privkey = hmac.new(prk, b"hns-sidechain-v1\x02", hashlib.sha256).digest()
|
||||
key_int = int.from_bytes(secp256k1_privkey, 'big') % (n - 1) + 1
|
||||
secp256k1_privkey = key_int.to_bytes(32, 'big')
|
||||
|
||||
# Derive additional keys for future use
|
||||
document_signing_key = hmac.new(prk, b"document-signing-v1\x01", hashlib.sha256).digest()
|
||||
audit_key = hmac.new(prk, b"audit-trail-v1\x01", hashlib.sha256).digest()
|
||||
|
||||
return {
|
||||
"source": {
|
||||
"type": "ed25519",
|
||||
"chain": "lethean-mainchain",
|
||||
"spend_key": spend_key_hex,
|
||||
},
|
||||
"derived": {
|
||||
"hns_sidechain": {
|
||||
"type": "secp256k1",
|
||||
"chain": "lethean-hns-sidechain",
|
||||
"private_key": secp256k1_privkey.hex(),
|
||||
"derivation": "HKDF(salt=lethean-hns-bridge, ikm=spend_key, info=hns-sidechain-v1)",
|
||||
},
|
||||
"document_signing": {
|
||||
"type": "ed25519",
|
||||
"purpose": "document hash timestamping",
|
||||
"private_key": document_signing_key.hex(),
|
||||
"derivation": "HKDF(salt=lethean-hns-bridge, ikm=spend_key, info=document-signing-v1)",
|
||||
},
|
||||
"audit": {
|
||||
"type": "ed25519",
|
||||
"purpose": "audit trail verification",
|
||||
"private_key": audit_key.hex(),
|
||||
"derivation": "HKDF(salt=lethean-hns-bridge, ikm=spend_key, info=audit-trail-v1)",
|
||||
},
|
||||
},
|
||||
"derivation_version": "1.0",
|
||||
"note": "All keys derived deterministically from the Lethean spend key. One seed, all chains and purposes.",
|
||||
}
|
||||
|
||||
|
||||
def get_spend_key_from_wallet(rpc_url: str) -> str:
|
||||
"""Fetch the spend key from a running wallet RPC."""
|
||||
payload = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": "0",
|
||||
"method": "get_wallet_info",
|
||||
"params": {}
|
||||
}).encode()
|
||||
|
||||
req = urllib.request.Request(
|
||||
rpc_url,
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=5) as resp:
|
||||
data = json.loads(resp.read())
|
||||
# The spend key isn't directly in get_wallet_info
|
||||
# We'd need a custom RPC or the seed to derive it
|
||||
# For prototype, use the address as a stand-in
|
||||
print("Note: Full implementation needs 'get_spend_key' RPC or seed phrase input")
|
||||
print(f"Wallet address: {data.get('result', {}).get('wi', {}).get('address', '?')}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error connecting to wallet: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
print("\nExample with test key:")
|
||||
test_key = "a" * 64 # 32 bytes of 0xAA
|
||||
result = derive_sidechain_key(test_key)
|
||||
print(json.dumps(result, indent=2))
|
||||
return
|
||||
|
||||
if sys.argv[1] == "--from-wallet":
|
||||
rpc_url = sys.argv[2] if len(sys.argv) > 2 else "http://127.0.0.1:46944/json_rpc"
|
||||
spend_key = get_spend_key_from_wallet(rpc_url)
|
||||
if not spend_key:
|
||||
print("\nUsing test key for demonstration:")
|
||||
spend_key = "7b9f1e2a3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7"
|
||||
else:
|
||||
spend_key = sys.argv[1]
|
||||
|
||||
result = derive_sidechain_key(spend_key)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
130
docker/tools/register-reserved.sh
Executable file
130
docker/tools/register-reserved.sh
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
#!/bin/bash
|
||||
# Lethean Reserved Name Registrar
|
||||
# Batch-registers protected aliases on the main chain to prevent squatting.
|
||||
#
|
||||
# Usage:
|
||||
# bash register-reserved.sh [names-file] [wallet-rpc] [batch-size] [delay]
|
||||
#
|
||||
# Defaults:
|
||||
# names-file: /tmp/protected-names-priority.txt
|
||||
# wallet-rpc: http://127.0.0.1:46944/json_rpc
|
||||
# batch-size: 10 (aliases per batch before checking balance)
|
||||
# delay: 2 (seconds between registrations)
|
||||
|
||||
NAMES_FILE="${1:-/tmp/protected-names-priority.txt}"
|
||||
WALLET_RPC="${2:-http://127.0.0.1:46944/json_rpc}"
|
||||
BATCH_SIZE="${3:-10}"
|
||||
DELAY="${4:-2}"
|
||||
COMMENT="v=lthn1;type=reserved;reason=hns-protected"
|
||||
LOG_FILE="/tmp/register-reserved.log"
|
||||
DONE_FILE="/tmp/registered-names.txt"
|
||||
|
||||
touch "$DONE_FILE"
|
||||
|
||||
get_balance() {
|
||||
curl -sf "$WALLET_RPC" -d '{"jsonrpc":"2.0","id":"0","method":"getbalance"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)['result']
|
||||
print(d['unlocked_balance'])
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
get_address() {
|
||||
curl -sf "$WALLET_RPC" -d '{"jsonrpc":"2.0","id":"0","method":"getaddress"}' \
|
||||
-H 'Content-Type: application/json' | python3 -c "
|
||||
import sys,json
|
||||
print(json.load(sys.stdin)['result']['address'])
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
register_alias() {
|
||||
local name="$1"
|
||||
local address="$2"
|
||||
|
||||
result=$(curl -sf "$WALLET_RPC" -d "{
|
||||
\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"register_alias\",
|
||||
\"params\":{
|
||||
\"alias\":\"$name\",
|
||||
\"address\":\"$address\",
|
||||
\"comment\":\"$COMMENT\"
|
||||
}
|
||||
}" -H 'Content-Type: application/json' 2>/dev/null)
|
||||
|
||||
if echo "$result" | grep -q '"result"'; then
|
||||
tx=$(echo "$result" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'].get('tx_hash','ok'))" 2>/dev/null)
|
||||
echo "$name" >> "$DONE_FILE"
|
||||
echo "$(date +%H:%M:%S) OK @$name ($tx)" | tee -a "$LOG_FILE"
|
||||
return 0
|
||||
else
|
||||
error=$(echo "$result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('error',{}).get('message','unknown')[:80])" 2>/dev/null)
|
||||
echo "$(date +%H:%M:%S) ERR @$name — $error" | tee -a "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Get wallet address
|
||||
ADDRESS=$(get_address)
|
||||
if [ -z "$ADDRESS" ]; then
|
||||
echo "ERROR: Can't connect to wallet at $WALLET_RPC"
|
||||
exit 1
|
||||
fi
|
||||
echo "Wallet: $ADDRESS"
|
||||
echo "Names file: $NAMES_FILE ($(wc -l < "$NAMES_FILE") names)"
|
||||
echo "Already registered: $(wc -l < "$DONE_FILE") names"
|
||||
echo "Comment: $COMMENT"
|
||||
echo "Batch: $BATCH_SIZE, Delay: ${DELAY}s"
|
||||
echo "Log: $LOG_FILE"
|
||||
echo "---"
|
||||
|
||||
BALANCE=$(get_balance)
|
||||
BALANCE_LTHN=$(echo "scale=2; $BALANCE / 1000000000000" | bc 2>/dev/null)
|
||||
echo "Balance: $BALANCE_LTHN LTHN"
|
||||
echo ""
|
||||
|
||||
COUNT=0
|
||||
REGISTERED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
|
||||
while IFS= read -r name; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$name" || "$name" == \#* ]] && continue
|
||||
|
||||
# Skip already registered
|
||||
if grep -qxF "$name" "$DONE_FILE" 2>/dev/null; then
|
||||
((SKIPPED++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check balance every batch
|
||||
if (( COUNT % BATCH_SIZE == 0 && COUNT > 0 )); then
|
||||
BALANCE=$(get_balance)
|
||||
BALANCE_LTHN=$(echo "scale=2; $BALANCE / 1000000000000" | bc 2>/dev/null)
|
||||
echo "--- Batch checkpoint: $BALANCE_LTHN LTHN, $REGISTERED registered, $FAILED failed ---"
|
||||
|
||||
# Stop if balance too low (need 1 LTHN + fee)
|
||||
if (( BALANCE < 1100000000000 )); then
|
||||
echo "Balance too low ($BALANCE_LTHN LTHN). Stopping."
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
register_alias "$name" "$ADDRESS"
|
||||
if [ $? -eq 0 ]; then
|
||||
((REGISTERED++))
|
||||
else
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
((COUNT++))
|
||||
sleep "$DELAY"
|
||||
|
||||
done < "$NAMES_FILE"
|
||||
|
||||
echo ""
|
||||
echo "=== Done ==="
|
||||
echo "Registered: $REGISTERED"
|
||||
echo "Failed: $FAILED"
|
||||
echo "Skipped: $SKIPPED"
|
||||
echo "Total processed: $COUNT"
|
||||
|
|
@ -58,12 +58,15 @@ RUN if [ "$BUILD_TESTNET" = "1" ]; then \
|
|||
|
||||
# use --target=build-artifacts to return the binaries
|
||||
FROM scratch AS build-artifacts
|
||||
COPY --from=build /code/build/release/src/lethean-* /
|
||||
COPY --from=build /code/build/release/src/lethean-* /bin/
|
||||
COPY --from=build /code/build/release/share/ /share/
|
||||
|
||||
# use --target=chain-service to return a working chain node
|
||||
FROM ubuntu:24.04 AS chain-service
|
||||
|
||||
COPY --from=build-artifacts --chmod=+x / /bin
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=build-artifacts --chmod=+x /bin/ /bin/
|
||||
COPY --from=build-artifacts /share/ /share/
|
||||
|
||||
EXPOSE 36941
|
||||
EXPOSE 36942
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue