move translation logic from backend into dlt-connector, refactor

This commit is contained in:
einhorn_b 2024-01-05 15:54:02 +01:00
parent 75a7481a1b
commit ed4e46a14a
12 changed files with 199 additions and 110 deletions

View File

@ -7,7 +7,6 @@ import { LogError } from '@/server/LogError'
import { timestampToDate } from '@/utils/typeConverter'
import { AbstractTransaction } from '../AbstractTransaction'
import { determineCrossGroupType, determineOtherGroup } from '../transactionBody.logic'
import { CommunityRoot } from './CommunityRoot'
import { PROTO_TRANSACTION_BODY_VERSION_NUMBER } from './const'
@ -25,19 +24,12 @@ import { Timestamp } from './Timestamp'
export class TransactionBody extends Message<TransactionBody> {
public constructor(transaction?: TransactionDraft | CommunityDraft) {
if (transaction) {
let type = CrossGroupType.LOCAL
let otherGroup = ''
if (transaction instanceof TransactionDraft) {
type = determineCrossGroupType(transaction)
otherGroup = determineOtherGroup(type, transaction)
}
super({
memo: 'Not implemented yet',
createdAt: new Timestamp(new Date(transaction.createdAt)),
versionNumber: PROTO_TRANSACTION_BODY_VERSION_NUMBER,
type,
otherGroup,
type: CrossGroupType.LOCAL,
otherGroup: '',
})
} else {
super()
@ -126,14 +118,12 @@ export class TransactionBody extends Message<TransactionBody> {
public getRecipientPublicKey(): Buffer | undefined {
if (this.transfer) {
// this.transfer.recipient contains the publicKey of the recipient
return this.transfer.recipient
}
if (this.creation) {
return this.creation.recipient.pubkey
}
if (this.deferredTransfer) {
// this.deferredTransfer.transfer.recipient contains the publicKey of the recipient
return this.deferredTransfer.transfer.recipient
}
return undefined

View File

@ -7,6 +7,7 @@ import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { LogError } from '@/server/LogError'
import { CommunityRoot } from './3_3/CommunityRoot'
import { CrossGroupType } from './3_3/enum/CrossGroupType'
import { GradidoCreation } from './3_3/GradidoCreation'
import { GradidoTransfer } from './3_3/GradidoTransfer'
import { TransactionBody } from './3_3/TransactionBody'
@ -78,6 +79,26 @@ export class TransactionBodyBuilder {
return this
}
public setCrossGroupType(type: CrossGroupType): this {
if (!this.body) {
throw new LogError(
'body is undefined, please call fromTransactionDraft or fromCommunityDraft before',
)
}
this.body.type = type
return this
}
public setOtherGroup(otherGroup: string): this {
if (!this.body) {
throw new LogError(
'body is undefined, please call fromTransactionDraft or fromCommunityDraft before',
)
}
this.body.otherGroup = otherGroup
return this
}
public fromTransactionDraft(transactionDraft: TransactionDraft): TransactionBodyBuilder {
this.body = new TransactionBody(transactionDraft)
// TODO: load pubkeys for sender and recipient user from db

View File

@ -1,57 +0,0 @@
import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { CrossGroupType } from './3_3/enum/CrossGroupType'
export const determineCrossGroupType = ({
senderUser,
recipientUser,
type,
}: TransactionDraft): CrossGroupType => {
if (
!recipientUser.communityUuid ||
recipientUser.communityUuid === '' ||
senderUser.communityUuid === recipientUser.communityUuid ||
type === InputTransactionType.CREATION
) {
return CrossGroupType.LOCAL
} else if (type === InputTransactionType.SEND) {
return CrossGroupType.INBOUND
} else if (type === InputTransactionType.RECEIVE) {
return CrossGroupType.OUTBOUND
}
throw new TransactionError(
TransactionErrorType.NOT_IMPLEMENTED_YET,
'cannot determine CrossGroupType',
)
}
export const determineOtherGroup = (
type: CrossGroupType,
{ senderUser, recipientUser }: TransactionDraft,
): string => {
switch (type) {
case CrossGroupType.LOCAL:
return ''
case CrossGroupType.INBOUND:
if (!recipientUser.communityUuid) {
throw new TransactionError(
TransactionErrorType.MISSING_PARAMETER,
'missing recipient user community id for cross group transaction',
)
}
return recipientUser.communityUuid
case CrossGroupType.OUTBOUND:
if (!senderUser.communityUuid) {
throw new TransactionError(
TransactionErrorType.MISSING_PARAMETER,
'missing sender user community id for cross group transaction',
)
}
return senderUser.communityUuid
case CrossGroupType.CROSS:
throw new TransactionError(TransactionErrorType.NOT_IMPLEMENTED_YET, 'not implemented yet')
}
}

View File

@ -14,12 +14,12 @@ export class TransactionDraft {
@Field(() => UserIdentifier)
@IsObject()
@ValidateNested()
senderUser: UserIdentifier
user: UserIdentifier
@Field(() => UserIdentifier)
@IsObject()
@ValidateNested()
recipientUser: UserIdentifier
linkedUser: UserIdentifier
@Field(() => Int)
@IsPositive()

View File

@ -62,8 +62,8 @@ const createUserStoreAccount = async (uuid: string): Promise<UserIdentifier> =>
}
describe('Transaction Resolver Test', () => {
let senderUser: UserIdentifier
let recipientUser: UserIdentifier
let user: UserIdentifier
let linkedUser: UserIdentifier
beforeAll(async () => {
await TestDB.instance.setupTestDB()
apolloTestServer = await createApolloTestServer()
@ -74,8 +74,8 @@ describe('Transaction Resolver Test', () => {
communityDraft.createdAt = new Date().toString()
const addCommunityContext = new AddCommunityContext(communityDraft)
await addCommunityContext.run()
senderUser = await createUserStoreAccount('0ec72b74-48c2-446f-91ce-31ad7d9f4d65')
recipientUser = await createUserStoreAccount('ddc8258e-fcb5-4e48-8d1d-3a07ec371dbe')
user = await createUserStoreAccount('0ec72b74-48c2-446f-91ce-31ad7d9f4d65')
linkedUser = await createUserStoreAccount('ddc8258e-fcb5-4e48-8d1d-3a07ec371dbe')
})
afterAll(async () => {
@ -88,8 +88,8 @@ describe('Transaction Resolver Test', () => {
'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {succeed, recipe { id, topic }} }',
variables: {
input: {
senderUser,
recipientUser,
user,
linkedUser,
type: getEnumValue(InputTransactionType, InputTransactionType.SEND),
amount: '10',
createdAt: '2012-04-17T17:12:00Z',
@ -110,8 +110,8 @@ describe('Transaction Resolver Test', () => {
'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }',
variables: {
input: {
senderUser,
recipientUser,
user,
linkedUser,
type: 'INVALID',
amount: '10',
createdAt: '2012-04-17T17:12:00Z',
@ -136,8 +136,8 @@ describe('Transaction Resolver Test', () => {
'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }',
variables: {
input: {
senderUser,
recipientUser,
user,
linkedUser,
type: getEnumValue(InputTransactionType, InputTransactionType.SEND),
amount: 'no number',
createdAt: '2012-04-17T17:12:00Z',
@ -162,8 +162,8 @@ describe('Transaction Resolver Test', () => {
'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }',
variables: {
input: {
senderUser,
recipientUser,
user,
linkedUser,
type: getEnumValue(InputTransactionType, InputTransactionType.SEND),
amount: '10',
createdAt: 'not valid',
@ -198,8 +198,8 @@ describe('Transaction Resolver Test', () => {
'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }',
variables: {
input: {
senderUser,
recipientUser,
user,
linkedUser,
type: getEnumValue(InputTransactionType, InputTransactionType.CREATION),
amount: '10',
createdAt: '2012-04-17T17:12:00Z',

View File

@ -28,8 +28,7 @@ export class HomeCommunityRole extends CommunityRole {
this.self.aufAccount = AccountFactory.createAufAccount(keyPair, this.self.createdAt)
this.self.gmwAccount = AccountFactory.createGmwAccount(keyPair, this.self.createdAt)
const transactionRecipeContext = new CreateTransactionRecipeContext(communityDraft)
transactionRecipeContext.setCommunity(this.self)
const transactionRecipeContext = new CreateTransactionRecipeContext(communityDraft, this.self)
await transactionRecipeContext.run()
this.transactionRecipe = transactionRecipeContext.getTransactionRecipe()
}

View File

@ -0,0 +1,62 @@
import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { TransactionError } from '@/graphql/model/TransactionError'
export abstract class AbstractTransactionRole {
// eslint-disable-next-line no-useless-constructor
public constructor(protected self: TransactionDraft) {}
abstract getSigningUser(): UserIdentifier
abstract getRecipientUser(): UserIdentifier
abstract getCrossGroupType(): CrossGroupType
public isCrossGroupTransaction(): boolean {
return (
this.self.user.communityUuid !== this.self.linkedUser.communityUuid &&
this.self.linkedUser.communityUuid !== ''
)
}
/**
* otherGroup is the group/community on which this part of the transaction isn't stored
* Alice from 'gdd1' Send 10 GDD to Bob in 'gdd2'
* OUTBOUND came from sender, stored on sender community blockchain
* OUTBOUND: stored on 'gdd1', otherGroup: 'gdd2'
* INBOUND: goes to receiver, stored on receiver community blockchain
* INBOUND: stored on 'gdd2', otherGroup: 'gdd1'
* @returns
*/
public getOtherGroup(): string {
let user: UserIdentifier
const type = this.getCrossGroupType()
switch (type) {
case CrossGroupType.LOCAL:
return ''
case CrossGroupType.INBOUND:
user = this.getSigningUser()
if (!user.communityUuid) {
throw new TransactionError(
TransactionErrorType.MISSING_PARAMETER,
'missing sender/signing user community id for cross group transaction',
)
}
return user.communityUuid
case CrossGroupType.OUTBOUND:
user = this.getRecipientUser()
if (!user.communityUuid) {
throw new TransactionError(
TransactionErrorType.MISSING_PARAMETER,
'missing recipient user community id for cross group transaction',
)
}
return user.communityUuid
default:
throw new TransactionError(
TransactionErrorType.NOT_IMPLEMENTED_YET,
`type not implemented yet ${type}`,
)
}
}
}

View File

@ -1,12 +1,17 @@
import { Community } from '@entity/Community'
import { Transaction } from '@entity/Transaction'
import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { AbstractTransactionRole } from './AbstractTransaction.role'
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
import { CreationTransactionRole } from './CreationTransaction.role'
import { ReceiveTransactionRole } from './ReceiveTransaction.role'
import { SendTransactionRole } from './SendTransaction.role'
import { TransactionRecipeRole } from './TransactionRecipe.role'
/**
@ -16,22 +21,11 @@ import { TransactionRecipeRole } from './TransactionRecipe.role'
export class CreateTransactionRecipeContext {
private transactionRecipeRole: TransactionRecipeRole
private community?: Community
public constructor(private draft: CommunityDraft | TransactionDraft) {
if (draft instanceof CommunityDraft) {
this.transactionRecipeRole = new CommunityRootTransactionRole()
} else if (draft instanceof TransactionDraft) {
this.transactionRecipeRole = new TransactionRecipeRole()
}
}
public setCommunity(community: Community) {
this.community = community
}
public getCommunity(): Community | undefined {
return this.community
}
// eslint-disable-next-line no-useless-constructor
public constructor(
private draft: CommunityDraft | TransactionDraft,
private community?: Community,
) {}
public getTransactionRecipe(): Transaction {
return this.transactionRecipeRole.getTransaction()
@ -43,7 +37,20 @@ export class CreateTransactionRecipeContext {
public async run(): Promise<boolean> {
if (this.draft instanceof TransactionDraft) {
this.transactionRecipeRole = new TransactionRecipeRole()
await this.transactionRecipeRole.create(this.draft)
// contain logic for translation from backend to dlt-connector format
let transactionTypeRole: AbstractTransactionRole
switch (this.draft.type) {
case InputTransactionType.CREATION:
transactionTypeRole = new CreationTransactionRole(this.draft)
break
case InputTransactionType.SEND:
transactionTypeRole = new SendTransactionRole(this.draft)
break
case InputTransactionType.RECEIVE:
transactionTypeRole = new ReceiveTransactionRole(this.draft)
break
}
await this.transactionRecipeRole.create(this.draft, transactionTypeRole)
return true
} else if (this.draft instanceof CommunityDraft) {
if (!this.community) {

View File

@ -0,0 +1,18 @@
import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { AbstractTransactionRole } from './AbstractTransaction.role'
export class CreationTransactionRole extends AbstractTransactionRole {
public getSigningUser(): UserIdentifier {
return this.self.linkedUser
}
public getRecipientUser(): UserIdentifier {
return this.self.user
}
public getCrossGroupType(): CrossGroupType {
return CrossGroupType.LOCAL
}
}

View File

@ -0,0 +1,21 @@
import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { AbstractTransactionRole } from './AbstractTransaction.role'
export class ReceiveTransactionRole extends AbstractTransactionRole {
public getSigningUser(): UserIdentifier {
return this.self.linkedUser
}
public getRecipientUser(): UserIdentifier {
return this.self.user
}
public getCrossGroupType(): CrossGroupType {
if (this.isCrossGroupTransaction()) {
return CrossGroupType.INBOUND
}
return CrossGroupType.LOCAL
}
}

View File

@ -0,0 +1,21 @@
import { CrossGroupType } from '@/data/proto/3_3/enum/CrossGroupType'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { AbstractTransactionRole } from './AbstractTransaction.role'
export class SendTransactionRole extends AbstractTransactionRole {
public getSigningUser(): UserIdentifier {
return this.self.user
}
public getRecipientUser(): UserIdentifier {
return this.self.linkedUser
}
public getCrossGroupType(): CrossGroupType {
if (this.isCrossGroupTransaction()) {
return CrossGroupType.OUTBOUND
}
return CrossGroupType.LOCAL
}
}

View File

@ -8,6 +8,8 @@ import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { AbstractTransactionRole } from './AbstractTransaction.role'
export class TransactionRecipeRole {
protected transactionBuilder: TransactionBuilder
@ -15,13 +17,16 @@ export class TransactionRecipeRole {
this.transactionBuilder = new TransactionBuilder()
}
public async create(transactionDraft: TransactionDraft): Promise<TransactionRecipeRole> {
const senderUser = transactionDraft.senderUser
const recipientUser = transactionDraft.recipientUser
public async create(
transactionDraft: TransactionDraft,
transactionTypeRole: AbstractTransactionRole,
): Promise<TransactionRecipeRole> {
const signingUser = transactionTypeRole.getSigningUser()
const recipientUser = transactionTypeRole.getRecipientUser()
// loading signing and recipient account
// TODO: look for ways to use only one db call for both
const signingAccount = await UserRepository.findAccountByUserIdentifier(senderUser)
const signingAccount = await UserRepository.findAccountByUserIdentifier(signingUser)
if (!signingAccount) {
throw new TransactionError(
TransactionErrorType.NOT_FOUND,
@ -40,13 +45,15 @@ export class TransactionRecipeRole {
.setSigningAccount(signingAccount)
.setRecipientAccount(recipientAccount)
.fromTransactionDraft(transactionDraft)
.setCrossGroupType(transactionTypeRole.getCrossGroupType())
.setOtherGroup(transactionTypeRole.getOtherGroup())
// build transaction entity
this.transactionBuilder
.fromTransactionBodyBuilder(transactionBodyBuilder)
.addBackendTransaction(transactionDraft)
await this.transactionBuilder.setSenderCommunityFromSenderUser(senderUser)
if (recipientUser.communityUuid !== senderUser.communityUuid) {
await this.transactionBuilder.setSenderCommunityFromSenderUser(signingUser)
if (recipientUser.communityUuid !== signingUser.communityUuid) {
await this.transactionBuilder.setOtherCommunityFromRecipientUser(recipientUser)
}
const transaction = this.transactionBuilder.getTransaction()