From fa179681087834aaeee6ea2fdaa1f1cedbec6419 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sun, 15 Feb 2026 22:25:48 +0300 Subject: [PATCH] fix: resolve parentId for Asset Hub staking to relay chain ValidatorProvider, DirectStakingProperties, and DirectStakingRecommendation were querying Asset Hub chainId for staking data (validators, exposures, minStake, maxNominations) but these live on the relay chain. Added chain.parentId resolution so parachain staking correctly routes to relay. Also: - Add VoterBagsList pallet support (Pezkuwi naming) - Wrap BagListRepository queries in runCatching for binding compat - Remove debug logging --- .../nova/common/utils/SubstrateSdkExt.kt | 3 ++- .../data/signer/proxy/ProxyCallFilterFactory.kt | 2 ++ .../data/repository/BagListRepository.kt | 16 ++++++++++------ .../domain/rewards/RewardCalculatorFactory.kt | 16 ---------------- .../direct/DirectStakingProperties.kt | 6 ++++-- .../direct/DirectStakingRecommendation.kt | 3 ++- .../domain/validators/ValidatorProvider.kt | 10 ++++++---- pezkuwi-config/chains.json | 16 ++++++++-------- 8 files changed, 34 insertions(+), 38 deletions(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt index c756602..932a29e 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt @@ -257,7 +257,7 @@ fun Module.constantOrNull(name: String) = constants[name] fun RuntimeMetadata.staking() = module(Modules.STAKING) -fun RuntimeMetadata.voterListOrNull() = firstExistingModuleOrNull(Modules.VOTER_LIST, Modules.BAG_LIST) +fun RuntimeMetadata.voterListOrNull() = firstExistingModuleOrNull(Modules.VOTER_LIST, Modules.BAG_LIST, Modules.VOTER_BAGS_LIST) fun RuntimeMetadata.voterListName(): String = requireNotNull(voterListOrNull()).name fun RuntimeMetadata.system() = module(Modules.SYSTEM) @@ -632,6 +632,7 @@ object Modules { const val VOTER_LIST = "VoterList" const val BAG_LIST = "BagsList" + const val VOTER_BAGS_LIST = "VoterBagsList" const val ELECTION_PROVIDER_MULTI_PHASE = "ElectionProviderMultiPhase" diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt index 648adbd..114d31b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt @@ -42,6 +42,7 @@ class ProxyCallFilterFactory { WhiteListFilter(Modules.SLOTS), WhiteListFilter(Modules.AUCTIONS), WhiteListFilter(Modules.VOTER_LIST), + WhiteListFilter(Modules.VOTER_BAGS_LIST), WhiteListFilter(Modules.NOMINATION_POOLS), WhiteListFilter(Modules.FAST_UNSTAKE) ) @@ -62,6 +63,7 @@ class ProxyCallFilterFactory { WhiteListFilter(Modules.UTILITY), WhiteListFilter(Modules.FAST_UNSTAKE), WhiteListFilter(Modules.VOTER_LIST), + WhiteListFilter(Modules.VOTER_BAGS_LIST), WhiteListFilter(Modules.NOMINATION_POOLS) ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt index 54e0978..92d2484 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt @@ -42,15 +42,19 @@ class LocalBagListRepository( ) : BagListRepository { override suspend fun bagThresholds(chainId: ChainId): List? { - return chainRegistry.withRuntime(chainId) { - runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score)) - } + return runCatching { + chainRegistry.withRuntime(chainId) { + runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score)) + } + }.getOrNull() } override suspend fun bagListSize(chainId: ChainId): BigInteger? { - return localStorage.query(chainId) { - runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber) - } + return runCatching { + localStorage.query(chainId) { + runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber) + } + }.getOrNull() } override suspend fun maxElectingVotes(chainId: ChainId): BigInteger? { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt index 90f2f30..0546e62 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt @@ -51,10 +51,6 @@ class RewardCalculatorFactory( val stakingChainId = stakingOption.assetWithChain.chain.parentId ?: stakingOption.assetWithChain.chain.id val totalIssuance = totalIssuanceRepository.getTotalIssuance(stakingChainId) - Log.d("PEZ_STAKING", "create(4-param) exposures=${exposures.size} validatorsPrefs=${validatorsPrefs.size}") - Log.d("PEZ_STAKING", "exposureKeys=${exposures.keys.take(3).map { it.take(16) }}") - Log.d("PEZ_STAKING", "prefKeys=${validatorsPrefs.keys.take(3).map { it.take(16) }}") - val validators = exposures.keys.mapNotNull { accountIdHex -> val exposure = exposures[accountIdHex] ?: accountIdNotFound(accountIdHex) val validatorPrefs = validatorsPrefs[accountIdHex] ?: return@mapNotNull null @@ -66,8 +62,6 @@ class RewardCalculatorFactory( ) } - Log.d("PEZ_STAKING", "totalIssuance=$totalIssuance validators=${validators.size} stakingChainId=${stakingChainId.take(12)}") - stakingOption.createRewardCalculator(validators, totalIssuance, stakingChainId, scope) } @@ -77,21 +71,11 @@ class RewardCalculatorFactory( // For parachains with a parent relay chain, staking exposures live on the relay chain val exposureChainId = chain.parentId ?: chainId - Log.d( - "PEZ_STAKING", - "RewardCalculatorFactory.create() chainId=${chainId.take(12)}" + - " exposureChainId=${exposureChainId.take(12)}" + - " stakingType=${stakingOption.additional.stakingType}" - ) - val activeEra = stakingRepository.getActiveEraIndex(exposureChainId) - Log.d("PEZ_STAKING", "ActiveEra: $activeEra for ${exposureChainId.take(12)}") val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra) - Log.d("PEZ_STAKING", "Exposures: ${exposures.size}") val validatorsPrefs = stakingRepository.getValidatorPrefs(exposureChainId, exposures.keys) - Log.d("PEZ_STAKING", "ValidatorPrefs: ${validatorsPrefs.size}") create(stakingOption, exposures, validatorsPrefs, scope) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt index 7ad13a1..ae82c35 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt @@ -98,8 +98,10 @@ private class DirectStakingProperties( enoughAvailableToStake() } + private val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id + override suspend fun minStake(): Balance { - return stakingSharedComputation.minStake(stakingOption.chain.id, scope) + return stakingSharedComputation.minStake(stakingChainId, scope) } private fun StartMultiStakingValidationSystemBuilder.noConflictingStaking() { @@ -125,7 +127,7 @@ private class DirectStakingProperties( private fun StartMultiStakingValidationSystemBuilder.maximumNominatorsReached() { maximumNominatorsReached( stakingRepository = stakingRepository, - chainId = { stakingOption.chain.id }, + chainId = { stakingChainId }, errorProducer = { StartMultiStakingValidationFailure.MaxNominatorsReached(stakingType) } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt index 8125696..56f1ad1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt @@ -29,7 +29,8 @@ class DirectStakingRecommendation( override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection { val provider = recommendationSettingsProvider.await() - val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingOption.chain.id, stake) + val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id + val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingChainId, stake) val recommendationSettings = provider.recommendedSettings(maximumValidatorsPerNominator) val recommendator = recommendator.await() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt index 9354cb6..64f483b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt @@ -45,20 +45,22 @@ class ValidatorProvider( ): List { val chain = stakingOption.assetWithChain.chain val chainId = chain.id + // For parachains (e.g. Asset Hub), staking validators live on the parent relay chain + val stakingChainId = chain.parentId ?: chainId val novaValidatorIds = validatorsPreferencesSource.getRecommendedValidatorIds(chainId) - val electedValidatorExposures = stakingSharedComputation.electedExposuresInActiveEra(chainId, scope) + val electedValidatorExposures = stakingSharedComputation.electedExposuresInActiveEra(stakingChainId, scope) val requestedValidatorIds = sources.allValidatorIds(chainId, electedValidatorExposures, novaValidatorIds) // we always need validator prefs for elected validators to construct reward calculator val validatorIdsToQueryPrefs = electedValidatorExposures.keys + requestedValidatorIds - val validatorPrefs = stakingRepository.getValidatorPrefs(chainId, validatorIdsToQueryPrefs) + val validatorPrefs = stakingRepository.getValidatorPrefs(stakingChainId, validatorIdsToQueryPrefs) val identities = identityRepository.getIdentitiesFromIdsHex(chainId, requestedValidatorIds) - val slashes = stakingRepository.getSlashes(chain.id, requestedValidatorIds) + val slashes = stakingRepository.getSlashes(stakingChainId, requestedValidatorIds) val rewardCalculator = rewardCalculatorFactory.create(stakingOption, electedValidatorExposures, validatorPrefs, scope) - val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId) + val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(stakingChainId) return requestedValidatorIds.map { accountIdHex -> val accountId = AccountIdKey.fromHex(accountIdHex).getOrThrow() diff --git a/pezkuwi-config/chains.json b/pezkuwi-config/chains.json index 1921da9..b87caaf 100644 --- a/pezkuwi-config/chains.json +++ b/pezkuwi-config/chains.json @@ -1,6 +1,6 @@ [ { - "chainId": "bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75", + "chainId": "1aa94987791a5544e9667ec249d2cef1b8fdd6083c85b93fc37892d54a1156ca", "name": "Pezkuwi", "icon": "https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/main/icons/tokens/colored/HEZ.svg", "addressPrefix": 42, @@ -72,13 +72,13 @@ "feeViaRuntimeCall": true, "disabledCheckMetadataHash": true, "stakingMaxElectingVoters": 22500, - "identityChain": "58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8", + "identityChain": "69a8d025ab7b63363935d7d9397e0f652826c94271c1bc55c4fdfe72cccf1cfa", "stakingWiki": "https://wiki.pezkuwichain.io/staking" } }, { - "chainId": "00d0e1d0581c3cd5c5768652d52f4520184018b44f56a2ae1e0dc9d65c00c948", - "parentId": "bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75", + "chainId": "e7c15092dcbe3f320260ddbbc685bfceed9125a3b3d8436db2766201dec3b949", + "parentId": "1aa94987791a5544e9667ec249d2cef1b8fdd6083c85b93fc37892d54a1156ca", "name": "Pezkuwi Asset Hub", "icon": "https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/main/icons/tokens/colored/PEZ.svg", "addressPrefix": 42, @@ -225,15 +225,15 @@ "disabledCheckMetadataHash": true, "relaychainAsNative": true, "stakingMaxElectingVoters": 22500, - "identityChain": "58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8", - "timelineChain": "bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75", + "identityChain": "69a8d025ab7b63363935d7d9397e0f652826c94271c1bc55c4fdfe72cccf1cfa", + "timelineChain": "1aa94987791a5544e9667ec249d2cef1b8fdd6083c85b93fc37892d54a1156ca", "defaultBlockTime": 6000, "stakingWiki": "https://wiki.pezkuwichain.io/staking" } }, { - "chainId": "58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8", - "parentId": "bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75", + "chainId": "69a8d025ab7b63363935d7d9397e0f652826c94271c1bc55c4fdfe72cccf1cfa", + "parentId": "1aa94987791a5544e9667ec249d2cef1b8fdd6083c85b93fc37892d54a1156ca", "name": "Pezkuwi People", "icon": "https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/main/icons/chains/PezkuwiPeople.png", "addressPrefix": 42,