Repository navigation
v0.1.2
v0.1.2 — maintenance update
A maintenance release for the Android full node. It fixes all eleven findings of an external audit of v0.1.1 (conducted with ChatGPT, delivered in two rounds, each finding verified against the code before fixing; no critical- or high-severity issues). No changes to the node's launch arguments, the database format, or the app's permissions: it installs over v0.1.1 and keeps your settings and synced data — verified on the reference device, where the node resumed at the chain tip after the update without re-syncing, and a stop → immediate start right after launch worked cleanly.
What's fixed
- A cancelled start can no longer collide with the next start — if a stop arrived while the node was still starting, the cancelled child was destroyed but its pid record was cleared before it had actually exited, so a quick stop → start could overlap the still-closing old node and the new node died on the old one's database lock. The teardown now waits for the child to exit (up to 5 s) before clearing the record. (The audit reproduced the lock failure with a real child process.)
- Android 12/12L compatibility restored — four places used
InputStream.readAllBytes(), which only exists from API 33; on API 31–32 devices the call escapes as aNoSuchMethodError— including in the pid-file read that runs at every node start. All four now use a plain read loop. Not verified on a real API 31–32 device (none available to us); behaviour on newer versions is unchanged. - A hostile RPC response can no longer kill the status poller — the recursive JSON parser overflows the stack on a deeply nested response body, and that
Errorescaped every catch, silently killing the polling thread while the node kept running. A failed parse is now treated like any failed call. (Reproduced against the AOSP JSON implementation, not on a device.) - "Synced" is no longer shown when it isn't — if
system_syncStateanswered without a highest block, the status flipped to SYNCED mid-sync by definition. It is now judged against the last known highest block. - The status notification unsticks itself — the de-duplication key survived across sessions, so a new session could stay showing "starting" in the notification shade. The key is now reset when a session is established.
- Log rotation accounts for the existing log — the rotation counter restarted at zero for every service instance, so a log written by many short sessions could grow past its 512 KB cap unchecked. It is now seeded with the existing file's length.
- Start-failure capture caps line length — captured lines are truncated at 8192 characters (the file log already capped lines separately).
- Display-name edge case — a name like
- --helpcould survive sanitizing and again start with-after trimming; values hiding dashes behind whitespace now fall back to the default name. - Build hygiene — Gradle builds now include the bundled CA list (
cacert.pem); the manual build now sets the previous APK aside before every preflight check, so a failed build can never leave a stale file at the release path; andProcess.pid()is now probed reflectively (see Verification for the honest detail).
Verification
- Every audit finding was re-checked against the actual code before fixing; two details of the audit report itself were corrected along the way (
readAllBytesis API 33, not 34; and the reported Gradle compile block onProcess.pid()could not be reproduced — the SDK stub does contain it. What stands:pid()appears in no level of the official API database and the target runtime throwsNoSuchMethodErrorfor it, so the call is now reflective, with identical behaviour). - API-surface check: all 345 external references of the compiled classes were resolved against the official API database — nothing outside the API ≤ 31 surface; the same check run on v0.1.1 flags exactly the two items above.
- JVM checks: translation tables (49 keys × 9 languages), the name-sanitizing suite including the new edge cases, the log-rotation fix against a pre-grown 614,400-byte log, the bounded child-exit wait against real processes, and the lifecycle desk check including the new stop → start interplay.
- Byte-level comparison with the v0.1.1 APK: only
classes.dex, the version inAndroidManifest.xml, and the signature files differ — the node binary, the CA bundle and all resources are byte-identical. - On device (Pixel 9a, GrapheneOS): updated in place over v0.1.1, resumed at the tip, steady state; stop → immediate start after launch confirmed working.
- Honest notes: a full Gradle build is still not runnable in the maintainer's build environment (the Gradle daemon does not start there), so Gradle compilation itself remains unverified; the lifecycle races are verified by code tracing and JVM exercises, not reproduced on a device.
Requirements
- Android 12 or newer, arm64 device
- Verified on: Pixel 9a + GrapheneOS. Other devices are expected to work, but vendor power management may stop the background service
- About 3 GB of free storage (measured after a full sync: 1.30 GB total)
- Wi-Fi recommended for the first sync (about 2–3 hours; battery went from 71% to 27% in our first-sync test, including time on mobile data)
Install
- Download
quantus-node-v0.1.2.apkfrom the assets below. - Install it (you may need to allow installs from your browser/file manager). If v0.1.1 is installed, install over it — do not uninstall first, or your synced data will be lost.
- Open the app, tap Start, and keep it charging on Wi-Fi for the first sync.
- Optional: set a node name and turn telemetry on in Settings if you want to appear on the public telemetry list.
See the repository's Usage Guide for details and troubleshooting.
Checksum (SHA-256)
7c196901d0160a6035bf931eb8bc69fbbf13e4ea17c9510e2891be7ac0548b7f — quantus-node-v0.1.2.apk (55,700,181 bytes)