mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-11-05 10:21:13 +00:00
35c71062f04a3fc77f4f3e4b6cfdeb806c9e277b
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
35c71062f0 |
Strip native libraries; XCM by genesis; CodeQL for Kotlin; bridge credits in history (#31)
* Strip native libraries in the app and keep light client line tables
The app module named no NDK, so AGP expected its default 27.0 against the 26.1 that CI installs,
could not strip any native library and made no native-debug-symbols.zip for Play. Pin 26.1 in the
app. The light client now keeps line tables for that symbol file; the packaged library is still
stripped to 8.6 MB.
Also adds an XCM encoding test for PezkuwiChain networks. This commit points WESTEND at Zagros
to check that the test fails against that change.
* Fix the encoding test's read of the interior field
* Encode PezkuwiChain networks by genesis in XCM
WESTEND held a stale genesis labelled Zagros. The runtimes declare ThisNetwork as ByGenesis and
the XCM v5 NetworkId has no Westend or Zagros variant, so the named mapping is removed and every
PezkuwiChain network is encoded as ByGenesis.
* Run CodeQL for every language on hosted runners, Kotlin with a build
Default setup analyses Java without a build and leaves out every Kotlin file, and the Java job on the
self-hosted runner stopped at the NDK install because sudo asks for a password, so Kotlin had not been
analysed since February. One matrix job covers actions, JavaScript/TypeScript, Rust and Java/Kotlin.
* Use CodeQL Action v4, v3 is deprecated in December 2026
* Build for CodeQL without Gradle and Kotlin daemons so the analysis has the memory
* Show incoming USDT bridge credits in history without a refresh
The realtime transfer extractor for assets pallets knew only transfer and transfer_keep_alive. The
bridge credits with transfer_approved, spending the multisig's allowance, so a credit appeared only
after the indexer caught up and the user refreshed. transfer_approved is now a transfer from owner to
destination; the signing delegate is not the sender.
* Stop the light client five minutes after RPC is back
A chain registered with the light client kept syncing for as long as the process lived. Measured on
the emulator over 5-minute windows, that was about 14 MB of data an hour and 2% of a core while the
wallet read nothing from it.
Once RPC has stayed connected for five minutes after a fallback, the connection releases the light
client: LightClients unregisters the chain, a relay chain only after its teyrchains. The engine gains
remove_chain. Its database saver now checks, under the registry lock, that the chain is still
registered: smoldot panics on a request to a removed chain. The next fallback starts cold from the
saved database.
* Refresh light client specifications from the signed config while RPC works
The light client started from the checkpoint the APK was built with, so a first fallback months after
a release warp-synced from months back, and on a phone that never fell back there is no saved database
to help. The fallback is wanted when RPC is blocked, and then the config repository may be blocked too.
On the first RPC connection of the process, each chain's specification is fetched from the config
repository through the app's HTTP client, which checks it against the signed targets and falls back to
the mirrors. It is kept when it is the same chain (id and genesis state root) with a later checkpoint;
an older file never rolls the checkpoint back. Registration uses the kept file when it is later than the
APK's own.
* Close the light client's peer connections once its last chain is removed
smoldot keeps its peer-to-peer network service, with the connections in it, for as long as the client
lives, so removing every chain left them open: on the emulator, four libp2p connections were still up
eleven minutes after the release, and data use stayed above the RPC-only level. The engine now
replaces the client once no chain and no loopback connection is left; the connections close about 20 s
later, when the attempts under way finish.
The database saver now checks a per-registration number instead of the smoldot chain id, which smoldot
reuses after a removal: a saver of a removed chain could otherwise keep running for the next chain
that got the same id. A connection whose chain is removed between the handshake and its setup now
ends instead of panicking.
* Obfuscate the app's own classes and build the R8 variant in pull requests
-keep class io.novafoundation.nova.** { *; } with -keepnames left 2.6% of the app's 26,602 classes
renamed in the 1.3.0 Play build (15.6% of all classes). It is replaced by keeping only the field names
of the app's classes, which Gson reads by name where a class has no @SerializedName, so class and method
names are obfuscated. No persisted value or reflective lookup in the app depends on a class or method
name: class names appear only in logs and in an in-memory cache key.
@JavascriptInterface methods had no rule of their own; the blanket keep covered the DApp browser's web3
bridge, its integrity check and the Transak bridge. They are kept explicitly.
Pull requests built only the unminified develop variant, so an R8 break would first show in the Play
build. A second job builds releaseTest (the release rules, debug-signed) and uploads its APK and mapping.
* Give the releaseTest build the debug package, which the CI Firebase config knows
* Build releaseTest under the production package, the one its Firebase config has
* Stop keeping every app class under the JNI rule
-keep class io.novafoundation.nova.** { native <methods>; } keeps every class the pattern matches,
whether or not it has a native method, so the app's class names stayed unobfuscated after the blanket
keep went: 2.6% of its classes were renamed in the R8 build. The rule under it already keeps the names
of classes with native methods and of those methods, which is what JNI binds by name; the Rust
bindings look up no other app class.
* Keep TypeToken and Retrofit generic signatures under R8 full mode
The R8 build failed to map every chain from the local database: R8 full mode keeps generic signatures
only on kept classes, and the app's anonymous TypeToken subclasses had been kept by the blanket rule.
These are the rules Gson 2.11 and Retrofit 2.10 ship; the app is on Gson 2.10.1 and Retrofit 2.9.0.
* Keep constructors of the app's classes so Gson can instantiate its models
The R8 build failed to read the DApp list: R8 full mode had made DappMetadataResponse abstract because
nothing in the code instantiates it, and Gson does so reflectively. It matches none of the name
patterns the rules keep models by. Keeping constructors costs no obfuscation, since they are all named
<init>.
* Keep the type argument of web3j TypeReference subclasses under R8 full mode
* Run R8 in compatibility mode
After the app's own classes became obfuscatable, the full-mode R8 build kept breaking reflective
decoding in new places: TypeToken and TypeReference subclasses lost their type argument, a Gson model
was made abstract, and SubQueryResponse<T> lost its class signature so a proxy query decoded to a
LinkedTreeMap. Full mode keeps generic signatures only on explicitly kept classes, and this code base
has many generic models outside any name pattern. Compatibility mode keeps them everywhere under
-keepattributes Signature and does not make uninstantiated classes abstract; obfuscation is unchanged.
* Allow the java.beans classes Jackson's Java 7 support refers to, missing on Android
|
||
|
|
8549501a62 |
Embedded PezkuwiChain light client with RPC fallback (Zagros) (#30)
* Add pezkuwi-light: embedded PezkuwiChain light client binding A JNI binding around the PezkuwiChain fork of smoldot (pezkuwichain/smoldot), which verifies PezkuwiChain blocks under the chain's sovereign constants. Upstream smoldot cannot. The wallet reaches its chains through JSON-RPC WebSockets, so the light client is served the same way: a WebSocket server bound to 127.0.0.1 on a random port. Every path carries a random per-process token: ws://127.0.0.1:<port>/<token>/<chain key>. - A wrong token, a missing token or an unregistered chain is refused during the handshake with 404. - Each connection adds its own smoldot chain instance. smoldot shares the sync between instances of the same chain. - Each registered chain's finalized state is written to disk every minute, through a temporary file and a rename. The next start resumes from it. Kotlin surface: PezkuwiLight.start(dir), addChain(key, spec, relayKey), chainUrl(base, key). The module is not yet a dependency of the app. Tested against the live Zagros testnet, with the relay chain and People specs: - dks.kurd resolves through NavnisanApi_resolve to the same bytes the full node returns. - An unregistered name returns 0x00. - The saved state lets a new client resolve. - Reverting the token check turns the routing test red. The library builds for arm, arm64, x86 and x86_64 with 16 KB aligned LOAD segments (0x4000). * pezkuwi-light: secure WebSockets and the app's resolver for peers smoldot's DefaultPlatform cannot open wss connections and resolves names through the system resolver. Where our domains are blocked, both fail: a poisoned DNS answer decides where the client connects, and plain libp2p TCP is recognisable on the wire. PezkuwiPlatform replaces it: - Opens /dns/<host>/tcp/<port>/wss over TLS, using rustls with the ring provider and the Mozilla roots, so a connection looks like any HTTPS WebSocket. - Resolves every name through a Resolve trait. On Android this is the app's HostResolver, called over JNI, so the light client follows the same DNS policy as the rest of the app (EncryptedFirstDns: DoH first, system resolver last). - Tries IPv4 before IPv6, and gives up on an address after 10 s. - With setSecureOnly(true) it opens nothing but secure WebSockets. Peers found through the DHT advertise plain TCP too, and none of them is used. The peer's identity is still checked end to end by libp2p Noise. Tested against live Zagros with only the /wss boot nodes in the specs and secure-only on: relay and People sync and dks.kurd resolves to the full node's bytes. Nail: with secure-only on and the specs' plain TCP boot nodes, nothing syncs and the test times out red. * pezkuwi-light: measure a start from the saved database in its own process An app launch resumes from the database in a fresh process. The in-process warm measurement ran beside a client that was still syncing, so it did not show that case. The new ignored test resolves dks.kurd after a start from the database in PEZKUWI_LIGHT_DB_DIR. On the first run, when no database exists yet, it syncs and waits for a save. The tests also log through env_logger, so RUST_LOG shows smoldot's own timeline when a measurement needs explaining. * Fall back to the embedded light client when every Zagros RPC node fails ChainConnection stays on RPC first. A light client loads the peer-to-peer nodes, and those nodes have a fixed number of light client slots, so RPC is the default. When every RPC node of a supported chain has failed in a row, the connection moves to the light client, served at ws://127.0.0.1:<port>/<token>/<genesis>. RPC is retried after one minute. The wait doubles up to 15 minutes while RPC keeps failing. The connection returns to RPC as soon as a retry connects. If the light client cannot start, or its own connection fails, the connection stays on RPC. LightFallbackPolicy holds these rules. Its tests cover each one, and five mutations of the policy each turn a test red. ChainConnection handles RPC node changes, socket states and a 15 s tick as one event stream, one event at a time, so a node change and a fallback cannot race. LightClients starts the client on first use. A wallet whose RPC answers never runs it. LightClients registers Zagros and Zagros People, the chains the fork can verify, from specifications bundled at build time from pezkuwi-wallet-utils light-client/: the genesis state root, the /wss boot nodes on 443 first, and the relay checkpoint. Peer names resolve through EncryptedFirstDns, which CommonApi now exposes to the runtime module. * pezkuwi-light: strip in the cargo profile The Android Gradle plugin packages a native library it cannot strip as it is. With debug info, libpezkuwi_light.so is 147 MB per ABI. Stripped it is 8.6 MB. The first CI build of this library ran out of disk: four targets with debug info filled the runner. Stripping in the release profile, with debug info off, fixes both: - The library is 8.6 MB for arm64 and keeps its three JNI symbols. - The build directory is 389 MB per target. * Log the light client fallback's decisions Each RPC failure the fallback sees, the light client URL it got, and each RPC retry are logged under LightFallback. The first device run on a blocked Zagros RPC showed no fallback and no trace of where it stopped. |