This commit is contained in:
einhornimmond 2025-11-14 12:07:24 +01:00
parent 56e4d6387c
commit a7c3bab20e
28 changed files with 549 additions and 333 deletions

View File

@ -39,7 +39,7 @@ export async function checkHomeCommunity(
// wait for backend server
await isPortOpenRetry(backend.url)
// ask backend for home community
let homeCommunity = await backend.getHomeCommunityDraft()
let homeCommunity = await backend.getHomeCommunityDraft()
// on missing topicId, create one
if (!homeCommunity.hieroTopicId) {
const topicId = await hiero.createTopic(homeCommunity.name)

View File

@ -1,3 +1,4 @@
import { Mutex } from 'async-mutex'
import { Subprocess, spawn } from 'bun'
import { getLogger, Logger } from 'log4js'
import { CONFIG } from '../../config'
@ -8,7 +9,6 @@ import {
GRADIDO_NODE_RUNTIME_PATH,
LOG4JS_BASE_CATEGORY,
} from '../../config/const'
import { Mutex } from 'async-mutex'
import { delay } from '../../utils/time'
/**
* A Singleton class defines the `getInstance` method that lets clients access
@ -94,11 +94,11 @@ export class GradidoNodeProcess {
public async restart() {
const release = await this.restartMutex.acquire()
try {
if (this.proc) {
await this.exit()
this.exitCalled = false
this.start()
}
if (this.proc) {
await this.exit()
this.exitCalled = false
this.start()
}
} finally {
release()
}
@ -111,8 +111,15 @@ export class GradidoNodeProcess {
public async exit(): Promise<void> {
this.exitCalled = true
if (this.proc) {
if (this.lastStarted && Date.now() - this.lastStarted.getTime() < GRADIDO_NODE_MIN_RUNTIME_BEFORE_EXIT_MILLISECONDS) {
await delay(GRADIDO_NODE_MIN_RUNTIME_BEFORE_EXIT_MILLISECONDS - Date.now() - this.lastStarted.getTime())
if (
this.lastStarted &&
Date.now() - this.lastStarted.getTime() < GRADIDO_NODE_MIN_RUNTIME_BEFORE_EXIT_MILLISECONDS
) {
await delay(
GRADIDO_NODE_MIN_RUNTIME_BEFORE_EXIT_MILLISECONDS -
Date.now() -
this.lastStarted.getTime(),
)
}
this.proc.kill('SIGTERM')
const timeout = setTimeout(() => {

View File

@ -66,7 +66,10 @@ export async function exportCommunities(homeFolder: string, client: BackendClien
logger.info(`exported ${communitiesForDltNodeServer.length} communities to ${communitiesPath}`)
}
export function checkCommunityAvailable(communityTopicIds: Set<HieroId>, homeFolder: string): boolean {
export function checkCommunityAvailable(
communityTopicIds: Set<HieroId>,
homeFolder: string,
): boolean {
const communitiesPath = path.join(homeFolder, 'communities.json')
if (!checkFileExist(communitiesPath)) {
return false
@ -81,6 +84,8 @@ export function checkCommunityAvailable(communityTopicIds: Set<HieroId>, homeFol
}
}
}
logger.debug(`community not found for topic ids: ${communityTopicIds}, communities: ${JSON.stringify(communities, null, 2)}`)
logger.debug(
`community not found for topic ids: ${communityTopicIds}, communities: ${JSON.stringify(communities, null, 2)}`,
)
return false
}

View File

@ -21,10 +21,10 @@ import * as v from 'valibot'
import { CONFIG } from '../../config'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema'
import { type TopicInfoOutput, topicInfoSchema } from './output.schema'
import { durationInMinutesFromDates, printTimeDuration } from '../../utils/time'
import { GradidoNodeClient } from '../GradidoNode/GradidoNodeClient'
import { GradidoNodeProcess } from '../GradidoNode/GradidoNodeProcess'
import { durationInMinutesFromDates, printTimeDuration } from '../../utils/time'
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
export const MAX_AUTORENEW_PERIOD = 8000001 // seconds
@ -109,9 +109,13 @@ export class HieroClient {
const process = GradidoNodeProcess.getInstance()
const lastStarted = process.getLastStarted()
if (lastStarted) {
const serverRunTime = printTimeDuration(durationInMinutesFromDates(lastStarted, new Date()))
this.logger.error(`transaction not found, restart GradidoNode after ${serverRunTime}`)
await GradidoNodeProcess.getInstance().restart()
const serverRunTime = printTimeDuration(
durationInMinutesFromDates(lastStarted, new Date()),
)
this.logger.error(
`transaction not found, restart GradidoNode after ${serverRunTime}`,
)
await GradidoNodeProcess.getInstance().restart()
} else {
this.logger.error('transaction not found, GradidoNode not running, start it')
GradidoNodeProcess.getInstance().start()
@ -126,9 +130,7 @@ export class HieroClient {
// only for logging
sendResponse.getRecordWithSigner(this.wallet).then((record) => {
logger.info(`message sent, cost: ${record.transactionFee.toString()}`)
logger.info(
`HieroClient.sendMessage used time (full process): ${timeUsed.string()}`,
)
logger.info(`HieroClient.sendMessage used time (full process): ${timeUsed.string()}`)
})
}
})
@ -139,7 +141,9 @@ export class HieroClient {
this.pendingPromises.splice(pendingPromiseIndex, 1)
}),
)
logger.debug(`create transactionId: ${hieroTransaction.transactionId?.toString()}, used time: ${timeUsed.string()}`)
logger.debug(
`create transactionId: ${hieroTransaction.transactionId?.toString()}, used time: ${timeUsed.string()}`,
)
return hieroTransaction.transactionId
}

View File

@ -99,10 +99,7 @@ export const configSchema = v.object({
),
'4000',
),
MYSQL_HOST: v.optional(
v.string('The host of the database'),
'localhost',
),
MYSQL_HOST: v.optional(v.string('The host of the database'), 'localhost'),
MYSQL_PORT: v.optional(
v.pipe(
v.string('The port of the database'),
@ -136,8 +133,5 @@ export const configSchema = v.object({
),
'',
),
MYSQL_DATABASE: v.optional(
v.string('The name of the database'),
'gradido_community',
),
MYSQL_DATABASE: v.optional(v.string('The name of the database'), 'gradido_community'),
})

View File

@ -107,7 +107,9 @@ export class KeyPairIdentifierLogic {
)
}
const resultString =
this.identifier.communityTopicId + this.identifier.account.userUuid.replace(/-/g, '') + accountNr.toString()
this.identifier.communityTopicId +
this.identifier.account.userUuid.replace(/-/g, '') +
accountNr.toString()
return new MemoryBlock(resultString).calculateHash().convertToHex()
}
}

View File

@ -32,7 +32,7 @@ import { UserKeyPairRole } from './UserKeyPair.role'
*/
export async function ResolveKeyPair(input: KeyPairIdentifierLogic): Promise<KeyPairEd25519> {
const cache = KeyPairCacheManager.getInstance()
return await cache.getKeyPair(
input.getKey(),
// function is called from cache manager, if key isn't currently cached

View File

@ -1,4 +1,9 @@
import { ConfirmedTransaction, GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js'
import {
ConfirmedTransaction,
GradidoTransactionBuilder,
GradidoTransfer,
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
import {
@ -42,7 +47,9 @@ export class RedeemDeferredTransferTransactionRole extends AbstractTransactionRo
if (!senderPublicKey) {
throw new Error("redeem deferred transfer: couldn't calculate sender public key")
}
const deferredTransferBody = this.parentDeferredTransaction.getGradidoTransaction()?.getTransactionBody()
const deferredTransferBody = this.parentDeferredTransaction
.getGradidoTransaction()
?.getTransactionBody()
if (!deferredTransferBody) {
throw new Error(
"redeem deferred transfer: couldn't deserialize deferred transfer from Gradido Node",

View File

@ -6,9 +6,9 @@ import {
ValidateType_SINGLE,
} from 'gradido-blockchain-js'
import { getLogger } from 'log4js'
import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient'
import * as v from 'valibot'
import { ensureCommunitiesAvailable } from '../../client/GradidoNode/communities'
import { GradidoNodeClient } from '../../client/GradidoNode/GradidoNodeClient'
import { HieroClient } from '../../client/hiero/HieroClient'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { InputTransactionType } from '../../data/InputTransactionType.enum'
@ -25,6 +25,7 @@ import {
identifierSeedSchema,
} from '../../schemas/typeGuard.schema'
import { isTopicStillOpen } from '../../utils/hiero'
import { LinkedTransactionKeyPairRole } from '../resolveKeyPair/LinkedTransactionKeyPair.role'
import { AbstractTransactionRole } from './AbstractTransaction.role'
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
import { CreationTransactionRole } from './CreationTransaction.role'
@ -32,7 +33,6 @@ import { DeferredTransferTransactionRole } from './DeferredTransferTransaction.r
import { RedeemDeferredTransferTransactionRole } from './RedeemDeferredTransferTransaction.role'
import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role'
import { TransferTransactionRole } from './TransferTransaction.role'
import { LinkedTransactionKeyPairRole } from '../resolveKeyPair/LinkedTransactionKeyPair.role'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.interactions.sendToHiero.SendToHieroContext`)
@ -146,9 +146,11 @@ async function chooseCorrectRole(
return new RegisterAddressTransactionRole(transaction)
case InputTransactionType.GRADIDO_DEFERRED_TRANSFER:
return new DeferredTransferTransactionRole(transaction)
case InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER:
case InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER: {
// load deferred transfer transaction from gradido node
const seedKeyPairRole = new LinkedTransactionKeyPairRole(v.parse(identifierSeedSchema, transaction.user.seed))
const seedKeyPairRole = new LinkedTransactionKeyPairRole(
v.parse(identifierSeedSchema, transaction.user.seed),
)
const seedPublicKey = seedKeyPairRole.generateKeyPair().getPublicKey()
if (!seedPublicKey) {
throw new Error("redeem deferred transfer: couldn't generate seed public key")
@ -158,9 +160,12 @@ async function chooseCorrectRole(
seedPublicKey.convertToHex(),
)
if (!transactions || transactions.length !== 1) {
throw new Error("redeem deferred transfer: couldn't find exactly one deferred transfer on Gradido Node")
throw new Error(
"redeem deferred transfer: couldn't find exactly one deferred transfer on Gradido Node",
)
}
return new RedeemDeferredTransferTransactionRole(transaction, transactions[0])
}
default:
throw new Error('not supported transaction type: ' + transaction.type)
}

View File

@ -1,17 +1,15 @@
import { heapStats } from 'bun:jsc'
import { drizzle, MySql2Database } from 'drizzle-orm/mysql2'
import mysql from 'mysql2/promise'
import { Filter, Profiler, SearchDirection_ASC } from 'gradido-blockchain-js'
import { getLogger, Logger } from 'log4js'
import { Uuidv4 } from '../../schemas/typeGuard.schema'
import { KeyPairCacheManager } from '../../cache/KeyPairCacheManager'
import mysql from 'mysql2/promise'
import { loadConfig } from '../../bootstrap/init'
import { KeyPairCacheManager } from '../../cache/KeyPairCacheManager'
import { CONFIG } from '../../config'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { heapStats } from 'bun:jsc'
import { CommunityContext } from './valibot.schema'
import { Uuidv4 } from '../../schemas/typeGuard.schema'
import { bytesToMbyte } from './utils'
import { CommunityContext } from './valibot.schema'
export class Context {
public logger: Logger
@ -36,12 +34,12 @@ export class Context {
user: CONFIG.MYSQL_USER,
password: CONFIG.MYSQL_PASSWORD,
database: CONFIG.MYSQL_DATABASE,
port: CONFIG.MYSQL_PORT
port: CONFIG.MYSQL_PORT,
})
return new Context(
getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.5`),
drizzle({ client: connection }),
KeyPairCacheManager.getInstance()
drizzle({ client: connection }),
KeyPairCacheManager.getInstance(),
)
}
@ -57,18 +55,18 @@ export class Context {
this.logger.info(`${this.timeUsed.string()} for synchronizing to blockchain`)
const runtimeStats = heapStats()
this.logger.info(
`Memory Statistics: heap size: ${bytesToMbyte(runtimeStats.heapSize)} MByte, heap capacity: ${bytesToMbyte(runtimeStats.heapCapacity)} MByte, extra memory: ${bytesToMbyte(runtimeStats.extraMemorySize)} MByte`
`Memory Statistics: heap size: ${bytesToMbyte(runtimeStats.heapSize)} MByte, heap capacity: ${bytesToMbyte(runtimeStats.heapCapacity)} MByte, extra memory: ${bytesToMbyte(runtimeStats.extraMemorySize)} MByte`,
)
}
logBlogchain(communityUuid: Uuidv4) {
const communityContext = this.getCommunityContextByUuid(communityUuid)
const f = new Filter()
const f = new Filter()
f.pagination.size = 0
f.searchDirection = SearchDirection_ASC
const transactions = communityContext.blockchain.findAll(f)
for(let i = 0; i < transactions.size(); i++) {
for (let i = 0; i < transactions.size(); i++) {
const transaction = transactions.get(i)
const confirmedTransaction = transaction?.getConfirmedTransaction()
this.logger.info(confirmedTransaction?.toJson(true))
@ -76,5 +74,4 @@ export class Context {
}
// TODO: move into utils
}

View File

@ -1,21 +1,30 @@
import { ConfirmedTransaction, Filter, InteractionSerialize, Profiler, SearchDirection_ASC } from 'gradido-blockchain-js'
import path from 'node:path'
import { CONFIG } from '../../config'
import fs from 'node:fs'
import { bytesToKbyte } from './utils'
import { calculateOneHashStep } from './utils'
import path from 'node:path'
import {
ConfirmedTransaction,
Filter,
InteractionSerialize,
Profiler,
SearchDirection_ASC,
} from 'gradido-blockchain-js'
import { CONFIG } from '../../config'
import { Context } from './Context'
import { bytesToKbyte, calculateOneHashStep } from './utils'
import { CommunityContext } from './valibot.schema'
export function exportAllCommunities(context: Context, batchSize: number) {
const timeUsed = new Profiler()
for(const communityContext of context.communities.values()) {
for (const communityContext of context.communities.values()) {
exportCommunity(communityContext, context, batchSize)
}
context.logger.info(`time used for exporting communities to binary file: ${timeUsed.string()}`)
}
export function exportCommunity(communityContext: CommunityContext, context: Context, batchSize: number) {
export function exportCommunity(
communityContext: CommunityContext,
context: Context,
batchSize: number,
) {
// write as binary file for GradidoNode
const f = new Filter()
f.pagination.size = batchSize
@ -28,7 +37,7 @@ export function exportCommunity(communityContext: CommunityContext, context: Con
do {
const transactions = communityContext.blockchain.findAll(f)
lastTransactionCount = transactions.size()
for (let i = 0; i < lastTransactionCount; i++) {
const confirmedTransaction = transactions.get(i)?.getConfirmedTransaction()
const transactionNr = f.pagination.page * batchSize + i
@ -39,22 +48,30 @@ export function exportCommunity(communityContext: CommunityContext, context: Con
}
f.pagination.page++
} while (lastTransactionCount === batchSize)
fs.appendFileSync(binFilePath, hash!)
context.logger.info(`binary file for community ${communityContext.communityId} written to ${binFilePath}`)
context.logger.info(
`transactions count: ${(f.pagination.page - 1) * batchSize + lastTransactionCount}, size: ${bytesToKbyte(fs.statSync(binFilePath).size)} KByte`
`binary file for community ${communityContext.communityId} written to ${binFilePath}`,
)
context.logger.info(
`transactions count: ${(f.pagination.page - 1) * batchSize + lastTransactionCount}, size: ${bytesToKbyte(fs.statSync(binFilePath).size)} KByte`,
)
}
function exportTransaction(confirmedTransaction: ConfirmedTransaction, hash: Buffer<ArrayBuffer>, binFilePath: string): Buffer<ArrayBuffer> {
const sizeBuffer = Buffer.alloc(2)
function exportTransaction(
confirmedTransaction: ConfirmedTransaction,
hash: Buffer<ArrayBuffer>,
binFilePath: string,
): Buffer<ArrayBuffer> {
const sizeBuffer = Buffer.alloc(2)
const interactionSerialize = new InteractionSerialize(confirmedTransaction)
const binBlock = interactionSerialize.run()
if (!binBlock) {
throw new Error(`invalid TransactionEntry at index: ${confirmedTransaction.getId()}, serialize into protobuf format failed`)
throw new Error(
`invalid TransactionEntry at index: ${confirmedTransaction.getId()}, serialize into protobuf format failed`,
)
}
hash = calculateOneHashStep(hash, binBlock.data())
sizeBuffer.writeUInt16LE(binBlock.size(), 0)
fs.appendFileSync(binFilePath, sizeBuffer)
@ -64,13 +81,13 @@ function exportTransaction(confirmedTransaction: ConfirmedTransaction, hash: Buf
function prepareFolder(communityContext: CommunityContext): string {
const binFileFolder = path.join(
CONFIG.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER,
CONFIG.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER,
'.gradido',
communityContext.folder,
)
const binFilePath = path.join(binFileFolder, 'blk00000001.dat')
// make sure we work with a clean folder, rm beforehand with all content
fs.rmSync(binFileFolder, { recursive: true })
fs.mkdirSync(binFileFolder, { recursive: true })
fs.mkdirSync(binFileFolder, { recursive: true })
return binFilePath
}
}

View File

@ -1,37 +1,47 @@
import {
InMemoryBlockchain,
GradidoTransactionBuilder,
Timestamp,
HieroTransactionId,
HieroAccountId,
InteractionSerialize,
import {
Filter,
GradidoTransactionBuilder,
HieroAccountId,
HieroTransactionId,
InMemoryBlockchain,
InteractionSerialize,
Timestamp,
} from 'gradido-blockchain-js'
import { getLogger } from 'log4js'
import { RegisterAddressTransactionRole } from '../../interactions/sendToHiero/RegisterAddressTransaction.role'
import { CommunityRootTransactionRole } from '../../interactions/sendToHiero/CommunityRootTransaction.role'
import { CreationTransactionRole } from '../../interactions/sendToHiero/CreationTransaction.role'
import * as v from 'valibot'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { Community, Transaction } from '../../schemas/transaction.schema'
import { TransferTransactionRole } from '../../interactions/sendToHiero/TransferTransaction.role'
import { DeferredTransferTransactionRole } from '../../interactions/sendToHiero/DeferredTransferTransaction.role'
import { RedeemDeferredTransferTransactionRole } from '../../interactions/sendToHiero/RedeemDeferredTransferTransaction.role'
import { InputTransactionType } from '../../data/InputTransactionType.enum'
import { LinkedTransactionKeyPairRole } from '../../interactions/resolveKeyPair/LinkedTransactionKeyPair.role'
import { CommunityRootTransactionRole } from '../../interactions/sendToHiero/CommunityRootTransaction.role'
import { CreationTransactionRole } from '../../interactions/sendToHiero/CreationTransaction.role'
import { DeferredTransferTransactionRole } from '../../interactions/sendToHiero/DeferredTransferTransaction.role'
import { RedeemDeferredTransferTransactionRole } from '../../interactions/sendToHiero/RedeemDeferredTransferTransaction.role'
import { RegisterAddressTransactionRole } from '../../interactions/sendToHiero/RegisterAddressTransaction.role'
import { TransferTransactionRole } from '../../interactions/sendToHiero/TransferTransaction.role'
import { Community, Transaction } from '../../schemas/transaction.schema'
import { identifierSeedSchema } from '../../schemas/typeGuard.schema'
import * as v from 'valibot'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.6.blockchain`)
const logger = getLogger(
`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.6.blockchain`,
)
export const defaultHieroAccount = new HieroAccountId(0, 0, 2)
function addToBlockchain(builder: GradidoTransactionBuilder, blockchain: InMemoryBlockchain, createdAtTimestamp: Timestamp): boolean {
function addToBlockchain(
builder: GradidoTransactionBuilder,
blockchain: InMemoryBlockchain,
createdAtTimestamp: Timestamp,
): boolean {
const transaction = builder.build()
// TOD: use actual transaction id if exist in dlt_transactions table
const transactionId = new HieroTransactionId(createdAtTimestamp, defaultHieroAccount)
const interactionSerialize = new InteractionSerialize(transactionId)
try {
const result = blockchain.createAndAddConfirmedTransaction(transaction, interactionSerialize.run(), createdAtTimestamp)
const result = blockchain.createAndAddConfirmedTransaction(
transaction,
interactionSerialize.run(),
createdAtTimestamp,
)
return result
} catch (error) {
logger.error(`Transaction ${transaction.toJson(true)} not added: ${error}`)
@ -39,68 +49,130 @@ function addToBlockchain(builder: GradidoTransactionBuilder, blockchain: InMemor
}
}
export async function addCommunityRootTransaction(blockchain: InMemoryBlockchain, community: Community): Promise<void> {
export async function addCommunityRootTransaction(
blockchain: InMemoryBlockchain,
community: Community,
): Promise<void> {
const communityRootTransactionRole = new CommunityRootTransactionRole(community)
if(addToBlockchain(await communityRootTransactionRole.getGradidoTransactionBuilder(), blockchain, new Timestamp(community.creationDate))) {
if (
addToBlockchain(
await communityRootTransactionRole.getGradidoTransactionBuilder(),
blockchain,
new Timestamp(community.creationDate),
)
) {
logger.info(`Community Root Transaction added`)
} else {
throw new Error(`Community Root Transaction not added`)
}
}
export async function addRegisterAddressTransaction(blockchain: InMemoryBlockchain, transaction: Transaction): Promise<void> {
export async function addRegisterAddressTransaction(
blockchain: InMemoryBlockchain,
transaction: Transaction,
): Promise<void> {
const registerAddressRole = new RegisterAddressTransactionRole(transaction)
if(addToBlockchain(await registerAddressRole.getGradidoTransactionBuilder(), blockchain, new Timestamp(transaction.createdAt))) {
logger.debug(`Register Address Transaction added for user ${transaction.user.account!.userUuid}`)
if (
addToBlockchain(
await registerAddressRole.getGradidoTransactionBuilder(),
blockchain,
new Timestamp(transaction.createdAt),
)
) {
logger.debug(
`Register Address Transaction added for user ${transaction.user.account!.userUuid}`,
)
} else {
throw new Error(`Register Address Transaction not added for user ${transaction.user.account!.userUuid}`)
throw new Error(
`Register Address Transaction not added for user ${transaction.user.account!.userUuid}`,
)
}
}
export async function addTransaction(
senderBlockchain: InMemoryBlockchain,
_recipientBlockchain: InMemoryBlockchain,
transaction: Transaction
transaction: Transaction,
): Promise<void> {
const createdAtTimestamp = new Timestamp(transaction.createdAt)
if (transaction.type === InputTransactionType.GRADIDO_CREATION) {
const creationTransactionRole = new CreationTransactionRole(transaction)
if(addToBlockchain(await creationTransactionRole.getGradidoTransactionBuilder(), senderBlockchain, createdAtTimestamp)) {
if (
addToBlockchain(
await creationTransactionRole.getGradidoTransactionBuilder(),
senderBlockchain,
createdAtTimestamp,
)
) {
logger.debug(`Creation Transaction added for user ${transaction.user.account!.userUuid}`)
} else {
throw new Error(`Creation Transaction not added for user ${transaction.user.account!.userUuid}`)
}
throw new Error(
`Creation Transaction not added for user ${transaction.user.account!.userUuid}`,
)
}
} else if (transaction.type === InputTransactionType.GRADIDO_TRANSFER) {
const transferTransactionRole = new TransferTransactionRole(transaction)
// will crash with cross group transaction
if(addToBlockchain(await transferTransactionRole.getGradidoTransactionBuilder(), senderBlockchain, createdAtTimestamp)) {
if (
addToBlockchain(
await transferTransactionRole.getGradidoTransactionBuilder(),
senderBlockchain,
createdAtTimestamp,
)
) {
logger.debug(`Transfer Transaction added for user ${transaction.user.account!.userUuid}`)
} else {
throw new Error(`Transfer Transaction not added for user ${transaction.user.account!.userUuid}`)
throw new Error(
`Transfer Transaction not added for user ${transaction.user.account!.userUuid}`,
)
}
} else if (transaction.type === InputTransactionType.GRADIDO_DEFERRED_TRANSFER) {
const transferTransactionRole = new DeferredTransferTransactionRole(transaction)
if(addToBlockchain(await transferTransactionRole.getGradidoTransactionBuilder(), senderBlockchain, createdAtTimestamp)) {
logger.debug(`Deferred Transfer Transaction added for user ${transaction.user.account!.userUuid}`)
if (
addToBlockchain(
await transferTransactionRole.getGradidoTransactionBuilder(),
senderBlockchain,
createdAtTimestamp,
)
) {
logger.debug(
`Deferred Transfer Transaction added for user ${transaction.user.account!.userUuid}`,
)
} else {
throw new Error(`Deferred Transfer Transaction not added for user ${transaction.user.account!.userUuid}`)
throw new Error(
`Deferred Transfer Transaction not added for user ${transaction.user.account!.userUuid}`,
)
}
} else if (transaction.type === InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER) {
const seedKeyPairRole = new LinkedTransactionKeyPairRole(v.parse(identifierSeedSchema, transaction.user.seed))
const seedKeyPairRole = new LinkedTransactionKeyPairRole(
v.parse(identifierSeedSchema, transaction.user.seed),
)
const f = new Filter()
f.involvedPublicKey = seedKeyPairRole.generateKeyPair().getPublicKey()
const deferredTransaction = senderBlockchain.findOne(f)
if (!deferredTransaction) {
throw new Error(`redeem deferred transfer: couldn't find parent deferred transfer on Gradido Node for ${JSON.stringify(transaction, null, 2)} and public key from seed: ${f.involvedPublicKey?.convertToHex()}`)
throw new Error(
`redeem deferred transfer: couldn't find parent deferred transfer on Gradido Node for ${JSON.stringify(transaction, null, 2)} and public key from seed: ${f.involvedPublicKey?.convertToHex()}`,
)
}
const confirmedDeferredTransaction = deferredTransaction.getConfirmedTransaction()
if (!confirmedDeferredTransaction) {
throw new Error("redeem deferred transfer: invalid TransactionEntry")
throw new Error('redeem deferred transfer: invalid TransactionEntry')
}
const redeemTransactionRole = new RedeemDeferredTransferTransactionRole(transaction, confirmedDeferredTransaction)
const involvedUser = transaction.user.account ? transaction.user.account.userUuid : transaction.linkedUser?.account?.userUuid
if(addToBlockchain(await redeemTransactionRole.getGradidoTransactionBuilder(), senderBlockchain, createdAtTimestamp)) {
const redeemTransactionRole = new RedeemDeferredTransferTransactionRole(
transaction,
confirmedDeferredTransaction,
)
const involvedUser = transaction.user.account
? transaction.user.account.userUuid
: transaction.linkedUser?.account?.userUuid
if (
addToBlockchain(
await redeemTransactionRole.getGradidoTransactionBuilder(),
senderBlockchain,
createdAtTimestamp,
)
) {
logger.debug(`Redeem Deferred Transfer Transaction added for user ${involvedUser}`)
} else {
throw new Error(`Redeem Deferred Transfer Transaction not added for user ${involvedUser}`)

View File

@ -1,11 +1,11 @@
import { Context } from './Context'
import { loadCommunities } from './database'
import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema'
import * as v from 'valibot'
import { InMemoryBlockchainProvider } from 'gradido-blockchain-js'
import { generateKeyPairCommunity } from './keyPair'
import { communityDbToCommunity } from './convert'
import * as v from 'valibot'
import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema'
import { addCommunityRootTransaction } from './blockchain'
import { Context } from './Context'
import { communityDbToCommunity } from './convert'
import { loadCommunities } from './database'
import { generateKeyPairCommunity } from './keyPair'
import { CommunityContext } from './valibot.schema'
export async function bootstrap(): Promise<Context> {
@ -18,18 +18,20 @@ async function bootstrapCommunities(context: Context): Promise<Map<string, Commu
const communities = new Map<string, CommunityContext>()
const communitiesDb = await loadCommunities(context.db)
const topicIds = new Set<HieroId>()
for (const communityDb of communitiesDb) {
const blockchain = InMemoryBlockchainProvider.getInstance().findBlockchain(communityDb.uniqueAlias)
const blockchain = InMemoryBlockchainProvider.getInstance().findBlockchain(
communityDb.uniqueAlias,
)
if (!blockchain) {
throw new Error(`Couldn't create Blockchain for community ${communityDb.communityUuid}`)
}
context.logger.info(`Blockchain for community '${communityDb.uniqueAlias}' created`)
// make sure topic id is unique
let topicId: HieroId
let topicId: HieroId
do {
topicId = v.parse(hieroIdSchema, '0.0.' + Math.floor(Math.random() * 10000))
} while(topicIds.has(topicId))
} while (topicIds.has(topicId))
topicIds.add(topicId)
communities.set(communityDb.communityUuid, {
@ -38,7 +40,7 @@ async function bootstrapCommunities(context: Context): Promise<Map<string, Commu
topicId,
folder: communityDb.uniqueAlias.replace(/[^a-zA-Z0-9]/g, '_'),
})
generateKeyPairCommunity(communityDb, context.cache, topicId)
// create community root transaction 1 minute before first user
const creationDate = new Date(new Date(communityDb.userMinCreatedAt).getTime() - 1000 * 60)

View File

@ -1,10 +1,21 @@
import { InputTransactionType } from '../../data/InputTransactionType.enum'
import { CommunityDb, TransactionDb, CreatedUserDb, TransactionLinkDb } from './valibot.schema'
import { Community, communitySchema, transactionSchema, Transaction, TransactionInput } from '../../schemas/transaction.schema'
import { AccountType } from '../../data/AccountType.enum'
import { gradidoAmountSchema, HieroId, memoSchema, timeoutDurationSchema } from '../../schemas/typeGuard.schema'
import * as v from 'valibot'
import { AccountType } from '../../data/AccountType.enum'
import { InputTransactionType } from '../../data/InputTransactionType.enum'
import {
Community,
communitySchema,
Transaction,
TransactionInput,
transactionSchema,
} from '../../schemas/transaction.schema'
import {
gradidoAmountSchema,
HieroId,
memoSchema,
timeoutDurationSchema,
} from '../../schemas/typeGuard.schema'
import { TransactionTypeId } from './TransactionTypeId'
import { CommunityDb, CreatedUserDb, TransactionDb, TransactionLinkDb } from './valibot.schema'
export function getInputTransactionTypeFromTypeId(typeId: TransactionTypeId): InputTransactionType {
switch (typeId) {
@ -19,13 +30,17 @@ export function getInputTransactionTypeFromTypeId(typeId: TransactionTypeId): In
}
}
export function communityDbToCommunity(topicId: HieroId, communityDb: CommunityDb, creationDate: Date): Community {
return v.parse(communitySchema, {
export function communityDbToCommunity(
topicId: HieroId,
communityDb: CommunityDb,
creationDate: Date,
): Community {
return v.parse(communitySchema, {
hieroTopicId: topicId,
uuid: communityDb.communityUuid,
foreign: communityDb.foreign,
creationDate,
})
creationDate,
})
}
export function userDbToTransaction(userDb: CreatedUserDb, communityTopicId: HieroId): Transaction {
@ -41,17 +56,18 @@ export function userDbToTransaction(userDb: CreatedUserDb, communityTopicId: Hie
}
export function transactionDbToTransaction(
transactionDb: TransactionDb,
communityTopicId: HieroId,
recipientCommunityTopicId: HieroId
transactionDb: TransactionDb,
communityTopicId: HieroId,
recipientCommunityTopicId: HieroId,
): Transaction {
if (
transactionDb.typeId !== TransactionTypeId.CREATION
&& transactionDb.typeId !== TransactionTypeId.SEND
&& transactionDb.typeId !== TransactionTypeId.RECEIVE) {
transactionDb.typeId !== TransactionTypeId.CREATION &&
transactionDb.typeId !== TransactionTypeId.SEND &&
transactionDb.typeId !== TransactionTypeId.RECEIVE
) {
throw new Error('not implemented')
}
const user = {
communityTopicId: communityTopicId,
account: { userUuid: transactionDb.user.gradidoId },
@ -80,7 +96,9 @@ export function transactionDbToTransaction(
}
if (transactionDb.transactionLinkCode) {
if (transactionDb.typeId !== TransactionTypeId.RECEIVE) {
throw new Error('linked transaction which isn\'t receive, send will taken care of on link creation')
throw new Error(
"linked transaction which isn't receive, send will taken care of on link creation",
)
}
transaction.user = {
communityTopicId: recipientCommunityTopicId,
@ -91,7 +109,10 @@ export function transactionDbToTransaction(
return v.parse(transactionSchema, transaction)
}
export function transactionLinkDbToTransaction(transactionLinkDb: TransactionLinkDb, communityTopicId: HieroId): Transaction {
export function transactionLinkDbToTransaction(
transactionLinkDb: TransactionLinkDb,
communityTopicId: HieroId,
): Transaction {
return v.parse(transactionSchema, {
user: {
communityTopicId: communityTopicId,
@ -105,7 +126,11 @@ export function transactionLinkDbToTransaction(transactionLinkDb: TransactionLin
amount: v.parse(gradidoAmountSchema, transactionLinkDb.amount),
memo: v.parse(memoSchema, transactionLinkDb.memo),
createdAt: transactionLinkDb.createdAt,
timeoutDuration: v.parse(timeoutDurationSchema, Math.round((transactionLinkDb.validUntil.getTime() - transactionLinkDb.createdAt.getTime()) / 1000)),
timeoutDuration: v.parse(
timeoutDurationSchema,
Math.round(
(transactionLinkDb.validUntil.getTime() - transactionLinkDb.createdAt.getTime()) / 1000,
),
),
})
}

View File

@ -1,38 +1,46 @@
import { asc, eq, inArray, isNotNull, sql } from 'drizzle-orm'
import { alias } from 'drizzle-orm/mysql-core'
import { MySql2Database } from 'drizzle-orm/mysql2'
import { GradidoUnit } from 'gradido-blockchain-js'
import { getLogger } from 'log4js'
import * as v from 'valibot'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { getLogger } from 'log4js'
import { MySql2Database } from 'drizzle-orm/mysql2'
import { communitiesTable, transactionLinksTable, transactionsTable, usersTable } from './drizzle.schema'
import { asc, sql, eq, isNotNull, inArray } from 'drizzle-orm'
import { alias } from 'drizzle-orm/mysql-core'
import { GradidoUnit } from 'gradido-blockchain-js'
import {
CommunityDb,
communityDbSchema,
CreatedUserDb,
createdUserDbSchema,
TransactionDb,
transactionDbSchema,
TransactionLinkDb,
transactionLinkDbSchema
} from './valibot.schema'
import {
communitiesTable,
transactionLinksTable,
transactionsTable,
usersTable,
} from './drizzle.schema'
import { TransactionTypeId } from './TransactionTypeId'
import {
CommunityDb,
CreatedUserDb,
communityDbSchema,
createdUserDbSchema,
TransactionDb,
TransactionLinkDb,
transactionDbSchema,
transactionLinkDbSchema,
} from './valibot.schema'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.6.blockchain`)
const logger = getLogger(
`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.6.blockchain`,
)
// queries
export async function loadCommunities(db: MySql2Database): Promise<CommunityDb[]> {
const result = await db.select({
foreign: communitiesTable.foreign,
communityUuid: communitiesTable.communityUuid,
name: communitiesTable.name,
creationDate: communitiesTable.creationDate,
userMinCreatedAt: sql`MIN(${usersTable.createdAt})`,
})
.from(communitiesTable)
.leftJoin(usersTable, eq(communitiesTable.communityUuid, usersTable.communityUuid))
.where(isNotNull(communitiesTable.communityUuid))
.groupBy(communitiesTable.communityUuid)
const result = await db
.select({
foreign: communitiesTable.foreign,
communityUuid: communitiesTable.communityUuid,
name: communitiesTable.name,
creationDate: communitiesTable.creationDate,
userMinCreatedAt: sql`MIN(${usersTable.createdAt})`,
})
.from(communitiesTable)
.leftJoin(usersTable, eq(communitiesTable.communityUuid, usersTable.communityUuid))
.where(isNotNull(communitiesTable.communityUuid))
.groupBy(communitiesTable.communityUuid)
const communityNames = new Set<string>()
return result.map((row: any) => {
@ -49,33 +57,50 @@ export async function loadCommunities(db: MySql2Database): Promise<CommunityDb[]
})
}
export async function loadUsers(db: MySql2Database, offset: number, count: number): Promise<CreatedUserDb[]> {
const result = await db.select()
.from(usersTable)
.orderBy(asc(usersTable.createdAt))
.limit(count).offset(offset)
export async function loadUsers(
db: MySql2Database,
offset: number,
count: number,
): Promise<CreatedUserDb[]> {
const result = await db
.select()
.from(usersTable)
.orderBy(asc(usersTable.createdAt))
.limit(count)
.offset(offset)
return result.map((row: any) => {
return v.parse(createdUserDbSchema, row)
})
}
export async function loadTransactions(db: MySql2Database, offset: number, count: number): Promise<TransactionDb[]> {
export async function loadTransactions(
db: MySql2Database,
offset: number,
count: number,
): Promise<TransactionDb[]> {
const linkedUsers = alias(usersTable, 'linkedUser')
const result = await db.select({
transaction: transactionsTable,
user: usersTable,
linkedUser: linkedUsers,
transactionLink: transactionLinksTable,
})
.from(transactionsTable)
.where(inArray(transactionsTable.typeId, [TransactionTypeId.CREATION, TransactionTypeId.RECEIVE]))
.leftJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
.leftJoin(linkedUsers, eq(transactionsTable.linkedUserId, linkedUsers.id))
.leftJoin(transactionLinksTable, eq(transactionsTable.transactionLinkId, transactionLinksTable.id))
.orderBy(asc(transactionsTable.balanceDate))
.limit(count).offset(offset)
const result = await db
.select({
transaction: transactionsTable,
user: usersTable,
linkedUser: linkedUsers,
transactionLink: transactionLinksTable,
})
.from(transactionsTable)
.where(
inArray(transactionsTable.typeId, [TransactionTypeId.CREATION, TransactionTypeId.RECEIVE]),
)
.leftJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
.leftJoin(linkedUsers, eq(transactionsTable.linkedUserId, linkedUsers.id))
.leftJoin(
transactionLinksTable,
eq(transactionsTable.transactionLinkId, transactionLinksTable.id),
)
.orderBy(asc(transactionsTable.balanceDate))
.limit(count)
.offset(offset)
return result.map((row: any) => {
// console.log(row)
@ -83,13 +108,14 @@ export async function loadTransactions(db: MySql2Database, offset: number, count
const userCreatedAt = new Date(row.user.createdAt)
const linkedUserCreatedAd = new Date(row.linkedUser.createdAt)
const balanceDate = new Date(row.transaction.balanceDate)
if (userCreatedAt.getTime() > balanceDate.getTime() ||
linkedUserCreatedAd.getTime() > balanceDate.getTime()
){
if (
userCreatedAt.getTime() > balanceDate.getTime() ||
linkedUserCreatedAd.getTime() > balanceDate.getTime()
) {
logger.error(`table row: `, row)
throw new Error('at least one user was created after transaction balance date, logic error!')
}
let amount = GradidoUnit.fromString(row.transaction.amount)
if (row.transaction.typeId === TransactionTypeId.SEND) {
amount = amount.mul(new GradidoUnit(-1))
@ -111,12 +137,18 @@ export async function loadTransactions(db: MySql2Database, offset: number, count
})
}
export async function loadTransactionLinks(db: MySql2Database, offset: number, count: number): Promise<TransactionLinkDb[]> {
const result = await db.select()
.from(transactionLinksTable)
.leftJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.orderBy(asc(transactionLinksTable.createdAt))
.limit(count).offset(offset)
export async function loadTransactionLinks(
db: MySql2Database,
offset: number,
count: number,
): Promise<TransactionLinkDb[]> {
const result = await db
.select()
.from(transactionLinksTable)
.leftJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.orderBy(asc(transactionLinksTable.createdAt))
.limit(count)
.offset(offset)
return result.map((row: any) => {
return v.parse(transactionLinkDbSchema, {
@ -126,23 +158,29 @@ export async function loadTransactionLinks(db: MySql2Database, offset: number, c
})
}
export async function loadDeletedTransactionLinks(db: MySql2Database, offset: number, count: number): Promise<TransactionDb[]> {
const result = await db.select()
.from(transactionLinksTable)
.leftJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.where(isNotNull(transactionLinksTable.deletedAt))
.orderBy(asc(transactionLinksTable.deletedAt))
.limit(count).offset(offset)
export async function loadDeletedTransactionLinks(
db: MySql2Database,
offset: number,
count: number,
): Promise<TransactionDb[]> {
const result = await db
.select()
.from(transactionLinksTable)
.leftJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.where(isNotNull(transactionLinksTable.deletedAt))
.orderBy(asc(transactionLinksTable.deletedAt))
.limit(count)
.offset(offset)
return result.map((row: any) => {
return v.parse(transactionDbSchema, {
typeId: TransactionTypeId.RECEIVE,
amount: row.transaction_links.amount,
amount: row.transaction_links.amount,
balanceDate: new Date(row.transaction_links.deletedAt),
memo: row.transaction_links.memo,
transactionLinkCode: row.transaction_links.code,
user: row.users,
linkedUser: row.users
linkedUser: row.users,
})
})
}
}

View File

@ -1,50 +1,66 @@
import { mysqlTable, unique, int, varchar, char, datetime, tinyint, decimal, index } from 'drizzle-orm/mysql-core'
import { sql } from 'drizzle-orm'
import {
char,
datetime,
decimal,
index,
int,
mysqlTable,
tinyint,
unique,
varchar,
} from 'drizzle-orm/mysql-core'
// use only fields needed in the migration, after update the rest of the project, import database instead
export const communitiesTable = mysqlTable('communities', {
foreign: tinyint().default(1).notNull(),
communityUuid: char('community_uuid', { length: 36 }).default(sql`NULL`),
name: varchar({ length: 40 }).default(sql`NULL`),
creationDate: datetime('creation_date', { mode: 'string', fsp: 3 }).default(sql`NULL`),
},
(table) => [
unique('uuid_key').on(table.communityUuid),
])
export const communitiesTable = mysqlTable(
'communities',
{
foreign: tinyint().default(1).notNull(),
communityUuid: char('community_uuid', { length: 36 }).default(sql`NULL`),
name: varchar({ length: 40 }).default(sql`NULL`),
creationDate: datetime('creation_date', { mode: 'string', fsp: 3 }).default(sql`NULL`),
},
(table) => [unique('uuid_key').on(table.communityUuid)],
)
export const usersTable = mysqlTable('users', {
id: int().autoincrement().notNull(),
gradidoId: char('gradido_id', { length: 36 }).notNull(),
communityUuid: varchar('community_uuid', { length: 36 }).default(sql`NULL`),
createdAt: datetime('created_at', { mode: 'string', fsp: 3 }).default(sql`current_timestamp(3)`).notNull(),
},
(table) => [
unique('uuid_key').on(table.gradidoId, table.communityUuid),
])
export const usersTable = mysqlTable(
'users',
{
id: int().autoincrement().notNull(),
gradidoId: char('gradido_id', { length: 36 }).notNull(),
communityUuid: varchar('community_uuid', { length: 36 }).default(sql`NULL`),
createdAt: datetime('created_at', { mode: 'string', fsp: 3 })
.default(sql`current_timestamp(3)`)
.notNull(),
},
(table) => [unique('uuid_key').on(table.gradidoId, table.communityUuid)],
)
export const transactionsTable = mysqlTable('transactions', {
id: int().autoincrement().notNull(),
typeId: int('type_id').default(sql`NULL`),
transactionLinkId: int('transaction_link_id').default(sql`NULL`),
amount: decimal({ precision: 40, scale: 20 }).default(sql`NULL`),
balanceDate: datetime('balance_date', { mode: 'string', fsp: 3 }).default(sql`current_timestamp(3)`).notNull(),
memo: varchar({ length: 255 }).notNull(),
creationDate: datetime('creation_date', { mode: 'string', fsp: 3 }).default(sql`NULL`),
userId: int('user_id').notNull(),
linkedUserId: int('linked_user_id').default(sql`NULL`),
},
(table) => [
index('user_id').on(table.userId),
])
export const transactionsTable = mysqlTable(
'transactions',
{
id: int().autoincrement().notNull(),
typeId: int('type_id').default(sql`NULL`),
transactionLinkId: int('transaction_link_id').default(sql`NULL`),
amount: decimal({ precision: 40, scale: 20 }).default(sql`NULL`),
balanceDate: datetime('balance_date', { mode: 'string', fsp: 3 })
.default(sql`current_timestamp(3)`)
.notNull(),
memo: varchar({ length: 255 }).notNull(),
creationDate: datetime('creation_date', { mode: 'string', fsp: 3 }).default(sql`NULL`),
userId: int('user_id').notNull(),
linkedUserId: int('linked_user_id').default(sql`NULL`),
},
(table) => [index('user_id').on(table.userId)],
)
export const transactionLinksTable = mysqlTable('transaction_links', {
id: int().autoincrement().notNull(),
id: int().autoincrement().notNull(),
userId: int().notNull(),
amount: decimal({ precision: 40, scale: 20 }).notNull(),
memo: varchar({ length: 255 }).notNull(),
code: varchar({ length: 24 }).notNull(),
createdAt: datetime({ mode: 'string'}).notNull(),
deletedAt: datetime({ mode: 'string'}).default(sql`NULL`),
validUntil: datetime({ mode: 'string'}).notNull(),
amount: decimal({ precision: 40, scale: 20 }).notNull(),
memo: varchar({ length: 255 }).notNull(),
code: varchar({ length: 24 }).notNull(),
createdAt: datetime({ mode: 'string' }).notNull(),
deletedAt: datetime({ mode: 'string' }).default(sql`NULL`),
validUntil: datetime({ mode: 'string' }).notNull(),
})

View File

@ -1,8 +1,8 @@
import { bootstrap } from './bootstrap'
import { onShutdown } from '../../../../shared/src/helper/onShutdown'
import { syncDbWithBlockchainContext } from './interaction/syncDbWithBlockchain/syncDbWithBlockchain.context'
import { exportAllCommunities } from './binaryExport'
import { Filter } from 'gradido-blockchain-js'
import { onShutdown } from '../../../../shared/src/helper/onShutdown'
import { exportAllCommunities } from './binaryExport'
import { bootstrap } from './bootstrap'
import { syncDbWithBlockchainContext } from './interaction/syncDbWithBlockchain/syncDbWithBlockchain.context'
const BATCH_SIZE = 100
@ -11,11 +11,11 @@ async function main() {
const context = await bootstrap()
onShutdown(async (reason, error) => {
context.logger.info(`shutdown reason: ${reason}`)
if(error) {
if (error) {
context.logger.error(error)
}
})
// synchronize to in memory blockchain
await syncDbWithBlockchainContext(context, BATCH_SIZE)
@ -33,4 +33,4 @@ main().catch((e) => {
// biome-ignore lint/suspicious/noConsole: maybe logger isn't initialized here
console.error(e)
process.exit(1)
})
})

View File

@ -1,7 +1,7 @@
import { Context } from '../../Context'
import { Profiler } from 'gradido-blockchain-js'
import { getLogger, Logger } from 'log4js'
import { LOG4JS_BASE_CATEGORY } from '../../../../config/const'
import { Profiler } from 'gradido-blockchain-js'
import { Context } from '../../Context'
export abstract class AbstractSyncRole<T> {
private items: T[] = []
@ -9,17 +9,18 @@ export abstract class AbstractSyncRole<T> {
protected logger: Logger
constructor(protected readonly context: Context) {
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.5.interaction.syncDbWithBlockchain`)
this.logger = getLogger(
`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.5.interaction.syncDbWithBlockchain`,
)
}
abstract getDate(): Date
abstract loadFromDb(offset: number, count: number): Promise<T[]>
abstract pushToBlockchain(item: T): Promise<void>
abstract itemTypeName(): string
// return count of new loaded items
async ensureFilled(batchSize: number): Promise<number>
{
async ensureFilled(batchSize: number): Promise<number> {
if (this.items.length === 0) {
let timeUsed: Profiler | undefined
if (this.logger.isDebugEnabled()) {
@ -28,7 +29,9 @@ export abstract class AbstractSyncRole<T> {
this.items = await this.loadFromDb(this.offset, batchSize)
this.offset += this.items.length
if (timeUsed && this.items.length) {
this.logger.debug(`${timeUsed.string()} for loading ${this.items.length} ${this.itemTypeName()} from db`)
this.logger.debug(
`${timeUsed.string()} for loading ${this.items.length} ${this.itemTypeName()} from db`,
)
}
return this.items.length
}

View File

@ -1,6 +1,6 @@
import { loadDeletedTransactionLinks } from '../../database'
import { TransactionsSyncRole } from './TransactionsSync.role'
import { TransactionDb } from '../../valibot.schema'
import { TransactionsSyncRole } from './TransactionsSync.role'
export class DeletedTransactionLinksSyncRole extends TransactionsSyncRole {
itemTypeName(): string {
@ -10,4 +10,4 @@ export class DeletedTransactionLinksSyncRole extends TransactionsSyncRole {
async loadFromDb(offset: number, count: number): Promise<TransactionDb[]> {
return await loadDeletedTransactionLinks(this.context.db, offset, count)
}
}
}

View File

@ -1,7 +1,7 @@
import { TransactionLinkDb } from '../../valibot.schema'
import { loadTransactionLinks } from '../../database'
import { transactionLinkDbToTransaction } from '../../convert'
import { addTransaction } from '../../blockchain'
import { transactionLinkDbToTransaction } from '../../convert'
import { loadTransactionLinks } from '../../database'
import { TransactionLinkDb } from '../../valibot.schema'
import { AbstractSyncRole } from './AbstractSync.role'
export class TransactionLinksSyncRole extends AbstractSyncRole<TransactionLinkDb> {
@ -23,4 +23,3 @@ export class TransactionLinksSyncRole extends AbstractSyncRole<TransactionLinkDb
await addTransaction(communityContext.blockchain, communityContext.blockchain, transaction)
}
}

View File

@ -1,7 +1,7 @@
import { TransactionDb } from '../../valibot.schema'
import { loadTransactions } from '../../database'
import { transactionDbToTransaction } from '../../convert'
import { addTransaction } from '../../blockchain'
import { transactionDbToTransaction } from '../../convert'
import { loadTransactions } from '../../database'
import { TransactionDb } from '../../valibot.schema'
import { AbstractSyncRole } from './AbstractSync.role'
export class TransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
@ -19,10 +19,19 @@ export class TransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
async pushToBlockchain(item: TransactionDb): Promise<void> {
const senderCommunityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
const recipientCommunityContext = this.context.getCommunityContextByUuid(item.linkedUser.communityUuid)
const recipientCommunityContext = this.context.getCommunityContextByUuid(
item.linkedUser.communityUuid,
)
this.context.cache.setHomeCommunityTopicId(senderCommunityContext.topicId)
const transaction = transactionDbToTransaction(item, senderCommunityContext.topicId, recipientCommunityContext.topicId)
await addTransaction(senderCommunityContext.blockchain, recipientCommunityContext.blockchain, transaction)
const transaction = transactionDbToTransaction(
item,
senderCommunityContext.topicId,
recipientCommunityContext.topicId,
)
await addTransaction(
senderCommunityContext.blockchain,
recipientCommunityContext.blockchain,
transaction,
)
}
}

View File

@ -1,10 +1,9 @@
import { CreatedUserDb } from '../../valibot.schema'
import { AbstractSyncRole } from './AbstractSync.role'
import { addRegisterAddressTransaction } from '../../blockchain'
import { userDbToTransaction } from '../../convert'
import { loadUsers } from '../../database'
import { generateKeyPairUserAccount } from '../../keyPair'
import { userDbToTransaction } from '../../convert'
import { addRegisterAddressTransaction } from '../../blockchain'
import { CreatedUserDb } from '../../valibot.schema'
import { AbstractSyncRole } from './AbstractSync.role'
export class UsersSyncRole extends AbstractSyncRole<CreatedUserDb> {
getDate(): Date {

View File

@ -1,8 +1,8 @@
import { Context } from '../../Context'
import { Profiler } from 'gradido-blockchain-js'
import { TransactionsSyncRole } from './TransactionsSync.role'
import { Context } from '../../Context'
import { DeletedTransactionLinksSyncRole } from './DeletedTransactionLinksSync.role'
import { TransactionLinksSyncRole } from './TransactionLinksSync.role'
import { TransactionsSyncRole } from './TransactionsSync.role'
import { UsersSyncRole } from './UsersSync.role'
export async function syncDbWithBlockchainContext(context: Context, batchSize: number) {
@ -11,20 +11,20 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
new UsersSyncRole(context),
new TransactionsSyncRole(context),
new DeletedTransactionLinksSyncRole(context),
new TransactionLinksSyncRole(context)
]
new TransactionLinksSyncRole(context),
]
while (true) {
timeUsed.reset()
const results = await Promise.all(containers.map(c => c.ensureFilled(batchSize)))
while (true) {
timeUsed.reset()
const results = await Promise.all(containers.map((c) => c.ensureFilled(batchSize)))
const loadedItemsCount = results.reduce((acc, c) => acc + c, 0)
// log only, if at least one new item was loaded
if (loadedItemsCount && context.logger.isInfoEnabled()) {
context.logger.info(`${loadedItemsCount} new items loaded from db in ${timeUsed.string()}`)
}
// remove empty containers
const available = containers.filter(c => !c.isEmpty())
const available = containers.filter((c) => !c.isEmpty())
if (available.length === 0) {
break
}
@ -36,4 +36,3 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
await available[0].toBlockchain()
}
}

View File

@ -1,17 +1,21 @@
import { CommunityDb, UserDb } from './valibot.schema'
import { KeyPairCacheManager } from '../../cache/KeyPairCacheManager'
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
import { KeyPairEd25519, MemoryBlock, MemoryBlockPtr } from 'gradido-blockchain-js'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { KeyPairCacheManager } from '../../cache/KeyPairCacheManager'
import { CONFIG } from '../../config'
import { HieroId } from '../../schemas/typeGuard.schema'
import { UserKeyPairRole } from '../../interactions/resolveKeyPair/UserKeyPair.role'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
import { AccountKeyPairRole } from '../../interactions/resolveKeyPair/AccountKeyPair.role'
import { UserKeyPairRole } from '../../interactions/resolveKeyPair/UserKeyPair.role'
import { HieroId } from '../../schemas/typeGuard.schema'
import { CommunityDb, UserDb } from './valibot.schema'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.6.keyPair`)
export function generateKeyPairCommunity(community: CommunityDb, cache: KeyPairCacheManager, topicId: HieroId): void {
export function generateKeyPairCommunity(
community: CommunityDb,
cache: KeyPairCacheManager,
topicId: HieroId,
): void {
let seed: MemoryBlock | null = null
if (community.foreign) {
const randomBuffer = Buffer.alloc(32)
@ -32,33 +36,37 @@ export function generateKeyPairCommunity(community: CommunityDb, cache: KeyPairC
}
export async function generateKeyPairUserAccount(
user: UserDb,
cache: KeyPairCacheManager,
communityTopicId: HieroId
): Promise<{userKeyPair: MemoryBlockPtr, accountKeyPair: MemoryBlockPtr}> {
user: UserDb,
cache: KeyPairCacheManager,
communityTopicId: HieroId,
): Promise<{ userKeyPair: MemoryBlockPtr; accountKeyPair: MemoryBlockPtr }> {
const communityKeyPair = cache.findKeyPair(communityTopicId)!
const userKeyPairRole = new UserKeyPairRole(user.gradidoId, communityKeyPair)
const userKeyPairKey = new KeyPairIdentifierLogic({
communityTopicId: communityTopicId,
const userKeyPairKey = new KeyPairIdentifierLogic({
communityTopicId: communityTopicId,
account: {
userUuid: user.gradidoId,
accountNr: 0
}
}).getKey()
const userKeyPair = await cache.getKeyPair(userKeyPairKey, () => Promise.resolve(userKeyPairRole.generateKeyPair()))
const accountKeyPairRole = new AccountKeyPairRole(1, userKeyPair)
const accountKeyPairKey = new KeyPairIdentifierLogic({
communityTopicId: communityTopicId,
account: {
userUuid: user.gradidoId,
accountNr: 1
}
accountNr: 0,
},
}).getKey()
const accountKeyPair = await cache.getKeyPair(accountKeyPairKey, () => Promise.resolve(accountKeyPairRole.generateKeyPair()))
const userKeyPair = await cache.getKeyPair(userKeyPairKey, () =>
Promise.resolve(userKeyPairRole.generateKeyPair()),
)
const accountKeyPairRole = new AccountKeyPairRole(1, userKeyPair)
const accountKeyPairKey = new KeyPairIdentifierLogic({
communityTopicId: communityTopicId,
account: {
userUuid: user.gradidoId,
accountNr: 1,
},
}).getKey()
const accountKeyPair = await cache.getKeyPair(accountKeyPairKey, () =>
Promise.resolve(accountKeyPairRole.generateKeyPair()),
)
//logger.info(`Key Pairs for user and account added, user: ${userKeyPairKey}, account: ${accountKeyPairKey}`)
return {
userKeyPair: userKeyPair.getPublicKey()!,
accountKeyPair: accountKeyPair.getPublicKey()!
accountKeyPair: accountKeyPair.getPublicKey()!,
}
}
}

View File

@ -12,4 +12,4 @@ export function calculateOneHashStep(hash: Buffer, data: Buffer): Buffer<ArrayBu
const outputHash = Buffer.alloc(crypto_generichash_KEYBYTES, 0)
crypto_generichash_batch(outputHash, [hash, data])
return outputHash
}
}

View File

@ -1,21 +1,26 @@
import { memoSchema, uuidv4Schema, identifierSeedSchema, gradidoAmountSchema, hieroIdSchema } from '../../schemas/typeGuard.schema'
import { dateSchema, booleanSchema } from '../../schemas/typeConverter.schema'
import { TransactionTypeId } from './TransactionTypeId'
import { InMemoryBlockchain } from 'gradido-blockchain-js'
import * as v from 'valibot'
import { booleanSchema, dateSchema } from '../../schemas/typeConverter.schema'
import {
gradidoAmountSchema,
hieroIdSchema,
identifierSeedSchema,
memoSchema,
uuidv4Schema,
} from '../../schemas/typeGuard.schema'
import { TransactionTypeId } from './TransactionTypeId'
export const createdUserDbSchema = v.object({
gradidoId: uuidv4Schema,
communityUuid: uuidv4Schema,
communityUuid: uuidv4Schema,
createdAt: dateSchema,
})
export const userDbSchema = v.object({
gradidoId: uuidv4Schema,
communityUuid: uuidv4Schema,
communityUuid: uuidv4Schema,
})
export const transactionDbSchema = v.object({
typeId: v.enum(TransactionTypeId),
amount: gradidoAmountSchema,
@ -53,8 +58,7 @@ export const communityContextSchema = v.object({
v.string(),
v.minLength(1, 'expect string length >= 1'),
v.maxLength(255, 'expect string length <= 255'),
v.regex(/^[a-zA-Z0-9-_]+$/,
'expect string to be a valid (alphanumeric, _, -) folder name'),
v.regex(/^[a-zA-Z0-9-_]+$/, 'expect string to be a valid (alphanumeric, _, -) folder name'),
),
})
@ -63,4 +67,4 @@ export type UserDb = v.InferOutput<typeof userDbSchema>
export type CreatedUserDb = v.InferOutput<typeof createdUserDbSchema>
export type TransactionLinkDb = v.InferOutput<typeof transactionLinkDbSchema>
export type CommunityDb = v.InferOutput<typeof communityDbSchema>
export type CommunityContext = v.InferOutput<typeof communityContextSchema>
export type CommunityContext = v.InferOutput<typeof communityContextSchema>

View File

@ -29,10 +29,14 @@ export const dateSchema = v.pipe(
)
export const booleanSchema = v.pipe(
v.union([v.boolean('expect boolean type'), v.number('expect boolean number type'), v.string('expect boolean string type')]),
v.union([
v.boolean('expect boolean type'),
v.number('expect boolean number type'),
v.string('expect boolean string type'),
]),
v.transform<boolean | number | string, boolean>((input) => {
if (typeof input === 'number') {
return input != 0
return input !== 0
} else if (typeof input === 'string') {
return input === 'true'
}

View File

@ -43,4 +43,4 @@ export const printTimeDuration = (duration: number): string => {
return result
}
export const delay = promisify(setTimeout)
export const delay = promisify(setTimeout)