mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
275 lines
11 KiB
TypeScript
275 lines
11 KiB
TypeScript
import {
|
|
Community as DbCommunity,
|
|
FederatedCommunity as DbFederatedCommunity,
|
|
PendingTransaction as DbPendingTransaction,
|
|
User as dbUser,
|
|
} from 'database'
|
|
import { Decimal } from 'decimal.js-light'
|
|
|
|
import { CONFIG } from '@/config'
|
|
|
|
import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient'
|
|
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
|
|
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
|
import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory'
|
|
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
|
|
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
|
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
|
|
import { LogError } from '@/server/LogError'
|
|
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
|
|
import { fullName } from '@/util/utilities'
|
|
import { getLogger } from 'log4js'
|
|
|
|
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
|
|
|
|
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.processXComSendCoins`)
|
|
|
|
export async function processXComPendingSendCoins(
|
|
receiverCom: DbCommunity,
|
|
senderCom: DbCommunity,
|
|
creationDate: Date,
|
|
amount: Decimal,
|
|
memo: string,
|
|
sender: dbUser,
|
|
recipientIdentifier: string,
|
|
): Promise<SendCoinsResult> {
|
|
let voteResult: SendCoinsResult
|
|
try {
|
|
logger.debug(
|
|
`XCom: processXComPendingSendCoins...`,
|
|
receiverCom,
|
|
senderCom,
|
|
amount,
|
|
memo,
|
|
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) {
|
|
throw new LogError('User has not enough GDD or amount is < 0', senderBalance)
|
|
}
|
|
logger.debug(`calculated senderBalance = `, senderBalance)
|
|
|
|
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
|
|
where: {
|
|
publicKey: receiverCom.publicKey,
|
|
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
|
|
},
|
|
})
|
|
const client = SendCoinsClientFactory.getInstance(receiverFCom)
|
|
|
|
if (client instanceof V1_0_SendCoinsClient) {
|
|
const args = new SendCoinsArgs()
|
|
if (receiverCom.communityUuid) {
|
|
args.recipientCommunityUuid = receiverCom.communityUuid
|
|
}
|
|
args.recipientUserIdentifier = recipientIdentifier
|
|
args.creationDate = creationDate.toISOString()
|
|
args.amount = amount
|
|
args.memo = memo
|
|
if (senderCom.communityUuid) {
|
|
args.senderCommunityUuid = senderCom.communityUuid
|
|
}
|
|
args.senderUserUuid = sender.gradidoID
|
|
args.senderUserName = fullName(sender.firstName, sender.lastName)
|
|
args.senderAlias = sender.alias
|
|
logger.debug(`ready for voteForSendCoins with args=`, args)
|
|
voteResult = await client.voteForSendCoins(args)
|
|
logger.debug(`returned from voteForSendCoins:`, voteResult)
|
|
if (voteResult.vote) {
|
|
logger.debug(`prepare pendingTransaction for sender...`)
|
|
// writing the pending transaction on receiver-side was successfull, so now write the sender side
|
|
try {
|
|
const pendingTx = DbPendingTransaction.create()
|
|
pendingTx.amount = amount.mul(-1)
|
|
pendingTx.balance = senderBalance.balance
|
|
pendingTx.balanceDate = creationDate
|
|
pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0)
|
|
pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null
|
|
if (receiverCom.communityUuid) {
|
|
pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid
|
|
}
|
|
if (voteResult.recipGradidoID) {
|
|
pendingTx.linkedUserGradidoID = voteResult.recipGradidoID
|
|
}
|
|
if (voteResult.recipFirstName && voteResult.recipLastName) {
|
|
pendingTx.linkedUserName = fullName(voteResult.recipFirstName, voteResult.recipLastName)
|
|
}
|
|
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.userId = sender.id
|
|
pendingTx.userGradidoID = sender.gradidoID
|
|
pendingTx.userName = fullName(sender.firstName, sender.lastName)
|
|
logger.debug(`initialized sender pendingTX=`, pendingTx)
|
|
|
|
await DbPendingTransaction.insert(pendingTx)
|
|
logger.debug(`sender pendingTx successfully inserted...`)
|
|
} catch (err) {
|
|
logger.error(`Error in writing sender pending transaction: `, err)
|
|
// revert the existing pending transaction on receiver side
|
|
let revertCount = 0
|
|
logger.debug(`first try to revertSendCoins of receiver`)
|
|
do {
|
|
if (await client.revertSendCoins(args)) {
|
|
logger.debug(`revertSendCoins()-1_0... successfull after revertCount=`, revertCount)
|
|
// treat revertingSendCoins as an error of the whole sendCoins-process
|
|
throw new LogError('Error in writing sender pending transaction: ', err)
|
|
}
|
|
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
|
|
throw new LogError(
|
|
`Error in reverting receiver pending transaction even after revertCount=`,
|
|
revertCount,
|
|
)
|
|
}
|
|
logger.debug(`voteForSendCoins()-1_0... successfull`)
|
|
} else {
|
|
logger.error(`break with error on writing pendingTransaction for recipient...`, voteResult)
|
|
}
|
|
return voteResult
|
|
}
|
|
} catch (err) {
|
|
throw new LogError(`Error:`, err)
|
|
}
|
|
return new SendCoinsResult()
|
|
}
|
|
|
|
export async function processXComCommittingSendCoins(
|
|
receiverCom: DbCommunity,
|
|
senderCom: DbCommunity,
|
|
creationDate: Date,
|
|
amount: Decimal,
|
|
memo: string,
|
|
sender: dbUser,
|
|
recipient: SendCoinsResult,
|
|
): Promise<SendCoinsResult> {
|
|
const sendCoinsResult = new SendCoinsResult()
|
|
try {
|
|
logger.debug(
|
|
`processXComCommittingSendCoins...`,
|
|
receiverCom,
|
|
senderCom,
|
|
creationDate,
|
|
amount,
|
|
memo,
|
|
sender,
|
|
recipient,
|
|
)
|
|
// first find pending Tx with given parameters
|
|
const pendingTx = await DbPendingTransaction.findOneBy({
|
|
userCommunityUuid: senderCom.communityUuid ?? 'homeCom-UUID',
|
|
userGradidoID: sender.gradidoID,
|
|
userName: fullName(sender.firstName, sender.lastName),
|
|
linkedUserCommunityUuid:
|
|
receiverCom.communityUuid ?? CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
|
|
linkedUserGradidoID: recipient.recipGradidoID ? recipient.recipGradidoID : undefined,
|
|
typeId: TransactionTypeId.SEND,
|
|
state: PendingTransactionState.NEW,
|
|
balanceDate: creationDate,
|
|
memo,
|
|
})
|
|
if (pendingTx) {
|
|
logger.debug('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)
|
|
|
|
if (client instanceof V1_0_SendCoinsClient) {
|
|
const args = new SendCoinsArgs()
|
|
args.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid
|
|
? pendingTx.linkedUserCommunityUuid
|
|
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
|
if (pendingTx.linkedUserGradidoID) {
|
|
args.recipientUserIdentifier = pendingTx.linkedUserGradidoID
|
|
}
|
|
args.creationDate = pendingTx.balanceDate.toISOString()
|
|
args.amount = pendingTx.amount.mul(-1)
|
|
args.memo = pendingTx.memo
|
|
args.senderCommunityUuid = pendingTx.userCommunityUuid
|
|
args.senderUserUuid = pendingTx.userGradidoID
|
|
if (pendingTx.userName) {
|
|
args.senderUserName = pendingTx.userName
|
|
}
|
|
args.senderAlias = sender.alias
|
|
logger.debug('ready for settleSendCoins with args=', args)
|
|
const acknowledge = await client.settleSendCoins(args)
|
|
logger.debug('returnd from settleSendCoins:', acknowledge)
|
|
if (acknowledge) {
|
|
// settle the pending transaction on receiver-side was successfull, so now settle the sender side
|
|
try {
|
|
sendCoinsResult.vote = await settlePendingSenderTransaction(
|
|
senderCom,
|
|
sender,
|
|
pendingTx,
|
|
)
|
|
if (sendCoinsResult.vote) {
|
|
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
|
|
sendCoinsResult.recipAlias = recipient.recipAlias
|
|
}
|
|
} catch (err) {
|
|
logger.error('Error in writing sender pending transaction: ', err)
|
|
// revert the existing pending transaction on receiver side
|
|
let revertCount = 0
|
|
logger.debug('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) {
|
|
logger.error('Error: ', err)
|
|
sendCoinsResult.vote = false
|
|
}
|
|
return sendCoinsResult
|
|
}
|