← Course home Part 1 Project Blockchain — Part 1 Capstone

MineRace — Build Your Own Blockchain

Build a complete balance-based blockchain from scratch in JavaScript. Create wallets, send transactions, compete to mine blocks against bots, visualise the chain in real-time, and chat with an AI agent — all running in Docker.

Individual project ⛏️ Part 1 Capstone Modules 0–6
📌 Section 1

Context & Motivation

You've completed Part 1 of the course. Over six modules, you learned how hashing works, how keys and signatures secure transactions, how blocks are chained together, how proof of work creates consensus, and how networks handle forks. Now it's time to assemble everything into a working blockchain you can interact with.

🧩

Apply What You Learned

This is not new theory. Every line of code you write maps directly to a concept from Modules 0–6: hashing (M2), signatures (M2), transactions (M3), blocks (M3), mining (M4), and consensus (M5).

🚀

Bridge to Ethereum

You'll use a balance/account model instead of UTXO. This is exactly how Ethereum works. When you start Part 2, you'll already think in Ethereum's native paradigm.

🎯

What You'll Build

A full-stack blockchain application: a blockchain engine (balance model, PoW, validation), a REST API (Express), a visual frontend with wallet management, mining competition, and chain explorer, an AI agent (Ollama, running locally for free), and your own mining bot — all containerised with Docker.

💰 Section 2

Balance Model vs UTXO

In Module 3, you built a cryptocurrency using the UTXO model (Unspent Transaction Outputs), which is how Bitcoin works. In this project, you'll use the balance/account model instead. Here's the difference:

UTXO (Bitcoin) Balance (This Project / Ethereum)
How balance is tracked Sum of unspent outputs scattered across blocks Single number per address, like a bank account
To send 30 coins Consume a 50-coin UTXO, create 2 outputs: 30 to recipient + 20 change back Debit sender by 30, credit recipient by 30
Complexity More complex: track all UTXOs, handle change Simpler: just read/write balances
Privacy Better: can use new address for each change output Weaker: one address, all history visible
Used by Bitcoin, Litecoin, Cardano Ethereum, Solana, BSC
💡

Why This Matters for Part 2

Ethereum uses the balance model. Every Ethereum address (EOA or contract) has a balance stored in the world state — a giant key-value map of address → {balance, nonce, code, storage}. By building your blockchain with balances now, you're already thinking the way Ethereum thinks. In Part 2, you'll see this exact pattern again when you interact with smart contracts. Note: Ethereum accounts also have a nonce (transaction counter) to prevent replay attacks. This project omits nonce for simplicity — you'll implement it in Part 2.

🏗️ Section 3

Architecture Overview

DOCKER-COMPOSE app (Node.js + Express) — port 3000 Blockchain Engine Balance map (address → coins) Proof of Work Chain validation Mempool (in memory) reads/writes data/blocks/ REST API /api/chain /api/balance/:addr /api/transaction /api/mempool /api/challenge /api/stats /api/submitBlock /api/chat Bot Miners (server-side, configurable, mine empty blocks by default) Bot-Paris Bot-Tokyo Bot-NYC ... Express serves public/ → Browser gets HTML/CSS/JS from same origin (no CORS) ollama (local LLM) Free • No API key • Runs offline llama3.2 mistral :11434/api/generate ~2 GB download (once, cached) /api/chat data/blocks/ (Docker volume) block-0.json block-1.json ... Persists across restarts Editable on host disk fs.writeFileSync() MINERS — anyone can compete via localhost:3000 Browser UI BEGINNER HTML / CSS / JavaScript Mining via Web Worker Keys stored in localStorage Your Custom Bot ADVANCED JS / Python / any language GET /api/challenge → hash → POST Can include mempool txs Any Script curl, Go, Rust, C... Same API, same rules Same chance of winning POST /api/submitBlock First valid block wins → server validates → saved to disk
📂

Data Storage — One File Per Block

Each block is saved as a separate JSON file on disk, inside a data/blocks/ folder. Block 0 is block-0.json, block 1 is block-1.json, and so on. No database. No abstraction. Just files you can see, open, read, and edit with any text editor.

Why files instead of a database or in-memory array? Because you can physically interact with them:

📂
ls data/blocks/

See your chain grow as new files appear

📄
cat data/blocks/block-3.json

Read any block's raw content

✏️
nano block-3.json

Change the amount of a transaction, save — you just tampered with the chain. Run validation and watch it break

🗑️
rm block-5.json

The chain is now incomplete. The server detects this on startup

🔄
docker-compose restart

The chain persists across restarts — your blocks are still there

💡

How It Works

On startup, the server reads all block-*.json files from data/blocks/, sorts them by index, validates the chain, and rebuilds the balance map. If no files exist, it creates the genesis block (block-0.json). When a new block is mined and accepted, the server writes it to disk immediately. The in-memory chain and the files are always in sync.

⚙️

Configuration

Create a config.js file at the root of your project. All tuneable parameters live here: mining difficulty, mining reward amount, bot count, bot names, bot speed (interval in ms), whether bots include transactions, server port. The server reads this file on startup. Changing a value and restarting the server changes the network's behaviour.

Project Structure

minerace/
├── config.js              # All tuneable parameters
├── server.js              # Express app entry point
├── blockchain/
│   ├── Block.js           # Block class
│   ├── Blockchain.js      # Chain + balances + mempool + validation
│   ├── Transaction.js     # Transaction class (sign/verify)
│   └── Wallet.js          # Key pair generation (ECDSA)
├── bots/
│   └── botMiner.js        # Server-side bot mining logic
├── routes/
│   ├── chain.js           # GET /api/chain, /api/chain/:index
│   ├── mining.js          # GET /api/challenge, POST /api/submitBlock
│   ├── transactions.js    # POST /api/transaction, GET /api/mempool
│   └── chat.js            # POST /api/chat (Ollama integration)
├── data/
│   └── blocks/            # ONE JSON FILE PER BLOCK
│       ├── block-0.json   # Genesis block (created on first start)
│       ├── block-1.json   # Mined by Bot-Paris
│       ├── block-2.json   # Mined by you
│       └── ...
├── public/                    # Served statically by Express
│   ├── index.html         # Main frontend page
│   ├── style.css          # Styles
│   ├── app.js             # Frontend logic (wallets, UI, polling)
│   └── miner-worker.js    # Web Worker for browser-side mining
├── my-bot.js                  # YOUR custom mining bot (deliverable)
├── test-step1.js ... test-step10.js  # Test files (provided)
├── Dockerfile
├── docker-compose.yml
└── README.md

Technology Stack

🟢
Node.js
Blockchain engine + API
Prerequisite
Express
REST API server
Prerequisite
🌐
HTML / CSS / JS
Frontend — no framework
Prerequisite
📊
SVG / Canvas
Visual blockchain explorer
Recommended
🧠
Ollama
Local LLM — free, no API key
M0–M6
🐳
Docker
Containerised deployment
Required
🔒
Node.js crypto
SHA-256, ECDSA, key pairs
M2
🤖
Custom Mining Bot
Student-written script
M4
⛏️ Section 4

Mining & Competition

The server is the single source of truth — it holds the canonical chain, the public mempool, and validates all submitted blocks. But anyone can compete to mine the next block. The server doesn't care how you mine — it only cares that your block is valid and that you're first.

Three Ways to Mine

🖱️

Browser UI

Beginner

Click the "Mine" button in the frontend. A Web Worker starts hashing, the nonce counter spins visually. You watch proof of work happen in real-time.

🤖

Server Bots

Always running

Built-in bots (Bot-Paris, Bot-Tokyo, Bot-NYC) mine empty blocks by default — they don't grab transactions from the mempool, but they still earn mining rewards. Their speed, count, and behaviour are fully configurable.

💻

Your Own Bot

The real challenge

Write a script (JS, Python, anything) that calls GET /api/challenge, grabs the mempool, assembles a block, finds a valid nonce, and submits via POST /api/submitBlock. If your bot includes transactions and the server bots don't — you control what gets confirmed.

The Mining Race

1

Challenge announced: The server exposes the current challenge via GET /api/challenge — the previous block's hash, the difficulty target, the current mempool, and the next block index. Anyone can read it.

2

Miners compete: The browser UI, server bots, and student bots all start hashing simultaneously. Each tries to find a nonce that makes the block hash start with the required number of zeros.

3

First valid block wins: The first miner to POST /api/submitBlock with a valid block gets it added to the chain. The server validates: correct previous hash? Valid PoW? All included transactions signed and funded? Exactly one mining reward of 50 coins?

4

Losers start over: If you submit a block but someone was faster, the server responds: "Block N already mined by Bot-Paris, start on block N+1". Your previousHash is stale — you must call /api/challenge again.

What a Block Looks Like

{
  "index": 3,
  "timestamp": 1711878400,
  "previousHash": "000a3f8c2e...",
  "transactions": [
    {
      "from": "04a1b2c3d4e5...",      // sender public key
      "to": "04f6a7b8c9d0...",        // recipient public key
      "amount": 20,
      "signature": "3045022100..."    // ECDSA signature
    },
    {
      "from": "MINING_REWARD",         // special: coinbase tx
      "to": "04a1b2c3d4e5...",        // miner's public key
      "amount": 50,
      "signature": null                // no signature needed
    }
  ],
  "nonce": 847,
  "hash": "000f8c2a7b..."             // starts with "000" (difficulty = 3)
}
💡

About Server Bots

Bots mine empty blocks by default: they don't include transactions from the mempool, they just find a valid nonce and collect the mining reward. This means pending transactions stay in the mempool until someone (you, via UI or bot) includes them in a block. Everything is configurable: number of bots, their speed, names, and whether they include transactions. Experiment with the settings to see how competition intensity affects the network.

🏆

Key Insight

The blockchain doesn't care how you mine. It doesn't care if you clicked a button, wrote a Python script, or built a GPU farm. It only validates the result: is the hash valid? Is the block well-formed? Are the transactions legitimate? That's the beauty of a permissionless protocol — the rules are the same for everyone.

🎯 Section 5

10 Mini-Goals

Build incrementally. Each step is standalone testable — you code it, test it, confirm it works, then move on. A test file is provided for each step. Don't skip ahead.

1

Block & Chain

M3

Create a Block class (index, timestamp, previousHash, transactions, nonce, hash) and a Blockchain class with a genesis block. Each block's hash is computed from its contents using SHA-256. The chain is kept in memory as an array, but every block is also written to disk as a JSON file in data/blocks/ (block-0.json, block-1.json, ...). On startup, the chain is rebuilt by reading all block files from disk.

✅ node test-step1.js → Genesis block created, hash starts with correct prefix
2

Proof of Work

M4

Add a mineBlock(difficulty) method. It increments the nonce until the hash starts with difficulty zeros. Start with difficulty 2 ("00") in config.js. If mining is instant (<0.5s), increase to 3 ("000"). If it takes >10s, decrease to 2. The goal: 1–3 seconds per block, enough to feel the work without waiting.

✅ node test-step2.js → Block mined, hash starts with "000", nonce > 0
3

Wallet (Keys & Signatures)

M2

Create a Wallet class using the elliptic npm package with the secp256k1 curve (same as Bitcoin). It generates a key pair, can sign(hash) a SHA-256 digest, and anyone can verify(hash, signature, publicKey). The public key (hex, starting with 04) is the wallet's address. Use the same library on server and browser to avoid format mismatches.

✅ node test-step3.js → Sign a message, verify OK, tamper → verify FAILS
4

Transactions & Balances

M3

Create a Transaction class (from, to, amount, signature). The blockchain tracks balances in a balances = {} map. Before accepting a transaction into the mempool: (1) verify the signature against the sender's public key, (2) compute available balance = confirmed balance (from mined blocks) minus sum of amounts in pending mempool txs from this sender, (3) reject if available < amount. Example: if Alice has 100 confirmed coins and 60 pending in the mempool, her available balance is 40 — a 50-coin tx is rejected.

✅ node test-step4.js → Valid tx accepted, overdraft rejected, bad signature rejected
5

Mining Rewards & Full Cycle

M3–M4

When a block is mined, the miner includes a special coinbase transaction: from: "MINING_REWARD", to: minerAddress, amount: 50. The server validates: exactly one reward tx per block, amount must be 50, no signature needed. This is the only way coins enter the system.

✅ node test-step5.js → Mine block, miner balance = 50, send 20 to another wallet, mine again, balances correct
6

REST API (Express)

Prerequisite

Wrap the blockchain engine in an Express server. Expose all endpoints: chain, balance, mempool, challenge, transaction submission, block submission, chat, and stats. Test each endpoint with curl.

✅ curl http://localhost:3000/api/chain → JSON response with genesis block
7

Bot Miners

M4–M5

Add server-side bots that mine automatically. They run on intervals, find valid nonces, and submit empty blocks (reward only, no transactions). Make them configurable: number of bots, speed (interval in ms), names. Bots should be enabled/disabled via config.

✅ Start server → wait 15s → GET /api/chain shows blocks mined by bots
8

Frontend & Visual Blockchain

Prerequisite

Build the web interface: wallet management (create, select, see balance), transaction form (recipient, amount, sign & send), mine button with live nonce counter, and a visual blockchain explorer. Use SVG or Canvas for the chain visualisation — blocks as connected nodes, colour-coded by miner, expandable to show transactions. Add a leaderboard showing mining stats.

✅ Open browser → create wallet, mine, send coins, see chain grow visually
9

AI Agent (Ollama)

New

Integrate a local LLM via Ollama (runs in Docker, free, no API key). The /api/chat endpoint sends the user's question plus a chain context summary (current block height, all balances, recent transactions, mining stats) to the LLM. The AI can answer: "Who has the most coins?", "Who mined block 5?", "Is the chain valid?", "Explain the last transaction".

✅ POST /api/chat {"message": "who mined block 3?"} → meaningful answer
10

Docker & Containerisation

Required

Create a Dockerfile for the app and a docker-compose.yml with two services: app (Node.js + Express + frontend + bots) and ollama (local LLM). One command to start everything: docker-compose up. Open the browser, and the full system is running.

✅ docker-compose up → open localhost:3000 → everything works end to end

docker-compose.yml Reference

docker-compose.yml

services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./data:/app/data          # blocks persist on host disk
    environment:
      - OLLAMA_HOST=http://ollama:11434
    depends_on:
      - ollama

  ollama:
    image: ollama/ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama-data:/root/.ollama  # model cache persists
    # Pull a model on first start:
    # docker exec minerace-ollama-1 ollama pull llama3.2

volumes:
  ollama-data:
💡

Important: Pull the Model

After docker-compose up, the Ollama container is running but has no model yet. You need to pull one: docker exec minerace-ollama-1 ollama pull llama3.2 (about 2 GB). This only needs to be done once — the model is cached in the ollama-data volume. Alternatively, add a startup script in your Dockerfile that pulls the model automatically.

⚠️

What Persists, What Doesn't

Blocks are saved to data/blocks/ and survive restarts. But the mempool is in memory only — pending transactions are lost if the server restarts. This is by design: just like Bitcoin, unconfirmed transactions are volatile. Miners should include pending transactions in blocks quickly, or they risk losing them. This is a teaching moment, not a bug.

📡 Section 6

REST API Reference

The API is the protocol. It's what the browser uses, what the bots use, and what your custom bot will use. Everyone speaks the same language.

Endpoint Method Description
GET /api/chain GET Full chain (all blocks)
GET /api/chain/:index GET Single block by index
GET /api/balance/:address GET Confirmed + available balance for an address
GET /api/mempool GET Pending transactions (public)
GET /api/challenge GET Current mining challenge: previousHash, difficulty, pendingTxs, blockIndex
POST /api/transaction POST Submit a signed transaction to the mempool
POST /api/submitBlock POST Submit a mined block — first valid submission wins
POST /api/chat POST Ask the AI agent about the chain state
GET /api/stats GET Mining stats: who mined what, win rates, leaderboard

Request & Response Examples

POST /api/transaction
Content-Type: application/json

{
  "from": "04a1b2c3d4e5f6...",       // sender public key (hex)
  "to": "04f6a7b8c9d0e1...",         // recipient public key (hex)
  "amount": 20,
  "signature": "3045022100abc..."     // ECDSA signature of "04a1...04f6...20"
}

Response (201): { "message": "Transaction added to mempool" }
Response (400): { "error": "Insufficient balance (have: 10, need: 20)" }
Response (400): { "error": "Invalid signature" }
POST /api/submitBlock
Content-Type: application/json

{
  "index": 4,
  "timestamp": 1711878400,
  "previousHash": "000a3f8c2e7b...",
  "transactions": [
    { "from": "04a1...", "to": "04f6...", "amount": 20, "signature": "3045..." },
    { "from": "MINING_REWARD", "to": "04a1...", "amount": 50, "signature": null }
  ],
  "nonce": 1847,
  "hash": "0002f8c1a3..."
}

Response (201): { "message": "Block 4 accepted", "miner": "04a1..." }
Response (409): { "error": "Block 4 already mined by Bot-Paris" }
Response (400): { "error": "Invalid previousHash — expected 000f8c..., got 000a3f..." }
🔒

Wallet Address Format

A wallet address is the full public key in hex (uncompressed secp256k1, starting with 04). To sign a transaction, compute SHA-256(from + to + amount) with no delimiter (e.g. SHA256("04a1b2...04f6a7...20")), then sign the resulting hash. Signatures are DER-encoded hex strings. The server verifies the same hash with the sender's public key. Truncate in the UI (e.g. 04a1b2...e5f6) but always use the full key for cryptographic operations.

🌐

CORS

Since the frontend is served by the same Express server (from public/), you won't have CORS issues for browser requests. But if your custom mining bot runs as a separate Node.js script or from another origin, add cors() middleware to Express: app.use(require('cors')()). This is one line but easy to forget.

🔍

Server Validation Rules

When a block is submitted via POST /api/submitBlock, the server checks: (1) previousHash matches the current chain tip, (2) the hash starts with the required number of zeros, (3) all included transactions have valid signatures and sufficient balances, (4) there is exactly one mining reward transaction with amount = 50, (5) no one has already mined this block index. If any check fails, the block is rejected with a clear error message.

Example: GET /api/stats Response

{
  "blockHeight": 12,
  "difficulty": 3,
  "chainValid": true,
  "miners": [
    { "address": "Bot-Paris",    "blocksMined": 5, "earnings": 250 },
    { "address": "Bot-Tokyo",    "blocksMined": 3, "earnings": 150 },
    { "address": "04a1b2...e5f6", "blocksMined": 4, "earnings": 200 }
  ],
  "mempool": {
    "pending": 2,
    "totalAmount": 45
  }
}
📋

How the Frontend Discovers Addresses

The recipient dropdown in the transaction form is populated by fetching GET /api/chain and extracting all unique from/to values from every transaction across all blocks. This includes bot addresses and other wallets. The frontend should refresh this list periodically (every 5s) to catch new miners and recipients.

🎨 Section 7

Frontend & AI Agent

The frontend is where the magic becomes visible. It's pure HTML, CSS, and JavaScript — no framework. The UI should make the student feel like a participant in a real blockchain network.

User Interface

💳

Wallet Panel

Create multiple wallets (key pairs generated in the browser). Switch between them. See each wallet's balance. The private key never leaves the browser — transactions are signed client-side.

📨

Transaction Panel

Select recipient address, enter amount, sign the transaction client-side, and broadcast to the server. See the transaction appear in the mempool. Watch it get confirmed when the next block is mined.

⛏️

Mining Panel

Click "Mine" and watch the nonce counter increment live. Mining runs in a Web Worker (miner-worker.js) so it doesn't freeze the UI. The worker receives the block data via postMessage(), hashes in a loop, and posts back progress (nonce count) and the result (valid hash + nonce). See who wins the race — you or a bot. When a block is mined (by anyone), it appears in the chain visualisation in real-time.

💬

AI Chat Panel

Ask the AI anything about the chain: "Who has the most coins?", "Is block 4 valid?", "Summarise the last 5 transactions". The AI has full context of the current chain state.

🔒

Crypto: Exact Specification

Recommended approach: use the elliptic npm package on both server and browser (avoids format mismatch). Use the secp256k1 curve (same as Bitcoin). Generate key pairs with ec.genKeyPair(), sign with key.sign(hash), verify with key.verify(hash, signature). The public key (hex, uncompressed, starting with 04) is the wallet address. To sign a transaction, hash the string from + to + amount concatenated with no delimiter (e.g. "04a1b2...04f6a7...20") using SHA-256, then sign the hash. Signatures are DER-encoded hex strings. Private keys stay in localStorage and are never sent to the server.

Visual Blockchain Explorer

This is the centrepiece of your frontend. Use SVG or Canvas to render the blockchain as a visual graph:

🔗

Block Nodes

Connected by arrows showing the previousHash link

🎨

Colour-Coded

Your blocks are one colour, each bot has its own

🔍

Expandable

Click a block to see transactions, nonce, hash, timestamp

Live Updates

New blocks animate into view as they are mined

Chain Validation

Valid blocks glow green, broken blocks turn red

🏆

Leaderboard

Who mined the most blocks, who earned the most rewards

🎨

Be Creative

The visual explorer is where you can shine. Animate block additions, show hash connections with dotted lines, use particle effects when a block is mined, show the nonce search as a progress bar. The more intuitive and beautiful your visualisation, the better. SVG is recommended for crisp rendering, Canvas for performance with many blocks.

AI Agent (Ollama)

The AI agent runs locally via Ollama — no API key, no cost. It runs as a Docker container alongside your app. The /api/chat endpoint builds a system prompt with:

📊 Block height & difficulty 💰 All wallet balances 📝 Last N transactions 🏆 Mining leaderboard ✅ Chain validity status

Then appends the user's question and sends everything to Ollama. The model generates a contextualised answer. Use llama3.2 or mistral — small models that run on any laptop.

How to Call Ollama

Ollama exposes a simple HTTP API. In your Docker network, the Ollama container is reachable at http://ollama:11434. Your backend's /api/chat handler does this:

// routes/chat.js  (server-side, Node.js)

app.post('/api/chat', async (req, res) => {
  const { message } = req.body;

  // 1. Build chain context from the blockchain engine
  const context = buildChainContext(blockchain);
  // e.g. "Block height: 12. Difficulty: 3.
  //        Balances: 04a1b2...=180, 04f6a7...=70, Bot-Paris=250
  //        Last 3 txs: 04a1... sent 20 to 04f6... (block 11) ..."

  // 2. Call Ollama's API
  const response = await fetch('http://ollama:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'llama3.2',          // or 'mistral'
      prompt: message,
      system: `You are a blockchain assistant. Here is the current
               state of the chain:\n\n${context}\n\n
               Answer the user's question based on this data.`,
      stream: false                // wait for full response
    })
  });

  const data = await response.json();
  res.json({ reply: data.response });
});

What buildChainContext() Returns

The quality of your AI depends on this function. It reads the in-memory chain and produces a text summary like this:

// Example output of buildChainContext(blockchain)

Blockchain Status:
- Block height: 12
- Difficulty: 3
- Chain valid: yes

Balances:
- 04a1b2...e5f6: 180 coins (mined 4 blocks)
- 04f6a7...d0e1: 70 coins (mined 0 blocks)
- Bot-Paris: 250 coins (mined 5 blocks)
- Bot-Tokyo: 150 coins (mined 3 blocks)

Last 5 transactions:
- Block 12: 04a1b2... sent 20 to 04f6a7...
- Block 12: MINING_REWARD → 04a1b2... (50 coins)
- Block 11: Bot-Paris mined empty block (reward only)
- Block 10: 04f6a7... sent 10 to 04a1b2...
- Block 10: MINING_REWARD → Bot-Tokyo (50 coins)

Mempool (pending): 1 transaction
- 04a1b2... → 04f6a7...: 15 coins (waiting to be mined)
💡

The Key Pattern

The LLM doesn't "read" the blockchain directly. Your backend reads the chain (it has direct access to the Blockchain object in memory), serialises it into a text summary, and injects it into the system prompt. The LLM then answers questions based on that text. This is the same pattern used in RAG systems — give the model context, then ask a question. The quality of your AI agent depends entirely on how well you build the buildChainContext() function.

⚠️

Graceful Fallback

Ollama may be slow (first request loads the model into memory), unreachable (container not started), or fail (model not pulled yet). Your /api/chat must handle all cases: set a 30-second timeout on the fetch call (signal: AbortSignal.timeout(30000)), catch errors, and return a helpful JSON response like {"error": "AI unavailable", "hint": "Run: docker exec minerace-ollama-1 ollama pull llama3.2"}. The rest of the app must work perfectly without the AI — it's an enhancement, not a dependency.

📋 Section 8

Deliverables & Grading

Required Deliverables

1 Blockchain Engine

Balance-based blockchain with PoW, transaction signing/verification, balance tracking, mining rewards, and chain validation. Pure JavaScript, no external blockchain libraries.

2 REST API

Express server exposing all endpoints (chain, balance, mempool, challenge, transaction, submitBlock, chat, stats). Proper error messages for invalid submissions.

3 Frontend & Visual Explorer

Wallet management, transaction form, mining UI with live nonce counter, visual blockchain (SVG/Canvas), chain validation display, and leaderboard. Responsive design.

4 Custom Mining Bot

A student-written script that calls the API, assembles blocks from the mempool, mines, and submits. This proves you understand the protocol at the API level.

5 AI Agent (Ollama)

Chat endpoint that gives the LLM full chain context and returns meaningful answers about blockchain state. Graceful fallback if Ollama is unavailable.

6 Docker & Documentation

Dockerfile + docker-compose.yml. One command to start everything. README with setup instructions, architecture diagram, and screenshots or demo video.

Grading Criteria

Criterion Weight Description
Blockchain Engine 25% Balance model, PoW, signature verification, chain validation, mining rewards
REST API 15% All endpoints working, proper validation and error messages
Frontend & Visual Explorer 20% Wallet, transactions, mining UI, visual chain (SVG/Canvas), leaderboard
Custom Mining Bot 15% Script that mines via API, includes transactions, competes with bots
AI Agent 10% Contextual answers, chain-aware responses, graceful Ollama fallback
Docker & Docs 10% docker-compose up works, README clear, architecture diagram included
Code Quality 5% Clean code, modular structure, no hardcoded secrets, consistent naming

Optional Extensions Bonus

+5 pts
🌐

P2P Simulation

Run 2+ app containers in Docker that gossip blocks to each other. Simulate a real multi-node network with fork resolution.

+5 pts
📈

Difficulty Adjustment

Dynamically adjust mining difficulty based on block time (like Bitcoin). If blocks come too fast, increase difficulty; too slow, decrease it.

+5 pts
🔍

Block Explorer Page

A dedicated page with searchable transaction history per address, block details, and a timeline of all mining activity.

+5 pts
💸

Transaction Fees

Add optional transaction fees. Miners can prioritise higher-fee transactions. Introduces the concept of a fee market.

+5 pts
🛡️

Chain Tampering Sandbox

A UI feature that lets the user modify a block's data and see the chain validation break in real-time. Visually demonstrates immutability.

+5 pts
🏁

Mining Race Dashboard

Real-time dashboard showing all active miners, their hash rates, who's closest to finding a block, and historical win rates with charts.

Module-by-Module Alignment

Every piece of this project maps directly to a concept you learned in Part 1:

Module Concept Application in Project
M0 Transaction & block anatomy Block structure, transaction structure, chain explorer
M1 The double-spend problem Balance validation, overdraft rejection
M2 SHA-256, ECDSA, digital signatures Block hashing, wallet key pairs, transaction signing
M3 Building a cryptocurrency Full blockchain engine (balances, transactions, blocks)
M4 Proof of Work, mining, consensus Mining competition, bot miners, block validation
M5 Network, forks, governance Stale block handling, chain tip management
M6 Balance model (Ethereum) Balance/account model instead of UTXO
🚀

Summary

This project takes everything you learned in Part 1 and turns it into a real, interactive blockchain you can see, touch, and compete on. You'll hash blocks, sign transactions, race against bots, and chat with an AI about your chain — all from your browser, all running in Docker. When you're done, you'll have built a blockchain from scratch. Not read about it. Not studied it. Built it. Good luck, and may the best miner win.