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.events.on(SIGNAL_PROFILE_MIGRATION_NEEDED_UPDATED) do(e: Args):
self.delegate.checkAndPerformProfileMigrationIfNeeded() 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 = proc isConnected*(self: Controller): bool =
return self.nodeService.isConnected() return self.nodeService.isConnected()
@ -494,8 +498,8 @@ proc getStatusForContactWithId*(self: Controller, publicKey: string): StatusUpda
proc getVerificationRequestFrom*(self: Controller, publicKey: string): VerificationRequest = proc getVerificationRequestFrom*(self: Controller, publicKey: string): VerificationRequest =
self.contactsService.getVerificationRequestFrom(publicKey) self.contactsService.getVerificationRequestFrom(publicKey)
proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] = proc getCommunityTokensDetailsAsync*(self: Controller, communityId: string) =
self.communityTokensService.getCommunityTokens(communityId) self.communityTokensService.getCommunityTokensDetailsAsync(communityId)
proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] = proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] =
return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress) 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/settings/service as settings_service
import app_service/service/node_configuration/service as node_configuration_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.} = method removeMockedKeycardAction*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") 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 # This way (using concepts) is used only for the modules managed by AppController
type type
DelegateInterface* = concept c 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) let destructedAmount = self.controller.getRemoteDestructedAmount(tokenDto.chainId, tokenDto.address)
result = initTokenItem(tokenDto, network, tokenOwners, ownerAddressName, burnState, remoteDestructedAddresses, remainingSupply, destructedAmount) 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 = proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem =
let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community
var communityDetails: CommunityDto var communityDetails: CommunityDto
var communityTokensItems: seq[TokenItem] var communityTokensItems: seq[TokenItem]
if (isCommunity): if (isCommunity):
communityDetails = self.controller.getCommunityById(channelGroup.id) communityDetails = self.controller.getCommunityById(channelGroup.id)
let communityTokens = self.controller.getCommunityTokens(channelGroup.id) self.controller.getCommunityTokensDetailsAsync(channelGroup.id)
communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem =
result = self.createTokenItem(tokenDto)
)
# Get community members' revealed accounts # Get community members' revealed accounts
# We will update the model later when we finish loading the accounts # We will update the model later when we finish loading the accounts
if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster: if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster:

View File

@ -3,7 +3,7 @@ import NimQml, Tables, strutils, strformat
import json import json
import section_item, member_model import section_item, member_model
import ../main/communities/tokens/models/token_item import ../main/communities/tokens/models/[token_item, token_model]
type type
ModelRole {.pure.} = enum ModelRole {.pure.} = enum
@ -425,3 +425,10 @@ QtObject:
for pubkey, revealedAccounts in communityMembersAirdropAddress.pairs: for pubkey, revealedAccounts in communityMembersAirdropAddress.pairs:
self.items[index].members.setAirdropAddress(pubkey, revealedAccounts) 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 "error": e.msg
} }
arg.finish(output) 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] = proc parseCommunityTokens*(response: RpcResponse[JsonNode]): seq[CommunityTokenDto] =
result = map(response.result.getElems(), result = map(response.result.getElems(),
proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto()) 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 ../ens/utils as ens_utils
import ../eth/dto/transaction import ../eth/dto/transaction
from backend/collectibles_types import CollectibleOwner from backend/collectibles_types import CollectibleOwner
import ../../../backend/backend
import ../../../backend/response_type import ../../../backend/response_type
@ -33,40 +34,11 @@ export community_token_owner
const ethSymbol = "ETH" 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 include async_tasks
logScope: logScope:
topics = "community-tokens-service" topics = "community-tokens-service"
type
WalletAndAmount* = object
walletAddress*: string
amount*: int
type type
CommunityTokenDeployedStatusArgs* = ref object of Args CommunityTokenDeployedStatusArgs* = ref object of Args
communityId*: string communityId*: string
@ -114,50 +86,6 @@ type
transactionHash*: string transactionHash*: string
status*: ContractTransactionStatus 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 type
ComputeFeeArgs* = ref object of Args ComputeFeeArgs* = ref object of Args
@ -205,6 +133,12 @@ type
chainId*: int chainId*: int
owners*: seq[CommunityCollectibleOwner] owners*: seq[CommunityCollectibleOwner]
type
CommunityTokensDetailsArgs* = ref object of Args
communityId*: string
communityTokens*: seq[CommunityTokenDto]
communityTokenJsonItems*: JsonNode
# Signals which may be emitted by this service: # Signals which may be emitted by this service:
const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus" const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus"
const SIGNAL_COMMUNITY_TOKEN_DEPLOYMENT_STARTED* = "communityTokenDeploymentStarted" 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_COMMUNITY_TOKEN_REMOVED* = "communityTokenRemoved"
const SIGNAL_OWNER_TOKEN_DEPLOY_STATUS* = "ownerTokenDeployStatus" const SIGNAL_OWNER_TOKEN_DEPLOY_STATUS* = "ownerTokenDeployStatus"
const SIGNAL_OWNER_TOKEN_DEPLOYMENT_STARTED* = "ownerTokenDeploymentStarted" const SIGNAL_OWNER_TOKEN_DEPLOYMENT_STARTED* = "ownerTokenDeploymentStarted"
const SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED* = "communityTokenDetailsLoaded"
const SIGNAL_DEPLOY_OWNER_TOKEN* = "deployOwnerToken" const SIGNAL_DEPLOY_OWNER_TOKEN* = "deployOwnerToken"
@ -542,6 +477,34 @@ QtObject:
except RpcException: except RpcException:
error "Error getting all community tokens", message = getCurrentExceptionMsg() 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) = proc removeCommunityToken*(self: Service, communityId: string, chainId: int, address: string) =
try: try:
let response = tokens_backend.removeCommunityToken(chainId, address) let response = tokens_backend.removeCommunityToken(chainId, address)
@ -579,8 +542,8 @@ QtObject:
proc getRemoteDestructedAddresses*(self: Service, chainId: int, contractAddress: string): seq[string] = proc getRemoteDestructedAddresses*(self: Service, chainId: int, contractAddress: string): seq[string] =
try: try:
let burnTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.RemoteDestructCollectible) let remoteDestructTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.RemoteDestructCollectible)
for transaction in burnTransactions: for transaction in remoteDestructTransactions:
let remoteDestructTransactionDetails = toRemoteDestroyTransactionDetails(parseJson(transaction.additionalData)) let remoteDestructTransactionDetails = toRemoteDestroyTransactionDetails(parseJson(transaction.additionalData))
if remoteDestructTransactionDetails.chainId == chainId and remoteDestructTransactionDetails.contractAddress == contractAddress: if remoteDestructTransactionDetails.chainId == chainId and remoteDestructTransactionDetails.contractAddress == contractAddress:
return remoteDestructTransactionDetails.addresses return remoteDestructTransactionDetails.addresses