fix: community freezes when editing because of sync operations

Fixes #11808

The problem was that when creating and editing communities, we got the community tokens and all the details about it.
The thing is, those fetches were all done sync, and half of them needed to go to status-go for it.

I fixed it by making those operations async.
This commit is contained in:
Jonathan Rainville 2023-09-21 16:15:11 -04:00
parent 0107203a46
commit 10197dc83a
7 changed files with 283 additions and 83 deletions

View File

@ -432,6 +432,10 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_PROFILE_MIGRATION_NEEDED_UPDATED) do(e: Args):
self.delegate.checkAndPerformProfileMigrationIfNeeded()
self.events.on(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED) do(e: Args):
let args = CommunityTokensDetailsArgs(e)
self.delegate.onCommunityTokensDetailsLoaded(args.communityId, args.communityTokens, args.communityTokenJsonItems)
proc isConnected*(self: Controller): bool =
return self.nodeService.isConnected()
@ -494,8 +498,8 @@ proc getStatusForContactWithId*(self: Controller, publicKey: string): StatusUpda
proc getVerificationRequestFrom*(self: Controller, publicKey: string): VerificationRequest =
self.contactsService.getVerificationRequestFrom(publicKey)
proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] =
self.communityTokensService.getCommunityTokens(communityId)
proc getCommunityTokensDetailsAsync*(self: Controller, communityId: string) =
self.communityTokensService.getCommunityTokensDetailsAsync(communityId)
proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] =
return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress)

View File

@ -1,4 +1,4 @@
import NimQml, stint
import NimQml, stint, json
import app_service/service/settings/service as settings_service
import app_service/service/node_configuration/service as node_configuration_service
@ -370,6 +370,10 @@ method insertMockedKeycardAction*(self: AccessInterface, cardIndex: int) {.base.
method removeMockedKeycardAction*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onCommunityTokensDetailsLoaded*(self: AccessInterface, communityId: string,
communityTokens: seq[CommunityTokenDto], communityTokenJsonItems: JsonNode) {.base.} =
raise newException(ValueError, "No implementation available")
# This way (using concepts) is used only for the modules managed by AppController
type
DelegateInterface* = concept c

View File

@ -255,16 +255,54 @@ proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto) : TokenIte
let destructedAmount = self.controller.getRemoteDestructedAmount(tokenDto.chainId, tokenDto.address)
result = initTokenItem(tokenDto, network, tokenOwners, ownerAddressName, burnState, remoteDestructedAddresses, remainingSupply, destructedAmount)
proc createTokenItemImproved[T](self: Module[T], tokenDto: CommunityTokenDto, communityTokenJsonItems: JsonNode) : TokenItem =
# These 3 values come from local caches so they can be done sync
let network = self.controller.getNetwork(tokenDto.chainId)
let tokenOwners = self.controller.getCommunityTokenOwners(tokenDto.communityId, tokenDto.chainId, tokenDto.address)
let ownerAddressName = if len(tokenDto.deployer) > 0: self.controller.getCommunityTokenOwnerName(tokenDto.deployer) else: ""
var tokenDetails: JsonNode
for details in communityTokenJsonItems.items:
if details["address"].getStr == tokenDto.address:
tokenDetails = details
break
if tokenDetails.kind == JNull:
error "Token details not found for token", name = tokenDto.name, address = tokenDto.address
return
let remainingSupply = tokenDetails["remainingSupply"].getStr
let burnState = tokenDetails["burnState"].getInt
let remoteDestructedAddresses = map(tokenDetails["remoteDestructedAddresses"].getElems(),
proc(remoteDestructAddress: JsonNode): string = remoteDestructAddress.getStr)
let destructedAmount = tokenDetails["destructedAmount"].getStr
result = initTokenItem(
tokenDto,
network,
tokenOwners,
ownerAddressName,
ContractTransactionStatus(burnState),
remoteDestructedAddresses,
stint.parse(remainingSupply, Uint256),
stint.parse(destructedAmount, Uint256),
)
method onCommunityTokensDetailsLoaded[T](self: Module[T], communityId: string,
communityTokens: seq[CommunityTokenDto], communityTokenJsonItems: JsonNode) =
let communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem =
result = self.createTokenItemImproved(tokenDto, communityTokenJsonItems)
)
self.view.model().setTokenItems(communityId, communityTokensItems)
proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem =
let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community
var communityDetails: CommunityDto
var communityTokensItems: seq[TokenItem]
if (isCommunity):
communityDetails = self.controller.getCommunityById(channelGroup.id)
let communityTokens = self.controller.getCommunityTokens(channelGroup.id)
communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem =
result = self.createTokenItem(tokenDto)
)
self.controller.getCommunityTokensDetailsAsync(channelGroup.id)
# Get community members' revealed accounts
# We will update the model later when we finish loading the accounts
if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster:

View File

@ -3,7 +3,7 @@ import NimQml, Tables, strutils, strformat
import json
import section_item, member_model
import ../main/communities/tokens/models/token_item
import ../main/communities/tokens/models/[token_item, token_model]
type
ModelRole {.pure.} = enum
@ -425,3 +425,10 @@ QtObject:
for pubkey, revealedAccounts in communityMembersAirdropAddress.pairs:
self.items[index].members.setAirdropAddress(pubkey, revealedAccounts)
proc setTokenItems*(self: SectionModel, id: string, communityTokensItems: seq[TokenItem]) =
let index = self.getItemIndex(id)
if (index == -1):
return
self.items[index].communityTokens.setItems(communityTokensItems)

View File

@ -209,3 +209,108 @@ const fetchCollectibleOwnersTaskArg: Task = proc(argEncoded: string) {.gcsafe, n
"error": e.msg
}
arg.finish(output)
type
GetCommunityTokensDetailsArg = ref object of QObjectTaskArg
communityId*: string
const getCommunityTokensDetailsTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetCommunityTokensDetailsArg](argEncoded)
try:
proc getRemainingSupply(chainId: int, contractAddress: string): string =
let response = tokens_backend.remainingSupply(chainId, contractAddress)
return response.result.getStr()
proc getPendingTransactions(): seq[TransactionDto] =
let response = backend.getPendingTransactions().result
if (response.kind == JArray and response.len > 0):
return response.getElems().map(x => x.toPendingTransactionDto())
return @[]
proc getCommunityTokenBurnState(chainId: int, contractAddress: string): ContractTransactionStatus =
let allPendingTransactions = getPendingTransactions()
let burnTransactions = allPendingTransactions.filter(x => x.typeValue == $PendingTransactionTypeDto.BurnCommunityToken)
for transaction in burnTransactions:
try:
let communityToken = toCommunityTokenDto(parseJson(transaction.additionalData))
if communityToken.chainId == chainId and communityToken.address == contractAddress:
return ContractTransactionStatus.InProgress
except Exception:
discard
return ContractTransactionStatus.Completed
proc getRemoteDestructedAddresses(chainId: int, contractAddress: string): seq[string] =
let allPendingTransactions = getPendingTransactions()
let remoteDestructTransactions = allPendingTransactions.filter(x => x.typeValue == $PendingTransactionTypeDto.RemoteDestructCollectible)
for transaction in remoteDestructTransactions:
let remoteDestructTransactionDetails = toRemoteDestroyTransactionDetails(parseJson(transaction.additionalData))
if remoteDestructTransactionDetails.chainId == chainId and remoteDestructTransactionDetails.contractAddress == contractAddress:
return remoteDestructTransactionDetails.addresses
proc getCommunityToken(communityTokens: seq[CommunityTokenDto], chainId: int, address: string): CommunityTokenDto =
for token in communityTokens:
if token.chainId == chainId and token.address == address:
return token
proc getRemoteDestructedAmount(communityTokens: seq[CommunityTokenDto],chainId: int, contractAddress: string): string =
let tokenType = getCommunityToken(communityTokens, chainId, contractAddress).tokenType
if tokenType != TokenType.ERC721:
return "0"
let response = tokens_backend.remoteDestructedAmount(chainId, contractAddress)
return response.result.getStr()
proc createTokenItemJson(communityTokens: seq[CommunityTokenDto], tokenDto: CommunityTokenDto): JsonNode =
try:
let remainingSupply = if tokenDto.infiniteSupply:
"0"
else:
getRemainingSupply(tokenDto.chainId, tokenDto.address)
let burnState = getCommunityTokenBurnState(tokenDto.chainId, tokenDto.address)
let remoteDestructedAddresses = getRemoteDestructedAddresses(tokenDto.chainId, tokenDto.address)
let destructedAmount = getRemoteDestructedAmount(communityTokens, tokenDto.chainId, tokenDto.address)
return %* {
"address": tokenDto.address,
"remainingSupply": remainingSupply,
"burnState": burnState.int,
"remoteDestructedAddresses": %* (remoteDestructedAddresses),
"destructedAmount": destructedAmount,
"error": "",
}
except Exception as e:
return %* {
"error": e.msg,
}
let response = tokens_backend.getCommunityTokens(arg.communityId)
if not response.error.isNil:
raise newException(ValueError, "Error getCommunityTokens" & response.error.message)
let communityTokens = parseCommunityTokens(response)
let communityTokenJsonItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): JsonNode =
result = createTokenItemJson(communityTokens, tokenDto)
if result["error"].getStr != "":
raise newException(ValueError, "Error creating token item" & result["error"].getStr)
)
let output = %* {
"communityTokensResponse": response,
"communityTokenJsonItems": communityTokenJsonItems,
"communityId": arg.communityId,
"error": ""
}
arg.finish(output)
except Exception as e:
let output = %* {
"communityId": arg.communityId,
"error": e.msg
}
arg.finish(output)

View File

@ -89,3 +89,82 @@ proc toCommunityTokenDto*(jsonObj: JsonNode): CommunityTokenDto =
proc parseCommunityTokens*(response: RpcResponse[JsonNode]): seq[CommunityTokenDto] =
result = map(response.result.getElems(),
proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto())
proc parseCommunityTokens*(response: JsonNode): seq[CommunityTokenDto] =
result = map(response.getElems(),
proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto())
type
CommunityTokenAndAmount* = object
communityToken*: CommunityTokenDto
amount*: Uint256 # for assets the value is converted to wei
type
ContractTuple* = tuple
chainId: int
address: string
proc `%`*(self: ContractTuple): JsonNode =
result = %* {
"address": self.address,
"chainId": self.chainId
}
proc toContractTuple*(json: JsonNode): ContractTuple =
return (json["chainId"].getInt, json["address"].getStr)
type
ChainWalletTuple* = tuple
chainId: int
address: string
type
WalletAndAmount* = object
walletAddress*: string
amount*: int
type
RemoteDestroyTransactionDetails* = object
chainId*: int
contractAddress*: string
addresses*: seq[string]
proc `%`*(self: RemoteDestroyTransactionDetails): JsonNode =
result = %* {
"contractAddress": self.contractAddress,
"chainId": self.chainId,
"addresses": self.addresses
}
type
OwnerTokenDeploymentTransactionDetails* = object
ownerToken*: ContractTuple
masterToken*: ContractTuple
communityId*: string
proc `%`*(self: OwnerTokenDeploymentTransactionDetails): JsonNode =
result = %* {
"ownerToken": %self.ownerToken,
"masterToken": %self.masterToken,
"communityId": self.communityId
}
proc toOwnerTokenDeploymentTransactionDetails*(jsonObj: JsonNode): OwnerTokenDeploymentTransactionDetails =
result = OwnerTokenDeploymentTransactionDetails()
try:
result.ownerToken = (jsonObj["ownerToken"]["chainId"].getInt, jsonObj["ownerToken"]["address"].getStr)
result.masterToken = (jsonObj["masterToken"]["chainId"].getInt, jsonObj["masterToken"]["address"].getStr)
result.communityId = jsonObj["communityId"].getStr
except Exception as e:
error "Error parsing OwnerTokenDeploymentTransactionDetails json", msg=e.msg
proc toRemoteDestroyTransactionDetails*(json: JsonNode): RemoteDestroyTransactionDetails =
return RemoteDestroyTransactionDetails(chainId: json["chainId"].getInt, contractAddress: json["contractAddress"].getStr, addresses: to(json["addresses"], seq[string]))
type
ComputeFeeErrorCode* {.pure.} = enum
Success,
Infura,
Balance,
Other

View File

@ -13,6 +13,7 @@ import ../wallet_account/service as wallet_account_service
import ../ens/utils as ens_utils
import ../eth/dto/transaction
from backend/collectibles_types import CollectibleOwner
import ../../../backend/backend
import ../../../backend/response_type
@ -33,40 +34,11 @@ export community_token_owner
const ethSymbol = "ETH"
type
CommunityTokenAndAmount* = object
communityToken*: CommunityTokenDto
amount*: Uint256 # for assets the value is converted to wei
type
ContractTuple* = tuple
chainId: int
address: string
proc `%`*(self: ContractTuple): JsonNode =
result = %* {
"address": self.address,
"chainId": self.chainId
}
proc toContractTuple*(json: JsonNode): ContractTuple =
return (json["chainId"].getInt, json["address"].getStr)
type
ChainWalletTuple* = tuple
chainId: int
address: string
include async_tasks
logScope:
topics = "community-tokens-service"
type
WalletAndAmount* = object
walletAddress*: string
amount*: int
type
CommunityTokenDeployedStatusArgs* = ref object of Args
communityId*: string
@ -114,50 +86,6 @@ type
transactionHash*: string
status*: ContractTransactionStatus
type
RemoteDestroyTransactionDetails* = object
chainId*: int
contractAddress*: string
addresses*: seq[string]
proc `%`*(self: RemoteDestroyTransactionDetails): JsonNode =
result = %* {
"contractAddress": self.contractAddress,
"chainId": self.chainId,
"addresses": self.addresses
}
type
OwnerTokenDeploymentTransactionDetails* = object
ownerToken*: ContractTuple
masterToken*: ContractTuple
communityId*: string
proc `%`*(self: OwnerTokenDeploymentTransactionDetails): JsonNode =
result = %* {
"ownerToken": %self.ownerToken,
"masterToken": %self.masterToken,
"communityId": self.communityId
}
proc toOwnerTokenDeploymentTransactionDetails*(jsonObj: JsonNode): OwnerTokenDeploymentTransactionDetails =
result = OwnerTokenDeploymentTransactionDetails()
try:
result.ownerToken = (jsonObj["ownerToken"]["chainId"].getInt, jsonObj["ownerToken"]["address"].getStr)
result.masterToken = (jsonObj["masterToken"]["chainId"].getInt, jsonObj["masterToken"]["address"].getStr)
result.communityId = jsonObj["communityId"].getStr
except Exception as e:
error "Error parsing OwnerTokenDeploymentTransactionDetails json", msg=e.msg
proc toRemoteDestroyTransactionDetails*(json: JsonNode): RemoteDestroyTransactionDetails =
return RemoteDestroyTransactionDetails(chainId: json["chainId"].getInt, contractAddress: json["contractAddress"].getStr, addresses: to(json["addresses"], seq[string]))
type
ComputeFeeErrorCode* {.pure.} = enum
Success,
Infura,
Balance,
Other
type
ComputeFeeArgs* = ref object of Args
@ -205,6 +133,12 @@ type
chainId*: int
owners*: seq[CommunityCollectibleOwner]
type
CommunityTokensDetailsArgs* = ref object of Args
communityId*: string
communityTokens*: seq[CommunityTokenDto]
communityTokenJsonItems*: JsonNode
# Signals which may be emitted by this service:
const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus"
const SIGNAL_COMMUNITY_TOKEN_DEPLOYMENT_STARTED* = "communityTokenDeploymentStarted"
@ -220,6 +154,7 @@ const SIGNAL_REMOVE_COMMUNITY_TOKEN_FAILED* = "removeCommunityTokenFailed"
const SIGNAL_COMMUNITY_TOKEN_REMOVED* = "communityTokenRemoved"
const SIGNAL_OWNER_TOKEN_DEPLOY_STATUS* = "ownerTokenDeployStatus"
const SIGNAL_OWNER_TOKEN_DEPLOYMENT_STARTED* = "ownerTokenDeploymentStarted"
const SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED* = "communityTokenDetailsLoaded"
const SIGNAL_DEPLOY_OWNER_TOKEN* = "deployOwnerToken"
@ -542,6 +477,34 @@ QtObject:
except RpcException:
error "Error getting all community tokens", message = getCurrentExceptionMsg()
proc getCommunityTokensDetailsAsync*(self: Service, communityId: string) =
let arg = GetCommunityTokensDetailsArg(
tptr: cast[ByteAddress](getCommunityTokensDetailsTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onCommunityTokensDetailsLoaded",
communityId: communityId
)
self.threadpool.start(arg)
proc onCommunityTokensDetailsLoaded*(self:Service, response: string) {.slot.} =
try:
let responseJson = response.parseJson()
if responseJson["error"].getStr != "":
raise newException(ValueError, responseJson["error"].getStr)
let communityTokens = parseCommunityTokens(responseJson["communityTokensResponse"]["result"])
let communityTokenJsonItems = responseJson["communityTokenJsonItems"]
self.events.emit(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED,
CommunityTokensDetailsArgs(
communityId: responseJson["communityId"].getStr,
communityTokens: communityTokens,
communityTokenJsonItems: communityTokenJsonItems,
))
except RpcException as e:
error "Error getting community tokens details", message = e.msg
proc removeCommunityToken*(self: Service, communityId: string, chainId: int, address: string) =
try:
let response = tokens_backend.removeCommunityToken(chainId, address)
@ -579,8 +542,8 @@ QtObject:
proc getRemoteDestructedAddresses*(self: Service, chainId: int, contractAddress: string): seq[string] =
try:
let burnTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.RemoteDestructCollectible)
for transaction in burnTransactions:
let remoteDestructTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.RemoteDestructCollectible)
for transaction in remoteDestructTransactions:
let remoteDestructTransactionDetails = toRemoteDestroyTransactionDetails(parseJson(transaction.additionalData))
if remoteDestructTransactionDetails.chainId == chainId and remoteDestructTransactionDetails.contractAddress == contractAddress:
return remoteDestructTransactionDetails.addresses