Lua blockchain node implemented on OpenResty with:
- browser-generated wallets and locally signed transactions
- account balances, nonces, mining rewards, fee handling, and mempool validation
- signed native TCP peer transport over TLS for handshake, headers sync, block push, and transaction relay
- UDP gossip announcements for directory fan-out and fast peer discovery
- persisted peer records with health, reputation, cooldown, and ban state
- peer admission controls with peer ID allowlists plus per-IP and per-subnet limits
- peer directory exchange, gossip seeding, bootstrap peers, and autonomous background maintenance
- most-cumulative-work fork choice with retargeted prefix-based proof of work
- production guardrails for rate limiting, admin authentication, peer authentication, and network isolation by
chain_id - SQLite-backed transactional state with a dedicated persistent data volume
- review scaffolding in
SECURITY.mdanddocs/ - containerized runtime with a dedicated persistent data volume
- OpenResty / LuaJIT
- pure Lua SHA-256 via the vendored
pure_lua_SHAmodule - OpenSSL CLI for key generation and signature verification
- LuaSocket + LuaSec for native TCP peer transport, local loopback RPC, and UDP gossip transport
- Docker Compose for local runtime
- Build the image:
./docker-build.sh - Start the application:
./start.sh - Open the frontend:
http://127.0.0.1:8080/ - Run the backend tests:
./test.sh - Open a shell in the container when needed:
./connect.sh - Stop the application:
./stop.sh
start.sh now starts the node in detached mode.
Copy .env.example to .env and replace the secrets and public URLs before starting the stack in production mode.
Important variables:
BLOCKCHAIN_MODE=productionBLOCKCHAIN_NODE_URL=https://node-1.example.comBLOCKCHAIN_CHAIN_ID=lua-blockchain-mainnetBLOCKCHAIN_DATA_FILE=/app/data/blockchain.dbBLOCKCHAIN_DIFFICULTY=0000BLOCKCHAIN_TARGET_BLOCK_SECONDS=30BLOCKCHAIN_DIFFICULTY_ADJUSTMENT_WINDOW=10BLOCKCHAIN_MIN_DIFFICULTY_PREFIX_LENGTH=2BLOCKCHAIN_MAX_DIFFICULTY_PREFIX_LENGTH=6BLOCKCHAIN_ADMIN_TOKEN=<long random secret>BLOCKCHAIN_PEER_SHARED_SECRET=<shared peer secret>BLOCKCHAIN_ALLOWED_PEER_IDS=<comma-separated peer id allowlist>BLOCKCHAIN_ALLOW_PLAINTEXT_GOSSIP=falseBLOCKCHAIN_REQUIRE_HTTPS_PEERS=trueBLOCKCHAIN_ENABLE_SERVER_WALLETS=falseBLOCKCHAIN_BOOTSTRAP_PEERS=https://node-2.example.com,https://node-3.example.comBLOCKCHAIN_BACKUP_DIR=/app/data/backupsBLOCKCHAIN_PEER_DISCOVERY_FANOUT=8BLOCKCHAIN_PEER_ADVERTISED_LIMIT=16BLOCKCHAIN_PEER_BACKOFF_BASE_SECONDS=15BLOCKCHAIN_PEER_BAN_SECONDS=300BLOCKCHAIN_PEER_MAX_FAILURES_BEFORE_BAN=5BLOCKCHAIN_MAX_PEERS_PER_IP=4BLOCKCHAIN_MAX_PEERS_PER_SUBNET=8BLOCKCHAIN_PEER_MAINTENANCE_ENABLED=trueBLOCKCHAIN_PEER_MAINTENANCE_INTERVAL_SECONDS=30BLOCKCHAIN_P2P_ENABLED=trueBLOCKCHAIN_P2P_PORT=19100BLOCKCHAIN_P2P_ADVERTISE_HOST=node-1.example.comBLOCKCHAIN_P2P_SEEDS=node-2.example.com:19100,node-3.example.com:19100BLOCKCHAIN_P2P_DIAL_DISCOVERED_PEERS=falseBLOCKCHAIN_P2P_CONNECT_INTERVAL_SECONDS=5BLOCKCHAIN_P2P_POLL_INTERVAL_SECONDS=2BLOCKCHAIN_P2P_TLS_ENABLED=trueBLOCKCHAIN_P2P_TLS_CERT_PATH=/app/data/node_p2p_cert.pemBLOCKCHAIN_P2P_TLS_KEY_PATH=/app/data/node_p2p_key.pemBLOCKCHAIN_NODE_IDENTITY_PRIVATE_KEY_PATH=/app/data/node_identity_private.pemBLOCKCHAIN_NODE_IDENTITY_PUBLIC_KEY_PATH=/app/data/node_identity_public.pemBLOCKCHAIN_GOSSIP_ENABLED=falseBLOCKCHAIN_GOSSIP_PORT=19090BLOCKCHAIN_GOSSIP_ADVERTISE_HOST=node-1.example.comBLOCKCHAIN_GOSSIP_SEEDS=node-2.example.com:19090,node-3.example.com:19090BLOCKCHAIN_GOSSIP_FANOUT=3BLOCKCHAIN_GOSSIP_INTERVAL_SECONDS=5BLOCKCHAIN_GOSSIP_MESSAGE_TTL_SECONDS=30BLOCKCHAIN_GOSSIP_MAX_HOPS=3
In production mode the node will refuse readiness if:
BLOCKCHAIN_ADMIN_TOKENis missingBLOCKCHAIN_ADMIN_TOKENis shorter than 32 charactersBLOCKCHAIN_PEER_SHARED_SECRETis missingBLOCKCHAIN_PEER_SHARED_SECRETis shorter than 32 charactersBLOCKCHAIN_NODE_URLis missing or nothttpsBLOCKCHAIN_REQUIRE_HTTPS_PEERSis nottrue- neither
BLOCKCHAIN_ALLOWED_PEER_IDSnorBLOCKCHAIN_ALLOWED_PEER_HOSTSis configured BLOCKCHAIN_P2P_DIAL_DISCOVERED_PEERS=truewithoutBLOCKCHAIN_ALLOWED_PEER_IDSBLOCKCHAIN_BOOTSTRAP_PEERScontains a non-httpsURL- native P2P TLS is disabled
- UDP gossip is enabled without explicitly allowing plaintext gossip
- server-side wallet generation is enabled
Persistent chain data is stored in the Docker volume mounted at /app/data/blockchain.db.
GET /api/healthGET /api/readyGET /api/infoGET /api/chainGET /api/headersGET /api/locatorGET /api/network/peersPOST /api/network/p2p/peer-updatePOST /api/network/p2p/peer-failurePOST /api/network/gossip/announceGET /api/statsGET /api/blocks/{index}GET /api/blocks/hash/{hash}GET /api/accountsGET /api/accounts/{address}GET /api/transactions/pendingPOST /api/walletsPOST /api/admin/backupPOST /api/transactionsPOST /api/mineGET /api/validateGET /api/peersPOST /api/peersPOST /api/consensus/resolvePOST /api/network/transactionsPOST /api/network/gossip/announcePOST /api/network/inventoryPOST /api/network/blocksGET /api/hash/sha256/{string}
Operational behavior:
POST /api/transactionsis public and rate limitedPOST /api/network/*requires the peer shared secret when configured- peers identify themselves with signed node identities over the native TCP transport, advertise TLS fingerprints in the signed hello payload, and persist observed peer IP/TLS metadata through the local
/api/network/p2p/*bridge - peers fetch missing headers with
get_headers, push missing blocks withblock, and relay signed transactions over native TCP sessions - peers can still exchange directories over
GET /api/network/peers, track health/cooldown state, and bootstrap autonomous maintenance fromBLOCKCHAIN_BOOTSTRAP_PEERS,BLOCKCHAIN_P2P_SEEDS, andBLOCKCHAIN_GOSSIP_SEEDS - peer advertisement deliberately strips observed IP and subnet metadata before forwarding discovered peers to the wider network
POST /api/admin/backupcreates a consistent SQLite snapshot plus node identity and TLS key material copies underBLOCKCHAIN_BACKUP_DIRPOST /api/mine,POST /api/peers,POST /api/consensus/resolve, andPOST /api/walletsare admin-protected when an admin token is configuredPOST /api/admin/backupis also admin-protectedPOST /api/walletsis disabled by default
Legacy routes such as /get_chain, /mine_block, /validate_chain, and /hashing/sha256/{string} still resolve for compatibility.
Each block stores:
indextimestamptransactionsproofdifficulty_prefixworkcumulative_workhash_formatprevious_hashhashmined_by
Each signed transfer stores:
senderrecipientamountfeenoncetimestampnotepublic_keysignature
Persistent state is stored in SQLite tables for:
metadatablockstransactionsblock_transactionspending_transactionspeerswith discovery source, success/failure counters, cooldown windows, last error, and advertised capabilities
The checked-in src/main/lua/com/brianlukonsolo/blockchain_data.json file is only a legacy repo artifact. Runtime state is stored in /app/data/blockchain.db inside the container volume.
The frontend lives under src/main/lua/com/brianlukonsolo/static/ and provides:
- local wallet generation and import/export
- browser-side ECDSA transaction signing
- account balance and nonce visibility
- mining controls
- peer registration, health visibility, and consensus sync
- mempool, account leaderboard, and block explorer views
src/test/lua/unit/blockchain_spec.lua is a plain Lua integration-style test script executed inside the container with luajit.
The Postman collection for the upgraded API is in:
src/test/postman/lua-blockchain_BrianLukonsolo.postman_collection.json
SECURITY.mddescribes disclosure expectations and the current security boundary.docs/threat-model.mdcaptures assets, trust boundaries, and mitigations.docs/security-review-checklist.mdlists the review items still required before claiming independent public-chain readiness.- the admin backup route provides an in-repo recovery primitive, but backup shipping and retention policy are still an operator responsibility
This repo now has materially stronger public-network hardening than the original demo, but there are still clear boundaries:
- peer networking now uses signed native TCP sessions over TLS for headers, blocks, and transaction relay, plus UDP gossip for discovery
- peer admission now enforces optional peer ID allowlists and caps peers per observed IP and subnet, which improves eclipse and Sybil resistance but does not eliminate those attacks
- peer discovery beyond configured seeds is intentionally conservative by default because the current daemon is single-process and not yet a full async mesh network
- consensus is now most-cumulative-work with retargeted prefix-based PoW, but it is still not a battle-tested public-chain fork-choice implementation
- storage is now transactional and SQLite-backed, but still not RocksDB/LevelDB-class node storage
- the UDP gossip channel is still plaintext discovery transport; public internet deployments should restrict it or disable it until a native authenticated/encrypted discovery layer exists
- there is no smart-contract VM, validator set management, or independent external security review
Treat this as a hardened blockchain node with documented review work still remaining, not a Bitcoin/Ethereum replacement.