diff --git a/.gitignore b/.gitignore
index 5c23ad8bd..44f403757 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
*.log
/node_modules/*
.vscode
+.skeema
+nbproject
\ No newline at end of file
diff --git a/community_server/db/setup_db_tables/insert_migrations.sql b/community_server/db/setup_db_tables/insert_migrations.sql
new file mode 100644
index 000000000..e6f38117d
--- /dev/null
+++ b/community_server/db/setup_db_tables/insert_migrations.sql
@@ -0,0 +1,2 @@
+INSERT INTO `migrations` (`id`, `db_version`) VALUES
+(1, 2);
diff --git a/community_server/db/setup_db_tables/insert_transaction_types.sql b/community_server/db/setup_db_tables/insert_transaction_types.sql
index 02ef8374a..37919045d 100644
--- a/community_server/db/setup_db_tables/insert_transaction_types.sql
+++ b/community_server/db/setup_db_tables/insert_transaction_types.sql
@@ -6,5 +6,5 @@ INSERT INTO `transaction_types` (`id`, `name`, `text`) VALUES
(5, 'group remove member', 'remove user from group, maybe he was moved elsewhere'),
(6, 'hedera topic create', 'create new topic on hedera'),
(7, 'hedera topic send message', 'send consensus message over hedera topic'),
-(8, 'hedera account create', 'create new account on hedera for holding some founds with unencrypted keys');
-
+(8, 'hedera account create', 'create new account on hedera for holding some founds with unencrypted keys'),
+(9, 'decay start', 'signalize the starting point for decay calculation, allowed only once per chain');
diff --git a/community_server/db/skeema/gradido_community/migrations.sql b/community_server/db/skeema/gradido_community/migrations.sql
new file mode 100644
index 000000000..7665bcf29
--- /dev/null
+++ b/community_server/db/skeema/gradido_community/migrations.sql
@@ -0,0 +1,5 @@
+CREATE TABLE `migrations` (
+ `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
+ `db_version` int DEFAULT 0,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/community_server/db/skeema/gradido_community/transaction_types.sql b/community_server/db/skeema/gradido_community/transaction_types.sql
index a3e6779d9..10aad25b0 100644
--- a/community_server/db/skeema/gradido_community/transaction_types.sql
+++ b/community_server/db/skeema/gradido_community/transaction_types.sql
@@ -1,6 +1,6 @@
CREATE TABLE `transaction_types` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `name` varchar(90) COLLATE utf8mb4_unicode_ci NOT NULL,
`text` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/community_server/src/Controller/AppController.php b/community_server/src/Controller/AppController.php
index 1cdcf4418..9f577d77a 100644
--- a/community_server/src/Controller/AppController.php
+++ b/community_server/src/Controller/AppController.php
@@ -156,8 +156,24 @@ class AppController extends Controller
}
}
+ protected function checkForMigration($html = true)
+ {
+ $migrationsTable = TableRegistry::getTableLocator()->get('Migrations');
+ $last_migration = $migrationsTable->find()->last();
+ $current_db_version = 1;
+ if($last_migration) {
+ $current_db_version = $last_migration->db_version;
+ }
+ $php_data_version = 2;
+ if($current_db_version < $php_data_version) {
+ $this->redirect(['controller' => 'Migrations', 'action' => 'migrate', 'html' => $html, 'db_version' => $current_db_version]);
+ }
+ }
+
+
protected function requestLogin($sessionId = 0, $redirect = true)
{
+ $this->checkForMigration($redirect);
$session = $this->getRequest()->getSession();
// check login
// disable encryption for cookies
diff --git a/community_server/src/Controller/MigrationsController.php b/community_server/src/Controller/MigrationsController.php
new file mode 100644
index 000000000..31fa41001
--- /dev/null
+++ b/community_server/src/Controller/MigrationsController.php
@@ -0,0 +1,167 @@
+Auth->allow('migrate');
+ }
+
+ /**
+ * Index method
+ *
+ * @return \Cake\Http\Response|null
+ */
+ public function index()
+ {
+ $migrations = $this->paginate($this->Migrations);
+
+ $this->set(compact('migrations'));
+ }
+
+ protected function callFunctions(array $callables)
+ {
+ foreach($callables as $callable) {
+ $result = call_user_func($callable);
+ if(!$result['success']) {
+ return $result;
+ }
+ }
+ return ['success' => true];
+ }
+
+ public function migrate()
+ {
+
+ $html = $this->request->getQuery('html');
+ $current_db_version = $this->request->getQuery('db_version');
+
+ $startTime = microtime(true);
+ $stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
+ $transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
+ $transactionTypesTable = TableRegistry::getTableLocator()->get('TransactionTypes');
+ $stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances');
+ $blockchainTypesTable = TableRegistry::getTableLocator()->get('BlockchainTypes');
+
+ $new_db_version = 1;
+
+ $commands = [];
+ // migrate from version 1 to 2
+ if($current_db_version == 1) {
+ $stateUserTransactionsTable->truncate();
+ $commands = [
+ [$blockchainTypesTable, 'fillWithDefault'],
+ [$transactionTypesTable, 'fillWithDefault'],
+ [$transactionsTable, 'fillStateUserTransactions'],
+ [$stateBalancesTable, 'updateAllBalances']
+ ];
+ $new_db_version = 2;
+ }
+
+ $migration_result = $this->callFunctions($commands);
+ if($migration_result['success']) {
+ $migration_entity = $this->Migrations->newEntity();
+ $migration_entity->db_version = $new_db_version;
+ $this->Migrations->save($migration_entity);
+ }
+ if(!$html) {
+ return $this->returnJson($migration_result);
+ } else {
+ $this->set('db_version', $current_db_version);
+ $this->set('result', $migration_result);
+ $this->set('timeUsed', microtime(true) - $startTime);
+ }
+ }
+
+ /**
+ * View method
+ *
+ * @param string|null $id Migration id.
+ * @return \Cake\Http\Response|null
+ * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
+ */
+ public function view($id = null)
+ {
+ $migration = $this->Migrations->get($id, [
+ 'contain' => [],
+ ]);
+
+ $this->set('migration', $migration);
+ }
+
+ /**
+ * Add method
+ *
+ * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
+ */
+ public function add()
+ {
+ $migration = $this->Migrations->newEntity();
+ if ($this->request->is('post')) {
+ $migration = $this->Migrations->patchEntity($migration, $this->request->getData());
+ if ($this->Migrations->save($migration)) {
+ $this->Flash->success(__('The migration has been saved.'));
+
+ return $this->redirect(['action' => 'index']);
+ }
+ $this->Flash->error(__('The migration could not be saved. Please, try again.'));
+ }
+ $this->set(compact('migration'));
+ }
+
+ /**
+ * Edit method
+ *
+ * @param string|null $id Migration id.
+ * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise.
+ * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
+ */
+ public function edit($id = null)
+ {
+ $migration = $this->Migrations->get($id, [
+ 'contain' => [],
+ ]);
+ if ($this->request->is(['patch', 'post', 'put'])) {
+ $migration = $this->Migrations->patchEntity($migration, $this->request->getData());
+ if ($this->Migrations->save($migration)) {
+ $this->Flash->success(__('The migration has been saved.'));
+
+ return $this->redirect(['action' => 'index']);
+ }
+ $this->Flash->error(__('The migration could not be saved. Please, try again.'));
+ }
+ $this->set(compact('migration'));
+ }
+
+ /**
+ * Delete method
+ *
+ * @param string|null $id Migration id.
+ * @return \Cake\Http\Response|null Redirects to index.
+ * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
+ */
+ public function delete($id = null)
+ {
+ $this->request->allowMethod(['post', 'delete']);
+ $migration = $this->Migrations->get($id);
+ if ($this->Migrations->delete($migration)) {
+ $this->Flash->success(__('The migration has been deleted.'));
+ } else {
+ $this->Flash->error(__('The migration could not be deleted. Please, try again.'));
+ }
+
+ return $this->redirect(['action' => 'index']);
+ }
+}
diff --git a/community_server/src/Controller/StateBalancesController.php b/community_server/src/Controller/StateBalancesController.php
index 3994b989a..655dbc026 100644
--- a/community_server/src/Controller/StateBalancesController.php
+++ b/community_server/src/Controller/StateBalancesController.php
@@ -62,7 +62,7 @@ class StateBalancesController extends AppController
$user = $session->read('StateUser');
$update_balance_result = $this->StateBalances->updateBalances($user['id']);
- if($update_balance_result !== true) {
+ if($update_balance_result['success'] !== true) {
$this->addAdminError('StateBalances', 'overview', $update_balance_result, $user['id']);
}
// sendRequestGDT
diff --git a/community_server/src/Controller/TransactionsController.php b/community_server/src/Controller/TransactionsController.php
index 4f8e04c2b..90e78b7fb 100644
--- a/community_server/src/Controller/TransactionsController.php
+++ b/community_server/src/Controller/TransactionsController.php
@@ -313,15 +313,20 @@ class TransactionsController extends AppController
if ($this->request->is('post')) {
$transaction = $this->Transactions->patchEntity($transaction, $this->request->getData());
if ($this->Transactions->save($transaction)) {
- $this->Flash->success(__('The transaction has been saved.'));
-
- return $this->redirect(['action' => 'index']);
+ $result = $this->Transactions->updateTxHash($transaction, 'start decay');
+ if($result === true) {
+ $this->Flash->success(__('The transaction has been saved.'));
+ return $this->redirect(['action' => 'index']);
+ } else {
+ $this->Flash->error(__('Error by saving: ' . json_encode($result)));
+ }
}
$this->Flash->error(__('The transaction could not be saved. Please, try again.'));
}
$stateGroups = $this->Transactions->StateGroups->find('list', ['limit' => 200]);
$transactionTypes = $this->Transactions->TransactionTypes->find('list', ['limit' => 200]);
- $this->set(compact('transaction', 'stateGroups', 'transactionTypes'));
+ $blockchainTypes = $this->Transactions->BlockchainTypes->find('list');
+ $this->set(compact('transaction', 'stateGroups', 'transactionTypes', 'blockchainTypes'));
}
/**
diff --git a/community_server/src/Model/Entity/Migration.php b/community_server/src/Model/Entity/Migration.php
new file mode 100644
index 000000000..700136cf8
--- /dev/null
+++ b/community_server/src/Model/Entity/Migration.php
@@ -0,0 +1,26 @@
+ true,
+ ];
+}
diff --git a/community_server/src/Model/Entity/StateBalance.php b/community_server/src/Model/Entity/StateBalance.php
index 7d595653e..334db222f 100644
--- a/community_server/src/Model/Entity/StateBalance.php
+++ b/community_server/src/Model/Entity/StateBalance.php
@@ -44,9 +44,7 @@ class StateBalance extends Entity
} else if(method_exists($dateOrTime, 'i18nFormat')) {
return $dateOrTime->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT);
} else {
- var_dump($dateOrTime);
- debug_print_backtrace(0, 6);
- die("date or time unexpected object");
+ return 0;
}
}
@@ -59,8 +57,12 @@ class StateBalance extends Entity
// SELECT TIMESTAMPDIFF(SECOND, modified, CURDATE()) AS age_in_seconds from state_balances
// decay_for_duration = decay_factor^seconds
// decay = gradido_cent * decay_for_duration
-
- $decay_duration = intval(Time::now()->getTimestamp() - $this->convertToTimestamp($this->record_date));
+ $startDate = $this->convertToTimestamp($this->record_date);
+ if($startDate == 0) {
+ return $this->amount;
+ }
+
+ $decay_duration = intval(Time::now()->getTimestamp() - $startDate);
if($decay_duration === 0) {
return $this->amount;
}
diff --git a/community_server/src/Model/Table/AppTable.php b/community_server/src/Model/Table/AppTable.php
new file mode 100644
index 000000000..138a7f949
--- /dev/null
+++ b/community_server/src/Model/Table/AppTable.php
@@ -0,0 +1,40 @@
+getSchema()->truncateSql($this->getConnection());
+ foreach ($truncateCommands as $truncateCommand) {
+ $this->getConnection()->query($truncateCommand);
+ }
+ $this->getConnection()->query('ALTER TABLE ' . $this->getSchema()->name() . ' AUTO_INCREMENT=1');
+ }
+ public function saveManyWithErrors($entities)
+ {
+ $save_results = $this->saveMany($entities);
+ // save all at once failed, no try one by one to get error message
+ if($save_results === false) {
+ foreach($entities as $entity) {
+ if(!$this->save($entity)) {
+ return ['success' => false, 'errors' => $entity->getErrors()];
+ }
+ }
+ } else {
+ return ['success' => true];
+ }
+ }
+}
diff --git a/community_server/src/Model/Table/BlockchainTypesTable.php b/community_server/src/Model/Table/BlockchainTypesTable.php
index 90213ac9f..3aa67a83d 100644
--- a/community_server/src/Model/Table/BlockchainTypesTable.php
+++ b/community_server/src/Model/Table/BlockchainTypesTable.php
@@ -18,7 +18,7 @@ use Cake\Validation\Validator;
* @method \App\Model\Entity\BlockchainType[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\BlockchainType findOrCreate($search, callable $callback = null, $options = [])
*/
-class BlockchainTypesTable extends Table
+class BlockchainTypesTable extends AppTable
{
/**
* Initialize method
@@ -65,4 +65,30 @@ class BlockchainTypesTable extends Table
return $validator;
}
+
+ public function fillWithDefault()
+ {
+ $entry_contents = [
+ [
+ 'id' => 1,
+ 'name' => 'mysql',
+ 'text' => 'use mysql db as blockchain, work only with single community-server',
+ 'symbol' => NULL
+ ],
+ [
+ 'id' => 2,
+ 'name' => 'hedera',
+ 'text' => 'use hedera for transactions',
+ 'symbol' => 'HBAR'
+ ]
+ ];
+ $entities = $this->newEntities($entry_contents);
+ $this->truncate();
+ $save_results = $this->saveManyWithErrors($entities);
+ if(!$save_results['success']) {
+ $save_results['msg'] = 'error by saving default transaction types';
+ }
+ return $save_results;
+
+ }
}
diff --git a/community_server/src/Model/Table/MigrationsTable.php b/community_server/src/Model/Table/MigrationsTable.php
new file mode 100644
index 000000000..b5cb42154
--- /dev/null
+++ b/community_server/src/Model/Table/MigrationsTable.php
@@ -0,0 +1,56 @@
+setTable('migrations');
+ $this->setDisplayField('id');
+ $this->setPrimaryKey('id');
+ }
+
+ /**
+ * Default validation rules.
+ *
+ * @param \Cake\Validation\Validator $validator Validator instance.
+ * @return \Cake\Validation\Validator
+ */
+ public function validationDefault(Validator $validator)
+ {
+ $validator
+ ->nonNegativeInteger('id')
+ ->allowEmptyString('id', null, 'create');
+
+ $validator
+ ->integer('db_version')
+ ->allowEmptyString('db_version');
+
+ return $validator;
+ }
+}
diff --git a/community_server/src/Model/Table/StateBalancesTable.php b/community_server/src/Model/Table/StateBalancesTable.php
index 7564c30f2..7e5f96be9 100644
--- a/community_server/src/Model/Table/StateBalancesTable.php
+++ b/community_server/src/Model/Table/StateBalancesTable.php
@@ -1,14 +1,12 @@
'INNER'
]);
}
+
+ public static function getDecayStartDateCached()
+ {
+ if(self::$startDecayDate == null) {
+ $transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
+ self::$startDecayDate = $transactionsTable->getDecayStartDate();
+ }
+ return self::$startDecayDate;
+ }
/**
* Default validation rules.
@@ -83,20 +91,60 @@ class StateBalancesTable extends Table
return $rules;
}
-
- public function sortTransactions($a, $b)
+ public function calculateDecay($startBalance, FrozenTime $startDate, FrozenTime $endDate, $withInterval = false)
{
- if ($a['date'] == $b['date']) {
- return 0;
+ $decayStartDate = self::getDecayStartDateCached();
+ // if no start decay block exist, we just return input
+ // if start date for decay is after enddate, we also just return input
+ if($decayStartDate === null || $decayStartDate >= $endDate) {
+ if($withInterval) {
+ return ['balance' => $startBalance, 'interval' => new \DateInterval('PT0S')];
+ } else {
+ return $startBalance;
+ }
}
- return ($a['date'] > $b['date']) ? -1 : 1;
+ $state_balance = $this->newEntity();
+ $state_balance->amount = $startBalance;
+ $interval = null;
+ // if decay start date is before start date we calculate decay for full duration
+ if($decayStartDate < $startDate) {
+ $state_balance->record_date = $startDate;
+ $interval = $endDate->diff($startDate);
+ }
+ // if decay start in between start date and end date we caculcate decay from decay start time to end date
+ else {
+ $state_balance->record_date = $decayStartDate;
+ $interval = $endDate->diff($decayStartDate);
+ }
+ $decay = $state_balance->partDecay($endDate);
+ if($withInterval) {
+ return ['balance' => $decay, 'interval' => $interval];
+ } else {
+ return $decay;
+ }
+
+
}
+ public function updateAllBalances()
+ {
+ $stateUserTable = TableRegistry::getTableLocator()->get('StateUsers');
+ $state_users = $stateUserTable->find()->select(['id'])->contain([]);
+ foreach($state_users as $state_user) {
+ $result = $this->updateBalances($state_user->id);
+ if($result['success'] === false) {
+ $result['state_user_id'] = $state_user->id;
+ return $result;
+ }
+ }
+ return ['success' => true];
+ }
public function updateBalances($stateUserId)
{
$stateUserTransactionsTable = TableRegistry::getTableLocator()->get('StateUserTransactions');
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
+ $now = new FrozenTime;
// info: cakephp use lazy loading, query will be executed later only if needed
$state_balances = $this->find('all')->where(['state_user_id' => $stateUserId]);
$state_user_transactions = $stateUserTransactionsTable
@@ -107,7 +155,7 @@ class StateBalancesTable extends Table
;
if(!$state_user_transactions || !$state_user_transactions->count()) {
- return true;
+ return ['success' => true];
}
// first: decide what todo
@@ -128,18 +176,26 @@ class StateBalancesTable extends Table
if($state_user_transactions->count() == 0){
$clear_state_balance = true;
} else {
+
+ $first_state_balance = $state_balances->first();
+ $first_state_balance_decayed = self::calculateDecay(
+ $first_state_balance->amount,
+ $first_state_balance->record_date,
+ $now);
+
$last_state_user_transaction = $state_user_transactions->last();
- $last_transaction = $this->newEntity();
- $last_transaction->amount = $last_state_user_transaction->balance;
- $last_transaction->record_date = $last_state_user_transaction->balance_date;
+ $last_state_user_transaction_decayed = self::calculateDecay(
+ $last_state_user_transaction->balance,
+ $last_state_user_transaction->balance_date,
+ $now);
// if entrys are nearly the same, we don't need doing anything
- if(abs($last_transaction->decay - $state_balances->first()->decay) > 100) {
+ if(floor($last_state_user_transaction_decayed/100) !== floor($first_state_balance_decayed/100)) {
$recalculate_state_user_transactions_balance = true;
$update_state_balance = true;
}
}
}
-
+
if(!$recalculate_state_user_transactions_balance) {
$last_state_user_transaction = $state_user_transactions->last();
if($last_state_user_transaction && $last_state_user_transaction->balance <= 0) {
@@ -193,37 +249,28 @@ class StateBalancesTable extends Table
$transaction = $transactions_indiced[$state_user_transaction->transaction_id];
if($transaction->transaction_type_id > 2) {
continue;
- }
- //echo "transaction id: ".$transaction->id . "
";
- $amount_date = null;
+ }
$amount = 0;
if($transaction->transaction_type_id == 1) { // creation
- $temp = $transaction->transaction_creation;
-
- /*$balance_temp = $this->newEntity();
- $balance_temp->amount = $temp->amount;
- $balance_temp->record_date = $temp->target_date;
- */
- $amount = intval($temp->amount);//$balance_temp->partDecay($transaction->received);
- $amount_date = $temp->target_date;
-
- //$amount_date =
+ $amount = intval($transaction->transaction_creation->amount);
} else if($transaction->transaction_type_id == 2) { // transfer
-
$temp = $transaction->transaction_send_coin;
$amount = intval($temp->amount);
// reverse if sender
if($stateUserId == $temp->state_user_id) {
$amount *= -1.0;
}
- $amount_date = $transaction->received;
-
}
+ $amount_date = $transaction->received;
if($i == 0) {
$balance_cursor->amount = $amount;
} else {
- $balance_cursor->amount = $balance_cursor->partDecay($amount_date) + $amount;
+
+ //$balance_cursor->amount = $balance_cursor->partDecay($amount_date) + $amount;
+ $balance_cursor->amount =
+ $this->calculateDecay($balance_cursor->amount, $balance_cursor->record_date, $amount_date)
+ + $amount;
}
//echo "new balance: " . $balance_cursor->amount . "
";
@@ -261,7 +308,7 @@ class StateBalancesTable extends Table
return ['success' => false, 'error' => 'error saving state balance', 'details' => $state_balance->getErrors()];
}
}
- return true;
+ return ['success' => true];
}
diff --git a/community_server/src/Model/Table/StateUserTransactionsTable.php b/community_server/src/Model/Table/StateUserTransactionsTable.php
index 6cfe94a23..2c984d964 100644
--- a/community_server/src/Model/Table/StateUserTransactionsTable.php
+++ b/community_server/src/Model/Table/StateUserTransactionsTable.php
@@ -22,7 +22,7 @@ use Cake\Validation\Validator;
* @method \App\Model\Entity\StateUserTransaction[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\StateUserTransaction findOrCreate($search, callable $callback = null, $options = [])
*/
-class StateUserTransactionsTable extends Table
+class StateUserTransactionsTable extends AppTable
{
/**
* Initialize method
diff --git a/community_server/src/Model/Table/TransactionTypesTable.php b/community_server/src/Model/Table/TransactionTypesTable.php
index 5cc842f5e..2ffd1e64c 100644
--- a/community_server/src/Model/Table/TransactionTypesTable.php
+++ b/community_server/src/Model/Table/TransactionTypesTable.php
@@ -20,7 +20,7 @@ use Cake\Validation\Validator;
* @method \App\Model\Entity\TransactionType[] patchEntities($entities, array $data, array $options = [])
* @method \App\Model\Entity\TransactionType findOrCreate($search, callable $callback = null, $options = [])
*/
-class TransactionTypesTable extends Table
+class TransactionTypesTable extends AppTable
{
/**
* Initialize method
@@ -55,7 +55,7 @@ class TransactionTypesTable extends Table
$validator
->scalar('name')
- ->maxLength('name', 24)
+ ->maxLength('name', 45)
->requirePresence('name', 'create')
->notEmptyString('name');
@@ -66,4 +66,55 @@ class TransactionTypesTable extends Table
return $validator;
}
+
+ public function fillWithDefault()
+ {
+
+ $entry_contents = [
+ [
+ 'id' => 1,
+ 'name' => 'creation',
+ 'text' => 'create new gradidos for member and also for group (in development)',
+ ], [
+ 'id' => 2,
+ 'name' => 'transfer',
+ 'text' => 'send gradidos from one member to another, also cross group transfer',
+ ], [
+ 'id' => 3,
+ 'name' => 'group create',
+ 'text' => 'create a new group, trigger creation of new hedera topic and new blockchain on node server'
+ ], [
+ 'id' => 4,
+ 'name' => 'group add member',
+ 'text' => 'add user to a group or move if he was already in a group'
+ ], [
+ 'id' => 5,
+ 'name' => 'group remove member',
+ 'text' => 'remove user from group, maybe he was moved elsewhere'
+ ],[
+ 'id' => 6,
+ 'name' => 'hedera topic create',
+ 'text' => 'create new topic on hedera'
+ ],[
+ 'id' => 7,
+ 'name' => 'hedera topic send message',
+ 'text' => 'send consensus message over hedera topic'
+ ],[
+ 'id' => 8,
+ 'name' => 'hedera account create',
+ 'text' => 'create new account on hedera for holding some founds with unencrypted keys'
+ ],[
+ 'id' => 9,
+ 'name' => 'decay start',
+ 'text' => 'signalize the starting point for decay calculation, allowed only once per chain'
+ ]
+ ];
+ $entities = $this->newEntities($entry_contents);
+ $this->truncate();
+ $save_results = $this->saveManyWithErrors($entities);
+ if(!$save_results['success']) {
+ $save_results['msg'] = 'error by saving default transaction types';
+ }
+ return $save_results;
+ }
}
diff --git a/community_server/src/Model/Table/TransactionsTable.php b/community_server/src/Model/Table/TransactionsTable.php
index 2ff39f469..742ce7882 100644
--- a/community_server/src/Model/Table/TransactionsTable.php
+++ b/community_server/src/Model/Table/TransactionsTable.php
@@ -1,12 +1,12 @@
0 && $decay == true)
- {
+ $prev = null;
+ if($i > 0 ) {
$prev = $stateUserTransactions[$i-1];
+ }
+ if($prev && $decay == true)
+ {
+
if($prev->balance > 0) {
// var_dump($stateUserTransactions);
$current = $su_transaction;
//echo "decay between " . $prev->transaction_id . " and " . $current->transaction_id . "
";
- $interval = $current->balance_date->diff($prev->balance_date);
- $state_balance->amount = $prev->balance;
- $state_balance->record_date = $prev->balance_date;
- $diff_amount = $state_balance->partDecay($current->balance_date);
- $balance = floatval($prev->balance - $diff_amount);
+ $calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
+ $balance = floatval($prev->balance - $calculated_decay['balance']);
// skip small decays (smaller than 0,00 GDD)
if(abs($balance) >= 100) {
@@ -192,7 +193,7 @@ class TransactionsTable extends Table
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
- 'decay_duration' => $interval->format('%a days, %H hours, %I minutes, %S seconds'),
+ 'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds'),
'memo' => ''
];
}
@@ -211,12 +212,16 @@ class TransactionsTable extends Table
echo "
";*/
if($su_transaction->transaction_type_id == 1) { // creation
$creation = $transaction->transaction_creation;
+ $balance = $stateBalancesTable->calculateDecay($creation->amount, $creation->target_date, $transaction->received);
+
$final_transactions[] = [
'name' => 'Gradido Akademie',
'type' => 'creation',
'transaction_id' => $transaction->id,
- 'date' => $creation->target_date,
- 'balance' => $creation->amount,
+ 'date' => $transaction->received,// $creation->target_date,
+ 'target_date' => $creation->target_date,
+ 'creation_amount' => $creation->amount,
+ 'balance' => $balance,
'memo' => $transaction->memo
];
} else if($su_transaction->transaction_type_id == 2) { // transfer or send coins
@@ -256,14 +261,20 @@ class TransactionsTable extends Table
}
if($i == $stateUserTransactionsCount-1 && $decay == true) {
- $state_balance->amount = $su_transaction->balance;
- $state_balance->record_date = $su_transaction->balance_date;
- $balance = floatval($su_transaction->balance - $state_balance->decay);
+ $calculated_decay = $stateBalancesTable->calculateDecay(
+ $su_transaction->balance,
+ $su_transaction->balance_date, new FrozenTime(), true);
+ $decay_start_date = $stateBalancesTable->getDecayStartDateCached();
+ $duration = $su_transaction->balance_date->timeAgoInWords();
+ if($decay_start_date > $su_transaction->balance_date) {
+ $duration = $decay_start_date->timeAgoInWords();
+ }
+ $balance = floatval($su_transaction->balance - $calculated_decay['balance']);
if($balance > 100) {
$final_transactions[] = [
'type' => 'decay',
'balance' => $balance,
- 'decay_duration' => $su_transaction->balance_date->timeAgoInWords(),
+ 'decay_duration' => $duration,
'memo' => ''
];
}
@@ -273,4 +284,166 @@ class TransactionsTable extends Table
return $final_transactions;
}
+
+ public function updateTxHash($transaction, $signatureMapString)
+ {
+ $transaction_id = $transaction->id;
+ $previousTxHash = null;
+ if($transaction_id > 1) {
+ try {
+ $previousTransaction = $this
+ ->find('all', ['contain' => false])
+ ->select(['tx_hash'])
+ ->where(['id' => $transaction_id - 1])
+ ->first();
+ /*$previousTransaction = $transactionsTable->get($this->mTransactionID - 1, [
+ 'contain' => false,
+ 'fields' => ['tx_hash']
+ ]);*/
+ } catch(Cake\Datasource\Exception\RecordNotFoundException $ex) {
+ return ['state' => 'error', 'msg' => 'previous transaction not found', 'details' => $ex->getMessage()];
+ }
+ if(!$previousTransaction) {
+ // shouldn't occur
+ return ['state' => 'error', 'msg' => 'previous transaction not found'];
+ }
+ $previousTxHash = $previousTransaction->tx_hash;
+ }
+ try {
+ //$transactionEntity->received = $transactionsTable->get($transactionEntity->id, ['contain' => false, 'fields' => ['received']])->received;
+ $transaction->received = $this
+ ->find('all', ['contain' => false])
+ ->where(['id' => $transaction->id])
+ ->select(['received'])->first()->received;
+ } catch(Cake\Datasource\Exception\RecordNotFoundException $ex) {
+ return ['state' => 'error', 'msg' => 'current transaction not found in db', 'details' => $ex->getMessage()];
+ }
+
+ // calculate tx hash
+ // previous tx hash + id + received + sigMap as string
+ // Sodium use for the generichash function BLAKE2b today (11.11.2019), mabye change in the future
+ $state = \Sodium\crypto_generichash_init();
+ //echo "prev hash: $previousTxHash\n";
+ if($previousTxHash != null) {
+ \Sodium\crypto_generichash_update($state, stream_get_contents($previousTxHash));
+ }
+ //echo "id: " . $transactionEntity->id . "\n";
+ \Sodium\crypto_generichash_update($state, strval($transaction->id));
+ //echo "received: " . $transactionEntity->received;
+ \Sodium\crypto_generichash_update($state, $transaction->received->i18nFormat('yyyy-MM-dd HH:mm:ss'));
+ \Sodium\crypto_generichash_update($state, $signatureMapString);
+ $transaction->tx_hash = \Sodium\crypto_generichash_final($state);
+ if ($this->save($transaction)) {
+ return true;
+ }
+ return ['state' => 'error', 'msg' => 'error by saving transaction', 'details' => $transaction->getErrors()];
+ }
+
+ /*!
+ * @return: false if no decay start block found
+ * @return: DateTime Object with start date if one start block found
+ * @return: ['state':'error'] if more than one found
+ */
+ public function getDecayStartDate()
+ {
+ $transaction = $this->find()->where(['transaction_type_id' => 9])->select(['received'])->order(['received' => 'ASC']);
+ if($transaction->count() == 0) {
+ return null;
+ }
+ return $transaction->first()->received;
+ }
+
+ public function fillStateUserTransactions()
+ {
+ $missing_transaction_ids = [];
+ $transaction_ids = $this
+ ->find('all')
+ ->select(['id', 'transaction_type_id'])
+ ->order(['id'])
+ ->where(['transaction_type_id <' => 6])
+ ->all()
+ ;
+ $state_user_transaction_ids = $this->StateUserTransactions
+ ->find('all')
+ ->select(['transaction_id'])
+ ->group(['transaction_id'])
+ ->order(['transaction_id'])
+ ->toArray()
+ ;
+ $i2 = 0;
+ $count = count($state_user_transaction_ids);
+ foreach($transaction_ids as $tr_id) {
+ //echo "$i1: ";
+ if($i2 >= $count) {
+ $missing_transaction_ids[] = $tr_id;
+ //echo "adding to missing: $tr_id, continue
";
+ continue;
+ }
+ $stu_id = $state_user_transaction_ids[$i2];
+ if($tr_id->id == $stu_id->transaction_id) {
+ $i2++;
+ //echo "after i2++: $i2
";
+ } else if($tr_id->id < $stu_id->transaction_id) {
+ $missing_transaction_ids[] = $tr_id;
+ //echo "adding to missing: $tr_id
";
+ }
+ }
+
+
+ $tablesForType = [
+ 1 => $this->TransactionCreations,
+ 2 => $this->TransactionSendCoins,
+ 3 => $this->TransactionGroupCreates,
+ 4 => $this->TransactionGroupAddaddress,
+ 5 => $this->TransactionGroupAddaddress
+ ];
+ $idsForType = [];
+ foreach($missing_transaction_ids as $i => $transaction) {
+ if(!isset($idsForType[$transaction->transaction_type_id])) {
+ $idsForType[$transaction->transaction_type_id] = [];
+ }
+ $idsForType[$transaction->transaction_type_id][] = $transaction->id;
+ }
+ $entities = [];
+ $state_user_ids = [];
+ foreach($idsForType as $type_id => $transaction_ids) {
+ $specific_transactions = $tablesForType[$type_id]->find('all')->where(['transaction_id IN' => $transaction_ids])->toArray();
+ $keys = $tablesForType[$type_id]->getSchema()->columns();
+ //var_dump($keys);
+ foreach($specific_transactions as $specific) {
+
+ foreach($keys as $key) {
+ if(preg_match('/_user_id/', $key)) {
+ $entity = $this->StateUserTransactions->newEntity();
+ $entity->transaction_id = $specific['transaction_id'];
+ $entity->transaction_type_id = $type_id;
+ $entity->state_user_id = $specific[$key];
+ if(!in_array($entity->state_user_id, $state_user_ids)) {
+ array_push($state_user_ids, $entity->state_user_id);
+ }
+ $entities[] = $entity;
+ }
+ }
+ }
+ }
+ //var_dump($entities);
+ $stateUsersTable = TableRegistry::getTableLocator()->get('StateUsers');
+ $existingStateUsers = $stateUsersTable->find('all')->select(['id'])->where(['id IN' => $state_user_ids])->order(['id'])->all();
+ $existing_state_user_ids = [];
+ $finalEntities = [];
+ foreach($existingStateUsers as $stateUser) {
+ $existing_state_user_ids[] = $stateUser->id;
+ }
+ foreach($entities as $entity) {
+ if(in_array($entity->state_user_id, $existing_state_user_ids)) {
+ array_push($finalEntities, $entity);
+ }
+ }
+
+ $save_results = $this->StateUserTransactions->saveManyWithErrors($finalEntities);
+ if(!$save_results['success']) {
+ $save_results['msg'] = 'error by saving at least one state user transaction';
+ }
+ return $save_results;
+ }
}
diff --git a/community_server/src/Model/Transactions/TransactionBase.php b/community_server/src/Model/Transactions/TransactionBase.php
index a688daf02..1f80cc687 100644
--- a/community_server/src/Model/Transactions/TransactionBase.php
+++ b/community_server/src/Model/Transactions/TransactionBase.php
@@ -72,9 +72,11 @@ class TransactionBase {
//debug($stateBalanceQuery);
if($stateBalanceQuery->count() > 0) {
+
$stateBalanceEntry = $stateBalanceQuery->first();
- $stateBalanceEntry->amount = $stateBalanceEntry->partDecay($recordDate) + $addAmountCent;
- $stateBalanceEntry->amount += $addAmountCent;
+ $stateBalanceEntry->amount =
+ $stateBalancesTable->calculateDecay($stateBalanceEntry->amount, $stateBalanceEntry->record_date, $recordDate)
+ + $addAmountCent;
} else {
$stateBalanceEntry = $stateBalancesTable->newEntity();
$stateBalanceEntry->state_user_id = $stateUserId;
@@ -93,11 +95,12 @@ class TransactionBase {
protected function addStateUserTransaction($stateUserId, $transactionId, $transactionTypeId, $balance, $balance_date) {
$stateUserTransactionTable = self::getTable('state_user_transactions');
+
$stateUserTransactions = $stateUserTransactionTable
->find('all')
->where(['state_user_id' => $stateUserId])
->order(['transaction_id DESC']);
-
+ $new_balance = $balance;
if($stateUserTransactions->count() > 0) {
$stateBalanceTable = self::getTable('state_balances');
$state_user_transaction = $stateUserTransactions->first();
@@ -105,16 +108,17 @@ class TransactionBase {
$this->addError('TransactionBase::addStateUserTransaction', 'state_user_transaction is zero, no first entry exist?');
return false;
}
- $balance_entity = $stateBalanceTable->newEntity();
- $balance_entity->amount = $state_user_transaction->balance;
- $balance_entity->record_date = $state_user_transaction->balance_date;
- $balance = $balance_entity->decay + $balance;
+ $new_balance += $stateBalanceTable->calculateDecay(
+ $state_user_transaction->balance,
+ $state_user_transaction->balance_date,
+ $balance_date
+ );
}
$entity = $stateUserTransactionTable->newEntity();
$entity->state_user_id = $stateUserId;
$entity->transaction_id = $transactionId;
$entity->transaction_type_id = $transactionTypeId;
- $entity->balance = $balance;
+ $entity->balance = $new_balance;
$entity->balance_date = $balance_date;
if(!$stateUserTransactionTable->save($entity)) {
diff --git a/community_server/src/Model/Transactions/TransactionCreation.php b/community_server/src/Model/Transactions/TransactionCreation.php
index 6aeddd955..63b886a3f 100644
--- a/community_server/src/Model/Transactions/TransactionCreation.php
+++ b/community_server/src/Model/Transactions/TransactionCreation.php
@@ -138,8 +138,9 @@ class TransactionCreation extends TransactionBase {
return true;
}
- public function save($transaction_id, $firstPublic)
+ public function save($transaction_id, $firstPublic, $received)
{
+ $stateBalancesTable = self::getTable('stateBalances');
$transactionCreationEntity = $this->transactionCreationsTable->newEntity();
$transactionCreationEntity->transaction_id = $transaction_id;
@@ -151,23 +152,27 @@ class TransactionCreation extends TransactionBase {
$this->addError('TransactionCreation::save', 'couldn\'t get state user id');
return false;
}
+
$transactionCreationEntity->state_user_id = $receiverUserId;
$transactionCreationEntity->amount = $this->getAmount();
$transactionCreationEntity->target_date = $this->protoTransactionCreation->getTargetDate()->getSeconds();
$target_date = new FrozenTime($transactionCreationEntity->target_date);
+
+ $decayed_balance = $stateBalancesTable->calculateDecay($this->getAmount(), $target_date, $received);
+
if(!$this->transactionCreationsTable->save($transactionCreationEntity)) {
$this->addError('TransactionCreation::save', 'error saving transactionCreation with errors: ' . json_encode($transactionCreationEntity->getErrors()));
return false;
}
// update state balance
- $final_balance = $this->updateStateBalance($receiverUserId, $this->getAmount(), $target_date);
+ $final_balance = $this->updateStateBalance($receiverUserId, $decayed_balance, $received);
if(false === $final_balance) {
return false;
}
// decay is a virtual field which is calculated from amount and now() - record_date
- if(!$this->addStateUserTransaction($receiverUserId, $transaction_id, 1, $this->getAmount(), $target_date)) {
+ if(!$this->addStateUserTransaction($receiverUserId, $transaction_id, 1, $decayed_balance, $received)) {
return false;
}
diff --git a/community_server/src/Template/AppRequests/list_transactions.ctp b/community_server/src/Template/AppRequests/list_transactions.ctp
index f829b5f16..28a76f2be 100644
--- a/community_server/src/Template/AppRequests/list_transactions.ctp
+++ b/community_server/src/Template/AppRequests/list_transactions.ctp
@@ -12,6 +12,9 @@ $body['gdtSum'] = $this->element('centToFloat', ['cent' => $body['gdtSum'], 'pre
foreach($body['transactions'] as $i => $transaction) {
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4]);
+ if(isset($transaction['creation_amount'])) {
+ $body['transactions'][$i]['creation_amount'] = $this->element('centToFloat', ['cent' => $transaction['creation_amount'], 'precision' => 4]);
+ }
}
?>= json_encode($body) ?>
\ No newline at end of file
diff --git a/community_server/src/Template/Migrations/add.ctp b/community_server/src/Template/Migrations/add.ctp
new file mode 100644
index 000000000..94a555733
--- /dev/null
+++ b/community_server/src/Template/Migrations/add.ctp
@@ -0,0 +1,23 @@
+
+
+
| = $this->Paginator->sort('id') ?> | += $this->Paginator->sort('db_version') ?> | += __('Actions') ?> | +
|---|---|---|
| = $this->Number->format($migration->id) ?> | += $this->Number->format($migration->db_version) ?> | ++ = $this->Html->link(__('View'), ['action' => 'view', $migration->id]) ?> + = $this->Html->link(__('Edit'), ['action' => 'edit', $migration->id]) ?> + = $this->Form->postLink(__('Delete'), ['action' => 'delete', $migration->id], ['confirm' => __('Are you sure you want to delete # {0}?', $migration->id)]) ?> + | +
= $this->Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?>
+Migrate from Version = $db_version ?>
+ += json_encode($result) ?>
+ += $this->Html->link('Back to Dashboard', ['controller' => 'Dashboard', 'action' => 'index']) ?>
+ diff --git a/community_server/src/Template/Migrations/view.ctp b/community_server/src/Template/Migrations/view.ctp new file mode 100644 index 000000000..dc4c5ded3 --- /dev/null +++ b/community_server/src/Template/Migrations/view.ctp @@ -0,0 +1,28 @@ + + +| = __('Id') ?> | += $this->Number->format($migration->id) ?> | +
|---|---|
| = __('Db Version') ?> | += $this->Number->format($migration->db_version) ?> | +
+
+