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/backend/src/config/index.ts b/backend/src/config/index.ts index 98a0b8323..a77840738 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -122,7 +122,7 @@ if ( } const federation = { - FEDERATION_BACKEND_SEND_ON_API: process.env.FEDERATION_BACKEND_SEND_ON_API || '1_0', + 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/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts index 3eb1419b5..d3ce76392 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 + recipGradidoID: string | null | undefined @Field(() => String, { nullable: true }) - recipName: string | null + recipName: string | null | undefined } 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 baadd5c55..00834830b 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,7 +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 { 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' @@ -41,22 +41,22 @@ 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 { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' import { transactionLinkSummary } from './util/transactionLinkSummary' -import { processXComPendingSendCoins } from './util/processXComSendCoins' +import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult' export const executeTransaction = async ( amount: Decimal, 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: [ @@ -101,15 +101,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) @@ -127,15 +121,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 @@ -523,8 +511,7 @@ export class TransactionResolver { logger.info( `sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, ) - const homeCom = await dbCommunity.findOneOrFail({ where: { foreign: false } }) - + const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const senderUser = getUser(context) if (!recipientCommunityIdentifier || (await isHomeCommunity(recipientCommunityIdentifier))) { @@ -535,7 +522,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) } else { // processing a x-community sendCoins @@ -546,17 +533,40 @@ export class TransactionResolver { if (!(await isCommunityAuthenticated(recipientCommunityIdentifier))) { throw new LogError('recipient commuity is connected, but still not authenticated yet!') } - const recipCom = await dbCommunity.findOneOrFail({ + const recipCom = await DbCommunity.findOneOrFail({ where: { communityUuid: recipientCommunityIdentifier }, }) - await processXComPendingSendCoins( - recipCom, - homeCom, - amount, - memo, - senderUser, - recipientIdentifier, - ) + let pendingResult: SendCoinsResult + let commitingResult: SendCoinsResult + const creationDate = new Date() + + try { + pendingResult = await processXComPendingSendCoins( + recipCom, + homeCom, + creationDate, + amount, + memo, + senderUser, + recipientIdentifier, + ) + if(pendingResult.vote && pendingResult.recipGradidoID) { + commitingResult = await processXComCommittingSendCoins( + recipCom, + homeCom, + creationDate, + amount, + memo, + senderUser, + pendingResult.recipGradidoID, + ) + if(!commitingResult.vote) { + + } + } + } catch (err) { + + } } return true } diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 2512d69c6..5809a862a 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -6,6 +6,7 @@ import { Decimal } from 'decimal.js-light' import { CONFIG } from '@/config' import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs' +import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult' // eslint-disable-next-line camelcase import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient' import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory' @@ -21,11 +22,13 @@ import { settlePendingSenderTransaction } from './settlePendingSenderTransaction export async function processXComPendingSendCoins( receiverCom: DbCommunity, senderCom: DbCommunity, + creationDate: Date, amount: Decimal, memo: string, sender: dbUser, recipientIdentifier: string, -): Promise { +): Promise { + let sendCoinsResult = new SendCoinsResult() try { logger.debug( `XCom: processXComPendingSendCoins...`, @@ -36,7 +39,6 @@ export async function processXComPendingSendCoins( sender, 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) { @@ -67,7 +69,7 @@ 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 sendCoinsResult = await client.voteForSendCoins(args) + 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 @@ -81,8 +83,12 @@ export async function processXComPendingSendCoins( if (receiverCom.communityUuid) { pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid } - pendingTx.linkedUserGradidoID = sendCoinsResult.recipGradidoID - pendingTx.linkedUserName = sendCoinsResult.recipName + if (sendCoinsResult.recipGradidoID) { + pendingTx.linkedUserGradidoID = sendCoinsResult.recipGradidoID + } + if (sendCoinsResult.recipName) { + pendingTx.linkedUserName = sendCoinsResult.recipName + } pendingTx.memo = memo pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW @@ -117,23 +123,22 @@ export async function processXComPendingSendCoins( } catch (err) { logger.error(`Error:`, err) } - return true + return sendCoinsResult } export async function processXComCommittingSendCoins( - receiverFCom: DbFederatedCommunity, receiverCom: DbCommunity, senderCom: DbCommunity, creationDate: Date, amount: Decimal, memo: string, sender: dbUser, - recipient: dbUser, -): Promise { + recipUuid: string, +): Promise { + let sendCoinsResult = new SendCoinsResult() try { logger.debug( `XCom: processXComCommittingSendCoins...`, - receiverFCom, receiverCom, senderCom, creationDate, @@ -150,7 +155,7 @@ export async function processXComCommittingSendCoins( linkedUserCommunityUuid: receiverCom.communityUuid ? receiverCom.communityUuid : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID, - linkedUserGradidoID: recipient.gradidoID, + linkedUserGradidoID: recipUuid, typeId: TransactionTypeId.SEND, state: PendingTransactionState.NEW, balanceDate: creationDate, @@ -158,6 +163,12 @@ export async function processXComCommittingSendCoins( }) if (pendingTx) { logger.debug(`X-Com: find pending Tx for settlement:`, pendingTx) + 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) { 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 779da77c9..22e7c36eb 100644 --- a/database/migrations/0072-add_communityuuid_to_transactions_table.ts +++ b/database/migrations/0072-add_communityuuid_to_transactions_table.ts @@ -10,10 +10,14 @@ 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 - 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`, ) @@ -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>) { 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'],