diff --git a/backend/src/apis/dltConnector/DltConnectorClient.test.ts b/backend/src/apis/dltConnector/DltConnectorClient.test.ts index 5cd7c3269..28c1c969c 100644 --- a/backend/src/apis/dltConnector/DltConnectorClient.test.ts +++ b/backend/src/apis/dltConnector/DltConnectorClient.test.ts @@ -10,11 +10,9 @@ import { Decimal } from 'decimal.js-light' import { cleanDB, testEnvironment } from '@test/helpers' import { CONFIG } from '@/config' -import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { DltConnectorClient } from './DltConnectorClient' -import { TransactionDraft } from './model/TransactionDraft' let con: Connection diff --git a/backend/src/apis/dltConnector/enum/TransactionType.ts b/backend/src/apis/dltConnector/enum/TransactionType.ts index 095f56c47..d12bb3fa2 100644 --- a/backend/src/apis/dltConnector/enum/TransactionType.ts +++ b/backend/src/apis/dltConnector/enum/TransactionType.ts @@ -9,6 +9,7 @@ export enum TransactionType { GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE', REGISTER_ADDRESS = 'REGISTER_ADDRESS', GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER', + GRADIDO_REDEEM_DEFERRED_TRANSFER = 'GRADIDO_REDEEM_DEFERRED_TRANSFER', COMMUNITY_ROOT = 'COMMUNITY_ROOT', } diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts index 742a0b96d..b761b71c6 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkDeleteToDlt.role.ts @@ -25,6 +25,7 @@ export class TransactionLinkDeleteToDltRole extends AbstractTransactionToDltRole ) .andWhere('TransactionLink.deletedAt IS NOT NULL') .withDeleted() + /* const queryBuilder2 = TransactionLink.createQueryBuilder() .leftJoinAndSelect('TransactionLink.user', 'user') .where('TransactionLink.deletedAt IS NOT NULL') @@ -41,6 +42,7 @@ export class TransactionLinkDeleteToDltRole extends AbstractTransactionToDltRole .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 @@ -67,9 +69,9 @@ export class TransactionLinkDeleteToDltRole extends AbstractTransactionToDltRole 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.linkedUser = new UserIdentifier(user.communityUuid, new CommunityUser(user.gradidoID, 1)) draft.createdAt = this.self.deletedAt.toISOString() - draft.type = TransactionType.GRADIDO_TRANSFER + draft.type = TransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER return draft } diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts index 916aacbfc..16ba7f6f8 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts @@ -40,10 +40,11 @@ export class TransactionLinkToDltRole extends AbstractTransactionToDltRole { + const parameter = { + pubkey: pubkey.convertToHex(), + format: 'base64', + firstTransactionNr, + maxResultCount, + communityId: iotaTopic, + } + logger.info('call listtransactionsforaddress on Node Server via jsonrpc 2.0', parameter) + const response = await client.exec( + 'listtransactionsforaddress', + parameter, + ) + return resolveResponse(response, (result: ConfirmedTransactionList) => { + logger.debug('GradidoNode used time', result.timeUsed) + return result.transactions.map((transactionBase64) => + confirmedTransactionFromBase64(transactionBase64), + ) + }) +} + +export { + getTransaction, + getLastTransaction, + getTransactions, + getAddressType, + getTransactionsForAccount, +} diff --git a/dlt-connector/src/data/KeyPairIdentifier.ts b/dlt-connector/src/data/KeyPairIdentifier.ts new file mode 100644 index 000000000..3a98c2e0e --- /dev/null +++ b/dlt-connector/src/data/KeyPairIdentifier.ts @@ -0,0 +1,72 @@ +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { LogError } from '@/server/LogError' +import { uuid4sToMemoryBlock } from '@/utils/typeConverter' + +export class KeyPairIdentifier { + // used for community key pair if it is only parameter or for user key pair + communityUuid?: string + // if set calculate key pair from seed, ignore all other parameter + seed?: string + // used for user key pair and account key pair, need also communityUuid + userUuid?: string + // used for account key pair, need also userUuid + accountNr?: number + + public constructor(input: UserIdentifier | string | undefined = undefined) { + if (input instanceof UserIdentifier) { + if (input.seed !== undefined) { + this.seed = input.seed.seed + } else { + this.communityUuid = input.communityUuid + this.userUuid = input.communityUser?.uuid + this.accountNr = input.communityUser?.accountNr + } + } else if (typeof input === 'string') { + this.communityUuid = input + } + } + + isCommunityKeyPair(): boolean { + return this.communityUuid !== undefined && this.userUuid === undefined + } + + isSeedKeyPair(): boolean { + return this.seed !== undefined + } + + isUserKeyPair(): boolean { + return ( + this.communityUuid !== undefined && + this.userUuid !== undefined && + this.accountNr === undefined + ) + } + + isAccountKeyPair(): boolean { + return ( + this.communityUuid !== undefined && + this.userUuid !== undefined && + this.accountNr !== undefined + ) + } + + getKey(): string { + if (this.seed && this.isSeedKeyPair()) { + return this.seed + } else if (this.communityUuid && this.isCommunityKeyPair()) { + return this.communityUuid + } + if (this.userUuid && this.communityUuid) { + const communityUserHash = uuid4sToMemoryBlock([this.userUuid, this.communityUuid]) + .calculateHash() + .convertToHex() + if (this.isUserKeyPair()) { + return communityUserHash + } + if (this.accountNr && this.isAccountKeyPair()) { + return communityUserHash + this.accountNr.toString() + } + } + throw new LogError('KeyPairIdentifier: unhandled input type', this) + } +} diff --git a/dlt-connector/src/graphql/const.ts b/dlt-connector/src/graphql/const.ts new file mode 100644 index 000000000..ce7186a54 --- /dev/null +++ b/dlt-connector/src/graphql/const.ts @@ -0,0 +1,2 @@ +export const MEMO_MAX_CHARS = 255 +export const MEMO_MIN_CHARS = 5 diff --git a/dlt-connector/src/graphql/enum/InputTransactionType.ts b/dlt-connector/src/graphql/enum/InputTransactionType.ts index 20e5e40f9..f1b56823e 100755 --- a/dlt-connector/src/graphql/enum/InputTransactionType.ts +++ b/dlt-connector/src/graphql/enum/InputTransactionType.ts @@ -8,6 +8,7 @@ export enum InputTransactionType { GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE', REGISTER_ADDRESS = 'REGISTER_ADDRESS', GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER', + GRADIDO_REDEEM_DEFERRED_TRANSFER = 'GRADIDO_REDEEM_DEFERRED_TRANSFER', COMMUNITY_ROOT = 'COMMUNITY_ROOT', } diff --git a/dlt-connector/src/graphql/enum/TransactionErrorType.ts b/dlt-connector/src/graphql/enum/TransactionErrorType.ts index 5a8a2a06b..f89df4afe 100644 --- a/dlt-connector/src/graphql/enum/TransactionErrorType.ts +++ b/dlt-connector/src/graphql/enum/TransactionErrorType.ts @@ -5,6 +5,7 @@ import { registerEnumType } from 'type-graphql' export enum TransactionErrorType { NOT_IMPLEMENTED_YET = 'Not Implemented yet', MISSING_PARAMETER = 'Missing parameter', + INVALID_PARAMETER = 'Invalid parameter', ALREADY_EXIST = 'Already exist', DB_ERROR = 'DB Error', PROTO_DECODE_ERROR = 'Proto Decode Error', diff --git a/dlt-connector/src/graphql/input/TransactionDraft.ts b/dlt-connector/src/graphql/input/TransactionDraft.ts index 3b2099ab6..0306d70e0 100755 --- a/dlt-connector/src/graphql/input/TransactionDraft.ts +++ b/dlt-connector/src/graphql/input/TransactionDraft.ts @@ -1,9 +1,10 @@ // https://www.npmjs.com/package/@apollo/protobufjs import { InputTransactionType } from '@enum/InputTransactionType' import { isValidDateString, isValidNumberString } from '@validator/DateString' -import { IsEnum, IsObject, ValidateNested } from 'class-validator' +import { IsEnum, IsObject, IsPositive, MaxLength, MinLength, ValidateNested } from 'class-validator' import { InputType, Field } from 'type-graphql' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql//const' import { AccountType } from '@/graphql/enum/AccountType' import { UserIdentifier } from './UserIdentifier' @@ -26,6 +27,11 @@ export class TransactionDraft { @isValidNumberString() amount?: string + @Field(() => String, { nullable: true }) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) + memo?: string + @Field(() => InputTransactionType) @IsEnum(InputTransactionType) type: InputTransactionType @@ -40,9 +46,10 @@ export class TransactionDraft { targetDate?: string // only for deferred transaction - @Field(() => String, { nullable: true }) - @isValidDateString() - timeoutDate?: string + // duration in seconds + @Field(() => Number, { nullable: true }) + @IsPositive() + timeoutDuration?: number // only for register address @Field(() => AccountType, { nullable: true }) diff --git a/dlt-connector/src/graphql/resolver/AccountsResolver.ts b/dlt-connector/src/graphql/resolver/AccountsResolver.ts index ab7203285..3db0ac26b 100644 --- a/dlt-connector/src/graphql/resolver/AccountsResolver.ts +++ b/dlt-connector/src/graphql/resolver/AccountsResolver.ts @@ -3,9 +3,9 @@ import { AddressType_NONE } from 'gradido-blockchain-js' import { Arg, Query, Resolver } from 'type-graphql' import { getAddressType } from '@/client/GradidoNode' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { KeyPairCalculation } from '@/interactions/keyPairCalculation/KeyPairCalculation.context' import { logger } from '@/logging/logger' -import { KeyPairCacheManager } from '@/manager/KeyPairCacheManager' import { uuid4ToHash } from '@/utils/typeConverter' import { TransactionErrorType } from '../enum/TransactionErrorType' @@ -17,7 +17,7 @@ import { TransactionResult } from '../model/TransactionResult' export class AccountResolver { @Query(() => Boolean) async isAccountExist(@Arg('data') userIdentifier: UserIdentifier): Promise { - const accountKeyPair = await KeyPairCalculation(userIdentifier) + const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifier(userIdentifier)) const publicKey = accountKeyPair.getPublicKey() if (!publicKey) { throw new TransactionResult( diff --git a/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts index 68dd65a21..fb062b011 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/ForeignCommunityKeyPair.role.ts @@ -26,6 +26,6 @@ export class ForeignCommunityKeyPairRole extends AbstractRemoteKeyPairRole { if (!communityRoot) { throw new LogError('invalid confirmed transaction') } - return new KeyPairEd25519(communityRoot.getPubkey()) + return new KeyPairEd25519(communityRoot.getPublicKey()) } } diff --git a/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts b/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts index 3cb1f43cf..0f9137453 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts @@ -1,8 +1,9 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js' -import { IdentifierSeed } from '@/graphql/input/IdentifierSeed' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { UserIdentifier } from '@/graphql/input/UserIdentifier' import { KeyPairCacheManager } from '@/manager/KeyPairCacheManager' +import { LogError } from '@/server/LogError' import { AccountKeyPairRole } from './AccountKeyPair.role' import { ForeignCommunityKeyPairRole } from './ForeignCommunityKeyPair.role' @@ -15,7 +16,7 @@ import { UserKeyPairRole } from './UserKeyPair.role' * @DCI-Context * Context for calculating key pair for signing transactions */ -export async function KeyPairCalculation(input: UserIdentifier | string): Promise { +export async function KeyPairCalculation(input: KeyPairIdentifier): Promise { const cache = KeyPairCacheManager.getInstance() // Try cache lookup first @@ -24,33 +25,49 @@ export async function KeyPairCalculation(input: UserIdentifier | string): Promis return keyPair } - const retrieveKeyPair = async (input: UserIdentifier | string): Promise => { - if (input instanceof UserIdentifier && input.seed) { - return new LinkedTransactionKeyPairRole(input.seed.seed).generateKeyPair() + const retrieveKeyPair = async (input: KeyPairIdentifier): Promise => { + if (input.isSeedKeyPair() && input.seed) { + return new LinkedTransactionKeyPairRole(input.seed).generateKeyPair() + } + if (!input.communityUuid) { + throw new LogError('missing community id') } - - const communityUUID = input instanceof UserIdentifier ? input.communityUuid : input - // If input does not belong to the home community, handle as remote key pair - if (cache.getHomeCommunityUUID() !== communityUUID) { + if (cache.getHomeCommunityUUID() !== input.communityUuid) { const role = input instanceof UserIdentifier ? new RemoteAccountKeyPairRole(input) - : new ForeignCommunityKeyPairRole(input) + : new ForeignCommunityKeyPairRole(input.communityUuid) return await role.retrieveKeyPair() } - let communityKeyPair = cache.findKeyPair(communityUUID) + let communityKeyPair = cache.findKeyPair(input) if (!communityKeyPair) { communityKeyPair = new HomeCommunityKeyPairRole().generateKeyPair() - cache.addKeyPair(communityUUID, communityKeyPair) } - if (input instanceof UserIdentifier) { - const userKeyPair = new UserKeyPairRole(input, communityKeyPair).generateKeyPair() - const accountNr = input.communityUser?.accountNr ?? 1 - return new AccountKeyPairRole(accountNr, userKeyPair).generateKeyPair() + if (input.isCommunityKeyPair()) { + return communityKeyPair } - return communityKeyPair + const userKeyPairIdentifier = new KeyPairIdentifier() + userKeyPairIdentifier.communityUuid = input.communityUuid + userKeyPairIdentifier.userUuid = input.userUuid + + let userKeyPair = cache.findKeyPair(userKeyPairIdentifier) + if (!userKeyPair && userKeyPairIdentifier.userUuid) { + userKeyPair = new UserKeyPairRole( + userKeyPairIdentifier.userUuid, + communityKeyPair, + ).generateKeyPair() + } + if (!userKeyPair) { + throw new LogError("couldn't generate user key pair") + } + if (input.isUserKeyPair()) { + return userKeyPair + } + + const accountNr = input.accountNr ?? 1 + return new AccountKeyPairRole(accountNr, userKeyPair).generateKeyPair() } keyPair = await retrieveKeyPair(input) diff --git a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts index 6f433a7df..ca724fc99 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts @@ -1,24 +1,20 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js' -import { UserIdentifier } from '@/graphql/input/UserIdentifier' -import { LogError } from '@/server/LogError' 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) { + public constructor(private userUuid: string, private communityKeys: KeyPairEd25519) { super() } public generateKeyPair(): KeyPairEd25519 { // example gradido id: 03857ac1-9cc2-483e-8a91-e5b10f5b8d16 => // wholeHex: '03857ac19cc2483e8a91e5b10f5b8d16'] - if (!this.user.communityUser) { - throw new LogError('missing community user') - } - const wholeHex = uuid4ToBuffer(this.user.communityUser.uuid) + + const wholeHex = uuid4ToBuffer(this.userUuid) const parts = [] for (let i = 0; i < 4; i++) { parts[i] = hardenDerivationIndex(wholeHex.subarray(i * 4, (i + 1) * 4).readUInt32BE()) diff --git a/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts index 713a2b906..a1a7b237d 100644 --- a/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/CommunityRootTransaction.role.ts @@ -1,5 +1,6 @@ import { GradidoTransactionBuilder } from 'gradido-blockchain-js' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { CommunityDraft } from '@/graphql/input/CommunityDraft' import { LogError } from '@/server/LogError' import { @@ -27,7 +28,7 @@ export class CommunityRootTransactionRole extends AbstractTransactionRole { public async getGradidoTransactionBuilder(): Promise { const builder = new GradidoTransactionBuilder() - const communityKeyPair = await KeyPairCalculation(this.self.uuid) + const communityKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.uuid)) const gmwKeyPair = communityKeyPair.deriveChild( hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX), ) diff --git a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts index 2865193a9..e3fa9a838 100644 --- a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts @@ -1,8 +1,16 @@ -import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js' +import { + AuthenticatedEncryption, + EncryptedMemo, + GradidoTransactionBuilder, + GradidoUnit, + TransferAmount, +} from 'gradido-blockchain-js' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { TransactionError } from '@/graphql/model/TransactionError' +import { KeyPairCacheManager } from '@/manager/KeyPairCacheManager' import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' @@ -40,15 +48,25 @@ export class CreationTransactionRole extends AbstractTransactionRole { if (!this.self.amount) { throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'creation: amount missing') } + if (!this.self.memo) { + throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'creation: memo missing') + } + const builder = new GradidoTransactionBuilder() - const recipientKeyPair = await KeyPairCalculation(this.self.user) - const signerKeyPair = await KeyPairCalculation(this.self.linkedUser) + const recipientKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.user)) + const signerKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.linkedUser)) + const homeCommunityKeyPair = await KeyPairCalculation( + new KeyPairIdentifier(KeyPairCacheManager.getInstance().getHomeCommunityUUID()), + ) builder .setCreatedAt(new Date(this.self.createdAt)) - .setMemo('dummy memo for creation') + .addMemo(new EncryptedMemo(this.self.memo, new AuthenticatedEncryption(homeCommunityKeyPair))) .setTransactionCreation( - new TransferAmount(recipientKeyPair.getPublicKey(), this.self.amount.toString()), + new TransferAmount( + recipientKeyPair.getPublicKey(), + GradidoUnit.fromString(this.self.amount), + ), new Date(this.self.targetDate), ) .sign(signerKeyPair) diff --git a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts index 511efa869..19b4dc649 100644 --- a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts @@ -1,5 +1,13 @@ -import { GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js' +import { + AuthenticatedEncryption, + EncryptedMemo, + GradidoTransactionBuilder, + GradidoTransfer, + GradidoUnit, + TransferAmount, +} from 'gradido-blockchain-js' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { TransactionError } from '@/graphql/model/TransactionError' @@ -37,27 +45,42 @@ export class DeferredTransferTransactionRole extends AbstractTransactionRole { 'deferred transfer: amount missing', ) } - if (!this.self.timeoutDate) { + if (!this.self.memo) { throw new TransactionError( TransactionErrorType.MISSING_PARAMETER, - 'deferred transfer: timeout date missing', + 'deferred transfer: memo missing', + ) + } + if (!this.self.timeoutDuration) { + throw new TransactionError( + TransactionErrorType.MISSING_PARAMETER, + 'deferred transfer: timeout duration missing', ) } const builder = new GradidoTransactionBuilder() - const senderKeyPair = await KeyPairCalculation(this.self.user) - const recipientKeyPair = await KeyPairCalculation(this.self.linkedUser) + const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.user)) + const recipientKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.linkedUser)) builder .setCreatedAt(new Date(this.self.createdAt)) - .setMemo('dummy memo for transfer') + .addMemo( + new EncryptedMemo( + this.self.memo, + new AuthenticatedEncryption(senderKeyPair), + new AuthenticatedEncryption(recipientKeyPair), + ), + ) .setDeferredTransfer( new GradidoTransfer( - new TransferAmount(senderKeyPair.getPublicKey(), this.self.amount.toString()), + new TransferAmount( + senderKeyPair.getPublicKey(), + GradidoUnit.fromString(this.self.amount), + ), recipientKeyPair.getPublicKey(), ), - new Date(this.self.timeoutDate), + this.self.timeoutDuration, ) - builder.sign(senderKeyPair) + .sign(senderKeyPair) return builder } } diff --git a/dlt-connector/src/interactions/sendToIota/RedeemDeferredTransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/RedeemDeferredTransferTransaction.role.ts new file mode 100644 index 000000000..eb858c322 --- /dev/null +++ b/dlt-connector/src/interactions/sendToIota/RedeemDeferredTransferTransaction.role.ts @@ -0,0 +1,103 @@ +import { + GradidoTransactionBuilder, + GradidoTransfer, + GradidoUnit, + TransferAmount, +} from 'gradido-blockchain-js' + +import { getTransactionsForAccount } from '@/client/GradidoNode' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' +import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' +import { TransactionDraft } from '@/graphql/input/TransactionDraft' +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { TransactionError } from '@/graphql/model/TransactionError' +import { communityUuidToTopic, uuid4ToHash } from '@/utils/typeConverter' + +import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' + +import { AbstractTransactionRole } from './AbstractTransaction.role' + +export class RedeemDeferredTransferTransactionRole extends AbstractTransactionRole { + private linkedUser: UserIdentifier + constructor(protected self: TransactionDraft) { + super() + if (!this.self.linkedUser) { + throw new TransactionError( + TransactionErrorType.MISSING_PARAMETER, + 'transfer: linked user missing', + ) + } + this.linkedUser = this.self.linkedUser + } + + getSenderCommunityUuid(): string { + return this.self.user.communityUuid + } + + getRecipientCommunityUuid(): string { + return this.linkedUser.communityUuid + } + + public async getGradidoTransactionBuilder(): Promise { + if (!this.self.amount) { + throw new TransactionError( + TransactionErrorType.MISSING_PARAMETER, + 'redeem deferred transfer: amount missing', + ) + } + const builder = new GradidoTransactionBuilder() + const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.user)) + const senderPublicKey = senderKeyPair.getPublicKey() + if (!senderPublicKey) { + throw new TransactionError( + TransactionErrorType.INVALID_PARAMETER, + "redeem deferred transfer: couldn't calculate sender public key", + ) + } + // load deferred transfer transaction from gradido node + const transactions = await getTransactionsForAccount( + senderPublicKey, + communityUuidToTopic(this.getSenderCommunityUuid()), + ) + if (!transactions || transactions.length !== 1) { + throw new TransactionError( + TransactionErrorType.NOT_FOUND, + "redeem deferred transfer: couldn't find deferred transfer on Gradido Node", + ) + } + const deferredTransfer = transactions[0] + const deferredTransferBody = deferredTransfer.getGradidoTransaction()?.getTransactionBody() + if (!deferredTransferBody) { + throw new TransactionError( + TransactionErrorType.NOT_FOUND, + "redeem deferred transfer: couldn't deserialize deferred transfer from Gradido Node", + ) + } + const recipientKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.linkedUser)) + + // TODO: fix getMemos in gradido-blockchain-js to return correct data + builder + .setCreatedAt(new Date(this.self.createdAt)) + .addMemo(deferredTransferBody.getMemos()[0]) + .setRedeemDeferredTransfer( + deferredTransfer.getId(), + new GradidoTransfer( + new TransferAmount( + senderKeyPair.getPublicKey(), + GradidoUnit.fromString(this.self.amount), + ), + recipientKeyPair.getPublicKey(), + ), + ) + const senderCommunity = this.self.user.communityUuid + const recipientCommunity = this.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/interactions/sendToIota/RegisterAddressTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts index f8c82586d..70d612e08 100644 --- a/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts @@ -1,6 +1,7 @@ /* eslint-disable camelcase */ import { GradidoTransactionBuilder } from 'gradido-blockchain-js' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { TransactionError } from '@/graphql/model/TransactionError' @@ -40,17 +41,25 @@ export class RegisterAddressTransactionRole extends AbstractTransactionRole { } const builder = new GradidoTransactionBuilder() - const communityKeyPair = await KeyPairCalculation(this.self.user.communityUuid) - const accountKeyPair = await KeyPairCalculation(this.self.user) + const communityKeyPair = await KeyPairCalculation( + new KeyPairIdentifier(this.self.user.communityUuid), + ) + const keyPairIdentifer = new KeyPairIdentifier(this.self.user) + const accountKeyPair = await KeyPairCalculation(keyPairIdentifer) + // unsetting accountNr change identifier from account key pair to user key pair + keyPairIdentifer.accountNr = undefined + const userKeyPair = await KeyPairCalculation(keyPairIdentifer) builder .setCreatedAt(new Date(this.self.createdAt)) .setRegisterAddress( - accountKeyPair.getPublicKey(), + userKeyPair.getPublicKey(), accountTypeToAddressType(this.self.accountType), uuid4ToHash(this.self.user.communityUser.uuid), + accountKeyPair.getPublicKey(), ) .sign(communityKeyPair) .sign(accountKeyPair) + .sign(userKeyPair) return builder } } diff --git a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts index be02eef55..2607f6c8f 100644 --- a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts +++ b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts @@ -17,12 +17,13 @@ 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 { communityUuidToTopic } from '@/utils/typeConverter' import { AbstractTransactionRole } from './AbstractTransaction.role' import { CommunityRootTransactionRole } from './CommunityRootTransaction.role' import { CreationTransactionRole } from './CreationTransaction.role' import { DeferredTransferTransactionRole } from './DeferredTransferTransaction.role' +import { RedeemDeferredTransferTransactionRole } from './RedeemDeferredTransferTransaction.role' import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role' import { TransferTransactionRole } from './TransferTransaction.role' @@ -87,6 +88,9 @@ export async function SendToIotaContext( case InputTransactionType.GRADIDO_DEFERRED_TRANSFER: role = new DeferredTransferTransactionRole(input) break + case InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER: + role = new RedeemDeferredTransferTransactionRole(input) + break default: throw new TransactionError( TransactionErrorType.NOT_IMPLEMENTED_YET, @@ -104,22 +108,19 @@ export async function SendToIotaContext( validate(outboundTransaction) const outboundIotaMessageId = await sendViaIota( outboundTransaction, - uuid4ToHash(role.getSenderCommunityUuid()).convertToHex(), + communityUuidToTopic(role.getSenderCommunityUuid()), ) builder.setParentMessageId(outboundIotaMessageId) const inboundTransaction = builder.buildInbound() validate(inboundTransaction) - await sendViaIota( - inboundTransaction, - uuid4ToHash(role.getRecipientCommunityUuid()).convertToHex(), - ) + await sendViaIota(inboundTransaction, communityUuidToTopic(role.getRecipientCommunityUuid())) return new TransactionResult(new TransactionRecipe(outboundTransaction, outboundIotaMessageId)) } else { const transaction = builder.build() validate(transaction) const iotaMessageId = await sendViaIota( transaction, - uuid4ToHash(role.getSenderCommunityUuid()).convertToHex(), + communityUuidToTopic(role.getSenderCommunityUuid()), ) 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 index 7a4cd7bb0..dd730f590 100644 --- a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts @@ -1,5 +1,12 @@ -import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js' +import { + AuthenticatedEncryption, + EncryptedMemo, + GradidoTransactionBuilder, + GradidoUnit, + TransferAmount, +} from 'gradido-blockchain-js' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { UserIdentifier } from '@/graphql/input/UserIdentifier' @@ -35,14 +42,27 @@ export class TransferTransactionRole extends AbstractTransactionRole { if (!this.self.amount) { throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'transfer: amount missing') } + if (!this.self.memo) { + throw new TransactionError( + TransactionErrorType.MISSING_PARAMETER, + 'deferred transfer: memo missing', + ) + } const builder = new GradidoTransactionBuilder() - const senderKeyPair = await KeyPairCalculation(this.self.user) - const recipientKeyPair = await KeyPairCalculation(this.linkedUser) + const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.user)) + const recipientKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.linkedUser)) + builder .setCreatedAt(new Date(this.self.createdAt)) - .setMemo('dummy memo for transfer') + .addMemo( + new EncryptedMemo( + this.self.memo, + new AuthenticatedEncryption(senderKeyPair), + new AuthenticatedEncryption(recipientKeyPair), + ), + ) .setTransactionTransfer( - new TransferAmount(senderKeyPair.getPublicKey(), this.self.amount.toString()), + new TransferAmount(senderKeyPair.getPublicKey(), GradidoUnit.fromString(this.self.amount)), recipientKeyPair.getPublicKey(), ) const senderCommunity = this.self.user.communityUuid diff --git a/dlt-connector/src/manager/KeyPairCacheManager.ts b/dlt-connector/src/manager/KeyPairCacheManager.ts index f492d127b..c3613f91d 100644 --- a/dlt-connector/src/manager/KeyPairCacheManager.ts +++ b/dlt-connector/src/manager/KeyPairCacheManager.ts @@ -1,8 +1,7 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js' -import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { KeyPairIdentifier } from '@/data/KeyPairIdentifier' import { logger } from '@/logging/logger' -import { LogError } from '@/server/LogError' // Source: https://refactoring.guru/design-patterns/singleton/typescript/example // and ../federation/client/FederationClientFactory.ts @@ -45,30 +44,19 @@ export class KeyPairCacheManager { return this.homeCommunityUUID } - public findKeyPair(input: UserIdentifier | string): KeyPairEd25519 | undefined { - return this.cache.get(this.getKey(input)) + public findKeyPair(input: KeyPairIdentifier): KeyPairEd25519 | undefined { + return this.cache.get(input.getKey()) } - public addKeyPair(input: UserIdentifier | string, keyPair: KeyPairEd25519): void { - const key = this.getKey(input) + public addKeyPair(input: KeyPairIdentifier, keyPair: KeyPairEd25519): void { + const key = input.getKey() if (this.cache.has(key)) { - logger.warn('key already exist, cannot add', key) + logger.warn('key already exist, cannot add', { + key, + publicKey: keyPair.getPublicKey()?.convertToHex(), + }) return } this.cache.set(key, keyPair) } - - protected getKey(input: UserIdentifier | string): string { - if (input instanceof UserIdentifier) { - if (input.communityUser) { - return input.communityUser.uuid - } else if (input.seed) { - return input.seed.seed - } - throw new LogError('unhandled branch') - } else if (typeof input === 'string') { - return input - } - throw new LogError('unhandled input type') - } } diff --git a/dlt-connector/src/utils/typeConverter.ts b/dlt-connector/src/utils/typeConverter.ts index 198c15042..219b95376 100644 --- a/dlt-connector/src/utils/typeConverter.ts +++ b/dlt-connector/src/utils/typeConverter.ts @@ -32,6 +32,14 @@ export const uuid4ToMemoryBlock = (uuid: string): MemoryBlock => { return MemoryBlock.fromHex(uuid.replace(/-/g, '')) } +export const uuid4sToMemoryBlock = (uuid: string[]): MemoryBlock => { + let resultHexString = '' + for (let i = 0; i < uuid.length; i++) { + resultHexString += uuid[i].replace(/-/g, '') + } + return MemoryBlock.fromHex(resultHexString) +} + export const uuid4ToHash = (communityUUID: string): MemoryBlock => { return uuid4ToMemoryBlock(communityUUID).calculateHash() } @@ -40,6 +48,10 @@ export const base64ToBuffer = (base64: string): Buffer => { return Buffer.from(base64, 'base64') } +export const communityUuidToTopic = (communityUUID: string): string => { + return uuid4ToHash(communityUUID).convertToHex() +} + export function getEnumValue>( enumType: T, value: number | string, diff --git a/dlt-connector/yarn.lock b/dlt-connector/yarn.lock index c4f244816..71cde5091 100644 --- a/dlt-connector/yarn.lock +++ b/dlt-connector/yarn.lock @@ -3277,9 +3277,9 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -"gradido-blockchain-js@git+https://github.com/gradido/gradido-blockchain-js#master": +"gradido-blockchain-js@git+https://github.com/gradido/gradido-blockchain-js#1c75576": version "0.0.1" - resolved "git+https://github.com/gradido/gradido-blockchain-js#5e7bc50af82d30ef0fdbe48414b1f916c592b6f4" + resolved "git+https://github.com/gradido/gradido-blockchain-js#1c755763b7f3f71c2ee9f396da5e9512fa666ee4" dependencies: bindings "^1.5.0" nan "^2.20.0"