Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dashpay): username request voting period bugs #1318

Open
wants to merge 2 commits into
base: dashpay-voting
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions wallet/src/de/schildbach/wallet/WalletApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -188,6 +189,7 @@ public class WalletApplication extends MultiDexApplication
public Activity currentActivity;

private AutoLogout autoLogout;
private AnrSupervisor anrSupervisor;

@Inject
RestartService restartService;
Expand Down Expand Up @@ -326,6 +328,8 @@ private void logState() {
}

resetBlockchainSyncProgress();
anrSupervisor = new AnrSupervisor();
anrSupervisor.start();
}

private void syncExploreData() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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<RequestUserNameUIState> = _uiState.asStateFlow()

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -247,7 +253,7 @@ class RequestUserNameViewModel @Inject constructor(
}

fun checkUsername(requestedUserName: String?) {
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it's better not to dispatch coroutines in IO scope at the top level, but go down to where the actual work is done and call it inside withContext(Dispatchers.IO) so that the top-level callers don't have to worry about threading, correct context and switching back to the main thread to update UI.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for this idea.

requestedUserName?.let { username ->
val usernameSearchResult = platformRepo.getUsername(username)
val usernameExists = when (usernameSearchResult.status) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading