mirror of
https://github.com/IT4Change/gradido.git
synced 2026-04-06 01:25:28 +00:00
add remote transactions, fix some stuff
This commit is contained in:
parent
02f64af3d3
commit
473d8139e8
@ -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.
|
||||
*
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)],
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user