direct sending transaction types to dlt

This commit is contained in:
einhornimmond 2025-09-24 16:25:43 +02:00
parent 9f22e5b16d
commit 4e3d119f12
17 changed files with 323 additions and 89 deletions

View File

@ -7,8 +7,12 @@ import {
Community as DbCommunity,
Contribution as DbContribution,
DltTransaction as DbDltTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
getHomeCommunity,
getCommunityByUuid,
getHomeCommunity,
getUserById,
UserLoggingView,
} from 'database'
import { TransactionDraft } from './model/TransactionDraft'
@ -92,7 +96,15 @@ export async function transferTransaction(
memo: string,
createdAt: Date
): Promise<DbDltTransaction | null> {
// load community if not already loaded, maybe they are remote communities
if (!senderUser.community) {
senderUser.community = await getCommunityByUuid(senderUser.communityUuid)
}
if (!recipientUser.community) {
recipientUser.community = await getCommunityByUuid(recipientUser.communityUuid)
}
logger.info(`sender user: ${new UserLoggingView(senderUser)}`)
logger.info(`recipient user: ${new UserLoggingView(recipientUser)}`)
const draft = TransactionDraft.createTransfer(senderUser, recipientUser, amount, memo, createdAt)
if (draft && dltConnectorClient) {
const clientResponse = dltConnectorClient.sendTransaction(draft)
@ -104,4 +116,50 @@ export async function transferTransaction(
return null
}
export async function deferredTransferTransaction(senderUser: DbUser, transactionLink: DbTransactionLink)
: Promise<DbDltTransaction | null> {
// load community if not already loaded
if (!senderUser.community) {
senderUser.community = await getCommunityByUuid(senderUser.communityUuid)
}
const draft = TransactionDraft.createDeferredTransfer(senderUser, transactionLink)
if (draft && dltConnectorClient) {
const clientResponse = dltConnectorClient.sendTransaction(draft)
let dltTransaction = new DbDltTransaction()
dltTransaction.typeId = DltTransactionType.DEFERRED_TRANSFER
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
return await dltTransaction.save()
}
return null
}
export async function redeemDeferredTransferTransaction(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser)
: Promise<DbDltTransaction | null> {
// load user and communities if not already loaded
if (!transactionLink.user) {
logger.debug('load sender user')
transactionLink.user = await getUserById(transactionLink.userId, true, false)
}
if (!transactionLink.user.community) {
logger.debug('load sender community')
transactionLink.user.community = await getCommunityByUuid(transactionLink.user.communityUuid)
}
if (!recipientUser.community) {
logger.debug('load recipient community')
recipientUser.community = await getCommunityByUuid(recipientUser.communityUuid)
}
logger.debug(`sender: ${new UserLoggingView(transactionLink.user)}`)
logger.debug(`recipient: ${new UserLoggingView(recipientUser)}`)
const draft = TransactionDraft.redeemDeferredTransfer(transactionLink, amount, createdAt, recipientUser)
if (draft && dltConnectorClient) {
const clientResponse = dltConnectorClient.sendTransaction(draft)
let dltTransaction = new DbDltTransaction()
dltTransaction.typeId = DltTransactionType.REDEEM_DEFERRED_TRANSFER
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
return await dltTransaction.save()
}
return null
}

View File

@ -3,10 +3,17 @@ import { AccountType } from '@dltConnector/enum/AccountType'
import { TransactionType } from '@dltConnector/enum/TransactionType'
import { AccountIdentifier } from './AccountIdentifier'
import { Community as DbCommunity, Contribution as DbContribution, User as DbUser } from 'database'
import {
Community as DbCommunity,
Contribution as DbContribution,
TransactionLink as DbTransactionLink,
User as DbUser
} from 'database'
import { CommunityAccountIdentifier } from './CommunityAccountIdentifier'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { IdentifierSeed } from './IdentifierSeed'
import { CODE_VALID_DAYS_DURATION } from '@/graphql/resolver/const/const'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.dltConnector.model.TransactionDraft`)
@ -21,7 +28,7 @@ export class TransactionDraft {
createdAt: string
// only for creation transaction
targetDate?: string
// only for deferred transaction
// only for deferred transaction, duration in seconds
timeoutDuration?: number
// only for register address
accountType?: AccountType
@ -75,4 +82,48 @@ export class TransactionDraft {
draft.memo = memo
return draft
}
static createDeferredTransfer(sendingUser: DbUser, transactionLink: DbTransactionLink)
: TransactionDraft | null {
if (!sendingUser.community) {
throw new Error(`missing community for user ${sendingUser.id}`)
}
const senderUserTopic = sendingUser.community.hieroTopicId
if (!senderUserTopic) {
throw new Error(`missing topicId for community ${sendingUser.community.id}`)
}
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(senderUserTopic, new CommunityAccountIdentifier(sendingUser.gradidoID))
draft.linkedUser = new AccountIdentifier(senderUserTopic, new IdentifierSeed(transactionLink.code))
draft.type = TransactionType.GRADIDO_DEFERRED_TRANSFER
draft.createdAt = transactionLink.createdAt.toISOString()
draft.amount = transactionLink.amount.toString()
draft.memo = transactionLink.memo
draft.timeoutDuration = CODE_VALID_DAYS_DURATION * 24 * 60 * 60
return draft
}
static redeemDeferredTransfer(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser): TransactionDraft | null {
if (!transactionLink.user.community) {
throw new Error(`missing community for user ${transactionLink.user.id}`)
}
if (!recipientUser.community) {
throw new Error(`missing community for user ${recipientUser.id}`)
}
const senderUserTopic = transactionLink.user.community.hieroTopicId
if (!senderUserTopic) {
throw new Error(`missing topicId for community ${transactionLink.user.community.id}`)
}
const recipientUserTopic = recipientUser.community.hieroTopicId
if (!recipientUserTopic) {
throw new Error(`missing topicId for community ${recipientUser.community.id}`)
}
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(senderUserTopic, new IdentifierSeed(transactionLink.code))
draft.linkedUser = new AccountIdentifier(recipientUserTopic, new CommunityAccountIdentifier(recipientUser.gradidoID))
draft.type = TransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER
draft.createdAt = createdAt.toISOString()
draft.amount = amount
return draft
}
}

View File

@ -436,9 +436,6 @@ export class ContributionResolver {
): Promise<boolean> {
const logger = createLogger()
logger.addContext('contribution', id)
let transaction: DbTransaction
let dltTransactionPromise: Promise<DbDltTransaction | null>
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
@ -463,8 +460,7 @@ export class ContributionResolver {
throw new LogError('Can not confirm contribution since the user was deleted')
}
const receivedCallDate = new Date()
dltTransactionPromise = contributionTransaction(contribution, moderatorUser, receivedCallDate)
const dltTransactionPromise = contributionTransaction(contribution, moderatorUser, receivedCallDate)
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
validateContribution(
creations,
@ -476,7 +472,6 @@ export class ContributionResolver {
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
const lastTransaction = await getLastTransaction(contribution.userId)
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
@ -493,7 +488,7 @@ export class ContributionResolver {
}
newBalance = newBalance.add(contribution.amount.toString())
transaction = new DbTransaction()
let transaction = new DbTransaction()
transaction.typeId = TransactionTypeId.CREATION
transaction.memo = contribution.memo
transaction.userId = contribution.userId
@ -520,7 +515,7 @@ export class ContributionResolver {
await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
await queryRunner.commitTransaction()
logger.info('creation commited successfuly.')
await sendContributionConfirmedEmail({
firstName: user.firstName,
@ -536,6 +531,17 @@ export class ContributionResolver {
contribution.createdAt,
),
})
// update transaction id in dlt transaction tables
// wait for finishing transaction by dlt-connector/hiero
const dltStartTime = new Date()
const dltTransaction = await dltTransactionPromise
if(dltTransaction) {
dltTransaction.transactionId = transaction.id
await dltTransaction.save()
}
const dltEndTime = new Date()
logger.debug(`dlt-connector contribution finished in ${dltEndTime.getTime() - dltStartTime.getTime()} ms`)
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Creation was not successful', e)
@ -546,16 +552,6 @@ export class ContributionResolver {
} finally {
releaseLock()
}
// update transaction id in dlt transaction tables
// wait for finishing transaction by dlt-connector/hiero
const startTime = new Date()
const dltTransaction = await dltTransactionPromise
if(dltTransaction) {
dltTransaction.transactionId = transaction.id
await dltTransaction.save()
}
const endTime = new Date()
logger.debug(`dlt-connector contribution finished in ${endTime.getTime() - startTime.getTime()} ms`)
return true
}

View File

@ -14,10 +14,13 @@ import { User } from '@model/User'
import { QueryLinkResult } from '@union/QueryLinkResult'
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
import {
AppDatabase, Community as DbCommunity, Contribution as DbContribution,
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
AppDatabase, Contribution as DbContribution,
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity,
DltTransaction as DbDltTransaction,
Transaction as DbTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
findModeratorCreatingContributionLink,
findTransactionLinkByCode,
getHomeCommunity
} from 'database'
@ -33,14 +36,10 @@ import {
} from '@/event/Events'
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import {
InterruptiveSleepManager,
TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY,
} from '@/util/InterruptiveSleepManager'
import { calculateBalance } from '@/util/validate'
import { fullName } from 'core'
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, RedeemJwtPayloadType, verify } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
@ -58,6 +57,8 @@ import {
import { getUserCreation, validateContribution } from './util/creations'
import { transactionLinkList } from './util/transactionLinkList'
import { SignedTransferPayloadType } from 'shared'
import { contributionTransaction, deferredTransferTransaction, redeemDeferredTransferTransaction } from '@/apis/dltConnector'
import { CODE_VALID_DAYS_DURATION } from './const/const'
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionLinkResolver.${method}`)
@ -71,7 +72,7 @@ export const transactionLinkCode = (date: Date): string => {
)
}
const CODE_VALID_DAYS_DURATION = 14
const db = AppDatabase.getInstance()
export const transactionLinkExpireDate = (date: Date): Date => {
@ -109,11 +110,20 @@ export class TransactionLinkResolver {
transactionLink.code = transactionLinkCode(createdDate)
transactionLink.createdAt = createdDate
transactionLink.validUntil = validUntil
const dltTransactionPromise = deferredTransferTransaction(user, transactionLink)
await DbTransactionLink.save(transactionLink).catch((e) => {
throw new LogError('Unable to save transaction link', e)
})
await EVENT_TRANSACTION_LINK_CREATE(user, transactionLink, amount)
// wait for dlt transaction to be created
const startTime = Date.now()
const dltTransaction = await dltTransactionPromise
const endTime = Date.now()
createLogger('createTransactionLink').debug(`dlt transaction created in ${endTime - startTime} ms`)
if (dltTransaction) {
dltTransaction.transactionLinkId = transactionLink.id
await DbDltTransaction.save(dltTransaction)
}
return new TransactionLink(transactionLink, new User(user))
}
@ -137,7 +147,6 @@ export class TransactionLinkResolver {
user.id,
)
}
if (transactionLink.redeemedBy) {
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
}
@ -146,7 +155,19 @@ export class TransactionLinkResolver {
throw new LogError('Transaction link could not be deleted', e)
})
transactionLink.user = user
const dltTransactionPromise = redeemDeferredTransferTransaction(transactionLink, transactionLink.amount.toString(), transactionLink.deletedAt!, user)
await EVENT_TRANSACTION_LINK_DELETE(user, transactionLink)
// wait for dlt transaction to be created
const startTime = Date.now()
const dltTransaction = await dltTransactionPromise
const endTime = Date.now()
createLogger('deleteTransactionLink').debug(`dlt transaction created in ${endTime - startTime} ms`)
if (dltTransaction) {
dltTransaction.transactionLinkId = transactionLink.id
await DbDltTransaction.save(dltTransaction)
}
return true
}
@ -279,7 +300,7 @@ export class TransactionLinkResolver {
throw new LogError('Contribution link has unknown cycle', contributionLink.cycle)
}
}
const moderatorPromise = findModeratorCreatingContributionLink(contributionLink)
const creations = await getUserCreation(user.id, clientTimezoneOffset)
methodLogger.info('open creations', creations)
validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset)
@ -293,6 +314,12 @@ export class TransactionLinkResolver {
contribution.contributionType = ContributionType.LINK
contribution.contributionStatus = ContributionStatus.CONFIRMED
let dltTransactionPromise: Promise<DbDltTransaction | null> = Promise.resolve(null)
const moderator = await moderatorPromise
if (moderator) {
dltTransactionPromise = contributionTransaction(contribution, moderator, now)
}
await queryRunner.manager.insert(DbContribution, contribution)
const lastTransaction = await getLastTransaction(user.id)
@ -338,6 +365,17 @@ export class TransactionLinkResolver {
contributionLink,
contributionLink.amount,
)
if (dltTransactionPromise) {
const startTime = new Date()
const dltTransaction = await dltTransactionPromise
const endTime = new Date()
methodLogger.info(`dlt-connector transaction finished in ${endTime.getTime() - startTime.getTime()} ms`)
if (dltTransaction) {
dltTransaction.transactionId = transaction.id
await dltTransaction.save()
}
}
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Creation from contribution link was not successful', e)
@ -346,10 +384,7 @@ export class TransactionLinkResolver {
}
} finally {
releaseLock()
}
// notify dlt-connector loop for new work
InterruptiveSleepManager.getInstance().interrupt(TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY)
}
return true
} else {
const now = new Date()
@ -399,8 +434,6 @@ export class TransactionLinkResolver {
} finally {
releaseLinkLock()
}
// notify dlt-connector loop for new work
InterruptiveSleepManager.getInstance().interrupt(TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY)
return true
}
}

View File

@ -6,7 +6,9 @@ import {
Transaction as dbTransaction,
TransactionLink as dbTransactionLink,
User as dbUser,
findUserByIdentifier
findUserByIdentifier,
TransactionLoggingView,
UserLoggingView
} from 'database'
import { Decimal } from 'decimal.js-light'
import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
@ -43,7 +45,7 @@ import { GdtResolver } from './GdtResolver'
import { getCommunityName, isHomeCommunity } from './util/communities'
import { getTransactionList } from './util/getTransactionList'
import { transactionLinkSummary } from './util/transactionLinkSummary'
import { transferTransaction } from '@/apis/dltConnector'
import { transferTransaction, redeemDeferredTransferTransaction } from '@/apis/dltConnector'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionResolver`)
@ -60,13 +62,15 @@ export const executeTransaction = async (
let dltTransactionPromise: Promise<DbDltTransaction | null> = Promise.resolve(null)
if (!transactionLink) {
dltTransactionPromise = transferTransaction(sender, recipient, amount.toString(), memo, receivedCallDate)
} else {
dltTransactionPromise = redeemDeferredTransferTransaction(transactionLink, amount.toString(), receivedCallDate, recipient)
}
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
logger.info('executeTransaction', amount, memo, sender, recipient)
logger.info('executeTransaction', memo)
if (await countOpenPendingTransactions([sender.gradidoID, recipient.gradidoID]) > 0) {
throw new LogError(
@ -85,7 +89,7 @@ export const executeTransaction = async (
receivedCallDate,
transactionLink,
)
logger.debug(`calculated Balance=${sendBalance}`)
logger.debug(`calculated balance=${sendBalance?.balance.toString()} decay=${sendBalance?.decay.decay.toString()} lastTransactionId=${sendBalance?.lastTransactionId}`)
if (!sendBalance) {
throw new LogError('User has not enough GDD or amount is < 0', sendBalance)
}
@ -144,7 +148,7 @@ export const executeTransaction = async (
// Save linked transaction id for send
transactionSend.linkedTransactionId = transactionReceive.id
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
logger.debug('send Transaction updated', transactionSend)
logger.debug('send Transaction updated', new TransactionLoggingView(transactionSend).toJSON())
if (transactionLink) {
logger.info('transactionLink', transactionLink)
@ -158,21 +162,23 @@ export const executeTransaction = async (
}
await queryRunner.commitTransaction()
// update dltTransaction with transactionId
const dltTransaction = await dltTransactionPromise
if (dltTransaction) {
dltTransaction.transactionId = transactionSend.id
await dltTransaction.save()
}
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
await EVENT_TRANSACTION_RECEIVE(
recipient,
sender,
transactionReceive,
transactionReceive.amount,
)
// update dltTransaction with transactionId
const startTime = new Date()
const dltTransaction = await dltTransactionPromise
const endTime = new Date()
logger.debug(`dlt-connector transaction finished in ${endTime.getTime() - startTime.getTime()} ms`)
if (dltTransaction) {
dltTransaction.transactionId = transactionSend.id
await dltTransaction.save()
}
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Transaction was not successful', e)

View File

@ -12,3 +12,4 @@ export const MEMO_MAX_CHARS = 512
export const MEMO_MIN_CHARS = 5
export const DEFAULT_PAGINATION_PAGE_SIZE = 25
export const FRONTEND_CONTRIBUTIONS_ITEM_ANCHOR_PREFIX = 'contributionListItem-'
export const CODE_VALID_DAYS_DURATION = 14

View File

@ -1,4 +1,4 @@
import { delay } from './utilities'
import { delay } from 'core'
/**
* Sleep, that can be interrupted

View File

@ -8,7 +8,7 @@ enum OptInType {
}
export class UserContactLoggingView extends AbstractLoggingView {
public constructor(private self: UserContact) {
public constructor(private self: UserContact, private showUser = true) {
super()
}
@ -16,7 +16,7 @@ export class UserContactLoggingView extends AbstractLoggingView {
return {
id: this.self.id,
type: this.self.type,
user: this.self.user
user: this.showUser && this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
email: this.self.email?.substring(0, 3) + '...',

View File

@ -4,6 +4,7 @@ import { ContributionLoggingView } from './ContributionLogging.view'
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'
import { UserContactLoggingView } from './UserContactLogging.view'
import { UserRoleLoggingView } from './UserRoleLogging.view'
import { CommunityLoggingView } from './CommunityLogging.view'
enum PasswordEncryptionType {
NO_PASSWORD = 0,
@ -21,10 +22,12 @@ export class UserLoggingView extends AbstractLoggingView {
id: this.self.id,
foreign: this.self.foreign,
gradidoID: this.self.gradidoID,
communityUuid: this.self.communityUuid,
community: this.self.community
? new CommunityLoggingView(this.self.community).toJSON()
: { id: this.self.communityUuid },
alias: this.self.alias?.substring(0, 3) + '...',
emailContact: this.self.emailContact
? new UserContactLoggingView(this.self.emailContact).toJSON()
? new UserContactLoggingView(this.self.emailContact, false).toJSON()
: { id: this.self.emailId },
firstName: this.self.firstName?.substring(0, 3) + '...',
lastName: this.self.lastName?.substring(0, 3) + '...',
@ -35,7 +38,7 @@ export class UserLoggingView extends AbstractLoggingView {
hideAmountGDD: this.self.hideAmountGDD,
hideAmountGDT: this.self.hideAmountGDT,
userRoles: this.self.userRoles
? this.self.userRoles.map((userRole) => new UserRoleLoggingView(userRole).toJSON())
? this.self.userRoles.map((userRole) => new UserRoleLoggingView(userRole, false).toJSON())
: undefined,
referrerId: this.self.referrerId,
contributionLinkId: this.self.contributionLinkId,
@ -50,7 +53,7 @@ export class UserLoggingView extends AbstractLoggingView {
: undefined,
userContacts: this.self.userContacts
? this.self.userContacts.map((userContact) =>
new UserContactLoggingView(userContact).toJSON(),
new UserContactLoggingView(userContact, false).toJSON(),
)
: undefined,
}

View File

@ -3,14 +3,14 @@ import { AbstractLoggingView } from './AbstractLogging.view'
import { UserLoggingView } from './UserLogging.view'
export class UserRoleLoggingView extends AbstractLoggingView {
public constructor(private self: UserRole) {
public constructor(private self: UserRole, private showUser = true) {
super()
}
public toJSON(): any {
return {
id: this.self.id,
user: this.self.user
user: this.showUser && this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
role: this.self.role,

View File

@ -0,0 +1,19 @@
import {
ContributionLink as DbContributionLink,
Event as DbEvent,
User as DbUser
} from '../entity'
export async function findModeratorCreatingContributionLink(contributionLink: DbContributionLink): Promise<DbUser | undefined> {
const event = await DbEvent.findOne(
{
where: {
involvedContributionLinkId: contributionLink.id,
// todo: move event types into db
type: 'ADMIN_CONTRIBUTION_LINK_CREATE'
},
relations: { actingUser: true }
}
)
return event?.actingUser
}

View File

@ -5,5 +5,6 @@ export * from './communities'
export * from './pendingTransactions'
export * from './transactions'
export * from './transactionLinks'
export * from './events'
export const LOG4JS_QUERIES_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.queries`

View File

@ -12,6 +12,13 @@ export async function aliasExists(alias: string): Promise<boolean> {
return user !== null
}
export async function getUserById(id: number, withCommunity: boolean = false, withEmailContact: boolean = false): Promise<DbUser> {
return DbUser.findOneOrFail({
where: { id },
relations: { community: withCommunity, emailContact: withEmailContact },
})
}
/**
*
* @param identifier could be gradidoID, alias or email of user

View File

@ -4,6 +4,7 @@
"": {
"name": "dlt-connector",
"dependencies": {
"async-exit-hook": "^2.0.1",
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js",
},
"devDependencies": {
@ -11,6 +12,7 @@
"@hashgraph/sdk": "^2.70.0",
"@sinclair/typebox": "^0.34.33",
"@sinclair/typemap": "^0.10.1",
"@types/async-exit-hook": "^2.0.2",
"@types/bun": "^1.2.17",
"dotenv": "^10.0.0",
"elysia": "1.3.8",
@ -247,6 +249,8 @@
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
"@types/async-exit-hook": ["@types/async-exit-hook@2.0.2", "", {}, "sha512-RJbTNivnnn+JzNiQTtUgwo/1S6QUHwI5JfXCeUPsqZXB4LuvRwvHhbKFSS5jFDYpk8XoEAYVW2cumBOdGpXL2Q=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
@ -301,6 +305,8 @@
"asn1js": ["asn1js@3.0.6", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA=="],
"async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="],
"async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],

View File

@ -9,6 +9,7 @@ import {
TopicInfoQuery,
TopicMessageSubmitTransaction,
TopicUpdateTransaction,
TransactionId,
TransactionReceipt,
TransactionResponse,
Wallet,
@ -30,6 +31,9 @@ export class HieroClient {
wallet: Wallet
client: Client
logger: Logger
// transaction counter for logging
transactionInternNr: number = 0
pendingPromises: Promise<void>[] = []
private constructor() {
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.HieroClient`)
@ -53,12 +57,23 @@ export class HieroClient {
return HieroClient.instance
}
public async waitForPendingPromises() {
const startTime = new Date()
this.logger.info(`waiting for ${this.pendingPromises.length} pending promises`)
await Promise.all(this.pendingPromises)
const endTime = new Date()
this.logger.info(`all pending promises resolved, used time: ${endTime.getTime() - startTime.getTime()}ms`)
}
public async sendMessage(
topicId: HieroId,
transaction: GradidoTransaction,
): Promise<{ receipt: TransactionReceipt; response: TransactionResponse }> {
let startTime = new Date()
this.logger.addContext('topicId', topicId.toString())
): Promise<TransactionId | null> {
const startTime = new Date()
this.transactionInternNr++
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.HieroClient`)
logger.addContext('trNr', this.transactionInternNr)
logger.addContext('topicId', topicId.toString())
const serializedTransaction = transaction.getSerializedTransaction()
if (!serializedTransaction) {
throw new Error('cannot serialize transaction')
@ -68,29 +83,34 @@ export class HieroClient {
topicId,
message: serializedTransaction.data(),
}).freezeWithSigner(this.wallet)
let endTime = new Date()
this.logger.info(`prepare message, until freeze, cost: ${endTime.getTime() - startTime.getTime()}ms`)
startTime = new Date()
const signedHieroTransaction = await hieroTransaction.signWithSigner(this.wallet)
endTime = new Date()
this.logger.info(`sign message, cost: ${endTime.getTime() - startTime.getTime()}ms`)
startTime = new Date()
const sendResponse = await signedHieroTransaction.executeWithSigner(this.wallet)
endTime = new Date()
this.logger.info(`send message, cost: ${endTime.getTime() - startTime.getTime()}ms`)
startTime = new Date()
const sendReceipt = await sendResponse.getReceiptWithSigner(this.wallet)
endTime = new Date()
this.logger.info(`get receipt, cost: ${endTime.getTime() - startTime.getTime()}ms`)
this.logger.info(
`message sent to topic ${topicId}, status: ${sendReceipt.status.toString()}, transaction id: ${sendResponse.transactionId.toString()}`,
// sign and execute transaction needs some time, so let it run in background
const pendingPromiseIndex = this.pendingPromises.push(
hieroTransaction.signWithSigner(this.wallet).then(async (signedHieroTransaction) => {
const sendResponse = await signedHieroTransaction.executeWithSigner(this.wallet)
logger.info(`message sent to topic ${topicId}, transaction id: ${sendResponse.transactionId.toString()}`)
if (logger.isInfoEnabled()) {
// only for logging
sendResponse.getReceiptWithSigner(this.wallet).then((receipt) => {
logger.info(
`message send status: ${receipt.status.toString()}`,
)
})
// only for logging
sendResponse.getRecordWithSigner(this.wallet).then((record) => {
logger.info(`message sent, cost: ${record.transactionFee.toString()}`)
const localEndTime = new Date()
logger.info(`HieroClient.sendMessage used time (full process): ${localEndTime.getTime() - startTime.getTime()}ms`)
})
}
}).catch((e) => {
logger.error(e)
}).finally(() => {
this.pendingPromises.splice(pendingPromiseIndex, 1)
})
)
startTime = new Date()
const record = await sendResponse.getRecordWithSigner(this.wallet)
endTime = new Date()
this.logger.info(`get record, cost: ${endTime.getTime() - startTime.getTime()}ms`)
this.logger.info(`message sent, cost: ${record.transactionFee.toString()}`)
return { receipt: sendReceipt, response: sendResponse }
const endTime = new Date()
logger.info(`HieroClient.sendMessage used time: ${endTime.getTime() - startTime.getTime()}ms`)
return hieroTransaction.transactionId
}
public async getBalance(): Promise<AccountBalance> {

View File

@ -46,9 +46,36 @@ async function main() {
// listen for rpc request from backend (graphql replaced with elysiaJS)
new Elysia().use(appRoutes).listen(CONFIG.DLT_CONNECTOR_PORT, () => {
logger.info(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`)
setupGracefulShutdown(logger)
})
}
function setupGracefulShutdown(logger: Logger) {
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
signals.forEach(sig => {
process.on(sig, async () => {
logger.info(`[shutdown] Got ${sig}, cleaning up…`)
await gracefulShutdown(logger)
process.exit(0)
})
})
if (process.platform === "win32") {
const rl = require("readline").createInterface({
input: process.stdin,
output: process.stdout,
})
rl.on("SIGINT", () => {
process.emit("SIGINT" as any)
})
}
}
async function gracefulShutdown(logger: Logger) {
logger.info('graceful shutdown')
await HieroClient.getInstance().waitForPendingPromises()
}
function loadConfig(): Logger {
// configure log4js
// TODO: replace late by loader from config-schema
@ -113,3 +140,7 @@ main().catch((e) => {
console.error(e)
process.exit(1)
})
function exitHook(arg0: () => void) {
throw new Error('Function not implemented.')
}

View File

@ -50,10 +50,12 @@ export async function SendToHieroContext(
topic: HieroId,
): Promise<string> => {
const client = HieroClient.getInstance()
const resultMessage = await client.sendMessage(topic, gradidoTransaction)
const transactionId = resultMessage.response.transactionId.toString()
logger.info('transmitted Gradido Transaction to Hiero', { transactionId })
return transactionId
const transactionId = await client.sendMessage(topic, gradidoTransaction)
if (!transactionId) {
throw new Error('missing transaction id from hiero')
}
logger.info('transmitted Gradido Transaction to Hiero', { transactionId: transactionId.toString() })
return transactionId.toString()
}
// choose correct role based on transaction type and input type