Skip to content
Open

Kubs #51

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PUBNUB_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The blockchain application uses PubNub for real-time peer-to-peer communication

1. **Copy the secrets template:**
```bash
cp env.example .env
cp backend/config/env.example backend/config/.env
```

2. **Edit secrets.env and add your keys:**
Expand Down
111 changes: 1 addition & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,110 +1 @@
## Python, JS, & React | Build a Blockchain & Cryptocurrency

![Course Logo](python_blockchain_logo.png)

**The course is designed to help you achieve three main goals:**
- Learn Python and Backend Web Development.
- Build a Blockchain and Cryptocurrency Project that you can add to your portfolio.
- Learn JavaScript, Frontend Web Development, React.js, and React Hooks.

The course's main project is to build a blockchain and cryptocurrency. With a blockchain and cryptocurrency system as the main goal, you will go through a course journey that starts with backend development using Python. Then, you will transaction to frontend web development with JavaScript, React.js, and React Hooks.

Check out the course: https://www.udemy.com/course/python-js-react-blockchain/?referralCode=9051A01550E782315B77

**Here's an overview of the overall course journey:**
- Get an introduction of the Python Fundamentals.
- Begin building the Blockchain Application with Python.
- Test the Application using Pytest.
- Incorporate the crucial concept of Proof of Work into the Blockchain.
- Enhance the application to prepare for networking.
- Create the Blockchain network using Flask and Pub/Sub.
- Integrate the Cryptocurrency, building Wallets, Keys, and Transactions.
- Extend the network implementation with the cryptocurrency.
- Transition from Python to JavaScript with a "From Python to JavaScript" introduction.
- Establish frontend web development skills and begin coding with React.js.
- Create the frontend portion for the blockchain portion of the system.
- Complete the frontend by building a UI for the cryptocurrency portion of the system.

**In addition, here are the skills that you'll gain from the course:**
- How to build a blockchain and cryptocurrency system from scratch.
- The fundamentals of python - data structures, object-oriented programming, modules, and more.
- The ins and outs of hashing and sha256.
- Encoding and decoding in utf-8.
- Testing Python applications with pytest.
- Python virtual environments.
- The concept of proof of work, and how it pertains to mining blocks.
- Conversion between hexadecimal to binary.
- HTTP APIs and requests.
- How to create APIs with Python Flask.
- The publish/subscribe pattern to set up networks.
- When to apply the concepts of serialization and deserialization.
- Public/private keypairs and generating data signatures.
- The fundamentals of JavaScript.
- Frontend web development and how web applications are constructed.
- The core concepts of React and React hooks.
- How the React engine works under the hood, and how React applies hooks.
- CORS - and how to get over the CORS error properly.
- How to build a pagination system.

***

#### Command Reference

**Activate the virtual environment**
```
source blockchain-env/bin/activate
```

**Install all packages**
```
pip3 install -r requirements.txt
```

**Run the tests**

Make sure to activate the virtual environment.

```
python3 -m pytest backend/tests
```

**Run the application and API**

Make sure to activate the virtual environment.

```
python3 -m backend.app
```

**Run a peer instance**

Make sure to activate the virtual environment.
Choose a unique PUBNUB_USER_ID per peer.

```
export PEER=True && export PUBNUB_USER_ID=blockchain-peer-1 && python3 -m backend.app
```

**Run the frontend**

In the frontend directory:
```
npm run start
```

**Seed the backend with data**

Make sure to activate the virtual environment.

```
export SEED_DATA=True && python3 -m backend.app
```

** PubNub Configuration**

This application uses PubNub for real-time peer-to-peer communication between blockchain nodes. **You must configure PubNub to run the application.**

**See [PUBNUB_CONFIG.md](PUBNUB_CONFIG.md) for detailed setup instructions.**

1. Get free PubNub keys at [https://www.pubnub.com/](https://www.pubnub.com/)
2. copy `backend/env.example` to `backend/.env` and configure it with your keys.
# TODO
17 changes: 17 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.11-slim

RUN apt-get update && apt-get install curl -y

ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY backend/ /app/backend/

EXPOSE 5050

CMD ["python", "-m", "backend.app"]
167 changes: 0 additions & 167 deletions backend/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,167 +0,0 @@
import os
import requests
import random
import threading
import time
from pathlib import Path
from dotenv import load_dotenv

# Load environment variables FIRST, before any other backend imports
env_path = Path(__file__).parent.parent / '.env'
load_dotenv(dotenv_path=env_path)

from flask import Flask, jsonify, request, Response
from flask_cors import CORS
import json

from backend.blockchain.blockchain import Blockchain
from backend.wallet.wallet import Wallet
from backend.wallet.transaction import Transaction
from backend.wallet.transaction_pool import TransactionPool
from backend.pubsub import PubSub

app = Flask(__name__)
CORS(app, resources={ r'/*': { 'origins': 'http://localhost:3000' } })

def json_response(data, status=200):
"""
Create a JSON response that preserves large integers.
Flask's default jsonify converts large ints to floats, which breaks
cryptographic signatures. This function ensures integers are preserved.
"""
return Response(
json.dumps(data, separators=(',', ':')),
status=status,
mimetype='application/json'
)
blockchain = Blockchain()
wallet = Wallet(blockchain)
transaction_pool = TransactionPool()
pubsub = PubSub(blockchain, transaction_pool)

@app.route('/')
def route_default():
return 'Welcome to the blockchain'

@app.route('/blockchain')
def route_blockchain():
return json_response(blockchain.to_json())

@app.route('/blockchain/range')
def route_blockchain_range():
# http://localhost:5050/blockchain/range?start=2&end=5
start = int(request.args.get('start'))
end = int(request.args.get('end'))

return jsonify(blockchain.to_json()[::-1][start:end])

@app.route('/blockchain/length')
def route_blockchain_length():
return jsonify(len(blockchain.chain))

@app.route('/blockchain/mine')
def route_blockchain_mine():
transaction_data = transaction_pool.transaction_data()
transaction_data.append(Transaction.reward_transaction(wallet).to_json())
blockchain.add_block(transaction_data)
block = blockchain.chain[-1]
pubsub.broadcast_block(block)
transaction_pool.clear_blockchain_transactions(blockchain)

return json_response(block.to_json())

@app.route('/wallet/transact', methods=['POST'])
def route_wallet_transact():
transaction_data = request.get_json()
transaction = transaction_pool.existing_transaction(wallet.address)

if transaction:
transaction.update(
wallet,
transaction_data['recipient'],
transaction_data['amount']
)
else:
transaction = Transaction(
wallet,
transaction_data['recipient'],
transaction_data['amount']
)

pubsub.broadcast_transaction(transaction)
transaction_pool.set_transaction(transaction)

return jsonify(transaction.to_json())

@app.route('/wallet/info')
def route_wallet_info():
return jsonify({ 'address': wallet.address, 'balance': wallet.balance })

@app.route('/known-addresses')
def route_known_addresses():
known_addresses = set()

for block in blockchain.chain:
for transaction in block.data:
known_addresses.update(transaction['output'].keys())

return jsonify(list(known_addresses))

@app.route('/transactions')
def route_transactions():
return jsonify(transaction_pool.transaction_data())

ROOT_PORT = 5050
PORT = ROOT_PORT
# In Docker, use service name supplied by an env variable instead of localhost
root_host = os.environ.get('ROOT_HOST', 'localhost')

if os.environ.get('PEER') == 'True':
PORT = random.randint(5051, 6000)

result = requests.get(f'http://{root_host}:{ROOT_PORT}/blockchain')
result_blockchain = Blockchain.from_json(result.json())

try:
blockchain.replace_chain(result_blockchain.chain)
print('\n -- Successfully synchronized the local chain')
except Exception as e:
print(f'\n -- Error synchronizing: {e}')

if os.environ.get('SEED_DATA') == 'True':
for i in range(10):
blockchain.add_block([
Transaction(Wallet(), Wallet().address, random.randint(2, 50)).to_json(),
Transaction(Wallet(), Wallet().address, random.randint(2, 50)).to_json()
])

for i in range(3):
transaction = Transaction(Wallet(), Wallet().address, random.randint(2, 50))
pubsub.broadcast_transaction(transaction)
transaction_pool.set_transaction(transaction)

def poll_root_blockchain():
poll_interval = int(os.environ.get('POLL_INTERVAL', '15'))
root_host = os.environ.get('ROOT_HOST', 'localhost')

print(f'\n -- Starting polling thread for {root_host}:{ROOT_PORT} every {poll_interval}s')

while True:
try:
result = requests.get(f'http://{root_host}:{ROOT_PORT}/blockchain')
result_blockchain = Blockchain.from_json(result.json())
blockchain.replace_chain(result_blockchain.chain)
print(f'\n -- Successfully polled blockchain from {root_host}')
except Exception as e:
print(f'\n -- Error polling root blockchain: {e}')

time.sleep(poll_interval)

if os.environ.get('POLL_ROOT') == 'True':
# Start polling in a background daemon thread so it doesn't block Flask
polling_thread = threading.Thread(target=poll_root_blockchain, daemon=True)
polling_thread.start()

if __name__ == "__main__":
app.run(host="0.0.0.0", port=PORT, debug=True)

57 changes: 55 additions & 2 deletions backend/app/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
from backend.app import app, PORT
"""
Application entry point.
Initializes and runs the Flask application with graceful shutdown handling.
"""
import sys
import signal
import time

from backend.app.logging import logger
from backend.app.factory import create_app
from backend.app.context import app_context
from backend.app.polling import start_polling_thread, shutdown_event
from backend.config import AppConfig


# Start the polling thread if configured
polling_thread = start_polling_thread(app_context.blockchain)

# Create the Flask application
app = create_app()


# ------------------------------
# Graceful shutdown
# ------------------------------
def shutdown(signum, frame):
"""
Handle shutdown signals (SIGTERM, SIGINT) gracefully.
Stops PubSub, polling thread, and cleans up resources.
"""
logger.info("Received shutdown signal — exiting...")

# Stop PubSub
if app_context.pubsub:
logger.info("Stopping PubSub")
app_context.pubsub.stop()

# Stop polling thread
shutdown_event.set()
if polling_thread and polling_thread.is_alive():
logger.info("Waiting for polling thread to finish...")
polling_thread.join(timeout=5)

time.sleep(0.5)
logger.info("Cleanup complete. Exiting.")
sys.exit(0)

signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)

# ------------------------------
# Run the Flask app
# ------------------------------
if __name__ == "__main__":
app.run(host="0.0.0.0", port=PORT, debug=True)
port = AppConfig.get_port()
logger.info(f"Starting blockchain node on port {port}")

app.run(host="0.0.0.0", port=port)
Loading