Merge pull request #1368 from gradido/database_transaction_signatures

database_transaction_signatures
This commit is contained in:
Ulf Gebhardt 2022-02-02 19:41:59 +01:00 committed by GitHub
commit 302b5ad75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 126 additions and 226 deletions

View File

@ -4,7 +4,7 @@ import dotenv from 'dotenv'
dotenv.config()
const constants = {
DB_VERSION: '0015-admin_pending_creations',
DB_VERSION: '0016-transaction_signatures',
}
const server = {

View File

@ -195,7 +195,6 @@ export class AdminResolver {
transaction.transactionTypeId = 1
transaction.memo = pendingCreation.memo
transaction.received = receivedCallDate
transaction.blockchainTypeId = 1
transaction = await transactionRepository.save(transaction)
if (!transaction) throw new Error('Could not create transaction')

View File

@ -36,172 +36,6 @@ import { hasUserAmount, isHexPublicKey } from '../../util/validate'
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
import { RIGHTS } from '../../auth/RIGHTS'
/*
# Test
## Prepare
> sudo systemctl start docker
> docker-compose up mariadb
> DROP all databases
> docker-compose down
> docker compose up mariadb database
> verify there is exactly one database `gradido_community`
TODO:
INSERT INTO `login_groups` (`id`, `alias`, `name`, `url`, `host`, `home`, `description`) VALUES
(1, 'docker', 'docker gradido group', 'localhost', 'nginx', '/', 'gradido test group for docker and stage2 with blockchain db');
>> Database is cool
### Start login server
> docker-compose up login-server community-server nginx
>> Login & community servers and nginx proxy are up and running
## Build database
> cd database
> yarn
> yarn build
> cd ..
>> Database has been built successful
### Start backend (no docker for debugging)
> cd backend
> yarn
> yarn dev
>> Backend is up and running
### Create users
> chromium http://localhost:4000/graphql
> mutation{createUser(email: "receiver@user.net", firstName: "Receiver", lastName: "user", password: "123!AAAb", language: "de")}
> mutation{createUser(email: "sender@user.net", firstName: "Sender", lastName: "user", password: "123!AAAb", language: "de")}
> mutation{createUser(email: "creator@user.net", firstName: "Creator", lastName: "user", password: "123!AAAb", language: "de")}
>> Verify you have 3 entries in `login_users`, `login_user_backups` and `state_users`
### make creator an admin
> INSERT INTO login_user_roles (id, user_id, role_id) VALUES (NULL, '3', '1');
> UPDATE login_users SET email_checked = 1 WHERE id = 3;
> uncomment line: 19 in community_server/src/Controller/ServerUsersController.php
> chromium http://localhost/server-users/add
> create user `creator` `123` `creator@different.net`
>> verify you have 1 entry in `server_users`
> login with user on http://localhost/server-users
> activate server user by changing the corresponding flag in the interface
> navigate to http://localhost/transaction-creations/create-multi
> create 1000GDD for user sender@user.net
> navigate to http://localhost
> login with `creator@user.net` `123!AAAb`
> confirm transaction (top right corner - click the thingy, click the green button `Transaktion abschließen`)
### the test:
> chromium http://localhost:4000/graphql
> query{login(email: "sender@user.net", password: "123!AAAb"){pubkey}}
>> copy token from network tab (inspect)
> mutation{sendCoins(email: "receiver@user.net", amount: 10.0, memo: "Hier!")}
> mutation{sendCoins(email: "receiver@user.net", amount: 10.0, memo: "Hier!")}
> Headers: {"Authorization": "Bearer ${token}"}
>> Verify via Database that stuff is as it should see `state_balance` & `transaction_send_coins`
### create decay block
> chromium http://localhost/transactions/add
> login with `creator` `123`
> select `decay start`
> press submit
> wait for at least 0.02 display of decay on user sender@user.net on old frontend, this should be aprox 10min
> chromium http://localhost:4000/graphql
> query{login(email: "sender@user.net", password: "123!AAAb"){pubkey}}
>> copy token from network tab (inspect)
> mutation{sendCoins(email: "receiver@user.net", amount: 10.0, memo: "Hier!")}
>> verify in `transaction_send_coins` that a decay was taken into account
>> same in `state_balances`
>> now check the old frontend
>>> sender@user.net should have a decay of 0.02
>>> while receiver@user.net should have zero decay on anything (old frontend)
### Export data
> docker-compose up phpmyadmin
> chromium http://localhost:8074/
> select gradido_community
> export
> select custom
> untick structure
> ok
## Results
NOTE: We decided not to write the `transaction_signatures` since its unused. This is the main difference.
NOTE: We fixed a bug in the `state_user_transactions code` with the new implementation of apollo
Master:
--
-- Dumping data for table `state_user_transactions`
--
INSERT INTO `state_user_transactions` (`id`, `state_user_id`, `transaction_id`, `transaction_type_id`, `balance`, `balance_date`) VALUES
(1, 2, 1, 1, 10000000, '2021-11-05 12:45:18'),
(2, 2, 2, 2, 9900000, '2021-11-05 12:48:35'),
(3, 1, 2, 2, 100000, '2021-11-05 12:48:35'),
(4, 2, 3, 2, 9800000, '2021-11-05 12:49:07'),
(5, 1, 3, 2, 200000, '2021-11-05 12:49:07'),
(6, 2, 5, 2, 9699845, '2021-11-05 13:03:50'),
(7, 1, 5, 2, 99996, '2021-11-05 13:03:50');
--
-- Dumping data for table `transactions`
--
INSERT INTO `transactions` (`id`, `state_group_id`, `transaction_type_id`, `tx_hash`, `memo`, `received`, `blockchain_type_id`) VALUES
(1, NULL, 1, 0x9ccdcd01ccb6320c09c2d1da2f0bf735a95ece0e7c1df6bbff51918fbaec061700000000000000000000000000000000, '', '2021-11-05 12:45:18', 1),
(2, NULL, 2, 0x58d7706a67fa4ff4b8038168c6be39a2963d7e28e9d3872759ad09c519fe093700000000000000000000000000000000, 'Hier!', '2021-11-05 12:48:35', 1),
(3, NULL, 2, 0x427cd214f92ef35af671129d50edc5a478c53d1e464f285b7615d9794a69f69b00000000000000000000000000000000, 'Hier!', '2021-11-05 12:49:07', 1),
(4, NULL, 9, 0x32807368f0906a21b94c072599795bc9eeab88fb565df82e85cc62a4fdcde48500000000000000000000000000000000, '', '2021-11-05 12:51:51', 1),
(5, NULL, 2, 0x75eb729e0f60a1c8cead1342955853d2440d7a2ea57dfef6d4a18bff0d94491e00000000000000000000000000000000, 'Hier!', '2021-11-05 13:03:50', 1);
--
-- Dumping data for table `transaction_signatures`
--
INSERT INTO `transaction_signatures` (`id`, `transaction_id`, `signature`, `pubkey`) VALUES
(1, 1, 0x5888edcdcf77aaadad6d321882903bc831d7416f17213fd5020a764365b5fcb336e4c7917385a1278ea44ccdb31eac4a09e448053b5e3f8f1fe5da3baf53c008, 0xd5b20f8dee415038bfa2b6b0e1b40ff54850351109444863b04d6d28825b7b7d),
(2, 2, 0xf6fef428f8f22faf7090f7d740e6088d1d90c58ae92d757117d7d91d799e659f3a3a0c65a3fd97cbde798e761f9d23eff13e8810779a184c97c411f28e7c4608, 0xdc74a589004377ab14836dce68ce2ca34e5b17147cd78ad4b3afe8137524ae8a),
(3, 3, 0x8ebe9730c6cf61f56ef401d6f2bd229f3c298ca3c2791ee9137e4827b7af6c6d6566fca616eb1fe7adc2e4d56b5c7350ae3990c9905580630fa75ecffca8e001, 0xdc74a589004377ab14836dce68ce2ca34e5b17147cd78ad4b3afe8137524ae8a),
(4, 5, 0x50cf418f7e217391e89ab9c2879ae68d7c7c597d846b4fe1c082b5b16e5d0c85c328fbf48ad3490bcfe94f446700ae0a4b0190e76d26cc752abced58f480c80f, 0xdc74a589004377ab14836dce68ce2ca34e5b17147cd78ad4b3afe8137524ae8a);
This Feature Branch:
--
-- Dumping data for table `state_user_transactions`
--
INSERT INTO `state_user_transactions` (`id`, `state_user_id`, `transaction_id`, `transaction_type_id`, `balance`, `balance_date`) VALUES
(1, 2, 1, 1, 10000000, '2021-11-05 00:25:46'),
(12, 2, 7, 2, 9900000, '2021-11-05 00:55:37'),
(13, 1, 7, 2, 100000, '2021-11-05 00:55:37'),
(14, 2, 8, 2, 9800000, '2021-11-05 01:00:04'),
(15, 1, 8, 2, 200000, '2021-11-05 01:00:04'),
(16, 2, 10, 2, 9699772, '2021-11-05 01:17:41'),
(17, 1, 10, 2, 299995, '2021-11-05 01:17:41');
--
-- Dumping data for table `transactions`
--
INSERT INTO `transactions` (`id`, `state_group_id`, `transaction_type_id`, `tx_hash`, `memo`, `received`, `blockchain_type_id`) VALUES
(1, NULL, 1, 0xdd030d475479877587d927ed9024784ba62266cf1f3d87862fc98ad68f7b26e400000000000000000000000000000000, '', '2021-11-05 00:25:46', 1),
(7, NULL, 2, NULL, 'Hier!', '2021-11-05 00:55:37', 1),
(8, NULL, 2, NULL, 'Hier!', '2021-11-05 01:00:04', 1),
(9, NULL, 9, 0xb1cbedbf126aa35f5edbf06e181c415361d05228ab4da9d19a4595285a673dfa00000000000000000000000000000000, '', '2021-11-05 01:05:34', 1),
(10, NULL, 2, NULL, 'Hier!', '2021-11-05 01:17:41', 1);
--
-- Dumping data for table `transaction_signatures`
--
INSERT INTO `transaction_signatures` (`id`, `transaction_id`, `signature`, `pubkey`) VALUES
(1, 1, 0x60d632479707e5d01cdc32c3326b5a5bae11173a0c06b719ee7b552f9fd644de1a0cd4afc207253329081d39dac1a63421f51571d836995c649fc39afac7480a, 0x48c45cb4fea925e83850f68f2fa8f27a1a4ed1bcba68cdb59fcd86adef3f52ee);
*/
// Helper function
async function calculateAndAddDecayTransactions(
userTransactions: dbUserTransaction[],

View File

@ -1,25 +1,34 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { TransactionCreation } from './TransactionCreation'
import { TransactionSendCoin } from './TransactionSendCoin'
import { TransactionCreation } from '../TransactionCreation'
import { TransactionSendCoin } from '../TransactionSendCoin'
@Entity('transactions')
export class Transaction extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ name: 'transaction_type_id' })
@Column({ name: 'state_group_id', unsigned: true, default: null })
stateGroupId: number
@Column({ name: 'transaction_type_id', unsigned: true, nullable: false })
transactionTypeId: number
@Column({ name: 'tx_hash', type: 'binary', length: 48 })
@Column({ name: 'tx_hash', type: 'binary', length: 48, default: null })
txHash: Buffer
@Column()
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ type: 'timestamp' })
@Column({ type: 'timestamp', nullable: false, default: () => 'CURRENT_TIMESTAMP' })
received: Date
@Column({ name: 'blockchain_type_id' })
@Column({
name: 'blockchain_type_id',
type: 'bigint',
unsigned: true,
nullable: false,
default: 1,
})
blockchainTypeId: number
@OneToOne(() => TransactionSendCoin, (transactionSendCoin) => transactionSendCoin.transaction)

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
import { Transaction } from './Transaction'
import { Transaction } from '../Transaction'
@Entity('transaction_creations')
export class TransactionCreation extends BaseEntity {

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
import { Transaction } from './Transaction'
import { Transaction } from '../Transaction'
@Entity('transaction_send_coins')
export class TransactionSendCoin extends BaseEntity {

View File

@ -1,21 +1,21 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'
import { Transaction } from './Transaction'
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
import { Transaction } from '../Transaction'
@Entity('transaction_signatures')
export class TransactionSignature extends BaseEntity {
@PrimaryGeneratedColumn()
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'transaction_id' })
transactionId: number
@Column({ type: 'binary', length: 64 })
@Column({ type: 'binary', length: 64, nullable: false })
signature: Buffer
@Column({ type: 'binary', length: 32 })
@Column({ type: 'binary', length: 32, nullable: false })
pubkey: Buffer
@ManyToOne(() => Transaction)
@OneToOne(() => Transaction)
@JoinColumn({ name: 'transaction_id' })
transaction: Transaction
}

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
import { UserSetting } from './UserSetting'
import { UserSetting } from '../UserSetting'
// Moriz: I do not like the idea of having two user tables
@Entity('state_users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'
import { User } from './User'
import { User } from '../User'
@Entity()
export class UserSetting extends BaseEntity {

View File

@ -0,0 +1,34 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { TransactionCreation } from '../TransactionCreation'
import { TransactionSendCoin } from '../TransactionSendCoin'
@Entity('transactions')
export class Transaction extends BaseEntity {
// TODO the id is defined as bigint(20) - there might be problems with that: https://github.com/typeorm/typeorm/issues/2400
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'transaction_type_id', unsigned: true, nullable: false })
transactionTypeId: number
@Column({ name: 'tx_hash', type: 'binary', length: 48, default: null })
txHash: Buffer
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ type: 'timestamp', nullable: false, default: () => 'CURRENT_TIMESTAMP' })
received: Date
@Column({ type: 'binary', length: 64, nullable: true, default: null })
signature: Buffer
@Column({ type: 'binary', length: 32, nullable: true, default: null })
pubkey: Buffer
@OneToOne(() => TransactionSendCoin, (transactionSendCoin) => transactionSendCoin.transaction)
transactionSendCoin: TransactionSendCoin
@OneToOne(() => TransactionCreation, (transactionCreation) => transactionCreation.transaction)
transactionCreation: TransactionCreation
}

View File

@ -1 +1 @@
export { Transaction } from './0001-init_db/Transaction'
export { Transaction } from './0016-transaction_signatures/Transaction'

View File

@ -1 +0,0 @@
export { TransactionSignature } from './0001-init_db/TransactionSignature'

View File

@ -7,7 +7,6 @@ import { Migration } from './Migration'
import { ServerUser } from './ServerUser'
import { Transaction } from './Transaction'
import { TransactionCreation } from './TransactionCreation'
import { TransactionSignature } from './TransactionSignature'
import { TransactionSendCoin } from './TransactionSendCoin'
import { User } from './User'
import { UserSetting } from './UserSetting'
@ -25,7 +24,6 @@ export const entities = [
ServerUser,
Transaction,
TransactionCreation,
TransactionSignature,
TransactionSendCoin,
User,
UserSetting,

View File

@ -0,0 +1,60 @@
/* MIGRATION TO CLEANUP TRANSACTIONS TABLE
*
* This migration cleans up the transactions table and
* combines its data with transaction_signatures.
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// Drop column `state_group_id` since it only contains "0" as value, no variation.
// Furthermore it was not present in our model itself (meaning that newly created )
await queryFn('ALTER TABLE `transactions` DROP COLUMN `state_group_id`;')
// Drop column `blockchain_type_id` since it only contains "1" as value, no variation.
await queryFn('ALTER TABLE `transactions` DROP COLUMN `blockchain_type_id`;')
// Create `signature` column - for data from `transaction_signatures` table.
await queryFn(
'ALTER TABLE `transactions` ADD COLUMN `signature` binary(64) DEFAULT NULL AFTER `received`;',
)
// Create `pubkey` column - for data from `transaction_signatures` table.
await queryFn(
'ALTER TABLE `transactions` ADD COLUMN `pubkey` binary(32) DEFAULT NULL AFTER `signature`;',
)
// Transfer data from `transaction_signatures` table to `transactions` table
await queryFn(`
UPDATE transactions
INNER JOIN transaction_signatures ON transactions.id = transaction_signatures.transaction_id
SET transactions.signature = transaction_signatures.signature, transactions.pubkey = transaction_signatures.pubkey;
`)
// Drop `transaction_signatures` table
await queryFn('DROP TABLE `transaction_signatures`;')
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
CREATE TABLE \`transaction_signatures\` (
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
\`transaction_id\` int(10) unsigned NOT NULL,
\`signature\` binary(64) NOT NULL,
\`pubkey\` binary(32) NOT NULL,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`)
await queryFn(`
INSERT INTO transaction_signatures (transaction_id, signature, pubkey)
(SELECT id as transaction_id, signature, pubkey FROM transactions WHERE signature IS NOT NULL and pubkey IS NOT NULL);
`)
await queryFn('ALTER TABLE `transactions` DROP COLUMN `pubkey`;')
await queryFn('ALTER TABLE `transactions` DROP COLUMN `signature`;')
await queryFn(
'ALTER TABLE `transactions` ADD COLUMN `blockchain_type_id` bigint(20) unsigned NOT NULL DEFAULT 1 AFTER `received` ;',
)
await queryFn(
'ALTER TABLE `transactions` ADD COLUMN `state_group_id` int(10) unsigned DEFAULT NULL AFTER `id`;',
)
// We have to set the correct values previously in the table , since its not the same as the column's default
await queryFn('UPDATE `transactions` SET `state_group_id` = 0;')
}

View File

@ -1,18 +0,0 @@
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { TransactionSignature } from '../../entity/TransactionSignature'
import { TransactionSignatureContext } from '../interface/TransactionContext'
import { randomBytes } from 'crypto'
define(TransactionSignature, (faker: typeof Faker, context?: TransactionSignatureContext) => {
if (!context || !context.transaction) {
throw new Error('TransactionSignature: No transaction present!')
}
const transactionSignature = new TransactionSignature()
transactionSignature.signature = context.signature ? context.signature : randomBytes(64)
transactionSignature.pubkey = context.pubkey ? context.pubkey : randomBytes(32)
transactionSignature.transaction = context.transaction
return transactionSignature
})

View File

@ -12,7 +12,8 @@ define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
transaction.txHash = context.txHash ? context.txHash : randomBytes(48)
transaction.memo = context.memo || context.memo === '' ? context.memo : faker.lorem.sentence()
transaction.received = context.received ? context.received : new Date()
transaction.blockchainTypeId = context.blockchainTypeId ? context.blockchainTypeId : 1
transaction.signature = context.signature ? context.signature : randomBytes(64)
transaction.pubkey = context.signaturePubkey ? context.signaturePubkey : randomBytes(32)
if (context.transactionSendCoin) transaction.transactionSendCoin = context.transactionSendCoin
if (context.transactionCreation) transaction.transactionCreation = context.transactionCreation

View File

@ -9,6 +9,8 @@ export interface TransactionContext {
memo?: string
received?: Date
blockchainTypeId?: number
signature?: Buffer
signaturePubkey?: Buffer
transactionSendCoin?: TransactionSendCoin
transactionCreation?: TransactionCreation
}
@ -43,10 +45,6 @@ export interface UserTransactionContext {
transactionTypeId?: number
balance?: number
balanceDate?: Date
}
export interface TransactionSignatureContext {
signature?: Buffer
pubkey?: Buffer
transaction?: Transaction
}

View File

@ -10,7 +10,6 @@ import {
TransactionContext,
TransactionCreationContext,
UserTransactionContext,
TransactionSignatureContext,
} from '../../interface/TransactionContext'
import { UserInterface } from '../../interface/UserInterface'
import { User } from '../../../entity/User'
@ -19,7 +18,6 @@ import { LoginUserBackup } from '../../../entity/LoginUserBackup'
import { ServerUser } from '../../../entity/ServerUser'
import { Balance } from '../../../entity/Balance'
import { Transaction } from '../../../entity/Transaction'
import { TransactionSignature } from '../../../entity/TransactionSignature'
import { UserTransaction } from '../../../entity/UserTransaction'
import { TransactionCreation } from '../../../entity/TransactionCreation'
import { Factory } from 'typeorm-seeding'
@ -46,9 +44,6 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro
await factory(UserTransaction)(
createUserTransactionContext(userData, user, transaction),
).create()
await factory(TransactionSignature)(
createTransactionSignatureContext(userData, transaction),
).create()
}
}
@ -161,16 +156,7 @@ const createUserTransactionContext = (
transactionTypeId: transaction.transactionTypeId,
balance: context.amount,
balanceDate: context.recordDate,
}
}
const createTransactionSignatureContext = (
context: UserInterface,
transaction: Transaction,
): TransactionSignatureContext => {
return {
signature: context.signature,
pubkey: context.signaturePubkey,
transaction,
}
}