fix linting

This commit is contained in:
einhornimmond 2026-02-26 09:08:29 +01:00
parent 4025175cea
commit 31766b83ef
42 changed files with 1112 additions and 798 deletions

View File

@ -5,7 +5,11 @@ export class AccountIdentifier {
account?: CommunityAccountIdentifier
seed?: string // used for deferred transfers
constructor(communityTopicId: string, communityUuid: string, input: CommunityAccountIdentifier | string) {
constructor(
communityTopicId: string,
communityUuid: string,
input: CommunityAccountIdentifier | string,
) {
if (input instanceof CommunityAccountIdentifier) {
this.account = input
} else {

View File

@ -133,7 +133,11 @@ export class TransactionDraft {
sendingUser.community.communityUuid!,
new CommunityAccountIdentifier(sendingUser.gradidoID),
)
draft.linkedUser = new AccountIdentifier(senderUserTopic, sendingUser.community.communityUuid!, transactionLink.code)
draft.linkedUser = new AccountIdentifier(
senderUserTopic,
sendingUser.community.communityUuid!,
transactionLink.code,
)
draft.type = TransactionType.GRADIDO_DEFERRED_TRANSFER
draft.createdAt = createdAtOnlySeconds.toISOString()
draft.amount = transactionLink.amount.toString()

View File

@ -2,7 +2,12 @@ import { Paginated } from '@arg/Paginated'
import { EditCommunityInput } from '@input/EditCommunityInput'
import { AdminCommunityView } from '@model/AdminCommunityView'
import { Community } from '@model/Community'
import { Community as DbCommunity, getAuthorizedCommunities, getHomeCommunity, getReachableCommunities } from 'database'
import {
Community as DbCommunity,
getAuthorizedCommunities,
getHomeCommunity,
getReachableCommunities,
} from 'database'
import { updateAllDefinedAndChanged } from 'shared'
import { Arg, Args, Authorized, Mutation, Query, Resolver } from 'type-graphql'
import { RIGHTS } from '@/auth/RIGHTS'
@ -38,9 +43,9 @@ export class CommunityResolver {
@Query(() => [Community])
async authorizedCommunities(): Promise<Community[]> {
const dbCommunities: DbCommunity[] = await getAuthorizedCommunities({
// order by
// order by
foreign: 'ASC', // home community first
name: 'ASC',
name: 'ASC',
})
return dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom))
}

View File

@ -45,7 +45,6 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
* Fix 0: Update transaction links to match holdAvailableAmount with validUntil, because the old formula lead to incorrect values
*/
let sumCount = 0
let count = 0
let lastProcessedId = 0
const LIMIT = 200
@ -106,7 +105,6 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
`,
[updates.map((u) => u.id)],
)
sumCount += updates.length
}
count = rows.length
lastProcessedId = rows[rows.length - 1].id

View File

@ -103,13 +103,12 @@ export async function getNotReachableCommunities(
// return the home community and all communities which had at least once make it through the first handshake
export async function getAuthorizedCommunities(
order?: FindOptionsOrder<DbCommunity>,
): Promise<DbCommunity[]>
{
): Promise<DbCommunity[]> {
return await DbCommunity.find({
where: [
{ authenticatedAt: Not(IsNull()) }, // or
{ foreign: false }
],
order
{ foreign: false },
],
order,
})
}
}

View File

@ -7,10 +7,7 @@ import { exportCommunities } from '../client/GradidoNode/communities'
import { GradidoNodeProcess } from '../client/GradidoNode/GradidoNodeProcess'
import { HieroClient } from '../client/hiero/HieroClient'
import { CONFIG } from '../config'
import {
GRADIDO_NODE_HOME_FOLDER_NAME,
LOG4JS_BASE_CATEGORY,
} from '../config/const'
import { GRADIDO_NODE_HOME_FOLDER_NAME, LOG4JS_BASE_CATEGORY } from '../config/const'
import { checkFileExist, checkPathExist } from '../utils/filesystem'
import { isPortOpen } from '../utils/network'
import { AppContextClients } from './appContext'

View File

@ -77,10 +77,7 @@ export class KeyPairCacheManager {
return keyPair
}
public getKeyPairSync(
input: string,
createKeyPair: () => KeyPairEd25519,
): KeyPairEd25519 {
public getKeyPairSync(input: string, createKeyPair: () => KeyPairEd25519): KeyPairEd25519 {
const keyPair = this.cache.get(input)
if (!keyPair) {
const keyPair = createKeyPair()

View File

@ -8,7 +8,7 @@ import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { AddressType } from '../../data/AddressType.enum'
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema'
import { Hex32, Hex32Input, Uuidv4, hex32Schema } from '../../schemas/typeGuard.schema'
import { Hex32, Hex32Input, hex32Schema, Uuidv4 } from '../../schemas/typeGuard.schema'
import { isPortOpenRetry } from '../../utils/network'
import { GradidoNodeErrorCodes } from './GradidoNodeErrorCodes'
import {
@ -75,7 +75,10 @@ export class GradidoNodeClient {
const response = await this.rpcCall<{ transaction: string }>('getTransaction', parameter)
if (response.isSuccess()) {
// this.logger.debug('result: ', response.result.transaction)
return v.parse(confirmedTransactionSchema, { base64: response.result.transaction, communityId: parameter.communityId })
return v.parse(confirmedTransactionSchema, {
base64: response.result.transaction,
communityId: parameter.communityId,
})
}
if (response.isError()) {
if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) {
@ -100,7 +103,10 @@ export class GradidoNodeClient {
}
const response = await this.rpcCall<{ transaction: string }>('getLastTransaction', parameter)
if (response.isSuccess()) {
return v.parse(confirmedTransactionSchema, { base64: response.result.transaction, communityId: parameter.communityId })
return v.parse(confirmedTransactionSchema, {
base64: response.result.transaction,
communityId: parameter.communityId,
})
}
if (response.isError()) {
if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) {
@ -137,7 +143,10 @@ export class GradidoNodeClient {
parameter,
)
return result.transactions.map((transactionBase64) =>
v.parse(confirmedTransactionSchema, { base64: transactionBase64, communityId: parameter.communityId }),
v.parse(confirmedTransactionSchema, {
base64: transactionBase64,
communityId: parameter.communityId,
}),
)
}
@ -163,7 +172,10 @@ export class GradidoNodeClient {
parameter,
)
return response.transactions.map((transactionBase64) =>
v.parse(confirmedTransactionSchema, { base64: transactionBase64, communityId: parameter.communityId }),
v.parse(confirmedTransactionSchema, {
base64: transactionBase64,
communityId: parameter.communityId,
}),
)
}

View File

@ -1,3 +1,4 @@
import path from 'node:path'
import { Mutex } from 'async-mutex'
import { Subprocess, spawn } from 'bun'
import { getLogger, Logger } from 'log4js'
@ -10,7 +11,6 @@ import {
LOG4JS_BASE_CATEGORY,
} from '../../config/const'
import { delay } from '../../utils/time'
import path from 'node:path'
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
@ -48,14 +48,7 @@ export class GradidoNodeProcess {
const isWindows = process.platform === 'win32'
const binaryName = isWindows ? 'GradidoNode.exe' : 'GradidoNode'
return path.join(
__dirname,
'..',
'..',
'gradido_node',
'bin',
binaryName,
)
return path.join(__dirname, '..', '..', 'gradido_node', 'bin', binaryName)
}
public start() {

View File

@ -39,7 +39,6 @@ export async function ensureCommunitiesAvailable(communityTopicIds: HieroId[]):
export async function exportCommunities(homeFolder: string, client: BackendClient): Promise<void> {
const communities = await client.getAuthorizedCommunities()
console.log(`communities: ${JSON.stringify(communities, null, 2)}`)
const communitiesPath = path.join(homeFolder, 'communities.json')
checkPathExist(path.dirname(communitiesPath), true)
const communitiesForDltNodeServer: CommunityForDltNodeServer[] = []
@ -52,7 +51,7 @@ export async function exportCommunities(homeFolder: string, client: BackendClien
hieroTopicId: com.hieroTopicId,
alias: com.name,
// use only alpha-numeric chars for folder name
folder: toFolderName(com.uuid),
folder: toFolderName(com.uuid),
})
}
fs.writeFileSync(communitiesPath, JSON.stringify(communitiesForDltNodeServer, null, 2))

View File

@ -1,14 +1,14 @@
import { beforeAll, describe, expect, it } from 'bun:test'
import { v4 as uuidv4 } from 'uuid'
import * as v from 'valibot'
import {
HieroTransactionIdString,
Uuidv4,
hieroIdSchema,
hieroTransactionIdStringSchema,
Uuidv4,
uuidv4Schema,
} from '../../schemas/typeGuard.schema'
import { transactionIdentifierSchema } from './input.schema'
import { v4 as uuidv4 } from 'uuid'
let communityId: Uuidv4
const uuidv4String = uuidv4()

View File

@ -1,5 +1,5 @@
import * as v from 'valibot'
import { uuidv4Schema, hieroTransactionIdStringSchema } from '../../schemas/typeGuard.schema'
import { hieroTransactionIdStringSchema, uuidv4Schema } from '../../schemas/typeGuard.schema'
export const transactionsRangeSchema = v.object({
// default value is 1, from first transactions

View File

@ -53,5 +53,3 @@ export const getAuthorizedCommunities = gql`
}
${communityFragment}
`

View File

@ -17,9 +17,7 @@ export function deriveFromCode(code: string): KeyPairEd25519 {
const hash = new MemoryBlock(code).calculateHash()
const keyPair = KeyPairEd25519.create(hash)
if (!keyPair) {
throw new ParameterError(
`error creating Ed25519 KeyPair from seed: ${code.substring(0, 5)}...`,
)
throw new ParameterError(`error creating Ed25519 KeyPair from seed: ${code.substring(0, 5)}...`)
}
return keyPair
}
@ -31,7 +29,10 @@ export function deriveFromKeyPairAndUuid(keyPair: KeyPairEd25519, uuid: Uuidv4):
parts[i] = hardenDerivationIndex(wholeHex.subarray(i * 4, (i + 1) * 4).readUInt32BE())
}
// parts: [2206563009, 2629978174, 2324817329, 2405141782]
return parts.reduce((keyPair: KeyPairEd25519, node: number) => deriveFromKeyPairAndIndex(keyPair, node), keyPair)
return parts.reduce(
(keyPair: KeyPairEd25519, node: number) => deriveFromKeyPairAndIndex(keyPair, node),
keyPair,
)
}
export function deriveFromKeyPairAndIndex(keyPair: KeyPairEd25519, index: number): KeyPairEd25519 {
@ -42,4 +43,4 @@ export function deriveFromKeyPairAndIndex(keyPair: KeyPairEd25519, index: number
)
}
return localKeyPair
}
}

View File

@ -35,10 +35,12 @@ export class RegisterAddressTransactionRole extends AbstractTransactionRole {
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
const builder = new GradidoTransactionBuilder()
const communityTopicId = this.registerAddressTransaction.user.communityTopicId
const communityKeyPair = await ResolveKeyPair(new KeyPairIdentifierLogic({
communityTopicId,
communityId: this.registerAddressTransaction.user.communityId,
}))
const communityKeyPair = await ResolveKeyPair(
new KeyPairIdentifierLogic({
communityTopicId,
communityId: this.registerAddressTransaction.user.communityId,
}),
)
const keyPairIdentifier = this.registerAddressTransaction.user
// when accountNr is 0 it is the user account
keyPairIdentifier.account.accountNr = 0

View File

@ -65,7 +65,9 @@ export async function SendToHieroContext(
)
// attach Hiero transaction ID to the builder for the inbound transaction
builder.setParentLedgerAnchor(new LedgerAnchor(new HieroTransactionId(outboundHieroTransactionIdString)))
builder.setParentLedgerAnchor(
new LedgerAnchor(new HieroTransactionId(outboundHieroTransactionIdString)),
)
// build and validate inbound transaction
const inboundTransaction = builder.buildInbound()
@ -103,7 +105,7 @@ function validate(transaction: GradidoTransaction): void {
async function sendViaHiero(
gradidoTransaction: GradidoTransaction,
topic: HieroId,
communityId: Uuidv4
communityId: Uuidv4,
): Promise<HieroTransactionIdString> {
const client = HieroClient.getInstance()
const transactionId = await client.sendMessage(topic, communityId, gradidoTransaction)

View File

@ -23,7 +23,12 @@ export class Context {
public balanceFixGradidoUser: UserDb | null
private timeUsed: Profiler
constructor(logger: Logger, db: MySql2Database, cache: KeyPairCacheManager, balanceFixGradidoUser: UserDb | null) {
constructor(
logger: Logger,
db: MySql2Database,
cache: KeyPairCacheManager,
balanceFixGradidoUser: UserDb | null,
) {
this.logger = logger
this.db = db
this.cache = cache
@ -42,23 +47,23 @@ export class Context {
database: CONFIG.MYSQL_DATABASE,
port: CONFIG.MYSQL_PORT,
})
const db = drizzle({ client: connection })
const db = drizzle({ client: connection })
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.5`)
let balanceFixGradidoUser: UserDb | null = null
if (process.env.MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID) {
balanceFixGradidoUser = await loadUserByGradidoId(db, process.env.MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID)
balanceFixGradidoUser = await loadUserByGradidoId(
db,
process.env.MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID,
)
if (!balanceFixGradidoUser) {
logger.error(`MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID was set to ${process.env.MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID} but user not found`)
logger.error(
`MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID was set to ${process.env.MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID} but user not found`,
)
}
} else {
logger.debug(`MIGRATION_ACCOUNT_BALANCE_FIX_GRADIDO_ID was not set`)
}
return new Context(
logger,
db,
KeyPairCacheManager.getInstance(),
balanceFixGradidoUser,
)
return new Context(logger, db, KeyPairCacheManager.getInstance(), balanceFixGradidoUser)
}
getCommunityContextByUuid(communityUuid: Uuidv4): CommunityContext {

View File

@ -43,7 +43,9 @@ export function exportCommunity(
const isDebug = context.logger.isDebugEnabled()
const printConsole = () => {
if (triggeredTransactionsCount > 0) {
process.stdout.write(`exported ${count} transactions + ${triggeredTransactionsCount} triggered from timeouted transaction links\r`)
process.stdout.write(
`exported ${count} transactions + ${triggeredTransactionsCount} triggered from timeouted transaction links\r`,
)
} else {
process.stdout.write(`exported ${count} transactions\r`)
}
@ -59,7 +61,12 @@ export function exportCommunity(
throw new Error(`invalid TransactionEntry at index: ${transactionNr} `)
}
hash = exportTransaction(confirmedTransaction, hash, binFilePath)
if (confirmedTransaction?.getGradidoTransaction()?.getTransactionBody()?.isTimeoutDeferredTransfer()) {
if (
confirmedTransaction
?.getGradidoTransaction()
?.getTransactionBody()
?.isTimeoutDeferredTransfer()
) {
triggeredTransactionsCount++
} else {
count++
@ -77,7 +84,7 @@ export function exportCommunity(
}
}
}
f.pagination.page++
f.pagination.page++
} while (lastTransactionCount === batchSize)
printConsole()
process.stdout.write(`\n`)
@ -86,7 +93,7 @@ export function exportCommunity(
context.logger.info(
`binary file for community ${communityContext.communityId} written to ${binFilePath}`,
)
const sumTransactionsCount = ((f.pagination.page - 2) * batchSize) + lastTransactionCount
const sumTransactionsCount = (f.pagination.page - 2) * batchSize + lastTransactionCount
const fileSize = fs.statSync(binFilePath).size
context.logger.info(
`exported ${sumTransactionsCount} transactions (${bytesString(fileSize)}) in ${timeUsed.string()}`,

View File

@ -11,7 +11,7 @@ import { NotEnoughGradidoBalanceError } from './errors'
export const defaultHieroAccount = new HieroAccountId(0, 0, 2)
export let callTime: number = 0
const timeUsed = new Profiler
const timeUsed = new Profiler()
export function addToBlockchain(
transaction: GradidoTransaction,
@ -19,7 +19,7 @@ export function addToBlockchain(
ledgerAnchor: LedgerAnchor,
accountBalances: AccountBalances,
): boolean {
try {
try {
timeUsed.reset()
const result = blockchain.createAndAddConfirmedTransactionExternFast(
transaction,
@ -30,7 +30,9 @@ export function addToBlockchain(
return result
} catch (error) {
if (error instanceof Error) {
const matches = error.message.match(/not enough Gradido Balance for (send coins|operation), needed: -?(\d+\.\d+), exist: (\d+\.\d+)/)
const matches = error.message.match(
/not enough Gradido Balance for (send coins|operation), needed: -?(\d+\.\d+), exist: (\d+\.\d+)/,
)
if (matches) {
const needed = parseFloat(matches[2])
const exist = parseFloat(matches[3])
@ -40,8 +42,9 @@ export function addToBlockchain(
// const wekingheim = InMemoryBlockchainProvider.getInstance().getBlockchain('wekingheim')
// const lastTransactionw = wekingheim?.findOne(Filter.LAST_TRANSACTION)
const lastTransaction = blockchain.findOne(Filter.LAST_TRANSACTION)
throw new Error(`Transaction ${transaction.toJson(true)} not added: ${error}, last transaction was: ${lastTransaction?.getConfirmedTransaction()?.toJson(true)}`)
const lastTransaction = blockchain.findOne(Filter.LAST_TRANSACTION)
throw new Error(
`Transaction ${transaction.toJson(true)} not added: ${error}, last transaction was: ${lastTransaction?.getConfirmedTransaction()?.toJson(true)}`,
)
}
}

View File

@ -1,23 +1,36 @@
import { randomBytes } from 'node:crypto'
import { AccountBalances, GradidoTransactionBuilder, InMemoryBlockchainProvider, LedgerAnchor } from 'gradido-blockchain-js'
import {
AccountBalances,
GradidoTransactionBuilder,
InMemoryBlockchainProvider,
LedgerAnchor,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { CONFIG } from '../../config'
import { deriveFromSeed } from '../../data/deriveKeyPair'
import { Hex32, hex32Schema } from '../../schemas/typeGuard.schema'
import { AUF_ACCOUNT_DERIVATION_INDEX, GMW_ACCOUNT_DERIVATION_INDEX, hardenDerivationIndex } from '../../utils/derivationHelper'
import {
AUF_ACCOUNT_DERIVATION_INDEX,
GMW_ACCOUNT_DERIVATION_INDEX,
hardenDerivationIndex,
} from '../../utils/derivationHelper'
import { toFolderName } from '../../utils/filesystem'
import { addToBlockchain } from './blockchain'
import { Context } from './Context'
import { Balance } from './data/Balance'
import { loadAdminUsersCache, loadCommunities, loadContributionLinkModeratorCache } from './database'
import {
loadAdminUsersCache,
loadCommunities,
loadContributionLinkModeratorCache,
} from './database'
import { CommunityContext } from './valibot.schema'
import { toFolderName } from '../../utils/filesystem'
export async function bootstrap(): Promise<Context> {
const context = await Context.create()
context.communities = await bootstrapCommunities(context)
await Promise.all([
loadContributionLinkModeratorCache(context.db),
loadAdminUsersCache(context.db)
loadAdminUsersCache(context.db),
])
return context
}
@ -39,17 +52,23 @@ async function bootstrapCommunities(context: Context): Promise<Map<string, Commu
} else {
seed = v.parse(hex32Schema, randomBytes(32).toString('hex'))
}
let creationDate = communityDb.creationDate
if (communityDb.userMinCreatedAt && communityDb.userMinCreatedAt < communityDb.creationDate) {
// create community root transaction 1 minute before first user
creationDate = new Date(new Date(communityDb.userMinCreatedAt).getTime() - 1000 * 60)
}
const communityKeyPair = deriveFromSeed(seed)
const gmwKeyPair = communityKeyPair.deriveChild(hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX))
const aufKeyPair = communityKeyPair.deriveChild(hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX))
const gmwKeyPair = communityKeyPair.deriveChild(
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
)
const aufKeyPair = communityKeyPair.deriveChild(
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
)
if (!communityKeyPair || !gmwKeyPair || !aufKeyPair) {
throw new Error(`Error on creating key pair for community ${JSON.stringify(communityDb, null, 2)}`)
throw new Error(
`Error on creating key pair for community ${JSON.stringify(communityDb, null, 2)}`,
)
}
const builder = new GradidoTransactionBuilder()
builder
@ -59,7 +78,7 @@ async function bootstrapCommunities(context: Context): Promise<Map<string, Commu
communityKeyPair.getPublicKey(),
gmwKeyPair.getPublicKey(),
aufKeyPair.getPublicKey(),
)
)
.sign(communityKeyPair)
const communityContext: CommunityContext = {

View File

@ -1,96 +1,104 @@
import Decimal from 'decimal.js-light'
import { AccountBalance, GradidoUnit, MemoryBlockPtr } from 'gradido-blockchain-js'
import { legacyCalculateDecay } from '../utils'
import { NegativeBalanceError } from '../errors'
import { legacyCalculateDecay } from '../utils'
export class Balance {
private balance: GradidoUnit
private date: Date
private publicKey: MemoryBlockPtr
private communityId: string
private balance: GradidoUnit
private date: Date
private publicKey: MemoryBlockPtr
private communityId: string
constructor(publicKey: MemoryBlockPtr, communityId: string) {
this.balance = new GradidoUnit(0)
this.date = new Date()
this.publicKey = publicKey
this.communityId = communityId
constructor(publicKey: MemoryBlockPtr, communityId: string) {
this.balance = new GradidoUnit(0)
this.date = new Date()
this.publicKey = publicKey
this.communityId = communityId
}
static fromAccountBalance(
accountBalance: AccountBalance,
confirmedAt: Date,
communityId: string,
): Balance {
const balance = new Balance(accountBalance.getPublicKey()!, communityId)
balance.update(accountBalance.getBalance(), confirmedAt)
return balance
}
getBalance(): GradidoUnit {
return this.balance
}
getDate(): Date {
return this.date
}
updateLegacyDecay(amount: GradidoUnit, date: Date) {
// make sure to copy instead of referencing
const previousBalanceString = this.balance.toString()
const previousDate = new Date(this.date.getTime())
if (this.balance.equal(GradidoUnit.zero())) {
this.balance = amount
this.date = date
} else {
const decayedBalance = legacyCalculateDecay(
new Decimal(this.balance.toString()),
this.date,
date,
).toDecimalPlaces(4, Decimal.ROUND_CEIL)
const newBalance = decayedBalance.add(new Decimal(amount.toString()))
this.balance = GradidoUnit.fromString(newBalance.toString())
this.date = date
}
static fromAccountBalance(accountBalance: AccountBalance, confirmedAt: Date, communityId: string): Balance {
const balance = new Balance(accountBalance.getPublicKey()!, communityId)
balance.update(accountBalance.getBalance(), confirmedAt)
return balance
if (this.balance.lt(GradidoUnit.zero())) {
if (this.balance.lt(GradidoUnit.fromGradidoCent(100).negated())) {
const previousDecayedBalance = legacyCalculateDecay(
new Decimal(previousBalanceString),
previousDate,
date,
)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.updateLegacyDecay`,
previousBalanceString,
amount.toString(),
previousDecayedBalance.toString(),
)
} else {
this.balance = GradidoUnit.zero()
}
}
}
getBalance(): GradidoUnit {
return this.balance
update(amount: GradidoUnit, date: Date) {
const previousBalance = new GradidoUnit(this.balance.toString())
const previousDate = new Date(this.date.getTime())
if (this.balance.equal(GradidoUnit.zero())) {
this.balance = amount
this.date = date
} else {
this.balance = this.balance.calculateDecay(this.date, date).add(amount)
this.date = date
}
getDate(): Date {
return this.date
if (this.balance.lt(GradidoUnit.zero())) {
// ignore diffs less than a gradido cent
if (this.balance.lt(GradidoUnit.fromGradidoCent(100).negated())) {
const previousDecayedBalance = this.balance.calculateDecay(previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.update`,
previousBalance.toString(),
amount.toString(),
previousDecayedBalance.toString(),
)
} else {
this.balance = GradidoUnit.zero()
}
}
}
updateLegacyDecay(amount: GradidoUnit, date: Date) {
// make sure to copy instead of referencing
const previousBalanceString = this.balance.toString()
const previousDate = new Date(this.date.getTime())
if (this.balance.equal(GradidoUnit.zero())) {
this.balance = amount
this.date = date
} else {
const decayedBalance = legacyCalculateDecay(new Decimal(this.balance.toString()), this.date, date ).toDecimalPlaces(4, Decimal.ROUND_CEIL)
const newBalance = decayedBalance.add(new Decimal(amount.toString()))
this.balance = GradidoUnit.fromString(newBalance.toString())
this.date = date
}
if (this.balance.lt(GradidoUnit.zero())) {
if (this.balance.lt(GradidoUnit.fromGradidoCent(100).negated())) {
const previousDecayedBalance = legacyCalculateDecay(new Decimal(previousBalanceString), previousDate, date)
const rounded = previousDecayedBalance.toDecimalPlaces(4, Decimal.ROUND_CEIL)
console.log(`rounded: ${rounded}}`)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.updateLegacyDecay`,
previousBalanceString,
amount.toString(),
previousDecayedBalance.toString(),
)
} else {
this.balance = GradidoUnit.zero()
}
}
}
update(amount: GradidoUnit, date: Date) {
const previousBalance = new GradidoUnit(this.balance.toString())
const previousDate = new Date(this.date.getTime())
if (this.balance.equal(GradidoUnit.zero())) {
this.balance = amount
this.date = date
} else {
this.balance = this.balance
.calculateDecay(this.date, date)
.add(amount)
this.date = date
}
if (this.balance.lt(GradidoUnit.zero())) {
// ignore diffs less than a gradido cent
if (this.balance.lt(GradidoUnit.fromGradidoCent(100).negated())) {
const previousDecayedBalance = this.balance.calculateDecay(previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.update`,
previousBalance.toString(),
amount.toString(),
previousDecayedBalance.toString(),
)
} else {
this.balance = GradidoUnit.zero()
}
}
}
getAccountBalance(): AccountBalance {
return new AccountBalance(this.publicKey, this.balance, this.communityId)
}
getAccountBalance(): AccountBalance {
return new AccountBalance(this.publicKey, this.balance, this.communityId)
}
}

View File

@ -30,7 +30,10 @@ export function generateKeyPairCommunity(
if (!keyPair) {
throw new Error(`Couldn't create key pair for community ${community.communityUuid}`)
}
const communityKeyPairKey = new KeyPairIdentifierLogic({ communityTopicId: topicId, communityId: community.communityUuid }).getKey()
const communityKeyPairKey = new KeyPairIdentifierLogic({
communityTopicId: topicId,
communityId: community.communityUuid,
}).getKey()
cache.addKeyPair(communityKeyPairKey, keyPair)
logger.info(`Community Key Pair added with key: ${communityKeyPairKey}`)
}

View File

@ -1,18 +1,8 @@
import { asc, eq, isNotNull, sql } from 'drizzle-orm'
import { MySql2Database } from 'drizzle-orm/mysql2'
import * as v from 'valibot'
import {
communitiesTable,
eventsTable,
userRolesTable,
usersTable
} from './drizzle.schema'
import {
CommunityDb,
UserDb,
communityDbSchema,
userDbSchema,
} from './valibot.schema'
import { communitiesTable, eventsTable, userRolesTable, usersTable } from './drizzle.schema'
import { CommunityDb, communityDbSchema, UserDb, userDbSchema } from './valibot.schema'
export const contributionLinkModerators = new Map<number, UserDb>()
export const adminUsers = new Map<string, UserDb>()
@ -29,7 +19,10 @@ export async function loadContributionLinkModeratorCache(db: MySql2Database): Pr
.orderBy(asc(eventsTable.id))
result.map((row: any) => {
contributionLinkModerators.set(row.event.involvedContributionLinkId, v.parse(userDbSchema, row.user))
contributionLinkModerators.set(
row.event.involvedContributionLinkId,
v.parse(userDbSchema, row.user),
)
})
}
@ -69,7 +62,10 @@ export async function loadCommunities(db: MySql2Database): Promise<CommunityDb[]
})
}
export async function loadUserByGradidoId(db: MySql2Database, gradidoId: string): Promise<UserDb | null> {
export async function loadUserByGradidoId(
db: MySql2Database,
gradidoId: string,
): Promise<UserDb | null> {
const result = await db
.select()
.from(usersTable)
@ -78,4 +74,3 @@ export async function loadUserByGradidoId(db: MySql2Database, gradidoId: string)
return result.length ? v.parse(userDbSchema, result[0]) : null
}

View File

@ -25,23 +25,23 @@ export const communitiesTable = mysqlTable(
)
export const contributionsTable = mysqlTable('contributions', {
id: int().autoincrement().notNull(),
id: int().autoincrement().notNull(),
userId: int('user_id').default(sql`NULL`),
contributionDate: datetime("contribution_date", { mode: 'string'}).default(sql`NULL`),
memo: varchar({ length: 512 }).notNull(),
amount: decimal({ precision: 40, scale: 20 }).notNull(),
contributionLinkId: int('contribution_link_id').default(sql`NULL`),
confirmedBy: int('confirmed_by').default(sql`NULL`),
confirmedAt: datetime('confirmed_at', { mode: 'string'}).default(sql`NULL`),
contributionStatus: varchar('contribution_status', { length: 12 }).default('\'PENDING\'').notNull(),
transactionId: int('transaction_id').default(sql`NULL`),
contributionDate: datetime('contribution_date', { mode: 'string' }).default(sql`NULL`),
memo: varchar({ length: 512 }).notNull(),
amount: decimal({ precision: 40, scale: 20 }).notNull(),
contributionLinkId: int('contribution_link_id').default(sql`NULL`),
confirmedBy: int('confirmed_by').default(sql`NULL`),
confirmedAt: datetime('confirmed_at', { mode: 'string' }).default(sql`NULL`),
contributionStatus: varchar('contribution_status', { length: 12 }).default("'PENDING'").notNull(),
transactionId: int('transaction_id').default(sql`NULL`),
})
export const eventsTable = mysqlTable('events', {
id: int().autoincrement().notNull(),
type: varchar({ length: 100 }).notNull(),
actingUserId: int('acting_user_id').notNull(),
involvedContributionLinkId: int('involved_contribution_link_id').default(sql`NULL`),
id: int().autoincrement().notNull(),
type: varchar({ length: 100 }).notNull(),
actingUserId: int('acting_user_id').notNull(),
involvedContributionLinkId: int('involved_contribution_link_id').default(sql`NULL`),
})
export const usersTable = mysqlTable(
@ -55,19 +55,19 @@ export const usersTable = mysqlTable(
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 userRolesTable = mysqlTable('user_roles', {
id: int().autoincrement().notNull(),
userId: int('user_id').notNull(),
role: varchar({ length: 40 }).notNull(),
},
(table) => [
index('user_id').on(table.userId),
])
export const userRolesTable = mysqlTable(
'user_roles',
{
id: int().autoincrement().notNull(),
userId: int('user_id').notNull(),
role: varchar({ length: 40 }).notNull(),
},
(table) => [index('user_id').on(table.userId)],
)
export const transactionsTable = mysqlTable(
'transactions',
@ -84,8 +84,8 @@ export const transactionsTable = mysqlTable(
creationDate: datetime('creation_date', { mode: 'string', fsp: 3 }).default(sql`NULL`),
userId: int('user_id').notNull(),
linkedUserId: int('linked_user_id').default(sql`NULL`),
linkedUserCommunityUuid: char("linked_user_community_uuid", { length: 36 }).default(sql`NULL`),
linkedUserGradidoId: char("linked_user_gradido_id", { length: 36 }).default(sql`NULL`),
linkedUserCommunityUuid: char('linked_user_community_uuid', { length: 36 }).default(sql`NULL`),
linkedUserGradidoId: char('linked_user_gradido_id', { length: 36 }).default(sql`NULL`),
linkedTransactionId: int('linked_transaction_id').default(sql`NULL`),
},
(table) => [index('user_id').on(table.userId)],
@ -95,12 +95,12 @@ export const transactionLinksTable = mysqlTable('transaction_links', {
id: int().autoincrement().notNull(),
userId: int().notNull(),
amount: decimal({ precision: 40, scale: 20 }).notNull(),
holdAvailableAmount: decimal("hold_available_amount", { precision: 40, scale: 20 }).notNull(),
holdAvailableAmount: decimal('hold_available_amount', { 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(),
redeemedAt: datetime({ mode: 'string'}).default(sql`NULL`),
redeemedBy: int().default(sql`NULL`),
redeemedAt: datetime({ mode: 'string' }).default(sql`NULL`),
redeemedBy: int().default(sql`NULL`),
})

View File

@ -1,8 +1,13 @@
import * as v from 'valibot'
export class NotEnoughGradidoBalanceError extends Error {
constructor(public needed: number, public exist: number) {
super(`Not enough Gradido Balance for send coins, needed: ${needed} Gradido, exist: ${exist} Gradido`)
constructor(
public needed: number,
public exist: number,
) {
super(
`Not enough Gradido Balance for send coins, needed: ${needed} Gradido, exist: ${exist} Gradido`,
)
this.name = 'NotEnoughGradidoBalanceError'
}
}
@ -46,7 +51,12 @@ export class BlockchainError extends Error {
}
export class NegativeBalanceError extends Error {
constructor(message: string, previousBalanceString: string, amount: string, previousDecayedBalance: string) {
constructor(
message: string,
previousBalanceString: string,
amount: string,
previousDecayedBalance: string,
) {
const parts: string[] = [`NegativeBalanceError in ${message}`]
parts.push(`Previous balance: ${previousBalanceString}`)
parts.push(`Amount: ${amount}`)
@ -54,4 +64,4 @@ export class NegativeBalanceError extends Error {
super(parts.join('\n'))
this.name = 'NegativeBalanceError'
}
}
}

View File

@ -1,15 +1,16 @@
import { Filter, Profiler, ThreadingPolicy_Half, verifySignatures } 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'
import { Filter, Profiler, ThreadingPolicy_Half, verifySignatures } from 'gradido-blockchain-js'
// import { hello } from '../../../zig/hello.zig'
// import { hello } from '../../../zig/hello.zig'
const BATCH_SIZE = 1000
async function main() {
// hello()
// return
// return
// prepare in memory blockchains
const context = await bootstrap()
onShutdown(async (reason, error) => {
@ -22,8 +23,8 @@ async function main() {
// synchronize to in memory blockchain
try {
await syncDbWithBlockchainContext(context, BATCH_SIZE)
} catch(e) {
console.error(e)
} catch (e) {
context.logger.error(e)
//context.logBlogchain(v.parse(uuidv4Schema, 'e70da33e-5976-4767-bade-aa4e4fa1c01a'))
}
@ -31,9 +32,15 @@ async function main() {
// bulk verify transaction signatures
for (const communityContext of context.communities.values()) {
// verifySignatures(Filter.ALL_TRANSACTIONS, ThreadingPolicy_Half)
const result = verifySignatures(Filter.ALL_TRANSACTIONS, communityContext.communityId, ThreadingPolicy_Half)
if(!result.isEmpty()){
throw new Error(`Verification of signatures failed for community ${communityContext.communityId}`)
const result = verifySignatures(
Filter.ALL_TRANSACTIONS,
communityContext.communityId,
ThreadingPolicy_Half,
)
if (!result.isEmpty()) {
throw new Error(
`Verification of signatures failed for community ${communityContext.communityId}`,
)
}
}
context.logger.info(`verified in ${timeUsed.string()}`)

View File

@ -1,12 +1,12 @@
import {
AccountBalances,
Filter,
InMemoryBlockchain,
KeyPairEd25519,
MemoryBlockPtr,
Profiler,
SearchDirection_DESC,
GradidoTransactionBuilder
import {
AccountBalances,
Filter,
GradidoTransactionBuilder,
InMemoryBlockchain,
KeyPairEd25519,
MemoryBlockPtr,
Profiler,
SearchDirection_DESC,
} from 'gradido-blockchain-js'
import { getLogger, Logger } from 'log4js'
import { LOG4JS_BASE_CATEGORY } from '../../../../config/const'
@ -21,7 +21,7 @@ export type IndexType = {
id: number
}
export let nanosBalanceForUser = 0
const lastBalanceOfUserTimeUsed = new Profiler
const lastBalanceOfUserTimeUsed = new Profiler()
export abstract class AbstractSyncRole<ItemType> {
private items: ItemType[] = []
@ -40,16 +40,22 @@ export abstract class AbstractSyncRole<ItemType> {
getAccountKeyPair(communityContext: CommunityContext, gradidoId: Uuidv4): KeyPairEd25519 {
return this.context.cache.getKeyPairSync(gradidoId, () => {
return deriveFromKeyPairAndIndex(deriveFromKeyPairAndUuid(communityContext.keyPair, gradidoId), 1)
return deriveFromKeyPairAndIndex(
deriveFromKeyPairAndUuid(communityContext.keyPair, gradidoId),
1,
)
})
}
getLastBalanceForUser(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, communityId: string
getLastBalanceForUser(
publicKey: MemoryBlockPtr,
blockchain: InMemoryBlockchain,
communityId: string,
): Balance {
lastBalanceOfUserTimeUsed.reset()
if (publicKey.isEmpty()) {
throw new Error('publicKey is empty')
}
}
const f = Filter.lastBalanceFor(publicKey)
f.setCommunityId(communityId)
const lastSenderTransaction = blockchain.findOne(f)
@ -58,19 +64,31 @@ export abstract class AbstractSyncRole<ItemType> {
}
const lastConfirmedTransaction = lastSenderTransaction.getConfirmedTransaction()
if (!lastConfirmedTransaction) {
throw new Error(`invalid transaction, getConfirmedTransaction call failed for transaction nr: ${lastSenderTransaction.getTransactionNr()}`)
throw new Error(
`invalid transaction, getConfirmedTransaction call failed for transaction nr: ${lastSenderTransaction.getTransactionNr()}`,
)
}
const senderLastAccountBalance = lastConfirmedTransaction.getAccountBalance(publicKey, communityId)
const senderLastAccountBalance = lastConfirmedTransaction.getAccountBalance(
publicKey,
communityId,
)
if (!senderLastAccountBalance) {
return new Balance(publicKey, communityId)
}
const result = Balance.fromAccountBalance(senderLastAccountBalance, lastConfirmedTransaction.getConfirmedAt().getDate(), communityId)
const result = Balance.fromAccountBalance(
senderLastAccountBalance,
lastConfirmedTransaction.getConfirmedAt().getDate(),
communityId,
)
nanosBalanceForUser += lastBalanceOfUserTimeUsed.nanos()
return result
}
logLastBalanceChangingTransactions(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, transactionCount: number = 5) {
logLastBalanceChangingTransactions(
publicKey: MemoryBlockPtr,
blockchain: InMemoryBlockchain,
transactionCount: number = 5,
) {
if (!this.context.logger.isDebugEnabled()) {
return
}
@ -111,7 +129,7 @@ export abstract class AbstractSyncRole<ItemType> {
return this.items.length
}
return 0
}
}
toBlockchain(): void {
if (this.isEmpty()) {

View File

@ -1,14 +1,14 @@
import { and, asc, eq, gt, isNotNull, or } from 'drizzle-orm'
import * as v from 'valibot'
import { Context } from '../../Context'
import { contributionLinkModerators } from '../../database'
import { CreationTransactionDb, creationTransactionDbSchema } from '../../valibot.schema'
import { CreationsSyncRole } from './CreationsSync.role'
import { contributionsTable, usersTable } from '../../drizzle.schema'
import { ContributionStatus } from '../../data/ContributionStatus'
import { contributionLinkModerators } from '../../database'
import { contributionsTable, usersTable } from '../../drizzle.schema'
import { DatabaseError } from '../../errors'
import { IndexType } from './AbstractSync.role'
import { toMysqlDateTime } from '../../utils'
import { CreationTransactionDb, creationTransactionDbSchema } from '../../valibot.schema'
import { IndexType } from './AbstractSync.role'
import { CreationsSyncRole } from './CreationsSync.role'
export class ContributionLinkTransactionSyncRole extends CreationsSyncRole {
constructor(readonly context: Context) {
@ -25,30 +25,34 @@ export class ContributionLinkTransactionSyncRole extends CreationsSyncRole {
user: usersTable,
})
.from(contributionsTable)
.where(and(
isNotNull(contributionsTable.contributionLinkId),
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
or(
gt(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
and(
eq(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
gt(contributionsTable.transactionId, lastIndex.id)
)
)
))
.where(
and(
isNotNull(contributionsTable.contributionLinkId),
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
or(
gt(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
and(
eq(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
gt(contributionsTable.transactionId, lastIndex.id),
),
),
),
)
.innerJoin(usersTable, eq(contributionsTable.userId, usersTable.id))
.orderBy(asc(contributionsTable.confirmedAt), asc(contributionsTable.transactionId))
.limit(count)
const verifiedCreationTransactions: CreationTransactionDb[] = []
for(const row of result) {
for (const row of result) {
if (!row.contribution.contributionLinkId) {
throw new Error(`expect contributionLinkId to be set: ${JSON.stringify(row.contribution, null, 2)}`)
throw new Error(
`expect contributionLinkId to be set: ${JSON.stringify(row.contribution, null, 2)}`,
)
}
const item = {
...row.contribution,
user: row.user,
confirmedByUser: contributionLinkModerators.get(row.contribution.contributionLinkId)
confirmedByUser: contributionLinkModerators.get(row.contribution.contributionLinkId),
}
if (!item.confirmedByUser || item.userId === item.confirmedByUser.id) {
this.context.logger.warn(`skipped Contribution Link Transaction ${row.contribution.id}`)

View File

@ -1,33 +1,33 @@
import { and, asc, eq, isNull, gt, or } from 'drizzle-orm'
import { and, asc, eq, gt, isNull, or } from 'drizzle-orm'
import { alias } from 'drizzle-orm/mysql-core'
import {
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
Filter,
GradidoTransactionBuilder,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
SearchDirection_DESC,
TransactionType_CREATION,
TransferAmount
import {
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
Filter,
GradidoTransactionBuilder,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
SearchDirection_DESC,
TransactionType_CREATION,
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { addToBlockchain } from '../../blockchain'
import { ContributionStatus } from '../../data/ContributionStatus'
import { Context } from '../../Context'
import {
contributionsTable,
usersTable
} from '../../drizzle.schema'
import { ContributionStatus } from '../../data/ContributionStatus'
import { contributionsTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError } from '../../errors'
import { CommunityContext, CreationTransactionDb, creationTransactionDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { toMysqlDateTime } from '../../utils'
import {
CommunityContext,
CreationTransactionDb,
creationTransactionDbSchema,
} from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
constructor(context: Context) {
super(context)
this.accountBalances.reserve(3)
@ -38,16 +38,16 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
}
getLastIndex(): IndexType {
const lastItem = this.peekLast()
return { date: lastItem.confirmedAt, id: lastItem.transactionId }
}
const lastItem = this.peekLast()
return { date: lastItem.confirmedAt, id: lastItem.transactionId }
}
itemTypeName(): string {
return 'creationTransactions'
}
async loadFromDb(lastIndex: IndexType, count: number): Promise<CreationTransactionDb[]> {
const confirmedByUsers = alias(usersTable, 'confirmedByUser')
const confirmedByUsers = alias(usersTable, 'confirmedByUser')
const result = await this.context.db
.select({
contribution: contributionsTable,
@ -55,22 +55,24 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
confirmedByUser: confirmedByUsers,
})
.from(contributionsTable)
.where(and(
isNull(contributionsTable.contributionLinkId),
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
or(
gt(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
and(
eq(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
gt(contributionsTable.transactionId, lastIndex.id)
)
)
))
.where(
and(
isNull(contributionsTable.contributionLinkId),
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
or(
gt(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
and(
eq(contributionsTable.confirmedAt, toMysqlDateTime(lastIndex.date)),
gt(contributionsTable.transactionId, lastIndex.id),
),
),
),
)
.innerJoin(usersTable, eq(contributionsTable.userId, usersTable.id))
.innerJoin(confirmedByUsers, eq(contributionsTable.confirmedBy, confirmedByUsers.id))
.orderBy(asc(contributionsTable.confirmedAt), asc(contributionsTable.transactionId))
.limit(count)
return result.map((row) => {
const item = {
...row.contribution,
@ -86,12 +88,12 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
}
buildTransaction(
item: CreationTransactionDb,
communityContext: CommunityContext,
recipientKeyPair: KeyPairEd25519,
signerKeyPair: KeyPairEd25519
item: CreationTransactionDb,
communityContext: CommunityContext,
recipientKeyPair: KeyPairEd25519,
signerKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
return this.transactionBuilder
.setCreatedAt(item.confirmedAt)
.setRecipientCommunity(communityContext.communityId)
.addMemo(
@ -102,19 +104,27 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
),
)
.setTransactionCreation(
new TransferAmount(recipientKeyPair.getPublicKey(), item.amount, communityContext.communityId),
new TransferAmount(
recipientKeyPair.getPublicKey(),
item.amount,
communityContext.communityId,
),
item.contributionDate,
)
.sign(signerKeyPair)
}
calculateAccountBalances(
item: CreationTransactionDb,
communityContext: CommunityContext,
recipientPublicKey: MemoryBlockPtr
item: CreationTransactionDb,
communityContext: CommunityContext,
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
const balance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain, communityContext.communityId)
const balance = this.getLastBalanceForUser(
recipientPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
// calculate decay since last balance with legacy calculation method
balance.updateLegacyDecay(item.amount, item.confirmedAt)
@ -131,9 +141,11 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
const blockchain = communityContext.blockchain
if (item.confirmedByUser.communityUuid !== item.user.communityUuid) {
throw new Error(`contribution was confirmed from other community: ${JSON.stringify(item, null, 2)}`)
throw new Error(
`contribution was confirmed from other community: ${JSON.stringify(item, null, 2)}`,
)
}
const recipientKeyPair = this.getAccountKeyPair(communityContext, item.user.gradidoId)
const recipientPublicKey = recipientKeyPair.getPublicKey()
const signerKeyPair = this.getAccountKeyPair(communityContext, item.confirmedByUser.gradidoId)
@ -148,14 +160,16 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_CONTRIBUTION_ID),
this.calculateAccountBalances(item, communityContext, recipientPublicKey),
)
} catch(e) {
const f= new Filter()
} catch (e) {
const f = new Filter()
f.transactionType = TransactionType_CREATION
f.searchDirection = SearchDirection_DESC
f.pagination.size = 1
const lastContribution = blockchain.findOne(f)
if (lastContribution) {
this.context.logger.warn(`last contribution: ${lastContribution.getConfirmedTransaction()?.toJson(true)}`)
this.context.logger.warn(
`last contribution: ${lastContribution.getConfirmedTransaction()?.toJson(true)}`,
)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}

View File

@ -1,34 +1,38 @@
import { CommunityContext, DeletedTransactionLinkDb, deletedTransactionLinKDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
import { and, lt, asc, isNotNull, eq, or, gt } from 'drizzle-orm'
import * as v from 'valibot'
import {
AccountBalance,
AccountBalances,
Filter,
GradidoDeferredTransfer,
import { and, asc, eq, gt, isNotNull, lt, or } from 'drizzle-orm'
import {
AccountBalance,
AccountBalances,
Filter,
GradidoDeferredTransfer,
GradidoTransactionBuilder,
GradidoTransfer,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
GradidoTransfer,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { addToBlockchain } from '../../blockchain'
import { BlockchainError, DatabaseError } from '../../errors'
import { Balance } from '../../data/Balance'
import { toMysqlDateTime } from '../../utils'
import { Context } from '../../Context'
import { Balance } from '../../data/Balance'
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError } from '../../errors'
import { toMysqlDateTime } from '../../utils'
import {
CommunityContext,
DeletedTransactionLinkDb,
deletedTransactionLinKDbSchema,
} from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTransactionLinkDb> {
constructor(context: Context) {
super(context)
this.accountBalances.reserve(2)
}
getDate(): Date {
return this.peek().deletedAt
}
@ -57,15 +61,15 @@ export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTra
gt(transactionLinksTable.deletedAt, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionLinksTable.deletedAt, toMysqlDateTime(lastIndex.date)),
gt(transactionLinksTable.id, lastIndex.id)
)
)
)
gt(transactionLinksTable.id, lastIndex.id),
),
),
),
)
.innerJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.orderBy(asc(transactionLinksTable.deletedAt), asc(transactionLinksTable.id))
.limit(count)
return result.map((row) => {
const item = {
...row.transactionLink,
@ -80,95 +84,118 @@ export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTra
}
buildTransaction(
communityContext: CommunityContext,
item: DeletedTransactionLinkDb,
linkFundingTransactionNr: number,
restAmount: GradidoUnit,
senderKeyPair: KeyPairEd25519,
linkFundingPublicKey: MemoryBlockPtr,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.deletedAt)
.setSenderCommunity(communityContext.communityId)
.setRedeemDeferredTransfer(
linkFundingTransactionNr,
new GradidoTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), restAmount, communityContext.communityId),
linkFundingPublicKey,
communityContext: CommunityContext,
item: DeletedTransactionLinkDb,
linkFundingTransactionNr: number,
restAmount: GradidoUnit,
senderKeyPair: KeyPairEd25519,
linkFundingPublicKey: MemoryBlockPtr,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.deletedAt)
.setSenderCommunity(communityContext.communityId)
.setRedeemDeferredTransfer(
linkFundingTransactionNr,
new GradidoTransfer(
new TransferAmount(
senderKeyPair.getPublicKey(),
restAmount,
communityContext.communityId,
),
)
.sign(senderKeyPair)
linkFundingPublicKey,
),
)
.sign(senderKeyPair)
}
calculateBalances(
item: DeletedTransactionLinkDb,
item: DeletedTransactionLinkDb,
fundingTransaction: GradidoDeferredTransfer,
senderLastBalance: Balance,
communityContext: CommunityContext,
senderPublicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
this.accountBalances.clear()
const fundingUserLastBalance = this.getLastBalanceForUser(
fundingTransaction.getSenderPublicKey()!,
communityContext.blockchain,
communityContext.communityId
fundingTransaction.getSenderPublicKey()!,
communityContext.blockchain,
communityContext.communityId,
)
fundingUserLastBalance.updateLegacyDecay(senderLastBalance.getBalance(), item.deletedAt)
// account of link is set to zero, gdd will be send back to initiator
this.accountBalances.add(new AccountBalance(senderPublicKey, GradidoUnit.zero(), communityContext.communityId))
this.accountBalances.add(
new AccountBalance(senderPublicKey, GradidoUnit.zero(), communityContext.communityId),
)
this.accountBalances.add(fundingUserLastBalance.getAccountBalance())
return this.accountBalances
}
pushToBlockchain(item: DeletedTransactionLinkDb): void {
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
const blockchain = communityContext.blockchain
const senderKeyPair = deriveFromCode(item.code)
const senderPublicKey = senderKeyPair.getPublicKey()
if (!senderKeyPair || !senderPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
const transaction = blockchain.findOne(Filter.lastBalanceFor(senderPublicKey))
if (!transaction) {
throw new Error(`expect transaction for code: ${item.code}`)
}
// should be funding transaction
if (!transaction.isDeferredTransfer()) {
throw new Error(`expect funding transaction: ${transaction.getConfirmedTransaction()?.toJson(true)}`)
}
const body = transaction.getConfirmedTransaction()?.getGradidoTransaction()?.getTransactionBody();
const deferredTransfer = body?.getDeferredTransfer()
if (!deferredTransfer || !deferredTransfer.getRecipientPublicKey()?.equal(senderPublicKey)) {
throw new Error(`expect funding transaction to belong to code: ${item.code}: ${transaction.getConfirmedTransaction()?.toJson(true)}`)
}
const linkFundingPublicKey = deferredTransfer.getSenderPublicKey()
if (!linkFundingPublicKey) {
throw new Error(`missing sender public key of transaction link founder`)
}
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.deletedAt)
try {
addToBlockchain(
this.buildTransaction(
communityContext,
item, transaction.getTransactionNr(),
senderLastBalance.getBalance(),
senderKeyPair,
linkFundingPublicKey,
).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
this.calculateBalances(item, deferredTransfer, senderLastBalance, communityContext, senderPublicKey),
)
} catch(e) {
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
const blockchain = communityContext.blockchain
const senderKeyPair = deriveFromCode(item.code)
const senderPublicKey = senderKeyPair.getPublicKey()
if (!senderKeyPair || !senderPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
const transaction = blockchain.findOne(Filter.lastBalanceFor(senderPublicKey))
if (!transaction) {
throw new Error(`expect transaction for code: ${item.code}`)
}
// should be funding transaction
if (!transaction.isDeferredTransfer()) {
throw new Error(
`expect funding transaction: ${transaction.getConfirmedTransaction()?.toJson(true)}`,
)
}
const body = transaction
.getConfirmedTransaction()
?.getGradidoTransaction()
?.getTransactionBody()
const deferredTransfer = body?.getDeferredTransfer()
if (!deferredTransfer || !deferredTransfer.getRecipientPublicKey()?.equal(senderPublicKey)) {
throw new Error(
`expect funding transaction to belong to code: ${item.code}: ${transaction.getConfirmedTransaction()?.toJson(true)}`,
)
}
const linkFundingPublicKey = deferredTransfer.getSenderPublicKey()
if (!linkFundingPublicKey) {
throw new Error(`missing sender public key of transaction link founder`)
}
const senderLastBalance = this.getLastBalanceForUser(
senderPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.deletedAt)
try {
addToBlockchain(
this.buildTransaction(
communityContext,
item,
transaction.getTransactionNr(),
senderLastBalance.getBalance(),
senderKeyPair,
linkFundingPublicKey,
).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
this.calculateBalances(
item,
deferredTransfer,
senderLastBalance,
communityContext,
senderPublicKey,
),
)
} catch (e) {
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
}
}

View File

@ -1,31 +1,36 @@
import { and, asc, eq, isNotNull, isNull, gt, or } from 'drizzle-orm'
import { and, asc, eq, gt, isNotNull, isNull, or } from 'drizzle-orm'
import { alias } from 'drizzle-orm/mysql-core'
import {
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
GradidoTransactionBuilder,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount
import {
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
GradidoTransactionBuilder,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { addToBlockchain } from '../../blockchain'
import { Context } from '../../Context'
import { TransactionTypeId } from '../../data/TransactionTypeId'
import { transactionsTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError, NegativeBalanceError, NotEnoughGradidoBalanceError } from '../../errors'
import {
BlockchainError,
DatabaseError,
NegativeBalanceError,
NotEnoughGradidoBalanceError,
} from '../../errors'
import { toMysqlDateTime } from '../../utils'
import { CommunityContext, TransactionDb, transactionDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { toMysqlDateTime } from '../../utils'
import { Context } from '../../Context'
export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
constructor(context: Context) {
super(context)
this.accountBalances.reserve(2)
}
getDate(): Date {
return this.peek().balanceDate
}
@ -58,74 +63,83 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
or(
gt(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
gt(transactionsTable.id, lastIndex.id)
)
)
)
eq(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
gt(transactionsTable.id, lastIndex.id),
),
),
),
)
.innerJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
.innerJoin(linkedUsers, eq(transactionsTable.linkedUserId, linkedUsers.id))
.orderBy(asc(transactionsTable.balanceDate), asc(transactionsTable.id))
.limit(count)
return result.map((row) => {
const item = {
...row.transaction,
user: row.user,
linkedUser: row.linkedUser,
}
...row.transaction,
user: row.user,
linkedUser: row.linkedUser,
}
try {
return v.parse(transactionDbSchema, item)
} catch (e) {
throw new DatabaseError('loadLocalTransferTransactions', item, e as Error)
}
})
})
}
buildTransaction(
communityContext: CommunityContext,
item: TransactionDb,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.balanceDate)
.addMemo(new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
),
)
.setSenderCommunity(communityContext.communityId)
.setTransactionTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, communityContext.communityId),
recipientKeyPair.getPublicKey(),
)
.sign(senderKeyPair)
communityContext: CommunityContext,
item: TransactionDb,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.balanceDate)
.addMemo(
new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
),
)
.setSenderCommunity(communityContext.communityId)
.setTransactionTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, communityContext.communityId),
recipientKeyPair.getPublicKey(),
)
.sign(senderKeyPair)
}
calculateBalances(
item: TransactionDb,
item: TransactionDb,
communityContext: CommunityContext,
senderPublicKey: MemoryBlockPtr,
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
const recipientLastBalance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain, communityContext.communityId)
const senderLastBalance = this.getLastBalanceForUser(
senderPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
const recipientLastBalance = this.getLastBalanceForUser(
recipientPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
try {
senderLastBalance.updateLegacyDecay(item.amount.negated(), item.balanceDate)
} catch(e) {
} catch (e) {
if (e instanceof NegativeBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, communityContext.blockchain)
throw e
}
}
recipientLastBalance.updateLegacyDecay(item.amount, item.balanceDate)
this.accountBalances.add(senderLastBalance.getAccountBalance())
this.accountBalances.add(recipientLastBalance.getAccountBalance())
return this.accountBalances
@ -135,15 +149,17 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
const blockchain = communityContext.blockchain
if (item.linkedUser.communityUuid !== item.user.communityUuid) {
throw new Error(`transfer between user from different communities: ${JSON.stringify(item, null, 2)}`)
throw new Error(
`transfer between user from different communities: ${JSON.stringify(item, null, 2)}`,
)
}
// I use the received transaction so user and linked user are swapped and user is recipient and linkedUser ist sender
const senderKeyPair = this.getAccountKeyPair(communityContext, item.linkedUser.gradidoId)
const senderPublicKey = senderKeyPair.getPublicKey()
const recipientKeyPair = this.getAccountKeyPair(communityContext, item.user.gradidoId)
const recipientPublicKey = recipientKeyPair.getPublicKey()
if (!senderKeyPair || !senderPublicKey || !recipientKeyPair || !recipientPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
@ -155,9 +171,9 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_ID),
this.calculateBalances(item, communityContext, senderPublicKey, recipientPublicKey),
)
} catch(e) {
} catch (e) {
if (e instanceof NotEnoughGradidoBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, blockchain)
this.logLastBalanceChangingTransactions(senderPublicKey, blockchain)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}

View File

@ -1,36 +1,40 @@
import { and, asc, eq, isNotNull, isNull, or, gt } from 'drizzle-orm'
import {
import { and, asc, eq, gt, isNotNull, isNull, or } from 'drizzle-orm'
import { alias } from 'drizzle-orm/mysql-core'
import {
AccountBalance,
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
Filter,
GradidoDeferredTransfer,
GradidoTransactionBuilder,
GradidoTransfer,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount
GradidoTransactionBuilder,
GradidoTransfer,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { addToBlockchain } from '../../blockchain'
import { Context } from '../../Context'
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError } from '../../errors'
import { CommunityContext, RedeemedTransactionLinkDb, redeemedTransactionLinkDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { alias } from 'drizzle-orm/mysql-core'
import { toMysqlDateTime } from '../../utils'
import { Context } from '../../Context'
import {
CommunityContext,
RedeemedTransactionLinkDb,
redeemedTransactionLinkDbSchema,
} from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTransactionLinkDb> {
export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTransactionLinkDb> {
constructor(context: Context) {
super(context)
this.accountBalances.reserve(3)
}
getDate(): Date {
return this.peek().redeemedAt
}
@ -62,17 +66,17 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
or(
gt(transactionLinksTable.redeemedAt, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionLinksTable.redeemedAt, toMysqlDateTime(lastIndex.date)),
gt(transactionLinksTable.id, lastIndex.id)
)
)
)
eq(transactionLinksTable.redeemedAt, toMysqlDateTime(lastIndex.date)),
gt(transactionLinksTable.id, lastIndex.id),
),
),
),
)
.innerJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.innerJoin(redeemedByUser, eq(transactionLinksTable.redeemedBy, redeemedByUser.id))
.orderBy(asc(transactionLinksTable.redeemedAt), asc(transactionLinksTable.id))
.limit(count)
return result.map((row) => {
const item = {
...row.transactionLink,
@ -88,66 +92,74 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
}
buildTransaction(
communityContext: CommunityContext,
item: RedeemedTransactionLinkDb,
linkFundingTransactionNr: number,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.redeemedAt)
.addMemo(
new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
communityContext: CommunityContext,
item: RedeemedTransactionLinkDb,
linkFundingTransactionNr: number,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.redeemedAt)
.addMemo(
new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
),
)
.setSenderCommunity(communityContext.communityId)
.setRedeemDeferredTransfer(
linkFundingTransactionNr,
new GradidoTransfer(
new TransferAmount(
senderKeyPair.getPublicKey(),
item.amount,
communityContext.communityId,
),
)
.setSenderCommunity(communityContext.communityId)
.setRedeemDeferredTransfer(
linkFundingTransactionNr,
new GradidoTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, communityContext.communityId),
recipientKeyPair.getPublicKey(),
),
)
.sign(senderKeyPair)
recipientKeyPair.getPublicKey(),
),
)
.sign(senderKeyPair)
}
calculateBalances(
item: RedeemedTransactionLinkDb,
item: RedeemedTransactionLinkDb,
fundingTransaction: GradidoDeferredTransfer,
communityContext: CommunityContext,
senderPublicKey: MemoryBlockPtr,
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
const senderLastBalance = this.getLastBalanceForUser(
senderPublicKey,
communityContext.blockchain,
communityContext.communityId
senderPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
const fundingUserLastBalance = this.getLastBalanceForUser(
fundingTransaction.getSenderPublicKey()!,
communityContext.blockchain,
communityContext.communityId
fundingTransaction.getSenderPublicKey()!,
communityContext.blockchain,
communityContext.communityId,
)
const recipientLastBalance = this.getLastBalanceForUser(
recipientPublicKey,
communityContext.blockchain,
communityContext.communityId
recipientPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
if (senderLastBalance.getAccountBalance().getBalance().lt(item.amount)) {
throw new Error(`sender has not enough balance (${senderLastBalance.getAccountBalance().getBalance().toString()}) to send ${item.amount.toString()} to ${recipientPublicKey.convertToHex()}`)
throw new Error(
`sender has not enough balance (${senderLastBalance.getAccountBalance().getBalance().toString()}) to send ${item.amount.toString()} to ${recipientPublicKey.convertToHex()}`,
)
}
senderLastBalance.updateLegacyDecay(item.amount.negated(), item.redeemedAt)
fundingUserLastBalance.updateLegacyDecay(senderLastBalance.getBalance(), item.redeemedAt)
recipientLastBalance.updateLegacyDecay(item.amount, item.redeemedAt)
// account of link is set to zero, and change send back to link creator
this.accountBalances.add(new AccountBalance(senderPublicKey, GradidoUnit.zero(), communityContext.communityId))
this.accountBalances.add(
new AccountBalance(senderPublicKey, GradidoUnit.zero(), communityContext.communityId),
)
this.accountBalances.add(recipientLastBalance.getAccountBalance())
this.accountBalances.add(fundingUserLastBalance.getAccountBalance())
return this.accountBalances
@ -165,29 +177,48 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
if (!senderKeyPair || !senderPublicKey || !recipientKeyPair || !recipientPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
const transaction = blockchain.findOne(Filter.lastBalanceFor(senderPublicKey))
if (!transaction) {
throw new Error(`expect transaction for code: ${item.code}`)
}
// should be funding transaction
if (!transaction.isDeferredTransfer()) {
throw new Error(`expect funding transaction: ${transaction.getConfirmedTransaction()?.toJson(true)}`)
throw new Error(
`expect funding transaction: ${transaction.getConfirmedTransaction()?.toJson(true)}`,
)
}
const body = transaction.getConfirmedTransaction()?.getGradidoTransaction()?.getTransactionBody();
const body = transaction
.getConfirmedTransaction()
?.getGradidoTransaction()
?.getTransactionBody()
const deferredTransfer = body?.getDeferredTransfer()
if (!deferredTransfer || !deferredTransfer.getRecipientPublicKey()?.equal(senderPublicKey)) {
throw new Error(`expect funding transaction to belong to code: ${item.code}: ${transaction.getConfirmedTransaction()?.toJson(true)}`)
throw new Error(
`expect funding transaction to belong to code: ${item.code}: ${transaction.getConfirmedTransaction()?.toJson(true)}`,
)
}
try {
addToBlockchain(
this.buildTransaction(communityContext, item, transaction.getTransactionNr(), senderKeyPair, recipientKeyPair).build(),
this.buildTransaction(
communityContext,
item,
transaction.getTransactionNr(),
senderKeyPair,
recipientKeyPair,
).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
this.calculateBalances(item, deferredTransfer, communityContext, senderPublicKey, recipientPublicKey),
this.calculateBalances(
item,
deferredTransfer,
communityContext,
senderPublicKey,
recipientPublicKey,
),
)
} catch(e) {
} catch (e) {
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
}

View File

@ -1,25 +1,39 @@
import { alias } from 'drizzle-orm/mysql-core'
import { transactionsTable, usersTable } from '../../drizzle.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { CommunityContext, TransactionDb, transactionDbSchema, UserDb } from '../../valibot.schema'
import * as v from 'valibot'
import { toMysqlDateTime } from '../../utils'
import { TransactionTypeId } from '../../data/TransactionTypeId'
import { DatabaseError, NegativeBalanceError } from '../../errors'
import { asc, and, eq, gt, ne, or, inArray, isNull } from 'drizzle-orm'
import { NotEnoughGradidoBalanceError } from '../../errors'
import { BlockchainError } from '../../errors'
import { addToBlockchain } from '../../blockchain'
import { AccountBalance, AccountBalances, AuthenticatedEncryption, EncryptedMemo, GradidoTransactionBuilder, GradidoUnit, KeyPairEd25519, LedgerAnchor, MemoryBlockPtr, TransferAmount } from 'gradido-blockchain-js'
import { Decimal } from 'decimal.js'
import { and, asc, eq, gt, inArray, isNull, ne, or } from 'drizzle-orm'
import { alias } from 'drizzle-orm/mysql-core'
import {
AccountBalance,
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
GradidoTransactionBuilder,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { addToBlockchain } from '../../blockchain'
import { Context } from '../../Context'
import { TransactionTypeId } from '../../data/TransactionTypeId'
import { transactionsTable, usersTable } from '../../drizzle.schema'
import {
BlockchainError,
DatabaseError,
NegativeBalanceError,
NotEnoughGradidoBalanceError,
} from '../../errors'
import { toMysqlDateTime } from '../../utils'
import { CommunityContext, TransactionDb, transactionDbSchema, UserDb } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
export class RemoteTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
constructor(context: Context) {
super(context)
this.accountBalances.reserve(1)
}
getDate(): Date {
return this.peek().balanceDate
}
@ -50,23 +64,23 @@ export class RemoteTransactionsSyncRole extends AbstractSyncRole<TransactionDb>
or(
gt(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
gt(transactionsTable.id, lastIndex.id)
)
)
)
eq(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
gt(transactionsTable.id, lastIndex.id),
),
),
),
)
.innerJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
.innerJoin(linkedUsers, eq(transactionsTable.linkedUserGradidoId, linkedUsers.gradidoId))
.orderBy(asc(transactionsTable.balanceDate), asc(transactionsTable.id))
.limit(count)
return result.map((row) => {
const item = {
...row.transaction,
user: row.user,
linkedUser: row.linkedUser,
}
...row.transaction,
user: row.user,
linkedUser: row.linkedUser,
}
if (item.typeId === TransactionTypeId.SEND && item.amount) {
item.amount = new Decimal(item.amount).neg().toString()
}
@ -75,101 +89,120 @@ export class RemoteTransactionsSyncRole extends AbstractSyncRole<TransactionDb>
} catch (e) {
throw new DatabaseError('loadRemoteTransferTransactions', item, e as Error)
}
})
})
}
buildTransaction(
item: TransactionDb,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
senderCommunityId: string,
recipientCommunityId: string,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.balanceDate)
.addMemo(new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
),
)
.setSenderCommunity(senderCommunityId)
.setRecipientCommunity(recipientCommunityId)
.setTransactionTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, senderCommunityId),
recipientKeyPair.getPublicKey(),
)
.sign(senderKeyPair)
item: TransactionDb,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
senderCommunityId: string,
recipientCommunityId: string,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.balanceDate)
.addMemo(
new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
),
)
.setSenderCommunity(senderCommunityId)
.setRecipientCommunity(recipientCommunityId)
.setTransactionTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, senderCommunityId),
recipientKeyPair.getPublicKey(),
)
.sign(senderKeyPair)
}
calculateBalances(
item: TransactionDb,
communityContext: CommunityContext,
coinCommunityId: string,
amount: GradidoUnit,
publicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
if (communityContext.foreign) {
this.accountBalances.add(new AccountBalance(publicKey, GradidoUnit.zero(), coinCommunityId))
return this.accountBalances
} else {
// try to use same coins from this community
let lastBalance = this.getLastBalanceForUser(publicKey, communityContext.blockchain, coinCommunityId)
if (lastBalance.getBalance().equal(GradidoUnit.zero()) || lastBalance.getBalance().calculateDecay(lastBalance.getDate(), item.balanceDate).lt(amount)) {
// don't work, so we use or own coins
lastBalance = this.getLastBalanceForUser(publicKey, communityContext.blockchain, communityContext.communityId)
}
try {
lastBalance.updateLegacyDecay(amount, item.balanceDate)
} catch(e) {
if (e instanceof NegativeBalanceError) {
console.log(`coin community id: ${coinCommunityId}, context community id: ${communityContext.communityId}`)
this.logLastBalanceChangingTransactions(publicKey, communityContext.blockchain, 1)
throw e
}
}
this.accountBalances.add(lastBalance.getAccountBalance())
return this.accountBalances
item: TransactionDb,
communityContext: CommunityContext,
coinCommunityId: string,
amount: GradidoUnit,
publicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
if (communityContext.foreign) {
this.accountBalances.add(new AccountBalance(publicKey, GradidoUnit.zero(), coinCommunityId))
return this.accountBalances
} else {
// try to use same coins from this community
let lastBalance = this.getLastBalanceForUser(
publicKey,
communityContext.blockchain,
coinCommunityId,
)
if (
lastBalance.getBalance().equal(GradidoUnit.zero()) ||
lastBalance.getBalance().calculateDecay(lastBalance.getDate(), item.balanceDate).lt(amount)
) {
// don't work, so we use or own coins
lastBalance = this.getLastBalanceForUser(
publicKey,
communityContext.blockchain,
communityContext.communityId,
)
}
try {
lastBalance.updateLegacyDecay(amount, item.balanceDate)
} catch (e) {
if (e instanceof NegativeBalanceError) {
this.logLastBalanceChangingTransactions(publicKey, communityContext.blockchain, 1)
throw e
}
}
this.accountBalances.add(lastBalance.getAccountBalance())
return this.accountBalances
}
}
getUser(item: TransactionDb): { senderUser: UserDb, recipientUser: UserDb } {
return (
item.typeId === TransactionTypeId.RECEIVE
? { senderUser: item.linkedUser, recipientUser: item.user }
: { senderUser: item.user, recipientUser: item.linkedUser }
)
getUser(item: TransactionDb): { senderUser: UserDb; recipientUser: UserDb } {
return item.typeId === TransactionTypeId.RECEIVE
? { senderUser: item.linkedUser, recipientUser: item.user }
: { senderUser: item.user, recipientUser: item.linkedUser }
}
pushToBlockchain(item: TransactionDb): void {
const { senderUser, recipientUser } = this.getUser(item)
const ledgerAnchor = new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_ID)
const ledgerAnchor = new LedgerAnchor(
item.id,
LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_ID,
)
if (senderUser.communityUuid === recipientUser.communityUuid) {
throw new Error(`transfer between user from same community: ${JSON.stringify(item, null, 2)}, check db query`)
throw new Error(
`transfer between user from same community: ${JSON.stringify(item, null, 2)}, check db query`,
)
}
const senderCommunityContext = this.context.getCommunityContextByUuid(senderUser.communityUuid)
const recipientCommunityContext = this.context.getCommunityContextByUuid(recipientUser.communityUuid)
const recipientCommunityContext = this.context.getCommunityContextByUuid(
recipientUser.communityUuid,
)
const senderBlockchain = senderCommunityContext.blockchain
const recipientBlockchain = recipientCommunityContext.blockchain
// I use the received transaction so user and linked user are swapped and user is recipient and linkedUser ist sender
const senderKeyPair = this.getAccountKeyPair(senderCommunityContext, senderUser.gradidoId)
const senderPublicKey = senderKeyPair.getPublicKey()
const recipientKeyPair = this.getAccountKeyPair(recipientCommunityContext, recipientUser.gradidoId)
const recipientKeyPair = this.getAccountKeyPair(
recipientCommunityContext,
recipientUser.gradidoId,
)
const recipientPublicKey = recipientKeyPair.getPublicKey()
if (!senderKeyPair || !senderPublicKey || !recipientKeyPair || !recipientPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
const transactionBuilder = this.buildTransaction(
item,
senderKeyPair,
recipientKeyPair,
senderCommunityContext.communityId,
recipientCommunityContext.communityId
item,
senderKeyPair,
recipientKeyPair,
senderCommunityContext.communityId,
recipientCommunityContext.communityId,
)
const outboundTransaction = transactionBuilder.buildOutbound()
@ -178,11 +211,17 @@ export class RemoteTransactionsSyncRole extends AbstractSyncRole<TransactionDb>
outboundTransaction,
senderBlockchain,
ledgerAnchor,
this.calculateBalances(item, senderCommunityContext, senderCommunityContext.communityId, item.amount.negated(), senderPublicKey),
this.calculateBalances(
item,
senderCommunityContext,
senderCommunityContext.communityId,
item.amount.negated(),
senderPublicKey,
),
)
} catch(e) {
} catch (e) {
if (e instanceof NotEnoughGradidoBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, senderBlockchain)
this.logLastBalanceChangingTransactions(senderPublicKey, senderBlockchain)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
@ -193,11 +232,17 @@ export class RemoteTransactionsSyncRole extends AbstractSyncRole<TransactionDb>
inboundTransaction,
recipientBlockchain,
ledgerAnchor,
this.calculateBalances(item, recipientCommunityContext, senderCommunityContext.communityId, item.amount, recipientPublicKey),
this.calculateBalances(
item,
recipientCommunityContext,
senderCommunityContext.communityId,
item.amount,
recipientPublicKey,
),
)
} catch(e) {
} catch (e) {
if (e instanceof NotEnoughGradidoBalanceError) {
this.logLastBalanceChangingTransactions(recipientPublicKey, recipientBlockchain)
this.logLastBalanceChangingTransactions(recipientPublicKey, recipientBlockchain)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}

View File

@ -1,28 +1,28 @@
import { asc, eq, or, gt, and } from 'drizzle-orm'
import {
import Decimal from 'decimal.js-light'
import { and, asc, eq, gt, or } from 'drizzle-orm'
import {
AccountBalance,
AccountBalances,
AuthenticatedEncryption,
DurationSeconds,
EncryptedMemo,
GradidoTransactionBuilder,
GradidoTransfer,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount
AccountBalances,
AuthenticatedEncryption,
DurationSeconds,
EncryptedMemo,
GradidoTransactionBuilder,
GradidoTransfer,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
TransferAmount,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { addToBlockchain } from '../../blockchain'
import { Context } from '../../Context'
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError, NegativeBalanceError } from '../../errors'
import { reverseLegacyDecay, toMysqlDateTime } from '../../utils'
import { CommunityContext, TransactionLinkDb, transactionLinkDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { reverseLegacyDecay, toMysqlDateTime } from '../../utils'
import Decimal from 'decimal.js-light'
import { Context } from '../../Context'
export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<TransactionLinkDb> {
constructor(context: Context) {
@ -47,16 +47,18 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
.select()
.from(transactionLinksTable)
.innerJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
.where(or(
gt(transactionLinksTable.createdAt, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionLinksTable.createdAt, toMysqlDateTime(lastIndex.date)),
gt(transactionLinksTable.id, lastIndex.id)
)
))
.where(
or(
gt(transactionLinksTable.createdAt, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionLinksTable.createdAt, toMysqlDateTime(lastIndex.date)),
gt(transactionLinksTable.id, lastIndex.id),
),
),
)
.orderBy(asc(transactionLinksTable.createdAt), asc(transactionLinksTable.id))
.limit(count)
return result.map((row) => {
const item = {
...row.transaction_links,
@ -72,11 +74,11 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
buildTransaction(
communityContext: CommunityContext,
item: TransactionLinkDb,
item: TransactionLinkDb,
blockedAmount: GradidoUnit,
duration: DurationSeconds,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.createdAt)
@ -90,81 +92,126 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
.setSenderCommunity(communityContext.communityId)
.setDeferredTransfer(
new GradidoTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), blockedAmount, communityContext.communityId),
new TransferAmount(
senderKeyPair.getPublicKey(),
blockedAmount,
communityContext.communityId,
),
recipientKeyPair.getPublicKey(),
),
duration,
)
)
.sign(senderKeyPair)
}
calculateBalances(
item: TransactionLinkDb,
blockedAmount: GradidoUnit,
communityContext: CommunityContext,
senderPublicKey: MemoryBlockPtr,
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
let senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
try {
senderLastBalance.updateLegacyDecay(blockedAmount.negated(), item.createdAt)
} catch(e) {
if (e instanceof NegativeBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, communityContext.blockchain)
this.context.logger.debug(`sender public key: ${senderPublicKey.convertToHex()}`)
throw e
}
item: TransactionLinkDb,
blockedAmount: GradidoUnit,
communityContext: CommunityContext,
senderPublicKey: MemoryBlockPtr,
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
this.accountBalances.clear()
const senderLastBalance = this.getLastBalanceForUser(
senderPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
try {
senderLastBalance.updateLegacyDecay(blockedAmount.negated(), item.createdAt)
} catch (e) {
if (e instanceof NegativeBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, communityContext.blockchain)
this.context.logger.debug(`sender public key: ${senderPublicKey.convertToHex()}`)
throw e
}
this.accountBalances.add(senderLastBalance.getAccountBalance())
this.accountBalances.add(new AccountBalance(recipientPublicKey, blockedAmount, communityContext.communityId))
return this.accountBalances
}
this.accountBalances.add(senderLastBalance.getAccountBalance())
this.accountBalances.add(
new AccountBalance(recipientPublicKey, blockedAmount, communityContext.communityId),
)
return this.accountBalances
}
pushToBlockchain(item: TransactionLinkDb): void {
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
const blockchain = communityContext.blockchain
const senderKeyPair = this.getAccountKeyPair(communityContext, item.user.gradidoId)
const senderPublicKey = senderKeyPair.getPublicKey()
const recipientKeyPair = deriveFromCode(item.code)
const recipientPublicKey = recipientKeyPair.getPublicKey()
if (!senderKeyPair || !senderPublicKey || !recipientKeyPair || !recipientPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
let duration = new DurationSeconds((item.validUntil.getTime() - item.createdAt.getTime()) / 1000)
let blockedAmount = GradidoUnit.fromString(reverseLegacyDecay(new Decimal(item.amount.toString()), duration.getSeconds()).toString())
const duration = new DurationSeconds(
(item.validUntil.getTime() - item.createdAt.getTime()) / 1000,
)
let blockedAmount = GradidoUnit.fromString(
reverseLegacyDecay(new Decimal(item.amount.toString()), duration.getSeconds()).toString(),
)
let accountBalances: AccountBalances
try {
accountBalances = this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey)
} catch(e) {
accountBalances = this.calculateBalances(
item,
blockedAmount,
communityContext,
senderPublicKey,
recipientPublicKey,
)
} catch (e) {
if (item.deletedAt && e instanceof NegativeBalanceError) {
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
const senderLastBalance = this.getLastBalanceForUser(
senderPublicKey,
communityContext.blockchain,
communityContext.communityId,
)
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.createdAt)
const oldBlockedAmountString = blockedAmount.toString()
blockedAmount = senderLastBalance.getBalance()
accountBalances = this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey)
accountBalances = this.calculateBalances(
item,
blockedAmount,
communityContext,
senderPublicKey,
recipientPublicKey,
)
this.context.logger.warn(
`workaround: fix founding for deleted link, reduce funding to actual sender balance: ${senderPublicKey.convertToHex()}: from ${oldBlockedAmountString} GDD to ${blockedAmount.toString()} GDD`
`workaround: fix founding for deleted link, reduce funding to actual sender balance: ${senderPublicKey.convertToHex()}: from ${oldBlockedAmountString} GDD to ${blockedAmount.toString()} GDD`,
)
} else {
this.context.logger.error(`error calculate account balances for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
this.context.logger.error(
`error calculate account balances for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`,
)
throw e
}
}
try {
addToBlockchain(
this.buildTransaction(communityContext, item, blockedAmount, duration, senderKeyPair, recipientKeyPair).build(),
this.buildTransaction(
communityContext,
item,
blockedAmount,
duration,
senderKeyPair,
recipientKeyPair,
).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
accountBalances,
)
} catch(e) {
} catch (e) {
if (e instanceof NegativeBalanceError) {
if (!item.deletedAt && !item.redeemedAt && item.validUntil.getTime() < new Date().getTime()) {
this.context.logger.warn(`TransactionLinks: ${item.id} skipped, because else it lead to negative balance error, but it wasn't used.`)
if (
!item.deletedAt &&
!item.redeemedAt &&
item.validUntil.getTime() < new Date().getTime()
) {
this.context.logger.warn(
`TransactionLinks: ${item.id} skipped, because else it lead to negative balance error, but it wasn't used.`,
)
return
}
}

View File

@ -1,24 +1,24 @@
import { asc, and, gt, eq, or } from 'drizzle-orm'
import {
AccountBalance,
AccountBalances,
AddressType_COMMUNITY_HUMAN,
GradidoTransactionBuilder,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr
import { and, asc, eq, gt, or } from 'drizzle-orm'
import {
AccountBalance,
AccountBalances,
AddressType_COMMUNITY_HUMAN,
GradidoTransactionBuilder,
GradidoUnit,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
} from 'gradido-blockchain-js'
import * as v from 'valibot'
import { deriveFromKeyPairAndUuid } from '../../../../data/deriveKeyPair'
import { Uuidv4Hash } from '../../../../data/Uuidv4Hash'
import { addToBlockchain } from '../../blockchain'
import { Context } from '../../Context'
import { usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError } from '../../errors'
import { toMysqlDateTime } from '../../utils'
import { CommunityContext, UserDb, userDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { toMysqlDateTime } from '../../utils'
import { Context } from '../../Context'
export class UsersSyncRole extends AbstractSyncRole<UserDb> {
constructor(context: Context) {
@ -40,20 +40,22 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
async loadFromDb(lastIndex: IndexType, count: number): Promise<UserDb[]> {
const result = await this.context.db
.select()
.from(usersTable)
.where(and(
.select()
.from(usersTable)
.where(
and(
or(
gt(usersTable.createdAt, toMysqlDateTime(lastIndex.date)),
and(
eq(usersTable.createdAt, toMysqlDateTime(lastIndex.date)),
gt(usersTable.id, lastIndex.id)
)
)
))
.orderBy(asc(usersTable.createdAt), asc(usersTable.id))
.limit(count)
gt(usersTable.id, lastIndex.id),
),
),
),
)
.orderBy(asc(usersTable.createdAt), asc(usersTable.id))
.limit(count)
return result.map((row) => {
try {
return v.parse(userDbSchema, row)
@ -65,10 +67,10 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
buildTransaction(
communityContext: CommunityContext,
item: UserDb,
communityKeyPair: KeyPairEd25519,
accountKeyPair: KeyPairEd25519,
userKeyPair: KeyPairEd25519
item: UserDb,
communityKeyPair: KeyPairEd25519,
accountKeyPair: KeyPairEd25519,
userKeyPair: KeyPairEd25519,
): GradidoTransactionBuilder {
return this.transactionBuilder
.setCreatedAt(item.createdAt)
@ -84,9 +86,14 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
.sign(userKeyPair)
}
calculateAccountBalances(accountPublicKey: MemoryBlockPtr, communityContext: CommunityContext,): AccountBalances {
calculateAccountBalances(
accountPublicKey: MemoryBlockPtr,
communityContext: CommunityContext,
): AccountBalances {
this.accountBalances.clear()
this.accountBalances.add(new AccountBalance(accountPublicKey, GradidoUnit.zero(), communityContext.communityId))
this.accountBalances.add(
new AccountBalance(accountPublicKey, GradidoUnit.zero(), communityContext.communityId),
)
return this.accountBalances
}
@ -101,7 +108,13 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
try {
addToBlockchain(
this.buildTransaction(communityContext, item, communityContext.keyPair, accountKeyPair, userKeyPair).build(),
this.buildTransaction(
communityContext,
item,
communityContext.keyPair,
accountKeyPair,
userKeyPair,
).build(),
communityContext.blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_USER_ID),
this.calculateAccountBalances(accountPublicKey, communityContext),

View File

@ -1,15 +1,15 @@
import { Profiler } from 'gradido-blockchain-js'
import { Context } from '../../Context'
import { CreationsSyncRole } from './CreationsSync.role'
import { LocalTransactionsSyncRole } from './LocalTransactionsSync.role'
import { UsersSyncRole } from './UsersSync.role'
import { TransactionLinkFundingsSyncRole } from './TransactionLinkFundingsSync.role'
import { RedeemTransactionLinksSyncRole } from './RedeemTransactionLinksSync.role'
import { ContributionLinkTransactionSyncRole } from './ContributionLinkTransactionSync.role'
import { DeletedTransactionLinksSyncRole } from './DeletedTransactionLinksSync.role'
import { RemoteTransactionsSyncRole } from './RemoteTransactionsSync.role'
import { callTime } from '../../blockchain'
import { Context } from '../../Context'
import { nanosBalanceForUser } from './AbstractSync.role'
import { ContributionLinkTransactionSyncRole } from './ContributionLinkTransactionSync.role'
import { CreationsSyncRole } from './CreationsSync.role'
import { DeletedTransactionLinksSyncRole } from './DeletedTransactionLinksSync.role'
import { LocalTransactionsSyncRole } from './LocalTransactionsSync.role'
import { RedeemTransactionLinksSyncRole } from './RedeemTransactionLinksSync.role'
import { RemoteTransactionsSyncRole } from './RemoteTransactionsSync.role'
import { TransactionLinkFundingsSyncRole } from './TransactionLinkFundingsSync.role'
import { UsersSyncRole } from './UsersSync.role'
export async function syncDbWithBlockchainContext(context: Context, batchSize: number) {
const timeUsedDB = new Profiler()
@ -38,7 +38,7 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
const loadedItemsCount = results.reduce((acc, c) => acc + c, 0)
// log only, if at least one new item was loaded
if (loadedItemsCount && isDebug) {
context.logger.debug(`${loadedItemsCount} new items loaded from db in ${timeUsedDB.string()}`)
context.logger.debug(`${loadedItemsCount} new items loaded from db in ${timeUsedDB.string()}`)
}
// remove empty containers
@ -53,17 +53,21 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
available.sort((a, b) => a.getDate().getTime() - b.getDate().getTime())
// context.logger.debug(`sorted ${available.length} containers in ${sortTime.string()}`)
}
available[0].toBlockchain()
available[0].toBlockchain()
transactionsCount++
if (isDebug) {
if (timeBetweenPrints.millis() > 100) {
if (isDebug) {
if (timeBetweenPrints.millis() > 100) {
process.stdout.write(`successfully added to blockchain: ${transactionsCount}\r`)
timeBetweenPrints.reset()
}
transactionsCountSinceLastLog++
transactionsCountSinceLastLog++
if (transactionsCountSinceLastLog >= batchSize) {
context.logger.debug(`${transactionsCountSinceLastLog} transactions added to blockchain in ${timeUsedBlockchain.string()}`)
context.logger.info(`Time for createAndConfirm: ${((callTime - lastPrintedCallTime) / 1000 / 1000).toFixed(2)} milliseconds`)
context.logger.debug(
`${transactionsCountSinceLastLog} transactions added to blockchain in ${timeUsedBlockchain.string()}`,
)
context.logger.info(
`Time for createAndConfirm: ${((callTime - lastPrintedCallTime) / 1000 / 1000).toFixed(2)} milliseconds`,
)
lastPrintedCallTime = callTime
timeUsedBlockchain.reset()
transactionsCountSinceLastLog = 0
@ -76,8 +80,14 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
}
}
}
process.stdout.write(`successfully added to blockchain: ${transactionsCount}\n`)
context.logger.info(`Synced ${transactionsCount} transactions to blockchain in ${timeUsedAll.string()}`)
context.logger.info(`Time for createAndConfirm: ${(callTime / 1000 / 1000 / 1000).toFixed(2)} seconds`)
context.logger.info(`Time for call lastBalance of user: ${(nanosBalanceForUser / 1000 / 1000 / 1000).toFixed(2)} seconds`)
process.stdout.write(`successfully added to blockchain: ${transactionsCount}\n`)
context.logger.info(
`Synced ${transactionsCount} transactions to blockchain in ${timeUsedAll.string()}`,
)
context.logger.info(
`Time for createAndConfirm: ${(callTime / 1000 / 1000 / 1000).toFixed(2)} seconds`,
)
context.logger.info(
`Time for call lastBalance of user: ${(nanosBalanceForUser / 1000 / 1000 / 1000).toFixed(2)} seconds`,
)
}

View File

@ -10,7 +10,7 @@ export function bytesToKbyte(bytes: number): string {
}
export function bytesString(bytes: number): string {
if (bytes > (1024 * 1024)) {
if (bytes > 1024 * 1024) {
return `${bytesToMbyte(bytes)} MB`
} else if (bytes > 1024) {
return `${bytesToKbyte(bytes)} KB`
@ -50,7 +50,9 @@ export function legacyCalculateDecay(amount: Decimal, from: Date, to: Date): Dec
const startBlockMs = DECAY_START_TIME.getTime()
if (toMs < fromMs) {
throw new Error(`calculateDecay: to (${to.toISOString()}) < from (${from.toISOString()}), reverse decay calculation is invalid`)
throw new Error(
`calculateDecay: to (${to.toISOString()}) < from (${from.toISOString()}), reverse decay calculation is invalid`,
)
}
// decay started after end date; no decay
@ -59,7 +61,7 @@ export function legacyCalculateDecay(amount: Decimal, from: Date, to: Date): Dec
}
// decay started before start date; decay for full duration
let duration = (toMs - fromMs) / 1000
// decay started between start and end date; decay from decay start till end date
if (startBlockMs >= fromMs) {
duration = (toMs - startBlockMs) / 1000

View File

@ -1,3 +1,4 @@
import Decimal from 'decimal.js-light'
import { GradidoUnit, InMemoryBlockchain, KeyPairEd25519 } from 'gradido-blockchain-js'
import * as v from 'valibot'
import { booleanSchema, dateSchema } from '../../schemas/typeConverter.schema'
@ -9,7 +10,6 @@ import {
} from '../../schemas/typeGuard.schema'
import { Balance } from './data/Balance'
import { TransactionTypeId } from './data/TransactionTypeId'
import Decimal from 'decimal.js-light'
const positiveNumberSchema = v.pipe(v.number(), v.minValue(1))
@ -51,53 +51,71 @@ export const transactionBaseSchema = v.object({
user: userDbSchema,
})
export const transactionDbSchema = v.pipe(
v.object({
...transactionBaseSchema.entries,
typeId: v.enum(TransactionTypeId),
balanceDate: dateSchema,
linkedUser: userDbSchema,
}),
v.custom((value: any) => {
if (
value.user &&
value.linkedUser &&
!value.transactionLinkCode &&
value.user.gradidoId === value.linkedUser.gradidoId
) {
throw new Error(
`expect user to be different from linkedUser: ${JSON.stringify(value, null, 2)}`,
)
}
// check that user and linked user exist before transaction balance date
const balanceDate = new Date(value.balanceDate)
if (
value.user.createdAt.getTime() >= balanceDate.getTime() ||
value.linkedUser?.createdAt.getTime() >= balanceDate.getTime()
) {
throw new Error(
`at least one user was created after transaction balance date, logic error! ${JSON.stringify(value, null, 2)}`,
)
}
export const transactionDbSchema = v.pipe(v.object({
...transactionBaseSchema.entries,
typeId: v.enum(TransactionTypeId),
balanceDate: dateSchema,
linkedUser: userDbSchema,
}), v.custom((value: any) => {
if (value.user && value.linkedUser && !value.transactionLinkCode && value.user.gradidoId === value.linkedUser.gradidoId) {
throw new Error(`expect user to be different from linkedUser: ${JSON.stringify(value, null, 2)}`)
}
// check that user and linked user exist before transaction balance date
const balanceDate = new Date(value.balanceDate)
if (
value.user.createdAt.getTime() >= balanceDate.getTime() ||
value.linkedUser?.createdAt.getTime() >= balanceDate.getTime()
) {
throw new Error(
`at least one user was created after transaction balance date, logic error! ${JSON.stringify(value, null, 2)}`,
)
}
return value
}))
return value
}),
)
export const creationTransactionDbSchema = v.pipe(v.object({
...transactionBaseSchema.entries,
contributionDate: dateSchema,
confirmedAt: dateSchema,
confirmedByUser: userDbSchema,
transactionId: positiveNumberSchema,
}), v.custom((value: any) => {
if (value.user && value.confirmedByUser && value.user.gradidoId === value.confirmedByUser.gradidoId) {
throw new Error(`expect user to be different from confirmedByUser: ${JSON.stringify(value, null, 2)}`)
}
// check that user and confirmedByUser exist before transaction balance date
const confirmedAt = new Date(value.confirmedAt)
if (
value.user.createdAt.getTime() >= confirmedAt.getTime() ||
value.confirmedByUser?.createdAt.getTime() >= confirmedAt.getTime()
) {
throw new Error(
`at least one user was created after transaction confirmedAt date, logic error! ${JSON.stringify(value, null, 2)}`,
)
}
return value
}))
export const creationTransactionDbSchema = v.pipe(
v.object({
...transactionBaseSchema.entries,
contributionDate: dateSchema,
confirmedAt: dateSchema,
confirmedByUser: userDbSchema,
transactionId: positiveNumberSchema,
}),
v.custom((value: any) => {
if (
value.user &&
value.confirmedByUser &&
value.user.gradidoId === value.confirmedByUser.gradidoId
) {
throw new Error(
`expect user to be different from confirmedByUser: ${JSON.stringify(value, null, 2)}`,
)
}
// check that user and confirmedByUser exist before transaction balance date
const confirmedAt = new Date(value.confirmedAt)
if (
value.user.createdAt.getTime() >= confirmedAt.getTime() ||
value.confirmedByUser?.createdAt.getTime() >= confirmedAt.getTime()
) {
throw new Error(
`at least one user was created after transaction confirmedAt date, logic error! ${JSON.stringify(value, null, 2)}`,
)
}
return value
}),
)
export const transactionLinkDbSchema = v.object({
...transactionBaseSchema.entries,

View File

@ -89,7 +89,7 @@ export const confirmedTransactionSchema = v.pipe(
if (typeof data === 'object' && 'base64' in data && 'communityId' in data) {
return confirmedTransactionFromBase64(data.base64, data.communityId)
}
throw new Error('invalid data, community id missing, couldn\'t deserialize')
throw new Error("invalid data, community id missing, couldn't deserialize")
},
),
)

View File

@ -31,4 +31,4 @@ export function checkPathExist(path: string, createIfMissing: boolean = false):
export function toFolderName(name: string): string {
return name.toLowerCase().replace(/[^a-z0-9]/g, '_')
}
}

View File

@ -8,7 +8,10 @@ import { AccountType } from '../data/AccountType.enum'
import { AddressType } from '../data/AddressType.enum'
import { Uuidv4 } from '../schemas/typeGuard.schema'
export const confirmedTransactionFromBase64 = (base64: string, communityId: Uuidv4): ConfirmedTransaction => {
export const confirmedTransactionFromBase64 = (
base64: string,
communityId: Uuidv4,
): ConfirmedTransaction => {
const confirmedTransactionBinaryPtr = MemoryBlock.createPtr(MemoryBlock.fromBase64(base64))
const deserializer = new InteractionDeserialize(
confirmedTransactionBinaryPtr,

View File

@ -22,9 +22,7 @@ export function decayFormula(value: Decimal, seconds: number): Decimal {
// chatgpt: We convert to string here to avoid precision loss:
// .pow(seconds) can internally round the result, especially for large values of `seconds`.
// Using .toString() ensures full precision is preserved in the multiplication.
return value.mul(
DECAY_FACTOR.pow(seconds).toString(),
)
return value.mul(DECAY_FACTOR.pow(seconds).toString())
}
// legacy reverse decay formula