From 6b5bf624e1830003aed9f9910302e5f85d970b4c Mon Sep 17 00:00:00 2001 From: Pedro Perafan Date: Sun, 9 Nov 2025 23:02:39 -0600 Subject: [PATCH] feat: Make default difficulty configurable via environment variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added DEFAULT_DIFFICULTY environment variable to allow configuring the default mining difficulty without code changes. Changes: - Created .env.example with DEFAULT_DIFFICULTY=2 - Removed .env from git tracking (security best practice) - Updated .env.test with DEFAULT_DIFFICULTY=2 - Updated Block model to use ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i as default - Updated main.rb to use ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i when difficulty not provided Benefits: - Allows different difficulty settings per environment (dev/staging/prod) - No code changes needed to adjust default difficulty - Follows 12-factor app configuration principles All tests passing (17 examples, 0 failures) RuboCop clean (10 files, no offenses) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env => .env.example | 1 + .env.test | 1 + .junie/guidelines.md | 266 +++++++++++++++++++++---------------------- main.rb | 2 +- src/block.rb | 2 +- 5 files changed, 135 insertions(+), 137 deletions(-) rename .env => .env.example (81%) diff --git a/.env b/.env.example similarity index 81% rename from .env rename to .env.example index 19472d2..934657c 100644 --- a/.env +++ b/.env.example @@ -2,3 +2,4 @@ MONGO_DB_NAME=chain_forge MONGO_DB_PORT=27017 MONGO_DB_HOST=localhost ENVIRONMENT=development +DEFAULT_DIFFICULTY=2 diff --git a/.env.test b/.env.test index 5997fb2..42929be 100644 --- a/.env.test +++ b/.env.test @@ -2,3 +2,4 @@ MONGO_DB_NAME=chain_forge_test MONGO_DB_PORT=27017 MONGO_DB_HOST=localhost ENVIRONMENT=test +DEFAULT_DIFFICULTY=2 diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 2909bb6..ac7ed4e 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -1,158 +1,154 @@ -# ChainForge β€” Development Guidelines +### ChainForge β€” Dev Guidelines (verified 2025-11-09 22:57 local) -This document captures project-specific practices for building, testing, and extending ChainForge. It assumes an advanced Ruby developer familiar with Bundler, RSpec, Docker, and MongoDB. +These notes capture project-specific setup, testing, and dev practices validated on a clean environment with Docker-provided MongoDB. -## Build and Configuration +#### Tech stack +- Ruby 3.2.2 (Bundler 2.4.13) +- Sinatra 4.x, Mongoid 7.0.x, MongoDB +- RSpec, SimpleCov, RuboCop (+ rubocop-rspec) +- dotenv for env loading; Rack::Attack middleware (disabled in test) -- Ruby: 3.2.2 (see `Gemfile` and `Dockerfile`). -- App stack: Sinatra 4.x, Mongoid 7.0.x, MongoDB. -- Environment configuration is consumed via `dotenv` and `Mongoid`: - - `Mongoid.load!('./config/mongoid.yml', ENV['ENVIRONMENT'] || :development)` in `main.rb` - - `Mongoid.load!('./config/mongoid.yml', ENV['ENVIRONMENT'] || :test)` in `spec/spec_helper.rb` -- Required ENV (both development and test environments): - - `ENVIRONMENT` β€” one of `development` or `test` (symbol allowed by Mongoid). Defaults to `development` in app, `test` in specs when unset. - - `MONGO_DB_NAME` β€” database name (e.g., `chain_forge_dev` or `chain_forge_test`). - - `MONGO_DB_HOST` β€” MongoDB host (e.g., `127.0.0.1`). - - `MONGO_DB_PORT` β€” MongoDB port (e.g., `27017`). -- Mongoid config (`config/mongoid.yml`) uses only these ENV variables (no auth block). Ensure the vars are set or use Docker. - -### Installing dependencies - -```bash -# Ruby 3.2.2 assumed -gem install bundler -v 2.4.13 -bundle install -``` - -### Running via Docker (recommended for a clean Mongo) - -```bash -docker-compose up -d -# Exposes app on :1910 and mongo on :27017 by default -``` - -### Running app locally - -```bash -# Ensure MongoDB is reachable at MONGO_DB_HOST:MONGO_DB_PORT and DB exists/auto-creates -ENVIRONMENT=development \ -MONGO_DB_NAME=chain_forge_dev \ -MONGO_DB_HOST=127.0.0.1 \ -MONGO_DB_PORT=27017 \ -bundle exec ruby main.rb -p 1910 -``` - -## Testing - -RSpec is configured in `spec/spec_helper.rb` and loads Mongoid in `:test` unless `ENVIRONMENT` overrides. - -### Baseline run (verified) - -The following has been verified to pass for unit-level specs on a fresh run with MongoDB available (e.g., via `docker-compose up -d`): - -```bash -ENVIRONMENT=test \ -MONGO_DB_NAME=chain_forge_test \ -MONGO_DB_HOST=127.0.0.1 \ -MONGO_DB_PORT=27017 \ -bundle exec rspec spec/block_spec.rb spec/blockchain_spec.rb -``` - -Notes: -- The full suite currently includes API specs that may fail depending on content-type behavior and rack/test expectations. If you want green runs while iterating on models, scope to the unit specs above or filter via `-e/--example` or `--pattern`. -- Full-suite run example (may include failing API specs today): +--- -```bash -ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ -bundle exec rspec -``` +### Build & Configuration -### Focused runs and filters +- Install deps (verified): + ```bash + gem install bundler -v 2.4.13 + bundle install + ``` -- Single file: `bundle exec rspec spec/block_spec.rb` -- Single example: `bundle exec rspec spec/block_spec.rb:37` -- By example name: `bundle exec rspec -e 'validates the blockchain'` +- Required env vars (used by `config/mongoid.yml`): + - `ENVIRONMENT` β€” `development` or `test`. Defaults: app β†’ `development`; specs set `test` in `spec/spec_helper.rb`. + - `MONGO_DB_NAME` β€” DB name (e.g., `chain_forge_dev`, `chain_forge_test`). + - `MONGO_DB_HOST` β€” Mongo host (e.g., `127.0.0.1` or `db` inside compose). + - `MONGO_DB_PORT` β€” Mongo port (e.g., `27017`). -### Coverage +- Mongoid loading points: + - App: `Mongoid.load!("./config/mongoid.yml", ENV['ENVIRONMENT'] || :development)` in `main.rb`. + - Specs: `Mongoid.load!("./config/mongoid.yml", :test)` in `spec/spec_helper.rb` (and `ENV['ENVIRONMENT']='test'`). -SimpleCov is available (loaded on demand). To enable coverage, set an env var before running: +- Docker (recommended for a clean Mongo): + ```bash + docker-compose up -d + # Exposes app on :1910 and Mongo on :27017 + ``` + - Service names: `app`, `db`. The app uses `ruby main.rb -p 1910` per `Dockerfile`. -```bash -COVERAGE=true ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ -bundle exec rspec -# Coverage report will be in coverage/index.html -``` +- Run app locally (outside Docker): + ```bash + ENVIRONMENT=development \ + MONGO_DB_NAME=chain_forge_dev \ + MONGO_DB_HOST=127.0.0.1 \ + MONGO_DB_PORT=27017 \ + bundle exec ruby main.rb -p 1910 + ``` + - Note: `before` block in `main.rb` sets `content_type :json` for POSTs; the root route sets `:html` explicitly. + - Rack::Attack is enabled except when `ENVIRONMENT=test`. -### Adding a new spec (demonstrated workflow) +--- -The following process was executed and verified end-to-end; replicate as needed: +### Testing -1. Create a new spec file under `spec/`, e.g. `spec/demo_math_spec.rb`: - ```ruby - # frozen_string_literal: true - require 'rspec' +- Verified baseline (unit-level specs only): + ```bash + ENVIRONMENT=test \ + MONGO_DB_NAME=chain_forge_test \ + MONGO_DB_HOST=127.0.0.1 \ + MONGO_DB_PORT=27017 \ + bundle exec rspec spec/block_spec.rb spec/blockchain_spec.rb + ``` + Result observed: 10 examples, 0 failures. - RSpec.describe 'Math demo' do - it 'adds numbers' do - expect(1 + 1).to eq(2) +- Full suite: + ```bash + ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ + bundle exec rspec + ``` + Note: API specs (`spec/api_spec.rb`) may be sensitive to content-type behavior or rack/test mounting. The app sets JSON content-type for POSTs globally and again in the `/api/v1` namespace, which generally aligns with expectations. + +- Focused runs and filters: + - Single file: `bundle exec rspec spec/block_spec.rb` + - Single example: `bundle exec rspec spec/block_spec.rb:37` + - By name: `bundle exec rspec -e 'validates the blockchain'` + +- Adding a new spec (workflow verified end-to-end): + 1) Create a file, e.g. `spec/demo_math_spec.rb`: + ```ruby + # frozen_string_literal: true + require 'rspec' + + RSpec.describe 'Math demo' do + it 'adds numbers' do + expect(1 + 1).to eq(2) + end end - end - ``` -2. Run just that spec: - ```bash - ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ - bundle exec rspec spec/demo_math_spec.rb - ``` - Expected: 1 example, 0 failures. -3. Remove the file once done (to keep repo clean): - ```bash - rm spec/demo_math_spec.rb - ``` - -### Test data and MongoDB - -- Specs create and persist documents via Mongoid (e.g., `Block`, `Blockchain`). Use a dedicated `MONGO_DB_NAME` for tests to avoid polluting dev data. -- No cleaning strategy is configured. If isolation becomes necessary, consider adding `database_cleaner-mongoid` or dropping the test DB between runs. - -## Additional Development Notes - -### API behavior - -- `main.rb` sets `content_type :json` for POST requests in a `before` block. If API specs expect more strict headers (e.g., `application/json` vs `text/html`), ensure routes are executed in the same Rack env as tests (Sinatra + rack-test). If content-type mismatches persist, review middleware or how tests mount the app. - -### Code style and linting - -- RuboCop with `rubocop-rspec` is configured in `.rubocop.yml`. - - Target Ruby: 3.2 - - Selected Metrics/Style cops are tuned; specs and config directories are largely excluded from method/ block length checks. -- Run lint: + ``` + 2) Run it: + ```bash + ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ + bundle exec rspec spec/demo_math_spec.rb + ``` + Expected: 1 example, 0 failures (observed). + 3) Remove the file when done: + ```bash + rm spec/demo_math_spec.rb + ``` + +- Coverage: ```bash - bundle exec rubocop + COVERAGE=true ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ + bundle exec rspec + # Report: coverage/index.html ``` + - `spec/spec_helper.rb` preloads SimpleCov when `COVERAGE` is set and applies filters/groups; minimum coverage is set to 80%. -### Project structure - -- Core domain: - - `src/block.rb` β€” `Block` model with SHA256 hashing and `valid_data?` verification. - - `src/blockchain.rb` β€” `Blockchain` aggregate with `add_genesis_block`, `add_block`, `integrity_valid?`. -- App entrypoint: `main.rb` (Sinatra routes for creating chains, adding blocks, validating block data). -- Tests: `spec/` directory with unit specs for `Block` and `Blockchain`, and API specs. +- Test data & MongoDB: + - Specs persist via Mongoid; use a dedicated test DB (`MONGO_DB_NAME=chain_forge_test`). + - No cleaning strategy is configured. For isolation across runs, consider dropping the test DB between runs or adding `database_cleaner-mongoid`. -### Debugging tips - -- Use focused RSpec runs (`-e`, `:line_number`). -- Inspect Mongo documents in a console by starting `irb` with the same environment: +- Running tests inside container (optional): ```bash - ENVIRONMENT=development MONGO_DB_NAME=chain_forge_dev MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ - bundle exec irb -r ./src/blockchain -r ./src/block + docker-compose exec app bash + # inside container + ENVIRONMENT=test MONGO_DB_NAME=chain_forge_test MONGO_DB_HOST=db MONGO_DB_PORT=27017 bundle exec rspec ``` -- When debugging hashing/time-sensitive assertions, compare against `created_at.to_i` (used in hash calculation). - -### Docker notes - -- The provided `docker-compose.yml` spins up `app` and `db`. The `app` container runs `ruby main.rb -p 1910` per `Dockerfile` CMD. For test runs, it’s simpler to run RSpec on the host and use the `db` service for Mongo. -- If you want to run tests inside the container, `docker-compose exec app bash` then run the same `ENVIRONMENT=test` commands with `MONGO_DB_HOST=db`. --- -This document reflects commands verified on 2025-11-08 23:58 local time. Keep it updated as the test suite and APIs evolve. \ No newline at end of file +### Code & Project Notes + +- Structure: + - `src/block.rb` β€” `Block` model with SHA256 hashing, PoW attributes (`nonce`, `difficulty`), and `valid_data?`. + - `src/blockchain.rb` β€” Chain aggregate; `add_genesis_block`, `add_block`, `last_block`, `integrity_valid?`. + - `main.rb` β€” Sinatra app; routes: + - `GET /` β€” hello text (HTML content-type). + - `POST /api/v1/chain` β€” creates a chain. + - `POST /api/v1/chain/:id/block` β€” validates payload via `BlockDataContract` and mines a block. + - `POST /api/v1/chain/:id/block/:block_id/valid` β€” validates provided data against stored block. + - `GET /api/v1/chain/:id/block/:block_id` β€” returns block details including `valid_hash` and mining metadata. + - `src/validators.rb` β€” Dry-validation contracts (e.g., `BlockDataContract`). + - `config/mongoid.yml` β€” uses only `MONGO_DB_*` envs; no auth block. + - `config/rack_attack.rb` β€” Rack::Attack throttling; disabled when `ENVIRONMENT=test`. + +- API behavior specifics: + - `before` in app namespace sets `content_type :json` for API routes; POSTs also get JSON via global `before`. If rack/test expectations change, ensure tests mount `Sinatra::Application` and set `CONTENT_TYPE` for JSON posts (as in specs). + +- Linting: + ```bash + bundle exec rubocop + ``` + - Target Ruby: 3.2; spec/config dirs are relaxed on Metrics. + +- Debugging tips: + - Time-sensitive assertions use `created_at.to_i` in hashing; compare against integer timestamps to avoid flakiness. + - Quick IRB with models loaded: + ```bash + ENVIRONMENT=development MONGO_DB_NAME=chain_forge_dev MONGO_DB_HOST=127.0.0.1 MONGO_DB_PORT=27017 \ + bundle exec irb -r ./src/blockchain -r ./src/block + ``` + +- Troubleshooting: + - Connection refused on Mongo: ensure `docker-compose up -d` is running or that `MONGO_DB_HOST/PORT` are correct. + - Content-Type mismatches in API tests: check the global/namespace `content_type :json` and `CONTENT_TYPE` header in requests. + - Rack::Attack blocks: ensure `ENVIRONMENT=test` for specs; middleware disabled in that env. diff --git a/main.rb b/main.rb index fb8cc7c..2cf4df3 100644 --- a/main.rb +++ b/main.rb @@ -45,7 +45,7 @@ chain_id = params[:id] blockchain = find_block_chain(chain_id) - difficulty = validation[:difficulty] || 2 + difficulty = validation[:difficulty] || ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i block = blockchain.add_block(validation[:data], difficulty: difficulty) { diff --git a/src/block.rb b/src/block.rb index 7ce8fcd..7d64c07 100644 --- a/src/block.rb +++ b/src/block.rb @@ -27,7 +27,7 @@ class Block field :previous_hash, type: String field :_hash, type: String, as: :hash field :nonce, type: Integer, default: 0 - field :difficulty, type: Integer, default: 2 + field :difficulty, type: Integer, default: -> { ENV.fetch('DEFAULT_DIFFICULTY', '2').to_i } belongs_to :blockchain