diff --git a/.gitignore b/.gitignore index 826c281..18393b6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ # RuboCop cache .rubocop-* +tasks/ diff --git a/CLAUDE.md b/CLAUDE.md index 89d7aed..b3ff96c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -350,7 +350,7 @@ Location: `.github/workflows/ci.yml` 3. Create validation contract in `src/validators.rb` (if needed) 4. Add tests in `spec/api_spec.rb` 5. Update README.md API Reference -6. Update API_DOCUMENTATION.md +6. Update docs/api/reference.md 7. Run tests and RuboCop ### Modifying Block/Blockchain Models @@ -370,7 +370,7 @@ Location: `.github/workflows/ci.yml` ### Adjusting Rate Limits 1. Edit `config/rack_attack.rb` 2. Update README.md Rate Limiting table -3. Update API_DOCUMENTATION.md +3. Update docs/api/reference.md and docs/api/rate-limiting.md 4. Consider impact on user experience 5. Test with actual API calls @@ -379,7 +379,7 @@ Location: `.github/workflows/ci.yml` ### Common Issues **MongoDB Connection Errors** -- Ensure MongoDB is running: `docker-compose up -d mongodb` +- Ensure MongoDB is running: `docker-compose up -d db` - Check `.env` has correct MONGO_DB_HOST, PORT, NAME - For Docker: use `host: mongodb` not `localhost` @@ -432,13 +432,22 @@ chain_forge/ │ └── workflows/ │ └── ci.yml # GitHub Actions CI pipeline └── docs/ - ├── README.md # Main user documentation - ├── CLAUDE.md # This file (Claude Code guidance) - ├── CHANGELOG.md # Version history - ├── CONTRIBUTING.md # Contribution guidelines - ├── SECURITY.md # Security policies - ├── API_DOCUMENTATION.md # Complete API reference - └── DEPLOYMENT.md # Production deployment guide + ├── README.md # Documentation index + ├── CLAUDE.md # This file (Claude Code guidance) + ├── CHANGELOG.md # Version history + ├── CONTRIBUTING.md # Contribution guidelines + ├── SECURITY.md # Security policies + ├── getting-started/ # Tutorial files + ├── architecture/ # Architecture documentation + ├── api/ + │ ├── reference.md # Complete API reference + │ ├── examples.md # Code examples + │ └── rate-limiting.md # Rate limiting guide + └── guides/ + ├── development-setup.md + ├── testing-guide.md + ├── deployment-guide.md # Production deployment guide + └── troubleshooting.md ``` ## Educational Objectives diff --git a/README.md b/README.md index b7b64c6..e987f31 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,22 @@ # ChainForge -> **Educational Side Project**: ChainForge is a learning-focused blockchain implementation built to understand core blockchain concepts like cryptographic hashing, Proof of Work mining, chain validation, and distributed ledger fundamentals. This is NOT intended for production use. +> **Educational Blockchain Implementation** - Learn blockchain fundamentals through hands-on development with Ruby, Sinatra, and MongoDB. [![Ruby](https://img.shields.io/badge/Ruby-3.2.2-red.svg)](https://www.ruby-lang.org/) [![MongoDB](https://img.shields.io/badge/MongoDB-Latest-green.svg)](https://www.mongodb.com/) [![Sinatra](https://img.shields.io/badge/Sinatra-4.0-blue.svg)](http://sinatrarb.com/) [![CI](https://img.shields.io/badge/CI-GitHub%20Actions-brightgreen.svg)](https://github.com/Perafan18/chain_forge/actions) -A blockchain implementation with REST API built using Ruby, Sinatra, and MongoDB. Explore how blocks are linked through cryptographic hashes, how Proof of Work mining secures the chain, and how API security works. +A blockchain implementation with Proof of Work mining, REST API, and comprehensive security features. ChainForge demonstrates core blockchain concepts including cryptographic hashing, chain validation, and immutability through a clean, well-tested Ruby codebase. ## Features -### Core Blockchain -- ✅ Create independent blockchain instances -- ✅ Add blocks with custom data -- ✅ SHA256 cryptographic hashing -- ✅ Chain integrity validation -- ✅ Genesis block auto-generation - -### Version 2 Features -- ✅ **Proof of Work (PoW)** mining algorithm with configurable difficulty -- ✅ **API Versioning** - All endpoints under `/api/v1` -- ✅ **Rate Limiting** - Rack::Attack protection (60 req/min) -- ✅ **Input Validation** - dry-validation with detailed errors -- ✅ **Environment Configuration** - Configurable mining difficulty -- ✅ **CI/CD Pipeline** - GitHub Actions with automated testing -- ✅ **Code Quality** - RuboCop linting and SimpleCov coverage +- ✅ **Proof of Work Mining** - Configurable difficulty (1-10) +- ✅ **RESTful API** - Versioned endpoints (`/api/v1`) +- ✅ **Chain Integrity** - SHA256 hashing and validation +- ✅ **Security** - Rate limiting, input validation +- ✅ **Well-Tested** - RSpec tests with >90% coverage +- ✅ **CI/CD** - Automated testing and linting ## Quick Start @@ -37,473 +28,180 @@ A blockchain implementation with REST API built using Ruby, Sinatra, and MongoDB ### Installation -1. **Install Ruby 3.2.2** - ```bash -rbenv install 3.2.2 -rbenv local 3.2.2 -``` +# Clone repository +git clone https://github.com/Perafan18/chain_forge.git +cd chain_forge -2. **Install Dependencies** - -```bash +# Install dependencies bundle install -``` - -3. **Configure Environment** -```bash +# Configure environment cp .env.example .env -``` - -Edit `.env` and configure: -- `MONGO_DB_NAME` - Database name (default: chain_forge) -- `MONGO_DB_HOST` - MongoDB host (default: localhost) -- `MONGO_DB_PORT` - MongoDB port (default: 27017) -- `DEFAULT_DIFFICULTY` - Mining difficulty 1-10 (default: 2) -- `ENVIRONMENT` - Runtime environment (development/test/production) - -4. **Start MongoDB** - -```bash -# Using Docker (recommended) -docker-compose up -d mongodb - -# Or install locally -brew install mongodb-community -brew services start mongodb-community -``` -5. **Run the Application** +# Start MongoDB (or use Docker) +brew services start mongodb-community # macOS +# Or: docker-compose up -d db -```bash +# Run application ruby main.rb -p 1910 ``` -Visit http://localhost:1910 to verify it's running. - ### Using Docker (Recommended) ```bash docker-compose up ``` -This starts both the application and MongoDB with proper networking. - -## Development - -### Run Tests - -```bash -# Run all tests -bundle exec rspec - -# Run with coverage report -COVERAGE=true bundle exec rspec - -# View coverage -open coverage/index.html -``` - -### Code Quality - -```bash -# Run RuboCop linter -bundle exec rubocop - -# Auto-fix RuboCop issues -bundle exec rubocop -a -``` - -### Continuous Integration - -This project uses GitHub Actions for automated testing. Each push and PR triggers: -- ✅ RuboCop style checks -- ✅ RSpec test suite (17 examples) -- ✅ SimpleCov coverage reporting -- ✅ MongoDB integration tests - -See `.github/workflows/ci.yml` for pipeline configuration. - -## API Reference +Application will be available at -All endpoints are prefixed with `/api/v1` and protected by rate limiting. - -### Rate Limiting - -| Endpoint Pattern | Limit | Window | -|-----------------|-------|--------| -| All endpoints | 60 requests | 1 minute | -| POST /api/v1/chain | 10 requests | 1 minute | -| POST /api/v1/chain/:id/block | 30 requests | 1 minute | - -When rate limit is exceeded: -```json -{ - "error": "Rate limit exceeded. Please try again later." -} -``` -**HTTP Status**: 429 (Too Many Requests) - -### Create a New Blockchain +### Your First Blockchain ```bash +# Create blockchain curl -X POST http://localhost:1910/api/v1/chain -``` - -**Response:** -```json -{"id": "507f1f77bcf86cd799439011"} -``` - -**Rate Limit:** 10 requests/minute per IP - ---- - -### Add a Block to Chain (Mine) - -```bash -curl -X POST http://localhost:1910/api/v1/chain/507f1f77bcf86cd799439011/block \ - -H 'Content-Type: application/json' \ - -d '{"data": "Transaction data here", "difficulty": 3}' -``` - -**Request Parameters:** -- `data` (required, string): The data to store in the block -- `difficulty` (optional, integer 1-10): Mining difficulty (default: uses `DEFAULT_DIFFICULTY` env var) - -**Response:** -```json -{ - "chain_id": "507f1f77bcf86cd799439011", - "block_id": "507f191e810c19729de860ea", - "block_hash": "000a1b2c3d4e5f...", - "nonce": 1542, - "difficulty": 3 -} -``` - -**Validation Errors (400):** -```json -{ - "errors": { - "data": ["must be filled"], - "difficulty": ["must be between 1 and 10"] - } -} -``` +# Returns: {"id":"674c8a1b2e4f5a0012345678"} -**Rate Limit:** 30 requests/minute per IP - -**Note:** Mining can take time depending on difficulty: -- Difficulty 1-2: ~1 second -- Difficulty 3-4: Few seconds -- Difficulty 5+: Minutes or longer - ---- - -### Get Block Details - -```bash -curl http://localhost:1910/api/v1/chain/507f1f77bcf86cd799439011/block/507f191e810c19729de860ea -``` - -**Response:** -```json -{ - "chain_id": "507f1f77bcf86cd799439011", - "block": { - "id": "507f191e810c19729de860ea", - "index": 1, - "data": "Transaction data here", - "hash": "000a1b2c3d4e5f...", - "previous_hash": "00f8a2b1c3d4...", - "nonce": 1542, - "difficulty": 3, - "timestamp": 1699564821, - "valid_hash": true - } -} -``` - ---- - -### Validate Block Data - -```bash -curl -X POST http://localhost:1910/api/v1/chain/507f1f77bcf86cd799439011/block/507f191e810c19729de860ea/valid \ +# Mine a block +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ -H 'Content-Type: application/json' \ - -d '{"data": "Transaction data here"}' -``` + -d '{"data": "Hello, Blockchain!", "difficulty": 2}' -**Response:** -```json -{ - "chain_id": "507f1f77bcf86cd799439011", - "block_id": "507f191e810c19729de860ea", - "valid": true -} +# Returns: { +# "chain_id": "674c8a1b2e4f5a0012345678", +# "block_id": "674c8b2c3e5f6a0012345679", +# "block_hash": "00a1b2c3d4e5f6789...", +# "nonce": 142, +# "difficulty": 2 +# } ``` ---- - -For complete API documentation, see [API_DOCUMENTATION.md](API_DOCUMENTATION.md). +## Documentation -## Proof of Work (PoW) +Comprehensive documentation is available in the [`docs/`](docs/) folder: -ChainForge implements a simplified Proof of Work consensus mechanism similar to Bitcoin's mining process. +### Getting Started +- **[Installation Guide](docs/getting-started/installation.md)** - Detailed setup instructions +- **[Quick Start Tutorial](docs/getting-started/quick-start.md)** - Create your first blockchain in 5 minutes +- **[First Blockchain Tutorial](docs/getting-started/first-blockchain-tutorial.md)** - Complete walkthrough with mining -### How Mining Works +### Architecture & Design +- **[System Overview](docs/architecture/overview.md)** - High-level architecture and data flow +- **[Proof of Work Deep Dive](docs/architecture/proof-of-work.md)** - Mining algorithm explained +- **[Data Models](docs/architecture/data-models.md)** - MongoDB schema and relationships +- **[Security Design](docs/architecture/security-design.md)** - Security layers and threat model -1. **Target Calculation**: Based on difficulty level (1-10), the system determines how many leading zeros the hash must have: - - Difficulty 1: Hash must start with `0` - - Difficulty 2: Hash must start with `00` - - Difficulty 3: Hash must start with `000` - - And so on... +### API Documentation +- **[API Reference](docs/api/reference.md)** - Complete endpoint documentation +- **[Code Examples](docs/api/examples.md)** - Integration examples (Python, JavaScript, Ruby, curl) +- **[Rate Limiting](docs/api/rate-limiting.md)** - Understanding and handling rate limits -2. **Mining Process**: - ``` - Start with nonce = 0 - Loop: - Calculate: hash = SHA256(index + timestamp + data + previous_hash + nonce) - If hash starts with required zeros: - Mining complete! ✓ - Else: - nonce++ - Try again - ``` +### Developer Guides +- **[Development Setup](docs/guides/development-setup.md)** - Complete development environment guide +- **[Testing Guide](docs/guides/testing-guide.md)** - RSpec, coverage, and CI/CD +- **[Deployment Guide](docs/guides/deployment-guide.md)** - Production deployment +- **[Troubleshooting](docs/guides/troubleshooting.md)** - Common issues and solutions -3. **Mining Example**: - ``` - Block: index=1, data="Hello World", difficulty=3 - Target: Hash must start with "000" +### Project Information +- **[CHANGELOG](docs/CHANGELOG.md)** - Version history +- **[CONTRIBUTING](docs/CONTRIBUTING.md)** - How to contribute +- **[SECURITY](docs/SECURITY.md)** - Security policies +- **[CLAUDE](CLAUDE.md)** - Claude Code development guide - Attempt 1: nonce=0 -> hash=a1b2c3d4... ✗ (invalid) - Attempt 2: nonce=1 -> hash=9f8e7d6c... ✗ (invalid) - Attempt 3: nonce=2 -> hash=3d4c5b6a... ✗ (invalid) - ... - Attempt 1542: nonce=1542 -> hash=000a1b2c... ✓ (valid!) - ``` +## API Endpoints -### Performance Impact +All endpoints are prefixed with `/api/v1`: -| Difficulty | Average Time | Leading Zeros | -|-----------|-------------|---------------| -| 1-2 | < 1 second | 0 or 00 | -| 3-4 | Few seconds | 000 or 0000 | -| 5-6 | Minutes | 00000 or 000000 | -| 7+ | Hours+ | 0000000+ | +| Endpoint | Method | Description | Rate Limit | +|----------|--------|-------------|-----------| +| `/chain` | POST | Create new blockchain | 10/min | +| `/chain/:id/block` | POST | Mine and add block | 30/min | +| `/chain/:id/block/:block_id` | GET | Get block details | 60/min | +| `/chain/:id/block/:block_id/valid` | POST | Validate block data | 60/min | -**Recommendation**: Use difficulty 2-4 for development and testing. +See [API Reference](docs/api/reference.md) for complete documentation. -### Configuring Difficulty +## Development -**Default (via environment):** -```bash -# .env file -DEFAULT_DIFFICULTY=2 -``` +### Run Tests -**Per-block (via API):** ```bash -curl -X POST http://localhost:1910/api/v1/chain/:id/block \ - -H 'Content-Type: application/json' \ - -d '{"data": "your_data", "difficulty": 4}' -``` +# Run all tests +bundle exec rspec -### Comparison with Bitcoin - -ChainForge's PoW demonstrates the core concept but is simplified: - -| Feature | ChainForge | Bitcoin | -|---------|-----------|---------| -| Hash Algorithm | Single SHA256 | Double SHA256 | -| Difficulty Range | 1-10 (fixed) | Dynamic (adjusts every 2016 blocks) | -| Current Difficulty | ~2-3 leading zeros | ~19 leading zeros (as of 2023) | -| Block Time | Variable | ~10 minutes (target) | -| Difficulty Adjustment | Manual/per-block | Automatic every 2 weeks | -| Merkle Trees | No | Yes | -| Block Rewards | No | Yes (currently 6.25 BTC) | - -**Educational Note**: This implementation demonstrates PoW fundamentals. Real blockchains include additional complexity like dynamic difficulty adjustment, merkle trees for transaction verification, and sophisticated networking protocols. - -## Architecture - -### Core Models - -**Blockchain** (`src/blockchain.rb`) -- Contains a collection of blocks (MongoDB: `has_many :blocks`) -- Automatically creates genesis block on initialization -- Validates chain integrity by checking: - - Hash links between consecutive blocks - - Each block's hash matches its calculated hash - - Each block's hash meets its difficulty requirement (PoW) -- Genesis block is NOT mined (to speed up creation) - -**Block** (`src/block.rb`) -- **Fields**: index, data, previous_hash, calculated SHA256 hash, nonce, difficulty, timestamp -- **Hash Calculation**: `SHA256(index + timestamp + data + previous_hash + nonce)` -- **Mining**: `mine_block` method increments nonce until valid hash found -- **Validation**: `valid_hash?` verifies hash meets difficulty requirement -- **Immutability**: Hash and PoW verification prevents tampering - -### How It Works - -1. **Genesis Block**: Each blockchain starts with block index 0 (no mining required) -2. **Adding Blocks**: - - Client sends data and optional difficulty - - System validates input (dry-validation) - - Block is created with reference to previous block's hash - - Mining process begins (PoW algorithm) - - Block is saved once valid hash is found -3. **Mining Process**: - - System tries different nonce values - - Calculates hash for each nonce - - Continues until hash starts with required leading zeros - - Higher difficulty = more zeros = more attempts = more secure -4. **Chain Validation**: - - Verifies each block's hash matches its calculated hash - - Verifies each block's hash meets difficulty requirement (valid_hash?) - - Verifies each block links to previous block's hash - - If any check fails, chain is invalid -5. **Data Integrity**: - - Changing any block invalidates its hash - - Invalid hash breaks PoW requirement - - Breaks link to next block - - Invalidates entire chain from that point forward -6. **Security Layers**: - - **PoW**: Computationally expensive to modify blocks - - **Rate Limiting**: Prevents API abuse (Rack::Attack) - - **Input Validation**: Prevents malformed data (dry-validation) - - **Hash Chaining**: Each block depends on previous block - -### Data Flow +# Run with coverage +COVERAGE=true bundle exec rspec +# View coverage report +open coverage/index.html ``` -Client Request - ↓ -Rate Limiting (Rack::Attack) - ↓ -Input Validation (dry-validation) - ↓ -Blockchain Integrity Check - ↓ -Block Creation - ↓ -Mining (Proof of Work) - ↓ -Block Saved to MongoDB - ↓ -JSON Response -``` - -## Security - -### Rate Limiting - -Implemented via Rack::Attack middleware: -- Protects against DoS attacks -- Prevents resource exhaustion -- Per-IP address enforcement -- Configurable limits per endpoint -- Disabled in test environment - -### Input Validation - -Implemented via dry-validation: -- Type checking (strings, integers) -- Range validation (difficulty 1-10) -- Required field enforcement -- Returns detailed error messages -- Prevents injection attacks -### Cryptographic Security - -- SHA256 hashing for block integrity -- Proof of Work for block creation -- Immutable chain structure -- Hash-based tamper detection - -### Known Limitations +### Code Quality -This is an educational project and lacks production-grade security: -- ❌ No encryption for data at rest -- ❌ No authentication/authorization -- ❌ No HTTPS enforcement -- ❌ Simple in-memory rate limiting -- ❌ No distributed consensus -- ❌ No peer-to-peer networking +```bash +# Run linter +bundle exec rubocop -For security best practices, see [SECURITY.md](SECURITY.md). +# Auto-fix issues +bundle exec rubocop -a +``` -## Configuration +### CI/CD -### Environment Variables +GitHub Actions runs automatically on push and PR: +- ✅ RuboCop linting +- ✅ RSpec test suite +- ✅ Coverage reporting -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `MONGO_DB_NAME` | MongoDB database name | chain_forge | Yes | -| `MONGO_DB_HOST` | MongoDB hostname | localhost | Yes | -| `MONGO_DB_PORT` | MongoDB port | 27017 | Yes | -| `ENVIRONMENT` | Runtime environment | development | Yes | -| `DEFAULT_DIFFICULTY` | Default mining difficulty (1-10) | 2 | No | +## Educational Project -### Docker Environment +ChainForge is designed for learning blockchain fundamentals and is **NOT intended for production use**. -When using Docker Compose, environment variables are configured automatically. Override in `docker-compose.yml` if needed. +**Learn:** +- ✅ Blockchain concepts (hashing, chain validation, immutability) +- ✅ Proof of Work mining algorithm +- ✅ API security (rate limiting, input validation) +- ✅ Ruby/Sinatra development +- ✅ MongoDB/Mongoid ODM +- ✅ Testing and CI/CD best practices -### Test Environment +**Do NOT use for:** +- ❌ Production applications +- ❌ Cryptocurrency implementation +- ❌ Storing valuable data +- ❌ Distributed systems -Test configuration is in `.env.test`: -- Uses separate database: `chain_forge_test` -- Rate limiting disabled -- Coverage reporting enabled with `COVERAGE=true` +For production blockchains, study Bitcoin or Ethereum implementations. ## Contributing Contributions are welcome! This is a learning project focused on understanding blockchain fundamentals. -For contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/my-feature`) +3. Write tests for your changes +4. Ensure tests pass (`bundle exec rspec`) +5. Ensure code quality (`bundle exec rubocop`) +6. Commit your changes (`git commit -m 'feat: Add my feature'`) +7. Push to the branch (`git push origin feature/my-feature`) +8. Open a Pull Request -### Development Guidelines - -- Maintain educational value (readable, well-commented code) -- Include RSpec tests for all features (aim for >90% coverage) -- Follow RuboCop style guidelines -- Update documentation when adding features -- All PRs must pass CI checks - -## Documentation - -- [API_DOCUMENTATION.md](API_DOCUMENTATION.md) - Complete API reference -- [SECURITY.md](SECURITY.md) - Security policies and best practices -- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines -- [DEPLOYMENT.md](DEPLOYMENT.md) - Production deployment guide -- [CHANGELOG.md](CHANGELOG.md) - Version history -- [CLAUDE.md](CLAUDE.md) - Claude Code guidance +See [CONTRIBUTING](docs/CONTRIBUTING.md) for detailed guidelines. ## License -MIT License - see LICENSE file for details +MIT License - see [LICENSE](LICENSE) file for details. ## Acknowledgments -Built as a learning exercise to understand blockchain technology fundamentals, including: -- Cryptographic hashing (SHA256) -- Proof of Work consensus -- Chain integrity validation -- API security best practices -- RESTful API design +Built as a learning exercise to understand blockchain technology fundamentals. Special thanks to the blockchain community for educational resources and inspiration. + +## Version -Special thanks to the blockchain community for educational resources and inspiration. +**Current Version:** 2.0.0 -## Version History +See [CHANGELOG](docs/CHANGELOG.md) for version history. -See [CHANGELOG.md](CHANGELOG.md) for detailed version history. +--- -**Current Version**: 2.0.0 -- ✅ Proof of Work mining -- ✅ API versioning -- ✅ Rate limiting and validation -- ✅ Environment configuration -- ✅ CI/CD pipeline +**Ready to start?** Check out the [Quick Start Tutorial](docs/getting-started/quick-start.md)! diff --git a/config/rack_attack.rb b/config/rack_attack.rb index 99cb2c0..abae662 100644 --- a/config/rack_attack.rb +++ b/config/rack_attack.rb @@ -6,14 +6,14 @@ class Attack # Throttle all requests by IP (60 requests per minute) throttle('req/ip', limit: 60, period: 60, &:ip) - # Throttle POST requests to /chain by IP (10 per minute) + # Throttle POST requests to /api/v1/chain by IP (10 per minute) throttle('chain/ip', limit: 10, period: 60) do |req| - req.ip if req.path == '/chain' && req.post? + req.ip if req.path == '/api/v1/chain' && req.post? end # Throttle POST requests to block creation (30 per minute) throttle('block/ip', limit: 30, period: 60) do |req| - req.ip if req.path.match?(%r{^/chain/.+/block$}) && req.post? + req.ip if req.path.match?(%r{^/api/v1/chain/.+/block$}) && req.post? end # Custom response for throttled requests diff --git a/CHANGELOG.md b/docs/CHANGELOG.md similarity index 97% rename from CHANGELOG.md rename to docs/CHANGELOG.md index 628a4ac..f8913e8 100644 --- a/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -66,8 +66,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **CHANGELOG.md**: This file, version history tracking - **CONTRIBUTING.md**: Contribution guidelines and workflow - **SECURITY.md**: Security policies and best practices -- **API_DOCUMENTATION.md**: Complete API reference with examples -- **DEPLOYMENT.md**: Production deployment guide +- **docs/api/reference.md**: Complete API reference with examples +- **docs/guides/deployment-guide.md**: Production deployment guide ### Changed - **Block Hash**: Now includes nonce in hash calculation diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 98% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md index 425169d..dd8f00b 100644 --- a/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -32,7 +32,7 @@ cp .env.example .env # Edit .env: set DEFAULT_DIFFICULTY, MongoDB config # Start MongoDB -docker-compose up -d mongodb +docker-compose up -d db # Run tests bundle exec rspec @@ -176,7 +176,7 @@ Update documentation when: - **README.md**: User-facing features and API - **CLAUDE.md**: Developer architecture and workflow - **CHANGELOG.md**: All changes (following Keep a Changelog format) -- **API_DOCUMENTATION.md**: Complete endpoint changes +- **docs/api/reference.md**: Complete endpoint changes - **Inline comments**: Complex logic ## Educational Focus @@ -249,7 +249,7 @@ When adding API endpoints: 2. Add rate limiting in `config/rack_attack.rb` 3. Create validation contract in `src/validators.rb` 4. Add tests in `spec/api_spec.rb` -5. Update README.md and API_DOCUMENTATION.md +5. Update README.md and docs/api/reference.md 6. Consider backward compatibility ### Breaking Changes diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0fa985c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,157 @@ +# ChainForge Documentation + +Welcome to the complete documentation for ChainForge, an educational blockchain implementation built with Ruby, Sinatra, and MongoDB. + +## Quick Navigation + +### Getting Started +- [Installation Guide](getting-started/installation.md) - Set up your development environment +- [Quick Start Tutorial](getting-started/quick-start.md) - Get your first blockchain running in 5 minutes +- [First Blockchain Tutorial](getting-started/first-blockchain-tutorial.md) - Complete walkthrough with mining examples + +### Architecture & Design +- [System Overview](architecture/overview.md) - High-level architecture and data flow +- [Proof of Work Deep Dive](architecture/proof-of-work.md) - Understanding the mining algorithm +- [Data Models](architecture/data-models.md) - MongoDB schema and relationships +- [Security Design](architecture/security-design.md) - Rate limiting, validation, and security layers + +### API Documentation +- [API Reference](api/reference.md) - Complete endpoint documentation with examples +- [Code Examples](api/examples.md) - Integration examples in multiple languages +- [Rate Limiting](api/rate-limiting.md) - Understanding rate limits and quotas + +### Developer Guides +- [Development Setup](guides/development-setup.md) - Complete development environment guide +- [Testing Guide](guides/testing-guide.md) - RSpec, coverage, and CI/CD +- [Deployment Guide](guides/deployment-guide.md) - Docker and production deployment +- [Troubleshooting](guides/troubleshooting.md) - Common issues and solutions + +### Project Information +- [CHANGELOG](CHANGELOG.md) - Version history and release notes +- [CONTRIBUTING](CONTRIBUTING.md) - How to contribute to the project +- [SECURITY](SECURITY.md) - Security policies and vulnerability reporting +- [CLAUDE](../CLAUDE.md) - Claude Code development guidance + +## What is ChainForge? + +ChainForge is an **educational blockchain implementation** designed to help developers understand core blockchain concepts through hands-on implementation. It demonstrates: + +- **Cryptographic Hashing**: SHA256-based block linking +- **Proof of Work**: Mining algorithm with configurable difficulty +- **Chain Integrity**: Validation and immutability +- **API Security**: Rate limiting and input validation +- **Modern Development**: Testing, linting, and CI/CD + +### Current Version: 2.0.0 + +ChainForge v2 includes: +- ✅ Proof of Work (PoW) mining with difficulty 1-10 +- ✅ Versioned REST API (`/api/v1`) +- ✅ Rate limiting (Rack::Attack) +- ✅ Input validation (dry-validation) +- ✅ Environment configuration +- ✅ GitHub Actions CI/CD +- ✅ Comprehensive test coverage + +## Educational Project Notice + +> **Important**: ChainForge is a learning-focused project and is NOT intended for production use. It demonstrates blockchain fundamentals but lacks many features required for production systems (authentication, encryption, distributed consensus, P2P networking, etc.). + +Use ChainForge to: +- ✅ Learn blockchain fundamentals +- ✅ Understand Proof of Work +- ✅ Experiment with mining algorithms +- ✅ Study API security patterns +- ✅ Practice Ruby development + +Do NOT use ChainForge for: +- ❌ Production applications +- ❌ Cryptocurrency implementation +- ❌ Storing valuable data +- ❌ Distributed systems + +## Quick Links + +### For New Users +1. [Installation Guide](getting-started/installation.md) - Install prerequisites and dependencies +2. [Quick Start](getting-started/quick-start.md) - Run your first blockchain in 5 minutes +3. [API Reference](api/reference.md) - Start making API calls + +### For Developers +1. [Development Setup](guides/development-setup.md) - Configure your dev environment +2. [Testing Guide](guides/testing-guide.md) - Write and run tests +3. [Architecture Overview](architecture/overview.md) - Understand the system design + +### For Contributors +1. [CONTRIBUTING](CONTRIBUTING.md) - Contribution guidelines +2. [Development Setup](guides/development-setup.md) - Set up your fork +3. [Testing Guide](guides/testing-guide.md) - Ensure quality + +## Core Concepts + +### Blockchain Basics +A **blockchain** is a distributed ledger of transactions organized into blocks. Each block contains: +- Data (transactions or information) +- A cryptographic hash of the previous block +- A timestamp +- A nonce (number used once) + +Blocks are linked together through their hashes, creating an immutable chain where altering any block invalidates all subsequent blocks. + +### Proof of Work +**Proof of Work (PoW)** is a consensus mechanism that requires computational work to add blocks to the chain. Miners must find a nonce value that, when hashed with the block data, produces a hash meeting specific criteria (e.g., starting with a certain number of zeros). + +This makes it computationally expensive to modify the blockchain, providing security against tampering. + +### Chain Integrity +ChainForge validates chain integrity by checking: +1. Each block's hash matches its calculated hash +2. Each block's hash meets its difficulty requirement (PoW) +3. Each block's `previous_hash` matches the prior block's hash + +If any check fails, the chain is invalid. + +## Technology Stack + +- **Language**: Ruby 3.2.2 +- **Web Framework**: Sinatra 4.0 +- **Database**: MongoDB (via Mongoid ODM) +- **Testing**: RSpec 3.10 + SimpleCov +- **Code Quality**: RuboCop 1.57 +- **Security**: Rack::Attack (rate limiting), dry-validation (input validation) +- **CI/CD**: GitHub Actions + +## Documentation Structure + +This documentation is organized into four main sections: + +### 1. Getting Started +Step-by-step tutorials for new users to install, configure, and run ChainForge. + +### 2. Architecture & Design +In-depth explanations of system design, data models, algorithms, and security. + +### 3. API Documentation +Complete API reference with endpoints, request/response formats, and integration examples. + +### 4. Developer Guides +Guides for developers contributing to or deploying ChainForge. + +## Getting Help + +- **Installation Issues**: See [Troubleshooting Guide](guides/troubleshooting.md) +- **API Questions**: Check [API Reference](api/reference.md) +- **Contributing**: Read [CONTRIBUTING](CONTRIBUTING.md) +- **Security Issues**: Report via [SECURITY](SECURITY.md) + +## License + +MIT License - See LICENSE file for details. + +## Acknowledgments + +Built as a learning exercise to understand blockchain technology. Special thanks to the blockchain community for educational resources and inspiration. + +--- + +**Ready to start?** Head to the [Quick Start Tutorial](getting-started/quick-start.md) to create your first blockchain in 5 minutes! diff --git a/SECURITY.md b/docs/SECURITY.md similarity index 100% rename from SECURITY.md rename to docs/SECURITY.md diff --git a/docs/api/examples.md b/docs/api/examples.md new file mode 100644 index 0000000..fe9f020 --- /dev/null +++ b/docs/api/examples.md @@ -0,0 +1,771 @@ +# API Examples + +Code examples for integrating with the ChainForge API in multiple programming languages. + +## Table of Contents + +1. [curl Examples](#curl-examples) +2. [Python Examples](#python-examples) +3. [JavaScript/Node.js Examples](#javascript-nodejs-examples) +4. [Ruby Examples](#ruby-examples) +5. [Complete Workflow Examples](#complete-workflow-examples) + +## curl Examples + +### Basic Operations + +**Create a Blockchain:** +```bash +curl -X POST http://localhost:1910/api/v1/chain +``` + +**Add a Block (Low Difficulty):** +```bash +CHAIN_ID="674c8a1b2e4f5a0012345678" + +curl -X POST http://localhost:1910/api/v1/chain/$CHAIN_ID/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Hello, Blockchain!", "difficulty": 2}' +``` + +**Get Block Details:** +```bash +CHAIN_ID="674c8a1b2e4f5a0012345678" +BLOCK_ID="674c8b2c3e5f6a0012345679" + +curl http://localhost:1910/api/v1/chain/$CHAIN_ID/block/$BLOCK_ID +``` + +**Validate Block Data:** +```bash +CHAIN_ID="674c8a1b2e4f5a0012345678" +BLOCK_ID="674c8b2c3e5f6a0012345679" + +curl -X POST http://localhost:1910/api/v1/chain/$CHAIN_ID/block/$BLOCK_ID/valid \ + -H 'Content-Type: application/json' \ + -d '{"data": "Hello, Blockchain!"}' +``` + +### Complete Workflow Script + +```bash +#!/bin/bash +# create_blockchain.sh + +# Create blockchain +echo "Creating blockchain..." +RESPONSE=$(curl -s -X POST http://localhost:1910/api/v1/chain) +CHAIN_ID=$(echo $RESPONSE | jq -r '.id') +echo "Blockchain created: $CHAIN_ID" + +# Add first block +echo "Mining first block..." +RESPONSE=$(curl -s -X POST http://localhost:1910/api/v1/chain/$CHAIN_ID/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "First block", "difficulty": 2}') +BLOCK1_ID=$(echo $RESPONSE | jq -r '.block_id') +echo "Block 1 mined: $BLOCK1_ID" + +# Add second block +echo "Mining second block..." +RESPONSE=$(curl -s -X POST http://localhost:1910/api/v1/chain/$CHAIN_ID/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Second block", "difficulty": 3}') +BLOCK2_ID=$(echo $RESPONSE | jq -r '.block_id') +echo "Block 2 mined: $BLOCK2_ID" + +# Get block details +echo "Getting block 1 details..." +curl -s http://localhost:1910/api/v1/chain/$CHAIN_ID/block/$BLOCK1_ID | jq + +# Validate block data +echo "Validating block 1 data..." +curl -s -X POST http://localhost:1910/api/v1/chain/$CHAIN_ID/block/$BLOCK1_ID/valid \ + -H 'Content-Type: application/json' \ + -d '{"data": "First block"}' | jq + +echo "Done!" +``` + +## Python Examples + +### Using requests Library + +**Installation:** +```bash +pip install requests +``` + +**Basic Example:** +```python +import requests +import json + +BASE_URL = "http://localhost:1910/api/v1" + +# Create blockchain +response = requests.post(f"{BASE_URL}/chain") +blockchain = response.json() +chain_id = blockchain["id"] +print(f"Created blockchain: {chain_id}") + +# Add block +block_data = { + "data": "Hello from Python!", + "difficulty": 2 +} +response = requests.post( + f"{BASE_URL}/chain/{chain_id}/block", + json=block_data +) +block = response.json() +block_id = block["block_id"] +print(f"Mined block: {block_id}") +print(f"Hash: {block['block_hash']}") +print(f"Nonce: {block['nonce']}") + +# Get block details +response = requests.get(f"{BASE_URL}/chain/{chain_id}/block/{block_id}") +block_details = response.json() +print(json.dumps(block_details, indent=2)) + +# Validate block data +validation_data = {"data": "Hello from Python!"} +response = requests.post( + f"{BASE_URL}/chain/{chain_id}/block/{block_id}/valid", + json=validation_data +) +result = response.json() +print(f"Data valid: {result['valid']}") +``` + +### ChainForge Python Client Class + +```python +import requests +from typing import Dict, Optional + +class ChainForgeClient: + """Python client for ChainForge API""" + + def __init__(self, base_url: str = "http://localhost:1910/api/v1"): + self.base_url = base_url + self.session = requests.Session() + + def create_blockchain(self) -> str: + """Create a new blockchain and return its ID""" + response = self.session.post(f"{self.base_url}/chain") + response.raise_for_status() + return response.json()["id"] + + def add_block( + self, + chain_id: str, + data: str, + difficulty: Optional[int] = None + ) -> Dict: + """Add a block to the blockchain""" + payload = {"data": data} + if difficulty is not None: + payload["difficulty"] = difficulty + + response = self.session.post( + f"{self.base_url}/chain/{chain_id}/block", + json=payload + ) + response.raise_for_status() + return response.json() + + def get_block(self, chain_id: str, block_id: str) -> Dict: + """Get block details""" + response = self.session.get( + f"{self.base_url}/chain/{chain_id}/block/{block_id}" + ) + response.raise_for_status() + return response.json() + + def validate_block_data( + self, + chain_id: str, + block_id: str, + data: str + ) -> bool: + """Validate block data""" + response = self.session.post( + f"{self.base_url}/chain/{chain_id}/block/{block_id}/valid", + json={"data": data} + ) + response.raise_for_status() + return response.json()["valid"] + +# Usage example +if __name__ == "__main__": + client = ChainForgeClient() + + # Create blockchain + chain_id = client.create_blockchain() + print(f"Blockchain ID: {chain_id}") + + # Add blocks + blocks = [] + for i in range(3): + block = client.add_block( + chain_id, + data=f"Block {i+1} data", + difficulty=2 + ) + blocks.append(block) + print(f"Mined block {i+1}: {block['block_hash'][:16]}...") + + # Get block details + block_details = client.get_block(chain_id, blocks[0]["block_id"]) + print(f"Block 1 details: {block_details}") + + # Validate data + is_valid = client.validate_block_data( + chain_id, + blocks[0]["block_id"], + "Block 1 data" + ) + print(f"Block 1 data valid: {is_valid}") +``` + +### Error Handling Example + +```python +import requests +from requests.exceptions import HTTPError + +def create_block_with_retry(chain_id, data, difficulty=2, max_retries=3): + """Create block with rate limit retry logic""" + base_url = "http://localhost:1910/api/v1" + + for attempt in range(max_retries): + try: + response = requests.post( + f"{base_url}/chain/{chain_id}/block", + json={"data": data, "difficulty": difficulty} + ) + response.raise_for_status() + return response.json() + + except HTTPError as e: + if e.response.status_code == 429: # Rate limit + print(f"Rate limited, retrying in 60s... (attempt {attempt+1})") + time.sleep(60) + elif e.response.status_code == 400: # Validation error + print(f"Validation error: {e.response.json()}") + raise + else: + raise + + raise Exception("Max retries exceeded") +``` + +## JavaScript/Node.js Examples + +### Using fetch (Node.js 18+) + +**Basic Example:** +```javascript +const BASE_URL = "http://localhost:1910/api/v1"; + +// Create blockchain +async function createBlockchain() { + const response = await fetch(`${BASE_URL}/chain`, { + method: "POST" + }); + const data = await response.json(); + return data.id; +} + +// Add block +async function addBlock(chainId, blockData, difficulty = 2) { + const response = await fetch(`${BASE_URL}/chain/${chainId}/block`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ data: blockData, difficulty }) + }); + return await response.json(); +} + +// Get block details +async function getBlock(chainId, blockId) { + const response = await fetch(`${BASE_URL}/chain/${chainId}/block/${blockId}`); + return await response.json(); +} + +// Validate block data +async function validateBlockData(chainId, blockId, data) { + const response = await fetch( + `${BASE_URL}/chain/${chainId}/block/${blockId}/valid`, + { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ data }) + } + ); + const result = await response.json(); + return result.valid; +} + +// Usage +(async () => { + try { + // Create blockchain + const chainId = await createBlockchain(); + console.log("Blockchain created:", chainId); + + // Add block + const block = await addBlock(chainId, "Hello from JavaScript!", 2); + console.log("Block mined:", block.block_id); + console.log("Hash:", block.block_hash); + console.log("Nonce:", block.nonce); + + // Get block details + const blockDetails = await getBlock(chainId, block.block_id); + console.log("Block details:", JSON.stringify(blockDetails, null, 2)); + + // Validate data + const isValid = await validateBlockData( + chainId, + block.block_id, + "Hello from JavaScript!" + ); + console.log("Data valid:", isValid); + + } catch (error) { + console.error("Error:", error); + } +})(); +``` + +### ChainForge JavaScript Client Class + +```javascript +class ChainForgeClient { + constructor(baseUrl = "http://localhost:1910/api/v1") { + this.baseUrl = baseUrl; + } + + async createBlockchain() { + const response = await fetch(`${this.baseUrl}/chain`, { + method: "POST" + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const data = await response.json(); + return data.id; + } + + async addBlock(chainId, data, difficulty = null) { + const payload = { data }; + if (difficulty !== null) payload.difficulty = difficulty; + + const response = await fetch(`${this.baseUrl}/chain/${chainId}/block`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return await response.json(); + } + + async getBlock(chainId, blockId) { + const response = await fetch( + `${this.baseUrl}/chain/${chainId}/block/${blockId}` + ); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return await response.json(); + } + + async validateBlockData(chainId, blockId, data) { + const response = await fetch( + `${this.baseUrl}/chain/${chainId}/block/${blockId}/valid`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ data }) + } + ); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const result = await response.json(); + return result.valid; + } +} + +// Usage +const client = new ChainForgeClient(); + +client.createBlockchain() + .then(chainId => { + console.log("Chain ID:", chainId); + return client.addBlock(chainId, "Test data", 2); + }) + .then(block => { + console.log("Block mined:", block); + }) + .catch(error => { + console.error("Error:", error); + }); +``` + +### Using axios + +**Installation:** +```bash +npm install axios +``` + +**Example:** +```javascript +const axios = require('axios'); + +const BASE_URL = "http://localhost:1910/api/v1"; + +async function main() { + try { + // Create blockchain + const { data: blockchain } = await axios.post(`${BASE_URL}/chain`); + const chainId = blockchain.id; + console.log("Blockchain created:", chainId); + + // Add block + const { data: block } = await axios.post( + `${BASE_URL}/chain/${chainId}/block`, + { + data: "Hello from axios!", + difficulty: 2 + } + ); + console.log("Block mined:", block); + + // Get block + const { data: blockDetails } = await axios.get( + `${BASE_URL}/chain/${chainId}/block/${block.block_id}` + ); + console.log("Block details:", blockDetails); + + // Validate + const { data: validation } = await axios.post( + `${BASE_URL}/chain/${chainId}/block/${block.block_id}/valid`, + { data: "Hello from axios!" } + ); + console.log("Valid:", validation.valid); + + } catch (error) { + if (error.response) { + console.error("API Error:", error.response.data); + } else { + console.error("Error:", error.message); + } + } +} + +main(); +``` + +## Ruby Examples + +### Using net/http (Standard Library) + +```ruby +require 'net/http' +require 'json' +require 'uri' + +BASE_URL = "http://localhost:1910/api/v1" + +# Create blockchain +def create_blockchain + uri = URI("#{BASE_URL}/chain") + response = Net::HTTP.post(uri, nil) + JSON.parse(response.body)["id"] +end + +# Add block +def add_block(chain_id, data, difficulty = 2) + uri = URI("#{BASE_URL}/chain/#{chain_id}/block") + request = Net::HTTP::Post.new(uri) + request['Content-Type'] = 'application/json' + request.body = { data: data, difficulty: difficulty }.to_json + + response = Net::HTTP.start(uri.hostname, uri.port) do |http| + http.request(request) + end + + JSON.parse(response.body) +end + +# Get block +def get_block(chain_id, block_id) + uri = URI("#{BASE_URL}/chain/#{chain_id}/block/#{block_id}") + response = Net::HTTP.get_response(uri) + JSON.parse(response.body) +end + +# Validate block data +def validate_block_data(chain_id, block_id, data) + uri = URI("#{BASE_URL}/chain/#{chain_id}/block/#{block_id}/valid") + request = Net::HTTP::Post.new(uri) + request['Content-Type'] = 'application/json' + request.body = { data: data }.to_json + + response = Net::HTTP.start(uri.hostname, uri.port) do |http| + http.request(request) + end + + JSON.parse(response.body)["valid"] +end + +# Usage +chain_id = create_blockchain +puts "Blockchain created: #{chain_id}" + +block = add_block(chain_id, "Hello from Ruby!", 2) +puts "Block mined: #{block['block_id']}" +puts "Hash: #{block['block_hash']}" + +block_details = get_block(chain_id, block['block_id']) +puts "Block details: #{JSON.pretty_generate(block_details)}" + +is_valid = validate_block_data(chain_id, block['block_id'], "Hello from Ruby!") +puts "Data valid: #{is_valid}" +``` + +### Using httparty Gem + +**Installation:** +```bash +gem install httparty +``` + +**Example:** +```ruby +require 'httparty' + +class ChainForgeClient + include HTTParty + base_uri 'http://localhost:1910/api/v1' + + def create_blockchain + response = self.class.post('/chain') + response['id'] + end + + def add_block(chain_id, data, difficulty = nil) + payload = { data: data } + payload[:difficulty] = difficulty if difficulty + + response = self.class.post( + "/chain/#{chain_id}/block", + body: payload.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + response.parsed_response + end + + def get_block(chain_id, block_id) + response = self.class.get("/chain/#{chain_id}/block/#{block_id}") + response.parsed_response + end + + def validate_block_data(chain_id, block_id, data) + response = self.class.post( + "/chain/#{chain_id}/block/#{block_id}/valid", + body: { data: data }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + response['valid'] + end +end + +# Usage +client = ChainForgeClient.new + +chain_id = client.create_blockchain +puts "Blockchain ID: #{chain_id}" + +3.times do |i| + block = client.add_block(chain_id, "Block #{i+1}", 2) + puts "Mined block #{i+1}: #{block['block_hash'][0..15]}..." +end + +block = client.add_block(chain_id, "Test data", 2) +is_valid = client.validate_block_data(chain_id, block['block_id'], "Test data") +puts "Valid: #{is_valid}" +``` + +## Complete Workflow Examples + +### Multi-Block Mining Workflow (Python) + +```python +import requests +import time + +BASE_URL = "http://localhost:1910/api/v1" + +def mine_blocks_workflow(): + # Create blockchain + response = requests.post(f"{BASE_URL}/chain") + chain_id = response.json()["id"] + print(f"✓ Created blockchain: {chain_id}") + + # Mine 5 blocks with increasing difficulty + blocks = [] + for i in range(1, 6): + difficulty = min(i, 4) # Cap at difficulty 4 + print(f"\n Mining block {i} (difficulty {difficulty})...") + + start_time = time.time() + response = requests.post( + f"{BASE_URL}/chain/{chain_id}/block", + json={ + "data": f"Block {i}: Transaction data", + "difficulty": difficulty + } + ) + elapsed = time.time() - start_time + + block = response.json() + blocks.append(block) + + print(f"✓ Mined in {elapsed:.2f}s") + print(f" Block ID: {block['block_id']}") + print(f" Hash: {block['block_hash'][:32]}...") + print(f" Nonce: {block['nonce']}") + + # Validate all blocks + print("\n Validating blocks...") + for i, block in enumerate(blocks, 1): + response = requests.post( + f"{BASE_URL}/chain/{chain_id}/block/{block['block_id']}/valid", + json={"data": f"Block {i}: Transaction data"} + ) + is_valid = response.json()["valid"] + status = "✓" if is_valid else "✗" + print(f"{status} Block {i}: {'Valid' if is_valid else 'Invalid'}") + + print(f"\nTotal blocks mined: {len(blocks)}") + +if __name__ == "__main__": + mine_blocks_workflow() +``` + +### Data Integrity Test (JavaScript) + +```javascript +const BASE_URL = "http://localhost:1910/api/v1"; + +async function testDataIntegrity() { + // Create blockchain + let response = await fetch(`${BASE_URL}/chain`, { method: "POST" }); + const { id: chainId } = await response.json(); + console.log("✓ Created blockchain:", chainId); + + // Add block + const originalData = "Sensitive transaction data"; + response = await fetch(`${BASE_URL}/chain/${chainId}/block`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ data: originalData, difficulty: 2 }) + }); + const block = await response.json(); + console.log("✓ Mined block:", block.block_id); + + // Test 1: Validate with correct data + response = await fetch( + `${BASE_URL}/chain/${chainId}/block/${block.block_id}/valid`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ data: originalData }) + } + ); + let result = await response.json(); + console.log(`\nTest 1 - Correct data: ${result.valid ? '✓ Valid' : '✗ Invalid'}`); + + // Test 2: Validate with tampered data + response = await fetch( + `${BASE_URL}/chain/${chainId}/block/${block.block_id}/valid`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ data: "Tampered data!" }) + } + ); + result = await response.json(); + console.log(`Test 2 - Tampered data: ${result.valid ? '✗ Valid (FAIL)' : '✓ Invalid (PASS)'}`); + + // Test 3: Validate with slightly modified data + response = await fetch( + `${BASE_URL}/chain/${chainId}/block/${block.block_id}/valid`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ data: originalData + " " }) // Extra space + } + ); + result = await response.json(); + console.log(`Test 3 - Extra space: ${result.valid ? '✗ Valid (FAIL)' : '✓ Invalid (PASS)'}`); + + console.log("\n✓ Data integrity test complete!"); +} + +testDataIntegrity().catch(console.error); +``` + +## Error Handling Patterns + +### Python Error Handling + +```python +import requests +from requests.exceptions import HTTPError, ConnectionError, Timeout + +def safe_api_call(func): + """Decorator for safe API calls with error handling""" + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except ConnectionError: + print("Error: Cannot connect to ChainForge API") + return None + except Timeout: + print("Error: Request timed out") + return None + except HTTPError as e: + if e.response.status_code == 400: + print(f"Validation Error: {e.response.json()}") + elif e.response.status_code == 429: + print("Rate limit exceeded. Wait 60 seconds.") + elif e.response.status_code == 404: + print("Blockchain or block not found") + else: + print(f"HTTP Error {e.response.status_code}") + return None + return wrapper + +@safe_api_call +def create_block_safe(chain_id, data, difficulty=2): + response = requests.post( + f"http://localhost:1910/api/v1/chain/{chain_id}/block", + json={"data": data, "difficulty": difficulty} + ) + response.raise_for_status() + return response.json() +``` + +## Next Steps + +- [API Reference](reference.md) - Complete endpoint documentation +- [Rate Limiting](rate-limiting.md) - Understanding rate limits +- [Quick Start Tutorial](../getting-started/quick-start.md) - Try the API yourself + +--- + +**Have a code example to share?** Contribute via [CONTRIBUTING](../CONTRIBUTING.md)! diff --git a/docs/api/rate-limiting.md b/docs/api/rate-limiting.md new file mode 100644 index 0000000..e43b9f2 --- /dev/null +++ b/docs/api/rate-limiting.md @@ -0,0 +1,658 @@ +# Rate Limiting + +Complete guide to understanding and working with ChainForge's rate limiting system. + +## Table of Contents + +1. [Overview](#overview) +2. [Rate Limits](#rate-limits) +3. [How It Works](#how-it-works) +4. [Handling Rate Limits](#handling-rate-limits) +5. [Best Practices](#best-practices) +6. [Monitoring](#monitoring) +7. [Production Considerations](#production-considerations) + +## Overview + +ChainForge uses **Rack::Attack** middleware to implement IP-based rate limiting. This protects the API from abuse, prevents resource exhaustion, and ensures fair usage across all clients. + +**Key Features:** +- IP-based throttling (per client) +- Multiple limit tiers (global + endpoint-specific) +- 60-second rolling windows +- Automatic 429 responses when exceeded +- Disabled in test environment + +## Rate Limits + +### Limit Tiers + +ChainForge enforces three levels of rate limits: + +| Tier | Endpoint Pattern | Method | Limit | Window | Purpose | +|------|-----------------|--------|-------|--------|---------| +| **1. Global** | `/api/*` | All | 60 req | 60s | Overall API protection | +| **2. Chain Creation** | `/api/v1/chain` | POST | 10 req | 60s | Prevent blockchain spam | +| **3. Block Mining** | `/api/v1/chain/:id/block` | POST | 30 req | 60s | Prevent mining abuse | + +**Note:** All limits are per IP address. + +### Endpoint-Specific Limits + +| Endpoint | Limit | Reason | +|----------|-------|--------| +| `POST /api/v1/chain` | 10/min | Creating blockchains is lightweight but should be limited | +| `POST /api/v1/chain/:id/block` | 30/min | Mining is CPU-intensive; prevent resource exhaustion | +| `GET /api/v1/chain/:id/block/:block_id` | Global only (60/min) | Read operations are fast | +| `POST /api/v1/chain/:id/block/:block_id/valid` | Global only (60/min) | Validation is fast | + +### How Limits Stack + +Clients are subject to **all applicable limits simultaneously**: + +**Example 1: Creating Blockchains** +``` +Request: POST /api/v1/chain + +Limits Applied: +✓ Global: 60 requests/minute +✓ Chain Creation: 10 requests/minute + +Effective Limit: 10/minute (whichever is reached first) +``` + +**Example 2: Mining Blocks** +``` +Request: POST /api/v1/chain/:id/block + +Limits Applied: +✓ Global: 60 requests/minute +✓ Block Mining: 30 requests/minute + +Effective Limit: 30/minute (whichever is reached first) +``` + +**Example 3: Reading Blocks** +``` +Request: GET /api/v1/chain/:id/block/:block_id + +Limits Applied: +✓ Global: 60 requests/minute + +Effective Limit: 60/minute +``` + +## How It Works + +### Implementation + +ChainForge uses **Rack::Attack** with in-memory storage. + +**Source:** `config/rack_attack.rb` + +**Middleware Configuration:** +```ruby +# main.rb:16 +use Rack::Attack unless ENV['ENVIRONMENT'] == 'test' +``` + +### Throttle Rules + +**Global Throttle:** +```ruby +throttle('api/ip', limit: 60, period: 1.minute) do |req| + req.ip if req.path.start_with?('/api/') +end +``` + +**Chain Creation Throttle:** +```ruby +throttle('api/chain/create', limit: 10, period: 1.minute) do |req| + req.ip if req.path == '/api/v1/chain' && req.post? +end +``` + +**Block Creation Throttle:** +```ruby +throttle('api/block/create', limit: 30, period: 1.minute) do |req| + req.ip if req.path =~ /^\/api\/v1\/chain\/[^\/]+\/block$/ && req.post? +end +``` + +### IP Detection + +Rack::Attack uses `request.ip` which: +- Checks `X-Forwarded-For` header (if behind proxy) +- Falls back to `REMOTE_ADDR` +- Returns the client's IP address + +**For Development:** +- Localhost: `127.0.0.1` or `::1` (IPv6) +- All local requests share the same rate limit + +**For Production:** +- Each client IP has independent limits +- Clients behind same NAT/proxy share limits + +### Storage + +**Memory-Based (Default):** +- Stores counters in Ruby process memory +- Fast lookups (no external dependencies) +- **Resets on server restart** +- **Not suitable for distributed systems** + +**Production Alternative:** +- Use Redis for persistent, distributed storage +- See [Production Considerations](#production-considerations) + +## Handling Rate Limits + +### Response Format + +When rate limit is exceeded, ChainForge returns: + +**HTTP Status:** `429 Too Many Requests` + +**Response Body:** +```json +{ + "error": "Rate limit exceeded. Please try again later." +} +``` + +**Headers:** +``` +Content-Type: application/json +``` + +### Client-Side Handling + +#### curl Example + +```bash +#!/bin/bash + +response=$(curl -s -w "\n%{http_code}" -X POST http://localhost:1910/api/v1/chain) +http_code=$(echo "$response" | tail -n1) +body=$(echo "$response" | sed '$d') + +if [ "$http_code" == "429" ]; then + echo "Rate limited! Waiting 60 seconds..." + sleep 60 + # Retry + curl -X POST http://localhost:1910/api/v1/chain +else + echo "Success: $body" +fi +``` + +#### Python Example + +```python +import requests +import time + +def create_blockchain_with_retry(): + max_retries = 3 + base_url = "http://localhost:1910/api/v1" + + for attempt in range(max_retries): + try: + response = requests.post(f"{base_url}/chain") + + if response.status_code == 429: + print(f"Rate limited (attempt {attempt + 1}). Waiting 60s...") + time.sleep(60) + continue + + response.raise_for_status() + return response.json()["id"] + + except requests.exceptions.HTTPError as e: + if e.response.status_code != 429: + raise + + raise Exception("Max retries exceeded") + +# Usage +chain_id = create_blockchain_with_retry() +print(f"Blockchain created: {chain_id}") +``` + +#### JavaScript Example + +```javascript +async function createBlockchainWithRetry() { + const BASE_URL = "http://localhost:1910/api/v1"; + const maxRetries = 3; + + for (let attempt = 0; attempt < maxRetries; attempt++) { + const response = await fetch(`${BASE_URL}/chain`, { + method: "POST" + }); + + if (response.status === 429) { + console.log(`Rate limited (attempt ${attempt + 1}). Waiting 60s...`); + await new Promise(resolve => setTimeout(resolve, 60000)); + continue; + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const data = await response.json(); + return data.id; + } + + throw new Error("Max retries exceeded"); +} + +// Usage +createBlockchainWithRetry() + .then(chainId => console.log("Blockchain created:", chainId)) + .catch(error => console.error("Error:", error)); +``` + +#### Ruby Example + +```ruby +require 'net/http' +require 'json' + +def create_blockchain_with_retry(max_retries = 3) + base_url = "http://localhost:1910/api/v1" + uri = URI("#{base_url}/chain") + + max_retries.times do |attempt| + response = Net::HTTP.post(uri, nil) + + case response.code.to_i + when 429 + puts "Rate limited (attempt #{attempt + 1}). Waiting 60s..." + sleep 60 + next + when 200 + return JSON.parse(response.body)["id"] + else + raise "HTTP Error #{response.code}" + end + end + + raise "Max retries exceeded" +end + +# Usage +chain_id = create_blockchain_with_retry +puts "Blockchain created: #{chain_id}" +``` + +## Best Practices + +### 1. Implement Exponential Backoff + +```python +import time + +def exponential_backoff_retry(func, max_retries=5): + """Retry with exponential backoff""" + for attempt in range(max_retries): + try: + return func() + except RateLimitError: + if attempt == max_retries - 1: + raise + + # Exponential backoff: 1s, 2s, 4s, 8s, 16s + wait_time = 2 ** attempt + print(f"Rate limited. Waiting {wait_time}s...") + time.sleep(wait_time) +``` + +### 2. Batch Operations + +**Bad:** +```python +# Creates 100 blockchains (will hit rate limit at 10) +for i in range(100): + create_blockchain() # Rate limited after 10! +``` + +**Good:** +```python +# Batch with delays +blockchains = [] +for i in range(100): + if i > 0 and i % 10 == 0: + print(f"Created {i} blockchains. Waiting 60s...") + time.sleep(60) + + blockchains.append(create_blockchain()) +``` + +### 3. Cache Results + +```python +import functools +import time + +@functools.lru_cache(maxsize=128) +def get_block_cached(chain_id, block_id): + """Cache block details to avoid repeated API calls""" + return get_block(chain_id, block_id) + +# First call: Makes API request +block1 = get_block_cached("674c...", "674d...") + +# Second call: Returns cached result (no API call) +block2 = get_block_cached("674c...", "674d...") +``` + +### 4. Monitor Your Usage + +```python +import requests + +def track_rate_limits(): + """Track API usage to stay within limits""" + call_timestamps = [] + + def make_request(url, **kwargs): + # Clean up timestamps older than 60s + now = time.time() + call_timestamps[:] = [ts for ts in call_timestamps if now - ts < 60] + + # Check if we're approaching limit + if len(call_timestamps) >= 55: # 55/60 = 91% of global limit + print("Warning: Approaching rate limit!") + wait_time = 60 - (now - call_timestamps[0]) + time.sleep(max(0, wait_time)) + + # Make request + response = requests.request(**kwargs) + call_timestamps.append(time.time()) + + return response + + return make_request + +# Usage +api_call = track_rate_limits() +response = api_call("POST", "http://localhost:1910/api/v1/chain") +``` + +### 5. Use Appropriate Delays + +```python +# For bulk operations, pace yourself +def create_blocks_paced(chain_id, count, delay=2): + """Create blocks with delay to stay under rate limits""" + blocks = [] + + for i in range(count): + block = add_block(chain_id, f"Block {i}") + blocks.append(block) + + if i < count - 1: # Don't delay after last block + time.sleep(delay) + + return blocks + +# Create 30 blocks in ~60 seconds (1 block every 2 seconds) +blocks = create_blocks_paced(chain_id, 30, delay=2) +``` + +## Monitoring + +### Detecting Rate Limits + +**Check HTTP Status:** +```python +response = requests.post(url) + +if response.status_code == 429: + print("Rate limit exceeded!") + # Handle accordingly +``` + +**Log Rate Limit Occurrences:** +```python +import logging + +logger = logging.getLogger(__name__) + +def api_call_with_logging(func): + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except RateLimitError as e: + logger.warning(f"Rate limit hit: {e}") + raise + return wrapper +``` + +### Tracking Metrics + +```python +class APIMetrics: + def __init__(self): + self.total_requests = 0 + self.rate_limit_hits = 0 + self.successful_requests = 0 + + def record_request(self, status_code): + self.total_requests += 1 + + if status_code == 429: + self.rate_limit_hits += 1 + elif 200 <= status_code < 300: + self.successful_requests += 1 + + def print_stats(self): + rate_limit_percentage = ( + 100 * self.rate_limit_hits / self.total_requests + if self.total_requests > 0 else 0 + ) + + print(f"Total Requests: {self.total_requests}") + print(f"Successful: {self.successful_requests}") + print(f"Rate Limited: {self.rate_limit_hits} ({rate_limit_percentage:.1f}%)") + +# Usage +metrics = APIMetrics() + +for i in range(100): + response = make_api_call() + metrics.record_request(response.status_code) + +metrics.print_stats() +``` + +## Production Considerations + +### Limitations of Current Implementation + +**In-Memory Storage:** +- ❌ Resets on server restart +- ❌ Not shared across multiple servers +- ❌ No persistence +- ❌ No historical data + +**Single-Server Only:** +- Each server instance has independent counters +- Load-balanced deployments won't enforce limits correctly +- Users can bypass limits by targeting different servers + +### Production Recommendations + +#### 1. Use Redis for Distributed Rate Limiting + +```ruby +# Gemfile +gem 'redis' +gem 'redis-rack-attack' + +# config/rack_attack.rb +require 'redis' + +Rack::Attack.cache.store = Rack::Attack::StoreProxy::RedisStoreProxy.new( + Redis.new(url: ENV['REDIS_URL']) +) + +# Same throttle rules as before +throttle('api/ip', limit: 60, period: 1.minute) do |req| + req.ip if req.path.start_with?('/api/') +end +``` + +**Benefits:** +- ✅ Persistent across restarts +- ✅ Shared across multiple servers +- ✅ Faster than database storage +- ✅ TTL support built-in + +#### 2. Implement IP Whitelisting + +```ruby +# config/rack_attack.rb +Rack::Attack.safelist('allow from localhost') do |req| + '127.0.0.1' == req.ip || '::1' == req.ip +end + +Rack::Attack.safelist('allow trusted IPs') do |req| + # Allow specific IPs (monitoring, trusted clients) + %w[192.168.1.100 10.0.0.50].include?(req.ip) +end +``` + +#### 3. Add Permanent Bans + +```ruby +# config/rack_attack.rb +Rack::Attack.blocklist('block bad actors') do |req| + # Block specific IPs permanently + BLOCKED_IPS.include?(req.ip) +end + +# Ban IPs after too many violations +Rack::Attack.blocklist('ban repeat offenders') do |req| + Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 10, findtime: 1.hour, bantime: 24.hours) do + # Track requests that hit rate limits + req.env['rack.attack.throttle_data']['api/ip'][:count] >= 60 + end +end +``` + +#### 4. Add Monitoring and Alerts + +```ruby +# config/initializers/rack_attack.rb +ActiveSupport::Notifications.subscribe('throttle.rack_attack') do |name, start, finish, request_id, payload| + req = payload[:request] + + # Log to monitoring service + logger.warn({ + message: 'Rate limit exceeded', + ip: req.ip, + path: req.path, + matched: payload[:matched] + }) + + # Send alert if threshold exceeded + if redis.incr("rate_limit_violations:#{req.ip}") > 100 + AlertService.notify("High rate limit violations from #{req.ip}") + end +end +``` + +#### 5. Implement API Keys + +```ruby +# For production, use API keys instead of IP-based limiting +throttle('api/key', limit: 1000, period: 1.hour) do |req| + # Extract API key from header + api_key = req.env['HTTP_X_API_KEY'] + + # Return key for throttling (nil bypasses throttle) + api_key if req.path.start_with?('/api/') +end +``` + +### Response Headers + +Add rate limit info to responses: + +```ruby +# config/rack_attack.rb +Rack::Attack.throttled_responder = lambda do |request| + match_data = request.env['rack.attack.match_data'] + now = Time.now.to_i + + headers = { + 'Content-Type' => 'application/json', + 'X-RateLimit-Limit' => match_data[:limit].to_s, + 'X-RateLimit-Remaining' => '0', + 'X-RateLimit-Reset' => (now + (match_data[:period] - now % match_data[:period])).to_s + } + + [429, headers, [{ error: 'Rate limit exceeded. Please try again later.' }.to_json]] +end +``` + +**Example Response:** +``` +HTTP/1.1 429 Too Many Requests +X-RateLimit-Limit: 60 +X-RateLimit-Remaining: 0 +X-RateLimit-Reset: 1699565400 +Content-Type: application/json + +{"error":"Rate limit exceeded. Please try again later."} +``` + +## Testing + +### Disabled in Test Environment + +```ruby +# main.rb:16 +use Rack::Attack unless ENV['ENVIRONMENT'] == 'test' +``` + +**Why?** +- Tests run faster without rate limiting delays +- Predictable test execution +- No flaky tests from rate limit state + +### Testing Rate Limits + +```ruby +# spec/api_spec.rb +context 'rate limiting' do + before do + # Temporarily enable Rack::Attack for this test + allow(ENV).to receive(:[]).with('ENVIRONMENT').and_return('development') + end + + it 'enforces global rate limit' do + 61.times { post '/api/v1/chain' } + expect(last_response.status).to eq(429) + end + + it 'enforces chain creation limit' do + 11.times { post '/api/v1/chain' } + expect(last_response.status).to eq(429) + end +end +``` + +## Next Steps + +- [API Reference](reference.md) - Complete endpoint documentation +- [API Examples](examples.md) - Integration examples with retry logic +- [Security Design](../architecture/security-design.md) - Security architecture + +--- + +**Questions about rate limits?** Open an issue on GitHub or see [CONTRIBUTING](../CONTRIBUTING.md). diff --git a/API_DOCUMENTATION.md b/docs/api/reference.md similarity index 100% rename from API_DOCUMENTATION.md rename to docs/api/reference.md diff --git a/docs/architecture/data-models.md b/docs/architecture/data-models.md new file mode 100644 index 0000000..c35c13a --- /dev/null +++ b/docs/architecture/data-models.md @@ -0,0 +1,680 @@ +# Data Models + +Comprehensive documentation of ChainForge's data models, MongoDB schema, and relationships. + +## Table of Contents + +1. [Overview](#overview) +2. [Blockchain Model](#blockchain-model) +3. [Block Model](#block-model) +4. [Model Relationships](#model-relationships) +5. [MongoDB Schema](#mongodb-schema) +6. [Lifecycle and Callbacks](#lifecycle-and-callbacks) +7. [Validation Rules](#validation-rules) +8. [Querying Patterns](#querying-patterns) + +## Overview + +ChainForge uses **Mongoid 7.0.5** as its Object-Document Mapper (ODM) to interact with MongoDB. The data model consists of two primary entities: + +- **Blockchain**: Parent model representing a blockchain instance +- **Block**: Child model representing individual blocks in the chain + +**Relationship:** One-to-Many (Blockchain has many Blocks) + +## Blockchain Model + +### Source Location + +`src/blockchain.rb` + +### Class Definition + +```ruby +class Blockchain + include Mongoid::Document + + has_many :blocks + + after_create :add_genesis_block +end +``` + +### Attributes + +| Field | Type | Description | Auto-Generated | +|-------|------|-------------|---------------| +| `_id` | BSON::ObjectId | MongoDB primary key | Yes | +| `created_at` | DateTime | Creation timestamp | Yes (Mongoid) | +| `updated_at` | DateTime | Last update timestamp | Yes (Mongoid) | + +**Note:** Blockchain has no user-defined fields, only relationships and timestamps. + +### Relationships + +**has_many :blocks** +- One Blockchain can have many Blocks +- Blocks are dependent (if Blockchain deleted, blocks deleted) +- Accessed via: `blockchain.blocks` + +### Methods + +#### add_block(data, difficulty: 2) + +Adds a new block to the blockchain. + +**Parameters:** +- `data` (String, required): Data to store in block +- `difficulty` (Integer, optional): Mining difficulty 1-10 (default: 2) + +**Returns:** +- `Block`: The newly created and mined block + +**Raises:** +- `RuntimeError`: If blockchain integrity is invalid + +**Process:** +1. Validates chain integrity +2. Gets last block +3. Builds new block with incremented index +4. Mines block (Proof of Work) +5. Saves block to database +6. Returns block + +**Example:** +```ruby +blockchain = Blockchain.create +block = blockchain.add_block("Transaction data", difficulty: 3) +# => # +``` + +**Source:** `src/blockchain.rb:22-34` + +#### integrity_valid? + +Validates the integrity of the entire blockchain. + +**Returns:** +- `true`: All blocks are valid and properly linked +- `false`: Chain integrity is compromised + +**Validation Checks:** +1. Each block's `previous_hash` matches prior block's `hash` +2. Each block's stored hash matches its calculated hash +3. Each block's hash meets its difficulty requirement + +**Example:** +```ruby +blockchain.integrity_valid? +# => true + +# If block data is tampered: +# => false +``` + +**Source:** `src/blockchain.rb:47-53` + +#### last_block + +Returns the most recent block in the chain. + +**Returns:** +- `Block`: The last block + +**Raises:** +- `RuntimeError`: If no blocks exist (shouldn't happen due to genesis block) + +**Example:** +```ruby +last = blockchain.last_block +# => # +``` + +**Source:** `src/blockchain.rb:40-42` + +### Callbacks + +#### after_create :add_genesis_block + +Automatically creates genesis block when blockchain is instantiated. + +**Genesis Block Properties:** +- `index`: 0 +- `data`: "Genesis Block" +- `previous_hash`: "0" +- `nonce`: 0 (not mined) +- `difficulty`: 0 + +**Triggered:** Immediately after `Blockchain.create` or `Blockchain.new.save` + +**Source:** `src/blockchain.rb:15` (callback), `src/blockchain.rb:59-61` (implementation) + +## Block Model + +### Source Location + +`src/block.rb` + +### Class Definition + +```ruby +class Block + include Mongoid::Document + include Mongoid::Timestamps + + # Fields + field :index, type: Integer + field :data, type: String + field :previous_hash, type: String + field :_hash, type: String, as: :hash + field :nonce, type: Integer, default: 0 + field :difficulty, type: Integer, default: -> { ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i } + + # Relationships + belongs_to :blockchain + + # Callbacks + before_validation :calculate_hash +end +``` + +### Attributes + +| Field | Type | Default | Description | Mutable | +|-------|------|---------|-------------|---------| +| `_id` | BSON::ObjectId | Auto | MongoDB primary key | No | +| `blockchain_id` | BSON::ObjectId | Required | Foreign key to Blockchain | No | +| `index` | Integer | Required | Position in chain (0, 1, 2, ...) | No | +| `data` | String | Required | Stored data/information | No* | +| `previous_hash` | String | Required | Previous block's hash | No | +| `_hash` | String | Auto | Block's SHA256 hash | No | +| `nonce` | Integer | 0 | Proof of Work nonce | No | +| `difficulty` | Integer | ENV or 2 | Mining difficulty (1-10) | No | +| `created_at` | DateTime | Auto | Block creation timestamp | No | +| `updated_at` | DateTime | Auto | Last update timestamp | Yes | + +**Note:** *Data is technically mutable in DB, but should never be changed (breaks immutability). + +### Special Field: _hash + +**Why `_hash` instead of `hash`?** + +Ruby objects have a built-in `hash` method: + +```ruby +object.hash # => Ruby's internal hash code (integer) +``` + +To avoid conflicts, Mongoid uses **field aliasing**: + +```ruby +field :_hash, type: String, as: :hash +``` + +**Usage:** +- **In Ruby code:** `block._hash` +- **In MongoDB:** Stored as `hash` field +- **In JSON responses:** Returned as `hash` + +**Example:** +```ruby +block._hash +# => "00abc123def456..." + +block.attributes['hash'] +# => "00abc123def456..." (MongoDB field name) +``` + +### Default Difficulty Lambda + +```ruby +field :difficulty, type: Integer, default: -> { ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i } +``` + +**Why lambda?** +- Environment variables are read at **runtime**, not parse time +- Allows different defaults per environment (dev, test, production) +- Falls back to 2 if `DEFAULT_DIFFICULTY` not set + +**Example:** +```bash +# .env +DEFAULT_DIFFICULTY=3 + +# Ruby +Block.new.difficulty # => 3 (from ENV) + +# If DEFAULT_DIFFICULTY not set: +Block.new.difficulty # => 2 (fallback) +``` + +### Relationships + +**belongs_to :blockchain** +- Each Block belongs to one Blockchain +- Foreign key: `blockchain_id` (BSON::ObjectId) +- Required: Cannot create block without blockchain +- Accessed via: `block.blockchain` + +### Methods + +#### calculate_hash + +Calculates the SHA256 hash of the block. + +**Returns:** +- `String`: 64-character hexadecimal hash + +**Hash Input:** +```ruby +"#{index}#{created_at.to_i}#{data}#{previous_hash}#{nonce}" +``` + +**Example:** +```ruby +block.index = 1 +block.created_at = Time.at(1699564821) +block.data = "Hello" +block.previous_hash = "abc123" +block.nonce = 7 + +block.calculate_hash +# => "0a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678" +``` + +**Source:** `src/block.rb:43-46` + +#### mine_block + +Performs Proof of Work mining to find valid nonce. + +**Returns:** +- `String`: Valid hash meeting difficulty requirement + +**Process:** +1. Generate target string (e.g., "000" for difficulty 3) +2. Loop: + - Calculate hash with current nonce + - Check if hash starts with target + - If yes: break (success) + - If no: increment nonce and retry + +**Example:** +```ruby +block.difficulty = 3 +block.mine_block +# => "000abc123def456..." (after ~4000 attempts) + +block.nonce +# => 4832 (number of attempts) +``` + +**Source:** `src/block.rb:60-69` + +#### valid_hash? + +Checks if block's hash meets difficulty requirement. + +**Returns:** +- `true`: Hash has required leading zeros +- `false`: Hash doesn't meet difficulty + +**Example:** +```ruby +block._hash = "000abc..." +block.difficulty = 3 +block.valid_hash? +# => true (starts with "000") + +block.difficulty = 4 +block.valid_hash? +# => false (doesn't start with "0000") +``` + +**Source:** `src/block.rb:74-77` + +#### valid_data?(data) + +Validates that provided data matches block's stored data. + +**Parameters:** +- `data` (String): Data to validate + +**Returns:** +- `true`: Data matches (hash verification succeeds) +- `false`: Data doesn't match (tampered) + +**Process:** +1. Recalculate hash using provided data +2. Compare with stored hash +3. Return true if match, false otherwise + +**Example:** +```ruby +block.data = "Original data" +block._hash = "abc123..." # Hash of original data + +block.valid_data?("Original data") +# => true (matches) + +block.valid_data?("Tampered data") +# => false (hash mismatch) +``` + +**Source:** `src/block.rb:50-52` + +### Callbacks + +#### before_validation :calculate_hash + +Automatically calculates hash before validation. + +**Triggered:** +- Before `block.valid?` +- Before `block.save` (validation runs before save) + +**Purpose:** +- Ensures hash is always calculated before saving +- Developer doesn't need to manually call `calculate_hash` + +**Example:** +```ruby +block = Block.new(index: 1, data: "test", previous_hash: "abc") +block._hash +# => nil (not calculated yet) + +block.valid? # Triggers before_validation +block._hash +# => "a1b2c3..." (calculated automatically) +``` + +**Source:** `src/block.rb:35` + +## Model Relationships + +### Entity Relationship Diagram + +``` +┌─────────────────────────────────┐ +│ Blockchain │ +│ ┌───────────────────────────┐ │ +│ │ _id: ObjectId │ │ +│ │ created_at: DateTime │ │ +│ │ updated_at: DateTime │ │ +│ └───────────────────────────┘ │ +└──────────────┬──────────────────┘ + │ 1 + │ + │ has_many :blocks + │ + │ * + ┌───────┴──────────────────────┐ + │ │ +┌──────▼───────────────┐ ┌───────────▼──────────┐ +│ Block (Genesis) │ │ Block 1 │ ... +│ ┌──────────────────┐ │ │ ┌──────────────────┐ │ +│ │ index: 0 │ │ │ │ index: 1 │ │ +│ │ data: "Genesis" │ │ │ │ data: "..." │ │ +│ │ previous_hash: 0 │ │ │ │ previous_hash: │ │ +│ │ hash: abc123... │ │ │ │ abc123... ──────┼──┐ +│ │ nonce: 0 │ │ │ │ hash: 00def4... │ │ │ +│ │ difficulty: 0 │ │ │ │ nonce: 142 │ │ │ +│ └──────────────────┘ │ │ │ difficulty: 2 │ │ │ +└──────────────────────┘ │ └──────────────────┘ │ │ + └──────────────────────┘ │ + Hash Link ◄┘ +``` + +### Relationship Details + +**Blockchain → Blocks (One-to-Many)** + +```ruby +# Create blockchain +blockchain = Blockchain.create + +# Access blocks +blockchain.blocks +# => [#] + +# Add block +blockchain.add_block("New data") + +blockchain.blocks.count +# => 2 + +# Query blocks +blockchain.blocks.where(difficulty: 3) +blockchain.blocks.order(index: :asc) +``` + +**Block → Blockchain (Belongs-to)** + +```ruby +# Access parent blockchain +block = Block.first +block.blockchain +# => # + +# Create block (requires blockchain) +blockchain = Blockchain.create +block = blockchain.blocks.create( + index: 1, + data: "test", + previous_hash: "abc" +) + +# Orphan blocks not allowed +Block.create(index: 1, data: "test") # Error: blockchain must exist +``` + +## MongoDB Schema + +### Database Structure + +ChainForge uses two collections in MongoDB: + +#### blockchains Collection + +```javascript +{ + "_id": ObjectId("674c8a1b2e4f5a0012345678"), + "created_at": ISODate("2025-11-09T12:34:56.789Z"), + "updated_at": ISODate("2025-11-09T12:34:56.789Z") +} +``` + +**Indexes:** +- `_id`: Primary key (auto-created, unique) + +**Size:** ~100 bytes per document + +#### blocks Collection + +```javascript +{ + "_id": ObjectId("674c8b2c3e5f6a0012345679"), + "blockchain_id": ObjectId("674c8a1b2e4f5a0012345678"), + "index": 1, + "data": "Hello, Blockchain!", + "previous_hash": "abc123def456...", + "hash": "00a1b2c3d4e5f6789abcdef...", + "nonce": 142, + "difficulty": 2, + "created_at": ISODate("2025-11-09T12:35:23.456Z"), + "updated_at": ISODate("2025-11-09T12:35:23.456Z") +} +``` + +**Indexes:** +- `_id`: Primary key (auto-created, unique) +- `blockchain_id`: Foreign key (auto-indexed by Mongoid) + +**Size:** ~500 bytes per document (depends on data field) + +### Data Types in MongoDB + +| Ruby Type | MongoDB BSON Type | Example | +|-----------|-------------------|---------| +| Integer | Int32/Int64 | `index: 1` | +| String | String (UTF-8) | `data: "Hello"` | +| BSON::ObjectId | ObjectId | `_id: ObjectId("...")` | +| DateTime | Date | `created_at: ISODate("...")` | + +### Querying Examples + +**Find blockchain by ID:** +```ruby +Blockchain.find("674c8a1b2e4f5a0012345678") +``` + +**Get all blocks in chain:** +```ruby +blockchain.blocks.to_a +``` + +**Find specific block:** +```ruby +blockchain.blocks.find("674c8b2c3e5f6a0012345679") +blockchain.blocks.where(index: 1).first +``` + +**Count blocks:** +```ruby +blockchain.blocks.count +``` + +**Get blocks by difficulty:** +```ruby +blockchain.blocks.where(difficulty: 3).to_a +``` + +**Order blocks by index:** +```ruby +blockchain.blocks.order(index: :asc).to_a +``` + +## Lifecycle and Callbacks + +### Blockchain Lifecycle + +``` +1. Creation + Blockchain.create + ↓ +2. Callback: after_create + add_genesis_block + ↓ +3. Genesis Block Created + Block(index: 0, data: "Genesis Block") + ↓ +4. Blockchain Ready + Can add blocks via add_block() +``` + +### Block Lifecycle + +``` +1. Build Block + blockchain.blocks.build(index: 1, data: "test", ...) + ↓ +2. Mining (if via add_block) + block.mine_block + ├─ Loop: increment nonce + ├─ Calculate hash + └─ Until valid hash found + ↓ +3. Validation + block.valid? + ├─ Callback: before_validation + ├─ Calls: calculate_hash (if not mined) + └─ Validates fields + ↓ +4. Save to MongoDB + block.save! + ├─ Sets created_at + ├─ Sets updated_at + └─ Writes to database + ↓ +5. Block Persisted + Immutable in blockchain +``` + +## Validation Rules + +### Blockchain Validation + +**No explicit validations** - Blockchain model has no custom validators. + +**Integrity Validation:** +- Manual via `integrity_valid?` method +- Called before adding new blocks + +### Block Validation + +**Mongoid Validations:** +- `blockchain`: Must be present (belongs_to relationship) +- All other validations implicit via field types + +**Application-Level Validation:** +- Difficulty: 1-10 (enforced by API validator, not model) +- Data: Must be filled (enforced by API validator, not model) + +**Hash Validation:** +- `valid_hash?`: Checks PoW requirement +- `valid_data?`: Checks data integrity + +## Querying Patterns + +### Common Queries + +**Get last N blocks:** +```ruby +blockchain.blocks.order(index: :desc).limit(5) +``` + +**Find blocks with specific data:** +```ruby +blockchain.blocks.where(data: /transaction/i) # Case-insensitive regex +``` + +**Get high-difficulty blocks:** +```ruby +blockchain.blocks.where(:difficulty.gte => 5) +``` + +**Count blocks by difficulty:** +```ruby +blockchain.blocks.where(difficulty: 3).count +``` + +**Get blocks created today:** +```ruby +blockchain.blocks.where(:created_at.gte => Time.now.beginning_of_day) +``` + +### Performance Considerations + +**Indexed Queries (Fast):** +- Find by `_id`: O(1) +- Find by `blockchain_id`: O(1) (indexed) + +**Non-Indexed Queries (Slower):** +- Find by `data`: O(n) (full collection scan) +- Find by `difficulty`: O(n) + +**For Production:** +- Add indexes for frequently queried fields +- Use projections to limit returned fields +- Implement pagination for large result sets + +## Next Steps + +- [Architecture Overview](overview.md) - System design and data flow +- [Proof of Work](proof-of-work.md) - Mining algorithm +- [Security Design](security-design.md) - Security analysis +- [API Reference](../api/reference.md) - HTTP endpoints + +--- + +**Questions?** See [CONTRIBUTING](../CONTRIBUTING.md) for how to ask questions or suggest improvements. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..665d3ee --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,588 @@ +# Architecture Overview + +This document provides a comprehensive overview of ChainForge's system architecture, data flow, and component interactions. + +## Table of Contents + +1. [High-Level Architecture](#high-level-architecture) +2. [Technology Stack](#technology-stack) +3. [Component Overview](#component-overview) +4. [Data Flow](#data-flow) +5. [Database Schema](#database-schema) +6. [Security Layers](#security-layers) +7. [Design Decisions](#design-decisions) + +## High-Level Architecture + +ChainForge follows a classic three-tier architecture: + +``` +┌─────────────────────────────────────────┐ +│ Client Layer │ +│ (HTTP Clients: curl, apps, browsers) │ +└───────────────┬─────────────────────────┘ + │ HTTP/JSON + ↓ +┌─────────────────────────────────────────┐ +│ Application Layer (Sinatra) │ +│ ┌────────────────────────────────────┐ │ +│ │ Rack::Attack (Rate Limiting) │ │ +│ └──────────────┬─────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ API Routes (/api/v1/*) │ │ +│ └──────────────┬─────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ Input Validation (dry-validation) │ │ +│ └──────────────┬─────────────────────┘ │ +│ ↓ │ +│ ┌────────────────────────────────────┐ │ +│ │ Business Logic (Models) │ │ +│ │ - Blockchain Model │ │ +│ │ - Block Model (with PoW) │ │ +│ └──────────────┬─────────────────────┘ │ +└─────────────────┼─────────────────────────┘ + │ Mongoid ODM + ↓ +┌─────────────────────────────────────────┐ +│ Data Layer (MongoDB) │ +│ ┌────────────────┐ ┌────────────────┐│ +│ │ Blockchains │ │ Blocks ││ +│ │ Collection │ │ Collection ││ +│ └────────────────┘ └────────────────┘│ +└─────────────────────────────────────────┘ +``` + +## Technology Stack + +### Core Technologies + +| Component | Technology | Version | Purpose | +|-----------|-----------|---------|---------| +| **Language** | Ruby | 3.2.2 | Application logic | +| **Web Framework** | Sinatra | 4.0 | HTTP routing and request handling | +| **Database** | MongoDB | Latest | NoSQL document storage | +| **ODM** | Mongoid | 7.0.5 | Object-Document Mapping | +| **Server** | Puma | (via Sinatra) | HTTP server | + +### Security & Validation + +| Component | Technology | Version | Purpose | +|-----------|-----------|---------|---------| +| **Rate Limiting** | Rack::Attack | 6.7 | DoS protection | +| **Input Validation** | dry-validation | 1.10 | Schema validation | +| **Cryptography** | Digest (Ruby stdlib) | - | SHA256 hashing | + +### Development & Quality + +| Component | Technology | Version | Purpose | +|-----------|-----------|---------|---------| +| **Testing** | RSpec | 3.10 | Unit and integration tests | +| **Coverage** | SimpleCov | - | Code coverage reporting | +| **Linting** | RuboCop | 1.57 | Code style enforcement | +| **CI/CD** | GitHub Actions | - | Automated testing | + +### Deployment + +| Component | Technology | Version | Purpose | +|-----------|-----------|---------|---------| +| **Containerization** | Docker | Latest | Application containers | +| **Orchestration** | Docker Compose | Latest | Multi-container deployment | +| **Environment** | dotenv | 2.7 | Configuration management | + +## Component Overview + +### 1. Application Entry Point (`main.rb`) + +The main Sinatra application that: +- Loads dependencies and configuration +- Initializes Mongoid connection +- Configures Rack::Attack middleware +- Defines API routes under `/api/v1` namespace +- Handles request parsing and error responses + +**Key Responsibilities:** +- Request routing +- Middleware configuration +- JSON parsing and response formatting +- Error handling +- Helper methods for common operations + +### 2. Blockchain Model (`src/blockchain.rb`) + +The core blockchain model that manages collections of blocks. + +**Attributes:** +- `id`: MongoDB ObjectId (auto-generated) +- `blocks`: Has-many relationship to Block model +- `created_at`, `updated_at`: Timestamps (Mongoid) + +**Key Methods:** +- `add_block(data, difficulty)`: Mines and adds new block to chain +- `integrity_valid?`: Validates entire chain integrity +- `last_block`: Returns the most recent block +- `add_genesis_block` (private): Auto-creates genesis block on creation + +**Callbacks:** +- `after_create :add_genesis_block`: Creates genesis block automatically + +**Source:** `/src/blockchain.rb` + +### 3. Block Model (`src/block.rb`) + +Represents individual blocks in the blockchain. + +**Attributes:** +- `index`: Integer - Block position in chain (0 = genesis) +- `data`: String - Stored information +- `previous_hash`: String - Hash of previous block +- `_hash`: String - Block's SHA256 hash (aliased as `hash`) +- `nonce`: Integer - Proof of Work nonce (default: 0) +- `difficulty`: Integer - Mining difficulty (default: from env) +- `created_at`, `updated_at`: Timestamps + +**Key Methods:** +- `calculate_hash`: Generates SHA256 hash from block data +- `mine_block`: Performs Proof of Work mining +- `valid_hash?`: Checks if hash meets difficulty requirement +- `valid_data?(data)`: Validates data integrity + +**Callbacks:** +- `before_validation :calculate_hash`: Auto-calculates hash before save + +**Relationships:** +- `belongs_to :blockchain`: Each block belongs to one blockchain + +**Source:** `/src/block.rb` + +### 4. Validators (`src/validators.rb`) + +Input validation using dry-validation schema contracts. + +**BlockDataContract:** +```ruby +schema do + required(:data).filled(:string) # Must be present and non-empty + optional(:difficulty).filled(:integer, gteq?: 1, lteq?: 10) # 1-10 if present +end +``` + +**Source:** `/src/validators.rb` + +### 5. Rate Limiting Configuration (`config/rack_attack.rb`) + +Rack::Attack middleware configuration for API protection. + +**Throttles:** +1. **Global Limit**: 60 requests/minute per IP (all endpoints) +2. **Chain Creation**: 10 requests/minute per IP (POST /api/v1/chain) +3. **Block Creation**: 30 requests/minute per IP (POST /api/v1/chain/:id/block) + +**Custom Response:** +- Status: 429 (Too Many Requests) +- Body: `{"error": "Rate limit exceeded. Please try again later."}` + +**Disabled in:** Test environment (`ENV['ENVIRONMENT'] == 'test'`) + +**Source:** `/config/rack_attack.rb` + +### 6. Database Configuration (`config/mongoid.yml`) + +MongoDB connection configuration using environment variables. + +**Environments:** +- `development`: Local development database +- `test`: Separate test database +- `production`: Production database + +**Key Configuration:** +- Database name: `ENV['MONGO_DB_NAME']` +- Host: `ENV['MONGO_DB_HOST']` +- Port: `ENV['MONGO_DB_PORT']` + +**Source:** `/config/mongoid.yml` + +## Data Flow + +### Creating a Blockchain + +``` +1. Client Request + POST /api/v1/chain + +2. Rate Limiting Check + Rack::Attack: Check global + chain creation limits + +3. Route Handler (main.rb:34) + blockchain = Blockchain.create + +4. Blockchain Model + - Create new Blockchain instance + - Trigger after_create callback + +5. Genesis Block Creation + - Create Block(index: 0, data: "Genesis Block", previous_hash: "0") + - Calculate hash (no mining for genesis) + - Save to database + +6. Response + {"id": "blockchain_id"} +``` + +### Mining a Block + +``` +1. Client Request + POST /api/v1/chain/:id/block + Body: {"data": "...", "difficulty": 3} + +2. Rate Limiting Check + Rack::Attack: Check global + block creation limits + +3. Input Validation (main.rb:42) + BlockDataContract.new.call(block_data) + - Validate data is filled string + - Validate difficulty is 1-10 (if present) + - Return 400 if validation fails + +4. Blockchain Retrieval (main.rb:47) + blockchain = Blockchain.find(chain_id) + - Raise error if not found + +5. Add Block (blockchain.rb:22) + blockchain.add_block(data, difficulty: 3) + +6. Integrity Check (blockchain.rb:23) + integrity_valid? or raise 'Blockchain is not valid' + - Validate all existing blocks + - Check hash links + - Check hash validity + - Check PoW requirements + +7. Block Creation (blockchain.rb:25-30) + - Build new block with: + - index: last_block.index + 1 + - data: from request + - previous_hash: last_block._hash + - difficulty: from request or env + +8. Mining Process (block.rb:60) + block.mine_block + + Mining Algorithm: + ┌─────────────────────────────────┐ + │ target = "000" (for difficulty 3)│ + │ nonce = 0 │ + └─────────────────────────────────┘ + ↓ + ┌─────────────────────────────────┐ + │ Loop: │ + │ 1. calculate_hash() │ + │ 2. if hash.start_with?(target)│ + │ → break (found!) │ + │ 3. else nonce++ │ + │ 4. repeat │ + └─────────────────────────────────┘ + ↓ + ┌─────────────────────────────────┐ + │ Valid hash found! │ + │ hash = "000abc123..." │ + │ nonce = 4832 │ + └─────────────────────────────────┘ + +9. Save Block (blockchain.rb:32) + block.save! + - Persists to MongoDB + +10. Response (main.rb:51-57) + { + "chain_id": "...", + "block_id": "...", + "block_hash": "000abc...", + "nonce": 4832, + "difficulty": 3 + } +``` + +### Validating Block Data + +``` +1. Client Request + POST /api/v1/chain/:id/block/:block_id/valid + Body: {"data": "original data"} + +2. Validation & Retrieval + - Validate input + - Find blockchain + - Find block + +3. Data Validation (block.rb:50) + block.valid_data?(data) + + Validation Process: + ┌──────────────────────────────────┐ + │ 1. Recalculate hash with data │ + │ new_hash = SHA256(index + │ + │ timestamp + data + │ + │ previous_hash + │ + │ nonce) │ + │ │ + │ 2. Compare with stored hash │ + │ new_hash == block._hash? │ + └──────────────────────────────────┘ + +4. Response + { + "chain_id": "...", + "block_id": "...", + "valid": true/false + } +``` + +## Database Schema + +### Collections + +MongoDB stores two collections: + +#### 1. `blockchains` Collection + +```javascript +{ + _id: ObjectId("674c8a1b2e4f5a0012345678"), + created_at: ISODate("2025-11-09T12:34:56.789Z"), + updated_at: ISODate("2025-11-09T12:34:56.789Z") +} +``` + +**Indexes:** +- `_id`: Primary key (auto-indexed) + +#### 2. `blocks` Collection + +```javascript +{ + _id: ObjectId("674c8b2c3e5f6a0012345679"), + blockchain_id: ObjectId("674c8a1b2e4f5a0012345678"), // Foreign key + index: 1, + data: "Hello, Blockchain!", + previous_hash: "abc123def456...", + hash: "00abc123...", // Stored as 'hash' but field is '_hash' in Ruby + nonce: 142, + difficulty: 2, + created_at: ISODate("2025-11-09T12:35:23.456Z"), + updated_at: ISODate("2025-11-09T12:35:23.456Z") +} +``` + +**Indexes:** +- `_id`: Primary key +- `blockchain_id`: Foreign key to blockchains collection + +**Relationships:** +- Each block document has a `blockchain_id` field referencing its parent blockchain +- Mongoid manages this relationship via `belongs_to :blockchain` + +### Data Size Considerations + +**Block Size:** +- Typical block: ~500 bytes +- Hash: 64 characters (SHA256 hex) +- Data: Variable (user-provided) +- Overhead: ~200 bytes (metadata) + +**Scalability:** +- MongoDB handles millions of documents efficiently +- ChainForge is educational (not optimized for production scale) +- For production: Consider sharding, indexing strategies, compression + +## Security Layers + +ChainForge implements multiple security layers: + +### 1. Rate Limiting (Rack::Attack) + +**Purpose:** Prevent abuse and DoS attacks + +**Implementation:** +- IP-based throttling +- Per-endpoint limits +- Memory-based storage (resets on restart) + +**Limitations:** +- Not suitable for distributed systems (single-server only) +- Can be bypassed with IP rotation +- Memory-only (no persistence) + +### 2. Input Validation (dry-validation) + +**Purpose:** Prevent malformed data and injection attacks + +**Implementation:** +- Schema-based validation +- Type checking +- Range enforcement +- Required field validation + +**Benefits:** +- Prevents NoSQL injection +- Ensures data integrity +- Returns detailed error messages + +### 3. Proof of Work + +**Purpose:** Make blockchain tampering computationally expensive + +**Implementation:** +- SHA256 hashing +- Difficulty-based mining +- Nonce iteration + +**Security Properties:** +- Changing block data invalidates hash +- Re-mining required to fix hash +- Cascade effect through chain + +### 4. Chain Integrity Validation + +**Purpose:** Detect tampering and ensure consistency + +**Implementation:** +- Hash chaining validation +- PoW validation +- Block hash verification + +**Validation Steps:** +1. Verify each block's hash matches calculated hash +2. Verify each block's hash meets difficulty requirement +3. Verify each block's previous_hash links correctly + +## Design Decisions + +### Why MongoDB? + +**Chosen for:** +- ✅ Flexible schema (JSON-like documents) +- ✅ Easy to learn and use +- ✅ Good Ruby support (Mongoid) +- ✅ Suitable for educational projects + +**Trade-offs:** +- ❌ Less ACID guarantees than SQL +- ❌ No built-in foreign key constraints +- ❌ Larger storage footprint + +**Alternatives considered:** PostgreSQL (JSON columns), Redis (fast but in-memory) + +### Why Sinatra? + +**Chosen for:** +- ✅ Lightweight and simple +- ✅ Minimal boilerplate +- ✅ Easy to understand for learners +- ✅ Sufficient for REST API needs + +**Trade-offs:** +- ❌ Less built-in features than Rails +- ❌ Manual configuration required +- ❌ Smaller ecosystem + +**Alternatives considered:** Rails (too heavy), Grape (similar complexity) + +### Why SHA256 (Single)? + +**Chosen for:** +- ✅ Industry standard +- ✅ Sufficient for educational purposes +- ✅ Fast computation +- ✅ Built into Ruby stdlib + +**Trade-offs:** +- ❌ Bitcoin uses double SHA256 +- ❌ Quantum computing concerns (future) + +**Alternatives considered:** Double SHA256 (overkill for education) + +### Why In-Memory Rate Limiting? + +**Chosen for:** +- ✅ Simple implementation +- ✅ No external dependencies +- ✅ Sufficient for single-server deployment + +**Trade-offs:** +- ❌ Resets on restart +- ❌ Not suitable for distributed systems +- ❌ No persistence + +**Alternatives considered:** Redis (adds complexity), persistent storage (overkill) + +### Why No Authentication? + +**Reasoning:** +- Educational focus (blockchain concepts, not auth) +- Simplifies API usage for learners +- Would add complexity without educational value +- NOT intended for production use + +**Production Requirements:** +- ✅ JWT or session-based authentication +- ✅ API keys +- ✅ Role-based access control + +### Why Fixed Difficulty Range (1-10)? + +**Chosen for:** +- ✅ Predictable mining times +- ✅ Prevents excessive CPU usage +- ✅ Easier to demonstrate PoW concepts +- ✅ Safe for development machines + +**Trade-offs:** +- ❌ Bitcoin's difficulty adjusts dynamically +- ❌ Not representative of production blockchains + +**Production Alternative:** Dynamic difficulty adjustment based on block time + +## Performance Characteristics + +### Mining Time Estimates + +| Difficulty | Avg Attempts | Time (typical CPU) | +|-----------|-------------|-------------------| +| 1 | 16 | < 0.1s | +| 2 | 256 | 0.1-1s | +| 3 | 4,096 | 1-5s | +| 4 | 65,536 | 10-30s | +| 5 | 1,048,576 | 2-5 min | +| 6+ | 16,777,216+ | 30+ min | + +### API Response Times + +| Endpoint | Typical Response Time | +|----------|---------------------| +| POST /chain | < 50ms (includes genesis block creation) | +| POST /chain/:id/block (difficulty 2) | 100ms - 1s (mining) | +| GET /chain/:id/block/:id | < 10ms | +| POST /chain/:id/block/:id/valid | < 10ms | + +### Database Performance + +- **Insert**: O(1) - Fast +- **Find by ID**: O(1) - Very fast (indexed) +- **Chain validation**: O(n) - Linear with chain length +- **Scalability**: Tested up to 10,000 blocks per chain + +## Next Steps + +For deeper dives into specific topics: + +- [Proof of Work Deep Dive](proof-of-work.md) - Mining algorithm details +- [Data Models](data-models.md) - MongoDB schema and relationships +- [Security Design](security-design.md) - Comprehensive security analysis +- [API Reference](../api/reference.md) - Complete endpoint documentation + +--- + +**Questions or improvements?** See [CONTRIBUTING](../CONTRIBUTING.md) for how to contribute to this documentation. diff --git a/docs/architecture/proof-of-work.md b/docs/architecture/proof-of-work.md new file mode 100644 index 0000000..4a18fc9 --- /dev/null +++ b/docs/architecture/proof-of-work.md @@ -0,0 +1,666 @@ +# Proof of Work Deep Dive + +An in-depth exploration of ChainForge's Proof of Work (PoW) implementation, mining algorithm, and consensus mechanism. + +## Table of Contents + +1. [What is Proof of Work?](#what-is-proof-of-work) +2. [Mining Algorithm](#mining-algorithm) +3. [Hash Function (SHA256)](#hash-function-sha256) +4. [Difficulty System](#difficulty-system) +5. [Mining Process Step-by-Step](#mining-process-step-by-step) +6. [Computational Complexity](#computational-complexity) +7. [Security Analysis](#security-analysis) +8. [Comparison with Bitcoin](#comparison-with-bitcoin) +9. [Code Deep Dive](#code-deep-dive) + +## What is Proof of Work? + +**Proof of Work (PoW)** is a consensus mechanism that requires participants (miners) to perform computational work to add new blocks to the blockchain. + +### Core Concept + +The basic idea is simple: +> **"It must be difficult to create, but easy to verify"** + +In ChainForge: +- **Difficult to create**: Finding a valid hash requires thousands of attempts (mining) +- **Easy to verify**: Checking if a hash is valid requires one calculation + +### Why Use Proof of Work? + +1. **Security**: Makes tampering computationally expensive +2. **Decentralization**: No central authority needed (in distributed systems) +3. **Immutability**: Historical blocks become harder to change over time +4. **Rate Limiting**: Controls rate of new block creation (in real blockchains) + +### The Puzzle + +The mining puzzle in ChainForge is: + +**Given:** +- Block index +- Block timestamp +- Block data +- Previous block's hash +- Target difficulty (number of leading zeros) + +**Find:** +- A nonce (number) such that: + ``` + SHA256(index + timestamp + data + previous_hash + nonce) + starts with N leading zeros (where N = difficulty) + ``` + +## Mining Algorithm + +ChainForge implements a simple but effective mining algorithm. + +### Algorithm Overview + +``` +Input: block (with index, data, previous_hash, difficulty) +Output: nonce that produces valid hash + +1. target ← generate string of N zeros (N = difficulty) +2. nonce ← 0 +3. LOOP: + a. hash ← SHA256(index + timestamp + data + previous_hash + nonce) + b. IF hash starts with target: + RETURN nonce (success!) + c. ELSE: + nonce ← nonce + 1 + GO TO step 3a +``` + +### Ruby Implementation + +From `src/block.rb:60-69`: + +```ruby +def mine_block + target = '0' * difficulty # Generate target: "000" for difficulty 3 + loop do + calculate_hash # Calculate hash with current nonce + break if _hash.start_with?(target) # Check if valid + + self.nonce += 1 # Increment nonce + end + _hash +end +``` + +### Key Characteristics + +- **Brute Force**: Tries every nonce sequentially (0, 1, 2, ...) +- **Non-Deterministic**: Can't predict which nonce will work +- **Probabilistic**: Each attempt has same probability of success +- **No Shortcuts**: Must calculate hash for each attempt + +## Hash Function (SHA256) + +ChainForge uses **SHA256 (Secure Hash Algorithm 256-bit)** for cryptographic hashing. + +### What is SHA256? + +SHA256 is a cryptographic hash function that: +- Takes any input (any size) +- Produces a 256-bit (64 hex characters) output +- Is deterministic (same input = same output) +- Is one-way (can't reverse hash to get input) +- Has avalanche effect (small input change = completely different hash) + +### Hash Calculation in ChainForge + +From `src/block.rb:43-46`: + +```ruby +def calculate_hash + set_created_at # Ensure timestamp is set + self._hash = Digest::SHA256.hexdigest( + "#{index}#{created_at.to_i}#{data}#{previous_hash}#{nonce}" + ) +end +``` + +**Input Components:** +1. `index`: Block position (0, 1, 2, ...) +2. `created_at.to_i`: Unix timestamp (seconds since 1970-01-01) +3. `data`: User-provided data (string) +4. `previous_hash`: Previous block's hash (links blocks) +5. `nonce`: Number used once (incremented during mining) + +### Example Hash Calculation + +```ruby +# Block 1 with: +index = 1 +timestamp = 1699564821 +data = "Hello, Blockchain!" +previous_hash = "abc123def456..." +nonce = 142 + +# Hash calculation: +input = "1" + "1699564821" + "Hello, Blockchain!" + "abc123def456..." + "142" +hash = SHA256(input) +# Result: "00a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678" +``` + +### Avalanche Effect + +Changing even one character completely changes the hash: + +```ruby +input1 = "Hello, Blockchain!" +hash1 = SHA256(input1) +# "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +input2 = "Hello, Blockchain?" # Changed ! to ? +hash2 = SHA256(input2) +# "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" +``` + +Completely different hashes! + +### Why SHA256? + +**Advantages:** +- ✅ Widely used and well-tested +- ✅ Cryptographically secure (no known collisions) +- ✅ Fast computation (hardware support) +- ✅ Standard in blockchain industry + +**Alternatives:** +- SHA3 (newer, slightly slower) +- BLAKE2 (faster, less adoption) +- Scrypt (memory-hard, ASIC-resistant) + +## Difficulty System + +ChainForge uses a **leading zeros** difficulty system. + +### How It Works + +**Difficulty Level:** +- Integer from 1 to 10 +- Represents number of leading zeros required in hash + +**Examples:** + +| Difficulty | Target Pattern | Example Valid Hash | +|-----------|---------------|-------------------| +| 1 | `0*` | `0a1b2c3d4e5f...` | +| 2 | `00*` | `00abc123def4...` | +| 3 | `000*` | `000def456abc...` | +| 4 | `0000*` | `0000123abc45...` | +| 5 | `00000*` | `00000abcdef1...` | + +**Validation:** + +From `src/block.rb:74-77`: + +```ruby +def valid_hash? + target = '0' * difficulty # "000" for difficulty 3 + _hash.start_with?(target) # Check if hash starts with target +end +``` + +### Difficulty Configuration + +**Per-Block (via API):** +```bash +curl -X POST http://localhost:1910/api/v1/chain/:id/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "test", "difficulty": 4}' +``` + +**Default (via environment):** +```bash +# .env file +DEFAULT_DIFFICULTY=2 +``` + +**In Code:** +```ruby +# src/block.rb:30 +field :difficulty, type: Integer, default: -> { ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i } +``` + +### Difficulty Constraints + +- **Minimum:** 1 (at least one leading zero) +- **Maximum:** 10 (practical limit for development) +- **Validation:** Enforced by dry-validation contract + +**Why limit to 10?** +- Difficulty 11 would take hours on typical hardware +- Educational project doesn't need Bitcoin-level difficulty +- Prevents accidental resource exhaustion + +## Mining Process Step-by-Step + +Let's walk through mining a block with **difficulty 3**. + +### Setup + +``` +Block to Mine: +├─ index: 2 +├─ data: "Transaction XYZ" +├─ previous_hash: "00abc123..." +├─ difficulty: 3 +├─ timestamp: 1699564821 +└─ nonce: 0 (starting point) +``` + +### Mining Attempts + +**Attempt 1: nonce = 0** +``` +Input: "2" + "1699564821" + "Transaction XYZ" + "00abc123..." + "0" +Hash: SHA256(input) +Result: "a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678" + +Check: Does "a1b2c3..." start with "000"? +Answer: No (starts with "a") +Action: nonce++ → nonce = 1 +``` + +**Attempt 2: nonce = 1** +``` +Input: "2" + "1699564821" + "Transaction XYZ" + "00abc123..." + "1" +Hash: SHA256(input) +Result: "9f8e7d6c5b4a321fedcba9876543210fedcba9876543210fedcba987654321" + +Check: Does "9f8e7d..." start with "000"? +Answer: No (starts with "9") +Action: nonce++ → nonce = 2 +``` + +**Attempt 3: nonce = 2** +``` +Input: "2" + "1699564821" + "Transaction XYZ" + "00abc123..." + "2" +Hash: SHA256(input) +Result: "3d4c5b6a7e8f9d0c1b2a3f4e5d6c7b8a9f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c" + +Check: Does "3d4c5b..." start with "000"? +Answer: No (starts with "3") +Action: nonce++ → nonce = 3 +``` + +**... (attempts 4-4831) ...** + +**Attempt 4832: nonce = 4832** +``` +Input: "2" + "1699564821" + "Transaction XYZ" + "00abc123..." + "4832" +Hash: SHA256(input) +Result: "000def123abc456789abcdef0123456789abcdef0123456789abcdef012345678" + +Check: Does "000def..." start with "000"? +Answer: Yes! ✓ +Action: Mining complete! +``` + +### Result + +``` +Mined Block: +├─ index: 2 +├─ data: "Transaction XYZ" +├─ previous_hash: "00abc123..." +├─ hash: "000def123abc456..." +├─ difficulty: 3 +├─ nonce: 4832 +└─ timestamp: 1699564821 + +Mining Statistics: +├─ Attempts: 4,832 +├─ Time: ~3-5 seconds +└─ Success Rate: 1/4,832 = 0.021% +``` + +## Computational Complexity + +### Time Complexity + +**Per Hash Calculation:** O(1) +- SHA256 is constant time regardless of input size + +**Mining Algorithm:** O(2^(4 * difficulty)) +- Expected attempts grows exponentially with difficulty +- Each increment in difficulty multiplies attempts by ~16 + +### Average Attempts by Difficulty + +| Difficulty | Expected Attempts | Formula | +|-----------|------------------|---------| +| 1 | 16 | 16^1 | +| 2 | 256 | 16^2 | +| 3 | 4,096 | 16^3 | +| 4 | 65,536 | 16^4 | +| 5 | 1,048,576 | 16^5 | +| 6 | 16,777,216 | 16^6 | +| 7 | 268,435,456 | 16^7 | + +**Why 16?** +- Each hex digit has 16 possible values (0-9, a-f) +- To get specific leading hex digit: 1/16 probability +- To get N specific digits: (1/16)^N + +### Mining Time Estimates + +Assuming **1 million hashes/second** (typical modern CPU): + +| Difficulty | Avg Attempts | Avg Time | +|-----------|-------------|----------| +| 1 | 16 | 0.000016s | +| 2 | 256 | 0.000256s | +| 3 | 4,096 | 0.004s | +| 4 | 65,536 | 0.066s | +| 5 | 1,048,576 | 1.05s | +| 6 | 16,777,216 | 16.8s | +| 7 | 268,435,456 | 4.5 min | +| 8 | 4,294,967,296 | 1.2 hours | +| 9 | 68,719,476,736 | 19 hours | +| 10 | 1,099,511,627,776 | 12.7 days | + +**Note:** Actual times vary based on CPU, Ruby performance, and randomness. + +### Space Complexity + +**Memory Usage:** O(1) +- Mining uses constant memory +- Only stores current nonce and hash +- No need to remember previous attempts + +**Storage:** +- Each block: ~500 bytes +- Hash: 64 bytes (256 bits) +- Nonce: 8 bytes (64-bit integer) + +## Security Analysis + +### Attack Scenarios + +#### Scenario 1: Change Historical Block Data + +**Attacker's Goal:** Change data in Block 5 (chain has 10 blocks) + +**Required Work:** + +1. **Modify Block 5 data:** + - Old hash: `000abc...` (valid) + - New hash: `9xyz...` (invalid - doesn't start with 000) + +2. **Re-mine Block 5:** + - Find new nonce to get valid hash starting with `000` + - Expected attempts: 4,096 (difficulty 3) + +3. **Block 6 is now invalid:** + - Its `previous_hash` points to old Block 5 hash + - Must update and re-mine + +4. **Re-mine Blocks 6-10:** + - Each requires ~4,096 attempts + - Total: 5 blocks × 4,096 attempts = 20,480 attempts + +**Computational Cost:** +- Time: ~10-15 seconds (for difficulty 3) +- Grows linearly with chain length +- Grows exponentially with difficulty + +**In Bitcoin:** +- Difficulty ~19 leading zeros +- Would require years of computation on supercomputers + +#### Scenario 2: Add Fraudulent Block + +**Attacker's Goal:** Add invalid block without mining + +**Prevention:** +1. **PoW Validation:** + ```ruby + # blockchain.rb:47-53 + def integrity_valid? + blocks.each_cons(2).all? do |previous_block, current_block| + # ... + current_block.valid_hash? # ← Checks PoW + end + end + ``` + +2. **Before Adding New Block:** + ```ruby + # blockchain.rb:23 + integrity_valid? or raise 'Blockchain is not valid' + ``` + +**Result:** Cannot add blocks without valid PoW + +#### Scenario 3: 51% Attack (Distributed Systems) + +**Note:** ChainForge is single-server, so this doesn't apply. But in distributed blockchains: + +**Attack:** Control >51% of network's mining power +**Impact:** Can re-write recent history faster than honest nodes +**Defense:** +- Larger network = harder to acquire 51% +- Bitcoin's network is too large for practical 51% attack + +### Security Properties + +**1. Tamper Detection:** +- Any data change invalidates hash +- Invalid hash detected immediately +- Chain validation fails + +**2. Computational Security:** +- Modifying history requires re-mining +- Time/cost increases with: + - Chain length (more blocks to re-mine) + - Difficulty (more attempts per block) + - Network hashrate (in distributed systems) + +**3. Cascade Effect:** +- Changing one block invalidates all subsequent blocks +- Creates amplification of required work + +**4. Verifiability:** +- Anyone can verify PoW in O(1) time +- Just check hash starts with required zeros +- No need to repeat mining + +### Limitations + +**ChainForge is Educational:** +- ❌ Single server (no distributed consensus) +- ❌ Low difficulty (easy to re-mine) +- ❌ No economic incentives +- ❌ No network protection + +**For Production:** +- ✅ Higher difficulty (19+ leading zeros) +- ✅ Distributed network +- ✅ Economic incentives (mining rewards) +- ✅ Dynamic difficulty adjustment + +## Comparison with Bitcoin + +| Feature | ChainForge | Bitcoin | +|---------|-----------|---------| +| **Hash Algorithm** | Single SHA256 | Double SHA256 | +| **Difficulty** | Fixed 1-10 | Dynamic (adjusts every 2016 blocks) | +| **Leading Zeros** | 1-10 | ~19 (as of 2023) | +| **Block Time** | Variable (no target) | ~10 minutes (target) | +| **Difficulty Adjust** | Manual/per-block | Every 2 weeks | +| **Merkle Trees** | No | Yes (for transactions) | +| **Block Rewards** | No | Yes (6.25 BTC as of 2023) | +| **Network** | Single server | Distributed P2P | +| **Consensus** | Not applicable | Longest chain rule | +| **Mining Hardware** | CPU (Ruby) | ASICs (specialized chips) | +| **Hash Rate** | ~1 MH/s (laptop) | ~400 EH/s (network) | +| **Security Model** | Educational | Production-grade | + +### Why Double SHA256 in Bitcoin? + +Bitcoin uses `SHA256(SHA256(data))`: + +**Reason:** +- Protection against length-extension attacks +- Additional security layer +- Historical decision (may be unnecessary) + +**ChainForge Decision:** +- Single SHA256 is sufficient for education +- Length-extension attacks not applicable here +- Simpler to understand + +## Code Deep Dive + +### Mining Implementation + +```ruby +# src/block.rb:60-69 +def mine_block + target = '0' * difficulty # Step 1: Generate target string + loop do # Step 2: Start infinite loop + calculate_hash # Step 3: Calculate hash + break if _hash.start_with?(target) # Step 4: Check if valid + + self.nonce += 1 # Step 5: Increment nonce + end # Step 6: Repeat until valid + _hash # Step 7: Return valid hash +end +``` + +**Optimization Opportunities (not implemented):** + +```ruby +# Potential optimization: parallel mining +def mine_block_parallel + threads = [] + result = Concurrent::AtomicReference.new(nil) + + 8.times do |i| + threads << Thread.new do + nonce = i + loop do + hash = calculate_hash_with_nonce(nonce) + if hash.start_with?(target) + result.set(nonce) + break + end + nonce += 8 + break if result.get + end + end + end + + threads.each(&:join) + self.nonce = result.get + calculate_hash +end +``` + +**Why not implemented?** +- Educational project (simplicity over performance) +- Ruby GIL limits true parallelism +- Single-threaded is easier to understand + +### Hash Validation + +```ruby +# src/block.rb:74-77 +def valid_hash? + target = '0' * difficulty # Generate target: "000" + _hash.start_with?(target) # Check prefix +end +``` + +**Verification Complexity:** +- O(N) where N = difficulty +- Typically N ≤ 10, so effectively O(1) +- Much faster than mining (O(16^N) attempts) + +### Chain Integrity Validation + +```ruby +# src/blockchain.rb:47-53 +def integrity_valid? + blocks.each_cons(2).all? do |previous_block, current_block| + # Check 1: Hash link + previous_block._hash == current_block.previous_hash && + + # Check 2: Hash integrity + current_block._hash == current_block.calculate_hash && + + # Check 3: PoW validation + current_block.valid_hash? + end +end +``` + +**Triple Validation:** +1. **Hash Link:** Ensures blocks are connected +2. **Hash Integrity:** Ensures data hasn't been modified +3. **PoW Validation:** Ensures mining was performed + +## Performance Tuning + +### Difficulty Selection Guide + +**For Development/Testing:** +- Use difficulty 1-2 +- Fast mining (~1 second) +- Easy to iterate + +**For Demonstrations:** +- Use difficulty 3-4 +- Visible mining delay (few seconds) +- Shows PoW concept clearly + +**For Security Experiments:** +- Use difficulty 5-6 +- Significant mining time (minutes) +- Demonstrates computational cost + +**Avoid:** +- Difficulty 7+ in development +- Can take hours to mine single block +- Risk of timeout/frustration + +### Monitoring Mining Performance + +```ruby +# Add timing to mine_block +def mine_block + start_time = Time.now + target = '0' * difficulty + attempts = 0 + + loop do + attempts += 1 + calculate_hash + break if _hash.start_with?(target) + + self.nonce += 1 + end + + elapsed = Time.now - start_time + puts "Mined in #{elapsed}s with #{attempts} attempts (#{(attempts/elapsed).to_i} H/s)" + + _hash +end +``` + +## Next Steps + +- [Data Models](data-models.md) - MongoDB schema and relationships +- [Security Design](security-design.md) - Comprehensive security analysis +- [API Reference](../api/reference.md) - Mining endpoints +- [Quick Start Tutorial](../getting-started/quick-start.md) - Try mining yourself + +--- + +**Want to experiment?** Try mining blocks with different difficulties and observe the time difference! diff --git a/docs/architecture/security-design.md b/docs/architecture/security-design.md new file mode 100644 index 0000000..240ef9b --- /dev/null +++ b/docs/architecture/security-design.md @@ -0,0 +1,804 @@ +# Security Design + +Comprehensive analysis of ChainForge's security architecture, threat model, and protection mechanisms. + +## Table of Contents + +1. [Security Overview](#security-overview) +2. [Threat Model](#threat-model) +3. [Security Layers](#security-layers) +4. [Rate Limiting](#rate-limiting) +5. [Input Validation](#input-validation) +6. [Cryptographic Security](#cryptographic-security) +7. [Known Vulnerabilities](#known-vulnerabilities) +8. [Security Best Practices](#security-best-practices) +9. [Production Security](#production-security) + +## Security Overview + +ChainForge implements **multiple layers of security** appropriate for an educational blockchain project: + +``` +┌────────────────────────────────────────┐ +│ Layer 5: Application Logic Security │ ← Chain integrity validation +├────────────────────────────────────────┤ +│ Layer 4: Cryptographic Security │ ← SHA256 hashing, PoW +├────────────────────────────────────────┤ +│ Layer 3: Input Validation │ ← dry-validation schemas +├────────────────────────────────────────┤ +│ Layer 2: Rate Limiting │ ← Rack::Attack throttling +├────────────────────────────────────────┤ +│ Layer 1: Transport Security │ ← HTTP (HTTPS in production) +└────────────────────────────────────────┘ +``` + +**Security Philosophy:** +- **Defense in Depth**: Multiple independent layers +- **Fail Securely**: Invalid requests rejected early +- **Educational Focus**: Balance security with simplicity +- **Not Production-Ready**: Missing auth, encryption, monitoring + +## Threat Model + +### Assets to Protect + +1. **Blockchain Data Integrity** + - Stored block data + - Hash linkages + - Proof of Work validations + +2. **System Availability** + - API responsiveness + - Database resources + - CPU/memory resources + +3. **Data Consistency** + - Chain integrity + - Block ordering + - Hash validity + +### Threat Actors + +#### 1. Malicious API User + +**Capabilities:** +- Send HTTP requests to public API +- Provide arbitrary input data +- Attempt to bypass validation + +**Goals:** +- Corrupt blockchain data +- Cause denial of service +- Inject malicious content + +**Mitigations:** +- Rate limiting (Rack::Attack) +- Input validation (dry-validation) +- PoW requirement for block creation + +#### 2. Internal Attacker (Database Access) + +**Capabilities:** +- Direct MongoDB access +- Modify documents directly +- Bypass application logic + +**Goals:** +- Alter historical blocks +- Invalidate chain integrity +- Corrupt genesis blocks + +**Mitigations:** +- Chain integrity validation +- Hash verification +- PoW validation +- **Not mitigated:** Database access controls (educational project) + +#### 3. Network Attacker + +**Capabilities:** +- Intercept traffic (if HTTP) +- Man-in-the-middle attacks +- Eavesdrop on communications + +**Goals:** +- Read sensitive data +- Modify requests/responses +- Impersonate users + +**Mitigations (in production):** +- HTTPS/TLS encryption +- Certificate validation +- **Not mitigated in development:** HTTP only + +### Out of Scope Threats + +ChainForge is educational and doesn't protect against: + +- ❌ **Distributed Attacks**: No P2P network +- ❌ **51% Attack**: Single server (not applicable) +- ❌ **Economic Attacks**: No financial incentives +- ❌ **Social Engineering**: No user accounts +- ❌ **Physical Security**: Development environment +- ❌ **Advanced Persistent Threats**: Not a high-value target + +## Security Layers + +### Layer 1: Transport Security + +**Current State:** HTTP (unencrypted) + +**Risks:** +- Eavesdropping on API requests/responses +- Man-in-the-middle attacks +- Data tampering in transit + +**Mitigation (Development):** +- Run on localhost only +- Use private networks +- Document HTTP-only limitation + +**Mitigation (Production):** +- Deploy with HTTPS/TLS +- Use valid SSL certificates +- Enforce HTTPS redirects +- Enable HSTS headers + +### Layer 2: Rate Limiting + +See [Rate Limiting](#rate-limiting) section below. + +### Layer 3: Input Validation + +See [Input Validation](#input-validation) section below. + +### Layer 4: Cryptographic Security + +See [Cryptographic Security](#cryptographic-security) section below. + +### Layer 5: Application Logic Security + +**Chain Integrity Validation:** + +Before adding blocks, ChainForge validates the entire chain: + +```ruby +# src/blockchain.rb:22-23 +def add_block(data, difficulty: 2) + integrity_valid? or raise 'Blockchain is not valid' + # ... rest of method +end +``` + +**Validation Checks:** + +```ruby +# src/blockchain.rb:47-53 +def integrity_valid? + blocks.each_cons(2).all? do |previous_block, current_block| + # Check 1: Hash linkage + previous_block._hash == current_block.previous_hash && + + # Check 2: Data integrity + current_block._hash == current_block.calculate_hash && + + # Check 3: Proof of Work + current_block.valid_hash? + end +end +``` + +**Security Properties:** +- Detects tampered blocks immediately +- Prevents adding blocks to corrupted chains +- Validates PoW requirements +- O(n) complexity (scales with chain length) + +## Rate Limiting + +### Implementation: Rack::Attack + +ChainForge uses **Rack::Attack** middleware for IP-based rate limiting. + +**Source:** `config/rack_attack.rb` + +### Configuration + +**Global Throttle:** +```ruby +throttle('api/ip', limit: 60, period: 1.minute) do |req| + req.ip if req.path.start_with?('/api/') +end +``` + +**Chain Creation Throttle:** +```ruby +throttle('api/chain/create', limit: 10, period: 1.minute) do |req| + req.ip if req.path == '/api/v1/chain' && req.post? +end +``` + +**Block Creation Throttle:** +```ruby +throttle('api/block/create', limit: 30, period: 1.minute) do |req| + req.ip if req.path =~ /^\/api\/v1\/chain\/[^\/]+\/block$/ && req.post? +end +``` + +### Rate Limits Table + +| Endpoint Pattern | Method | Limit | Window | Purpose | +|-----------------|--------|-------|--------|---------| +| `/api/*` | All | 60 req | 1 min | Global protection | +| `/api/v1/chain` | POST | 10 req | 1 min | Prevent blockchain spam | +| `/api/v1/chain/:id/block` | POST | 30 req | 1 min | Prevent mining abuse | +| Other endpoints | All | Global only | - | Read operations | + +### Custom Response + +When rate limit exceeded: + +```json +{ + "error": "Rate limit exceeded. Please try again later." +} +``` + +**HTTP Status:** 429 (Too Many Requests) + +**Implementation:** +```ruby +# config/rack_attack.rb +Rack::Attack.throttled_responder = lambda do |_request| + [429, { 'Content-Type' => 'application/json' }, [{ error: 'Rate limit exceeded. Please try again later.' }.to_json]] +end +``` + +### Security Analysis + +**Strengths:** +- ✅ Prevents brute-force attacks +- ✅ Mitigates DoS attempts +- ✅ Protects expensive operations (mining) +- ✅ Per-IP granularity + +**Weaknesses:** +- ❌ Memory-based (resets on restart) +- ❌ Single-server only (not distributed) +- ❌ Bypassable with IP rotation +- ❌ No persistent ban list +- ❌ Doesn't track across proxies + +**Production Improvements:** +- Use Redis for distributed rate limiting +- Implement permanent bans for abuse +- Add CAPTCHA for suspicious IPs +- Monitor and alert on violations +- Consider API keys for accountability + +### Disabled in Tests + +```ruby +# main.rb:16 +use Rack::Attack unless ENV['ENVIRONMENT'] == 'test' +``` + +**Reason:** Test suite needs to run without delays + +## Input Validation + +### Implementation: dry-validation + +ChainForge uses **dry-validation** for schema-based input validation. + +**Source:** `src/validators.rb` + +### BlockDataContract + +```ruby +class BlockDataContract < Dry::Validation::Contract + params do + required(:data).filled(:string) + optional(:difficulty).filled(:integer, gteq?: 1, lteq?: 10) + end +end +``` + +**Rules:** +1. `data`: **Required**, must be filled (non-empty) string +2. `difficulty`: **Optional**, if present must be integer 1-10 + +### Validation Flow + +``` +1. Client Request + POST /api/v1/chain/:id/block + Body: {"data": "test", "difficulty": 15} + ↓ +2. Parse JSON + parse_json_body + ↓ +3. Validate Schema + BlockDataContract.new.call(block_data) + ↓ +4. Check Result + validation.failure? # true (difficulty > 10) + ↓ +5. Return Errors + halt 400, {"errors": {"difficulty": ["must be between 1 and 10"]}} +``` + +### Error Response Format + +**Valid Request:** +```bash +curl -X POST http://localhost:1910/api/v1/chain/:id/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "test", "difficulty": 3}' +``` + +Response: 200 OK (proceeds to mining) + +**Invalid: Missing data:** +```bash +curl -X POST http://localhost:1910/api/v1/chain/:id/block \ + -H 'Content-Type: application/json' \ + -d '{"difficulty": 3}' +``` + +Response: 400 Bad Request +```json +{ + "errors": { + "data": ["must be filled"] + } +} +``` + +**Invalid: Out-of-range difficulty:** +```bash +curl -X POST http://localhost:1910/api/v1/chain/:id/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "test", "difficulty": 15}' +``` + +Response: 400 Bad Request +```json +{ + "errors": { + "difficulty": ["must be between 1 and 10"] + } +} +``` + +**Invalid: Wrong type:** +```bash +curl -X POST http://localhost:1910/api/v1/chain/:id/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "test", "difficulty": "high"}' +``` + +Response: 400 Bad Request +```json +{ + "errors": { + "difficulty": ["must be an integer"] + } +} +``` + +### Security Benefits + +**Prevents:** +- ✅ Type confusion attacks +- ✅ NoSQL injection +- ✅ Resource exhaustion (difficulty > 10) +- ✅ Empty data blocks +- ✅ Malformed requests + +**Provides:** +- ✅ Clear error messages +- ✅ Schema documentation +- ✅ Type safety +- ✅ Range enforcement + +## Cryptographic Security + +### SHA256 Hashing + +**Algorithm:** SHA-256 (Secure Hash Algorithm 256-bit) + +**Properties:** +- **Pre-image Resistance**: Can't find input from hash +- **Collision Resistance**: Virtually impossible to find two inputs with same hash +- **Avalanche Effect**: Small input change = completely different hash +- **Deterministic**: Same input always produces same output + +**Usage in ChainForge:** + +```ruby +# src/block.rb:45 +Digest::SHA256.hexdigest("#{index}#{created_at.to_i}#{data}#{previous_hash}#{nonce}") +``` + +**Security Analysis:** + +| Threat | SHA256 Mitigation | +|--------|------------------| +| Data tampering | Hash changes if data modified | +| Block forgery | Can't create valid hash without PoW | +| Chain corruption | Invalid hashes detected immediately | +| Collision attacks | Computationally infeasible (2^128 attempts) | +| Rainbow tables | Not applicable (unique inputs per block) | + +**Known Weaknesses:** +- Quantum computing threat (future, theoretical) +- SHA1 is broken, but SHA256 still secure +- Length-extension attacks (not applicable to ChainForge) + +### Proof of Work + +**Security Purpose:** Computational cost to modify blockchain + +**Difficulty Levels:** + +| Difficulty | Avg Attempts | CPU Time | Security Level | +|-----------|-------------|----------|---------------| +| 1-2 | 16-256 | < 1s | Low (educational) | +| 3-4 | 4K-65K | Seconds | Medium (demonstration) | +| 5-6 | 1M-16M | Minutes | High (testing) | +| 7-10 | 268M+ | Hours+ | Very High (impractical) | + +**Attack Scenario:** + +Attacker wants to change Block 5 in a 10-block chain (difficulty 3): + +``` +Required Work: +- Re-mine Block 5: ~4,096 attempts +- Re-mine Block 6: ~4,096 attempts +- Re-mine Block 7: ~4,096 attempts +- Re-mine Block 8: ~4,096 attempts +- Re-mine Block 9: ~4,096 attempts +- Re-mine Block 10: ~4,096 attempts + +Total: ~24,576 attempts (~10-15 seconds) +``` + +**Comparison with Bitcoin:** + +``` +Bitcoin (Difficulty ~19 leading zeros): +- Attack Block 5 in 10-block chain +- Each block: ~2^76 attempts (astronomical) +- Time: Years on supercomputers +- Cost: Millions of dollars in electricity +``` + +**Security Trade-off:** +- ChainForge: Educational (low difficulty acceptable) +- Production: Needs much higher difficulty + distributed consensus + +## Known Vulnerabilities + +ChainForge is an **educational project** and has known security limitations: + +### 1. No Authentication/Authorization + +**Vulnerability:** +- Any user can create blockchains +- Any user can add blocks to any chain +- No user accounts or permissions + +**Risk:** Medium (for educational project) + +**Mitigation:** +- Document as limitation +- Add authentication for production + +**Example Attack:** +```bash +# Attacker can spam blockchains +for i in {1..100}; do + curl -X POST http://localhost:1910/api/v1/chain +done +``` + +### 2. No Data Encryption at Rest + +**Vulnerability:** +- MongoDB stores data in plaintext +- No field-level encryption +- Database backups are unencrypted + +**Risk:** Low (for educational project with non-sensitive data) + +**Mitigation:** +- MongoDB Enterprise supports encryption at rest +- Application-level encryption for sensitive fields +- Encrypt database backups + +### 3. HTTP Only (No HTTPS) + +**Vulnerability:** +- Traffic unencrypted in transit +- Man-in-the-middle attacks possible +- Credentials exposed (if auth added) + +**Risk:** High (if deployed publicly) + +**Mitigation:** +- Run on localhost only (development) +- Deploy with HTTPS (production) +- Use reverse proxy (nginx, Cloudflare) + +### 4. In-Memory Rate Limiting + +**Vulnerability:** +- Rate limits reset on restart +- No persistence of violations +- Single-server only + +**Risk:** Medium (DoS protection incomplete) + +**Mitigation:** +- Use Redis for distributed rate limiting +- Persist violation logs +- Implement IP banning + +### 5. No Input Sanitization for Output + +**Vulnerability:** +- Stored data returned as-is in JSON +- Potential for stored XSS (if data rendered in HTML) +- No content security policy + +**Risk:** Low (JSON API, not HTML) + +**Mitigation:** +- Sanitize/encode when rendering in HTML +- Implement Content Security Policy headers +- Validate on output, not just input + +### 6. Difficulty Limit (Max 10) + +**Vulnerability:** +- Low difficulty easy to brute-force +- Re-mining entire chain feasible + +**Risk:** Low (educational project) + +**Mitigation:** +- Increase max difficulty for production +- Implement dynamic difficulty adjustment +- Add distributed consensus + +### 7. No Database Access Controls + +**Vulnerability:** +- MongoDB typically runs without authentication (dev) +- Direct database access bypasses application logic +- No audit logging + +**Risk:** High (if MongoDB exposed) + +**Mitigation:** +- Enable MongoDB authentication +- Restrict network access (firewall) +- Implement audit logging +- Use connection string with credentials + +### 8. No Request Signing + +**Vulnerability:** +- Requests not cryptographically signed +- Cannot verify request origin +- Replay attacks possible + +**Risk:** Medium (if authentication added) + +**Mitigation:** +- Implement HMAC request signing +- Use nonces to prevent replay +- Add timestamps to requests + +## Security Best Practices + +### For Development + +1. **Run Locally:** + ```bash + # Bind to localhost only + ruby main.rb -p 1910 -o 127.0.0.1 + ``` + +2. **Use Firewall:** + ```bash + # Block external access to port 1910 + sudo ufw deny 1910/tcp + sudo ufw allow from 127.0.0.1 to any port 1910 + ``` + +3. **Secure MongoDB:** + ```bash + # Enable auth in mongod.conf + security: + authorization: enabled + ``` + +4. **Monitor Logs:** + ```bash + # Watch for suspicious activity + tail -f logs/development.log | grep -i "error\|attack" + ``` + +### For Testing + +1. **Use Separate Database:** + ```bash + # .env.test + MONGO_DB_NAME=chain_forge_test + ENVIRONMENT=test + ``` + +2. **Clean Up After Tests:** + ```ruby + # spec/spec_helper.rb + config.after(:each) do + Mongoid.purge! + end + ``` + +3. **Test Security Features:** + ```ruby + # Test rate limiting + it 'enforces rate limits' do + 11.times { post '/api/v1/chain' } + expect(last_response.status).to eq(429) + end + + # Test input validation + it 'rejects invalid difficulty' do + post '/api/v1/chain/:id/block', {data: "test", difficulty: 15}.to_json + expect(last_response.status).to eq(400) + end + ``` + +## Production Security + +For production deployment, implement these additional security measures: + +### 1. HTTPS/TLS + +```ruby +# Use production server with SSL +# config.ru +require 'rack/ssl' +use Rack::SSL +``` + +### 2. Authentication + +```ruby +# Add JWT authentication +require 'jwt' + +before do + authenticate_user! unless public_endpoint? +end + +def authenticate_user! + token = request.env['HTTP_AUTHORIZATION']&.split(' ')&.last + payload = JWT.decode(token, ENV['JWT_SECRET'], true, algorithm: 'HS256') + @current_user = User.find(payload['user_id']) +rescue JWT::DecodeError + halt 401, {error: 'Unauthorized'}.to_json +end +``` + +### 3. Database Security + +```yaml +# config/mongoid.yml production +production: + clients: + default: + uri: <%= ENV['MONGODB_URI'] %> # mongodb://user:pass@host:27017/db + options: + ssl: true + ssl_verify: true +``` + +### 4. Rate Limiting with Redis + +```ruby +# config/rack_attack.rb +Rack::Attack.cache.store = Rack::Attack::StoreProxy::RedisStoreProxy.new( + Redis.new(url: ENV['REDIS_URL']) +) +``` + +### 5. Security Headers + +```ruby +# Add security headers +use Rack::Protection +use Rack::Protection::JsonCsrf + +before do + headers['X-Frame-Options'] = 'DENY' + headers['X-Content-Type-Options'] = 'nosniff' + headers['X-XSS-Protection'] = '1; mode=block' + headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' +end +``` + +### 6. Logging & Monitoring + +```ruby +# Add request logging +require 'logger' +use Rack::CommonLogger, Logger.new('log/production.log') + +# Log security events +logger.warn "Rate limit exceeded for IP: #{request.ip}" +logger.error "Invalid authentication attempt from: #{request.ip}" +``` + +### 7. Input Sanitization + +```ruby +# Sanitize data before storage +require 'sanitize' + +def sanitize_input(data) + Sanitize.clean(data, Sanitize::Config::RESTRICTED) +end +``` + +### 8. Environment Variables + +```bash +# Use secrets management +# .env.production (not in version control) +JWT_SECRET= +MONGODB_URI=mongodb://user:pass@host:27017/db?authSource=admin +DEFAULT_DIFFICULTY=5 +RACK_ENV=production +``` + +## Security Checklist + +### Development +- [ ] Run on localhost only +- [ ] Use separate test database +- [ ] Enable MongoDB authentication +- [ ] Review code for SQL/NoSQL injection +- [ ] Test rate limiting +- [ ] Test input validation +- [ ] Document security limitations + +### Production +- [ ] Deploy with HTTPS/TLS +- [ ] Implement authentication/authorization +- [ ] Use environment variables for secrets +- [ ] Enable database encryption at rest +- [ ] Configure firewall rules +- [ ] Set up monitoring/alerting +- [ ] Implement proper logging +- [ ] Add security headers +- [ ] Use Redis for rate limiting +- [ ] Regular security audits +- [ ] Vulnerability scanning +- [ ] Backup and recovery procedures + +## Next Steps + +- [Architecture Overview](overview.md) - System design +- [Proof of Work](proof-of-work.md) - Mining security +- [API Reference](../api/reference.md) - Endpoint security +- [Deployment Guide](../guides/deployment-guide.md) - Production deployment + +--- + +**Found a security issue?** Please report it responsibly via the [SECURITY](../SECURITY.md) policy. diff --git a/docs/getting-started/first-blockchain-tutorial.md b/docs/getting-started/first-blockchain-tutorial.md new file mode 100644 index 0000000..e91e192 --- /dev/null +++ b/docs/getting-started/first-blockchain-tutorial.md @@ -0,0 +1,620 @@ +# First Blockchain Tutorial + +A comprehensive, step-by-step tutorial that explains blockchain concepts as you build your first blockchain with ChainForge. + +## Tutorial Overview + +In this tutorial, you'll: +1. Understand what blockchains are and how they work +2. Create a blockchain instance +3. Mine blocks with Proof of Work +4. Explore chain integrity and immutability +5. Experiment with different mining difficulties +6. Learn how tampering is detected + +**Time Required:** 15-20 minutes + +**Prerequisites:** +- ChainForge installed ([Installation Guide](installation.md)) +- Basic understanding of HTTP requests +- `curl` or API client (Postman, Insomnia) + +## Part 1: Understanding Blockchains + +### What is a Blockchain? + +A blockchain is a **distributed ledger** - a digital record of transactions organized into blocks and linked together in a chain. Think of it as a digital notebook where: + +- Each page is a "block" +- Pages are numbered sequentially (index) +- Each page references the previous page's fingerprint (hash) +- You can only add pages, never remove or modify them +- Everyone can verify the pages haven't been tampered with + +### Key Concepts + +**Block**: A container for data with metadata +- `index`: Position in the chain (0, 1, 2, ...) +- `data`: Information stored in the block +- `timestamp`: When the block was created +- `hash`: Cryptographic fingerprint of the block +- `previous_hash`: Link to the previous block's hash +- `nonce`: Number used to meet Proof of Work requirement +- `difficulty`: Mining difficulty (how many leading zeros) + +**Hash**: A cryptographic fingerprint (SHA256) +- Fixed length (64 characters) +- Unique for each input +- Changing even 1 character completely changes the hash +- Impossible to reverse (can't get data from hash) + +**Proof of Work (PoW)**: Mining algorithm that requires computational work +- Must find a nonce that produces a valid hash +- Valid hash starts with required number of zeros +- Higher difficulty = more zeros = more attempts = more secure + +**Chain Integrity**: Validation that ensures data hasn't been tampered with +- Each block links to the previous block +- Changing any data invalidates the hash +- Invalid hash breaks the chain + +## Part 2: Create Your Blockchain + +### Step 1: Start ChainForge + +```bash +# Using Docker +docker-compose up + +# Or local installation +ruby main.rb -p 1910 +``` + +Verify it's running: +```bash +curl http://localhost:1910 +# Output: Hello to ChainForge! +``` + +### Step 2: Create a Blockchain Instance + +```bash +curl -X POST http://localhost:1910/api/v1/chain +``` + +**Response:** +```json +{ + "id": "674c8a1b2e4f5a0012345678" +} +``` + +**What just happened?** + +1. ChainForge created a new MongoDB collection for this blockchain +2. Generated a **genesis block** (the first block): + ```json + { + "index": 0, + "data": "Genesis Block", + "hash": "calculated_hash_here", + "previous_hash": null, + "nonce": 0, + "difficulty": 0, + "timestamp": 1699564821 + } + ``` +3. Genesis blocks are special: + - Always index 0 + - No previous_hash (they're first) + - NOT mined (no Proof of Work requirement) + - Auto-created when blockchain is instantiated + +Save the blockchain ID for subsequent requests! + +## Part 3: Mining Your First Block + +### Step 3: Add a Block with Low Difficulty + +Let's start with difficulty 1 (easy mining): + +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "My first transaction", "difficulty": 1}' +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8b2c3e5f6a0012345679", + "block_hash": "0a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678", + "nonce": 7, + "difficulty": 1 +} +``` + +**Mining process breakdown:** + +The system performed these steps: + +1. **Validation**: Checked that `data` is provided and `difficulty` is 1-10 ✓ +2. **Block Creation**: Created a new block with: + - index: 1 (next available) + - data: "My first transaction" + - previous_hash: (genesis block's hash) + - difficulty: 1 + - nonce: 0 (starting point) + +3. **Mining Algorithm** (Proof of Work): + ``` + Target: Hash must start with "0" (one leading zero) + + Attempt 1: nonce=0 + hash = SHA256("1" + timestamp + "My first transaction" + previous_hash + "0") + hash = "a1b2c3d4..." ✗ (doesn't start with "0") + + Attempt 2: nonce=1 + hash = SHA256("1" + timestamp + "My first transaction" + previous_hash + "1") + hash = "9f8e7d6c..." ✗ (doesn't start with "0") + + ... + + Attempt 7: nonce=7 + hash = SHA256("1" + timestamp + "My first transaction" + previous_hash + "7") + hash = "0a1b2c3d..." ✓ (starts with "0"!) + ``` + +4. **Block Saved**: Block stored in MongoDB with nonce=7 + +**Why different nonce values each time?** +The timestamp changes every second, so even the same data produces different hashes! + +### Step 4: View the Complete Block + +```bash +curl http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block/674c8b2c3e5f6a0012345679 +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block": { + "id": "674c8b2c3e5f6a0012345679", + "index": 1, + "data": "My first transaction", + "hash": "0a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678", + "previous_hash": "genesis_block_hash_here", + "nonce": 7, + "difficulty": 1, + "timestamp": 1699564821, + "valid_hash": true + } +} +``` + +**Field explanations:** + +- `index: 1` - Second block in the chain (0 = genesis) +- `hash: 0a1b2...` - Starts with "0" (meets difficulty 1) +- `previous_hash: genesis_hash` - Links to genesis block +- `nonce: 7` - Took 7 attempts to find valid hash +- `valid_hash: true` - Hash meets difficulty requirement + +## Part 4: Exploring Mining Difficulty + +### Step 5: Mine with Difficulty 2 + +Now let's try difficulty 2 (requires "00"): + +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Second block with higher difficulty", "difficulty": 2}' +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8c3d4e6f7a001234567a", + "block_hash": "00abc123def456789abcdef0123456789abcdef0123456789abcdef012345678", + "nonce": 142, + "difficulty": 2 +} +``` + +**Notice:** +- Hash starts with "00" (2 leading zeros) +- Nonce: 142 (many more attempts than difficulty 1) +- Mining took longer (~1-2 seconds) + +**Why exponentially harder?** + +| Difficulty | Target Pattern | Probability per Attempt | Avg Attempts | +|-----------|---------------|------------------------|--------------| +| 1 | 0* | 1/16 (6.25%) | ~16 | +| 2 | 00* | 1/256 (0.39%) | ~256 | +| 3 | 000* | 1/4096 (0.024%) | ~4096 | +| 4 | 0000* | 1/65536 (0.0015%) | ~65536 | + +Each additional zero multiplies attempts by 16! + +### Step 6: Mine with Difficulty 3 + +Let's push it further: + +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Third block - even harder", "difficulty": 3}' +``` + +**This will take several seconds!** Watch the process: + +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8d4e5f7g8a001234567b", + "block_hash": "000def123abc456789abcdef0123456789abcdef0123456789abcdef012345678", + "nonce": 3847, + "difficulty": 3 +} +``` + +**Observations:** +- Hash: "000..." (3 leading zeros) +- Nonce: 3847 (thousands of attempts!) +- Time: Several seconds of CPU work + +**Bitcoin comparison:** +- Bitcoin uses ~19 leading zeros (as of 2023) +- Requires specialized hardware (ASICs) +- Difficulty adjusts every 2 weeks to maintain ~10 min block time +- ChainForge difficulty is fixed (educational simplification) + +## Part 5: Chain Integrity and Validation + +### Step 7: Understanding Chain Structure + +Your blockchain now looks like this: + +``` +Blockchain: 674c8a1b2e4f5a0012345678 + +Block 0 (Genesis) +├─ index: 0 +├─ hash: abc123... +├─ previous_hash: null +└─ nonce: 0 (not mined) + │ + ↓ (previous_hash link) + │ +Block 1 +├─ index: 1 +├─ hash: 0a1b2c... (difficulty 1) +├─ previous_hash: abc123... ← links to Block 0 +└─ nonce: 7 + │ + ↓ (previous_hash link) + │ +Block 2 +├─ index: 2 +├─ hash: 00abc1... (difficulty 2) +├─ previous_hash: 0a1b2c... ← links to Block 1 +└─ nonce: 142 + │ + ↓ (previous_hash link) + │ +Block 3 +├─ index: 3 +├─ hash: 000def... (difficulty 3) +├─ previous_hash: 00abc1... ← links to Block 2 +└─ nonce: 3847 +``` + +### Step 8: Validate Block Data (Correct) + +Verify Block 1's data is unchanged: + +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block/674c8b2c3e5f6a0012345679/valid \ + -H 'Content-Type: application/json' \ + -d '{"data": "My first transaction"}' +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8b2c3e5f6a0012345679", + "valid": true +} +``` + +**Validation process:** +1. Retrieve block from database +2. Recalculate hash using provided data: `SHA256(1 + timestamp + "My first transaction" + previous_hash + 7)` +3. Compare calculated hash with stored hash +4. If match: `valid: true` ✓ + +### Step 9: Validate Block Data (Tampered) + +Now try with wrong data to see tampering detection: + +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block/674c8b2c3e5f6a0012345679/valid \ + -H 'Content-Type: application/json' \ + -d '{"data": "Tampered data!"}' +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8b2c3e5f6a0012345679", + "valid": false +} +``` + +**Why invalid?** +1. Recalculates hash: `SHA256(1 + timestamp + "Tampered data!" + previous_hash + 7)` +2. Calculated hash: `9xyz...` (completely different!) +3. Stored hash: `0a1b2c...` +4. Hashes don't match → `valid: false` ✗ + +**Key insight:** Changing even one character produces a completely different hash! + +## Part 6: Immutability in Action + +### Understanding Why Blockchains Are Immutable + +**Scenario:** An attacker wants to change Block 1's data from "My first transaction" to "Fraudulent transaction" + +**What would need to happen:** + +1. **Change Block 1 data:** + ``` + Old: "My first transaction" → hash: 0a1b2c... + New: "Fraudulent transaction" → hash: 9xyz... ✗ + ``` + Problem: Hash no longer starts with "0" (doesn't meet difficulty 1) + +2. **Re-mine Block 1:** + Find new nonce to get valid hash starting with "0" + ``` + After re-mining: hash: 0def456... + ``` + +3. **Block 2 is now invalid:** + ``` + Block 2: + ├─ previous_hash: 0a1b2c... (old Block 1 hash) + └─ Stored hash: 00abc1... + + Problem: previous_hash doesn't match Block 1's new hash (0def456) + ``` + +4. **Re-mine Block 2:** + Update previous_hash and re-mine + ``` + After re-mining: hash: 00ghi789... + ``` + +5. **Block 3 is now invalid:** + Same problem - previous_hash doesn't match + +6. **Re-mine ALL subsequent blocks:** + Must re-mine Block 3, Block 4, Block 5, ... (entire chain!) + +**Computational Security:** +- Each block took time to mine (PoW) +- Attacker must re-mine faster than network adds new blocks +- In real blockchains, this requires >51% of network computing power +- Practically impossible for established blockchains like Bitcoin + +### ChainForge's Integrity Validation + +ChainForge validates chain integrity before adding new blocks: + +```ruby +# Pseudocode from src/blockchain.rb +def valid? + blocks.each_with_index do |block, index| + # Check 1: Hash matches calculated hash + return false unless block.hash == block.calculate_hash + + # Check 2: Hash meets difficulty requirement + return false unless block.valid_hash? + + # Check 3: Links to previous block (skip genesis) + if index > 0 + return false unless block.previous_hash == blocks[index - 1].hash + end + end + + true +end +``` + +If validation fails, ChainForge rejects new blocks until integrity is restored. + +## Part 7: Advanced Experiments + +### Experiment 1: Default Difficulty + +Omit the `difficulty` parameter to use default (from `DEFAULT_DIFFICULTY` env var): + +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Using default difficulty"}' +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8e5f6g8h9a001234567c", + "block_hash": "00abc...", + "nonce": 234, + "difficulty": 2 +} +``` + +Default difficulty is 2 (set in `.env` file). + +### Experiment 2: Multiple Independent Blockchains + +Create multiple blockchains to see they're independent: + +```bash +# Create first blockchain +curl -X POST http://localhost:1910/api/v1/chain +# Response: {"id": "blockchain_1_id"} + +# Create second blockchain +curl -X POST http://localhost:1910/api/v1/chain +# Response: {"id": "blockchain_2_id"} + +# Add block to first blockchain +curl -X POST http://localhost:1910/api/v1/chain/blockchain_1_id/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Blockchain 1 data"}' + +# Add block to second blockchain +curl -X POST http://localhost:1910/api/v1/chain/blockchain_2_id/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Blockchain 2 data"}' +``` + +Each blockchain has its own genesis block and independent chain! + +### Experiment 3: Rate Limiting + +Try exceeding rate limits to see protection in action: + +```bash +# Create many blockchains rapidly (limit: 10/minute) +for i in {1..15}; do + curl -X POST http://localhost:1910/api/v1/chain +done +``` + +**Response (after 10 requests):** +```json +{ + "error": "Rate limit exceeded. Please try again later." +} +``` +**HTTP Status:** 429 (Too Many Requests) + +### Experiment 4: Input Validation + +Try invalid inputs: + +**Missing data:** +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ + -H 'Content-Type: application/json' \ + -d '{"difficulty": 2}' +``` + +**Response:** +```json +{ + "errors": { + "data": ["must be filled"] + } +} +``` +**HTTP Status:** 400 (Bad Request) + +**Invalid difficulty:** +```bash +curl -X POST http://localhost:1910/api/v1/chain/674c8a1b2e4f5a0012345678/block \ + -H 'Content-Type: application/json' \ + -d '{"data": "test", "difficulty": 15}' +``` + +**Response:** +```json +{ + "errors": { + "difficulty": ["must be between 1 and 10"] + } +} +``` + +## Part 8: Recap and Next Steps + +### What You've Learned + +✅ **Blockchain Basics:** +- Blocks contain data and link via hashes +- Genesis blocks start each chain +- Chains are append-only (immutable) + +✅ **Proof of Work:** +- Mining finds nonce producing valid hash +- Difficulty determines leading zeros required +- Higher difficulty = exponentially more work + +✅ **Chain Integrity:** +- Hash chaining links blocks together +- Tampering invalidates hashes +- Breaking chain requires re-mining all subsequent blocks + +✅ **Security Features:** +- Rate limiting prevents abuse +- Input validation prevents malformed data +- PoW makes tampering computationally expensive + +### Key Takeaways + +1. **Immutability through computation**: PoW makes changing history expensive +2. **Hash chaining**: Each block depends on all previous blocks +3. **Verifiability**: Anyone can validate block data +4. **Decentralization** (in real blockchains): No single point of control + +### Next Steps + +**Explore Further:** +1. [API Reference](../api/reference.md) - Complete endpoint documentation +2. [Proof of Work Deep Dive](../architecture/proof-of-work.md) - Mining algorithm details +3. [Architecture Overview](../architecture/overview.md) - System design +4. [API Examples](../api/examples.md) - Code examples in multiple languages + +**Build Something:** +- Create a simple blockchain explorer web app +- Implement a transaction system on top of ChainForge +- Write scripts to automate blockchain creation +- Build a visualization tool for the blockchain + +**Learn More:** +- Study Bitcoin's whitepaper (Satoshi Nakamoto, 2008) +- Research Ethereum's smart contracts +- Explore consensus mechanisms (PoW, PoS, etc.) +- Learn about Merkle trees and their role in blockchains + +## Troubleshooting + +**Q: Mining is taking forever!** +A: Lower the difficulty (1-3 for development). Difficulty 5+ can take minutes. + +**Q: I get "Blockchain not found" errors** +A: Double-check your blockchain ID. Each blockchain has a unique ID. + +**Q: Rate limit errors** +A: Wait 1 minute for the rate limit window to reset. + +**Q: MongoDB connection errors** +A: Ensure MongoDB is running: `mongosh --eval "db.version()"` + +For more help, see [Troubleshooting Guide](../guides/troubleshooting.md). + +--- + +**Congratulations!** You've completed the comprehensive blockchain tutorial. You now understand how blockchains work and how ChainForge implements these concepts! diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..708b1f8 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,388 @@ +# Installation Guide + +This guide will walk you through installing ChainForge and all its dependencies on your local machine. + +## Prerequisites + +Before installing ChainForge, ensure you have the following installed: + +### Required Software + +- **Ruby 3.2.2** - Programming language +- **MongoDB** - Database for storing blockchain data +- **Git** - Version control (for cloning the repository) +- **Bundler** - Ruby dependency manager + +### Optional Software + +- **Docker** - For containerized deployment (recommended for beginners) +- **Docker Compose** - For multi-container orchestration +- **rbenv** or **rvm** - Ruby version managers (recommended) + +## Installation Methods + +Choose one of the following installation methods: + +### Method 1: Docker Installation (Recommended for Beginners) + +Docker provides the easiest installation path with minimal configuration. + +#### Step 1: Install Docker + +**macOS:** +```bash +# Install Docker Desktop +brew install --cask docker + +# Start Docker Desktop application +open -a Docker +``` + +**Linux (Ubuntu/Debian):** +```bash +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# Install Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +``` + +**Windows:** +Download and install [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop) + +#### Step 2: Clone the Repository + +```bash +git clone https://github.com/Perafan18/chain_forge.git +cd chain_forge +``` + +#### Step 3: Start the Application + +```bash +# Start both ChainForge and MongoDB +docker-compose up + +# Or run in detached mode (background) +docker-compose up -d +``` + +The application will be available at `http://localhost:1910`. + +#### Verify Installation + +```bash +# Test the application +curl http://localhost:1910 + +# Should return: "Hello to ChainForge!" +``` + +**That's it!** Skip to the [Quick Start Tutorial](quick-start.md) to create your first blockchain. + +--- + +### Method 2: Local Installation (For Developers) + +For developers who want to modify the code or run tests locally. + +#### Step 1: Install Ruby 3.2.2 + +**Using rbenv (Recommended):** + +```bash +# Install rbenv +brew install rbenv ruby-build # macOS +# or +sudo apt install rbenv # Linux + +# Install Ruby 3.2.2 +rbenv install 3.2.2 + +# Set Ruby version for this project +cd chain_forge +rbenv local 3.2.2 + +# Verify installation +ruby -v +# Should output: ruby 3.2.2 +``` + +**Using rvm:** + +```bash +# Install rvm +curl -sSL https://get.rvm.io | bash -s stable + +# Install Ruby 3.2.2 +rvm install 3.2.2 + +# Use Ruby 3.2.2 +rvm use 3.2.2 + +# Verify installation +ruby -v +``` + +#### Step 2: Install MongoDB + +**macOS:** + +```bash +# Install MongoDB +brew tap mongodb/brew +brew install mongodb-community + +# Start MongoDB service +brew services start mongodb-community + +# Verify MongoDB is running +mongosh --eval "db.version()" +``` + +**Linux (Ubuntu/Debian):** + +```bash +# Import MongoDB public key +wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add - + +# Add MongoDB repository +echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu $(lsb_release -cs)/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + +# Install MongoDB +sudo apt update +sudo apt install -y mongodb-org + +# Start MongoDB service +sudo systemctl start mongod +sudo systemctl enable mongod + +# Verify MongoDB is running +mongosh --eval "db.version()" +``` + +**Windows:** + +Download and install [MongoDB Community Server](https://www.mongodb.com/try/download/community) + +#### Step 3: Clone the Repository + +```bash +git clone https://github.com/Perafan18/chain_forge.git +cd chain_forge +``` + +#### Step 4: Install Ruby Dependencies + +```bash +# Install Bundler if not already installed +gem install bundler + +# Install project dependencies +bundle install +``` + +This will install: +- Sinatra (web framework) +- Mongoid (MongoDB ODM) +- RSpec (testing framework) +- RuboCop (code linter) +- Rack::Attack (rate limiting) +- dry-validation (input validation) +- And other dependencies + +#### Step 5: Configure Environment Variables + +```bash +# Copy example environment file +cp .env.example .env +``` + +Edit `.env` with your preferred text editor: + +```bash +# .env file +MONGO_DB_NAME=chain_forge +MONGO_DB_HOST=localhost +MONGO_DB_PORT=27017 +ENVIRONMENT=development +DEFAULT_DIFFICULTY=2 +``` + +**Configuration Options:** + +| Variable | Description | Default | Valid Values | +|----------|-------------|---------|--------------| +| `MONGO_DB_NAME` | Database name | chain_forge | Any string | +| `MONGO_DB_HOST` | MongoDB hostname | localhost | localhost, IP, hostname | +| `MONGO_DB_PORT` | MongoDB port | 27017 | 1-65535 | +| `ENVIRONMENT` | Runtime environment | development | development, test, production | +| `DEFAULT_DIFFICULTY` | Mining difficulty | 2 | 1-10 | + +#### Step 6: Verify Installation + +```bash +# Check Ruby version +ruby -v +# Should output: ruby 3.2.2 + +# Check MongoDB connection +mongosh --eval "db.version()" +# Should output MongoDB version + +# Check bundle dependencies +bundle check +# Should output: "The Gemfile's dependencies are satisfied" +``` + +#### Step 7: Run the Application + +```bash +# Start the server +ruby main.rb -p 1910 +``` + +You should see output like: +``` +== Sinatra (v4.0.0) has taken the stage on 1910 for development with backup from Puma +Puma starting in single mode... +* Listening on http://0.0.0.0:1910 +``` + +#### Step 8: Test the Installation + +Open a new terminal and run: + +```bash +# Test basic connectivity +curl http://localhost:1910 + +# Should return: "Hello to ChainForge!" + +# Create a test blockchain +curl -X POST http://localhost:1910/api/v1/chain + +# Should return: {"id":""} +``` + +--- + +## Troubleshooting + +### Ruby Version Issues + +**Error:** `Ruby version is not 3.2.2` + +**Solution:** +```bash +# Using rbenv +rbenv install 3.2.2 +rbenv local 3.2.2 + +# Using rvm +rvm install 3.2.2 +rvm use 3.2.2 +``` + +### MongoDB Connection Issues + +**Error:** `Failed to connect to MongoDB` + +**Solution:** +```bash +# Check if MongoDB is running +mongosh --eval "db.version()" + +# If not running, start it: +# macOS +brew services start mongodb-community + +# Linux +sudo systemctl start mongod + +# Docker +docker-compose up mongodb +``` + +**Error:** `Connection refused on port 27017` + +**Solution:** +- Verify MongoDB is running on the correct port +- Check your `.env` file has the correct `MONGO_DB_HOST` and `MONGO_DB_PORT` +- For Docker: Use `MONGO_DB_HOST=mongodb` instead of `localhost` + +### Bundle Install Issues + +**Error:** `An error occurred while installing ` + +**Solution:** +```bash +# Update RubyGems +gem update --system + +# Update Bundler +gem install bundler + +# Try again +bundle install +``` + +### Port Already in Use + +**Error:** `Address already in use - bind(2) for "0.0.0.0" port 1910` + +**Solution:** +```bash +# Find process using port 1910 +lsof -ti:1910 + +# Kill the process +kill -9 $(lsof -ti:1910) + +# Or use a different port +ruby main.rb -p 3000 +``` + +### Permission Denied Errors + +**Error:** `Permission denied` + +**Solution:** +```bash +# Linux/macOS: Don't use sudo with gem/bundle +# Instead, configure bundler to install gems in user directory + +bundle config set --local path 'vendor/bundle' +bundle install +``` + +## Verification Checklist + +Before proceeding to the Quick Start tutorial, verify: + +- [ ] Ruby 3.2.2 is installed: `ruby -v` +- [ ] MongoDB is running: `mongosh --eval "db.version()"` +- [ ] Dependencies are installed: `bundle check` +- [ ] Environment is configured: `.env` file exists +- [ ] Application starts: `ruby main.rb -p 1910` +- [ ] Basic connectivity works: `curl http://localhost:1910` + +## Next Steps + +Now that ChainForge is installed, proceed to: + +1. [Quick Start Tutorial](quick-start.md) - Create your first blockchain in 5 minutes +2. [First Blockchain Tutorial](first-blockchain-tutorial.md) - Complete walkthrough with mining +3. [API Reference](../api/reference.md) - Learn the available endpoints + +## Additional Resources + +- [Development Setup Guide](../guides/development-setup.md) - For contributors and developers +- [Deployment Guide](../guides/deployment-guide.md) - For production deployment +- [Troubleshooting Guide](../guides/troubleshooting.md) - Common issues and solutions + +--- + +**Need help?** Check the [Troubleshooting Guide](../guides/troubleshooting.md) or open an issue on GitHub. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md new file mode 100644 index 0000000..d35d104 --- /dev/null +++ b/docs/getting-started/quick-start.md @@ -0,0 +1,332 @@ +# Quick Start Tutorial + +Get your first blockchain running in 5 minutes! This tutorial assumes you've completed the [Installation Guide](installation.md). + +## Prerequisites + +- ChainForge installed and running +- `curl` command-line tool (or Postman/Insomnia) +- MongoDB running + +## Step 1: Start the Server + +```bash +# If using Docker +docker-compose up + +# If using local installation +ruby main.rb -p 1910 +``` + +Verify the server is running: +```bash +curl http://localhost:1910 +``` + +Expected output: +``` +Hello to ChainForge! +``` + +## Step 2: Create Your First Blockchain + +```bash +curl -X POST http://localhost:1910/api/v1/chain +``` + +**Response:** +```json +{ + "id": "674c8a1b2e4f5a0012345678" +} +``` + +Save this `id` - you'll need it for the next steps! + +**What happened?** +- ChainForge created a new blockchain instance in MongoDB +- Automatically generated a genesis block (index 0) +- Genesis block is NOT mined (no Proof of Work required) +- The blockchain is now ready to accept new blocks + +## Step 3: Add Your First Block (Mining!) + +Replace `` with the ID from Step 2: + +```bash +curl -X POST http://localhost:1910/api/v1/chain//block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Hello, Blockchain!", "difficulty": 2}' +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8b2c3e5f6a0012345679", + "block_hash": "00a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678", + "nonce": 142, + "difficulty": 2 +} +``` + +**What happened?** +1. ChainForge validated your input (data and difficulty) +2. Created a new block with index 1 +3. Linked it to the genesis block (previous_hash) +4. Started mining (Proof of Work algorithm) +5. Tried different nonce values (0, 1, 2, ..., 142) +6. Found nonce 142 produces a hash starting with "00" (2 leading zeros) +7. Saved the block to MongoDB + +**Mining explained:** +- Difficulty 2 means hash must start with "00" +- System increments nonce until hash meets requirement +- Higher difficulty = more leading zeros = more attempts = more time + +## Step 4: View Your Block + +```bash +curl http://localhost:1910/api/v1/chain//block/ +``` + +**Response:** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block": { + "id": "674c8b2c3e5f6a0012345679", + "index": 1, + "data": "Hello, Blockchain!", + "hash": "00a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef012345678", + "previous_hash": "genesis_block_hash_here", + "nonce": 142, + "difficulty": 2, + "timestamp": 1699564821, + "valid_hash": true + } +} +``` + +**Block fields explained:** +- `index`: Block position in chain (0 = genesis, 1 = first block, etc.) +- `data`: Your stored data +- `hash`: Block's SHA256 hash (starts with "00" for difficulty 2) +- `previous_hash`: Links to previous block's hash +- `nonce`: Number that produces valid hash (142 attempts) +- `difficulty`: Mining difficulty used (2 = "00") +- `timestamp`: Unix timestamp when block was created +- `valid_hash`: Whether hash meets difficulty requirement + +## Step 5: Add More Blocks + +Add a second block: + +```bash +curl -X POST http://localhost:1910/api/v1/chain//block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Second block data", "difficulty": 3}' +``` + +This time with difficulty 3 (hash must start with "000"). Mining will take longer! + +**Expected mining times:** +| Difficulty | Average Time | Leading Zeros | +|-----------|-------------|---------------| +| 1-2 | < 1 second | 0 or 00 | +| 3-4 | Few seconds | 000 or 0000 | +| 5-6 | Minutes | 00000 or 000000 | +| 7+ | Hours+ | 0000000+ | + +## Step 6: Validate Block Data + +Verify that your block data hasn't been tampered with: + +```bash +curl -X POST http://localhost:1910/api/v1/chain//block//valid \ + -H 'Content-Type: application/json' \ + -d '{"data": "Hello, Blockchain!"}' +``` + +**Response (Valid):** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8b2c3e5f6a0012345679", + "valid": true +} +``` + +**Response (Invalid - tampered data):** +```json +{ + "chain_id": "674c8a1b2e4f5a0012345678", + "block_id": "674c8b2c3e5f6a0012345679", + "valid": false +} +``` + +**What's being validated?** +- ChainForge recalculates the block's hash using the provided data +- Compares it with the stored hash +- If they match: data is unchanged (valid) +- If they don't match: data was tampered with (invalid) + +## Congratulations! 🎉 + +You've just: +- ✅ Created a blockchain instance +- ✅ Mined your first block with Proof of Work +- ✅ Added multiple blocks with different difficulties +- ✅ Validated block data integrity +- ✅ Experienced how mining difficulty affects time + +## Understanding What You Built + +### Blockchain Structure + +``` +Blockchain ID: 674c8a1b2e4f5a0012345678 +├── Block 0 (Genesis) +│ ├── index: 0 +│ ├── hash: genesis_hash +│ ├── previous_hash: null +│ └── (not mined) +│ +├── Block 1 +│ ├── index: 1 +│ ├── data: "Hello, Blockchain!" +│ ├── hash: 00a1b2c3... (starts with "00") +│ ├── previous_hash: genesis_hash +│ ├── nonce: 142 +│ └── difficulty: 2 +│ +└── Block 2 + ├── index: 2 + ├── data: "Second block data" + ├── hash: 000d4e5f... (starts with "000") + ├── previous_hash: 00a1b2c3... + ├── nonce: 1823 + └── difficulty: 3 +``` + +### Chain Integrity + +The blockchain is secure because: + +1. **Hash Chaining**: Each block links to previous block's hash +2. **Proof of Work**: Each hash must meet difficulty requirement +3. **Immutability**: Changing any data invalidates the hash +4. **Cascade Effect**: Invalid hash breaks all subsequent blocks + +**Example of tampering:** +``` +Original Block 1: data="Hello" → hash=00abc... +Tampered Block 1: data="Goodbye" → hash=99xyz... ❌ (doesn't match stored hash) +Result: Block 1 invalid, entire chain from Block 1 onward is invalid +``` + +## Next Steps + +### Learn More + +1. [First Blockchain Tutorial](first-blockchain-tutorial.md) - Detailed walkthrough with explanations +2. [API Reference](../api/reference.md) - Complete endpoint documentation +3. [Proof of Work Deep Dive](../architecture/proof-of-work.md) - Understanding the mining algorithm + +### Experiment + +Try these challenges: + +1. **Low Difficulty Mining**: Create blocks with difficulty 1 + ```bash + curl -X POST http://localhost:1910/api/v1/chain//block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Easy mining!", "difficulty": 1}' + ``` + +2. **High Difficulty Mining**: Try difficulty 4 (will take longer) + ```bash + curl -X POST http://localhost:1910/api/v1/chain//block \ + -H 'Content-Type: application/json' \ + -d '{"data": "Hard mining!", "difficulty": 4}' + ``` + +3. **Multiple Blockchains**: Create multiple independent blockchains + ```bash + curl -X POST http://localhost:1910/api/v1/chain + curl -X POST http://localhost:1910/api/v1/chain + ``` + +4. **Data Validation**: Try validating with wrong data to see `valid: false` + ```bash + curl -X POST http://localhost:1910/api/v1/chain//block//valid \ + -H 'Content-Type: application/json' \ + -d '{"data": "Wrong data!"}' + ``` + +### Integration Examples + +Check out [API Examples](../api/examples.md) for code in: +- Python +- JavaScript (Node.js) +- Ruby +- curl scripts + +## Common Questions + +**Q: Can I delete blocks?** +A: No! Blockchains are append-only. You can only add new blocks. + +**Q: Can I modify existing blocks?** +A: No! Blocks are immutable. Changing data invalidates the hash and breaks the chain. + +**Q: Why does mining take so long with high difficulty?** +A: Higher difficulty requires more leading zeros, which is exponentially harder. Each additional zero multiplies attempts by ~16. + +**Q: What happens if I use the wrong blockchain ID?** +A: You'll get a 404 error: `Blockchain not found` + +**Q: Can multiple blockchains exist?** +A: Yes! Each `POST /api/v1/chain` creates an independent blockchain instance. + +**Q: What's the maximum difficulty?** +A: Difficulty ranges from 1-10. Values outside this range return a validation error. + +## Rate Limiting + +Be aware of API rate limits: + +| Endpoint | Limit | Window | +|----------|-------|--------| +| All endpoints | 60 requests | 1 minute | +| Create blockchain | 10 requests | 1 minute | +| Add block (mining) | 30 requests | 1 minute | + +If you exceed limits, you'll receive: +```json +{ + "error": "Rate limit exceeded. Please try again later." +} +``` +**HTTP Status:** 429 (Too Many Requests) + +## Troubleshooting + +**Error:** `{"errors": {"data": ["must be filled"]}}` +**Solution:** Include `data` field in request body + +**Error:** `{"errors": {"difficulty": ["must be between 1 and 10"]}}` +**Solution:** Use difficulty value between 1-10 + +**Error:** Connection refused +**Solution:** Verify server is running (`ruby main.rb -p 1910`) + +**Error:** MongoDB connection error +**Solution:** Verify MongoDB is running (`mongosh --eval "db.version()"`) + +For more issues, see [Troubleshooting Guide](../guides/troubleshooting.md). + +--- + +**Ready to dive deeper?** Continue to the [First Blockchain Tutorial](first-blockchain-tutorial.md) for a comprehensive walkthrough with detailed explanations! diff --git a/DEPLOYMENT.md b/docs/guides/deployment-guide.md similarity index 100% rename from DEPLOYMENT.md rename to docs/guides/deployment-guide.md diff --git a/docs/guides/development-setup.md b/docs/guides/development-setup.md new file mode 100644 index 0000000..239b6cb --- /dev/null +++ b/docs/guides/development-setup.md @@ -0,0 +1,644 @@ +# Development Setup Guide + +Complete guide for setting up a ChainForge development environment. + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [Clone Repository](#clone-repository) +3. [Ruby Setup](#ruby-setup) +4. [MongoDB Setup](#mongodb-setup) +5. [Install Dependencies](#install-dependencies) +6. [Environment Configuration](#environment-configuration) +7. [Database Setup](#database-setup) +8. [Running the Application](#running-the-application) +9. [Running Tests](#running-tests) +10. [Development Workflow](#development-workflow) +11. [IDE Setup](#ide-setup) +12. [Troubleshooting](#troubleshooting) + +## Prerequisites + +### Required Software + +- **Git** - Version control +- **Ruby 3.2.2** - Programming language +- **MongoDB** - Database (latest version) +- **Bundler** - Ruby dependency manager + +### Recommended Software + +- **rbenv** or **rvm** - Ruby version management +- **Docker** & **Docker Compose** - Containerization (optional) +- **IDE** - RubyMine, VS Code, or Sublime Text +- **curl** or **Postman** - API testing + +## Clone Repository + +```bash +# Clone via HTTPS +git clone https://github.com/Perafan18/chain_forge.git +cd chain_forge + +# Or clone via SSH +git clone git@github.com:Perafan18/chain_forge.git +cd chain_forge + +# Create your feature branch +git checkout -b feature/your-feature-name +``` + +## Ruby Setup + +### Using rbenv (Recommended) + +**macOS:** +```bash +# Install rbenv +brew install rbenv ruby-build + +# Add to shell profile (~/.zshrc or ~/.bash_profile) +echo 'eval "$(rbenv init -)"' >> ~/.zshrc +source ~/.zshrc + +# Install Ruby 3.2.2 +rbenv install 3.2.2 + +# Set as local version for project +cd chain_forge +rbenv local 3.2.2 + +# Verify +ruby -v # Should show: ruby 3.2.2 +``` + +**Linux:** +```bash +# Install rbenv +curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash + +# Add to shell profile +echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc +echo 'eval "$(rbenv init -)"' >> ~/.bashrc +source ~/.bashrc + +# Install Ruby 3.2.2 +rbenv install 3.2.2 +rbenv local 3.2.2 + +# Verify +ruby -v +``` + +### Using rvm (Alternative) + +```bash +# Install rvm +curl -sSL https://get.rvm.io | bash -s stable + +# Load rvm +source ~/.rvm/scripts/rvm + +# Install Ruby 3.2.2 +rvm install 3.2.2 +rvm use 3.2.2 + +# Verify +ruby -v +``` + +## MongoDB Setup + +### macOS + +```bash +# Install MongoDB Community Edition +brew tap mongodb/brew +brew install mongodb-community + +# Start MongoDB service +brew services start mongodb-community + +# Verify MongoDB is running +mongosh --eval "db.version()" + +# Stop MongoDB (when needed) +brew services stop mongodb-community +``` + +### Linux (Ubuntu/Debian) + +```bash +# Import MongoDB public key +wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add - + +# Add MongoDB repository +echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu $(lsb_release -cs)/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + +# Update and install +sudo apt update +sudo apt install -y mongodb-org + +# Start MongoDB +sudo systemctl start mongod +sudo systemctl enable mongod # Start on boot + +# Verify +mongosh --eval "db.version()" +``` + +### Using Docker (Alternative) + +```bash +# Start MongoDB only +docker-compose up -d db + +# Verify +docker ps # Should show mongodb container + +# MongoDB will be available at localhost:27017 +``` + +## Install Dependencies + +```bash +# Install Bundler +gem install bundler + +# Install project dependencies +bundle install + +# This installs: +# - Sinatra (web framework) +# - Mongoid (MongoDB ODM) +# - RSpec (testing) +# - RuboCop (linting) +# - Rack::Attack (rate limiting) +# - dry-validation (input validation) +# - And more... +``` + +**Common Issues:** + +```bash +# If bundle install fails +gem update --system +gem install bundler +bundle install + +# If specific gem fails +bundle config build. --with-