From 0c72fae9a23d86ec5bf5bd4de42f48f72221b188 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 17 Aug 2023 14:09:15 +0200 Subject: [PATCH 01/51] first steps of starting X-sendCoins --- .../src/graphql/arg/TransactionSendArgs.ts | 3 ++ .../graphql/resolver/TransactionResolver.ts | 39 ++++++++++++------- .../src/graphql/resolver/util/communities.ts | 19 +++++++++ frontend/src/graphql/mutations.js | 2 +- 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 backend/src/graphql/resolver/util/communities.ts diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index ecda848d1..010c71183 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -3,6 +3,9 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export class TransactionSendArgs { + @Field(() => String) + communityIdentifier: string + @Field(() => String) identifier: string diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index ba5d6e155..2cf6abb0d 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -34,6 +34,7 @@ import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualT import { BalanceResolver } from './BalanceResolver' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' +import { isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' @@ -317,24 +318,32 @@ export class TransactionResolver { @Authorized([RIGHTS.SEND_COINS]) @Mutation(() => Boolean) async sendCoins( - @Args() { identifier, amount, memo }: TransactionSendArgs, + @Args() { communityIdentifier, identifier, amount, memo }: TransactionSendArgs, @Ctx() context: Context, ): Promise { - logger.info(`sendCoins(identifier=${identifier}, amount=${amount}, memo=${memo})`) - if (amount.lte(0)) { - throw new LogError('Amount to send must be positive', amount) + logger.info( + `sendCoins(communityIdentifier=${communityIdentifier}, identifier=${identifier}, amount=${amount}, memo=${memo})`, + ) + if (!communityIdentifier || (await isHomeCommunity(communityIdentifier))) { + // processing a local sendCoins + if (amount.lte(0)) { + throw new LogError('Amount to send must be positive', amount) + } + + const senderUser = getUser(context) + + // validate recipient user + const recipientUser = await findUserByIdentifier(identifier) + if (!recipientUser) { + throw new LogError('The recipient user was not found', recipientUser) + } + + await executeTransaction(amount, memo, senderUser, recipientUser) + logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser) + } else { + // processing a x-community sendCoins + logger.debug('processing a x-community transaction...') } - - const senderUser = getUser(context) - - // validate recipient user - const recipientUser = await findUserByIdentifier(identifier) - if (!recipientUser) { - throw new LogError('The recipient user was not found', recipientUser) - } - - await executeTransaction(amount, memo, senderUser, recipientUser) - logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser) return true } } diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts new file mode 100644 index 000000000..49a3f7744 --- /dev/null +++ b/backend/src/graphql/resolver/util/communities.ts @@ -0,0 +1,19 @@ +import { Community as DbCommunity } from '@entity/Community' + +export async function isHomeCommunity(communityIdentifier: string): Promise { + const homeCommunity = await DbCommunity.findOneByOrFail({ foreign: false }) + if (communityIdentifier === homeCommunity.name) { + return true + } else if (communityIdentifier === homeCommunity.communityUuid) { + return true + } else if (communityIdentifier === homeCommunity.url) { + return true + } else { + return false + } +} + +export async function getCommunityUrl(communityIdentifier: string): Promise { + const community = await DbCommunity.findOneByOrFail({ name: communityIdentifier }) + return community.url +} diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index 2f6b53ac9..2065d92a8 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -72,7 +72,7 @@ export const createUser = gql` export const sendCoins = gql` mutation($identifier: String!, $amount: Decimal!, $memo: String!) { - sendCoins(identifier: $identifier, amount: $amount, memo: $memo) + sendCoins(communityIdentifier: $communityIdentifier, identifier: $identifier, amount: $amount, memo: $memo) } ` From 77128f85a323caa1478116c4be5ca7f7a52a9584 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 17 Aug 2023 19:12:51 +0200 Subject: [PATCH 02/51] save the work --- .../src/graphql/arg/TransactionSendArgs.ts | 6 +- .../graphql/resolver/TransactionResolver.ts | 170 +++++++++++++++++- backend/src/seeds/graphql/mutations.ts | 9 +- frontend/src/graphql/mutations.js | 9 +- 4 files changed, 179 insertions(+), 15 deletions(-) diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index 010c71183..aa1313350 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -3,9 +3,6 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export class TransactionSendArgs { - @Field(() => String) - communityIdentifier: string - @Field(() => String) identifier: string @@ -14,4 +11,7 @@ export class TransactionSendArgs { @Field(() => String) memo: string + + @Field(() => String) + communityIdentifier: string } diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 2cf6abb0d..8398abe91 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -191,6 +191,158 @@ export const executeTransaction = async ( return true } +/* +export const executeCrossTransaction = async ( + amount: Decimal, + memo: string, + sender: dbUser, + recipientIdentifier: string, + transactionLink?: dbTransactionLink | null, +): Promise => { + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + try { + logger.info('executeCrossTransaction', amount, memo, sender, recipientIdentifier) + + if (sender.id === recipient.id) { + throw new LogError('Sender and Recipient are the same', sender.id) + } + + if (memo.length < MEMO_MIN_CHARS) { + throw new LogError('Memo text is too short', memo.length) + } + + if (memo.length > MEMO_MAX_CHARS) { + throw new LogError('Memo text is too long', memo.length) + } + + // validate amount + const receivedCallDate = new Date() + const sendBalance = await calculateBalance( + sender.id, + amount.mul(-1), + receivedCallDate, + transactionLink, + ) + logger.debug(`calculated Balance=${sendBalance}`) + if (!sendBalance) { + throw new LogError('User has not enough GDD or amount is < 0', sendBalance) + } + + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`open Transaction to write...`) + try { + // transaction + const transactionSend = new dbTransaction() + transactionSend.typeId = TransactionTypeId.SEND + transactionSend.memo = memo + transactionSend.userId = sender.id + transactionSend.userGradidoID = sender.gradidoID + transactionSend.userName = fullName(sender.firstName, sender.lastName) + transactionSend.linkedUserId = recipient.id + transactionSend.linkedUserGradidoID = recipient.gradidoID + transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) + transactionSend.amount = amount.mul(-1) + transactionSend.balance = sendBalance.balance + transactionSend.balanceDate = receivedCallDate + transactionSend.decay = sendBalance.decay.decay + transactionSend.decayStart = sendBalance.decay.start + transactionSend.previous = sendBalance.lastTransactionId + transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null + await queryRunner.manager.insert(dbTransaction, transactionSend) + + logger.debug(`sendTransaction inserted: ${dbTransaction}`) + + const transactionReceive = new dbTransaction() + transactionReceive.typeId = TransactionTypeId.RECEIVE + transactionReceive.memo = memo + transactionReceive.userId = recipient.id + transactionReceive.userGradidoID = recipient.gradidoID + transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) + transactionReceive.linkedUserId = sender.id + transactionReceive.linkedUserGradidoID = sender.gradidoID + transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) + transactionReceive.amount = amount + const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) + transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount + transactionReceive.balanceDate = receivedCallDate + transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null + transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null + transactionReceive.linkedTransactionId = transactionSend.id + transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null + await queryRunner.manager.insert(dbTransaction, transactionReceive) + logger.debug(`receive Transaction inserted: ${dbTransaction}`) + + // Save linked transaction id for send + transactionSend.linkedTransactionId = transactionReceive.id + await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) + logger.debug('send Transaction updated', transactionSend) + + if (transactionLink) { + logger.info('transactionLink', transactionLink) + transactionLink.redeemedAt = receivedCallDate + transactionLink.redeemedBy = recipient.id + await queryRunner.manager.update( + dbTransactionLink, + { id: transactionLink.id }, + transactionLink, + ) + } + + await queryRunner.commitTransaction() + logger.info(`commit Transaction successful...`) + + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + + // trigger to send transaction via dlt-connector + void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('Transaction was not successful', e) + } finally { + await queryRunner.release() + } + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + return true +} +*/ + @Resolver() export class TransactionResolver { @Authorized([RIGHTS.TRANSACTION_LIST]) @@ -318,19 +470,21 @@ export class TransactionResolver { @Authorized([RIGHTS.SEND_COINS]) @Mutation(() => Boolean) async sendCoins( - @Args() { communityIdentifier, identifier, amount, memo }: TransactionSendArgs, + @Args() { identifier, amount, memo, communityIdentifier }: TransactionSendArgs, @Ctx() context: Context, ): Promise { logger.info( - `sendCoins(communityIdentifier=${communityIdentifier}, identifier=${identifier}, amount=${amount}, memo=${memo})`, + `sendCoins(identifier=${identifier}, amount=${amount}, memo=${memo}, communityIdentifier=${communityIdentifier})`, ) - if (!communityIdentifier || (await isHomeCommunity(communityIdentifier))) { - // processing a local sendCoins - if (amount.lte(0)) { - throw new LogError('Amount to send must be positive', amount) - } - const senderUser = getUser(context) + if (amount.lte(0)) { + throw new LogError('Amount to send must be positive', amount) + } + + const senderUser = getUser(context) + + if (!communityIdentifier || (await isHomeCommunity(communityIdentifier))) { + // processing sendCoins within sender and recepient are both in home community // validate recipient user const recipientUser = await findUserByIdentifier(identifier) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 87231531f..f2f9937a0 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -79,8 +79,13 @@ export const sendActivationEmail = gql` ` export const sendCoins = gql` - mutation ($identifier: String!, $amount: Decimal!, $memo: String!) { - sendCoins(identifier: $identifier, amount: $amount, memo: $memo) + mutation ($identifier: String!, $amount: Decimal!, $memo: String!, $communityIdentifier: String) { + sendCoins( + identifier: $identifier + amount: $amount + memo: $memo + communityIdentifier: $communityIdentifier + ) } ` diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index 2065d92a8..2efed09fd 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -71,8 +71,13 @@ export const createUser = gql` ` export const sendCoins = gql` - mutation($identifier: String!, $amount: Decimal!, $memo: String!) { - sendCoins(communityIdentifier: $communityIdentifier, identifier: $identifier, amount: $amount, memo: $memo) + mutation ($identifier: String!, $amount: Decimal!, $memo: String!, $communityIdentifier: String) { + sendCoins( + identifier: $identifier + amount: $amount + memo: $memo + communityIdentifier: $communityIdentifier + ) } ` From 6acf2f3c5b869709d6ad2726d86f4d027a4e68e2 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 29 Aug 2023 00:49:51 +0200 Subject: [PATCH 03/51] first draft for settlePendingReceiverTransaction --- .../federation/client/1_0/SendCoinsClient.ts | 15 +- .../client/1_0/query/settleSendCoins.ts | 25 +++ .../graphql/resolver/TransactionResolver.ts | 18 ++ .../resolver/util/processXComSendCoins.ts | 67 ++++++ federation/src/graphql/api/1_0/const/const.ts | 12 ++ .../api/1_0/resolver/SendCoinsResolver.ts | 93 +++++++- .../1_0}/util/calculateRecepientBalance.ts | 7 +- .../util/settlePendingReceiveTransaction.ts | 199 ++++++++++++++++++ .../src/graphql/util/TRANSACTIONS_LOCK.ts | 4 + 9 files changed, 427 insertions(+), 13 deletions(-) create mode 100644 backend/src/federation/client/1_0/query/settleSendCoins.ts create mode 100644 federation/src/graphql/api/1_0/const/const.ts rename federation/src/graphql/{ => api/1_0}/util/calculateRecepientBalance.ts (76%) create mode 100644 federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts create mode 100644 federation/src/graphql/util/TRANSACTIONS_LOCK.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index c57ca4823..d26ad80ee 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -7,6 +7,7 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' +import { settleSendCoins } from './query/settleSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { @@ -72,22 +73,20 @@ export class SendCoinsClient { } } - /* - commitSendCoins = async (args: SendCoinsArgs): Promise => { - logger.debug(`X-Com: commitSendCoins against endpoint='${this.endpoint}'...`) + settleSendCoins = async (args: SendCoinsArgs): Promise => { + logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await this.client.rawRequest(commitSendCoins, { args }) + const { data } = await this.client.rawRequest(settleSendCoins, { args }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.commitSendCoins?.acknowledged) { - logger.warn('X-Com: commitSendCoins without response data from endpoint', this.endpoint) + logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint) return false } - logger.debug(`X-Com: commitSendCoins successful from endpoint=${this.endpoint}`) + logger.debug(`X-Com: settleSendCoins successful from endpoint=${this.endpoint}`) return true } catch (err) { - throw new LogError(`X-Com: commitSendCoins failed for endpoint=${this.endpoint}`, err) + throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err) } } - */ } diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts new file mode 100644 index 000000000..99f784bc7 --- /dev/null +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -0,0 +1,25 @@ +import { gql } from 'graphql-request' + +export const settleSendCoins = gql` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + settleSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index ba5d6e155..2a3dd645a 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { getConnection, In } from '@dbTools/typeorm' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User as dbUser } from '@entity/User' @@ -12,6 +13,7 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' import { Paginated } from '@arg/Paginated' import { TransactionSendArgs } from '@arg/TransactionSendArgs' import { Order } from '@enum/Order' +import { PendingTransactionState } from '@enum/PendingTransactionState' import { TransactionTypeId } from '@enum/TransactionTypeId' import { Transaction } from '@model/Transaction' import { TransactionList } from '@model/TransactionList' @@ -52,6 +54,22 @@ export const executeTransaction = async ( try { logger.info('executeTransaction', amount, memo, sender, recipient) + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: sender.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: sender.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: recipient.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: recipient.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) { + throw new LogError('There are still pending Transactions for Sender and/or Recipient') + } + if (sender.id === recipient.id) { throw new LogError('Sender and Recipient are the same', sender.id) } diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 107f65cde..ef37685a9 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -114,3 +114,70 @@ export async function processXComPendingSendCoins( } return true } + +export async function processXComCommittingSendCoins( + receiverFCom: DbFederatedCommunity, + receiverCom: DbCommunity, + senderCom: DbCommunity, + creationDate: Date, + amount: Decimal, + memo: string, + sender: dbUser, + recipient: dbUser, +): Promise { + try { + logger.debug( + `XCom: processXComCommittingSendCoins...`, + receiverFCom, + receiverCom, + senderCom, + creationDate, + amount, + memo, + sender, + recipient, + ) + // first find pending Tx with given parameters + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: senderCom.communityUuid ? senderCom.communityUuid : 'homeCom-UUID', + userGradidoID: sender.gradidoID, + userName: fullName(sender.firstName, sender.lastName), + linkedUserCommunityUuid: receiverCom.communityUuid + ? receiverCom.communityUuid + : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID, + linkedUserGradidoID: recipient.gradidoID, + typeId: TransactionTypeId.SEND, + state: PendingTransactionState.NEW, + balanceDate: creationDate, + memo, + }) + if (pendingTx) { + logger.debug(`X-Com: find pending Tx for settlement:`, pendingTx) + const client = SendCoinsClientFactory.getInstance(receiverFCom) + // eslint-disable-next-line camelcase + if (client instanceof V1_0_SendCoinsClient) { + const args = new SendCoinsArgs() + args.communityReceiverIdentifier = pendingTx.linkedUserCommunityUuid + ? pendingTx.linkedUserCommunityUuid + : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID + if (pendingTx.linkedUserGradidoID) { + args.userReceiverIdentifier = pendingTx.linkedUserGradidoID + } + args.creationDate = pendingTx.balanceDate + args.amount = pendingTx.amount + args.memo = pendingTx.memo + args.communitySenderIdentifier = pendingTx.userCommunityUuid + args.userSenderIdentifier = pendingTx.userGradidoID + if (pendingTx.userName) { + args.userSenderName = pendingTx.userName + } + logger.debug(`X-Com: ready for settleSendCoins with args=`, args) + const acknoleged = await client.settleSendCoins(args) + logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) + } + } + } catch (err) { + logger.error(`Error:`, err) + } + return true +} diff --git a/federation/src/graphql/api/1_0/const/const.ts b/federation/src/graphql/api/1_0/const/const.ts new file mode 100644 index 000000000..b97694221 --- /dev/null +++ b/federation/src/graphql/api/1_0/const/const.ts @@ -0,0 +1,12 @@ +import { Decimal } from 'decimal.js-light' + +export const MAX_CREATION_AMOUNT = new Decimal(1000) +export const FULL_CREATION_AVAILABLE = [ + MAX_CREATION_AMOUNT, + MAX_CREATION_AMOUNT, + MAX_CREATION_AMOUNT, +] +export const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100 +export const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5 +export const MEMO_MAX_CHARS = 255 +export const MEMO_MIN_CHARS = 5 diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index d03f1e4f0..97c59f3ba 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -8,9 +8,11 @@ import { User as DbUser } from '@entity/User' import { LogError } from '@/server/LogError' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' -import { calculateRecepientBalance } from '@/graphql/util/calculateRecepientBalance' +import { calculateRecepientBalance } from '../util/calculateRecepientBalance' import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' +import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -50,6 +52,14 @@ export class SendCoinsResolver { homeCom.name, ) } + if (memo.length < MEMO_MIN_CHARS) { + throw new LogError('Memo text is too short', memo.length) + } + + if (memo.length > MEMO_MAX_CHARS) { + throw new LogError('Memo text is too long', memo.length) + } + const receiveBalance = await calculateRecepientBalance(receiverUser.id, amount, creationDate) const pendingTx = DbPendingTransaction.create() pendingTx.amount = amount @@ -151,4 +161,85 @@ export class SendCoinsResolver { throw new LogError(`Error in revertSendCoins: `, err) } } + + @Mutation(() => Boolean) + async settleSendCoins( + @Args() + { + communityReceiverIdentifier, + userReceiverIdentifier, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + }: SendCoinsArgs, + ): Promise { + logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) + try { + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, + }) + /* + if (!homeCom) { + throw new LogError( + `settleSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + */ + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) + /* + if (!receiverUser) { + throw new LogError( + `settleSendCoins 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: settleSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + try { + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: settlePendingReceiveTransaction successfully...') + } catch (err) { + throw new LogError('Error in settlePendingReceiveTransaction: ', err) + } + } else { + logger.debug( + 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', + ) + throw new LogError( + `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.NEW, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } + logger.debug(`settlePendingReceiveTransaction()-1_0... successfull`) + return true + } catch (err) { + throw new LogError(`Error in settlePendingReceiveTransaction: `, err) + } + } } diff --git a/federation/src/graphql/util/calculateRecepientBalance.ts b/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts similarity index 76% rename from federation/src/graphql/util/calculateRecepientBalance.ts rename to federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts index 56914afba..228862041 100644 --- a/federation/src/graphql/util/calculateRecepientBalance.ts +++ b/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts @@ -1,8 +1,7 @@ +import { calculateDecay } from '@/graphql/util/decay' +import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { Decimal } from 'decimal.js-light' - -import { getLastTransaction } from './getLastTransaction' -import { calculateDecay } from './decay' -import { Decay } from '../api/1_0/model/Decay' +import { Decay } from '../model/Decay' export async function calculateRecepientBalance( userId: number, diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts new file mode 100644 index 000000000..4c6334074 --- /dev/null +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -0,0 +1,199 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable new-cap */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { getConnection, In } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +import { User as DbUser } from '@entity/User' +import { Decimal } from 'decimal.js-light' + +import { PendingTransactionState } from '../enum/PendingTransactionState' +import { TransactionTypeId } from '../enum/TransactionTypeId' + +import { LogError } from '@/server/LogError' +import { federationLogger as logger } from '@/server/logger' + +import { getLastTransaction } from '@/graphql/util/getLastTransaction' +import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' + +export async function settlePendingReceiveTransaction( + homeCom: DbCommunity, + receiverUser: DbUser, + pendingTx: DbPendingTransaction, +): Promise { + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + try { + logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) + + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx?.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx?.linkedUserGradidoID, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { + throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') + } + + if ( + communityReceiverIdentifier === communitySenderIdentifier && + communitySenderIdentifier === userSenderIdentifier + ) { + throw new LogError('Sender and Recipient are the same user: ', userSenderName) + } + + if (memo.length < MEMO_MIN_CHARS) { + throw new LogError('Memo text is too short', memo.length) + } + + if (memo.length > MEMO_MAX_CHARS) { + throw new LogError('Memo text is too long', memo.length) + } + + const recipientUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) + const lastTransaction = await getLastTransaction(recipientUser.id) + const pendingTx = await DbPendingTransaction.findOneByOrFail({ + userId: recipientUser.id, + userGradidoID: recipientUser.gradidoID, + }) + + if (lastTransaction?.id !== pendingTx.previous) { + throw new LogError( + `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, + ) + } + // validate amount + const receivedCallDate = new Date() + const sendBalance = await calculateBalance( + sender.id, + amount.mul(-1), + receivedCallDate, + transactionLink, + ) + logger.debug(`calculated Balance=${sendBalance}`) + if (!sendBalance) { + throw new LogError('User has not enough GDD or amount is < 0', sendBalance) + } + + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`open Transaction to write...`) + try { + // transaction + const transactionSend = new dbTransaction() + transactionSend.typeId = TransactionTypeId.SEND + transactionSend.memo = memo + transactionSend.userId = sender.id + transactionSend.userGradidoID = sender.gradidoID + transactionSend.userName = fullName(sender.firstName, sender.lastName) + transactionSend.linkedUserId = recipient.id + transactionSend.linkedUserGradidoID = recipient.gradidoID + transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) + transactionSend.amount = amount.mul(-1) + transactionSend.balance = sendBalance.balance + transactionSend.balanceDate = receivedCallDate + transactionSend.decay = sendBalance.decay.decay + transactionSend.decayStart = sendBalance.decay.start + transactionSend.previous = sendBalance.lastTransactionId + transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null + await queryRunner.manager.insert(dbTransaction, transactionSend) + + logger.debug(`sendTransaction inserted: ${dbTransaction}`) + + const transactionReceive = new dbTransaction() + transactionReceive.typeId = TransactionTypeId.RECEIVE + transactionReceive.memo = memo + transactionReceive.userId = recipient.id + transactionReceive.userGradidoID = recipient.gradidoID + transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) + transactionReceive.linkedUserId = sender.id + transactionReceive.linkedUserGradidoID = sender.gradidoID + transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) + transactionReceive.amount = amount + const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) + transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount + transactionReceive.balanceDate = receivedCallDate + transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null + transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null + transactionReceive.linkedTransactionId = transactionSend.id + transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null + await queryRunner.manager.insert(dbTransaction, transactionReceive) + logger.debug(`receive Transaction inserted: ${dbTransaction}`) + + // Save linked transaction id for send + transactionSend.linkedTransactionId = transactionReceive.id + await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) + logger.debug('send Transaction updated', transactionSend) + + if (transactionLink) { + logger.info('transactionLink', transactionLink) + transactionLink.redeemedAt = receivedCallDate + transactionLink.redeemedBy = recipient.id + await queryRunner.manager.update( + dbTransactionLink, + { id: transactionLink.id }, + transactionLink, + ) + } + + await queryRunner.commitTransaction() + logger.info(`commit Transaction successful...`) + + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + + // trigger to send transaction via dlt-connector + void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('Transaction was not successful', e) + } finally { + await queryRunner.release() + } + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + return true +} diff --git a/federation/src/graphql/util/TRANSACTIONS_LOCK.ts b/federation/src/graphql/util/TRANSACTIONS_LOCK.ts new file mode 100644 index 000000000..847386e4d --- /dev/null +++ b/federation/src/graphql/util/TRANSACTIONS_LOCK.ts @@ -0,0 +1,4 @@ +import { Semaphore } from 'await-semaphore' + +const CONCURRENT_TRANSACTIONS = 1 +export const TRANSACTIONS_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS) From 9aedbea8f424d1e250284408667616de7dcc8093 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 29 Aug 2023 02:24:08 +0200 Subject: [PATCH 04/51] first draft of simple tradingLevel checks --- .../federation/client/1_0/SendCoinsClient.ts | 2 +- federation/src/config/index.ts | 5 ++++ .../api/1_0/resolver/SendCoinsResolver.ts | 14 ++++++++++ .../util/settlePendingReceiveTransaction.ts | 28 ++++--------------- .../src/graphql/util/checkTradingLevel.ts | 25 +++++++++++++++++ 5 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 federation/src/graphql/util/checkTradingLevel.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index d26ad80ee..3f22fdc73 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -6,8 +6,8 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' -import { voteForSendCoins } from './query/voteForSendCoins' import { settleSendCoins } from './query/settleSendCoins' +import { voteForSendCoins } from './query/voteForSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index aceb15e98..1200bc1b8 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -55,6 +55,11 @@ const federation = { FEDERATION_API: process.env.FEDERATION_API || '1_0', FEDERATION_PORT: process.env.FEDERATION_PORT || 5010, FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null, + FEDERATION_TRADING_LEVEL: { + RECEIVER_COMMUNITY_URL: 'https://stage3.gradido.net/api/', + SEND_COINS: true, + AMOUNT: 100, + }, } const CONFIG = { diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 97c59f3ba..3218ea815 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -13,6 +13,7 @@ import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const' +import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -44,6 +45,11 @@ export class SendCoinsResolver { communityReceiverIdentifier, ) } + if (!(await checkTradingLevel(homeCom, amount))) { + throw new LogError( + `X-Com: configuration of Trading-Level doesn't permit requested x-com sendCoin action!`, + ) + } // second check if receiver user exists in this community const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) if (!receiverUser) { @@ -52,6 +58,14 @@ export class SendCoinsResolver { homeCom.name, ) } + if ( + communitySenderIdentifier === communityReceiverIdentifier && + userReceiverIdentifier === userSenderIdentifier + ) { + throw new LogError( + `Sender and Recipient are the same: communityUUID=${communityReceiverIdentifier}, gradidoID=${userReceiverIdentifier}`, + ) + } if (memo.length < MEMO_MIN_CHARS) { throw new LogError('Memo text is too short', memo.length) } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 4c6334074..a1f9b95a7 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -29,43 +29,25 @@ export async function settlePendingReceiveTransaction( try { logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) + // ensure that no other pendingTx with the same sender or recipient exists const openSenderPendingTx = await DbPendingTransaction.count({ where: [ - { userGradidoID: pendingTx?.userGradidoID, state: PendingTransactionState.NEW }, - { linkedUserGradidoID: pendingTx?.linkedUserGradidoID, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, ], }) const openReceiverPendingTx = await DbPendingTransaction.count({ where: [ - { userGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, - { linkedUserGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, ], }) if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') } - if ( - communityReceiverIdentifier === communitySenderIdentifier && - communitySenderIdentifier === userSenderIdentifier - ) { - throw new LogError('Sender and Recipient are the same user: ', userSenderName) - } - - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('Memo text is too short', memo.length) - } - - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('Memo text is too long', memo.length) - } - const recipientUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) const lastTransaction = await getLastTransaction(recipientUser.id) - const pendingTx = await DbPendingTransaction.findOneByOrFail({ - userId: recipientUser.id, - userGradidoID: recipientUser.gradidoID, - }) if (lastTransaction?.id !== pendingTx.previous) { throw new LogError( diff --git a/federation/src/graphql/util/checkTradingLevel.ts b/federation/src/graphql/util/checkTradingLevel.ts new file mode 100644 index 000000000..c67d70ad7 --- /dev/null +++ b/federation/src/graphql/util/checkTradingLevel.ts @@ -0,0 +1,25 @@ +import CONFIG from '@/config' +import { Community as DbCommunity } from '@entity/Community' +import { Decimal } from 'decimal.js-light' +import { federationLogger as logger } from '@/server/logger' + +export async function checkTradingLevel(homeCom: DbCommunity, amount: Decimal): Promise { + const tradingLevel = CONFIG.FEDERATION_TRADING_LEVEL + if (homeCom.url !== tradingLevel.RECEIVER_COMMUNITY_URL) { + logger.warn( + `X-Com: tradingLevel allows to receive coins only wiht url ${tradingLevel.RECEIVER_COMMUNITY_URL}`, + ) + return false + } + if (!tradingLevel.SEND_COINS) { + logger.warn(`X-Com: tradingLevel disable general x-com sendcoin actions!`) + return false + } + if (new Decimal(tradingLevel.AMOUNT) < amount) { + logger.warn( + `X-Com: tradingLevel only allows to receive coins lower than amount of ${tradingLevel.AMOUNT}`, + ) + return false + } + return true +} From 99017802cd738959b326d2164b38778a20b6d6a1 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 29 Aug 2023 02:54:35 +0200 Subject: [PATCH 05/51] next version of settlePendingReceiveTransaction --- .../api/1_0/resolver/SendCoinsResolver.ts | 3 +- .../util/settlePendingReceiveTransaction.ts | 85 +++++++++++++------ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 3218ea815..fff969ae9 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -45,12 +45,13 @@ export class SendCoinsResolver { communityReceiverIdentifier, ) } + // second check is configured trading level if (!(await checkTradingLevel(homeCom, amount))) { throw new LogError( `X-Com: configuration of Trading-Level doesn't permit requested x-com sendCoin action!`, ) } - // second check if receiver user exists in this community + // third check if receiver user exists in this community const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) if (!receiverUser) { throw new LogError( diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index a1f9b95a7..938f3438f 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -6,18 +6,16 @@ import { getConnection, In } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' -import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User as DbUser } from '@entity/User' -import { Decimal } from 'decimal.js-light' import { PendingTransactionState } from '../enum/PendingTransactionState' -import { TransactionTypeId } from '../enum/TransactionTypeId' import { LogError } from '@/server/LogError' import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' +import { calculateRecepientBalance } from './calculateRecepientBalance' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -26,6 +24,11 @@ export async function settlePendingReceiveTransaction( ): Promise { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`open Transaction to write...`) + try { logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) @@ -46,14 +49,44 @@ export async function settlePendingReceiveTransaction( throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') } - const recipientUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - const lastTransaction = await getLastTransaction(recipientUser.id) + const lastTransaction = await getLastTransaction(receiverUser.id) if (lastTransaction?.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) } + + const transactionReceive = new dbTransaction() + transactionReceive.typeId = pendingTx.typeId + transactionReceive.memo = pendingTx.memo + transactionReceive.userId = pendingTx.userId + transactionReceive.userGradidoID = pendingTx.userGradidoID + transactionReceive.userName = pendingTx.userName + transactionReceive.linkedUserId = pendingTx.linkedUserId + transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID + transactionReceive.linkedUserName = pendingTx.linkedUserName + transactionReceive.amount = pendingTx.amount + const receiveBalance = await calculateRecepientBalance( + receiverUser.id, + pendingTx.amount, + pendingTx.balanceDate, + ) + if (receiveBalance?.balance !== pendingTx.balance) { + throw new LogError( + `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, + ) + } + transactionReceive.balance = pendingTx.balance + transactionReceive.balanceDate = pendingTx.balanceDate + transactionReceive.decay = pendingTx.decay + transactionReceive.decayStart = pendingTx.decayStart + transactionReceive.previous = pendingTx.previous + transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId + await queryRunner.manager.insert(dbTransaction, transactionReceive) + logger.debug(`receive Transaction inserted: ${dbTransaction}`) + + /* // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance( @@ -129,27 +162,30 @@ export async function settlePendingReceiveTransaction( transactionLink, ) } + */ + await queryRunner.commitTransaction() + logger.info(`commit Transaction successful...`) - await queryRunner.commitTransaction() - logger.info(`commit Transaction successful...`) + /* + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) - await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) - - await EVENT_TRANSACTION_RECEIVE( - recipient, - sender, - transactionReceive, - transactionReceive.amount, - ) - - // trigger to send transaction via dlt-connector - void sendTransactionsToDltConnector() - } catch (e) { - await queryRunner.rollbackTransaction() - throw new LogError('Transaction was not successful', e) - } finally { - await queryRunner.release() - } + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + */ + // trigger to send transaction via dlt-connector + // void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('Transaction was not successful', e) + } finally { + await queryRunner.release() + releaseLock() + } + /* void sendTransactionReceivedEmail({ firstName: recipient.firstName, lastName: recipient.lastName, @@ -177,5 +213,6 @@ export async function settlePendingReceiveTransaction( } finally { releaseLock() } + */ return true } From d403087ed14c860b7d52ccef896a4e71779263df Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 30 Aug 2023 15:42:18 +0200 Subject: [PATCH 06/51] revert settlement in federation modul --- federation/package.json | 1 + .../api/1_0/enum/PendingTransactionState.ts | 7 +- .../api/1_0/resolver/SendCoinsResolver.ts | 117 +++++++++++---- .../util/revertSettledReceiveTransaction.ts | 139 ++++++++++++++++++ .../util/settlePendingReceiveTransaction.ts | 89 ++--------- federation/tsconfig.json | 6 +- federation/yarn.lock | 5 + 7 files changed, 248 insertions(+), 116 deletions(-) create mode 100644 federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts diff --git a/federation/package.json b/federation/package.json index b70196149..eb78d6be0 100644 --- a/federation/package.json +++ b/federation/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "apollo-server-express": "^2.25.2", + "await-semaphore": "0.1.3", "class-validator": "^0.13.2", "cors": "2.8.5", "cross-env": "^7.0.3", diff --git a/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts b/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts index 6a614be96..d89b0b0eb 100644 --- a/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts +++ b/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts @@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql' export enum PendingTransactionState { NEW = 1, - WAIT_ON_PENDING = 2, - PENDING = 3, - WAIT_ON_CONFIRM = 4, - CONFIRMED = 5, + PENDING = 2, + SETTLED = 3, + REVERTED = 4, } registerEnumType(PendingTransactionState, { diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index fff969ae9..9d43ada62 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -14,6 +14,7 @@ import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const' import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' +import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -146,7 +147,7 @@ export class SendCoinsResolver { linkedUserGradidoID: userSenderIdentifier, }) logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount) { + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { logger.debug('XCom: revertSendCoins matching pendingTX for remove...') try { await pendingTx.remove() @@ -193,28 +194,6 @@ export class SendCoinsResolver { ): Promise { logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) try { - // first check if receiver community is correct - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - /* - if (!homeCom) { - throw new LogError( - `settleSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, - ) - } - */ - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - /* - if (!receiverUser) { - throw new LogError( - `settleSendCoins with unknown userReceiverIdentifier in the community=`, - homeCom.name, - ) - } - */ const pendingTx = await DbPendingTransaction.findOneBy({ userCommunityUuid: communityReceiverIdentifier, userGradidoID: userReceiverIdentifier, @@ -227,12 +206,15 @@ export class SendCoinsResolver { logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx) if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') - try { - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug('XCom: settlePendingReceiveTransaction successfully...') - } catch (err) { - throw new LogError('Error in settlePendingReceiveTransaction: ', err) - } + + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, + }) + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) + + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) + return true } else { logger.debug( 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', @@ -251,10 +233,83 @@ export class SendCoinsResolver { userSenderName, ) } - logger.debug(`settlePendingReceiveTransaction()-1_0... successfull`) - return true } catch (err) { throw new LogError(`Error in settlePendingReceiveTransaction: `, err) } } + + @Mutation(() => Boolean) + async revertSettledSendCoins( + @Args() + { + communityReceiverIdentifier, + userReceiverIdentifier, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + }: SendCoinsArgs, + ): Promise { + try { + logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `revertSettledSendCoins 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( + `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.SETTLED, + typeId: TransactionTypeId.RECEIVE, + balanceDate: creationDate, + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') + try { + await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: revertSettledSendCoins pendingTX successfully') + } catch (err) { + throw new LogError('Error in revertSettledSendCoins of receiver: ', err) + } + } else { + logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') + throw new LogError( + `Can't find in revertSettledSendCoins the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.SETTLED, + 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) + } + } } diff --git a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts new file mode 100644 index 000000000..ecf615667 --- /dev/null +++ b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts @@ -0,0 +1,139 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable new-cap */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { User as DbUser } from '@entity/User' + +import { PendingTransactionState } from '../enum/PendingTransactionState' + +import { LogError } from '@/server/LogError' +import { federationLogger as logger } from '@/server/logger' + +import { getLastTransaction } from '@/graphql/util/getLastTransaction' +import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' + +export async function revertSettledReceiveTransaction( + homeCom: DbCommunity, + receiverUser: DbUser, + pendingTx: DbPendingTransaction, +): Promise { + // TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!! + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`start Transaction for write-access...`) + + try { + logger.info('X-Com: revertSettledReceiveTransaction:', homeCom, receiverUser, pendingTx) + + // ensure that no other pendingTx with the same sender or recipient exists + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.SETTLED }, + { + linkedUserGradidoID: pendingTx.linkedUserGradidoID!, + state: PendingTransactionState.NEW, + }, + { + linkedUserGradidoID: pendingTx.linkedUserGradidoID!, + state: PendingTransactionState.SETTLED, + }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.SETTLED }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.SETTLED }, + ], + }) + if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { + throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') + } + + const lastTransaction = await getLastTransaction(receiverUser.id) + // now the last Tx must be the equivalant to the pendingTX + if ( + lastTransaction && + lastTransaction.balance === pendingTx.balance && + lastTransaction.balanceDate === pendingTx.balanceDate && + lastTransaction.userGradidoID === pendingTx.userGradidoID && + lastTransaction.userName === pendingTx.userName && + lastTransaction.amount === pendingTx.amount && + lastTransaction.memo === pendingTx.memo && + lastTransaction.linkedUserGradidoID === pendingTx.linkedUserGradidoID && + lastTransaction.linkedUserName === pendingTx.linkedUserName + ) { + await queryRunner.manager.remove(dbTransaction, lastTransaction) + logger.debug(`X-Com: revert settlement receive Transaction removed:`, lastTransaction) + // and mark the pendingTx in the pending_transactions table as reverted + pendingTx.state = PendingTransactionState.REVERTED + await queryRunner.manager.save(DbPendingTransaction, pendingTx) + + await queryRunner.commitTransaction() + logger.info(`commit revert settlement recipient Transaction successful...`) + } else { + // TODO: if the last TX is not equivelant to pendingTX, the transactions must be corrected in EXPERT-MODE + throw new LogError( + `X-Com: missmatching transaction order for revert settlement! lastTransation=${lastTransaction} != pendingTx=${pendingTx}`, + ) + } + + /* + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + */ + // trigger to send transaction via dlt-connector + // void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('X-Com: revert settlement recipient Transaction was not successful', e) + } finally { + await queryRunner.release() + releaseLock() + } + /* + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + */ + return true +} diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 938f3438f..0823e7d41 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -2,7 +2,7 @@ /* eslint-disable new-cap */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { getConnection, In } from '@dbTools/typeorm' +import { getConnection } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' @@ -22,12 +22,13 @@ export async function settlePendingReceiveTransaction( receiverUser: DbUser, pendingTx: DbPendingTransaction, ): Promise { + // TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!! // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') - logger.debug(`open Transaction to write...`) + logger.debug(`start Transaction for write-access...`) try { logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) @@ -57,6 +58,7 @@ export async function settlePendingReceiveTransaction( ) } + // transfer the pendingTx to the transactions table const transactionReceive = new dbTransaction() transactionReceive.typeId = pendingTx.typeId transactionReceive.memo = pendingTx.memo @@ -86,85 +88,12 @@ export async function settlePendingReceiveTransaction( await queryRunner.manager.insert(dbTransaction, transactionReceive) logger.debug(`receive Transaction inserted: ${dbTransaction}`) - /* - // validate amount - const receivedCallDate = new Date() - const sendBalance = await calculateBalance( - sender.id, - amount.mul(-1), - receivedCallDate, - transactionLink, - ) - logger.debug(`calculated Balance=${sendBalance}`) - if (!sendBalance) { - throw new LogError('User has not enough GDD or amount is < 0', sendBalance) - } + // and mark the pendingTx in the pending_transactions table as settled + pendingTx.state = PendingTransactionState.SETTLED + await queryRunner.manager.save(DbPendingTransaction, pendingTx) - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') - logger.debug(`open Transaction to write...`) - try { - // transaction - const transactionSend = new dbTransaction() - transactionSend.typeId = TransactionTypeId.SEND - transactionSend.memo = memo - transactionSend.userId = sender.id - transactionSend.userGradidoID = sender.gradidoID - transactionSend.userName = fullName(sender.firstName, sender.lastName) - transactionSend.linkedUserId = recipient.id - transactionSend.linkedUserGradidoID = recipient.gradidoID - transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) - transactionSend.amount = amount.mul(-1) - transactionSend.balance = sendBalance.balance - transactionSend.balanceDate = receivedCallDate - transactionSend.decay = sendBalance.decay.decay - transactionSend.decayStart = sendBalance.decay.start - transactionSend.previous = sendBalance.lastTransactionId - transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null - await queryRunner.manager.insert(dbTransaction, transactionSend) - - logger.debug(`sendTransaction inserted: ${dbTransaction}`) - - const transactionReceive = new dbTransaction() - transactionReceive.typeId = TransactionTypeId.RECEIVE - transactionReceive.memo = memo - transactionReceive.userId = recipient.id - transactionReceive.userGradidoID = recipient.gradidoID - transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) - transactionReceive.linkedUserId = sender.id - transactionReceive.linkedUserGradidoID = sender.gradidoID - transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) - transactionReceive.amount = amount - const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) - transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount - transactionReceive.balanceDate = receivedCallDate - transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) - transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null - transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null - transactionReceive.linkedTransactionId = transactionSend.id - transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null - await queryRunner.manager.insert(dbTransaction, transactionReceive) - logger.debug(`receive Transaction inserted: ${dbTransaction}`) - - // Save linked transaction id for send - transactionSend.linkedTransactionId = transactionReceive.id - await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) - logger.debug('send Transaction updated', transactionSend) - - if (transactionLink) { - logger.info('transactionLink', transactionLink) - transactionLink.redeemedAt = receivedCallDate - transactionLink.redeemedBy = recipient.id - await queryRunner.manager.update( - dbTransactionLink, - { id: transactionLink.id }, - transactionLink, - ) - } - */ await queryRunner.commitTransaction() - logger.info(`commit Transaction successful...`) + logger.info(`commit recipient Transaction successful...`) /* await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) @@ -180,7 +109,7 @@ export async function settlePendingReceiveTransaction( // void sendTransactionsToDltConnector() } catch (e) { await queryRunner.rollbackTransaction() - throw new LogError('Transaction was not successful', e) + throw new LogError('X-Com: recipient Transaction was not successful', e) } finally { await queryRunner.release() releaseLock() diff --git a/federation/tsconfig.json b/federation/tsconfig.json index 2326786ac..ba0d6fef3 100644 --- a/federation/tsconfig.json +++ b/federation/tsconfig.json @@ -53,13 +53,17 @@ // "@model/*": ["src/graphql/model/*"], "@repository/*": ["src/typeorm/repository/*"], "@test/*": ["test/*"], + /* common */ + "@common/*": ["../common/src/*"], + "@email/*": ["../common/scr/email/*"], + "@event/*": ["../common/src/event/*"], /* external */ "@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"], "@dbTools/*": ["../database/src/*", "../../database/build/src/*"], "@entity/*": ["../database/entity/*", "../../database/build/entity/*"] }, // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - "typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */ + "typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ diff --git a/federation/yarn.lock b/federation/yarn.lock index a811712fa..758e7e4ce 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -1529,6 +1529,11 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +await-semaphore@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3" + integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q== + babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" From 04a84c0ae2060f552f36b19eaca4c79d51b54838 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 30 Aug 2023 15:56:33 +0200 Subject: [PATCH 07/51] settle pending transactions on backend side --- .../federation/client/1_0/SendCoinsClient.ts | 23 ++- .../1_0/query/revertSettledSendCoins.ts | 25 +++ .../graphql/enum/PendingTransactionState.ts | 7 +- .../resolver/util/processXComSendCoins.ts | 26 ++++ .../util/settlePendingSenderTransaction.ts | 146 ++++++++++++++++++ 5 files changed, 222 insertions(+), 5 deletions(-) create mode 100644 backend/src/federation/client/1_0/query/revertSettledSendCoins.ts create mode 100644 backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 3f22fdc73..90c3e2e20 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -8,6 +8,7 @@ import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' import { settleSendCoins } from './query/settleSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' +import { revertSettledSendCoins } from './query/revertSettledSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { @@ -79,7 +80,7 @@ export class SendCoinsClient { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(settleSendCoins, { args }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.commitSendCoins?.acknowledged) { + if (!data?.settleSendCoins?.acknowledged) { logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint) return false } @@ -89,4 +90,24 @@ export class SendCoinsClient { throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err) } } + + revertSettledSendCoins = async (args: SendCoinsArgs): Promise => { + logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`) + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = await this.client.rawRequest(revertSettledSendCoins, { args }) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (!data?.revertSettledSendCoins?.acknowledged) { + logger.warn( + 'X-Com: revertSettledSendCoins without response data from endpoint', + this.endpoint, + ) + return false + } + logger.debug(`X-Com: revertSettledSendCoins successful from endpoint=${this.endpoint}`) + return true + } catch (err) { + throw new LogError(`X-Com: revertSettledSendCoins failed for endpoint=${this.endpoint}`, err) + } + } } diff --git a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts new file mode 100644 index 000000000..0d4447507 --- /dev/null +++ b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts @@ -0,0 +1,25 @@ +import { gql } from 'graphql-request' + +export const revertSettledSendCoins = gql` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + revertSettledSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` diff --git a/backend/src/graphql/enum/PendingTransactionState.ts b/backend/src/graphql/enum/PendingTransactionState.ts index 6a614be96..d89b0b0eb 100644 --- a/backend/src/graphql/enum/PendingTransactionState.ts +++ b/backend/src/graphql/enum/PendingTransactionState.ts @@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql' export enum PendingTransactionState { NEW = 1, - WAIT_ON_PENDING = 2, - PENDING = 3, - WAIT_ON_CONFIRM = 4, - CONFIRMED = 5, + PENDING = 2, + SETTLED = 3, + REVERTED = 4, } registerEnumType(PendingTransactionState, { diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index ef37685a9..40b7dd775 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -15,6 +15,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { fullName } from '@/util/utilities' +import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' export async function processXComPendingSendCoins( receiverFCom: DbFederatedCommunity, @@ -174,6 +175,31 @@ export async function processXComCommittingSendCoins( logger.debug(`X-Com: ready for settleSendCoins with args=`, args) const acknoleged = await client.settleSendCoins(args) logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) + if (acknoleged) { + // settle the pending transaction on receiver-side was successfull, so now settle the sender side + try { + await settlePendingSenderTransaction(senderCom, sender, pendingTx) + } catch (err) { + logger.error(`Error in writing sender pending transaction: `, err) + // revert the existing pending transaction on receiver side + let revertCount = 0 + logger.debug(`X-Com: first try to revertSetteledSendCoins of receiver`) + do { + if (await client.revertSettledSendCoins(args)) { + logger.debug( + `revertSettledSendCoins()-1_0... successfull after revertCount=`, + revertCount, + ) + // treat revertingSettledSendCoins as an error of the whole sendCoins-process + throw new LogError('Error in settle sender pending transaction: `, err') + } + } while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++) + throw new LogError( + `Error in reverting receiver pending transaction even after revertCount=`, + revertCount, + ) + } + } } } } catch (err) { diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts new file mode 100644 index 000000000..8db8ba695 --- /dev/null +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable new-cap */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { User as DbUser } from '@entity/User' + +import { LogError } from '@/server/LogError' +import { backendLogger as logger } from '@/server/logger' +import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' + +import { getLastTransaction } from './getLastTransaction' +import { calculateSenderBalance } from '@/util/calculateSenderBalance' +import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' + +export async function settlePendingSenderTransaction( + homeCom: DbCommunity, + senderUser: DbUser, + pendingTx: DbPendingTransaction, +): Promise { + // TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!! + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`start Transaction for write-access...`) + + try { + logger.info('X-Com: settlePendingSenderTransaction:', homeCom, senderUser, pendingTx) + + // ensure that no other pendingTx with the same sender or recipient exists + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { + throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') + } + + const lastTransaction = await getLastTransaction(senderUser.id) + + if (lastTransaction?.id !== pendingTx.previous) { + throw new LogError( + `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, + ) + } + + // transfer the pendingTx to the transactions table + const transactionSend = new dbTransaction() + transactionSend.typeId = pendingTx.typeId + transactionSend.memo = pendingTx.memo + transactionSend.userId = pendingTx.userId + transactionSend.userGradidoID = pendingTx.userGradidoID + transactionSend.userName = pendingTx.userName + transactionSend.linkedUserId = pendingTx.linkedUserId + transactionSend.linkedUserGradidoID = pendingTx.linkedUserGradidoID + transactionSend.linkedUserName = pendingTx.linkedUserName + transactionSend.amount = pendingTx.amount + const sendBalance = await calculateSenderBalance( + senderUser.id, + pendingTx.amount, + pendingTx.balanceDate, + ) + if (sendBalance?.balance !== pendingTx.balance) { + throw new LogError( + `X-Com: Calculation-Error on receiver balance: receiveBalance=${sendBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, + ) + } + transactionSend.balance = pendingTx.balance + transactionSend.balanceDate = pendingTx.balanceDate + transactionSend.decay = pendingTx.decay + transactionSend.decayStart = pendingTx.decayStart + transactionSend.previous = pendingTx.previous + transactionSend.linkedTransactionId = pendingTx.linkedTransactionId + await queryRunner.manager.insert(dbTransaction, transactionSend) + logger.debug(`send Transaction inserted: ${dbTransaction}`) + + // and mark the pendingTx in the pending_transactions table as settled + pendingTx.state = PendingTransactionState.SETTLED + await queryRunner.manager.save(DbPendingTransaction, pendingTx) + + await queryRunner.commitTransaction() + logger.info(`commit send Transaction successful...`) + + /* + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + */ + // trigger to send transaction via dlt-connector + // void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('X-Com: send Transaction was not successful', e) + } finally { + await queryRunner.release() + releaseLock() + } + /* + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + */ + return true +} From bea2ceda4c87429b8255f6b58b94a1271334d225 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 31 Aug 2023 23:36:07 +0200 Subject: [PATCH 08/51] federation module now with Decimal and decay calculations as in backend --- .../federation/client/1_0/SendCoinsClient.ts | 2 +- .../resolver/util/processXComSendCoins.ts | 1 + .../util/settlePendingSenderTransaction.ts | 4 +- federation/src/config/index.ts | 9 +- .../1_0/resolver/SendCoinsResolver.test.ts | 107 ++++++++++++++++++ federation/src/index.ts | 4 +- federation/src/server/createServer.ts | 4 +- federation/test/extensions.ts | 37 ++++++ federation/test/helpers.test.ts | 7 ++ federation/test/helpers.ts | 66 +++++++++++ federation/tsconfig.json | 6 +- 11 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts create mode 100644 federation/test/extensions.ts create mode 100644 federation/test/helpers.test.ts create mode 100644 federation/test/helpers.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 90c3e2e20..7f1fad186 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -6,9 +6,9 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' +import { revertSettledSendCoins } from './query/revertSettledSendCoins' import { settleSendCoins } from './query/settleSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' -import { revertSettledSendCoins } from './query/revertSettledSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 40b7dd775..1fd526251 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -15,6 +15,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { fullName } from '@/util/utilities' + import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' export async function processXComPendingSendCoins( diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index 8db8ba695..e22276823 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -8,13 +8,13 @@ import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTrans import { Transaction as dbTransaction } from '@entity/Transaction' import { User as DbUser } from '@entity/User' +import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' +import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { getLastTransaction } from './getLastTransaction' -import { calculateSenderBalance } from '@/util/calculateSenderBalance' -import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' export async function settlePendingSenderTransaction( homeCom: DbCommunity, diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 1200bc1b8..f56f9decc 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -1,14 +1,15 @@ // ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env) +/* eslint-disable n/no-process-env */ + +import { Decimal } from 'decimal.js-light' import dotenv from 'dotenv' + dotenv.config() -/* -import Decimal from 'decimal.js-light' Decimal.set({ precision: 25, rounding: Decimal.ROUND_HALF_UP, }) -*/ const constants = { DB_VERSION: '0071-add-pending_transactions-table', @@ -62,7 +63,7 @@ const federation = { }, } -const CONFIG = { +export const CONFIG = { ...constants, ...server, ...database, diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts new file mode 100644 index 000000000..1400cfbeb --- /dev/null +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -0,0 +1,107 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/* +import { createTestClient } from 'apollo-server-testing' +import { Community as DbCommunity } from '@entity/Community' +import CONFIG from '@/config' +import { cleanDB, testEnvironment } from '@test/helpers' +import { createServer } from '@/server/createServer' + +let query: any + +// to do: We need a setup for the tests that closes the connection +let con: any + +CONFIG.FEDERATION_API = '1_0' + +beforeAll(async () => { + const server = await createServer() + con = server.con + query = createTestClient(server.apollo).query + await cleanDB() + // DbCommunity.clear() +}) + +afterAll(async () => { + await con.close() +}) + +describe('SendCoinsResolver', () => { + const voteForSendCoinsMutation = ` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + voteForSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` + + describe('get 1st TX at all', () => { + let homeCom: DbCommunity + let foreignCom: DbCommunity + const varsPeterLustig = { + email: 'peter@lustig.de', + firstName: 'Peter', + lastName: 'Lustig', + language: 'de', + publisherId: 1234, + } + const varsBibiBloxberg = { + email: 'bibi@bloxberg.de', + firstName: 'Bibi', + lastName: 'Bloxberg', + language: 'de', + publisherId: 1234, + } + beforeEach(async () => { + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCommunity-url' + homeCom.name = 'Community-Name' + homeCom.description = 'Community-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCommunity-publicKey') + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCommunity-url' + foreignCom.name = 'foreignCommunity-Name' + foreignCom.description = 'foreign Community-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCommunity-publicKey') + await DbCommunity.insert(foreignCom) + + }) + + it('returns public CommunityInfo', async () => { + await expect(query({ query: getPublicCommunityInfoQuery })).resolves.toMatchObject({ + data: { + getPublicCommunityInfo: { + name: 'Community-Name', + description: 'Community-Description', + creationDate: homeCom.creationDate?.toISOString(), + publicKey: expect.stringMatching('homeCommunity-publicKey'), + }, + }, + }) + }) + }) +}) +*/ diff --git a/federation/src/index.ts b/federation/src/index.ts index 997edb7aa..bdc66c87a 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import createServer from './server/createServer' +import { createServer } from './server/createServer' // config -import CONFIG from './config' +import { CONFIG } from './config' async function main() { // eslint-disable-next-line no-console diff --git a/federation/src/server/createServer.ts b/federation/src/server/createServer.ts index a47b6da7f..b79847254 100644 --- a/federation/src/server/createServer.ts +++ b/federation/src/server/createServer.ts @@ -13,7 +13,7 @@ import cors from './cors' import plugins from './plugins' // config -import CONFIG from '@/config' +import { CONFIG } from '@/config' // graphql import schema from '@/graphql/schema' @@ -33,7 +33,7 @@ import { Logger } from 'log4js' type ServerDef = { apollo: ApolloServer; app: Express; con: Connection } -const createServer = async ( +export const createServer = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any // context: any = serverContext, logger: Logger = apolloLogger, diff --git a/federation/test/extensions.ts b/federation/test/extensions.ts new file mode 100644 index 000000000..262a9bcdb --- /dev/null +++ b/federation/test/extensions.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import { Decimal } from 'decimal.js-light' + +expect.extend({ + decimalEqual(received, value) { + const pass = new Decimal(value).equals(received.toString()) + if (pass) { + return { + message: () => `expected ${received} to not equal ${value}`, + pass: true, + } + } else { + return { + message: () => `expected ${received} to equal ${value}`, + pass: false, + } + } + }, +}) + +interface CustomMatchers { + decimalEqual(value: number): R +} + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Expect extends CustomMatchers {} + interface Matchers extends CustomMatchers {} + interface InverseAsymmetricMatchers extends CustomMatchers {} + } +} diff --git a/federation/test/helpers.test.ts b/federation/test/helpers.test.ts new file mode 100644 index 000000000..69d8f3fa4 --- /dev/null +++ b/federation/test/helpers.test.ts @@ -0,0 +1,7 @@ +import { contributionDateFormatter } from '@test/helpers' + +describe('contributionDateFormatter', () => { + it('formats the date correctly', () => { + expect(contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11'))).toEqual('2/29/2024') + }) +}) diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts new file mode 100644 index 000000000..b5352e508 --- /dev/null +++ b/federation/test/helpers.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +import { createServer } from '@/server/createServer' +import { entities } from '@entity/index' +import { createTestClient } from 'apollo-server-testing' +import { logger } from './testSetup' + +// import { createServer } from '@/server/createServer' + +// import { i18n, logger } from './testSetup' + +export const headerPushMock = jest.fn((t) => { + context.token = t.value +}) + +const context = { + token: '', + setHeaders: { + push: headerPushMock, + forEach: jest.fn(), + }, + clientTimezoneOffset: 0, +} + +export const cleanDB = async () => { + // this only works as long we do not have foreign key constraints + for (const entity of entities) { + await resetEntity(entity) + } +} + +export const testEnvironment = async (testLogger = logger) => { + // , testI18n = i18n) => { + const server = await createServer(testLogger) // context, testLogger, testI18n) + const con = server.con + const testClient = createTestClient(server.apollo) + const mutate = testClient.mutate + const query = testClient.query + return { mutate, query, con } +} + +export const resetEntity = async (entity: any) => { + const items = await entity.find({ withDeleted: true }) + if (items.length > 0) { + const ids = items.map((e: any) => e.id) + await entity.delete(ids) + } +} + +export const resetToken = () => { + context.token = '' +} + +// format date string as it comes from the frontend for the contribution date +export const contributionDateFormatter = (date: Date): string => { + return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` +} + +export const setClientTimezoneOffset = (offset: number): void => { + context.clientTimezoneOffset = offset +} diff --git a/federation/tsconfig.json b/federation/tsconfig.json index ba0d6fef3..50ce9d0c7 100644 --- a/federation/tsconfig.json +++ b/federation/tsconfig.json @@ -54,9 +54,9 @@ "@repository/*": ["src/typeorm/repository/*"], "@test/*": ["test/*"], /* common */ - "@common/*": ["../common/src/*"], - "@email/*": ["../common/scr/email/*"], - "@event/*": ["../common/src/event/*"], + // "@common/*": ["../common/src/*"], + // "@email/*": ["../common/scr/email/*"], + // "@event/*": ["../common/src/event/*"], /* external */ "@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"], "@dbTools/*": ["../database/src/*", "../../database/build/src/*"], From 89e106ae6d996a5b3c4b6ded36b36d69722109ec Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 1 Sep 2023 00:31:33 +0200 Subject: [PATCH 09/51] linting --- backend/src/graphql/resolver/TransactionResolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 6cd31adb9..57307cba4 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -33,7 +33,6 @@ import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { BalanceResolver } from './BalanceResolver' -import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' From 47ea0fdb9b2a0ae4f2a9ac51c8e5745f711df1cf Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 1 Sep 2023 01:41:38 +0200 Subject: [PATCH 10/51] activate recipientCommunityIdentifier for sendCoins api --- .../src/graphql/arg/TransactionSendArgs.ts | 3 -- .../resolver/TransactionResolver.test.ts | 28 +++++++++++++++++++ .../src/graphql/resolver/semaphore.test.ts | 15 ++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/arg/TransactionSendArgs.ts b/backend/src/graphql/arg/TransactionSendArgs.ts index 9d6842bdc..48827be8d 100644 --- a/backend/src/graphql/arg/TransactionSendArgs.ts +++ b/backend/src/graphql/arg/TransactionSendArgs.ts @@ -23,7 +23,4 @@ export class TransactionSendArgs { @MaxLength(MEMO_MAX_CHARS) @MinLength(MEMO_MIN_CHARS) memo: string - - @Field(() => String) - communityIdentifier: string } diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index bd7d0f2a8..0f4171dd4 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { Connection, In } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { DltTransaction } from '@entity/DltTransaction' import { Event as DbEvent } from '@entity/Event' import { Transaction } from '@entity/Transaction' @@ -56,12 +57,24 @@ let user: User[] let bob: User let peter: User +let homeCom: DbCommunity + describe('send coins', () => { beforeAll(async () => { peter = await userFactory(testEnv, peterLustig) bob = await userFactory(testEnv, bobBaumeister) await userFactory(testEnv, stephenHawking) await userFactory(testEnv, garrickOllivander) + homeCom = DbCommunity.create() + homeCom.communityUuid = 'homeCom-UUID' + homeCom.creationDate = new Date('2000-01-01') + homeCom.description = 'homeCom description' + homeCom.foreign = false + homeCom.name = 'homeCom name' + homeCom.privateKey = Buffer.from('homeCom privateKey') + homeCom.publicKey = Buffer.from('homeCom publicKey') + homeCom.url = 'homeCom url' + homeCom = await DbCommunity.save(homeCom) bobData = { email: 'bob@baumeister.de', @@ -91,6 +104,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'wrong@email.com', amount: 100, memo: 'test test', @@ -119,6 +133,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'stephen@hawking.uk', amount: 100, memo: 'test test', @@ -148,6 +163,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'garrick@ollivander.com', amount: 100, memo: 'test test', @@ -184,6 +200,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'bob@baumeister.de', amount: 100, memo: 'test test', @@ -207,6 +224,7 @@ describe('send coins', () => { const { errors: errorObjects } = await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 100, memo: 'Test', @@ -238,6 +256,7 @@ describe('send coins', () => { const { errors: errorObjects } = await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 100, memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t', @@ -270,6 +289,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 100, memo: 'testing', @@ -319,6 +339,7 @@ describe('send coins', () => { const { errors: errorObjects } = await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: -50, memo: 'testing negative', @@ -350,6 +371,7 @@ describe('send coins', () => { await mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 50, memo: 'unrepeatable memo', @@ -456,6 +478,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: peter?.gradidoID, amount: 10, memo: 'send via gradido ID', @@ -496,6 +519,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'bob', amount: 6.66, memo: 'send via alias', @@ -564,6 +588,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 10, memo: 'first transaction', @@ -580,6 +605,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 20, memo: 'second transaction', @@ -596,6 +622,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 30, memo: 'third transaction', @@ -612,6 +639,7 @@ describe('send coins', () => { mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'peter@lustig.de', amount: 40, memo: 'fourth transaction', diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index 37331d832..dc6c5b364 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ import { Connection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { ApolloServerTestClient } from 'apollo-server-testing' import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' @@ -48,9 +49,21 @@ describe('semaphore', () => { let bibisTransactionLinkCode = '' let bibisOpenContributionId = -1 let bobsOpenContributionId = -1 + let homeCom: DbCommunity beforeAll(async () => { const now = new Date() + homeCom = DbCommunity.create() + homeCom.communityUuid = 'homeCom-UUID' + homeCom.creationDate = new Date('2000-01-01') + homeCom.description = 'homeCom description' + homeCom.foreign = false + homeCom.name = 'homeCom name' + homeCom.privateKey = Buffer.from('homeCom privateKey') + homeCom.publicKey = Buffer.from('homeCom publicKey') + homeCom.url = 'homeCom url' + homeCom = await DbCommunity.save(homeCom) + await userFactory(testEnv, bibiBloxberg) await userFactory(testEnv, peterLustig) await userFactory(testEnv, bobBaumeister) @@ -157,6 +170,7 @@ describe('semaphore', () => { const bibisTransaction = mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob', @@ -177,6 +191,7 @@ describe('semaphore', () => { const bobsTransaction = mutate({ mutation: sendCoins, variables: { + recipientCommunityIdentifier: homeCom.communityUuid, recipientIdentifier: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi', From a16c1fa99110a9345dd3d5b98ec5fa205ed9549e Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 1 Sep 2023 02:25:15 +0200 Subject: [PATCH 11/51] add config-switch and authentication-check before starting X-Com-sendCoins --- backend/src/config/index.ts | 4 +++- .../graphql/resolver/TransactionResolver.test.ts | 1 + .../src/graphql/resolver/TransactionResolver.ts | 9 ++++++++- backend/src/graphql/resolver/util/communities.ts | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 744f1d3cc..c96f60d17 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -19,7 +19,7 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v18.2023-07-10', + EXPECTED: 'v19.2023-09-01', CURRENT: '', }, } @@ -124,6 +124,8 @@ if ( const federation = { FEDERATION_VALIDATE_COMMUNITY_TIMER: Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000, + FEDERATION_XCOM_SENDCOINS_ENABLED: + process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false, } export const CONFIG = { diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 0f4171dd4..ab5558048 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -58,6 +58,7 @@ let bob: User let peter: User let homeCom: DbCommunity +let foreignCom: DbCommunity describe('send coins', () => { beforeAll(async () => { diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 57307cba4..558f2beb4 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -33,12 +33,13 @@ import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { BalanceResolver } from './BalanceResolver' -import { isHomeCommunity } from './util/communities' +import { isCommunityAuthenticated, isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' import { transactionLinkSummary } from './util/transactionLinkSummary' +import { CONFIG } from '@/config' export const executeTransaction = async ( amount: Decimal, @@ -484,6 +485,12 @@ export class TransactionResolver { } else { // processing a x-community sendCoins logger.debug('processing a x-community transaction...') + if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) { + throw new LogError('X-Community sendCoins disabled per configuration!') + } + if (!(await isCommunityAuthenticated(recipientCommunityIdentifier))) { + throw new LogError('recipient commuity is connected, but still not authenticated yet!') + } } return true } diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index e4ceeb54b..59e79f077 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -19,3 +19,18 @@ export async function getCommunityUrl(communityIdentifier: string): Promise { + const community = await DbCommunity.findOneOrFail({ + where: [ + { communityUuid: communityIdentifier }, + { name: communityIdentifier }, + { url: communityIdentifier }, + ], + }) + if (community.authenticatedAt) { + return true + } else { + return false + } +} From c3d821bb09955efe00cf5ccd0b3b930447a336ef Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 6 Sep 2023 17:47:20 +0200 Subject: [PATCH 12/51] linting --- backend/src/graphql/resolver/TransactionResolver.test.ts | 2 +- backend/src/graphql/resolver/TransactionResolver.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index ab5558048..a59ca5c0a 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -58,7 +58,7 @@ let bob: User let peter: User let homeCom: DbCommunity -let foreignCom: DbCommunity +// let foreignCom: DbCommunity describe('send coins', () => { beforeAll(async () => { diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index eee222d87..e860b50a1 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -18,6 +18,7 @@ import { TransactionList } from '@model/TransactionList' import { User } from '@model/User' import { RIGHTS } from '@/auth/RIGHTS' +import { CONFIG } from '@/config' import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail, @@ -39,7 +40,6 @@ import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' import { transactionLinkSummary } from './util/transactionLinkSummary' -import { CONFIG } from '@/config' export const executeTransaction = async ( amount: Decimal, From 27ccb3b3e9fdd713f5d08504a982b5d1a7a4772f Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 12 Sep 2023 15:22:45 +0200 Subject: [PATCH 13/51] renaming after merge --- ...alculateRecepientBalance.ts => calculateRecipientBalance.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename federation/src/graphql/api/1_0/util/{calculateRecepientBalance.ts => calculateRecipientBalance.ts} (93%) diff --git a/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts b/federation/src/graphql/api/1_0/util/calculateRecipientBalance.ts similarity index 93% rename from federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts rename to federation/src/graphql/api/1_0/util/calculateRecipientBalance.ts index 228862041..1b296f845 100644 --- a/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts +++ b/federation/src/graphql/api/1_0/util/calculateRecipientBalance.ts @@ -3,7 +3,7 @@ import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { Decimal } from 'decimal.js-light' import { Decay } from '../model/Decay' -export async function calculateRecepientBalance( +export async function calculateRecipientBalance( userId: number, amount: Decimal, time: Date, From 0a61b61c63bfb0a25cffc0c67ca79cbaf687e11a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 13 Sep 2023 02:46:47 +0200 Subject: [PATCH 14/51] add tests for settleSendCoins --- .../PublicCommunityInfoResolver.test.ts | 3 +- .../1_0/resolver/SendCoinsResolver.test.ts | 170 ++++++++++++++ .../api/1_0/resolver/SendCoinsResolver.ts | 212 ++++++++++-------- .../util/settlePendingReceiveTransaction.ts | 6 +- federation/test/helpers.ts | 40 +--- 5 files changed, 294 insertions(+), 137 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts index d18a30a7c..08544834f 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -4,11 +4,12 @@ import { createTestClient } from 'apollo-server-testing' import createServer from '@/server/createServer' import { Community as DbCommunity } from '@entity/Community' import CONFIG from '@/config' +import { Connection } from '@dbTools/typeorm' let query: any // to do: We need a setup for the tests that closes the connection -let con: any +let con: Connection CONFIG.FEDERATION_API = '1_0' diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 7b580b240..ce01a0f85 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -9,6 +9,11 @@ import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { Connection } from '@dbTools/typeorm' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import Decimal from 'decimal.js-light' +import { calculateRecipientBalance } from '../util/calculateRecipientBalance' +import { PendingTransactionState } from '../enum/PendingTransactionState' +import { TransactionTypeId } from '../enum/TransactionTypeId' let mutate: ApolloServerTestClient['mutate'], con: Connection // let query: ApolloServerTestClient['query'] @@ -89,6 +94,29 @@ describe('SendCoinsResolver', () => { } ` + const settleSendCoinsMutation = ` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + settleSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` describe('voteForSendCoins', () => { let homeCom: DbCommunity let foreignCom: DbCommunity @@ -353,4 +381,146 @@ describe('SendCoinsResolver', () => { }) }) }) + + describe('settleSendCoins', () => { + let homeCom: DbCommunity + let foreignCom: DbCommunity + let sendUser: DbUser + let recipUser: DbUser + let pendingTx: DbPendingTransaction + const creationDate = new Date() + + beforeEach(async () => { + await cleanDB() + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCom-url' + homeCom.name = 'homeCom-Name' + homeCom.description = 'homeCom-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCom-publicKey') + homeCom.communityUuid = 'homeCom-UUID' + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCom-url' + foreignCom.name = 'foreignCom-Name' + foreignCom.description = 'foreignCom-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCom-publicKey') + foreignCom.communityUuid = 'foreignCom-UUID' + await DbCommunity.insert(foreignCom) + + sendUser = DbUser.create() + sendUser.alias = 'sendUser-alias' + sendUser.firstName = 'sendUser-FirstName' + sendUser.gradidoID = 'sendUser-GradidoID' + sendUser.lastName = 'sendUser-LastName' + await DbUser.insert(sendUser) + + recipUser = DbUser.create() + recipUser.alias = 'recipUser-alias' + recipUser.firstName = 'recipUser-FirstName' + recipUser.gradidoID = 'recipUser-GradidoID' + recipUser.lastName = 'recipUser-LastName' + await DbUser.insert(recipUser) + + pendingTx = DbPendingTransaction.create() + pendingTx.amount = new Decimal(100) + pendingTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + pendingTx.linkedUserId = sendUser.id + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + pendingTx.linkedUserGradidoID = sendUser.gradidoID + pendingTx.state = PendingTransactionState.NEW + pendingTx.typeId = TransactionTypeId.RECEIVE + pendingTx.memo = 'X-Com-TX memo' + pendingTx.userId = recipUser.id + pendingTx.userCommunityUuid = homeCom.communityUuid + pendingTx.userGradidoID = recipUser.gradidoID + await DbPendingTransaction.insert(pendingTx) + }) + + describe('unknown recipient community', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: 'invalid foreignCom', + userReceiverIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('settleSendCoins with wrong communityReceiverIdentifier')], + }), + ) + }) + }) + + describe('unknown recipient user', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: homeCom.communityUuid, + userReceiverIdentifier: 'invalid recipient', + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError( + 'settleSendCoins with unknown userReceiverIdentifier in the community=', + ), + ], + }), + ) + }) + }) + + describe('valid X-Com-TX settled', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: homeCom.communityUuid, + userReceiverIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + data: { + settleSendCoins: true, + }, + }), + ) + }) + }) + }) }) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index c83c074b4..39c6f1e95 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -12,7 +12,7 @@ import { calculateRecipientBalance } from '../util/calculateRecipientBalance' import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' -import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' +// import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' @Resolver() @@ -176,50 +176,70 @@ export class SendCoinsResolver { userSenderName, }: SendCoinsArgs, ): Promise { - logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) - try { - const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, - state: PendingTransactionState.NEW, - typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + logger.debug( + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${communityReceiverIdentifier}, userGradidoID=${userReceiverIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${communitySenderIdentifier}, userSenderIdentifier=${userSenderIdentifier}, userSenderName=${userSenderName}`, + ) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `settleSendCoins 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( + `settleSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + // try { + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.NEW, + typeId: TransactionTypeId.RECEIVE, + balanceDate: new Date(creationDate), + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) + if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, }) - logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { - logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) - return true - } else { - logger.debug( - 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', - ) - throw new LogError( - `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, - PendingTransactionState.NEW, - TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, - ) - } + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) + return true + } else { + logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') + throw new LogError( + `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.NEW, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } +/* } catch (err) { throw new LogError(`Error in settlePendingReceiveTransaction: `, err) } +*/ } @Mutation(() => Boolean) @@ -236,64 +256,66 @@ export class SendCoinsResolver { userSenderName, }: SendCoinsArgs, ): Promise { - try { - logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) - // first check if receiver community is correct - const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, - }) - if (!homeCom) { - throw new LogError( - `revertSettledSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, - ) + // try { + logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `revertSettledSendCoins 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( + `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.SETTLED, + typeId: TransactionTypeId.RECEIVE, + balanceDate: new Date(creationDate), + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') + try { + await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: revertSettledSendCoins pendingTX successfully') + } catch (err) { + throw new LogError('Error in revertSettledSendCoins of receiver: ', err) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { - throw new LogError( - `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, - homeCom.name, - ) - } - const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, - state: PendingTransactionState.SETTLED, - typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, - }) - logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { - logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') - try { - await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug('XCom: revertSettledSendCoins pendingTX successfully') - } catch (err) { - throw new LogError('Error in revertSettledSendCoins of receiver: ', err) - } - } else { - logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') - throw new LogError( - `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, - PendingTransactionState.SETTLED, - TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, - ) - } - logger.debug(`revertSendCoins()-1_0... successfull`) - return true + } else { + logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') + throw new LogError( + `Can't find in revertSettledSendCoins the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.SETTLED, + 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) } +*/ } } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 0823e7d41..586c57998 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -15,7 +15,7 @@ import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' -import { calculateRecepientBalance } from './calculateRecepientBalance' +import { calculateRecipientBalance } from './calculateRecipientBalance' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -52,7 +52,7 @@ export async function settlePendingReceiveTransaction( const lastTransaction = await getLastTransaction(receiverUser.id) - if (lastTransaction?.id !== pendingTx.previous) { + if (lastTransaction === undefined && lastTransaction.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) @@ -69,7 +69,7 @@ export async function settlePendingReceiveTransaction( transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID transactionReceive.linkedUserName = pendingTx.linkedUserName transactionReceive.amount = pendingTx.amount - const receiveBalance = await calculateRecepientBalance( + const receiveBalance = await calculateRecipientBalance( receiverUser.id, pendingTx.amount, pendingTx.balanceDate, diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts index 3c118ec43..3b05edf4d 100644 --- a/federation/test/helpers.ts +++ b/federation/test/helpers.ts @@ -4,45 +4,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-return */ +import { entities } from '@entity/index' +import { createTestClient } from 'apollo-server-testing' -/* <<<<<<< HEAD import { createServer } from '@/server/createServer' -import { entities } from '@entity/index' -import { createTestClient } from 'apollo-server-testing' -import { logger } from './testSetup' - -// import { createServer } from '@/server/createServer' - -// import { i18n, logger } from './testSetup' - -export const headerPushMock = jest.fn((t) => { - context.token = t.value -}) - -const context = { - token: '', - setHeaders: { - push: headerPushMock, - forEach: jest.fn(), - }, - clientTimezoneOffset: 0, -} - -export const cleanDB = async () => { - // this only works as long we do not have foreign key constraints - for (const entity of entities) { - await resetEntity(entity) - } -} - -export const testEnvironment = async (testLogger = logger) => { - // , testI18n = i18n) => { -======= -*/ -import { entities } from '@entity/index' -import { createTestClient } from 'apollo-server-testing' - -import createServer from '@/server/createServer' import { logger } from './testSetup' @@ -67,7 +32,6 @@ export const cleanDB = async () => { } export const testEnvironment = async (testLogger = logger) => { - // >>>>>>> refs/remotes/origin/master const server = await createServer(testLogger) // context, testLogger, testI18n) const con = server.con const testClient = createTestClient(server.apollo) From 3745c6a8e7fce0827805681241e12ad0610f1f99 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Sep 2023 00:09:58 +0200 Subject: [PATCH 15/51] DB migration add communityUuid to transactions table --- backend/src/config/index.ts | 2 +- .../Transaction.ts | 158 ++++++++++++++++++ database/entity/Transaction.ts | 2 +- ...add_communityuuid_to_transactions_table.ts | 32 ++++ dht-node/src/config/index.ts | 2 +- federation/src/config/index.ts | 2 +- 6 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts create mode 100644 database/migrations/0072-add_communityuuid_to_transactions_table.ts diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 26e759c47..7c849f7d9 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -12,7 +12,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0071-add-pending_transactions-table', + DB_VERSION: '0072-add_communityuuid_to_transactions_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts new file mode 100644 index 000000000..225ef9d23 --- /dev/null +++ b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts @@ -0,0 +1,158 @@ +/* eslint-disable no-use-before-define */ +import { Decimal } from 'decimal.js-light' +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { Contribution } from '../Contribution' + +@Entity('transactions') +export class Transaction extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ type: 'int', unsigned: true, unique: true, nullable: true, default: null }) + previous: number | null + + @Column({ name: 'type_id', unsigned: true, nullable: false }) + typeId: number + + @Column({ + name: 'transaction_link_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + transactionLinkId?: number | null + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + amount: Decimal + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + balance: Decimal + + @Column({ + name: 'balance_date', + type: 'datetime', + default: () => 'CURRENT_TIMESTAMP', + nullable: false, + }) + balanceDate: Date + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + decay: Decimal + + @Column({ + name: 'decay_start', + type: 'datetime', + nullable: true, + default: null, + }) + decayStart: Date | null + + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) + memo: string + + @Column({ name: 'creation_date', type: 'datetime', nullable: true, default: null }) + creationDate: Date | null + + @Column({ name: 'user_id', unsigned: true, nullable: false }) + userId: number + + @Column({ + name: 'user_community_uuid', + type: 'varchar', + length: 36, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + userCommunityUuid: string + + @Column({ + name: 'user_gradido_id', + type: 'varchar', + length: 36, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + userGradidoID: string + + @Column({ + name: 'user_name', + type: 'varchar', + length: 512, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + userName: string | null + + @Column({ + name: 'linked_user_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + linkedUserId?: number | null + + @Column({ + name: 'linked_user_community_uuid', + type: 'varchar', + length: 36, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + linkedUserCommunityUuid: string | null + + @Column({ + name: 'linked_user_gradido_id', + type: 'varchar', + length: 36, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + linkedUserGradidoID: string | null + + @Column({ + name: 'linked_user_name', + type: 'varchar', + length: 512, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + linkedUserName: string | null + + @Column({ + name: 'linked_transaction_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + linkedTransactionId?: number | null + + @OneToOne(() => Contribution, (contribution) => contribution.transaction) + @JoinColumn({ name: 'id', referencedColumnName: 'transactionId' }) + contribution?: Contribution | null + + @OneToOne(() => Transaction) + @JoinColumn({ name: 'previous' }) + previousTransaction?: Transaction | null +} diff --git a/database/entity/Transaction.ts b/database/entity/Transaction.ts index d08c84667..d1d7075a9 100644 --- a/database/entity/Transaction.ts +++ b/database/entity/Transaction.ts @@ -1 +1 @@ -export { Transaction } from './0070-add_dlt_transactions_table/Transaction' +export { Transaction } from './0072-add_communityuuid_to_transactions_table/Transaction' diff --git a/database/migrations/0072-add_communityuuid_to_transactions_table.ts b/database/migrations/0072-add_communityuuid_to_transactions_table.ts new file mode 100644 index 000000000..779da77c9 --- /dev/null +++ b/database/migrations/0072-add_communityuuid_to_transactions_table.ts @@ -0,0 +1,32 @@ +/* MIGRATION TO add users that have a transaction but do not exist */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `user_community_uuid` char(36) DEFAULT NULL NULL AFTER `user_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `linked_user_community_uuid` char(36) DEFAULT NULL NULL AFTER `linked_user_id`;', + ) + // read the community uuid of the homeCommunity + const result = await queryFn(`SELECT c.community_uuid from communities as c WHERE c.foreign = 0`) + // and if uuid exists enter the home_community_uuid for sender and recipient of each still existing transaction + if (result[0]) { + await queryFn( + `UPDATE transactions as t SET t.user_community_uuid = "${result[0].community_uuid}" WHERE t.user_id IS NOT NULL AND t.user_community_uuid IS NULL`, + ) + await queryFn( + `UPDATE transactions as t SET t.linked_user_community_uuid = "${result[0].community_uuid}" WHERE t.linked_user_id IS NOT NULL AND t.linked_user_community_uuid IS NULL`, + ) + } + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `user_community_uuid` char(36) NOT NULL AFTER `user_id`;', + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_community_uuid`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `linked_user_community_uuid`;') +} diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 1d5d0672e..78755f280 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv' dotenv.config() const constants = { - DB_VERSION: '0071-add-pending_transactions-table', + DB_VERSION: '0072-add_communityuuid_to_transactions_table', LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL || 'info', diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 50d902219..e88267589 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0071-add-pending_transactions-table', + DB_VERSION: '0072-add_communityuuid_to_transactions_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info From a71877abed87ea89a6a715a8ae11f73120eff029 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Sep 2023 21:10:38 +0200 Subject: [PATCH 16/51] tests for all sendCoins 2-Phase-Commit handshake requests --- .../client/1_0/model/SendCoinsArgs.ts | 10 +- .../client/1_0/query/revertSendCoins.ts | 20 +- .../1_0/query/revertSettledSendCoins.ts | 22 +- .../client/1_0/query/settleSendCoins.ts | 22 +- .../client/1_0/query/voteForSendCoins.ts | 20 +- .../Transaction.ts | 5 + federation/package.json | 4 +- .../graphql/api/1_0/model/SendCoinsArgs.ts | 10 +- .../1_0/resolver/SendCoinsResolver.test.ts | 581 ++++++++++-------- .../api/1_0/resolver/SendCoinsResolver.ts | 213 +++---- .../util/revertSettledReceiveTransaction.ts | 8 +- .../util/settlePendingReceiveTransaction.ts | 20 +- .../src/graphql/util/findUserByIdentifier.ts | 42 ++ federation/src/graphql/util/validateAlias.ts | 39 ++ federation/yarn.lock | 15 +- 15 files changed, 616 insertions(+), 415 deletions(-) create mode 100644 federation/src/graphql/util/findUserByIdentifier.ts create mode 100644 federation/src/graphql/util/validateAlias.ts diff --git a/backend/src/federation/client/1_0/model/SendCoinsArgs.ts b/backend/src/federation/client/1_0/model/SendCoinsArgs.ts index 545aab822..fb97da925 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsArgs.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsArgs.ts @@ -4,10 +4,10 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export class SendCoinsArgs { @Field(() => String) - communityReceiverIdentifier: string + recipientCommunityUuid: string @Field(() => String) - userReceiverIdentifier: string + recipientUserIdentifier: string @Field(() => String) creationDate: string @@ -19,11 +19,11 @@ export class SendCoinsArgs { memo: string @Field(() => String) - communitySenderIdentifier: string + senderCommunityUuid: string @Field(() => String) - userSenderIdentifier: string + senderUserUuid: string @Field(() => String) - userSenderName: string + senderUserName: string } diff --git a/backend/src/federation/client/1_0/query/revertSendCoins.ts b/backend/src/federation/client/1_0/query/revertSendCoins.ts index 881107cb4..9cc23fe64 100644 --- a/backend/src/federation/client/1_0/query/revertSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const revertSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { revertSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts index 0d4447507..74cdbd867 100644 --- a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const revertSettledSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: Date! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { revertSettledSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts index 99f784bc7..1696a0900 100644 --- a/backend/src/federation/client/1_0/query/settleSendCoins.ts +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const settleSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: Date! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { settleSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/backend/src/federation/client/1_0/query/voteForSendCoins.ts b/backend/src/federation/client/1_0/query/voteForSendCoins.ts index f0f75198f..0f16ff32b 100644 --- a/backend/src/federation/client/1_0/query/voteForSendCoins.ts +++ b/backend/src/federation/client/1_0/query/voteForSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const voteForSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { voteForSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts index 225ef9d23..3efa78ada 100644 --- a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts +++ b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts @@ -3,6 +3,7 @@ import { Decimal } from 'decimal.js-light' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' import { Contribution } from '../Contribution' +import { DltTransaction } from '../DltTransaction' @Entity('transactions') export class Transaction extends BaseEntity { @@ -152,6 +153,10 @@ export class Transaction extends BaseEntity { @JoinColumn({ name: 'id', referencedColumnName: 'transactionId' }) contribution?: Contribution | null + @OneToOne(() => DltTransaction, (dlt) => dlt.transactionId) + @JoinColumn({ name: 'id', referencedColumnName: 'transactionId' }) + dltTransaction?: DltTransaction | null + @OneToOne(() => Transaction) @JoinColumn({ name: 'previous' }) previousTransaction?: Transaction | null diff --git a/federation/package.json b/federation/package.json index eb78d6be0..aaeaff08d 100644 --- a/federation/package.json +++ b/federation/package.json @@ -16,6 +16,7 @@ "lint": "eslint --max-warnings=0 --ext .js,.ts ." }, "dependencies": { + "@types/uuid": "8.3.4", "apollo-server-express": "^2.25.2", "await-semaphore": "0.1.3", "class-validator": "^0.13.2", @@ -28,7 +29,8 @@ "lodash.clonedeep": "^4.5.0", "log4js": "^6.7.1", "reflect-metadata": "^0.1.13", - "type-graphql": "^1.1.1" + "type-graphql": "^1.1.1", + "uuid": "8.3.2" }, "devDependencies": { "@types/express": "4.17.12", diff --git a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts index 545aab822..fb97da925 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts @@ -4,10 +4,10 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export class SendCoinsArgs { @Field(() => String) - communityReceiverIdentifier: string + recipientCommunityUuid: string @Field(() => String) - userReceiverIdentifier: string + recipientUserIdentifier: string @Field(() => String) creationDate: string @@ -19,11 +19,11 @@ export class SendCoinsArgs { memo: string @Field(() => String) - communitySenderIdentifier: string + senderCommunityUuid: string @Field(() => String) - userSenderIdentifier: string + senderUserUuid: string @Field(() => String) - userSenderName: string + senderUserName: string } diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index ce01a0f85..5120391d6 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -4,6 +4,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Community as DbCommunity } from '@entity/Community' import CONFIG from '@/config' import { User as DbUser } from '@entity/User' +import { UserContact as DbUserContact } from '@entity/UserContact' import { fullName } from '@/graphql/util/fullName' import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' @@ -11,9 +12,9 @@ import { logger } from '@test/testSetup' import { Connection } from '@dbTools/typeorm' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import Decimal from 'decimal.js-light' -import { calculateRecipientBalance } from '../util/calculateRecipientBalance' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' +import { Transaction as DbTransaction } from '@entity/Transaction' let mutate: ApolloServerTestClient['mutate'], con: Connection // let query: ApolloServerTestClient['query'] @@ -26,18 +27,18 @@ let testEnv: { CONFIG.FEDERATION_API = '1_0' +let homeCom: DbCommunity +let foreignCom: DbCommunity +let sendUser: DbUser +let sendContact: DbUserContact +let recipUser: DbUser +let recipContact: DbUserContact + beforeAll(async () => { testEnv = await testEnvironment(logger) mutate = testEnv.mutate // query = testEnv.query con = testEnv.con - - // const server = await createServer() - // con = server.con - // query = createTestClient(server.apollo).query - // mutate = createTestClient(server.apollo).mutate - // DbCommunity.clear() - // DbUser.clear() await cleanDB() }) @@ -48,118 +49,103 @@ afterAll(async () => { describe('SendCoinsResolver', () => { const voteForSendCoinsMutation = ` - mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! - ) { - voteForSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName - ) - } -` - const revertSendCoinsMutation = ` - mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: String! - $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 - ) - } -` + mutation ( + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! + ) { + voteForSendCoins( + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + ) + }` const settleSendCoinsMutation = ` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { settleSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) - } -` + }` + + beforeEach(async () => { + await cleanDB() + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCom-url' + homeCom.name = 'homeCom-Name' + homeCom.description = 'homeCom-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCom-publicKey') + homeCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba' + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCom-url' + foreignCom.name = 'foreignCom-Name' + foreignCom.description = 'foreignCom-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCom-publicKey') + foreignCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb' + await DbCommunity.insert(foreignCom) + + sendUser = DbUser.create() + sendUser.alias = 'sendUser-alias' + sendUser.firstName = 'sendUser-FirstName' + sendUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebc' + sendUser.lastName = 'sendUser-LastName' + await DbUser.insert(sendUser) + + sendContact = await newEmailContact('send.user@email.de', sendUser.id) + sendContact = await DbUserContact.save(sendContact) + + sendUser.emailContact = sendContact + sendUser.emailId = sendContact.id + await DbUser.save(sendUser) + + recipUser = DbUser.create() + recipUser.alias = 'recipUser-alias' + recipUser.firstName = 'recipUser-FirstName' + recipUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebd' + recipUser.lastName = 'recipUser-LastName' + await DbUser.insert(recipUser) + + recipContact = await newEmailContact('recip.user@email.de', recipUser.id) + recipContact = await DbUserContact.save(recipContact) + + recipUser.emailContact = recipContact + recipUser.emailId = recipContact.id + await DbUser.save(recipUser) + }) + describe('voteForSendCoins', () => { - let homeCom: DbCommunity - let foreignCom: DbCommunity - let sendUser: DbUser - let recipUser: DbUser - - beforeEach(async () => { - await cleanDB() - homeCom = DbCommunity.create() - homeCom.foreign = false - homeCom.url = 'homeCom-url' - homeCom.name = 'homeCom-Name' - homeCom.description = 'homeCom-Description' - homeCom.creationDate = new Date() - homeCom.publicKey = Buffer.from('homeCom-publicKey') - homeCom.communityUuid = 'homeCom-UUID' - await DbCommunity.insert(homeCom) - - foreignCom = DbCommunity.create() - foreignCom.foreign = true - foreignCom.url = 'foreignCom-url' - foreignCom.name = 'foreignCom-Name' - foreignCom.description = 'foreignCom-Description' - foreignCom.creationDate = new Date() - foreignCom.publicKey = Buffer.from('foreignCom-publicKey') - foreignCom.communityUuid = 'foreignCom-UUID' - await DbCommunity.insert(foreignCom) - - sendUser = DbUser.create() - sendUser.alias = 'sendUser-alias' - sendUser.firstName = 'sendUser-FirstName' - sendUser.gradidoID = 'sendUser-GradidoID' - sendUser.lastName = 'sendUser-LastName' - await DbUser.insert(sendUser) - - recipUser = DbUser.create() - recipUser.alias = 'recipUser-alias' - recipUser.firstName = 'recipUser-FirstName' - recipUser.gradidoID = 'recipUser-GradidoID' - recipUser.lastName = 'recipUser-LastName' - await DbUser.insert(recipUser) - }) - describe('unknown recipient community', () => { it('throws an error', async () => { jest.clearAllMocks() @@ -167,19 +153,19 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: 'invalid foreignCom', - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, creationDate: new Date().toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('voteForSendCoins with wrong communityReceiverIdentifier')], + errors: [new GraphQLError('voteForSendCoins with wrong recipientCommunityUuid')], }), ) }) @@ -192,21 +178,21 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: 'invalid recipient', + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', creationDate: new Date().toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'voteForSendCoins with unknown userReceiverIdentifier in the community=', + 'voteForSendCoins with unknown recipientUserIdentifier in the community=', ), ], }), @@ -221,14 +207,14 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: new Date().toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( @@ -243,59 +229,43 @@ describe('SendCoinsResolver', () => { }) describe('revertSendCoins', () => { - let homeCom: DbCommunity - let foreignCom: DbCommunity - let sendUser: DbUser - let recipUser: DbUser + const revertSendCoinsMutation = ` + mutation ( + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! + ) { + revertSendCoins( + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + ) + }` + const creationDate = new Date() beforeEach(async () => { - await cleanDB() - homeCom = DbCommunity.create() - homeCom.foreign = false - homeCom.url = 'homeCom-url' - homeCom.name = 'homeCom-Name' - homeCom.description = 'homeCom-Description' - homeCom.creationDate = new Date() - homeCom.publicKey = Buffer.from('homeCom-publicKey') - homeCom.communityUuid = 'homeCom-UUID' - await DbCommunity.insert(homeCom) - - foreignCom = DbCommunity.create() - foreignCom.foreign = true - foreignCom.url = 'foreignCom-url' - foreignCom.name = 'foreignCom-Name' - foreignCom.description = 'foreignCom-Description' - foreignCom.creationDate = new Date() - foreignCom.publicKey = Buffer.from('foreignCom-publicKey') - foreignCom.communityUuid = 'foreignCom-UUID' - await DbCommunity.insert(foreignCom) - - sendUser = DbUser.create() - sendUser.alias = 'sendUser-alias' - sendUser.firstName = 'sendUser-FirstName' - sendUser.gradidoID = 'sendUser-GradidoID' - sendUser.lastName = 'sendUser-LastName' - await DbUser.insert(sendUser) - - recipUser = DbUser.create() - recipUser.alias = 'recipUser-alias' - recipUser.firstName = 'recipUser-FirstName' - recipUser.gradidoID = 'recipUser-GradidoID' - recipUser.lastName = 'recipUser-LastName' - await DbUser.insert(recipUser) - await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }) }) @@ -307,19 +277,19 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: revertSendCoinsMutation, variables: { - communityReceiverIdentifier: 'invalid foreignCom', - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('revertSendCoins with wrong communityReceiverIdentifier')], + errors: [new GraphQLError('revertSendCoins with wrong recipientCommunityUuid')], }), ) }) @@ -332,21 +302,21 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: revertSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: 'invalid recipient', + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'revertSendCoins with unknown userReceiverIdentifier in the community=', + 'revertSendCoins with unknown recipientUserIdentifier in the community=', ), ], }), @@ -361,14 +331,14 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: revertSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( @@ -383,61 +353,26 @@ describe('SendCoinsResolver', () => { }) describe('settleSendCoins', () => { - let homeCom: DbCommunity - let foreignCom: DbCommunity - let sendUser: DbUser - let recipUser: DbUser let pendingTx: DbPendingTransaction const creationDate = new Date() beforeEach(async () => { - await cleanDB() - homeCom = DbCommunity.create() - homeCom.foreign = false - homeCom.url = 'homeCom-url' - homeCom.name = 'homeCom-Name' - homeCom.description = 'homeCom-Description' - homeCom.creationDate = new Date() - homeCom.publicKey = Buffer.from('homeCom-publicKey') - homeCom.communityUuid = 'homeCom-UUID' - await DbCommunity.insert(homeCom) - - foreignCom = DbCommunity.create() - foreignCom.foreign = true - foreignCom.url = 'foreignCom-url' - foreignCom.name = 'foreignCom-Name' - foreignCom.description = 'foreignCom-Description' - foreignCom.creationDate = new Date() - foreignCom.publicKey = Buffer.from('foreignCom-publicKey') - foreignCom.communityUuid = 'foreignCom-UUID' - await DbCommunity.insert(foreignCom) - - sendUser = DbUser.create() - sendUser.alias = 'sendUser-alias' - sendUser.firstName = 'sendUser-FirstName' - sendUser.gradidoID = 'sendUser-GradidoID' - sendUser.lastName = 'sendUser-LastName' - await DbUser.insert(sendUser) - - recipUser = DbUser.create() - recipUser.alias = 'recipUser-alias' - recipUser.firstName = 'recipUser-FirstName' - recipUser.gradidoID = 'recipUser-GradidoID' - recipUser.lastName = 'recipUser-LastName' - await DbUser.insert(recipUser) - pendingTx = DbPendingTransaction.create() pendingTx.amount = new Decimal(100) pendingTx.balanceDate = creationDate // pendingTx.balance = new Decimal(0) pendingTx.linkedUserId = sendUser.id - pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + if (foreignCom.communityUuid) { + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + } pendingTx.linkedUserGradidoID = sendUser.gradidoID pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.RECEIVE pendingTx.memo = 'X-Com-TX memo' pendingTx.userId = recipUser.id - pendingTx.userCommunityUuid = homeCom.communityUuid + if (homeCom.communityUuid) { + pendingTx.userCommunityUuid = homeCom.communityUuid + } pendingTx.userGradidoID = recipUser.gradidoID await DbPendingTransaction.insert(pendingTx) }) @@ -449,19 +384,19 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: settleSendCoinsMutation, variables: { - communityReceiverIdentifier: 'invalid foreignCom', - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: foreignCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('settleSendCoins with wrong communityReceiverIdentifier')], + errors: [new GraphQLError('settleSendCoins with wrong recipientCommunityUuid')], }), ) }) @@ -474,21 +409,21 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: settleSendCoinsMutation, variables: { - communityReceiverIdentifier: homeCom.communityUuid, - userReceiverIdentifier: 'invalid recipient', + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: foreignCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'settleSendCoins with unknown userReceiverIdentifier in the community=', + 'settleSendCoins with unknown recipientUserIdentifier in the community=', ), ], }), @@ -503,14 +438,14 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: settleSendCoinsMutation, variables: { - communityReceiverIdentifier: homeCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: foreignCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( @@ -523,4 +458,164 @@ describe('SendCoinsResolver', () => { }) }) }) + + describe('revertSettledSendCoins', () => { + const revertSettledSendCoinsMutation = ` + mutation ( + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! + ) { + revertSettledSendCoins( + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + ) + }` + + let pendingTx: DbPendingTransaction + let settledTx: DbTransaction + const creationDate = new Date() + + beforeEach(async () => { + pendingTx = DbPendingTransaction.create() + pendingTx.amount = new Decimal(100) + pendingTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + pendingTx.linkedUserId = sendUser.id + if (foreignCom.communityUuid) { + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + } + pendingTx.linkedUserGradidoID = sendUser.gradidoID + pendingTx.linkedUserName = fullName(sendUser.firstName, sendUser.lastName) + pendingTx.state = PendingTransactionState.SETTLED + pendingTx.typeId = TransactionTypeId.RECEIVE + pendingTx.memo = 'X-Com-TX memo' + pendingTx.userId = recipUser.id + if (homeCom.communityUuid) { + pendingTx.userCommunityUuid = homeCom.communityUuid + } + pendingTx.userGradidoID = recipUser.gradidoID + await DbPendingTransaction.insert(pendingTx) + + settledTx = DbTransaction.create() + settledTx.amount = new Decimal(100) + settledTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + settledTx.linkedUserId = sendUser.id + settledTx.linkedUserCommunityUuid = foreignCom.communityUuid + settledTx.linkedUserGradidoID = sendUser.gradidoID + settledTx.linkedUserName = fullName(sendUser.firstName, sendUser.lastName) + settledTx.typeId = TransactionTypeId.RECEIVE + settledTx.memo = 'X-Com-TX memo' + settledTx.userId = recipUser.id + if (homeCom.communityUuid) { + settledTx.userCommunityUuid = homeCom.communityUuid + } + settledTx.userGradidoID = recipUser.gradidoID + await DbTransaction.insert(settledTx) + }) + + describe('unknown recipient community', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: revertSettledSendCoinsMutation, + variables: { + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('revertSettledSendCoins with wrong recipientCommunityUuid')], + }), + ) + }) + }) + + describe('unknown recipient user', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: revertSettledSendCoinsMutation, + variables: { + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError( + 'revertSettledSendCoins with unknown recipientUserIdentifier in the community=', + ), + ], + }), + ) + }) + }) + + describe('valid X-Com-TX settled', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: revertSettledSendCoinsMutation, + variables: { + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + data: { + revertSettledSendCoins: true, + }, + }), + ) + }) + }) + }) }) + +async function newEmailContact(email: string, userId: number): Promise { + const emailContact = new DbUserContact() + emailContact.email = email + emailContact.userId = userId + emailContact.type = 'EMAIL' + emailContact.emailChecked = false + emailContact.emailOptInTypeId = 1 + emailContact.emailVerificationCode = '1' + userId + return emailContact +} diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 39c6f1e95..4196e015c 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -4,7 +4,6 @@ import { federationLogger as logger } from '@/server/logger' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { SendCoinsArgs } from '../model/SendCoinsArgs' -import { User as DbUser } from '@entity/User' import { LogError } from '@/server/LogError' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' @@ -14,6 +13,7 @@ import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' // import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' +import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -22,33 +22,46 @@ export class SendCoinsResolver { async voteForSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, - ): Promise { - logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`) - let result: string | null = null + ): Promise { + logger.debug( + `voteForSendCoins() via apiVersion=1_0 ...`, + recipientCommunityUuid, + recipientUserIdentifier, + creationDate, + amount.toString(), + memo, + senderCommunityUuid, + senderUserUuid, + senderUserName, + ) + let result: string // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `voteForSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `voteForSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `voteForSendCoins with unknown userReceiverIdentifier in the community=`, + `voteForSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } @@ -57,21 +70,21 @@ export class SendCoinsResolver { const receiveBalance = await calculateRecipientBalance(receiverUser.id, amount, txDate) const pendingTx = DbPendingTransaction.create() pendingTx.amount = amount - pendingTx.balance = receiveBalance ? receiveBalance.balance : new Decimal(0) + pendingTx.balance = receiveBalance ? receiveBalance.balance : amount pendingTx.balanceDate = txDate pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null pendingTx.creationDate = new Date() - pendingTx.linkedUserCommunityUuid = communitySenderIdentifier - pendingTx.linkedUserGradidoID = userSenderIdentifier - pendingTx.linkedUserName = userSenderName + pendingTx.linkedUserCommunityUuid = senderCommunityUuid + pendingTx.linkedUserGradidoID = senderUserUuid + pendingTx.linkedUserName = senderUserName pendingTx.memo = memo pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.RECEIVE pendingTx.userId = receiverUser.id - pendingTx.userCommunityUuid = communityReceiverIdentifier - pendingTx.userGradidoID = userReceiverIdentifier + pendingTx.userCommunityUuid = recipientCommunityUuid + pendingTx.userGradidoID = receiverUser.gradidoID pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) await DbPendingTransaction.insert(pendingTx) @@ -87,44 +100,47 @@ export class SendCoinsResolver { async revertSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, ): Promise { logger.debug(`revertSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `revertSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `revertSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `revertSendCoins with unknown userReceiverIdentifier in the community=`, + `revertSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } try { const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, + userCommunityUuid: recipientCommunityUuid, + userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + linkedUserCommunityUuid: senderCommunityUuid, + linkedUserGradidoID: senderUserUuid, }) logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) if (pendingTx && pendingTx.amount.toString() === amount.toString()) { @@ -143,16 +159,16 @@ export class SendCoinsResolver { ) throw new LogError( `Can't find in revertSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) @@ -166,56 +182,53 @@ export class SendCoinsResolver { async settleSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, ): Promise { logger.debug( - `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${communityReceiverIdentifier}, userGradidoID=${userReceiverIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${communitySenderIdentifier}, userSenderIdentifier=${userSenderIdentifier}, userSenderName=${userSenderName}`, + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${recipientCommunityUuid}, userGradidoID=${recipientUserIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${senderCommunityUuid}, userSenderIdentifier=${senderUserUuid}, userSenderName=${senderUserName}`, ) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `settleSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `settleSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `settleSendCoins with unknown userReceiverIdentifier in the community=`, + `settleSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } - // try { const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, + userCommunityUuid: recipientCommunityUuid, + userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + linkedUserCommunityUuid: senderCommunityUuid, + linkedUserGradidoID: senderUserUuid, }) logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) return true @@ -223,70 +236,67 @@ export class SendCoinsResolver { logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') throw new LogError( `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, ) } -/* - } catch (err) { - throw new LogError(`Error in settlePendingReceiveTransaction: `, err) - } -*/ } @Mutation(() => Boolean) async revertSettledSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, ): Promise { - // try { logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `revertSettledSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `revertSettledSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + `revertSettledSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, + userCommunityUuid: recipientCommunityUuid, + userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.SETTLED, typeId: TransactionTypeId.RECEIVE, balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + linkedUserCommunityUuid: senderCommunityUuid, + linkedUserGradidoID: senderUserUuid, }) logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') try { await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) @@ -298,24 +308,19 @@ export class SendCoinsResolver { logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') throw new LogError( `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, PendingTransactionState.SETTLED, TransactionTypeId.RECEIVE, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) return true -/* - } catch (err) { - throw new LogError(`Error in revertSendCoins: `, err) - } -*/ } } diff --git a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts index ecf615667..6eb933f6a 100644 --- a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts @@ -64,10 +64,10 @@ export async function revertSettledReceiveTransaction( if ( lastTransaction && lastTransaction.balance === pendingTx.balance && - lastTransaction.balanceDate === pendingTx.balanceDate && + lastTransaction.balanceDate.toISOString() === pendingTx.balanceDate.toISOString() && lastTransaction.userGradidoID === pendingTx.userGradidoID && lastTransaction.userName === pendingTx.userName && - lastTransaction.amount === pendingTx.amount && + lastTransaction.amount.toString() === pendingTx.amount.toString() && lastTransaction.memo === pendingTx.memo && lastTransaction.linkedUserGradidoID === pendingTx.linkedUserGradidoID && lastTransaction.linkedUserName === pendingTx.linkedUserName @@ -83,7 +83,9 @@ export async function revertSettledReceiveTransaction( } else { // TODO: if the last TX is not equivelant to pendingTX, the transactions must be corrected in EXPERT-MODE throw new LogError( - `X-Com: missmatching transaction order for revert settlement! lastTransation=${lastTransaction} != pendingTx=${pendingTx}`, + `X-Com: missmatching transaction order for revert settlement!`, + lastTransaction, + pendingTx, ) } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 586c57998..5c522061c 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -16,6 +16,7 @@ import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' import { calculateRecipientBalance } from './calculateRecipientBalance' +import Decimal from 'decimal.js-light' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -52,7 +53,7 @@ export async function settlePendingReceiveTransaction( const lastTransaction = await getLastTransaction(receiverUser.id) - if (lastTransaction === undefined && lastTransaction.id !== pendingTx.previous) { + if (lastTransaction !== null && lastTransaction.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) @@ -63,9 +64,11 @@ export async function settlePendingReceiveTransaction( transactionReceive.typeId = pendingTx.typeId transactionReceive.memo = pendingTx.memo transactionReceive.userId = pendingTx.userId + transactionReceive.userCommunityUuid = pendingTx.userCommunityUuid transactionReceive.userGradidoID = pendingTx.userGradidoID transactionReceive.userName = pendingTx.userName transactionReceive.linkedUserId = pendingTx.linkedUserId + transactionReceive.linkedUserCommunityUuid = pendingTx.linkedUserCommunityUuid transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID transactionReceive.linkedUserName = pendingTx.linkedUserName transactionReceive.amount = pendingTx.amount @@ -74,16 +77,19 @@ export async function settlePendingReceiveTransaction( pendingTx.amount, pendingTx.balanceDate, ) - if (receiveBalance?.balance !== pendingTx.balance) { + if ( + receiveBalance !== null && + receiveBalance.balance.toString() !== pendingTx.balance.toString() + ) { throw new LogError( - `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, + `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance.balance}, pendingTx.balance=${pendingTx.balance}`, ) } - transactionReceive.balance = pendingTx.balance + transactionReceive.balance = receiveBalance ? receiveBalance.balance : pendingTx.amount transactionReceive.balanceDate = pendingTx.balanceDate - transactionReceive.decay = pendingTx.decay - transactionReceive.decayStart = pendingTx.decayStart - transactionReceive.previous = pendingTx.previous + transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null + transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId await queryRunner.manager.insert(dbTransaction, transactionReceive) logger.debug(`receive Transaction inserted: ${dbTransaction}`) diff --git a/federation/src/graphql/util/findUserByIdentifier.ts b/federation/src/graphql/util/findUserByIdentifier.ts new file mode 100644 index 000000000..96c9eb458 --- /dev/null +++ b/federation/src/graphql/util/findUserByIdentifier.ts @@ -0,0 +1,42 @@ +import { User as DbUser } from '@entity/User' +import { UserContact as DbUserContact } from '@entity/UserContact' +import { validate, version } from 'uuid' + +import { LogError } from '@/server/LogError' + +import { VALID_ALIAS_REGEX } from './validateAlias' + +export const findUserByIdentifier = async (identifier: string): Promise => { + let user: DbUser | null + if (validate(identifier) && version(identifier) === 4) { + user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] }) + if (!user) { + throw new LogError('No user found to given identifier', identifier) + } + } else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) { + const userContact = await DbUserContact.findOne({ + where: { + email: identifier, + emailChecked: true, + }, + relations: ['user'], + }) + if (!userContact) { + throw new LogError('No user with this credentials', identifier) + } + if (!userContact.user) { + throw new LogError('No user to given contact', identifier) + } + user = userContact.user + user.emailContact = userContact + } else if (VALID_ALIAS_REGEX.exec(identifier)) { + user = await DbUser.findOne({ where: { alias: identifier }, relations: ['emailContact'] }) + if (!user) { + throw new LogError('No user found to given identifier', identifier) + } + } else { + throw new LogError('Unknown identifier type', identifier) + } + + return user +} diff --git a/federation/src/graphql/util/validateAlias.ts b/federation/src/graphql/util/validateAlias.ts new file mode 100644 index 000000000..cd2d8fe1f --- /dev/null +++ b/federation/src/graphql/util/validateAlias.ts @@ -0,0 +1,39 @@ +import { Raw } from '@dbTools/typeorm' +import { User as DbUser } from '@entity/User' + +import { LogError } from '@/server/LogError' + +export const VALID_ALIAS_REGEX = /^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/ + +const RESERVED_ALIAS = [ + 'admin', + 'email', + 'gast', + 'gdd', + 'gradido', + 'guest', + 'home', + 'root', + 'support', + 'temp', + 'tmp', + 'tmp', + 'user', + 'usr', + 'var', +] + +export const validateAlias = async (alias: string): Promise => { + if (alias.length < 3) throw new LogError('Given alias is too short', alias) + if (alias.length > 20) throw new LogError('Given alias is too long', alias) + if (!alias.match(VALID_ALIAS_REGEX)) throw new LogError('Invalid characters in alias', alias) + if (RESERVED_ALIAS.includes(alias.toLowerCase())) + throw new LogError('Alias is not allowed', alias) + const aliasInUse = await DbUser.find({ + where: { alias: Raw((a) => `LOWER(${a}) = "${alias.toLowerCase()}"`) }, + }) + if (aliasInUse.length !== 0) { + throw new LogError('Alias already in use', alias) + } + return true +} diff --git a/federation/yarn.lock b/federation/yarn.lock index 758e7e4ce..87bd7f0d4 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -1057,6 +1057,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/ws@^7.0.0": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -5442,16 +5447,16 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@8.3.2, uuid@^8.0.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.0.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" From 9a20a85ec9d665005f40fca710c6c313c1b5f17b Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Sep 2023 21:20:42 +0200 Subject: [PATCH 17/51] changes base on renaming of SendCoinArgs --- .../resolver/util/processXComSendCoins.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 19b29d47a..3bae8acd3 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -51,18 +51,16 @@ export async function processXComPendingSendCoins( // eslint-disable-next-line camelcase if (client instanceof V1_0_SendCoinsClient) { const args = new SendCoinsArgs() - args.communityReceiverIdentifier = receiverCom.communityUuid + args.recipientCommunityUuid = receiverCom.communityUuid ? receiverCom.communityUuid : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID - args.userReceiverIdentifier = recipient.gradidoID + args.recipientUserIdentifier = recipient.gradidoID args.creationDate = creationDate.toISOString() args.amount = amount args.memo = memo - args.communitySenderIdentifier = senderCom.communityUuid - ? senderCom.communityUuid - : 'homeCom-UUID' - args.userSenderIdentifier = sender.gradidoID - args.userSenderName = fullName(sender.firstName, sender.lastName) + args.senderCommunityUuid = senderCom.communityUuid ? senderCom.communityUuid : 'homeCom-UUID' + args.senderUserUuid = sender.gradidoID + args.senderUserName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: ready for voteForSendCoins with args=`, args) const recipientName = await client.voteForSendCoins(args) logger.debug(`X-Com: returnd from voteForSendCoins:`, recipientName) @@ -159,19 +157,19 @@ export async function processXComCommittingSendCoins( // eslint-disable-next-line camelcase if (client instanceof V1_0_SendCoinsClient) { const args = new SendCoinsArgs() - args.communityReceiverIdentifier = pendingTx.linkedUserCommunityUuid + args.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid ? pendingTx.linkedUserCommunityUuid : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID if (pendingTx.linkedUserGradidoID) { - args.userReceiverIdentifier = pendingTx.linkedUserGradidoID + args.recipientUserIdentifier = pendingTx.linkedUserGradidoID } - args.creationDate = pendingTx.balanceDate + args.creationDate = pendingTx.balanceDate.toISOString() args.amount = pendingTx.amount args.memo = pendingTx.memo - args.communitySenderIdentifier = pendingTx.userCommunityUuid - args.userSenderIdentifier = pendingTx.userGradidoID + args.senderCommunityUuid = pendingTx.userCommunityUuid + args.senderUserUuid = pendingTx.userGradidoID if (pendingTx.userName) { - args.userSenderName = pendingTx.userName + args.senderUserName = pendingTx.userName } logger.debug(`X-Com: ready for settleSendCoins with args=`, args) const acknoleged = await client.settleSendCoins(args) From b88ff9b3dfe10828a5a7ddc6547b44104dcf5c7a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 18 Sep 2023 21:49:08 +0200 Subject: [PATCH 18/51] correct seeding with home community --- .../graphql/resolver/ContributionResolver.ts | 5 +++ backend/src/seeds/community/index.ts | 34 +++++++++++++++++++ backend/src/seeds/index.ts | 4 +++ backend/src/util/virtualTransactions.ts | 4 +++ 4 files changed, 47 insertions(+) create mode 100644 backend/src/seeds/community/index.ts diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 1ffa53b27..af313a9a7 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -1,4 +1,5 @@ import { IsNull, getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionMessage } from '@entity/ContributionMessage' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -447,6 +448,7 @@ export class ContributionResolver { if (user.deletedAt) { throw new LogError('Can not confirm contribution since the user was deleted') } + const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) validateContribution( creations, @@ -480,6 +482,9 @@ export class ContributionResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + if (homeCom.communityUuid) { + transaction.userCommunityUuid = homeCom.communityUuid + } transaction.userGradidoID = user.gradidoID transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null diff --git a/backend/src/seeds/community/index.ts b/backend/src/seeds/community/index.ts new file mode 100644 index 000000000..d66c02c21 --- /dev/null +++ b/backend/src/seeds/community/index.ts @@ -0,0 +1,34 @@ +import { Community as DbCommunity } from '@entity/Community' +import { v4 as uuidv4 } from 'uuid' + +import { CONFIG } from '@/config' + +export async function writeHomeCommunityEntry(): Promise { + try { + // check for existing homeCommunity entry + let homeCom = await DbCommunity.findOne({ where: { foreign: false } }) + if (homeCom) { + // simply update the existing entry, but it MUST keep the ID and UUID because of possible relations + homeCom.publicKey = Buffer.from('public-key-data-seeding') // keyPair.publicKey + // homeCom.privateKey = keyPair.secretKey + homeCom.url = 'http://localhost/api/' + homeCom.name = CONFIG.COMMUNITY_NAME + homeCom.description = CONFIG.COMMUNITY_DESCRIPTION + await DbCommunity.save(homeCom) + } else { + // insert a new homecommunity entry including a new ID and a new but ensured unique UUID + homeCom = new DbCommunity() + homeCom.foreign = false + homeCom.publicKey = Buffer.from('public-key-data-seeding') // keyPair.publicKey + // homeCom.privateKey = keyPair.secretKey + homeCom.communityUuid = uuidv4() // await newCommunityUuid() + homeCom.url = 'http://localhost/api/' + homeCom.name = CONFIG.COMMUNITY_NAME + homeCom.description = CONFIG.COMMUNITY_DESCRIPTION + homeCom.creationDate = new Date() + await DbCommunity.insert(homeCom) + } + } catch (err) { + throw new Error(`Seeding: Error writing HomeCommunity-Entry: ${err}`) + } +} diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index bc7950f26..b22409759 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -12,6 +12,7 @@ import { CONFIG } from '@/config' import { createServer } from '@/server/createServer' import { backendLogger as logger } from '@/server/logger' +import { writeHomeCommunityEntry } from './community' import { contributionLinks } from './contributionLink/index' import { creations } from './creation/index' import { contributionLinkFactory } from './factory/contributionLink' @@ -57,6 +58,9 @@ const run = async () => { await cleanDB() logger.info('##seed## clean database successful...') + // seed home community + await writeHomeCommunityEntry() + // seed the standard users for (const user of users) { await userFactory(seedClient, user) diff --git a/backend/src/util/virtualTransactions.ts b/backend/src/util/virtualTransactions.ts index a10e566d1..74a065d55 100644 --- a/backend/src/util/virtualTransactions.ts +++ b/backend/src/util/virtualTransactions.ts @@ -58,6 +58,8 @@ const virtualLinkTransaction = ( userName: null, linkedUserGradidoID: null, linkedUserName: null, + userCommunityUuid: '', + linkedUserCommunityUuid: null, } return new Transaction(linkDbTransaction, user) } @@ -92,6 +94,8 @@ const virtualDecayTransaction = ( userName: null, linkedUserGradidoID: null, linkedUserName: null, + userCommunityUuid: '', + linkedUserCommunityUuid: null, } return new Transaction(decayDbTransaction, user) } From 8b2bbfdb2bcc10c3a8f7e5534e206bed697c6866 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 18 Sep 2023 22:28:54 +0200 Subject: [PATCH 19/51] local transactions with homeCom_uuid --- .../resolver/TransactionLinkResolver.ts | 6 +++++ .../graphql/resolver/TransactionResolver.ts | 23 +++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 32ec5a654..667e168b5 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,6 +1,7 @@ import { randomBytes } from 'crypto' import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -165,6 +166,7 @@ export class TransactionLinkResolver { @Ctx() context: Context, ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) + const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const user = getUser(context) if (code.match(/^CL-/)) { @@ -271,6 +273,9 @@ export class TransactionLinkResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + if (homeCom.communityUuid) { + transaction.userCommunityUuid = homeCom.communityUuid + } transaction.userGradidoID = user.gradidoID transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null @@ -343,6 +348,7 @@ export class TransactionLinkResolver { transactionLink.memo, linkedUser, user, + homeCom, transactionLink, ) await EVENT_TRANSACTION_LINK_REDEEM( diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 2dff4af79..7560ba306 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { getConnection, In, IsNull } from '@dbTools/typeorm' +import { Community as dbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' @@ -46,12 +47,13 @@ export const executeTransaction = async ( memo: string, sender: dbUser, recipient: dbUser, + homeCom: dbCommunity, transactionLink?: dbTransactionLink | null, ): Promise => { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() try { - logger.info('executeTransaction', amount, memo, sender, recipient) + logger.info('executeTransaction', amount, memo, homeCom, sender, recipient) const openSenderPendingTx = await DbPendingTransaction.count({ where: [ @@ -96,9 +98,15 @@ export const executeTransaction = async ( transactionSend.typeId = TransactionTypeId.SEND transactionSend.memo = memo transactionSend.userId = sender.id + if (homeCom.communityUuid) { + transactionSend.userCommunityUuid = homeCom.communityUuid + } transactionSend.userGradidoID = sender.gradidoID transactionSend.userName = fullName(sender.firstName, sender.lastName) transactionSend.linkedUserId = recipient.id + if (homeCom.communityUuid) { + transactionSend.linkedUserCommunityUuid = homeCom.communityUuid + } transactionSend.linkedUserGradidoID = recipient.gradidoID transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) transactionSend.amount = amount.mul(-1) @@ -116,9 +124,15 @@ export const executeTransaction = async ( transactionReceive.typeId = TransactionTypeId.RECEIVE transactionReceive.memo = memo transactionReceive.userId = recipient.id + if (homeCom.communityUuid) { + transactionReceive.userCommunityUuid = homeCom.communityUuid + } transactionReceive.userGradidoID = recipient.gradidoID transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) transactionReceive.linkedUserId = sender.id + if (homeCom.communityUuid) { + transactionReceive.linkedUserCommunityUuid = homeCom.communityUuid + } transactionReceive.linkedUserGradidoID = sender.gradidoID transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) transactionReceive.amount = amount @@ -348,12 +362,13 @@ export class TransactionResolver { @Mutation(() => Boolean) async sendCoins( @Args() - { /* recipientCommunityIdentifier, */ recipientIdentifier, amount, memo }: TransactionSendArgs, + { recipientCommunityIdentifier, recipientIdentifier, amount, memo }: TransactionSendArgs, @Ctx() context: Context, ): Promise { logger.info( - `sendCoins(recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, + `sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, ) + const homeCom = await dbCommunity.findOneOrFail({ where: { foreign: false } }) const senderUser = getUser(context) @@ -363,7 +378,7 @@ export class TransactionResolver { throw new LogError('The recipient user was not found', recipientUser) } - await executeTransaction(amount, memo, senderUser, recipientUser) + await executeTransaction(amount, memo, senderUser, recipientUser, homeCom) logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser) return true } From 90c51596a1d00564ccfa3d5fe737accd67440dec Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 18 Sep 2023 23:16:24 +0200 Subject: [PATCH 20/51] linting --- backend/src/seeds/community/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/seeds/community/index.ts b/backend/src/seeds/community/index.ts index d66c02c21..6a639ee44 100644 --- a/backend/src/seeds/community/index.ts +++ b/backend/src/seeds/community/index.ts @@ -29,6 +29,6 @@ export async function writeHomeCommunityEntry(): Promise { await DbCommunity.insert(homeCom) } } catch (err) { - throw new Error(`Seeding: Error writing HomeCommunity-Entry: ${err}`) + throw new Error(`Seeding: Error writing HomeCommunity-Entry`) // : ${err}`) } } From d53073783c68d3ec7f874b5b3cc7e47e0971104c Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Tue, 19 Sep 2023 21:06:22 +0200 Subject: [PATCH 21/51] should prevent crash in empty tables --- .../migrations/0072-add_communityuuid_to_transactions_table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/0072-add_communityuuid_to_transactions_table.ts b/database/migrations/0072-add_communityuuid_to_transactions_table.ts index 779da77c9..04685141f 100644 --- a/database/migrations/0072-add_communityuuid_to_transactions_table.ts +++ b/database/migrations/0072-add_communityuuid_to_transactions_table.ts @@ -13,7 +13,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis // read the community uuid of the homeCommunity const result = await queryFn(`SELECT c.community_uuid from communities as c WHERE c.foreign = 0`) // and if uuid exists enter the home_community_uuid for sender and recipient of each still existing transaction - if (result[0]) { + if (result && result[0]) { await queryFn( `UPDATE transactions as t SET t.user_community_uuid = "${result[0].community_uuid}" WHERE t.user_id IS NOT NULL AND t.user_community_uuid IS NULL`, ) From 4aeb1b60a225413484691afd222c04e926c3507e Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 19 Sep 2023 21:19:22 +0200 Subject: [PATCH 22/51] lokaler zwischenstand --- backend/src/config/index.ts | 3 +- .../federation/client/1_0/SendCoinsClient.ts | 26 ++++++----- .../client/1_0/model/SendCoinsResult.ts | 8 ++-- .../client/1_0/query/voteForSendCoins.ts | 6 ++- .../resolver/TransactionResolver.test.ts | 36 +++++++++++++++- .../graphql/resolver/TransactionResolver.ts | 12 ++++++ .../resolver/util/processXComSendCoins.ts | 43 +++++++++++-------- .../graphql/api/1_0/model/SendCoinsResult.ts | 17 ++++++++ .../api/1_0/resolver/SendCoinsResolver.ts | 9 ++-- 9 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 federation/src/graphql/api/1_0/model/SendCoinsResult.ts diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 07e2ded36..98a0b8323 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -19,7 +19,7 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v19.2023-09-01', + EXPECTED: 'v20.2023-09-19', CURRENT: '', }, } @@ -122,6 +122,7 @@ if ( } const federation = { + FEDERATION_BACKEND_SEND_ON_API: process.env.FEDERATION_BACKEND_SEND_ON_API || '1_0', FEDERATION_VALIDATE_COMMUNITY_TIMER: Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000, FEDERATION_XCOM_SENDCOINS_ENABLED: diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 7f1fad186..f9c318e81 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 { SendCoinsResult } from './model/SendCoinsResult' import { revertSendCoins } from './query/revertSendCoins' import { revertSettledSendCoins } from './query/revertSettledSendCoins' import { settleSendCoins } from './query/settleSendCoins' @@ -30,27 +31,30 @@ export class SendCoinsClient { }) } - voteForSendCoins = async (args: SendCoinsArgs): Promise => { + voteForSendCoins = async (args: SendCoinsArgs): Promise => { 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 }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.voteForSendCoins?.voteForSendCoins) { + if (!data?.voteForSendCoins?.vote) { logger.warn( 'X-Com: voteForSendCoins failed with: ', // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins.voteForSendCoins, + data.voteForSendCoins.recipGradidoID, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + data.voteForSendCoins.recipName, ) - return + return new SendCoinsResult() } - logger.debug( - 'X-Com: voteForSendCoins successful with result=', - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins, - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access - return data.voteForSendCoins.voteForSendCoins + const result = new SendCoinsResult() + result.vote = true + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + result.recipGradidoID = data.voteForSendCoins.recipGradidoID + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + result.recipName = data.voteForSendCoins.recipName + logger.debug('X-Com: voteForSendCoins successful with result=', result) + return result } catch (err) { throw new LogError(`X-Com: voteForSendCoins failed for endpoint=${this.endpoint}:`, err) } diff --git a/backend/src/federation/client/1_0/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts index 1897410cc..3eb1419b5 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsResult.ts @@ -9,9 +9,9 @@ export class SendCoinsResult { @Field(() => Boolean) vote: boolean - @Field(() => String) - receiverFirstName: string + @Field(() => String, { nullable: true }) + recipGradidoID: string | null - @Field(() => String) - receiverLastName: string + @Field(() => String, { nullable: true }) + recipName: string | null } diff --git a/backend/src/federation/client/1_0/query/voteForSendCoins.ts b/backend/src/federation/client/1_0/query/voteForSendCoins.ts index 0f16ff32b..9658cecfd 100644 --- a/backend/src/federation/client/1_0/query/voteForSendCoins.ts +++ b/backend/src/federation/client/1_0/query/voteForSendCoins.ts @@ -20,6 +20,10 @@ export const voteForSendCoins = gql` senderCommunityUuid: $senderCommunityUuid senderUserUuid: $senderUserUuid senderUserName: $senderUserName - ) + ) { + vote + recipGradidoID + recipName + } } ` diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index a59ca5c0a..20765e952 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -58,7 +58,7 @@ let bob: User let peter: User let homeCom: DbCommunity -// let foreignCom: DbCommunity +let foreignCom: DbCommunity describe('send coins', () => { beforeAll(async () => { @@ -67,7 +67,7 @@ describe('send coins', () => { await userFactory(testEnv, stephenHawking) await userFactory(testEnv, garrickOllivander) homeCom = DbCommunity.create() - homeCom.communityUuid = 'homeCom-UUID' + homeCom.communityUuid = '7f474922-b6d8-4b64-8cd0-ebf0a1d8756e' homeCom.creationDate = new Date('2000-01-01') homeCom.description = 'homeCom description' homeCom.foreign = false @@ -77,6 +77,17 @@ describe('send coins', () => { homeCom.url = 'homeCom url' homeCom = await DbCommunity.save(homeCom) + foreignCom = DbCommunity.create() + foreignCom.communityUuid = '7f474922-b6d8-4b64-8cd0-cea0a1d8756e' + foreignCom.creationDate = new Date('2000-06-06') + foreignCom.description = 'homeCom description' + foreignCom.foreign = true + foreignCom.name = 'foreignCom name' + foreignCom.privateKey = Buffer.from('foreignCom privateKey') + foreignCom.publicKey = Buffer.from('foreignCom publicKey') + foreignCom.url = 'foreignCom url' + foreignCom = await DbCommunity.save(foreignCom) + bobData = { email: 'bob@baumeister.de', password: 'Aa12345_', @@ -583,6 +594,27 @@ describe('send coins', () => { }) }) + describe.only('X-Com send coins via gradido ID', () => { + it('sends the coins', async () => { + await expect( + mutate({ + mutation: sendCoins, + variables: { + recipientCommunityIdentifier: foreignCom.communityUuid, + recipientIdentifier: peter?.gradidoID, + amount: 10, + memo: 'x-com send via gradido ID', + }, + }), + ).resolves.toMatchObject({ + data: { + sendCoins: true, + }, + errors: undefined, + }) + }) + }) + describe('more transactions to test semaphore', () => { it('sends the coins four times in a row', async () => { await expect( diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index fed605a54..baadd5c55 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -43,6 +43,7 @@ import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' import { transactionLinkSummary } from './util/transactionLinkSummary' +import { processXComPendingSendCoins } from './util/processXComSendCoins' export const executeTransaction = async ( amount: Decimal, @@ -545,6 +546,17 @@ export class TransactionResolver { if (!(await isCommunityAuthenticated(recipientCommunityIdentifier))) { throw new LogError('recipient commuity is connected, but still not authenticated yet!') } + const recipCom = await dbCommunity.findOneOrFail({ + where: { communityUuid: recipientCommunityIdentifier }, + }) + await processXComPendingSendCoins( + recipCom, + homeCom, + amount, + memo, + senderUser, + recipientIdentifier, + ) } return true } diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 3bae8acd3..2512d69c6 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -19,27 +19,24 @@ import { fullName } from '@/util/utilities' import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' export async function processXComPendingSendCoins( - receiverFCom: DbFederatedCommunity, receiverCom: DbCommunity, senderCom: DbCommunity, - creationDate: Date, amount: Decimal, memo: string, sender: dbUser, - recipient: dbUser, + recipientIdentifier: string, ): Promise { try { logger.debug( `XCom: processXComPendingSendCoins...`, - receiverFCom, receiverCom, senderCom, - creationDate, amount, memo, sender, - recipient, + recipientIdentifier, ) + const creationDate = new Date() // first calculate the sender balance and check if the transaction is allowed const senderBalance = await calculateSenderBalance(sender.id, amount.mul(-1), creationDate) if (!senderBalance) { @@ -47,24 +44,32 @@ export async function processXComPendingSendCoins( } logger.debug(`X-Com: calculated senderBalance = `, senderBalance) + const receiverFCom = await DbFederatedCommunity.findOneOrFail({ + where: { + publicKey: receiverCom.publicKey, + apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API, + }, + }) const client = SendCoinsClientFactory.getInstance(receiverFCom) // eslint-disable-next-line camelcase if (client instanceof V1_0_SendCoinsClient) { const args = new SendCoinsArgs() - args.recipientCommunityUuid = receiverCom.communityUuid - ? receiverCom.communityUuid - : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID - args.recipientUserIdentifier = recipient.gradidoID + if (receiverCom.communityUuid) { + args.recipientCommunityUuid = receiverCom.communityUuid + } + args.recipientUserIdentifier = recipientIdentifier args.creationDate = creationDate.toISOString() args.amount = amount args.memo = memo - args.senderCommunityUuid = senderCom.communityUuid ? senderCom.communityUuid : 'homeCom-UUID' + if (senderCom.communityUuid) { + args.senderCommunityUuid = senderCom.communityUuid + } args.senderUserUuid = sender.gradidoID args.senderUserName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: ready for voteForSendCoins with args=`, args) - const recipientName = await client.voteForSendCoins(args) - logger.debug(`X-Com: returnd from voteForSendCoins:`, recipientName) - if (recipientName) { + const sendCoinsResult = await client.voteForSendCoins(args) + logger.debug(`X-Com: returnd from voteForSendCoins:`, sendCoinsResult) + if (sendCoinsResult.vote) { // writing the pending transaction on receiver-side was successfull, so now write the sender side try { const pendingTx = DbPendingTransaction.create() @@ -73,11 +78,11 @@ export async function processXComPendingSendCoins( pendingTx.balanceDate = creationDate pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0) pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null - pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid - ? receiverCom.communityUuid - : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID - pendingTx.linkedUserGradidoID = recipient.gradidoID - pendingTx.linkedUserName = recipientName + if (receiverCom.communityUuid) { + pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid + } + pendingTx.linkedUserGradidoID = sendCoinsResult.recipGradidoID + pendingTx.linkedUserName = sendCoinsResult.recipName pendingTx.memo = memo pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW diff --git a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts new file mode 100644 index 000000000..3eb1419b5 --- /dev/null +++ b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts @@ -0,0 +1,17 @@ +import { ArgsType, Field } from 'type-graphql' + +@ArgsType() +export class SendCoinsResult { + constructor() { + this.vote = false + } + + @Field(() => Boolean) + vote: boolean + + @Field(() => String, { nullable: true }) + recipGradidoID: string | null + + @Field(() => String, { nullable: true }) + recipName: string | null +} diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 4196e015c..0f6f38633 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -14,6 +14,7 @@ import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTra // import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier' +import { SendCoinsResult } from '../model/SendCoinsResult' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -31,7 +32,7 @@ export class SendCoinsResolver { senderUserUuid, senderUserName, }: SendCoinsArgs, - ): Promise { + ): Promise { logger.debug( `voteForSendCoins() via apiVersion=1_0 ...`, recipientCommunityUuid, @@ -43,7 +44,7 @@ export class SendCoinsResolver { senderUserUuid, senderUserName, ) - let result: string + const result = new SendCoinsResult() // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ communityUuid: recipientCommunityUuid, @@ -88,7 +89,9 @@ export class SendCoinsResolver { pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) await DbPendingTransaction.insert(pendingTx) - result = pendingTx.userName + result.vote = true + result.recipName = pendingTx.userName + result.recipGradidoID = pendingTx.userGradidoID logger.debug(`voteForSendCoins()-1_0... successfull`) } catch (err) { throw new LogError(`Error in voteForSendCoins: `, err) From dcc221fb0515f0fa8970a93a2544ee6fbc4c858b Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 19 Sep 2023 23:09:46 +0200 Subject: [PATCH 23/51] no homeComUuid for local users in transactions --- .../graphql/resolver/ContributionResolver.ts | 5 ----- .../resolver/TransactionLinkResolver.ts | 6 +++--- .../graphql/resolver/TransactionResolver.ts | 20 ++----------------- backend/src/util/virtualTransactions.ts | 4 ++-- .../Transaction.ts | 4 ++-- ...add_communityuuid_to_transactions_table.ts | 6 ++++++ 6 files changed, 15 insertions(+), 30 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index af313a9a7..1ffa53b27 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -1,5 +1,4 @@ import { IsNull, getConnection } from '@dbTools/typeorm' -import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionMessage } from '@entity/ContributionMessage' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -448,7 +447,6 @@ export class ContributionResolver { if (user.deletedAt) { throw new LogError('Can not confirm contribution since the user was deleted') } - const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) validateContribution( creations, @@ -482,9 +480,6 @@ export class ContributionResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId - if (homeCom.communityUuid) { - transaction.userCommunityUuid = homeCom.communityUuid - } transaction.userGradidoID = user.gradidoID transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 667e168b5..63134a9a8 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,7 +1,6 @@ import { randomBytes } from 'crypto' import { getConnection } from '@dbTools/typeorm' -import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -166,7 +165,7 @@ export class TransactionLinkResolver { @Ctx() context: Context, ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) - const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) + // const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const user = getUser(context) if (code.match(/^CL-/)) { @@ -273,9 +272,11 @@ export class TransactionLinkResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + /* local transaction will not carry homeComUuid for local users if (homeCom.communityUuid) { transaction.userCommunityUuid = homeCom.communityUuid } + */ transaction.userGradidoID = user.gradidoID transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null @@ -348,7 +349,6 @@ export class TransactionLinkResolver { transactionLink.memo, linkedUser, user, - homeCom, transactionLink, ) await EVENT_TRANSACTION_LINK_REDEEM( diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 7560ba306..f368abca3 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,7 +3,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { getConnection, In, IsNull } from '@dbTools/typeorm' -import { Community as dbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' @@ -47,13 +46,12 @@ export const executeTransaction = async ( memo: string, sender: dbUser, recipient: dbUser, - homeCom: dbCommunity, transactionLink?: dbTransactionLink | null, ): Promise => { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() try { - logger.info('executeTransaction', amount, memo, homeCom, sender, recipient) + logger.info('executeTransaction', amount, memo, sender, recipient) const openSenderPendingTx = await DbPendingTransaction.count({ where: [ @@ -98,15 +96,9 @@ export const executeTransaction = async ( transactionSend.typeId = TransactionTypeId.SEND transactionSend.memo = memo transactionSend.userId = sender.id - if (homeCom.communityUuid) { - transactionSend.userCommunityUuid = homeCom.communityUuid - } transactionSend.userGradidoID = sender.gradidoID transactionSend.userName = fullName(sender.firstName, sender.lastName) transactionSend.linkedUserId = recipient.id - if (homeCom.communityUuid) { - transactionSend.linkedUserCommunityUuid = homeCom.communityUuid - } transactionSend.linkedUserGradidoID = recipient.gradidoID transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) transactionSend.amount = amount.mul(-1) @@ -124,15 +116,9 @@ export const executeTransaction = async ( transactionReceive.typeId = TransactionTypeId.RECEIVE transactionReceive.memo = memo transactionReceive.userId = recipient.id - if (homeCom.communityUuid) { - transactionReceive.userCommunityUuid = homeCom.communityUuid - } transactionReceive.userGradidoID = recipient.gradidoID transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) transactionReceive.linkedUserId = sender.id - if (homeCom.communityUuid) { - transactionReceive.linkedUserCommunityUuid = homeCom.communityUuid - } transactionReceive.linkedUserGradidoID = sender.gradidoID transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) transactionReceive.amount = amount @@ -368,8 +354,6 @@ export class TransactionResolver { logger.info( `sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, ) - const homeCom = await dbCommunity.findOneOrFail({ where: { foreign: false } }) - const senderUser = getUser(context) // validate recipient user @@ -378,7 +362,7 @@ export class TransactionResolver { throw new LogError('The recipient user was not found', recipientUser) } - await executeTransaction(amount, memo, senderUser, recipientUser, homeCom) + await executeTransaction(amount, memo, senderUser, recipientUser) logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser) return true } diff --git a/backend/src/util/virtualTransactions.ts b/backend/src/util/virtualTransactions.ts index 74a065d55..063f926e0 100644 --- a/backend/src/util/virtualTransactions.ts +++ b/backend/src/util/virtualTransactions.ts @@ -58,7 +58,7 @@ const virtualLinkTransaction = ( userName: null, linkedUserGradidoID: null, linkedUserName: null, - userCommunityUuid: '', + userCommunityUuid: null, linkedUserCommunityUuid: null, } return new Transaction(linkDbTransaction, user) @@ -94,7 +94,7 @@ const virtualDecayTransaction = ( userName: null, linkedUserGradidoID: null, linkedUserName: null, - userCommunityUuid: '', + userCommunityUuid: null, linkedUserCommunityUuid: null, } return new Transaction(decayDbTransaction, user) diff --git a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts index 3efa78ada..8f13de58a 100644 --- a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts +++ b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts @@ -81,10 +81,10 @@ export class Transaction extends BaseEntity { name: 'user_community_uuid', type: 'varchar', length: 36, - nullable: false, + nullable: true, collation: 'utf8mb4_unicode_ci', }) - userCommunityUuid: string + userCommunityUuid: string | null @Column({ name: 'user_gradido_id', diff --git a/database/migrations/0072-add_communityuuid_to_transactions_table.ts b/database/migrations/0072-add_communityuuid_to_transactions_table.ts index 04685141f..22e7c36eb 100644 --- a/database/migrations/0072-add_communityuuid_to_transactions_table.ts +++ b/database/migrations/0072-add_communityuuid_to_transactions_table.ts @@ -10,6 +10,10 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis await queryFn( 'ALTER TABLE `transactions` ADD COLUMN `linked_user_community_uuid` char(36) DEFAULT NULL NULL AFTER `linked_user_id`;', ) + /* the migration of the HomeCom-UUID for local users in the transactions table will be skipped + and be solved with the future users table migration for treating home- and foreign-users including + homeCom- and foreignCom-UUIDs + // read the community uuid of the homeCommunity const result = await queryFn(`SELECT c.community_uuid from communities as c WHERE c.foreign = 0`) // and if uuid exists enter the home_community_uuid for sender and recipient of each still existing transaction @@ -21,9 +25,11 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis `UPDATE transactions as t SET t.linked_user_community_uuid = "${result[0].community_uuid}" WHERE t.linked_user_id IS NOT NULL AND t.linked_user_community_uuid IS NULL`, ) } + // leads to an error in case of empty communties table during CD/CI-pipeline-tests await queryFn( 'ALTER TABLE `transactions` MODIFY COLUMN `user_community_uuid` char(36) NOT NULL AFTER `user_id`;', ) + */ } export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { From c3bc7182b74fb61b6fd5eb91f9f9bf2eb64ee8ef Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 19 Sep 2023 23:12:53 +0200 Subject: [PATCH 24/51] temporary reduce coverage --- backend/jest.config.js | 2 +- federation/jest.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 1529fad55..5d562bdc5 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -7,7 +7,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 86, + lines: 83, }, }, setupFiles: ['/test/testSetup.ts'], diff --git a/federation/jest.config.js b/federation/jest.config.js index 25ff58fb3..cffb79835 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -6,7 +6,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 76, + lines: 74, }, }, setupFiles: ['/test/testSetup.ts'], From ee8498daf3e43042e765e8b1612d5b50e4b45013 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:24:00 +0200 Subject: [PATCH 25/51] Update backend/src/graphql/resolver/util/processXComSendCoins.ts Co-authored-by: einhornimmond --- backend/src/graphql/resolver/util/processXComSendCoins.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 3bae8acd3..c75212392 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -174,7 +174,7 @@ export async function processXComCommittingSendCoins( logger.debug(`X-Com: ready for settleSendCoins with args=`, args) const acknoleged = await client.settleSendCoins(args) logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) - if (acknoleged) { + if (acknowledge) { // settle the pending transaction on receiver-side was successfull, so now settle the sender side try { await settlePendingSenderTransaction(senderCom, sender, pendingTx) From 9c37e8f3c3932d2ca620d4c6b316706e902c4ad1 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:35:33 +0200 Subject: [PATCH 26/51] Update federation/src/graphql/util/checkTradingLevel.ts Co-authored-by: einhornimmond --- federation/src/graphql/util/checkTradingLevel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federation/src/graphql/util/checkTradingLevel.ts b/federation/src/graphql/util/checkTradingLevel.ts index c67d70ad7..9f5463a0d 100644 --- a/federation/src/graphql/util/checkTradingLevel.ts +++ b/federation/src/graphql/util/checkTradingLevel.ts @@ -7,7 +7,7 @@ export async function checkTradingLevel(homeCom: DbCommunity, amount: Decimal): const tradingLevel = CONFIG.FEDERATION_TRADING_LEVEL if (homeCom.url !== tradingLevel.RECEIVER_COMMUNITY_URL) { logger.warn( - `X-Com: tradingLevel allows to receive coins only wiht url ${tradingLevel.RECEIVER_COMMUNITY_URL}`, + `X-Com: tradingLevel allows to receive coins only with url ${tradingLevel.RECEIVER_COMMUNITY_URL}`, ) return false } From 32496a035395ac0dfff83de244a9cd19f6d94479 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 20 Sep 2023 14:24:23 +0200 Subject: [PATCH 27/51] introduce tx-switch for local and x-com sendcoins --- .../graphql/resolver/TransactionResolver.ts | 33 ++++++++++++++----- .../resolver/util/processXComSendCoins.ts | 23 +++++++++---- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 00834830b..e7c2f751c 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -27,6 +27,7 @@ import { sendTransactionReceivedEmail, } from '@/emails/sendEmailVariants' import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events' +import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult' import { Context, getUser } from '@/server/context' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' @@ -41,10 +42,12 @@ import { isCommunityAuthenticated, isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' -import { processXComCommittingSendCoins, processXComPendingSendCoins } from './util/processXComSendCoins' +import { + processXComCommittingSendCoins, + processXComPendingSendCoins, +} from './util/processXComSendCoins' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' import { transactionLinkSummary } from './util/transactionLinkSummary' -import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult' export const executeTransaction = async ( amount: Decimal, @@ -537,7 +540,7 @@ export class TransactionResolver { where: { communityUuid: recipientCommunityIdentifier }, }) let pendingResult: SendCoinsResult - let commitingResult: SendCoinsResult + let committingResult: SendCoinsResult const creationDate = new Date() try { @@ -550,8 +553,8 @@ export class TransactionResolver { senderUser, recipientIdentifier, ) - if(pendingResult.vote && pendingResult.recipGradidoID) { - commitingResult = await processXComCommittingSendCoins( + if (pendingResult.vote && pendingResult.recipGradidoID) { + committingResult = await processXComCommittingSendCoins( recipCom, homeCom, creationDate, @@ -560,12 +563,26 @@ export class TransactionResolver { senderUser, pendingResult.recipGradidoID, ) - if(!commitingResult.vote) { - + if (!committingResult.vote) { + logger.fatal('FATAL ERROR: on processXComCommittingSendCoins for', committingResult) + throw new LogError( + 'FATAL ERROR: on processXComCommittingSendCoins with ', + recipientCommunityIdentifier, + recipientIdentifier, + amount.toString(), + memo, + ) } } } catch (err) { - + throw new LogError( + 'ERROR: on processXComCommittingSendCoins with ', + recipientCommunityIdentifier, + recipientIdentifier, + amount.toString(), + memo, + err, + ) } } return true diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 5809a862a..8b94e03c5 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -135,7 +135,7 @@ export async function processXComCommittingSendCoins( sender: dbUser, recipUuid: string, ): Promise { - let sendCoinsResult = new SendCoinsResult() + const sendCoinsResult = new SendCoinsResult() try { logger.debug( `XCom: processXComCommittingSendCoins...`, @@ -145,7 +145,7 @@ export async function processXComCommittingSendCoins( amount, memo, sender, - recipient, + recipUuid, ) // first find pending Tx with given parameters const pendingTx = await DbPendingTransaction.findOneBy({ @@ -188,12 +188,20 @@ export async function processXComCommittingSendCoins( args.senderUserName = pendingTx.userName } logger.debug(`X-Com: ready for settleSendCoins with args=`, args) - const acknoleged = await client.settleSendCoins(args) - logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) - if (acknoleged) { + const acknowledge = await client.settleSendCoins(args) + logger.debug(`X-Com: returnd from settleSendCoins:`, acknowledge) + if (acknowledge) { // settle the pending transaction on receiver-side was successfull, so now settle the sender side try { - await settlePendingSenderTransaction(senderCom, sender, pendingTx) + sendCoinsResult.vote = await settlePendingSenderTransaction( + senderCom, + sender, + pendingTx, + ) + if (sendCoinsResult.vote) { + sendCoinsResult.recipName = pendingTx.linkedUserName + sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID + } } catch (err) { logger.error(`Error in writing sender pending transaction: `, err) // revert the existing pending transaction on receiver side @@ -219,6 +227,7 @@ export async function processXComCommittingSendCoins( } } catch (err) { logger.error(`Error:`, err) + sendCoinsResult.vote = false } - return true + return sendCoinsResult } From fe85e45d385dcbc33ac6d86ec07554bb06403fc7 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 20 Sep 2023 16:47:59 +0200 Subject: [PATCH 28/51] correct graphql result definition for voteForSendCoins --- .../src/federation/client/1_0/SendCoinsClient.ts | 11 +++++++---- .../graphql/resolver/util/processXComSendCoins.ts | 2 +- .../src/graphql/api/1_0/model/SendCoinsResult.ts | 8 ++++---- .../api/1_0/resolver/SendCoinsResolver.test.ts | 15 ++++++++++++--- .../graphql/api/1_0/resolver/SendCoinsResolver.ts | 2 +- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index f9c318e81..c03755c16 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -34,18 +34,21 @@ export class SendCoinsClient { voteForSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint) try { + logger.debug(`SendCoinsClient: voteForSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(voteForSendCoins, { args }) + logger.debug(`SendCoinsClient: after rawRequest...`, data) + const sendCoinsResult = data as SendCoinsResult // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.voteForSendCoins?.vote) { + if (!sendCoinsResult.vote) { logger.warn( 'X-Com: voteForSendCoins failed with: ', // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins.recipGradidoID, + sendCoinsResult.recipGradidoID, // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins.recipName, + sendCoinsResult.recipName, ) - return new SendCoinsResult() + return sendCoinsResult } const result = new SendCoinsResult() result.vote = true diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 8b94e03c5..5f1349b70 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -121,7 +121,7 @@ export async function processXComPendingSendCoins( } } } catch (err) { - logger.error(`Error:`, err) + throw new LogError(`Error:`, err) } return sendCoinsResult } diff --git a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts index 3eb1419b5..749ca87cb 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts @@ -1,6 +1,6 @@ -import { ArgsType, Field } from 'type-graphql' +import { Field, ObjectType } from 'type-graphql' -@ArgsType() +@ObjectType() export class SendCoinsResult { constructor() { this.vote = false @@ -9,9 +9,9 @@ export class SendCoinsResult { @Field(() => Boolean) vote: boolean - @Field(() => String, { nullable: true }) + @Field(() => String) recipGradidoID: string | null - @Field(() => String, { nullable: true }) + @Field(() => String) recipName: string | null } diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 5120391d6..c84965ce2 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -68,8 +68,13 @@ describe('SendCoinsResolver', () => { senderCommunityUuid: $senderCommunityUuid senderUserUuid: $senderUserUuid senderUserName: $senderUserName - ) - }` + ) { + vote + recipGradidoID + recipName + } + } +` const settleSendCoinsMutation = ` mutation ( @@ -220,7 +225,11 @@ describe('SendCoinsResolver', () => { ).toEqual( expect.objectContaining({ data: { - voteForSendCoins: 'recipUser-FirstName recipUser-LastName', + voteForSendCoins: { + recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd', + recipName: 'recipUser-FirstName recipUser-LastName', + vote: true, + }, }, }), ) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 0f6f38633..ccae52edc 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -19,7 +19,7 @@ import { SendCoinsResult } from '../model/SendCoinsResult' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars export class SendCoinsResolver { - @Mutation(() => String) + @Mutation(() => SendCoinsResult) async voteForSendCoins( @Args() { From 2fe9b4e55e3598c819cdcf15da933485b60b8f98 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 20 Sep 2023 17:01:25 +0200 Subject: [PATCH 29/51] changed CONFIG.VERSION distributable in .env.template for deployment --- backend/.env.template | 3 ++- dht-node/.env.template | 3 ++- federation/.env.template | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/.env.template b/backend/.env.template index 06bf81088..e79122368 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -1,4 +1,5 @@ -CONFIG_VERSION=$BACKEND_CONFIG_VERSION +# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts +CONFIG_VERSION=v20.2023-09-19 # Server JWT_SECRET=$JWT_SECRET diff --git a/dht-node/.env.template b/dht-node/.env.template index a7603c15a..1278f61be 100644 --- a/dht-node/.env.template +++ b/dht-node/.env.template @@ -1,4 +1,5 @@ -CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION +# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts +CONFIG_VERSION=v3.2023-04-26 # Database DB_HOST=localhost diff --git a/federation/.env.template b/federation/.env.template index 295c3c480..e6ac8ad7d 100644 --- a/federation/.env.template +++ b/federation/.env.template @@ -1,4 +1,5 @@ -CONFIG_VERSION=$FEDERATION_CONFIG_VERSION +# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts +CONFIG_VERSION=v2.2023-08-24 LOG_LEVEL=$LOG_LEVEL # this is set fix to false, because it is important for 'production' environments. only set to true if a graphql-playground should be in use From d5ea0b7724fa153864693c4599a78638c6db7b69 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 20 Sep 2023 17:48:24 +0200 Subject: [PATCH 30/51] next try for graphql api definition --- .../federation/client/1_0/SendCoinsClient.ts | 2 +- .../api/1_0/resolver/SendCoinsResolver.ts | 178 ++++++++++-------- 2 files changed, 103 insertions(+), 77 deletions(-) diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index c03755c16..359393cda 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -23,7 +23,7 @@ export class SendCoinsClient { dbCom.apiVersion }/` this.client = new GraphQLClient(this.endpoint, { - method: 'GET', + method: 'POST', jsonSerializer: { parse: JSON.parse, stringify: JSON.stringify, diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index ccae52edc..054bdfdd3 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Args, Mutation, Resolver } from 'type-graphql' +import { Arg, Args, Mutation, Resolver } from 'type-graphql' import { federationLogger as logger } from '@/server/logger' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' @@ -21,7 +21,9 @@ import { SendCoinsResult } from '../model/SendCoinsResult' export class SendCoinsResolver { @Mutation(() => SendCoinsResult) async voteForSendCoins( - @Args() + @Arg('data') + args: SendCoinsArgs, + /* { recipientCommunityUuid, recipientUserIdentifier, @@ -31,34 +33,35 @@ export class SendCoinsResolver { senderCommunityUuid, senderUserUuid, senderUserName, - }: SendCoinsArgs, + } + */ ): Promise { logger.debug( `voteForSendCoins() via apiVersion=1_0 ...`, - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount.toString(), - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.recipientCommunityUuid, + args.recipientUserIdentifier, + args.creationDate, + args.amount.toString(), + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) const result = new SendCoinsResult() // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `voteForSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -67,24 +70,24 @@ export class SendCoinsResolver { ) } try { - const txDate = new Date(creationDate) - const receiveBalance = await calculateRecipientBalance(receiverUser.id, amount, txDate) + const txDate = new Date(args.creationDate) + const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate) const pendingTx = DbPendingTransaction.create() - pendingTx.amount = amount - pendingTx.balance = receiveBalance ? receiveBalance.balance : amount + pendingTx.amount = args.amount + pendingTx.balance = receiveBalance ? receiveBalance.balance : args.amount pendingTx.balanceDate = txDate pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null pendingTx.creationDate = new Date() - pendingTx.linkedUserCommunityUuid = senderCommunityUuid - pendingTx.linkedUserGradidoID = senderUserUuid - pendingTx.linkedUserName = senderUserName - pendingTx.memo = memo + pendingTx.linkedUserCommunityUuid = args.senderCommunityUuid + pendingTx.linkedUserGradidoID = args.senderUserUuid + pendingTx.linkedUserName = args.senderUserName + pendingTx.memo = args.memo pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.RECEIVE pendingTx.userId = receiverUser.id - pendingTx.userCommunityUuid = recipientCommunityUuid + pendingTx.userCommunityUuid = args.recipientCommunityUuid pendingTx.userGradidoID = receiverUser.gradidoID pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) @@ -101,7 +104,9 @@ export class SendCoinsResolver { @Mutation(() => Boolean) async revertSendCoins( - @Args() + @Arg('data') + args: SendCoinsArgs, + /* { recipientCommunityUuid, recipientUserIdentifier, @@ -112,22 +117,23 @@ export class SendCoinsResolver { senderUserUuid, senderUserName, }: SendCoinsArgs, + */ ): Promise { logger.debug(`revertSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `revertSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -137,16 +143,16 @@ export class SendCoinsResolver { } try { const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: recipientCommunityUuid, + userCommunityUuid: args.recipientCommunityUuid, userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: senderCommunityUuid, - linkedUserGradidoID: senderUserUuid, + balanceDate: new Date(args.creationDate), + linkedUserCommunityUuid: args.senderCommunityUuid, + linkedUserGradidoID: args.senderUserUuid, }) logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount.toString() === amount.toString()) { + if (pendingTx && pendingTx.amount.toString() === args.amount.toString()) { logger.debug('XCom: revertSendCoins matching pendingTX for remove...') try { await pendingTx.remove() @@ -158,20 +164,20 @@ export class SendCoinsResolver { logger.debug( 'XCom: revertSendCoins NOT matching pendingTX for remove:', pendingTx?.amount, - amount, + args.amount, ) throw new LogError( `Can't find in revertSendCoins the pending receiver TX for args=`, - recipientCommunityUuid, - recipientUserIdentifier, + args.recipientCommunityUuid, + args.recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.creationDate, + args.amount, + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) @@ -183,7 +189,9 @@ export class SendCoinsResolver { @Mutation(() => Boolean) async settleSendCoins( - @Args() + @Arg('data') + args: SendCoinsArgs, + /* { recipientCommunityUuid, recipientUserIdentifier, @@ -194,24 +202,31 @@ export class SendCoinsResolver { senderUserUuid, senderUserName, }: SendCoinsArgs, + */ ): Promise { logger.debug( - `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${recipientCommunityUuid}, userGradidoID=${recipientUserIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${senderCommunityUuid}, userSenderIdentifier=${senderUserUuid}, userSenderName=${senderUserName}`, + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${ + args.recipientCommunityUuid + }, userGradidoID=${args.recipientUserIdentifier}, balanceDate=${ + args.creationDate + },amount=${args.amount.valueOf()}, memo=${args.memo}, linkedUserCommunityUuid = ${ + args.senderCommunityUuid + }, userSenderIdentifier=${args.senderUserUuid}, userSenderName=${args.senderUserName}`, ) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `settleSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -220,16 +235,20 @@ export class SendCoinsResolver { ) } const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: recipientCommunityUuid, + userCommunityUuid: args.recipientCommunityUuid, userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: senderCommunityUuid, - linkedUserGradidoID: senderUserUuid, + balanceDate: new Date(args.creationDate), + linkedUserCommunityUuid: args.senderCommunityUuid, + linkedUserGradidoID: args.senderUserUuid, }) logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) - if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + if ( + pendingTx && + pendingTx.amount.toString() === args.amount.toString() && + pendingTx.memo === args.memo + ) { logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) @@ -239,23 +258,25 @@ export class SendCoinsResolver { logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') throw new LogError( `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - recipientCommunityUuid, - recipientUserIdentifier, + args.recipientCommunityUuid, + args.recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.creationDate, + args.amount, + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) } } @Mutation(() => Boolean) async revertSettledSendCoins( - @Args() + @Arg('data') + args: SendCoinsArgs, + /* { recipientCommunityUuid, recipientUserIdentifier, @@ -266,22 +287,23 @@ export class SendCoinsResolver { senderUserUuid, senderUserName, }: SendCoinsArgs, + */ ): Promise { logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `revertSettledSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -290,16 +312,20 @@ export class SendCoinsResolver { ) } const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: recipientCommunityUuid, + userCommunityUuid: args.recipientCommunityUuid, userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.SETTLED, typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: senderCommunityUuid, - linkedUserGradidoID: senderUserUuid, + balanceDate: new Date(args.creationDate), + linkedUserCommunityUuid: args.senderCommunityUuid, + linkedUserGradidoID: args.senderUserUuid, }) logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + if ( + pendingTx && + pendingTx.amount.toString() === args.amount.toString() && + pendingTx.memo === args.memo + ) { logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') try { await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) @@ -311,16 +337,16 @@ export class SendCoinsResolver { logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') throw new LogError( `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - recipientCommunityUuid, - recipientUserIdentifier, + args.recipientCommunityUuid, + args.recipientUserIdentifier, PendingTransactionState.SETTLED, TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.creationDate, + args.amount, + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) From 34be808472dc16e0439b0ad0e7c11760845b33df Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 21 Sep 2023 00:10:34 +0200 Subject: [PATCH 31/51] change SendCoinsArgs to @InputType() --- federation/src/graphql/api/1_0/model/SendCoinsArgs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts index fb97da925..e658209ca 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts @@ -1,7 +1,7 @@ import { Decimal } from 'decimal.js-light' -import { ArgsType, Field } from 'type-graphql' +import { Field, InputType } from 'type-graphql' -@ArgsType() +@InputType() export class SendCoinsArgs { @Field(() => String) recipientCommunityUuid: string From 7561025c99589d9c7fafd40ff32a3fa2e0928e42 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 21 Sep 2023 16:11:00 +0200 Subject: [PATCH 32/51] next try of graphql mutation --- .../client/1_0/model/SendCoinsResult.ts | 4 +-- .../client/1_0/query/voteForSendCoins.ts | 30 +++++++++++++------ .../api/1_0/resolver/SendCoinsResolver.ts | 12 -------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/backend/src/federation/client/1_0/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts index d3ce76392..1258db4cb 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsResult.ts @@ -1,6 +1,6 @@ -import { ArgsType, Field } from 'type-graphql' +import { Field, ObjectType } from 'type-graphql' -@ArgsType() +@ObjectType() export class SendCoinsResult { constructor() { this.vote = false diff --git a/backend/src/federation/client/1_0/query/voteForSendCoins.ts b/backend/src/federation/client/1_0/query/voteForSendCoins.ts index 9658cecfd..a5456bec7 100644 --- a/backend/src/federation/client/1_0/query/voteForSendCoins.ts +++ b/backend/src/federation/client/1_0/query/voteForSendCoins.ts @@ -1,6 +1,16 @@ import { gql } from 'graphql-request' export const voteForSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + voteForSendCoins(data: $args) { + vote + recipGradidoID + recipName + } + } +` + +/* mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -12,18 +22,20 @@ export const voteForSendCoins = gql` $senderUserName: String! ) { voteForSendCoins( - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName + data: { + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + } ) { vote recipGradidoID recipName } } -` +*/ diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 054bdfdd3..4a31b0bbb 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -23,18 +23,6 @@ export class SendCoinsResolver { async voteForSendCoins( @Arg('data') args: SendCoinsArgs, - /* - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - } - */ ): Promise { logger.debug( `voteForSendCoins() via apiVersion=1_0 ...`, From 31d47d753743c52f4d362204f9d65dd99fb29e1c Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 21 Sep 2023 18:45:53 +0200 Subject: [PATCH 33/51] next try for x-com sendCoins --- backend/log4js-config.json | 1 + .../federation/client/1_0/SendCoinsClient.ts | 57 ++++++++++++------- .../client/1_0/model/SendCoinsResult.ts | 4 +- .../client/1_0/query/revertSendCoins.ts | 7 ++- .../1_0/query/revertSettledSendCoins.ts | 8 ++- .../client/1_0/query/settleSendCoins.ts | 7 ++- .../graphql/resolver/TransactionResolver.ts | 6 +- .../src/graphql/resolver/util/communities.ts | 34 ++++++----- .../resolver/util/processXComSendCoins.ts | 25 +++++--- .../graphql/api/1_0/model/SendCoinsResult.ts | 4 +- .../api/1_0/resolver/SendCoinsResolver.ts | 54 ++++++------------ 11 files changed, 119 insertions(+), 88 deletions(-) diff --git a/backend/log4js-config.json b/backend/log4js-config.json index 1cbd4519c..90111a5ba 100644 --- a/backend/log4js-config.json +++ b/backend/log4js-config.json @@ -121,6 +121,7 @@ { "appenders": [ + "out", "backend", "errors" ], diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 359393cda..e8bd5c578 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -34,21 +34,14 @@ export class SendCoinsClient { voteForSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint) try { - logger.debug(`SendCoinsClient: voteForSendCoins with args=`, args) + logger.debug(`X-Com: SendCoinsClient: voteForSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(voteForSendCoins, { args }) - logger.debug(`SendCoinsClient: after rawRequest...`, data) - const sendCoinsResult = data as SendCoinsResult + logger.debug(`X-Com: SendCoinsClient: after rawRequest...data:`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!sendCoinsResult.vote) { - logger.warn( - 'X-Com: voteForSendCoins failed with: ', - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - sendCoinsResult.recipGradidoID, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - sendCoinsResult.recipName, - ) - return sendCoinsResult + if (!data?.voteForSendCoins?.vote) { + logger.warn('X-Com: voteForSendCoins failed with: ', data) + return new SendCoinsResult() } const result = new SendCoinsResult() result.vote = true @@ -66,17 +59,24 @@ export class SendCoinsClient { revertSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint) try { + logger.debug(`X-Com: SendCoinsClient: revertSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(revertSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 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}`) + logger.debug( + `X-Com: SendCoinsClient: revertSendCoins successful from endpoint=${this.endpoint}`, + ) return true } catch (err) { - logger.error(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err) + logger.error( + `X-Com: SendCoinsClient: revertSendCoins failed for endpoint=${this.endpoint}`, + err, + ) return false } } @@ -84,37 +84,54 @@ export class SendCoinsClient { settleSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`) try { + logger.debug(`X-Com: SendCoinsClient: settleSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(settleSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.settleSendCoins?.acknowledged) { - logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint) + logger.warn( + 'X-Com: SendCoinsClient: settleSendCoins without response data from endpoint', + this.endpoint, + ) return false } - logger.debug(`X-Com: settleSendCoins successful from endpoint=${this.endpoint}`) + logger.debug( + `X-Com: SendCoinsClient: settleSendCoins successful from endpoint=${this.endpoint}`, + ) return true } catch (err) { - throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err) + throw new LogError( + `X-Com: SendCoinsClient: settleSendCoins failed for endpoint=${this.endpoint}`, + err, + ) } } revertSettledSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`) try { + logger.debug(`X-Com: SendCoinsClient: revertSettledSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(revertSettledSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.revertSettledSendCoins?.acknowledged) { logger.warn( - 'X-Com: revertSettledSendCoins without response data from endpoint', + 'X-Com: SendCoinsClient: revertSettledSendCoins without response data from endpoint', this.endpoint, ) return false } - logger.debug(`X-Com: revertSettledSendCoins successful from endpoint=${this.endpoint}`) + logger.debug( + `X-Com: SendCoinsClient: revertSettledSendCoins successful from endpoint=${this.endpoint}`, + ) return true } catch (err) { - throw new LogError(`X-Com: revertSettledSendCoins failed for endpoint=${this.endpoint}`, err) + throw new LogError( + `X-Com: SendCoinsClient: revertSettledSendCoins failed for endpoint=${this.endpoint}`, + err, + ) } } } diff --git a/backend/src/federation/client/1_0/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts index 1258db4cb..930d22ff5 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsResult.ts @@ -10,8 +10,8 @@ export class SendCoinsResult { vote: boolean @Field(() => String, { nullable: true }) - recipGradidoID: string | null | undefined + recipGradidoID: string | null @Field(() => String, { nullable: true }) - recipName: string | null | undefined + recipName: string | null } diff --git a/backend/src/federation/client/1_0/query/revertSendCoins.ts b/backend/src/federation/client/1_0/query/revertSendCoins.ts index 9cc23fe64..ea7d28f77 100644 --- a/backend/src/federation/client/1_0/query/revertSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSendCoins.ts @@ -1,6 +1,11 @@ import { gql } from 'graphql-request' export const revertSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + revertSendCoins(data: $args) + } +` +/* mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -22,4 +27,4 @@ export const revertSendCoins = gql` senderUserName: $senderUserName ) } -` +*/ diff --git a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts index 74cdbd867..3965df396 100644 --- a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts @@ -1,6 +1,12 @@ import { gql } from 'graphql-request' export const revertSettledSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + revertSettledSendCoins(data: $args) + } +` +/* + mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -22,4 +28,4 @@ export const revertSettledSendCoins = gql` senderUserName: $senderUserName ) } -` +*/ diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts index 1696a0900..206a6544c 100644 --- a/backend/src/federation/client/1_0/query/settleSendCoins.ts +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -1,6 +1,11 @@ import { gql } from 'graphql-request' export const settleSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + settleSendCoins(data: $args) + } +` +/* mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -22,4 +27,4 @@ export const settleSendCoins = gql` senderUserName: $senderUserName ) } -` +*/ \ No newline at end of file diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index e7c2f751c..0c35ed401 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -74,7 +74,9 @@ export const executeTransaction = async ( ], }) if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) { - throw new LogError('There are still pending Transactions for Sender and/or Recipient') + throw new LogError( + `There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`, + ) } if (sender.id === recipient.id) { @@ -529,7 +531,7 @@ export class TransactionResolver { logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser) } else { // processing a x-community sendCoins - logger.debug('processing a x-community transaction...') + logger.debug('X-Com: processing a x-community transaction...') if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) { throw new LogError('X-Community sendCoins disabled per configuration!') } diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index 59e79f077..f55816229 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -1,14 +1,14 @@ import { Community as DbCommunity } from '@entity/Community' export async function isHomeCommunity(communityIdentifier: string): Promise { - const homeCommunity = await DbCommunity.findOneByOrFail({ foreign: false }) - if (communityIdentifier === homeCommunity.id.toString()) { - return true - } else if (communityIdentifier === homeCommunity.name) { - return true - } else if (communityIdentifier === homeCommunity.communityUuid) { - return true - } else if (communityIdentifier === homeCommunity.url) { + const homeCommunity = await DbCommunity.findOne({ + where: [ + { foreign: false, communityUuid: communityIdentifier }, + { foreign: false, name: communityIdentifier }, + { foreign: false, url: communityIdentifier }, + ], + }) + if (homeCommunity) { return true } else { return false @@ -16,11 +16,6 @@ export async function isHomeCommunity(communityIdentifier: string): Promise { - const community = await DbCommunity.findOneByOrFail({ name: communityIdentifier }) - return community.url -} - -export async function isCommunityAuthenticated(communityIdentifier: string): Promise { const community = await DbCommunity.findOneOrFail({ where: [ { communityUuid: communityIdentifier }, @@ -28,7 +23,18 @@ export async function isCommunityAuthenticated(communityIdentifier: string): Pro { url: communityIdentifier }, ], }) - if (community.authenticatedAt) { + return community.url +} + +export async function isCommunityAuthenticated(communityIdentifier: string): Promise { + const community = await DbCommunity.findOne({ + where: [ + { communityUuid: communityIdentifier }, + { name: communityIdentifier }, + { url: communityIdentifier }, + ], + }) + if (community?.authenticatedAt) { return true } else { return false diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 5f1349b70..1cd443cd4 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -28,7 +28,7 @@ export async function processXComPendingSendCoins( sender: dbUser, recipientIdentifier: string, ): Promise { - let sendCoinsResult = new SendCoinsResult() + let voteResult: SendCoinsResult try { logger.debug( `XCom: processXComPendingSendCoins...`, @@ -69,9 +69,10 @@ export async function processXComPendingSendCoins( args.senderUserUuid = sender.gradidoID args.senderUserName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: ready for voteForSendCoins with args=`, args) - sendCoinsResult = await client.voteForSendCoins(args) - logger.debug(`X-Com: returnd from voteForSendCoins:`, sendCoinsResult) - if (sendCoinsResult.vote) { + voteResult = await client.voteForSendCoins(args) + logger.debug(`X-Com: returnd from voteForSendCoins:`, voteResult) + if (voteResult.vote) { + logger.debug(`X-Com: prepare pendingTransaction for sender...`) // writing the pending transaction on receiver-side was successfull, so now write the sender side try { const pendingTx = DbPendingTransaction.create() @@ -83,17 +84,18 @@ export async function processXComPendingSendCoins( if (receiverCom.communityUuid) { pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid } - if (sendCoinsResult.recipGradidoID) { - pendingTx.linkedUserGradidoID = sendCoinsResult.recipGradidoID + if (voteResult.recipGradidoID) { + pendingTx.linkedUserGradidoID = voteResult.recipGradidoID } - if (sendCoinsResult.recipName) { - pendingTx.linkedUserName = sendCoinsResult.recipName + if (voteResult.recipName) { + pendingTx.linkedUserName = voteResult.recipName } pendingTx.memo = memo pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.SEND if (senderCom.communityUuid) pendingTx.userCommunityUuid = senderCom.communityUuid + pendingTx.id = sender.id pendingTx.userGradidoID = sender.gradidoID pendingTx.userName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: initialized sender pendingTX=`, pendingTx) @@ -118,12 +120,17 @@ export async function processXComPendingSendCoins( ) } logger.debug(`voteForSendCoins()-1_0... successfull`) + } else { + logger.debug( + `X-Com: break with error on writing pendingTransaction for recipient...`, + voteResult, + ) } } } catch (err) { throw new LogError(`Error:`, err) } - return sendCoinsResult + return new SendCoinsResult() } export async function processXComCommittingSendCoins( diff --git a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts index 749ca87cb..930d22ff5 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts @@ -9,9 +9,9 @@ export class SendCoinsResult { @Field(() => Boolean) vote: boolean - @Field(() => String) + @Field(() => String, { nullable: true }) recipGradidoID: string | null - @Field(() => String) + @Field(() => String, { nullable: true }) recipName: string | null } diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 4a31b0bbb..caa60c0d1 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -57,6 +57,24 @@ export class SendCoinsResolver { homeCom.name, ) } + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: args.senderUserUuid, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: args.senderUserUuid, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) { + throw new LogError( + `There exist still ongoing 'Pending-Transactions' for the involved users on receiver-side!`, + ) + } + try { const txDate = new Date(args.creationDate) const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate) @@ -94,18 +112,6 @@ export class SendCoinsResolver { async revertSendCoins( @Arg('data') args: SendCoinsArgs, - /* - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, - */ ): Promise { logger.debug(`revertSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct @@ -179,18 +185,6 @@ export class SendCoinsResolver { async settleSendCoins( @Arg('data') args: SendCoinsArgs, - /* - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, - */ ): Promise { logger.debug( `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${ @@ -264,18 +258,6 @@ export class SendCoinsResolver { async revertSettledSendCoins( @Arg('data') args: SendCoinsArgs, - /* - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, - */ ): Promise { logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct From dfa2a223db455d8f94ad6bd4acf9211d692b9e00 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 22 Sep 2023 00:30:30 +0200 Subject: [PATCH 34/51] next try x-com-sendCoins --- .../federation/client/1_0/SendCoinsClient.ts | 6 +- .../graphql/resolver/TransactionResolver.ts | 152 ------------------ .../resolver/util/processXComSendCoins.ts | 35 ++-- .../util/settlePendingSenderTransaction.ts | 13 +- .../api/1_0/resolver/SendCoinsResolver.ts | 9 +- .../util/settlePendingReceiveTransaction.ts | 15 +- 6 files changed, 46 insertions(+), 184 deletions(-) diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index e8bd5c578..3b3bf4f2c 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -64,7 +64,7 @@ export class SendCoinsClient { const { data } = await this.client.rawRequest(revertSendCoins, { args }) logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.revertSendCoins?.revertSendCoins) { + if (!data?.revertSendCoins) { logger.warn('X-Com: revertSendCoins without response data from endpoint', this.endpoint) return false } @@ -89,7 +89,7 @@ export class SendCoinsClient { const { data } = await this.client.rawRequest(settleSendCoins, { args }) logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.settleSendCoins?.acknowledged) { + if (!data?.settleSendCoins) { logger.warn( 'X-Com: SendCoinsClient: settleSendCoins without response data from endpoint', this.endpoint, @@ -116,7 +116,7 @@ export class SendCoinsClient { const { data } = await this.client.rawRequest(revertSettledSendCoins, { args }) logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.revertSettledSendCoins?.acknowledged) { + if (!data?.revertSettledSendCoins) { logger.warn( 'X-Com: SendCoinsClient: revertSettledSendCoins without response data from endpoint', this.endpoint, diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 0c35ed401..73f998b43 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -209,158 +209,6 @@ export const executeTransaction = async ( return true } -/* -export const executeCrossTransaction = async ( - amount: Decimal, - memo: string, - sender: dbUser, - recipientIdentifier: string, - transactionLink?: dbTransactionLink | null, -): Promise => { - // acquire lock - const releaseLock = await TRANSACTIONS_LOCK.acquire() - try { - logger.info('executeCrossTransaction', amount, memo, sender, recipientIdentifier) - - if (sender.id === recipient.id) { - throw new LogError('Sender and Recipient are the same', sender.id) - } - - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('Memo text is too short', memo.length) - } - - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('Memo text is too long', memo.length) - } - - // validate amount - const receivedCallDate = new Date() - const sendBalance = await calculateBalance( - sender.id, - amount.mul(-1), - receivedCallDate, - transactionLink, - ) - logger.debug(`calculated Balance=${sendBalance}`) - if (!sendBalance) { - throw new LogError('User has not enough GDD or amount is < 0', sendBalance) - } - - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') - logger.debug(`open Transaction to write...`) - try { - // transaction - const transactionSend = new dbTransaction() - transactionSend.typeId = TransactionTypeId.SEND - transactionSend.memo = memo - transactionSend.userId = sender.id - transactionSend.userGradidoID = sender.gradidoID - transactionSend.userName = fullName(sender.firstName, sender.lastName) - transactionSend.linkedUserId = recipient.id - transactionSend.linkedUserGradidoID = recipient.gradidoID - transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) - transactionSend.amount = amount.mul(-1) - transactionSend.balance = sendBalance.balance - transactionSend.balanceDate = receivedCallDate - transactionSend.decay = sendBalance.decay.decay - transactionSend.decayStart = sendBalance.decay.start - transactionSend.previous = sendBalance.lastTransactionId - transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null - await queryRunner.manager.insert(dbTransaction, transactionSend) - - logger.debug(`sendTransaction inserted: ${dbTransaction}`) - - const transactionReceive = new dbTransaction() - transactionReceive.typeId = TransactionTypeId.RECEIVE - transactionReceive.memo = memo - transactionReceive.userId = recipient.id - transactionReceive.userGradidoID = recipient.gradidoID - transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) - transactionReceive.linkedUserId = sender.id - transactionReceive.linkedUserGradidoID = sender.gradidoID - transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) - transactionReceive.amount = amount - const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) - transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount - transactionReceive.balanceDate = receivedCallDate - transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) - transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null - transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null - transactionReceive.linkedTransactionId = transactionSend.id - transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null - await queryRunner.manager.insert(dbTransaction, transactionReceive) - logger.debug(`receive Transaction inserted: ${dbTransaction}`) - - // Save linked transaction id for send - transactionSend.linkedTransactionId = transactionReceive.id - await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) - logger.debug('send Transaction updated', transactionSend) - - if (transactionLink) { - logger.info('transactionLink', transactionLink) - transactionLink.redeemedAt = receivedCallDate - transactionLink.redeemedBy = recipient.id - await queryRunner.manager.update( - dbTransactionLink, - { id: transactionLink.id }, - transactionLink, - ) - } - - await queryRunner.commitTransaction() - logger.info(`commit Transaction successful...`) - - await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) - - await EVENT_TRANSACTION_RECEIVE( - recipient, - sender, - transactionReceive, - transactionReceive.amount, - ) - - // trigger to send transaction via dlt-connector - void sendTransactionsToDltConnector() - } catch (e) { - await queryRunner.rollbackTransaction() - throw new LogError('Transaction was not successful', e) - } finally { - await queryRunner.release() - } - void sendTransactionReceivedEmail({ - firstName: recipient.firstName, - lastName: recipient.lastName, - email: recipient.emailContact.email, - language: recipient.language, - senderFirstName: sender.firstName, - senderLastName: sender.lastName, - senderEmail: sender.emailContact.email, - transactionAmount: amount, - }) - if (transactionLink) { - void sendTransactionLinkRedeemedEmail({ - firstName: sender.firstName, - lastName: sender.lastName, - email: sender.emailContact.email, - language: sender.language, - senderFirstName: recipient.firstName, - senderLastName: recipient.lastName, - senderEmail: recipient.emailContact.email, - transactionAmount: amount, - transactionMemo: memo, - }) - } - logger.info(`finished executeTransaction successfully`) - } finally { - releaseLock() - } - return true -} -*/ - @Resolver() export class TransactionResolver { @Authorized([RIGHTS.TRANSACTION_LIST]) diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 1cd443cd4..a7cf4c9f4 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -39,6 +39,23 @@ export async function processXComPendingSendCoins( sender, recipientIdentifier, ) + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: sender.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: sender.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: recipientIdentifier, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: recipientIdentifier, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) { + throw new LogError( + `There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`, + ) + } // first calculate the sender balance and check if the transaction is allowed const senderBalance = await calculateSenderBalance(sender.id, amount.mul(-1), creationDate) if (!senderBalance) { @@ -77,10 +94,10 @@ export async function processXComPendingSendCoins( try { const pendingTx = DbPendingTransaction.create() pendingTx.amount = amount.mul(-1) - pendingTx.balance = senderBalance ? senderBalance.balance : new Decimal(0) + pendingTx.balance = senderBalance.balance pendingTx.balanceDate = creationDate - pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0) - pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null + pendingTx.decay = senderBalance.decay.decay + pendingTx.decayStart = senderBalance.decay.start if (receiverCom.communityUuid) { pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid } @@ -95,7 +112,7 @@ export async function processXComPendingSendCoins( pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.SEND if (senderCom.communityUuid) pendingTx.userCommunityUuid = senderCom.communityUuid - pendingTx.id = sender.id + pendingTx.userId = sender.id pendingTx.userGradidoID = sender.gradidoID pendingTx.userName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: initialized sender pendingTX=`, pendingTx) @@ -126,6 +143,7 @@ export async function processXComPendingSendCoins( voteResult, ) } + return voteResult } } catch (err) { throw new LogError(`Error:`, err) @@ -156,12 +174,11 @@ export async function processXComCommittingSendCoins( ) // first find pending Tx with given parameters const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: senderCom.communityUuid ? senderCom.communityUuid : 'homeCom-UUID', + userCommunityUuid: senderCom.communityUuid ?? 'homeCom-UUID', userGradidoID: sender.gradidoID, userName: fullName(sender.firstName, sender.lastName), - linkedUserCommunityUuid: receiverCom.communityUuid - ? receiverCom.communityUuid - : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID, + linkedUserCommunityUuid: + receiverCom.communityUuid ?? CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID, linkedUserGradidoID: recipUuid, typeId: TransactionTypeId.SEND, state: PendingTransactionState.NEW, @@ -187,7 +204,7 @@ export async function processXComCommittingSendCoins( args.recipientUserIdentifier = pendingTx.linkedUserGradidoID } args.creationDate = pendingTx.balanceDate.toISOString() - args.amount = pendingTx.amount + args.amount = pendingTx.amount.mul(-1) args.memo = pendingTx.memo args.senderCommunityUuid = pendingTx.userCommunityUuid args.senderUserUuid = pendingTx.userGradidoID diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index e22276823..eb100c621 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -15,6 +15,7 @@ import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { getLastTransaction } from './getLastTransaction' +import Decimal from 'decimal.js-light' export async function settlePendingSenderTransaction( homeCom: DbCommunity, @@ -73,15 +74,13 @@ export async function settlePendingSenderTransaction( pendingTx.amount, pendingTx.balanceDate, ) - if (sendBalance?.balance !== pendingTx.balance) { - throw new LogError( - `X-Com: Calculation-Error on receiver balance: receiveBalance=${sendBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, - ) + if (!sendBalance) { + throw new LogError(`Sender has not enough GDD or amount is < 0', sendBalance`) } - transactionSend.balance = pendingTx.balance + transactionSend.balance = sendBalance?.balance ?? new Decimal(0) transactionSend.balanceDate = pendingTx.balanceDate - transactionSend.decay = pendingTx.decay - transactionSend.decayStart = pendingTx.decayStart + transactionSend.decay = sendBalance.decay.decay // pendingTx.decay + transactionSend.decayStart = sendBalance.decay.start // pendingTx.decayStart transactionSend.previous = pendingTx.previous transactionSend.linkedTransactionId = pendingTx.linkedTransactionId await queryRunner.manager.insert(dbTransaction, transactionSend) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index caa60c0d1..f2fe5a88f 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -78,12 +78,15 @@ export class SendCoinsResolver { try { const txDate = new Date(args.creationDate) const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate) + if (!receiveBalance) { + throw new LogError('Receiver has not enough GDD or amount is < 0', receiveBalance) + } const pendingTx = DbPendingTransaction.create() pendingTx.amount = args.amount - pendingTx.balance = receiveBalance ? receiveBalance.balance : args.amount + pendingTx.balance = receiveBalance.balance pendingTx.balanceDate = txDate - pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) - pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null + pendingTx.decay = receiveBalance.decay.decay + pendingTx.decayStart = receiveBalance.decay.start pendingTx.creationDate = new Date() pendingTx.linkedUserCommunityUuid = args.senderCommunityUuid pendingTx.linkedUserGradidoID = args.senderUserUuid diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 5c522061c..106b2beb1 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -77,18 +77,13 @@ export async function settlePendingReceiveTransaction( pendingTx.amount, pendingTx.balanceDate, ) - if ( - receiveBalance !== null && - receiveBalance.balance.toString() !== pendingTx.balance.toString() - ) { - throw new LogError( - `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance.balance}, pendingTx.balance=${pendingTx.balance}`, - ) + if (!receiveBalance) { + throw new LogError(`Receiver has not enough GDD or amount is < 0', sendBalance`) } - transactionReceive.balance = receiveBalance ? receiveBalance.balance : pendingTx.amount + transactionReceive.balance = receiveBalance.balance transactionReceive.balanceDate = pendingTx.balanceDate - transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) - transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null + transactionReceive.decay = receiveBalance.decay.decay + transactionReceive.decayStart = receiveBalance.decay.start transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId await queryRunner.manager.insert(dbTransaction, transactionReceive) From 2e326e123c85f05c55924357ea826b951b8ce0b3 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 22 Sep 2023 00:42:54 +0200 Subject: [PATCH 35/51] correct accidental checkin of log4j.config --- backend/log4js-config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/log4js-config.json b/backend/log4js-config.json index 90111a5ba..1cbd4519c 100644 --- a/backend/log4js-config.json +++ b/backend/log4js-config.json @@ -121,7 +121,6 @@ { "appenders": [ - "out", "backend", "errors" ], From f4e2588d0ea119677d9332359f661a4717cd3d14 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 22 Sep 2023 01:24:19 +0200 Subject: [PATCH 36/51] remove check of receiver balance after calculation --- .../src/graphql/api/1_0/resolver/SendCoinsResolver.ts | 11 ++++------- .../api/1_0/util/settlePendingReceiveTransaction.ts | 9 +++------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index f2fe5a88f..12e7eab20 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -8,13 +8,13 @@ import { LogError } from '@/server/LogError' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' import { calculateRecipientBalance } from '../util/calculateRecipientBalance' -import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' // import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier' import { SendCoinsResult } from '../model/SendCoinsResult' +import Decimal from 'decimal.js-light' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -78,15 +78,12 @@ export class SendCoinsResolver { try { const txDate = new Date(args.creationDate) const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate) - if (!receiveBalance) { - throw new LogError('Receiver has not enough GDD or amount is < 0', receiveBalance) - } const pendingTx = DbPendingTransaction.create() pendingTx.amount = args.amount - pendingTx.balance = receiveBalance.balance + pendingTx.balance = receiveBalance ? receiveBalance.balance : args.amount pendingTx.balanceDate = txDate - pendingTx.decay = receiveBalance.decay.decay - pendingTx.decayStart = receiveBalance.decay.start + pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null pendingTx.creationDate = new Date() pendingTx.linkedUserCommunityUuid = args.senderCommunityUuid pendingTx.linkedUserGradidoID = args.senderUserUuid diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 106b2beb1..e0e600be9 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -77,13 +77,10 @@ export async function settlePendingReceiveTransaction( pendingTx.amount, pendingTx.balanceDate, ) - if (!receiveBalance) { - throw new LogError(`Receiver has not enough GDD or amount is < 0', sendBalance`) - } - transactionReceive.balance = receiveBalance.balance + transactionReceive.balance = receiveBalance ? receiveBalance.balance : pendingTx.amount transactionReceive.balanceDate = pendingTx.balanceDate - transactionReceive.decay = receiveBalance.decay.decay - transactionReceive.decayStart = receiveBalance.decay.start + transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId await queryRunner.manager.insert(dbTransaction, transactionReceive) From d5be82ad7eac961e08e8a58eb12e20b0d8df6eab Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 22 Sep 2023 19:53:12 +0200 Subject: [PATCH 37/51] insert in transactionList the remoteUser of X-Com-Tx --- backend/src/graphql/model/User.ts | 36 ++++++++++--------- .../graphql/resolver/TransactionResolver.ts | 30 ++++++++++++++-- .../src/graphql/resolver/util/communities.ts | 11 ++++++ .../util/settlePendingSenderTransaction.ts | 3 +- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 9e4c0fdf9..3474c6901 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -5,24 +5,26 @@ import { KlickTipp } from './KlickTipp' @ObjectType() export class User { - constructor(user: dbUser) { - this.id = user.id - this.gradidoID = user.gradidoID - this.alias = user.alias - if (user.emailContact) { - this.emailChecked = user.emailContact.emailChecked + constructor(user: dbUser | null) { + if (user) { + this.id = user.id + this.gradidoID = user.gradidoID + this.alias = user.alias + if (user.emailContact) { + this.emailChecked = user.emailContact.emailChecked + } + this.firstName = user.firstName + this.lastName = user.lastName + this.deletedAt = user.deletedAt + this.createdAt = user.createdAt + this.language = user.language + this.publisherId = user.publisherId + this.roles = user.userRoles?.map((userRole) => userRole.role) ?? [] + this.klickTipp = null + this.hasElopage = null + this.hideAmountGDD = user.hideAmountGDD + this.hideAmountGDT = user.hideAmountGDT } - this.firstName = user.firstName - this.lastName = user.lastName - this.deletedAt = user.deletedAt - this.createdAt = user.createdAt - this.language = user.language - this.publisherId = user.publisherId - this.roles = user.userRoles?.map((userRole) => userRole.role) ?? [] - this.klickTipp = null - this.hasElopage = null - this.hideAmountGDD = user.hideAmountGDD - this.hideAmountGDT = user.hideAmountGDT } @Field(() => Int) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 73f998b43..a1811daec 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -38,7 +38,7 @@ import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { BalanceResolver } from './BalanceResolver' -import { isCommunityAuthenticated, isHomeCommunity } from './util/communities' +import { getCommunityName, isCommunityAuthenticated, isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' @@ -249,12 +249,21 @@ export class TransactionResolver { // find involved users; I am involved const involvedUserIds: number[] = [user.id] + const involvedRemoteUsers: User[] = [] userTransactions.forEach((transaction: dbTransaction) => { if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) { involvedUserIds.push(transaction.linkedUserId) } + if (!transaction.linkedUserId && transaction.linkedUserGradidoID) { + const remoteUser = new User(null) + remoteUser.gradidoID = transaction.linkedUserGradidoID + remoteUser.firstName = transaction.linkedUserName + remoteUser.lastName = transaction.linkedUserCommunityUuid + involvedRemoteUsers.push(remoteUser) + } }) - logger.debug(`involvedUserIds=${involvedUserIds}`) + logger.debug(`involvedUserIds=`, involvedUserIds) + logger.debug(`involvedRemoteUsers=`, involvedRemoteUsers) // We need to show the name for deleted users for old transactions const involvedDbUsers = await dbUser.find({ @@ -263,7 +272,7 @@ export class TransactionResolver { relations: ['emailContact'], }) const involvedUsers = involvedDbUsers.map((u) => new User(u)) - logger.debug(`involvedUsers=${involvedUsers}`) + logger.debug(`involvedUsers=`, involvedUsers) const self = new User(user) const transactions: Transaction[] = [] @@ -333,10 +342,25 @@ export class TransactionResolver { // transactions userTransactions.forEach((userTransaction: dbTransaction) => { + /* const linkedUser = userTransaction.typeId === TransactionTypeId.CREATION ? communityUser : involvedUsers.find((u) => u.id === userTransaction.linkedUserId) + */ + let linkedUser: User | undefined + if (userTransaction.typeId === TransactionTypeId.CREATION) { + linkedUser = communityUser + logger.debug('CREATION-linkedUser=', linkedUser) + } else if (userTransaction.linkedUserId) { + linkedUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId) + logger.debug('local linkedUser=', linkedUser) + } else if (userTransaction.linkedUserCommunityUuid) { + linkedUser = involvedRemoteUsers.find( + (u) => u.gradidoID === userTransaction.linkedUserGradidoID, + ) + logger.debug('remote linkedUser=', linkedUser) + } transactions.push(new Transaction(userTransaction, self, linkedUser)) }) logger.debug(`TransactionTypeId.CREATION: transactions=${transactions}`) diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index f55816229..52fee86b2 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -40,3 +40,14 @@ export async function isCommunityAuthenticated(communityIdentifier: string): Pro return false } } + +export async function getCommunityName(communityIdentifier: string): Promise { + const community = await DbCommunity.findOne({ + where: [{ communityUuid: communityIdentifier }, { url: communityIdentifier }], + }) + if (community?.name) { + return community.name + } else { + return '' + } +} diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index eb100c621..b9c7d8b36 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -7,6 +7,7 @@ import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { User as DbUser } from '@entity/User' +import { Decimal } from 'decimal.js-light' import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' import { LogError } from '@/server/LogError' @@ -15,7 +16,6 @@ import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { getLastTransaction } from './getLastTransaction' -import Decimal from 'decimal.js-light' export async function settlePendingSenderTransaction( homeCom: DbCommunity, @@ -66,6 +66,7 @@ export async function settlePendingSenderTransaction( transactionSend.userGradidoID = pendingTx.userGradidoID transactionSend.userName = pendingTx.userName transactionSend.linkedUserId = pendingTx.linkedUserId + transactionSend.linkedUserCommunityUuid = pendingTx.linkedUserCommunityUuid transactionSend.linkedUserGradidoID = pendingTx.linkedUserGradidoID transactionSend.linkedUserName = pendingTx.linkedUserName transactionSend.amount = pendingTx.amount From fade56a55760e60a016db3c09d003ca6018caafe Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 00:40:48 +0200 Subject: [PATCH 38/51] additional tests for x-com-sendcoins --- .../federation/client/1_0/SendCoinsClient.ts | 78 +-- .../client/1_0/model/SendCoinsResult.ts | 12 +- .../client/1_0/query/revertSendCoins.ts | 7 +- .../1_0/query/revertSettledSendCoins.ts | 8 +- .../client/1_0/query/settleSendCoins.ts | 7 +- .../client/1_0/query/voteForSendCoins.ts | 36 +- federation/jest.config.js | 2 +- .../graphql/api/1_0/model/SendCoinsArgs.ts | 4 +- .../graphql/api/1_0/model/SendCoinsResult.ts | 17 + .../1_0/resolver/SendCoinsResolver.test.ts | 456 +++++++++--------- .../api/1_0/resolver/SendCoinsResolver.ts | 243 +++++----- .../util/revertSettledReceiveTransaction.ts | 21 +- .../util/settlePendingReceiveTransaction.ts | 8 - 13 files changed, 500 insertions(+), 399 deletions(-) create mode 100644 federation/src/graphql/api/1_0/model/SendCoinsResult.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 7f1fad186..3b3bf4f2c 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 { SendCoinsResult } from './model/SendCoinsResult' import { revertSendCoins } from './query/revertSendCoins' import { revertSettledSendCoins } from './query/revertSettledSendCoins' import { settleSendCoins } from './query/settleSendCoins' @@ -22,7 +23,7 @@ export class SendCoinsClient { dbCom.apiVersion }/` this.client = new GraphQLClient(this.endpoint, { - method: 'GET', + method: 'POST', jsonSerializer: { parse: JSON.parse, stringify: JSON.stringify, @@ -30,27 +31,26 @@ export class SendCoinsClient { }) } - voteForSendCoins = async (args: SendCoinsArgs): Promise => { + voteForSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint) try { + logger.debug(`X-Com: SendCoinsClient: voteForSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(voteForSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after rawRequest...data:`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.voteForSendCoins?.voteForSendCoins) { - logger.warn( - 'X-Com: voteForSendCoins failed with: ', - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins.voteForSendCoins, - ) - return + if (!data?.voteForSendCoins?.vote) { + logger.warn('X-Com: voteForSendCoins failed with: ', data) + return new SendCoinsResult() } - logger.debug( - 'X-Com: voteForSendCoins successful with result=', - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins, - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access - return data.voteForSendCoins.voteForSendCoins + const result = new SendCoinsResult() + result.vote = true + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + result.recipGradidoID = data.voteForSendCoins.recipGradidoID + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + result.recipName = data.voteForSendCoins.recipName + logger.debug('X-Com: voteForSendCoins successful with result=', result) + return result } catch (err) { throw new LogError(`X-Com: voteForSendCoins failed for endpoint=${this.endpoint}:`, err) } @@ -59,17 +59,24 @@ export class SendCoinsClient { revertSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint) try { + logger.debug(`X-Com: SendCoinsClient: revertSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(revertSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.revertSendCoins?.revertSendCoins) { + if (!data?.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}`) + logger.debug( + `X-Com: SendCoinsClient: revertSendCoins successful from endpoint=${this.endpoint}`, + ) return true } catch (err) { - logger.error(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err) + logger.error( + `X-Com: SendCoinsClient: revertSendCoins failed for endpoint=${this.endpoint}`, + err, + ) return false } } @@ -77,37 +84,54 @@ export class SendCoinsClient { settleSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`) try { + logger.debug(`X-Com: SendCoinsClient: settleSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(settleSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.settleSendCoins?.acknowledged) { - logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint) + if (!data?.settleSendCoins) { + logger.warn( + 'X-Com: SendCoinsClient: settleSendCoins without response data from endpoint', + this.endpoint, + ) return false } - logger.debug(`X-Com: settleSendCoins successful from endpoint=${this.endpoint}`) + logger.debug( + `X-Com: SendCoinsClient: settleSendCoins successful from endpoint=${this.endpoint}`, + ) return true } catch (err) { - throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err) + throw new LogError( + `X-Com: SendCoinsClient: settleSendCoins failed for endpoint=${this.endpoint}`, + err, + ) } } revertSettledSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`) try { + logger.debug(`X-Com: SendCoinsClient: revertSettledSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(revertSettledSendCoins, { args }) + logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.revertSettledSendCoins?.acknowledged) { + if (!data?.revertSettledSendCoins) { logger.warn( - 'X-Com: revertSettledSendCoins without response data from endpoint', + 'X-Com: SendCoinsClient: revertSettledSendCoins without response data from endpoint', this.endpoint, ) return false } - logger.debug(`X-Com: revertSettledSendCoins successful from endpoint=${this.endpoint}`) + logger.debug( + `X-Com: SendCoinsClient: revertSettledSendCoins successful from endpoint=${this.endpoint}`, + ) return true } catch (err) { - throw new LogError(`X-Com: revertSettledSendCoins failed for endpoint=${this.endpoint}`, err) + throw new LogError( + `X-Com: SendCoinsClient: revertSettledSendCoins failed for endpoint=${this.endpoint}`, + err, + ) } } } diff --git a/backend/src/federation/client/1_0/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts index 1897410cc..930d22ff5 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsResult.ts @@ -1,6 +1,6 @@ -import { ArgsType, Field } from 'type-graphql' +import { Field, ObjectType } from 'type-graphql' -@ArgsType() +@ObjectType() export class SendCoinsResult { constructor() { this.vote = false @@ -9,9 +9,9 @@ export class SendCoinsResult { @Field(() => Boolean) vote: boolean - @Field(() => String) - receiverFirstName: string + @Field(() => String, { nullable: true }) + recipGradidoID: string | null - @Field(() => String) - receiverLastName: string + @Field(() => String, { nullable: true }) + recipName: string | null } diff --git a/backend/src/federation/client/1_0/query/revertSendCoins.ts b/backend/src/federation/client/1_0/query/revertSendCoins.ts index 9cc23fe64..ea7d28f77 100644 --- a/backend/src/federation/client/1_0/query/revertSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSendCoins.ts @@ -1,6 +1,11 @@ import { gql } from 'graphql-request' export const revertSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + revertSendCoins(data: $args) + } +` +/* mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -22,4 +27,4 @@ export const revertSendCoins = gql` senderUserName: $senderUserName ) } -` +*/ diff --git a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts index 74cdbd867..3965df396 100644 --- a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts @@ -1,6 +1,12 @@ import { gql } from 'graphql-request' export const revertSettledSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + revertSettledSendCoins(data: $args) + } +` +/* + mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -22,4 +28,4 @@ export const revertSettledSendCoins = gql` senderUserName: $senderUserName ) } -` +*/ diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts index 1696a0900..206a6544c 100644 --- a/backend/src/federation/client/1_0/query/settleSendCoins.ts +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -1,6 +1,11 @@ import { gql } from 'graphql-request' export const settleSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + settleSendCoins(data: $args) + } +` +/* mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -22,4 +27,4 @@ export const settleSendCoins = gql` senderUserName: $senderUserName ) } -` +*/ \ No newline at end of file diff --git a/backend/src/federation/client/1_0/query/voteForSendCoins.ts b/backend/src/federation/client/1_0/query/voteForSendCoins.ts index 0f16ff32b..a5456bec7 100644 --- a/backend/src/federation/client/1_0/query/voteForSendCoins.ts +++ b/backend/src/federation/client/1_0/query/voteForSendCoins.ts @@ -1,6 +1,16 @@ import { gql } from 'graphql-request' export const voteForSendCoins = gql` + mutation ($args: SendCoinsArgs!) { + voteForSendCoins(data: $args) { + vote + recipGradidoID + recipName + } + } +` + +/* mutation ( $recipientCommunityUuid: String! $recipientUserIdentifier: String! @@ -12,14 +22,20 @@ export const voteForSendCoins = gql` $senderUserName: String! ) { voteForSendCoins( - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName - ) + data: { + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + } + ) { + vote + recipGradidoID + recipName + } } -` +*/ diff --git a/federation/jest.config.js b/federation/jest.config.js index cffb79835..f859bbe2f 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -6,7 +6,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 74, + lines: 75, }, }, setupFiles: ['/test/testSetup.ts'], diff --git a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts index fb97da925..e658209ca 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts @@ -1,7 +1,7 @@ import { Decimal } from 'decimal.js-light' -import { ArgsType, Field } from 'type-graphql' +import { Field, InputType } from 'type-graphql' -@ArgsType() +@InputType() export class SendCoinsArgs { @Field(() => String) recipientCommunityUuid: string diff --git a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts new file mode 100644 index 000000000..930d22ff5 --- /dev/null +++ b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts @@ -0,0 +1,17 @@ +import { Field, ObjectType } from 'type-graphql' + +@ObjectType() +export class SendCoinsResult { + constructor() { + this.vote = false + } + + @Field(() => Boolean) + vote: boolean + + @Field(() => String, { nullable: true }) + recipGradidoID: string | null + + @Field(() => String, { nullable: true }) + recipName: string | null +} diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 5120391d6..897e06179 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -15,6 +15,7 @@ import Decimal from 'decimal.js-light' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' import { Transaction as DbTransaction } from '@entity/Transaction' +import { SendCoinsArgs } from '../model/SendCoinsArgs' let mutate: ApolloServerTestClient['mutate'], con: Connection // let query: ApolloServerTestClient['query'] @@ -49,49 +50,24 @@ afterAll(async () => { describe('SendCoinsResolver', () => { const voteForSendCoinsMutation = ` - mutation ( - $recipientCommunityUuid: String! - $recipientUserIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $senderCommunityUuid: String! - $senderUserUuid: String! - $senderUserName: String! - ) { - voteForSendCoins( - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName - ) + mutation ($args: SendCoinsArgs!) { + voteForSendCoins(data: $args) { + vote + recipGradidoID + recipName + } }` - const settleSendCoinsMutation = ` - mutation ( - $recipientCommunityUuid: String! - $recipientUserIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $senderCommunityUuid: String! - $senderUserUuid: String! - $senderUserName: String! - ) { - settleSendCoins( - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName - ) + mutation ($args: SendCoinsArgs!) { + settleSendCoins(data: $args) + }` + const revertSendCoinsMutation = ` + mutation ($args: SendCoinsArgs!) { + revertSendCoins(data: $args) + }` + const revertSettledSendCoinsMutation = ` + mutation ($args: SendCoinsArgs!) { + revertSettledSendCoins(data: $args) }` beforeEach(async () => { @@ -149,19 +125,21 @@ describe('SendCoinsResolver', () => { describe('unknown recipient community', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + args.recipientCommunityUuid = 'invalid foreignCom' + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = new Date().toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: voteForSendCoinsMutation, - variables: { - recipientCommunityUuid: 'invalid foreignCom', - recipientUserIdentifier: recipUser.gradidoID, - creationDate: new Date().toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -174,19 +152,23 @@ describe('SendCoinsResolver', () => { describe('unknown recipient user', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = 'invalid recipient' + args.creationDate = new Date().toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: voteForSendCoinsMutation, - variables: { - recipientCommunityUuid: foreignCom.communityUuid, - recipientUserIdentifier: 'invalid recipient', - creationDate: new Date().toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -203,24 +185,32 @@ describe('SendCoinsResolver', () => { describe('valid X-Com-TX voted', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = new Date().toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: voteForSendCoinsMutation, - variables: { - recipientCommunityUuid: foreignCom.communityUuid, - recipientUserIdentifier: recipUser.gradidoID, - creationDate: new Date().toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ data: { - voteForSendCoins: 'recipUser-FirstName recipUser-LastName', + voteForSendCoins: { + recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd', + recipName: 'recipUser-FirstName recipUser-LastName', + vote: true, + }, }, }), ) @@ -229,63 +219,46 @@ describe('SendCoinsResolver', () => { }) describe('revertSendCoins', () => { - const revertSendCoinsMutation = ` - mutation ( - $recipientCommunityUuid: String! - $recipientUserIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $senderCommunityUuid: String! - $senderUserUuid: String! - $senderUserName: String! - ) { - revertSendCoins( - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName - ) - }` - const creationDate = new Date() beforeEach(async () => { + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) await mutate({ mutation: voteForSendCoinsMutation, - variables: { - recipientCommunityUuid: foreignCom.communityUuid, - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }) }) describe('unknown recipient community', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + args.recipientCommunityUuid = 'invalid foreignCom' + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: revertSendCoinsMutation, - variables: { - recipientCommunityUuid: 'invalid foreignCom', - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -298,19 +271,23 @@ describe('SendCoinsResolver', () => { describe('unknown recipient user', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = 'invalid recipient' + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: revertSendCoinsMutation, - variables: { - recipientCommunityUuid: foreignCom.communityUuid, - recipientUserIdentifier: 'invalid recipient', - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -327,19 +304,23 @@ describe('SendCoinsResolver', () => { describe('valid X-Com-TX reverted', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: revertSendCoinsMutation, - variables: { - recipientCommunityUuid: foreignCom.communityUuid, - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: homeCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -357,6 +338,24 @@ describe('SendCoinsResolver', () => { const creationDate = new Date() beforeEach(async () => { + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) + await mutate({ + mutation: voteForSendCoinsMutation, + variables: { args }, + }) + /* pendingTx = DbPendingTransaction.create() pendingTx.amount = new Decimal(100) pendingTx.balanceDate = creationDate @@ -375,24 +374,27 @@ describe('SendCoinsResolver', () => { } pendingTx.userGradidoID = recipUser.gradidoID await DbPendingTransaction.insert(pendingTx) + */ }) describe('unknown recipient community', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + args.recipientCommunityUuid = 'invalid foreignCom' + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: settleSendCoinsMutation, - variables: { - recipientCommunityUuid: 'invalid foreignCom', - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: foreignCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -405,19 +407,23 @@ describe('SendCoinsResolver', () => { describe('unknown recipient user', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = 'invalid recipient' + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: settleSendCoinsMutation, - variables: { - recipientCommunityUuid: homeCom.communityUuid, - recipientUserIdentifier: 'invalid recipient', - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: foreignCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -434,19 +440,23 @@ describe('SendCoinsResolver', () => { describe('valid X-Com-TX settled', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: settleSendCoinsMutation, - variables: { - recipientCommunityUuid: homeCom.communityUuid, - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: foreignCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -460,34 +470,33 @@ describe('SendCoinsResolver', () => { }) describe('revertSettledSendCoins', () => { - const revertSettledSendCoinsMutation = ` - mutation ( - $recipientCommunityUuid: String! - $recipientUserIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $senderCommunityUuid: String! - $senderUserUuid: String! - $senderUserName: String! - ) { - revertSettledSendCoins( - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName - ) - }` - let pendingTx: DbPendingTransaction let settledTx: DbTransaction const creationDate = new Date() beforeEach(async () => { + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) + await mutate({ + mutation: voteForSendCoinsMutation, + variables: { args }, + }) + await mutate({ + mutation: settleSendCoinsMutation, + variables: { args }, + }) + /* pendingTx = DbPendingTransaction.create() pendingTx.amount = new Decimal(100) pendingTx.balanceDate = creationDate @@ -524,24 +533,27 @@ describe('SendCoinsResolver', () => { } settledTx.userGradidoID = recipUser.gradidoID await DbTransaction.insert(settledTx) + */ }) describe('unknown recipient community', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + args.recipientCommunityUuid = 'invalid foreignCom' + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: revertSettledSendCoinsMutation, - variables: { - recipientCommunityUuid: 'invalid foreignCom', - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: foreignCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -554,19 +566,23 @@ describe('SendCoinsResolver', () => { describe('unknown recipient user', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = 'invalid recipient' + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: revertSettledSendCoinsMutation, - variables: { - recipientCommunityUuid: homeCom.communityUuid, - recipientUserIdentifier: 'invalid recipient', - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: foreignCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ @@ -583,19 +599,23 @@ describe('SendCoinsResolver', () => { describe('valid X-Com-TX settled', () => { it('throws an error', async () => { jest.clearAllMocks() + const args = new SendCoinsArgs() + if (foreignCom.communityUuid) { + args.recipientCommunityUuid = foreignCom.communityUuid + } + args.recipientUserIdentifier = recipUser.gradidoID + args.creationDate = creationDate.toISOString() + args.amount = new Decimal(100) + args.memo = 'X-Com-TX memo' + if (homeCom.communityUuid) { + args.senderCommunityUuid = homeCom.communityUuid + } + args.senderUserUuid = sendUser.gradidoID + args.senderUserName = fullName(sendUser.firstName, sendUser.lastName) expect( await mutate({ mutation: revertSettledSendCoinsMutation, - variables: { - recipientCommunityUuid: homeCom.communityUuid, - recipientUserIdentifier: recipUser.gradidoID, - creationDate: creationDate.toISOString(), - amount: 100, - memo: 'X-Com-TX memo', - senderCommunityUuid: foreignCom.communityUuid, - senderUserUuid: sendUser.gradidoID, - senderUserName: fullName(sendUser.firstName, sendUser.lastName), - }, + variables: { args }, }), ).toEqual( expect.objectContaining({ diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 4196e015c..d942c48f3 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Args, Mutation, Resolver } from 'type-graphql' +import { Arg, Args, Mutation, Resolver } from 'type-graphql' import { federationLogger as logger } from '@/server/logger' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' @@ -8,56 +8,48 @@ import { LogError } from '@/server/LogError' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' import { calculateRecipientBalance } from '../util/calculateRecipientBalance' -import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' // import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier' +import { SendCoinsResult } from '../model/SendCoinsResult' +import Decimal from 'decimal.js-light' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars export class SendCoinsResolver { - @Mutation(() => String) + @Mutation(() => SendCoinsResult) async voteForSendCoins( - @Args() - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, - ): Promise { + @Arg('data') + args: SendCoinsArgs, + ): Promise { logger.debug( `voteForSendCoins() via apiVersion=1_0 ...`, - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount.toString(), - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.recipientCommunityUuid, + args.recipientUserIdentifier, + args.creationDate, + args.amount.toString(), + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) - let result: string + const result = new SendCoinsResult() // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `voteForSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -65,30 +57,50 @@ export class SendCoinsResolver { homeCom.name, ) } + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: args.senderUserUuid, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: args.senderUserUuid, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) { + throw new LogError( + `There exist still ongoing 'Pending-Transactions' for the involved users on receiver-side!`, + ) + } + try { - const txDate = new Date(creationDate) - const receiveBalance = await calculateRecipientBalance(receiverUser.id, amount, txDate) + const txDate = new Date(args.creationDate) + const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate) const pendingTx = DbPendingTransaction.create() - pendingTx.amount = amount - pendingTx.balance = receiveBalance ? receiveBalance.balance : amount + pendingTx.amount = args.amount + pendingTx.balance = receiveBalance ? receiveBalance.balance : args.amount pendingTx.balanceDate = txDate pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null pendingTx.creationDate = new Date() - pendingTx.linkedUserCommunityUuid = senderCommunityUuid - pendingTx.linkedUserGradidoID = senderUserUuid - pendingTx.linkedUserName = senderUserName - pendingTx.memo = memo + pendingTx.linkedUserCommunityUuid = args.senderCommunityUuid + pendingTx.linkedUserGradidoID = args.senderUserUuid + pendingTx.linkedUserName = args.senderUserName + pendingTx.memo = args.memo pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.RECEIVE pendingTx.userId = receiverUser.id - pendingTx.userCommunityUuid = recipientCommunityUuid + pendingTx.userCommunityUuid = args.recipientCommunityUuid pendingTx.userGradidoID = receiverUser.gradidoID pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) await DbPendingTransaction.insert(pendingTx) - result = pendingTx.userName + result.vote = true + result.recipName = pendingTx.userName + result.recipGradidoID = pendingTx.userGradidoID logger.debug(`voteForSendCoins()-1_0... successfull`) } catch (err) { throw new LogError(`Error in voteForSendCoins: `, err) @@ -98,33 +110,24 @@ export class SendCoinsResolver { @Mutation(() => Boolean) async revertSendCoins( - @Args() - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, + @Arg('data') + args: SendCoinsArgs, ): Promise { logger.debug(`revertSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `revertSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -134,16 +137,16 @@ export class SendCoinsResolver { } try { const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: recipientCommunityUuid, + userCommunityUuid: args.recipientCommunityUuid, userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: senderCommunityUuid, - linkedUserGradidoID: senderUserUuid, + balanceDate: new Date(args.creationDate), + linkedUserCommunityUuid: args.senderCommunityUuid, + linkedUserGradidoID: args.senderUserUuid, }) logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount.toString() === amount.toString()) { + if (pendingTx && pendingTx.amount.toString() === args.amount.toString()) { logger.debug('XCom: revertSendCoins matching pendingTX for remove...') try { await pendingTx.remove() @@ -154,21 +157,21 @@ export class SendCoinsResolver { } else { logger.debug( 'XCom: revertSendCoins NOT matching pendingTX for remove:', - pendingTx?.amount, - amount, + pendingTx?.amount.toString(), + args.amount.toString(), ) throw new LogError( `Can't find in revertSendCoins the pending receiver TX for args=`, - recipientCommunityUuid, - recipientUserIdentifier, + args.recipientCommunityUuid, + args.recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.creationDate, + args.amount, + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) @@ -180,35 +183,32 @@ export class SendCoinsResolver { @Mutation(() => Boolean) async settleSendCoins( - @Args() - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, + @Arg('data') + args: SendCoinsArgs, ): Promise { logger.debug( - `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${recipientCommunityUuid}, userGradidoID=${recipientUserIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${senderCommunityUuid}, userSenderIdentifier=${senderUserUuid}, userSenderName=${senderUserName}`, + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${ + args.recipientCommunityUuid + }, userGradidoID=${args.recipientUserIdentifier}, balanceDate=${ + args.creationDate + },amount=${args.amount.valueOf()}, memo=${args.memo}, linkedUserCommunityUuid = ${ + args.senderCommunityUuid + }, userSenderIdentifier=${args.senderUserUuid}, userSenderName=${args.senderUserName}`, ) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `settleSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -217,16 +217,20 @@ export class SendCoinsResolver { ) } const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: recipientCommunityUuid, + userCommunityUuid: args.recipientCommunityUuid, userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: senderCommunityUuid, - linkedUserGradidoID: senderUserUuid, + balanceDate: new Date(args.creationDate), + linkedUserCommunityUuid: args.senderCommunityUuid, + linkedUserGradidoID: args.senderUserUuid, }) logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) - if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + if ( + pendingTx && + pendingTx.amount.toString() === args.amount.toString() && + pendingTx.memo === args.memo + ) { logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) @@ -236,49 +240,40 @@ export class SendCoinsResolver { logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') throw new LogError( `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - recipientCommunityUuid, - recipientUserIdentifier, + args.recipientCommunityUuid, + args.recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.creationDate, + args.amount, + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) } } @Mutation(() => Boolean) async revertSettledSendCoins( - @Args() - { - recipientCommunityUuid, - recipientUserIdentifier, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, - }: SendCoinsArgs, + @Arg('data') + args: SendCoinsArgs, ): Promise { logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: recipientCommunityUuid, + communityUuid: args.recipientCommunityUuid, }) if (!homeCom) { throw new LogError( `revertSettledSendCoins with wrong recipientCommunityUuid`, - recipientCommunityUuid, + args.recipientCommunityUuid, ) } let receiverUser try { // second check if receiver user exists in this community - receiverUser = await findUserByIdentifier(recipientUserIdentifier) + receiverUser = await findUserByIdentifier(args.recipientUserIdentifier) } catch (err) { logger.error('Error in findUserByIdentifier:', err) throw new LogError( @@ -287,16 +282,20 @@ export class SendCoinsResolver { ) } const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: recipientCommunityUuid, - userGradidoID: receiverUser.gradidoID, + userCommunityUuid: args.recipientCommunityUuid, + userGradidoID: args.recipientUserIdentifier, state: PendingTransactionState.SETTLED, typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: senderCommunityUuid, - linkedUserGradidoID: senderUserUuid, + balanceDate: new Date(args.creationDate), + linkedUserCommunityUuid: args.senderCommunityUuid, + linkedUserGradidoID: args.senderUserUuid, }) logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + if ( + pendingTx && + pendingTx.amount.toString() === args.amount.toString() && + pendingTx.memo === args.memo + ) { logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') try { await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) @@ -308,16 +307,16 @@ export class SendCoinsResolver { logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') throw new LogError( `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - recipientCommunityUuid, - recipientUserIdentifier, + args.recipientCommunityUuid, + args.recipientUserIdentifier, PendingTransactionState.SETTLED, TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - senderCommunityUuid, - senderUserUuid, - senderUserName, + args.creationDate, + args.amount, + args.memo, + args.senderCommunityUuid, + args.senderUserUuid, + args.senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) diff --git a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts index 6eb933f6a..4b0f989ba 100644 --- a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts @@ -60,14 +60,31 @@ export async function revertSettledReceiveTransaction( } const lastTransaction = await getLastTransaction(receiverUser.id) + logger.debug(`LastTransaction vs PendingTransaction`) + logger.debug(`balance:`, lastTransaction?.balance.toString(), pendingTx.balance.toString()) + logger.debug( + `balanceDate:`, + lastTransaction?.balanceDate.toISOString(), + pendingTx.balanceDate.toISOString(), + ) + logger.debug(`GradidoID:`, lastTransaction?.userGradidoID, pendingTx.userGradidoID) + logger.debug(`Name:`, lastTransaction?.userName, pendingTx.userName) + logger.debug(`amount:`, lastTransaction?.amount.toString(), pendingTx.amount.toString()) + logger.debug(`memo:`, lastTransaction?.memo, pendingTx.memo) + logger.debug( + `linkedUserGradidoID:`, + lastTransaction?.linkedUserGradidoID, + pendingTx.linkedUserGradidoID, + ) + logger.debug(`linkedUserName:`, lastTransaction?.linkedUserName, pendingTx.linkedUserName) // now the last Tx must be the equivalant to the pendingTX if ( lastTransaction && - lastTransaction.balance === pendingTx.balance && + lastTransaction.balance.comparedTo(pendingTx.balance) === 0 && lastTransaction.balanceDate.toISOString() === pendingTx.balanceDate.toISOString() && lastTransaction.userGradidoID === pendingTx.userGradidoID && lastTransaction.userName === pendingTx.userName && - lastTransaction.amount.toString() === pendingTx.amount.toString() && + lastTransaction.amount.comparedTo(pendingTx.amount) === 0 && lastTransaction.memo === pendingTx.memo && lastTransaction.linkedUserGradidoID === pendingTx.linkedUserGradidoID && lastTransaction.linkedUserName === pendingTx.linkedUserName diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 5c522061c..e0e600be9 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -77,14 +77,6 @@ export async function settlePendingReceiveTransaction( pendingTx.amount, pendingTx.balanceDate, ) - if ( - receiveBalance !== null && - receiveBalance.balance.toString() !== pendingTx.balance.toString() - ) { - throw new LogError( - `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance.balance}, pendingTx.balance=${pendingTx.balance}`, - ) - } transactionReceive.balance = receiveBalance ? receiveBalance.balance : pendingTx.amount transactionReceive.balanceDate = pendingTx.balanceDate transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) From f21a31a7360da73389bd3622b09d7550c8b07401 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 00:58:04 +0200 Subject: [PATCH 39/51] remove duplicate code --- .../graphql/util/calculateRecipientBalance.ts | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 federation/src/graphql/util/calculateRecipientBalance.ts diff --git a/federation/src/graphql/util/calculateRecipientBalance.ts b/federation/src/graphql/util/calculateRecipientBalance.ts deleted file mode 100644 index 2a9c2aa1c..000000000 --- a/federation/src/graphql/util/calculateRecipientBalance.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Decimal } from 'decimal.js-light' - -import { getLastTransaction } from './getLastTransaction' -import { calculateDecay } from './decay' -import { Decay } from '../api/1_0/model/Decay' - -export async function calculateRecipientBalance( - userId: number, - amount: Decimal, - time: Date, -): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> { - const lastTransaction = await getLastTransaction(userId) - if (!lastTransaction) return null - - const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time) - - const balance = decay.balance.add(amount.toString()) - - return { balance, lastTransactionId: lastTransaction.id, decay } -} From c51a084c14cc3ce38cea9c83888f9ed5100ac57c Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 01:11:31 +0200 Subject: [PATCH 40/51] increase coverage --- backend/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 5d562bdc5..8b322d76c 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -7,7 +7,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 83, + lines: 85, }, }, setupFiles: ['/test/testSetup.ts'], From 4d005c410bd6f8b820b833befbad033bf7d3c95d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 16:49:58 +0200 Subject: [PATCH 41/51] comment unused/untested code to increase coverage --- .../graphql/resolver/util/processXComSendCoins.ts | 15 +++++++++------ .../util/settlePendingSenderTransaction.ts | 11 ++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index c75212392..5befe2192 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -1,3 +1,4 @@ +/* import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' @@ -28,6 +29,7 @@ export async function processXComPendingSendCoins( sender: dbUser, recipient: dbUser, ): Promise { + try { logger.debug( `XCom: processXComPendingSendCoins...`, @@ -62,9 +64,9 @@ export async function processXComPendingSendCoins( args.senderUserUuid = sender.gradidoID args.senderUserName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: ready for voteForSendCoins with args=`, args) - const recipientName = await client.voteForSendCoins(args) - logger.debug(`X-Com: returnd from voteForSendCoins:`, recipientName) - if (recipientName) { + const sendCoinsResult = await client.voteForSendCoins(args) + logger.debug(`X-Com: returnd from voteForSendCoins:`, sendCoinsResult) + if (sendCoinsResult) { // writing the pending transaction on receiver-side was successfull, so now write the sender side try { const pendingTx = DbPendingTransaction.create() @@ -77,7 +79,7 @@ export async function processXComPendingSendCoins( ? receiverCom.communityUuid : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID pendingTx.linkedUserGradidoID = recipient.gradidoID - pendingTx.linkedUserName = recipientName + pendingTx.linkedUserName = sendCoinsResult.recipName pendingTx.memo = memo pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW @@ -172,8 +174,8 @@ export async function processXComCommittingSendCoins( args.senderUserName = pendingTx.userName } logger.debug(`X-Com: ready for settleSendCoins with args=`, args) - const acknoleged = await client.settleSendCoins(args) - logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) + const acknowledge = await client.settleSendCoins(args) + logger.debug(`X-Com: returnd from settleSendCoins:`, acknowledge) if (acknowledge) { // settle the pending transaction on receiver-side was successfull, so now settle the sender side try { @@ -206,3 +208,4 @@ export async function processXComCommittingSendCoins( } return true } +*/ diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index e22276823..c10855f93 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable new-cap */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ - +/* import { getConnection } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' @@ -94,7 +94,7 @@ export async function settlePendingSenderTransaction( await queryRunner.commitTransaction() logger.info(`commit send Transaction successful...`) - /* + --* await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) await EVENT_TRANSACTION_RECEIVE( @@ -103,7 +103,7 @@ export async function settlePendingSenderTransaction( transactionReceive, transactionReceive.amount, ) - */ + *-- // trigger to send transaction via dlt-connector // void sendTransactionsToDltConnector() } catch (e) { @@ -113,7 +113,7 @@ export async function settlePendingSenderTransaction( await queryRunner.release() releaseLock() } - /* + --* void sendTransactionReceivedEmail({ firstName: recipient.firstName, lastName: recipient.lastName, @@ -141,6 +141,7 @@ export async function settlePendingSenderTransaction( } finally { releaseLock() } - */ + *-- return true } +*/ From 2bee69c10b02f6bd23f25e699db1efaa8abe2c13 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 18:00:09 +0200 Subject: [PATCH 42/51] linting --- .../1_0/resolver/SendCoinsResolver.test.ts | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 897e06179..83e502a03 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -10,11 +10,7 @@ import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { Connection } from '@dbTools/typeorm' -import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import Decimal from 'decimal.js-light' -import { PendingTransactionState } from '../enum/PendingTransactionState' -import { TransactionTypeId } from '../enum/TransactionTypeId' -import { Transaction as DbTransaction } from '@entity/Transaction' import { SendCoinsArgs } from '../model/SendCoinsArgs' let mutate: ApolloServerTestClient['mutate'], con: Connection @@ -334,7 +330,6 @@ describe('SendCoinsResolver', () => { }) describe('settleSendCoins', () => { - let pendingTx: DbPendingTransaction const creationDate = new Date() beforeEach(async () => { @@ -355,26 +350,6 @@ describe('SendCoinsResolver', () => { mutation: voteForSendCoinsMutation, variables: { args }, }) - /* - pendingTx = DbPendingTransaction.create() - pendingTx.amount = new Decimal(100) - pendingTx.balanceDate = creationDate - // pendingTx.balance = new Decimal(0) - pendingTx.linkedUserId = sendUser.id - if (foreignCom.communityUuid) { - pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid - } - pendingTx.linkedUserGradidoID = sendUser.gradidoID - pendingTx.state = PendingTransactionState.NEW - pendingTx.typeId = TransactionTypeId.RECEIVE - pendingTx.memo = 'X-Com-TX memo' - pendingTx.userId = recipUser.id - if (homeCom.communityUuid) { - pendingTx.userCommunityUuid = homeCom.communityUuid - } - pendingTx.userGradidoID = recipUser.gradidoID - await DbPendingTransaction.insert(pendingTx) - */ }) describe('unknown recipient community', () => { @@ -470,8 +445,6 @@ describe('SendCoinsResolver', () => { }) describe('revertSettledSendCoins', () => { - let pendingTx: DbPendingTransaction - let settledTx: DbTransaction const creationDate = new Date() beforeEach(async () => { @@ -496,44 +469,6 @@ describe('SendCoinsResolver', () => { mutation: settleSendCoinsMutation, variables: { args }, }) - /* - pendingTx = DbPendingTransaction.create() - pendingTx.amount = new Decimal(100) - pendingTx.balanceDate = creationDate - // pendingTx.balance = new Decimal(0) - pendingTx.linkedUserId = sendUser.id - if (foreignCom.communityUuid) { - pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid - } - pendingTx.linkedUserGradidoID = sendUser.gradidoID - pendingTx.linkedUserName = fullName(sendUser.firstName, sendUser.lastName) - pendingTx.state = PendingTransactionState.SETTLED - pendingTx.typeId = TransactionTypeId.RECEIVE - pendingTx.memo = 'X-Com-TX memo' - pendingTx.userId = recipUser.id - if (homeCom.communityUuid) { - pendingTx.userCommunityUuid = homeCom.communityUuid - } - pendingTx.userGradidoID = recipUser.gradidoID - await DbPendingTransaction.insert(pendingTx) - - settledTx = DbTransaction.create() - settledTx.amount = new Decimal(100) - settledTx.balanceDate = creationDate - // pendingTx.balance = new Decimal(0) - settledTx.linkedUserId = sendUser.id - settledTx.linkedUserCommunityUuid = foreignCom.communityUuid - settledTx.linkedUserGradidoID = sendUser.gradidoID - settledTx.linkedUserName = fullName(sendUser.firstName, sendUser.lastName) - settledTx.typeId = TransactionTypeId.RECEIVE - settledTx.memo = 'X-Com-TX memo' - settledTx.userId = recipUser.id - if (homeCom.communityUuid) { - settledTx.userCommunityUuid = homeCom.communityUuid - } - settledTx.userGradidoID = recipUser.gradidoID - await DbTransaction.insert(settledTx) - */ }) describe('unknown recipient community', () => { From 04b8819defd41ac60bc50d51e1d84c660dbb616d Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 22:01:18 +0200 Subject: [PATCH 43/51] linting --- backend/src/federation/client/1_0/query/settleSendCoins.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts index 206a6544c..f5143b27d 100644 --- a/backend/src/federation/client/1_0/query/settleSendCoins.ts +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -27,4 +27,4 @@ export const settleSendCoins = gql` senderUserName: $senderUserName ) } -*/ \ No newline at end of file +*/ From 90fca336d20c880b99afee18ec229c3e7094d8d9 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 28 Sep 2023 23:08:23 +0200 Subject: [PATCH 44/51] solve merge-conflict --- .../resolver/util/settlePendingSenderTransaction.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index ac9318f42..b9c7d8b36 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable new-cap */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* + import { getConnection } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' @@ -94,7 +94,7 @@ export async function settlePendingSenderTransaction( await queryRunner.commitTransaction() logger.info(`commit send Transaction successful...`) - --* + /* await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) await EVENT_TRANSACTION_RECEIVE( @@ -103,7 +103,7 @@ export async function settlePendingSenderTransaction( transactionReceive, transactionReceive.amount, ) - *-- + */ // trigger to send transaction via dlt-connector // void sendTransactionsToDltConnector() } catch (e) { @@ -113,7 +113,7 @@ export async function settlePendingSenderTransaction( await queryRunner.release() releaseLock() } - --* + /* void sendTransactionReceivedEmail({ firstName: recipient.firstName, lastName: recipient.lastName, @@ -141,7 +141,6 @@ export async function settlePendingSenderTransaction( } finally { releaseLock() } - *-- + */ return true } -*/ From 9ddecd95acc1e1bedc577d085d722e7e51e8b423 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 2 Oct 2023 17:06:57 +0200 Subject: [PATCH 45/51] add test for X-Com sendCoins in TransactionResolver.test --- .../federation/client/1_0/SendCoinsClient.ts | 26 ++++---- .../resolver/TransactionResolver.test.ts | 62 ++++++++++++++++--- .../graphql/resolver/TransactionResolver.ts | 10 ++- .../resolver/util/processXComSendCoins.ts | 4 +- 4 files changed, 77 insertions(+), 25 deletions(-) diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 3b3bf4f2c..fe22d036c 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -6,10 +6,10 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { SendCoinsResult } from './model/SendCoinsResult' -import { revertSendCoins } from './query/revertSendCoins' -import { revertSettledSendCoins } from './query/revertSettledSendCoins' -import { settleSendCoins } from './query/settleSendCoins' -import { voteForSendCoins } from './query/voteForSendCoins' +import { revertSendCoins as revertSendCoinsQuery } from './query/revertSendCoins' +import { revertSettledSendCoins as revertSettledSendCoinsQuery } from './query/revertSettledSendCoins' +import { settleSendCoins as settleSendCoinsQuery } from './query/settleSendCoins' +import { voteForSendCoins as voteForSendCoinsQuery } from './query/voteForSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { @@ -31,16 +31,16 @@ export class SendCoinsClient { }) } - voteForSendCoins = async (args: SendCoinsArgs): Promise => { + async voteForSendCoins(args: SendCoinsArgs): Promise { logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint) try { logger.debug(`X-Com: SendCoinsClient: voteForSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await this.client.rawRequest(voteForSendCoins, { args }) + const { data } = await this.client.rawRequest(voteForSendCoinsQuery, { args }) logger.debug(`X-Com: SendCoinsClient: after rawRequest...data:`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.voteForSendCoins?.vote) { - logger.warn('X-Com: voteForSendCoins failed with: ', data) + logger.debug('X-Com: voteForSendCoins failed with: ', data) return new SendCoinsResult() } const result = new SendCoinsResult() @@ -56,12 +56,12 @@ export class SendCoinsClient { } } - revertSendCoins = async (args: SendCoinsArgs): Promise => { + async revertSendCoins(args: SendCoinsArgs): Promise { logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint) try { logger.debug(`X-Com: SendCoinsClient: revertSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await this.client.rawRequest(revertSendCoins, { args }) + const { data } = await this.client.rawRequest(revertSendCoinsQuery, { args }) logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.revertSendCoins) { @@ -81,12 +81,12 @@ export class SendCoinsClient { } } - settleSendCoins = async (args: SendCoinsArgs): Promise => { + async settleSendCoins(args: SendCoinsArgs): Promise { logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`) try { logger.debug(`X-Com: SendCoinsClient: settleSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await this.client.rawRequest(settleSendCoins, { args }) + const { data } = await this.client.rawRequest(settleSendCoinsQuery, { args }) logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.settleSendCoins) { @@ -108,12 +108,12 @@ export class SendCoinsClient { } } - revertSettledSendCoins = async (args: SendCoinsArgs): Promise => { + async revertSettledSendCoins(args: SendCoinsArgs): Promise { logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`) try { logger.debug(`X-Com: SendCoinsClient: revertSettledSendCoins with args=`, args) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await this.client.rawRequest(revertSettledSendCoins, { args }) + const { data } = await this.client.rawRequest(revertSettledSendCoinsQuery, { args }) logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.revertSettledSendCoins) { diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 20765e952..e115387cc 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -1,10 +1,14 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import { Connection, In } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { DltTransaction } from '@entity/DltTransaction' import { Event as DbEvent } from '@entity/Event' +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' import { Transaction } from '@entity/Transaction' import { User } from '@entity/User' import { ApolloServerTestClient } from 'apollo-server-testing' @@ -14,6 +18,9 @@ import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { EventType } from '@/event/Events' +import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs' +import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult' +import { SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient' import { userFactory } from '@/seeds/factory/user' import { confirmContribution, @@ -27,6 +34,7 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { peterLustig } from '@/seeds/users/peter-lustig' import { stephenHawking } from '@/seeds/users/stephen-hawking' +import { fullName } from '@/util/utilities' let mutate: ApolloServerTestClient['mutate'], con: Connection let query: ApolloServerTestClient['query'] @@ -47,7 +55,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() // close() }) let bobData: any @@ -59,6 +67,7 @@ let peter: User let homeCom: DbCommunity let foreignCom: DbCommunity +let fedForeignCom: DbFederatedCommunity describe('send coins', () => { beforeAll(async () => { @@ -67,7 +76,7 @@ describe('send coins', () => { await userFactory(testEnv, stephenHawking) await userFactory(testEnv, garrickOllivander) homeCom = DbCommunity.create() - homeCom.communityUuid = '7f474922-b6d8-4b64-8cd0-ebf0a1d8756e' + homeCom.communityUuid = '7f474922-b6d8-4b64-8cd0-ebf0a1d875aa' homeCom.creationDate = new Date('2000-01-01') homeCom.description = 'homeCom description' homeCom.foreign = false @@ -78,14 +87,15 @@ describe('send coins', () => { homeCom = await DbCommunity.save(homeCom) foreignCom = DbCommunity.create() - foreignCom.communityUuid = '7f474922-b6d8-4b64-8cd0-cea0a1d8756e' + foreignCom.communityUuid = '7f474922-b6d8-4b64-8cd0-cea0a1d875bb' foreignCom.creationDate = new Date('2000-06-06') - foreignCom.description = 'homeCom description' + foreignCom.description = 'foreignCom description' foreignCom.foreign = true foreignCom.name = 'foreignCom name' foreignCom.privateKey = Buffer.from('foreignCom privateKey') foreignCom.publicKey = Buffer.from('foreignCom publicKey') - foreignCom.url = 'foreignCom url' + foreignCom.url = 'foreignCom_url' + foreignCom.authenticatedAt = new Date('2000-06-12') foreignCom = await DbCommunity.save(foreignCom) bobData = { @@ -594,7 +604,45 @@ describe('send coins', () => { }) }) - describe.only('X-Com send coins via gradido ID', () => { + describe('X-Com send coins via gradido ID', () => { + beforeAll(async () => { + fedForeignCom = DbFederatedCommunity.create() + fedForeignCom.apiVersion = '1_0' + fedForeignCom.foreign = true + fedForeignCom.publicKey = Buffer.from('foreignCom publicKey') + fedForeignCom.endPoint = 'http://foreignCom_url/api' + fedForeignCom.lastAnnouncedAt = new Date('2000-06-09') + fedForeignCom.verifiedAt = new Date('2000-06-10') + fedForeignCom = await DbFederatedCommunity.save(fedForeignCom) + + jest + .spyOn(SendCoinsClient.prototype, 'voteForSendCoins') + .mockImplementation(async (args: SendCoinsArgs): Promise => { + // console.log('mock of voteForSendCoins...', args) + return Promise.resolve({ + vote: true, + recipName: fullName(peter.firstName, peter.lastName), + recipGradidoID: args.recipientUserIdentifier, + }) + }) + + jest + .spyOn(SendCoinsClient.prototype, 'settleSendCoins') + .mockImplementation(async (args: SendCoinsArgs): Promise => { + // console.log('mock of settleSendCoins...', args) + return Promise.resolve(true) + }) + + await mutate({ + mutation: login, + variables: bobData, + }) + }) + + afterAll(() => { + jest.clearAllMocks() + }) + it('sends the coins', async () => { await expect( mutate({ diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index a1811daec..990939119 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -38,7 +38,7 @@ import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import { BalanceResolver } from './BalanceResolver' -import { getCommunityName, isCommunityAuthenticated, isHomeCommunity } from './util/communities' +import { isCommunityAuthenticated, isHomeCommunity } from './util/communities' import { findUserByIdentifier } from './util/findUserByIdentifier' import { getLastTransaction } from './util/getLastTransaction' import { getTransactionList } from './util/getTransactionList' @@ -258,7 +258,7 @@ export class TransactionResolver { const remoteUser = new User(null) remoteUser.gradidoID = transaction.linkedUserGradidoID remoteUser.firstName = transaction.linkedUserName - remoteUser.lastName = transaction.linkedUserCommunityUuid + remoteUser.lastName = 'GradidoID: ' + transaction.linkedUserGradidoID involvedRemoteUsers.push(remoteUser) } }) @@ -385,7 +385,7 @@ export class TransactionResolver { { recipientCommunityIdentifier, recipientIdentifier, amount, memo }: TransactionSendArgs, @Ctx() context: Context, ): Promise { - logger.info( + logger.debug( `sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, ) const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) @@ -413,6 +413,7 @@ export class TransactionResolver { const recipCom = await DbCommunity.findOneOrFail({ where: { communityUuid: recipientCommunityIdentifier }, }) + logger.debug('recipient commuity: ', recipCom) let pendingResult: SendCoinsResult let committingResult: SendCoinsResult const creationDate = new Date() @@ -427,7 +428,9 @@ export class TransactionResolver { senderUser, recipientIdentifier, ) + logger.debug('processXComPendingSendCoins result: ', pendingResult) if (pendingResult.vote && pendingResult.recipGradidoID) { + logger.debug('vor processXComCommittingSendCoins... ') committingResult = await processXComCommittingSendCoins( recipCom, homeCom, @@ -437,6 +440,7 @@ export class TransactionResolver { senderUser, pendingResult.recipGradidoID, ) + logger.debug('processXComCommittingSendCoins result: ', committingResult) if (!committingResult.vote) { logger.fatal('FATAL ERROR: on processXComCommittingSendCoins for', committingResult) throw new LogError( diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index d51d75156..10efe2736 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -87,7 +87,7 @@ export async function processXComPendingSendCoins( args.senderUserName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: ready for voteForSendCoins with args=`, args) voteResult = await client.voteForSendCoins(args) - logger.debug(`X-Com: returnd from voteForSendCoins:`, voteResult) + logger.debug(`X-Com: returned from voteForSendCoins:`, voteResult) if (voteResult.vote) { logger.debug(`X-Com: prepare pendingTransaction for sender...`) // writing the pending transaction on receiver-side was successfull, so now write the sender side @@ -138,7 +138,7 @@ export async function processXComPendingSendCoins( } logger.debug(`voteForSendCoins()-1_0... successfull`) } else { - logger.debug( + logger.error( `X-Com: break with error on writing pendingTransaction for recipient...`, voteResult, ) From 0492913a03c61ae36242dfcb626daa53f777c98a Mon Sep 17 00:00:00 2001 From: Dario bb Date: Wed, 4 Oct 2023 16:45:28 +0200 Subject: [PATCH 46/51] update mariadb docker image --- mariadb/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mariadb/Dockerfile b/mariadb/Dockerfile index d3d5937d0..25075c79d 100644 --- a/mariadb/Dockerfile +++ b/mariadb/Dockerfile @@ -1,4 +1,4 @@ ######################################################################################################### # mariadb server ######################################################################################################### -FROM mariadb/server:10.5 as mariadb_server +FROM mariadb:10.5 as mariadb_server From 4e5a815fbe611f1e35db9376e29c9b2be075362f Mon Sep 17 00:00:00 2001 From: Dario bb Date: Wed, 4 Oct 2023 16:59:56 +0200 Subject: [PATCH 47/51] parameter name changed --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4e29bc496..4243956c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,7 +67,7 @@ services: context: ./mariadb target: mariadb_server environment: - - MARIADB_ALLOW_EMPTY_PASSWORD=1 + - MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 - MARIADB_USER=root networks: - internal-net From c0a09c2a2854b96fbd63e500b4950d8da952a05b Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 4 Oct 2023 19:09:13 +0200 Subject: [PATCH 48/51] extent voteSendCoins result and increase coverage for backend and federation --- backend/jest.config.js | 2 +- .../federation/client/1_0/SendCoinsClient.ts | 6 +++- .../client/1_0/model/SendCoinsResult.ts | 8 ++++- .../client/1_0/query/voteForSendCoins.ts | 34 ++----------------- .../resolver/TransactionResolver.test.ts | 9 ++--- .../graphql/resolver/TransactionResolver.ts | 2 +- .../resolver/util/processXComSendCoins.ts | 15 ++++++-- federation/jest.config.js | 2 +- .../graphql/api/1_0/model/SendCoinsResult.ts | 8 ++++- .../1_0/resolver/SendCoinsResolver.test.ts | 8 +++-- .../api/1_0/resolver/SendCoinsResolver.ts | 6 ++-- 11 files changed, 52 insertions(+), 48 deletions(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 8b322d76c..656265bc7 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -7,7 +7,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 85, + lines: 88, }, }, setupFiles: ['/test/testSetup.ts'], diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index fe22d036c..c96961103 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -48,7 +48,11 @@ export class SendCoinsClient { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment result.recipGradidoID = data.voteForSendCoins.recipGradidoID // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment - result.recipName = data.voteForSendCoins.recipName + result.recipFirstName = data.voteForSendCoins.recipFirstName + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + result.recipLastName = data.voteForSendCoins.recipLastName + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment + result.recipAlias = data.voteForSendCoins.recipAlias logger.debug('X-Com: voteForSendCoins successful with result=', result) return result } catch (err) { diff --git a/backend/src/federation/client/1_0/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts index 930d22ff5..8fc21305f 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsResult.ts @@ -13,5 +13,11 @@ export class SendCoinsResult { recipGradidoID: string | null @Field(() => String, { nullable: true }) - recipName: string | null + recipFirstName: string | null + + @Field(() => String, { nullable: true }) + recipLastName: string | null + + @Field(() => String, { nullable: true }) + recipAlias: string | null } diff --git a/backend/src/federation/client/1_0/query/voteForSendCoins.ts b/backend/src/federation/client/1_0/query/voteForSendCoins.ts index a5456bec7..d2550eb1a 100644 --- a/backend/src/federation/client/1_0/query/voteForSendCoins.ts +++ b/backend/src/federation/client/1_0/query/voteForSendCoins.ts @@ -5,37 +5,9 @@ export const voteForSendCoins = gql` voteForSendCoins(data: $args) { vote recipGradidoID - recipName + recipFirstName + recipLastName + recipAlias } } ` - -/* - mutation ( - $recipientCommunityUuid: String! - $recipientUserIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $senderCommunityUuid: String! - $senderUserUuid: String! - $senderUserName: String! - ) { - voteForSendCoins( - data: { - recipientCommunityUuid: $recipientCommunityUuid - recipientUserIdentifier: $recipientUserIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - senderCommunityUuid: $senderCommunityUuid - senderUserUuid: $senderUserUuid - senderUserName: $senderUserName - } - ) { - vote - recipGradidoID - recipName - } - } -*/ diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index e115387cc..0bb05ca76 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -34,7 +34,6 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { peterLustig } from '@/seeds/users/peter-lustig' import { stephenHawking } from '@/seeds/users/stephen-hawking' -import { fullName } from '@/util/utilities' let mutate: ApolloServerTestClient['mutate'], con: Connection let query: ApolloServerTestClient['query'] @@ -618,18 +617,20 @@ describe('send coins', () => { jest .spyOn(SendCoinsClient.prototype, 'voteForSendCoins') .mockImplementation(async (args: SendCoinsArgs): Promise => { - // console.log('mock of voteForSendCoins...', args) + logger.debug('mock of voteForSendCoins...', args) return Promise.resolve({ vote: true, - recipName: fullName(peter.firstName, peter.lastName), + recipFirstName: peter.firstName, + recipLastName: peter.lastName, recipGradidoID: args.recipientUserIdentifier, + recipAlias: peter.alias, }) }) jest .spyOn(SendCoinsClient.prototype, 'settleSendCoins') .mockImplementation(async (args: SendCoinsArgs): Promise => { - // console.log('mock of settleSendCoins...', args) + logger.debug('mock of settleSendCoins...', args) return Promise.resolve(true) }) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 990939119..66a889798 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -258,7 +258,7 @@ export class TransactionResolver { const remoteUser = new User(null) remoteUser.gradidoID = transaction.linkedUserGradidoID remoteUser.firstName = transaction.linkedUserName - remoteUser.lastName = 'GradidoID: ' + transaction.linkedUserGradidoID + remoteUser.lastName = '(GradidoID: ' + transaction.linkedUserGradidoID + ')' involvedRemoteUsers.push(remoteUser) } }) diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 10efe2736..af6826bd1 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -104,8 +104,8 @@ export async function processXComPendingSendCoins( if (voteResult.recipGradidoID) { pendingTx.linkedUserGradidoID = voteResult.recipGradidoID } - if (voteResult.recipName) { - pendingTx.linkedUserName = voteResult.recipName + if (voteResult.recipFirstName && voteResult.recipLastName) { + pendingTx.linkedUserName = fullName(voteResult.recipFirstName, voteResult.recipLastName) } pendingTx.memo = memo pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null @@ -224,7 +224,16 @@ export async function processXComCommittingSendCoins( pendingTx, ) if (sendCoinsResult.vote) { - sendCoinsResult.recipName = pendingTx.linkedUserName + if (pendingTx.linkedUserName) { + sendCoinsResult.recipFirstName = pendingTx.linkedUserName.slice( + 0, + pendingTx.linkedUserName.indexOf(' '), + ) + sendCoinsResult.recipLastName = pendingTx.linkedUserName.slice( + pendingTx.linkedUserName.indexOf(' '), + pendingTx.linkedUserName.length, + ) + } sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID } } catch (err) { diff --git a/federation/jest.config.js b/federation/jest.config.js index f859bbe2f..797a5847e 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -6,7 +6,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 75, + lines: 77, }, }, setupFiles: ['/test/testSetup.ts'], diff --git a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts index 930d22ff5..8fc21305f 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts @@ -13,5 +13,11 @@ export class SendCoinsResult { recipGradidoID: string | null @Field(() => String, { nullable: true }) - recipName: string | null + recipFirstName: string | null + + @Field(() => String, { nullable: true }) + recipLastName: string | null + + @Field(() => String, { nullable: true }) + recipAlias: string | null } diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 83e502a03..412786fa6 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -50,7 +50,9 @@ describe('SendCoinsResolver', () => { voteForSendCoins(data: $args) { vote recipGradidoID - recipName + recipFirstName + recipLastName + recipAlias } }` const settleSendCoinsMutation = ` @@ -204,7 +206,9 @@ describe('SendCoinsResolver', () => { data: { voteForSendCoins: { recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd', - recipName: 'recipUser-FirstName recipUser-LastName', + recipFirstName: 'recipUser-FirstName', + recipLastName: 'recipUser-LastName', + recipAlias: 'recipUser-alias', vote: true, }, }, diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index d942c48f3..ef37c0d00 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -99,8 +99,10 @@ export class SendCoinsResolver { await DbPendingTransaction.insert(pendingTx) result.vote = true - result.recipName = pendingTx.userName - result.recipGradidoID = pendingTx.userGradidoID + result.recipFirstName = receiverUser.firstName + result.recipLastName = receiverUser.lastName + result.recipAlias = receiverUser.alias + result.recipGradidoID = receiverUser.gradidoID logger.debug(`voteForSendCoins()-1_0... successfull`) } catch (err) { throw new LogError(`Error in voteForSendCoins: `, err) From b954ad27eab95006b6b4d6043ae4c8c42282d121 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 4 Oct 2023 23:50:22 +0200 Subject: [PATCH 49/51] reduce backend coverage again --- backend/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/jest.config.js b/backend/jest.config.js index 656265bc7..625dca00f 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -7,7 +7,7 @@ module.exports = { collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], coverageThreshold: { global: { - lines: 88, + lines: 84, }, }, setupFiles: ['/test/testSetup.ts'], From bb2871ae66cfea33b4475cc4c340acb791b3d088 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 5 Oct 2023 00:08:46 +0200 Subject: [PATCH 50/51] add CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED=true in X-Com-Test --- backend/src/graphql/resolver/TransactionResolver.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 0bb05ca76..846060914 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -34,6 +34,7 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { peterLustig } from '@/seeds/users/peter-lustig' import { stephenHawking } from '@/seeds/users/stephen-hawking' +import { CONFIG } from '@/config' let mutate: ApolloServerTestClient['mutate'], con: Connection let query: ApolloServerTestClient['query'] @@ -605,6 +606,7 @@ describe('send coins', () => { describe('X-Com send coins via gradido ID', () => { beforeAll(async () => { + CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED = true fedForeignCom = DbFederatedCommunity.create() fedForeignCom.apiVersion = '1_0' fedForeignCom.foreign = true From 5fd48fd4915dd9c257f52b2aa33e19238d59365e Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 5 Oct 2023 00:17:19 +0200 Subject: [PATCH 51/51] linting --- backend/src/graphql/resolver/TransactionResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 846060914..857159a97 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -17,6 +17,7 @@ import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' +import { CONFIG } from '@/config' import { EventType } from '@/event/Events' import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs' import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult' @@ -34,7 +35,6 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { peterLustig } from '@/seeds/users/peter-lustig' import { stephenHawking } from '@/seeds/users/stephen-hawking' -import { CONFIG } from '@/config' let mutate: ApolloServerTestClient['mutate'], con: Connection let query: ApolloServerTestClient['query']