Skip to content

Commit

Permalink
fix: consolidate timeSkew functions and link better into CoinJoinService
Browse files Browse the repository at this point in the history
  • Loading branch information
HashEngineering committed Feb 12, 2024
1 parent d93d0aa commit c904b14
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 47 deletions.
51 changes: 23 additions & 28 deletions wallet/src/de/schildbach/wallet/service/CoinJoinService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.google.common.base.Stopwatch
import dagger.hilt.android.qualifiers.ApplicationContext
import de.schildbach.wallet.data.CoinJoinConfig
import de.schildbach.wallet.ui.dashpay.PlatformRepo
import de.schildbach.wallet.util.getTimeSkew
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
Expand Down Expand Up @@ -66,15 +67,12 @@ import org.bouncycastle.crypto.params.KeyParameter
import org.dash.wallet.common.WalletDataProvider
import org.dash.wallet.common.data.NetworkStatus
import org.dash.wallet.common.services.BlockchainStateProvider
import org.dash.wallet.common.util.Constants
import org.dash.wallet.common.util.head
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs

enum class CoinJoinMode {
NONE,
Expand All @@ -91,6 +89,7 @@ interface CoinJoinService {
suspend fun getMixingState(): MixingStatus
fun observeMixingState(): Flow<MixingStatus>
fun observeMixingProgress(): Flow<Double>
fun updateTimeSkew(timeSkew: Long)
}

enum class MixingStatus {
Expand All @@ -117,7 +116,7 @@ class CoinJoinMixingService @Inject constructor(
const val DEFAULT_SESSIONS = 4
const val DEFAULT_DENOMINATIONS_GOAL = 50
const val DEFAULT_DENOMINATIONS_HARDCAP = 300
const val MAX_ALLOWED_TIMESKEW = 2000L // 2 seconds
const val MAX_ALLOWED_TIMESKEW = 4000L // 4 seconds

// these are not for production
val FAST_MIXING_DENOMINATIONS_REMOVE = listOf<Denomination>() // Denomination.THOUSANDTH)
Expand Down Expand Up @@ -162,7 +161,7 @@ class CoinJoinMixingService @Inject constructor(
// Time has changed, handle the change here
log.info("Time or Time Zone changed")
coroutineScope.launch {
updateTimeSkew(getTimeSkew())
updateTimeSkewInternal(getTimeSkew())
}
}
}
Expand Down Expand Up @@ -218,7 +217,14 @@ class CoinJoinMixingService @Inject constructor(
.launchIn(coroutineScope)
}

private suspend fun updateTimeSkew(timeSkew: Long) {
/** updates timeSkew in #[coroutineScope] */
override fun updateTimeSkew(timeSkew: Long) {
coroutineScope.launch {
updateTimeSkewInternal(timeSkew)
}
}

suspend fun updateTimeSkewInternal(timeSkew: Long) {
updateState(config.getMode(), timeSkew, hasAnonymizableBalance, networkStatus, isSynced, blockChain)
}

Expand Down Expand Up @@ -247,10 +253,14 @@ class CoinJoinMixingService @Inject constructor(
else -> false
}

log.info("coinjoin: mixing can occur: $hasBalanceLeftToMix = balance: (${anonBalance.isGreaterThan(CoinJoin.getSmallestDenomination())} && canDenominate: $canDenominate) || partially-mixed: $hasPartiallyMixedCoins")
log.info(
"coinjoin: mixing can occur: $hasBalanceLeftToMix = balance: (${anonBalance.isGreaterThan(
CoinJoin.getSmallestDenomination()
)} && canDenominate: $canDenominate) || partially-mixed: $hasPartiallyMixedCoins"
)
updateState(
config.getMode(),
timeSkew,
getTimeSkew(),
hasBalanceLeftToMix,
networkStatus,
isSynced,
Expand All @@ -268,13 +278,14 @@ class CoinJoinMixingService @Inject constructor(
) {
updateMutex.lock()
log.info(
"coinjoin-old-state: ${this.mode}, ${this.timeSkew / 1000}s, ${this.hasAnonymizableBalance}, ${this.networkStatus}, synced: ${this.isSynced} ${blockChain != null}"
"coinjoin-old-state: ${this.mode}, ${this.timeSkew}ms, ${this.hasAnonymizableBalance}, ${this.networkStatus}, synced: ${this.isSynced} ${blockChain != null}"
)
try {
setBlockchain(blockChain)
log.info(
"coinjoin-new-state: $mode, ${timeSkew / 1000}s, $hasAnonymizableBalance, $networkStatus, synced: $isSynced, ${blockChain != null}"
"coinjoin-new-state: $mode, $timeSkew ms, $hasAnonymizableBalance, $networkStatus, synced: $isSynced, ${blockChain != null}"
)
log.info("coinjoin-Current timeskew: ${getTimeSkew()}")
this.networkStatus = networkStatus
this.hasAnonymizableBalance = hasAnonymizableBalance
this.isSynced = isSynced
Expand Down Expand Up @@ -336,24 +347,6 @@ class CoinJoinMixingService @Inject constructor(
updateState(mode, timeSkew, hasAnonymizableBalance, networkStatus, isSynced, blockChain)
}

private suspend fun getTimeSkew(): Long {
val systemTimeMillis = System.currentTimeMillis()
var result = Constants.HTTP_CLIENT.head("https://www.dash.org/")
var networkTime = result.headers.getDate("date")?.time
if (networkTime == null) {
result = Constants.HTTP_CLIENT.head("https://insight.dash.org/insight")
networkTime = result.headers.getDate("date")?.time
}
requireNotNull(networkTime)
if (networkStatus == NetworkStatus.CONNECTED) {
val peerList = walletDataProvider.wallet!!.context.peerGroup.connectedPeers
peerList.forEach { peer ->
peer.lastPingTime
}
}
return abs(systemTimeMillis - networkTime)
}

private suspend fun updateMode(mode: CoinJoinMode) {
CoinJoinClientOptions.setEnabled(mode != CoinJoinMode.NONE)
if (mode != CoinJoinMode.NONE && this.mode == CoinJoinMode.NONE) {
Expand Down Expand Up @@ -532,6 +525,8 @@ class CoinJoinMixingService @Inject constructor(
} else {
// run this on a different thread?
val asyncStart = coroutineScope.async(Dispatchers.IO) {
// though coroutineScope is on a Context propogated thread, we still need this
org.bitcoinj.core.Context.propagate(walletDataProvider.wallet!!.context)
coinJoinManager?.initMasternodeGroup(blockChain)
clientManager.doAutomaticDenominating()
}
Expand Down
11 changes: 0 additions & 11 deletions wallet/src/de/schildbach/wallet/ui/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,6 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm
requestDisableBatteryOptimisation()
}

private val timeChangedReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == Intent.ACTION_TIME_CHANGED) {
// Time has changed, handle the change here
checkTimeSkew(viewModel)
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
Expand Down Expand Up @@ -157,7 +148,6 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm
val timeChangedFilter = IntentFilter().apply {
addAction(Intent.ACTION_TIME_CHANGED)
}
registerReceiver(timeChangedReceiver, timeChangedFilter)
}

override fun onStart() {
Expand Down Expand Up @@ -571,7 +561,6 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm
override fun onDestroy() {
super.onDestroy()
viewModel.platformRepo.onIdentityResolved = null
unregisterReceiver(timeChangedReceiver)
}

override fun onLockScreenDeactivated() {
Expand Down
12 changes: 5 additions & 7 deletions wallet/src/de/schildbach/wallet/ui/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import de.schildbach.wallet.ui.dashpay.PlatformRepo
import de.schildbach.wallet.ui.dashpay.utils.DashPayConfig
import de.schildbach.wallet.ui.dashpay.work.SendContactRequestOperation
import de.schildbach.wallet.ui.transactions.TransactionRowView
import de.schildbach.wallet.util.getTimeSkew
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -136,8 +137,8 @@ class MainViewModel @Inject constructor(
companion object {
private const val THROTTLE_DURATION = 500L
private const val DIRECTION_KEY = "tx_direction"
private const val TIME_SKEW_TOLERANCE = 3600 // seconds (1 hour)
private const val TIME_SKEW_TOLERANCE_COINJOIN = 2 // seconds
private const val TIME_SKEW_TOLERANCE = 3600000 // seconds (1 hour)
private const val TIME_SKEW_TOLERANCE_COINJOIN = 4000 // seconds
private val log = LoggerFactory.getLogger(MainViewModel::class.java)
}

Expand Down Expand Up @@ -387,16 +388,13 @@ class MainViewModel @Inject constructor(

suspend fun getDeviceTimeSkew(): Pair<Boolean, Long> {
return try {
val systemTimeMillis = System.currentTimeMillis()
val result = HTTP_CLIENT.head("https://www.dash.org/")
val networkTime = result.headers.getDate("date")?.time
requireNotNull(networkTime)
val timeSkew = getTimeSkew()
val maxAllowedTimeSkew = if (coinJoinConfig.getMode() == CoinJoinMode.NONE) {
TIME_SKEW_TOLERANCE
} else {
TIME_SKEW_TOLERANCE_COINJOIN
}
val timeSkew = abs(systemTimeMillis - networkTime) / 1000
coinJoinService.updateTimeSkew(timeSkew)
return Pair(timeSkew > maxAllowedTimeSkew, timeSkew)
} catch (ex: Exception) {
// Ignore errors
Expand Down
3 changes: 2 additions & 1 deletion wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ object WalletActivityExt {
val coinJoinOn = viewModel.getCoinJoinMode() != CoinJoinMode.NONE
if (isTimeSkewed && (!timeSkewDialogShown || coinJoinOn)) {
timeSkewDialogShown = true
showTimeSkewAlertDialog(timeSkew, coinJoinOn)
// add 1 to round up so 2.2 seconds appears as 3
showTimeSkewAlertDialog(1 + timeSkew / 1000, coinJoinOn)
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions wallet/src/de/schildbach/wallet/util/TimeUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package de.schildbach.wallet.util

import org.dash.wallet.common.util.Constants
import org.dash.wallet.common.util.head
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import kotlin.math.abs

private fun queryNtpTime(server: String): Long? {
try {
val address = InetAddress.getByName(server)
val message = ByteArray(48)
message[0] = 0b00100011 // NTP mode client (3) and version (4)

val socket = DatagramSocket().apply {
soTimeout = 3000 // Set timeout to 3000ms
}

val request = DatagramPacket(message, message.size, address, 123)
socket.send(request) // Send request

// Receive response
val response = DatagramPacket(message, message.size)
socket.receive(response)

// Timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First byte is the high-order byte of the integer;
// the last byte is the low-order byte. The high word is the seconds field,
// and the low word is the fractional field.
val seconds = message[40].toLong() and 0xff shl 24 or
(message[41].toLong() and 0xff shl 16) or
(message[42].toLong() and 0xff shl 8) or
(message[43].toLong() and 0xff)

// Convert seconds to milliseconds and adjust from 1900 to epoch (1970)
return (seconds - 2208988800L) * 1000
} catch (e: Exception) {
e.printStackTrace()
}
return null
}

suspend fun getTimeSkew(): Long {
var networkTime = queryNtpTime("pool.ntp.org")
if (networkTime == null) {
var result = Constants.HTTP_CLIENT.head("https://www.dash.org/")

networkTime = result.headers.getDate("date")?.time
if (networkTime == null) {
result = Constants.HTTP_CLIENT.head("https://insight.dash.org/insight")
networkTime = result.headers.getDate("date")?.time
}
requireNotNull(networkTime)
}
val systemTimeMillis = System.currentTimeMillis()
return abs(systemTimeMillis - networkTime)
}

0 comments on commit c904b14

Please sign in to comment.