A simple blockchain implementation for distributed systems demonstration.
- 📌 Overview
- 🚀 Quick Start
- ⚙️ Configuration
- ⌨️ CLI Interface
- 🏗️ Architecture
- ✨ Features
- 🫡 Consensus
- 🌐 Networking
- ⛓️ Blockchain
- 📁 Project Structure
- 🔍 How It Works
- 🧰 Troubleshooting
- 🧪 Advanced Usage
- ✅ Testing
- 📚 Documentation
- 🛠️ Development
- 📜 License
- 🔌 API Reference
- 🐳 Docker (Quick Start)
- 🔐 Remote Access (UH VPN + SSH Gateway)
MiniChain is a minimal blockchain implementation that demonstrates key distributed systems concepts:
- Shared distributed state: Replicated blockchain ledger across nodes
- Data consistency: Block validation and chain synchronization
- Consensus: Round-robin Proof-of-Authority (PoA) consensus
- Fault tolerance: Leader crash recovery and node rejoin
You can spin up the API and frontend locally without setting up Python/node manually.
# From repo root
VITE_API_URL="http://localhost:8080" docker compose up --build
# Frontend: http://localhost:5173
# API: http://localhost:8080/statusNotes:
- Environment variable
VITE_API_URLpoints the frontend at the API. - Compose builds both images and binds ports
5173(frontend) and8080(API). - See
DOCKER.mdfor multi-node, reverse proxy, and TLS guidance.
chmod +x setup.sh
./setup.shThis will:
- Check Python 3.8+ installation (3.11+ recommended)
- Create virtual environment
- Install all dependencies
Edit peers.txt to include all nodes in your network:
svm-11.cs.helsinki.fi:8000
svm-11-2.cs.helsinki.fi:8000
svm-11-3.cs.helsinki.fi:8000
Important: All nodes must have the same peers.txt file with all peers listed.
chmod +x start.sh
./start.sh <hostname> [--clean]Usage:
./start.sh <hostname> [--clean]Parameters:
hostname: REQUIRED - The hostname of this node (must match one inpeers.txt)--clean: Optional flag to clear all data and logs before starting
Examples:
On svm-11.cs.helsinki.fi:
./start.sh svm-11.cs.helsinki.fiOn svm-11-2.cs.helsinki.fi:
./start.sh svm-11-2.cs.helsinki.fiOn svm-11-3.cs.helsinki.fi:
./start.sh svm-11-3.cs.helsinki.fiClean Start: To clear all data and logs before starting:
./start.sh svm-11.cs.helsinki.fi --cleanThe script will:
- Use the provided hostname (no auto-detection)
- Find the hostname in
peers.txt - Configure node_id and peers automatically
- Start the node
Note: Hostname is required. The script will exit with an error if not provided.
This will remove:
- All blockchain data (
data/directory) - All log files (
minichain.log)
The frontend now supports connecting to multiple backend nodes.
To run a full 3-node network on VMs and access it locally:
-
Start Backend Nodes (on VMs):
Open 3 separate terminals on your local machine and run the following commands to start 3 nodes with port forwarding:
Node 1 (Port 8001):
ssh -L 8001:localhost:8001 -J <user>@melkki.cs.helsinki.fi <user>@svm-11.cs.helsinki.fi cd MiniChain/ ./start.sh svm-11.cs.helsinki.fi --api-port 8001 --no-cli
Node 2 (Port 8002):
ssh -L 8002:localhost:8002 -J <user>@melkki.cs.helsinki.fi <user>@svm-11-2.cs.helsinki.fi cd MiniChain/ ./start.sh svm-11-2.cs.helsinki.fi --api-port 8002 --no-cli
Node 3 (Port 8003):
ssh -L 8003:localhost:8003 -J <user>@melkki.cs.helsinki.fi <user>@svm-11-3.cs.helsinki.fi cd MiniChain/ ./start.sh svm-11-3.cs.helsinki.fi --api-port 8003 --no-cli
Replace
<user>with your username. -
Start Frontend (Locally):
cd frontend npm install npm run dev -
Access Dashboard: Open
http://localhost:5173in your browser.
You can spin up the API and frontend locally without setting up Python/node manually.
# From repo root
VITE_API_URL="http://localhost:8080" docker compose up --build
# Frontend: http://localhost:5173
# API: http://localhost:8080/statusNotes:
- Environment variable
VITE_API_URLpoints the frontend at the API. - Compose builds both images and binds ports
5173(frontend) and8080(API). - See
DOCKER.mdfor multi-node, reverse proxy, and TLS guidance.
The frontend includes a node selector in the sidebar. You can switch between the connected nodes to view the state of different parts of the network.
- Default Nodes: The frontend is configured to connect to
localhost:8001,localhost:8002, andlocalhost:8003by default. - Adding More Nodes: To add or modify nodes, edit
frontend/src/nodeConfig.js.
// frontend/src/nodeConfig.js
export const nodes = [
{ id: "node1", name: "Node 1", url: "http://localhost:8001" },
// ... add more nodes here
];Edit config.yaml to adjust blockchain and consensus parameters:
consensus:
block_interval: 5 # Seconds between block proposals
proposal_timeout: 10 # Seconds to wait for ACKs
quorum_size: 2 # Minimum ACKs needed (for 3 nodes: 2/3)
blockchain:
max_block_size: 100 # Max transactions per block
logging:
level: "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
file: "minichain.log"
console: trueNote: This file contains only blockchain settings. Node-specific settings (node_id, peers) are auto-detected from peers.txt.
The HTTP API binds to 0.0.0.0 and defaults to the port derived from peers.txt (falls back to 8000). You can explicitly set an API port when starting:
./start.sh <hostname> --api-port 8080List all nodes in your network (one per line, format: hostname:port):
svm-11.cs.helsinki.fi:8000
svm-11-2.cs.helsinki.fi:8000
svm-11-3.cs.helsinki.fi:8000
The start.sh script automatically:
- Detects which machine it's running on
- Sets node_id to the matching hostname
- Configures peers (all other nodes in the list)
When you start a node, an interactive CLI automatically launches. Use it to submit transactions and monitor the blockchain.
Show help message with all available commands.
Submit a new transaction to the blockchain.
Example:
minichain> submit alice bob 10.5
✓ Transaction submitted: a1b2c3d4e5f6g7h8
alice -> bob: 10.5
Display current node status and blockchain information.
minichain> status
============================================================
Node Status
============================================================
Node ID: svm-11.cs.helsinki.fi
Hostname: svm-11.cs.helsinki.fi
Port: 8000
Blockchain Height: 5
Latest Block Hash: 1a2b3c4d5e6f7g8h9...
Mempool Size: 3 transactions
Connected Peers: 2
Current Leader: svm-11-2.cs.helsinki.fi
I am Leader: No (for next block)
============================================================
Show a summary of recent blocks (default: 10).
minichain> chain 5
Show detailed information about a specific block.
minichain> block 5
Show pending transactions in mempool.
Show connected peer nodes.
Show last n lines from log file (default: 20).
minichain> logs 50
Note: Use --log-level DEBUG when starting to see debug logs:
./start.sh --log-level DEBUGClear screen.
Exit the node.
The project is organized into the following modules:
src/common/: Configuration, logging, and cryptographic utilitiessrc/chain/: Blockchain data structures (Block, Transaction) and chain managementsrc/mempool/: Transaction mempool for pending transactionssrc/consensus/: Consensus mechanism (round-robin PoA)src/p2p/: Peer-to-peer networking and message passingsrc/node/: Main node implementation that orchestrates all componentssrc/cli/: Interactive command-line interface
- Round-robin PoA: Leaders rotate based on block height
- Quorum-based: Requires majority ACKs before committing blocks
- View change: Automatic leader rotation on timeout
- TCP-based: Reliable message delivery
- Gossip protocol: Transaction and block propagation
- State synchronization: Catch-up sync for new/rejoining nodes
- Genesis block: Automatically created on first run (deterministic)
- Block validation: Height, hash, and linkage checks
- Fork resolution: Longest chain wins
MiniChain/
├── src/ # Backend (Python)
│ ├── api/ # FastAPI server
│ ├── chain/ # Blockchain data structures
│ ├── cli/ # Interactive CLI
│ ├── common/ # Config, crypto, logging
│ ├── consensus/ # PoA and helpers
│ ├── mempool/ # Pending transactions
│ ├── node/ # Node orchestration
│ ├── p2p/ # Networking & messages
│ └── main.py # Entry point
├── frontend/ # Vite React UI
│ ├── src/ # Components, pages, api client
│ ├── Dockerfile # Frontend container
│ └── package.json # Frontend deps & scripts
├── docs/ # Documentation
│ ├── ARCHITECTURE.md # Internals & wiring
│ ├── ENDPOINT.md # API specs
│ ├── CURL.md # curl examples
│ └── PYTEST.md # Test guide
├── tests/ # Pytest suite
│ ├── test_block.py
│ ├── test_blockchain.py
│ ├── test_mempool.py
│ └── test_poa.py
├── data/ # Persisted chain state
├── config.yaml # Blockchain settings
├── peers.txt # Network peers (all nodes)
├── Dockerfile.api # API container
├── docker-compose.yml # Compose stack (API + UI)
├── setup.sh # Python env bootstrap
├── start.sh # Node launcher
├── requirements.txt # Python dependencies
└── README.md # Project overview
- Transaction Submission: User submits transaction via CLI
- Gossip Propagation: Transaction is broadcast to all peers
- Mempool: All nodes add transaction to their mempool
- Block Proposal: Leader creates block from mempool transactions
- Validation: Followers validate and ACK the proposal
- Commit: Leader commits block after quorum is reached
- Finalization: All nodes add block to their blockchain
- TX: Transaction propagation (gossip)
- PROPOSE: Block proposal from leader
- ACK: Acknowledgment from followers (sent to leader only)
- COMMIT: Final commit message from leader
- HELLO: Connection handshake
- Leader rotates based on block height (round-robin)
- Leader proposes block when
block_intervalelapsed - Followers validate and send ACK to leader
- Leader waits for quorum (majority of validators)
- Leader commits and broadcasts COMMIT
- All nodes finalize the block
- Check Python version:
python3 --version(needs 3.8+) - Ensure virtual environment is activated
- Check
peers.txtcontains the current machine's hostname
- Verify hostnames in
peers.txtare resolvable:ping <hostname> - Check firewall settings allow connections on port 8000
- Ensure all nodes are running
- Verify all nodes have the same
peers.txtfile - Check that hostnames match exactly (FQDN vs short name)
- Use
--log-level DEBUGto see detailed logs
# View logs via CLI
minichain> logs 50
# Or view log file directly
tail -f minichain.logpython3 src/main.py --no-cli --node-id <id> --peers "..."You can still override settings via command-line:
python3 src/main.py \
--node-id custom-id \
--port 8001 \
--peers "peer1:8000,peer2:8000" \
--log-level DEBUG \
--config custom_config.yamlMiniChain ships with a lightweight pytest suite that exercises the block/mempool primitives, blockchain persistence, and consensus helpers. Run it from the repo root after activating the project virtual environment so that dependencies such as msgpack, cryptography, and pytest are available:
source .venv/bin/activate # created via ./setup.sh
pip install -r requirements.txt # no-op if already installed
pytest -q # or python -m pytestTests live under tests/ and can be filtered (e.g., pytest tests/test_poa.py -k quorum). The suite expects the dependencies listed in requirements.txt—rerun setup.sh if the environment gets out of sync.
docs/ARCHITECTURE.md– runtime wiring, lifecycles, and extension points.PRD.md– product requirements, personas, milestones, and risks.TODO.md– backlog aligned with the PRD milestones.PYTEST.md– detailed instructions for running/extending the automated test suite.docs/CURL.md– curl examples and SSH tunneling tips (--api-portexamples).DOCKER.md– step-by-step Docker and Compose deployment guide.
Keep these documents in sync when features land so reviewers can trace code back to the design intent.
The codebase is designed to be:
- Modular: Each component is independent
- Configurable: Easy to adjust for different deployments
- Extensible: Easy to add new features
[Your License Here]
MiniChain includes an HTTP API for interacting with the node and debugging. To enable it, start the node with the --api-port flag:
./start.sh <hostname> --api-port 8080
# OR
python3 src/main.py --node-id <id> --api-port 8080curl http://localhost:8080/statuscurl "http://localhost:8080/blocks?limit=5"curl http://localhost:8080/blocks/0curl -X POST http://localhost:8080/submit \
-H "Content-Type: application/json" \
-d '{"sender": "alice", "recipient": "bob", "amount": 10.5}'curl http://localhost:8080/transactions/<tx_id>curl http://localhost:8080/mempoolThese endpoints are for testing edge cases and simulating network conditions.
Remove all pending transactions from the mempool.
curl -X POST http://localhost:8080/debug/mempool/clearDisconnect from all peers to simulate a network partition.
curl -X POST http://localhost:8080/debug/network/disconnectReconnect to peers after a partition.
curl -X POST http://localhost:8080/debug/network/reconnectSimulate a consensus timeout (e.g., to force a view change).
curl -X POST http://localhost:8080/debug/consensus/timeoutcurl -X POST http://localhost:8080/shutdowncurl http://localhost:8080/peers/statuscurl -X POST http://localhost:8080/sync/requestcurl "http://localhost:8080/logs?lines=100&level=INFO&tail=true" | jq .curl -N "http://localhost:8080/logs/stream?level=INFO"You can run the API and frontend via Docker Compose. See DOCKER.md for full details.
# From repo root
VITE_API_URL="http://localhost:8080" docker compose up --build
# Frontend: http://localhost:5173
# API: http://localhost:8080/statusFor Internet deployment, use a public VM with a reverse proxy and TLS; DOCKER.md includes guidance and an optional Nginx setup.
Recommended setup using a VPN and SSH jump host to reach svm-11* machines from your laptop.
- Connect to UH VPN:
- SSH with local port forwarding via gateway:
# Single command using jump host (-J)
ssh -L 5173:localhost:5173 -L 8000:localhost:8000 \
-J <user>@melkki.cs.helsinki.fi <user>@svm-11.cs.helsinki.fiNotes:
-Juses the gateway as a jump host.- You can replace
melkkiwithmelkinkariormelkinpaasi. - Manual chaining also works (SSH to gateway first, then to
svm-11).
- On
svm-11, start backend + frontend:
cd ~/MiniChain
./start.sh svm-11.cs.helsinki.fi --api-port 8000
cd frontend
VITE_API_URL="http://localhost:8000" npm run dev -- --host- On your local machine, open:
- Frontend:
http://localhost:5173 - API health:
http://localhost:8000/status
This avoids firewall issues by tunneling through SSH.
If you’re already on the UH network and want to access directly:
-
Ensure Vite binds to
0.0.0.0(seefrontend/vite.config.jswithserver.host: true). -
Start frontend pointing at the VM IP:
VITE_API_URL="http://128.214.11.91:8000" npm run dev -- --host- Open
http://128.214.11.91:5173/from your host.
If it doesn’t load:
- Open ports on the VM firewall:
sudo ufw allow 5173/tcp
sudo ufw allow 8000/tcp
sudo ufw status- Check listeners:
ss -ltnp | grep 5173
ss -ltnp | grep 8000- Institutional firewall may still block; fall back to the SSH tunnel method above.
If you run into errors with the jump host command, share the exact error and we’ll adjust (e.g., ProxyJump in ~/.ssh/config, or -W/-J alternatives).
If you cannot access the API:
- Check if node is running:
ps aux | grep main.py - Check if port is listening:
netstat -tulpn | grep 8080 - Check logs:
tail -f minichain.log
- Missing Dependencies:
If you see
ModuleNotFoundError, run:./setup.sh
If accessing from a local machine via SSH tunnel:
- Ensure the node is running on the remote machine.
- Ensure the tunnel is correctly set up:
ssh -L 8080:localhost:8080 user@host - Verify the node is listening on
0.0.0.0orlocalhost(default is0.0.0.0).

