Skip to content

Commit

Permalink
feat: add backend for submitting username votes
Browse files Browse the repository at this point in the history
  • Loading branch information
HashEngineering committed Sep 28, 2024
1 parent 2d27826 commit 48576be
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,40 @@

package de.schildbach.wallet.service.platform

import com.google.common.base.Preconditions
import de.schildbach.wallet.database.entity.DashPayContactRequest
import de.schildbach.wallet.database.entity.DashPayProfile
import de.schildbach.wallet.security.SecurityGuard
import de.schildbach.wallet.ui.dashpay.PlatformRepo
import org.bitcoinj.core.Context
import org.bitcoinj.core.ECKey
import org.bitcoinj.core.KeyId
import org.bitcoinj.core.Sha256Hash
import org.bitcoinj.evolution.EvolutionContact
import org.bouncycastle.crypto.params.KeyParameter
import org.dash.wallet.common.WalletDataProvider
import org.dash.wallet.common.services.analytics.AnalyticsConstants
import org.dash.wallet.common.services.analytics.AnalyticsService
import org.dash.wallet.common.services.analytics.AnalyticsTimer
import org.dashj.platform.dashpay.callback.SimpleSignerCallback
import org.dashj.platform.dashpay.callback.WalletSignerCallback
import org.dashj.platform.dpp.identifier.Identifier
import org.dashj.platform.dpp.voting.ResourceVoteChoice
import org.dashj.platform.dpp.voting.Vote
import org.dashj.platform.sdk.Purpose
import org.dashj.platform.wallet.IdentityVerifyDocument
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import javax.inject.Inject


interface PlatformBroadcastService {
suspend fun broadcastUpdatedProfile(dashPayProfile: DashPayProfile, encryptionKey: KeyParameter): DashPayProfile
suspend fun sendContactRequest(toUserId: String): DashPayContactRequest
suspend fun sendContactRequest(toUserId: String, encryptionKey: KeyParameter): DashPayContactRequest
suspend fun broadcastIdentityVerify(username: String, url: String, encryptionKey: KeyParameter?): IdentityVerifyDocument
suspend fun broadcastUsernameVotes(usernames: List<String>, resourceVoteChoices: List<ResourceVoteChoice>, masternodeKeys: List<ByteArray>, encryptionKey: KeyParameter?): List<Vote>
}

class PlatformDocumentBroadcastService @Inject constructor(
Expand Down Expand Up @@ -115,6 +127,50 @@ class PlatformDocumentBroadcastService @Inject constructor(
return identityVerifyDocument
}

override suspend fun broadcastUsernameVotes(
usernames: List<String>,
resourceVoteChoices: List<ResourceVoteChoice>,
masternodeKeys: List<ByteArray>,
encryptionKey: KeyParameter?
): List<Vote> {
Preconditions.checkArgument(usernames.size == resourceVoteChoices.size)
val votes = arrayListOf<Vote>()
masternodeKeys.forEach { masternodeKeyBytes ->
// determine identity
val masternodeKey = ECKey.fromPrivate(masternodeKeyBytes)
val votingKeyId = KeyId.fromBytes(masternodeKey.pubKeyHash)
val boas = ByteArrayOutputStream(32 + 20)
val masternodes = walletDataProvider.wallet!!.context.masternodeListManager.masternodeList.getMasternodesByVotingKey(votingKeyId)
masternodes.forEach { masternode ->
try {
boas.write(masternode.proTxHash.bytes)
boas.write(masternodeKey.pubKeyHash)
val idBytes = Sha256Hash.of(boas.toByteArray())
val identity = platform.identities.get(Identifier.from(idBytes.bytes))
val votingIdentityPublicKey = identity!!.publicKeys.first { it.purpose == Purpose.VOTING }

usernames.forEachIndexed { index, username ->
val resourceVoteChoice = resourceVoteChoices[index]
val vote = platform.names.broadcastVote(
resourceVoteChoice,
username,
masternode.proTxHash,
votingIdentityPublicKey,
SimpleSignerCallback(
mapOf(votingIdentityPublicKey to masternodeKey),
encryptionKey
)
)
votes.add(vote)
}
} catch (e: Exception) {
log.info("broadcast username vote failed:", e)
}
}
}
return votes
}

@Throws(Exception::class)
override suspend fun broadcastUpdatedProfile(dashPayProfile: DashPayProfile, encryptionKey: KeyParameter): DashPayProfile {
log.info("broadcast profile")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,8 @@ class PlatformSynchronizationService @Inject constructor(
checkUsernameVotingStatus()
try {
val contestedNames = platform.platform.names.getContestedNames()
val myIdentifier = platformRepo.blockchainIdentity.uniqueIdentifier
// usernameRequestDao.clear()
// val myIdentifier = platformRepo.blockchainIdentity.uniqueIdentifier
for (name in contestedNames) {
try {
val voteContender = platformRepo.getVoteContenders(name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2024 Dash Core Group
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package de.schildbach.wallet.ui.dashpay.work

import android.annotation.SuppressLint
import android.app.Application
import androidx.work.*
import de.schildbach.wallet.Constants
import de.schildbach.wallet.security.SecurityGuard
import org.bitcoinj.core.ECKey
import org.dashj.platform.dpp.voting.ResourceVoteChoice
import org.slf4j.LoggerFactory

class BroadcastUsernameVotesOperation(val application: Application) {

class BroadcastUsernameVotesOperationException(message: String) : java.lang.Exception(message)

companion object {
private val log = LoggerFactory.getLogger(BroadcastUsernameVotesOperation::class.java)

private const val WORK_NAME = "BroadcastUsernameVotes.WORK#"

fun uniqueWorkName(usernames: String) = WORK_NAME + usernames
}

private val workManager: WorkManager = WorkManager.getInstance(application)

/**
* Gets the list of all BroadcastUsernameVotesOperation WorkInfo's
*/
val allOperationsData = workManager.getWorkInfosByTagLiveData(BroadcastUsernameVotesWorker::class.qualifiedName!!)

@SuppressLint("EnqueueWork")
fun create(usernames: List<String>, voteChoices: List<ResourceVoteChoice>, masternodeKeys: List<ByteArray>): WorkContinuation {

val password = SecurityGuard().retrievePassword()
val verifyIdentityWorker = OneTimeWorkRequestBuilder<BroadcastUsernameVotesWorker>()
.setInputData(
workDataOf(
BroadcastUsernameVotesWorker.KEY_PASSWORD to password,
BroadcastUsernameVotesWorker.KEY_USERNAMES to usernames.toTypedArray(),
BroadcastUsernameVotesWorker.KEY_VOTE_CHOICES to voteChoices.map { it.toString() }.toTypedArray(),
BroadcastUsernameVotesWorker.KEY_MASTERNODE_KEYS to masternodeKeys.map {
ECKey.fromPrivate(it).getPrivateKeyAsWiF(Constants.NETWORK_PARAMETERS)
}.toTypedArray()
)
)
.addTag("usernames:$usernames")
.build()
log.info("creating BroadcastUsernameVotesOperation({}, {})", usernames, voteChoices)
return WorkManager.getInstance(application)
.beginUniqueWork(uniqueWorkName(usernames.joinToString(",")),
ExistingWorkPolicy.KEEP,
verifyIdentityWorker)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2024 Dash Core Group
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.schildbach.wallet.ui.dashpay.work

import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.bumptech.glide.load.engine.Resource
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import de.schildbach.wallet.Constants
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.service.platform.PlatformBroadcastService
import de.schildbach.wallet.service.platform.PlatformSyncService
import de.schildbach.wallet.ui.dashpay.PlatformRepo
import org.bitcoinj.core.DumpedPrivateKey
import org.bitcoinj.crypto.KeyCrypterException
import org.bouncycastle.crypto.params.KeyParameter
import org.dash.wallet.common.WalletDataProvider
import org.dash.wallet.common.services.analytics.AnalyticsService
import org.dashj.platform.dpp.voting.ResourceVoteChoice
import org.dashj.platform.sdk.ContestedDocumentResourceVotePoll
import org.slf4j.LoggerFactory

@HiltWorker
class BroadcastUsernameVotesWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted parameters: WorkerParameters,
val analytics: AnalyticsService,
val platformBroadcastService: PlatformBroadcastService,
val platformSyncService: PlatformSyncService,
val walletDataProvider: WalletDataProvider
) : BaseWorker(context, parameters) {

companion object {
private val log = LoggerFactory.getLogger(BroadcastUsernameVotesWorker::class.java)

const val KEY_PASSWORD = "BroadcastUsernameVotesWorker.PASSWORD"
const val KEY_USERNAMES = "BroadcastUsernameVotesWorker.USERNAMES"
const val KEY_VOTE_CHOICES = "BroadcastUsernameVotesWorker.VOTE_CHOICES"
const val KEY_MASTERNODE_KEYS = "BroadcastUsernameVotesWorker.MASTERNODE_KEYS"
}

override suspend fun doWorkWithBaseProgress(): Result {
val password = inputData.getString(KEY_PASSWORD)
?: return Result.failure(workDataOf(KEY_ERROR_MESSAGE to "missing KEY_PASSWORD parameter"))
val usernames = inputData.getStringArray(KEY_USERNAMES)
?: return Result.failure(workDataOf(KEY_ERROR_MESSAGE to "missing KEY_USERNAMES parameter"))
val voteChoices = inputData.getStringArray(KEY_VOTE_CHOICES)
?: return Result.failure(workDataOf(KEY_ERROR_MESSAGE to "missing KEY_VOTE_CHOICES parameter"))
val masternodeKeys = inputData.getStringArray(KEY_MASTERNODE_KEYS)
?: return Result.failure(workDataOf(KEY_ERROR_MESSAGE to "missing KEY_MASTERNODE_KEYS parameter"))

val encryptionKey: KeyParameter
try {
encryptionKey = walletDataProvider.wallet!!.keyCrypter!!.deriveKey(password)
} catch (ex: KeyCrypterException) {
analytics.logError(ex, "Broadcast Username Vote: failed to derive encryption key")
val msg = formatExceptionMessage("derive encryption key", ex)
return Result.failure(workDataOf(KEY_ERROR_MESSAGE to msg))
}

return try {
log.info("creating BroadcastUsernameVotesWorker({}, {})", usernames, voteChoices)

val votes = platformBroadcastService.broadcastUsernameVotes(
usernames.toList(),
voteChoices.map { ResourceVoteChoice.from(it) },
masternodeKeys.map { DumpedPrivateKey.fromBase58(Constants.NETWORK_PARAMETERS, it).key.privKeyBytes },
encryptionKey
)
// this will update the DB and trigger observers
platformSyncService.updateUsernameRequestsWithVotes()
Result.success(
workDataOf(
KEY_USERNAMES to if(votes.isNotEmpty()) {
votes.map { (it.resourceVote.votePoll as? ContestedDocumentResourceVotePoll)?.index_values?.get(1) ?: "null" }.toTypedArray()
} else {
listOf("").toTypedArray()
},
)
)
} catch (ex: Exception) {
analytics.logError(ex, "Username Voting: failed to broadcast votes")
Result.failure(workDataOf(
KEY_ERROR_MESSAGE to formatExceptionMessage("broadcast username vote", ex)))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.schildbach.wallet.Constants
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.database.dao.ImportedMasternodeKeyDao
import de.schildbach.wallet.database.dao.UsernameRequestDao
import de.schildbach.wallet.database.dao.UsernameVoteDao
Expand All @@ -29,6 +30,7 @@ import de.schildbach.wallet.database.entity.UsernameRequest
import de.schildbach.wallet.database.entity.UsernameVote
import de.schildbach.wallet.service.platform.PlatformSyncService
import de.schildbach.wallet.ui.dashpay.utils.DashPayConfig
import de.schildbach.wallet.ui.dashpay.work.BroadcastUsernameVotesOperation
import de.schildbach.wallet.ui.username.adapters.UsernameRequestGroupView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -57,6 +59,8 @@ import org.bitcoinj.wallet.AuthenticationKeyChain
import org.bitcoinj.wallet.authentication.AuthenticationKeyStatus
import org.bitcoinj.wallet.authentication.AuthenticationKeyUsage
import org.dash.wallet.common.WalletDataProvider
import org.dashj.platform.dpp.identifier.Identifier
import org.dashj.platform.dpp.voting.ResourceVoteChoice
import org.dashj.platform.sdk.platform.Names
import java.util.UUID
import javax.inject.Inject
Expand Down Expand Up @@ -92,7 +96,8 @@ class UsernameRequestsViewModel @Inject constructor(
private val usernameVoteDao: UsernameVoteDao,
private val importedMasternodeKeyDao: ImportedMasternodeKeyDao,
private val platformSyncService: PlatformSyncService,
private val walletDataProvider: WalletDataProvider
private val walletDataProvider: WalletDataProvider,
private val walletApplication: WalletApplication
): ViewModel() {
private val workerJob = SupervisorJob()
private val viewModelWorkerScope = CoroutineScope(Dispatchers.IO + workerJob)
Expand Down Expand Up @@ -243,6 +248,12 @@ class UsernameRequestsViewModel @Inject constructor(

viewModelScope.launch {
usernameRequestDao.getRequest(requestId)?.let { request ->
BroadcastUsernameVotesOperation(walletApplication).create(
listOf(request.normalizedLabel),
listOf(ResourceVoteChoice.towardsIdentity(Identifier.from(request.identity))),
masternodes.value.map { it.votingPrivateKey }
).enqueue()

usernameRequestDao.removeApproval(request.username)
usernameRequestDao.update(request.copy(votes = request.votes + keysAmount, isApproved = true))
_uiState.update { it.copy(voteSubmitted = true) }
Expand All @@ -264,6 +275,11 @@ class UsernameRequestsViewModel @Inject constructor(

viewModelScope.launch {
usernameRequestDao.getRequest(requestId)?.let { request ->
BroadcastUsernameVotesOperation(walletApplication).create(
listOf(request.normalizedLabel),
listOf(ResourceVoteChoice.abstain()),
masternodes.value.map { it.votingPrivateKey }
).enqueue()
usernameRequestDao.update(request.copy(votes = request.votes - keysAmount, isApproved = false))
_uiState.update { it.copy(voteCancelled = true) }
usernameVoteDao.insert(
Expand Down Expand Up @@ -296,6 +312,21 @@ class UsernameRequestsViewModel @Inject constructor(
keysAmount
)
_uiState.update { it.copy(voteSubmitted = true) }

val usernames = arrayListOf<String>()
val voteChoices = arrayListOf<ResourceVoteChoice>()
requestIds.forEach {
usernameRequestDao.getRequest(it)?.let { request ->
usernames.add(request.normalizedLabel)
voteChoices.add(ResourceVoteChoice.towardsIdentity(Identifier.from(request.identity)))
}
}

BroadcastUsernameVotesOperation(walletApplication).create(
usernames,
voteChoices,
masternodes.value.map { it.votingPrivateKey }
).enqueue()
}
}

Expand Down Expand Up @@ -472,23 +503,28 @@ class UsernameRequestsViewModel @Inject constructor(
}
}

fun block(requestId: String) {
fun block(username: String) {
if (keysAmount == 0) {
return
}

viewModelScope.launch {
usernameRequestDao.getRequest(requestId)?.let { request ->
usernameRequestDao.update(request.copy(lockVotes = request.lockVotes + keysAmount, isApproved = true))
// usernameRequestDao.getRequest(requestId)?.let { request ->
BroadcastUsernameVotesOperation(walletApplication).create(
listOf(username),
listOf(ResourceVoteChoice.lock()),
masternodes.value.map { it.votingPrivateKey }
).enqueue()
//usernameRequestDao.update(request.copy(lockVotes = request.lockVotes + keysAmount, isApproved = true))
_uiState.update { it.copy(voteSubmitted = true) }
usernameVoteDao.insert(
UsernameVote(
request.username,
request.identity,
username,
"",
UsernameVote.LOCK
)
)
}
}
//}
}
}

0 comments on commit 48576be

Please sign in to comment.