Merge branch 'master' into iota6_protobuf_models

This commit is contained in:
einhornimmond 2023-09-13 21:08:13 +02:00 committed by GitHub
commit 83ae9060b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 307 additions and 8 deletions

View File

@ -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 = {

View File

@ -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 {

View 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
)
}
`

View File

@ -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`)
}

View File

@ -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,
},
}),
)
})
})
})
})

View File

@ -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)
}
}
}