mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge pull request #3193 from gradido/3186-feature-x-sendcoins-22-add-graphql-endpoints-for-revert-pending-tx
feat(federation): x-com sendcoins 22: add graphql endpoints for revert pending tx
This commit is contained in:
commit
dff714fe0d
@ -127,6 +127,8 @@ const federation = {
|
||||
// default value for community-uuid is equal uuid of stage-3
|
||||
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID:
|
||||
process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc',
|
||||
FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS:
|
||||
process.env.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS ?? 3,
|
||||
}
|
||||
|
||||
export const CONFIG = {
|
||||
|
||||
@ -5,6 +5,7 @@ import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { SendCoinsArgs } from './model/SendCoinsArgs'
|
||||
import { revertSendCoins } from './query/revertSendCoins'
|
||||
import { voteForSendCoins } from './query/voteForSendCoins'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
@ -28,7 +29,7 @@ export class SendCoinsClient {
|
||||
}
|
||||
|
||||
voteForSendCoins = async (args: SendCoinsArgs): Promise<string | undefined> => {
|
||||
logger.debug('X-Com: voteForSendCoins against endpoint', this.endpoint)
|
||||
logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(voteForSendCoins, { args })
|
||||
@ -53,24 +54,25 @@ export class SendCoinsClient {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
revertSendCoins = async (args: SendCoinsArgs): Promise<boolean> => {
|
||||
logger.debug(`X-Com: revertSendCoins against endpoint='${this.endpoint}'...`)
|
||||
logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(revertSendCoins, { args })
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (!data?.revertSendCoins?.acknowledged) {
|
||||
if (!data?.revertSendCoins?.revertSendCoins) {
|
||||
logger.warn('X-Com: revertSendCoins without response data from endpoint', this.endpoint)
|
||||
return false
|
||||
}
|
||||
logger.debug(`X-Com: revertSendCoins successful from endpoint=${this.endpoint}`)
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new LogError(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err)
|
||||
logger.error(`X-Com: revertSendCoins failed for endpoint=${this.endpoint}`, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
commitSendCoins = async (args: SendCoinsArgs): Promise<boolean> => {
|
||||
logger.debug(`X-Com: commitSendCoins against endpoint='${this.endpoint}'...`)
|
||||
try {
|
||||
|
||||
25
backend/src/federation/client/1_0/query/revertSendCoins.ts
Normal file
25
backend/src/federation/client/1_0/query/revertSendCoins.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const revertSendCoins = gql`
|
||||
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
|
||||
)
|
||||
}
|
||||
`
|
||||
@ -16,9 +16,8 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
|
||||
import { fullName } from '@/util/utilities'
|
||||
|
||||
export async function processXComSendCoins(
|
||||
export async function processXComPendingSendCoins(
|
||||
receiverFCom: DbFederatedCommunity,
|
||||
senderFCom: DbFederatedCommunity,
|
||||
receiverCom: DbCommunity,
|
||||
senderCom: DbCommunity,
|
||||
creationDate: Date,
|
||||
@ -28,11 +27,23 @@ export async function processXComSendCoins(
|
||||
recipient: dbUser,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
logger.debug(
|
||||
`XCom: processXComPendingSendCoins...`,
|
||||
receiverFCom,
|
||||
receiverCom,
|
||||
senderCom,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
sender,
|
||||
recipient,
|
||||
)
|
||||
// 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(`X-Com: calculated senderBalance = `, senderBalance)
|
||||
|
||||
const client = SendCoinsClientFactory.getInstance(receiverFCom)
|
||||
// eslint-disable-next-line camelcase
|
||||
@ -50,7 +61,9 @@ export async function processXComSendCoins(
|
||||
: 'homeCom-UUID'
|
||||
args.userSenderIdentifier = sender.gradidoID
|
||||
args.userSenderName = 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) {
|
||||
// writing the pending transaction on receiver-side was successfull, so now write the sender side
|
||||
try {
|
||||
@ -72,12 +85,26 @@ export async function processXComSendCoins(
|
||||
if (senderCom.communityUuid) pendingTx.userCommunityUuid = senderCom.communityUuid
|
||||
pendingTx.userGradidoID = sender.gradidoID
|
||||
pendingTx.userName = fullName(sender.firstName, sender.lastName)
|
||||
logger.debug(`X-Com: initialized sender pendingTX=`, pendingTx)
|
||||
|
||||
await DbPendingTransaction.insert(pendingTx)
|
||||
logger.debug(`X-Com: sender pendingTx successfully inserted...`)
|
||||
} catch (err) {
|
||||
logger.error(`Error in writing sender pending transaction: `, err)
|
||||
// revert the existing pending transaction on receiver side
|
||||
// TODO in the issue #3186
|
||||
let revertCount = 0
|
||||
logger.debug(`X-Com: 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`)
|
||||
}
|
||||
|
||||
@ -65,6 +65,29 @@ describe('SendCoinsResolver', () => {
|
||||
)
|
||||
}
|
||||
`
|
||||
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
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
describe('voteForSendCoins', () => {
|
||||
let homeCom: DbCommunity
|
||||
@ -190,4 +213,144 @@ describe('SendCoinsResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('revertSendCoins', () => {
|
||||
let homeCom: DbCommunity
|
||||
let foreignCom: DbCommunity
|
||||
let sendUser: DbUser
|
||||
let recipUser: DbUser
|
||||
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,
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: 'invalid foreignCom',
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('revertSendCoins with wrong communityReceiverIdentifier')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: 'invalid recipient',
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'revertSendCoins with unknown userReceiverIdentifier in the community=',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid X-Com-TX reverted', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
variables: {
|
||||
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||
userReceiverIdentifier: recipUser.gradidoID,
|
||||
creationDate: creationDate.toISOString(),
|
||||
amount: 100,
|
||||
memo: 'X-Com-TX memo',
|
||||
communitySenderIdentifier: homeCom.communityUuid,
|
||||
userSenderIdentifier: sendUser.gradidoID,
|
||||
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
revertSendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -58,6 +58,7 @@ export class SendCoinsResolver {
|
||||
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
|
||||
@ -78,4 +79,83 @@ export class SendCoinsResolver {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async revertSendCoins(
|
||||
@Args()
|
||||
{
|
||||
communityReceiverIdentifier,
|
||||
userReceiverIdentifier,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
communitySenderIdentifier,
|
||||
userSenderIdentifier,
|
||||
userSenderName,
|
||||
}: SendCoinsArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(`revertSendCoins() via apiVersion=1_0 ...`)
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: communityReceiverIdentifier,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`revertSendCoins with wrong communityReceiverIdentifier`,
|
||||
communityReceiverIdentifier,
|
||||
)
|
||||
}
|
||||
// second check if receiver user exists in this community
|
||||
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
||||
if (!receiverUser) {
|
||||
throw new LogError(
|
||||
`revertSendCoins with unknown userReceiverIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
}
|
||||
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: revertSendCoins found pendingTX=', pendingTx)
|
||||
if (pendingTx && pendingTx.amount.toString() === amount.toString()) {
|
||||
logger.debug('XCom: revertSendCoins matching pendingTX for remove...')
|
||||
try {
|
||||
await pendingTx.remove()
|
||||
logger.debug('XCom: revertSendCoins pendingTX for remove successfully')
|
||||
} catch (err) {
|
||||
throw new LogError('Error in revertSendCoins on removing pendingTx of receiver: ', err)
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
'XCom: revertSendCoins NOT matching pendingTX for remove:',
|
||||
pendingTx?.amount,
|
||||
amount,
|
||||
)
|
||||
throw new LogError(
|
||||
`Can't find in revertSendCoins the pending receiver TX for args=`,
|
||||
communityReceiverIdentifier,
|
||||
userReceiverIdentifier,
|
||||
PendingTransactionState.NEW,
|
||||
TransactionTypeId.RECEIVE,
|
||||
creationDate,
|
||||
amount,
|
||||
memo,
|
||||
communitySenderIdentifier,
|
||||
userSenderIdentifier,
|
||||
userSenderName,
|
||||
)
|
||||
}
|
||||
logger.debug(`revertSendCoins()-1_0... successfull`)
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new LogError(`Error in revertSendCoins: `, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user