Skip to content

Commit

Permalink
feat(dashpay): support coinjoin on the entire wallet balance (#1222)
Browse files Browse the repository at this point in the history
* chore: update to dashj 20.0.0-CJ-SNAPSHOT

* feat: use CoinJoinConfig for Mixing Mode (none, interm, adv)

* fix: fix block explorer for devnet

* feat: make SendCoinsTaskRunner coinjoin aware

* tests: fix DatabaseMigrationTest

* feat(coinjoin): mix entire balance

* tests: add mock of CoinJoinConfig

* fix: remove BlockchainServiceExt and further simplify CoinJoinService

* fix: always run CoinJoinService and simplify
  • Loading branch information
HashEngineering authored Nov 21, 2023
1 parent e61bcac commit d911db1
Show file tree
Hide file tree
Showing 17 changed files with 385 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ open class DatabaseMigrationTest {
private val service = ServiceName.CrowdNode
}

private val migrations = arrayOf(AppDatabaseMigrations.migration11To17, AppDatabaseMigrations.migration17To18)
private val migrations = arrayOf(
AppDatabaseMigrations.migration11To12,
AppDatabaseMigrations.migration12To17,
AppDatabaseMigrations.migration17To18
)

@Rule
@JvmField
Expand Down
5 changes: 3 additions & 2 deletions wallet/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,9 @@
<string name="coinjoin_advanced_time">Multiple hours</string>
<string name="coinjoin_start">Start Mixing</string>
<string name="coinjoin_stop">Stop Mixing</string>
<string name="coinjoin_progress">Mixing · %1$s of %2$s</string>
<string name="coinjoin_progress">Mixing · %1$s of %2$s (%3$s)</string>
<string name="coinjoin_progress_finished">Finished · %1$s of %2$s</string>
<string name="coinjoin_change_level_confirmation">Are you sure you want to change the privacy level?</string>
<string name="coinjoin_stop_mixing_title">Are you sure you want to stop mixing?</string>
<string name="coinjoin_stop_mixing_message">Any funds that have been mixed will be combined with your un mixed funds</string>
<string name="coinjoin_stop_mixing_message">Any funds that have been mixed will be combined with your unmixed funds</string>
</resources>
2 changes: 1 addition & 1 deletion wallet/schnapps/res/values/values.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<item>µtDASH, no decimal places</item>
</string-array>
<string-array name="preferences_block_explorer_values">
<item>https://insight.bintang.networks.dash.org:3002/insight</item>
<item>https://insight.ouzo.networks.dash.org:3002/insight</item>
</string-array>
<string-array name="preferences_block_explorer_labels">
<item>Insight Devnet Block Explorer</item>
Expand Down
18 changes: 18 additions & 0 deletions wallet/src/de/schildbach/wallet/data/CoinJoinConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import de.schildbach.wallet.service.CoinJoinMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import org.dash.wallet.common.WalletDataProvider
import org.dash.wallet.common.data.BaseConfig
import javax.inject.Inject
Expand All @@ -34,10 +39,23 @@ open class CoinJoinConfig @Inject constructor(
) : BaseConfig(context, PREFERENCES_NAME, walletDataProvider) {
companion object {
const val PREFERENCES_NAME = "coinjoin"
val COINJOIN_MODE = stringPreferencesKey("coinjoin_mode")
val COINJOIN_ROUNDS = intPreferencesKey("coinjoin_rounds")
val COINJOIN_SESSIONS = intPreferencesKey("coinjoin_sessions")
val COINJOIN_MULTISESSION = booleanPreferencesKey("coinjoin_multisession")
val COINJOIN_AMOUNT = longPreferencesKey("coinjoin_amount")
val FIRST_TIME_INFO_SHOWN = booleanPreferencesKey("first_time_info_shown")
}

fun observeMode(): Flow<CoinJoinMode> {
return observe(COINJOIN_MODE).filterNotNull().map { mode -> CoinJoinMode.valueOf(mode!!) }
}

suspend fun getMode(): CoinJoinMode {
return get(COINJOIN_MODE).let { CoinJoinMode.valueOf(it!!) }
}

suspend fun setMode(mode: CoinJoinMode) {
set(COINJOIN_MODE, mode.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ data class BlockchainIdentityData(var creationState: CreationState = CreationSta
enum class CreationState {
NONE, // this should always be the first value
UPGRADING_WALLET,
MIXING_FUNDS,
CREDIT_FUNDING_TX_CREATING,
CREDIT_FUNDING_TX_SENDING,
CREDIT_FUNDING_TX_SENT,
Expand Down
6 changes: 4 additions & 2 deletions wallet/src/de/schildbach/wallet/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.data.CoinJoinConfig
import de.schildbach.wallet.payments.ConfirmTransactionLauncher
import de.schildbach.wallet.payments.SendCoinsTaskRunner
import de.schildbach.wallet.security.SecurityFunctions
Expand Down Expand Up @@ -99,9 +100,10 @@ abstract class AppModule {
walletData: WalletDataProvider,
walletApplication: WalletApplication,
securityFunctions: SecurityFunctions,
packageInfoProvider: PackageInfoProvider
packageInfoProvider: PackageInfoProvider,
coinJoinConfig: CoinJoinConfig
): SendPaymentService {
val realService = SendCoinsTaskRunner(walletData, walletApplication, securityFunctions, packageInfoProvider)
val realService = SendCoinsTaskRunner(walletData, walletApplication, securityFunctions, packageInfoProvider, coinJoinConfig)

return if (BuildConfig.FLAVOR.lowercase() == "prod") {
realService
Expand Down
37 changes: 27 additions & 10 deletions wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ package de.schildbach.wallet.payments

import androidx.annotation.VisibleForTesting
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.data.CoinJoinConfig
import de.schildbach.wallet.data.PaymentIntent
import de.schildbach.wallet.payments.parsers.PaymentIntentParser
import de.schildbach.wallet.security.SecurityFunctions
import de.schildbach.wallet.security.SecurityGuard
import de.schildbach.wallet.service.CoinJoinMode
import de.schildbach.wallet.service.PackageInfoProvider
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import okhttp3.CacheControl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
Expand All @@ -34,7 +39,6 @@ import okio.IOException
import org.bitcoin.protocols.payments.Protos
import org.bitcoin.protocols.payments.Protos.Payment
import org.bitcoinj.coinjoin.CoinJoinCoinSelector
import org.bitcoinj.coinjoin.UnmixedZeroConfCoinSelector
import org.bitcoinj.core.*
import org.bitcoinj.crypto.IKey
import org.bitcoinj.crypto.KeyCrypterException
Expand All @@ -57,12 +61,25 @@ class SendCoinsTaskRunner @Inject constructor(
private val walletData: WalletDataProvider,
private val walletApplication: WalletApplication,
private val securityFunctions: SecurityFunctions,
private val packageInfoProvider: PackageInfoProvider
private val packageInfoProvider: PackageInfoProvider,
coinJoinConfig: CoinJoinConfig
) : SendPaymentService {
companion object {
private const val WALLET_EXCEPTION_MESSAGE = "this method can't be used before creating the wallet"
private val log = LoggerFactory.getLogger(SendCoinsTaskRunner::class.java)
}
private var coinJoinSend = false
private val coroutineScope = CoroutineScope(Dispatchers.IO)

init {
coinJoinConfig
.observeMode()
.filterNotNull()
.onEach { mode ->
coinJoinSend = mode != CoinJoinMode.NONE
}
.launchIn(coroutineScope)
}

@Throws(LeftoverBalanceException::class)
override suspend fun sendCoins(
Expand Down Expand Up @@ -233,12 +250,11 @@ class SendCoinsTaskRunner @Inject constructor(
mayEditAmount: Boolean,
paymentIntent: PaymentIntent,
signInputs: Boolean,
forceEnsureMinRequiredFee: Boolean,
coinJoin: Boolean = false
forceEnsureMinRequiredFee: Boolean
): SendRequest {
val wallet = walletData.wallet ?: throw RuntimeException(WALLET_EXCEPTION_MESSAGE)
val sendRequest = paymentIntent.toSendRequest()
sendRequest.coinSelector = getCoinSelector(coinJoin)
sendRequest.coinSelector = getCoinSelector()
sendRequest.useInstantSend = false
sendRequest.feePerKb = Constants.ECONOMIC_FEE
sendRequest.ensureMinRequiredFee = forceEnsureMinRequiredFee
Expand All @@ -256,15 +272,14 @@ class SendCoinsTaskRunner @Inject constructor(
amount: Coin,
coinSelector: CoinSelector? = null,
emptyWallet: Boolean = false,
forceMinFee: Boolean = true,
coinJoin: Boolean = false
forceMinFee: Boolean = true
): SendRequest {
return SendRequest.to(address, amount).apply {
this.feePerKb = Constants.ECONOMIC_FEE
this.ensureMinRequiredFee = forceMinFee
this.emptyWallet = emptyWallet

val selector = coinSelector ?: getCoinSelector(coinJoin)
val selector = coinSelector ?: getCoinSelector()
this.coinSelector = selector

if (selector is ByAddressCoinSelector) {
Expand All @@ -273,10 +288,12 @@ class SendCoinsTaskRunner @Inject constructor(
}
}

private fun getCoinSelector(coinJoin: Boolean) = if (coinJoin) {
private fun getCoinSelector() = if (coinJoinSend) {
// mixed only
CoinJoinCoinSelector(walletData.wallet)
} else {
UnmixedZeroConfCoinSelector(walletData.wallet)
// collect all coins, mixed and unmixed
ZeroConfCoinSelector.get()
}

@Throws(LeftoverBalanceException::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
import org.bitcoinj.utils.MonetaryFormat;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.DefaultRiskAnalysis;
import org.bitcoinj.wallet.Protos;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension;
import org.dash.wallet.common.Configuration;
Expand Down Expand Up @@ -165,7 +164,7 @@ public class BlockchainServiceImpl extends LifecycleService implements Blockchai
@Inject PackageInfoProvider packageInfoProvider;
@Inject ConnectivityManager connectivityManager;
@Inject BlockchainStateDataProvider blockchainStateDataProvider;

@Inject CoinJoinService coinJoinService; // not used in this class, but we need to create it
private BlockStore blockStore;
private BlockStore headerStore;
private File blockChainFile;
Expand Down
Loading

0 comments on commit d911db1

Please sign in to comment.