From 6971acb56733e4688f02fa7b7ff00c7f9f7b453a Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 10:00:21 -0800 Subject: [PATCH 01/20] fix: remove metadataHandler and use coroutines to avoid ANR --- .../wallet/service/BlockchainServiceImpl.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt index bd3dee8192..b679614438 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt @@ -215,7 +215,6 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { private var peerGroup: PeerGroup? = null private val handler = Handler() private val delayHandler = Handler() - private val metadataHandler = Handler() private var wakeLock: PowerManager.WakeLock? = null private var peerConnectivityListener: PeerConnectivityListener? = null private var nm: NotificationManager? = null @@ -254,10 +253,8 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { CrowdNodeDepositReceivedResponse(Constants.NETWORK_PARAMETERS) private var apiConfirmationHandler: CrowdNodeAPIConfirmationHandler? = null private fun handleMetadata(tx: Transaction) { - metadataHandler.post { - transactionMetadataProvider.syncTransactionBlocking( - tx - ) + serviceScope.launch { + transactionMetadataProvider.syncTransaction(tx) } } @@ -708,7 +705,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { packageInfoProvider.packageInfo ) } - org.bitcoinj.core.Context.propagate(wallet.context) + propagateContext() dashSystemService.system.initDashSync(getDir("masternode", MODE_PRIVATE).absolutePath) log.info("starting peergroup") peerGroup = PeerGroup(Constants.NETWORK_PARAMETERS, blockChain, headerChain) @@ -1012,6 +1009,9 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { } private fun propagateContext() { + if (application.wallet?.context != Constants.CONTEXT) { + log.warn("wallet context does not equal Constants.CONTEXT") + } org.bitcoinj.core.Context.propagate(Constants.CONTEXT) } @@ -1330,6 +1330,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { throw RuntimeException(x) } if (!deleteWalletFileOnShutdown) { + propagateContext() application.saveWallet() } if (wakeLock!!.isHeld) { From 0a593bbcdf086ad3a85f2cca9170bc4dc437e4b8 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 10:01:18 -0800 Subject: [PATCH 02/20] fix: handle several username creation bugs --- .../ui/dashpay/CreateIdentityService.kt | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt index f3fbb972db..7edaae1a9f 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt @@ -9,6 +9,7 @@ import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.data.CoinJoinConfig +import de.schildbach.wallet.data.CreditBalanceInfo import de.schildbach.wallet.data.InvitationLinkData import de.schildbach.wallet.database.dao.UserAlertDao import de.schildbach.wallet.database.dao.UsernameRequestDao @@ -318,10 +319,12 @@ class CreateIdentityService : LifecycleService() { } if (blockchainIdentityData.creationState == CreationState.USERNAME_REGISTERING) { - if (blockchainIdentityData.creationStateErrorMessage?.contains("preorderDocument was not found with a salted domain hash") == true) { + val errorMessage = blockchainIdentityData.creationStateErrorMessage ?: "" + if (errorMessage.contains("preorderDocument was not found with a salted domain hash") || + errorMessage.contains("cannot find preorder document, though it should be somewhere")) { blockchainIdentityData.creationState = CreationState.PREORDER_REGISTERING platformRepo.updateBlockchainIdentityData(blockchainIdentityData) - } else if (blockchainIdentityData.creationStateErrorMessage?.contains("missing domain document for") == true) { + } else if (errorMessage.contains("missing domain document for")) { blockchainIdentityData.creationState = CreationState.PREORDER_REGISTERING platformRepo.updateBlockchainIdentityData(blockchainIdentityData) } else if (retryWithNewUserName) { @@ -374,26 +377,28 @@ class CreateIdentityService : LifecycleService() { assetLockTransaction = blockchainIdentity.assetLockTransaction } } else { - val balanceInfo = platformRepo.getIdentityBalance() - val balanceRequirement = if (Names.isUsernameContestable(blockchainIdentityData.username!!)) { - Constants.DASH_PAY_FEE_CONTESTED + // don't use platformRepo.getIdentityBalance() because platformRepo.blockchainIdentity is not initialized + val balanceInfo = blockchainIdentityData.identity?.let { platformRepo.getIdentityBalance(it.id) } + ?: CreditBalanceInfo(0L) + val balanceRequirement = if (Names.isUsernameContestable(blockchainIdentityData.username!!)) { + Constants.DASH_PAY_FEE_CONTESTED + } else { + Constants.DASH_PAY_FEE + } + + if (balanceInfo.balance < balanceRequirement.value * 1000) { + val topupValue = if (Names.isUsernameContestable(blockchainIdentityData.username!!)) { + Constants.DASH_PAY_FEE_CONTESTED_NAME } else { Constants.DASH_PAY_FEE } - - if (balanceInfo.balance < balanceRequirement.value * 1000) { - val topupValue = if (Names.isUsernameContestable(blockchainIdentityData.username!!)) { - Constants.DASH_PAY_FEE_CONTESTED_NAME - } else { - Constants.DASH_PAY_FEE - } - assetLockTransaction = topUpRepository.createTopupTransaction( - blockchainIdentity, - topupValue, - encryptionKey, - useCoinJoin - ) - } + assetLockTransaction = topUpRepository.createTopupTransaction( + blockchainIdentity, + topupValue, + encryptionKey, + useCoinJoin + ) + } } } From 4fb3b50da6b346039233ab85fdf598769cd08d01 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 10:01:59 -0800 Subject: [PATCH 03/20] fix: handle LockScreenActivity ANR with backup reminder --- wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt index 8e14e66d08..ae1fa3d9d2 100644 --- a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt @@ -53,6 +53,7 @@ import de.schildbach.wallet.ui.verify.VerifySeedActivity import de.schildbach.wallet.ui.widget.PinPreviewView import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivityLockScreenRootBinding +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.bitcoinj.wallet.Wallet.BalanceType import org.dash.wallet.common.Configuration @@ -173,9 +174,11 @@ open class LockScreenActivity : SecureActivity() { } private fun setupBackupSeedReminder() { - val hasBalance = walletData.wallet?.getBalance(BalanceType.ESTIMATED)?.isPositive ?: false - if (hasBalance && configuration.lastBackupSeedTime == 0L) { - configuration.setLastBackupSeedTime() + lifecycleScope.launch { + val hasBalance = walletApplication.observeBalance().first().isPositive + if (hasBalance && configuration.lastBackupSeedTime == 0L) { + configuration.setLastBackupSeedTime() + } } } From a037f1e46695c8821ae48b86e11bd0902c207238 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 10:02:26 -0800 Subject: [PATCH 04/20] fix: sort transaction group tx's by date --- .../wallet/ui/transactions/TransactionGroupViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt index a49f9977d6..70a7d5b596 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionGroupViewModel.kt @@ -102,7 +102,7 @@ class TransactionGroupViewModel @Inject constructor( TransactionRowView.fromTransaction( it, walletData.wallet!!, walletData.wallet!!.context, txMetadata, null, resourceMapper, chainLockBlockHeight ) - } + }.sortedBy { row -> row.time } _dashValue.value = transactionWrapper.getValue(walletData.transactionBag) } } \ No newline at end of file From ad6dbc594a5660c78073e3810b604fd4ac808de7 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 10:02:58 -0800 Subject: [PATCH 05/20] fix: prevent ANR's in WalletMostRecentTransactionsObserver --- .../WalletMostRecentTransactionsObserver.kt | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/transactions/WalletMostRecentTransactionsObserver.kt b/wallet/src/de/schildbach/wallet/transactions/WalletMostRecentTransactionsObserver.kt index e41dd04f5f..f5afc588e2 100644 --- a/wallet/src/de/schildbach/wallet/transactions/WalletMostRecentTransactionsObserver.kt +++ b/wallet/src/de/schildbach/wallet/transactions/WalletMostRecentTransactionsObserver.kt @@ -19,27 +19,35 @@ package de.schildbach.wallet.transactions import de.schildbach.wallet.Constants import de.schildbach.wallet.util.ThrottlingWalletChangeListener +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch import org.bitcoinj.core.Coin import org.bitcoinj.core.Transaction import org.bitcoinj.utils.Threading import org.bitcoinj.wallet.Wallet class WalletMostRecentTransactionsObserver(private val wallet: Wallet) { + private val workerJob = SupervisorJob() + private val workerScope = CoroutineScope(Dispatchers.IO + workerJob) fun observe(): Flow = callbackFlow { fun emitMostRecentTransaction() { - org.bitcoinj.core.Context.propagate(Constants.CONTEXT) - val allTxs = wallet.walletTransactions - if (allTxs.any()) { - var mostRecentTx = allTxs.first() - allTxs.forEach { - if (it.transaction.updateTime > mostRecentTx.transaction.updateTime) { - mostRecentTx = it + workerScope.launch { + org.bitcoinj.core.Context.propagate(Constants.CONTEXT) + val allTxs = wallet.walletTransactions + if (allTxs.any()) { + var mostRecentTx = allTxs.first() + allTxs.forEach { + if (it.transaction.updateTime > mostRecentTx.transaction.updateTime) { + mostRecentTx = it + } } + trySend(mostRecentTx.transaction) } - trySend(mostRecentTx.transaction) } } @@ -84,6 +92,7 @@ class WalletMostRecentTransactionsObserver(private val wallet: Wallet) { wallet.removeCoinsSentEventListener(walletChangeListener) wallet.removeCoinsReceivedEventListener(walletChangeListener) walletChangeListener.removeCallbacks() + workerJob.cancel() } } } \ No newline at end of file From ef127ea729c591eac7bfc9603818d9756e396a55 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 11:34:01 -0800 Subject: [PATCH 06/20] fix: improve updating views based on balance changes --- .../wallet/service/BlockchainServiceImpl.kt | 17 ++++++++++--- .../wallet/ui/main/MainViewModel.kt | 16 +++++++++---- .../wallet/ui/main/WalletFragment.kt | 24 +++++++++++-------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt index b679614438..7a91e57b55 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt @@ -240,6 +240,8 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { private var syncPercentage = 0 // 0 to 100% private var mixingStatus = MixingStatus.NOT_STARTED private var mixingProgress = 0.0 + private var balance = Coin.ZERO + private var mixedBalance = Coin.ZERO private var foregroundService = ForegroundService.NONE // Risk Analyser for Transactions that is PeerGroup Aware @@ -1125,14 +1127,23 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { coinJoinService.observeMixingProgress().observe(this@BlockchainServiceImpl) { mixingProgress -> handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) } + + application.observeBalance().observe(this@BlockchainServiceImpl) { + balance = it + handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) + } + + application.observeBalance(Wallet.BalanceType.COINJOIN_SPENDABLE).observe(this@BlockchainServiceImpl) { + mixedBalance = it + handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) + } + onCreateCompleted.complete(Unit) // Signal completion of onCreate log.info(".onCreate() finished") } } private fun createCoinJoinNotification(): Notification { - val mixedBalance = (application.wallet as WalletEx?)!!.coinJoinBalance - val totalBalance = application.wallet!!.balance val notificationIntent = createIntent(this) val pendingIntent = PendingIntent.getActivity( this, 0, @@ -1150,7 +1161,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { getString(statusStringId), mixingProgress, decimalFormat.format(mixedBalance.toBigDecimal()), - decimalFormat.format(totalBalance.toBigDecimal()) + decimalFormat.format(balance.toBigDecimal()) ) return NotificationCompat.Builder( this, diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt index 99722b54b6..2363ab4564 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt @@ -108,6 +108,7 @@ import org.dash.wallet.common.util.toBigDecimal import org.dash.wallet.integrations.crowdnode.api.CrowdNodeApi import org.dash.wallet.integrations.crowdnode.transactions.FullCrowdNodeSignUpTxSetFactory import org.slf4j.LoggerFactory +import java.math.BigDecimal import java.text.DecimalFormat import java.time.Instant import java.time.LocalDate @@ -205,6 +206,9 @@ class MainViewModel @Inject constructor( private val _balance = MutableLiveData() val balance: LiveData get() = _balance + private val _mixedBalance = MutableLiveData() + val mixedBalance: LiveData + get() = _mixedBalance private var txByHash: Map = mapOf() private var metadata: Map = mapOf() @@ -249,11 +253,11 @@ class MainViewModel @Inject constructor( get() = coinJoinService.observeActiveSessions() var decimalFormat: DecimalFormat = DecimalFormat("0.000") - val walletBalance: String - get() = decimalFormat.format(walletData.wallet!!.getBalance(Wallet.BalanceType.ESTIMATED).toBigDecimal()) + val walletBalanceString: String + get() = decimalFormat.format(balance.value?.toBigDecimal() ?: BigDecimal.ZERO) - val mixedBalance: String - get() = decimalFormat.format((walletData.wallet as WalletEx).coinJoinBalance.toBigDecimal()) + val mixedBalanceString: String + get() = decimalFormat.format(mixedBalance.value?.toBigDecimal() ?: BigDecimal.ZERO) // DashPay private val isPlatformAvailable = MutableStateFlow(false) @@ -351,6 +355,10 @@ class MainViewModel @Inject constructor( .onEach(_balance::postValue) .launchIn(viewModelScope) + walletData.observeBalance(Wallet.BalanceType.COINJOIN_SPENDABLE) + .onEach(_mixedBalance::postValue) + .launchIn(viewModelScope) + walletData.observeMostRecentTransaction() .onEach(_mostRecentTransaction::postValue) .launchIn(viewModelScope) diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt index 3e9155caf3..0f11493f68 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletFragment.kt @@ -172,11 +172,7 @@ class WalletFragment : Fragment(R.layout.home_content) { } viewModel.mixingProgress.observe(viewLifecycleOwner) { progress -> - mixingBinding.balance.text = getString( - R.string.coinjoin_progress_balance, - viewModel.mixedBalance, - viewModel.walletBalance - ) + updateMixedAndTotalBalance() mixingBinding.mixingPercent.text = getString(R.string.percent, progress.toInt()) mixingBinding.mixingProgress.progress = progress.toInt() } @@ -210,11 +206,11 @@ class WalletFragment : Fragment(R.layout.home_content) { } viewModel.balance.observe(viewLifecycleOwner) { - mixingBinding.balance.text = getString( - R.string.coinjoin_progress_balance, - viewModel.mixedBalance, - viewModel.walletBalance - ) + updateMixedAndTotalBalance() + } + + viewModel.mixedBalance.observe(viewLifecycleOwner) { + updateMixedAndTotalBalance() } viewModel.hasContacts.observe(viewLifecycleOwner) { @@ -222,6 +218,14 @@ class WalletFragment : Fragment(R.layout.home_content) { } } + private fun updateMixedAndTotalBalance() { + mixingBinding.balance.text = getString( + R.string.coinjoin_progress_balance, + viewModel.mixedBalanceString, + viewModel.walletBalanceString + ) + } + fun scrollToTop() { if (!isAdded) { return From db582b19e8dd1bab088c1b56c1329496245356cf Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 15 Feb 2025 11:34:25 -0800 Subject: [PATCH 07/20] fix: update to dashj 1.7.3-SNAPSHOT for username creation fixes --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f1fe1097e7..5a5e101781 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' dashjVersion = '21.1.6' - dppVersion = "1.7.2" + dppVersion = "1.7.3-SNAPSHOT" hiltVersion = '2.51' hiltCompilerVersion = '1.2.0' hiltWorkVersion = '1.0.0' From a6ce6ab68f4c8ca57bbe3b91cc6ba32630d6d362 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 17 Feb 2025 09:10:32 -0800 Subject: [PATCH 08/20] fix: disable tx metadata --- wallet/src/de/schildbach/wallet/Constants.java | 8 +++++++- .../wallet/service/platform/PlatformSyncService.kt | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/wallet/src/de/schildbach/wallet/Constants.java b/wallet/src/de/schildbach/wallet/Constants.java index f78f7b0738..e7600ca2a6 100644 --- a/wallet/src/de/schildbach/wallet/Constants.java +++ b/wallet/src/de/schildbach/wallet/Constants.java @@ -63,6 +63,8 @@ public final class Constants { public static boolean SUPPORTS_PLATFORM; // TODO: remove all references to this when invites are enabled and functional public static boolean SUPPORTS_INVITES; + // TODO: remove all references to this when transaction metadata is saved on platform + public static final boolean SUPPORTS_TXMETADATA; public static final EnumSet SYNC_FLAGS = MasternodeSync.SYNC_DEFAULT_SPV; public static final EnumSet VERIFY_FLAGS = MasternodeSync.VERIFY_DEFAULT_SPV; @@ -92,6 +94,7 @@ public final class Constants { org.dash.wallet.common.util.Constants.INSTANCE.setEXPLORE_GC_FILE_PATH("explore/explore.db"); SUPPORTS_PLATFORM = !is32Bit; SUPPORTS_INVITES = false; + SUPPORTS_TXMETADATA = false; SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_HEADERS_MN_LIST_FIRST); if (SUPPORTS_PLATFORM) { SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_BLOCKS_AFTER_PREPROCESSING); @@ -110,6 +113,7 @@ public final class Constants { WALLET_NAME_CURRENCY_CODE = "tdash"; SUPPORTS_PLATFORM = !is32Bit; SUPPORTS_INVITES = false; + SUPPORTS_TXMETADATA = false; SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_HEADERS_MN_LIST_FIRST); if (SUPPORTS_PLATFORM) { SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_BLOCKS_AFTER_PREPROCESSING); @@ -130,7 +134,9 @@ public final class Constants { FEE_NETWORK_SUFFIX = "-testnet"; // use the same fee file as testnet WALLET_NAME_CURRENCY_CODE = "tdash"; org.dash.wallet.common.util.Constants.EXPLORE_GC_FILE_PATH = "explore/explore-devnet.db"; - SUPPORTS_PLATFORM = true; + SUPPORTS_PLATFORM = !is32Bit; + SUPPORTS_INVITES = false; + SUPPORTS_TXMETADATA = false; SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_HEADERS_MN_LIST_FIRST); SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_BLOCKS_AFTER_PREPROCESSING); org.dash.wallet.common.util.Constants.FAUCET_URL = String.format("http://faucet.%s.networks.dash.org/", devNetName); diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt index e4df0b27a7..5b849d9094 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt @@ -981,6 +981,9 @@ class PlatformSynchronizationService @Inject constructor( } private suspend fun publishChangeCache(before: Long) { + if (!Constants.SUPPORTS_TXMETADATA) { + return + } log.info("publishing updates to tx metadata items before $before") val itemsToPublish = hashMapOf() val changedItems = transactionMetadataChangeCacheDao.findAllBefore(before) From 15c7a14b63aa9b6d358c0cc9ee8fb129a222d366 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 17 Feb 2025 11:28:31 -0800 Subject: [PATCH 09/20] refactor: create WalletBalanceObserver instance in WalletApplication --- build.gradle | 2 +- .../dash/wallet/common/WalletDataProvider.kt | 2 + .../schildbach/wallet/WalletApplication.java | 41 +++++++++---- .../wallet/service/BlockchainServiceImpl.kt | 3 +- .../transactions/WalletBalanceObserver.kt | 60 ++++++++++++++++--- 5 files changed, 88 insertions(+), 20 deletions(-) diff --git a/build.gradle b/build.gradle index 5a5e101781..c562f63be7 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' dashjVersion = '21.1.6' - dppVersion = "1.7.3-SNAPSHOT" + dppVersion = "1.7.3" hiltVersion = '2.51' hiltCompilerVersion = '1.2.0' hiltWorkVersion = '1.0.0' diff --git a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt index 4a3cc7ce74..d8a1dd944a 100644 --- a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt +++ b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt @@ -74,4 +74,6 @@ interface WalletDataProvider { fun checkSendingConditions(address: Address?, amount: Coin) fun observeMostRecentTransaction(): Flow + fun observeMixedBalance(): Flow + fun observeBalance(): Flow } diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index 4a4e2611f3..f4c863a8e7 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -61,17 +61,13 @@ import org.bitcoinj.core.TransactionBag; import org.bitcoinj.core.VerificationException; import org.bitcoinj.crypto.LinuxSecureRandom; -import org.bitcoinj.manager.DashSystem; import org.bitcoinj.utils.Threading; -import org.bitcoinj.core.VersionMessage; -import org.bitcoinj.crypto.IKey; import org.bitcoinj.wallet.AuthenticationKeyChain; import org.bitcoinj.wallet.CoinSelector; import org.bitcoinj.wallet.Protos; import org.bitcoinj.wallet.UnreadableWalletException; import org.bitcoinj.wallet.Wallet; import org.bitcoinj.wallet.WalletEx; -import org.bitcoinj.wallet.WalletExtension; import org.bitcoinj.wallet.WalletProtobufSerializer; import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension; import org.bitcoinj.wallet.authentication.AuthenticationKeyUsage; @@ -91,17 +87,18 @@ import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import ch.qos.logback.core.util.FileSize; +import de.schildbach.wallet.data.CoinJoinConfig; import de.schildbach.wallet.service.BlockchainStateDataProvider; import de.schildbach.wallet.service.DashSystemService; import de.schildbach.wallet.service.PackageInfoProvider; import de.schildbach.wallet.service.WalletFactory; import de.schildbach.wallet.transactions.MasternodeObserver; +import de.schildbach.wallet.transactions.WalletBalanceObserver; import de.schildbach.wallet.ui.buy_sell.LiquidClient; import org.dash.wallet.integrations.uphold.api.UpholdClient; import org.dash.wallet.integrations.uphold.data.UpholdConstants; import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConfig; import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeBalanceCondition; -import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConfig; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -118,7 +115,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -130,8 +126,6 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.rolling.RollingFileAppender; import dagger.hilt.android.HiltAndroidApp; -import org.dash.wallet.common.data.entity.BlockchainState; -import de.schildbach.wallet.database.dao.BlockchainStateDao; import de.schildbach.wallet.security.SecurityGuard; import de.schildbach.wallet.service.BlockchainService; import de.schildbach.wallet.service.BlockchainServiceImpl; @@ -216,6 +210,7 @@ public class WalletApplication extends MultiDexApplication WalletFactory walletFactory; @Inject DashSystemService dashSystemService; + private WalletBalanceObserver walletBalanceObserver; @Override protected void attachBaseContext(Context base) { @@ -580,6 +575,9 @@ private void afterLoadWallet() { // make sure there is at least one recent backup if (!getFileStreamPath(Constants.Files.WALLET_KEY_BACKUP_PROTOBUF).exists()) backupWallet(); + + // setup WalletBalanceObserver + walletBalanceObserver = new WalletBalanceObserver(wallet); } private void deleteBlockchainFiles() { @@ -1041,6 +1039,8 @@ public void finalizeWipe() { // wallet must be null for the OnboardingActivity flow log.info("removing wallet from memory during wipe"); wallet = null; + walletBalanceObserver.close(); + walletBalanceObserver = null; if (afterWipeFunction != null) afterWipeFunction.invoke(); afterWipeFunction = null; @@ -1103,7 +1103,28 @@ public Coin getWalletBalance() { return Coin.ZERO; } - return wallet.getBalance(Wallet.BalanceType.ESTIMATED); + //return wallet.getBalance(Wallet.BalanceType.ESTIMATED); + return walletBalanceObserver.getTotalBalance().getValue(); + } + + @NonNull + @Override + public Flow observeBalance() { + if (wallet == null) { + return FlowKt.emptyFlow(); + } + + return walletBalanceObserver.getTotalBalance(); + } + + @NonNull + @Override + public Flow observeMixedBalance() { + if (wallet == null) { + return FlowKt.emptyFlow(); + } + + return walletBalanceObserver.getMixedBalance(); } @NonNull @@ -1116,7 +1137,7 @@ public Flow observeBalance( return FlowKt.emptyFlow(); } - return new WalletBalanceObserver(wallet, balanceType, coinSelector).observe(); + return walletBalanceObserver.observe(balanceType, coinSelector); } @NonNull diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt index 7a91e57b55..dfd5d68fb1 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt @@ -1133,7 +1133,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) } - application.observeBalance(Wallet.BalanceType.COINJOIN_SPENDABLE).observe(this@BlockchainServiceImpl) { + application.observeMixedBalance().observe(this@BlockchainServiceImpl) { mixedBalance = it handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) } @@ -1151,6 +1151,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { ) val decimalFormat = DecimalFormat("0.000") val statusStringId = when (mixingStatus) { + MixingStatus.NOT_STARTED -> R.string.coinjoin_not_started MixingStatus.MIXING -> R.string.coinjoin_mixing MixingStatus.PAUSED -> R.string.coinjoin_paused MixingStatus.FINISHED -> R.string.coinjoin_progress_finished diff --git a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt index 68955edf6b..9df4e5e1d5 100644 --- a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt +++ b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt @@ -24,6 +24,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch import org.bitcoinj.core.Coin @@ -33,17 +35,62 @@ import org.bitcoinj.wallet.Wallet import org.bitcoinj.wallet.Wallet.BalanceType import org.slf4j.LoggerFactory + class WalletBalanceObserver( - private val wallet: Wallet, - private val balanceType: BalanceType = BalanceType.ESTIMATED, - private val coinSelector: CoinSelector? = null + private val wallet: Wallet ) { companion object { private val log = LoggerFactory.getLogger(WalletBalanceObserver::class.java) } private val emitterJob = SupervisorJob() private val emitterScope = CoroutineScope(Dispatchers.IO + emitterJob) - fun observe(): Flow = callbackFlow { + + private val _totalBalance = MutableStateFlow(Coin.ZERO) + val totalBalance: StateFlow + get() = _totalBalance + + private val _mixedBalance = MutableStateFlow(Coin.ZERO) + val mixedBalance: StateFlow + get() = _mixedBalance + + private val walletChangeListener = object : ThrottlingWalletChangeListener() { + override fun onThrottledWalletChanged() { + // log.info("emitting balance: wallet changed {}", this@WalletBalanceObserver) + emitBalances() + } + } + + init { + wallet.addChangeEventListener(Threading.SAME_THREAD, walletChangeListener) + emitBalances() + } + + fun close() { + wallet.removeChangeEventListener(walletChangeListener) + walletChangeListener.removeCallbacks() + emitterJob.cancel() + } + + fun emitBalances() { + emitterScope.launch { + //log.info("emitting balance {}", this@WalletBalanceObserver) + //val watch = Stopwatch.createStarted() + org.bitcoinj.core.Context.propagate(Constants.CONTEXT) + + _mixedBalance.emit(wallet.getBalance(BalanceType.COINJOIN_SPENDABLE)) + _totalBalance.emit(wallet.getBalance(BalanceType.ESTIMATED)) + + //log.info("emit balance time: {} ms", watch.elapsed(TimeUnit.MILLISECONDS)) + } + } + + /** custom observer */ + fun observe( + balanceType: BalanceType = BalanceType.ESTIMATED, + coinSelector: CoinSelector? = null + ): Flow = callbackFlow { + val emitterJob = SupervisorJob() + val emitterScope = CoroutineScope(Dispatchers.IO + emitterJob) fun emitBalance() { emitterScope.launch { //log.info("emitting balance {}", this@WalletBalanceObserver) @@ -68,17 +115,14 @@ class WalletBalanceObserver( } } - wallet.addCoinsReceivedEventListener(Threading.SAME_THREAD, walletChangeListener) - wallet.addCoinsSentEventListener(Threading.SAME_THREAD, walletChangeListener) wallet.addChangeEventListener(Threading.SAME_THREAD, walletChangeListener) emitBalance() awaitClose { wallet.removeChangeEventListener(walletChangeListener) - wallet.removeCoinsSentEventListener(walletChangeListener) - wallet.removeCoinsReceivedEventListener(walletChangeListener) walletChangeListener.removeCallbacks() + emitterJob.cancel() } } } \ No newline at end of file From e16f16c831c0c63c321258a7cafa3d45809050ec Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 17 Feb 2025 11:29:01 -0800 Subject: [PATCH 10/20] fix: improve loading tx view --- wallet/res/layout/wallet_transactions_fragment.xml | 3 ++- wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt | 5 ++++- .../schildbach/wallet/ui/main/WalletTransactionsFragment.kt | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/wallet/res/layout/wallet_transactions_fragment.xml b/wallet/res/layout/wallet_transactions_fragment.xml index 3fc146a53b..d2eba50666 100644 --- a/wallet/res/layout/wallet_transactions_fragment.xml +++ b/wallet/res/layout/wallet_transactions_fragment.xml @@ -22,7 +22,8 @@ android:gravity="center_horizontal" android:textAlignment="gravity" android:text="@string/wallet_transactions_fragment_empty_text" - android:textSize="@dimen/font_size_small" /> + android:textSize="@dimen/font_size_small" + android:visibility="invisible"/> >>() val transactions: LiveData>> get() = _transactions @@ -355,7 +357,7 @@ class MainViewModel @Inject constructor( .onEach(_balance::postValue) .launchIn(viewModelScope) - walletData.observeBalance(Wallet.BalanceType.COINJOIN_SPENDABLE) + walletData.observeMixedBalance() .onEach(_mixedBalance::postValue) .launchIn(viewModelScope) @@ -554,6 +556,7 @@ class MainViewModel @Inject constructor( } viewModelScope.launch { + transactionsLoaded = true _transactions.value = allTransactionViews this@MainViewModel.txByHash = txByHash updateContacts(contactsToUpdate) diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt index 1046df567b..cf92036a6e 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt @@ -230,7 +230,7 @@ class WalletTransactionsFragment : Fragment(R.layout.wallet_transactions_fragmen } private fun showEmptyView() { - binding.walletTransactionsEmpty.isVisible = true + binding.walletTransactionsEmpty.isVisible = viewModel.transactionsLoaded binding.walletTransactionsList.isVisible = false } From 1f5135ae9ce5a89ea2e0182c4109260bed009c6b Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 17 Feb 2025 14:11:00 -0800 Subject: [PATCH 11/20] fix: fixed issue that would create a topup during username creation --- .../de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt index 7edaae1a9f..e79765ca82 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt @@ -379,14 +379,13 @@ class CreateIdentityService : LifecycleService() { } else { // don't use platformRepo.getIdentityBalance() because platformRepo.blockchainIdentity is not initialized val balanceInfo = blockchainIdentityData.identity?.let { platformRepo.getIdentityBalance(it.id) } - ?: CreditBalanceInfo(0L) val balanceRequirement = if (Names.isUsernameContestable(blockchainIdentityData.username!!)) { Constants.DASH_PAY_FEE_CONTESTED } else { Constants.DASH_PAY_FEE } - if (balanceInfo.balance < balanceRequirement.value * 1000) { + if (balanceInfo != null && balanceInfo.balance < balanceRequirement.value * 1000) { val topupValue = if (Names.isUsernameContestable(blockchainIdentityData.username!!)) { Constants.DASH_PAY_FEE_CONTESTED_NAME } else { From 0637006839a56744a8ae9cf4cd928e2fc7f90816 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 18 Feb 2025 09:07:48 -0800 Subject: [PATCH 12/20] fix: first emit the last balance --- .../org/dash/wallet/common/data/WalletUIConfig.kt | 2 ++ .../src/de/schildbach/wallet/WalletApplication.java | 1 + .../wallet/transactions/WalletBalanceObserver.kt | 12 +++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt b/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt index 532d932429..5c6ba0dabe 100644 --- a/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt +++ b/common/src/main/java/org/dash/wallet/common/data/WalletUIConfig.kt @@ -63,6 +63,8 @@ open class WalletUIConfig @Inject constructor( val SHOW_TAP_TO_HIDE_HINT = booleanPreferencesKey("show_tap_to_hide_balance_hint") val SELECTED_CURRENCY = stringPreferencesKey("exchange_currency") val EXCHANGE_CURRENCY_DETECTED = booleanPreferencesKey("exchange_currency_detected") + val LAST_TOTAL_BALANCE = longPreferencesKey("last_total_balance") + val LAST_MIXED_BALANCE = longPreferencesKey("last_mixed_balance") } suspend fun getExchangeCurrencyCode(): String { diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index f4c863a8e7..c5f0c2cfa3 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -75,6 +75,7 @@ import org.dash.wallet.common.Configuration; import org.dash.wallet.common.InteractionAwareActivity; import org.dash.wallet.common.WalletDataProvider; +import org.dash.wallet.common.data.WalletUIConfig; import org.dash.wallet.common.services.LeftoverBalanceException; import org.dash.wallet.common.services.TransactionMetadataProvider; import org.dash.wallet.common.services.analytics.AnalyticsService; diff --git a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt index 9df4e5e1d5..c3b408eeac 100644 --- a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt +++ b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt @@ -33,11 +33,13 @@ import org.bitcoinj.utils.Threading import org.bitcoinj.wallet.CoinSelector import org.bitcoinj.wallet.Wallet import org.bitcoinj.wallet.Wallet.BalanceType +import org.dash.wallet.common.data.WalletUIConfig import org.slf4j.LoggerFactory class WalletBalanceObserver( - private val wallet: Wallet + private val wallet: Wallet, + private val walletUIConfig: WalletUIConfig ) { companion object { private val log = LoggerFactory.getLogger(WalletBalanceObserver::class.java) @@ -62,6 +64,7 @@ class WalletBalanceObserver( init { wallet.addChangeEventListener(Threading.SAME_THREAD, walletChangeListener) + emitLastBalances() emitBalances() } @@ -71,6 +74,13 @@ class WalletBalanceObserver( emitterJob.cancel() } + private fun emitLastBalances() { + emitterScope.launch { + _totalBalance.value = Coin.valueOf(walletUIConfig.get(WalletUIConfig.LAST_TOTAL_BALANCE) ?: 0L) + _mixedBalance.value = Coin.valueOf(walletUIConfig.get(WalletUIConfig.LAST_MIXED_BALANCE) ?: 0L) + } + } + fun emitBalances() { emitterScope.launch { //log.info("emitting balance {}", this@WalletBalanceObserver) From 19910b32274a1a643e7f59a22c433b7809b75ee6 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 18 Feb 2025 09:08:08 -0800 Subject: [PATCH 13/20] fix: first emit the last balance --- wallet/src/de/schildbach/wallet/WalletApplication.java | 4 +++- .../wallet/transactions/WalletBalanceObserver.kt | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index c5f0c2cfa3..ad2b4e22a4 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -211,6 +211,8 @@ public class WalletApplication extends MultiDexApplication WalletFactory walletFactory; @Inject DashSystemService dashSystemService; + @Inject + WalletUIConfig walletUIConfig; private WalletBalanceObserver walletBalanceObserver; @Override @@ -578,7 +580,7 @@ private void afterLoadWallet() { backupWallet(); // setup WalletBalanceObserver - walletBalanceObserver = new WalletBalanceObserver(wallet); + walletBalanceObserver = new WalletBalanceObserver(wallet, walletUIConfig); } private void deleteBlockchainFiles() { diff --git a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt index c3b408eeac..6a94bacf7d 100644 --- a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt +++ b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt @@ -87,8 +87,12 @@ class WalletBalanceObserver( //val watch = Stopwatch.createStarted() org.bitcoinj.core.Context.propagate(Constants.CONTEXT) - _mixedBalance.emit(wallet.getBalance(BalanceType.COINJOIN_SPENDABLE)) - _totalBalance.emit(wallet.getBalance(BalanceType.ESTIMATED)) + val mixedBalance = wallet.getBalance(BalanceType.COINJOIN_SPENDABLE) + walletUIConfig.set(WalletUIConfig.LAST_MIXED_BALANCE, mixedBalance.value) + _mixedBalance.emit(mixedBalance) + val totalBalance = wallet.getBalance(BalanceType.ESTIMATED) + walletUIConfig.set(WalletUIConfig.LAST_TOTAL_BALANCE, totalBalance.value) + _totalBalance.emit(totalBalance) //log.info("emit balance time: {} ms", watch.elapsed(TimeUnit.MILLISECONDS)) } From 4b3d0a818f5eaaa514765d9995603b08795fcead Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 18 Feb 2025 17:30:22 -0800 Subject: [PATCH 14/20] refactor: rename observeBalance() to observeTotalBalance() --- .../src/main/java/org/dash/wallet/common/WalletDataProvider.kt | 2 +- .../features/exploredash/ui/dashdirect/DashDirectViewModel.kt | 2 +- .../integrations/coinbase/viewmodels/TransferDashViewModel.kt | 2 +- .../dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt | 2 +- .../wallet/integrations/crowdnode/CrowdNodeViewModelTest.kt | 2 +- wallet/src/de/schildbach/wallet/WalletApplication.java | 2 +- .../src/de/schildbach/wallet/service/BlockchainServiceImpl.kt | 2 +- wallet/src/de/schildbach/wallet/service/CoinJoinService.kt | 2 +- wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt | 2 +- .../src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt | 2 +- wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt | 2 +- .../de/schildbach/wallet/util/viewModels/MainViewModelTest.kt | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt index d8a1dd944a..f39ef2474c 100644 --- a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt +++ b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt @@ -75,5 +75,5 @@ interface WalletDataProvider { fun observeMostRecentTransaction(): Flow fun observeMixedBalance(): Flow - fun observeBalance(): Flow + fun observeTotalBalance(): Flow } diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/dashdirect/DashDirectViewModel.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/dashdirect/DashDirectViewModel.kt index 02057821a8..761781abfd 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/dashdirect/DashDirectViewModel.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/dashdirect/DashDirectViewModel.kt @@ -99,7 +99,7 @@ class DashDirectViewModel @Inject constructor( .launchIn(viewModelScope) walletDataProvider - .observeBalance() + .observeTotalBalance() .distinctUntilChanged() .onEach(_balance::postValue) .launchIn(viewModelScope) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt index 0726156f0d..91a800c0e6 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt @@ -96,7 +96,7 @@ class TransferDashViewModel @Inject constructor( init { getUserAccountAddress() getUserData() - walletDataProvider.observeBalance() + walletDataProvider.observeTotalBalance() .onEach(_dashBalanceInWalletState::postValue) .launchIn(viewModelScope) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt index 98edda9bef..39d14e77dd 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt @@ -131,7 +131,7 @@ class CrowdNodeViewModel @Inject constructor( (crowdNodeBalance.value?.balance?.isLessThan(CrowdNodeConstants.MINIMUM_DASH_DEPOSIT) ?: true) init { - walletDataProvider.observeBalance() + walletDataProvider.observeTotalBalance() .distinctUntilChanged() .onEach { _dashBalance.postValue(it) diff --git a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeViewModelTest.kt b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeViewModelTest.kt index 674ea0c816..1916f5720e 100644 --- a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeViewModelTest.kt +++ b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeViewModelTest.kt @@ -77,7 +77,7 @@ class CrowdNodeViewModelTest { } private val walletData = mock { - on { observeBalance() } doReturn MutableStateFlow(balance) + on { observeTotalBalance() } doReturn MutableStateFlow(balance) on { freshReceiveAddress() } doReturn Address.fromBase58(TestNet3Params.get(), "ydW78zVxRgNhANX2qtG4saSCC5ejNQjw2U") diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index ad2b4e22a4..733e9b9d2e 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -1112,7 +1112,7 @@ public Coin getWalletBalance() { @NonNull @Override - public Flow observeBalance() { + public Flow observeTotalBalance() { if (wallet == null) { return FlowKt.emptyFlow(); } diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt index dfd5d68fb1..75061159d9 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt @@ -1128,7 +1128,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) } - application.observeBalance().observe(this@BlockchainServiceImpl) { + application.observeTotalBalance().observe(this@BlockchainServiceImpl) { balance = it handleBlockchainStateNotification(blockchainState, mixingStatus, mixingProgress) } diff --git a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt index 7a23391b56..92249d282e 100644 --- a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt +++ b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt @@ -228,7 +228,7 @@ class CoinJoinMixingService @Inject constructor( } .launchIn(coroutineScope) - walletDataProvider.observeBalance() + walletDataProvider.observeTotalBalance() .distinctUntilChanged() .onEach { balance -> // switch to our context diff --git a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt index ae1fa3d9d2..23be6a7024 100644 --- a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt @@ -175,7 +175,7 @@ open class LockScreenActivity : SecureActivity() { private fun setupBackupSeedReminder() { lifecycleScope.launch { - val hasBalance = walletApplication.observeBalance().first().isPositive + val hasBalance = walletApplication.observeTotalBalance().first().isPositive if (hasBalance && configuration.lastBackupSeedTime == 0L) { configuration.setLastBackupSeedTime() } diff --git a/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt b/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt index 01d7a160bc..7ca0d595e0 100644 --- a/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt @@ -59,7 +59,7 @@ class CreateInviteViewModel @Inject constructor( addSource(blockchainIdentity) { value = combineLatestData() } - addSource(walletData.observeBalance().asLiveData()) { + addSource(walletData.observeTotalBalance().asLiveData()) { value = combineLatestData() } } diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt index 153c5792ba..a27314097b 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt @@ -353,7 +353,7 @@ class MainViewModel @Inject constructor( } .launchIn(viewModelWorkerScope) - walletData.observeBalance() + walletData.observeTotalBalance() .onEach(_balance::postValue) .launchIn(viewModelScope) diff --git a/wallet/test/de/schildbach/wallet/util/viewModels/MainViewModelTest.kt b/wallet/test/de/schildbach/wallet/util/viewModels/MainViewModelTest.kt index 2fdcb59e70..f9fdc292a6 100644 --- a/wallet/test/de/schildbach/wallet/util/viewModels/MainViewModelTest.kt +++ b/wallet/test/de/schildbach/wallet/util/viewModels/MainViewModelTest.kt @@ -174,7 +174,7 @@ class MainViewModelTest { every { blockchainStateMock.observeState() } returns flow { BlockchainState() } every { blockchainStateMock.observeSyncStage() } returns MutableStateFlow(PeerGroup.SyncStage.BLOCKS) every { exchangeRatesMock.observeExchangeRate(any()) } returns flow { ExchangeRate("USD", "100") } - every { walletDataMock.observeBalance() } returns flow { Coin.COIN } + every { walletDataMock.observeTotalBalance() } returns flow { Coin.COIN } every { walletDataMock.observeMostRecentTransaction() } returns flow { Transaction( TestNet3Params.get(), From 20cd5110e4d00566766a55d45bfdcc37d0b8f291 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 18 Feb 2025 17:31:21 -0800 Subject: [PATCH 15/20] fix: update WalletBalanceObserver on send/receive --- .../src/de/schildbach/wallet/service/CoinJoinService.kt | 2 +- .../wallet/transactions/WalletBalanceObserver.kt | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt index 92249d282e..c1e25f1312 100644 --- a/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt +++ b/wallet/src/de/schildbach/wallet/service/CoinJoinService.kt @@ -388,7 +388,7 @@ class CoinJoinMixingService @Inject constructor( if (mode != CoinJoinMode.NONE && this.mode == CoinJoinMode.NONE) { configureMixing() } - updateBalance(walletDataProvider.wallet!!.getBalance(Wallet.BalanceType.AVAILABLE)) + updateBalance(walletDataProvider.getWalletBalance()) val currentTimeSkew = getCurrentTimeSkew() updateState(mode, currentTimeSkew, hasAnonymizableBalance, networkStatus, blockchainState, blockChain) } diff --git a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt index 6a94bacf7d..b1049acf87 100644 --- a/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt +++ b/wallet/src/de/schildbach/wallet/transactions/WalletBalanceObserver.kt @@ -64,12 +64,16 @@ class WalletBalanceObserver( init { wallet.addChangeEventListener(Threading.SAME_THREAD, walletChangeListener) + wallet.addCoinsSentEventListener(Threading.SAME_THREAD, walletChangeListener) + wallet.addCoinsReceivedEventListener(Threading.SAME_THREAD, walletChangeListener) emitLastBalances() emitBalances() } fun close() { wallet.removeChangeEventListener(walletChangeListener) + wallet.removeCoinsSentEventListener(walletChangeListener) + wallet.removeCoinsReceivedEventListener(walletChangeListener) walletChangeListener.removeCallbacks() emitterJob.cancel() } @@ -130,11 +134,15 @@ class WalletBalanceObserver( } wallet.addChangeEventListener(Threading.SAME_THREAD, walletChangeListener) + wallet.addCoinsSentEventListener(Threading.SAME_THREAD, walletChangeListener) + wallet.addCoinsReceivedEventListener(Threading.SAME_THREAD, walletChangeListener) emitBalance() awaitClose { wallet.removeChangeEventListener(walletChangeListener) + wallet.removeCoinsSentEventListener(walletChangeListener) + wallet.removeCoinsReceivedEventListener(walletChangeListener) walletChangeListener.removeCallbacks() emitterJob.cancel() } From f246f1f1a0b367c44cc80e34ada8e6aa757b808c Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 19 Feb 2025 01:41:50 -0800 Subject: [PATCH 16/20] fix: add mutex for the check() function and make sure that it executes before destroy --- .../wallet/service/BlockchainServiceImpl.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt index 75061159d9..20801cf757 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.kt @@ -65,6 +65,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withContext import org.bitcoinj.core.Address import org.bitcoinj.core.Block @@ -169,6 +170,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { private val serviceJob = SupervisorJob() private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) private val onCreateCompleted = CompletableDeferred() + private var checkMutex = Mutex(false) @Inject lateinit var application: WalletApplication @@ -682,7 +684,12 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { serviceScope.launch { // make sure that onCreate is finished onCreateCompleted.await() - checkService() + checkMutex.lock() + try { + checkService() + } finally { + checkMutex.unlock() + } } } @@ -1312,6 +1319,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { serviceScope.launch { try { onCreateCompleted.await() // wait until onCreate is finished + checkMutex.lock() WalletApplication.scheduleStartBlockchainService(this@BlockchainServiceImpl) //disconnect feature application.wallet!!.removeChangeEventListener(walletEventListener) application.wallet!!.removeCoinsSentEventListener(walletEventListener) @@ -1366,6 +1374,7 @@ class BlockchainServiceImpl : LifecycleService(), BlockchainService { } finally { log.info("serviceJob cancelled after " + (System.currentTimeMillis() - serviceCreatedAt) / 1000 / 60 + " minutes") serviceJob.cancel() + checkMutex.unlock() cleanupDeferred?.complete(Unit) } } From e7f2b9e77eb54b9977e06ab80d6b60c6be0b160f Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 20 Feb 2025 01:08:08 -0800 Subject: [PATCH 17/20] fix: fix rescan and reset functions --- .../wallet/ui/dashpay/PlatformRepo.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt index 4bbc501344..bef13dd1ee 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt @@ -115,8 +115,9 @@ class PlatformRepo @Inject constructor( val hasIdentity: Boolean get() = this::blockchainIdentity.isInitialized - var authenticationGroupExtension: AuthenticationGroupExtension? = null - private set + val authenticationGroupExtension: AuthenticationGroupExtension? + get() = walletApplication.wallet!!.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as? AuthenticationGroupExtension + private val dashPayProfileDao = appDatabase.dashPayProfileDao() private val dashPayContactRequestDao = appDatabase.dashPayContactRequestDao() @@ -125,6 +126,7 @@ class PlatformRepo @Inject constructor( private val backgroundThread = HandlerThread("background", Process.THREAD_PRIORITY_BACKGROUND) private val backgroundHandler: Handler + private var platformSDKLoaded = false private val analytics: AnalyticsService by lazy { walletApplication.analyticsService @@ -143,17 +145,15 @@ class PlatformRepo @Inject constructor( } suspend fun init() { - if (authenticationGroupExtension == null) { + if (!platformSDKLoaded) { // load the dash-sdk library System.loadLibrary("sdklib") - authenticationGroupExtension = walletApplication.wallet?.getKeyChainExtension(AuthenticationGroupExtension.EXTENSION_ID) as? AuthenticationGroupExtension + platformSDKLoaded = true } - if (!hasIdentity) { - blockchainIdentityDataStorage.load()?.let { - blockchainIdentity = initBlockchainIdentity(it, walletApplication.wallet!!) - initializeStateRepository() - } + blockchainIdentityDataStorage.load()?.let { + blockchainIdentity = initBlockchainIdentity(it, walletApplication.wallet!!) + initializeStateRepository() } } @@ -568,7 +568,6 @@ class PlatformRepo @Inject constructor( var authenticationGroupExtension = AuthenticationGroupExtension(wallet) authenticationGroupExtension = wallet.addOrGetExistingExtension(authenticationGroupExtension) as AuthenticationGroupExtension authenticationGroupExtension.addEncryptedKeyChains(wallet.params, seed, keyParameter, keyChainTypes) - this@PlatformRepo.authenticationGroupExtension = authenticationGroupExtension } } @@ -934,7 +933,6 @@ class PlatformRepo @Inject constructor( if (includeInvitations) { invitationsDao.clear() } - authenticationGroupExtension = null // remove references to current wallet } fun getBlockchainIdentityKey(index: Int, keyParameter: KeyParameter?): IDeterministicKey? { From 518a6772c5b9d0adcf9b78ce3702f4235eea3399 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 11 Feb 2025 09:19:13 -0800 Subject: [PATCH 18/20] chore: log lock screen activation parameters --- wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt index 23be6a7024..3b0e5a4ac3 100644 --- a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt @@ -224,6 +224,7 @@ open class LockScreenActivity : SecureActivity() { if (!keepUnlocked && configuration.autoLogoutEnabled && (autoLogout.keepLockedUntilPinEntered || autoLogout.shouldLogout()) ) { + log.info("onStart: $keepUnlocked && ${configuration.autoLogoutEnabled} && (${autoLogout.keepLockedUntilPinEntered} || ${autoLogout.shouldLogout()})") setLockState( if (pinRetryController.isLocked) { State.LOCKED From 8795d19a9f863b76576e82964e81cdba8184e2bd Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 12 Feb 2025 07:50:39 -0800 Subject: [PATCH 19/20] fix: log errors with more detail --- wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt index 3b0e5a4ac3..b9d4128ddf 100644 --- a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt @@ -221,10 +221,10 @@ open class LockScreenActivity : SecureActivity() { super.onStart() autoLogout.setOnLogoutListener(onLogoutListener) - if (!keepUnlocked && configuration.autoLogoutEnabled && - (autoLogout.keepLockedUntilPinEntered || autoLogout.shouldLogout()) - ) { - log.info("onStart: $keepUnlocked && ${configuration.autoLogoutEnabled} && (${autoLogout.keepLockedUntilPinEntered} || ${autoLogout.shouldLogout()})") + val showLockScreen = !keepUnlocked && configuration.autoLogoutEnabled && + (autoLogout.keepLockedUntilPinEntered || autoLogout.shouldLogout()) + log.info("show lock screen $showLockScreen = !$keepUnlocked && ${configuration.autoLogoutEnabled} && (${autoLogout.keepLockedUntilPinEntered} || ${autoLogout.shouldLogout()})") + if (showLockScreen) { setLockState( if (pinRetryController.isLocked) { State.LOCKED From 5afe1b868b9b40a412f2d272377bef16a11932c7 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 21 Feb 2025 13:11:56 -0800 Subject: [PATCH 20/20] fix: add more logging for autologout --- wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt | 2 +- .../schildbach/wallet/ui/more/AdvancedSecurityActivity.kt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt index b9d4128ddf..ddd2be3fe5 100644 --- a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt @@ -223,7 +223,7 @@ open class LockScreenActivity : SecureActivity() { val showLockScreen = !keepUnlocked && configuration.autoLogoutEnabled && (autoLogout.keepLockedUntilPinEntered || autoLogout.shouldLogout()) - log.info("show lock screen $showLockScreen = !$keepUnlocked && ${configuration.autoLogoutEnabled} && (${autoLogout.keepLockedUntilPinEntered} || ${autoLogout.shouldLogout()})") + log.info("show lock screen $showLockScreen = ![keepUnlocked=$keepUnlocked] && autologout=${configuration.autoLogoutEnabled} && (keepUnlockedUntilPenEntered=${autoLogout.keepLockedUntilPinEntered} || shouldLogout=${autoLogout.shouldLogout()})") if (showLockScreen) { setLockState( if (pinRetryController.isLocked) { diff --git a/wallet/src/de/schildbach/wallet/ui/more/AdvancedSecurityActivity.kt b/wallet/src/de/schildbach/wallet/ui/more/AdvancedSecurityActivity.kt index b9f4870a9a..c1bc15c3af 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/AdvancedSecurityActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/AdvancedSecurityActivity.kt @@ -29,6 +29,7 @@ import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivityAdvancedSecurityBinding import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService +import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -38,6 +39,9 @@ enum class SecurityLevel { @AndroidEntryPoint class AdvancedSecurityActivity : LockScreenActivity() { + companion object { + private val log = LoggerFactory.getLogger(AdvancedSecurityActivity::class.java) + } @Inject lateinit var analytics: AnalyticsService private lateinit var binding: ActivityAdvancedSecurityBinding @@ -101,6 +105,7 @@ class AdvancedSecurityActivity : LockScreenActivity() { ) } configuration.autoLogoutEnabled = enabled + log.info("setting autologout = $enabled") updateView() } @@ -125,6 +130,7 @@ class AdvancedSecurityActivity : LockScreenActivity() { binding.resetToDefaultBtn.setOnClickListener { resetToDefault() } updateView() + log.info("autologout = ${configuration.autoLogoutEnabled}") } private fun getSecurityLevel(): SecurityLevel {