╔═══════════════════════════════════════════════════════════╗
║ ║
║ ██████╗ ██╗ ██████╗ ██████╗██╗ ██╗ ║
║ ██╔══██╗██║ ██╔═══██╗██╔════╝██║ ██╔╝ ║
║ ██████╔╝██║ ██║ ██║██║ █████╔╝ ║
║ ██╔══██╗██║ ██║ ██║██║ ██╔═██╗ ║
║ ██████╔╝███████╗╚██████╔╝╚██████╗██║ ██╗ ║
║ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ║
║ ║
║ ██████╗██╗ ██╗ █████╗ ██╗███╗ ██╗ ║
║ ██╔════╝██║ ██║██╔══██╗██║████╗ ██║ ║
║ ██║ ███████║███████║██║██╔██╗ ██║ ║
║ ██║ ██╔══██║██╔══██║██║██║╚██╗██║ ║
║ ╚██████╗██║ ██║██║ ██║██║██║ ╚████║ ║
║ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ║
║ ║
╚═══════════════════════════════════════════════════════════╝
A production-ready FastAPI blockchain with PoW, Consensus, and Multi-Node Docker
A lightweight, educational blockchain framework built with FastAPI, featuring:
- ⛏️ Proof-of-Work mining with adjustable difficulty
- 🔗 SHA-256 cryptographic block hashing
- 🌐 Multi-node peer-to-peer consensus
- 📡 RESTful + OpenAPI with auto-generated docs
- 🎨 Real-time HTMX/Tailwind UI dashboard
- 🐳 Multi-node Docker orchestration
- ✅ Full Pydantic validation and type safety
Perfect for learning how blockchains work under the hood or as a foundation for custom distributed ledger experiments.
┌──────────────────────────────────────────┐
│ FastAPI Application │
│ (Uvicorn ASGI Server - Port 5000) │
└──────────────┬───────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌───────────┐ ┌─────────────────┐
│ UI Routes │ │ API Routes│ │ Legacy Routes │
│ (HTMX/Jinja2) │ │ (/api/*) │ │ (/mine, /chain) │
└────────┬─────────┘ └─────┬─────┘ └────────┬────────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌──────────▼──────────┐
│ Blockchain Core │
│ (In-Memory State) │
└──────────┬──────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐
│ Block Chain │ │ Mempool │ │ Peer Nodes │
│ [Block₀, B₁...] │ │ [Pending Txs] │ │ {node1, node2} │
└─────────────────┘ └─────────────────┘ └────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐
│ PoW Mining │ │ Tx Validation │ │ Consensus │
│ (SHA-256 hash) │ │ (Pydantic) │ │ (Longest chain)│
└─────────────────┘ └─────────────────┘ └────────────────┘
Internet/Host Machine (localhost)
│
┌──────────────┼──────────────┐
│ │ │
Port 5000 Port 5001 Port 5002
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node 1 │◄──►│ Node 2 │◄──►│ Node 3 │
│ (node1) │ │ (node2) │ │ (node3) │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
│
Docker Bridge Network
(blockchain-net)
│
Auto-registration via PEERS env
py-blockchain-tutorial/
│
├── 📁 app/ # Core application package
│ ├── __init__.py # Flask legacy factory (deprecated)
│ ├── api.py # 🚀 FastAPI app + all endpoints
│ ├── blockchain.py # ⛓️ Blockchain logic (PoW, consensus)
│ ├── models.py # 📋 Pydantic schemas for validation
│ ├── main.py # 🎬 Uvicorn entrypoint
│ └── routes.py # (deprecated, migrated to api.py)
│
├── 📁 templates/ # Jinja2 HTML templates
│ ├── index.html # 🎨 Main dashboard (HTMX + Tailwind)
│ └── _status.html # 📊 Live status partial
│
├── 📁 tests/ # Pytest test suite
│ ├── test_app.py # ✅ API & integration tests
│ └── requirements.txt # Test dependencies
│
├── 📁 .github/workflows/ # CI/CD pipelines
│ └── ci.yml # GitHub Actions: lint→test→audit
│
├── 🐳 Dockerfile # Production container image
├── 🐳 docker-compose.yml # Multi-node orchestration
├── ⚙️ pyproject.toml # Project metadata + tool configs
├── 🔧 .pre-commit-config.yaml # Git hooks for quality checks
├── 📄 requirements.txt # Legacy root deps (use pyproject.toml)
└── 📖 README.md # This file
# Install dependencies
pip install -e .[dev]
# Run the server
python -m app.main
# Open in browser
# UI: http://127.0.0.1:5000
# API docs: http://127.0.0.1:5000/docs
# ReDoc: http://127.0.0.1:5000/redoc# Spin up 3 interconnected nodes
docker compose up --build -d
# Access nodes
# Node 1: http://127.0.0.1:5000
# Node 2: http://127.0.0.1:5001
# Node 3: http://127.0.0.1:5002
# Example: Mine a block on node 1
curl http://localhost:5000/mine
# View the chain
curl http://localhost:5000/chain | jq
# Trigger consensus on node 2 (sync from peers)
curl http://localhost:5001/nodes/resolve | jq
# Stop nodes
docker compose downThe live dashboard (powered by HTMX + Tailwind) provides real-time updates, interactive buttons, and toast notifications:
Features:
- 📊 Live status cards (height, difficulty, mempool, last block hash)
- ➕ Add Tx button (green) - adds test transactions to mempool
- ⛏️ Mine Block button (blue) - mines pending transactions
- 📦 Recent blocks viewer with auto-refresh
- 📝 Mempool viewer showing pending transactions
- 🔔 Toast notifications for success/error feedback
- 🔄 Auto-refresh every 2-5 seconds
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/status |
Chain status + metrics |
| GET | /api/blocks |
Recent blocks (paginated) |
| GET | /api/mempool |
Pending transactions |
| POST | /api/tx |
Submit a transaction |
| POST | /api/mine |
Mine pending txs into a block |
| Method | Endpoint | Description |
|---|---|---|
| GET | /mine |
Mine block with reward tx |
| POST | /transactions/new |
Add transaction (validated) |
| GET | /chain |
Full chain + length |
| POST | /nodes/register |
Register peer nodes |
| GET | /nodes/resolve |
Consensus (longest chain wins) |
- Swagger UI: http://127.0.0.1:5000/docs
- ReDoc: http://127.0.0.1:5000/redoc
# 1. Add transactions to mempool
curl -X POST http://127.0.0.1:5000/api/tx \
-H "Content-Type: application/json" \
-d '{"from": "alice", "to": "bob", "amount": 10}'
curl -X POST http://127.0.0.1:5000/api/tx \
-H "Content-Type: application/json" \
-d '{"from": "charlie", "to": "dave", "amount": 5}'
# 2. Check mempool
curl http://127.0.0.1:5000/api/mempool | jq
# 3. Mine a block
curl -X POST http://127.0.0.1:5000/api/mine | jq
# 4. Verify chain
curl http://127.0.0.1:5000/chain | jq '.chain[-1]'# Node 1: Mine several blocks
for i in {1..3}; do
curl -X POST http://127.0.0.1:5000/api/tx \
-H "Content-Type: application/json" \
-d "{\"from\":\"miner\",\"to\":\"user$i\",\"amount\":1}"
curl http://127.0.0.1:5000/mine
done
# Node 2: Check initial state
curl http://127.0.0.1:5001/chain | jq '.length'
# Output: 1 (genesis only)
# Node 2: Resolve conflicts (sync from node 1)
curl http://127.0.0.1:5001/nodes/resolve | jq
# Node 2: Verify sync
curl http://127.0.0.1:5001/chain | jq '.length'
# Output: 4 (genesis + 3 mined blocks)# Run all tests
pytest
# With coverage
pytest --cov=app
# Type check
mypy app
# Lint & format
ruff check --fix .
ruff format .
black .
isort .
# Security audit
pip-auditTest Coverage:
- ✅ Status endpoint
- ✅ Transaction submission + mining flow
- ✅ Legacy
/mineendpoint - ✅ Transaction validation (Pydantic)
- ✅ Chain retrieval
- ✅ Node registration
- ✅ Consensus resolution
┌─────────────────────────────────────────────────────────┐
│ Layer │ Technology │
├─────────────────┼───────────────────────────────────────┤
│ Web Framework │ FastAPI 0.121+ (ASGI) │
│ Server │ Uvicorn (asyncio event loop) │
│ Validation │ Pydantic 2.12+ │
│ Templates │ Jinja2 3.1+ │
│ Frontend │ HTMX 1.9 + Tailwind CSS (CDN) │
│ Testing │ Pytest 9.0+, Hypothesis 6.147+ │
│ Type Checking │ Mypy 1.18+ (strict mode) │
│ Linting │ Ruff 0.14+, Black, Isort │
│ Security │ Bandit, pip-audit │
│ Containerization│ Docker + Docker Compose │
│ CI/CD │ GitHub Actions │
└─────────────────────────────────────────────────────────┘
- 🔒 SHA-256 hashing for block integrity
- ✅ Pydantic validation for all inputs
- 🛡️ Request size limits (FastAPI defaults)
- 🔍 Dependency auditing via pip-audit
- 🔐 No secrets in code (env var best practices)
- 📊 Type safety with mypy strict mode
Block {
index: int # Sequential block number
timestamp: float # Unix timestamp
previous_hash: str # SHA-256 of previous block
nonce: int # Proof-of-Work solution
transactions: [...] # List of transactions
hash: str # SHA-256 of this block
}┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Block 0 │ │ Block 1 │ │ Block 2 │
│ (Genesis) │◄───│ │◄───│ │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ idx: 0 │ │ idx: 1 │ │ idx: 2 │
│ prev: 000...│ │ prev: abc...│ │ prev: def...│
│ nonce: 0 │ │ nonce: 1234 │ │ nonce: 5678 │
│ txs: [gen] │ │ txs: [tx1] │ │ txs: [tx2] │
│ hash: abc...│ │ hash: def...│ │ hash: ghi...│
└─────────────┘ └─────────────┘ └─────────────┘
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 5000 |
NODE_ID |
Unique identifier for this node | Random UUID |
PEERS |
Comma-separated peer addresses | (empty) |
Nodes auto-register peers via PEERS env:
node1:
environment:
- NODE_ID=node1
- PEERS=blockchain-node2:8000,blockchain-node3:8000- ✅ Run locally and explore the UI
- ✅ Submit transactions via
/api/tx - ✅ Mine blocks with
/api/mine - ✅ Inspect chain structure at
/chain
- ✅ Study
blockchain.pyPoW algorithm - ✅ Add custom transaction fields
- ✅ Modify mining difficulty
- ✅ Run multi-node Docker setup
- ✅ Implement Merkle tree for transactions
- ✅ Add ECDSA signatures (secp256k1)
- ✅ Persistent storage (SQLite/LevelDB)
- ✅ WebSocket live updates
- ✅ UTXO model with double-spend prevention
Contributions welcome! Please:
- Fork the repo
- Create a feature branch
- Run tests + linting:
pytest && mypy app && ruff check . - Submit a PR
MIT License - feel free to use for learning or commercial projects.
- Original tutorial inspired by classic blockchain educational resources
- Built with modern Python best practices (2025)
- Community feedback and contributions
⭐ Star this repo if you found it helpful!
Made with ❤️ for the blockchain learning community
