From 43ca9d6a7a311d9232f3635369a5610ab2c63b53 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 3 Nov 2026 03:37:13 +0400 Subject: [PATCH 1/6] 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. --- app/build.gradle | 4 ++ bindings/pezkuwi-light/rust/Cargo.toml | 8 ++-- .../MultiLocationEncodingTest.kt | 42 +++++++++++++++++++ .../nova/runtime/ext/ChainExt.kt | 2 +- 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt diff --git a/app/build.gradle b/app/build.gradle index 92958d34..c5e1438d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,10 @@ apply from: "../scripts/versions.gradle" apply from: "../scripts/secrets.gradle" android { + // The NDK that CI installs and the bindings build with. Without it AGP expects its default NDK, + // cannot strip the native libraries: they ship with their debug sections and no native-debug-symbols.zip is made. + ndkVersion "26.1.10909125" + defaultConfig { applicationId rootProject.applicationId versionCode computeVersionCode() diff --git a/bindings/pezkuwi-light/rust/Cargo.toml b/bindings/pezkuwi-light/rust/Cargo.toml index 720553aa..64a0a93d 100644 --- a/bindings/pezkuwi-light/rust/Cargo.toml +++ b/bindings/pezkuwi-light/rust/Cargo.toml @@ -31,11 +31,11 @@ android_logger = { version = "0.14", default-features = false } env_logger = { version = "0.11", default-features = false, features = ["humantime"] } tungstenite = "0.24" -# Stripped here, not left to the Android Gradle plugin: it packages a library it cannot strip as -# it is, and with debug info this one is 147 MB per ABI instead of 8.6 MB. +# Line tables only: enough for the Play native-debug-symbols.zip to give file and line, at 59 MB +# per ABI before the app strips it to 8.6 MB. Full debug info (`debug = 2`) is 147 MB per ABI. [profile.release] -strip = true -debug = false +strip = false +debug = "line-tables-only" lto = true opt-level = "s" diff --git a/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt b/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt new file mode 100644 index 00000000..ba3d9233 --- /dev/null +++ b/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt @@ -0,0 +1,42 @@ +package io.novafoundation.nova.feature_xcm_api.multiLocation + +import io.novafoundation.nova.common.utils.HexString +import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.GlobalConsensus +import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.NetworkId +import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion +import io.novasama.substrate_sdk_android.extensions.toHexString +import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum +import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct +import org.junit.Assert.assertEquals +import org.junit.Test + +class MultiLocationEncodingTest { + + // PezkuwiChain runtimes declare `ThisNetwork = ByGenesis(..)`, and XCM v5 has no named variant for them + @Test + fun `zagros is encoded by its genesis`() { + assertEncodedByGenesis(ZAGROS) + } + + @Test + fun `pezkuwi is encoded by its genesis`() { + assertEncodedByGenesis(PEZKUWI) + } + + private fun assertEncodedByGenesis(genesis: HexString) { + val location = RelativeMultiLocation(parents = 1, interior = Junctions(GlobalConsensus(NetworkId.Substrate(genesis)))) + + val encoded = location.toEncodableInstanceExt(XcmVersion.V5) as Struct.Instance + val junction = ((encoded["interior"] as DictEnum.Entry<*>).value as List<*>).single() as DictEnum.Entry<*> + val network = junction.value as DictEnum.Entry<*> + + assertEquals("GlobalConsensus", junction.name) + assertEquals("ByGenesis", network.name) + assertEquals(genesis, (network.value as ByteArray).toHexString(withPrefix = false)) + } + + private companion object { + const val ZAGROS = "1b0b4727bbb5e44ed587d1056b4ad537ffb330034509a58866a088d77e792cd5" + const val PEZKUWI = "1aa94987791a5544e9667ec249d2cef1b8fdd6083c85b93fc37892d54a1156ca" + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt index 97a1d7e7..68b2f00b 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt @@ -462,7 +462,7 @@ object ChainGeneses { const val POLKADOT = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3" // Westend constant now points to Zagros Testnet - const val WESTEND = "96eb58af1bb7288115b5e4ff1590422533e749293f231974536dc6672417d06f" + const val WESTEND = "1b0b4727bbb5e44ed587d1056b4ad537ffb330034509a58866a088d77e792cd5" const val KUSAMA_ASSET_HUB = "48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a" From 8b0be959e440f227783d9ebf8797259d18a6cdfa Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 3 Nov 2026 03:46:12 +0400 Subject: [PATCH 2/6] Fix the encoding test's read of the interior field --- .../multiLocation/MultiLocationEncodingTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt b/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt index ba3d9233..67ac556e 100644 --- a/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt +++ b/feature-xcm/api/src/test/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncodingTest.kt @@ -6,7 +6,6 @@ import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Networ import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion import io.novasama.substrate_sdk_android.extensions.toHexString import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum -import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct import org.junit.Assert.assertEquals import org.junit.Test @@ -26,8 +25,9 @@ class MultiLocationEncodingTest { private fun assertEncodedByGenesis(genesis: HexString) { val location = RelativeMultiLocation(parents = 1, interior = Junctions(GlobalConsensus(NetworkId.Substrate(genesis)))) - val encoded = location.toEncodableInstanceExt(XcmVersion.V5) as Struct.Instance - val junction = ((encoded["interior"] as DictEnum.Entry<*>).value as List<*>).single() as DictEnum.Entry<*> + val encoded = location.toEncodableInstanceExt(XcmVersion.V5) + val interior = requireNotNull(encoded.get>("interior")) + val junction = (interior.value as List<*>).single() as DictEnum.Entry<*> val network = junction.value as DictEnum.Entry<*> assertEquals("GlobalConsensus", junction.name) From 431dc63333761c22743ce4831caa0e0f3c9d425d Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 3 Nov 2026 03:56:34 +0400 Subject: [PATCH 3/6] 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. --- .../feature_xcm_api/multiLocation/MultiLocationEncoding.kt | 2 -- .../main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt | 3 --- 2 files changed, 5 deletions(-) diff --git a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt index 4d32399e..e0887705 100644 --- a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt +++ b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt @@ -112,7 +112,6 @@ private fun bindGlobalConsensusJunction(instance: Any?): Junction { "Polkadot" -> Junction.GlobalConsensus(NetworkId.Substrate(Chain.Geneses.POLKADOT)) "Kusama" -> Junction.GlobalConsensus(NetworkId.Substrate(Chain.Geneses.KUSAMA)) - "Westend" -> Junction.GlobalConsensus(NetworkId.Substrate(Chain.Geneses.WESTEND)) "Ethereum" -> { val chainId = bindInt(asDictEnum.value.castToStruct()["chain_id"]) Junction.GlobalConsensus(NetworkId.Ethereum(chainId)) @@ -189,7 +188,6 @@ private fun NetworkId.Substrate.toEncodableInstance(): Any { return when (genesisHash) { Chain.Geneses.POLKADOT -> DictEnum.Entry("Polkadot", null) Chain.Geneses.KUSAMA -> DictEnum.Entry("Kusama", null) - Chain.Geneses.WESTEND -> DictEnum.Entry("Westend", null) Chain.Ids.ETHEREUM -> DictEnum.Entry("Ethereum", null) else -> DictEnum.Entry("ByGenesis", genesisHash.fromHex()) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt index 68b2f00b..527366cf 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt @@ -461,9 +461,6 @@ object ChainGeneses { const val KUSAMA = "b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe" const val POLKADOT = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3" - // Westend constant now points to Zagros Testnet - const val WESTEND = "1b0b4727bbb5e44ed587d1056b4ad537ffb330034509a58866a088d77e792cd5" - const val KUSAMA_ASSET_HUB = "48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a" const val ACALA = "fc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c" From 9ef18c745f36d7b2448183df31099e6647ae3d83 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 3 Nov 2026 03:56:34 +0400 Subject: [PATCH 4/6] 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. --- .github/workflows/security.yml | 39 ++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 195c1e79..022cf72d 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -26,24 +26,51 @@ env: EHTERSCAN_API_KEY_ETHEREUM: ${{ secrets.EHTERSCAN_API_KEY_ETHEREUM }} INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} DWELLIR_API_KEY: ${{ secrets.DWELLIR_API_KEY }} + TRONGRID_API_KEY: ${{ secrets.TRONGRID_API_KEY }} WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} DEBUG_GOOGLE_OAUTH_ID: ${{ secrets.DEBUG_GOOGLE_OAUTH_ID }} RELEASE_GOOGLE_OAUTH_ID: ${{ secrets.RELEASE_GOOGLE_OAUTH_ID }} jobs: codeql: - name: CodeQL Analysis - runs-on: [self-hosted, wallet] - if: github.event_name == 'push' # Only run on push, not PRs (self-hosted security) + # Advanced setup, because default setup analyses Java without a build and leaves out every + # Kotlin file, which is nearly all of this app. + name: CodeQL (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: javascript-typescript + build-mode: none + - language: rust + build-mode: none + - language: java-kotlin + build-mode: manual steps: + - name: Free disk space + if: matrix.language == 'java-kotlin' + run: | + # The CodeQL bundle in the tool cache is kept: the init step below uses it. + sudo rm -rf /usr/share/dotnet /usr/share/swift /opt/ghc /usr/local/.ghcup \ + /usr/local/share/boost /usr/local/share/powershell \ + /usr/local/lib/node_modules /usr/local/julia* /opt/microsoft + sudo docker image prune -af > /dev/null 2>&1 || true + df -h / + - name: Checkout uses: actions/checkout@v4 - name: Install dependencies + if: matrix.language == 'java-kotlin' uses: ./.github/workflows/install/ - name: Set up DEV Google Services + if: matrix.language == 'java-kotlin' uses: davidSchuppa/base64Secret-toFile-action@v3 with: secret: ${{ secrets.CI_DEVELOP_GOOGLE_SERVICES }} @@ -53,15 +80,19 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: - languages: java-kotlin + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} - name: Build for CodeQL + if: matrix.build-mode == 'manual' run: ./gradlew assembleDebug -x test -x lint env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 + with: + category: /language:${{ matrix.language }} dependency-review: name: Dependency Review From 3792984abe0d7a50014f3527d0620618da4deb9b Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 3 Nov 2026 04:23:09 +0400 Subject: [PATCH 5/6] Use CodeQL Action v4, v3 is deprecated in December 2026 --- .github/workflows/security.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 022cf72d..ed4fe53a 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -78,7 +78,7 @@ jobs: destination-path: ./app/ - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -90,7 +90,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: /language:${{ matrix.language }} From b37251e8d54cc8b683b85e5945c55552fb79c7e3 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 3 Nov 2026 04:52:34 +0400 Subject: [PATCH 6/6] Build for CodeQL without Gradle and Kotlin daemons so the analysis has the memory --- .github/workflows/security.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ed4fe53a..28f87cbd 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -85,7 +85,11 @@ jobs: - name: Build for CodeQL if: matrix.build-mode == 'manual' - run: ./gradlew assembleDebug -x test -x lint + # No Gradle or Kotlin daemon is left running: the analysis after the build needs that memory, and + # with them alive the runner ran out of it during query evaluation. + run: | + ./gradlew assembleDebug -x test -x lint --no-daemon -Pkotlin.compiler.execution.strategy=in-process + free -m env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}