add remote transactions, fix some stuff

This commit is contained in:
einhornimmond 2026-01-23 09:55:52 +01:00
parent 02f64af3d3
commit 473d8139e8
16 changed files with 345 additions and 72 deletions

View File

@ -251,6 +251,18 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
WHERE u.created_at >= t.first_date;
;`)
// linked user also, but we need to use gradido_id as index, because on cross group transactions linked_user_id is empty
await queryFn(`
UPDATE users u
LEFT JOIN (
SELECT linked_user_gradido_id , MIN(balance_date) AS first_date
FROM transactions
GROUP BY linked_user_gradido_id
) t ON t.linked_user_gradido_id = u.gradido_id
SET u.created_at = DATE_SUB(t.first_date, INTERVAL 1 SECOND)
WHERE u.created_at >= t.first_date;
;`)
/**
* Fix 4: Ensure all transaction memos meet the minimum length requirement.
*

View File

@ -36,7 +36,7 @@ export class CreationTransactionRole extends AbstractTransactionRole {
}
getRecipientCommunityTopicId(): HieroId {
throw new Error('creation: cannot be used as cross group transaction')
return this.creationTransaction.user.communityTopicId
}
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {

View File

@ -1,7 +1,7 @@
import {
AccountBalances,
Filter,
GradidoTransactionBuilder,
GradidoTransaction,
HieroAccountId,
InMemoryBlockchain,
LedgerAnchor,
@ -11,12 +11,12 @@ import { NotEnoughGradidoBalanceError } from './errors'
export const defaultHieroAccount = new HieroAccountId(0, 0, 2)
export function addToBlockchain(
builder: GradidoTransactionBuilder,
transaction: GradidoTransaction,
blockchain: InMemoryBlockchain,
ledgerAnchor: LedgerAnchor,
accountBalances: AccountBalances,
): boolean {
const transaction = builder.build()
try {
const result = blockchain.createAndAddConfirmedTransactionExtern(
transaction,
@ -33,7 +33,7 @@ export function addToBlockchain(
throw new NotEnoughGradidoBalanceError(needed, exist)
}
}
const lastTransaction = blockchain.findOne(Filter.LAST_TRANSACTION)
const lastTransaction = blockchain.findOne(Filter.LAST_TRANSACTION)
throw new Error(`Transaction ${transaction.toJson(true)} not added: ${error}, last transaction was: ${lastTransaction?.getConfirmedTransaction()?.toJson(true)}`)
}
}

View File

@ -1,5 +1,5 @@
import { randomBytes } from 'node:crypto'
import { AccountBalances, GradidoTransactionBuilder, InMemoryBlockchainProvider, LedgerAnchor } from 'gradido-blockchain-js'
import { Abstract, AccountBalances, GradidoTransactionBuilder, InMemoryBlockchain, InMemoryBlockchainProvider, LedgerAnchor } from 'gradido-blockchain-js'
import * as v from 'valibot'
import { CONFIG } from '../../config'
import { deriveFromSeed } from '../../data/deriveKeyPair'
@ -27,15 +27,13 @@ async function bootstrapCommunities(context: Context): Promise<Map<string, Commu
const communityNames = new Set<string>()
for (const communityDb of communitiesDb) {
let alias = communityDb.name
if (communityNames.has(communityDb.name)) {
let alias = communityDb.name.toLowerCase()
if (communityNames.has(alias)) {
alias = communityDb.communityUuid
} else {
communityNames.add(communityDb.name)
communityNames.add(alias)
}
const blockchain = InMemoryBlockchainProvider.getInstance().findBlockchain(
alias,
)
const blockchain = InMemoryBlockchainProvider.getInstance().findBlockchain(alias)
if (!blockchain) {
throw new Error(`Couldn't create Blockchain for community ${alias}`)
}
@ -66,22 +64,24 @@ async function bootstrapCommunities(context: Context): Promise<Map<string, Commu
gmwKeyPair.getPublicKey(),
aufKeyPair.getPublicKey(),
)
.setSenderCommunity(alias)
.sign(communityKeyPair)
const communityContext: CommunityContext = {
communityId: alias,
foreign: communityDb.foreign,
blockchain,
keyPair: communityKeyPair,
folder: alias.replace(/[^a-zA-Z0-9]/g, '_'),
gmwBalance: new Balance(gmwKeyPair.getPublicKey()!),
aufBalance: new Balance(aufKeyPair.getPublicKey()!),
folder: alias.replace(/[^a-z0-9]/g, '_'),
gmwBalance: new Balance(gmwKeyPair.getPublicKey()!, alias),
aufBalance: new Balance(aufKeyPair.getPublicKey()!, alias),
}
communities.set(communityDb.communityUuid, communityContext)
const accountBalances = new AccountBalances()
accountBalances.add(communityContext.aufBalance.getAccountBalance())
accountBalances.add(communityContext.gmwBalance.getAccountBalance())
addToBlockchain(
builder,
builder.build(),
blockchain,
new LedgerAnchor(communityDb.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_COMMUNITY_ID),
accountBalances,

View File

@ -7,17 +7,17 @@ export class Balance {
private balance: GradidoUnit
private date: Date
private publicKey: MemoryBlockPtr
private communityId?: string | null
private communityId: string
constructor(publicKey: MemoryBlockPtr, communityId?: string | null) {
constructor(publicKey: MemoryBlockPtr, communityId: string) {
this.balance = new GradidoUnit(0)
this.date = new Date()
this.publicKey = publicKey
this.communityId = communityId
}
static fromAccountBalance(accountBalance: AccountBalance, confirmedAt: Date): Balance {
const balance = new Balance(accountBalance.getPublicKey()!, accountBalance.getCommunityId() || null)
static fromAccountBalance(accountBalance: AccountBalance, confirmedAt: Date, communityId: string): Balance {
const balance = new Balance(accountBalance.getPublicKey()!, communityId)
balance.update(accountBalance.getBalance(), confirmedAt)
return balance
}
@ -39,19 +39,25 @@ export class Balance {
this.balance = amount
this.date = date
} else {
const decayedBalance = legacyCalculateDecay(new Decimal(this.balance.toString()), this.date, date )
const decayedBalance = legacyCalculateDecay(new Decimal(this.balance.toString()), this.date, date ).toDecimalPlaces(4, Decimal.ROUND_CEIL)
const newBalance = decayedBalance.add(new Decimal(amount.toString()))
this.balance = GradidoUnit.fromString(newBalance.toString())
this.date = date
}
if (this.balance.lt(GradidoUnit.zero())) {
const previousDecayedBalance = legacyCalculateDecay(new Decimal(previousBalanceString), previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.updateLegacyDecay`,
previousBalanceString,
amount.toString(),
previousDecayedBalance.toString(),
)
if (this.balance.lt(GradidoUnit.fromGradidoCent(100).negated())) {
const previousDecayedBalance = legacyCalculateDecay(new Decimal(previousBalanceString), previousDate, date)
const rounded = previousDecayedBalance.toDecimalPlaces(4, Decimal.ROUND_CEIL)
console.log(`rounded: ${rounded}}`)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.updateLegacyDecay`,
previousBalanceString,
amount.toString(),
previousDecayedBalance.toString(),
)
} else {
this.balance = GradidoUnit.zero()
}
}
}
@ -69,17 +75,22 @@ export class Balance {
this.date = date
}
if (this.balance.lt(GradidoUnit.zero())) {
const previousDecayedBalance = this.balance.calculateDecay(previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.update`,
previousBalance.toString(),
amount.toString(),
previousDecayedBalance.toString(),
)
// ignore diffs less than a gradido cent
if (this.balance.lt(GradidoUnit.fromGradidoCent(100).negated())) {
const previousDecayedBalance = this.balance.calculateDecay(previousDate, date)
throw new NegativeBalanceError(
`negative Gradido amount detected in Balance.update`,
previousBalance.toString(),
amount.toString(),
previousDecayedBalance.toString(),
)
} else {
this.balance = GradidoUnit.zero()
}
}
}
getAccountBalance(): AccountBalance {
return new AccountBalance(this.publicKey, this.balance, this.communityId || '')
return new AccountBalance(this.publicKey, this.balance, this.communityId)
}
}

View File

@ -84,6 +84,8 @@ export const transactionsTable = mysqlTable(
creationDate: datetime('creation_date', { mode: 'string', fsp: 3 }).default(sql`NULL`),
userId: int('user_id').notNull(),
linkedUserId: int('linked_user_id').default(sql`NULL`),
linkedUserCommunityUuid: char("linked_user_community_uuid", { length: 36 }).default(sql`NULL`),
linkedUserGradidoId: char("linked_user_gradido_id", { length: 36 }).default(sql`NULL`),
linkedTransactionId: int('linked_transaction_id').default(sql`NULL`),
},
(table) => [index('user_id').on(table.userId)],

View File

@ -29,7 +29,8 @@ export abstract class AbstractSyncRole<ItemType> {
})
}
getLastBalanceForUser(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, communityId: string = ''): Balance {
getLastBalanceForUser(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, communityId: string
): Balance {
if (publicKey.isEmpty()) {
throw new Error('publicKey is empty')
}
@ -45,7 +46,7 @@ export abstract class AbstractSyncRole<ItemType> {
if (!senderLastAccountBalance) {
return new Balance(publicKey, communityId)
}
return Balance.fromAccountBalance(senderLastAccountBalance, lastConfirmedTransaction.getConfirmedAt().getDate())
return Balance.fromAccountBalance(senderLastAccountBalance, lastConfirmedTransaction.getConfirmedAt().getDate(), communityId)
}
logLastBalanceChangingTransactions(publicKey: MemoryBlockPtr, blockchain: InMemoryBlockchain, transactionCount: number = 5) {

View File

@ -95,9 +95,10 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
),
)
.setTransactionCreation(
new TransferAmount(recipientKeyPair.getPublicKey(), item.amount),
new TransferAmount(recipientKeyPair.getPublicKey(), item.amount, communityContext.communityId),
item.contributionDate,
)
.setRecipientCommunity(communityContext.communityId)
.sign(signerKeyPair)
}
@ -107,7 +108,7 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
recipientPublicKey: MemoryBlockPtr
): AccountBalances {
const accountBalances = new AccountBalances()
const balance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain)
const balance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain, communityContext.communityId)
// calculate decay since last balance with legacy calculation method
balance.updateLegacyDecay(item.amount, item.confirmedAt)
@ -136,7 +137,7 @@ export class CreationsSyncRole extends AbstractSyncRole<CreationTransactionDb> {
try {
addToBlockchain(
this.buildTransaction(item, communityContext, recipientKeyPair, signerKeyPair),
this.buildTransaction(item, communityContext, recipientKeyPair, signerKeyPair).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_CONTRIBUTION_ID),
this.calculateAccountBalances(item, communityContext, recipientPublicKey),

View File

@ -74,6 +74,7 @@ export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTra
}
buildTransaction(
communityContext: CommunityContext,
item: DeletedTransactionLinkDb,
linkFundingTransactionNr: number,
restAmount: GradidoUnit,
@ -85,10 +86,11 @@ export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTra
.setRedeemDeferredTransfer(
linkFundingTransactionNr,
new GradidoTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), restAmount),
new TransferAmount(senderKeyPair.getPublicKey(), restAmount, communityContext.communityId),
linkFundingPublicKey,
),
)
.setSenderCommunity(communityContext.communityId)
.sign(senderKeyPair)
}
@ -101,11 +103,15 @@ export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTra
): AccountBalances {
const accountBalances = new AccountBalances()
const fundingUserLastBalance = this.getLastBalanceForUser(fundingTransaction.getSenderPublicKey()!, communityContext.blockchain)
const fundingUserLastBalance = this.getLastBalanceForUser(
fundingTransaction.getSenderPublicKey()!,
communityContext.blockchain,
communityContext.communityId
)
fundingUserLastBalance.updateLegacyDecay(senderLastBalance.getBalance(), item.deletedAt)
// account of link is set to zero, gdd will be send back to initiator
accountBalances.add(new AccountBalance(senderPublicKey, GradidoUnit.zero(), ''))
accountBalances.add(new AccountBalance(senderPublicKey, GradidoUnit.zero(), communityContext.communityId))
accountBalances.add(fundingUserLastBalance.getAccountBalance())
return accountBalances
}
@ -138,17 +144,18 @@ export class DeletedTransactionLinksSyncRole extends AbstractSyncRole<DeletedTra
if (!linkFundingPublicKey) {
throw new Error(`missing sender public key of transaction link founder`)
}
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.deletedAt)
try {
addToBlockchain(
this.buildTransaction(
communityContext,
item, transaction.getTransactionNr(),
senderLastBalance.getBalance(),
senderKeyPair,
linkFundingPublicKey,
),
).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
this.calculateBalances(item, deferredTransfer, senderLastBalance, communityContext, senderPublicKey),

View File

@ -4,12 +4,10 @@ import {
AccountBalances,
AuthenticatedEncryption,
EncryptedMemo,
Filter,
GradidoTransactionBuilder,
KeyPairEd25519,
LedgerAnchor,
MemoryBlockPtr,
SearchDirection_DESC,
TransferAmount
} from 'gradido-blockchain-js'
import * as v from 'valibot'
@ -81,6 +79,7 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
}
buildTransaction(
communityContext: CommunityContext,
item: TransactionDb,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
@ -94,9 +93,10 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
),
)
.setTransactionTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount),
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, communityContext.communityId),
recipientKeyPair.getPublicKey(),
)
.setSenderCommunity(communityContext.communityId)
.sign(senderKeyPair)
}
@ -108,8 +108,8 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
): AccountBalances {
const accountBalances = new AccountBalances()
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
const recipientLastBalance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain)
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
const recipientLastBalance = this.getLastBalanceForUser(recipientPublicKey, communityContext.blockchain, communityContext.communityId)
try {
senderLastBalance.updateLegacyDecay(item.amount.negated(), item.balanceDate)
@ -145,7 +145,7 @@ export class LocalTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
try {
addToBlockchain(
this.buildTransaction(item, senderKeyPair, recipientKeyPair),
this.buildTransaction(communityContext, item, senderKeyPair, recipientKeyPair).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_ID),
this.calculateBalances(item, communityContext, senderPublicKey, recipientPublicKey),

View File

@ -82,6 +82,7 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
}
buildTransaction(
communityContext: CommunityContext,
item: RedeemedTransactionLinkDb,
linkFundingTransactionNr: number,
senderKeyPair: KeyPairEd25519,
@ -99,10 +100,11 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
.setRedeemDeferredTransfer(
linkFundingTransactionNr,
new GradidoTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount),
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, communityContext.communityId),
recipientKeyPair.getPublicKey(),
),
)
.setSenderCommunity(communityContext.communityId)
.sign(senderKeyPair)
}
@ -115,9 +117,21 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
): 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)
const senderLastBalance = this.getLastBalanceForUser(
senderPublicKey,
communityContext.blockchain,
communityContext.communityId
)
const fundingUserLastBalance = this.getLastBalanceForUser(
fundingTransaction.getSenderPublicKey()!,
communityContext.blockchain,
communityContext.communityId
)
const recipientLastBalance = this.getLastBalanceForUser(
recipientPublicKey,
communityContext.blockchain,
communityContext.communityId
)
if (senderLastBalance.getAccountBalance().getBalance().lt(item.amount)) {
throw new Error(`sender has not enough balance (${senderLastBalance.getAccountBalance().getBalance().toString()}) to send ${item.amount.toString()} to ${recipientPublicKey.convertToHex()}`)
@ -127,7 +141,7 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
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(new AccountBalance(senderPublicKey, GradidoUnit.zero(), communityContext.communityId))
accountBalances.add(recipientLastBalance.getAccountBalance())
accountBalances.add(fundingUserLastBalance.getAccountBalance())
return accountBalances
@ -162,7 +176,7 @@ export class RedeemTransactionLinksSyncRole extends AbstractSyncRole<RedeemedTra
try {
addToBlockchain(
this.buildTransaction(item, transaction.getTransactionNr(), senderKeyPair, recipientKeyPair),
this.buildTransaction(communityContext, item, transaction.getTransactionNr(), senderKeyPair, recipientKeyPair).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
this.calculateBalances(item, deferredTransfer, communityContext, senderPublicKey, recipientPublicKey),

View File

@ -0,0 +1,193 @@
import { alias } from 'drizzle-orm/mysql-core'
import { transactionsTable, usersTable } from '../../drizzle.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { CommunityContext, TransactionDb, transactionDbSchema, UserDb } from '../../valibot.schema'
import * as v from 'valibot'
import { toMysqlDateTime } from '../../utils'
import { TransactionTypeId } from '../../data/TransactionTypeId'
import { DatabaseError, NegativeBalanceError } from '../../errors'
import { asc, and, eq, gt, ne, or, inArray, isNull } from 'drizzle-orm'
import { NotEnoughGradidoBalanceError } from '../../errors'
import { BlockchainError } from '../../errors'
import { addToBlockchain } from '../../blockchain'
import { AccountBalance, AccountBalances, AuthenticatedEncryption, EncryptedMemo, GradidoTransactionBuilder, GradidoUnit, KeyPairEd25519, LedgerAnchor, MemoryBlockPtr, TransferAmount } from 'gradido-blockchain-js'
import { Decimal } from 'decimal.js'
export class RemoteTransactionsSyncRole extends AbstractSyncRole<TransactionDb> {
getDate(): Date {
return this.peek().balanceDate
}
getLastIndex(): IndexType {
const lastItem = this.peekLast()
return { date: lastItem.balanceDate, id: lastItem.id }
}
itemTypeName(): string {
return 'remoteTransactions'
}
async loadFromDb(lastIndex: IndexType, 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(
inArray(transactionsTable.typeId, [TransactionTypeId.RECEIVE, TransactionTypeId.SEND]),
isNull(transactionsTable.transactionLinkId),
ne(usersTable.communityUuid, linkedUsers.communityUuid),
or(
gt(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
and(
eq(transactionsTable.balanceDate, toMysqlDateTime(lastIndex.date)),
gt(transactionsTable.id, lastIndex.id)
)
)
)
)
.innerJoin(usersTable, eq(transactionsTable.userId, usersTable.id))
.innerJoin(linkedUsers, eq(transactionsTable.linkedUserGradidoId, linkedUsers.gradidoId))
.orderBy(asc(transactionsTable.balanceDate), asc(transactionsTable.id))
.limit(count)
return result.map((row) => {
const item = {
...row.transaction,
user: row.user,
linkedUser: row.linkedUser,
}
if (item.typeId === TransactionTypeId.SEND && item.amount) {
item.amount = new Decimal(item.amount).neg().toString()
}
try {
return v.parse(transactionDbSchema, item)
} catch (e) {
throw new DatabaseError('loadRemoteTransferTransactions', item, e as Error)
}
})
}
buildTransaction(
item: TransactionDb,
senderKeyPair: KeyPairEd25519,
recipientKeyPair: KeyPairEd25519,
senderCommunityId: string,
recipientCommunityId: string,
): GradidoTransactionBuilder {
const builder = new GradidoTransactionBuilder()
.setCreatedAt(item.balanceDate)
.addMemo(new EncryptedMemo(
item.memo,
new AuthenticatedEncryption(senderKeyPair),
new AuthenticatedEncryption(recipientKeyPair),
),
)
.setTransactionTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), item.amount, senderCommunityId),
recipientKeyPair.getPublicKey(),
)
.setSenderCommunity(senderCommunityId)
.setRecipientCommunity(recipientCommunityId)
.sign(senderKeyPair)
return builder
}
calculateBalances(
item: TransactionDb,
communityContext: CommunityContext,
amount: GradidoUnit,
publicKey: MemoryBlockPtr,
): AccountBalances {
const accountBalances = new AccountBalances()
if (communityContext.foreign) {
accountBalances.add(new AccountBalance(publicKey, GradidoUnit.zero(), communityContext.communityId))
return accountBalances
} else {
const lastBalance = this.getLastBalanceForUser(publicKey, communityContext.blockchain, communityContext.communityId)
try {
lastBalance.updateLegacyDecay(amount, item.balanceDate)
} catch(e) {
if (e instanceof NegativeBalanceError) {
this.logLastBalanceChangingTransactions(publicKey, communityContext.blockchain, 10)
throw e
}
}
accountBalances.add(lastBalance.getAccountBalance())
return accountBalances
}
}
getUser(item: TransactionDb): { senderUser: UserDb, recipientUser: UserDb } {
return (
item.typeId === TransactionTypeId.RECEIVE
? { senderUser: item.linkedUser, recipientUser: item.user }
: { senderUser: item.user, recipientUser: item.linkedUser }
)
}
pushToBlockchain(item: TransactionDb): void {
const { senderUser, recipientUser } = this.getUser(item)
const ledgerAnchor = new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_ID)
if (senderUser.communityUuid === recipientUser.communityUuid) {
throw new Error(`transfer between user from same community: ${JSON.stringify(item, null, 2)}, check db query`)
}
const senderCommunityContext = this.context.getCommunityContextByUuid(senderUser.communityUuid)
const recipientCommunityContext = this.context.getCommunityContextByUuid(recipientUser.communityUuid)
const senderBlockchain = senderCommunityContext.blockchain
const recipientBlockchain = recipientCommunityContext.blockchain
// I use the received transaction so user and linked user are swapped and user is recipient and linkedUser ist sender
const senderKeyPair = this.getAccountKeyPair(senderCommunityContext, senderUser.gradidoId)
const senderPublicKey = senderKeyPair.getPublicKey()
const recipientKeyPair = this.getAccountKeyPair(recipientCommunityContext, recipientUser.gradidoId)
const recipientPublicKey = recipientKeyPair.getPublicKey()
if (!senderKeyPair || !senderPublicKey || !recipientKeyPair || !recipientPublicKey) {
throw new Error(`missing key for ${this.itemTypeName()}: ${JSON.stringify(item, null, 2)}`)
}
const transactionBuilder = this.buildTransaction(
item,
senderKeyPair,
recipientKeyPair,
senderCommunityContext.communityId,
recipientCommunityContext.communityId
)
const outboundTransaction = transactionBuilder.buildOutbound()
try {
addToBlockchain(
outboundTransaction,
senderBlockchain,
ledgerAnchor,
this.calculateBalances(item, senderCommunityContext, item.amount.negated(), senderPublicKey),
)
} catch(e) {
if (e instanceof NotEnoughGradidoBalanceError) {
this.logLastBalanceChangingTransactions(senderPublicKey, senderBlockchain)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
transactionBuilder.setParentLedgerAnchor(ledgerAnchor)
const inboundTransaction = transactionBuilder.buildInbound()
try {
addToBlockchain(
inboundTransaction,
recipientBlockchain,
ledgerAnchor,
this.calculateBalances(item, recipientCommunityContext, item.amount, recipientPublicKey),
)
} catch(e) {
if (e instanceof NotEnoughGradidoBalanceError) {
this.logLastBalanceChangingTransactions(recipientPublicKey, recipientBlockchain)
}
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)
}
}
}

View File

@ -1,4 +1,4 @@
import { asc, eq, or, gt, and, isNull } from 'drizzle-orm'
import { asc, eq, or, gt, and } from 'drizzle-orm'
import {
AccountBalance,
AccountBalances,
@ -20,7 +20,7 @@ import { BlockchainError, DatabaseError, NegativeBalanceError } from '../../erro
import { CommunityContext, TransactionLinkDb, transactionLinkDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { deriveFromCode } from '../../../../data/deriveKeyPair'
import { calculateEffectiveSeconds, reverseLegacyDecay, toMysqlDateTime } from '../../utils'
import { reverseLegacyDecay, toMysqlDateTime } from '../../utils'
import Decimal from 'decimal.js-light'
export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<TransactionLinkDb> {
@ -66,6 +66,7 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
}
buildTransaction(
communityContext: CommunityContext,
item: TransactionLinkDb,
blockedAmount: GradidoUnit,
duration: DurationSeconds,
@ -83,11 +84,12 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
)
.setDeferredTransfer(
new GradidoTransfer(
new TransferAmount(senderKeyPair.getPublicKey(), blockedAmount),
new TransferAmount(senderKeyPair.getPublicKey(), blockedAmount, communityContext.communityId),
recipientKeyPair.getPublicKey(),
),
duration,
)
.setSenderCommunity(communityContext.communityId)
.sign(senderKeyPair)
}
@ -99,7 +101,7 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
recipientPublicKey: MemoryBlockPtr,
): AccountBalances {
const accountBalances = new AccountBalances()
let senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
let senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
try {
senderLastBalance.updateLegacyDecay(blockedAmount.negated(), item.createdAt)
} catch(e) {
@ -111,7 +113,7 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
}
accountBalances.add(senderLastBalance.getAccountBalance())
accountBalances.add(new AccountBalance(recipientPublicKey, blockedAmount, ''))
accountBalances.add(new AccountBalance(recipientPublicKey, blockedAmount, communityContext.communityId))
return accountBalances
}
@ -134,7 +136,7 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
accountBalances = this.calculateBalances(item, blockedAmount, communityContext, senderPublicKey, recipientPublicKey)
} catch(e) {
if (item.deletedAt && e instanceof NegativeBalanceError) {
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain)
const senderLastBalance = this.getLastBalanceForUser(senderPublicKey, communityContext.blockchain, communityContext.communityId)
senderLastBalance.updateLegacyDecay(GradidoUnit.zero(), item.createdAt)
const oldBlockedAmountString = blockedAmount.toString()
blockedAmount = senderLastBalance.getBalance()
@ -149,7 +151,7 @@ export class TransactionLinkFundingsSyncRole extends AbstractSyncRole<Transactio
}
try {
addToBlockchain(
this.buildTransaction(item, blockedAmount, duration, senderKeyPair, recipientKeyPair),
this.buildTransaction(communityContext, item, blockedAmount, duration, senderKeyPair, recipientKeyPair).build(),
blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_TRANSACTION_LINK_ID),
accountBalances,

View File

@ -15,7 +15,7 @@ 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 { CommunityContext, UserDb, userDbSchema } from '../../valibot.schema'
import { AbstractSyncRole, IndexType } from './AbstractSync.role'
import { toMysqlDateTime } from '../../utils'
@ -39,7 +39,6 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
.select()
.from(usersTable)
.where(and(
eq(usersTable.foreign, 0),
or(
gt(usersTable.createdAt, toMysqlDateTime(lastIndex.date)),
and(
@ -61,6 +60,7 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
}
buildTransaction(
communityContext: CommunityContext,
item: UserDb,
communityKeyPair: KeyPairEd25519,
accountKeyPair: KeyPairEd25519,
@ -74,14 +74,15 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
new Uuidv4Hash(item.gradidoId).getAsMemoryBlock(),
accountKeyPair.getPublicKey(),
)
.setSenderCommunity(communityContext.communityId)
.sign(communityKeyPair)
.sign(accountKeyPair)
.sign(userKeyPair)
}
calculateAccountBalances(accountPublicKey: MemoryBlockPtr): AccountBalances {
calculateAccountBalances(accountPublicKey: MemoryBlockPtr, communityContext: CommunityContext,): AccountBalances {
const accountBalances = new AccountBalances()
accountBalances.add(new AccountBalance(accountPublicKey, GradidoUnit.zero(), ''))
accountBalances.add(new AccountBalance(accountPublicKey, GradidoUnit.zero(), communityContext.communityId))
return accountBalances
}
@ -96,10 +97,10 @@ export class UsersSyncRole extends AbstractSyncRole<UserDb> {
try {
addToBlockchain(
this.buildTransaction(item, communityContext.keyPair, accountKeyPair, userKeyPair),
this.buildTransaction(communityContext, item, communityContext.keyPair, accountKeyPair, userKeyPair).build(),
communityContext.blockchain,
new LedgerAnchor(item.id, LedgerAnchor.Type_LEGACY_GRADIDO_DB_USER_ID),
this.calculateAccountBalances(accountPublicKey),
this.calculateAccountBalances(accountPublicKey, communityContext),
)
} catch (e) {
throw new BlockchainError(`Error adding ${this.itemTypeName()}`, item, e as Error)

View File

@ -7,6 +7,7 @@ import { TransactionLinkFundingsSyncRole } from './TransactionLinkFundingsSync.r
import { RedeemTransactionLinksSyncRole } from './RedeemTransactionLinksSync.role'
import { ContributionLinkTransactionSyncRole } from './ContributionLinkTransactionSync.role'
import { DeletedTransactionLinksSyncRole } from './DeletedTransactionLinksSync.role'
import { RemoteTransactionsSyncRole } from './RemoteTransactionsSync.role'
export async function syncDbWithBlockchainContext(context: Context, batchSize: number) {
const timeUsedDB = new Profiler()
@ -20,6 +21,7 @@ export async function syncDbWithBlockchainContext(context: Context, batchSize: n
new RedeemTransactionLinksSyncRole(context),
new ContributionLinkTransactionSyncRole(context),
new DeletedTransactionLinksSyncRole(context),
new RemoteTransactionsSyncRole(context),
]
let transactionsCount = 0
let transactionsCountSinceLastLog = 0

View File

@ -1,4 +1,4 @@
import { InMemoryBlockchain, KeyPairEd25519 } from 'gradido-blockchain-js'
import { GradidoUnit, InMemoryBlockchain, KeyPairEd25519 } from 'gradido-blockchain-js'
import * as v from 'valibot'
import { booleanSchema, dateSchema } from '../../schemas/typeConverter.schema'
import {
@ -9,6 +9,7 @@ import {
} from '../../schemas/typeGuard.schema'
import { Balance } from './data/Balance'
import { TransactionTypeId } from './data/TransactionTypeId'
import Decimal from 'decimal.js-light'
const positiveNumberSchema = v.pipe(v.number(), v.minValue(1))
@ -18,7 +19,31 @@ export const userDbSchema = v.object({
communityUuid: uuidv4Schema,
createdAt: dateSchema,
})
/*
declare const validLegacyAmount: unique symbol
export type LegacyAmount = string & { [validLegacyAmount]: true }
export const legacyAmountSchema = v.pipe(
v.string(),
v.regex(/^-?[0-9]+(\.[0-9]+)?$/),
v.transform<string, LegacyAmount>((input: string) => input as LegacyAmount),
)
declare const validGradidoAmount: unique symbol
export type GradidoAmount = GradidoUnit & { [validGradidoAmount]: true }
export const gradidoAmountSchema = v.pipe(
v.union([legacyAmountSchema, v.instance(GradidoUnit, 'expect GradidoUnit type')]),
v.transform<LegacyAmount | GradidoUnit, GradidoAmount>((input: LegacyAmount | GradidoUnit) => {
if (input instanceof GradidoUnit) {
return input as GradidoAmount
}
// round floor with decimal js beforehand
const rounded = new Decimal(input).toDecimalPlaces(4, Decimal.ROUND_FLOOR).toString()
return GradidoUnit.fromString(rounded) as GradidoAmount
}),
)
*/
export const transactionBaseSchema = v.object({
id: positiveNumberSchema,
amount: gradidoAmountSchema,
@ -26,6 +51,7 @@ export const transactionBaseSchema = v.object({
user: userDbSchema,
})
export const transactionDbSchema = v.pipe(v.object({
...transactionBaseSchema.entries,
typeId: v.enum(TransactionTypeId),
@ -107,6 +133,7 @@ export const communityDbSchema = v.object({
export const communityContextSchema = v.object({
communityId: v.string(),
foreign: booleanSchema,
blockchain: v.instance(InMemoryBlockchain, 'expect InMemoryBlockchain type'),
keyPair: v.instance(KeyPairEd25519),
folder: v.pipe(