refactor for deferred transfer

This commit is contained in:
einhornimmond 2025-01-10 20:27:59 +01:00
parent a2c3b82b0f
commit eb424fc6c5
27 changed files with 403 additions and 96 deletions

View File

@ -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

View File

@ -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',
}

View File

@ -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
}

View File

@ -40,10 +40,11 @@ export class TransactionLinkToDltRole extends AbstractTransactionToDltRole<Trans
const draft = new TransactionDraft()
draft.amount = this.self.amount.abs().toString()
const user = this.self.user
draft.user = new UserIdentifier(user.communityUuid, new CommunityUser(user.gradidoID))
draft.user = new UserIdentifier(user.communityUuid, new CommunityUser(user.gradidoID, 1))
draft.linkedUser = new UserIdentifier(user.communityUuid, new IdentifierSeed(this.self.code))
draft.createdAt = this.self.createdAt.toISOString()
draft.timeoutDate = this.self.validUntil.toISOString()
draft.timeoutDuration = (this.self.validUntil.getTime() - this.self.createdAt.getTime()) / 1000
draft.memo = this.self.memo
draft.type = TransactionType.GRADIDO_DEFERRED_TRANSFER
return draft
}

View File

@ -81,13 +81,14 @@ export class TransactionToDltRole extends AbstractTransactionToDltRole<Transacti
} else {
draft.user = new UserIdentifier(
this.self.userCommunityUuid,
new CommunityUser(this.self.userGradidoID),
new CommunityUser(this.self.userGradidoID, 1),
)
}
draft.linkedUser = new UserIdentifier(
this.self.linkedUserCommunityUuid,
new CommunityUser(this.self.linkedUserGradidoID),
new CommunityUser(this.self.linkedUserGradidoID, 1),
)
draft.memo = this.self.memo
draft.createdAt = this.self.balanceDate.toISOString()
draft.targetDate = this.self.creationDate?.toISOString()
return draft

View File

@ -10,12 +10,13 @@ export class TransactionDraft {
linkedUser?: UserIdentifier
// not used for register address
amount?: string
memo?: string
type: TransactionType
createdAt: string
// only for creation transaction
targetDate?: string
// only for deferred transaction
timeoutDate?: string
timeoutDuration?: number
// only for register address
accountType?: AccountType
}

View File

@ -26,7 +26,7 @@
"dotenv": "10.0.0",
"express": "4.17.1",
"express-slow-down": "^2.0.1",
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js#master",
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js#1c75576",
"graphql": "^16.7.1",
"graphql-request": "^6.1.0",
"graphql-scalars": "^1.22.2",

View File

@ -1,5 +1,5 @@
/* eslint-disable camelcase */
import { AddressType, ConfirmedTransaction, stringToAddressType } from 'gradido-blockchain-js'
import { AddressType, ConfirmedTransaction, MemoryBlock, stringToAddressType } from 'gradido-blockchain-js'
import JsonRpcClient from 'jsonrpc-ts-client'
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
@ -121,4 +121,36 @@ async function getAddressType(pubkey: Buffer, iotaTopic: string): Promise<Addres
)
}
export { getTransaction, getLastTransaction, getTransactions, getAddressType }
async function getTransactionsForAccount(
pubkey: MemoryBlock,
iotaTopic: string,
maxResultCount = 0,
firstTransactionNr = 1,
): Promise<ConfirmedTransaction[] | undefined> {
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<ConfirmedTransactionList>(
'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,
}

View File

@ -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)
}
}

View File

@ -0,0 +1,2 @@
export const MEMO_MAX_CHARS = 255
export const MEMO_MIN_CHARS = 5

View File

@ -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',
}

View File

@ -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',

View File

@ -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 })

View File

@ -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<boolean> {
const accountKeyPair = await KeyPairCalculation(userIdentifier)
const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifier(userIdentifier))
const publicKey = accountKeyPair.getPublicKey()
if (!publicKey) {
throw new TransactionResult(

View File

@ -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())
}
}

View File

@ -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<KeyPairEd25519> {
export async function KeyPairCalculation(input: KeyPairIdentifier): Promise<KeyPairEd25519> {
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<KeyPairEd25519> => {
if (input instanceof UserIdentifier && input.seed) {
return new LinkedTransactionKeyPairRole(input.seed.seed).generateKeyPair()
const retrieveKeyPair = async (input: KeyPairIdentifier): Promise<KeyPairEd25519> => {
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)

View File

@ -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())

View File

@ -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<GradidoTransactionBuilder> {
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),
)

View File

@ -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)

View File

@ -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
}
}

View File

@ -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<GradidoTransactionBuilder> {
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
}
}

View File

@ -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
}
}

View File

@ -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))
}

View File

@ -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

View File

@ -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')
}
}

View File

@ -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<T extends Record<string, unknown>>(
enumType: T,
value: number | string,

View File

@ -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"