more detailed error handling

This commit is contained in:
Einhornimmond 2025-12-28 15:31:29 +01:00
parent 02483a5993
commit e1a94e3c93
6 changed files with 95 additions and 35 deletions

View File

@ -1,6 +1,7 @@
import Decimal from 'decimal.js-light'
import { AccountBalance, GradidoUnit, MemoryBlockPtr } from 'gradido-blockchain-js'
import { legacyCalculateDecay } from '../utils'
import { NegativeBalanceError } from '../errors'
export class Balance {
private balance: GradidoUnit
@ -25,8 +26,15 @@ export class Balance {
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
@ -37,12 +45,20 @@ export class Balance {
this.date = date
}
if (this.balance.lt(GradidoUnit.zero())) {
throw new Error(`negative Gradido amount detected in Balance.updateLegacyDecay, previous balance: ${previousBalanceString}, amount: ${amount.toString()}`)
const previousDecayedBalance = legacyCalculateDecay(new Decimal(previousBalanceString), previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.updateLegacyDecay`,
previousBalanceString,
amount.toString(),
previousDecayedBalance.toString(),
)
}
}
update(amount: GradidoUnit, date: Date) {
const previousBalanceString = this.balance.toString()
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
@ -53,7 +69,13 @@ export class Balance {
this.date = date
}
if (this.balance.lt(GradidoUnit.zero())) {
throw new Error(`negative Gradido amount detected in Balance.update, previous balance: ${previousBalanceString}, amount: ${amount.toString()}`)
const previousDecayedBalance = this.balance.calculateDecay(previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.update`,
previousBalance.toString(),
amount.toString(),
previousDecayedBalance.toString(),
)
}
}

View File

@ -3,6 +3,7 @@ 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`)
this.name = 'NotEnoughGradidoBalanceError'
}
}
@ -42,3 +43,14 @@ export class BlockchainError extends Error {
this.stack = originalError.stack
}
}
export class NegativeBalanceError extends Error {
constructor(message: string, previousBalanceString: string, amount: string, previousDecayedBalance: string) {
const parts: string[] = [`NegativeBalanceError in ${message}`]
parts.push(`Previous balance: ${previousBalanceString}`)
parts.push(`Amount: ${amount}`)
parts.push(`Previous decayed balance: ${previousDecayedBalance}`)
super(parts.join('\n'))
this.name = 'NegativeBalanceError'
}
}

View File

@ -43,6 +43,21 @@ export abstract class AbstractSyncRole<T> {
return Balance.fromAccountBalance(senderLastAccountBalance, lastConfirmedTransaction.getConfirmedAt().getDate())
}
logLastBalanceChangingTransactions(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, transactionCount: number = 5) {
if (!this.context.logger.isDebugEnabled()) {
return
}
const f = new Filter()
f.updatedBalancePublicKey = publicKey
f.searchDirection = SearchDirection_DESC
f.pagination.size = transactionCount
const lastTransactions = blockchain.findAll(f)
for (let i = lastTransactions.size() - 1; i >= 0; i--) {
const tx = lastTransactions.get(i)
this.context.logger.debug(`${tx?.getConfirmedTransaction()!.toJson(true)}`)
}
}
abstract getDate(): Date
abstract loadFromDb(offset: number, count: number): Promise<T[]>
abstract pushToBlockchain(item: T): void

View File

@ -15,7 +15,7 @@ import * as v from 'valibot'
import { addToBlockchain } from '../../blockchain'
import { TransactionTypeId } from '../../data/TransactionTypeId'
import { transactionsTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError } from '../../errors'
import { BlockchainError, DatabaseError, NegativeBalanceError, NotEnoughGradidoBalanceError } from '../../errors'
import { CommunityContext, TransactionDb, transactionDbSchema } from '../../valibot.schema'
import { AbstractSyncRole } from './AbstractSync.role'
@ -99,20 +99,14 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
const recipientLastBalance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain)
if (senderLastBalance.getAccountBalance().getBalance().lt(item.amount)) {
const f = new Filter()
f.updatedBalancePublicKey = senderPublicKey
f.searchDirection = SearchDirection_DESC
f.pagination.size = 5
const lastTransactions = communityContext.blockchain.findAll(f)
for (let i = lastTransactions.size() - 1; i >= 0; i--) {
const tx = lastTransactions.get(i)
this.context.logger.error(`${tx?.getConfirmedTransaction()!.toJson(true)}`)
try {
senderLastBalance.updateLegacyDecay(item.amount.negated(), item.balanceDate)
} catch(e) {
if (e instanceof NegativeBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, communityContext.blockchain)
throw e
}
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.balanceDate)
recipientLastBalance.updateLegacyDecay(item.amount, item.balanceDate)
accountBalances.add(senderLastBalance.getAccountBalance())
@ -145,6 +139,9 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
this.calculateBalances(item, communityContext, senderPublicKey, recipientPublicKey),
)
} catch(e) {
if (e instanceof NotEnoughGradidoBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, blockchain)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
}

View File

@ -17,10 +17,12 @@ import {
import * as v from 'valibot'
import { addToBlockchain } from '../../blockchain'
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
import { BlockchainError, DatabaseError } from '../../errors'
import { BlockchainError, DatabaseError, NegativeBalanceError } from '../../errors'
import { CommunityContext, TransactionLinkDb, transactionLinkDbSchema } from '../../valibot.schema'
import { AbstractSyncRole } from './AbstractSync.role'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { legacyCalculateDecay } from '../../utils'
import Decimal from 'decimal.js-light'
export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<TransactionLinkDb> {
getDate(): Date {
@ -87,22 +89,9 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
const accountBalances = new AccountBalances()
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
if (senderLastBalance.getBalance().lt(blockedAmount)) {
const f = new Filter()
f.updatedBalancePublicKey = senderPublicKey
f.pagination.size = 4
f.searchDirection = SearchDirection_DESC
const lastSenderTransactions = communityContext.blockchain.findAll(f)
this.context.logger.error(`sender hasn't enough balance: ${senderPublicKey.convertToHex()}, last ${lastSenderTransactions.size()} balance changing transactions:`)
for(let i = lastSenderTransactions.size() - 1; i >= 0; i--) {
const lastSenderTransaction = lastSenderTransactions.get(i)
this.context.logger.error(`${lastSenderTransaction?.getConfirmedTransaction()?.toJson(true)}`)
}
}
let senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
senderLastBalance.updateLegacyDecay(blockedAmount.negated(), item.createdAt)
accountBalances.add(senderLastBalance.getAccountBalance())
accountBalances.add(new AccountBalance(recipientPublicKey, blockedAmount, ''))
return accountBalances
@ -122,16 +111,39 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
}
const duration = new DurationSeconds((item.validUntil.getTime() - item.createdAt.getTime()) / 1000)
const blockedAmount = item.amount.calculateCompoundInterest(duration.getSeconds())
let blockedAmount = item.amount.calculateCompoundInterest(duration.getSeconds())
let accountBalances: AccountBalances
try {
accountBalances = this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey)
} catch(e) {
if (item.deletedAt && e instanceof NegativeBalanceError) {
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.createdAt)
blockedAmount = senderLastBalance.getBalance()
accountBalances = this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey)
} else {
throw e
}
}
/*
const decayedAmount = GradidoUnit.fromString(legacyCalculateDecay(new Decimal(item.amount.toString()), item.createdAt, item.validUntil).toString())
const blockedAmount = item.amount.add(item.amount.minus(decayedAmount))
*/
try {
addToBlockchain(
this.buildTransaction(item, blockedAmount, duration, senderKeyPair, recipientKeyPair),
blockchain,
item.id,
this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey),
accountBalances,
)
} 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.`)
return
}
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
}

View File

@ -77,6 +77,8 @@ export const transactionLinkDbSchema = v.object({
code: identifierSeedSchema,
createdAt: dateSchema,
validUntil: dateSchema,
redeemedAt: v.nullish(dateSchema),
deletedAt: v.nullish(dateSchema),
})
export const redeemedTransactionLinkDbSchema = v.object({