From bf2e7fb79d62a15c05de9569cb4fa4543d611f84 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 6 Dec 2022 11:24:51 +0100 Subject: [PATCH 01/21] await semaphore package --- backend/package.json | 1 + backend/yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/backend/package.json b/backend/package.json index 519f9e6c0..bca7deb6b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,6 +20,7 @@ "dependencies": { "@hyperswarm/dht": "^6.2.0", "apollo-server-express": "^2.25.2", + "await-semaphore": "^0.1.3", "axios": "^0.21.1", "class-validator": "^0.13.1", "cors": "^2.8.5", diff --git a/backend/yarn.lock b/backend/yarn.lock index 940906cfa..82bcd6b1f 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1643,6 +1643,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +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== + axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" From 7c528d5f9bbd163d6e6da3f9877c52afb8ee8f4e Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 6 Dec 2022 11:25:37 +0100 Subject: [PATCH 02/21] use semaphore to lock transactions --- .../graphql/resolver/TransactionResolver.ts | 265 +++++++++--------- 1 file changed, 139 insertions(+), 126 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 3dbd4afb9..67085cc35 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -40,6 +40,11 @@ import { sendTransactionLinkRedeemedEmail } from '@/mailer/sendTransactionLinkRe import { Event, EventTransactionReceive, EventTransactionSend } from '@/event/Event' import { eventProtocol } from '@/event/EventProtocolEmitter' +import { Semaphore } from 'await-semaphore' + +const CONCURRENT_TRANSACTIONS = 1 +const LOCK_TRANSACTIONS = new Semaphore(CONCURRENT_TRANSACTIONS) + export const executeTransaction = async ( amount: Decimal, memo: string, @@ -51,141 +56,149 @@ export const executeTransaction = async ( `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, ) - if (sender.id === recipient.id) { - logger.error(`Sender and Recipient are the same.`) - throw new Error('Sender and Recipient are the same.') - } - - if (memo.length > MEMO_MAX_CHARS) { - logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) - throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) - } - - if (memo.length < MEMO_MIN_CHARS) { - logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) - throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) - } - - // validate amount - const receivedCallDate = new Date() - const sendBalance = await calculateBalance( - sender.id, - amount.mul(-1), - receivedCallDate, - transactionLink, - ) - logger.debug(`calculated Balance=${sendBalance}`) - if (!sendBalance) { - logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`) - throw new Error("user hasn't enough GDD or amount is < 0") - } - - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') - logger.debug(`open Transaction to write...`) + // acquire lock + const releaseLock = await LOCK_TRANSACTIONS.acquire() try { - // transaction - const transactionSend = new dbTransaction() - transactionSend.typeId = TransactionTypeId.SEND - transactionSend.memo = memo - transactionSend.userId = sender.id - transactionSend.linkedUserId = recipient.id - 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.linkedUserId = sender.id - 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, - ) + if (sender.id === recipient.id) { + logger.error(`Sender and Recipient are the same.`) + throw new Error('Sender and Recipient are the same.') } - await queryRunner.commitTransaction() - logger.info(`commit Transaction successful...`) + if (memo.length > MEMO_MAX_CHARS) { + logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) + throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) + } - const eventTransactionSend = new EventTransactionSend() - eventTransactionSend.userId = transactionSend.userId - eventTransactionSend.xUserId = transactionSend.linkedUserId - eventTransactionSend.transactionId = transactionSend.id - eventTransactionSend.amount = transactionSend.amount.mul(-1) - await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend)) + if (memo.length < MEMO_MIN_CHARS) { + logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) + throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) + } - const eventTransactionReceive = new EventTransactionReceive() - eventTransactionReceive.userId = transactionReceive.userId - eventTransactionReceive.xUserId = transactionReceive.linkedUserId - eventTransactionReceive.transactionId = transactionReceive.id - eventTransactionReceive.amount = transactionReceive.amount - await eventProtocol.writeEvent(new Event().setEventTransactionReceive(eventTransactionReceive)) - } catch (e) { - await queryRunner.rollbackTransaction() - logger.error(`Transaction was not successful: ${e}`) - throw new Error(`Transaction was not successful: ${e}`) - } finally { - await queryRunner.release() - } - logger.debug(`prepare Email for transaction received...`) - // send notification email - // TODO: translate - await sendTransactionReceivedEmail({ - senderFirstName: sender.firstName, - senderLastName: sender.lastName, - recipientFirstName: recipient.firstName, - recipientLastName: recipient.lastName, - email: recipient.emailContact.email, - senderEmail: sender.emailContact.email, - amount, - overviewURL: CONFIG.EMAIL_LINK_OVERVIEW, - }) - if (transactionLink) { - await sendTransactionLinkRedeemedEmail({ - senderFirstName: recipient.firstName, - senderLastName: recipient.lastName, - recipientFirstName: sender.firstName, - recipientLastName: sender.lastName, - email: sender.emailContact.email, - senderEmail: recipient.emailContact.email, + // validate amount + const receivedCallDate = new Date() + const sendBalance = await calculateBalance( + sender.id, + amount.mul(-1), + receivedCallDate, + transactionLink, + ) + logger.debug(`calculated Balance=${sendBalance}`) + if (!sendBalance) { + logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`) + throw new Error("user hasn't enough GDD or amount is < 0") + } + + 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.linkedUserId = recipient.id + 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.linkedUserId = sender.id + 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...`) + + const eventTransactionSend = new EventTransactionSend() + eventTransactionSend.userId = transactionSend.userId + eventTransactionSend.xUserId = transactionSend.linkedUserId + eventTransactionSend.transactionId = transactionSend.id + eventTransactionSend.amount = transactionSend.amount.mul(-1) + await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend)) + + const eventTransactionReceive = new EventTransactionReceive() + eventTransactionReceive.userId = transactionReceive.userId + eventTransactionReceive.xUserId = transactionReceive.linkedUserId + eventTransactionReceive.transactionId = transactionReceive.id + eventTransactionReceive.amount = transactionReceive.amount + await eventProtocol.writeEvent( + new Event().setEventTransactionReceive(eventTransactionReceive), + ) + } catch (e) { + await queryRunner.rollbackTransaction() + logger.error(`Transaction was not successful: ${e}`) + throw new Error(`Transaction was not successful: ${e}`) + } finally { + await queryRunner.release() + } + logger.debug(`prepare Email for transaction received...`) + // send notification email + // TODO: translate + await sendTransactionReceivedEmail({ + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + recipientFirstName: recipient.firstName, + recipientLastName: recipient.lastName, + email: recipient.emailContact.email, + senderEmail: sender.emailContact.email, amount, - memo, overviewURL: CONFIG.EMAIL_LINK_OVERVIEW, }) + if (transactionLink) { + await sendTransactionLinkRedeemedEmail({ + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + recipientFirstName: sender.firstName, + recipientLastName: sender.lastName, + email: sender.emailContact.email, + senderEmail: recipient.emailContact.email, + amount, + memo, + overviewURL: CONFIG.EMAIL_LINK_OVERVIEW, + }) + } + logger.info(`finished executeTransaction successfully`) + return true + } finally { + releaseLock() } - logger.info(`finished executeTransaction successfully`) - return true } @Resolver() From 1b31d09278abdb30f74db3cb1c2635e2ad950c23 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 13 Dec 2022 20:42:20 +0100 Subject: [PATCH 03/21] add tests for semaphore --- .../resolver/TransactionResolver.test.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 1d4fe5708..69673d47c 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -368,5 +368,74 @@ describe('send coins', () => { ) }) }) + + describe('more transactions to test semaphore', () => { + it('sends the coins four times in a row', async () => { + await expect( + mutate({ + mutation: sendCoins, + variables: { + email: 'peter@lustig.de', + amount: 50, + memo: 'first transaction', + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + sendCoins: 'true', + }, + }), + ) + await expect( + mutate({ + mutation: sendCoins, + variables: { + email: 'peter@lustig.de', + amount: 50, + memo: 'second transaction', + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + sendCoins: 'true', + }, + }), + ) + await expect( + mutate({ + mutation: sendCoins, + variables: { + email: 'peter@lustig.de', + amount: 50, + memo: 'third transaction', + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + sendCoins: 'true', + }, + }), + ) + await expect( + mutate({ + mutation: sendCoins, + variables: { + email: 'peter@lustig.de', + amount: 50, + memo: 'fourth transaction', + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + sendCoins: 'true', + }, + }), + ) + }) + }) }) }) From 1f765bd3f4d0750d56001870fec6cc779315d855 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 12:41:42 +0100 Subject: [PATCH 04/21] fix find last Transaction --- backend/src/graphql/resolver/TransactionResolver.test.ts | 8 ++++---- backend/src/util/validate.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 69673d47c..6115ef846 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -376,7 +376,7 @@ describe('send coins', () => { mutation: sendCoins, variables: { email: 'peter@lustig.de', - amount: 50, + amount: 10, memo: 'first transaction', }, }), @@ -392,7 +392,7 @@ describe('send coins', () => { mutation: sendCoins, variables: { email: 'peter@lustig.de', - amount: 50, + amount: 20, memo: 'second transaction', }, }), @@ -408,7 +408,7 @@ describe('send coins', () => { mutation: sendCoins, variables: { email: 'peter@lustig.de', - amount: 50, + amount: 30, memo: 'third transaction', }, }), @@ -424,7 +424,7 @@ describe('send coins', () => { mutation: sendCoins, variables: { email: 'peter@lustig.de', - amount: 50, + amount: 40, memo: 'fourth transaction', }, }), diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index edd8d55f6..f182ab7c1 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -24,7 +24,7 @@ async function calculateBalance( time: Date, transactionLink?: dbTransactionLink | null, ): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> { - const lastTransaction = await Transaction.findOne({ userId }, { order: { balanceDate: 'DESC' } }) + const lastTransaction = await Transaction.findOne({ userId }, { order: { id: 'DESC' } }) if (!lastTransaction) return null const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time) From c2c7e345393a5ab14f6f2ecc64ee87ae9cc88557 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 13:05:21 +0100 Subject: [PATCH 05/21] share the lock for transactions via external file. Do all possible checks before acquiring the lock --- .../graphql/resolver/TransactionResolver.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 4433885f5..344a61be1 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -36,10 +36,7 @@ import { BalanceResolver } from './BalanceResolver' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { findUserByEmail } from './UserResolver' -import { Semaphore } from 'await-semaphore' - -const CONCURRENT_TRANSACTIONS = 1 -const LOCK_TRANSACTIONS = new Semaphore(CONCURRENT_TRANSACTIONS) +import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' export const executeTransaction = async ( amount: Decimal, @@ -52,24 +49,25 @@ export const executeTransaction = async ( `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, ) + if (sender.id === recipient.id) { + logger.error(`Sender and Recipient are the same.`) + throw new Error('Sender and Recipient are the same.') + } + + if (memo.length > MEMO_MAX_CHARS) { + logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) + throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) + } + + if (memo.length < MEMO_MIN_CHARS) { + logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) + throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) + } + // acquire lock - const releaseLock = await LOCK_TRANSACTIONS.acquire() + const releaseLock = await TRANSACTIONS_LOCK.acquire() + try { - if (sender.id === recipient.id) { - logger.error(`Sender and Recipient are the same.`) - throw new Error('Sender and Recipient are the same.') - } - - if (memo.length > MEMO_MAX_CHARS) { - logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) - throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) - } - - if (memo.length < MEMO_MIN_CHARS) { - logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) - throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) - } - // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance( From 3d5287558808ccbf90ec96b2f149741f24d9c983 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 13:05:59 +0100 Subject: [PATCH 06/21] external semaphore transactions lock file --- backend/src/util/TRANSACTIONS_LOCK.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 backend/src/util/TRANSACTIONS_LOCK.ts diff --git a/backend/src/util/TRANSACTIONS_LOCK.ts b/backend/src/util/TRANSACTIONS_LOCK.ts new file mode 100644 index 000000000..847386e4d --- /dev/null +++ b/backend/src/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 a98d569fb3e37931d43ffa8578a1a46995c6896c Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 13:06:46 +0100 Subject: [PATCH 07/21] lock also on transactionLink --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 9041aae67..901c5936b 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -31,6 +31,7 @@ import { calculateDecay } from '@/util/decay' import { getUserCreation, validateContribution } from './util/creations' import { executeTransaction } from './TransactionResolver' import QueryLinkResult from '@union/QueryLinkResult' +import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { @@ -168,6 +169,8 @@ export class TransactionLinkResolver { const now = new Date() if (code.match(/^CL-/)) { + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() logger.info('redeem contribution link...') const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() @@ -309,6 +312,7 @@ export class TransactionLinkResolver { throw new Error(`Creation from contribution link was not successful. ${e}`) } finally { await queryRunner.release() + releaseLock() } return true } else { From 95379104135c6b150a4fb26ffc342897a32feddf Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 13:06:55 +0100 Subject: [PATCH 08/21] lock also on contributions --- backend/src/graphql/resolver/ContributionResolver.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 32c72b9b1..a2b1a99fc 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -50,6 +50,7 @@ import { sendContributionConfirmedEmail, sendContributionRejectedEmail, } from '@/emails/sendEmailVariants' +import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' @Resolver() export class ContributionResolver { @@ -581,6 +582,8 @@ export class ContributionResolver { const receivedCallDate = new Date() + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED') @@ -643,6 +646,7 @@ export class ContributionResolver { throw new Error(`Creation was not successful.`) } finally { await queryRunner.release() + releaseLock() } const event = new Event() From cb55da5d329d5ab4a8f11b818f9016ddb4bdc1b9 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 15 Dec 2022 15:58:02 +0100 Subject: [PATCH 09/21] confirming two contributions at once does not throw anymore --- backend/src/graphql/resolver/ContributionResolver.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 387018624..cf2d55d94 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -1961,8 +1961,7 @@ describe('ContributionResolver', () => { }) }) - // In the futrue this should not throw anymore - it('throws an error for the second confirmation', async () => { + it('throws no error for the second confirmation', async () => { const r1 = mutate({ mutation: confirmContribution, variables: { @@ -1982,8 +1981,7 @@ describe('ContributionResolver', () => { ) await expect(r2).resolves.toEqual( expect.objectContaining({ - // data: { confirmContribution: true }, - errors: [new GraphQLError('Creation was not successful.')], + data: { confirmContribution: true }, }), ) }) From d03a3f601c141d090ecfbb093a2d3255482823cd Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 15 Dec 2022 17:00:35 +0100 Subject: [PATCH 10/21] mock semaphore to allow to use jest fake timers --- .../src/graphql/resolver/TransactionLinkResolver.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 28422af26..9f7d30244 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -23,6 +23,11 @@ import { User } from '@entity/User' import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import Decimal from 'decimal.js-light' import { GraphQLError } from 'graphql' +import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' + +// mock semaphore to allow use fake timers +jest.mock('@/util/TRANSACTIONS_LOCK') +TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn()) let mutate: any, query: any, con: any let testEnv: any @@ -185,8 +190,7 @@ describe('TransactionLinkResolver', () => { describe('after one day', () => { beforeAll(async () => { jest.useFakeTimers() - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - setTimeout(() => {}, 1000 * 60 * 60 * 24) + setTimeout(jest.fn(), 1000 * 60 * 60 * 24) jest.runAllTimers() await mutate({ mutation: login, From 20fbaa276f510ef716c236226144e7c8427bb0e7 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 15 Dec 2022 18:35:11 +0100 Subject: [PATCH 11/21] feat(backend): test semaphore --- .../src/graphql/resolver/semaphore.test.ts | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 backend/src/graphql/resolver/semaphore.test.ts diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts new file mode 100644 index 000000000..e334910f1 --- /dev/null +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -0,0 +1,190 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import Decimal from 'decimal.js-light' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { logger } from '@test/testSetup' +import { userFactory } from '@/seeds/factory/user' +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' +import { bobBaumeister } from '@/seeds/users/bob-baumeister' +import { peterLustig } from '@/seeds/users/peter-lustig' +import { creationFactory, nMonthsBefore } from '@/seeds/factory/creation' +import { cleanDB, testEnvironment, contributionDateFormatter } from '@test/helpers' +import { + confirmContribution, + createContribution, + createTransactionLink, + redeemTransactionLink, + login, + createContributionLink, + sendCoins, +} from '@/seeds/graphql/mutations' + +let mutate: any, con: any +let testEnv: any + +beforeAll(async () => { + testEnv = await testEnvironment() + mutate = testEnv.mutate + con = testEnv.con + await cleanDB() +}) + +afterAll(async () => { + await cleanDB() + await con.close() +}) + +describe('semaphore', () => { + let contributionLinkCode = '' + let bobsTransactionLinkCode = '' + let bibisTransactionLinkCode = '' + let bibisOpenContributionId = -1 + let bobsOpenContributionId = -1 + + beforeAll(async () => { + const now = new Date() + await userFactory(testEnv, bibiBloxberg) + await userFactory(testEnv, peterLustig) + await userFactory(testEnv, bobBaumeister) + await creationFactory(testEnv, { + email: 'bibi@bloxberg.de', + amount: 1000, + memo: 'Herzlich Willkommen bei Gradido!', + creationDate: nMonthsBefore(new Date()), + confirmed: true, + }) + await creationFactory(testEnv, { + email: 'bob@baumeister.de', + amount: 1000, + memo: 'Herzlich Willkommen bei Gradido!', + creationDate: nMonthsBefore(new Date()), + confirmed: true, + }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ + mutation: createContributionLink, + variables: { + amount: new Decimal(200), + name: 'Test Contribution Link', + memo: 'Danke für deine Teilnahme an dem Test der Contribution Links', + cycle: 'ONCE', + validFrom: new Date(2022, 5, 18).toISOString(), + validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) + contributionLinkCode = 'CL-' + contributionLink.code + await mutate({ + mutation: login, + variables: { email: 'bob@baumeister.de', password: 'Aa12345_' }, + }) + const { + data: { createTransactionLink: bobsLink }, + } = await mutate({ + mutation: createTransactionLink, + variables: { + email: 'bob@baumeister.de', + amount: 20, + memo: 'Bobs Link', + }, + }) + const { + data: { createContribution: bobsContribution }, + } = await mutate({ + mutation: createContribution, + variables: { + creationDate: contributionDateFormatter(new Date()), + amount: 200, + memo: 'Bobs Contribution', + }, + }) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + const { + data: { createTransactionLink: bibisLink }, + } = await mutate({ + mutation: createTransactionLink, + variables: { + amount: 20, + memo: 'Bibis Link', + }, + }) + const { + data: { createContribution: bibisContribution }, + } = await mutate({ + mutation: createContribution, + variables: { + creationDate: contributionDateFormatter(new Date()), + amount: 200, + memo: 'Bibis Contribution', + }, + }) + bobsTransactionLinkCode = bobsLink.code + bibisTransactionLinkCode = bibisLink.code + bibisOpenContributionId = bibisContribution.id + bobsOpenContributionId = bobsContribution.id + }) + + it('creates a lot of transactions without errors', async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + const bibiRedeemContributionLink = mutate({ + mutation: redeemTransactionLink, + variables: { code: contributionLinkCode }, + }) + const redeemBobsLink = mutate({ + mutation: redeemTransactionLink, + variables: { code: bobsTransactionLinkCode }, + }) + const bibisTransaction = mutate({ + mutation: sendCoins, + variables: { email: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob' }, + }) + await mutate({ + mutation: login, + variables: { email: 'bob@baumeister.de', password: 'Aa12345_' }, + }) + const bobRedeemContributionLink = mutate({ + mutation: redeemTransactionLink, + variables: { code: contributionLinkCode }, + }) + const redeemBibisLink = mutate({ + mutation: redeemTransactionLink, + variables: { code: bibisTransactionLinkCode }, + }) + const bobsTransaction = mutate({ + mutation: sendCoins, + variables: { email: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi' }, + }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + const confirmBibisContribution = mutate({ + mutation: confirmContribution, + variables: { id: bibisOpenContributionId }, + }) + const confirmBobsContribution = mutate({ + mutation: confirmContribution, + variables: { id: bobsOpenContributionId }, + }) + await expect(bibiRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) + await expect(redeemBobsLink).resolves.toMatchObject({ errors: undefined }) + await expect(bibisTransaction).resolves.toMatchObject({ errors: undefined }) + await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) + await expect(redeemBibisLink).resolves.toMatchObject({ errors: undefined }) + await expect(bobsTransaction).resolves.toMatchObject({ errors: undefined }) + await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined }) + await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined }) + }) +}) From f306dddfafd58725668981c9d9036a6d2785b3ae Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 23:06:29 +0100 Subject: [PATCH 12/21] console logs, fix order by id instead of date --- backend/src/graphql/resolver/ContributionResolver.ts | 6 ++++-- backend/src/graphql/resolver/TransactionLinkResolver.ts | 2 ++ backend/src/graphql/resolver/TransactionResolver.ts | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index a2b1a99fc..49c6ea379 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -584,6 +584,7 @@ export class ContributionResolver { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() + console.log(`locked for confirmContribution ${id}`) const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED') @@ -593,7 +594,7 @@ export class ContributionResolver { .select('transaction') .from(DbTransaction, 'transaction') .where('transaction.userId = :id', { id: contribution.userId }) - .orderBy('transaction.balanceDate', 'DESC') + .orderBy('transaction.id', 'DESC') .getOne() logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined') @@ -642,10 +643,11 @@ export class ContributionResolver { }) } catch (e) { await queryRunner.rollbackTransaction() - logger.error(`Creation was not successful: ${e}`) + console.log(`Creation was not successful:`, e) throw new Error(`Creation was not successful.`) } finally { await queryRunner.release() + console.log(`release for confirmContribution ${id}`) releaseLock() } diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 901c5936b..5ab23f2b0 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -171,6 +171,7 @@ export class TransactionLinkResolver { if (code.match(/^CL-/)) { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() + console.log(`locked for redeemTransactionLink ${code}`) logger.info('redeem contribution link...') const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() @@ -312,6 +313,7 @@ export class TransactionLinkResolver { throw new Error(`Creation from contribution link was not successful. ${e}`) } finally { await queryRunner.release() + console.log(`release for redeemTransactionLink ${code}`) releaseLock() } return true diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 344a61be1..51ec0faaa 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -66,6 +66,7 @@ export const executeTransaction = async ( // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() + console.log(`locked for executeTransaction ${amount.toString()} ${recipient.firstName}`) try { // validate amount @@ -189,6 +190,7 @@ export const executeTransaction = async ( logger.info(`finished executeTransaction successfully`) return true } finally { + console.log(`release for executeTransaction ${amount.toString()} ${recipient.firstName}`) releaseLock() } } From 5a925c0526a8c5a2e9677e9829ef543493077616 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 15 Dec 2022 23:08:19 +0100 Subject: [PATCH 13/21] "fixed" semaphore tests --- .../src/graphql/resolver/semaphore.test.ts | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index e334910f1..d3cee5bb6 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -154,10 +154,16 @@ describe('semaphore', () => { mutation: login, variables: { email: 'bob@baumeister.de', password: 'Aa12345_' }, }) - const bobRedeemContributionLink = mutate({ + /* + - "errors": undefined, + + "errors": Array [ + + [GraphQLError: Creation from contribution link was not successful. Error: to < from, reverse decay calculation is invalid], + + ], + */ + /* const bobRedeemContributionLink = mutate({ mutation: redeemTransactionLink, variables: { code: contributionLinkCode }, - }) + }) */ const redeemBibisLink = mutate({ mutation: redeemTransactionLink, variables: { code: bibisTransactionLinkCode }, @@ -170,21 +176,23 @@ describe('semaphore', () => { mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - const confirmBibisContribution = mutate({ + // Creation was not successful: Error: to < from, reverse decay calculation is invalid + /* const confirmBibisContribution = mutate({ mutation: confirmContribution, variables: { id: bibisOpenContributionId }, - }) - const confirmBobsContribution = mutate({ + }) */ + // Creation was not successful: Error: to < from, reverse decay calculation is invalid + /* const confirmBobsContribution = mutate({ mutation: confirmContribution, variables: { id: bobsOpenContributionId }, - }) + }) */ await expect(bibiRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) await expect(redeemBobsLink).resolves.toMatchObject({ errors: undefined }) await expect(bibisTransaction).resolves.toMatchObject({ errors: undefined }) - await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) + // await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) await expect(redeemBibisLink).resolves.toMatchObject({ errors: undefined }) await expect(bobsTransaction).resolves.toMatchObject({ errors: undefined }) - await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined }) - await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined }) + // await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined }) + // await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined }) }) }) From 2f17ec565e850dd7dd7c6e649c2e52a299754e42 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 21 Dec 2022 00:05:50 +0100 Subject: [PATCH 14/21] all tests are running --- .../src/graphql/resolver/semaphore.test.ts | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index d3cee5bb6..e334910f1 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -154,16 +154,10 @@ describe('semaphore', () => { mutation: login, variables: { email: 'bob@baumeister.de', password: 'Aa12345_' }, }) - /* - - "errors": undefined, - + "errors": Array [ - + [GraphQLError: Creation from contribution link was not successful. Error: to < from, reverse decay calculation is invalid], - + ], - */ - /* const bobRedeemContributionLink = mutate({ + const bobRedeemContributionLink = mutate({ mutation: redeemTransactionLink, variables: { code: contributionLinkCode }, - }) */ + }) const redeemBibisLink = mutate({ mutation: redeemTransactionLink, variables: { code: bibisTransactionLinkCode }, @@ -176,23 +170,21 @@ describe('semaphore', () => { mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - // Creation was not successful: Error: to < from, reverse decay calculation is invalid - /* const confirmBibisContribution = mutate({ + const confirmBibisContribution = mutate({ mutation: confirmContribution, variables: { id: bibisOpenContributionId }, - }) */ - // Creation was not successful: Error: to < from, reverse decay calculation is invalid - /* const confirmBobsContribution = mutate({ + }) + const confirmBobsContribution = mutate({ mutation: confirmContribution, variables: { id: bobsOpenContributionId }, - }) */ + }) await expect(bibiRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) await expect(redeemBobsLink).resolves.toMatchObject({ errors: undefined }) await expect(bibisTransaction).resolves.toMatchObject({ errors: undefined }) - // await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) + await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined }) await expect(redeemBibisLink).resolves.toMatchObject({ errors: undefined }) await expect(bobsTransaction).resolves.toMatchObject({ errors: undefined }) - // await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined }) - // await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined }) + await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined }) + await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined }) }) }) From 55236f1f8e9e0443f02db2a1e271501400543724 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 21 Dec 2022 00:08:46 +0100 Subject: [PATCH 15/21] fix another order by `id` instead of `balanceDate`. Have the now calculation for contribution links within the semaphore lock --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 5ab23f2b0..983420e2a 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -166,13 +166,13 @@ export class TransactionLinkResolver { ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) const user = getUser(context) - const now = new Date() if (code.match(/^CL-/)) { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() console.log(`locked for redeemTransactionLink ${code}`) logger.info('redeem contribution link...') + const now = new Date() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') @@ -277,7 +277,7 @@ export class TransactionLinkResolver { .select('transaction') .from(DbTransaction, 'transaction') .where('transaction.userId = :id', { id: user.id }) - .orderBy('transaction.balanceDate', 'DESC') + .orderBy('transaction.id', 'DESC') .getOne() let newBalance = new Decimal(0) @@ -318,6 +318,7 @@ export class TransactionLinkResolver { } return true } else { + const now = new Date() const transactionLink = await DbTransactionLink.findOneOrFail({ code }) const linkedUser = await DbUser.findOneOrFail( { id: transactionLink.userId }, @@ -328,6 +329,9 @@ export class TransactionLinkResolver { throw new Error('Cannot redeem own transaction link.') } + // TODO: The now check should be done within the semaphore lock, + // since the program might wait a while till it is ready to proceed + // writing the transaction. if (transactionLink.validUntil.getTime() < now.getTime()) { throw new Error('Transaction Link is not valid anymore.') } From fb7c61f3b2d5107e99932ce60a53c0a5ff21704f Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 21 Dec 2022 00:16:28 +0100 Subject: [PATCH 16/21] require 77% coverage on backend --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c3238507a..12891851a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -528,7 +528,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 74 + min_coverage: 77 token: ${{ github.token }} ########################################################################## From 32cea45bbf3e52fa35d447ee6e7f4c3ad28ee454 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 21 Dec 2022 00:24:54 +0100 Subject: [PATCH 17/21] move timestamp into semaphore transaction lock --- backend/src/graphql/resolver/ContributionResolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 49c6ea379..4baf3d010 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -580,11 +580,11 @@ export class ContributionResolver { clientTimezoneOffset, ) - const receivedCallDate = new Date() - // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() console.log(`locked for confirmContribution ${id}`) + + const receivedCallDate = new Date() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED') From 6a36d9afb24963624ef1ab3fd404ee3b4bcb3f7d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 22 Dec 2022 14:08:32 +0100 Subject: [PATCH 18/21] remove console logs --- backend/src/graphql/resolver/ContributionResolver.ts | 3 --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 2 -- backend/src/graphql/resolver/TransactionResolver.ts | 2 -- 3 files changed, 7 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 4baf3d010..8834046ad 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -582,7 +582,6 @@ export class ContributionResolver { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - console.log(`locked for confirmContribution ${id}`) const receivedCallDate = new Date() const queryRunner = getConnection().createQueryRunner() @@ -643,11 +642,9 @@ export class ContributionResolver { }) } catch (e) { await queryRunner.rollbackTransaction() - console.log(`Creation was not successful:`, e) throw new Error(`Creation was not successful.`) } finally { await queryRunner.release() - console.log(`release for confirmContribution ${id}`) releaseLock() } diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 983420e2a..897cf9252 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -170,7 +170,6 @@ export class TransactionLinkResolver { if (code.match(/^CL-/)) { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - console.log(`locked for redeemTransactionLink ${code}`) logger.info('redeem contribution link...') const now = new Date() const queryRunner = getConnection().createQueryRunner() @@ -313,7 +312,6 @@ export class TransactionLinkResolver { throw new Error(`Creation from contribution link was not successful. ${e}`) } finally { await queryRunner.release() - console.log(`release for redeemTransactionLink ${code}`) releaseLock() } return true diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 51ec0faaa..344a61be1 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -66,7 +66,6 @@ export const executeTransaction = async ( // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - console.log(`locked for executeTransaction ${amount.toString()} ${recipient.firstName}`) try { // validate amount @@ -190,7 +189,6 @@ export const executeTransaction = async ( logger.info(`finished executeTransaction successfully`) return true } finally { - console.log(`release for executeTransaction ${amount.toString()} ${recipient.firstName}`) releaseLock() } } From c4214eb2c6386f045856ad698dc6d0ca8b275b13 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 22 Dec 2022 14:10:34 +0100 Subject: [PATCH 19/21] remove timeouts in seeds --- backend/src/seeds/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index 3675d381d..9e1939db8 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -75,10 +75,7 @@ const run = async () => { // create GDD for (let i = 0; i < creations.length; i++) { - const now = new Date().getTime() // we have to wait a little! quick fix for account sum problem of bob@baumeister.de, (see https://github.com/gradido/gradido/issues/1886) await creationFactory(seedClient, creations[i]) - // eslint-disable-next-line no-empty - while (new Date().getTime() < now + 1000) {} // we have to wait a little! quick fix for account sum problem of bob@baumeister.de, (see https://github.com/gradido/gradido/issues/1886) } logger.info('##seed## seeding all creations successful...') From dcd2ec708d9752a90688aba9a48b1177a483cbd1 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 22 Dec 2022 15:56:37 +0100 Subject: [PATCH 20/21] include logger for error when creation is not successful --- backend/src/graphql/resolver/ContributionResolver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 8834046ad..2587aab61 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -642,7 +642,8 @@ export class ContributionResolver { }) } catch (e) { await queryRunner.rollbackTransaction() - throw new Error(`Creation was not successful.`) + logger.error('Creation was not successful', e) + throw new Error('Creation was not successful.') } finally { await queryRunner.release() releaseLock() From 7023fdba2a43826c2d8a4c9a2642ab64b38acde4 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 22 Dec 2022 19:45:16 +0100 Subject: [PATCH 21/21] 76% backend coverage requirement --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e31f7dda..c136ca4b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -527,7 +527,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 77 + min_coverage: 76 token: ${{ github.token }} ##########################################################################