diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index 881ce9e0f..bcb6b0b86 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -143,6 +143,7 @@ import de.schildbach.wallet.transactions.WalletMostRecentTransactionsObserver; import de.schildbach.wallet.security.PinRetryController; import de.schildbach.wallet.util.AllowLockTimeRiskAnalysis; +import de.schildbach.wallet.util.AnrSupervisor; import de.schildbach.wallet.util.CrashReporter; import de.schildbach.wallet.util.LogMarkerFilter; import de.schildbach.wallet.util.MnemonicCodeExt; @@ -188,6 +189,7 @@ public class WalletApplication extends MultiDexApplication public Activity currentActivity; private AutoLogout autoLogout; + private AnrSupervisor anrSupervisor; @Inject RestartService restartService; @@ -326,6 +328,8 @@ private void logState() { } resetBlockchainSyncProgress(); + anrSupervisor = new AnrSupervisor(); + anrSupervisor.start(); } private void syncExploreData() { diff --git a/wallet/src/de/schildbach/wallet/database/entity/UsernameRequest.kt b/wallet/src/de/schildbach/wallet/database/entity/UsernameRequest.kt index 1dfe36821..b848d5cc7 100644 --- a/wallet/src/de/schildbach/wallet/database/entity/UsernameRequest.kt +++ b/wallet/src/de/schildbach/wallet/database/entity/UsernameRequest.kt @@ -19,6 +19,9 @@ package de.schildbach.wallet.database.entity import androidx.room.Entity import androidx.room.PrimaryKey +import de.schildbach.wallet.Constants +import org.bitcoinj.core.NetworkParameters +import java.util.concurrent.TimeUnit @Entity(tableName = "username_requests") data class UsernameRequest( @@ -34,6 +37,8 @@ data class UsernameRequest( val isApproved: Boolean ) { companion object { + val VOTING_PERIOD_MILLIS = if (Constants.NETWORK_PARAMETERS.id == NetworkParameters.ID_MAINNET) TimeUnit.DAYS.toMillis(14) else TimeUnit.MINUTES.toMillis(90) + val SUBMIT_PERIOD_MILLIS = VOTING_PERIOD_MILLIS / 2 fun getRequestId(identity: String, username: String): String { return String.format("%s-%s", identity, username) } diff --git a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt index 5292df3dd..137be316d 100644 --- a/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt +++ b/wallet/src/de/schildbach/wallet/service/platform/PlatformSyncService.kt @@ -74,6 +74,8 @@ import org.dashj.platform.contracts.wallet.TxMetadataItem import org.dashj.platform.dashpay.ContactRequest import org.dashj.platform.dashpay.UsernameRequestStatus import org.dashj.platform.dpp.identifier.Identifier +import org.dashj.platform.dpp.voting.ContestedDocumentResourceVotePoll +import org.dashj.platform.sdk.PlatformValue import org.dashj.platform.sdk.platform.DomainDocument import org.dashj.platform.wallet.IdentityVerify import org.slf4j.Logger @@ -225,7 +227,7 @@ class PlatformSynchronizationService @Inject constructor( if (blockchainIdentityData.creationState < BlockchainIdentityData.CreationState.DONE) { // Is the Voting Period complete? if (blockchainIdentityData.creationState == BlockchainIdentityData.CreationState.VOTING) { - val timeWindow = if (Constants.NETWORK_PARAMETERS.id == NetworkParameters.ID_MAINNET) TimeUnit.DAYS.toMillis(14) else TimeUnit.MINUTES.toMillis(90) + val timeWindow = UsernameRequest.VOTING_PERIOD_MILLIS if (System.currentTimeMillis() - blockchainIdentityData.votingPeriodStart!! >= timeWindow) { val resource = platformRepo.getUsername(blockchainIdentityData.username!!) if (resource.status == Status.SUCCESS) { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt index 06ba75a94..6e28c8b78 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt @@ -337,6 +337,12 @@ class CreateIdentityService : LifecycleService() { if (blockchainIdentityData.creationStateErrorMessage?.contains("preorderDocument was not found with a salted domain hash") == true) { blockchainIdentityData.creationState = CreationState.PREORDER_REGISTERING platformRepo.updateBlockchainIdentityData(blockchainIdentityData) + } else if (retryWithNewUserName) { + // lets rewind the state to allow for a new username registration or request + // it may have failed later in the process + if (blockchainIdentityData.creationState > CreationState.USERNAME_REGISTERING) { + blockchainIdentityData.creationState = CreationState.USERNAME_REGISTERING + } } } } @@ -919,7 +925,18 @@ class CreateIdentityService : LifecycleService() { false ) ) - val usernameInfo = blockchainIdentity.usernameStatuses[blockchainIdentity.currentUsername!!]!! + // what if usernameInfo would have been null, we should create it. + + var usernameInfo = blockchainIdentity.usernameStatuses[blockchainIdentity.currentUsername!!] + if (usernameInfo == null) { + usernameInfo = UsernameInfo( + null, + UsernameStatus.CONFIRMED, + blockchainIdentity.currentUsername!!, + UsernameRequestStatus.VOTING + ) + blockchainIdentity.usernameStatuses[blockchainIdentity.currentUsername!!] = usernameInfo + } // determine when voting started by finding the minimum timestamp val earliestCreatedAt = voteContenders.map.values.minOf { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt index bfa403dba..55ccd6921 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/HistoryHeaderAdapter.kt @@ -101,7 +101,10 @@ class HistoryHeaderAdapter( if (blockchainIdentityData.creationStateErrorMessage != null) { if (blockchainIdentityData.creationState == BlockchainIdentityData.CreationState.USERNAME_REGISTERING && - blockchainIdentityData.creationStateErrorMessage.contains("Document transitions with duplicate unique properties")) { + (blockchainIdentityData.creationStateErrorMessage.contains("Document transitions with duplicate unique properties") || + blockchainIdentityData.creationStateErrorMessage.contains("Document Contest for vote_poll ContestedDocumentResourceVotePoll")) || + blockchainIdentityData.creationStateErrorMessage.contains(Regex("does not have .* as a contender")) + ) { binding.identityCreation.title.text = binding.root.context.getString(R.string.processing_username_unavailable_title) binding.identityCreation.subtitle.visibility = View.VISIBLE binding.identityCreation.icon.setImageResource(R.drawable.ic_username_unavailable) diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/work/GetUsernameVotingResultOperation.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/work/GetUsernameVotingResultOperation.kt index e8d3b094a..c35555f07 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/work/GetUsernameVotingResultOperation.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/work/GetUsernameVotingResultOperation.kt @@ -21,6 +21,7 @@ import android.annotation.SuppressLint import android.app.Application import androidx.work.* import de.schildbach.wallet.Constants +import de.schildbach.wallet.database.entity.UsernameRequest import org.bitcoinj.core.NetworkParameters import org.slf4j.LoggerFactory import java.text.DateFormat @@ -49,11 +50,7 @@ class GetUsernameVotingResultOperation(val application: Application) { @SuppressLint("EnqueueWork") fun create(username: String, identityId: String, votingStartedAt: Long): WorkContinuation { log.info("scheduling work to check username voting status") - val delay = System.currentTimeMillis() - if (Constants.NETWORK_PARAMETERS.id != NetworkParameters.ID_MAINNET) { - TimeUnit.MINUTES.toMillis(90) - } else { - TimeUnit.DAYS.toMillis(14) - } + votingStartedAt + TimeUnit.MINUTES.toMillis(2) + val delay = System.currentTimeMillis() - UsernameRequest.VOTING_PERIOD_MILLIS + votingStartedAt + TimeUnit.MINUTES.toMillis(2) log.info("scheduling work to check username voting status on {}", DateFormat.getDateInstance(DateFormat.FULL).format( Date(System.currentTimeMillis() + delay) diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt index 9b105324b..b485ff29a 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletTransactionsFragment.kt @@ -261,7 +261,8 @@ class WalletTransactionsFragment : Fragment(R.layout.wallet_transactions_fragmen private fun openIdentityCreation() { viewModel.blockchainIdentity.value?.let { blockchainIdentityData -> if (blockchainIdentityData.creationStateErrorMessage != null) { - if (blockchainIdentityData.creationState == BlockchainIdentityData.CreationState.USERNAME_REGISTERING) { + if (blockchainIdentityData.creationState == BlockchainIdentityData.CreationState.USERNAME_REGISTERING || + blockchainIdentityData.creationState == BlockchainIdentityData.CreationState.REQUESTED_NAME_CHECKING) { startActivity(CreateUsernameActivity.createIntentReuseTransaction(requireActivity(), blockchainIdentityData)) } else { Toast.makeText(requireContext(), blockchainIdentityData.creationStateErrorMessage, Toast.LENGTH_LONG).show() diff --git a/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt b/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt index 2802b7d3c..f16df916e 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt @@ -36,6 +36,7 @@ import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.database.entity.BlockchainIdentityData import de.schildbach.wallet.database.entity.DashPayProfile +import de.schildbach.wallet.database.entity.UsernameRequest import de.schildbach.wallet.livedata.Status import de.schildbach.wallet.service.PackageInfoProvider import de.schildbach.wallet.ui.CreateUsernameActivity @@ -237,7 +238,7 @@ class MoreFragment : Fragment(R.layout.fragment_more) { binding.joinDashpayContainer.visibility = View.GONE binding.requestedUsernameContainer.visibility = View.VISIBLE val votingPeriod = it.votingPeriodStart?.let { startTime -> - val endTime = startTime + if (Constants.NETWORK_PARAMETERS.id == NetworkParameters.ID_MAINNET) TimeUnit.DAYS.toMillis(14) else TimeUnit.MINUTES.toMillis(90) + val endTime = startTime + UsernameRequest.VOTING_PERIOD_MILLIS val dateFormat = DateFormat.getMediumDateFormat(requireContext()) String.format("%s", dateFormat.format(endTime)) } ?: "Voting Period not found" diff --git a/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUserNameViewModel.kt b/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUserNameViewModel.kt index 331489be5..7d908b12a 100644 --- a/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUserNameViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUserNameViewModel.kt @@ -35,6 +35,7 @@ import de.schildbach.wallet.service.CoinJoinMode import de.schildbach.wallet.ui.dashpay.CreateIdentityService import de.schildbach.wallet.ui.dashpay.PlatformRepo import de.schildbach.wallet.ui.dashpay.work.BroadcastIdentityVerifyOperation +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -54,6 +55,7 @@ import org.dashj.platform.dashpay.UsernameRequestStatus import org.dashj.platform.dpp.identifier.Identifier import org.dashj.platform.sdk.platform.DomainDocument import org.dashj.platform.sdk.platform.Names +import org.slf4j.LoggerFactory import javax.inject.Inject import kotlin.math.max @@ -78,13 +80,16 @@ data class RequestUserNameUIState( @HiltViewModel class RequestUserNameViewModel @Inject constructor( val walletApplication: WalletApplication, - val identityConfig: BlockchainIdentityConfig, + private val identityConfig: BlockchainIdentityConfig, val walletData: WalletDataProvider, val platformRepo: PlatformRepo, val usernameRequestDao: UsernameRequestDao, val coinJoinConfig: CoinJoinConfig, val analytics: AnalyticsService ) : ViewModel() { + companion object { + private val log = LoggerFactory.getLogger(RequestUserNameViewModel::class.java) + } private val _uiState = MutableStateFlow(RequestUserNameUIState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -136,6 +141,7 @@ class RequestUserNameViewModel @Inject constructor( identityBalance = identity?.let { identity -> platformRepo.getIdentityBalance(Identifier.from(identity.userId)).balance } ?: 0 + log.info("identity balance: {}", identityBalance) if (requestedUserName == null) { requestedUserName = identityConfig.get(USERNAME) } diff --git a/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUsernameFragment.kt b/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUsernameFragment.kt index 301d4eda9..82dffa0ef 100644 --- a/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUsernameFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/username/voting/RequestUsernameFragment.kt @@ -13,6 +13,7 @@ import com.google.android.material.textfield.TextInputLayout import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.Constants import de.schildbach.wallet.database.entity.BlockchainIdentityData +import de.schildbach.wallet.database.entity.UsernameRequest import de.schildbach.wallet.ui.dashpay.DashPayViewModel import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentRequestUsernameBinding @@ -135,8 +136,10 @@ class RequestUsernameFragment : Fragment(R.layout.fragment_request_username) { binding.walletBalanceContainer.isVisible = !it.enoughBalance if (it.usernameContestable || it.usernameContested) { val startDate = Date(it.votingPeriodStart) - val endDate = Date(startDate.time + if (Constants.NETWORK_PARAMETERS.id == NetworkParameters.ID_MAINNET) TimeUnit.DAYS.toMillis(14) else TimeUnit.MINUTES.toMillis(90)) - if (it.votingPeriodStart == -1L && System.currentTimeMillis() - it.votingPeriodStart > TimeUnit.DAYS.toMillis(7)) { + val endDate = Date(startDate.time + UsernameRequest.VOTING_PERIOD_MILLIS) + if (it.votingPeriodStart == -1L && System.currentTimeMillis() - it.votingPeriodStart > UsernameRequest.VOTING_PERIOD_MILLIS) { + binding.votingPeriodContainer.isVisible = false + } else if (it.votingPeriodStart == -1L && System.currentTimeMillis() - it.votingPeriodStart > UsernameRequest.SUBMIT_PERIOD_MILLIS) { binding.votingPeriodContainer.isVisible = false } else { val dateFormat = DateFormat.getMediumDateFormat(context) @@ -162,6 +165,12 @@ class RequestUsernameFragment : Fragment(R.layout.fragment_request_username) { binding.checkAvailable.setImageResource(getCheckMarkImage(false, false)) binding.votingPeriodContainer.isVisible = false } + it.usernameContestable && (it.votingPeriodStart == -1L && System.currentTimeMillis() - it.votingPeriodStart > UsernameRequest.VOTING_PERIOD_MILLIS) -> { + // the submission period has ended, let us just say the username is taken + binding.usernameAvailableMessage.text = getString(R.string.request_username_taken) + binding.checkAvailable.setImageResource(getCheckMarkImage(false, false)) + binding.votingPeriodContainer.isVisible = false + } it.usernameContestable -> { // voting period container will be visible binding.usernameAvailableContainer.isVisible = false @@ -197,7 +206,8 @@ class RequestUsernameFragment : Fragment(R.layout.fragment_request_username) { return@observe } if (it?.creationStateErrorMessage != null) { - requireActivity().finish() + //why are we closing, we should allow the user to chose a new name + //requireActivity().finish() } else if ((it?.creationState?.ordinal ?: 0) > BlockchainIdentityData.CreationState.NONE.ordinal) { // completeUsername = it.username ?: "" // showCompleteState() diff --git a/wallet/src/de/schildbach/wallet/ui/username/voting/VotingRequestDetailsFragment.kt b/wallet/src/de/schildbach/wallet/ui/username/voting/VotingRequestDetailsFragment.kt index 22cf0e958..675548d2a 100644 --- a/wallet/src/de/schildbach/wallet/ui/username/voting/VotingRequestDetailsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/username/voting/VotingRequestDetailsFragment.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import de.schildbach.wallet.Constants +import de.schildbach.wallet.database.entity.UsernameRequest import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentVotingRequestDetailsBinding import org.bitcoinj.core.NetworkParameters @@ -58,7 +59,7 @@ class VotingRequestDetailsFragment : Fragment(R.layout.fragment_voting_request_d binding.identity.text = myUsernameRequest?.identity var isVotingOver = false val votingResults = myUsernameRequest?.createdAt?.let { startTime -> - val endTime = startTime + if (Constants.NETWORK_PARAMETERS.id == NetworkParameters.ID_MAINNET) TimeUnit.DAYS.toMillis(14) else TimeUnit.MINUTES.toMillis(90) + val endTime = startTime + UsernameRequest.VOTING_PERIOD_MILLIS val dateFormat = DateFormat.getMediumDateFormat(requireContext()) isVotingOver = endTime < System.currentTimeMillis() if (isVotingOver) {