handle deletiono of transaction link with dlt

This commit is contained in:
einhornimmond 2024-11-15 19:58:50 +01:00
parent a24ed8ef98
commit ec2d5cb98d
13 changed files with 142 additions and 24 deletions

View File

@ -0,0 +1,8 @@
export enum DltTransactionType {
REGISTER_ADDRESS = 1,
CREATION = 2,
TRANSFER = 3,
DEFERRED_TRANSFER = 4,
REDEEM_DEFERRED_TRANSFER = 5,
DELETE_DEFERRED_TRANSFER = 6,
}

View File

@ -22,7 +22,7 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
const dltTransaction = DltTransaction.create()
dltTransaction.messageId = messageId
dltTransaction.error = error
this.setJoinId(dltTransaction)
this.setJoinIdAndType(dltTransaction)
await DltTransaction.save(dltTransaction)
if (dltTransaction.error) {
logger.error(
@ -36,7 +36,7 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
}
// intern
protected abstract setJoinId(dltTransaction: DltTransaction): void
protected abstract setJoinIdAndType(dltTransaction: DltTransaction): void
// helper
protected createQueryForPendingItems(
@ -50,7 +50,6 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
.andWhere('dltTransaction.transaction_id IS NULL')
.andWhere('dltTransaction.transaction_link_Id IS NULL')
.orderBy(orderBy)
.limit(1)
}
protected createDltTransactionEntry(messageId: string, error: string | null): DltTransaction {

View File

@ -0,0 +1,83 @@
import { DltTransaction } from '@entity/DltTransaction'
import { TransactionLink } from '@entity/TransactionLink'
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
import { TransactionType } from '@dltConnector/enum/TransactionType'
import { CommunityUser } from '@dltConnector/model/CommunityUser'
import { IdentifierSeed } from '@dltConnector/model/IdentifierSeed'
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
import { UserIdentifier } from '@dltConnector/model/UserIdentifier'
import { LogError } from '@/server/LogError'
import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
/**
* redeem deferred transfer transaction by creator, so "deleting" it
*/
export class TransactionLinkDeleteToDltRole extends AbstractTransactionToDltRole<TransactionLink> {
async initWithLast(): Promise<this> {
const queryBuilder = this.createQueryForPendingItems(
TransactionLink.createQueryBuilder().leftJoinAndSelect('TransactionLink.user', 'user'),
'TransactionLink.id = dltTransaction.transactionLinkId and dltTransaction.type_id <> 4',
// eslint-disable-next-line camelcase
{ TransactionLink_deletedAt: 'ASC', User_id: 'ASC' },
)
.andWhere('TransactionLink.deletedAt IS NOT NULL')
.withDeleted()
const queryBuilder2 = TransactionLink.createQueryBuilder()
.leftJoinAndSelect('TransactionLink.user', 'user')
.where('TransactionLink.deletedAt IS NOT NULL')
.andWhere(() => {
const subQuery = DltTransaction.createQueryBuilder()
.select('1')
.where('DltTransaction.transaction_link_id = TransactionLink.id')
.andWhere('DltTransaction.type_id = :typeId', {
typeId: DltTransactionType.DELETE_DEFERRED_TRANSFER,
})
.getQuery()
return `NOT EXIST (${subQuery})`
})
.withDeleted()
// eslint-disable-next-line camelcase
.orderBy({ TransactionLink_deletedAt: 'ASC', User_id: 'ASC' })
// console.log('query: ', queryBuilder.getSql())
this.self = await queryBuilder.getOne()
return this
}
public getTimestamp(): number {
if (!this.self) {
return Infinity
}
if (!this.self.deletedAt) {
throw new LogError('not deleted transaction link selected')
}
return this.self.deletedAt.getTime()
}
public convertToGraphqlInput(): TransactionDraft {
if (!this.self) {
throw new LogError('try to create dlt entry for empty transaction link')
}
if (!this.self.deletedAt) {
throw new LogError('not deleted transaction link selected')
}
const draft = new TransactionDraft()
draft.amount = this.self.amount.abs().toString()
const user = this.self.user
draft.user = new UserIdentifier(user.communityUuid, new IdentifierSeed(this.self.code))
draft.linkedUser = new UserIdentifier(user.communityUuid, new CommunityUser(user.gradidoID))
draft.createdAt = this.self.deletedAt.toISOString()
draft.type = TransactionType.GRADIDO_TRANSFER
return draft
}
protected setJoinIdAndType(dltTransaction: DltTransaction): void {
if (!this.self) {
throw new LogError('try to create dlt entry for empty transaction link')
}
dltTransaction.transactionLinkId = this.self.id
dltTransaction.typeId = DltTransactionType.DELETE_DEFERRED_TRANSFER
}
}

View File

@ -1,6 +1,7 @@
import { DltTransaction } from '@entity/DltTransaction'
import { TransactionLink } from '@entity/TransactionLink'
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
import { TransactionType } from '@dltConnector/enum/TransactionType'
import { CommunityUser } from '@dltConnector/model/CommunityUser'
import { IdentifierSeed } from '@dltConnector/model/IdentifierSeed'
@ -47,10 +48,11 @@ export class TransactionLinkToDltRole extends AbstractTransactionToDltRole<Trans
return draft
}
protected setJoinId(dltTransaction: DltTransaction): void {
protected setJoinIdAndType(dltTransaction: DltTransaction): void {
if (!this.self) {
throw new LogError('try to create dlt entry for empty transaction link')
}
dltTransaction.transactionLinkId = this.self.id
dltTransaction.typeId = DltTransactionType.DEFERRED_TRANSFER
}
}

View File

@ -1,6 +1,7 @@
import { DltTransaction } from '@entity/DltTransaction'
import { Transaction } from '@entity/Transaction'
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
import { TransactionType } from '@dltConnector/enum/TransactionType'
import { CommunityUser } from '@dltConnector/model/CommunityUser'
import { IdentifierSeed } from '@dltConnector/model/IdentifierSeed'
@ -16,6 +17,7 @@ import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
* send transfer and creations transactions to dlt connector as GradidoTransfer and GradidoCreation
*/
export class TransactionToDltRole extends AbstractTransactionToDltRole<Transaction> {
private type: DltTransactionType
async initWithLast(): Promise<this> {
this.self = await this.createQueryForPendingItems(
Transaction.createQueryBuilder().leftJoinAndSelect(
@ -27,7 +29,7 @@ export class TransactionToDltRole extends AbstractTransactionToDltRole<Transacti
{ balance_date: 'ASC', Transaction_id: 'ASC' },
)
// we don't need the receive transactions, there contain basically the same data as the send transactions
.andWhere('transaction.typeId NOT :typeId', { typeId: TransactionTypeId.RECEIVE })
.andWhere('Transaction.type_id <> :typeId', { typeId: TransactionTypeId.RECEIVE })
.getOne()
return this
}
@ -55,6 +57,19 @@ export class TransactionToDltRole extends AbstractTransactionToDltRole<Transacti
`missing necessary field in transaction: ${this.self.id}, need linkedUserGradidoID, linkedUserCommunityUuid and userCommunityUuid`,
)
}
switch (this.self.typeId as TransactionTypeId) {
case TransactionTypeId.CREATION:
draft.type = TransactionType.GRADIDO_CREATION
this.type = DltTransactionType.CREATION
break
case TransactionTypeId.SEND:
case TransactionTypeId.RECEIVE:
draft.type = TransactionType.GRADIDO_TRANSFER
this.type = DltTransactionType.TRANSFER
break
default:
throw new LogError('wrong role for type', this.self.typeId as TransactionTypeId)
}
// it is a redeem of a transaction link?
const transactionLink = this.self.transactionLink
if (transactionLink) {
@ -62,6 +77,7 @@ export class TransactionToDltRole extends AbstractTransactionToDltRole<Transacti
this.self.userCommunityUuid,
new IdentifierSeed(transactionLink.code),
)
this.type = DltTransactionType.REDEEM_DEFERRED_TRANSFER
} else {
draft.user = new UserIdentifier(
this.self.userCommunityUuid,
@ -74,24 +90,14 @@ export class TransactionToDltRole extends AbstractTransactionToDltRole<Transacti
)
draft.createdAt = this.self.balanceDate.toISOString()
draft.targetDate = this.self.creationDate?.toISOString()
switch (this.self.typeId as TransactionTypeId) {
case TransactionTypeId.CREATION:
draft.type = TransactionType.GRADIDO_CREATION
break
case TransactionTypeId.SEND:
case TransactionTypeId.RECEIVE:
draft.type = TransactionType.GRADIDO_TRANSFER
break
default:
throw new LogError('wrong role for type', this.self.typeId as TransactionTypeId)
}
return draft
}
protected setJoinId(dltTransaction: DltTransaction): void {
protected setJoinIdAndType(dltTransaction: DltTransaction): void {
if (!this.self) {
throw new LogError('try to create dlt entry for empty transaction')
}
dltTransaction.transactionId = this.self.id
dltTransaction.typeId = this.type
}
}

View File

@ -2,6 +2,7 @@ import { DltTransaction } from '@entity/DltTransaction'
import { User } from '@entity/User'
import { AccountType } from '@dltConnector/enum/AccountType'
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
import { TransactionType } from '@dltConnector/enum/TransactionType'
import { CommunityUser } from '@dltConnector/model/CommunityUser'
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
@ -44,10 +45,11 @@ export class UserToDltRole extends AbstractTransactionToDltRole<User> {
return draft
}
protected setJoinId(dltTransaction: DltTransaction): void {
protected setJoinIdAndType(dltTransaction: DltTransaction): void {
if (!this.self) {
throw new LogError('try to create dlt entry for empty user')
}
dltTransaction.userId = this.self.id
dltTransaction.typeId = DltTransactionType.REGISTER_ADDRESS
}
}

View File

@ -6,6 +6,7 @@ import { DltConnectorClient } from '@/apis/dltConnector/DltConnectorClient'
import { backendLogger as logger } from '@/server/logger'
import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
import { TransactionLinkDeleteToDltRole } from './TransactionLinkDeleteToDlt.role'
import { TransactionLinkToDltRole } from './TransactionLinkToDlt.role'
import { TransactionToDltRole } from './TransactionToDlt.role'
import { UserToDltRole } from './UserToDlt.role'
@ -23,6 +24,7 @@ export async function transactionToDlt(dltConnector: DltConnectorClient): Promis
new TransactionToDltRole().initWithLast(),
new UserToDltRole().initWithLast(),
new TransactionLinkToDltRole().initWithLast(),
new TransactionLinkDeleteToDltRole().initWithLast(),
])
// sort array to get oldest at first place

View File

@ -33,6 +33,10 @@ import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { calculateDecay } from '@/util/decay'
import {
InterruptiveSleepManager,
TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY,
} from '@/util/InterruptiveSleepManager'
import { TRANSACTION_LINK_LOCK } from '@/util/TRANSACTION_LINK_LOCK'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { fullName } from '@/util/utilities'
@ -300,6 +304,8 @@ export class TransactionLinkResolver {
contributionLink,
contributionLink.amount,
)
// notify dlt-connector loop for new work
InterruptiveSleepManager.getInstance().interrupt(TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY)
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Creation from contribution link was not successful', e)
@ -354,6 +360,8 @@ export class TransactionLinkResolver {
transactionLink,
transactionLink.amount,
)
// notify dlt-connector loop for new work
InterruptiveSleepManager.getInstance().interrupt(TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY)
} finally {
releaseLinkLock()
}

View File

@ -17,6 +17,9 @@ export class DltTransaction extends BaseEntity {
@Column({ name: 'transaction_link_id', type: 'int', unsigned: true, nullable: true })
transactionLinkId?: number | null
@Column({ name: 'type_id', unsigned: true, nullable: false })
typeId: number
@Column({
name: 'message_id',
length: 64,

View File

@ -171,6 +171,6 @@ export class Transaction extends BaseEntity {
previousTransaction?: Transaction | null
@ManyToOne(() => TransactionLink, (transactionLink) => transactionLink.transactions)
@JoinColumn({ name: 'transactionLinkId' })
@JoinColumn({ name: 'transaction_link_id' })
transactionLink?: TransactionLink | null
}

View File

@ -80,6 +80,6 @@ export class TransactionLink extends BaseEntity {
user: User
@OneToMany(() => Transaction, (transaction) => transaction.transactionLink)
@JoinColumn({ referencedColumnName: 'transactionLinkId' })
@JoinColumn({ referencedColumnName: 'transaction_link_id' })
transactions: Transaction[]
}

View File

@ -7,7 +7,9 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
ALTER TABLE \`dlt_transactions\`
CHANGE \`transaction_id\` \`transaction_id\` INT(10) UNSIGNED NULL DEFAULT NULL,
ADD \`user_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`transaction_id\`,
ADD \`transaction_link_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`user_id\`;
ADD \`transaction_link_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`user_id\`
ADD \`type_id\` INT UNSIGNED NOT NULL AFTER \`transaction_link_id\`
;
`)
}
@ -28,6 +30,8 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom
ALTER TABLE \`dlt_transactions\`
CHANGE \`transaction_id\` \`transaction_id\` INT(10) UNSIGNED NOT NULL,
DROP COLUMN \`user_id\`,
DROP COLUMN \`transaction_link_id\`;
DROP COLUMN \`transaction_link_id\`
DROP COLUMN \`type_id\`
;
`)
}

View File

@ -1,7 +1,7 @@
import { KeyPairEd25519 } from 'gradido-blockchain-js'
import { IdentifierSeed } from '@/graphql/input/IdentifierSeed'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { logger } from '@/logging/logger'
import { LogError } from '@/server/LogError'
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
@ -52,7 +52,8 @@ export class KeyPairCacheManager {
public addKeyPair(input: UserIdentifier | string, keyPair: KeyPairEd25519): void {
const key = this.getKey(input)
if (this.cache.has(key)) {
throw new LogError('key already exist, cannot add', key)
logger.warn('key already exist, cannot add', key)
return
}
this.cache.set(key, keyPair)
}