(@desktop/wallet): Fix for pending tx not shown in history view

fixes #7530
This commit is contained in:
Khushboo Mehta 2022-11-14 15:47:44 +01:00 committed by Khushboo-dev-cpp
parent ed42928f0b
commit 6c0806c2e1
16 changed files with 179 additions and 51 deletions

View File

@ -81,11 +81,14 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_SUGGESTED_ROUTES_READY) do(e:Args):
self.delegate.suggestedRoutesReady(SuggestedRoutesArgs(e).suggestedRoutes)
proc checkPendingTransactions*(self: Controller) =
self.transactionService.checkPendingTransactions()
self.events.on(SIGNAL_PENDING_TX_COMPLETED) do(e:Args):
self.walletAccountService.checkRecentHistory()
proc checkRecentHistory*(self: Controller) =
self.walletAccountService.checkRecentHistory()
proc checkPendingTransactions*(self: Controller): seq[TransactionDto] =
return self.transactionService.checkPendingTransactions()
proc checkRecentHistory*(self: Controller, calledFromTimerOrInit = false) =
self.walletAccountService.checkRecentHistory(calledFromTimerOrInit)
proc getWalletAccounts*(self: Controller): seq[WalletAccountDto] =
self.walletAccountService.getWalletAccounts()

View File

@ -16,7 +16,7 @@ method load*(self: AccessInterface) {.base.} =
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method checkRecentHistory*(self: AccessInterface) {.base.} =
method checkRecentHistory*(self: AccessInterface, calledFromTimerOrInit: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method switchAccount*(self: AccessInterface, accountIndex: int) {.base.} =

View File

@ -27,6 +27,7 @@ type
baseGasFees: string
totalFees: string
maxTotalFees: string
symbol: string
proc initItem*(
id: string,
@ -53,7 +54,8 @@ proc initItem*(
isTimeStamp: bool,
baseGasFees: string,
totalFees: string,
maxTotalFees: string
maxTotalFees: string,
symbol: string
): Item =
result.id = id
result.typ = typ
@ -80,6 +82,7 @@ proc initItem*(
result.baseGasFees = baseGasFees
result.totalFees = totalFees
result.maxTotalFees = maxTotalFees
result.symbol = symbol
proc `$`*(self: Item): string =
result = fmt"""AllTokensItem(
@ -108,6 +111,7 @@ proc `$`*(self: Item): string =
baseGasFees: {self.baseGasFees},
totalFees: {self.totalFees},
maxTotalFees: {self.maxTotalFees},
symbol: {self.symbol},
]"""
proc getId*(self: Item): string =
@ -183,4 +187,7 @@ proc getTotalFees*(self: Item): string =
return self.totalFees
proc getMaxTotalFees*(self: Item): string =
return self.maxTotalFees
return self.maxTotalFees
proc getSymbol*(self: Item): string =
return self.symbol

View File

@ -31,6 +31,7 @@ type
BaseGasFees
TotalFees
MaxTotalFees
Symbol
QtObject:
type
@ -94,7 +95,8 @@ QtObject:
ModelRole.IsTimeStamp.int: "isTimeStamp",
ModelRole.BaseGasFees.int: "baseGasFees",
ModelRole.TotalFees.int: "totalFees",
ModelRole.MaxTotalFees.int: "maxTotalFees"
ModelRole.MaxTotalFees.int: "maxTotalFees",
ModelRole.Symbol.int: "symbol"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -158,6 +160,8 @@ QtObject:
result = newQVariant(item.getTotalFees())
of ModelRole.MaxTotalFees:
result = newQVariant(item.getMaxTotalFees())
of ModelRole.Symbol:
result = newQVariant(item.getSymbol())
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()
@ -187,6 +191,8 @@ QtObject:
proc cmpTransactions*(x, y: Item): int =
# Sort proc to compare transactions from a single account.
# Compares first by block number, then by nonce
if x.getBlockNumber().isEmptyOrWhitespace or y.getBlockNumber().isEmptyOrWhitespace :
return cmp(x.getTimestamp(), y.getTimestamp())
result = cmp(x.getBlockNumber().parseHexInt, y.getBlockNumber().parseHexInt)
if result == 0:
result = cmp(x.getNonce(), y.getNonce())
@ -222,19 +228,20 @@ QtObject:
t.baseGasFees,
t.totalFees,
t.maxTotalFees,
t.symbol
))
var allTxs = self.items.concat(newTxItems)
allTxs.sort(cmpTransactions, SortOrder.Descending)
eth_service_utils.deduplicate(allTxs, tx => tx.getId())
eth_service_utils.deduplicate(allTxs, tx => tx.getTxHash())
# add day headers to the transaction list
var itemsWithDateHeaders: seq[Item] = @[]
var tempTimeStamp: Time
for tx in allTxs:
let duration = fromUnix(tx.getTimestamp()) - tempTimeStamp
if(duration.inDays != 0):
itemsWithDateHeaders.add(initItem("", "", "", "", "", tx.getTimestamp(), "", "", "", "", "", "", "", "", "", 0, "", "", "", "", 0, true, "", "", ""))
let durationInDays = (tempTimeStamp.toTimeInterval() - fromUnix(tx.getTimestamp()).toTimeInterval()).days
if(durationInDays != 0):
itemsWithDateHeaders.add(initItem("", "", "", "", "", tx.getTimestamp(), "", "", "", "", "", "", "", "", "", 0, "", "", "", "", 0, true, "", "", "", ""))
itemsWithDateHeaders.add(tx)
tempTimeStamp = fromUnix(tx.getTimestamp())

View File

@ -29,7 +29,6 @@ type
tmpSendTransactionDetails: TmpSendTransactionDetails
# Forward declarations
method checkRecentHistory*(self: Module)
method getWalletAccounts*(self: Module): seq[WalletAccountDto]
method loadTransactions*(self: Module, address: string, toBlock: string = "0x0", limit: int = 20, loadMore: bool = false)
@ -59,21 +58,18 @@ method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.checkRecentHistory()
self.controller.checkRecentHistory(calledFromTimerOrInit = true)
let accounts = self.getWalletAccounts()
self.moduleLoaded = true
self.delegate.transactionsModuleDidLoad()
self.controller.checkPendingTransactions()
self.view.setPendingTx(self.controller.checkPendingTransactions())
method switchAccount*(self: Module, accountIndex: int) =
let walletAccount = self.controller.getWalletAccount(accountIndex)
self.view.switchAccount(walletAccount)
method checkRecentHistory*(self: Module) =
self.controller.checkRecentHistory()
method getWalletAccounts*(self: Module): seq[WalletAccountDto] =
self.controller.getWalletAccounts()
@ -146,6 +142,7 @@ method onUserAuthenticated*(self: Module, password: string) =
method transactionWasSent*(self: Module, result: string) =
self.view.transactionWasSent(result)
self.view.setPendingTx(self.controller.checkPendingTransactions())
method suggestedFees*(self: Module, chainId: int): string =
return self.controller.suggestedFees(chainId)

View File

@ -69,9 +69,6 @@ QtObject:
self.setHistoryFetchState(address, true)
self.delegate.loadTransactions(address, toBlock, limit, loadMore)
proc checkRecentHistory*(self: View) {.slot.} =
self.delegate.checkRecentHistory()
proc setTrxHistoryResult*(self: View, transactions: seq[TransactionDto], address: string, wasFetchMore: bool) =
if not self.models.hasKey(address):
self.models[address] = newModel()
@ -167,3 +164,9 @@ QtObject:
return self.delegate.getLastTxBlockNumber()
proc suggestedRoutesReady*(self: View, suggestedRoutes: string) {.signal.}
proc setPendingTx*(self: View, pendingTx: seq[TransactionDto]) =
for tx in pendingTx:
if not self.models.hasKey(tx.fromAddress):
self.models[tx.fromAddress] = newModel()
self.models[tx.fromAddress].addNewTransactions(@[tx], false)

View File

@ -93,3 +93,22 @@ const getSuggestedRoutesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall
"error": fmt"Error getting suggested routes: {e.msg}"
}
arg.finish(output)
type
WatchTransactionTaskArg* = ref object of QObjectTaskArg
chainId: int
txHash: string
const watchTransactionsTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[WatchTransactionTaskArg](argEncoded)
try:
let output = %*{
"txHash": arg.txHash,
"isSuccessfull": transactions.watchTransaction(arg.chainId, arg.txHash).error.isNil,
}
arg.finish(output)
except Exception as e:
let output = %* {
"hash": arg.txHash,
"isSuccessfull": false
}

View File

@ -1,6 +1,7 @@
import json, strutils, stint, json_serialization, strformat
include ../../common/json_utils
import ../network/dto
import ../../common/conversion as service_conversion
type
PendingTransactionTypeDto* {.pure.} = enum
@ -53,7 +54,8 @@ type
baseGasFees*: string
totalFees*: string
maxTotalFees*: string
additionalData*: string
symbol*: string
proc getTotalFees(tip: string, baseFee: string, gasUsed: string, maxFee: string): string =
var maxFees = stint.fromHex(Uint256, maxFee)
@ -93,6 +95,22 @@ proc toTransactionDto*(jsonObj: JsonNode): TransactionDto =
result.totalFees = getTotalFees(result.maxPriorityFeePerGas, result.baseGasFees, result.gasUsed, result.maxFeePerGas)
result.maxTotalFees = getMaxTotalFees(result.maxFeePerGas, result.gasLimit)
proc toPendingTransactionDto*(jsonObj: JsonNode): TransactionDto =
result = TransactionDto()
result.value = "0x" & toHex(toUInt256(parseFloat(jsonObj{"value"}.getStr)))
result.timestamp = u256(jsonObj{"timestamp"}.getInt)
discard jsonObj.getProp("hash", result.txHash)
discard jsonObj.getProp("from", result.fromAddress)
discard jsonObj.getProp("to", result.to)
discard jsonObj.getProp("gasPrice", result.gasPrice)
discard jsonObj.getProp("gasLimit", result.gasLimit)
discard jsonObj.getProp("type", result.typeValue)
discard jsonObj.getProp("network_id", result.chainId)
discard jsonObj.getProp("multi_transaction_id", result.multiTransactionID)
discard jsonObj.getProp("additionalData", result.additionalData)
discard jsonObj.getProp("data", result.input)
discard jsonObj.getProp("symbol", result.symbol)
proc cmpTransactions*(x, y: TransactionDto): int =
# Sort proc to compare transactions from a single account.
# Compares first by block number, then by nonce

View File

@ -33,6 +33,7 @@ include ../../common/json_utils
const SIGNAL_TRANSACTIONS_LOADED* = "transactionsLoaded"
const SIGNAL_TRANSACTION_SENT* = "transactionSent"
const SIGNAL_SUGGESTED_ROUTES_READY* = "suggestedRoutesReady"
const SIGNAL_PENDING_TX_COMPLETED* = "pendingTransactionCompleted"
type
EstimatedTime* {.pure.} = enum
@ -64,6 +65,10 @@ type
SuggestedRoutesArgs* = ref object of Args
suggestedRoutes*: string
type
PendingTxCompletedArgs* = ref object of Args
txHash*: string
QtObject:
type Service* = ref object of QObject
events: EventEmitter
@ -73,7 +78,7 @@ QtObject:
tokenService: token_service.Service
# Forward declaration
proc checkPendingTransactions*(self: Service)
proc checkPendingTransactions*(self: Service): seq[TransactionDto]
proc checkPendingTransactions*(self: Service, address: string)
proc delete*(self: Service) =
@ -103,6 +108,23 @@ QtObject:
proc init*(self: Service) =
self.doConnect()
proc watchTransactionResult*(self: Service, watchTxResult: string) {.slot.} =
let watchTxResult = parseJson(watchTxResult)
let txHash = watchTxResult["txHash"].getStr
let success = watchTxResult["isSuccessfull"].getBool
if(success):
self.events.emit(SIGNAL_PENDING_TX_COMPLETED, PendingTxCompletedArgs(txHash: txHash))
proc watchTransaction*(self: Service, chainId: int, transactionHash: string) =
let arg = WatchTransactionTaskArg(
chainId: chainId,
txHash: transactionHash,
tptr: cast[ByteAddress](watchTransactionsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "watchTransactionResult",
)
self.threadpool.start(arg)
proc getTransactionReceipt*(self: Service, chainId: int, transactionHash: string): JsonNode =
try:
@ -119,19 +141,19 @@ QtObject:
let errDescription = e.msg
error "error deleting pending transaction: ", errDescription
proc confirmTransactionStatus(self: Service, pendingTransactions: JsonNode) =
for trx in pendingTransactions.getElems():
let transactionReceipt = self.getTransactionReceipt(trx["network_id"].getInt, trx["hash"].getStr)
proc confirmTransactionStatus(self: Service, pendingTransactions: seq[TransactionDto]) =
for trx in pendingTransactions:
let transactionReceipt = self.getTransactionReceipt(trx.chainId, trx.txHash)
if transactionReceipt != nil and transactionReceipt.kind != JNull:
self.deletePendingTransaction(trx["network_id"].getInt, trx["hash"].getStr)
self.deletePendingTransaction(trx.chainId, trx.txHash)
let ev = TransactionMinedArgs(
data: trx["additionalData"].getStr,
transactionHash: trx["hash"].getStr,
chainId: trx["network_id"].getInt,
data: trx.additionalData,
transactionHash: trx.txHash,
chainId: trx.chainId,
success: transactionReceipt{"status"}.getStr == "0x1",
revertReason: ""
)
self.events.emit(parseEnum[PendingTransactionTypeDto](trx["type"].getStr).event, ev)
self.events.emit(parseEnum[PendingTransactionTypeDto](trx.typeValue).event, ev)
proc getPendingTransactions*(self: Service): JsonNode =
try:
@ -151,14 +173,19 @@ QtObject:
let errDescription = e.msg
error "error getting pending txs by address: ", errDescription, address
proc checkPendingTransactions*(self: Service) =
proc checkPendingTransactions*(self: Service): seq[TransactionDto] =
# TODO move this to a thread
let pendingTransactions = self.getPendingTransactions()
if (pendingTransactions.kind == JArray and pendingTransactions.len > 0):
self.confirmTransactionStatus(pendingTransactions)
let pendingTxs = pendingTransactions.getElems().map(x => x.toPendingTransactionDto())
self.confirmTransactionStatus(pendingTxs)
for tx in pendingTxs:
self.watchTransaction(tx.chainId, tx.txHash)
return pendingTxs
proc checkPendingTransactions*(self: Service, address: string) =
self.confirmTransactionStatus(self.getPendingOutboundTransactionsByAddress(address))
let pendingTxs = self.getPendingOutboundTransactionsByAddress(address).getElems().map(x => x.toPendingTransactionDto())
self.confirmTransactionStatus(pendingTxs)
proc trackPendingTransaction*(self: Service, hash: string, fromAddress: string, toAddress: string, trxType: string,
data: string, chainId: int) =
@ -337,6 +364,11 @@ QtObject:
paths,
password,
)
# Watch for this transaction to be completed
if response.result{"hashes"} != nil:
for hash in response.result["hashes"][$chainID]:
let txHash = hash.getStr
self.watchTransaction(chainID, txHash)
let output = %* {"result": response.result{"hashes"}, "success":true, "uuid": %uuid }
self.events.emit(SIGNAL_TRANSACTION_SENT, TransactionSentArgs(result: $output))
except Exception as e:

View File

@ -97,6 +97,7 @@ type KeycardActivityArgs* = ref object of Args
const CheckBalanceSlotExecuteIntervalInSeconds = 15 * 60 # 15 mins
const CheckBalanceTimerIntervalInMilliseconds = 5000 # 5 sec
const CheckHistoryIntervalInMilliseconds = 20 * 60 * 1000 # 20 mins
include async_tasks
include ../../common/json_utils
@ -115,10 +116,13 @@ QtObject:
timerStartTimeInSeconds: int64
priceCache: TimedCache
processedKeyPair: KeyPairDto
ignoreTimeInitiatedHistoryFetchBuild: bool
isHistoryFetchTimerAlreadyRunning: bool
# Forward declaration
proc buildAllTokens(self: Service, calledFromTimerOrInit = false)
proc startBuildingTokensTimer(self: Service, resetTimeToNow = true)
proc startFetchingHistoryTimer(self: Service, resetTimeToNow = true)
proc delete*(self: Service) =
self.closingApp = true
@ -144,6 +148,8 @@ QtObject:
result.networkService = networkService
result.walletAccounts = initOrderedTable[string, WalletAccountDto]()
result.priceCache = newTimedCache()
result.ignoreTimeInitiatedHistoryFetchBuild = false
result.isHistoryFetchTimerAlreadyRunning = false
proc getPrice*(self: Service, crypto: string, fiat: string): float64 =
let cacheKey = crypto & fiat
@ -191,6 +197,21 @@ QtObject:
if settingsField.name == KEY_CURRENCY:
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
self.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
case data.eventType:
of "recent-history-ready":
# run timer again...
self.startFetchingHistoryTimer()
of "non-archival-node-detected":
# run timer again...
self.startFetchingHistoryTimer()
of "fetching-history-error":
# run timer again...
self.startFetchingHistoryTimer()
else:
echo "Unhandled wallet signal: ", data.eventType
proc getAccountByAddress*(self: Service, address: string): WalletAccountDto =
if not self.walletAccounts.hasKey(address):
return
@ -210,7 +231,14 @@ QtObject:
if(accounts[i].address == address):
return i
proc checkRecentHistory*(self: Service) =
proc checkRecentHistory*(self: Service, calledFromTimerOrInit = false) =
# Since we don't have a way to re-run TimerTaskArg (to stop it and run again), we introduced some flags which will
# just ignore buildAllTokens in case that proc is called by some action in the time window between two successive calls
# initiated by TimerTaskArg.
if not calledFromTimerOrInit:
self.ignoreTimeInitiatedHistoryFetchBuild = true
try:
let addresses = self.getWalletAccounts().map(a => a.address)
let chainIds = self.networkService.getNetworks().map(a => a.chainId)
@ -657,3 +685,27 @@ QtObject:
except Exception as e:
error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg
return "error: " & e.msg
proc onStartHistoryFetchingTimer*(self: Service, response: string) {.slot.} =
self.isHistoryFetchTimerAlreadyRunning = false
if self.ignoreTimeInitiatedHistoryFetchBuild:
self.ignoreTimeInitiatedHistoryFetchBuild = false
return
self.checkRecentHistory(true)
proc startFetchingHistoryTimer(self: Service, resetTimeToNow = true) =
if(self.closingApp):
return
if(self.isHistoryFetchTimerAlreadyRunning):
return
self.isHistoryFetchTimerAlreadyRunning = true
let arg = TimerTaskArg(
tptr: cast[ByteAddress](timerTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onStartHistoryFetchingTimer",
timeoutInMilliseconds: CheckHistoryIntervalInMilliseconds
)
self.threadpool.start(arg)

View File

@ -49,3 +49,7 @@ proc createMultiTransaction*(multiTransaction: MultiTransactionDto, data: seq[Tr
var hashed_password = "0x" & $keccak_256.digest(password)
let payload = %* [multiTransaction, data, hashed_password]
result = core.callPrivateRPC("wallet_createMultiTransaction", payload)
proc watchTransaction*(chainId: int, hash: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [chainId, hash]
core.callPrivateRPC("wallet_watchTransactionByChainID", payload)

View File

@ -78,14 +78,6 @@ Item {
// }
}
Timer {
id: recentHistoryTimer
interval: Constants.walletFetchRecentHistoryInterval
running: true
repeat: true
onTriggered: RootStore.checkRecentHistory()
}
leftPanel: LeftTabView {
id: leftTab
anchors.fill: parent

View File

@ -160,10 +160,6 @@ QtObject {
return globalUtils.hex2Dec(value)
}
function checkRecentHistory() {
walletSectionTransactions.checkRecentHistory()
}
function fetchCollectionCollectiblesList(slug) {
walletSectionCollectiblesCollectibles.fetch(slug)
}

View File

@ -119,7 +119,7 @@ ColumnLayout {
networkIcon: modelData !== undefined && !!modelData ? RootStore.getNetworkIcon(modelData.chainId) : ""
networkColor: modelData !== undefined && !!modelData ? RootStore.getNetworkColor(modelData.chainId) : ""
networkName: modelData !== undefined && !!modelData ? RootStore.getNetworkShortName(modelData.chainId) : ""
symbol: modelData !== undefined && !!modelData ? RootStore.findTokenSymbolByAddress(modelData.contract) : ""
symbol: modelData !== undefined && !!modelData ? !!modelData.symbol ? modelData.symbol : RootStore.findTokenSymbolByAddress(modelData.contract) : ""
transferStatus: modelData !== undefined && !!modelData ? RootStore.hex2Dec(modelData.txStatus) : ""
shortTimeStamp: modelData !== undefined && !!modelData ? Utils.formatShortTime(modelData.timestamp * 1000, RootStore.accountSensitiveSettings.is24hTimeFormat) : ""
savedAddressName: modelData !== undefined && !!modelData ? RootStore.getNameForSavedWalletAddress(modelData.to) : ""

View File

@ -565,8 +565,6 @@ QtObject {
readonly property int fetchRangeLast3Days: 259200
readonly property int fetchRangeLast7Days: 604800
readonly property int walletFetchRecentHistoryInterval: 1200000 // 20 mins
readonly property int limitLongChatText: 500
readonly property int limitLongChatTextCompactMode: 1000

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit f4eb1668d58dd5a042085e0b809e7eba9c16584c
Subproject commit d41fcaf8a9d95fe8d820c1928736a7a36d783032