Merge branch 'master' into dlt_connector_direct_usage

This commit is contained in:
einhornimmond 2025-09-20 15:27:47 +02:00
commit 9f22e5b16d
89 changed files with 1636 additions and 524 deletions

View File

@ -63,7 +63,6 @@ LOG_LEVEL=INFO
# Federation
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
FEDERATION_XCOM_SENDCOINS_ENABLED=false
# GMS
# GMS_ACTIVE=true

View File

@ -63,7 +63,6 @@ WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
# Federation
FEDERATION_VALIDATE_COMMUNITY_TIMER=$FEDERATION_VALIDATE_COMMUNITY_TIMER
FEDERATION_XCOM_SENDCOINS_ENABLED=$FEDERATION_XCOM_SENDCOINS_ENABLED
# GMS
GMS_ACTIVE=$GMS_ACTIVE

View File

@ -59,21 +59,20 @@ export class TransactionDraft {
static createTransfer(sendingUser: DbUser, receivingUser: DbUser, amount: string, memo: string, createdAt: Date): TransactionDraft | null {
if (!sendingUser.community || !receivingUser.community) {
logger.warn(`missing community for user ${sendingUser.id} and/or ${receivingUser.id}`)
return null
throw new Error(`missing community for user ${sendingUser.id} and/or ${receivingUser.id}`)
}
if (sendingUser.community.hieroTopicId && receivingUser.community.hieroTopicId) {
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(sendingUser.community.hieroTopicId, new CommunityAccountIdentifier(sendingUser.gradidoID))
draft.linkedUser = new AccountIdentifier(receivingUser.community.hieroTopicId, new CommunityAccountIdentifier(receivingUser.gradidoID))
draft.type = TransactionType.GRADIDO_TRANSFER
draft.createdAt = createdAt.toISOString()
draft.amount = amount
draft.memo = memo
return draft
} else {
logger.warn(`missing topicId for community ${community.id}`)
const senderUserTopic = sendingUser.community.hieroTopicId
const receiverUserTopic = receivingUser.community.hieroTopicId
if (!senderUserTopic || !receiverUserTopic) {
throw new Error(`missing topicId for community ${sendingUser.community.id} and/or ${receivingUser.community.id}`)
}
return null
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(senderUserTopic, new CommunityAccountIdentifier(sendingUser.gradidoID))
draft.linkedUser = new AccountIdentifier(receiverUserTopic, new CommunityAccountIdentifier(receivingUser.gradidoID))
draft.type = TransactionType.GRADIDO_TRANSFER
draft.createdAt = createdAt.toISOString()
draft.amount = amount
draft.memo = memo
return draft
}
}

View File

@ -4,7 +4,7 @@ import { httpAgent, httpsAgent } from '@/apis/ConnectionAgents'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from 'core'
import { getLogger } from 'log4js'
import { GmsUser } from './model/GmsUser'

View File

@ -99,19 +99,9 @@ const webhook = {
process.env.APP_SECRET = server.JWT_SECRET
const federation = {
FEDERATION_BACKEND_SEND_ON_API: process.env.FEDERATION_BACKEND_SEND_ON_API ?? '1_0',
// ?? operator don't work here as expected
FEDERATION_VALIDATE_COMMUNITY_TIMER: Number(
process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER ?? 60000,
),
FEDERATION_XCOM_SENDCOINS_ENABLED:
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false,
// 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: parseInt(
process.env.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS ?? '3',
),
}
const gms = {

View File

@ -209,39 +209,13 @@ export const schema = Joi.object({
.description('Time in minutes before a new code can be requested')
.required(),
FEDERATION_BACKEND_SEND_ON_API: Joi.string()
.pattern(/^\d+_\d+$/)
.default('1_0')
.description('API Version of sending requests to another communities, e.g., "1_0"')
.required(),
FEDERATION_VALIDATE_COMMUNITY_TIMER: Joi.number()
FEDERATION_VALIDATE_COMMUNITY_TIMER: Joi.number()
.integer()
.min(1000)
.default(60000)
.description('Timer interval in milliseconds for community validation')
.required(),
FEDERATION_XCOM_SENDCOINS_ENABLED: Joi.boolean()
.default(false)
.description('Enable or disable the federation send coins feature')
.optional(),
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID: Joi.string()
.uuid()
.default('56a55482-909e-46a4-bfa2-cd025e894ebc')
.description(
'UUID of the receiver community for federation cross-community transactions if the receiver is unknown',
)
.required(),
FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS: Joi.number()
.integer()
.min(0)
.default(3)
.description('Maximum number of retries for reverting send coins transactions')
.required(),
GMS_CREATE_USER_THROW_ERRORS: Joi.boolean()
.default(false)
.when('GMS_ACTIVE', { is: true, then: Joi.required(), otherwise: Joi.optional() })

View File

@ -1,7 +1,7 @@
import { Decimal } from 'decimal.js-light'
import { CONFIG } from '@/config'
import { decimalSeparatorByLanguage } from '@/util/utilities'
import { decimalSeparatorByLanguage } from 'core'
import { sendEmailTranslated } from './sendEmailTranslated'

View File

@ -1,10 +1,10 @@
import { CommunityLoggingView, Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, FederatedCommunityLoggingView, getHomeCommunity } from 'database'
import { validate as validateUUID, version as versionUUID } from 'uuid'
import { randombytes_random } from 'sodium-native'
import { CONFIG } from '@/config'
import { CONFIG as CONFIG_CORE } from 'core'
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from 'core'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
@ -27,7 +27,7 @@ export async function startCommunityAuthentication(
methodLogger.debug('homeComA', new CommunityLoggingView(homeComA!))
const homeFedComA = await DbFederatedCommunity.findOneByOrFail({
foreign: false,
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API,
})
methodLogger.debug('homeFedComA', new FederatedCommunityLoggingView(homeFedComA))
const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey })

View File

@ -1,11 +1,11 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from 'core'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { EncryptedTransferArgs } from 'core'
import { getLogger } from 'log4js'
import { EncryptedTransferArgs } from 'core/src/graphql/model/EncryptedTransferArgs'
import { openConnection } from './query/openConnection'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.AuthenticationClient`)

View File

@ -0,0 +1,47 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { ensureUrlEndsWithSlash } from 'core'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { EncryptedTransferArgs } from 'core'
import { processDisburseJwtOnSenderCommunity as processDisburseJwtOnSenderCommunityQuery } from './query/processDisburseJwtOnSenderCommunity'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.DisbursementClient`)
export class DisbursementClient {
dbCom: DbFederatedCommunity
endpoint: string
client: GraphQLClient
constructor(dbCom: DbFederatedCommunity) {
this.dbCom = dbCom
this.endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion).concat('/')
this.client = new GraphQLClient(this.endpoint, {
method: 'POST',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
}
async sendDisburseJwtToSenderCommunity(args: EncryptedTransferArgs): Promise<string | null> {
logger.debug('sendDisburseJwtToSenderCommunity against endpoint=', this.endpoint)
try {
const { data } = await this.client.rawRequest<{ processDisburseJwtOnSenderCommunity: string }>(processDisburseJwtOnSenderCommunityQuery, { args })
const response = data?.processDisburseJwtOnSenderCommunity
if (response) {
logger.debug('received response:', response)
return response
}
} catch (err) {
const errmsg = `sendDisburseJwtToSenderCommunity failed for endpoint=${this.endpoint}, err=${err}`
logger.error(errmsg)
throw new Error(errmsg)
}
return null
}
}

View File

@ -4,7 +4,7 @@ import { GraphQLClient } from 'graphql-request'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getPublicCommunityInfo } from '@/federation/client/1_0/query/getPublicCommunityInfo'
import { getPublicKey } from '@/federation/client/1_0/query/getPublicKey'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from 'core'
import { getLogger } from 'log4js'
import { PublicCommunityInfoLoggingView } from './logging/PublicCommunityInfoLogging.view'

View File

@ -0,0 +1,7 @@
import { gql } from 'graphql-request'
export const processDisburseJwtOnSenderCommunity = gql`
mutation ($args: EncryptedTransferArgs!) {
processDisburseJwtOnSenderCommunity(data: $args)
}
`

View File

@ -0,0 +1,3 @@
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
export class DisbursementClient extends V1_0_DisbursementClient {}

View File

@ -1,3 +0,0 @@
import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient'
export class SendCoinsClient extends V1_0_SendCoinsClient {}

View File

@ -3,7 +3,7 @@ import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
import { AuthenticationClient as V1_1_AuthenticationClient } from '@/federation/client/1_1/AuthenticationClient'
import { ApiVersionType } from '@/federation/enum/apiVersionType'
import { ApiVersionType } from 'core'
type AuthenticationClient = V1_0_AuthenticationClient | V1_1_AuthenticationClient

View File

@ -0,0 +1,58 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
import { DisbursementClient as V1_1_DisbursementClient } from '@/federation/client/1_1/DisbursementClient'
import { ApiVersionType } from 'core'
type DisbursementClient = V1_0_DisbursementClient | V1_1_DisbursementClient
interface DisbursementClientInstance {
id: number
client: DisbursementClient
}
export class DisbursementClientFactory {
private static instanceArray: DisbursementClientInstance[] = []
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() {}
private static createDisbursementClient = (dbCom: DbFederatedCommunity) => {
switch (dbCom.apiVersion) {
case ApiVersionType.V1_0:
return new V1_0_DisbursementClient(dbCom)
case ApiVersionType.V1_1:
return new V1_1_DisbursementClient(dbCom)
default:
return null
}
}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(dbCom: DbFederatedCommunity): DisbursementClient | null {
const instance = DisbursementClientFactory.instanceArray.find(
(instance) => instance.id === dbCom.id,
)
if (instance) {
return instance.client
}
const client = DisbursementClientFactory.createDisbursementClient(dbCom)
if (client) {
DisbursementClientFactory.instanceArray.push({
id: dbCom.id,
client,
} as DisbursementClientInstance)
}
return client
}
}

View File

@ -1,10 +1,8 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
import { FederationClient as V1_1_FederationClient } from '@/federation/client/1_1/FederationClient'
import { ApiVersionType } from '@/federation/enum/apiVersionType'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ApiVersionType, ensureUrlEndsWithSlash } from 'core'
type FederationClient = V1_0_FederationClient | V1_1_FederationClient

View File

@ -15,7 +15,7 @@ import { createKeyPair } from 'shared'
import { getLogger } from 'log4js'
import { startCommunityAuthentication } from './authenticateCommunities'
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
import { ApiVersionType } from './enum/apiVersionType'
import { ApiVersionType } from 'core'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.validateCommunities`)

View File

@ -1,7 +1,7 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { Field, Int, ObjectType } from 'type-graphql'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from 'core'
@ObjectType()
export class FederatedCommunity {

View File

@ -2,9 +2,8 @@ import { Transaction as dbTransaction } from 'database'
import { Decimal } from 'decimal.js-light'
import { Field, Int, ObjectType } from 'type-graphql'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { Decay, TransactionTypeId } from 'core'
import { Decay } from './Decay'
import { User } from './User'
@ObjectType()

View File

@ -7,14 +7,14 @@ import { Balance } from '@model/Balance'
import { RIGHTS } from '@/auth/RIGHTS'
import { BalanceLoggingView } from '@/logging/BalanceLogging.view'
import { DecayLoggingView } from '@/logging/DecayLogging.view'
import { Context, getUser } from '@/server/context'
import { DecayLoggingView } from 'core'
import { calculateDecay } from 'shared'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLastTransaction } from 'database'
import { getLogger } from 'log4js'
import { GdtResolver } from './GdtResolver'
import { getLastTransaction } from './util/getLastTransaction'
import { transactionLinkSummary } from './util/transactionLinkSummary'
@Resolver()

View File

@ -50,7 +50,7 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { getFirstDayOfPreviousNMonth } from '@/util/utilities'
import { getFirstDayOfPreviousNMonth } from 'core'
import { getLogger } from 'config-schema/test/testSetup'
import { getLogger as originalGetLogger } from 'log4js'

View File

@ -17,11 +17,11 @@ import { Paginated } from '@arg/Paginated'
import { SearchContributionsFilterArgs } from '@arg/SearchContributionsFilterArgs'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionType } from '@enum/ContributionType'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
import { Contribution, ContributionListResult } from '@model/Contribution'
import { OpenCreation } from '@model/OpenCreation'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { TransactionTypeId } from 'core'
import { RIGHTS } from '@/auth/RIGHTS'
import {
@ -43,9 +43,9 @@ import {
import { UpdateUnconfirmedContributionContext } from '@/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context'
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { TRANSACTIONS_LOCK } from 'database'
import { fullName } from 'core'
import { calculateDecay, Decay } from 'shared'
import { fullName } from '@/util/utilities'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { ContributionMessageType } from '@enum/ContributionMessageType'
@ -59,8 +59,7 @@ import {
import { getOpenCreations, getUserCreation, validateContribution } from './util/creations'
import { extractGraphQLFields } from './util/extractGraphQLFields'
import { findContributions } from './util/findContributions'
import { getLastTransaction } from './util/getLastTransaction'
import { InterruptiveSleepManager, TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY } from '@/util/InterruptiveSleepManager'
import { getLastTransaction } from 'database'
import { contributionTransaction } from '@/apis/dltConnector'
const db = AppDatabase.getInstance()

View File

@ -32,7 +32,7 @@ import { listTransactionLinksAdmin } from '@/seeds/graphql/queries'
import { transactionLinks } from '@/seeds/transactionLink/index'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { TRANSACTIONS_LOCK } from 'database'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'config-schema/test/testSetup'
@ -43,7 +43,7 @@ const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
jest.mock('@/password/EncryptorUtils')
// mock semaphore to allow use fake timers
jest.mock('@/util/TRANSACTIONS_LOCK')
jest.mock('database/src/util/TRANSACTIONS_LOCK')
TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn())
let mutate: ApolloServerTestClient['mutate']

View File

@ -6,22 +6,20 @@ import { TransactionLinkFilters } from '@arg/TransactionLinkFilters'
import { ContributionCycleType } from '@enum/ContributionCycleType'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionType } from '@enum/ContributionType'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { Community } from '@model/Community'
import { ContributionLink } from '@model/ContributionLink'
import { Decay } from '@model/Decay'
import { RedeemJwtLink } from '@model/RedeemJwtLink'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { User } from '@model/User'
import { QueryLinkResult } from '@union/QueryLinkResult'
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
import {
AppDatabase,
Contribution as DbContribution,
ContributionLink as DbContributionLink,
Transaction as DbTransaction,
AppDatabase, Community as DbCommunity, Contribution as DbContribution,
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
getHomeCommunity,
findTransactionLinkByCode,
getHomeCommunity
} from 'database'
import { Decimal } from 'decimal.js-light'
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
@ -39,24 +37,29 @@ import {
InterruptiveSleepManager,
TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY,
} from '@/util/InterruptiveSleepManager'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { TRANSACTION_LINK_LOCK } from '@/util/TRANSACTION_LINK_LOCK'
import { fullName } from '@/util/utilities'
import { calculateBalance } from '@/util/validate'
import { calculateDecay, decode, DisburseJwtPayloadType, encode, RedeemJwtPayloadType, verify } from 'shared'
import { fullName } from 'core'
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
import { DisbursementClientFactory } from '@/federation/client/DisbursementClientFactory'
import { EncryptedTransferArgs } from 'core'
import { getLastTransaction } from 'database'
import { getLogger, Logger } from 'log4js'
import { randombytes_random } from 'sodium-native'
import { executeTransaction } from './TransactionResolver'
import {
getAuthenticatedCommunities,
getCommunityByPublicKey,
getCommunityByUuid,
} from './util/communities'
import { getUserCreation, validateContribution } from './util/creations'
import { getLastTransaction } from './util/getLastTransaction'
import { transactionLinkList } from './util/transactionLinkList'
import { SignedTransferPayloadType } from 'shared'
const createLogger = () => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionLinkResolver`)
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionLinkResolver.${method}`)
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {
@ -151,9 +154,9 @@ export class TransactionLinkResolver {
@Authorized([RIGHTS.QUERY_TRANSACTION_LINK])
@Query(() => QueryLinkResult)
async queryTransactionLink(@Arg('code') code: string): Promise<typeof QueryLinkResult> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.queryTransactionLink...')
const methodLogger = createLogger('queryTransactionLink')
methodLogger.addContext('handshakeID', randombytes_random().toString())
methodLogger.debug('queryTransactionLink...')
if (code.match(/^CL-/)) {
const contributionLink = await DbContributionLink.findOneOrFail({
where: { code: code.replace('CL-', '') },
@ -164,17 +167,14 @@ export class TransactionLinkResolver {
let txLinkFound = false
let dbTransactionLink!: DbTransactionLink
try {
dbTransactionLink = await DbTransactionLink.findOneOrFail({
where: { code },
withDeleted: true,
})
dbTransactionLink = await findTransactionLinkByCode(code)
txLinkFound = true
} catch (_err) {
txLinkFound = false
}
// normal redeem code
if (txLinkFound) {
logger.debug(
methodLogger.debug(
'TransactionLinkResolver.queryTransactionLink... normal redeem code found=',
txLinkFound,
)
@ -189,7 +189,7 @@ export class TransactionLinkResolver {
return new TransactionLink(dbTransactionLink, new User(user), redeemedBy, communities)
} else {
// redeem jwt-token
return await this.queryRedeemJwtLink(code, logger)
return await this.queryRedeemJwtLink(code, methodLogger)
}
}
}
@ -200,8 +200,8 @@ export class TransactionLinkResolver {
@Arg('code', () => String) code: string,
@Ctx() context: Context,
): Promise<boolean> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
const methodLogger = createLogger('redeemTransactionLink')
methodLogger.addContext('code', code.substring(0, 6))
const clientTimezoneOffset = getClientTimezoneOffset(context)
// const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } })
const user = getUser(context)
@ -209,7 +209,7 @@ export class TransactionLinkResolver {
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
logger.info('redeem contribution link...')
methodLogger.info('redeem contribution link...')
const now = new Date()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
@ -224,7 +224,7 @@ export class TransactionLinkResolver {
if (!contributionLink) {
throw new LogError('No contribution link found to given code', code)
}
logger.info('...contribution link found with id', contributionLink.id)
methodLogger.info('...contribution link found with id', contributionLink.id)
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
throw new LogError('Contribution link is not valid yet', contributionLink.validFrom)
}
@ -281,7 +281,7 @@ export class TransactionLinkResolver {
}
const creations = await getUserCreation(user.id, clientTimezoneOffset)
logger.info('open creations', creations)
methodLogger.info('open creations', creations)
validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset)
const contribution = new DbContribution()
contribution.userId = user.id
@ -387,7 +387,7 @@ export class TransactionLinkResolver {
transactionLink.memo,
linkedUser,
user,
logger,
methodLogger,
transactionLink,
)
await EVENT_TRANSACTION_LINK_REDEEM(
@ -419,9 +419,9 @@ export class TransactionLinkResolver {
@Arg('alias', { nullable: true }) alias?: string,
@Arg('validUntil', { nullable: true }) validUntil?: string,
): Promise<string> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.queryRedeemJwt... args=', {
const methodLogger = createLogger('createRedeemJwt')
methodLogger.addContext('code', code.substring(0, 6))
methodLogger.debug('args=', {
gradidoId,
senderCommunityUuid,
senderCommunityName,
@ -433,27 +433,65 @@ export class TransactionLinkResolver {
alias,
validUntil,
})
try {
const redeemJwtPayloadType = new RedeemJwtPayloadType(
senderCommunityUuid,
gradidoId,
alias ?? firstName ?? '',
code,
amount,
memo,
validUntil ?? '',
)
// encode/sign the jwt with the private key of the sender/home community
const senderCom = await getCommunityByUuid(senderCommunityUuid)
if (!senderCom) {
throw new LogError('Sender community not found')
}
if (!senderCom.privateJwtKey) {
throw new LogError('Sender community privateJwtKey is not set')
}
const recipientCom = await getCommunityByUuid(recipientCommunityUuid)
if (!recipientCom) {
throw new LogError('Recipient community not found')
}
if (!recipientCom.publicJwtKey) {
throw new LogError('Recipient community publicJwtKey is not set')
}
const redeemJwt = await encryptAndSign(redeemJwtPayloadType, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
if (!redeemJwt) {
throw new LogError('Redeem JWT was not created successfully')
}
// prepare the args for the client invocation
const args = new EncryptedTransferArgs()
args.publicKey = senderCom.publicKey.toString('hex')
args.jwt = redeemJwt
args.handshakeID = randombytes_random().toString()
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('successfully created RedeemJWT-Response with args:', args)
}
const signedTransferPayload = new SignedTransferPayloadType(
args.publicKey,
args.jwt,
args.handshakeID,
)
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('successfully created RedeemJWT-Response with signedTransferPayload:', signedTransferPayload)
}
const signedTransferJwt = await encode(signedTransferPayload, senderCom.privateJwtKey!)
if (!signedTransferJwt) {
throw new LogError('SignedTransfer JWT was not created successfully')
}
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('successfully created RedeemJWT-Response with signedTransferJwt:', signedTransferJwt)
}
const redeemJwtPayloadType = new RedeemJwtPayloadType(
senderCommunityUuid,
gradidoId,
alias ?? firstName ?? '',
code,
amount,
memo,
validUntil ?? '',
)
// TODO:encode/sign the jwt normally with the private key of the sender/home community, but interims with uuid
const homeCom = await getHomeCommunity()
if (!homeCom) {
throw new LogError('Home community not found')
return signedTransferJwt
} catch (e) {
const errmsg = `Error on creating Redeem JWT: error=${e}`
methodLogger.error(errmsg)
throw new LogError(errmsg)
}
if (!homeCom.communityUuid) {
throw new LogError('Home community UUID is not set')
}
const redeemJwt = await encode(redeemJwtPayloadType, homeCom.communityUuid)
// TODO: encrypt the payload with the public key of the target community
return redeemJwt
}
@Authorized([RIGHTS.DISBURSE_TRANSACTION_LINK])
@ -472,40 +510,80 @@ export class TransactionLinkResolver {
@Arg('validUntil', { nullable: true }) validUntil?: string,
@Arg('recipientAlias', { nullable: true }) recipientAlias?: string,
): Promise<boolean> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.disburseTransactionLink... args=', {
senderGradidoId,
senderCommunityUuid,
recipientCommunityUuid,
recipientCommunityName,
recipientGradidoId,
recipientFirstName,
code,
amount,
memo,
validUntil,
const handshakeID = randombytes_random().toString()
const methodLogger = createLogger(`disburseTransactionLink`)
methodLogger.addContext('handshakeID', handshakeID)
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('args=', {
senderGradidoId,
senderCommunityUuid,
recipientCommunityUuid,
recipientCommunityName,
recipientGradidoId,
recipientFirstName,
code,
amount,
memo,
validUntil,
recipientAlias,
})
const disburseJwt = await this.createDisburseJwt(
senderCommunityUuid,
senderGradidoId,
recipientCommunityUuid,
recipientCommunityName,
recipientGradidoId,
recipientFirstName,
code,
amount,
memo,
validUntil ?? '',
recipientAlias ?? '',
)
try {
logger.debug('TransactionLinkResolver.disburseTransactionLink... disburseJwt=', disburseJwt)
// now send the disburseJwt to the sender community to invoke a x-community-tx to disbures the redeemLink
// await sendDisburseJwtToSenderCommunity(context, disburseJwt)
} catch (e) {
throw new LogError('Disburse JWT was not sent successfully', e)
})
}
const senderCom = await getCommunityByUuid(senderCommunityUuid)
if (!senderCom) {
const errmsg = `Sender community not found with uuid=${senderCommunityUuid}`
methodLogger.error(errmsg)
throw new LogError(errmsg)
}
const senderFedCom = await DbFederatedCommunity.findOneBy({ publicKey: senderCom.publicKey })
if (!senderFedCom) {
const errmsg = `Sender federated community not found with publicKey=${senderCom.publicKey}`
methodLogger.error(errmsg)
throw new LogError(errmsg)
}
const recipientCom = await getCommunityByUuid(recipientCommunityUuid)
if (!recipientCom) {
const errmsg = `Recipient community not found with uuid=${recipientCommunityUuid}`
methodLogger.error(errmsg)
throw new LogError(errmsg)
}
const client = DisbursementClientFactory.getInstance(senderFedCom)
if (client instanceof V1_0_DisbursementClient) {
const disburseJwtPayload = new DisburseJwtPayloadType(
handshakeID,
senderCommunityUuid,
senderGradidoId,
recipientCommunityUuid,
recipientCommunityName,
recipientGradidoId,
recipientFirstName,
code,
amount,
memo,
validUntil!,
recipientAlias!,
)
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('disburseJwtPayload=', disburseJwtPayload)
}
const jws = await encryptAndSign(disburseJwtPayload, recipientCom.privateJwtKey!, senderCom.publicJwtKey!)
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('jws=', jws)
}
const args = new EncryptedTransferArgs()
args.publicKey = recipientCom.publicKey.toString('hex')
args.jwt = jws
args.handshakeID = handshakeID
try {
// now send the disburseJwt to the sender community to invoke a x-community-tx to disbures the redeemLink
const result = await client.sendDisburseJwtToSenderCommunity(args)
if(methodLogger.isDebugEnabled()) {
methodLogger.debug('Disburse JWT was sent successfully with result=', result)
}
} catch (e) {
const errmsg = `Disburse JWT was not sent successfully with error=${e}`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
}
return true
}
@ -546,98 +624,77 @@ export class TransactionLinkResolver {
}
async queryRedeemJwtLink(code: string, logger: Logger): Promise<RedeemJwtLink> {
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... redeem jwt-token found')
// decode token first to get the senderCommunityUuid as input for verify token
const decodedPayload = decode(code)
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... decodedPayload=', decodedPayload)
if (
decodedPayload != null &&
decodedPayload.tokentype === RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE
) {
const redeemJwtPayload = new RedeemJwtPayloadType(
decodedPayload.sendercommunityuuid as string,
decodedPayload.sendergradidoid as string,
decodedPayload.sendername as string,
decodedPayload.redeemcode as string,
decodedPayload.amount as string,
decodedPayload.memo as string,
decodedPayload.validuntil as string,
)
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... redeemJwtPayload=',
redeemJwtPayload,
)
const senderCom = await getCommunityByUuid(redeemJwtPayload.sendercommunityuuid)
logger.debug('queryRedeemJwtLink... redeem jwt-token found')
// decode token first to get the EncryptedTransferArgs with the senderCommunity.publicKey as input to verify token
const decodedPayload = decode(code) as SignedTransferPayloadType
logger.debug('queryRedeemJwtLink... decodedPayload=', decodedPayload)
logger.debug('switch logger-context to received token-handshakeID:' + decodedPayload.handshakeID)
logger.addContext('handshakeID', decodedPayload.handshakeID)
if(decodedPayload !== null && decodedPayload.tokentype === SignedTransferPayloadType.SIGNED_TRANSFER_TYPE) {
const signedTransferPayload = new SignedTransferPayloadType(
decodedPayload.publicKey,
decodedPayload.jwt,
decodedPayload.handshakeID)
logger.debug('queryRedeemJwtLink... signedTransferPayload=', signedTransferPayload)
const senderCom = await getCommunityByPublicKey(Buffer.from(signedTransferPayload.publicKey, 'hex'))
if (!senderCom) {
throw new LogError('Sender community not found:', redeemJwtPayload.sendercommunityuuid)
const errmsg = `Sender community not found with publicKey=${signedTransferPayload.publicKey}`
logger.error(errmsg)
throw new Error(errmsg)
}
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... senderCom=', senderCom)
if (!senderCom.communityUuid) {
throw new LogError('Sender community UUID is not set')
}
// now with the sender community UUID the jwt token can be verified
const verifiedJwtPayload = await verify('handshakeID', code, senderCom.communityUuid)
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... nach verify verifiedJwtPayload=',
verifiedJwtPayload,
)
logger.debug('queryRedeemJwtLink... senderCom=', senderCom)
const jweVerifyResult = await verify(signedTransferPayload.handshakeID, signedTransferPayload.jwt, senderCom.publicJwtKey!)
logger.debug('queryRedeemJwtLink... jweVerifyResult=', jweVerifyResult)
let verifiedRedeemJwtPayload: RedeemJwtPayloadType | null = null
if (verifiedJwtPayload !== null) {
if (verifiedJwtPayload.exp !== undefined) {
const expDate = new Date(verifiedJwtPayload.exp * 1000)
if (jweVerifyResult === null) {
const errmsg = `Error on verify transferred redeem token with publicKey=${signedTransferPayload.publicKey}`
logger.error(errmsg)
throw new Error(errmsg)
} else {
const encryptedTransferArgs = new EncryptedTransferArgs()
encryptedTransferArgs.publicKey = signedTransferPayload.publicKey
encryptedTransferArgs.jwt = signedTransferPayload.jwt
encryptedTransferArgs.handshakeID = signedTransferPayload.handshakeID
verifiedRedeemJwtPayload = await interpretEncryptedTransferArgs(encryptedTransferArgs) as RedeemJwtPayloadType
if(logger.isDebugEnabled()) {
logger.debug(`queryRedeemJwtLink() ...`, verifiedRedeemJwtPayload)
}
if (!verifiedRedeemJwtPayload) {
const errmsg = `invalid authentication payload of requesting community with publicKey` + signedTransferPayload.publicKey
logger.error(errmsg)
throw new Error(errmsg)
}
if (verifiedRedeemJwtPayload.tokentype !== RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE) {
const errmsg = `Wrong tokentype in redeem JWT: type=` + verifiedRedeemJwtPayload.tokentype + ' vs expected ' + RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE
logger.error(errmsg)
throw new Error(errmsg)
}
if(senderCom?.communityUuid !== verifiedRedeemJwtPayload.sendercommunityuuid) {
const errmsg = `Mismatch of sender community UUID in redeem JWT against transfer JWT: uuid=` + senderCom.communityUuid + ' vs ' + verifiedRedeemJwtPayload.sendercommunityuuid
logger.error(errmsg)
throw new Error(errmsg)
}
if (verifiedRedeemJwtPayload.exp !== undefined) {
const expDate = new Date(verifiedRedeemJwtPayload.exp * 1000)
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... expDate, exp =',
'queryRedeemJwtLink... expDate, exp =',
expDate,
verifiedJwtPayload.exp,
verifiedRedeemJwtPayload.exp,
)
if (expDate < new Date()) {
throw new LogError('Redeem JWT-Token expired! jwtPayload.exp=', expDate)
const errmsg = `Redeem JWT-Token expired! jwtPayload.exp=${expDate}`
logger.error(errmsg)
throw new Error(errmsg)
}
}
if (verifiedJwtPayload.tokentype === RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE) {
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... verifiedJwtPayload.tokentype=',
verifiedJwtPayload.tokentype,
)
verifiedRedeemJwtPayload = new RedeemJwtPayloadType(
verifiedJwtPayload.sendercommunityuuid as string,
verifiedJwtPayload.sendergradidoid as string,
verifiedJwtPayload.sendername as string,
verifiedJwtPayload.redeemcode as string,
verifiedJwtPayload.amount as string,
verifiedJwtPayload.memo as string,
verifiedJwtPayload.validuntil as string,
)
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... nach verify verifiedRedeemJwtPayload=',
verifiedRedeemJwtPayload,
)
}
}
if (verifiedRedeemJwtPayload === null) {
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... verifiedRedeemJwtPayload===null',
)
verifiedRedeemJwtPayload = new RedeemJwtPayloadType(
decodedPayload.sendercommunityuuid as string,
decodedPayload.sendergradidoid as string,
decodedPayload.sendername as string,
decodedPayload.redeemcode as string,
decodedPayload.amount as string,
decodedPayload.memo as string,
decodedPayload.validuntil as string,
)
} else {
// TODO: as long as the verification fails, fallback to simply decoded payload
verifiedRedeemJwtPayload = redeemJwtPayload
logger.debug(
'TransactionLinkResolver.queryRedeemJwtLink... fallback to decode verifiedRedeemJwtPayload=',
verifiedRedeemJwtPayload,
)
}
const homeCommunity = await getHomeCommunity()
if (!homeCommunity) {
throw new LogError('Home community not found')
const errmsg = `Home community not found`
logger.error(errmsg)
throw new Error(errmsg)
}
const recipientCommunity = new Community(homeCommunity)
const senderCommunity = new Community(senderCom)
@ -653,59 +710,9 @@ export class TransactionLinkResolver {
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... redeemJwtLink=', redeemJwtLink)
return redeemJwtLink
} else {
throw new LogError(
'Redeem with wrong type of JWT-Token or expired! decodedPayload=',
decodedPayload,
)
const errmsg = `transfer of redeem JWT with wrong envelope! code=${code}`
logger.error(errmsg)
throw new Error(errmsg)
}
}
async createDisburseJwt(
senderCommunityUuid: string,
senderGradidoId: string,
recipientCommunityUuid: string,
recipientCommunityName: string,
recipientGradidoId: string,
recipientFirstName: string,
code: string,
amount: string,
memo: string,
validUntil: string,
recipientAlias: string,
): Promise<string> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.createDisburseJwt... args=', {
senderCommunityUuid,
senderGradidoId,
recipientCommunityUuid,
recipientCommunityName,
recipientGradidoId,
recipientFirstName,
code,
amount,
memo,
validUntil,
recipientAlias,
})
const disburseJwtPayloadType = new DisburseJwtPayloadType(
senderCommunityUuid,
senderGradidoId,
recipientCommunityUuid,
recipientCommunityName,
recipientGradidoId,
recipientFirstName,
code,
amount,
memo,
validUntil,
recipientAlias,
)
// TODO:encode/sign the jwt normally with the private key of the recipient community, but interims with uuid
const disburseJwt = await encode(disburseJwtPayloadType, recipientCommunityUuid)
logger.debug('TransactionLinkResolver.createDisburseJwt... disburseJwt=', disburseJwt)
// TODO: encrypt the payload with the public key of the target/sender community
return disburseJwt
}
}

View File

@ -13,12 +13,12 @@ import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { CONFIG } from '@/config'
// import { CONFIG } from '@/config'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { EventType } from '@/event/Events'
import { 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 { V1_0_SendCoinsClient } from 'core'
// import { SendCoinsArgs } from 'core'
// import { SendCoinsResult } from 'core'
import { userFactory } from '@/seeds/factory/user'
import {
confirmContribution,

View File

@ -3,7 +3,6 @@ import {
countOpenPendingTransactions,
Community as DbCommunity,
DltTransaction as DbDltTransaction,
PendingTransaction as DbPendingTransaction,
Transaction as dbTransaction,
TransactionLink as dbTransactionLink,
User as dbUser,
@ -16,11 +15,10 @@ import { In, IsNull } from 'typeorm'
import { Paginated } from '@arg/Paginated'
import { TransactionSendArgs } from '@arg/TransactionSendArgs'
import { Order } from '@enum/Order'
import { PendingTransactionState, SendCoinsResponseJwtPayloadType } from 'shared'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList'
import { User } from '@model/User'
import { processXComCompleteTransaction, TransactionTypeId } from 'core'
import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config'
@ -29,31 +27,21 @@ import {
sendTransactionReceivedEmail,
} from '@/emails/sendEmailVariants'
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
import { LogError } from '@/server/LogError'
import { Context, getUser } from '@/server/context'
import {
InterruptiveSleepManager,
TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY,
} from '@/util/InterruptiveSleepManager'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { communityUser } from '@/util/communityUser'
import { fullName } from '@/util/utilities'
import { calculateBalance } from '@/util/validate'
import { virtualDecayTransaction, virtualLinkTransaction } from '@/util/virtualTransactions'
import { fullName } from 'core'
import { TRANSACTIONS_LOCK } from 'database'
import { Logger, getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLastTransaction } from 'database'
import { getLogger, Logger } from 'log4js'
import { BalanceResolver } from './BalanceResolver'
import { GdtResolver } from './GdtResolver'
import { getCommunityByIdentifier, getCommunityName, isHomeCommunity } from './util/communities'
import { getLastTransaction } from './util/getLastTransaction'
import { getCommunityName, isHomeCommunity } from './util/communities'
import { getTransactionList } from './util/getTransactionList'
import {
processXComCommittingSendCoins,
processXComPendingSendCoins,
} from './util/processXComSendCoins'
import { storeForeignUser } from './util/storeForeignUser'
import { transactionLinkSummary } from './util/transactionLinkSummary'
import { transferTransaction } from '@/apis/dltConnector'
@ -482,78 +470,14 @@ export class TransactionResolver {
await executeTransaction(amount, memo, senderUser, recipientUser, logger)
logger.info('successful executeTransaction')
} else {
// processing a x-community sendCoins
logger.info('X-Com: processing a x-community transaction...')
if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) {
throw new LogError('X-Community sendCoins disabled per configuration!')
}
const recipCom = await getCommunityByIdentifier(recipientCommunityIdentifier)
logger.debug('recipient community: ', recipCom?.id)
if (recipCom === null) {
throw new LogError(
`no recipient community found for identifier: ${recipientCommunityIdentifier}`,
)
}
if (recipCom !== null && recipCom.authenticatedAt === null) {
throw new LogError('recipient community is connected, but still not authenticated yet!')
}
let pendingResult: SendCoinsResponseJwtPayloadType | null = null
let committingResult: SendCoinsResult
const creationDate = new Date()
try {
pendingResult = await processXComPendingSendCoins(
recipCom,
homeCom,
creationDate,
amount,
memo,
senderUser,
recipientIdentifier,
)
logger.debug('processXComPendingSendCoins result: ', pendingResult)
if (pendingResult && pendingResult.vote && pendingResult.recipGradidoID) {
logger.debug('vor processXComCommittingSendCoins... ')
committingResult = await processXComCommittingSendCoins(
recipCom,
homeCom,
creationDate,
amount,
memo,
senderUser,
pendingResult,
)
logger.debug('processXComCommittingSendCoins result: ', committingResult)
if (!committingResult.vote) {
logger.fatal('FATAL ERROR: on processXComCommittingSendCoins for', committingResult)
throw new LogError(
'FATAL ERROR: on processXComCommittingSendCoins with ',
recipientCommunityIdentifier,
recipientIdentifier,
amount.toString(),
memo,
)
}
// after successful x-com-tx store the recipient as foreign user
logger.debug('store recipient as foreign user...')
if (await storeForeignUser(recipCom, committingResult)) {
logger.info(
'X-Com: new foreign user inserted successfully...',
recipCom.communityUuid,
committingResult.recipGradidoID,
)
}
}
} catch (err) {
throw new LogError(
'ERROR: on processXComCommittingSendCoins with ',
recipientCommunityIdentifier,
recipientIdentifier,
amount.toString(),
memo,
err,
)
}
await processXComCompleteTransaction(
senderUser.communityUuid,
senderUser.gradidoID,
recipientCommunityIdentifier,
recipientIdentifier,
amount.valueOf(),
memo,
)
}
return true
}

View File

@ -65,7 +65,7 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { printTimeDuration } from '@/util/time'
import { objectValuesToArray } from '@/util/utilities'
import { objectValuesToArray } from 'core'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'config-schema/test/testSetup'

View File

@ -1,7 +1,6 @@
import {
AppDatabase,
ContributionLink as DbContributionLink,
DltTransaction as DbDltTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
UserContact as DbUserContact,
@ -86,7 +85,7 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { communityDbUser } from '@/util/communityUser'
import { hasElopageBuys } from '@/util/hasElopageBuys'
import { durationInMinutesFromDates, getTimeDurationObject, printTimeDuration } from '@/util/time'
import { delay } from '@/util/utilities'
import { delay } from 'core'
import random from 'random-bigint'
import { randombytes_random } from 'sodium-native'

View File

@ -4,7 +4,7 @@ import { verifyAuthToken } from '@/apis/gms/GmsClient'
import { CONFIG } from '@/config'
import { GmsUserAuthenticationResult } from '@/graphql/model/GmsUserAuthenticationResult'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from 'core'
import { getLogger } from 'log4js'
const logger = getLogger(

View File

@ -78,6 +78,11 @@ export async function getCommunityByUuid(communityUuid: string): Promise<DbCommu
where: [{ communityUuid }],
})
}
export async function getCommunityByPublicKey(publicKey: Buffer): Promise<DbCommunity | null> {
return await DbCommunity.findOne({
where: [{ publicKey: publicKey }],
})
}
export async function getAuthenticatedCommunities(): Promise<DbCommunity[]> {
const dbCommunities: DbCommunity[] = await DbCommunity.find({

View File

@ -6,7 +6,7 @@ import { OpenCreation } from '@model/OpenCreation'
import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '@/graphql/resolver/const/const'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { LogError } from '@/server/LogError'
import { getFirstDayOfPreviousNMonth } from '@/util/utilities'
import { getFirstDayOfPreviousNMonth } from 'core'
import { AppDatabase } from 'database'
import { getLogger } from 'log4js'

View File

@ -0,0 +1,799 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Community, DltTransaction, Transaction } from 'database'
import { Decimal } from 'decimal.js-light'
// import { GraphQLClient } from 'graphql-request'
// import { Response } from 'graphql-request/dist/types'
import { GraphQLClient } from 'graphql-request'
import { Response } from 'graphql-request/dist/types'
import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { CONFIG } from '@/config'
import { TransactionTypeId } from 'core'
import { creations } from '@/seeds/creation'
import { creationFactory } from '@/seeds/factory/creation'
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 { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(
`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.sendTransactionsToDltConnector`,
)
/*
// Mock the GraphQLClient
jest.mock('graphql-request', () => {
const originalModule = jest.requireActual('graphql-request')
let testCursor = 0
return {
__esModule: true,
...originalModule,
GraphQLClient: jest.fn().mockImplementation((url: string) => {
if (url === 'invalid') {
throw new Error('invalid url')
}
return {
// why not using mockResolvedValueOnce or mockReturnValueOnce?
// I have tried, but it didn't work and return every time the first value
request: jest.fn().mockImplementation(() => {
testCursor++
if (testCursor === 4) {
return Promise.resolve(
// invalid, is 33 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
},
},
)
} else if (testCursor === 5) {
throw Error('Connection error')
} else {
return Promise.resolve(
// valid, is 32 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
)
}
}),
}
}),
}
})
let mutate: ApolloServerTestClient['mutate'],
query: ApolloServerTestClient['query'],
con: Connection
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
}
*/
async function createHomeCommunity(): Promise<Community> {
const homeCommunity = Community.create()
homeCommunity.foreign = false
homeCommunity.communityUuid = uuidv4()
homeCommunity.url = 'localhost'
homeCommunity.publicKey = Buffer.from('0x6e6a6c6d6feffe', 'hex')
await Community.save(homeCommunity)
return homeCommunity
}
async function createTxCREATION1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(100)
tx.balanceDate = new Date('01.01.2023 00:00:00')
tx.memo = 'txCREATION1'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION1.userGradidoID'
tx.userId = 1
tx.userName = 'txCREATION 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('01.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('01.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxCREATION2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(200)
tx.balanceDate = new Date('02.01.2023 00:00:00')
tx.memo = 'txCREATION2'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION2.userGradidoID'
tx.userId = 2
tx.userName = 'txCREATION 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('02.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('02.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxCREATION3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(300)
tx.balanceDate = new Date('03.01.2023 00:00:00')
tx.memo = 'txCREATION3'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION3.userGradidoID'
tx.userId = 3
tx.userName = 'txCREATION 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('03.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('03.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxSend1ToReceive2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(100)
tx.balance = new Decimal(1000)
tx.balanceDate = new Date('11.01.2023 00:00:00')
tx.memo = 'txSEND1 to txRECEIVE2'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND1.userGradidoID'
tx.userId = 1
tx.userName = 'txSEND 1'
tx.linkedUserGradidoID = 'txRECEIVE2.linkedUserGradidoID'
tx.linkedUserId = 2
tx.linkedUserName = 'txRECEIVE 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('11.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive2FromSend1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(100)
tx.balance = new Decimal(1300)
tx.balanceDate = new Date('11.01.2023 00:00:00')
tx.memo = 'txSEND1 to txRECEIVE2'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE2.linkedUserGradidoID'
tx.userId = 2
tx.userName = 'txRECEIVE 2'
tx.linkedUserGradidoID = 'txSEND1.userGradidoID'
tx.linkedUserId = 1
tx.linkedUserName = 'txSEND 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('11.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
/*
async function createTxSend2ToReceive3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(200)
tx.balance = new Decimal(1100)
tx.balanceDate = new Date('23.01.2023 00:00:00')
tx.memo = 'txSEND2 to txRECEIVE3'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND2.userGradidoID'
tx.userId = 2
tx.userName = 'txSEND 2'
tx.linkedUserGradidoID = 'txRECEIVE3.linkedUserGradidoID'
tx.linkedUserId = 3
tx.linkedUserName = 'txRECEIVE 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('23.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive3FromSend2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(200)
tx.balance = new Decimal(1500)
tx.balanceDate = new Date('23.01.2023 00:00:00')
tx.memo = 'txSEND2 to txRECEIVE3'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE3.linkedUserGradidoID'
tx.userId = 3
tx.userName = 'txRECEIVE 3'
tx.linkedUserGradidoID = 'txSEND2.userGradidoID'
tx.linkedUserId = 2
tx.linkedUserName = 'txSEND 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('23.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxSend3ToReceive1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(300)
tx.balance = new Decimal(1200)
tx.balanceDate = new Date('31.01.2023 00:00:00')
tx.memo = 'txSEND3 to txRECEIVE1'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND3.userGradidoID'
tx.userId = 3
tx.userName = 'txSEND 3'
tx.linkedUserGradidoID = 'txRECEIVE1.linkedUserGradidoID'
tx.linkedUserId = 1
tx.linkedUserName = 'txRECEIVE 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('31.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive1FromSend3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(300)
tx.balance = new Decimal(1300)
tx.balanceDate = new Date('31.01.2023 00:00:00')
tx.memo = 'txSEND3 to txRECEIVE1'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE1.linkedUserGradidoID'
tx.userId = 1
tx.userName = 'txRECEIVE 1'
tx.linkedUserGradidoID = 'txSEND3.userGradidoID'
tx.linkedUserId = 3
tx.linkedUserName = 'txSEND 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('31.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
*/
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: DataSource
}
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.destroy()
})
describe('create and send Transactions to DltConnector', () => {
let txCREATION1: Transaction
let txCREATION2: Transaction
let txCREATION3: Transaction
let txSEND1to2: Transaction
let txRECEIVE2From1: Transaction
// let txSEND2To3: Transaction
// let txRECEIVE3From2: Transaction
// let txSEND3To1: Transaction
// let txRECEIVE1From3: Transaction
beforeEach(() => {
jest.clearAllMocks()
})
afterEach(async () => {
await cleanDB()
})
describe('with 3 creations but inactive dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(false)
txCREATION2 = await createTxCREATION2(false)
txCREATION3 = await createTxCREATION3(false)
await createHomeCommunity()
CONFIG.DLT_CONNECTOR = false
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
expect(logger.info).nthCalledWith(2, 'sending to DltConnector currently not configured...')
})
})
describe('with 3 creations and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig)
await userFactory(testEnv, raeuberHotzenplotz)
await userFactory(testEnv, bobBaumeister)
let count = 0
for (const creation of creations) {
await creationFactory(testEnv, creation)
count++
// we need only 3 for testing
if (count >= 3) {
break
}
}
await createHomeCommunity()
CONFIG.DLT_CONNECTOR = true
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
sendTransaction: { succeed: true },
},
} as Response<unknown>
})
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
})
describe('with 3 verified creations, 1 sendCoins and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(true)
txCREATION2 = await createTxCREATION2(true)
txCREATION3 = await createTxCREATION3(true)
await createHomeCommunity()
txSEND1to2 = await createTxSend1ToReceive2(false)
txRECEIVE2From1 = await createTxReceive2FromSend1(false)
/*
txSEND2To3 = await createTxSend2ToReceive3()
txRECEIVE3From2 = await createTxReceive3FromSend2()
txSEND3To1 = await createTxSend3ToReceive1()
txRECEIVE1From3 = await createTxReceive1FromSend3()
*/
CONFIG.DLT_CONNECTOR = true
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
sendTransaction: { succeed: true },
},
} as Response<unknown>
})
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
/*
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
*/
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION1.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1',
verified: true,
createdAt: new Date('01.01.2023 00:00:10'),
verifiedAt: new Date('01.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION2.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2',
verified: true,
createdAt: new Date('02.01.2023 00:00:10'),
verifiedAt: new Date('02.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION3.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3',
verified: true,
createdAt: new Date('03.01.2023 00:00:10'),
verifiedAt: new Date('03.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txSEND1to2.id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txRECEIVE2From1.id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
/*
describe('with one Community of api 1_0 and not matching pubKey', () => {
beforeEach(async () => {
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
getPublicKey: {
publicKey: 'somePubKey',
},
},
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables1)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
jest.clearAllMocks()
// await validateCommunities()
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs not matching publicKeys', () => {
expect(logger.warn).toBeCalledWith(
'Federation: received not matching publicKey:',
'somePubKey',
expect.stringMatching('11111111111111111111111111111111'),
)
})
})
describe('with one Community of api 1_0 and matching pubKey', () => {
beforeEach(async () => {
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
getPublicKey: {
publicKey: '11111111111111111111111111111111',
},
},
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables1)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs community pubKey verified', () => {
expect(logger.info).toHaveBeenNthCalledWith(
3,
'Federation: verified community with',
'http//localhost:5001/api/',
)
})
})
describe('with two Communities of api 1_0 and 1_1', () => {
beforeEach(async () => {
jest.clearAllMocks()
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
getPublicKey: {
publicKey: '11111111111111111111111111111111',
},
},
} as Response<unknown>
})
const variables2 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_1',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables2)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs two communities found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
})
describe('with three Communities of api 1_0, 1_1 and 2_0', () => {
let dbCom: DbFederatedCommunity
beforeEach(async () => {
const variables3 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '2_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables3)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
dbCom = await DbFederatedCommunity.findOneOrFail({
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
})
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs three community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
it('logs unsupported api for community with api 2_0 ', () => {
expect(logger.warn).toBeCalledWith(
'Federation: dbCom with unsupported apiVersion',
dbCom.endPoint,
'2_0',
)
})
})
*/
})
})

View File

@ -2,9 +2,9 @@ import { TransactionLink as dbTransactionLink } from 'database'
import { Decimal } from 'decimal.js-light'
import { validate, version } from 'uuid'
import { Decay } from '@model/Decay'
import { Decay } from 'core'
import { getLastTransaction } from '@/graphql/resolver/util/getLastTransaction'
import { getLastTransaction } from 'database'
import { transactionLinkSummary } from '@/graphql/resolver/util/transactionLinkSummary'
import { calculateDecay } from 'shared'

View File

@ -2,7 +2,7 @@ import { Transaction as dbTransaction } from 'database'
import { Decimal } from 'decimal.js-light'
import { RemoveOptions, SaveOptions } from 'typeorm'
import { TransactionTypeId } from '@enum/TransactionTypeId'
import { TransactionTypeId } from 'core'
import { Transaction } from '@model/Transaction'
import { User } from '@model/User'

View File

@ -184,14 +184,22 @@
"dependencies": {
"database": "*",
"esbuild": "^0.25.2",
"i18n": "^0.15.1",
"jose": "^4.14.4",
"log4js": "^6.9.1",
"shared": "*",
"sodium-native": "^3.4.1",
"zod": "^3.25.61",
},
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@types/i18n": "^0.13.4",
"@types/node": "^17.0.21",
"@types/sodium-native": "^2.3.5",
"config-schema": "*",
"decimal.js-light": "^2.5.1",
"graphql-request": "5.0.0",
"jest": "27.2.4",
"type-graphql": "^1.1.1",
"typescript": "^4.9.5",
},
@ -228,6 +236,7 @@
"@types/geojson": "^7946.0.13",
"@types/jest": "27.0.2",
"@types/node": "^18.7.14",
"await-semaphore": "^0.1.3",
"crypto-random-bigint": "^2.1.1",
"jest": "27.2.4",
"ts-jest": "27.0.5",

View File

@ -24,20 +24,28 @@
"lint:fix": "biome check --error-on-warnings . --write",
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
},
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@types/node": "^17.0.21",
"type-graphql": "^1.1.1",
"typescript": "^4.9.5"
},
"dependencies": {
"database": "*",
"esbuild": "^0.25.2",
"i18n": "^0.15.1",
"jose": "^4.14.4",
"log4js": "^6.9.1",
"shared": "*",
"sodium-native": "^3.4.1",
"zod": "^3.25.61"
},
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@types/i18n": "^0.13.4",
"@types/node": "^17.0.21",
"@types/sodium-native": "^2.3.5",
"config-schema": "*",
"decimal.js-light": "^2.5.1",
"graphql-request": "5.0.0",
"jest": "27.2.4",
"type-graphql": "^1.1.1",
"typescript": "^4.9.5"
},
"engines": {
"node": ">=18"
}

27
core/src/config/index.ts Normal file
View File

@ -0,0 +1,27 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
import { LogLevel, validate } from 'config-schema'
import dotenv from 'dotenv'
import { schema } from './schema'
dotenv.config()
const federation = {
FEDERATION_BACKEND_SEND_ON_API: process.env.FEDERATION_BACKEND_SEND_ON_API ?? '1_0',
FEDERATION_XCOM_SENDCOINS_ENABLED:
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false,
// 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: parseInt(
process.env.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS ?? '3',
),
}
export const CONFIG = {
...federation,
}
validate(schema, CONFIG)

29
core/src/config/schema.ts Normal file
View File

@ -0,0 +1,29 @@
import Joi from 'joi'
export const schema = Joi.object({
FEDERATION_BACKEND_SEND_ON_API: Joi.string()
.pattern(/^\d+_\d+$/)
.default('1_0')
.description('API Version of sending requests to another communities, e.g., "1_0"')
.required(),
FEDERATION_XCOM_SENDCOINS_ENABLED: Joi.boolean()
.default(false)
.description('Enable or disable the federation send coins feature')
.optional(),
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID: Joi.string()
.uuid()
.default('56a55482-909e-46a4-bfa2-cd025e894ebc')
.description(
'UUID of the receiver community for federation cross-community transactions if the receiver is unknown',
)
.required(),
FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS: Joi.number()
.integer()
.min(0)
.default(3)
.description('Maximum number of retries for reverting send coins transactions')
.required(),
})

View File

@ -1,16 +1,15 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { LogError } from '@/server/LogError'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { ensureUrlEndsWithSlash } from '../../../util/utilities'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const'
import { revertSendCoins as revertSendCoinsQuery } from './query/revertSendCoins'
import { revertSettledSendCoins as revertSettledSendCoinsQuery } from './query/revertSettledSendCoins'
import { settleSendCoins as settleSendCoinsQuery } from './query/settleSendCoins'
import { voteForSendCoins as voteForSendCoinsQuery } from './query/voteForSendCoins'
import { EncryptedTransferArgs } from 'core'
import { EncryptedTransferArgs } from '../../../graphql/model/EncryptedTransferArgs'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.SendCoinsClient`)
@ -83,7 +82,9 @@ export class SendCoinsClient {
logger.debug(`settleSendCoins successful from endpoint=${this.endpoint}`)
return true
} catch (err) {
throw new LogError(`settleSendCoins failed for endpoint=${this.endpoint}`, err)
const errmsg = `settleSendCoins failed for endpoint=${this.endpoint}, err=${err}`
logger.error(errmsg)
throw new Error(errmsg)
}
}
@ -103,7 +104,9 @@ export class SendCoinsClient {
logger.debug(`revertSettledSendCoins successful from endpoint=${this.endpoint}`)
return true
} catch (err) {
throw new LogError(`revertSettledSendCoins failed for endpoint=${this.endpoint}`, err)
const errmsg = `revertSettledSendCoins failed for endpoint=${this.endpoint}, err=${err}`
logger.error(errmsg)
throw new Error(errmsg)
}
}
}

View File

@ -1,6 +1,6 @@
import { AbstractLoggingView } from 'database'
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
import { SendCoinsArgs } from '../model/SendCoinsArgs'
export class SendCoinsArgsLoggingView extends AbstractLoggingView {
public constructor(private self: SendCoinsArgs) {

View File

@ -1,6 +1,6 @@
import { AbstractLoggingView } from 'database'
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
import { SendCoinsResult } from '../model/SendCoinsResult'
export class SendCoinsResultLoggingView extends AbstractLoggingView {
public constructor(private self: SendCoinsResult) {

View File

@ -5,13 +5,4 @@ export const voteForSendCoins = gql`
voteForSendCoins(data: $args)
}
`
/*
{
vote
recipGradidoID
recipFirstName
recipLastName
recipAlias
}
}
*/

View File

@ -0,0 +1,3 @@
import { SendCoinsClient as V1_0_SendCoinsClient } from '../1_0/SendCoinsClient'
export class SendCoinsClient extends V1_0_SendCoinsClient {}

View File

@ -1,9 +1,8 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient'
import { SendCoinsClient as V1_1_SendCoinsClient } from '@/federation/client/1_1/SendCoinsClient'
import { ApiVersionType } from '@/federation/enum/apiVersionType'
import { SendCoinsClient as V1_0_SendCoinsClient } from './1_0/SendCoinsClient'
import { SendCoinsClient as V1_1_SendCoinsClient } from './1_1/SendCoinsClient'
import { ApiVersionType } from '../enum/apiVersionType'
type SendCoinsClient = V1_0_SendCoinsClient | V1_1_SendCoinsClient

View File

@ -1,6 +1,6 @@
import { AbstractLoggingView } from 'database'
import { Decay } from '@/graphql/model/Decay'
import { Decay } from '../model/Decay'
import type { Decay as DecayInterface } from 'shared'
export class DecayLoggingView extends AbstractLoggingView {

View File

@ -6,10 +6,10 @@ import { CommunityLoggingView, getHomeCommunity } from 'database'
import { verifyAndDecrypt } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.interpretEncryptedTransferArgs`)
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs`)
export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs): Promise<JwtPayloadType | null> => {
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.interpretEncryptedTransferArgs-method`)
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs-method`)
methodLogger.addContext('handshakeID', args.handshakeID)
methodLogger.debug('interpretEncryptedTransferArgs()... args:', args)
// first find with args.publicKey the community 'requestingCom', which starts the request

View File

@ -1,37 +1,160 @@
import {
CommunityLoggingView,
countOpenPendingTransactions,
Community as DbCommunity,
FederatedCommunity as DbFederatedCommunity,
PendingTransaction as DbPendingTransaction,
User as dbUser,
findTransactionLinkByCode,
findUserByIdentifier,
getCommunityByUuid,
PendingTransactionLoggingView,
CommunityLoggingView,
UserLoggingView,
countOpenPendingTransactions,
UserLoggingView
} from 'database'
import { Decimal } from 'decimal.js-light'
import { CONFIG } from '@/config'
import { CONFIG as CONFIG_CORE } from '../../config'
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
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 { SendCoinsClient as V1_0_SendCoinsClient } from '../../federation/client/1_0/SendCoinsClient'
import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult'
import { SendCoinsClientFactory } from '../../federation/client/SendCoinsClientFactory'
import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
import { encryptAndSign, PendingTransactionState, SendCoinsJwtPayloadType, SendCoinsResponseJwtPayloadType, verifyAndDecrypt } from 'shared'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { LogError } from '@/server/LogError'
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
import { fullName } from '@/util/utilities'
// import { LogError } from '@server/LogError'
import { calculateSenderBalance } from '../../util/calculateSenderBalance'
import { fullName } from '../../util/utilities'
import { getLogger } from 'log4js'
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
import { SendCoinsArgsLoggingView } from '@/federation/client/1_0/logging/SendCoinsArgsLogging.view'
import { SendCoinsResultLoggingView } from '@/federation/client/1_0/logging/SendCoinsResultLogging.view'
import { EncryptedTransferArgs } from 'core'
import { SendCoinsResultLoggingView } from '../../federation/client/1_0/logging/SendCoinsResultLogging.view'
import { EncryptedTransferArgs } from '../../graphql/model/EncryptedTransferArgs'
import { randombytes_random } from 'sodium-native'
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
import { storeForeignUser } from './storeForeignUser'
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins.${method}`)
export async function processXComCompleteTransaction(
senderCommunityUuid: string,
senderGradidoId: string,
recipientCommunityUuid: string,
recipientGradidoId: string,
amount: string,
memo: string,
code?: string,
recipientFirstName?: string,
recipientAlias?: string,
creationDate?: Date,
): Promise<boolean> {
const methodLogger = createLogger(`processXComCompleteTransaction`)
// processing a x-community sendCoins
methodLogger.info('processing a x-community transaction...')
if (!CONFIG_CORE.FEDERATION_XCOM_SENDCOINS_ENABLED) {
const errmsg = 'X-Community sendCoins disabled per configuration!'
methodLogger.error(errmsg)
throw new Error(errmsg)
}
const senderCom = await getCommunityByUuid(senderCommunityUuid)
methodLogger.debug('sender community: ', senderCom?.id)
if (senderCom === null) {
const errmsg = `no sender community found for identifier: ${senderCommunityUuid}`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
const senderUser = await findUserByIdentifier(senderGradidoId, senderCommunityUuid)
if (senderUser === null) {
const errmsg = `no sender user found for identifier: ${senderCommunityUuid}:${senderGradidoId}`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
const recipientCom = await getCommunityByUuid(recipientCommunityUuid)
methodLogger.debug('recipient community: ', recipientCom?.id)
if (recipientCom === null) {
const errmsg = `no recipient community found for identifier: ${recipientCommunityUuid}`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
if (recipientCom !== null && recipientCom.authenticatedAt === null) {
const errmsg = 'recipient community is connected, but still not authenticated yet!'
methodLogger.error(errmsg)
throw new Error(errmsg)
}
if(code !== undefined) {
try {
const dbTransactionLink = await findTransactionLinkByCode(code)
if (dbTransactionLink && dbTransactionLink.validUntil < new Date()) {
const errmsg = `TransactionLink ${code} is expired!`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
} catch (_err) {
const errmsg = `TransactionLink ${code} not found any more!`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
}
if(creationDate === undefined) {
creationDate = new Date()
}
let pendingResult: SendCoinsResponseJwtPayloadType | null = null
let committingResult: SendCoinsResult
try {
pendingResult = await processXComPendingSendCoins(
recipientCom,
senderCom,
creationDate,
new Decimal(amount),
memo,
senderUser,
recipientGradidoId,
)
methodLogger.debug('processXComPendingSendCoins result: ', pendingResult)
if (pendingResult && pendingResult.vote && pendingResult.recipGradidoID) {
methodLogger.debug('vor processXComCommittingSendCoins... ')
committingResult = await processXComCommittingSendCoins(
recipientCom,
senderCom,
creationDate,
new Decimal(amount),
memo,
senderUser,
pendingResult,
)
methodLogger.debug('processXComCommittingSendCoins result: ', committingResult)
if (!committingResult.vote) {
methodLogger.fatal('FATAL ERROR: on processXComCommittingSendCoins for', committingResult)
throw new Error(
'FATAL ERROR: on processXComCommittingSendCoins with ' +
recipientCom.communityUuid +
recipientGradidoId +
amount.toString() +
memo,
)
}
// after successful x-com-tx store the recipient as foreign user
methodLogger.debug('store recipient as foreign user...')
if (await storeForeignUser(recipientCom, committingResult)) {
methodLogger.info(
'X-Com: new foreign user inserted successfully...',
recipientCom.communityUuid,
committingResult.recipGradidoID,
)
}
}
} catch (err) {
const errmsg = `ERROR: on processXComCommittingSendCoins with ` +
recipientCommunityUuid +
recipientGradidoId +
amount.toString() +
memo +
err
methodLogger.error(errmsg)
throw new Error(errmsg)
}
return true
}
export async function processXComPendingSendCoins(
receiverCom: DbCommunity,
senderCom: DbCommunity,
@ -60,7 +183,7 @@ export async function processXComPendingSendCoins(
if (await countOpenPendingTransactions([sender.gradidoID, recipientIdentifier]) > 0) {
const errmsg = `There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`
methodLogger.error(errmsg)
throw new LogError(errmsg)
throw new Error(errmsg)
}
const handshakeID = randombytes_random().toString()
methodLogger.addContext('handshakeID', handshakeID)
@ -69,7 +192,7 @@ export async function processXComPendingSendCoins(
if (!senderBalance) {
const errmsg = `User has not enough GDD or amount is < 0`
methodLogger.error(errmsg)
throw new LogError(errmsg)
throw new Error(errmsg)
}
if(methodLogger.isDebugEnabled()) {
methodLogger.debug(`calculated senderBalance = ${JSON.stringify(senderBalance, null, 2)}`)
@ -78,7 +201,7 @@ export async function processXComPendingSendCoins(
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
where: {
publicKey: Buffer.from(receiverCom.publicKey),
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API,
},
})
const client = SendCoinsClientFactory.getInstance(receiverFCom)
@ -173,7 +296,7 @@ export async function processXComPendingSendCoins(
methodLogger.error(errmsg)
throw new Error(errmsg)
}
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
} while (CONFIG_CORE.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
const errmsg = `Error in reverting receiver pending transaction even after revertCount=${revertCount}` + JSON.stringify(err, null, 2)
methodLogger.error(errmsg)
throw new Error(errmsg)
@ -228,7 +351,7 @@ export async function processXComCommittingSendCoins(
userGradidoID: sender.gradidoID,
userName: fullName(sender.firstName, sender.lastName),
linkedUserCommunityUuid:
receiverCom.communityUuid ?? CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
receiverCom.communityUuid ?? CONFIG_CORE.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
linkedUserGradidoID: recipient.recipGradidoID ? recipient.recipGradidoID : undefined,
typeId: TransactionTypeId.SEND,
state: PendingTransactionState.NEW,
@ -242,7 +365,7 @@ export async function processXComCommittingSendCoins(
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
where: {
publicKey: Buffer.from(receiverCom.publicKey),
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API,
},
})
const client = SendCoinsClientFactory.getInstance(receiverFCom)
@ -252,7 +375,7 @@ export async function processXComCommittingSendCoins(
handshakeID,
pendingTx.linkedUserCommunityUuid
? pendingTx.linkedUserCommunityUuid
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
: CONFIG_CORE.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
pendingTx.linkedUserGradidoID!,
pendingTx.balanceDate.toISOString(),
pendingTx.amount.mul(-1),
@ -264,7 +387,7 @@ export async function processXComCommittingSendCoins(
)
payload.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid
? pendingTx.linkedUserCommunityUuid
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
: CONFIG_CORE.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
if (pendingTx.linkedUserGradidoID) {
payload.recipientUserIdentifier = pendingTx.linkedUserGradidoID
}
@ -305,20 +428,21 @@ export async function processXComCommittingSendCoins(
methodLogger.error(`Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`)
// revert the existing pending transaction on receiver side
let revertCount = 0
methodLogger.debug('first try to revertSetteledSendCoins of receiver')
methodLogger.debug('first try to revertSettledSendCoins of receiver')
do {
if (await client.revertSettledSendCoins(args)) {
methodLogger.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)
const errmsg = `Error in settle sender pending transaction: ${JSON.stringify(err, null, 2)}`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
throw new LogError(
`Error in reverting receiver pending transaction even after revertCount=${revertCount}`,
err,
)
} while (CONFIG_CORE.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
const errmsg = `Error in reverting receiver pending transaction even after revertCount=${revertCount}`
methodLogger.error(errmsg)
throw new Error(errmsg)
}
}
}

View File

@ -10,17 +10,16 @@ import {
} from 'database'
import { Decimal } from 'decimal.js-light'
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
import { PendingTransactionState } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { LogError } from '@/server/LogError'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
// import { LogError } from '@/server/LogError'
import { calculateSenderBalance } from 'core'
import { TRANSACTIONS_LOCK, getLastTransaction } from 'database'
import { getLogger } from 'log4js'
import { getLastTransaction } from './getLastTransaction'
const db = AppDatabase.getInstance()
const logger = getLogger(
`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.settlePendingSenderTransaction`,
`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.settlePendingSenderTransaction`,
)
export async function settlePendingSenderTransaction(
@ -53,15 +52,17 @@ export async function settlePendingSenderTransaction(
],
})
if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) {
throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient')
const errmsg = `There are more than 1 pending Transactions for Sender and/or Recipient`
logger.error(errmsg)
throw new Error(errmsg)
}
const lastTransaction = await getLastTransaction(senderUser.id)
if (lastTransaction?.id !== pendingTx.previous) {
throw new LogError(
`X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`,
)
const errmsg = `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`
logger.error(errmsg)
throw new Error(errmsg)
}
// transfer the pendingTx to the transactions table
@ -82,7 +83,9 @@ export async function settlePendingSenderTransaction(
pendingTx.balanceDate,
)
if (!sendBalance) {
throw new LogError(`Sender has not enough GDD or amount is < 0', sendBalance`)
const errmsg = 'Sender has not enough GDD or amount is < 0'
logger.error(errmsg)
throw new Error(errmsg)
}
transactionSend.balance = sendBalance?.balance ?? new Decimal(0)
transactionSend.balanceDate = pendingTx.balanceDate
@ -114,7 +117,8 @@ export async function settlePendingSenderTransaction(
// void sendTransactionsToDltConnector()
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('X-Com: send Transaction was not successful', e)
logger.error('X-Com: send Transaction was not successful', e)
throw new Error('X-Com: send Transaction was not successful')
} finally {
await queryRunner.release()
releaseLock()

View File

@ -1,10 +1,10 @@
import { Community as DbCommunity, User as DbUser } from 'database'
import { Community as DbCommunity, User as DbUser, findForeignUserByUuids } from 'database'
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult'
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.storeForeignUser`)
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.storeForeignUser`)
export async function storeForeignUser(
recipCom: DbCommunity,
@ -12,13 +12,7 @@ export async function storeForeignUser(
): Promise<boolean> {
if (recipCom.communityUuid !== null && committingResult.recipGradidoID !== null) {
try {
const user = await DbUser.findOne({
where: {
foreign: true,
communityUuid: recipCom.communityUuid,
gradidoID: committingResult.recipGradidoID,
},
})
const user = await findForeignUserByUuids(recipCom.communityUuid, committingResult.recipGradidoID)
if (!user) {
logger.debug(
'no foreignUser found for:',

View File

@ -1,3 +1,25 @@
export * from './validation/user'
export {SendCoinsClient as V1_0_SendCoinsClient} from './federation/client/1_0/SendCoinsClient'
export * from './federation/client/1_0/logging/SendCoinsArgsLogging.view'
export * from './federation/client/1_0/logging/SendCoinsResultLogging.view'
export * from './federation/client/1_0/model/SendCoinsArgs'
export * from './federation/client/1_0/model/SendCoinsResult'
export * from './federation/client/1_0/query/revertSendCoins'
export * from './federation/client/1_0/query/revertSettledSendCoins'
export * from './federation/client/1_0/query/settleSendCoins'
export * from './federation/client/1_0/query/voteForSendCoins'
export {SendCoinsClient as V1_1_SendCoinsClient} from './federation/client/1_1/SendCoinsClient'
export * from './federation/client/SendCoinsClientFactory'
export * from './federation/enum/apiVersionType'
export * from './graphql/enum/TransactionTypeId'
export * from './graphql/logging/DecayLogging.view'
export * from './graphql/logic/interpretEncryptedTransferArgs'
export * from './graphql/logic/processXComSendCoins'
export * from './graphql/logic/settlePendingSenderTransaction'
export * from './graphql/logic/storeForeignUser'
export * from './graphql/model/Decay'
export * from './graphql/model/EncryptedTransferArgs'
export * from './util/calculateSenderBalance'
export * from './util/utilities'
export * from './validation/user'
export * from './config/index'

View File

@ -1,8 +1,8 @@
import { Decimal } from 'decimal.js-light'
import { Decay } from '@model/Decay'
import { Decay } from '../graphql/model/Decay'
import { getLastTransaction } from '@/graphql/resolver/util/getLastTransaction'
import { getLastTransaction } from 'database'
import { calculateDecay } from 'shared'

View File

@ -44,8 +44,8 @@
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
// "paths": { }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": ["bun-types"], /* Type declaration files to be included in compilation. */

View File

@ -41,6 +41,7 @@
"@types/geojson": "^7946.0.13",
"@types/jest": "27.0.2",
"@types/node": "^18.7.14",
"await-semaphore": "^0.1.3",
"crypto-random-bigint": "^2.1.1",
"jest": "27.2.4",
"ts-jest": "27.0.5",

View File

@ -60,4 +60,5 @@ export const entities = [
export { latestDbVersion }
export * from './logging'
export * from './queries'
export * from './util'
export { AppDatabase } from './AppDatabase'

View File

@ -1,4 +1,4 @@
import { Community as DbCommunity } from '../entity/Community'
import { Community as DbCommunity } from '../entity'
/**
* Retrieves the home community, i.e., a community that is not foreign.
@ -8,4 +8,10 @@ export async function getHomeCommunity(): Promise<DbCommunity | null> {
return await DbCommunity.findOne({
where: { foreign: false },
})
}
}
export async function getCommunityByUuid(communityUuid: string): Promise<DbCommunity | null> {
return await DbCommunity.findOne({
where: [{ communityUuid }],
})
}

View File

@ -3,5 +3,7 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
export * from './user'
export * from './communities'
export * from './pendingTransactions'
export * from './transactions'
export * from './transactionLinks'
export const LOG4JS_QUERIES_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.queries`

View File

@ -0,0 +1,8 @@
import { TransactionLink as DbTransactionLink } from "../entity"
export async function findTransactionLinkByCode(code: string): Promise<DbTransactionLink> {
return await DbTransactionLink.findOneOrFail({
where: { code },
withDeleted: true,
})
}

View File

@ -1,4 +1,4 @@
import { Transaction as DbTransaction } from 'database'
import { Transaction as DbTransaction } from '../entity'
export const getLastTransaction = async (
userId: number,

View File

@ -61,3 +61,12 @@ export const findUserByIdentifier = async (
}
return null
}
export async function findForeignUserByUuids(
communityUuid: string,
gradidoID: string,
): Promise<DbUser | null> {
return DbUser.findOne({
where: { foreign: true, communityUuid, gradidoID },
})
}

View File

@ -0,0 +1,2 @@
export * from './TRANSACTIONS_LOCK'
export * from './TRANSACTION_LINK_LOCK'

View File

@ -35,8 +35,8 @@ export default defineConfig({
excludeSpecPattern: '*.js',
baseUrl: 'http://127.0.0.1:3000',
chromeWebSecurity: false,
defaultCommandTimeout: 25000,
pageLoadTimeout: 24000,
defaultCommandTimeout: 50000,
pageLoadTimeout: 32000,
supportFile: 'cypress/support/index.ts',
viewportHeight: 720,
viewportWidth: 1280,

View File

@ -3,7 +3,7 @@ import { GraphQLClient } from 'graphql-request'
import { getLogger, Logger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { EncryptedTransferArgs } from 'core/src/graphql/model/EncryptedTransferArgs'
import { EncryptedTransferArgs } from 'core'
import { authenticate } from './query/authenticate'
import { openConnectionCallback } from './query/openConnectionCallback'

View File

@ -3,7 +3,7 @@ import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { AuthenticationClient as V1_0_AuthenticationClient } from './1_0/AuthenticationClient'
import { AuthenticationClient as V1_1_AuthenticationClient } from './1_1/AuthenticationClient'
import { ApiVersionType } from './enum/ApiVersionType'
import { ApiVersionType } from 'core'
type AuthenticationClient = V1_0_AuthenticationClient | V1_1_AuthenticationClient

View File

@ -1,4 +0,0 @@
export enum ApiVersionType {
V1_0 = '1_0',
V1_1 = '1_1',
}

View File

@ -0,0 +1,54 @@
import { getLogger } from "log4js"
import { Arg, Mutation, Resolver } from "type-graphql"
import { LOG4JS_BASE_CATEGORY_NAME } from "@/config/const"
import { interpretEncryptedTransferArgs } from "core"
import { EncryptedTransferArgs } from "core"
import { DisburseJwtPayloadType } from "shared"
import { processXComCompleteTransaction } from "core"
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.resolver.DisbursementResolver.${method}`)
@Resolver()
export class DisbursementResolver {
@Mutation(() => String)
async processDisburseJwtOnSenderCommunity(
@Arg('data')
args: EncryptedTransferArgs,
): Promise<string> {
const methodLogger = createLogger(`processDisburseJwtOnSenderCommunity`)
methodLogger.addContext('handshakeID', args.handshakeID)
if(methodLogger.isDebugEnabled()) {
methodLogger.debug(`processDisburseJwtOnSenderCommunity() via apiVersion=1_0 ...`, args)
}
const authArgs = await interpretEncryptedTransferArgs(args) as DisburseJwtPayloadType
if (!authArgs) {
const errmsg = `invalid disbursement payload of requesting community with publicKey` + args.publicKey
methodLogger.error(errmsg)
throw new Error(errmsg)
}
if(methodLogger.isDebugEnabled()) {
methodLogger.debug(`processDisburseJwtOnSenderCommunity() via apiVersion=1_0 ...`, authArgs)
}
let result = 'Disbursement of Redeem-Link failed!'
try {
if(await processXComCompleteTransaction(
authArgs.sendercommunityuuid,
authArgs.sendergradidoid,
authArgs.recipientcommunityuuid,
authArgs.recipientgradidoid,
authArgs.amount,
authArgs.memo,
authArgs.code,
authArgs.recipientfirstname,
authArgs.recipientalias,
)) {
result = 'Disbursement of Redeem-Link successfull!'
}
} catch (err) {
result = `Error in Disbursement of Redeem-Link: ` + err
methodLogger.error(result)
}
return result
}
}

View File

@ -1,9 +1,10 @@
import { NonEmptyArray } from 'type-graphql'
import { AuthenticationResolver } from './resolver/AuthenticationResolver'
import { DisbursementResolver } from './resolver/DisbursementResolver'
import { PublicCommunityInfoResolver } from './resolver/PublicCommunityInfoResolver'
import { PublicKeyResolver } from './resolver/PublicKeyResolver'
import { SendCoinsResolver } from './resolver/SendCoinsResolver'
export const getApiResolvers = (): NonEmptyArray<Function> => {
return [AuthenticationResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver]
return [AuthenticationResolver, DisbursementResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver]
}

View File

@ -340,10 +340,7 @@ function setRedeemJwtLinkInformation() {
if (deepCopy) {
// recipientUser is only set if the user is logged in
if (store.state.gradidoID !== null) {
// console.log(
// 'TransactionLink.setRedeemJwtLinkInformation... gradidoID=',
// store.state.gradidoID,
// )
// console.log('TransactionLink.setRedeemJwtLinkInformation... gradidoID=', store.state.gradidoID)
deepCopy.queryTransactionLink.recipientUser = {
__typename: 'User',
gradidoID: store.state.gradidoID,
@ -362,11 +359,23 @@ function setRedeemJwtLinkInformation() {
async function mutationLink(amount) {
// console.log('TransactionLink.mutationLink... params=', params)
// console.log('TransactionLink.mutationLink... linkData.value=', linkData.value)
// console.log('TransactionLink.mutationLink... linkData=', linkData)
if (isRedeemJwtLink.value) {
// console.log('TransactionLink.mutationLink... trigger disbursement from recipient-community')
try {
await disburseMutate({
code: params.code,
senderCommunityUuid: linkData.value.senderCommunity.uuid,
senderGradidoId: linkData.value.senderUser.gradidoID,
recipientCommunityUuid: linkData.value.recipientCommunity.uuid,
recipientCommunityName: linkData.value.recipientCommunity.name,
recipientGradidoId: linkData.value.recipientUser.gradidoID,
recipientFirstName: linkData.value.recipientUser.firstName,
code: linkData.value.code,
amount: linkData.value.amount,
memo: linkData.value.memo,
validUntil: linkData.value.validUntil,
recipientAlias: linkData.value.recipientUser.alias,
})
toastSuccess(t('gdd_per_link.disbured', { n: amount }))
await router.push('/overview')

View File

@ -12,4 +12,5 @@ export * from './jwt/payloadtypes/OpenConnectionCallbackJwtPayloadType'
export * from './jwt/payloadtypes/RedeemJwtPayloadType'
export * from './jwt/payloadtypes/SendCoinsJwtPayloadType'
export * from './jwt/payloadtypes/SendCoinsResponseJwtPayloadType'
export * from './jwt/payloadtypes/SignedTransferPayloadType'

View File

@ -17,6 +17,7 @@ export class DisburseJwtPayloadType extends JwtPayloadType {
recipientalias: string
constructor(
handshakeID: string,
senderCommunityUuid: string,
senderGradidoId: string,
recipientCommunityUuid: string,
@ -29,9 +30,7 @@ export class DisburseJwtPayloadType extends JwtPayloadType {
validUntil: string,
recipientAlias: string,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super('handshakeID')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
super(handshakeID)
this.tokentype = DisburseJwtPayloadType.DISBURSE_ACTIVATION_TYPE
this.sendercommunityuuid = senderCommunityUuid
this.sendergradidoid = senderGradidoId

View File

@ -9,9 +9,7 @@ export class EncryptedJWEJwtPayloadType extends JwtPayloadType {
handshakeID: string,
jwe: string,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(handshakeID)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.tokentype = EncryptedJWEJwtPayloadType.ENCRYPTED_JWE_TYPE
this.jwe = jwe
}

View File

@ -11,9 +11,7 @@ export class OpenConnectionCallbackJwtPayloadType extends JwtPayloadType {
oneTimeCode: string,
url: string,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(handshakeID)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.tokentype = OpenConnectionCallbackJwtPayloadType.OPEN_CONNECTION_CALLBACK_TYPE
this.oneTimeCode = oneTimeCode
this.url = url

View File

@ -9,9 +9,7 @@ export class OpenConnectionJwtPayloadType extends JwtPayloadType {
handshakeID: string,
url: string,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(handshakeID)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.tokentype = OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE
this.url = url
}

View File

@ -20,9 +20,7 @@ export class RedeemJwtPayloadType extends JwtPayloadType {
memo: string,
validUntil: string,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super('handshakeID')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.tokentype = RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE
this.sendercommunityuuid = senderCom
this.sendergradidoid = senderUser

View File

@ -26,9 +26,7 @@ export class SendCoinsJwtPayloadType extends JwtPayloadType {
senderUserName: string,
senderAlias?: string | null,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(handshakeID)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.tokentype = SendCoinsJwtPayloadType.SEND_COINS_TYPE
this.recipientCommunityUuid = recipientCommunityUuid
this.recipientUserIdentifier = recipientUserIdentifier

View File

@ -17,9 +17,7 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType {
recipLastName: string | null,
recipAlias: string | null,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super(handshakeID)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.tokentype = SendCoinsResponseJwtPayloadType.SEND_COINS_RESPONSE_TYPE
this.vote = vote
this.recipGradidoID = recipGradidoID

View File

@ -0,0 +1,19 @@
import { JwtPayloadType } from './JwtPayloadType'
export class SignedTransferPayloadType extends JwtPayloadType {
static SIGNED_TRANSFER_TYPE = 'signed-transfer'
publicKey: string
jwt: string
constructor(
publicKey: string,
jwt: string,
handshakeID: string,
) {
super(handshakeID)
this.tokentype = SignedTransferPayloadType.SIGNED_TRANSFER_TYPE
this.publicKey = publicKey
this.jwt = jwt
}
}