From bd6640388d623baa1d1d57e1b9867e5331b83119 Mon Sep 17 00:00:00 2001 From: Dario Rekowski on RockPI Date: Fri, 6 Nov 2020 14:10:38 +0000 Subject: [PATCH] finish parsing transactions from gradido node --- skeema/gradido_community/address_types.sql | 7 + .../insert/address_types.sql | 2 + .../insert/insert_transaction_types.sql | 15 +- .../state_group_addresses.sql | 3 +- .../transaction_group_addaddress.sql | 1 + .../transaction_send_coins.sql | 5 +- src/Controller/AddressTypesController.php | 5 +- .../JsonRpcRequestClientComponent.php | 13 +- src/Controller/ErrorController.php | 2 +- .../JsonRequestHandlerController.php | 41 ++ src/Controller/StateBalancesController.php | 8 +- src/Model/Entity/AddressType.php | 4 +- .../Entity/TransactionGroupAddaddres.php | 1 + src/Model/Table/AddressTypesTable.php | 11 +- src/Model/Transactions/Record.php | 471 ++++++++++++++++++ src/Template/AddressTypes/add.ctp | 4 +- src/Template/AddressTypes/edit.ctp | 4 +- src/Template/AddressTypes/index.ctp | 8 +- src/Template/AddressTypes/view.ctp | 10 +- src/Template/StateBalances/overview.ctp | 4 +- tests/Fixture/AddressTypesFixture.php | 12 +- 21 files changed, 586 insertions(+), 45 deletions(-) create mode 100644 skeema/gradido_community/address_types.sql create mode 100644 skeema/gradido_community/insert/address_types.sql create mode 100644 src/Model/Transactions/Record.php diff --git a/skeema/gradido_community/address_types.sql b/skeema/gradido_community/address_types.sql new file mode 100644 index 000000000..cb5b1b943 --- /dev/null +++ b/skeema/gradido_community/address_types.sql @@ -0,0 +1,7 @@ +CREATE TABLE `address_types` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) 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/skeema/gradido_community/insert/address_types.sql b/skeema/gradido_community/insert/address_types.sql new file mode 100644 index 000000000..2a6dc00a5 --- /dev/null +++ b/skeema/gradido_community/insert/address_types.sql @@ -0,0 +1,2 @@ +INSERT INTO `address_types` (`id`, `name`, `text`) VALUES +(1, 'user main', 'user main address'); diff --git a/skeema/gradido_community/insert/insert_transaction_types.sql b/skeema/gradido_community/insert/insert_transaction_types.sql index 7cc2ebfdd..02ef8374a 100644 --- a/skeema/gradido_community/insert/insert_transaction_types.sql +++ b/skeema/gradido_community/insert/insert_transaction_types.sql @@ -1,9 +1,10 @@ INSERT INTO `transaction_types` (`id`, `name`, `text`) VALUES -(1, 'group create', 'create a new group, trigger creation of new hedera topic and new blockchain on node server'), -(2, 'group add member', 'add user to a group or move if he was already in a group'), -(3, 'creation', 'create new gradidos for member and also for group (in development)'), -(4, 'transfer', 'send gradidos from one member to another, also cross group transfer'), -(5, 'hedera topic create', 'create new topic on hedera'), -(6, 'hedera topic send message', 'send consensus message over hedera topic'), -(7, 'hedera account create', 'create new account on hedera for holding some founds with unencrypted keys'); +(1, 'creation', 'create new gradidos for member and also for group (in development)'), +(2, 'transfer', 'send gradidos from one member to another, also cross group transfer'), +(3, 'group create', 'create a new group, trigger creation of new hedera topic and new blockchain on node server'), +(4, 'group add member', 'add user to a group or move if he was already in a group'), +(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'); diff --git a/skeema/gradido_community/state_group_addresses.sql b/skeema/gradido_community/state_group_addresses.sql index 1d256f3e0..698a79b98 100644 --- a/skeema/gradido_community/state_group_addresses.sql +++ b/skeema/gradido_community/state_group_addresses.sql @@ -3,5 +3,6 @@ CREATE TABLE `state_group_addresses` ( `group_id` int(10) unsigned NOT NULL, `public_key` binary(32) NOT NULL, `address_type_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + UNIQUE(`public_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/skeema/gradido_community/transaction_group_addaddress.sql b/skeema/gradido_community/transaction_group_addaddress.sql index dceac273c..b9ca95651 100644 --- a/skeema/gradido_community/transaction_group_addaddress.sql +++ b/skeema/gradido_community/transaction_group_addaddress.sql @@ -2,6 +2,7 @@ CREATE TABLE `transaction_group_addaddress` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `transaction_id` int(10) unsigned NOT NULL, `address_type_id` int(10) unsigned NOT NULL, + `remove_from_group` BOOLEAN DEFAULT FALSE, `public_key` binary(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/skeema/gradido_community/transaction_send_coins.sql b/skeema/gradido_community/transaction_send_coins.sql index f57a2175e..49423fdbc 100644 --- a/skeema/gradido_community/transaction_send_coins.sql +++ b/skeema/gradido_community/transaction_send_coins.sql @@ -1,9 +1,10 @@ CREATE TABLE `transaction_send_coins` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `transaction_id` int(10) unsigned NOT NULL, - `state_user_id` int(10) unsigned NOT NULL, + `sender_public_key` binary(32) NOT NULL, + `state_user_id` int(10) unsigned DEFAULT 0, `receiver_public_key` binary(32) NOT NULL, - `receiver_user_id` int(10) unsigned NOT NULL, + `receiver_user_id` int(10) unsigned DEFAULT 0, `amount` bigint(20) NOT NULL, `sender_final_balance` bigint(20) NOT NULL, PRIMARY KEY (`id`) diff --git a/src/Controller/AddressTypesController.php b/src/Controller/AddressTypesController.php index 348a8e9e6..9b13a7720 100644 --- a/src/Controller/AddressTypesController.php +++ b/src/Controller/AddressTypesController.php @@ -2,7 +2,6 @@ namespace App\Controller; use App\Controller\AppController; -use Cake\I18n\Number; /** * AddressTypes Controller @@ -35,7 +34,7 @@ class AddressTypesController extends AppController public function view($id = null) { $addressType = $this->AddressTypes->get($id, [ - 'contain' => ['StateGroupAddresses', 'TransactionGroupAddaddress'] + 'contain' => ['StateGroupAddresses', 'TransactionGroupAddaddress'], ]); $this->set('addressType', $addressType); @@ -71,7 +70,7 @@ class AddressTypesController extends AppController public function edit($id = null) { $addressType = $this->AddressTypes->get($id, [ - 'contain' => [] + 'contain' => [], ]); if ($this->request->is(['patch', 'post', 'put'])) { $addressType = $this->AddressTypes->patchEntity($addressType, $this->request->getData()); diff --git a/src/Controller/Component/JsonRpcRequestClientComponent.php b/src/Controller/Component/JsonRpcRequestClientComponent.php index d5ca72808..980a64e8d 100644 --- a/src/Controller/Component/JsonRpcRequestClientComponent.php +++ b/src/Controller/Component/JsonRpcRequestClientComponent.php @@ -41,7 +41,11 @@ class JsonRpcRequestClientComponent extends Component public function sendRequest($message) { $http = new Client(); - $response = $http->post($this->getGradidoNodeUrl(), $message, ['type' => 'json']); + try { + $response = $http->post($this->pickGradidoNodeUrl(), $message, ['type' => 'json']); + } catch(Exception $e) { + return ['state' => 'error', 'type' => 'http exception', 'details' => $e->getMessage()]; + } $responseStatus = $response->getStatusCode(); if($responseStatus != 200) { return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response status code isn\'t 200', 'details' => $responseStatus]; @@ -59,10 +63,11 @@ class JsonRpcRequestClientComponent extends Component //return ['state' => 'success', 'data' => $json]; } - static public function getGradidoNodeUrl() + static public function pickGradidoNodeUrl() { - $gradidoNode = Configure::read('GradidoNode'); - return $gradidoNode['host'] . ':' . $gradidoNode['port']; + $gradidoNodes = Configure::read('GradidoNode'); + $i = rand(0, count($gradidoNodes)-1); + return $gradidoNodes[$i]['host'] . ':' . $gradidoNodes[$i]['port']; } diff --git a/src/Controller/ErrorController.php b/src/Controller/ErrorController.php index 43bd2fb52..c5421a24d 100644 --- a/src/Controller/ErrorController.php +++ b/src/Controller/ErrorController.php @@ -54,7 +54,7 @@ class ErrorController extends AppController public function beforeRender(Event $event) { parent::beforeRender($event); - + $this->RequestHandler->renderAs($this, 'json'); $this->viewBuilder()->setTemplatePath('Error'); } diff --git a/src/Controller/JsonRequestHandlerController.php b/src/Controller/JsonRequestHandlerController.php index 84a9c4b92..928559d7b 100644 --- a/src/Controller/JsonRequestHandlerController.php +++ b/src/Controller/JsonRequestHandlerController.php @@ -10,6 +10,7 @@ use Cake\Core\Configure; use Model\Transactions\TransactionTransfer; use Model\Transactions\Transaction; +use Model\Transactions\Record; /*! * @author: Dario Rekowski# * @@ -24,6 +25,7 @@ class JsonRequestHandlerController extends AppController { { parent::initialize(); $this->loadComponent('JsonRequestClient'); + $this->loadComponent('JsonRpcRequestClient'); //$this->Auth->allow(['add', 'edit']); $this->Auth->allow('index'); } @@ -69,6 +71,45 @@ class JsonRequestHandlerController extends AppController { // Called from login server like a cron job every 10 minutes or after sending transaction to hedera private function updateReadNode() { + $this->autoRender = false; + $response = $this->response->withType('application/json'); + + $transactionsTable = TableRegistry::getTableLocator()->get('Transactions'); + $last_transaction = $transactionsTable->find('all')->order(['id' => 'DESC'])->first(); + $group_alias = Configure::read('GroupAlias'); + $result = $this->JsonRpcRequestClient->request('getTransactions', ['groupAlias' => $group_alias, 'lastKnownSequenceNumber' => $last_transaction->id]); + if(isset($result['state']) && $result['state'] == 'error') { + return $this->returnJson(['state' => 'error', 'msg' => 'jsonrpc error', 'details' => $result]); + } + $part_count = -1; + $temp_record = new Record; + $errors = []; + foreach($result as $_record) { + $parse_result = $temp_record->parseRecord($_record); + if($parse_result == true) { + $sequenceNumber = $temp_record->getSequenceNumber(); + if($part_count == -1) { + $part_count = $temp_record->getPartCount(); + } + $part_count--; + + if($part_count == 0) { + $finalize_result = $temp_record->finalize(); + if($finalize_result != true) { + $errors[] = ['msg' => 'error in finalize', 'record' => $_record, 'details' => $finalize_result, 'sequenceNumber' => $sequenceNumber]; + } + $temp_record = new Record; + $part_count = -1; + } + } else { + $temp_record = new Record; + $part_count = -1; + $errors[] = ['msg' => 'error in parse record', 'record' => $_record, 'details' => $parse_result]; + } + } + if(count($errors)) { + return $this->returnJson(['state' => 'error', 'msg' => 'error in parsing records', 'details' => $errors]); + } return $this->returnJson(['state' => 'success']); } diff --git a/src/Controller/StateBalancesController.php b/src/Controller/StateBalancesController.php index bddf35125..2436d83d3 100644 --- a/src/Controller/StateBalancesController.php +++ b/src/Controller/StateBalancesController.php @@ -170,17 +170,22 @@ class StateBalancesController extends AppController foreach ($transferTransactions as $sendCoins) { $type = ''; $otherUser = null; + $other_user_public = ''; if ($sendCoins->state_user_id == $user['id']) { $type = 'send'; if(isset($involvedUserIndices[$sendCoins->receiver_user_id])) { $otherUser = $involvedUserIndices[$sendCoins->receiver_user_id]; } + $other_user_public = bin2hex(stream_get_contents($sendCoins->receiver_public_key)); } else if ($sendCoins->receiver_user_id == $user['id']) { $type = 'receive'; if(isset($involvedUserIndices[$sendCoins->state_user_id])) { $otherUser = $involvedUserIndices[$sendCoins->state_user_id]; } + if($sendCoins->sender_public_key) { + $other_user_public = bin2hex(stream_get_contents($sendCoins->sender_public_key)); + } } if(null == $otherUser) { $otherUser = $this->StateBalances->StateUsers->newEntity(); @@ -192,7 +197,8 @@ class StateBalancesController extends AppController 'transaction_id' => $sendCoins->transaction_id, 'date' => $sendCoins->transaction->received, 'balance' => $sendCoins->amount, - 'memo' => $sendCoins->transaction->memo + 'memo' => $sendCoins->transaction->memo, + 'pubkey' => $other_user_public ]); } uasort($transactions, array($this, 'sortTransactions')); diff --git a/src/Model/Entity/AddressType.php b/src/Model/Entity/AddressType.php index 08dbba281..87d64d61a 100644 --- a/src/Model/Entity/AddressType.php +++ b/src/Model/Entity/AddressType.php @@ -8,7 +8,7 @@ use Cake\ORM\Entity; * * @property int $id * @property string $name - * @property string|null $text + * @property string $text * * @property \App\Model\Entity\StateGroupAddress[] $state_group_addresses * @property \App\Model\Entity\TransactionGroupAddaddres[] $transaction_group_addaddress @@ -28,6 +28,6 @@ class AddressType extends Entity 'name' => true, 'text' => true, 'state_group_addresses' => true, - 'transaction_group_addaddress' => true + 'transaction_group_addaddress' => true, ]; } diff --git a/src/Model/Entity/TransactionGroupAddaddres.php b/src/Model/Entity/TransactionGroupAddaddres.php index 3cb73b9ee..0559a6302 100644 --- a/src/Model/Entity/TransactionGroupAddaddres.php +++ b/src/Model/Entity/TransactionGroupAddaddres.php @@ -28,6 +28,7 @@ class TransactionGroupAddaddres extends Entity protected $_accessible = [ 'transaction_id' => true, 'address_type_id' => true, + 'remove_from_group' => true, 'public_key' => true, 'transaction' => true, 'address_type' => true diff --git a/src/Model/Table/AddressTypesTable.php b/src/Model/Table/AddressTypesTable.php index ea0c86d6e..ba94c99c7 100644 --- a/src/Model/Table/AddressTypesTable.php +++ b/src/Model/Table/AddressTypesTable.php @@ -38,10 +38,10 @@ class AddressTypesTable extends Table $this->setPrimaryKey('id'); $this->hasMany('StateGroupAddresses', [ - 'foreignKey' => 'address_type_id' + 'foreignKey' => 'address_type_id', ]); $this->hasMany('TransactionGroupAddaddress', [ - 'foreignKey' => 'address_type_id' + 'foreignKey' => 'address_type_id', ]); } @@ -54,19 +54,20 @@ class AddressTypesTable extends Table public function validationDefault(Validator $validator) { $validator - ->integer('id') + ->nonNegativeInteger('id') ->allowEmptyString('id', null, 'create'); $validator ->scalar('name') - ->maxLength('name', 25) + ->maxLength('name', 45) ->requirePresence('name', 'create') ->notEmptyString('name'); $validator ->scalar('text') ->maxLength('text', 255) - ->allowEmptyString('text'); + ->requirePresence('text', 'create') + ->notEmptyString('text'); return $validator; } diff --git a/src/Model/Transactions/Record.php b/src/Model/Transactions/Record.php new file mode 100644 index 000000000..da52db8ac --- /dev/null +++ b/src/Model/Transactions/Record.php @@ -0,0 +1,471 @@ +signature = $signature; + $this->publicKey = $pubkey; + } + + public function finalize($transactionId) + { + $signaturesTable = TableRegistry::getTableLocator()->get('TransactionSignatures'); + $entity = $signaturesTable->newEntity(); + $entity->transaction_id = $transactionId; + if(count($this->signature) != 128) { + return ['state' => 'error', 'msg' => 'invalid signature size', 'details' => count($this->signature)]; + } + if(count($this->publicKey) != 64) { + return ['state' => 'error', 'msg' => 'invalid pubkey size', 'details' => count($this->publicKey)]; + } + if(!preg_match('/^[0-9a-fA-F]*$/', $this->signature)) { + return ['state' => 'error', 'msg' => 'signature isn\'t in hex format']; + } + if(!preg_match('/^[0-9a-fA-F]*$/', $this->publicKey)) { + return ['state' => 'error', 'msg' => 'publicKey isn\'t in hex format']; + } + $entity->signature = hex2bin($this->signature); + $entity->pubkey = hex2bin($this->publicKey); + + if(!$signaturesTable->save($entity)) { + return ['state' => 'error', 'msg' => 'error saving signature', 'details' => $entity->getErrors()]; + } + return true; + } +} + + +class ManageNodeGroupAdd +{ + /* + "add_user": { + "user\": " << user << ", + }, + OR + + "move_user_inbound|move_user_outbound": { + "user": " << user << ", + "other_group": " << other_group << ", + "paired_transaction_id": { + "seconds": << ts.seconds <<, + "nanos": << ts.nanos + } + }, + + */ + + private $user_pubkey; + private $other_group = ''; + private $remove_from_group = false; + + public function __construct($data) + { + $this->user_pubkey = $data['user']; + if(isset($data['other_group'])) { + $this->other_group = $data['other_group']; + } + } + + public function finalize($transactionId, $received) + { + $transactionGroupAddadressTable = TableRegistry::getTableLocator()->get('TransactionGroupAddaddress'); + $stateGroupAddresses = TableRegistry::getTableLocator()->get('StateGroupAddresses'); + $transactionGroupEntity = $transactionGroupAddadressTable->newEntity(); + $transactionGroupEntity->transaction_id = $transactionId; + if(count($this->user_pubkey) != 64) { + return ['state' => 'error', 'msg' => 'invalid size user pubkey', 'details' => count($this->user_pubkey)]; + } + if(!preg_match('/^[0-9a-fA-F]*$/', $this->user_pubkey)) { + return ['state' => 'error', 'msg' => 'user_pubkey isn\'t in hex format']; + } + + $transactionGroupEntity->public_key = hex2bin($this->user_pubkey); + $transactionGroupEntity->remove_from_group = $this->remove_from_group; + if(!$transactionGroupAddadressTable->save($transactionGroupEntity)) { + return ['state' => 'error', 'msg' => 'error saving TransactionGroupAddaddress Entity', 'details' => $transactionGroupEntity->getErrors()]; + } + $userPubkeyBin = hex2bin($this->user_pubkey); + if($this->remove_from_group) { + $stateGroup_query = $stateGroupAddresses->find('all')->where(['public_key' => hex2bin($this->user_pubkey)]); + if(!$stateGroup_query->isEmpty()) { + $stateGroupAddresses->delete($stateGroup_query->first()); + } + } else { + $stateGroupAddressesEntity = $stateGroupAddresses->newEntity(); + $stateGroupAddressesEntity->group_id = 1; + $stateGroupAddressesEntity->public_key = $userPubkeyBin; + $stateGroupAddressesEntity->address_type_id = 1; + if(!$stateGroupAddresses->save($stateGroupAddressesEntity)) { + return ['state' => 'error', 'msg' => 'error saving state group addresses entity', 'details' => $stateGroupAddressesEntity->getErrors()]; + } + } + + return true; + } + + public function setRemoveFromGroup($removeFromGroup) { + $this->remove_from_group = $removeFromGroup; + } +} + +class GradidoModifieUserBalance +{ + + public function getUserId($userPublicKey) + { + $stateUsersTable = TableRegistry::getTableLocator()->get('StateUsers'); + $stateUser = $stateUsersTable->find('all')->where(['public_key' => hex2bin($userPublicKey)]); + if($stateUser->isEmpty()) { + return ['state' => 'error', 'msg' => 'couldn\'t find user via public key']; + } + return $stateUser->first()->id; + } + + public function updateBalance($newBalance, $recordDate, $userId) + { + $stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances'); + $stateBalanceQuery = $stateBalancesTable->find('all')->where(['state_user_id' => $userId]); + $entity = null; + + if(!$stateBalanceQuery->isEmpty()) { + $entity = $stateBalanceQuery->first(); + if($entity->record_date != NULL && $entity->record_date > $recordDate) { + return false; + } + } else { + $entity = $stateBalancesTable->newEntity(); + $entity->state_user_id = $userId; + } + $entity->record_date = $recordDate; + $entity->amount = $newBalance; + if(!$stateBalancesTable->save($entity)) { + return ['state' => 'error', 'msg' => 'error saving state balance', 'details' => $entity->getErrors()]; + } + return true; + } +} + +class GradidoCreation extends GradidoModifieUserBalance +{ + /* + * "gradido_creation": { + "user": " << user << ", + "new_balance": << v.new_balance << , + "prev_transfer_rec_num": << v.prev_transfer_rec_num <<, + "amount": << v.amount << + } + */ + private $userPubkey; + private $amount; + private $targetDate; // seems currently not in node server implementet, use hedera date until it is implemented + private $new_balance; + + public function __construct($data) + { + $this->userPubkey = $data['user']; + $this->amount = $data['amount']; + $this->new_balance = $data['new_balance']; + //$this->targetDate = $received; + } + + public function finalize($transactionId, $received) + { + // TODO: don't use, after node server transmit correct date + $this->targetDate = $received; + + $transactionCreationTable = TableRegistry::getTableLocator()->get('TransactionCreations'); + + + $state_user_id = $this->getUserId($this->userPubkey); + if(!is_int($state_user_id)) { + return $state_user_id; + } + + $entity = $transactionCreationTable->newEntity(); + $entity->transaction_id = $transactionId; + $entity->amount = $this->amount; + $entity->target_date = $this->targetDate; + $entity->state_user_id = $state_user_id; + + if(!$transactionCreationTable->save($entity)) { + return ['state' => 'error', 'msg' => 'error saving create transaction', 'details' => $entity->getErrors()]; + } + + $balance_result = $this->updateBalance($this->new_balance, $received, $state_user_id); + if(is_array($balance_result)) { + return $balance_result; + } + + return true; + } + +} + +class GradidoTransfer extends GradidoModifieUserBalance +{ + /* + "local_transfer|inbound_transfer|outbound_transfer": { + "sender": { + "user": " << sender << ", + "new_balance": << tt.sender.new_balance << , + "prev_transfer_rec_num": << tt.sender.prev_transfer_rec_num << + }, + "receiver": { + "user": " << receiver << ", + "new_balance": << tt.receiver.new_balance << , + "prev_transfer_rec_num": << tt.receiver.prev_transfer_rec_num << + }, + "amount": << tt.amount << + }, + * */ + private $amount; + private $sender_new_balance; + private $sender_pubkey; + + private $receiver_pubkey; + private $receiver_new_balance; + + public function __construct($data) + { + $this->amount = $data['amount']; + + $sender = $data['sender']; + $this->sender_pubkey = $sender['user']; + $this->sender_new_balance = $sender['new_balance']; + + $receiver = $data['receiver']; + $this->receiver_pubkey = $receiver['user']; + $this->receiver_new_balance = $receiver['new_balance']; + + } + + public function finalize($transactionId, $received) + { + $transactionTransferTable = TableRegistry::getTableLocator()->get('TransactionSendCoins'); + if(count($this->sender_pubkey) != 64) { + return ['state' => 'error', 'msg' => 'invalid size sender pubkey', 'details' => count($this->user_pubkey)]; + } + if(!preg_match('/^[0-9a-fA-F]*$/', $this->sender_pubkey)) { + return ['state' => 'error', 'msg' => 'sender_pubkey isn\'t in hex format']; + } + if(count($this->receiver_pubkey) != 64) { + return ['state' => 'error', 'msg' => 'invalid size receiver pubkey', 'details' => count($this->user_pubkey)]; + } + if(!preg_match('/^[0-9a-fA-F]*$/', $this->receiver_pubkey)) { + return ['state' => 'error', 'msg' => 'receiver_pubkey isn\'t in hex format']; + } + + $sender_id = $this->getUserId($this->sender_pubkey); + $receiver_id = $this->getUserId($this->receiver_pubkey); + if(is_array($sender_id) && is_array($receiver_id)) { + return ['state' => 'error', 'msg' => 'neither sender or receiver known']; + } + $transferEntity = $transactionTransferTable->newEntity(); + $transferEntity->transaction_id = $transactionId; + $transferEntity->sender_public_key = hex2bin($this->sender_pubkey); + $transferEntity->receiver_public_key = hex2bin($this->receiver_pubkey); + $transferEntity->amount = $this->amount; + $transferEntity->sender_final_balance = $this->sender_new_balance; + + if(is_int($sender_id) && $sender_id > 0) { + $transferEntity->state_user_id = $sender_id; + $balance_result = $this->updateBalance($this->sender_new_balance, $received, $sender_id); + if(is_array($balance_result)) { + return $balance_result; + } + } + if(is_int($receiver_id) && $receiver_id > 0) { + $transferEntity->receiver_user_id = $receiver_id; + $balance_result = $this->updateBalance($this->receiver_new_balance, $received, $receiver_id); + if(is_array($balance_result)) { + return $balance_result; + } + } + + if(!$transactionTransferTable->save($transferEntity)) { + return ['state' => 'error', 'msg' => 'error saving transaction send coins entity', 'details' => $transferEntity->getErrors()]; + } + + return true; + } +} + + + + +class Record +{ + private $sequenceNumber = 0; + private $transactionType = ''; + private $memo = ''; + private $signatures = []; + private $received; + private $transactionObj = null; + private $result; + private $partCount = 0; + + public function __construct() + { + + } + + + public function parseRecord($json) { + switch($json['record_type']) { + case 'GRADIDO_TRANSACTION': + return $this->parseTransaction($json['transaction']); + case 'MEMO': + $this->memo = $json['memo']; + return true; + case 'SIGNATURES': + return $this->parseSignatures($json['signature']); + case 'STRUCTURALLY_BAD_MESSAGE': + case 'RAW_MESSAGE': + case 'BLANK': + return false; + } + } + + /*! + * \brief save data parts in db + */ + public function finalize() + { + $transactionTypesTable = TableRegistry::getTableLocator()->get('TransactionTypes'); + $transactionsTable = TableRegistry::getTableLocator()->get('Transactions'); + + $transactionTypeName = $this->nodeTransactionTypeToDBTransactionType($this->transactionType); + $transactionTypeResults = $transactionTypesTable->find('all')->where(['name' => $transactionTypeName]); + if($transactionTypeResults->isEmpty()) { + return [ + 'state' => 'error', 'msg' => 'transaction type not found', + 'details' => ['nodeType' => $this->transactionType, 'dbType' => $transactionTypeName] + ]; + } + if(!$this->transactionObj) { + return ['state' => 'error', 'msg' => 'transaction obj is null']; + } + if($this->sequenceNumber <= 0) { + return ['state' => 'error', 'msg' => 'sequence number invalid', 'details' => $this->sequenceNumber]; + } + $transactionExistResult = $transactionsTable->find('all')->where(['id' => $this->sequenceNumber]); + if(!$transactionExistResult->isEmpty()) { + return ['state' => 'warning', 'msg' => 'transaction already exist in db', 'details' => $this->sequenceNumber]; + } + $newTransaction = $transactionsTable->newEntity(); + $newTransaction->id = $this->sequenceNumber; + $newTransaction->transaction_type_id = $transactionTypeResults->first()->id; + $newTransaction->memo = $this->memo; + $newTransaction->received = $this->received; + + if(!$transactionsTable->save($newTransaction)) { + return ['state' => 'error', 'msg' => 'error saving transaction', 'details' => $newTransaction->getErrors()]; + } + foreach($this->signatures as $sign) { + $sign_result = $sign->finalize($this->sequenceNumber); + iF($sign_result != true) { + return ['state' => 'error', 'msg', 'error finalizing signature', 'details' => $sign_result]; + } + } + $transaction_obj_result = $this->transactionObj->finalize($newTransaction->id, $this->received); + if($transaction_obj_result != true) { + return ['state' => 'error', 'msg' => 'error finalizing transaction object', 'details' => $transaction_obj_result]; + } + return true; + + } + + private function nodeTransactionTypeToDBTransactionType($nodeTransactionType) + { + switch($nodeTransactionType) { + case 'GRADIDO_CREATION': + return 'creation'; + + case 'MOVE_USER_INBOUND': + case 'ADD_USER': + return 'group add member'; + + case 'MOVE_USER_OUTBOUND': + return 'group remove member'; + + case 'LOCAL_TRANSFER': + case 'INBOUND_TRANSFER': + case 'OUTBOUND_TRANSFER': + return 'transfer'; + } + return 'unknown'; + } + + private function parseSignatures($signaturesArray) + { + foreach($signaturesArray as $sign) { + $this->signatures[] = new Signature($sign['signature'], $sign['pubkey']); + } + return true; + } + + private function parseTransaction($data) + { + $this->transactionType = $data['transaction_type']; + $sign = $data['signature']; + $this->signatures[] = new Signature($sign['signature'], $sign['pubkey']); + + $hedera = $data['hedera_transaction']; + $this->sequenceNumber = $hedera['sequenceNumber']; + $this->received = Time::createFromTimestamp($hedera['consensusTimestamp']['seconds']); + + $field_index = ''; + $class_name = ''; + + $removeFromGroup = false; + switch($this->transactionType) + { + case 'GRADIDO_CREATION': $field_index = 'gradido_creation'; $class_name = 'GradidoCreation'; break; + case 'ADD_USER': $field_index = 'add_user'; $class_name = 'ManageNodeGroupAdd'; break; + case 'MOVE_USER_INBOUND': $field_index = 'move_user_inbound'; $class_name = 'ManageNodeGroupAdd'; break; + case 'MOVE_USER_OUTBOUND': $field_index = 'move_user_outbound'; $class_name = 'ManageNodeGroupAdd'; $removeFromGroup = true; break; + case 'LOCAL_TRANSFER': $field_index = 'local_transfer'; $class_name = 'GradidoTransfer'; break; + case 'INBOUND_TRANSFER': $field_index = 'inbound_transfer'; $class_name = 'GradidoTransfer'; break; + case 'OUTBOUND_TRANSFER': $field_index = 'outbound_transfer'; $class_name = 'GradidoTransfer'; break; + } + if($class_name == '' || $field_index == '') { + return ['state' => 'error', 'msg' => 'node transaction type unknown', 'details' => $this->transactionType]; + } + $this->transactionObj = new $class_name($data[$field_index]); + if($class_name == 'ManageNodeGroupAdd') { + $this->transactionObj->setRemoveFromGroup($removeFromGroup); + } + + $this->result = $data['result']; + $this->partCount = intval($data['parts']); + $this->memo = $data['memo']; + return true; + } + + public function getSequenceNumber() { + return $this->sequenceNumber; + } + public function getPartCount() { + return $this->partCount; + } + +} \ No newline at end of file diff --git a/src/Template/AddressTypes/add.ctp b/src/Template/AddressTypes/add.ctp index 7422b666a..f62c5115e 100644 --- a/src/Template/AddressTypes/add.ctp +++ b/src/Template/AddressTypes/add.ctp @@ -4,8 +4,8 @@ * @var \App\Model\Entity\AddressType $addressType */ ?> -