-
Notifications
You must be signed in to change notification settings - Fork 35
⚡ Bolt: Optimized Blockchain for Field Officer Visits #590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -199,6 +199,13 @@ def index_exists(table, index_name): | |
| if not index_exists("field_officer_visits", "ix_field_officer_visits_check_in_time"): | ||
| conn.execute(text("CREATE INDEX IF NOT EXISTS ix_field_officer_visits_check_in_time ON field_officer_visits (check_in_time)")) | ||
|
|
||
| if not column_exists("field_officer_visits", "previous_visit_hash"): | ||
| conn.execute(text("ALTER TABLE field_officer_visits ADD COLUMN previous_visit_hash VARCHAR")) | ||
| logger.info("Added previous_visit_hash column to field_officer_visits") | ||
|
|
||
| if not index_exists("field_officer_visits", "ix_field_officer_visits_previous_visit_hash"): | ||
| conn.execute(text("CREATE INDEX IF NOT EXISTS ix_field_officer_visits_previous_visit_hash ON field_officer_visits (previous_visit_hash)")) | ||
|
Comment on lines
+202
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This migration is not wired into the running app.
Minimal startup fix outside this file- # await run_in_threadpool(migrate_db)
+ await run_in_threadpool(migrate_db)🤖 Prompt for AI Agents |
||
|
|
||
| logger.info("Database migration check completed successfully.") | ||
|
|
||
| except Exception as e: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,9 +27,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.geofencing_service import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_within_geofence, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| generate_visit_hash, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verify_visit_integrity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| calculate_visit_metrics, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| get_geofencing_service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.cache import visit_last_hash_cache | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.schemas import BlockchainVerificationResponse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
36
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,6 +61,7 @@ def officer_check_in(request: OfficerCheckInRequest, db: Session = Depends(get_d | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| - **geofence_radius_meters**: Acceptable distance from site (default: 100m) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Geo-Fencing**: Automatically verifies if officer is within acceptable radius of issue location | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Blockchain Chaining**: Cryptographically links this visit to the previous one for immutability | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate issue exists | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -95,6 +99,22 @@ def officer_check_in(request: OfficerCheckInRequest, db: Session = Depends(get_d | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create visit record | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check_in_time = datetime.now(timezone.utc) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Blockchain Chaining Logic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Use thread-safe cache to eliminate database lookups for the previous hash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev_hash = visit_last_hash_cache.get("last_hash") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Race condition: the read → hash → commit → cache-update sequence is not atomic, so concurrent check-ins (or requests across multiple worker processes) will read the same To fix this properly, either use a database-level advisory lock / Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if prev_hash is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cache miss: fetch only the last hash and ID from DB | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Optimization: Use column projection to avoid full model loading | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_visit = db.query(FieldOfficerVisit.id, FieldOfficerVisit.visit_hash).order_by(FieldOfficerVisit.id.desc()).first() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if last_visit: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev_hash = last_visit[1] or "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| visit_last_hash_cache.set(data=prev_hash, key="last_hash") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| visit_last_hash_cache.set(data=last_visit[0], key="last_id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| visit_last_hash_cache.set(data=last_visit[0], key="last_id") |
Copilot
AI
Mar 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chaining via a process-local cache isn’t atomic across concurrent requests: two check-ins can read the same prev_hash before either commits, producing a fork (multiple visits with the same previous_visit_hash). If a strictly linear chain is required, consider determining prev_hash from the DB within the same transaction as the insert (or using a DB-level lock/sequence) rather than relying on the in-memory cache alone.
| # Use thread-safe cache to eliminate database lookups for the previous hash | |
| prev_hash = visit_last_hash_cache.get("last_hash") | |
| if prev_hash is None: | |
| # Cache miss: fetch only the last hash and ID from DB | |
| # Optimization: Use column projection to avoid full model loading | |
| last_visit = db.query(FieldOfficerVisit.id, FieldOfficerVisit.visit_hash).order_by(FieldOfficerVisit.id.desc()).first() | |
| if last_visit: | |
| prev_hash = last_visit[1] or "" | |
| visit_last_hash_cache.set(data=prev_hash, key="last_hash") | |
| visit_last_hash_cache.set(data=last_visit[0], key="last_id") | |
| else: | |
| prev_hash = "" | |
| visit_last_hash_cache.set(data=prev_hash, key="last_hash") | |
| # Determine previous hash from the database within the same transaction | |
| last_visit = ( | |
| db.query(FieldOfficerVisit) | |
| .order_by(FieldOfficerVisit.id.desc()) | |
| .with_for_update() | |
| .first() | |
| ) | |
| if last_visit: | |
| prev_hash = last_visit.visit_hash or "" | |
| else: | |
| prev_hash = "" |
Copilot
AI
Mar 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BlockchainVerificationResponse is reused here for visit verification, but its field descriptions are issue-specific (e.g., "issue integrity" / "previous issue's hash"), which will make the OpenAPI schema misleading for this route. Consider adding a visit-specific response model or making the existing response model descriptions generic.
Copilot
AI
Mar 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The endpoint docstring claims it verifies that the visit "links to the previous record", but the implementation only recomputes the hash using the stored previous_visit_hash value; it does not validate that previous_visit_hash matches the actual previous visit’s visit_hash. If link validation is intended, fetch the previous visit (by ID/order) and compare its visit_hash to previous_visit_hash (or adjust the docstring/message to describe what is actually verified).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generate_visit_hash()constructsdata_stringby concatenating fields without any delimiter/structured encoding. This is ambiguous (different field combinations can produce the same string), which undermines the integrity guarantee even with HMAC. Use deterministic structured serialization (e.g., JSON with sorted keys) or add unambiguous separators and normalized numeric formatting before hashing.