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

Monitor transaction submitted for execution #1990

Merged
merged 23 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import io.gnosis.data.db.HeimdallDatabase.Companion.MIGRATION_5_6
import io.gnosis.data.db.HeimdallDatabase.Companion.MIGRATION_6_7
import io.gnosis.data.db.HeimdallDatabase.Companion.MIGRATION_7_8
import io.gnosis.data.db.HeimdallDatabase.Companion.MIGRATION_8_9
import io.gnosis.data.db.HeimdallDatabase.Companion.MIGRATION_9_10
import io.gnosis.data.db.daos.ChainDao
import io.gnosis.data.db.daos.OwnerDao
import io.gnosis.data.db.daos.SafeDao
import io.gnosis.data.db.daos.TransactionLocalDao
import io.gnosis.safe.di.ApplicationContext
import javax.inject.Singleton

Expand All @@ -34,7 +36,8 @@ class DatabaseModule {
MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9
MIGRATION_8_9,
MIGRATION_9_10
)
.build()

Expand All @@ -49,4 +52,8 @@ class DatabaseModule {
@Provides
@Singleton
fun providesChainDao(heimdallDatabase: HeimdallDatabase): ChainDao = heimdallDatabase.chainDao()

@Provides
@Singleton
fun providesTransactionLocalDao(heimdallDatabase: HeimdallDatabase): TransactionLocalDao = heimdallDatabase.transactionLocalDao()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.gnosis.data.backend.GatewayApi
import io.gnosis.data.backend.rpc.RpcClient
import io.gnosis.data.db.daos.ChainDao
import io.gnosis.data.db.daos.SafeDao
import io.gnosis.data.db.daos.TransactionLocalDao
import io.gnosis.data.repositories.*
import io.gnosis.safe.BuildConfig
import io.gnosis.safe.workers.WorkRepository
Expand Down Expand Up @@ -75,6 +76,11 @@ class RepositoryModule {
fun providesTransactionRepository(gatewayApi: GatewayApi): TransactionRepository =
TransactionRepository(gatewayApi)

@Provides
@Singleton
fun providesTransactionLocalRepository(localTxDao: TransactionLocalDao, rpcClient: RpcClient): TransactionLocalRepository =
TransactionLocalRepository(localTxDao, rpcClient)

@Provides
@Singleton
fun providesWorkRepository(workManager: WorkManager): WorkRepository =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,33 @@ import io.gnosis.data.models.AddressInfo
import io.gnosis.data.models.Chain
import io.gnosis.data.models.Owner
import io.gnosis.data.models.Safe
import io.gnosis.data.models.transaction.*
import io.gnosis.data.models.TransactionLocal
import io.gnosis.data.models.transaction.ConflictType
import io.gnosis.data.models.transaction.SafeAppInfo
import io.gnosis.data.models.transaction.Transaction
import io.gnosis.data.models.transaction.TransactionDirection
import io.gnosis.data.models.transaction.TransactionInfo
import io.gnosis.data.models.transaction.TransactionStatus
import io.gnosis.data.models.transaction.TransferInfo
import io.gnosis.data.models.transaction.TransferType
import io.gnosis.data.models.transaction.TxListEntry
import io.gnosis.data.models.transaction.decimals
import io.gnosis.data.models.transaction.symbol
import io.gnosis.data.models.transaction.value
import io.gnosis.data.repositories.CredentialsRepository
import io.gnosis.data.repositories.SafeRepository
import io.gnosis.data.repositories.TransactionLocalRepository
import io.gnosis.safe.R
import io.gnosis.safe.ui.base.AppDispatchers
import io.gnosis.safe.ui.base.BaseStateViewModel
import io.gnosis.safe.ui.transactions.paging.TransactionPagingProvider
import io.gnosis.safe.ui.transactions.paging.TransactionPagingSource
import io.gnosis.safe.utils.*
import io.gnosis.safe.utils.BalanceFormatter
import io.gnosis.safe.utils.DEFAULT_ERC20_SYMBOL
import io.gnosis.safe.utils.DEFAULT_ERC721_SYMBOL
import io.gnosis.safe.utils.formatBackendDateTime
import io.gnosis.safe.utils.formatBackendTimeOfDay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import pm.gnosis.utils.asEthereumAddressString
Expand All @@ -30,6 +46,7 @@ import javax.inject.Inject
class TransactionListViewModel
@Inject constructor(
private val transactionsPager: TransactionPagingProvider,
private val transactionLocalRepository: TransactionLocalRepository,
private val safeRepository: SafeRepository,
private val credentialsRepository: CredentialsRepository,
private val balanceFormatter: BalanceFormatter,
Expand Down Expand Up @@ -66,37 +83,69 @@ class TransactionListViewModel
}
}

private fun getTransactions(
private suspend fun getTransactions(
safe: Safe,
safes: List<Safe>,
owners: List<Owner>,
type: TransactionPagingSource.Type
): Flow<PagingData<TransactionView>> {

var txLocal: TransactionLocal? = null
if (type == TransactionPagingSource.Type.QUEUE ) {
txLocal = transactionLocalRepository.updateLocalTxLatest(safe)
}

val safeTxItems: Flow<PagingData<TransactionView>> = transactionsPager.getTransactionsStream(safe, type)
.map { pagingData ->
pagingData
.map { txListEntry ->
when (txListEntry) {
is TxListEntry.Transaction -> {
val isConflict = txListEntry.conflictType != ConflictType.None
val txView =
getTransactionView(
chain = safe.chain,
transaction = txListEntry.transaction,
safes = safes,
needsYourConfirmation = txListEntry.transaction.canBeSignedByAnyOwner(owners),
isConflict = isConflict,
localOwners = owners
)
if (isConflict) {
TransactionView.Conflict(txView, txListEntry.conflictType)
} else txView
// conflict is resolved if there is local tx with same nonce
// that was submitted for execution
if (txLocal?.safeTxNonce == txListEntry.transaction.executionInfo?.nonce) {
if (txLocal?.safeTxHash == txListEntry.transaction.id.split("_").last() && txListEntry.transaction.txStatus == TransactionStatus.AWAITING_EXECUTION) {
elgatovital marked this conversation as resolved.
Show resolved Hide resolved
val tx = txListEntry.transaction.copy(txStatus = TransactionStatus.PENDING)
txListEntry.transaction
getTransactionView(
chain = safe.chain,
transaction = tx,
safes = safes,
needsYourConfirmation = false,
isConflict = false,
localOwners = owners
)
} else {
TransactionView.Unknown
}
} else {
val isConflict = txListEntry.conflictType != ConflictType.None
val txView =
getTransactionView(
chain = safe.chain,
transaction = txListEntry.transaction,
safes = safes,
needsYourConfirmation = txListEntry.transaction.canBeSignedByAnyOwner(owners),
isConflict = isConflict,
localOwners = owners
)
if (isConflict) {
TransactionView.Conflict(txView, txListEntry.conflictType)
} else txView
}
}
is TxListEntry.DateLabel -> TransactionView.SectionDateHeader(date = txListEntry.timestamp)
is TxListEntry.Label -> TransactionView.SectionLabelHeader(label = txListEntry.label)
is TxListEntry.ConflictHeader -> TransactionView.SectionConflictHeader(nonce = txListEntry.nonce)
TxListEntry.Unknown -> TransactionView.Unknown
is TxListEntry.ConflictHeader -> {
// conflict is resolved if there is local tx with same nonce
// that was submitted for execution
if (txLocal?.safeTxNonce == txListEntry.nonce.toBigInteger()) {
TransactionView.Unknown
} else {
TransactionView.SectionConflictHeader(nonce = txListEntry.nonce)
}
}
else -> TransactionView.Unknown
}
}
.filter { it !is TransactionView.Unknown }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package io.gnosis.safe.ui.transactions.details
import androidx.annotation.VisibleForTesting
import io.gnosis.data.models.Owner
import io.gnosis.data.models.Safe
import io.gnosis.data.models.TransactionLocal
import io.gnosis.data.models.transaction.DetailedExecutionInfo
import io.gnosis.data.models.transaction.TransactionDetails
import io.gnosis.data.models.transaction.TransactionStatus
import io.gnosis.data.repositories.CredentialsRepository
import io.gnosis.data.repositories.SafeRepository
import io.gnosis.data.repositories.TransactionLocalRepository
import io.gnosis.data.repositories.TransactionRepository
import io.gnosis.data.utils.SemVer
import io.gnosis.data.utils.calculateSafeTxHash
Expand All @@ -28,6 +30,7 @@ import javax.inject.Inject
class TransactionDetailsViewModel
@Inject constructor(
private val transactionRepository: TransactionRepository,
private val transactionLocalRepository: TransactionLocalRepository,
private val safeRepository: SafeRepository,
private val credentialsRepository: CredentialsRepository,
private val settingsHandler: SettingsHandler,
Expand All @@ -48,6 +51,8 @@ class TransactionDetailsViewModel
activeSafe.chainId,
txId
)
var txLocal: TransactionLocal? = null

val safes = safeRepository.getSafes()

val executionInfo = txDetails?.detailedExecutionInfo
Expand All @@ -61,20 +66,37 @@ class TransactionDetailsViewModel
canExecute = canBeExecutedFromDevice(executionInfo, owners)
nextInLine = safeInfo.nonce == executionInfo.nonce
safeOwner = isOwner(executionInfo, owners)
txLocal = transactionLocalRepository.updateLocalTx(activeSafe, executionInfo.safeTxHash)
}

var txDetailsViewData = txDetails?.toTransactionDetailsViewData(
safes = safes,
canSign = canSign,
canExecute = canExecute,
owners = owners,
nextInLine = nextInLine,
hasOwnerKey = safeOwner
)

txLocal?.let {
when {
txDetailsViewData?.txStatus == TransactionStatus.AWAITING_EXECUTION -> {
txDetailsViewData = txDetailsViewData?.copy(txStatus = TransactionStatus.PENDING)
}
txDetailsViewData?.txStatus == TransactionStatus.SUCCESS ||
txDetailsViewData?.txStatus == TransactionStatus.FAILED -> {
// tx was indexed by the transaction service
// local transaction can be deleted
transactionLocalRepository.delete(it)
}
}
}

updateState { TransactionDetailsViewState(ViewAction.Loading(false)) }
updateState {
TransactionDetailsViewState(
UpdateDetails(
txDetails?.toTransactionDetailsViewData(
safes = safes,
canSign = canSign,
canExecute = canExecute,
owners = owners,
nextInLine = nextInLine,
hasOwnerKey = safeOwner
)
txDetailsViewData
)
)
}
Expand Down Expand Up @@ -255,6 +277,9 @@ class TransactionDetailsViewModel
val canExecute = canBeExecutedFromDevice(newExecutionInfo, owners)
val safeOwner = isOwner(newExecutionInfo, owners)

//// reload details
elgatovital marked this conversation as resolved.
Show resolved Hide resolved
// loadDetails(newExecutionInfo.safeTxHash)

updateState {
TransactionDetailsViewState(
ConfirmationSubmitted(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@ package io.gnosis.safe.ui.transactions.details.viewdata

import android.os.Parcelable
import androidx.annotation.VisibleForTesting
import io.gnosis.data.adapters.SolidityAddressNullableParceler
import io.gnosis.data.adapters.SolidityAddressParceler
import io.gnosis.data.models.AddressInfo
import io.gnosis.data.models.Owner
import io.gnosis.data.models.Safe
import io.gnosis.data.models.transaction.*
import io.gnosis.data.models.transaction.DataDecoded
import io.gnosis.data.models.transaction.DetailedExecutionInfo
import io.gnosis.data.models.transaction.SafeAppInfo
import io.gnosis.data.models.transaction.SettingsInfo
import io.gnosis.data.models.transaction.SettingsInfoType
import io.gnosis.data.models.transaction.TransactionDetails
import io.gnosis.data.models.transaction.TransactionDirection
import io.gnosis.data.models.transaction.TransactionInfo
import io.gnosis.data.models.transaction.TransactionStatus
import io.gnosis.data.models.transaction.TransactionType
import io.gnosis.data.models.transaction.TransferInfo
import io.gnosis.data.models.transaction.TxData
import io.gnosis.safe.ui.transactions.AddressInfoData
import io.gnosis.data.adapters.SolidityAddressNullableParceler
import io.gnosis.data.adapters.SolidityAddressParceler
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.TypeParceler
import pm.gnosis.crypto.utils.asEthereumAddressChecksumString
import pm.gnosis.model.Solidity
import pm.gnosis.utils.asEthereumAddressString
import java.math.BigInteger
import java.util.*
import java.util.Date

@Parcelize
data class TransactionDetailsViewData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.gnosis.data.models.Safe
import io.gnosis.data.models.transaction.DetailedExecutionInfo
import io.gnosis.data.models.transaction.TxData
import io.gnosis.data.repositories.CredentialsRepository
import io.gnosis.data.repositories.TransactionLocalRepository
import io.gnosis.data.repositories.SafeRepository
import io.gnosis.data.utils.toSignature
import io.gnosis.safe.Tracker
Expand Down Expand Up @@ -36,6 +37,7 @@ class TxReviewViewModel
@Inject constructor(
private val safeRepository: SafeRepository,
private val credentialsRepository: CredentialsRepository,
private val localTxRepository: TransactionLocalRepository,
private val settingsHandler: SettingsHandler,
private val rpcClient: RpcClient,
private val balanceFormatter: BalanceFormatter,
Expand Down Expand Up @@ -453,7 +455,13 @@ class TxReviewViewModel
safeLaunch {
ethTxSignature?.let {
kotlin.runCatching {
rpcClient.send(ethTx!!, it)
val txHash = rpcClient.send(ethTx!!, it)
localTxRepository.saveLocally(
tx = ethTx!!,
txHash = txHash,
safeTxHash = (executionInfo as DetailedExecutionInfo.MultisigExecutionDetails).safeTxHash,
safeTxNonce = (executionInfo as DetailedExecutionInfo.MultisigExecutionDetails).nonce
)
}.onSuccess {
updateState {
TxReviewState(
Expand Down
Loading
Loading