Replaced by the /ws WebSocket channel. Deletes the SSE connection manager,
the vendored sse_starlette, and the unused /bitcoin/block-sub and
/system/hardware-info-sub streaming endpoints. Also removes the
test_block_sub_error.py regression test, which exclusively covered the
now-deleted handle_block_sub per-request generator (handle_block_sub_redis,
which broadcasts over /ws, is retained and unaffected).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same #277 root cause in the block-subscription path: Bitcoin Core 28+
uses strict JSON-RPC 2.0, so an errored getblock/getbestblockhash reply
has no 'result' key. handle_block_sub and handle_block_sub_redis accessed
r['result'] unguarded, so a transient RPC error would raise
KeyError: 'result' and kill the block stream / block-update task.
Skip and log the block when the reply has no result instead of crashing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bitcoin Core returns the -28 warmup error ('Loading block index',
'Verifying blocks', 'Starting network threads') as an HTTP 200 response
with a JSON-RPC error body. _process_response only classified errors on
the non-200 path and passed a 200 body through unchanged, so the result
had an 'error' key but no 'status' key. Callers doing
'raise HTTPException(result["status"], ...)' then crashed with
KeyError: 'status' instead of HTTPException(425), so the startup
warmup-retry loop in initialize_bitcoin_repo never engaged and the API
(and login) failed while bitcoind was still warming up.
Same root cause, different -28 warmup message.
Fixes#287Fixes#285
Normalize any JSON-RPC error to an {error, status} dict regardless of the
HTTP status, mapping the warmup messages to 425 TOO_EARLY as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_event_loop() is deprecated on Python 3.11+ when there is no running
loop and is slated to change behaviour further.
- SSEManager.setup() ran at import time (app.api.utils) via
get_event_loop(); this only worked because uvicorn imports the app
inside its loop and would break when imported without a running loop
(e.g. a Celery worker). Start the broadcast consumer lazily from
within a running loop instead.
- everywhere else the pattern was get_event_loop().create_task(x)
inside a coroutine; replace with asyncio.create_task(x).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
If the environment variable BAPI_ENV_PATH is set, the config system will
try to read configs from the given path instead of .env in pwd
If no file is found .env in pwd will be used as a fallback
Env variables will always override settings in .env files.