From 2727b6ebe99e31993f686a903a18afc8282c538c Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sun, 22 Sep 2024 16:35:15 +0200 Subject: [PATCH] add new send to iota interaction --- dlt-connector/.env.dist | 4 +- dlt-connector/.env.template | 4 +- dlt-connector/package.json | 1 + dlt-connector/src/client/GradidoNode.ts | 124 ++++++++++++++++++ dlt-connector/src/config/index.ts | 7 +- .../src/data/Community.repository.ts | 10 +- .../src/graphql/model/TransactionRecipe.ts | 31 ++--- .../src/graphql/resolver/CommunityResolver.ts | 4 +- dlt-connector/src/index.ts | 25 ++-- .../community/AddCommunity.context.ts | 4 +- .../CreateTransactionRecipe.context.test.ts | 6 +- .../AbstractKeyPair.role.ts | 5 + .../AbstractRemoteKeyPair.role.ts | 5 + .../keyPairCalculation/AccountKeyPair.role.ts | 13 ++ .../ForeignCommunityKeyPair.role.ts | 31 +++++ .../HomeCommunityKeyPair.role.ts | 22 ++++ .../KeyPairCalculation.context.ts | 56 ++++++++ .../RemoteAccountKeyPair.role.ts | 35 +++++ .../keyPairCalculation/UserKeyPair.role.ts | 28 ++++ .../sendToIota/AbstractTransaction.role.ts | 7 + .../CommunityRootTransaction.role.ts | 47 +++++++ .../sendToIota/CreationTransaction.role.ts | 39 ++++++ .../RegisterAddressTransaction.role.ts | 40 ++++++ .../sendToIota/SendToIota.context.ts | 116 ++++++++++++++++ .../sendToIota/TransferTransaction.role.ts | 44 +++++++ .../src/manager/KeyPairCacheManager.ts | 66 ++++++++++ dlt-connector/src/utils/derivationHelper.ts | 2 + dlt-connector/src/utils/typeConverter.test.ts | 4 +- dlt-connector/src/utils/typeConverter.ts | 30 ++++- dlt-connector/test/seeding/Community.seed.ts | 4 +- dlt-connector/yarn.lock | 33 +++-- 31 files changed, 779 insertions(+), 68 deletions(-) create mode 100644 dlt-connector/src/client/GradidoNode.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/AbstractKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/AbstractRemoteKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts create mode 100644 dlt-connector/src/interactions/sendToIota/AbstractTransaction.role.ts create mode 100644 dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts create mode 100644 dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts create mode 100644 dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts create mode 100644 dlt-connector/src/interactions/sendToIota/SendToIota.context.ts create mode 100644 dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts create mode 100644 dlt-connector/src/manager/KeyPairCacheManager.ts diff --git a/dlt-connector/.env.dist b/dlt-connector/.env.dist index 164e23036..8ad78348d 100644 --- a/dlt-connector/.env.dist +++ b/dlt-connector/.env.dist @@ -21,10 +21,12 @@ TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log # DLT-Connector DLT_CONNECTOR_PORT=6010 +# Gradido Node Server URL +NODE_SERVER_URL=http://localhost:8340 + # Gradido Blockchain GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=21ffbbc616fe GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=a51ef8ac7ef1abf162fb7a65261acd7a -GRADIDO_BLOCKCHAIN_PRIVATE_KEY_ENCRYPTION_PASSWORD=YourPassword # Route to Backend BACKEND_SERVER_URL=http://localhost:4000 diff --git a/dlt-connector/.env.template b/dlt-connector/.env.template index d7d46dad7..15c2ae5a9 100644 --- a/dlt-connector/.env.template +++ b/dlt-connector/.env.template @@ -19,10 +19,12 @@ TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log # DLT-Connector DLT_CONNECTOR_PORT=$DLT_CONNECTOR_PORT +# Gradido Node Server URL +NODE_SERVER_URL=$NODE_SERVER_URL + # Gradido Blockchain GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=$GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=$GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY -GRADIDO_BLOCKCHAIN_PRIVATE_KEY_ENCRYPTION_PASSWORD=$GRADIDO_BLOCKCHAIN_PRIVATE_KEY_ENCRYPTION_PASSWORD # Route to Backend BACKEND_SERVER_URL=http://localhost:4000 \ No newline at end of file diff --git a/dlt-connector/package.json b/dlt-connector/package.json index cf29417f5..ca889b833 100644 --- a/dlt-connector/package.json +++ b/dlt-connector/package.json @@ -33,6 +33,7 @@ "graphql-scalars": "^1.22.2", "helmet": "^7.1.0", "jose": "^5.2.2", + "jsonrpc-ts-client": "^0.2.3", "log4js": "^6.7.1", "nodemon": "^2.0.20", "reflect-metadata": "^0.1.13", diff --git a/dlt-connector/src/client/GradidoNode.ts b/dlt-connector/src/client/GradidoNode.ts new file mode 100644 index 000000000..92a211768 --- /dev/null +++ b/dlt-connector/src/client/GradidoNode.ts @@ -0,0 +1,124 @@ +/* eslint-disable camelcase */ +import { AddressType, ConfirmedTransaction, stringToAddressType } from 'gradido-blockchain-js' +import JsonRpcClient from 'jsonrpc-ts-client' +import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc' + +import { CONFIG } from '@/config' +import { logger } from '@/logging/logger' +import { LogError } from '@/server/LogError' +import { confirmedTransactionFromBase64, getEnumValue } from '@/utils/typeConverter' + +const client = new JsonRpcClient({ + url: CONFIG.NODE_SERVER_URL, +}) +/* +enum JsonRPCErrorCodes { + NONE = 0, + GRADIDO_NODE_ERROR = -10000, + UNKNOWN_GROUP = -10001, + NOT_IMPLEMENTED = -10002, + TRANSACTION_NOT_FOUND = -10003, + // default errors from json rpc standard: https://www.jsonrpc.org/specification + // -32700 Parse error Invalid JSON was received by the server. + PARSE_ERROR = -32700, + // -32600 Invalid Request The JSON sent is not a valid Request object. + INVALID_REQUEST = -32600, + // -32601 Method not found The method does not exist / is not available. + METHODE_NOT_FOUND = -32601, + // -32602 Invalid params Invalid method parameter(s). + INVALID_PARAMS = -32602, + // -32603 Internal error Internal JSON - RPC error. + INTERNAL_ERROR = -32603, + // -32000 to -32099 Server error Reserved for implementation-defined server-errors. +} +*/ + +interface ConfirmedTransactionList { + transactions: string[] + timeUsed: string +} + +interface ConfirmedTransactionResponse { + transaction: string + timeUsed: string +} + +interface AddressTypeResult { + addressType: string +} + +function resolveResponse(response: JsonRpcEitherResponse, onSuccess: (result: T) => R): R { + if (response.isSuccess()) { + return onSuccess(response.result) + } else if (response.isError()) { + throw new LogError('error by json rpc request to gradido node server', response.error) + } + throw new LogError('no success and no error', response) +} + +async function getTransactions( + fromTransactionId: number, + maxResultCount: number, + iotaTopic: string, +): Promise { + const parameter = { + format: 'base64', + fromTransactionId, + maxResultCount, + communityId: iotaTopic, + } + logger.info('call getTransactions on Node Server via jsonrpc 2.0 with ', parameter) + const response = await client.exec('getTransactions', parameter) // sends payload {jsonrpc: '2.0', params: ...} + return resolveResponse(response, (result: ConfirmedTransactionList) => { + logger.debug('GradidoNode used time', result.timeUsed) + return result.transactions.map((transactionBase64) => + confirmedTransactionFromBase64(transactionBase64), + ) + }) +} + +async function getTransaction( + transactionId: number | Buffer, + iotaTopic: string, +): Promise { + logger.info('call gettransaction on Node Server via jsonrpc 2.0') + const response = await client.exec('gettransaction', { + format: 'base64', + communityId: iotaTopic, + transactionId: typeof transactionId === 'number' ? transactionId : undefined, + iotaMessageId: transactionId instanceof Buffer ? transactionId.toString('hex') : undefined, + }) + return resolveResponse(response, (result: ConfirmedTransactionResponse) => { + logger.debug('GradidoNode used time', result.timeUsed) + return result.transaction && result.transaction !== '' + ? confirmedTransactionFromBase64(result.transaction) + : undefined + }) +} + +async function getLastTransaction(iotaTopic: string): Promise { + logger.info('call getlasttransaction on Node Server via jsonrpc 2.0') + const response = await client.exec('getlasttransaction', { + format: 'base64', + communityId: iotaTopic, + }) + return resolveResponse(response, (result: ConfirmedTransactionResponse) => { + logger.debug('GradidoNode used time', result.timeUsed) + return result.transaction && result.transaction !== '' + ? confirmedTransactionFromBase64(result.transaction) + : undefined + }) +} + +async function getAddressType(pubkey: Buffer, iotaTopic: string): Promise { + logger.info('call getaddresstype on Node Server via jsonrpc 2.0') + const response = await client.exec('getaddresstype', { + pubkey: pubkey.toString('hex'), + communityId: iotaTopic, + }) + return resolveResponse(response, (result: AddressTypeResult) => + stringToAddressType(result.addressType), + ) +} + +export { getTransaction, getLastTransaction, getTransactions, getAddressType } diff --git a/dlt-connector/src/config/index.ts b/dlt-connector/src/config/index.ts index f2f01cd8d..ff50eb3fe 100644 --- a/dlt-connector/src/config/index.ts +++ b/dlt-connector/src/config/index.ts @@ -39,13 +39,15 @@ const dltConnector = { DLT_CONNECTOR_PORT: process.env.DLT_CONNECTOR_PORT ?? 6010, } +const nodeServer = { + NODE_SERVER_URL: process.env.NODE_SERVER_URL ?? 'http://localhost:8340', +} + const gradidoBlockchain = { GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET: process.env.GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET ?? 'invalid', GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: process.env.GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY ?? 'invalid', - GRADIDO_BLOCKCHAIN_PRIVATE_KEY_ENCRYPTION_PASSWORD: - process.env.GRADIDO_BLOCKCHAIN_PRIVATE_KEY_ENCRYPTION_PASSWORD, } const backendServer = { @@ -70,6 +72,7 @@ export const CONFIG = { ...database, ...iota, ...dltConnector, + ...nodeServer, ...gradidoBlockchain, ...backendServer, } diff --git a/dlt-connector/src/data/Community.repository.ts b/dlt-connector/src/data/Community.repository.ts index a9fdf4e09..eb52fb38c 100644 --- a/dlt-connector/src/data/Community.repository.ts +++ b/dlt-connector/src/data/Community.repository.ts @@ -8,7 +8,7 @@ import { UserIdentifier } from '@/graphql/input/UserIdentifier' import { TransactionError } from '@/graphql/model/TransactionError' import { LogError } from '@/server/LogError' import { getDataSource } from '@/typeorm/DataSource' -import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' +import { uuid4ToHash } from '@/utils/typeConverter' import { KeyPair } from './KeyPair' @@ -17,7 +17,7 @@ export const CommunityRepository = getDataSource() .extend({ async isExist(community: CommunityDraft | string): Promise { const iotaTopic = - community instanceof CommunityDraft ? iotaTopicFromCommunityUUID(community.uuid) : community + community instanceof CommunityDraft ? uuid4ToHash(community.uuid) : community const result = await this.find({ where: { iotaTopic }, }) @@ -27,7 +27,7 @@ export const CommunityRepository = getDataSource() async findByCommunityArg({ uuid, foreign, confirmed }: CommunityArg): Promise { return await this.find({ where: { - ...(uuid && { iotaTopic: iotaTopicFromCommunityUUID(uuid) }), + ...(uuid && { iotaTopic: uuid4ToHash(uuid) }), ...(foreign && { foreign }), ...(confirmed && { confirmedAt: Not(IsNull()) }), }, @@ -35,7 +35,7 @@ export const CommunityRepository = getDataSource() }, async findByCommunityUuid(communityUuid: string): Promise { - return await this.findOneBy({ iotaTopic: iotaTopicFromCommunityUUID(communityUuid) }) + return await this.findOneBy({ iotaTopic: uuid4ToHash(communityUuid) }) }, async findByIotaTopic(iotaTopic: string): Promise { @@ -54,7 +54,7 @@ export const CommunityRepository = getDataSource() } return ( (await this.findOneBy({ - iotaTopic: iotaTopicFromCommunityUUID(identifier.communityUuid), + iotaTopic: uuid4ToHash(identifier.communityUuid), })) ?? undefined ) }, diff --git a/dlt-connector/src/graphql/model/TransactionRecipe.ts b/dlt-connector/src/graphql/model/TransactionRecipe.ts index 78fa7fc31..eae7f1a6f 100644 --- a/dlt-connector/src/graphql/model/TransactionRecipe.ts +++ b/dlt-connector/src/graphql/model/TransactionRecipe.ts @@ -1,26 +1,20 @@ -import { Transaction } from '@entity/Transaction' -import { Field, Int, ObjectType } from 'type-graphql' +import { GradidoTransaction, MemoryBlock, transactionTypeToString } from 'gradido-blockchain-js' +import { Field, ObjectType } from 'type-graphql' -import { TransactionType } from '@/data/proto/3_3/enum/TransactionType' import { LogError } from '@/server/LogError' -import { getEnumValue } from '@/utils/typeConverter' @ObjectType() export class TransactionRecipe { - public constructor({ id, createdAt, type, community, signature }: Transaction) { - const transactionType = getEnumValue(TransactionType, type) - if (!transactionType) { - throw new LogError('invalid transaction, type is missing') + public constructor(transaction: GradidoTransaction, messageId: MemoryBlock) { + const body = transaction.getTransactionBody() + if (!body) { + throw new LogError('invalid gradido transaction, cannot geht valid TransactionBody') } - this.id = id - this.createdAt = createdAt.toString() - this.type = transactionType.toString() - this.topic = community.iotaTopic - this.signatureHex = signature.toString('hex') - } - @Field(() => Int) - id: number + this.createdAt = body.getCreatedAt().getDate().toString() + this.type = transactionTypeToString(body?.getTransactionType()) + this.messageIdHex = messageId.convertToHex() + } @Field(() => String) createdAt: string @@ -29,8 +23,5 @@ export class TransactionRecipe { type: string @Field(() => String) - topic: string - - @Field(() => String) - signatureHex: string + messageIdHex: string } diff --git a/dlt-connector/src/graphql/resolver/CommunityResolver.ts b/dlt-connector/src/graphql/resolver/CommunityResolver.ts index dd9db2b23..848dd0733 100644 --- a/dlt-connector/src/graphql/resolver/CommunityResolver.ts +++ b/dlt-connector/src/graphql/resolver/CommunityResolver.ts @@ -10,7 +10,7 @@ import { CommunityRepository } from '@/data/Community.repository' import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context' import { logger } from '@/logging/logger' import { LogError } from '@/server/LogError' -import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' +import { uuid4ToHash } from '@/utils/typeConverter' @Resolver() export class CommunityResolver { @@ -46,7 +46,7 @@ export class CommunityResolver { communityDraft: CommunityDraft, ): Promise { logger.info('addCommunity', communityDraft) - const topic = iotaTopicFromCommunityUUID(communityDraft.uuid) + const topic = uuid4ToHash(communityDraft.uuid) // check if community was already written to db if (await CommunityRepository.isExist(topic)) { return new TransactionResult( diff --git a/dlt-connector/src/index.ts b/dlt-connector/src/index.ts index 8d140faf3..b61157dfb 100644 --- a/dlt-connector/src/index.ts +++ b/dlt-connector/src/index.ts @@ -6,10 +6,10 @@ import { loadCryptoKeys, MemoryBlock } from 'gradido-blockchain-js' import { CONFIG } from '@/config' import { BackendClient } from './client/BackendClient' -import { CommunityRepository } from './data/Community.repository' import { CommunityDraft } from './graphql/input/CommunityDraft' import { AddCommunityContext } from './interactions/backendToDb/community/AddCommunity.context' import { logger } from './logging/logger' +import { KeyPairCacheManager } from './manager/KeyPairCacheManager' import createServer from './server/createServer' import { LogError } from './server/LogError' import { stopTransmitToIota, transmitToIota } from './tasks/transmitToIota' @@ -61,20 +61,17 @@ async function main() { const { app } = await createServer() // ask backend for home community if we haven't one - try { - await CommunityRepository.loadHomeCommunityKeyPair() - } catch (e) { - const backend = BackendClient.getInstance() - if (!backend) { - throw new LogError('cannot create backend client') - } - // wait for backend server to be ready - await waitForServer(backend, 2500, 10) - - const communityDraft = await backend.getHomeCommunityDraft() - const addCommunityContext = new AddCommunityContext(communityDraft) - await addCommunityContext.run() + const backend = BackendClient.getInstance() + if (!backend) { + throw new LogError('cannot create backend client') } + // wait for backend server to be ready + await waitForServer(backend, 2500, 10) + + const communityDraft = await backend.getHomeCommunityDraft() + KeyPairCacheManager.getInstance().setHomeCommunityUUID(communityDraft.uuid) + const addCommunityContext = new AddCommunityContext(communityDraft) + await addCommunityContext.run() // loop run all the time, check for new transaction for sending to iota void transmitToIota() diff --git a/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts b/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts index bc8f90c32..16bfd8513 100644 --- a/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts +++ b/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts @@ -1,5 +1,5 @@ import { CommunityDraft } from '@/graphql/input/CommunityDraft' -import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' +import { uuid4ToHash } from '@/utils/typeConverter' import { CommunityRole } from './Community.role' import { ForeignCommunityRole } from './ForeignCommunity.role' @@ -15,7 +15,7 @@ export class AddCommunityContext { private iotaTopic: string public constructor(private communityDraft: CommunityDraft, iotaTopic?: string) { if (!iotaTopic) { - this.iotaTopic = iotaTopicFromCommunityUUID(this.communityDraft.uuid) + this.iotaTopic = uuid4ToHash(this.communityDraft.uuid) } else { this.iotaTopic = iotaTopic } diff --git a/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts b/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts index a38d9952f..6671e6e78 100644 --- a/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts +++ b/dlt-connector/src/interactions/backendToDb/transaction/CreateTransactionRecipe.context.test.ts @@ -22,7 +22,7 @@ import { AccountType } from '@/graphql/enum/AccountType' import { InputTransactionType } from '@/graphql/enum/InputTransactionType' import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { UserAccountDraft } from '@/graphql/input/UserAccountDraft' -import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' +import { uuid4ToHash } from '@/utils/typeConverter' import { CreateTransactionRecipeContext } from './CreateTransactionRecipe.context' @@ -50,8 +50,8 @@ let secondUser: UserSet let foreignUser: UserSet let homeCommunity: Community -const topic = iotaTopicFromCommunityUUID(homeCommunityUuid) -const foreignTopic = iotaTopicFromCommunityUUID(foreignCommunityUuid) +const topic = uuid4ToHash(homeCommunityUuid) +const foreignTopic = uuid4ToHash(foreignCommunityUuid) describe('interactions/backendToDb/transaction/Create Transaction Recipe Context Test', () => { beforeAll(async () => { diff --git a/dlt-connector/src/interactions/keyPairCalculation/AbstractKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/AbstractKeyPair.role.ts new file mode 100644 index 000000000..cd17a3832 --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/AbstractKeyPair.role.ts @@ -0,0 +1,5 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +export abstract class AbstractKeyPairRole { + public abstract generateKeyPair(): KeyPairEd25519 +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/AbstractRemoteKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/AbstractRemoteKeyPair.role.ts new file mode 100644 index 000000000..1450d9cdb --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/AbstractRemoteKeyPair.role.ts @@ -0,0 +1,5 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +export abstract class AbstractRemoteKeyPairRole { + public abstract retrieveKeyPair(): Promise +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts new file mode 100644 index 000000000..517d33ae1 --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/AccountKeyPair.role.ts @@ -0,0 +1,13 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +import { AbstractKeyPairRole } from './AbstractKeyPair.role' + +export class AccountKeyPairRole extends AbstractKeyPairRole { + public constructor(private accountNr: number, private userKeyPair: KeyPairEd25519) { + super() + } + + public generateKeyPair(): KeyPairEd25519 { + return this.userKeyPair.deriveChild(this.accountNr) + } +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts new file mode 100644 index 000000000..68dd65a21 --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts @@ -0,0 +1,31 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +import { getTransaction } from '@/client/GradidoNode' +import { LogError } from '@/server/LogError' +import { uuid4ToHash } from '@/utils/typeConverter' + +import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role' + +export class ForeignCommunityKeyPairRole extends AbstractRemoteKeyPairRole { + public constructor(private communityUuid: string) { + super() + } + + public async retrieveKeyPair(): Promise { + const firstTransaction = await getTransaction(1, uuid4ToHash(this.communityUuid).convertToHex()) + if (!firstTransaction) { + throw new LogError( + "GradidoNode Server don't know this community with uuid " + this.communityUuid, + ) + } + const transactionBody = firstTransaction.getGradidoTransaction()?.getTransactionBody() + if (!transactionBody || !transactionBody.isCommunityRoot()) { + throw new LogError('get invalid confirmed transaction from gradido node') + } + const communityRoot = transactionBody.getCommunityRoot() + if (!communityRoot) { + throw new LogError('invalid confirmed transaction') + } + return new KeyPairEd25519(communityRoot.getPubkey()) + } +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts new file mode 100644 index 000000000..b888351cd --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/HomeCommunityKeyPair.role.ts @@ -0,0 +1,22 @@ +import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js' + +import { CONFIG } from '@/config' +import { LogError } from '@/server/LogError' + +import { AbstractKeyPairRole } from './AbstractKeyPair.role' + +export class HomeCommunityKeyPairRole extends AbstractKeyPairRole { + public generateKeyPair(): KeyPairEd25519 { + if (!CONFIG.IOTA_HOME_COMMUNITY_SEED) { + throw new LogError( + 'IOTA_HOME_COMMUNITY_SEED is missing either in config or as environment variable', + ) + } + const seed = MemoryBlock.fromHex(CONFIG.IOTA_HOME_COMMUNITY_SEED) + const keyPair = KeyPairEd25519.create(seed) + if (!keyPair) { + throw new LogError("couldn't create keyPair from IOTA_HOME_COMMUNITY_SEED") + } + return keyPair + } +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts b/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts new file mode 100644 index 000000000..8e6466df5 --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts @@ -0,0 +1,56 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { KeyPairCacheManager } from '@/manager/KeyPairCacheManager' + +import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role' +import { AccountKeyPairRole } from './AccountKeyPair.role' +import { ForeignCommunityKeyPairRole } from './ForeignCommunityKeyPair.role' +import { HomeCommunityKeyPairRole } from './HomeCommunityKeyPair.role' +import { RemoteAccountKeyPairRole } from './RemoteAccountKeyPair.role' +import { UserKeyPairRole } from './UserKeyPair.role' + +/** + * @DCI-Context + * Context for calculating key pair for signing transactions + */ +export async function KeyPairCalculation(input: UserIdentifier | string): Promise { + const cache = KeyPairCacheManager.getInstance() + const keyPair = cache.findKeyPair(input) + if (keyPair) { + return keyPair + } + let communityUUID: string + if (input instanceof UserIdentifier) { + communityUUID = input.communityUuid + } else { + communityUUID = input + } + + if (cache.getHomeCommunityUUID() !== communityUUID) { + // it isn't home community so we can only retrieve public keys + let role: AbstractRemoteKeyPairRole + if (input instanceof UserIdentifier) { + role = new RemoteAccountKeyPairRole(input) + } else { + role = new ForeignCommunityKeyPairRole(input) + } + const keyPair = await role.retrieveKeyPair() + cache.addKeyPair(input, keyPair) + return keyPair + } + + let communityKeyPair = cache.findKeyPair(communityUUID) + if (!communityKeyPair) { + communityKeyPair = new HomeCommunityKeyPairRole().generateKeyPair() + cache.addKeyPair(communityUUID, communityKeyPair) + } + if (input instanceof UserIdentifier) { + const userKeyPair = new UserKeyPairRole(input, communityKeyPair).generateKeyPair() + const accountNr = input.accountNr ?? 1 + const accountKeyPair = new AccountKeyPairRole(accountNr, userKeyPair).generateKeyPair() + cache.addKeyPair(input, accountKeyPair) + return accountKeyPair + } + return communityKeyPair +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts new file mode 100644 index 000000000..1cc9c9891 --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts @@ -0,0 +1,35 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +import { getTransactions } from '@/client/GradidoNode' +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { LogError } from '@/server/LogError' +import { uuid4ToHash } from '@/utils/typeConverter' + +import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role' + +export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole { + public constructor(private user: UserIdentifier) { + super() + } + + public async retrieveKeyPair(): Promise { + const nameHash = uuid4ToHash(this.user.uuid) + const confirmedTransactions = await getTransactions( + 0, + 30, + uuid4ToHash(this.user.communityUuid).convertToHex(), + ) + for (let i = 0; i < confirmedTransactions.length; i++) { + const transactionBody = confirmedTransactions[i].getGradidoTransaction()?.getTransactionBody() + if (transactionBody && transactionBody.isRegisterAddress()) { + const registerAddress = transactionBody.getRegisterAddress() + if (registerAddress && registerAddress.getNameHash()?.equal(nameHash)) { + return new KeyPairEd25519(registerAddress.getAccountPublicKey()) + } + } + } + throw new LogError( + 'cannot find remote user in first 30 transaction from remote blockchain, please wait for better recover implementation', + ) + } +} diff --git a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts new file mode 100644 index 000000000..473b203cd --- /dev/null +++ b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts @@ -0,0 +1,28 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { hardenDerivationIndex } from '@/utils/derivationHelper' +import { uuid4ToBuffer } from '@/utils/typeConverter' + +import { AbstractKeyPairRole } from './AbstractKeyPair.role' + +export class UserKeyPairRole extends AbstractKeyPairRole { + public constructor(private user: UserIdentifier, private communityKeys: KeyPairEd25519) { + super() + } + + public generateKeyPair(): KeyPairEd25519 { + // example gradido id: 03857ac1-9cc2-483e-8a91-e5b10f5b8d16 => + // wholeHex: '03857ac19cc2483e8a91e5b10f5b8d16'] + const wholeHex = uuid4ToBuffer(this.user.uuid) + const parts = [] + for (let i = 0; i < 4; i++) { + parts[i] = hardenDerivationIndex(wholeHex.subarray(i * 4, (i + 1) * 4).readUInt32BE()) + } + // parts: [2206563009, 2629978174, 2324817329, 2405141782] + return parts.reduce( + (keyPair: KeyPairEd25519, node: number) => keyPair.deriveChild(node), + this.communityKeys, + ) + } +} diff --git a/dlt-connector/src/interactions/sendToIota/AbstractTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/AbstractTransaction.role.ts new file mode 100644 index 000000000..ac9120e3d --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/AbstractTransaction.role.ts @@ -0,0 +1,7 @@ +import { GradidoTransactionBuilder } from 'gradido-blockchain-js' + +export abstract class AbstractTransactionRole { + abstract getGradidoTransactionBuilder(): Promise + abstract getSenderCommunityUuid(): string + abstract getRecipientCommunityUuid(): string +} diff --git a/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts new file mode 100644 index 000000000..713a2b906 --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts @@ -0,0 +1,47 @@ +import { GradidoTransactionBuilder } from 'gradido-blockchain-js' + +import { CommunityDraft } from '@/graphql/input/CommunityDraft' +import { LogError } from '@/server/LogError' +import { + AUF_ACCOUNT_DERIVATION_INDEX, + GMW_ACCOUNT_DERIVATION_INDEX, + hardenDerivationIndex, +} from '@/utils/derivationHelper' + +import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' + +import { AbstractTransactionRole } from './AbstractTransaction.role' + +export class CommunityRootTransactionRole extends AbstractTransactionRole { + constructor(private self: CommunityDraft) { + super() + } + + getSenderCommunityUuid(): string { + return this.self.uuid + } + + getRecipientCommunityUuid(): string { + throw new LogError('cannot be used as cross group transaction') + } + + public async getGradidoTransactionBuilder(): Promise { + const builder = new GradidoTransactionBuilder() + const communityKeyPair = await KeyPairCalculation(this.self.uuid) + const gmwKeyPair = communityKeyPair.deriveChild( + hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX), + ) + const aufKeyPair = communityKeyPair.deriveChild( + hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX), + ) + builder + .setCreatedAt(new Date(this.self.createdAt)) + .setCommunityRoot( + communityKeyPair.getPublicKey(), + gmwKeyPair.getPublicKey(), + aufKeyPair.getPublicKey(), + ) + .sign(communityKeyPair) + return builder + } +} diff --git a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts new file mode 100644 index 000000000..0e80e19a4 --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts @@ -0,0 +1,39 @@ +import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js' + +import { TransactionDraft } from '@/graphql/input/TransactionDraft' +import { LogError } from '@/server/LogError' + +import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' + +import { AbstractTransactionRole } from './AbstractTransaction.role' + +export class CreationTransactionRole extends AbstractTransactionRole { + constructor(private self: TransactionDraft) { + super() + } + + getSenderCommunityUuid(): string { + return this.self.user.communityUuid + } + + getRecipientCommunityUuid(): string { + throw new LogError('cannot be used as cross group transaction') + } + + public async getGradidoTransactionBuilder(): Promise { + const builder = new GradidoTransactionBuilder() + const recipientKeyPair = await KeyPairCalculation(this.self.user) + const signerKeyPair = await KeyPairCalculation(this.self.linkedUser) + if (!this.self.targetDate) { + throw new LogError('target date missing for creation transaction') + } + builder + .setCreatedAt(new Date(this.self.createdAt)) + .setTransactionCreation( + new TransferAmount(recipientKeyPair.getPublicKey(), this.self.amount.toString()), + new Date(this.self.targetDate), + ) + .sign(signerKeyPair) + return builder + } +} diff --git a/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts new file mode 100644 index 000000000..498e71bf7 --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts @@ -0,0 +1,40 @@ +/* eslint-disable camelcase */ +import { AddressType_COMMUNITY_HUMAN, GradidoTransactionBuilder } from 'gradido-blockchain-js' + +import { UserAccountDraft } from '@/graphql/input/UserAccountDraft' +import { LogError } from '@/server/LogError' +import { uuid4ToHash } from '@/utils/typeConverter' + +import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' + +import { AbstractTransactionRole } from './AbstractTransaction.role' + +export class RegisterAddressTransactionRole extends AbstractTransactionRole { + constructor(private self: UserAccountDraft) { + super() + } + + getSenderCommunityUuid(): string { + return this.self.user.communityUuid + } + + getRecipientCommunityUuid(): string { + throw new LogError('cannot yet be used as cross group transaction') + } + + public async getGradidoTransactionBuilder(): Promise { + const builder = new GradidoTransactionBuilder() + const communityKeyPair = await KeyPairCalculation(this.self.user.communityUuid) + const accountKeyPair = await KeyPairCalculation(this.self.user) + builder + .setCreatedAt(new Date(this.self.createdAt)) + .setRegisterAddress( + accountKeyPair.getPublicKey(), + AddressType_COMMUNITY_HUMAN, + uuid4ToHash(this.self.user.uuid), + ) + .sign(communityKeyPair) + .sign(accountKeyPair) + return builder + } +} diff --git a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts new file mode 100644 index 000000000..a10a95c35 --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts @@ -0,0 +1,116 @@ +/* eslint-disable camelcase */ +import { + GradidoTransaction, + InteractionSerialize, + InteractionValidate, + MemoryBlock, + ValidateType_SINGLE, +} from 'gradido-blockchain-js' + +import { sendMessage as iotaSendMessage } from '@/client/IotaClient' +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 { UserAccountDraft } from '@/graphql/input/UserAccountDraft' +import { TransactionError } from '@/graphql/model/TransactionError' +import { TransactionRecipe } from '@/graphql/model/TransactionRecipe' +import { TransactionResult } from '@/graphql/model/TransactionResult' +import { logger } from '@/logging/logger' +import { LogError } from '@/server/LogError' +import { uuid4ToHash } from '@/utils/typeConverter' + +import { AbstractTransactionRole } from './AbstractTransaction.role' +import { CommunityRootTransactionRole } from './CommunityRootTransaction.role' +import { CreationTransactionRole } from './CreationTransaction.role' +import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role' +import { TransferTransactionRole } from './TransferTransaction.role' + +/** + * @DCI-Context + * Context for sending transaction to iota + * send every transaction only once to iota! + */ +export async function SendToIotaContext( + input: TransactionDraft | UserAccountDraft | CommunityDraft, +): Promise { + const validate = (transaction: GradidoTransaction): void => { + try { + // throw an exception when something is wrong + const validator = new InteractionValidate(transaction) + validator.run(ValidateType_SINGLE) + } catch (e) { + if (e instanceof Error) { + throw new TransactionError(TransactionErrorType.VALIDATION_ERROR, e.message) + } else if (typeof e === 'string') { + throw new TransactionError(TransactionErrorType.VALIDATION_ERROR, e) + } else { + throw e + } + } + } + + const sendViaIota = async ( + gradidoTransaction: GradidoTransaction, + topic: string, + ): Promise => { + // protobuf serializing function + const serialized = new InteractionSerialize(gradidoTransaction).run() + if (!serialized) { + throw new TransactionError( + TransactionErrorType.PROTO_ENCODE_ERROR, + 'cannot serialize transaction', + ) + } + const resultMessage = await iotaSendMessage( + Uint8Array.from(serialized.data()), + Uint8Array.from(Buffer.from(topic, 'hex')), + ) + logger.info('transmitted Gradido Transaction to Iota', { + messageId: resultMessage.messageId, + }) + return MemoryBlock.fromHex(resultMessage.messageId) + } + + let role: AbstractTransactionRole + if (input instanceof TransactionDraft) { + if (input.type === InputTransactionType.CREATION) { + role = new CreationTransactionRole(input) + } else if (input.type === InputTransactionType.SEND) { + role = new TransferTransactionRole(input) + } else { + throw new LogError('not supported transaction type') + } + } else if (input instanceof UserAccountDraft) { + role = new RegisterAddressTransactionRole(input) + } else if (input instanceof CommunityDraft) { + role = new CommunityRootTransactionRole(input) + } else { + throw new LogError('not expected input') + } + const builder = await role.getGradidoTransactionBuilder() + if (builder.isCrossCommunityTransaction()) { + const outboundTransaction = builder.buildOutbound() + validate(outboundTransaction) + const outboundIotaMessageId = await sendViaIota( + outboundTransaction, + uuid4ToHash(role.getSenderCommunityUuid()).convertToHex(), + ) + builder.setParentMessageId(outboundIotaMessageId) + const inboundTransaction = builder.buildInbound() + validate(inboundTransaction) + await sendViaIota( + inboundTransaction, + uuid4ToHash(role.getRecipientCommunityUuid()).convertToHex(), + ) + return new TransactionResult(new TransactionRecipe(outboundTransaction, outboundIotaMessageId)) + } else { + const transaction = builder.build() + validate(transaction) + const iotaMessageId = await sendViaIota( + transaction, + uuid4ToHash(role.getSenderCommunityUuid()).convertToHex(), + ) + return new TransactionResult(new TransactionRecipe(transaction, iotaMessageId)) + } +} diff --git a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts new file mode 100644 index 000000000..c26f8c61f --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts @@ -0,0 +1,44 @@ +import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js' + +import { TransactionDraft } from '@/graphql/input/TransactionDraft' +import { uuid4ToHash } from '@/utils/typeConverter' + +import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' + +import { AbstractTransactionRole } from './AbstractTransaction.role' + +export class TransferTransactionRole extends AbstractTransactionRole { + constructor(private self: TransactionDraft) { + super() + } + + getSenderCommunityUuid(): string { + return this.self.user.communityUuid + } + + getRecipientCommunityUuid(): string { + return this.self.linkedUser.communityUuid + } + + public async getGradidoTransactionBuilder(): Promise { + const builder = new GradidoTransactionBuilder() + const senderKeyPair = await KeyPairCalculation(this.self.user) + const recipientKeyPair = await KeyPairCalculation(this.self.linkedUser) + builder + .setCreatedAt(new Date(this.self.createdAt)) + .setTransactionTransfer( + new TransferAmount(senderKeyPair.getPublicKey(), this.self.amount.toString()), + recipientKeyPair.getPublicKey(), + ) + const senderCommunity = this.self.user.communityUuid + const recipientCommunity = this.self.linkedUser.communityUuid + if (senderCommunity !== recipientCommunity) { + // we have a cross group transaction + builder + .setSenderCommunity(uuid4ToHash(senderCommunity).convertToHex()) + .setRecipientCommunity(uuid4ToHash(recipientCommunity).convertToHex()) + } + builder.sign(senderKeyPair) + return builder + } +} diff --git a/dlt-connector/src/manager/KeyPairCacheManager.ts b/dlt-connector/src/manager/KeyPairCacheManager.ts new file mode 100644 index 000000000..f5c11e388 --- /dev/null +++ b/dlt-connector/src/manager/KeyPairCacheManager.ts @@ -0,0 +1,66 @@ +import { KeyPairEd25519 } from 'gradido-blockchain-js' + +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { LogError } from '@/server/LogError' + +// Source: https://refactoring.guru/design-patterns/singleton/typescript/example +// and ../federation/client/FederationClientFactory.ts +/** + * A Singleton class defines the `getInstance` method that lets clients access + * the unique singleton instance. + */ +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class KeyPairCacheManager { + // eslint-disable-next-line no-use-before-define + private static instance: KeyPairCacheManager + private cache: Map = new Map() + private homeCommunityUUID: string + + /** + * The Singleton's constructor should always be private to prevent direct + * construction calls with the `new` operator. + */ + // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function + private constructor() {} + + /** + * 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(): KeyPairCacheManager { + if (!KeyPairCacheManager.instance) { + KeyPairCacheManager.instance = new KeyPairCacheManager() + } + return KeyPairCacheManager.instance + } + + public setHomeCommunityUUID(uuid: string): void { + this.homeCommunityUUID = uuid + } + + public getHomeCommunityUUID(): string { + return this.homeCommunityUUID + } + + public findKeyPair(input: UserIdentifier | string): KeyPairEd25519 | undefined { + return this.cache.get(this.getKey(input)) + } + + 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) + } + this.cache.set(key, keyPair) + } + + protected getKey(input: UserIdentifier | string): string { + if (input instanceof UserIdentifier) { + return input.uuid + } else { + return input + } + } +} diff --git a/dlt-connector/src/utils/derivationHelper.ts b/dlt-connector/src/utils/derivationHelper.ts index 0431ec339..9de785306 100644 --- a/dlt-connector/src/utils/derivationHelper.ts +++ b/dlt-connector/src/utils/derivationHelper.ts @@ -1,4 +1,6 @@ export const HARDENED_KEY_BITMASK = 0x80000000 +export const GMW_ACCOUNT_DERIVATION_INDEX = 1 +export const AUF_ACCOUNT_DERIVATION_INDEX = 2 /* * change derivation index from x => x' diff --git a/dlt-connector/src/utils/typeConverter.test.ts b/dlt-connector/src/utils/typeConverter.test.ts index 05fb903b6..527e9dd17 100644 --- a/dlt-connector/src/utils/typeConverter.test.ts +++ b/dlt-connector/src/utils/typeConverter.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' -import { base64ToBuffer, iotaTopicFromCommunityUUID, uuid4ToBuffer } from './typeConverter' +import { base64ToBuffer, uuid4ToHash, uuid4ToBuffer } from './typeConverter' describe('utils/typeConverter', () => { it('uuid4ToBuffer', () => { @@ -10,7 +10,7 @@ describe('utils/typeConverter', () => { }) it('iotaTopicFromCommunityUUID', () => { - expect(iotaTopicFromCommunityUUID('4f28e081-5c39-4dde-b6a4-3bde71de8d65')).toBe( + expect(uuid4ToHash('4f28e081-5c39-4dde-b6a4-3bde71de8d65')).toBe( '3138b3590311fdf0a823e173caa9487b7d275c23fab07106b4b1364cb038affd', ) }) diff --git a/dlt-connector/src/utils/typeConverter.ts b/dlt-connector/src/utils/typeConverter.ts index 9290fdd82..198c15042 100644 --- a/dlt-connector/src/utils/typeConverter.ts +++ b/dlt-connector/src/utils/typeConverter.ts @@ -8,10 +8,14 @@ import { AddressType_CRYPTO_ACCOUNT, AddressType_NONE, AddressType_SUBACCOUNT, + ConfirmedTransaction, + DeserializeType_CONFIRMED_TRANSACTION, + InteractionDeserialize, + MemoryBlock, } from 'gradido-blockchain-js' -import { crypto_generichash as cryptoHash } from 'sodium-native' import { AccountType } from '@/graphql/enum/AccountType' +import { LogError } from '@/server/LogError' export const uuid4ToBuffer = (uuid: string): Buffer => { // Remove dashes from the UUIDv4 string @@ -23,10 +27,13 @@ export const uuid4ToBuffer = (uuid: string): Buffer => { return buffer } -export const iotaTopicFromCommunityUUID = (communityUUID: string): string => { - const hash = Buffer.alloc(32) - cryptoHash(hash, uuid4ToBuffer(communityUUID)) - return hash.toString('hex') +export const uuid4ToMemoryBlock = (uuid: string): MemoryBlock => { + // Remove dashes from the UUIDv4 string + return MemoryBlock.fromHex(uuid.replace(/-/g, '')) +} + +export const uuid4ToHash = (communityUUID: string): MemoryBlock => { + return uuid4ToMemoryBlock(communityUUID).calculateHash() } export const base64ToBuffer = (base64: string): Buffer => { @@ -86,3 +93,16 @@ export const addressTypeToAccountType = (type: AddressType): AccountType => { return AccountType.NONE } } + +export const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransaction => { + const deserializer = new InteractionDeserialize( + MemoryBlock.fromBase64(base64), + DeserializeType_CONFIRMED_TRANSACTION, + ) + deserializer.run() + const confirmedTransaction = deserializer.getConfirmedTransaction() + if (!confirmedTransaction) { + throw new LogError("invalid data, couldn't deserialize") + } + return confirmedTransaction +} diff --git a/dlt-connector/test/seeding/Community.seed.ts b/dlt-connector/test/seeding/Community.seed.ts index 9c492eedb..8dee407f3 100644 --- a/dlt-connector/test/seeding/Community.seed.ts +++ b/dlt-connector/test/seeding/Community.seed.ts @@ -3,7 +3,7 @@ import { Community } from '@entity/Community' import { KeyPair } from '@/data/KeyPair' import { CommunityDraft } from '@/graphql/input/CommunityDraft' import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context' -import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter' +import { uuid4ToHash } from '@/utils/typeConverter' export const communitySeed = async ( uuid: string, @@ -14,7 +14,7 @@ export const communitySeed = async ( homeCommunityDraft.uuid = uuid homeCommunityDraft.foreign = foreign homeCommunityDraft.createdAt = new Date().toISOString() - const iotaTopic = iotaTopicFromCommunityUUID(uuid) + const iotaTopic = uuid4ToHash(uuid) const addCommunityContext = new AddCommunityContext(homeCommunityDraft, iotaTopic) await addCommunityContext.run() diff --git a/dlt-connector/yarn.lock b/dlt-connector/yarn.lock index 33c00a820..64ebb063a 100644 --- a/dlt-connector/yarn.lock +++ b/dlt-connector/yarn.lock @@ -1589,6 +1589,13 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + axios@^1.6.5: version "1.7.7" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" @@ -2254,7 +2261,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: +debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -2463,9 +2470,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.5.4: - version "1.5.26" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.26.tgz#449b4fa90e83ab98abbe3b6a96c8ee395de94452" - integrity sha512-Z+OMe9M/V6Ep9n/52+b7lkvYEps26z4Yz3vjWL1V61W0q+VLF1pOHhMY17sa4roz4AWmULSI8E6SAojZA5L0YQ== + version "1.5.27" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz#5203ce5d6054857d84ba84d3681cbe59132ade78" + integrity sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw== emittery@^0.8.1: version "0.8.1" @@ -3182,7 +3189,7 @@ flatted@^3.2.7, flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -follow-redirects@^1.15.6: +follow-redirects@^1.14.4, follow-redirects@^1.15.6: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== @@ -3468,7 +3475,7 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: "gradido-blockchain-js@git+https://github.com/gradido/gradido-blockchain-js#master": version "0.0.1" - resolved "git+https://github.com/gradido/gradido-blockchain-js#02aaeefc015c8ec8b1a2c453d75e7c2cf803a7c2" + resolved "git+https://github.com/gradido/gradido-blockchain-js#5e7bc50af82d30ef0fdbe48414b1f916c592b6f4" dependencies: bindings "^1.5.0" nan "^2.20.0" @@ -4528,6 +4535,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonrpc-ts-client@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/jsonrpc-ts-client/-/jsonrpc-ts-client-0.2.3.tgz#ec50c413d84041564e6c8a4003ab4bb360d5cfcc" + integrity sha512-9uYpKrZKN3/3+9MYA/0vdhl9/esn59u6I9Qj6ohczxKwJ+e7DD4prf3i2nSdAl0Wlw5gBHZOL3wajSa1uiE16g== + dependencies: + axios "^0.24.0" + debug "^4.3.3" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -4554,9 +4569,9 @@ levn@^0.4.1: type-check "~0.4.0" libphonenumber-js@^1.10.53: - version "1.11.8" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz#697fdd36500a97bc672d7927d867edf34b4bd2a7" - integrity sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg== + version "1.11.9" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.9.tgz#e653042b11da2b50b7ea3b206fa7ca998436ae99" + integrity sha512-Zs5wf5HaWzW2/inlupe2tstl0I/Tbqo7lH20ZLr6Is58u7Dz2n+gRFGNlj9/gWxFvNfp9+YyDsiegjNhdixB9A== lines-and-columns@^1.1.6: version "1.2.4"