diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 9fbf66507..26e759c47 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -127,6 +127,8 @@ const federation = { // default value for community-uuid is equal uuid of stage-3 FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID: process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc', + FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS: + process.env.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS ?? 3, } export const CONFIG = { diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index f599dbafd..c57ca4823 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -5,6 +5,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' +import { revertSendCoins } from './query/revertSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' // eslint-disable-next-line camelcase @@ -28,7 +29,7 @@ export class SendCoinsClient { } voteForSendCoins = async (args: SendCoinsArgs): Promise => { - logger.debug('X-Com: voteForSendCoins against endpoint', this.endpoint) + logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(voteForSendCoins, { args }) @@ -53,24 +54,25 @@ export class SendCoinsClient { } } - /* revertSendCoins = async (args: SendCoinsArgs): Promise => { - logger.debug(`X-Com: revertSendCoins against endpoint='${this.endpoint}'...`) + logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(revertSendCoins, { args }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.revertSendCoins?.acknowledged) { + if (!data?.revertSendCoins?.revertSendCoins) { logger.warn('X-Com: revertSendCoins without response data from endpoint', this.endpoint) return false } logger.debug(`X-Com: revertSendCoins successful from endpoint=${this.endpoint}`) return true } catch (err) { - throw new LogError(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err) + logger.error(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err) + return false } } + /* commitSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug(`X-Com: commitSendCoins against endpoint='${this.endpoint}'...`) try { diff --git a/backend/src/federation/client/1_0/query/revertSendCoins.ts b/backend/src/federation/client/1_0/query/revertSendCoins.ts new file mode 100644 index 000000000..63c3cff63 --- /dev/null +++ b/backend/src/federation/client/1_0/query/revertSendCoins.ts @@ -0,0 +1,25 @@ +import { gql } from 'graphql-request' + +export const revertSendCoins = gql` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + revertSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 1cd854c60..59b0246d4 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -77,7 +77,14 @@ export async function processXComSendCoins( } catch (err) { logger.error(`Error in writing sender pending transaction: `, err) // revert the existing pending transaction on receiver side - // TODO in the issue #3186 + let revertCount = 0 + do { + if (await client.revertSendCoins(args)) { + logger.debug('revertSendCoins()-1_0... successfull') + throw new LogError('Error in writing sender pending transaction: `, err') + } + } while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++) + throw new LogError('Error in reverting receiver pending transaction even after retries') } logger.debug(`voteForSendCoins()-1_0... successfull`) } diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index ba23ae530..d03f1e4f0 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -15,7 +15,7 @@ import { fullName } from '@/graphql/util/fullName' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars export class SendCoinsResolver { - @Mutation(() => Boolean) + @Mutation(() => String) async voteForSendCoins( @Args() { @@ -76,4 +76,79 @@ export class SendCoinsResolver { } return result } + + @Mutation(() => Boolean) + async revertSendCoins( + @Args() + { + communityReceiverIdentifier, + userReceiverIdentifier, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + }: SendCoinsArgs, + ): Promise { + logger.debug(`revertSendCoins() via apiVersion=1_0 ...`) + try { + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `revertSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) + if (!receiverUser) { + throw new LogError( + `revertSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.NEW, + typeId: TransactionTypeId.RECEIVE, + balanceDate: creationDate, + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount) { + logger.debug('XCom: revertSendCoins matching pendingTX for remove...') + try { + await pendingTx.remove() + logger.debug('XCom: revertSendCoins pendingTX for remove successfully') + } catch (err) { + throw new LogError('Error in revertSendCoins on removing pendingTx of receiver: ', err) + } + } else { + logger.debug('XCom: revertSendCoins NOT matching pendingTX for remove...') + throw new LogError( + `Can't find in revertSendCoins the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.NEW, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } + logger.debug(`revertSendCoins()-1_0... successfull`) + return true + } catch (err) { + throw new LogError(`Error in revertSendCoins: `, err) + } + } }