Skip to content

bornofgreatness/BGA-home-task

Repository files navigation

Blockchain HomeTask Project

A blockchain implementation with a layered Express backend and a React frontend.

For Applicants: See INSTRUCTIONS.md for task requirements (2 tasks, 4–6 hours). See SETUP.md for a quick-start guide.


Changes

Task 1 — Cryptographic wallet system

Backend

  • POST /api/wallets — generates a secp256k1 key pair (publicKey as SPKI DER hex, privateKey as PKCS#8 PEM) via crypto.generateKeyPairSync.
  • Transaction.signTransaction(signingKey) — signs the transaction hash with Node crypto.sign / ECDSA; accepts PEM or KeyObject.
  • Transaction.isValid() — rejects unsigned user transactions; mining rewards (fromAddress === null) remain valid.
  • Blockchain.addTransaction() — requires a non-empty signature before accepting a transaction.
  • POST /api/transactions — body now includes timestamp and signature (client signs before submit).
  • Demo seed uses real wallets and signed transactions instead of plain-string addresses.

Frontend

  • Wallet component — generates a wallet, shows address and balance, keeps the private key in React state only.
  • TransactionForm — signs with the stored private key (src/utils/walletSigning.js + @noble/curves) before posting.
  • Mine block pays the active wallet’s public key when a wallet is loaded.
  • Balance display includes pending transfers immediately; shows on-chain confirmed balance when they differ.

New / updated files: utils/walletCrypto.js, controllers/wallet.controller.js, routes/wallet.routes.js, src/components/Wallet.js, src/utils/walletSigning.js, and related API/UI wiring.

Task 2 — Blockchain persistence

  • services/persistence.service.jssave(blockchain), load(), and clear() read/write blockchain.json in the project root (gitignored).
  • models/index.js — restores from disk on startup when valid; otherwise seeds demo data. Auto-saves after every successful addTransaction and minePendingTransactions.
  • Corrupt JSON, invalid shape, empty chain, or failed isChainValid() → log a warning, remove the bad file, start fresh (no crash).

Environment variables

No new env vars were introduced. Existing variables still apply:

Variable Default Purpose
PORT 3002 API server port
NODE_ENV development Logging level
CORS_ORIGIN http://localhost:3000 Allowed frontend origin
BLOCKCHAIN_DIFFICULTY 2 Proof-of-work difficulty
BLOCKCHAIN_MINING_REWARD 100 Coins per mined block
INITIAL_MINER_ADDRESS genesis-miner Demo seed mining reward recipient
SEED_DEMO_DATA enabled (!== 'false') Seed demo chain when no valid snapshot exists

Persistence path is fixed at ./blockchain.json (not configurable via env).

New npm dependencies

  • @noble/curves, @noble/hashes — browser-side ECDSA signing compatible with Node crypto.verify.

Known limitations and trade-offs

  • Single process, synchronous I/OwriteFileSync / readFileSync block the event loop briefly; fine for a demo, not for high throughput.
  • No spend validation — the chain accepts signed transfers even when the sender’s balance is insufficient; balances are informational only.
  • Private keys in the browser — stored in React state only, but not encrypted at rest; refreshing the page loses the key unless you export it yourself.
  • Browser signing dependency — frontend uses @noble/curves to match Node signatures; PEM parsing on the client is minimal (secp256k1 PKCS#8 only).
  • Multi-tab / multi-wallet — a second browser tab does not auto-sync balances; use Refresh Balance after the other tab mines.
  • Demo seed vs snapshot — if blockchain.json loads successfully, demo seed is skipped even when SEED_DEMO_DATA is enabled.
  • Config drift — changing BLOCKCHAIN_DIFFICULTY or BLOCKCHAIN_MINING_REWARD in env does not migrate an existing file; difficulty/reward come from the saved snapshot when loaded.

Project Structure

hometask-blockchain/
│
├── config/
│   └── index.js                  # Environment config (port, CORS, blockchain settings)
│
├── models/
│   ├── blockchain.js             # Block, Transaction, Blockchain domain classes
│   └── index.js                  # Singleton instance + demo data seeding
│
├── services/
│   └── persistence.service.js    # save / load / clear blockchain.json
│
├── utils/
│   ├── logger.js                 # Levelled logger (error / warn / info / debug)
│   ├── response.js               # Unified sendSuccess / sendCreated / sendError helpers
│   ├── validator.js              # isValidAddress, isValidAmount, sanitizers
│   └── walletCrypto.js           # secp256k1 key generation helpers
│
├── middleware/
│   ├── cors.middleware.js        # CORS policy
│   ├── logger.middleware.js      # Morgan HTTP request logger
│   ├── errorHandler.middleware.js# Centralised error handler (must be last)
│   ├── notFound.middleware.js    # 404 handler
│   ├── validateRequest.middleware.js  # validateBody / validateParams factories
│   └── rateLimit.middleware.js   # apiLimiter (100 req/min) + writeLimiter (20 req/min)
│
├── routes/
│   ├── index.js                  # Aggregates all /api sub-routes
│   ├── blockchain.routes.js      # /api/chain
│   ├── transaction.routes.js     # /api/transactions
│   ├── wallet.routes.js          # /api/wallets
│   ├── mining.routes.js          # /api/mine
│   ├── balance.routes.js         # /api/balance
│   ├── stats.routes.js           # /api/stats
│   └── health.routes.js          # /health (no rate limit)
│
├── controllers/
│   ├── blockchain.controller.js
│   ├── transaction.controller.js
│   ├── wallet.controller.js
│   ├── mining.controller.js
│   ├── balance.controller.js
│   └── stats.controller.js
│
├── src/                          # React frontend
│   ├── api/
│   │   ├── client.js             # Axios instance with request/response interceptors
│   │   ├── endpoints.js          # All API URL constants
│   │   └── blockchain.api.js     # Typed fetch functions (fetchChain, addTransaction…)
│   ├── hooks/
│   │   ├── useBlockchain.js      # Polls /api/chain + /api/stats, returns state
│   │   └── usePolling.js         # Reusable interval-based polling hook
│   ├── utils/
│   │   ├── formatters.js         # truncateHash, formatTimestamp, formatAmount
│   │   ├── helpers.js            # isPositiveNumber, groupTransactionsByBlock, etc.
│   │   └── walletSigning.js      # Client-side transaction hash + ECDSA sign
│   ├── constants/
│   │   └── index.js              # POLL_INTERVAL_MS, DEFAULT_MINER_ADDRESS, enums
│   ├── components/
│   │   ├── BlockchainViewer.js
│   │   ├── Wallet.js
│   │   ├── TransactionForm.js
│   │   ├── StatsPanel.js
│   │   ├── Header.js
│   │   └── ErrorBoundary.js      # React class error boundary
│   ├── App.js
│   └── index.js
│
├── blockchain.js                 # Backward-compat re-export → models/blockchain.js
├── server.js                     # Entry point — wires middleware, routes, starts server
└── package.json

Getting Started

Prerequisites

  • Node.js v16 or higher
  • npm

Install & Configure

npm install

Run in Development

# Terminal 1 — React dev server on http://localhost:3000
npm start

# Terminal 2 — API server on http://localhost:3002, with auto-reload
npm run dev

The React app proxies all /api/* requests to the API server automatically via src/setupProxy.js.

Run in Production

npm run serve   # builds the React app, then serves everything from port 3002

API Reference

All API responses share a common envelope:

{ "success": true, ...payload }
{ "success": false, "error": "message" }

Chain

Method Path Description
GET /api/chain Full chain + length
GET /api/chain/valid { isValid: bool }

Transactions

Method Path Description
POST /api/transactions Add a pending transaction
GET /api/transactions/pending All pending transactions
GET /api/transactions/all All confirmed transactions

POST /api/transactions body:

{
  "fromAddress": "<spki-der-hex-public-key>",
  "toAddress": "<spki-der-hex-public-key>",
  "amount": 100,
  "timestamp": 1710000000000,
  "signature": "<ecdsa-der-hex>"
}

Wallets

Method Path Description
POST /api/wallets Generate { publicKey, privateKey } (PEM private key)

Mining

Method Path Description
POST /api/mine Mine pending transactions into a new block

POST /api/mine body:

{ "miningRewardAddress": "miner1" }

Balance

Method Path Description
GET /api/balance/:address Confirmed balance of an address

Stats

Method Path Description
GET /api/stats Chain length, difficulty, validity, pending count

Health

Method Path Description
GET /health Server uptime, env, timestamp — no rate limit

Frontend Architecture

The React app is organised into distinct concerns:

  • src/api/ — all network calls live here. Components never call fetch/axios directly.
  • src/hooks/useBlockchain — single source of truth for chain + stats state; polls every 5 s.
  • src/utils/formatters — pure formatting functions (hash truncation, timestamps, amounts).
  • src/constants/ — magic strings and numbers in one place.
  • ErrorBoundary — catches any unhandled React render errors gracefully.

Technologies

Backend

  • Node.js + Express
  • morgan — HTTP request logging
  • dotenv — environment variable loading
  • express-rate-limit — API rate limiting
  • cors — CORS policy middleware
  • Node.js built-in crypto — SHA-256 hashing, secp256k1 wallets, ECDSA verify

Frontend

  • React 18
  • Axios (with interceptors)
  • @noble/curves / @noble/hashes — browser ECDSA signing
  • CSS3 (glassmorphism, gradients, animations)

Troubleshooting

Port already in use

# Use a different port
PORT=3003 npm run dev

Frontend can't reach the API

  • Confirm npm run dev is running on port 3002
  • Confirm src/setupProxy.js target matches PORT

Chain resets on every restart

  • State is persisted to blockchain.json. Delete that file (or call persistence.clear() from a script) to start fresh.
  • Corrupt blockchain.json is removed automatically; the server falls back to demo seed when SEED_DEMO_DATA is enabled.

License

MIT — for learning and assessment purposes.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors