mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
change for hiero
This commit is contained in:
parent
c06f82bf29
commit
18879cf207
@ -1,11 +1,11 @@
|
||||
import { GraphQLClient, gql } from 'graphql-request'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { getLogger } from 'log4js'
|
||||
|
||||
import { TransactionDraft } from './model/TransactionDraft'
|
||||
import { TransactionResult } from './model/TransactionResult'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.dltConnector`)
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from '@dbTools/typeorm'
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from 'typeorm'
|
||||
import { DltTransaction } from 'database'
|
||||
|
||||
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
|
||||
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
|
||||
export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
|
||||
protected self: T | null
|
||||
@ -14,6 +14,9 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
|
||||
public abstract getTimestamp(): number
|
||||
public abstract convertToGraphqlInput(): TransactionDraft
|
||||
|
||||
|
||||
public constructor(protected logger: Logger) {}
|
||||
|
||||
public getEntity(): T | null {
|
||||
return this.self
|
||||
}
|
||||
@ -25,11 +28,11 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
|
||||
this.setJoinIdAndType(dltTransaction)
|
||||
await DltTransaction.save(dltTransaction)
|
||||
if (dltTransaction.error) {
|
||||
logger.error(
|
||||
this.logger.error(
|
||||
`Store dltTransaction with error: id=${dltTransaction.id}, error=${dltTransaction.error}`,
|
||||
)
|
||||
} else {
|
||||
logger.info(
|
||||
this.logger.info(
|
||||
`Store dltTransaction: messageId=${dltTransaction.messageId}, id=${dltTransaction.id}`,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
import { DltTransaction, TransactionLink } from 'database'
|
||||
|
||||
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
|
||||
import { TransactionType } from '@dltConnector/enum/TransactionType'
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { DltTransaction, Transaction } from 'database'
|
||||
|
||||
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
|
||||
import { TransactionType } from '@dltConnector/enum/TransactionType'
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { User } from '@entity/User'
|
||||
import { DltTransaction, User } from 'database'
|
||||
|
||||
import { AccountType } from '@dltConnector/enum/AccountType'
|
||||
import { DltTransactionType } from '@dltConnector/enum/DltTransactionType'
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
import { User } from '@entity/User'
|
||||
import { Transaction, TransactionLink, User } from 'database'
|
||||
|
||||
import { DltConnectorClient } from '@/apis/dltConnector/DltConnectorClient'
|
||||
import { TransactionResult } from '@/apis/dltConnector/model/TransactionResult'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
|
||||
import { TransactionLinkDeleteToDltRole } from './TransactionLinkDeleteToDlt.role'
|
||||
import { TransactionLinkToDltRole } from './TransactionLinkToDlt.role'
|
||||
import { TransactionToDltRole } from './TransactionToDlt.role'
|
||||
import { UserToDltRole } from './UserToDlt.role'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
|
||||
/**
|
||||
* @DCI-Context
|
||||
@ -20,12 +19,13 @@ export async function transactionToDlt(dltConnector: DltConnectorClient): Promis
|
||||
async function findNextPendingTransaction(): Promise<
|
||||
AbstractTransactionToDltRole<Transaction | User | TransactionLink>
|
||||
> {
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}/apis/dltConnector/interaction/transactionToDlt`)
|
||||
// collect each oldest not sended entity from db and choose oldest
|
||||
const results = await Promise.all([
|
||||
new TransactionToDltRole().initWithLast(),
|
||||
new UserToDltRole().initWithLast(),
|
||||
new TransactionLinkToDltRole().initWithLast(),
|
||||
new TransactionLinkDeleteToDltRole().initWithLast(),
|
||||
new TransactionToDltRole(logger).initWithLast(),
|
||||
new UserToDltRole(logger).initWithLast(),
|
||||
new TransactionLinkToDltRole(logger).initWithLast(),
|
||||
new TransactionLinkDeleteToDltRole(logger).initWithLast(),
|
||||
])
|
||||
|
||||
// sort array to get oldest at first place
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { CONFIG } from '@/config'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { TypeORMError } from '@dbTools/typeorm'
|
||||
import { TypeORMError } from 'typeorm'
|
||||
// eslint-disable-next-line import/named, n/no-extraneous-import
|
||||
import { FetchError } from 'node-fetch'
|
||||
|
||||
@ -14,6 +13,10 @@ import {
|
||||
} from '@/util/InterruptiveSleepManager'
|
||||
|
||||
import { transactionToDlt } from './interaction/transactionToDlt/transactionToDlt.context'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}/apis/dltConnector/sendTransactionsToDltConnector`)
|
||||
|
||||
let isLoopRunning = true
|
||||
|
||||
@ -25,11 +28,11 @@ export async function sendTransactionsToDltConnector(): Promise<void> {
|
||||
const dltConnector = DltConnectorClient.getInstance()
|
||||
|
||||
if (!dltConnector) {
|
||||
logger.info('Sending to DltConnector currently not configured...')
|
||||
logger.info('currently not configured...')
|
||||
isLoopRunning = false
|
||||
return
|
||||
}
|
||||
logger.info('Starting sendTransactionsToDltConnector task')
|
||||
logger.info('task started')
|
||||
|
||||
// define outside of loop for reuse and reducing gb collection
|
||||
// const queries = getFindNextPendingTransactionQueries()
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const DLT_CONNECTOR_RIGHTS = [RIGHTS.COMMUNITY_BY_IDENTIFIER, RIGHTS.HOME_COMMUNITY]
|
||||
export const DLT_CONNECTOR_RIGHTS = [RIGHTS.COMMUNITY_BY_IDENTIFIER, RIGHTS.HOME_COMMUNITY, RIGHTS.COMMUNITY_UPDATE]
|
||||
|
||||
@ -25,13 +25,11 @@
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="],
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.4", "", {}, "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.28.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.3", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ=="],
|
||||
"@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
|
||||
|
||||
@ -51,9 +49,9 @@
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="],
|
||||
"@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="],
|
||||
|
||||
"@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
|
||||
|
||||
@ -85,15 +83,15 @@
|
||||
|
||||
"@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.3", "", {}, "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA=="],
|
||||
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
|
||||
"@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="],
|
||||
|
||||
"@babel/traverse--for-generate-function-map": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
|
||||
"@babel/traverse--for-generate-function-map": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
|
||||
|
||||
"@biomejs/biome": ["@biomejs/biome@2.0.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.0", "@biomejs/cli-darwin-x64": "2.0.0", "@biomejs/cli-linux-arm64": "2.0.0", "@biomejs/cli-linux-arm64-musl": "2.0.0", "@biomejs/cli-linux-x64": "2.0.0", "@biomejs/cli-linux-x64-musl": "2.0.0", "@biomejs/cli-win32-arm64": "2.0.0", "@biomejs/cli-win32-x64": "2.0.0" }, "bin": { "biome": "bin/biome" } }, "sha512-BlUoXEOI/UQTDEj/pVfnkMo8SrZw3oOWBDrXYFT43V7HTkIUDkBRY53IC5Jx1QkZbaB+0ai1wJIfYwp9+qaJTQ=="],
|
||||
|
||||
@ -183,6 +181,8 @@
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
|
||||
@ -265,7 +265,7 @@
|
||||
|
||||
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
|
||||
|
||||
"@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="],
|
||||
"@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
||||
|
||||
@ -359,7 +359,7 @@
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001739", "", {}, "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001741", "", {}, "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
@ -425,7 +425,7 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.213", "", {}, "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.214", "", {}, "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q=="],
|
||||
|
||||
"elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="],
|
||||
|
||||
@ -535,7 +535,7 @@
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"gradido-blockchain-js": ["gradido-blockchain-js@github:gradido/gradido-blockchain-js#eccade8", { "dependencies": { "bindings": "^1.5.0", "nan": "^2.20.0", "node-addon-api": "^7.1.1", "node-gyp-build": "^4.8.1", "prebuildify": "git+https://github.com/einhornimmond/prebuildify#65d94455fab86b902c0d59bb9c06ac70470e56b2" } }, "gradido-gradido-blockchain-js-eccade8"],
|
||||
"gradido-blockchain-js": ["gradido-blockchain-js@github:gradido/gradido-blockchain-js#f1c4bbc", { "dependencies": { "bindings": "^1.5.0", "nan": "^2.20.0", "node-addon-api": "^7.1.1", "node-gyp-build": "^4.8.1", "prebuildify": "git+https://github.com/einhornimmond/prebuildify#65d94455fab86b902c0d59bb9c06ac70470e56b2" } }, "gradido-gradido-blockchain-js-f1c4bbc"],
|
||||
|
||||
"graphql": ["graphql@16.11.0", "", {}, "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw=="],
|
||||
|
||||
@ -729,7 +729,7 @@
|
||||
|
||||
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
|
||||
"node-abi": ["node-abi@3.77.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ=="],
|
||||
|
||||
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
@ -739,7 +739,7 @@
|
||||
|
||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||
"node-releases": ["node-releases@2.0.20", "", {}, "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
@ -785,7 +785,7 @@
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"pino": ["pino@9.9.1", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-40SszWPOPwGhUIJ3zj0PsbMNV1bfg8nw5Qp/tP2FE2p3EuycmhDeYimKOMBAu6rtxcSw2QpjJsuK5A6v+en8Yw=="],
|
||||
"pino": ["pino@9.9.4", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg=="],
|
||||
|
||||
"pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"pattern": "yyyy-MM-dd",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{url}] [%f : %l] - %m"
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%f : %l] - %m"
|
||||
},
|
||||
"compress": true,
|
||||
"keepFileExt" : true,
|
||||
|
||||
251
dlt-connector/src/client/GradidoNode/GradidoNodeClient.ts
Normal file
251
dlt-connector/src/client/GradidoNode/GradidoNodeClient.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import { ConfirmedTransaction } from 'gradido-blockchain-js'
|
||||
import JsonRpcClient from 'jsonrpc-ts-client'
|
||||
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { parse } from 'valibot'
|
||||
import { CONFIG } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import { AddressType } from '../../enum/AddressType'
|
||||
import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes'
|
||||
import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema'
|
||||
import { Hex32, Hex32Input, HieroId, hex32Schema } from '../../schemas/typeGuard.schema'
|
||||
import { isPortOpenRetry } from '../../utils/network'
|
||||
import {
|
||||
TransactionIdentifierInput,
|
||||
TransactionsRangeInput,
|
||||
transactionIdentifierSchema,
|
||||
transactionsRangeSchema,
|
||||
} from './input.schema'
|
||||
|
||||
export class GradidoNodeRequestError<T> extends Error {
|
||||
private response?: JsonRpcEitherResponse<T>
|
||||
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
|
||||
super(message)
|
||||
this.name = 'GradidoNodeRequestError'
|
||||
this.response = response
|
||||
}
|
||||
getResponse(): JsonRpcEitherResponse<T> | undefined {
|
||||
return this.response
|
||||
}
|
||||
}
|
||||
|
||||
type WithTimeUsed<T> = T & { timeUsed?: string }
|
||||
|
||||
export class GradidoNodeClient {
|
||||
private static instance: GradidoNodeClient
|
||||
client: JsonRpcClient
|
||||
logger: Logger
|
||||
|
||||
private constructor() {
|
||||
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNodeClient`)
|
||||
this.client = new JsonRpcClient({
|
||||
url: CONFIG.NODE_SERVER_URL,
|
||||
})
|
||||
}
|
||||
public static getInstance(): GradidoNodeClient {
|
||||
if (!GradidoNodeClient.instance) {
|
||||
GradidoNodeClient.instance = new GradidoNodeClient()
|
||||
}
|
||||
return GradidoNodeClient.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransaction
|
||||
* get a specific confirmed transaction from a specific community
|
||||
* @param transactionIdentifier
|
||||
* @returns the confirmed transaction or undefined if transaction is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
public async getTransaction(
|
||||
transactionIdentifier: TransactionIdentifierInput,
|
||||
): Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
...parse(transactionIdentifierSchema, transactionIdentifier),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await this.rpcCall<{ transaction: string }>('gettransaction', parameter)
|
||||
if (response.isSuccess()) {
|
||||
this.logger.debug('result: ', response.result.transaction)
|
||||
return parse(confirmedTransactionSchema, response.result.transaction)
|
||||
}
|
||||
if (response.isError()) {
|
||||
if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* getLastTransaction
|
||||
* get the last confirmed transaction from a specific community
|
||||
* @param hieroTopic the community hiero topic id
|
||||
* @returns the last confirmed transaction or undefined if blockchain for community is empty or not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
public async getLastTransaction(hieroTopic: HieroId): Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
format: 'base64',
|
||||
topic: hieroTopic,
|
||||
}
|
||||
const response = await this.rpcCall<{ transaction: string }>('getlasttransaction', parameter)
|
||||
if (response.isSuccess()) {
|
||||
return parse(confirmedTransactionSchema, response.result.transaction)
|
||||
}
|
||||
if (response.isError()) {
|
||||
if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransactions
|
||||
* get list of confirmed transactions from a specific community
|
||||
* @param input fromTransactionId is the id of the first transaction to return
|
||||
* @param input maxResultCount is the max number of transactions to return
|
||||
* @param input topic is the community hiero topic id
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
* @example
|
||||
* ```
|
||||
* const transactions = await getTransactions({
|
||||
* fromTransactionId: 1,
|
||||
* maxResultCount: 100,
|
||||
* topic: communityUuid,
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
public async getTransactions(input: TransactionsRangeInput): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = {
|
||||
...parse(transactionsRangeSchema, input),
|
||||
format: 'base64',
|
||||
}
|
||||
const result = await this.rpcCallResolved<{ transactions: string[] }>(
|
||||
'getTransactions',
|
||||
parameter,
|
||||
)
|
||||
return result.transactions.map((transactionBase64) =>
|
||||
parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransactionsForAccount
|
||||
* get list of confirmed transactions for a specific account
|
||||
* @param transactionRange the range of transactions to return
|
||||
* @param pubkey the public key of the account
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
public async getTransactionsForAccount(
|
||||
transactionRange: TransactionsRangeInput,
|
||||
pubkey: Hex32Input,
|
||||
): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = {
|
||||
...parse(transactionsRangeSchema, transactionRange),
|
||||
pubkey: parse(hex32Schema, pubkey),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await this.rpcCallResolved<{ transactions: string[] }>(
|
||||
'listtransactionsforaddress',
|
||||
parameter,
|
||||
)
|
||||
return response.transactions.map((transactionBase64) =>
|
||||
parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* getAddressType
|
||||
* get the address type of a specific user
|
||||
* can be used to check if user/account exists on blockchain
|
||||
* look also for gmw, auf and deferred transfer accounts
|
||||
* @param pubkey the public key of the user or account
|
||||
* @param hieroTopic the community hiero topic id
|
||||
* @returns the address type of the user/account, AddressType.NONE if not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
public async getAddressType(pubkey: Hex32Input, hieroTopic: HieroId): Promise<AddressType> {
|
||||
const parameter = {
|
||||
pubkey: parse(hex32Schema, pubkey),
|
||||
topic: hieroTopic,
|
||||
}
|
||||
const response = await this.rpcCallResolved<{ addressType: string }>(
|
||||
'getaddresstype',
|
||||
parameter,
|
||||
)
|
||||
return parse(addressTypeSchema, response.addressType)
|
||||
}
|
||||
|
||||
/**
|
||||
* findUserByNameHash
|
||||
* find a user by name hash
|
||||
* @param nameHash the name hash of the user
|
||||
* @param hieroTopic the community hiero topic id
|
||||
* @returns the public key of the user as hex32 string or undefined if user is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
public async findUserByNameHash(
|
||||
nameHash: Uuidv4Hash,
|
||||
hieroTopic: HieroId,
|
||||
): Promise<Hex32 | undefined> {
|
||||
const parameter = {
|
||||
nameHash: nameHash.getAsHexString(),
|
||||
topic: hieroTopic,
|
||||
}
|
||||
const response = await this.rpcCall<{ pubkey: string; timeUsed: string }>(
|
||||
'findUserByNameHash',
|
||||
parameter,
|
||||
)
|
||||
if (response.isSuccess()) {
|
||||
this.logger.info(`call findUserByNameHash, used ${response.result.timeUsed}`)
|
||||
return parse(hex32Schema, response.result.pubkey)
|
||||
}
|
||||
if (
|
||||
response.isError() &&
|
||||
response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND
|
||||
) {
|
||||
this.logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// ---------------- intern helper functions -----------------------------------
|
||||
|
||||
// return result on success or throw error
|
||||
protected resolveResponse<T, R>(
|
||||
response: JsonRpcEitherResponse<T>,
|
||||
onSuccess: (result: T) => R,
|
||||
): R {
|
||||
if (response.isSuccess()) {
|
||||
return onSuccess(response.result)
|
||||
} else if (response.isError()) {
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
throw new GradidoNodeRequestError('no success and no error')
|
||||
}
|
||||
|
||||
// template rpcCall, check first if port is open before executing json rpc 2.0 request
|
||||
protected async rpcCall<T>(method: string, parameter: any): Promise<JsonRpcEitherResponse<T>> {
|
||||
this.logger.debug('call %s with %s', method, parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
return this.client.exec<T>(method, parameter)
|
||||
}
|
||||
|
||||
// template rpcCall, check first if port is open before executing json rpc 2.0 request,
|
||||
// throw error on failure, return result on success
|
||||
protected async rpcCallResolved<T>(method: string, parameter: any): Promise<T> {
|
||||
const response = await this.rpcCall<WithTimeUsed<T>>(method, parameter)
|
||||
return this.resolveResponse(response, (result: WithTimeUsed<T>) => {
|
||||
if (result.timeUsed) {
|
||||
this.logger.info(`call %s, used ${result.timeUsed}`, method)
|
||||
}
|
||||
return result as T
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,182 +0,0 @@
|
||||
import { AddressType, ConfirmedTransaction } from 'gradido-blockchain-js'
|
||||
import { getLogger } from 'log4js'
|
||||
import * as v from 'valibot'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes'
|
||||
import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema'
|
||||
import { Hex32, Hex32Input, HieroId, hex32Schema } from '../../schemas/typeGuard.schema'
|
||||
import {
|
||||
TransactionIdentifierInput,
|
||||
TransactionsRangeInput,
|
||||
transactionIdentifierSchema,
|
||||
transactionsRangeSchema,
|
||||
} from './input.schema'
|
||||
import { GradidoNodeRequestError, rpcCall, rpcCallResolved } from './jsonrpc'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
/**
|
||||
* getTransactions
|
||||
* get list of confirmed transactions from a specific community
|
||||
* @param input fromTransactionId is the id of the first transaction to return
|
||||
* @param input maxResultCount is the max number of transactions to return
|
||||
* @param input topic is the community hiero topic id
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
* @example
|
||||
* ```
|
||||
* const transactions = await getTransactions({
|
||||
* fromTransactionId: 1,
|
||||
* maxResultCount: 100,
|
||||
* topic: communityUuid,
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async function getTransactions(input: TransactionsRangeInput): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = { ...v.parse(transactionsRangeSchema, input), format: 'base64' }
|
||||
const result = await rpcCallResolved<{ transactions: string[] }>('getTransactions', parameter)
|
||||
return result.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransaction
|
||||
* get a specific confirmed transaction from a specific community
|
||||
* @param transactionIdentifier
|
||||
* @returns the confirmed transaction or undefined if transaction is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function getTransaction(
|
||||
transactionIdentifier: TransactionIdentifierInput,
|
||||
): Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
...v.parse(transactionIdentifierSchema, transactionIdentifier),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await rpcCall<{ transaction: string }>('gettransaction', parameter)
|
||||
if (response.isSuccess()) {
|
||||
return v.parse(confirmedTransactionSchema, response.result.transaction)
|
||||
}
|
||||
if (response.isError()) {
|
||||
if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* getLastTransaction
|
||||
* get the last confirmed transaction from a specific community
|
||||
* @param iotaTopic the community topic
|
||||
* @returns the last confirmed transaction or undefined if blockchain for community is empty or not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
async function getLastTransaction(
|
||||
iotaTopic: Uuidv4Hash,
|
||||
): Promise<ConfirmedTransaction | undefined> {
|
||||
const response = await rpcCall<{ transaction: string }>('getlasttransaction', {
|
||||
format: 'base64',
|
||||
topic: iotaTopic.getAsHexString(),
|
||||
})
|
||||
if (response.isSuccess()) {
|
||||
return v.parse(confirmedTransactionSchema, response.result.transaction)
|
||||
}
|
||||
if (response.isError()) {
|
||||
if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) {
|
||||
return undefined
|
||||
}
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getAddressType
|
||||
* get the address type of a specific user
|
||||
* can be used to check if user/account exists on blockchain
|
||||
* look also for gmw, auf and deferred transfer accounts
|
||||
* @param pubkey the public key of the user or account
|
||||
* @param iotaTopic the community topic
|
||||
* @returns the address type of the user/account or undefined
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
async function getAddressType(pubkey: Hex32Input, hieroTopic: HieroId): Promise<AddressType> {
|
||||
const parameter = {
|
||||
pubkey: v.parse(hex32Schema, pubkey),
|
||||
communityId: hieroTopic,
|
||||
}
|
||||
const response = await rpcCallResolved<{ addressType: string }>('getaddresstype', parameter)
|
||||
return v.parse(addressTypeSchema, response.addressType)
|
||||
}
|
||||
|
||||
/**
|
||||
* findUserByNameHash
|
||||
* find a user by name hash
|
||||
* @param nameHash the name hash of the user
|
||||
* @param iotaTopic the community topic
|
||||
* @returns the public key of the user as hex32 string or undefined if user is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function findUserByNameHash(
|
||||
nameHash: Uuidv4Hash,
|
||||
hieroTopic: HieroId,
|
||||
): Promise<Hex32 | undefined> {
|
||||
const parameter = {
|
||||
nameHash: nameHash.getAsHexString(),
|
||||
communityId: hieroTopic,
|
||||
}
|
||||
const response = await rpcCall<{ pubkey: string; timeUsed: string }>(
|
||||
'findUserByNameHash',
|
||||
parameter,
|
||||
)
|
||||
if (response.isSuccess()) {
|
||||
logger.info(`call findUserByNameHash, used ${response.result.timeUsed}`)
|
||||
return v.parse(hex32Schema, response.result.pubkey)
|
||||
}
|
||||
if (
|
||||
response.isError() &&
|
||||
response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND
|
||||
) {
|
||||
logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransactionsForAccount
|
||||
* get list of confirmed transactions for a specific account
|
||||
* @param transactionRange the range of transactions to return
|
||||
* @param pubkey the public key of the account
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function getTransactionsForAccount(
|
||||
transactionRange: TransactionsRangeInput,
|
||||
pubkey: Hex32Input,
|
||||
): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = {
|
||||
...v.parse(transactionsRangeSchema, transactionRange),
|
||||
pubkey: v.parse(hex32Schema, pubkey),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await rpcCallResolved<{ transactions: string[] }>(
|
||||
'listtransactionsforaddress',
|
||||
parameter,
|
||||
)
|
||||
return response.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
getTransaction,
|
||||
getLastTransaction,
|
||||
getTransactions,
|
||||
getAddressType,
|
||||
getTransactionsForAccount,
|
||||
findUserByNameHash,
|
||||
}
|
||||
60
dlt-connector/src/client/GradidoNode/input.schema.test.ts
Normal file
60
dlt-connector/src/client/GradidoNode/input.schema.test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { beforeAll, describe, expect, it } from 'bun:test'
|
||||
import { parse } from 'valibot'
|
||||
import {
|
||||
HieroId,
|
||||
HieroTransactionId,
|
||||
hieroIdSchema,
|
||||
hieroTransactionIdSchema,
|
||||
} from '../../schemas/typeGuard.schema'
|
||||
import { transactionIdentifierSchema } from './input.schema'
|
||||
|
||||
let topic: HieroId
|
||||
const topicString = '0.0.261'
|
||||
let hieroTransactionId: HieroTransactionId
|
||||
beforeAll(() => {
|
||||
topic = parse(hieroIdSchema, topicString)
|
||||
hieroTransactionId = parse(hieroTransactionIdSchema, '0.0.261-1755348116-1281621')
|
||||
})
|
||||
|
||||
describe('transactionIdentifierSchema ', () => {
|
||||
it('valid, transaction identified by transactionNr and topic', () => {
|
||||
expect(
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionId: 1,
|
||||
topic: topicString,
|
||||
}),
|
||||
).toEqual({
|
||||
transactionId: 1,
|
||||
hieroTransactionId: undefined,
|
||||
topic,
|
||||
})
|
||||
})
|
||||
it('valid, transaction identified by hieroTransactionId and topic', () => {
|
||||
expect(
|
||||
parse(transactionIdentifierSchema, {
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
topic: topicString,
|
||||
}),
|
||||
).toEqual({
|
||||
hieroTransactionId,
|
||||
topic,
|
||||
})
|
||||
})
|
||||
it('invalid, missing topic', () => {
|
||||
expect(() =>
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionId: 1,
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
}),
|
||||
).toThrowError(new Error('Invalid key: Expected "topic" but received undefined'))
|
||||
})
|
||||
it('invalid, transactionNr and iotaMessageId set', () => {
|
||||
expect(() =>
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionId: 1,
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
topic,
|
||||
}),
|
||||
).toThrowError(new Error('expect transactionNr or hieroTransactionId not both'))
|
||||
})
|
||||
})
|
||||
@ -14,7 +14,7 @@ export type TransactionsRangeInput = v.InferInput<typeof transactionsRangeSchema
|
||||
// allow TransactionIdentifier to only contain either transactionNr or iotaMessageId
|
||||
export const transactionIdentifierSchema = v.pipe(
|
||||
v.object({
|
||||
transactionNr: v.nullish(
|
||||
transactionId: v.nullish(
|
||||
v.pipe(v.number('expect number type'), v.minValue(1, 'expect number >= 1')),
|
||||
undefined,
|
||||
),
|
||||
@ -23,7 +23,7 @@ export const transactionIdentifierSchema = v.pipe(
|
||||
}),
|
||||
v.custom((value: any) => {
|
||||
const setFieldsCount =
|
||||
Number(value.transactionNr !== undefined) + Number(value.hieroTransactionId !== undefined)
|
||||
Number(value.transactionId !== undefined) + Number(value.hieroTransactionId !== undefined)
|
||||
if (setFieldsCount !== 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import JsonRpcClient from 'jsonrpc-ts-client'
|
||||
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
|
||||
import { getLogger } from 'log4js'
|
||||
import { CONFIG } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { isPortOpenRetry } from '../../utils/network'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
export const client = new JsonRpcClient({
|
||||
url: CONFIG.NODE_SERVER_URL,
|
||||
})
|
||||
|
||||
export class GradidoNodeRequestError<T> extends Error {
|
||||
private response?: JsonRpcEitherResponse<T>
|
||||
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
|
||||
super(message)
|
||||
this.name = 'GradidoNodeRequestError'
|
||||
this.response = response
|
||||
}
|
||||
getResponse(): JsonRpcEitherResponse<T> | undefined {
|
||||
return this.response
|
||||
}
|
||||
}
|
||||
|
||||
// return result on success or throw error
|
||||
export function resolveResponse<T, R>(
|
||||
response: JsonRpcEitherResponse<T>,
|
||||
onSuccess: (result: T) => R,
|
||||
): R {
|
||||
if (response.isSuccess()) {
|
||||
return onSuccess(response.result)
|
||||
} else if (response.isError()) {
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
throw new GradidoNodeRequestError('no success and no error')
|
||||
}
|
||||
|
||||
type WithTimeUsed<T> = T & { timeUsed?: string }
|
||||
|
||||
// template rpcCall, check first if port is open before executing json rpc 2.0 request
|
||||
export async function rpcCall<T>(
|
||||
method: string,
|
||||
parameter: any,
|
||||
): Promise<JsonRpcEitherResponse<T>> {
|
||||
logger.debug('call %s with %s', method, parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
return client.exec<T>(method, parameter)
|
||||
}
|
||||
|
||||
// template rpcCall, check first if port is open before executing json rpc 2.0 request, throw error on failure, return result on success
|
||||
export async function rpcCallResolved<T>(method: string, parameter: any): Promise<T> {
|
||||
const response = await rpcCall<WithTimeUsed<T>>(method, parameter)
|
||||
return resolveResponse(response, (result: WithTimeUsed<T>) => {
|
||||
if (result.timeUsed) {
|
||||
logger.info(`call %s, used ${result.timeUsed}`, method)
|
||||
}
|
||||
return result as T
|
||||
})
|
||||
}
|
||||
@ -1,17 +1,16 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { SignJWT } from 'jose'
|
||||
|
||||
import { CONFIG } from '../../config'
|
||||
import {
|
||||
communitySchema,
|
||||
type Community,
|
||||
homeCommunityGraphqlQuery,
|
||||
setHomeCommunityTopicId
|
||||
} from './community.schema'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import * as v from 'valibot'
|
||||
import { CONFIG } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { HieroId, Uuidv4 } from '../../schemas/typeGuard.schema'
|
||||
import {
|
||||
type Community,
|
||||
communitySchema,
|
||||
homeCommunityGraphqlQuery,
|
||||
setHomeCommunityTopicId,
|
||||
} from './community.schema'
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
@ -22,7 +21,7 @@ import { HieroId, Uuidv4 } from '../../schemas/typeGuard.schema'
|
||||
export class BackendClient {
|
||||
private static instance: BackendClient
|
||||
client: GraphQLClient
|
||||
logger: Logger
|
||||
logger: Logger
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
@ -49,17 +48,19 @@ export class BackendClient {
|
||||
* This implementation let you subclass the Singleton class while keeping
|
||||
* just one instance of each subclass around.
|
||||
*/
|
||||
public static getInstance(): BackendClient | undefined {
|
||||
public static getInstance(): BackendClient {
|
||||
if (!BackendClient.instance) {
|
||||
BackendClient.instance = new BackendClient()
|
||||
}
|
||||
}
|
||||
return BackendClient.instance
|
||||
}
|
||||
|
||||
public async getHomeCommunityDraft(): Promise<Community> {
|
||||
this.logger.info('check home community on backend')
|
||||
const { data, errors } = await this.client.rawRequest<{ homeCommunity: Community }>(
|
||||
homeCommunityGraphqlQuery, {}, await this.getRequestHeader(),
|
||||
homeCommunityGraphqlQuery,
|
||||
{},
|
||||
await this.getRequestHeader(),
|
||||
)
|
||||
if (errors) {
|
||||
throw errors[0]
|
||||
@ -70,11 +71,13 @@ export class BackendClient {
|
||||
public async setHomeCommunityTopicId(uuid: Uuidv4, hieroTopicId: HieroId): Promise<Community> {
|
||||
this.logger.info('update home community on backend')
|
||||
const { data, errors } = await this.client.rawRequest<{ updateHomeCommunity: Community }>(
|
||||
setHomeCommunityTopicId, { uuid, hieroTopicId }, await this.getRequestHeader(),
|
||||
setHomeCommunityTopicId,
|
||||
{ uuid, hieroTopicId },
|
||||
await this.getRequestHeader(),
|
||||
)
|
||||
if (errors) {
|
||||
throw errors[0]
|
||||
}
|
||||
}
|
||||
return v.parse(communitySchema, data.updateHomeCommunity)
|
||||
}
|
||||
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
// only for IDE, bun don't need this to work
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import * as v from 'valibot'
|
||||
import { uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import { communitySchema } from './community.schema'
|
||||
import { hieroIdSchema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
describe('community.schema', () => {
|
||||
it('community', () => {
|
||||
expect(
|
||||
v.parse(communitySchema, {
|
||||
uuid: '4f28e081-5c39-4dde-b6a4-3bde71de8d65',
|
||||
topicId: '0.0.4',
|
||||
hieroTopicId: '0.0.4',
|
||||
foreign: false,
|
||||
createdAt: '2021-01-01',
|
||||
creationDate: '2021-01-01',
|
||||
}),
|
||||
).toEqual({
|
||||
topicId: v.parse(hieroIdSchema, '0.0.4'),
|
||||
hieroTopicId: v.parse(hieroIdSchema, '0.0.4'),
|
||||
uuid: v.parse(uuidv4Schema, '4f28e081-5c39-4dde-b6a4-3bde71de8d65'),
|
||||
foreign: false,
|
||||
createdAt: new Date('2021-01-01'),
|
||||
creationDate: new Date('2021-01-01'),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,17 +4,13 @@ import { dateSchema } from '../../schemas/typeConverter.schema'
|
||||
import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
/**
|
||||
* Schema Definitions for rpc call parameter, when dlt-connector is called from backend
|
||||
*/
|
||||
|
||||
/**
|
||||
* Schema for community, for creating new CommunityRoot Transaction on gradido blockchain
|
||||
* Schema Definitions for graphql response
|
||||
*/
|
||||
export const communitySchema = v.object({
|
||||
uuid: uuidv4Schema,
|
||||
topicId: v.nullish(hieroIdSchema),
|
||||
hieroTopicId: v.nullish(hieroIdSchema),
|
||||
foreign: v.boolean('expect boolean type'),
|
||||
createdAt: dateSchema,
|
||||
creationDate: dateSchema,
|
||||
})
|
||||
|
||||
export type CommunityInput = v.InferInput<typeof communitySchema>
|
||||
@ -25,7 +21,7 @@ export const homeCommunityGraphqlQuery = gql`
|
||||
query {
|
||||
homeCommunity {
|
||||
uuid
|
||||
topicId
|
||||
hieroTopicId
|
||||
foreign
|
||||
creationDate
|
||||
}
|
||||
@ -33,10 +29,10 @@ export const homeCommunityGraphqlQuery = gql`
|
||||
`
|
||||
|
||||
export const setHomeCommunityTopicId = gql`
|
||||
mutation ($uuid: string, $hieroTopicId: string){
|
||||
mutation ($uuid: String!, $hieroTopicId: String){
|
||||
updateHomeCommunity(uuid: $uuid, hieroTopicId: $hieroTopicId) {
|
||||
uuid
|
||||
topicId
|
||||
hieroTopicId
|
||||
foreign
|
||||
creationDate
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
PrivateKey,
|
||||
Timestamp,
|
||||
TopicCreateTransaction,
|
||||
TopicId,
|
||||
TopicId,
|
||||
TopicInfoQuery,
|
||||
TopicMessageSubmitTransaction,
|
||||
TopicUpdateTransaction,
|
||||
@ -14,13 +14,13 @@ import {
|
||||
TransactionResponse,
|
||||
Wallet,
|
||||
} from '@hashgraph/sdk'
|
||||
import { parse } from 'valibot'
|
||||
import { GradidoTransaction, HieroTopicId } from 'gradido-blockchain-js'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { parse } from 'valibot'
|
||||
import { CONFIG } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema'
|
||||
import { topicInfoSchema, type TopicInfoOutput } from './output.schema'
|
||||
import { type TopicInfoOutput, topicInfoSchema } from './output.schema'
|
||||
|
||||
// https://docs.hedera.com/hedera/sdks-and-apis/hedera-api/consensus/consensusupdatetopic
|
||||
export const MIN_AUTORENEW_PERIOD = 6999999 //seconds
|
||||
@ -29,7 +29,7 @@ export const MAX_AUTORENEW_PERIOD = 8000001 // seconds
|
||||
export class HieroClient {
|
||||
private static instance: HieroClient
|
||||
wallet: Wallet
|
||||
client: Client
|
||||
client: Client
|
||||
logger: Logger
|
||||
|
||||
private constructor() {
|
||||
@ -43,12 +43,10 @@ export class HieroClient {
|
||||
operatorKey = PrivateKey.fromStringECDSA(CONFIG.HIERO_OPERATOR_KEY)
|
||||
}
|
||||
this.wallet = new Wallet(CONFIG.HIERO_OPERATOR_ID, operatorKey, provider)
|
||||
this.client.setOperator(CONFIG.HIERO_OPERATOR_ID, operatorKey)
|
||||
}
|
||||
|
||||
public static getInstance(): HieroClient {
|
||||
if (!CONFIG.HIERO_ACTIVE) {
|
||||
throw new Error('hiero is disabled via config...')
|
||||
}
|
||||
if (!HieroClient.instance) {
|
||||
HieroClient.instance = new HieroClient()
|
||||
}
|
||||
@ -87,14 +85,14 @@ export class HieroClient {
|
||||
|
||||
public async getTopicInfo(topicId: HieroId): Promise<TopicInfoOutput> {
|
||||
const info = await new TopicInfoQuery()
|
||||
.setTopicId(TopicId.fromString(topicId))
|
||||
.execute(this.client)
|
||||
.setTopicId(TopicId.fromString(topicId))
|
||||
.execute(this.client)
|
||||
this.logger.debug(JSON.stringify(info, null, 2))
|
||||
return parse(topicInfoSchema, {
|
||||
topicId: topicId.toString(),
|
||||
sequenceNumber: info.sequenceNumber.toNumber(),
|
||||
expirationTime: info.expirationTime?.toString(),
|
||||
autoRenewPeriod: info.autoRenewPeriod?.seconds,
|
||||
expirationTime: info.expirationTime?.toDate(),
|
||||
autoRenewPeriod: info.autoRenewPeriod?.seconds.toNumber(),
|
||||
autoRenewAccountId: info.autoRenewAccountId?.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import * as v from 'valibot'
|
||||
import { hieroIdSchema } from '../../schemas/typeGuard.schema'
|
||||
import { dateSchema } from '../../schemas/typeConverter.schema'
|
||||
import { hieroIdSchema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
// schema definitions for exporting data from hiero request as json back to caller
|
||||
// schema definitions for exporting data from hiero request as json back to caller
|
||||
/*export const dateStringSchema = v.pipe(
|
||||
v.enum([v.string(), v.date()],
|
||||
v.transform(in: string | Date)
|
||||
@ -11,12 +11,11 @@ import { dateSchema } from '../../schemas/typeConverter.schema'
|
||||
export const positiveNumberSchema = v.pipe(v.number(), v.minValue(0))
|
||||
|
||||
export const topicInfoSchema = v.object({
|
||||
topicId: hieroIdSchema,
|
||||
sequenceNumber: positiveNumberSchema,
|
||||
expirationTime: dateSchema,
|
||||
autoRenewPeriod: v.optional(positiveNumberSchema, 0),
|
||||
autoRenewAccountId: v.optional(hieroIdSchema, '0.0.0'),
|
||||
topicId: hieroIdSchema,
|
||||
sequenceNumber: positiveNumberSchema,
|
||||
expirationTime: dateSchema,
|
||||
autoRenewPeriod: v.optional(positiveNumberSchema, 0),
|
||||
autoRenewAccountId: v.optional(hieroIdSchema, '0.0.0'),
|
||||
})
|
||||
|
||||
export type TopicInfoOutput = v.InferOutput<typeof topicInfoSchema>
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export const LOG4JS_BASE_CATEGORY = 'dlt'
|
||||
// 7 days
|
||||
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7
|
||||
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7
|
||||
|
||||
@ -1,60 +1,23 @@
|
||||
/* eslint-disable n/no-process-env */
|
||||
import dotenv from 'dotenv'
|
||||
import { parse, InferOutput, ValiError } from 'valibot'
|
||||
import { configSchema } from './schema'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const logging = {
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
|
||||
type ConfigOutput = InferOutput<typeof configSchema>
|
||||
|
||||
let config: ConfigOutput
|
||||
console.info('Config loading...')
|
||||
try {
|
||||
config = parse(configSchema, process.env)
|
||||
} catch (error: Error | unknown) {
|
||||
if (error instanceof ValiError) {
|
||||
console.error(`${error.issues[0].path[0].key}: ${error.message} received: ${error.issues[0].received}`)
|
||||
} else {
|
||||
console.error(error)
|
||||
}
|
||||
// console.error('Config error:', JSON.stringify(error, null, 2))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const server = {
|
||||
PRODUCTION: process.env.NODE_ENV === 'production',
|
||||
DLT_CONNECTOR_PORT: process.env.DLT_CONNECTOR_PORT ?? 6010,
|
||||
}
|
||||
|
||||
const secrets = {
|
||||
JWT_SECRET: process.env.JWT_SECRET ?? 'secret123',
|
||||
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',
|
||||
}
|
||||
|
||||
const iota = {
|
||||
IOTA_HOME_COMMUNITY_SEED: process.env.IOTA_HOME_COMMUNITY_SEED ?? null,
|
||||
}
|
||||
|
||||
const hiero = {
|
||||
HIERO_ACTIVE: process.env.HIERO_ACTIVE === 'true' || false,
|
||||
HIERO_HEDERA_NETWORK: process.env.HIERO_HEDERA_NETWORK ?? 'testnet',
|
||||
HIERO_OPERATOR_ID: process.env.HIERO_OPERATOR_ID ?? '0.0.2',
|
||||
HIERO_OPERATOR_KEY:
|
||||
process.env.HIERO_OPERATOR_KEY ??
|
||||
'302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137',
|
||||
}
|
||||
|
||||
const apis = {
|
||||
CONNECT_TIMEOUT_MS: process.env.CONNECT_TIMEOUT_MS
|
||||
? Number.parseInt(process.env.CONNECT_TIMEOUT_MS)
|
||||
: 1000,
|
||||
CONNECT_RETRY_COUNT: process.env.CONNECT_RETRY_COUNT
|
||||
? Number.parseInt(process.env.CONNECT_RETRY_COUNT)
|
||||
: 15,
|
||||
CONNECT_RETRY_DELAY_MS: process.env.CONNECT_RETRY_DELAY_MS
|
||||
? Number.parseInt(process.env.CONNECT_RETRY_DELAY_MS)
|
||||
: 500,
|
||||
IOTA_API_URL: process.env.IOTA_API_URL ?? 'https://chrysalis-nodes.iota.org',
|
||||
NODE_SERVER_URL: process.env.NODE_SERVER_URL ?? 'http://127.0.0.1:8340',
|
||||
BACKEND_SERVER_URL: process.env.BACKEND_SERVER_URL ?? 'http://127.0.0.1:4000',
|
||||
}
|
||||
|
||||
export const CONFIG = {
|
||||
...logging,
|
||||
...server,
|
||||
...secrets,
|
||||
...iota,
|
||||
...hiero,
|
||||
...apis,
|
||||
}
|
||||
export const CONFIG = config
|
||||
|
||||
@ -1,24 +1,77 @@
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
|
||||
export const HIERO_ACTIVE = v.nullish(
|
||||
v.boolean('Flag to indicate if the Hiero (Hedera Hashgraph Ledger) service is used.'),
|
||||
false,
|
||||
)
|
||||
const hexSchema = v.pipe(v.string('expect string type'), v.hexadecimal('expect hexadecimal string'))
|
||||
|
||||
export const HIERO_HEDERA_NETWORK = v.nullish(
|
||||
v.union([v.literal('mainnet'), v.literal('testnet'), v.literal('previewnet')]),
|
||||
'testnet',
|
||||
)
|
||||
const hex16Schema = v.pipe(hexSchema, v.length(32, 'expect string length = 32'))
|
||||
|
||||
export const HIERO_OPERATOR_ID = v.nullish(
|
||||
v.pipe(v.string('The operator ID for Hiero integration'), v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/)),
|
||||
'0.0.2',
|
||||
)
|
||||
|
||||
export const HIERO_OPERATOR_KEY = v.nullish(
|
||||
v.pipe(
|
||||
v.string('The operator key for Hiero integration, default is for local default node'),
|
||||
v.regex(/^[0-9a-fA-F]{64,96}$/),
|
||||
export const configSchema = v.object({
|
||||
LOG4JS_CONFIG: v.optional(
|
||||
v.string('The path to the log4js configuration file'),
|
||||
'./log4js-config.json',
|
||||
),
|
||||
'302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137',
|
||||
)
|
||||
LOG_LEVEL: v.optional(v.string('The log level'), 'info'),
|
||||
DLT_CONNECTOR_PORT: v.optional(
|
||||
v.pipe(
|
||||
v.string('A valid port on which the DLT connector is running'),
|
||||
v.transform<string, number>((input: string) => Number(input)),
|
||||
v.minValue(1),
|
||||
v.maxValue(65535),
|
||||
),
|
||||
'6010',
|
||||
),
|
||||
JWT_SECRET: v.pipe(
|
||||
v.string('The JWT secret for connecting to the backend'),
|
||||
v.custom((input: unknown): boolean => {
|
||||
if (process.env.NODE_ENV === 'production' && input === 'secret123') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, "Shouldn't use default value in production"),
|
||||
),
|
||||
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET: hexSchema,
|
||||
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: hex16Schema,
|
||||
HOME_COMMUNITY_SEED: v.pipe(
|
||||
hexSchema,
|
||||
v.length(64, 'expect seed length minimum 64 characters (32 Bytes)'),
|
||||
v.transform<string, MemoryBlock>((input: string) => MemoryBlock.fromHex(input)),
|
||||
),
|
||||
HIERO_HEDERA_NETWORK: v.optional(
|
||||
v.union([v.literal('mainnet'), v.literal('testnet'), v.literal('previewnet')]),
|
||||
'testnet',
|
||||
),
|
||||
HIERO_OPERATOR_ID: v.pipe(
|
||||
v.string('The operator ID (Account id) for Hiero integration'),
|
||||
v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/),
|
||||
),
|
||||
HIERO_OPERATOR_KEY: v.pipe(
|
||||
v.string('The operator key (Private key) for Hiero integration'),
|
||||
v.hexadecimal(),
|
||||
v.minLength(64),
|
||||
v.maxLength(96),
|
||||
),
|
||||
CONNECT_TIMEOUT_MS: v.optional(
|
||||
v.pipe(v.number('The connection timeout in milliseconds'), v.minValue(200), v.maxValue(120000)),
|
||||
1000,
|
||||
),
|
||||
CONNECT_RETRY_COUNT: v.optional(
|
||||
v.pipe(v.number('The connection retry count'), v.minValue(1), v.maxValue(50)),
|
||||
15,
|
||||
),
|
||||
CONNECT_RETRY_DELAY_MS: v.optional(
|
||||
v.pipe(
|
||||
v.number('The connection retry delay in milliseconds'),
|
||||
v.minValue(100),
|
||||
v.maxValue(10000),
|
||||
),
|
||||
500,
|
||||
),
|
||||
NODE_SERVER_URL: v.optional(
|
||||
v.string('The URL of the gradido node server'),
|
||||
'http://localhost:6010',
|
||||
),
|
||||
BACKEND_SERVER_URL: v.optional(
|
||||
v.string('The URL of the gradido backend server'),
|
||||
'http://localhost:6010',
|
||||
),
|
||||
})
|
||||
|
||||
@ -17,7 +17,7 @@ export class KeyPairIdentifierLogic {
|
||||
isUserKeyPair(): boolean {
|
||||
return (
|
||||
this.identifier.seed === undefined &&
|
||||
this.identifier.account != undefined &&
|
||||
this.identifier.account != null &&
|
||||
this.identifier.account.accountNr === 0
|
||||
)
|
||||
}
|
||||
@ -25,7 +25,7 @@ export class KeyPairIdentifierLogic {
|
||||
isAccountKeyPair(): boolean {
|
||||
return (
|
||||
this.identifier.seed === undefined &&
|
||||
this.identifier.account != undefined &&
|
||||
this.identifier.account != null &&
|
||||
this.identifier.account.accountNr > 0
|
||||
)
|
||||
}
|
||||
|
||||
@ -35,6 +35,20 @@ export class GradidoNodeInvalidTransactionError extends GradidoNodeError {
|
||||
}
|
||||
}
|
||||
|
||||
export class GradidoBlockchainError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = 'GradidoBlockchainError'
|
||||
}
|
||||
}
|
||||
|
||||
export class GradidoBlockchainCryptoError extends GradidoBlockchainError {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = 'GradidoBlockchainCryptoError'
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
|
||||
@ -1,83 +1,111 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { Elysia } from 'elysia'
|
||||
import { loadCryptoKeys, MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { configure, getLogger } from 'log4js'
|
||||
import * as v from 'valibot'
|
||||
import { configure, getLogger, Logger } from 'log4js'
|
||||
import { parse } from 'valibot'
|
||||
import { BackendClient } from './client/backend/BackendClient'
|
||||
import { GradidoNodeClient } from './client/GradidoNode/GradidoNodeClient'
|
||||
import { HieroClient } from './client/hiero/HieroClient'
|
||||
import { getTransaction } from './client/GradidoNode/api'
|
||||
import { CONFIG } from './config'
|
||||
import { SendToIotaContext } from './interactions/sendToIota/SendToIota.context'
|
||||
import { KeyPairCacheManager } from './KeyPairCacheManager'
|
||||
import { keyGenerationSeedSchema } from './schemas/base.schema'
|
||||
import { isPortOpenRetry } from './utils/network'
|
||||
import { appRoutes } from './server'
|
||||
import { MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE } from './config/const'
|
||||
import { SendToHieroContext } from './interactions/sendToHiero/SendToHiero.context'
|
||||
import { KeyPairCacheManager } from './KeyPairCacheManager'
|
||||
import { Community, communitySchema } from './schemas/transaction.schema'
|
||||
import { appRoutes } from './server'
|
||||
import { isPortOpenRetry } from './utils/network'
|
||||
|
||||
type Clients = {
|
||||
backend: BackendClient
|
||||
hiero: HieroClient
|
||||
gradidoNode: GradidoNodeClient
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// load everything from .env
|
||||
const logger = loadConfig()
|
||||
const clients = createClients()
|
||||
const { hiero, gradidoNode } = clients
|
||||
|
||||
// show hiero account balance, double also as check if valid hiero account was given in config
|
||||
const balance = await hiero.getBalance()
|
||||
logger.info(`Hiero Account Balance: ${balance.hbars.toString()}`)
|
||||
|
||||
// get home community, create topic if not exist, or check topic expiration and update it if needed
|
||||
const homeCommunity = await homeCommunitySetup(clients, logger)
|
||||
|
||||
// ask gradido node if community blockchain was created
|
||||
try {
|
||||
if (
|
||||
!(await gradidoNode.getTransaction({ transactionId: 1, topic: homeCommunity.hieroTopicId }))
|
||||
) {
|
||||
// if not exist, create community root transaction
|
||||
await SendToHieroContext(homeCommunity)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`error requesting gradido node: ${e}`)
|
||||
}
|
||||
// listen for rpc request from backend (graphql replaced with elysiaJS)
|
||||
new Elysia().use(appRoutes).listen(CONFIG.DLT_CONNECTOR_PORT, () => {
|
||||
logger.info(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`)
|
||||
})
|
||||
}
|
||||
|
||||
function loadConfig(): Logger {
|
||||
// configure log4js
|
||||
// TODO: replace late by loader from config-schema
|
||||
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
|
||||
configure(options)
|
||||
const logger = getLogger('dlt')
|
||||
// check if valid seed for root key pair generation is present
|
||||
if (!v.safeParse(keyGenerationSeedSchema, CONFIG.IOTA_HOME_COMMUNITY_SEED).success) {
|
||||
logger.error('IOTA_HOME_COMMUNITY_SEED must be a valid hex string, at least 64 characters long')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// load crypto keys for gradido blockchain lib
|
||||
loadCryptoKeys(
|
||||
MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET),
|
||||
MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY),
|
||||
)
|
||||
return logger
|
||||
}
|
||||
|
||||
// ask backend for home community if we haven't one
|
||||
const backend = BackendClient.getInstance()
|
||||
if (!backend) {
|
||||
throw new Error('cannot create backend client')
|
||||
}
|
||||
const hieroClient = HieroClient.getInstance()
|
||||
if (!hieroClient) {
|
||||
throw new Error('cannot create hiero client')
|
||||
// needed to be called after loading config
|
||||
function createClients(): Clients {
|
||||
return {
|
||||
backend: BackendClient.getInstance(),
|
||||
hiero: HieroClient.getInstance(),
|
||||
gradidoNode: GradidoNodeClient.getInstance(),
|
||||
}
|
||||
}
|
||||
|
||||
async function homeCommunitySetup({ backend, hiero }: Clients, logger: Logger): Promise<Community> {
|
||||
// wait for backend server
|
||||
await isPortOpenRetry(CONFIG.BACKEND_SERVER_URL)
|
||||
// ask backend for home community
|
||||
let homeCommunity = await backend.getHomeCommunityDraft()
|
||||
// on missing topicId, create one
|
||||
if (!homeCommunity.topicId) {
|
||||
const topicId = await hieroClient.createTopic()
|
||||
if (!homeCommunity.hieroTopicId) {
|
||||
const topicId = await hiero.createTopic()
|
||||
// update topic on backend server
|
||||
homeCommunity = await backend.setHomeCommunityTopicId(homeCommunity.uuid, topicId)
|
||||
} else {
|
||||
// if topic exist, check if we need to update it
|
||||
let topicInfo = await hieroClient.getTopicInfo(homeCommunity.topicId)
|
||||
if (topicInfo.expirationTime.getTime() - new Date().getTime() < MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE) {
|
||||
await hieroClient.updateTopic(homeCommunity.topicId)
|
||||
topicInfo = await hieroClient.getTopicInfo(homeCommunity.topicId)
|
||||
logger.info('updated topic info, new expiration time: %s', topicInfo.expirationTime.toLocaleDateString())
|
||||
let topicInfo = await hiero.getTopicInfo(homeCommunity.hieroTopicId)
|
||||
if (
|
||||
topicInfo.expirationTime.getTime() - new Date().getTime() <
|
||||
MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE
|
||||
) {
|
||||
await hiero.updateTopic(homeCommunity.hieroTopicId)
|
||||
topicInfo = await hiero.getTopicInfo(homeCommunity.hieroTopicId)
|
||||
logger.info(
|
||||
`updated topic info, new expiration time: ${topicInfo.expirationTime.toLocaleDateString()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!homeCommunity.topicId) {
|
||||
if (!homeCommunity.hieroTopicId) {
|
||||
throw new Error('still no topic id, after creating topic and update community in backend.')
|
||||
}
|
||||
KeyPairCacheManager.getInstance().setHomeCommunityTopicId(homeCommunity.topicId)
|
||||
logger.info('home community topic: %s', homeCommunity.topicId)
|
||||
logger.info('gradido node server: %s', CONFIG.NODE_SERVER_URL)
|
||||
// ask gradido node if community blockchain was created
|
||||
try {
|
||||
if (!(await getTransaction({ transactionNr: 1, topic: homeCommunity.topicId }))) {
|
||||
// if not exist, create community root transaction
|
||||
await SendToIotaContext(homeCommunity)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('error requesting gradido node: ', e)
|
||||
}
|
||||
// listen for rpc request from backend (graphql replaced with trpc and elysia)
|
||||
new Elysia()
|
||||
.use(appRoutes)
|
||||
.listen(CONFIG.DLT_CONNECTOR_PORT, () => {
|
||||
logger.info(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`)
|
||||
})
|
||||
KeyPairCacheManager.getInstance().setHomeCommunityTopicId(homeCommunity.hieroTopicId)
|
||||
logger.info(`home community topic: ${homeCommunity.hieroTopicId}`)
|
||||
logger.info(`gradido node server: ${CONFIG.NODE_SERVER_URL}`)
|
||||
logger.info(`gradido backend server: ${CONFIG.BACKEND_SERVER_URL}`)
|
||||
return parse(communitySchema, homeCommunity)
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { GradidoBlockchainCryptoError } from '../../errors'
|
||||
import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
|
||||
export class AccountKeyPairRole extends AbstractKeyPairRole {
|
||||
@ -11,6 +11,12 @@ export class AccountKeyPairRole extends AbstractKeyPairRole {
|
||||
}
|
||||
|
||||
public generateKeyPair(): KeyPairEd25519 {
|
||||
return this.userKeyPair.deriveChild(this.accountNr)
|
||||
const keyPair = this.userKeyPair.deriveChild(this.accountNr)
|
||||
if (!keyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${this.userKeyPair.hasPrivateKey()}, accountNr: ${this.accountNr}`,
|
||||
)
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { getTransaction } from '../../client/GradidoNode/api'
|
||||
import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient'
|
||||
import {
|
||||
GradidoNodeInvalidTransactionError,
|
||||
GradidoNodeMissingTransactionError,
|
||||
} from '../../errors'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role'
|
||||
|
||||
export class ForeignCommunityKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
public constructor(communityTopicId: HieroId) {
|
||||
super(communityTopicId)
|
||||
}
|
||||
|
||||
public async retrieveKeyPair(): Promise<KeyPairEd25519> {
|
||||
const transactionIdentifier = {
|
||||
transactionNr: 1,
|
||||
transactionId: 1,
|
||||
topic: this.topic,
|
||||
}
|
||||
const firstTransaction = await getTransaction(transactionIdentifier)
|
||||
const firstTransaction =
|
||||
await GradidoNodeClient.getInstance().getTransaction(transactionIdentifier)
|
||||
if (!firstTransaction) {
|
||||
throw new GradidoNodeMissingTransactionError('Cannot find transaction', transactionIdentifier)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { CONFIG } from '../../config'
|
||||
import { ParameterError } from '../../errors'
|
||||
@ -6,16 +6,9 @@ import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
|
||||
export class HomeCommunityKeyPairRole extends AbstractKeyPairRole {
|
||||
public generateKeyPair(): KeyPairEd25519 {
|
||||
// TODO: prevent this check with valibot test on config
|
||||
if (!CONFIG.IOTA_HOME_COMMUNITY_SEED) {
|
||||
throw new Error(
|
||||
'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)
|
||||
const keyPair = KeyPairEd25519.create(CONFIG.HOME_COMMUNITY_SEED)
|
||||
if (!keyPair) {
|
||||
throw new ParameterError("couldn't create keyPair from IOTA_HOME_COMMUNITY_SEED")
|
||||
throw new ParameterError("couldn't create keyPair from HOME_COMMUNITY_SEED")
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js'
|
||||
|
||||
import { findUserByNameHash } from '../../client/GradidoNode/api'
|
||||
import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient'
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import { GradidoNodeMissingUserError, ParameterError } from '../../errors'
|
||||
import { IdentifierAccount } from '../../schemas/account.schema'
|
||||
@ -16,7 +15,7 @@ export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
throw new ParameterError('missing account')
|
||||
}
|
||||
|
||||
const accountPublicKey = await findUserByNameHash(
|
||||
const accountPublicKey = await GradidoNodeClient.getInstance().findUserByNameHash(
|
||||
new Uuidv4Hash(this.identifier.account.userUuid),
|
||||
this.topic,
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { GradidoBlockchainCryptoError } from '../../errors'
|
||||
import { hardenDerivationIndex } from '../../utils/derivationHelper'
|
||||
import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
|
||||
@ -21,9 +21,14 @@ export class UserKeyPairRole extends AbstractKeyPairRole {
|
||||
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,
|
||||
)
|
||||
return parts.reduce((keyPair: KeyPairEd25519, node: number) => {
|
||||
const localKeyPair = keyPair.deriveChild(node)
|
||||
if (!localKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${keyPair.hasPrivateKey()} for user: ${this.userUuid}`,
|
||||
)
|
||||
}
|
||||
return localKeyPair
|
||||
}, this.communityKeys)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
import { Community } from '../../client/backend/community.schema'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import { GradidoBlockchainCryptoError } from '../../errors'
|
||||
import { Community } from '../../schemas/transaction.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import {
|
||||
AUF_ACCOUNT_DERIVATION_INDEX,
|
||||
@ -16,7 +17,7 @@ export class CommunityRootTransactionRole extends AbstractTransactionRole {
|
||||
}
|
||||
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.community.topicId
|
||||
return this.community.hieroTopicId
|
||||
}
|
||||
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
@ -26,16 +27,26 @@ export class CommunityRootTransactionRole extends AbstractTransactionRole {
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const communityKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic({ communityTopicId: this.community.topicId }),
|
||||
new KeyPairIdentifierLogic({ communityTopicId: this.community.hieroTopicId }),
|
||||
)
|
||||
const gmwKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
|
||||
) // as unknown as KeyPairEd25519
|
||||
)
|
||||
if (!gmwKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${this.community.uuid}`,
|
||||
)
|
||||
}
|
||||
const aufKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
|
||||
) // as unknown as KeyPairEd25519
|
||||
)
|
||||
if (!aufKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${this.community.uuid}`,
|
||||
)
|
||||
}
|
||||
builder
|
||||
.setCreatedAt(this.community.createdAt)
|
||||
.setCreatedAt(this.community.creationDate)
|
||||
.setCommunityRoot(
|
||||
communityKeyPair.getPublicKey(),
|
||||
gmwKeyPair.getPublicKey(),
|
||||
@ -1,6 +1,6 @@
|
||||
import { GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js'
|
||||
import { parse } from 'valibot'
|
||||
import { getTransactionsForAccount } from '../../client/GradidoNode/api'
|
||||
import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import {
|
||||
RedeemDeferredTransferTransaction,
|
||||
@ -42,7 +42,7 @@ export class RedeemDeferredTransferTransactionRole extends AbstractTransactionRo
|
||||
throw new Error("redeem deferred transfer: couldn't calculate sender public key")
|
||||
}
|
||||
// load deferred transfer transaction from gradido node
|
||||
const transactions = await getTransactionsForAccount(
|
||||
const transactions = await GradidoNodeClient.getInstance().getTransactionsForAccount(
|
||||
{ maxResultCount: 2, topic: this.getSenderCommunityTopicId() },
|
||||
senderPublicKey.convertToHex(),
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
import { GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
import { AddressType, GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
import { parse } from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
@ -53,7 +53,7 @@ export class RegisterAddressTransactionRole extends AbstractTransactionRole {
|
||||
.setCreatedAt(this.registerAddressTransaction.createdAt)
|
||||
.setRegisterAddress(
|
||||
userKeyPair.getPublicKey(),
|
||||
this.registerAddressTransaction.accountType,
|
||||
this.registerAddressTransaction.accountType as AddressType,
|
||||
new Uuidv4Hash(this.account.userUuid).getAsMemoryBlock(),
|
||||
accountKeyPair.getPublicKey(),
|
||||
)
|
||||
@ -1,16 +1,25 @@
|
||||
import {
|
||||
GradidoTransaction,
|
||||
InteractionValidate,
|
||||
MemoryBlock,
|
||||
InteractionValidate,
|
||||
MemoryBlock,
|
||||
ValidateType_SINGLE,
|
||||
} from 'gradido-blockchain-js'
|
||||
import { getLogger } from 'log4js'
|
||||
import { safeParse, parse } from 'valibot'
|
||||
import { Community, communitySchema } from '../../client/backend/community.schema'
|
||||
import { parse, safeParse } from 'valibot'
|
||||
import { HieroClient } from '../../client/hiero/HieroClient'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { Transaction, transactionSchema } from '../../schemas/transaction.schema'
|
||||
import { HieroId, HieroTransactionId, hieroTransactionIdSchema } from '../../schemas/typeGuard.schema'
|
||||
import { InputTransactionType } from '../../enum/InputTransactionType'
|
||||
import {
|
||||
Community,
|
||||
communitySchema,
|
||||
Transaction,
|
||||
transactionSchema,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import {
|
||||
HieroId,
|
||||
HieroTransactionId,
|
||||
hieroTransactionIdSchema,
|
||||
} from '../../schemas/typeGuard.schema'
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
|
||||
import { CreationTransactionRole } from './CreationTransaction.role'
|
||||
@ -18,16 +27,15 @@ import { DeferredTransferTransactionRole } from './DeferredTransferTransaction.r
|
||||
import { RedeemDeferredTransferTransactionRole } from './RedeemDeferredTransferTransaction.role'
|
||||
import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role'
|
||||
import { TransferTransactionRole } from './TransferTransaction.role'
|
||||
import { InputTransactionType } from '../../enum/InputTransactionType'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.interactions.sendToIota.SendToIotaContext`)
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.interactions.sendToHiero.SendToHieroContext`)
|
||||
|
||||
/**
|
||||
* @DCI-Context
|
||||
* Context for sending transaction to iota
|
||||
* send every transaction only once to iota!
|
||||
* Context for sending transaction to hiero
|
||||
* send every transaction only once to hiero!
|
||||
*/
|
||||
export async function SendToIotaContext(
|
||||
export async function SendToHieroContext(
|
||||
input: Transaction | Community,
|
||||
): Promise<HieroTransactionId> {
|
||||
// let gradido blockchain validator run, it will throw an exception when something is wrong
|
||||
@ -44,7 +52,7 @@ export async function SendToIotaContext(
|
||||
const client = HieroClient.getInstance()
|
||||
const resultMessage = await client.sendMessage(topic, gradidoTransaction)
|
||||
const transactionId = resultMessage.response.transactionId.toString()
|
||||
logger.info('transmitted Gradido Transaction to Iota', { transactionId })
|
||||
logger.info('transmitted Gradido Transaction to Hiero', { transactionId })
|
||||
return transactionId
|
||||
}
|
||||
|
||||
@ -75,7 +83,7 @@ export async function SendToIotaContext(
|
||||
}
|
||||
}
|
||||
|
||||
const role = chooseCorrectRole(input)
|
||||
const role = chooseCorrectRole(input)
|
||||
const builder = await role.getGradidoTransactionBuilder()
|
||||
if (builder.isCrossCommunityTransaction()) {
|
||||
const outboundTransaction = builder.buildOutbound()
|
||||
@ -92,10 +100,7 @@ export async function SendToIotaContext(
|
||||
} else {
|
||||
const transaction = builder.build()
|
||||
validate(transaction)
|
||||
const iotaMessageId = await sendViaHiero(
|
||||
transaction,
|
||||
role.getSenderCommunityTopicId(),
|
||||
)
|
||||
const iotaMessageId = await sendViaHiero(transaction, role.getSenderCommunityTopicId())
|
||||
return parse(hieroTransactionIdSchema, iotaMessageId)
|
||||
}
|
||||
}
|
||||
@ -7,13 +7,13 @@ import {
|
||||
import { parse } from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import {
|
||||
Transaction,
|
||||
TransferTransaction,
|
||||
transferTransactionSchema,
|
||||
Transaction,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
|
||||
export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
private transferTransaction: TransferTransaction
|
||||
@ -33,7 +33,9 @@ export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
// sender + signer
|
||||
const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(this.transferTransaction.user))
|
||||
const senderKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.transferTransaction.user),
|
||||
)
|
||||
// recipient
|
||||
const recipientKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.transferTransaction.linkedUser),
|
||||
@ -56,9 +58,7 @@ export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
const recipientCommunity = this.transferTransaction.linkedUser.communityTopicId
|
||||
if (senderCommunity !== recipientCommunity) {
|
||||
// we have a cross group transaction
|
||||
builder
|
||||
.setSenderCommunity(senderCommunity)
|
||||
.setRecipientCommunity(recipientCommunity)
|
||||
builder.setSenderCommunity(senderCommunity).setRecipientCommunity(recipientCommunity)
|
||||
}
|
||||
builder.sign(senderKeyPair)
|
||||
return builder
|
||||
@ -1,25 +1,19 @@
|
||||
import { describe, expect, it, beforeAll } from 'bun:test'
|
||||
import { beforeAll, describe, expect, it } from 'bun:test'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { parse } from 'valibot'
|
||||
import { InputTransactionType } from '../enum/InputTransactionType'
|
||||
import {
|
||||
TransactionInput,
|
||||
transactionSchema,
|
||||
} from './transaction.schema'
|
||||
import { transactionIdentifierSchema } from '../client/GradidoNode/input.schema'
|
||||
import {
|
||||
gradidoAmountSchema,
|
||||
HieroId,
|
||||
hieroIdSchema,
|
||||
HieroTransactionId,
|
||||
hieroTransactionIdSchema,
|
||||
Memo,
|
||||
memoSchema,
|
||||
timeoutDurationSchema,
|
||||
Uuidv4,
|
||||
uuidv4Schema
|
||||
gradidoAmountSchema,
|
||||
HieroId,
|
||||
hieroIdSchema,
|
||||
Memo,
|
||||
memoSchema,
|
||||
timeoutDurationSchema,
|
||||
Uuidv4,
|
||||
uuidv4Schema,
|
||||
} from '../schemas/typeGuard.schema'
|
||||
import { TransactionInput, transactionSchema } from './transaction.schema'
|
||||
|
||||
const transactionLinkCode = (date: Date): string => {
|
||||
const time = date.getTime().toString(16)
|
||||
@ -31,197 +25,150 @@ const transactionLinkCode = (date: Date): string => {
|
||||
}
|
||||
let topic: HieroId
|
||||
const topicString = '0.0.261'
|
||||
let hieroTransactionId: HieroTransactionId
|
||||
beforeAll(() => {
|
||||
topic = parse(hieroIdSchema, topicString)
|
||||
hieroTransactionId = parse(hieroTransactionIdSchema, '0.0.261-1755348116-1281621')
|
||||
})
|
||||
|
||||
describe('transaction schemas', () => {
|
||||
describe('transactionIdentifierSchema ', () => {
|
||||
it('valid, transaction identified by transactionNr and topic', () => {
|
||||
expect(
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
topic: topicString,
|
||||
}),
|
||||
).toEqual({
|
||||
transactionNr: 1,
|
||||
hieroTransactionId: undefined,
|
||||
topic,
|
||||
})
|
||||
let userUuid: Uuidv4
|
||||
let userUuidString: string
|
||||
let memoString: string
|
||||
let memo: Memo
|
||||
beforeAll(() => {
|
||||
userUuidString = uuidv4()
|
||||
userUuid = parse(uuidv4Schema, userUuidString)
|
||||
memoString = 'TestMemo'
|
||||
memo = parse(memoSchema, memoString)
|
||||
})
|
||||
it('valid, register new user address', () => {
|
||||
const registerAddress: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
type: InputTransactionType.REGISTER_ADDRESS,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(parse(transactionSchema, registerAddress)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
type: registerAddress.type,
|
||||
createdAt: new Date(registerAddress.createdAt),
|
||||
})
|
||||
it('valid, transaction identified by hieroTransactionId and topic', () => {
|
||||
expect(
|
||||
parse(transactionIdentifierSchema, {
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
topic: topicString,
|
||||
}),
|
||||
).toEqual({
|
||||
hieroTransactionId,
|
||||
topic
|
||||
})
|
||||
})
|
||||
it('invalid, missing topic', () => {
|
||||
expect(() =>
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
}),
|
||||
).toThrowError(new Error('Invalid key: Expected "topic" but received undefined'))
|
||||
})
|
||||
it('invalid, transactionNr and iotaMessageId set', () => {
|
||||
expect(() =>
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
topic
|
||||
}),
|
||||
).toThrowError(new Error('expect transactionNr or hieroTransactionId not both'))
|
||||
})
|
||||
it('valid, gradido transfer', () => {
|
||||
const gradidoTransfer: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
amount: '100',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoTransfer)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoTransfer.amount!),
|
||||
memo,
|
||||
type: gradidoTransfer.type,
|
||||
createdAt: new Date(gradidoTransfer.createdAt),
|
||||
})
|
||||
})
|
||||
|
||||
describe('transactionSchema', () => {
|
||||
let userUuid: Uuidv4
|
||||
let userUuidString: string
|
||||
let memoString: string
|
||||
let memo: Memo
|
||||
beforeAll(() => {
|
||||
userUuidString = uuidv4()
|
||||
userUuid = parse(uuidv4Schema, userUuidString)
|
||||
memoString = 'TestMemo'
|
||||
memo = parse(memoSchema, memoString)
|
||||
it('valid, gradido creation', () => {
|
||||
const gradidoCreation: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
amount: '1000',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_CREATION,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
targetDate: '2021-11-01T10:00',
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoCreation)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: { userUuid, accountNr: 0 },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
account: { userUuid, accountNr: 0 },
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoCreation.amount!),
|
||||
memo,
|
||||
type: gradidoCreation.type,
|
||||
createdAt: new Date(gradidoCreation.createdAt),
|
||||
targetDate: new Date(gradidoCreation.targetDate!),
|
||||
})
|
||||
it('valid, register new user address', () => {
|
||||
const registerAddress: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
})
|
||||
it('valid, gradido transaction link / deferred transfer', () => {
|
||||
const gradidoTransactionLink: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: {
|
||||
userUuid: userUuidString,
|
||||
},
|
||||
type: InputTransactionType.REGISTER_ADDRESS,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(parse(transactionSchema, registerAddress)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topicString,
|
||||
seed: {
|
||||
seed: transactionLinkCode(new Date()),
|
||||
},
|
||||
type: registerAddress.type,
|
||||
createdAt: new Date(registerAddress.createdAt),
|
||||
})
|
||||
})
|
||||
it('valid, gradido transfer', () => {
|
||||
const gradidoTransfer: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
amount: '100',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_DEFERRED_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
timeoutDuration: 60 * 60 * 24 * 30,
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoTransactionLink)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
seed: {
|
||||
seed: gradidoTransactionLink.linkedUser!.seed!.seed,
|
||||
},
|
||||
amount: '100',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoTransfer)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoTransfer.amount!),
|
||||
memo,
|
||||
type: gradidoTransfer.type,
|
||||
createdAt: new Date(gradidoTransfer.createdAt),
|
||||
})
|
||||
})
|
||||
|
||||
it('valid, gradido creation', () => {
|
||||
const gradidoCreation: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
amount: '1000',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_CREATION,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
targetDate: '2021-11-01T10:00',
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoCreation)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: { userUuid, accountNr: 0 },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
account: { userUuid, accountNr: 0 },
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoCreation.amount!),
|
||||
memo,
|
||||
type: gradidoCreation.type,
|
||||
createdAt: new Date(gradidoCreation.createdAt),
|
||||
targetDate: new Date(gradidoCreation.targetDate!),
|
||||
})
|
||||
})
|
||||
it('valid, gradido transaction link / deferred transfer', () => {
|
||||
const gradidoTransactionLink: TransactionInput = {
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: {
|
||||
userUuid: userUuidString,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topicString,
|
||||
seed: {
|
||||
seed: transactionLinkCode(new Date()),
|
||||
},
|
||||
},
|
||||
amount: '100',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_DEFERRED_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
timeoutDuration: 60 * 60 * 24 * 30,
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoTransactionLink)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
seed: {
|
||||
seed: gradidoTransactionLink.linkedUser!.seed!.seed,
|
||||
},
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoTransactionLink.amount!),
|
||||
memo,
|
||||
type: gradidoTransactionLink.type,
|
||||
createdAt: new Date(gradidoTransactionLink.createdAt),
|
||||
timeoutDuration: parse(timeoutDurationSchema, gradidoTransactionLink.timeoutDuration!),
|
||||
})
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoTransactionLink.amount!),
|
||||
memo,
|
||||
type: gradidoTransactionLink.type,
|
||||
createdAt: new Date(gradidoTransactionLink.createdAt),
|
||||
timeoutDuration: parse(timeoutDurationSchema, gradidoTransactionLink.timeoutDuration!),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,8 +11,22 @@ import {
|
||||
hieroIdSchema,
|
||||
memoSchema,
|
||||
timeoutDurationSchema,
|
||||
uuidv4Schema,
|
||||
} from './typeGuard.schema'
|
||||
|
||||
/**
|
||||
* Schema for community, for creating new CommunityRoot Transaction on gradido blockchain
|
||||
*/
|
||||
export const communitySchema = v.object({
|
||||
uuid: uuidv4Schema,
|
||||
hieroTopicId: hieroIdSchema,
|
||||
foreign: v.boolean('expect boolean type'),
|
||||
creationDate: dateSchema,
|
||||
})
|
||||
|
||||
export type CommunityInput = v.InferInput<typeof communitySchema>
|
||||
export type Community = v.InferOutput<typeof communitySchema>
|
||||
|
||||
export const transactionSchema = v.object({
|
||||
user: identifierAccountSchema,
|
||||
linkedUser: v.nullish(identifierAccountSchema, undefined),
|
||||
|
||||
@ -51,10 +51,10 @@ describe('basic.schema', () => {
|
||||
it('confirmedTransactionSchema', () => {
|
||||
const confirmedTransaction = v.parse(
|
||||
confirmedTransactionSchema,
|
||||
'CAcSAgoAGgYIwvK5/wUiAzMuNCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
||||
'CAcS5AEKZgpkCiCBZwMplGmI7fRR9MQkaR2Dz1qQQ5BCiC1btyJD71Ue9BJABODQ9sS70th9yHn8X3K+SNv2gsiIdX/V09baCvQCb+yEj2Dd/fzShIYqf3pooIMwJ01BkDJdNGBZs5MDzEAkChJ6ChkIAhIVRGFua2UgZnVlciBkZWluIFNlaW4hEggIgMy5/wUQABoDMy41IAAyTAooCiDbDtYSWhTwMKvtG/yDHgohjPn6v87n7NWBwMDniPAXxxCUmD0aABIgJE0o18xb6P6PsNjh0bkN52AzhggteTzoh09jV+blMq0aCAjC8rn/BRAAIgMzLjUqICiljeEjGHifWe4VNzoe+DN9oOLNZvJmv3VlkP+1RH7MMiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADomCiDbDtYSWhTwMKvtG/yDHgohjPn6v87n7NWBwMDniPAXxxDAhD06JwogJE0o18xb6P6PsNjh0bkN52AzhggteTzoh09jV+blMq0Q65SlBA==',
|
||||
)
|
||||
expect(confirmedTransaction.getId()).toBe(7)
|
||||
expect(confirmedTransaction.getConfirmedAt().getSeconds()).toBe(1609464130)
|
||||
expect(confirmedTransaction.getVersionNumber()).toBe('3.4')
|
||||
expect(confirmedTransaction.getVersionNumber()).toBe('3.5')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { AddressType, ConfirmedTransaction } from 'gradido-blockchain-js'
|
||||
import { ConfirmedTransaction } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
import { AddressType } from '../enum/AddressType'
|
||||
import {
|
||||
confirmedTransactionFromBase64,
|
||||
isAddressType,
|
||||
|
||||
@ -1,50 +1,27 @@
|
||||
import { TypeBoxFromValibot } from '@sinclair/typemap'
|
||||
import { Elysia, status } from 'elysia'
|
||||
import { AddressType_NONE } from 'gradido-blockchain-js'
|
||||
import { getLogger } from 'log4js'
|
||||
import { parse } from 'valibot'
|
||||
import { getAddressType } from '../client/GradidoNode/api'
|
||||
import { GradidoNodeClient } from '../client/GradidoNode/GradidoNodeClient'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
import { KeyPairIdentifierLogic } from '../data/KeyPairIdentifier.logic'
|
||||
import { KeyPairCalculation } from '../interactions/keyPairCalculation/KeyPairCalculation.context'
|
||||
import { SendToIotaContext } from '../interactions/sendToIota/SendToIota.context'
|
||||
import { SendToHieroContext } from '../interactions/sendToHiero/SendToHiero.context'
|
||||
import { IdentifierAccount, identifierAccountSchema } from '../schemas/account.schema'
|
||||
import { transactionSchema } from '../schemas/transaction.schema'
|
||||
import { hieroTransactionIdSchema } from '../schemas/typeGuard.schema'
|
||||
import {
|
||||
accountIdentifierSeedSchema,
|
||||
accountIdentifierUserSchema,
|
||||
existSchema,
|
||||
} from './input.schema'
|
||||
import { TypeBoxFromValibot } from '@sinclair/typemap'
|
||||
import { transactionSchema } from '../schemas/transaction.schema'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.server`)
|
||||
|
||||
async function isAccountExist(identifierAccount: IdentifierAccount): Promise<boolean> {
|
||||
const startTime = Date.now()
|
||||
const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(identifierAccount))
|
||||
const publicKey = accountKeyPair.getPublicKey()
|
||||
if (!publicKey) {
|
||||
throw status(404, "couldn't calculate account key pair")
|
||||
}
|
||||
|
||||
// ask gradido node server for account type, if type !== NONE account exist
|
||||
const addressType = await getAddressType(
|
||||
publicKey.convertToHex(),
|
||||
identifierAccount.communityTopicId,
|
||||
)
|
||||
const endTime = Date.now()
|
||||
logger.info(
|
||||
`isAccountExist: ${addressType !== AddressType_NONE}, time used: ${endTime - startTime}ms`,
|
||||
)
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug('params', identifierAccount)
|
||||
}
|
||||
return addressType !== AddressType_NONE
|
||||
}
|
||||
|
||||
export const appRoutes = new Elysia()
|
||||
.get(
|
||||
'/isAccountExist/:communityTopicId/:userUuid/:accountNr',
|
||||
'/isAccountExist/by-user/:communityTopicId/:userUuid/:accountNr',
|
||||
async ({ params: { communityTopicId, userUuid, accountNr } }) => {
|
||||
const accountIdentifier = parse(identifierAccountSchema, {
|
||||
communityTopicId,
|
||||
@ -56,7 +33,7 @@ export const appRoutes = new Elysia()
|
||||
{ params: accountIdentifierUserSchema, response: existSchema },
|
||||
)
|
||||
.get(
|
||||
'/isAccountExist/:communityTopicId/:seed',
|
||||
'/isAccountExist/by-seed/:communityTopicId/:seed',
|
||||
async ({ params: { communityTopicId, seed } }) => {
|
||||
const accountIdentifier = parse(identifierAccountSchema, {
|
||||
communityTopicId,
|
||||
@ -69,7 +46,33 @@ export const appRoutes = new Elysia()
|
||||
)
|
||||
.post(
|
||||
'/sendTransaction',
|
||||
async ({ body }) => await SendToIotaContext(parse(transactionSchema, body)),
|
||||
async ({ body }) => await SendToHieroContext(parse(transactionSchema, body)),
|
||||
// validation schemas
|
||||
{ body: TypeBoxFromValibot(transactionSchema), response: TypeBoxFromValibot(hieroTransactionIdSchema) },
|
||||
{
|
||||
body: TypeBoxFromValibot(transactionSchema),
|
||||
response: TypeBoxFromValibot(hieroTransactionIdSchema),
|
||||
},
|
||||
)
|
||||
|
||||
async function isAccountExist(identifierAccount: IdentifierAccount): Promise<boolean> {
|
||||
const startTime = Date.now()
|
||||
const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(identifierAccount))
|
||||
const publicKey = accountKeyPair.getPublicKey()
|
||||
if (!publicKey) {
|
||||
throw status(404, "couldn't calculate account key pair")
|
||||
}
|
||||
|
||||
// ask gradido node server for account type, if type !== NONE account exist
|
||||
const addressType = await GradidoNodeClient.getInstance().getAddressType(
|
||||
publicKey.convertToHex(),
|
||||
identifierAccount.communityTopicId,
|
||||
)
|
||||
const endTime = Date.now()
|
||||
logger.info(
|
||||
`isAccountExist: ${addressType !== AddressType_NONE}, time used: ${endTime - startTime}ms`,
|
||||
)
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug('params', identifierAccount)
|
||||
}
|
||||
return addressType !== AddressType_NONE
|
||||
}
|
||||
|
||||
@ -1,19 +1,11 @@
|
||||
import {
|
||||
AddressType,
|
||||
AddressType_COMMUNITY_AUF,
|
||||
AddressType_COMMUNITY_GMW,
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
AddressType_COMMUNITY_PROJECT,
|
||||
AddressType_CRYPTO_ACCOUNT,
|
||||
AddressType_DEFERRED_TRANSFER,
|
||||
AddressType_NONE,
|
||||
AddressType_SUBACCOUNT,
|
||||
ConfirmedTransaction,
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
InteractionDeserialize,
|
||||
MemoryBlock,
|
||||
} from 'gradido-blockchain-js'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
import { AddressType } from '../enum/AddressType'
|
||||
|
||||
export const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransaction => {
|
||||
const confirmedTransactionBinaryPtr = MemoryBlock.createPtr(MemoryBlock.fromBase64(base64))
|
||||
@ -34,14 +26,14 @@ export const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransac
|
||||
* AccountType is the enum defined in TypeScript but with the same options
|
||||
*/
|
||||
const accountToAddressMap: Record<AccountType, AddressType> = {
|
||||
[AccountType.COMMUNITY_AUF]: AddressType_COMMUNITY_AUF,
|
||||
[AccountType.COMMUNITY_GMW]: AddressType_COMMUNITY_GMW,
|
||||
[AccountType.COMMUNITY_HUMAN]: AddressType_COMMUNITY_HUMAN,
|
||||
[AccountType.COMMUNITY_PROJECT]: AddressType_COMMUNITY_PROJECT,
|
||||
[AccountType.CRYPTO_ACCOUNT]: AddressType_CRYPTO_ACCOUNT,
|
||||
[AccountType.SUBACCOUNT]: AddressType_SUBACCOUNT,
|
||||
[AccountType.DEFERRED_TRANSFER]: AddressType_DEFERRED_TRANSFER,
|
||||
[AccountType.NONE]: AddressType_NONE,
|
||||
[AccountType.COMMUNITY_AUF]: AddressType.COMMUNITY_AUF,
|
||||
[AccountType.COMMUNITY_GMW]: AddressType.COMMUNITY_GMW,
|
||||
[AccountType.COMMUNITY_HUMAN]: AddressType.COMMUNITY_HUMAN,
|
||||
[AccountType.COMMUNITY_PROJECT]: AddressType.COMMUNITY_PROJECT,
|
||||
[AccountType.CRYPTO_ACCOUNT]: AddressType.CRYPTO_ACCOUNT,
|
||||
[AccountType.SUBACCOUNT]: AddressType.SUBACCOUNT,
|
||||
[AccountType.DEFERRED_TRANSFER]: AddressType.DEFERRED_TRANSFER,
|
||||
[AccountType.NONE]: AddressType.NONE,
|
||||
}
|
||||
|
||||
const addressToAccountMap: Record<AddressType, AccountType> = Object.entries(
|
||||
@ -66,7 +58,7 @@ export function toAddressType(input: AccountType | AddressType): AddressType {
|
||||
if (isAddressType(input)) {
|
||||
return input
|
||||
}
|
||||
return accountToAddressMap[input as AccountType] ?? AddressType_NONE
|
||||
return accountToAddressMap[input as AccountType] ?? AddressType.NONE
|
||||
}
|
||||
|
||||
export function toAccountType(input: AccountType | AddressType): AccountType {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user