fix: Era type encoding, CheckMortality cleanup, and release log guard

- Add Era AliasTo in PezkuwiPathTypeMapping for correct SCALE encoding
- Remove redundant isPezkuwi CheckMortality logic from ExtrinsicBuilderFactory
  and PolkadotExternalSignInteractor (standard path now works for all chains)
- Remove payload/signature hex logs from PezkuwiKeyPairSigner (security)
- Wrap debug logs with BuildConfig.DEBUG in PezkuwiKeyPairSigner,
  MetadataShortenerService, and WalletConnectSessionsEvent
This commit is contained in:
2026-02-24 04:12:12 +03:00
parent 8ed1909dc1
commit 7a1e7d8270
7 changed files with 50 additions and 79 deletions
@@ -39,8 +39,6 @@ import io.novafoundation.nova.runtime.ext.anyAddressToAccountId
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.extrinsic.CustomTransactionExtensions
import io.novafoundation.nova.runtime.extrinsic.extensions.ChargeAssetTxPayment.Companion.chargeAssetTxPayment
import io.novafoundation.nova.runtime.extrinsic.extensions.PezkuwiCheckImmortal
import io.novafoundation.nova.runtime.extrinsic.extensions.PezkuwiCheckMortality
import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
@@ -50,7 +48,6 @@ import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Era
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.EraType
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
import io.novasama.substrate_sdk_android.runtime.extrinsic.BatchMode
@@ -224,19 +221,9 @@ class PolkadotExternalSignInteractor(
val signingContext = signingContextFactory.default(chain)
val isPezkuwi = isPezkuwiChain(runtime)
val extrinsic = with(parsedExtrinsic) {
ExtrinsicBuilder(runtime, ExtrinsicVersion.V4, BatchMode.BATCH_ALL).apply {
// Use custom CheckMortality for Pezkuwi chains to avoid DictEnum type lookup issues
if (isPezkuwi) {
when (era) {
is Era.Mortal -> setTransactionExtension(PezkuwiCheckMortality(era, blockHash))
is Era.Immortal -> setTransactionExtension(PezkuwiCheckImmortal(genesisHash))
}
} else {
setTransactionExtension(CheckMortality(era, blockHash))
}
setTransactionExtension(CheckMortality(era, blockHash))
setTransactionExtension(CheckGenesis(genesisHash))
setTransactionExtension(ChargeTransactionPayment(tip))
setTransactionExtension(CheckMetadataHash(actualMetadataHash.checkMetadataHash))
@@ -363,11 +350,6 @@ class PolkadotExternalSignInteractor(
private fun PolkadotSignPayload.Json.tryDecodeAssetId(runtime: RuntimeSnapshot): Any? {
return assetId?.let(runtime::decodeCustomTxPaymentId)
}
private fun isPezkuwiChain(runtime: RuntimeSnapshot): Boolean {
val signedExtIds = runtime.metadata.extrinsic.signedExtensions.map { it.id }
return signedExtIds.any { it == "AuthorizeCall" }
}
}
private fun CheckMetadataHashMode(hash: ByteArray?): CheckMetadataHashMode {
@@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions
import android.util.Log
import com.walletconnect.web3.wallet.client.Wallet
import io.novafoundation.nova.feature_wallet_connect_impl.BuildConfig
import com.walletconnect.web3.wallet.client.Web3Wallet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -26,51 +27,51 @@ fun Web3Wallet.sessionEventsFlow(scope: CoroutineScope): Flow<WalletConnectSessi
setWalletDelegate(object : Web3Wallet.WalletDelegate {
override fun onAuthRequest(authRequest: Wallet.Model.AuthRequest, verifyContext: Wallet.Model.VerifyContext) {
Log.d("WalletConnect", "Auth request: $authRequest")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "Auth request: $authRequest")
}
override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) {
Log.d("WalletConnect", "on connection state change: $state")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "on connection state change: $state")
}
override fun onError(error: Wallet.Model.Error) {
Log.e("WalletConnect", "Wallet Connect error", error.throwable)
if (BuildConfig.DEBUG) Log.e("WalletConnect", "Wallet Connect error", error.throwable)
}
override fun onProposalExpired(proposal: Wallet.Model.ExpiredProposal) {
Log.d("WalletConnect", "Proposal expired: $proposal")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "Proposal expired: $proposal")
}
override fun onRequestExpired(request: Wallet.Model.ExpiredRequest) {
Log.d("WalletConnect", "Request expired: $request")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "Request expired: $request")
}
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
Log.d("WalletConnect", "on session delete: $sessionDelete")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "on session delete: $sessionDelete")
channel.trySend(WalletConnectSessionsEvent.SessionDeleted(sessionDelete))
}
override fun onSessionExtend(session: Wallet.Model.Session) {
Log.d("WalletConnect", "On session extend: $session")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "On session extend: $session")
}
override fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal, verifyContext: Wallet.Model.VerifyContext) {
Log.d("WalletConnect", "on session proposal: $sessionProposal")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "on session proposal: $sessionProposal")
channel.trySend(WalletConnectSessionsEvent.SessionProposal(sessionProposal))
}
override fun onSessionRequest(sessionRequest: Wallet.Model.SessionRequest, verifyContext: Wallet.Model.VerifyContext) {
Log.d("WalletConnect", "on session request: $sessionRequest")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "on session request: $sessionRequest")
channel.trySend(WalletConnectSessionsEvent.SessionRequest(sessionRequest))
}
override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
Log.d("WalletConnect", "on session settled: $settleSessionResponse")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "on session settled: $settleSessionResponse")
channel.trySend(WalletConnectSessionsEvent.SessionSettlement(settleSessionResponse))
}
override fun onSessionUpdateResponse(sessionUpdateResponse: Wallet.Model.SessionUpdateResponse) {
Log.d("WalletConnect", "on session update: $sessionUpdateResponse")
if (BuildConfig.DEBUG) Log.d("WalletConnect", "on session update: $sessionUpdateResponse")
}
})
@@ -2,13 +2,11 @@ package io.novafoundation.nova.runtime.extrinsic
import io.novafoundation.nova.common.utils.orZero
import io.novafoundation.nova.runtime.ext.requireGenesisHash
import io.novafoundation.nova.runtime.extrinsic.extensions.PezkuwiCheckMortality
import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.extrinsic.BatchMode
import io.novasama.substrate_sdk_android.runtime.extrinsic.ExtrinsicVersion
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
@@ -45,20 +43,13 @@ class ExtrinsicBuilderFactory(
val mortality = mortalityConstructor.constructMortality(chain.id)
val metadataProof = metadataShortenerService.generateMetadataProof(chain.id)
val isPezkuwi = isPezkuwiChain(runtime)
return generateSequence {
ExtrinsicBuilder(
runtime = runtime,
extrinsicVersion = ExtrinsicVersion.V4,
batchMode = options.batchMode,
).apply {
// Use custom CheckMortality for Pezkuwi chains to avoid type lookup issues
if (isPezkuwi) {
setTransactionExtension(PezkuwiCheckMortality(mortality.era, mortality.blockHash.fromHex()))
} else {
setTransactionExtension(CheckMortality(mortality.era, mortality.blockHash.fromHex()))
}
setTransactionExtension(CheckMortality(mortality.era, mortality.blockHash.fromHex()))
setTransactionExtension(CheckGenesis(chain.requireGenesisHash().fromHex()))
setTransactionExtension(ChargeTransactionPayment(chain.additional?.defaultTip.orZero()))
setTransactionExtension(CheckMetadataHash(metadataProof.checkMetadataHash))
@@ -69,9 +60,4 @@ class ExtrinsicBuilderFactory(
}
}
}
private fun isPezkuwiChain(runtime: RuntimeSnapshot): Boolean {
val signedExtIds = runtime.metadata.extrinsic.signedExtensions.map { it.id }
return signedExtIds.any { it == "AuthorizeCall" }
}
}
@@ -2,6 +2,7 @@ package io.novafoundation.nova.runtime.extrinsic.metadata
import android.util.Log
import io.novafoundation.nova.common.utils.hasSignedExtension
import io.novafoundation.nova.runtime.BuildConfig
import io.novafoundation.nova.metadata_shortener.MetadataShortener
import io.novafoundation.nova.runtime.ext.shouldDisableMetadataHashCheck
import io.novafoundation.nova.runtime.ext.utilityAsset
@@ -173,15 +174,22 @@ internal class RealMetadataShortenerService(
val atLeastMinimumVersion = runtimeMetadata.metadataVersion >= MINIMUM_METADATA_VERSION_TO_CALCULATE_HASH
val hasSignedExtension = runtimeMetadata.extrinsic.hasSignedExtension(DefaultSignedExtensions.CHECK_METADATA_HASH)
Log.d(
"MetadataShortenerService",
"Chain: ${chain.name}, disabledByConfig=$disabledByConfig, canBeEnabled=$canBeEnabled, " +
"atLeastMinimumVersion=$atLeastMinimumVersion, hasSignedExtension=$hasSignedExtension"
)
Log.d("MetadataShortenerService", "chain.additional: ${chain.additional}, disabledCheckMetadataHash=${chain.additional?.disabledCheckMetadataHash}")
if (BuildConfig.DEBUG) {
Log.d(
"MetadataShortenerService",
"Chain: ${chain.name}, disabledByConfig=$disabledByConfig, canBeEnabled=$canBeEnabled, " +
"atLeastMinimumVersion=$atLeastMinimumVersion, hasSignedExtension=$hasSignedExtension"
)
Log.d("MetadataShortenerService", "chain.additional: ${chain.additional}, disabledCheckMetadataHash=${chain.additional?.disabledCheckMetadataHash}")
}
val result = canBeEnabled && atLeastMinimumVersion && hasSignedExtension
Log.d("MetadataShortenerService", "shouldCalculateMetadataHash result: $result (will use ${if (result) "ENABLED" else "DISABLED"} mode)")
if (BuildConfig.DEBUG) {
Log.d(
"MetadataShortenerService",
"shouldCalculateMetadataHash result: $result (will use ${if (result) "ENABLED" else "DISABLED"} mode)"
)
}
return result
}
}
@@ -1,6 +1,7 @@
package io.novafoundation.nova.runtime.extrinsic.signer
import android.util.Log
import io.novafoundation.nova.runtime.BuildConfig
import io.novafoundation.nova.sr25519.BizinikiwSr25519
import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
import io.novasama.substrate_sdk_android.runtime.AccountId
@@ -30,23 +31,20 @@ class PezkuwiKeyPairSigner private constructor(
fun fromSeed(seed: ByteArray): PezkuwiKeyPairSigner {
require(seed.size == 32) { "Seed must be 32 bytes, got ${seed.size}" }
Log.d("PezkuwiSigner", "Creating signer from seed")
if (BuildConfig.DEBUG) Log.d("PezkuwiSigner", "Creating signer from seed")
// Expand seed to 96-byte keypair
val expandedKeypair = BizinikiwSr25519.keypairFromSeed(seed)
Log.d("PezkuwiSigner", "Expanded keypair size: ${expandedKeypair.size}")
if (BuildConfig.DEBUG) Log.d("PezkuwiSigner", "Expanded keypair size: ${expandedKeypair.size}")
// Extract 64-byte secret key and 32-byte public key
val secretKey = BizinikiwSr25519.secretKeyFromKeypair(expandedKeypair)
val publicKey = BizinikiwSr25519.publicKeyFromKeypair(expandedKeypair)
Log.d("PezkuwiSigner", "Secret key size: ${secretKey.size}")
Log.d("PezkuwiSigner", "Public key: ${publicKey.toHex()}")
if (BuildConfig.DEBUG) Log.d("PezkuwiSigner", "Secret key size: ${secretKey.size}")
return PezkuwiKeyPairSigner(secretKey, publicKey)
}
private fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) }
}
override suspend fun signInheritedImplication(
@@ -55,9 +53,10 @@ class PezkuwiKeyPairSigner private constructor(
): SignatureWrapper {
val payload = inheritedImplication.signingPayload()
Log.d("PezkuwiSigner", "=== SIGNING WITH BIZINIKIWI ===")
Log.d("PezkuwiSigner", "Payload size: ${payload.size}")
Log.d("PezkuwiSigner", "Payload: ${payload.toHex()}")
if (BuildConfig.DEBUG) {
Log.d("PezkuwiSigner", "=== SIGNING WITH BIZINIKIWI ===")
Log.d("PezkuwiSigner", "Payload size: ${payload.size}")
}
// Use BizinikiwSr25519 native library with "bizinikiwi" signing context
val signature = BizinikiwSr25519.sign(
@@ -66,17 +65,13 @@ class PezkuwiKeyPairSigner private constructor(
message = payload
)
Log.d("PezkuwiSigner", "Signature: ${signature.toHex()}")
// Verify locally
val verified = BizinikiwSr25519.verify(signature, payload, publicKey)
Log.d("PezkuwiSigner", "Local verification: $verified")
if (BuildConfig.DEBUG) Log.d("PezkuwiSigner", "Local verification: $verified")
return SignatureWrapper.Sr25519(signature)
}
private fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) }
suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw {
// Use BizinikiwSr25519 native library with "bizinikiwi" signing context
val signature = BizinikiwSr25519.sign(
@@ -207,7 +207,6 @@ class RuntimeFactory(
"pezsp_runtime.multiaddress.MultiAddress" to "MultiAddress",
"pezsp_runtime.MultiSignature" to "ExtrinsicSignature",
"pezsp_runtime.MultiSignature" to "MultiSignature",
"pezsp_runtime.generic.era.Era" to "Era",
// Fee-related types
"pezframe_support.dispatch.DispatchInfo" to "DispatchInfo",
"pezpallet_transaction_payment.types.RuntimeDispatchInfo" to "RuntimeDispatchInfo",
@@ -7,25 +7,25 @@ import io.novasama.substrate_sdk_android.runtime.definitions.v14.typeMapping.Pat
* PathMatchTypeMapping for Pezkuwi chains that use pezsp_* and pezframe_* package prefixes
* instead of the standard sp_* and frame_* prefixes used by Polkadot/Substrate chains.
*
* This maps specific Pezkuwi type paths to standard type names:
* - RuntimeCall/RuntimeEvent -> GenericCall/GenericEvent
* This maps specific Pezkuwi type paths to standard type names during metadata parsing.
* Aliasing at parse time ensures the type ID mapping also uses the correct type.
*
* IMPORTANT: Era, MultiSignature, MultiAddress, and Weight types are NOT aliased here.
* They need to be parsed as actual types from metadata. RuntimeFactory.addPezkuwiTypeAliases()
* handles copying these types to standard names after parsing.
* Era MUST be aliased here (not in RuntimeFactory.addPezkuwiTypeAliases) because the built-in
* EraType has special encode/decode logic for Era.Mortal/Era.Immortal that the raw DictEnum
* from metadata doesn't support. Aliasing at parse time ensures the type ID resolves correctly.
*
* NOTE: Weight types (pezsp_weights.weight_v2.Weight) are NOT aliased because the SDK
* doesn't have a WeightV1 type defined. They are parsed as structs from metadata.
* MultiSignature, MultiAddress: NOT aliased here — their DictEnum versions from metadata are
* structurally identical to built-ins. RuntimeFactory.addPezkuwiTypeAliases() copies them.
*
* Weight types: NOT aliased — SDK doesn't have a WeightV1 type. Parsed as structs from metadata.
*/
fun PezkuwiPathTypeMapping(): PathMatchTypeMapping = PathMatchTypeMapping(
// NOTE: Do NOT alias pezsp_runtime.generic.era.Era, pezsp_runtime.MultiSignature,
// pezsp_runtime.multiaddress.MultiAddress, or pezsp_weights.weight_v2.Weight here.
// These need to be parsed as actual types from metadata.
// RuntimeFactory.addPezkuwiTypeAliases() copies the parsed types to standard names.
// Runtime call/event types for Pezkuwi
"*.RuntimeCall" to AliasTo("GenericCall"),
"*.RuntimeEvent" to AliasTo("GenericEvent"),
"*_runtime.Call" to AliasTo("GenericCall"),
"*_runtime.Event" to AliasTo("GenericEvent"),
// Era: alias to built-in EraType so ExtrinsicBuilder can encode Era.Mortal/Era.Immortal
"pezsp_runtime.generic.era.Era" to AliasTo("Era"),
)