Closes failure mode adjacent to FM3 (docs/bulletproof-containers.md): on
a syncing pruned node, bitcoind's RPC thread blocks for 5-10s during block
validation. The old 10s client-side timeout was rejecting roughly 30% of
UI calls even though the node was perfectly healthy. 20x stress test on
the live .116 node (caught in IBD catch-up at block 797k) used to drop
10 of 20 calls; now drops 0 of 20.
What changed:
- core/archipelago/src/api/rpc/bitcoin.rs: bitcoin_rpc_call now retries up
to 3 times with 500ms and 1500ms backoffs between attempts. Only
transient transport errors (timeout, connect refused, send/recv IO)
trigger retry. A well-formed bitcoind error response is surfaced
immediately - real RPC bugs are never masked.
- Per-attempt hard deadline (tokio::time::timeout, 15s) layered on top
of reqwest's own timeout, so DNS starvation or TLS wedging can't
steal the entire retry budget.
- handle_bitcoin_getinfo client builder gained a 3s connect_timeout
so a dead bitcoind is fast-failed inside the first attempt instead
of eating the whole 15s.
- Retry policy extracted into a RetryConfig struct so tests can dial
down timeouts to ~100ms per attempt. Production defaults live in
RetryConfig::production().
Not changed (tracked as follow-up):
- mesh/mod.rs bitcoin_rpc_getblockcount and related helpers use the
same 10s-timeout pattern. Not migrated to the new wrapper in this
release; scheduled for v1.7.43 alongside the render_bitcoin_conf
work.
- lnd/info.rs and electrs_status have similar 10s/15s timeouts but
different failure profiles - audit first, migrate only the ones
that actually exhibit the bug.
Tests: 6 new unit tests under api::rpc::bitcoin::tests, all passing.
Uses an in-process hyper server (already a transitive dep) to simulate
bitcoind responses; no new crates required.
- happy_path_first_attempt: no retry when first attempt succeeds
- retries_on_timeout_then_succeeds: first attempt times out, second
succeeds, returns OK (uses a short-timeout RetryConfig so the test
runs in <1s instead of 15s)
- retries_exhausted_on_persistent_connect_refused: all attempts fail
against a closed port, error bubbles up, elapsed time confirms
backoffs actually ran
- does_not_retry_on_rpc_level_error: bitcoind-returned error body is
surfaced immediately, no retry
- does_not_retry_parse_errors: non-JSON response (e.g. 503 with html
body) is NOT retried - guards against the tempting "retry all
non-2xx" mistake that would mask real bitcoind misconfig
- retry_budget_invariants: asserts total wall-time ceiling stays
under 60s so a bumped constant can't silently hang a UI call
forever
Validated live on .116: 20/20 bitcoin.getinfo calls succeed during IBD
catch-up (chain at block 797419 -> 797464), vs ~40% baseline under the
old 10s timeout. Worst-case latency was 48.9s during peak validation;
happy-path latency (cached result) remains 28-77ms.
68 lines
2.4 KiB
JSON
68 lines
2.4 KiB
JSON
{
|
|
"name": "neode-ui",
|
|
"private": true,
|
|
"version": "1.7.42-alpha",
|
|
"type": "module",
|
|
"scripts": {
|
|
"start": "./start-dev.sh",
|
|
"stop": "./stop-dev.sh",
|
|
"test": "vitest run",
|
|
"test:watch": "vitest",
|
|
"dev": "vite",
|
|
"dev:mock": "concurrently --raw \"node mock-backend.js\" \"VITE_AIUI_URL=http://localhost:5173 vite\" \"cd ../../AIUI && perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' -- pnpm dev 2>/dev/null || echo '[AIUI] Not found at ../../AIUI — chat will show placeholder'\"",
|
|
"dev:boot": "VITE_DEV_MODE=boot concurrently --raw \"VITE_DEV_MODE=boot node mock-backend.js\" \"VITE_DEV_MODE=boot vite\"",
|
|
"dev:real": "echo 'Start backend: cd ../core && cargo run --release' && vite",
|
|
"backend:mock": "node mock-backend.js",
|
|
"backend:real": "cd ../core && cargo run --release",
|
|
"prebuild": "cp ../app-catalog/catalog.json public/catalog.json",
|
|
"build": "vue-tsc -b && vite build",
|
|
"build:docker": "vite build",
|
|
"build:production": "NODE_ENV=production vue-tsc -b && vite build --mode production",
|
|
"preview": "vite preview",
|
|
"type-check": "vue-tsc --noEmit",
|
|
"generate-pwa-icons": "pwa-assets-generator --preset minimal-2023 public/assets/icon/favico-black.svg && cp public/assets/icon/favicon.ico public/favicon.ico",
|
|
"generate-welcome-speech": "node scripts/generate-welcome-speech.js"
|
|
},
|
|
"dependencies": {
|
|
"@types/dompurify": "^3.0.5",
|
|
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
|
"d3": "^7.9.0",
|
|
"dompurify": "^3.3.3",
|
|
"fast-json-patch": "^3.1.1",
|
|
"fuse.js": "^7.1.0",
|
|
"leaflet": "^1.9.4",
|
|
"pinia": "^3.0.4",
|
|
"qrcode": "^1.5.4",
|
|
"vue": "^3.5.24",
|
|
"vue-i18n": "^11.3.0",
|
|
"vue-router": "^4.6.3"
|
|
},
|
|
"devDependencies": {
|
|
"@playwright/test": "^1.58.2",
|
|
"@types/d3": "^7.4.3",
|
|
"@types/leaflet": "^1.9.21",
|
|
"@types/node": "^24.10.0",
|
|
"@types/qrcode": "^1.5.6",
|
|
"@vite-pwa/assets-generator": "^1.0.2",
|
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
"@vitest/coverage-v8": "^3.2.4",
|
|
"@vue/test-utils": "^2.4.6",
|
|
"@vue/tsconfig": "^0.8.1",
|
|
"autoprefixer": "^10.4.22",
|
|
"concurrently": "^9.1.2",
|
|
"cookie-parser": "^1.4.7",
|
|
"cors": "^2.8.5",
|
|
"dockerode": "^4.0.9",
|
|
"express": "^4.21.2",
|
|
"jsdom": "^25.0.1",
|
|
"postcss": "^8.5.6",
|
|
"tailwindcss": "^3.4.18",
|
|
"typescript": "~5.9.3",
|
|
"vite": "^7.2.2",
|
|
"vite-plugin-pwa": "^1.2.0",
|
|
"vitest": "^3.1.1",
|
|
"vue-tsc": "^3.1.3",
|
|
"ws": "^8.18.0"
|
|
}
|
|
}
|