mirror of
https://github.com/IT4Change/gradido.git
synced 2026-04-06 01:25:28 +00:00
refactor, make it easier to read
This commit is contained in:
parent
5c753a3b32
commit
02483a5993
@ -68,6 +68,16 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
||||
AND t.user_id <> u.id
|
||||
;`)
|
||||
|
||||
await queryFn(`
|
||||
UPDATE contributions c
|
||||
JOIN users u ON(c.moderator_id = u.id)
|
||||
SET c.confirmed_by = u.id
|
||||
WHERE c.contribution_status = 'CONFIRMED'
|
||||
AND c.user_id = c.confirmed_by
|
||||
AND u.created_at < c.confirmed_at
|
||||
AND c.user_id <> u.id
|
||||
;`)
|
||||
|
||||
/**
|
||||
* Fix 2: Replace invalid moderators with the earliest ADMIN.
|
||||
*
|
||||
@ -112,6 +122,25 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
||||
AND t.user_id <> moderator.id
|
||||
;`)
|
||||
|
||||
// similar but with confirmed by user
|
||||
await queryFn(`
|
||||
UPDATE contributions c
|
||||
JOIN (
|
||||
SELECT c_sub.id as sub_c_id, u_sub.created_at, u_sub.id
|
||||
FROM contributions c_sub
|
||||
JOIN users u_sub ON (c_sub.confirmed_by <> u_sub.id AND c_sub.user_id <> u_sub.id)
|
||||
JOIN user_roles r_sub ON (u_sub.id = r_sub.user_id)
|
||||
WHERE r_sub.role IN ('ADMIN', 'MODERATOR')
|
||||
GROUP BY c_sub.id
|
||||
ORDER BY r_sub.created_at ASC
|
||||
) confirmingUser ON (c.id = confirmingUser.sub_c_id)
|
||||
LEFT JOIN users u on(c.confirmed_by = u.id)
|
||||
SET c.confirmed_by = confirmingUser.id
|
||||
WHERE c.confirmed_at <= u.created_at
|
||||
AND c.confirmed_at > confirmingUser.created_at
|
||||
AND c.user_id <> confirmingUser.id
|
||||
;`)
|
||||
|
||||
/**
|
||||
* Fix 3: Update user creation dates to ensure historical consistency.
|
||||
*
|
||||
@ -174,6 +203,26 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
||||
WHERE CHAR_LENGTH(t.memo) < 5
|
||||
;`)
|
||||
|
||||
await queryFn(`
|
||||
UPDATE contributions t
|
||||
SET t.memo = CASE
|
||||
WHEN CHAR_LENGTH(t.memo) = 0 THEN 'empty empty'
|
||||
WHEN CHAR_LENGTH(t.memo) < 5 THEN LPAD(t.memo, 5, ' ')
|
||||
ELSE t.memo
|
||||
END
|
||||
WHERE CHAR_LENGTH(t.memo) < 5
|
||||
;`)
|
||||
|
||||
await queryFn(`
|
||||
UPDATE transaction_links t
|
||||
SET t.memo = CASE
|
||||
WHEN CHAR_LENGTH(t.memo) = 0 THEN 'empty empty'
|
||||
WHEN CHAR_LENGTH(t.memo) < 5 THEN LPAD(t.memo, 5, ' ')
|
||||
ELSE t.memo
|
||||
END
|
||||
WHERE CHAR_LENGTH(t.memo) < 5
|
||||
;`)
|
||||
|
||||
/**
|
||||
* Fix 5: Insert missing 'ADMIN_CONTRIBUTION_LINK_CREATE' events for contribution_links.
|
||||
*
|
||||
|
||||
13
dlt-connector/src/cache/KeyPairCacheManager.ts
vendored
13
dlt-connector/src/cache/KeyPairCacheManager.ts
vendored
@ -76,4 +76,17 @@ export class KeyPairCacheManager {
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
|
||||
public getKeyPairSync(
|
||||
input: string,
|
||||
createKeyPair: () => KeyPairEd25519,
|
||||
): KeyPairEd25519 {
|
||||
const keyPair = this.cache.get(input)
|
||||
if (!keyPair) {
|
||||
const keyPair = createKeyPair()
|
||||
this.cache.set(input, keyPair)
|
||||
return keyPair
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
}
|
||||
|
||||
45
dlt-connector/src/data/deriveKeyPair.ts
Normal file
45
dlt-connector/src/data/deriveKeyPair.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { GradidoBlockchainCryptoError, ParameterError } from '../errors'
|
||||
import { Hex32, Uuidv4 } from '../schemas/typeGuard.schema'
|
||||
import { hardenDerivationIndex } from '../utils/derivationHelper'
|
||||
|
||||
export function deriveFromSeed(seed: Hex32): KeyPairEd25519 {
|
||||
const keyPair = KeyPairEd25519.create(MemoryBlock.fromHex(seed))
|
||||
if (!keyPair) {
|
||||
throw new Error(`couldn't create keyPair from seed: ${seed}`)
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
|
||||
export function deriveFromCode(code: string): KeyPairEd25519 {
|
||||
// code is expected to be 24 bytes long, but we need 32
|
||||
// so hash the seed with blake2 and we have 32 Bytes
|
||||
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)}...`,
|
||||
)
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
|
||||
export function deriveFromKeyPairAndUuid(keyPair: KeyPairEd25519, uuid: Uuidv4): KeyPairEd25519 {
|
||||
const wholeHex = Buffer.from(uuid.replace(/-/g, ''), 'hex')
|
||||
const parts = []
|
||||
for (let i = 0; i < 4; i++) {
|
||||
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)
|
||||
}
|
||||
|
||||
export function deriveFromKeyPairAndIndex(keyPair: KeyPairEd25519, index: number): KeyPairEd25519 {
|
||||
const localKeyPair = keyPair.deriveChild(index)
|
||||
if (!localKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${keyPair.hasPrivateKey()}, index: ${index}`,
|
||||
)
|
||||
}
|
||||
return localKeyPair
|
||||
}
|
||||
@ -11,7 +11,7 @@ import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { Uuidv4 } from '../../schemas/typeGuard.schema'
|
||||
import { loadUserByGradidoId } from './database'
|
||||
import { bytesToMbyte } from './utils'
|
||||
import { CommunityContext, CreatedUserDb } from './valibot.schema'
|
||||
import { CommunityContext, UserDb } from './valibot.schema'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
@ -20,10 +20,10 @@ export class Context {
|
||||
public db: MySql2Database
|
||||
public communities: Map<string, CommunityContext>
|
||||
public cache: KeyPairCacheManager
|
||||
public balanceFixGradidoUser: CreatedUserDb | null
|
||||
public balanceFixGradidoUser: UserDb | null
|
||||
private timeUsed: Profiler
|
||||
|
||||
constructor(logger: Logger, db: MySql2Database, cache: KeyPairCacheManager, balanceFixGradidoUser: CreatedUserDb | null) {
|
||||
constructor(logger: Logger, db: MySql2Database, cache: KeyPairCacheManager, balanceFixGradidoUser: UserDb | null) {
|
||||
this.logger = logger
|
||||
this.db = db
|
||||
this.cache = cache
|
||||
@ -44,7 +44,7 @@ export class Context {
|
||||
})
|
||||
const db = drizzle({ client: connection })
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.5`)
|
||||
let balanceFixGradidoUser: CreatedUserDb | null = null
|
||||
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)
|
||||
if (!balanceFixGradidoUser) {
|
||||
|
||||
@ -32,7 +32,7 @@ let transactionAddedToBlockchainSum = 0
|
||||
let addToBlockchainSum = 0
|
||||
const sizeBuffer = Buffer.alloc(2)
|
||||
|
||||
function addToBlockchain(
|
||||
export function addToBlockchain(
|
||||
builder: GradidoTransactionBuilder,
|
||||
blockchain: InMemoryBlockchain,
|
||||
transactionId: number,
|
||||
@ -53,7 +53,7 @@ function addToBlockchain(
|
||||
sizeBuffer.writeUInt16LE(binTransaction.size(), 0)
|
||||
fs.appendFileSync(filePath, sizeBuffer)
|
||||
fs.appendFileSync(filePath, binTransaction.data())
|
||||
//
|
||||
//*/
|
||||
|
||||
try {
|
||||
const result = blockchain.createAndAddConfirmedTransactionExtern(
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
import { AccountBalance, AccountBalances, GradidoUnit, InMemoryBlockchainProvider } from 'gradido-blockchain-js'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { AccountBalances, GradidoTransactionBuilder, InMemoryBlockchainProvider } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import { GradidoBlockchainCryptoError } from '../../errors'
|
||||
import { ResolveKeyPair } from '../../interactions/resolveKeyPair/ResolveKeyPair.context'
|
||||
import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema'
|
||||
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 { addCommunityRootTransaction } from './blockchain'
|
||||
import { addToBlockchain } from './blockchain'
|
||||
import { Context } from './Context'
|
||||
import { communityDbToCommunity } from './convert'
|
||||
import { loadAdminUsersCache, loadCommunities, loadContributionLinkModeratorCache } from './database'
|
||||
import { generateKeyPairCommunity } from './data/keyPair'
|
||||
import { CommunityContext } from './valibot.schema'
|
||||
import { Balance } from './data/Balance'
|
||||
import { loadAdminUsersCache, loadCommunities, loadContributionLinkModeratorCache } from './database'
|
||||
import { CommunityContext } from './valibot.schema'
|
||||
|
||||
export async function bootstrap(): Promise<Context> {
|
||||
const context = await Context.create()
|
||||
@ -26,62 +24,63 @@ export async function bootstrap(): Promise<Context> {
|
||||
async function bootstrapCommunities(context: Context): Promise<Map<string, CommunityContext>> {
|
||||
const communities = new Map<string, CommunityContext>()
|
||||
const communitiesDb = await loadCommunities(context.db)
|
||||
const topicIds = new Set<HieroId>()
|
||||
const communityNames = new Set<string>()
|
||||
|
||||
for (const communityDb of communitiesDb) {
|
||||
let alias = communityDb.name
|
||||
if (communityNames.has(communityDb.name)) {
|
||||
alias = communityDb.communityUuid
|
||||
} else {
|
||||
communityNames.add(communityDb.name)
|
||||
}
|
||||
const blockchain = InMemoryBlockchainProvider.getInstance().findBlockchain(
|
||||
communityDb.uniqueAlias,
|
||||
alias,
|
||||
)
|
||||
if (!blockchain) {
|
||||
throw new Error(`Couldn't create Blockchain for community ${communityDb.communityUuid}`)
|
||||
throw new Error(`Couldn't create Blockchain for community ${alias}`)
|
||||
}
|
||||
context.logger.info(`Blockchain for community '${communityDb.uniqueAlias}' created`)
|
||||
// make sure topic id is unique
|
||||
let topicId: HieroId
|
||||
do {
|
||||
topicId = v.parse(hieroIdSchema, '0.0.' + Math.floor(Math.random() * 10000))
|
||||
} while (topicIds.has(topicId))
|
||||
topicIds.add(topicId)
|
||||
|
||||
communities.set(communityDb.communityUuid, {
|
||||
communityId: communityDb.uniqueAlias,
|
||||
blockchain,
|
||||
topicId,
|
||||
folder: communityDb.uniqueAlias.replace(/[^a-zA-Z0-9]/g, '_'),
|
||||
gmwBalance: new Balance(),
|
||||
aufBalance: new Balance(),
|
||||
})
|
||||
|
||||
generateKeyPairCommunity(communityDb, context.cache, topicId)
|
||||
context.logger.info(`Blockchain for community '${alias}' created`)
|
||||
let seed: Hex32
|
||||
if (!communityDb.foreign) {
|
||||
seed = v.parse(hex32Schema, CONFIG.HOME_COMMUNITY_SEED.convertToHex())
|
||||
} 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)
|
||||
}
|
||||
// community from db to community format the dlt connector normally uses
|
||||
const community = communityDbToCommunity(topicId, communityDb, creationDate)
|
||||
// TODO: remove code for gmw and auf key somewhere else
|
||||
const communityKeyPair = await ResolveKeyPair(new KeyPairIdentifierLogic({ communityTopicId: topicId }))
|
||||
const gmwKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
if (!gmwKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${communityDb.communityUuid}`,
|
||||
)
|
||||
const communityKeyPair = deriveFromSeed(seed)
|
||||
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)}`)
|
||||
}
|
||||
const aufKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
if (!aufKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${communityDb.communityUuid}`,
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
builder
|
||||
.setCreatedAt(creationDate)
|
||||
.setCommunityRoot(
|
||||
communityKeyPair.getPublicKey(),
|
||||
gmwKeyPair.getPublicKey(),
|
||||
aufKeyPair.getPublicKey(),
|
||||
)
|
||||
.sign(communityKeyPair)
|
||||
|
||||
const communityContext: CommunityContext = {
|
||||
communityId: alias,
|
||||
blockchain,
|
||||
keyPair: communityKeyPair,
|
||||
folder: alias.replace(/[^a-zA-Z0-9]/g, '_'),
|
||||
gmwBalance: new Balance(gmwKeyPair.getPublicKey()!),
|
||||
aufBalance: new Balance(aufKeyPair.getPublicKey()!),
|
||||
}
|
||||
communities.set(communityDb.communityUuid, communityContext)
|
||||
const accountBalances = new AccountBalances()
|
||||
accountBalances.add(new AccountBalance(gmwKeyPair.getPublicKey(), GradidoUnit.zero(), ''))
|
||||
accountBalances.add(new AccountBalance(aufKeyPair.getPublicKey(), GradidoUnit.zero(), ''))
|
||||
await addCommunityRootTransaction(blockchain, community, accountBalances)
|
||||
accountBalances.add(communityContext.aufBalance.getAccountBalance())
|
||||
accountBalances.add(communityContext.gmwBalance.getAccountBalance())
|
||||
addToBlockchain(builder, blockchain, communityDb.id, accountBalances)
|
||||
}
|
||||
return communities
|
||||
}
|
||||
|
||||
@ -1,25 +1,63 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { GradidoUnit } from 'gradido-blockchain-js'
|
||||
import { AccountBalance, GradidoUnit, MemoryBlockPtr } from 'gradido-blockchain-js'
|
||||
import { legacyCalculateDecay } from '../utils'
|
||||
|
||||
export class Balance {
|
||||
private balance: GradidoUnit
|
||||
private date: Date
|
||||
public constructor()
|
||||
{
|
||||
private publicKey: MemoryBlockPtr
|
||||
private communityId?: string | null
|
||||
|
||||
constructor(publicKey: MemoryBlockPtr, communityId?: string | null) {
|
||||
this.balance = new GradidoUnit(0)
|
||||
this.date = new Date()
|
||||
this.publicKey = publicKey
|
||||
this.communityId = communityId
|
||||
}
|
||||
|
||||
public update(amount: Decimal, date: Date) {
|
||||
static fromAccountBalance(accountBalance: AccountBalance, confirmedAt: Date): Balance {
|
||||
const balance = new Balance(accountBalance.getPublicKey()!, accountBalance.getCommunityId() || null)
|
||||
balance.update(accountBalance.getBalance(), confirmedAt)
|
||||
return balance
|
||||
}
|
||||
|
||||
getBalance(): GradidoUnit {
|
||||
return this.balance
|
||||
}
|
||||
|
||||
updateLegacyDecay(amount: GradidoUnit, date: Date) {
|
||||
const previousBalanceString = this.balance.toString()
|
||||
if (this.balance.equal(GradidoUnit.zero())) {
|
||||
this.balance = GradidoUnit.fromString(amount.toString())
|
||||
this.balance = amount
|
||||
this.date = date
|
||||
} else {
|
||||
const decayedBalance = legacyCalculateDecay(new Decimal(this.balance.toString()), this.date, date )
|
||||
const newBalance = decayedBalance.add(amount)
|
||||
const newBalance = decayedBalance.add(new Decimal(amount.toString()))
|
||||
this.balance = GradidoUnit.fromString(newBalance.toString())
|
||||
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()}`)
|
||||
}
|
||||
}
|
||||
|
||||
update(amount: GradidoUnit, date: Date) {
|
||||
const previousBalanceString = this.balance.toString()
|
||||
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())) {
|
||||
throw new Error(`negative Gradido amount detected in Balance.update, previous balance: ${previousBalanceString}, amount: ${amount.toString()}`)
|
||||
}
|
||||
}
|
||||
|
||||
getAccountBalance(): AccountBalance {
|
||||
return new AccountBalance(this.publicKey, this.balance, this.communityId || '')
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export enum ContributionStatus {
|
||||
PENDING = 'PENDING',
|
||||
DELETED = 'DELETED',
|
||||
IN_PROGRESS = 'IN_PROGRESS',
|
||||
DENIED = 'DENIED',
|
||||
CONFIRMED = 'CONFIRMED',
|
||||
}
|
||||
@ -4,6 +4,8 @@ import { MySql2Database } from 'drizzle-orm/mysql2'
|
||||
import { getLogger } from 'log4js'
|
||||
import * as v from 'valibot'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { ContributionStatus } from './data/ContributionStatus'
|
||||
import { TransactionTypeId } from './data/TransactionTypeId'
|
||||
import {
|
||||
communitiesTable,
|
||||
contributionsTable,
|
||||
@ -17,12 +19,14 @@ import {
|
||||
userSelectSchema,
|
||||
usersTable
|
||||
} from './drizzle.schema'
|
||||
import { TransactionTypeId } from './data/TransactionTypeId'
|
||||
import { DatabaseError } from './errors'
|
||||
import {
|
||||
CommunityDb,
|
||||
CreatedUserDb,
|
||||
UserDb,
|
||||
CreationTransactionDb,
|
||||
communityDbSchema,
|
||||
createdUserDbSchema,
|
||||
userDbSchema,
|
||||
creationTransactionDbSchema,
|
||||
TransactionDb,
|
||||
TransactionLinkDb,
|
||||
transactionDbSchema,
|
||||
@ -33,8 +37,8 @@ const logger = getLogger(
|
||||
`${LOG4JS_BASE_CATEGORY}.migrations.db-v2.7.0_to_blockchain-v3.6.blockchain`,
|
||||
)
|
||||
|
||||
export const contributionLinkModerators = new Map<number, CreatedUserDb>()
|
||||
export const adminUsers = new Map<string, CreatedUserDb>()
|
||||
export const contributionLinkModerators = new Map<number, UserDb>()
|
||||
export const adminUsers = new Map<string, UserDb>()
|
||||
const transactionIdSet = new Set<number>()
|
||||
|
||||
export async function loadContributionLinkModeratorCache(db: MySql2Database): Promise<void> {
|
||||
@ -49,7 +53,7 @@ export async function loadContributionLinkModeratorCache(db: MySql2Database): Pr
|
||||
.orderBy(asc(eventsTable.id))
|
||||
|
||||
result.map((row: any) => {
|
||||
contributionLinkModerators.set(row.event.involvedContributionLinkId, v.parse(createdUserDbSchema, row.user))
|
||||
contributionLinkModerators.set(row.event.involvedContributionLinkId, v.parse(userDbSchema, row.user))
|
||||
})
|
||||
}
|
||||
|
||||
@ -63,7 +67,7 @@ export async function loadAdminUsersCache(db: MySql2Database): Promise<void> {
|
||||
.leftJoin(usersTable, eq(userRolesTable.userId, usersTable.id))
|
||||
|
||||
result.map((row: any) => {
|
||||
adminUsers.set(row.gradidoId, v.parse(createdUserDbSchema, row.user))
|
||||
adminUsers.set(row.gradidoId, v.parse(userDbSchema, row.user))
|
||||
})
|
||||
}
|
||||
|
||||
@ -71,6 +75,7 @@ export async function loadAdminUsersCache(db: MySql2Database): Promise<void> {
|
||||
export async function loadCommunities(db: MySql2Database): Promise<CommunityDb[]> {
|
||||
const result = await db
|
||||
.select({
|
||||
id: communitiesTable.id,
|
||||
foreign: communitiesTable.foreign,
|
||||
communityUuid: communitiesTable.communityUuid,
|
||||
name: communitiesTable.name,
|
||||
@ -83,18 +88,8 @@ export async function loadCommunities(db: MySql2Database): Promise<CommunityDb[]
|
||||
.orderBy(asc(communitiesTable.id))
|
||||
.groupBy(communitiesTable.communityUuid)
|
||||
|
||||
const communityNames = new Set<string>()
|
||||
return result.map((row: any) => {
|
||||
let alias = row.name
|
||||
if (communityNames.has(row.name)) {
|
||||
alias = row.community_uuid
|
||||
} else {
|
||||
communityNames.add(row.name)
|
||||
}
|
||||
return v.parse(communityDbSchema, {
|
||||
...row,
|
||||
uniqueAlias: alias,
|
||||
})
|
||||
return v.parse(communityDbSchema, row)
|
||||
})
|
||||
}
|
||||
|
||||
@ -102,7 +97,7 @@ export async function loadUsers(
|
||||
db: MySql2Database,
|
||||
offset: number,
|
||||
count: number,
|
||||
): Promise<CreatedUserDb[]> {
|
||||
): Promise<UserDb[]> {
|
||||
const result = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
@ -110,19 +105,59 @@ export async function loadUsers(
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row: any) => {
|
||||
return v.parse(createdUserDbSchema, row)
|
||||
})
|
||||
return result.map((row: any) => v.parse(userDbSchema, row))
|
||||
}
|
||||
|
||||
export async function loadUserByGradidoId(db: MySql2Database, gradidoId: string): Promise<CreatedUserDb | null> {
|
||||
export async function loadUserByGradidoId(db: MySql2Database, gradidoId: string): Promise<UserDb | null> {
|
||||
const result = await db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.gradidoId, gradidoId))
|
||||
.limit(1)
|
||||
|
||||
return result.length ? v.parse(createdUserDbSchema, result[0]) : null
|
||||
return result.length ? v.parse(userDbSchema, result[0]) : null
|
||||
}
|
||||
|
||||
export async function loadLocalTransferTransactions(
|
||||
db: MySql2Database,
|
||||
offset: number,
|
||||
count: number,
|
||||
): Promise<TransactionDb[]> {
|
||||
const linkedUsers = alias(usersTable, 'linkedUser')
|
||||
const result = await db
|
||||
.select({
|
||||
transaction: transactionsTable,
|
||||
user: usersTable,
|
||||
linkedUser: linkedUsers,
|
||||
})
|
||||
.from(transactionsTable)
|
||||
.where(
|
||||
and(
|
||||
eq(transactionsTable.typeId, TransactionTypeId.RECEIVE),
|
||||
isNull(transactionsTable.transactionLinkId),
|
||||
isNotNull(transactionsTable.linkedUserId),
|
||||
eq(usersTable.foreign, 0),
|
||||
eq(linkedUsers.foreign, 0),
|
||||
)
|
||||
)
|
||||
.leftJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
|
||||
.leftJoin(linkedUsers, eq(transactionsTable.linkedUserId, linkedUsers.id))
|
||||
.orderBy(asc(transactionsTable.balanceDate), asc(transactionsTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row: any) => {
|
||||
const item = {
|
||||
...row.transaction,
|
||||
user: row.user,
|
||||
linkedUser: row.linkedUser,
|
||||
}
|
||||
try {
|
||||
return v.parse(transactionDbSchema, item)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadLocalTransferTransactions', item, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function loadTransactions(
|
||||
@ -131,8 +166,7 @@ export async function loadTransactions(
|
||||
count: number,
|
||||
): Promise<TransactionDb[]> {
|
||||
const linkedUsers = alias(usersTable, 'linkedUser')
|
||||
const linkedTransactions = alias(transactionsTable, 'linkedTransaction')
|
||||
|
||||
|
||||
const result = await db
|
||||
.select({
|
||||
transaction: transactionsTable,
|
||||
@ -142,7 +176,6 @@ export async function loadTransactions(
|
||||
id: transactionLinksTable.id,
|
||||
code: transactionLinksTable.code
|
||||
},
|
||||
linkedUserBalance: linkedTransactions.balance,
|
||||
})
|
||||
.from(transactionsTable)
|
||||
.where(
|
||||
@ -158,7 +191,6 @@ export async function loadTransactions(
|
||||
transactionLinksTable,
|
||||
eq(transactionsTable.transactionLinkId, transactionLinksTable.id),
|
||||
)
|
||||
.leftJoin(linkedTransactions, eq(transactionsTable.linkedTransactionId, linkedTransactions.id))
|
||||
.orderBy(asc(transactionsTable.balanceDate), asc(transactionsTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
@ -176,7 +208,6 @@ export async function loadTransactions(
|
||||
transactionLinkCode: row.transactionLink ? row.transactionLink.code : null,
|
||||
user: row.user,
|
||||
linkedUser: row.linkedUser,
|
||||
linkedUserBalance: row.linkedUserBalance,
|
||||
})
|
||||
} catch (e) {
|
||||
logger.error(`table row: ${JSON.stringify(row, null, 2)}`)
|
||||
@ -188,6 +219,43 @@ export async function loadTransactions(
|
||||
})
|
||||
}
|
||||
|
||||
export async function loadCreations(
|
||||
db: MySql2Database,
|
||||
offset: number,
|
||||
count: number,
|
||||
): Promise<CreationTransactionDb[]> {
|
||||
const confirmedByUsers = alias(usersTable, 'confirmedByUser')
|
||||
const result = await db
|
||||
.select({
|
||||
contribution: contributionsTable,
|
||||
user: usersTable,
|
||||
confirmedByUser: confirmedByUsers,
|
||||
})
|
||||
.from(contributionsTable)
|
||||
.where(and(
|
||||
isNull(contributionsTable.contributionLinkId),
|
||||
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
|
||||
))
|
||||
.leftJoin(usersTable, eq(contributionsTable.userId, usersTable.id))
|
||||
.leftJoin(confirmedByUsers, eq(contributionsTable.confirmedBy, confirmedByUsers.id))
|
||||
.orderBy(asc(contributionsTable.confirmedAt), asc(contributionsTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
const creationTransactionDb = {
|
||||
...row.contribution,
|
||||
user: row.user,
|
||||
confirmedByUser: row.confirmedByUser,
|
||||
}
|
||||
try {
|
||||
return v.parse(creationTransactionDbSchema, creationTransactionDb)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadCreations', creationTransactionDb, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function loadInvalidContributionTransactions(
|
||||
db: MySql2Database,
|
||||
offset: number,
|
||||
|
||||
@ -28,10 +28,14 @@ export const communitiesTable = mysqlTable(
|
||||
|
||||
export const contributionsTable = mysqlTable('contributions', {
|
||||
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`),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string'}).default(sql`NULL`),
|
||||
contributionStatus: varchar('contribution_status', { length: 12 }).default('\'PENDING\'').notNull(),
|
||||
transactionId: int('transaction_id').default(sql`NULL`),
|
||||
})
|
||||
|
||||
@ -49,9 +53,11 @@ export const usersTable = mysqlTable(
|
||||
foreign: tinyint().default(0).notNull(),
|
||||
gradidoId: char('gradido_id', { length: 36 }).notNull(),
|
||||
communityUuid: varchar('community_uuid', { length: 36 }).default(sql`NULL`),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string', fsp: 3 }).default(sql`NULL`),
|
||||
createdAt: datetime('created_at', { mode: 'string', fsp: 3 })
|
||||
.default(sql`current_timestamp(3)`)
|
||||
.notNull(),
|
||||
|
||||
},
|
||||
(table) => [unique('uuid_key').on(table.gradidoId, table.communityUuid)],
|
||||
)
|
||||
@ -100,4 +106,6 @@ export const transactionLinksTable = mysqlTable('transaction_links', {
|
||||
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`),
|
||||
})
|
||||
|
||||
@ -1,6 +1,44 @@
|
||||
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`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class DatabaseError extends Error {
|
||||
constructor(message: string, rows: any, originalError: Error) {
|
||||
const parts: string[] = [`DatabaseError in ${message}`]
|
||||
|
||||
// Valibot-specific
|
||||
if (originalError instanceof v.ValiError) {
|
||||
const flattened = v.flatten(originalError.issues)
|
||||
parts.push('Validation errors:')
|
||||
parts.push(JSON.stringify(flattened, null, 2))
|
||||
} else {
|
||||
parts.push(`Original error: ${originalError.message}`)
|
||||
}
|
||||
|
||||
parts.push('Rows:')
|
||||
parts.push(JSON.stringify(rows, null, 2))
|
||||
|
||||
super(parts.join('\n\n'))
|
||||
|
||||
this.name = 'DatabaseError'
|
||||
}
|
||||
}
|
||||
|
||||
export class BlockchainError extends Error {
|
||||
constructor(message: string, item: any, originalError: Error) {
|
||||
const parts: string[] = [`BlockchainError in ${message}`]
|
||||
|
||||
parts.push(`Original error: ${originalError.message}`)
|
||||
parts.push('Item:')
|
||||
parts.push(JSON.stringify(item, null, 2))
|
||||
|
||||
super(parts.join('\n\n'))
|
||||
|
||||
this.name = 'BlockchainError'
|
||||
this.stack = originalError.stack
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Filter } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { onShutdown } from '../../../../shared/src/helper/onShutdown'
|
||||
import { uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import { exportAllCommunities } from './binaryExport'
|
||||
import { bootstrap } from './bootstrap'
|
||||
import { syncDbWithBlockchainContext } from './interaction/syncDbWithBlockchain/syncDbWithBlockchain.context'
|
||||
|
||||
const BATCH_SIZE = 1000
|
||||
const BATCH_SIZE = 500
|
||||
|
||||
async function main() {
|
||||
// prepare in memory blockchains
|
||||
@ -17,7 +18,12 @@ async function main() {
|
||||
})
|
||||
|
||||
// synchronize to in memory blockchain
|
||||
await syncDbWithBlockchainContext(context, BATCH_SIZE)
|
||||
try {
|
||||
await syncDbWithBlockchainContext(context, BATCH_SIZE)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
//context.logBlogchain(v.parse(uuidv4Schema, 'e70da33e-5976-4767-bade-aa4e4fa1c01a'))
|
||||
}
|
||||
|
||||
// write as binary file for GradidoNode
|
||||
exportAllCommunities(context, BATCH_SIZE)
|
||||
|
||||
@ -1,10 +1,38 @@
|
||||
import { AccountBalances } from 'gradido-blockchain-js'
|
||||
import { AccountBalances, Filter, InMemoryBlockchain, SearchDirection_DESC } from 'gradido-blockchain-js'
|
||||
import { KeyPairIdentifierLogic } from '../../../../data/KeyPairIdentifier.logic'
|
||||
import { ResolveKeyPair } from '../../../../interactions/resolveKeyPair/ResolveKeyPair.context'
|
||||
import { IdentifierAccount } from '../../../../schemas/account.schema'
|
||||
import { Transaction } from '../../../../schemas/transaction.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { Balance } from '../../data/Balance'
|
||||
|
||||
export class AbstractBalancesRole {
|
||||
public accountBalances: AccountBalances
|
||||
export abstract class AbstractBalancesRole {
|
||||
public constructor(protected transaction: Transaction) {}
|
||||
|
||||
public constructor() {
|
||||
this.accountBalances = new AccountBalances()
|
||||
abstract getAccountBalances(context: Context): Promise<AccountBalances>
|
||||
|
||||
async getLastBalanceForUser(identifierAccount: IdentifierAccount, blockchain: InMemoryBlockchain): Promise<Balance> {
|
||||
const userKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(identifierAccount),
|
||||
)
|
||||
const f = new Filter()
|
||||
f.involvedPublicKey = userKeyPair.getPublicKey()
|
||||
f.pagination.size = 1
|
||||
f.searchDirection = SearchDirection_DESC
|
||||
const lastSenderTransaction = blockchain.findOne(f)
|
||||
if (!lastSenderTransaction) {
|
||||
throw new Error(`no last transaction found for user: ${JSON.stringify(identifierAccount, null, 2)}`)
|
||||
}
|
||||
|
||||
const lastConfirmedTransaction = lastSenderTransaction.getConfirmedTransaction()
|
||||
if (!lastConfirmedTransaction) {
|
||||
throw new Error(`invalid transaction, getConfirmedTransaction call failed for transaction nr: ${lastSenderTransaction.getTransactionNr()}`)
|
||||
}
|
||||
const senderLastAccountBalance = lastConfirmedTransaction.getAccountBalance(userKeyPair.getPublicKey(), '')
|
||||
if (!senderLastAccountBalance) {
|
||||
throw new Error(
|
||||
`no sender account balance found for transaction nr: ${lastSenderTransaction.getTransactionNr()} and public key: ${userKeyPair.getPublicKey()?.convertToHex()}`
|
||||
)
|
||||
}
|
||||
return Balance.fromAccountBalance(senderLastAccountBalance, lastConfirmedTransaction.getConfirmedAt().getDate())
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,33 @@
|
||||
import { AccountBalances } from 'gradido-blockchain-js'
|
||||
import { Transaction } from '../../../../schemas/transaction.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { TransactionDb } from '../../valibot.schema'
|
||||
import { AbstractBalancesRole } from './AbstractBalances.role'
|
||||
|
||||
export class CreationBalancesRole extends AbstractBalancesRole {
|
||||
|
||||
}
|
||||
|
||||
constructor(transaction: Transaction, protected dbTransaction: TransactionDb) {
|
||||
super(transaction)
|
||||
}
|
||||
|
||||
async getAccountBalances(context: Context): Promise<AccountBalances> {
|
||||
if (this.dbTransaction.linkedUser.communityUuid !== this.dbTransaction.user.communityUuid) {
|
||||
throw new Error('creation: both recipient and signer must belong to same community')
|
||||
}
|
||||
|
||||
const accountBalances = new AccountBalances()
|
||||
const communityContext = context.getCommunityContextByUuid(this.dbTransaction.user.communityUuid)
|
||||
const balance = await this.getLastBalanceForUser(this.transaction.user, communityContext.blockchain)
|
||||
|
||||
// calculate decay since last balance with legacy calculation method
|
||||
balance.updateLegacyDecay(this.dbTransaction.amount, this.dbTransaction.balanceDate)
|
||||
communityContext.aufBalance.updateLegacyDecay(this.dbTransaction.amount, this.dbTransaction.balanceDate)
|
||||
communityContext.gmwBalance.updateLegacyDecay(this.dbTransaction.amount, this.dbTransaction.balanceDate)
|
||||
|
||||
accountBalances.add(balance.getAccountBalance())
|
||||
accountBalances.add(communityContext.aufBalance.getAccountBalance())
|
||||
accountBalances.add(communityContext.gmwBalance.getAccountBalance())
|
||||
|
||||
return accountBalances
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import { AccountBalance, AccountBalances, GradidoUnit } from 'gradido-blockchain-js'
|
||||
import { KeyPairIdentifierLogic } from '../../../../data/KeyPairIdentifier.logic'
|
||||
import { ResolveKeyPair } from '../../../../interactions/resolveKeyPair/ResolveKeyPair.context'
|
||||
import { Transaction } from '../../../../schemas/transaction.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { TransactionLinkDb } from '../../valibot.schema'
|
||||
import { AbstractBalancesRole } from './AbstractBalances.role'
|
||||
|
||||
|
||||
export class DeferredTransferBalancesRole extends AbstractBalancesRole {
|
||||
constructor(transaction: Transaction, protected dbTransactionLink: TransactionLinkDb) {
|
||||
super(transaction)
|
||||
}
|
||||
|
||||
async getAccountBalances(context: Context): Promise<AccountBalances> {
|
||||
const senderCommunityContext = context.getCommunityContextByUuid(this.dbTransactionLink.user.communityUuid)
|
||||
const accountBalances = new AccountBalances()
|
||||
|
||||
const seededIdentifier = new KeyPairIdentifierLogic(this.transaction.linkedUser!)
|
||||
if (!seededIdentifier.isSeedKeyPair()) {
|
||||
throw new Error(`linked user is not a seed: ${JSON.stringify(this.transaction, null, 2)}`)
|
||||
}
|
||||
const seedKeyPair = await ResolveKeyPair(seededIdentifier)
|
||||
const senderAccountBalance = await this.getLastBalanceForUser(this.transaction.user, senderCommunityContext.blockchain)
|
||||
|
||||
let amount = GradidoUnit.fromString(this.dbTransactionLink.amount.toString())
|
||||
amount = amount.calculateCompoundInterest((this.dbTransactionLink.validUntil.getTime() - this.dbTransactionLink.createdAt.getTime()) / 60000)
|
||||
senderAccountBalance.updateLegacyDecay(amount.negated(), this.dbTransactionLink.createdAt)
|
||||
accountBalances.add(senderAccountBalance.getAccountBalance())
|
||||
accountBalances.add(new AccountBalance(seedKeyPair.getPublicKey(), amount, ''))
|
||||
return accountBalances
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { AccountBalances } from 'gradido-blockchain-js'
|
||||
import { Transaction } from '../../../../schemas/transaction.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { TransactionDb } from '../../valibot.schema'
|
||||
import { AbstractBalancesRole } from './AbstractBalances.role'
|
||||
|
||||
export class RedeemDeferredTransferBalancesRole extends AbstractBalancesRole {
|
||||
constructor(transaction: Transaction, protected dbTransaction: TransactionDb) {
|
||||
super(transaction)
|
||||
}
|
||||
|
||||
async getAccountBalances(context: Context): Promise<AccountBalances> {
|
||||
// I use the receiving part of transaction pair, so the user is the recipient and the linked user the sender and amount is positive
|
||||
const senderCommunityContext = context.getCommunityContextByUuid(this.dbTransaction.linkedUser.communityUuid)
|
||||
const recipientCommunityContext = context.getCommunityContextByUuid(this.dbTransaction.user.communityUuid)
|
||||
const accountBalances = new AccountBalances()
|
||||
|
||||
context.cache.setHomeCommunityTopicId(senderCommunityContext.topicId)
|
||||
const senderLastBalance = await this.getLastBalanceForUser(this.transaction.linkedUser!, senderCommunityContext.blockchain)
|
||||
context.cache.setHomeCommunityTopicId(recipientCommunityContext.topicId)
|
||||
const recipientLastBalance = await this.getLastBalanceForUser(this.transaction.user, recipientCommunityContext.blockchain)
|
||||
|
||||
senderLastBalance.updateLegacyDecay(this.dbTransaction.amount.negate(), this.dbTransaction.balanceDate)
|
||||
recipientLastBalance.updateLegacyDecay(this.dbTransaction.amount, this.dbTransaction.balanceDate)
|
||||
|
||||
accountBalances.add(senderLastBalance.getAccountBalance())
|
||||
accountBalances.add(recipientLastBalance.getAccountBalance())
|
||||
return accountBalances
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import { AccountBalance, AccountBalances, GradidoUnit } from 'gradido-blockchain-js'
|
||||
import { KeyPairIdentifierLogic } from '../../../../data/KeyPairIdentifier.logic'
|
||||
import { ResolveKeyPair } from '../../../../interactions/resolveKeyPair/ResolveKeyPair.context'
|
||||
import { Context } from '../../Context'
|
||||
import { AbstractBalancesRole } from './AbstractBalances.role'
|
||||
|
||||
|
||||
export class RegisterAddressBalancesRole extends AbstractBalancesRole {
|
||||
async getAccountBalances(_context: Context): Promise<AccountBalances> {
|
||||
const accountBalances = new AccountBalances()
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(this.transaction.user),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), GradidoUnit.zero(), ''))
|
||||
return accountBalances
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { AccountBalances } from 'gradido-blockchain-js'
|
||||
import { Transaction } from '../../../../schemas/transaction.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { TransactionDb } from '../../valibot.schema'
|
||||
import { AbstractBalancesRole } from './AbstractBalances.role'
|
||||
|
||||
export class TransferBalancesRole extends AbstractBalancesRole {
|
||||
constructor(transaction: Transaction, protected dbTransaction: TransactionDb) {
|
||||
super(transaction)
|
||||
}
|
||||
|
||||
async getAccountBalances(context: Context): Promise<AccountBalances> {
|
||||
// I use the receiving part of transaction pair, so the user is the recipient and the linked user the sender and amount is positive
|
||||
const senderCommunityContext = context.getCommunityContextByUuid(this.dbTransaction.linkedUser.communityUuid)
|
||||
const recipientCommunityContext = context.getCommunityContextByUuid(this.dbTransaction.user.communityUuid)
|
||||
const accountBalances = new AccountBalances()
|
||||
|
||||
context.cache.setHomeCommunityTopicId(senderCommunityContext.topicId)
|
||||
const senderLastBalance = await this.getLastBalanceForUser(this.transaction.linkedUser!, senderCommunityContext.blockchain)
|
||||
context.cache.setHomeCommunityTopicId(recipientCommunityContext.topicId)
|
||||
const recipientLastBalance = await this.getLastBalanceForUser(this.transaction.user, recipientCommunityContext.blockchain)
|
||||
|
||||
senderLastBalance.updateLegacyDecay(this.dbTransaction.amount.negate(), this.dbTransaction.balanceDate)
|
||||
recipientLastBalance.updateLegacyDecay(this.dbTransaction.amount, this.dbTransaction.balanceDate)
|
||||
|
||||
accountBalances.add(senderLastBalance.getAccountBalance())
|
||||
accountBalances.add(recipientLastBalance.getAccountBalance())
|
||||
return accountBalances
|
||||
}
|
||||
}
|
||||
@ -1,70 +1,32 @@
|
||||
import { AccountBalances } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { InputTransactionType } from '../../../../data/InputTransactionType.enum'
|
||||
import { Transaction } from '../../../../schemas/transaction.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { TransactionDb } from '../../valibot.schema'
|
||||
import { TransactionDb, TransactionLinkDb, transactionDbSchema, transactionLinkDbSchema } from '../../valibot.schema'
|
||||
import { AbstractBalancesRole } from './AbstractBalances.role'
|
||||
import { CreationBalancesRole } from './CreationBalances.role'
|
||||
import { DeferredTransferBalancesRole } from './DeferredTransferBalances.role'
|
||||
import { RedeemDeferredTransferBalancesRole } from './RedeemDeferredTransferBalances.role'
|
||||
import { RegisterAddressBalancesRole } from './RegisterAddressBalances.role'
|
||||
import { TransferBalancesRole } from './TransferBalances.role'
|
||||
|
||||
export function accountBalancesContext(transaction: Transaction, dbTransaction: TransactionDb, context: Context) {
|
||||
let role: AbstractBalancesRole | null = null
|
||||
if (InputTransactionType.GRADIDO_CREATION === transaction.type) {
|
||||
role = new CreationBalancesRole()
|
||||
}
|
||||
export async function accountBalancesContext(transaction: Transaction, item: TransactionDb | TransactionLinkDb, context: Context): Promise<AccountBalances> {
|
||||
let role: AbstractBalancesRole | null = null
|
||||
if (InputTransactionType.GRADIDO_CREATION === transaction.type) {
|
||||
role = new CreationBalancesRole(transaction, v.parse(transactionDbSchema, item))
|
||||
} else if (InputTransactionType.GRADIDO_TRANSFER === transaction.type) {
|
||||
role = new TransferBalancesRole(transaction, v.parse(transactionDbSchema, item))
|
||||
} else if (InputTransactionType.GRADIDO_DEFERRED_TRANSFER === transaction.type) {
|
||||
role = new DeferredTransferBalancesRole(transaction, v.parse(transactionLinkDbSchema, item))
|
||||
} else if (InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER === transaction.type) {
|
||||
role = new RedeemDeferredTransferBalancesRole(transaction, v.parse(transactionDbSchema, item))
|
||||
} else if (InputTransactionType.REGISTER_ADDRESS === transaction.type) {
|
||||
role = new RegisterAddressBalancesRole(transaction)
|
||||
}
|
||||
if (!role) {
|
||||
throw new Error(`No role found for transaction type ${transaction.type}`)
|
||||
}
|
||||
return await role.getAccountBalances(context)
|
||||
}
|
||||
|
||||
/*
|
||||
const senderCommunityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
|
||||
const recipientCommunityContext = this.context.getCommunityContextByUuid(
|
||||
item.linkedUser.communityUuid,
|
||||
)
|
||||
this.context.cache.setHomeCommunityTopicId(senderCommunityContext.topicId)
|
||||
const transaction = transactionDbToTransaction(
|
||||
item,
|
||||
senderCommunityContext.topicId,
|
||||
recipientCommunityContext.topicId,
|
||||
)
|
||||
const accountBalances = new AccountBalances()
|
||||
if (InputTransactionType.GRADIDO_CREATION === transaction.type) {
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.linkedUser!),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), item.balance, ''))
|
||||
// update gmw and auf
|
||||
this.updateGmwAuf(new Decimal(item.amount.toString(4)), item.balanceDate)
|
||||
const communityKeyPair = await ResolveKeyPair(new KeyPairIdentifierLogic({ communityTopicId: senderCommunityContext.topicId }))
|
||||
const gmwKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
if (!gmwKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${senderCommunityContext.communityId}`,
|
||||
)
|
||||
}
|
||||
const aufKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
if (!aufKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${senderCommunityContext.communityId}`,
|
||||
)
|
||||
}
|
||||
accountBalances.add(new AccountBalance(gmwKeyPair.getPublicKey(), GradidoUnit.fromString(
|
||||
TransactionsSyncRole.gmwBalance!.balance.toString()), ''))
|
||||
accountBalances.add(new AccountBalance(aufKeyPair.getPublicKey(), GradidoUnit.fromString(
|
||||
TransactionsSyncRole.aufBalance!.balance.toString()), ''))
|
||||
} else if (InputTransactionType.REGISTER_ADDRESS === transaction.type) {
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.user),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), GradidoUnit.zero(), ''))
|
||||
} else {
|
||||
// I use the receiving part of transaction pair, so the user is the recipient and the linked user the sender
|
||||
const senderKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.linkedUser!),
|
||||
)
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.user),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(senderKeyPair.getPublicKey(), item.linkedUserBalance, ''))
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), item.balance, ''))
|
||||
*/
|
||||
@ -1,7 +1,11 @@
|
||||
import { Profiler } from 'gradido-blockchain-js'
|
||||
import { Filter, InMemoryBlockchain, KeyPairEd25519, MemoryBlockPtr, Profiler, SearchDirection_DESC } from 'gradido-blockchain-js'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../../../config/const'
|
||||
import { deriveFromKeyPairAndIndex, deriveFromKeyPairAndUuid } from '../../../../data/deriveKeyPair'
|
||||
import { Uuidv4 } from '../../../../schemas/typeGuard.schema'
|
||||
import { Context } from '../../Context'
|
||||
import { Balance } from '../../data/Balance'
|
||||
import { CommunityContext } from '../../valibot.schema'
|
||||
|
||||
export abstract class AbstractSyncRole<T> {
|
||||
private items: T[] = []
|
||||
@ -14,9 +18,34 @@ export abstract class AbstractSyncRole<T> {
|
||||
)
|
||||
}
|
||||
|
||||
getAccountKeyPair(communityContext: CommunityContext, gradidoId: Uuidv4): KeyPairEd25519 {
|
||||
return this.context.cache.getKeyPairSync(gradidoId, () => {
|
||||
return deriveFromKeyPairAndIndex(deriveFromKeyPairAndUuid(communityContext.keyPair, gradidoId), 1)
|
||||
})
|
||||
}
|
||||
|
||||
getLastBalanceForUser(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, communityId: string = ''): Balance {
|
||||
if (publicKey.isEmpty()) {
|
||||
throw new Error('publicKey is empty')
|
||||
}
|
||||
const lastSenderTransaction = blockchain.findOne(Filter.lastBalanceFor(publicKey))
|
||||
if (!lastSenderTransaction) {
|
||||
return new Balance(publicKey, communityId)
|
||||
}
|
||||
const lastConfirmedTransaction = lastSenderTransaction.getConfirmedTransaction()
|
||||
if (!lastConfirmedTransaction) {
|
||||
throw new Error(`invalid transaction, getConfirmedTransaction call failed for transaction nr: ${lastSenderTransaction.getTransactionNr()}`)
|
||||
}
|
||||
const senderLastAccountBalance = lastConfirmedTransaction.getAccountBalance(publicKey, communityId)
|
||||
if (!senderLastAccountBalance) {
|
||||
return new Balance(publicKey, communityId)
|
||||
}
|
||||
return Balance.fromAccountBalance(senderLastAccountBalance, lastConfirmedTransaction.getConfirmedAt().getDate())
|
||||
}
|
||||
|
||||
abstract getDate(): Date
|
||||
abstract loadFromDb(offset: number, count: number): Promise<T[]>
|
||||
abstract pushToBlockchain(item: T): Promise<void>
|
||||
abstract pushToBlockchain(item: T): void
|
||||
abstract itemTypeName(): string
|
||||
|
||||
// return count of new loaded items
|
||||
@ -38,11 +67,11 @@ export abstract class AbstractSyncRole<T> {
|
||||
return 0
|
||||
}
|
||||
|
||||
async toBlockchain(): Promise<void> {
|
||||
toBlockchain(): void {
|
||||
if (this.isEmpty()) {
|
||||
throw new Error(`[toBlockchain] No items, please call this only if isEmpty returns false`)
|
||||
}
|
||||
await this.pushToBlockchain(this.shift())
|
||||
this.pushToBlockchain(this.shift())
|
||||
}
|
||||
|
||||
peek(): T {
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { and, asc, eq, isNotNull } from 'drizzle-orm'
|
||||
import * as v from 'valibot'
|
||||
import { Context } from '../../Context'
|
||||
import { adminUsers, contributionLinkModerators, loadContributionLinkTransactions } from '../../database'
|
||||
import { CreatedUserDb, TransactionDb, transactionDbSchema } from '../../valibot.schema'
|
||||
import { TransactionsSyncRole } from './TransactionsSync.role'
|
||||
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 { DatabaseError } from '../../errors'
|
||||
|
||||
export class ContributionLinkTransactionSyncRole extends TransactionsSyncRole {
|
||||
export class ContributionLinkTransactionSyncRole extends CreationsSyncRole {
|
||||
constructor(readonly context: Context) {
|
||||
super(context)
|
||||
}
|
||||
@ -12,24 +16,42 @@ export class ContributionLinkTransactionSyncRole extends TransactionsSyncRole {
|
||||
return 'contributionLinkTransaction'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<TransactionDb[]> {
|
||||
const transactionUsers = await loadContributionLinkTransactions(this.context.db, offset, count)
|
||||
return transactionUsers.map((transactionUser) => {
|
||||
let linkedUser: CreatedUserDb | null | undefined = null
|
||||
linkedUser = contributionLinkModerators.get(transactionUser.contributionLinkId)
|
||||
if (linkedUser?.gradidoId === transactionUser.user.gradidoId) {
|
||||
for (const adminUser of adminUsers.values()) {
|
||||
if (adminUser.gradidoId !== transactionUser.user.gradidoId) {
|
||||
linkedUser = adminUser
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return v.parse(transactionDbSchema, {
|
||||
...transactionUser.transaction,
|
||||
user: transactionUser.user,
|
||||
linkedUser,
|
||||
async loadFromDb(offset: number, count: number): Promise<CreationTransactionDb[]> {
|
||||
const result = await this.context.db
|
||||
.select({
|
||||
contribution: contributionsTable,
|
||||
user: usersTable,
|
||||
})
|
||||
})
|
||||
.from(contributionsTable)
|
||||
.where(and(
|
||||
isNotNull(contributionsTable.contributionLinkId),
|
||||
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
|
||||
))
|
||||
.innerJoin(usersTable, eq(contributionsTable.userId, usersTable.id))
|
||||
.orderBy(asc(contributionsTable.confirmedAt), asc(contributionsTable.transactionId))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
const verifiedCreationTransactions: CreationTransactionDb[] = []
|
||||
for(const row of result) {
|
||||
if (!row.contribution.contributionLinkId) {
|
||||
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)
|
||||
}
|
||||
if (!item.confirmedByUser || item.userId === item.confirmedByUser.id) {
|
||||
this.context.logger.warn(`skipped Contribution Link Transaction ${row.contribution.id}`)
|
||||
continue
|
||||
}
|
||||
try {
|
||||
verifiedCreationTransactions.push(v.parse(creationTransactionDbSchema, item))
|
||||
} catch (e) {
|
||||
throw new DatabaseError('load contributions with contribution link id', item, e as Error)
|
||||
}
|
||||
}
|
||||
return verifiedCreationTransactions
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,132 @@
|
||||
import { and, asc, eq, isNull } from 'drizzle-orm'
|
||||
import { alias } from 'drizzle-orm/mysql-core'
|
||||
import {
|
||||
AccountBalances,
|
||||
AuthenticatedEncryption,
|
||||
EncryptedMemo,
|
||||
GradidoTransactionBuilder,
|
||||
KeyPairEd25519,
|
||||
MemoryBlockPtr,
|
||||
TransferAmount
|
||||
} from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { addToBlockchain } from '../../blockchain'
|
||||
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 } from './AbstractSync.role'
|
||||
|
||||
export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
|
||||
|
||||
getDate(): Date {
|
||||
return this.peek().confirmedAt
|
||||
}
|
||||
|
||||
itemTypeName(): string {
|
||||
return 'creationTransactions'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<CreationTransactionDb[]> {
|
||||
const confirmedByUsers = alias(usersTable, 'confirmedByUser')
|
||||
const result = await this.context.db
|
||||
.select({
|
||||
contribution: contributionsTable,
|
||||
user: usersTable,
|
||||
confirmedByUser: confirmedByUsers,
|
||||
})
|
||||
.from(contributionsTable)
|
||||
.where(and(
|
||||
isNull(contributionsTable.contributionLinkId),
|
||||
eq(contributionsTable.contributionStatus, ContributionStatus.CONFIRMED),
|
||||
))
|
||||
.innerJoin(usersTable, eq(contributionsTable.userId, usersTable.id))
|
||||
.innerJoin(confirmedByUsers, eq(contributionsTable.confirmedBy, confirmedByUsers.id))
|
||||
.orderBy(asc(contributionsTable.confirmedAt), asc(contributionsTable.transactionId))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
const item = {
|
||||
...row.contribution,
|
||||
user: row.user,
|
||||
confirmedByUser: row.confirmedByUser,
|
||||
}
|
||||
try {
|
||||
return v.parse(creationTransactionDbSchema, item)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadCreations', item, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
buildTransaction(
|
||||
item: CreationTransactionDb,
|
||||
communityContext: CommunityContext,
|
||||
recipientKeyPair: KeyPairEd25519,
|
||||
signerKeyPair: KeyPairEd25519
|
||||
): GradidoTransactionBuilder {
|
||||
return new GradidoTransactionBuilder()
|
||||
.setCreatedAt(item.confirmedAt)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
item.memo,
|
||||
new AuthenticatedEncryption(communityContext.keyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
)
|
||||
.setTransactionCreation(
|
||||
new TransferAmount(recipientKeyPair.getPublicKey(), item.amount),
|
||||
item.contributionDate,
|
||||
)
|
||||
.sign(signerKeyPair)
|
||||
}
|
||||
|
||||
calculateAccountBalances(
|
||||
item: CreationTransactionDb,
|
||||
communityContext: CommunityContext,
|
||||
recipientPublicKey: MemoryBlockPtr
|
||||
): AccountBalances {
|
||||
const accountBalances = new AccountBalances()
|
||||
const balance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain)
|
||||
|
||||
// calculate decay since last balance with legacy calculation method
|
||||
balance.updateLegacyDecay(item.amount, item.confirmedAt)
|
||||
communityContext.aufBalance.updateLegacyDecay(item.amount, item.confirmedAt)
|
||||
communityContext.gmwBalance.updateLegacyDecay(item.amount, item.confirmedAt)
|
||||
|
||||
accountBalances.add(balance.getAccountBalance())
|
||||
accountBalances.add(communityContext.aufBalance.getAccountBalance())
|
||||
accountBalances.add(communityContext.gmwBalance.getAccountBalance())
|
||||
return accountBalances
|
||||
}
|
||||
|
||||
pushToBlockchain(item: CreationTransactionDb): void {
|
||||
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)}`)
|
||||
}
|
||||
|
||||
const recipientKeyPair = this.getAccountKeyPair(communityContext, item.user.gradidoId)
|
||||
const recipientPublicKey = recipientKeyPair.getPublicKey()
|
||||
const signerKeyPair = this.getAccountKeyPair(communityContext, item.confirmedByUser.gradidoId)
|
||||
if (!recipientKeyPair || !signerKeyPair || !recipientPublicKey) {
|
||||
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
|
||||
}
|
||||
|
||||
try {
|
||||
addToBlockchain(
|
||||
this.buildTransaction(item, communityContext, recipientKeyPair, signerKeyPair),
|
||||
blockchain,
|
||||
item.id,
|
||||
this.calculateAccountBalances(item, communityContext, recipientPublicKey),
|
||||
)
|
||||
} catch(e) {
|
||||
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,148 @@
|
||||
import { loadDeletedTransactionLinks } from '../../database'
|
||||
import { TransactionDb } from '../../valibot.schema'
|
||||
import { TransactionsSyncRole } from './TransactionsSync.role'
|
||||
import { CommunityContext, DeletedTransactionLinkDb, deletedTransactionLinKDbSchema } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
|
||||
import { and, lt, asc, isNotNull, eq } from 'drizzle-orm'
|
||||
import * as v from 'valibot'
|
||||
import {
|
||||
AccountBalance,
|
||||
AccountBalances,
|
||||
Filter,
|
||||
GradidoDeferredTransfer,
|
||||
GradidoTransactionBuilder,
|
||||
GradidoTransfer,
|
||||
GradidoUnit,
|
||||
KeyPairEd25519,
|
||||
MemoryBlockPtr,
|
||||
TransferAmount
|
||||
} from 'gradido-blockchain-js'
|
||||
import { deriveFromCode } from '../../../../data/deriveKeyPair'
|
||||
import { addToBlockchain } from '../../blockchain'
|
||||
import { BlockchainError, DatabaseError } from '../../errors'
|
||||
import { Balance } from '../../data/Balance'
|
||||
|
||||
export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTransactionLinkDb> {
|
||||
getDate(): Date {
|
||||
return this.peek().deletedAt
|
||||
}
|
||||
|
||||
export class DeletedTransactionLinksSyncRole extends TransactionsSyncRole {
|
||||
itemTypeName(): string {
|
||||
return 'deletedTransactionLinks'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<TransactionDb[]> {
|
||||
return await loadDeletedTransactionLinks(this.context.db, offset, count)
|
||||
async loadFromDb(offset: number, count: number): Promise<DeletedTransactionLinkDb[]> {
|
||||
const result = await this.context.db
|
||||
.select({
|
||||
transactionLink: transactionLinksTable,
|
||||
user: usersTable,
|
||||
})
|
||||
.from(transactionLinksTable)
|
||||
.where(
|
||||
and(
|
||||
isNotNull(transactionLinksTable.deletedAt),
|
||||
lt(transactionLinksTable.deletedAt, transactionLinksTable.validUntil)
|
||||
)
|
||||
)
|
||||
.innerJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
|
||||
.orderBy(asc(transactionLinksTable.deletedAt), asc(transactionLinksTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
const item = {
|
||||
...row.transactionLink,
|
||||
user: row.user,
|
||||
}
|
||||
try {
|
||||
return v.parse(deletedTransactionLinKDbSchema, item)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadDeletedTransactionLinks', item, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
buildTransaction(
|
||||
item: DeletedTransactionLinkDb,
|
||||
linkFundingTransactionNr: number,
|
||||
restAmount: GradidoUnit,
|
||||
senderKeyPair: KeyPairEd25519,
|
||||
linkFundingPublicKey: MemoryBlockPtr,
|
||||
): GradidoTransactionBuilder {
|
||||
return new GradidoTransactionBuilder()
|
||||
.setCreatedAt(item.deletedAt)
|
||||
.setRedeemDeferredTransfer(
|
||||
linkFundingTransactionNr,
|
||||
new GradidoTransfer(
|
||||
new TransferAmount(senderKeyPair.getPublicKey(), restAmount),
|
||||
linkFundingPublicKey,
|
||||
),
|
||||
)
|
||||
.sign(senderKeyPair)
|
||||
}
|
||||
|
||||
calculateBalances(
|
||||
item: DeletedTransactionLinkDb,
|
||||
fundingTransaction: GradidoDeferredTransfer,
|
||||
senderLastBalance: Balance,
|
||||
communityContext: CommunityContext,
|
||||
senderPublicKey: MemoryBlockPtr,
|
||||
): AccountBalances {
|
||||
const accountBalances = new AccountBalances()
|
||||
|
||||
const fundingUserLastBalance = this.getLastBalanceForUser(fundingTransaction.getSenderPublicKey()!, communityContext.blockchain)
|
||||
fundingUserLastBalance.updateLegacyDecay(senderLastBalance.getBalance(), item.deletedAt)
|
||||
|
||||
// account of link is set to zero, gdd will be send back to initiator
|
||||
accountBalances.add(new AccountBalance(senderPublicKey, GradidoUnit.zero(), ''))
|
||||
accountBalances.add(fundingUserLastBalance.getAccountBalance())
|
||||
return 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)
|
||||
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.deletedAt)
|
||||
|
||||
try {
|
||||
addToBlockchain(
|
||||
this.buildTransaction(
|
||||
item, transaction.getTransactionNr(),
|
||||
senderLastBalance.getBalance(),
|
||||
senderKeyPair,
|
||||
linkFundingPublicKey,
|
||||
),
|
||||
blockchain,
|
||||
item.id,
|
||||
this.calculateBalances(item, deferredTransfer, senderLastBalance, communityContext, senderPublicKey),
|
||||
)
|
||||
} catch(e) {
|
||||
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,151 @@
|
||||
import { and, asc, eq, isNotNull, isNull } from 'drizzle-orm'
|
||||
import { alias } from 'drizzle-orm/mysql-core'
|
||||
import {
|
||||
AccountBalances,
|
||||
AuthenticatedEncryption,
|
||||
EncryptedMemo,
|
||||
Filter,
|
||||
GradidoTransactionBuilder,
|
||||
KeyPairEd25519,
|
||||
MemoryBlockPtr,
|
||||
SearchDirection_DESC,
|
||||
TransferAmount
|
||||
} from 'gradido-blockchain-js'
|
||||
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 { CommunityContext, TransactionDb, transactionDbSchema } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
|
||||
export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
|
||||
|
||||
getDate(): Date {
|
||||
return this.peek().balanceDate
|
||||
}
|
||||
|
||||
itemTypeName(): string {
|
||||
return 'localTransactions'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<TransactionDb[]> {
|
||||
const linkedUsers = alias(usersTable, 'linkedUser')
|
||||
const result = await this.context.db
|
||||
.select({
|
||||
transaction: transactionsTable,
|
||||
user: usersTable,
|
||||
linkedUser: linkedUsers,
|
||||
})
|
||||
.from(transactionsTable)
|
||||
.where(
|
||||
and(
|
||||
eq(transactionsTable.typeId, TransactionTypeId.RECEIVE),
|
||||
isNull(transactionsTable.transactionLinkId),
|
||||
isNotNull(transactionsTable.linkedUserId),
|
||||
eq(usersTable.foreign, 0),
|
||||
eq(linkedUsers.foreign, 0),
|
||||
)
|
||||
)
|
||||
.innerJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
|
||||
.innerJoin(linkedUsers, eq(transactionsTable.linkedUserId, linkedUsers.id))
|
||||
.orderBy(asc(transactionsTable.balanceDate), asc(transactionsTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
const item = {
|
||||
...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(
|
||||
item: TransactionDb,
|
||||
senderKeyPair: KeyPairEd25519,
|
||||
recipientKeyPair: KeyPairEd25519,
|
||||
): GradidoTransactionBuilder {
|
||||
return new GradidoTransactionBuilder()
|
||||
.setCreatedAt(item.balanceDate)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
item.memo,
|
||||
new AuthenticatedEncryption(senderKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
)
|
||||
.setTransactionTransfer(
|
||||
new TransferAmount(senderKeyPair.getPublicKey(), item.amount),
|
||||
recipientKeyPair.getPublicKey(),
|
||||
)
|
||||
.sign(senderKeyPair)
|
||||
}
|
||||
|
||||
calculateBalances(
|
||||
item: TransactionDb,
|
||||
communityContext: CommunityContext,
|
||||
senderPublicKey: MemoryBlockPtr,
|
||||
recipientPublicKey: MemoryBlockPtr,
|
||||
): AccountBalances {
|
||||
const accountBalances = new AccountBalances()
|
||||
|
||||
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)}`)
|
||||
}
|
||||
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())
|
||||
accountBalances.add(recipientLastBalance.getAccountBalance())
|
||||
return accountBalances
|
||||
}
|
||||
|
||||
pushToBlockchain(item: TransactionDb): void {
|
||||
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)}`)
|
||||
}
|
||||
|
||||
// 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)}`)
|
||||
}
|
||||
|
||||
try {
|
||||
addToBlockchain(
|
||||
this.buildTransaction(item, senderKeyPair, recipientKeyPair),
|
||||
blockchain,
|
||||
item.id,
|
||||
this.calculateBalances(item, communityContext, senderPublicKey, recipientPublicKey),
|
||||
)
|
||||
} catch(e) {
|
||||
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
import { and, asc, eq, isNotNull, isNull } from 'drizzle-orm'
|
||||
import {
|
||||
AccountBalance,
|
||||
AccountBalances,
|
||||
AuthenticatedEncryption,
|
||||
EncryptedMemo,
|
||||
Filter,
|
||||
GradidoDeferredTransfer,
|
||||
GradidoTransactionBuilder,
|
||||
GradidoTransfer,
|
||||
GradidoUnit,
|
||||
KeyPairEd25519,
|
||||
MemoryBlockPtr,
|
||||
TransferAmount
|
||||
} from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { addToBlockchain } from '../../blockchain'
|
||||
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
|
||||
import { BlockchainError, DatabaseError } from '../../errors'
|
||||
import { CommunityContext, RedeemedTransactionLinkDb, redeemedTransactionLinkDbSchema } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
import { deriveFromCode } from '../../../../data/deriveKeyPair'
|
||||
import { alias } from 'drizzle-orm/mysql-core'
|
||||
|
||||
export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTransactionLinkDb> {
|
||||
getDate(): Date {
|
||||
return this.peek().redeemedAt
|
||||
}
|
||||
|
||||
itemTypeName(): string {
|
||||
return 'redeemTransactionLinks'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<RedeemedTransactionLinkDb[]> {
|
||||
const redeemedByUser = alias(usersTable, 'redeemedByUser')
|
||||
const result = await this.context.db
|
||||
.select({
|
||||
transactionLink: transactionLinksTable,
|
||||
user: usersTable,
|
||||
redeemedBy: redeemedByUser,
|
||||
})
|
||||
.from(transactionLinksTable)
|
||||
.where(
|
||||
and(
|
||||
isNull(transactionLinksTable.deletedAt),
|
||||
isNotNull(transactionLinksTable.redeemedAt),
|
||||
eq(usersTable.foreign, 0),
|
||||
eq(redeemedByUser.foreign, 0)
|
||||
)
|
||||
)
|
||||
.innerJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
|
||||
.innerJoin(redeemedByUser, eq(transactionLinksTable.redeemedBy, redeemedByUser.id))
|
||||
.orderBy(asc(transactionLinksTable.redeemedAt), asc(transactionLinksTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
const item = {
|
||||
...row.transactionLink,
|
||||
redeemedBy: row.redeemedBy,
|
||||
user: row.user,
|
||||
}
|
||||
try {
|
||||
return v.parse(redeemedTransactionLinkDbSchema, item)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadRedeemTransactionLinks', item, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
buildTransaction(
|
||||
item: RedeemedTransactionLinkDb,
|
||||
linkFundingTransactionNr: number,
|
||||
senderKeyPair: KeyPairEd25519,
|
||||
recipientKeyPair: KeyPairEd25519,
|
||||
): GradidoTransactionBuilder {
|
||||
return new GradidoTransactionBuilder()
|
||||
.setCreatedAt(item.redeemedAt)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
item.memo,
|
||||
new AuthenticatedEncryption(senderKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
)
|
||||
.setRedeemDeferredTransfer(
|
||||
linkFundingTransactionNr,
|
||||
new GradidoTransfer(
|
||||
new TransferAmount(senderKeyPair.getPublicKey(), item.amount),
|
||||
recipientKeyPair.getPublicKey(),
|
||||
),
|
||||
)
|
||||
.sign(senderKeyPair)
|
||||
}
|
||||
|
||||
calculateBalances(
|
||||
item: RedeemedTransactionLinkDb,
|
||||
fundingTransaction: GradidoDeferredTransfer,
|
||||
communityContext: CommunityContext,
|
||||
senderPublicKey: MemoryBlockPtr,
|
||||
recipientPublicKey: MemoryBlockPtr,
|
||||
): AccountBalances {
|
||||
const accountBalances = new AccountBalances()
|
||||
|
||||
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
|
||||
const fundingUserLastBalance = this.getLastBalanceForUser(fundingTransaction.getSenderPublicKey()!, communityContext.blockchain)
|
||||
const recipientLastBalance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain)
|
||||
|
||||
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()}`)
|
||||
}
|
||||
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
|
||||
accountBalances.add(new AccountBalance(senderPublicKey, GradidoUnit.zero(), ''))
|
||||
accountBalances.add(recipientLastBalance.getAccountBalance())
|
||||
accountBalances.add(fundingUserLastBalance.getAccountBalance())
|
||||
return accountBalances
|
||||
}
|
||||
|
||||
pushToBlockchain(item: RedeemedTransactionLinkDb): void {
|
||||
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
|
||||
const blockchain = communityContext.blockchain
|
||||
|
||||
const senderKeyPair = deriveFromCode(item.code)
|
||||
const senderPublicKey = senderKeyPair.getPublicKey()
|
||||
const recipientKeyPair = this.getAccountKeyPair(communityContext, item.redeemedBy.gradidoId)
|
||||
const recipientPublicKey = recipientKeyPair.getPublicKey()
|
||||
|
||||
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)}`)
|
||||
}
|
||||
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)}`)
|
||||
}
|
||||
|
||||
try {
|
||||
addToBlockchain(
|
||||
this.buildTransaction(item, transaction.getTransactionNr(), senderKeyPair, recipientKeyPair),
|
||||
blockchain,
|
||||
item.id,
|
||||
this.calculateBalances(item, deferredTransfer, communityContext, senderPublicKey, recipientPublicKey),
|
||||
)
|
||||
} catch(e) {
|
||||
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
import { asc, eq } from 'drizzle-orm'
|
||||
import {
|
||||
AccountBalance,
|
||||
AccountBalances,
|
||||
AuthenticatedEncryption,
|
||||
DurationSeconds,
|
||||
EncryptedMemo,
|
||||
Filter,
|
||||
GradidoTransactionBuilder,
|
||||
GradidoTransfer,
|
||||
GradidoUnit,
|
||||
KeyPairEd25519,
|
||||
MemoryBlockPtr,
|
||||
SearchDirection_DESC,
|
||||
TransferAmount
|
||||
} from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { addToBlockchain } from '../../blockchain'
|
||||
import { transactionLinksTable, usersTable } from '../../drizzle.schema'
|
||||
import { BlockchainError, DatabaseError } from '../../errors'
|
||||
import { CommunityContext, TransactionLinkDb, transactionLinkDbSchema } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
import { deriveFromCode } from '../../../../data/deriveKeyPair'
|
||||
|
||||
export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<TransactionLinkDb> {
|
||||
getDate(): Date {
|
||||
return this.peek().createdAt
|
||||
}
|
||||
|
||||
itemTypeName(): string {
|
||||
return 'transactionLinkFundings'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<TransactionLinkDb[]> {
|
||||
const result = await this.context.db
|
||||
.select()
|
||||
.from(transactionLinksTable)
|
||||
.innerJoin(usersTable, eq(transactionLinksTable.userId, usersTable.id))
|
||||
.orderBy(asc(transactionLinksTable.createdAt), asc(transactionLinksTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
const item = {
|
||||
...row.transaction_links,
|
||||
user: row.users,
|
||||
}
|
||||
try {
|
||||
return v.parse(transactionLinkDbSchema, item)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadTransactionLinkFundings', item, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
buildTransaction(
|
||||
item: TransactionLinkDb,
|
||||
blockedAmount: GradidoUnit,
|
||||
duration: DurationSeconds,
|
||||
senderKeyPair: KeyPairEd25519,
|
||||
recipientKeyPair: KeyPairEd25519,
|
||||
): GradidoTransactionBuilder {
|
||||
return new GradidoTransactionBuilder()
|
||||
.setCreatedAt(item.createdAt)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
item.memo,
|
||||
new AuthenticatedEncryption(senderKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
)
|
||||
.setDeferredTransfer(
|
||||
new GradidoTransfer(
|
||||
new TransferAmount(senderKeyPair.getPublicKey(), blockedAmount),
|
||||
recipientKeyPair.getPublicKey(),
|
||||
),
|
||||
duration,
|
||||
)
|
||||
.sign(senderKeyPair)
|
||||
}
|
||||
|
||||
calculateBalances(
|
||||
item: TransactionLinkDb,
|
||||
blockedAmount: GradidoUnit,
|
||||
communityContext: CommunityContext,
|
||||
senderPublicKey: MemoryBlockPtr,
|
||||
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)}`)
|
||||
}
|
||||
}
|
||||
senderLastBalance.updateLegacyDecay(blockedAmount.negated(), item.createdAt)
|
||||
|
||||
accountBalances.add(senderLastBalance.getAccountBalance())
|
||||
accountBalances.add(new AccountBalance(recipientPublicKey, blockedAmount, ''))
|
||||
return 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)}`)
|
||||
}
|
||||
|
||||
const duration = new DurationSeconds((item.validUntil.getTime() - item.createdAt.getTime()) / 1000)
|
||||
const blockedAmount = item.amount.calculateCompoundInterest(duration.getSeconds())
|
||||
|
||||
try {
|
||||
addToBlockchain(
|
||||
this.buildTransaction(item, blockedAmount, duration, senderKeyPair, recipientKeyPair),
|
||||
blockchain,
|
||||
item.id,
|
||||
this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey),
|
||||
)
|
||||
} catch(e) {
|
||||
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import { AccountBalance, AccountBalances, Filter, MemoryBlockPtr, SearchDirection_DESC } from 'gradido-blockchain-js'
|
||||
import { KeyPairIdentifierLogic } from '../../../../data/KeyPairIdentifier.logic'
|
||||
import { ResolveKeyPair } from '../../../../interactions/resolveKeyPair/ResolveKeyPair.context'
|
||||
import { addTransaction } from '../../blockchain'
|
||||
import { transactionLinkDbToTransaction } from '../../convert'
|
||||
import { loadTransactionLinks } from '../../database'
|
||||
import { TransactionLinkDb } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
|
||||
export class TransactionLinksSyncRole extends AbstractSyncRole<TransactionLinkDb> {
|
||||
getDate(): Date {
|
||||
return this.peek().createdAt
|
||||
}
|
||||
|
||||
itemTypeName(): string {
|
||||
return 'transactionLinks'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<TransactionLinkDb[]> {
|
||||
return await loadTransactionLinks(this.context.db, offset, count)
|
||||
}
|
||||
|
||||
async pushToBlockchain(item: TransactionLinkDb): Promise<void> {
|
||||
const communityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
|
||||
const transaction = transactionLinkDbToTransaction(item, communityContext.topicId)
|
||||
// I use the receiving part of transaction pair, so the user is the recipient and the linked user the sender
|
||||
const accountBalances = new AccountBalances()
|
||||
const senderKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.user),
|
||||
)
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.linkedUser!),
|
||||
)
|
||||
const f = new Filter()
|
||||
f.involvedPublicKey = senderKeyPair.getPublicKey()
|
||||
f.pagination.size = 1
|
||||
f.searchDirection = SearchDirection_DESC
|
||||
communityContext.blockchain.findOne(f)
|
||||
accountBalances.add(new AccountBalance(senderKeyPair.getPublicKey(), item.linkedUserBalance, ''))
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), item.amount, ''))
|
||||
|
||||
|
||||
await addTransaction(communityContext.blockchain, communityContext.blockchain, transaction, item.id, accountBalances)
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { AccountBalance, AccountBalances, GradidoUnit } from 'gradido-blockchain-js'
|
||||
import { InputTransactionType } from '../../../../data/InputTransactionType.enum'
|
||||
import { KeyPairIdentifierLogic } from '../../../../data/KeyPairIdentifier.logic'
|
||||
import { GradidoBlockchainCryptoError } from '../../../../errors'
|
||||
import { ResolveKeyPair } from '../../../../interactions/resolveKeyPair/ResolveKeyPair.context'
|
||||
import { AUF_ACCOUNT_DERIVATION_INDEX, GMW_ACCOUNT_DERIVATION_INDEX, hardenDerivationIndex } from '../../../../utils/derivationHelper'
|
||||
import { addTransaction } from '../../blockchain'
|
||||
import { transactionDbToTransaction } from '../../convert'
|
||||
import { loadTransactions } from '../../database'
|
||||
import { legacyCalculateDecay } from '../../utils'
|
||||
import { TransactionDb } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
|
||||
type BalanceDate = {
|
||||
balance: Decimal
|
||||
date: Date
|
||||
}
|
||||
|
||||
export class TransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
|
||||
private static transactionLinkCodes = new Set<string>()
|
||||
static doubleTransactionLinkCodes: string[] = []
|
||||
|
||||
getDate(): Date {
|
||||
return this.peek().balanceDate
|
||||
}
|
||||
|
||||
itemTypeName(): string {
|
||||
return 'transactions'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<TransactionDb[]> {
|
||||
const result = await loadTransactions(this.context.db, offset, count)
|
||||
return result.filter((item) => {
|
||||
if (item.transactionLinkCode) {
|
||||
if (TransactionsSyncRole.transactionLinkCodes.has(item.transactionLinkCode)) {
|
||||
TransactionsSyncRole.doubleTransactionLinkCodes.push(item.transactionLinkCode)
|
||||
return false
|
||||
}
|
||||
TransactionsSyncRole.transactionLinkCodes.add(item.transactionLinkCode)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
async pushToBlockchain(item: TransactionDb): Promise<void> {
|
||||
const senderCommunityContext = this.context.getCommunityContextByUuid(item.user.communityUuid)
|
||||
const recipientCommunityContext = this.context.getCommunityContextByUuid(
|
||||
item.linkedUser.communityUuid,
|
||||
)
|
||||
this.context.cache.setHomeCommunityTopicId(senderCommunityContext.topicId)
|
||||
const transaction = transactionDbToTransaction(
|
||||
item,
|
||||
senderCommunityContext.topicId,
|
||||
recipientCommunityContext.topicId,
|
||||
)
|
||||
const accountBalances = new AccountBalances()
|
||||
if (InputTransactionType.GRADIDO_CREATION === transaction.type) {
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.linkedUser!),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), item.balance, ''))
|
||||
// update gmw and auf
|
||||
this.updateGmwAuf(new Decimal(item.amount.toString(4)), item.balanceDate)
|
||||
const communityKeyPair = await ResolveKeyPair(new KeyPairIdentifierLogic({ communityTopicId: senderCommunityContext.topicId }))
|
||||
const gmwKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
if (!gmwKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${senderCommunityContext.communityId}`,
|
||||
)
|
||||
}
|
||||
const aufKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
if (!aufKeyPair) {
|
||||
throw new GradidoBlockchainCryptoError(
|
||||
`KeyPairEd25519 child derivation failed, has private key: ${communityKeyPair.hasPrivateKey()} for community: ${senderCommunityContext.communityId}`,
|
||||
)
|
||||
}
|
||||
accountBalances.add(new AccountBalance(gmwKeyPair.getPublicKey(), GradidoUnit.fromString(
|
||||
TransactionsSyncRole.gmwBalance!.balance.toString()), ''))
|
||||
accountBalances.add(new AccountBalance(aufKeyPair.getPublicKey(), GradidoUnit.fromString(
|
||||
TransactionsSyncRole.aufBalance!.balance.toString()), ''))
|
||||
} else if (InputTransactionType.REGISTER_ADDRESS === transaction.type) {
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.user),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), GradidoUnit.zero(), ''))
|
||||
} else {
|
||||
// I use the receiving part of transaction pair, so the user is the recipient and the linked user the sender
|
||||
const senderKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.linkedUser!),
|
||||
)
|
||||
const recipientKeyPair = await ResolveKeyPair(
|
||||
new KeyPairIdentifierLogic(transaction.user),
|
||||
)
|
||||
accountBalances.add(new AccountBalance(senderKeyPair.getPublicKey(), item.linkedUserBalance, ''))
|
||||
accountBalances.add(new AccountBalance(recipientKeyPair.getPublicKey(), item.balance, ''))
|
||||
}
|
||||
|
||||
try {
|
||||
await addTransaction(
|
||||
senderCommunityContext.blockchain,
|
||||
recipientCommunityContext.blockchain,
|
||||
transaction,
|
||||
item.id,
|
||||
accountBalances,
|
||||
)
|
||||
} catch(e) {
|
||||
this.context.logger.error(`error adding transaction: ${JSON.stringify(transaction, null, 2)}`)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,24 @@
|
||||
import { addTransaction } from '../../blockchain'
|
||||
import { userDbToTransaction } from '../../convert'
|
||||
import { loadUsers } from '../../database'
|
||||
import { generateKeyPairUserAccount } from '../../data/keyPair'
|
||||
import { CreatedUserDb } from '../../valibot.schema'
|
||||
import { asc } from 'drizzle-orm'
|
||||
import {
|
||||
AccountBalance,
|
||||
AccountBalances,
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
GradidoTransactionBuilder,
|
||||
GradidoUnit,
|
||||
KeyPairEd25519,
|
||||
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 { usersTable } from '../../drizzle.schema'
|
||||
import { BlockchainError, DatabaseError } from '../../errors'
|
||||
import { UserDb, userDbSchema } from '../../valibot.schema'
|
||||
import { AbstractSyncRole } from './AbstractSync.role'
|
||||
|
||||
export class UsersSyncRole extends AbstractSyncRole<CreatedUserDb> {
|
||||
export class UsersSyncRole extends AbstractSyncRole<UserDb> {
|
||||
|
||||
getDate(): Date {
|
||||
return this.peek().createdAt
|
||||
}
|
||||
@ -14,18 +27,66 @@ export class UsersSyncRole extends AbstractSyncRole<CreatedUserDb> {
|
||||
return 'users'
|
||||
}
|
||||
|
||||
async loadFromDb(offset: number, count: number): Promise<CreatedUserDb[]> {
|
||||
const users = await loadUsers(this.context.db, offset, count)
|
||||
for (const user of users) {
|
||||
const communityContext = this.context.getCommunityContextByUuid(user.communityUuid)
|
||||
await generateKeyPairUserAccount(user, this.context.cache, communityContext.topicId)
|
||||
}
|
||||
return users
|
||||
async loadFromDb(offset: number, count: number): Promise<UserDb[]> {
|
||||
const result = await this.context.db
|
||||
.select()
|
||||
.from(usersTable)
|
||||
.orderBy(asc(usersTable.createdAt), asc(usersTable.id))
|
||||
.limit(count)
|
||||
.offset(offset)
|
||||
|
||||
return result.map((row) => {
|
||||
try {
|
||||
return v.parse(userDbSchema, row)
|
||||
} catch (e) {
|
||||
throw new DatabaseError('loadUsers', row, e as Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async pushToBlockchain(item: CreatedUserDb): Promise<void> {
|
||||
buildTransaction(
|
||||
item: UserDb,
|
||||
communityKeyPair: KeyPairEd25519,
|
||||
accountKeyPair: KeyPairEd25519,
|
||||
userKeyPair: KeyPairEd25519
|
||||
): GradidoTransactionBuilder {
|
||||
return new GradidoTransactionBuilder()
|
||||
.setCreatedAt(item.createdAt)
|
||||
.setRegisterAddress(
|
||||
userKeyPair.getPublicKey(),
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
new Uuidv4Hash(item.gradidoId).getAsMemoryBlock(),
|
||||
accountKeyPair.getPublicKey(),
|
||||
)
|
||||
.sign(communityKeyPair)
|
||||
.sign(accountKeyPair)
|
||||
.sign(userKeyPair)
|
||||
}
|
||||
|
||||
calculateAccountBalances(accountPublicKey: MemoryBlockPtr): AccountBalances {
|
||||
const accountBalances = new AccountBalances()
|
||||
accountBalances.add(new AccountBalance(accountPublicKey, GradidoUnit.zero(), ''))
|
||||
return accountBalances
|
||||
}
|
||||
|
||||
pushToBlockchain(item: UserDb): void {
|
||||
const communityContext = this.context.getCommunityContextByUuid(item.communityUuid)
|
||||
const transaction = userDbToTransaction(item, communityContext.topicId)
|
||||
return await addTransaction(communityContext.blockchain, communityContext.blockchain, transaction, item.id)
|
||||
const userKeyPair = deriveFromKeyPairAndUuid(communityContext.keyPair, item.gradidoId)
|
||||
const accountKeyPair = this.getAccountKeyPair(communityContext, item.gradidoId)
|
||||
const accountPublicKey = accountKeyPair.getPublicKey()
|
||||
if (!userKeyPair || !accountKeyPair || !accountPublicKey) {
|
||||
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
|
||||
}
|
||||
|
||||
try {
|
||||
addToBlockchain(
|
||||
this.buildTransaction(item, communityContext.keyPair, accountKeyPair, userKeyPair),
|
||||
communityContext.blockchain,
|
||||
item.id,
|
||||
this.calculateAccountBalances(accountPublicKey),
|
||||
)
|
||||
} catch (e) {
|
||||
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { Profiler } from 'gradido-blockchain-js'
|
||||
import { Context } from '../../Context'
|
||||
import { CreationsSyncRole } from './CreationsSync.role'
|
||||
import { InvalidContributionTransactionSyncRole } from './InvalidContributionTransactionSync.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 { InvalidContributionTransactionSyncRole } from './InvalidContributionTransactionSync.role'
|
||||
import { TransactionLinksSyncRole } from './TransactionLinksSync.role'
|
||||
import { TransactionsSyncRole } from './TransactionsSync.role'
|
||||
import { UsersSyncRole } from './UsersSync.role'
|
||||
|
||||
export async function syncDbWithBlockchainContext(context: Context, batchSize: number) {
|
||||
const timeUsedDB = new Profiler()
|
||||
@ -13,11 +15,12 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
|
||||
const timeUsedAll = new Profiler()
|
||||
const containers = [
|
||||
new UsersSyncRole(context),
|
||||
new TransactionsSyncRole(context),
|
||||
new DeletedTransactionLinksSyncRole(context),
|
||||
new TransactionLinksSyncRole(context),
|
||||
new InvalidContributionTransactionSyncRole(context),
|
||||
new CreationsSyncRole(context),
|
||||
new LocalTransactionsSyncRole(context),
|
||||
new TransactionLinkFundingsSyncRole(context),
|
||||
new RedeemTransactionLinksSyncRole(context),
|
||||
new ContributionLinkTransactionSyncRole(context),
|
||||
new DeletedTransactionLinksSyncRole(context),
|
||||
]
|
||||
let transactionsCount = 0
|
||||
let transactionsCountSinceLastLog = 0
|
||||
@ -38,10 +41,12 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
|
||||
}
|
||||
|
||||
// sort by date, to ensure container on index 0 is the one with the smallest date
|
||||
if (available.length > 0) {
|
||||
if (available.length > 1) {
|
||||
// const sortTime = new Profiler()
|
||||
available.sort((a, b) => a.getDate().getTime() - b.getDate().getTime())
|
||||
// context.logger.debug(`sorted ${available.length} containers in ${sortTime.string()}`)
|
||||
}
|
||||
await available[0].toBlockchain()
|
||||
available[0].toBlockchain()
|
||||
process.stdout.write(`successfully added to blockchain: ${transactionsCount}\r`)
|
||||
transactionsCount++
|
||||
transactionsCountSinceLastLog++
|
||||
@ -57,8 +62,8 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
|
||||
if (context.logger.isDebugEnabled()) {
|
||||
context.logger.debug(InvalidContributionTransactionSyncRole.allTransactionIds.join(', '))
|
||||
}
|
||||
context.logger.info(`Double linked transactions: ${TransactionsSyncRole.doubleTransactionLinkCodes.length}`)
|
||||
/*context.logger.info(`Double linked transactions: ${TransactionsSyncRole.doubleTransactionLinkCodes.length}`)
|
||||
if (context.logger.isDebugEnabled()) {
|
||||
context.logger.debug(TransactionsSyncRole.doubleTransactionLinkCodes.join(', '))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@ -1,40 +1,36 @@
|
||||
import { InMemoryBlockchain } from 'gradido-blockchain-js'
|
||||
import { InMemoryBlockchain, KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { booleanSchema, dateSchema } from '../../schemas/typeConverter.schema'
|
||||
import {
|
||||
gradidoAmountSchema,
|
||||
hieroIdSchema,
|
||||
identifierSeedSchema,
|
||||
memoSchema,
|
||||
uuidv4Schema,
|
||||
} from '../../schemas/typeGuard.schema'
|
||||
import { TransactionTypeId } from './data/TransactionTypeId'
|
||||
import { Balance } from './data/Balance'
|
||||
import { TransactionTypeId } from './data/TransactionTypeId'
|
||||
|
||||
export const createdUserDbSchema = v.object({
|
||||
id: v.pipe(v.number(), v.minValue(1)),
|
||||
const positiveNumberSchema = v.pipe(v.number(), v.minValue(1))
|
||||
|
||||
export const userDbSchema = v.object({
|
||||
id: positiveNumberSchema,
|
||||
gradidoId: uuidv4Schema,
|
||||
communityUuid: uuidv4Schema,
|
||||
createdAt: dateSchema,
|
||||
})
|
||||
|
||||
export const userDbSchema = v.object({
|
||||
gradidoId: uuidv4Schema,
|
||||
communityUuid: uuidv4Schema,
|
||||
export const transactionBaseSchema = v.object({
|
||||
id: positiveNumberSchema,
|
||||
amount: gradidoAmountSchema,
|
||||
memo: memoSchema,
|
||||
user: userDbSchema,
|
||||
})
|
||||
|
||||
export const transactionDbSchema = v.pipe(v.object({
|
||||
id: v.pipe(v.number(), v.minValue(1)),
|
||||
...transactionBaseSchema.entries,
|
||||
typeId: v.enum(TransactionTypeId),
|
||||
amount: gradidoAmountSchema,
|
||||
balanceDate: dateSchema,
|
||||
balance: gradidoAmountSchema,
|
||||
linkedUserBalance: gradidoAmountSchema,
|
||||
memo: memoSchema,
|
||||
creationDate: v.nullish(dateSchema),
|
||||
user: createdUserDbSchema,
|
||||
linkedUser: createdUserDbSchema,
|
||||
transactionLinkCode: v.nullish(identifierSeedSchema),
|
||||
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)}`)
|
||||
@ -53,29 +49,62 @@ export const transactionDbSchema = v.pipe(v.object({
|
||||
return value
|
||||
}))
|
||||
|
||||
export const creationTransactionDbSchema = v.pipe(v.object({
|
||||
...transactionBaseSchema.entries,
|
||||
contributionDate: dateSchema,
|
||||
confirmedAt: dateSchema,
|
||||
confirmedByUser: userDbSchema,
|
||||
}), 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({
|
||||
id: v.pipe(v.number(), v.minValue(1)),
|
||||
user: userDbSchema,
|
||||
...transactionBaseSchema.entries,
|
||||
code: identifierSeedSchema,
|
||||
amount: gradidoAmountSchema,
|
||||
memo: memoSchema,
|
||||
createdAt: dateSchema,
|
||||
validUntil: dateSchema,
|
||||
})
|
||||
|
||||
export const redeemedTransactionLinkDbSchema = v.object({
|
||||
...transactionLinkDbSchema.entries,
|
||||
redeemedAt: dateSchema,
|
||||
redeemedBy: userDbSchema,
|
||||
})
|
||||
|
||||
export const deletedTransactionLinKDbSchema = v.object({
|
||||
id: positiveNumberSchema,
|
||||
user: userDbSchema,
|
||||
code: identifierSeedSchema,
|
||||
deletedAt: dateSchema,
|
||||
})
|
||||
|
||||
export const communityDbSchema = v.object({
|
||||
id: positiveNumberSchema,
|
||||
foreign: booleanSchema,
|
||||
communityUuid: uuidv4Schema,
|
||||
name: v.string(),
|
||||
creationDate: dateSchema,
|
||||
userMinCreatedAt: v.nullish(dateSchema),
|
||||
uniqueAlias: v.string(),
|
||||
})
|
||||
|
||||
export const communityContextSchema = v.object({
|
||||
communityId: v.string(),
|
||||
blockchain: v.instance(InMemoryBlockchain, 'expect InMemoryBlockchain type'),
|
||||
topicId: hieroIdSchema,
|
||||
keyPair: v.instance(KeyPairEd25519),
|
||||
folder: v.pipe(
|
||||
v.string(),
|
||||
v.minLength(1, 'expect string length >= 1'),
|
||||
@ -87,9 +116,10 @@ export const communityContextSchema = v.object({
|
||||
})
|
||||
|
||||
export type TransactionDb = v.InferOutput<typeof transactionDbSchema>
|
||||
export type CreationTransactionDb = v.InferOutput<typeof creationTransactionDbSchema>
|
||||
export type UserDb = v.InferOutput<typeof userDbSchema>
|
||||
export type CreatedUserDb = v.InferOutput<typeof createdUserDbSchema>
|
||||
export type TransactionLinkDb = v.InferOutput<typeof transactionLinkDbSchema>
|
||||
export type RedeemedTransactionLinkDb = v.InferOutput<typeof redeemedTransactionLinkDbSchema>
|
||||
export type DeletedTransactionLinkDb = v.InferOutput<typeof deletedTransactionLinKDbSchema>
|
||||
export type CommunityDb = v.InferOutput<typeof communityDbSchema>
|
||||
export type Balance = v.InferOutput<typeof balanceSchema>
|
||||
export type CommunityContext = v.InferOutput<typeof communityContextSchema>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user