mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 13:45:48 +00:00
fix: cloud backup schema dropped Tron address/keypair on every round trip
WalletPublicInfo/WalletPrivateInfo had no tron field at all, so any wallet created via the (default) cloud-backup wallet creation flow, or restored from a cloud backup, silently lost its Tron address and keypair even though AccountSecretsFactory/SecretsMetaAccountLocalFactory derived them correctly moments earlier - this is why TRX/USDT-TRC20 never appeared for any wallet, new or old, confirmed via a device-side diagnostic dump of the actual DB row (tronAddress=NULL) after a fresh wallet creation. Also reserves (but does not wire up) solana/bitcoin fields in the same schema, since native derivation for those doesn't exist yet - adding them now means that work won't need another silent-drop-prone pass through this same schema later. Adds a regression test exercising the exact apply-diff path that lost the data, plus tron support in the shared cloud-backup test builder DSL.
This commit is contained in:
+22
-3
@@ -17,6 +17,8 @@ import io.novafoundation.nova.common.data.secrets.v2.publicKey
|
||||
import io.novafoundation.nova.common.data.secrets.v2.seed
|
||||
import io.novafoundation.nova.common.data.secrets.v2.substrateDerivationPath
|
||||
import io.novafoundation.nova.common.data.secrets.v2.substrateKeypair
|
||||
import io.novafoundation.nova.common.data.secrets.v2.tronDerivationPath
|
||||
import io.novafoundation.nova.common.data.secrets.v2.tronKeypair
|
||||
import io.novafoundation.nova.common.utils.filterNotNull
|
||||
import io.novafoundation.nova.common.utils.findById
|
||||
import io.novafoundation.nova.common.utils.mapToSet
|
||||
@@ -90,6 +92,7 @@ class RealLocalAccountsCloudBackupFacade(
|
||||
substrate = baseSecrets.getSubstrateBackupSecrets(),
|
||||
ethereum = baseSecrets.getEthereumBackupSecrets(),
|
||||
chainAccounts = emptyList(),
|
||||
tron = baseSecrets.getTronBackupSecrets(),
|
||||
)
|
||||
|
||||
return CloudBackup(
|
||||
@@ -357,6 +360,7 @@ class RealLocalAccountsCloudBackupFacade(
|
||||
substrate = prepareSubstrateBackupSecrets(baseSecrets, joinedMetaAccountInfo),
|
||||
ethereum = baseSecrets.getEthereumBackupSecrets(),
|
||||
chainAccounts = chainAccountsFromChainSecrets + chainAccountFromAdditionalSecrets,
|
||||
tron = baseSecrets.getTronBackupSecrets(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -466,7 +470,9 @@ class RealLocalAccountsCloudBackupFacade(
|
||||
substrateKeyPair = substrate?.keypair?.toLocalKeyPair() ?: return null,
|
||||
substrateDerivationPath = substrate?.derivationPath,
|
||||
ethereumKeypair = ethereum?.keypair?.toLocalKeyPair(),
|
||||
ethereumDerivationPath = ethereum?.derivationPath
|
||||
ethereumDerivationPath = ethereum?.derivationPath,
|
||||
tronKeypair = tron?.keypair?.toLocalKeyPair(),
|
||||
tronDerivationPath = tron?.derivationPath
|
||||
)
|
||||
}
|
||||
|
||||
@@ -479,6 +485,15 @@ class RealLocalAccountsCloudBackupFacade(
|
||||
)
|
||||
}
|
||||
|
||||
private fun EncodableStruct<MetaAccountSecrets>?.getTronBackupSecrets(): CloudBackup.WalletPrivateInfo.TronSecrets? {
|
||||
if (this == null) return null
|
||||
|
||||
return CloudBackup.WalletPrivateInfo.TronSecrets(
|
||||
keypair = tronKeypair?.toBackupKeypairSecrets() ?: return null,
|
||||
derivationPath = tronDerivationPath
|
||||
)
|
||||
}
|
||||
|
||||
private fun EncodableStruct<MetaAccountSecrets>?.getSubstrateBackupSecrets(): CloudBackup.WalletPrivateInfo.SubstrateSecrets? {
|
||||
if (this == null) return null
|
||||
|
||||
@@ -520,7 +535,9 @@ class RealLocalAccountsCloudBackupFacade(
|
||||
ethereumPublicKey = metaAccount.ethereumPublicKey,
|
||||
name = metaAccount.name,
|
||||
type = metaAccount.type.toBackupWalletType() ?: return null,
|
||||
chainAccounts = chainAccounts.mapToSet { chainAccount -> chainAccount.toBackupPublicChainAccount(chainsById) }
|
||||
chainAccounts = chainAccounts.mapToSet { chainAccount -> chainAccount.toBackupPublicChainAccount(chainsById) },
|
||||
tronAddress = metaAccount.tronAddress,
|
||||
tronPublicKey = metaAccount.tronPublicKey,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -542,7 +559,9 @@ class RealLocalAccountsCloudBackupFacade(
|
||||
isSelected = isSelected,
|
||||
position = accountPosition,
|
||||
status = MetaAccountLocal.Status.ACTIVE,
|
||||
typeExtras = null
|
||||
typeExtras = null,
|
||||
tronAddress = tronAddress,
|
||||
tronPublicKey = tronPublicKey,
|
||||
).also {
|
||||
if (localIdOverwrite != null) {
|
||||
it.id = localIdOverwrite
|
||||
|
||||
+68
@@ -7,6 +7,8 @@ import io.novafoundation.nova.common.data.secrets.v2.ChainAccountSecrets
|
||||
import io.novafoundation.nova.common.data.secrets.v2.MetaAccountSecrets
|
||||
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
|
||||
import io.novafoundation.nova.common.data.secrets.v2.entropy
|
||||
import io.novafoundation.nova.common.data.secrets.v2.tronDerivationPath
|
||||
import io.novafoundation.nova.common.data.secrets.v2.tronKeypair
|
||||
import io.novafoundation.nova.core.model.CryptoType
|
||||
import io.novafoundation.nova.core_db.dao.MetaAccountDao
|
||||
import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal
|
||||
@@ -536,6 +538,64 @@ class RealLocalAccountsCloudBackupFacadeTest {
|
||||
verifyEvent(expectedEvent)
|
||||
}
|
||||
|
||||
// Regression test for a wallet's Tron address/keypair being silently dropped on a cloud backup round trip:
|
||||
// WalletPublicInfo/WalletPrivateInfo had no tron field at all until this fix, so a backup written from a
|
||||
// wallet that genuinely had a Tron address, once applied back to local (e.g. after "clear all data" +
|
||||
// restore, or on a fresh install created via the cloud-backup-first-wallet flow), landed with
|
||||
// tronAddress/tronPublicKey/tronKeypair = null - found via real-device testing, not by this test.
|
||||
@Test
|
||||
fun shouldApplyAddAccountDiffWithTronSecrets() = runBlocking {
|
||||
LocalAccountsMocker.setupMocks(metaAccountDao) {}
|
||||
SecretStoreMocker.setupMocks(secretStore) {}
|
||||
|
||||
allChainsAreEvm(false)
|
||||
|
||||
val localBackup = buildTestCloudBackup {
|
||||
publicData { }
|
||||
privateData { }
|
||||
}
|
||||
|
||||
val bytes32 = bytes32of(0)
|
||||
val tronAddressBytes = bytes20of(1)
|
||||
val tronDerivationPath = "//44//195//0/0/0"
|
||||
|
||||
val cloudBackup = buildTestCloudBackup {
|
||||
publicData {
|
||||
wallet(walletUUid(0)) {
|
||||
substrateAccountId(bytes32)
|
||||
substrateCryptoType(CryptoType.SR25519)
|
||||
substratePublicKey(bytes32)
|
||||
|
||||
tronPublicKey(bytes32)
|
||||
tronAddress(tronAddressBytes)
|
||||
}
|
||||
}
|
||||
|
||||
privateData {
|
||||
wallet(walletUUid(0)) {
|
||||
entropy(bytes32)
|
||||
|
||||
substrate {
|
||||
seed(bytes32)
|
||||
keypair(KeyPairSecrets(bytes32, bytes32, bytes32))
|
||||
}
|
||||
|
||||
tron {
|
||||
derivationPath(tronDerivationPath)
|
||||
keypair(KeyPairSecrets(bytes32, bytes32, nonce = null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val diff = localBackup.localVsCloudDiff(cloudBackup, BackupDiffStrategy.overwriteLocal())
|
||||
|
||||
facade.applyBackupDiff(diff, cloudBackup)
|
||||
|
||||
verify(metaAccountDao).insertMetaAccount(metaAccountWithTronAddress(tronAddressBytes))
|
||||
verify(secretStore).putMetaAccountSecrets(eq(0), metaAccountSecretsWithTronDerivationPath(tronDerivationPath))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldApplyRemoveAccountDiff(): Unit = runBlocking {
|
||||
allChainsAreEvm(false)
|
||||
@@ -1205,6 +1265,14 @@ class RealLocalAccountsCloudBackupFacadeTest {
|
||||
return argThat { it.globallyUniqueId == id }
|
||||
}
|
||||
|
||||
private fun metaAccountWithTronAddress(tronAddress: ByteArray): MetaAccountLocal {
|
||||
return argThat { it.tronAddress.contentEquals(tronAddress) }
|
||||
}
|
||||
|
||||
private fun metaAccountSecretsWithTronDerivationPath(derivationPath: String): EncodableStruct<MetaAccountSecrets> {
|
||||
return argThat { it.tronDerivationPath == derivationPath && it.tronKeypair != null }
|
||||
}
|
||||
|
||||
private suspend fun verifyNoAdditionalSecretsInserted() {
|
||||
verify(secretStore, never()).putAdditionalMetaAccountSecret(anyLong(), any(), any())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user