mirror of
https://github.com/IT4Change/gradido.git
synced 2026-04-06 01:25:28 +00:00
move transaction link validUntil correction into database migration
This commit is contained in:
parent
170d4bcb78
commit
d9666f3a88
@ -1,3 +1,10 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { DECAY_FACTOR, reverseLegacyDecay } from 'shared'
|
||||
|
||||
function calculateEffectiveSeconds(holdOriginal: Decimal, holdCorrected: Decimal): Decimal {
|
||||
return holdOriginal.div(holdCorrected).ln().div(DECAY_FACTOR.ln())
|
||||
}
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
/**
|
||||
* Migration: Correct historical inconsistencies in transactions, users, and contribution_links.
|
||||
@ -34,6 +41,77 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
||||
* ensuring blockchain consistency for contributions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
do {
|
||||
const rows = await queryFn(
|
||||
`
|
||||
SELECT id, amount, hold_available_amount, validUntil, createdAt, redeemedAt, deletedAt
|
||||
FROM transaction_links
|
||||
WHERE id > ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?
|
||||
`,
|
||||
[lastProcessedId, LIMIT],
|
||||
)
|
||||
if (!rows.length) {
|
||||
break
|
||||
}
|
||||
const updates: Array<{ id: number; newValidUntil: string }> = []
|
||||
for (const row of rows) {
|
||||
const validUntil = new Date(row.validUntil)
|
||||
const redeemedAt = row.redeemedAt ? new Date(row.redeemedAt) : null
|
||||
const deletedAt = row.deletedAt ? new Date(row.deletedAt) : null
|
||||
const createdAt = new Date(row.createdAt)
|
||||
const amount = new Decimal(row.amount)
|
||||
const duration = (validUntil.getTime() - createdAt.getTime()) / 1000
|
||||
const blockedAmountCorrected = reverseLegacyDecay(amount, duration)
|
||||
// fix only if the difference is big enough to have an impact
|
||||
if (blockedAmountCorrected.sub(amount).abs().lt(new Decimal('0.001'))) {
|
||||
continue
|
||||
}
|
||||
const holdAvailableAmount = new Decimal(row.hold_available_amount)
|
||||
const secondsDiff = calculateEffectiveSeconds(
|
||||
new Decimal(holdAvailableAmount.toString()),
|
||||
new Decimal(blockedAmountCorrected.toString()),
|
||||
)
|
||||
const newValidUntil = new Date(validUntil.getTime() - secondsDiff.mul(1000).toNumber())
|
||||
if (
|
||||
(redeemedAt && redeemedAt.getTime() < validUntil.getTime()) ||
|
||||
(deletedAt && deletedAt.getTime() < validUntil.getTime())
|
||||
) {
|
||||
continue
|
||||
}
|
||||
updates.push({
|
||||
id: row.id,
|
||||
newValidUntil: newValidUntil.toISOString().replace('T', ' ').replace('Z', ''),
|
||||
})
|
||||
}
|
||||
if (updates.length > 0) {
|
||||
const caseStatements = updates.map((u) => `WHEN ${u.id} THEN '${u.newValidUntil}'`).join('\n')
|
||||
|
||||
await queryFn(
|
||||
`
|
||||
UPDATE transaction_links
|
||||
SET validUntil = CASE id
|
||||
${caseStatements}
|
||||
END
|
||||
WHERE id IN (?)
|
||||
`,
|
||||
[updates.map((u) => u.id)],
|
||||
)
|
||||
sumCount += updates.length
|
||||
}
|
||||
count = rows.length
|
||||
lastProcessedId = rows[rows.length - 1].id
|
||||
} while (count === LIMIT)
|
||||
///*/
|
||||
/**
|
||||
* Fix 1: Remove self-signed contributions.
|
||||
*
|
||||
|
||||
@ -127,32 +127,8 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
|
||||
if (!senderKeyPair || !senderPublicKey || !recipientKeyPair || !recipientPublicKey) {
|
||||
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
|
||||
}
|
||||
let endDateTime: number = item.validUntil.getTime()
|
||||
|
||||
if (item.redeemedAt) {
|
||||
endDateTime = item.redeemedAt.getTime() + (1000 * 120)
|
||||
} else if (item.deletedAt) {
|
||||
endDateTime = item.deletedAt.getTime() + (1000 * 120)
|
||||
} else {
|
||||
const duration = new DurationSeconds((endDateTime - item.createdAt.getTime()) / 1000)
|
||||
const blockedAmount = GradidoUnit.fromString(reverseLegacyDecay(new Decimal(item.amount.toString()), duration.getSeconds()).toString())
|
||||
const secondsDiff = calculateEffectiveSeconds(
|
||||
new Decimal(item.holdAvailableAmount.toString()),
|
||||
new Decimal(blockedAmount.toString())
|
||||
)
|
||||
endDateTime = endDateTime - secondsDiff.toNumber() * 1000
|
||||
}
|
||||
if (endDateTime > item.validUntil.getTime()) {
|
||||
endDateTime = item.validUntil.getTime()
|
||||
}
|
||||
let duration = new DurationSeconds((endDateTime - item.createdAt.getTime()) / 1000)
|
||||
const hourInSeconds = 60 * 60
|
||||
if (duration.getSeconds() < hourInSeconds) {
|
||||
duration = new DurationSeconds(hourInSeconds)
|
||||
}
|
||||
let duration = new DurationSeconds((item.validUntil.getTime() - item.createdAt.getTime()) / 1000)
|
||||
let blockedAmount = GradidoUnit.fromString(reverseLegacyDecay(new Decimal(item.amount.toString()), duration.getSeconds()).toString())
|
||||
blockedAmount = blockedAmount.add(GradidoUnit.fromGradidoCent(1))
|
||||
// let blockedAmount = decayedAmount.calculateCompoundInterest(duration.getSeconds())
|
||||
let accountBalances: AccountBalances
|
||||
try {
|
||||
accountBalances = this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey)
|
||||
@ -171,11 +147,6 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
|
||||
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),
|
||||
|
||||
@ -44,10 +44,6 @@ export function reverseLegacyDecay(result: Decimal, seconds: number): Decimal {
|
||||
return result.div(FACTOR.pow(seconds).toString())
|
||||
}
|
||||
|
||||
export function calculateEffectiveSeconds(holdOriginal: Decimal, holdCorrected: Decimal): Decimal {
|
||||
return holdOriginal.div(holdCorrected).ln().div(FACTOR.ln());
|
||||
}
|
||||
|
||||
export function legacyCalculateDecay(amount: Decimal, from: Date, to: Date): Decimal {
|
||||
const fromMs = from.getTime()
|
||||
const toMs = to.getTime()
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
export const DECAY_START_TIME = new Date('2021-05-13T17:46:31Z')
|
||||
export const DECAY_FACTOR = new Decimal('0.99999997803504048973201202316767079413460520837376')
|
||||
export const SECONDS_PER_YEAR_GREGORIAN_CALENDER = 31556952.0
|
||||
export const LOG4JS_BASE_CATEGORY_NAME = 'shared'
|
||||
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { DECAY_START_TIME, SECONDS_PER_YEAR_GREGORIAN_CALENDER } from '../const'
|
||||
import { DECAY_FACTOR, DECAY_START_TIME, SECONDS_PER_YEAR_GREGORIAN_CALENDER } from '../const'
|
||||
|
||||
Decimal.set({
|
||||
precision: 25,
|
||||
@ -16,21 +16,30 @@ export interface Decay {
|
||||
duration: number | null
|
||||
}
|
||||
|
||||
// legacy decay formula
|
||||
export function decayFormula(value: Decimal, seconds: number): Decimal {
|
||||
// TODO why do we need to convert this here to a string to work properly?
|
||||
// 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(
|
||||
new Decimal('0.99999997803504048973201202316767079413460520837376').pow(seconds).toString(),
|
||||
DECAY_FACTOR.pow(seconds).toString(),
|
||||
)
|
||||
}
|
||||
|
||||
// legacy reverse decay formula
|
||||
export function reverseLegacyDecay(result: Decimal, seconds: number): Decimal {
|
||||
return result.div(DECAY_FACTOR.pow(seconds).toString())
|
||||
}
|
||||
|
||||
// fast and more correct decay formula
|
||||
export function decayFormulaFast(value: Decimal, seconds: number): Decimal {
|
||||
return value.mul(
|
||||
new Decimal(2).pow(new Decimal(-seconds).div(new Decimal(SECONDS_PER_YEAR_GREGORIAN_CALENDER))),
|
||||
)
|
||||
}
|
||||
|
||||
// compound interest formula, the reverse decay formula for decayFormulaFast
|
||||
export function compoundInterest(value: Decimal, seconds: number): Decimal {
|
||||
return value.mul(
|
||||
new Decimal(2).pow(new Decimal(seconds).div(new Decimal(SECONDS_PER_YEAR_GREGORIAN_CALENDER))),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user