Web Interfaces of PROSUME

readhistory.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <?php
  2. /**
  3. * Copyright: Mangrovia Blockchain Solutions S.r.l.
  4. * Date: 04/01/2019
  5. */
  6. error_reporting(E_ALL);
  7. ini_set('display_errors', 1);
  8. $fp = fopen(__DIR__ . '/../locks/lock.txt', 'w') or die ('Cannot create lock file');
  9. if (! flock($fp, LOCK_EX | LOCK_NB)) {
  10. echo 'Unable to obtain lock';
  11. exit(-1);
  12. }
  13. $conf = parse_ini_file(dirname(__FILE__).'/../client.ini', true);
  14. $host = $conf['db']['host'] ?? null;
  15. $db = $conf['db']['name'] ?? null;
  16. $user = $conf['db']['user'] ?? null;
  17. $pass = $conf['db']['password'] ?? null;
  18. $charset = $conf['db']['charset'] ?? null;
  19. $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
  20. $options = [
  21. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  22. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  23. PDO::ATTR_EMULATE_PREPARES => false,
  24. ];
  25. try {
  26. $pdo = new PDO($dsn, $user, $pass, $options);
  27. } catch (\PDOException $e) {
  28. throw new \PDOException($e->getMessage(), (int)$e->getCode());
  29. }
  30. $wallet = new WalletEprosume($pdo, $conf);
  31. // fetch accounts registered in the db
  32. $stmt = $pdo->prepare('select id,name,sell,buy from meter');
  33. $stmt->execute();
  34. $accounts = $stmt->fetchAll();
  35. foreach($accounts as $account) {
  36. echo PHP_EOL."Account: {$account['name']}".PHP_EOL;
  37. $wallet->balance($pdo, $account);
  38. $wallet->updateHistoryDB($pdo, $account);
  39. }
  40. flock($fp, LOCK_UN);
  41. class WalletEprosume
  42. {
  43. public $walletExecutable;
  44. public $walletConfiguration;
  45. public $walletAccount;
  46. public $walletAccountDirectory;
  47. public $walletCommandUpdatePrices = "admin updatePrices";
  48. public $walletCommandBalance = "wallet balance";
  49. public $walletCommandHistory = "wallet history";
  50. public $walletCommandTransfer = "wallet transfer";
  51. private $conf = [];
  52. public function __construct($pdo=null, $conf) {
  53. $this->walletExecutable = $conf['wallet']['executable'] ?? null;
  54. $this->walletConfiguration = $conf['wallet']['configuration'] ?? null;
  55. $this->walletAccount = $conf['wallet']['account'] ?? null;
  56. $this->walletAccountDirectory = dirname($conf['wallet']['account']) ?? null;
  57. if($pdo) {
  58. // fetch configuration
  59. $stmt = $pdo->prepare('select conf_key, conf_value from conf');
  60. $stmt->execute();
  61. $conf_rows = $stmt->fetchAll();
  62. foreach($conf_rows as $v) {
  63. $this->conf[$v['conf_key']] = $v['conf_value'];
  64. }
  65. }
  66. }
  67. public function balance($pdo=null, $account=null)
  68. {
  69. $username = $account['name'];
  70. $id = $account['id'];
  71. $walletExec = "$this->walletExecutable $this->walletCommandBalance --config $this->walletConfiguration " .
  72. "--accounts-file $this->walletAccount --of $username 2>/dev/null";
  73. $type = "";
  74. $values = ['Meter' => $id, 'PRS' => '' , 'TFT' => ''];
  75. exec($walletExec, $arrayOutput);
  76. foreach ($arrayOutput as $line) {
  77. $parameter = explode(': ', $line);
  78. if (isset($parameter[1])) {
  79. $key = $parameter[0];
  80. $val = $parameter[1];
  81. if ($key == "Asset") {
  82. $type = $val;
  83. } else if ($key == "Balance") {
  84. $amount = $val;
  85. $values[$type] = $amount;
  86. }
  87. }
  88. }
  89. // check if the last registered balance is changed
  90. $stmt = $pdo->prepare('select * from balances where meter_id = :id order by timestamp desc limit 1');
  91. $stmt->execute(['id'=>$id]);
  92. $result = $stmt->fetch();
  93. if(empty($result) || $result['prs'] != $values['PRS'] || $result['tft'] != $values['TFT']) {
  94. $stmt = $pdo->prepare('insert into balances ( meter_id, prs, tft) values ( :Meter, :PRS, :TFT ) ');
  95. $stmt->execute($values);
  96. $stmt2 = $pdo->prepare('update meter set prs = :PRS, tft = :TFT where id = :Meter');
  97. $stmt2->execute($values);
  98. }
  99. /* TOP UP */
  100. foreach(['prs', 'tft'] as $v) {
  101. $from = $this->conf['utility_account'] ?? null;
  102. $limit = $this->conf['topup_'.$v.'_limit'] ?? null;
  103. $transfer = $this->conf['topup_'.$v.'_transfer'] ?? null;
  104. $memo = $this->conf['topup_'.$v.'_memo'] ?? "Automatic topup";
  105. if(!$from || !$limit || !$transfer) {
  106. continue;
  107. }
  108. if($values[strtoupper($v)] < (int)$limit) {
  109. var_dump("TRANSFER $from, ".$account['name'].", $transfer, $v, $memo");
  110. $this->transfer($from, $account['name'], $transfer, $v, $memo);
  111. }
  112. }
  113. }
  114. public function history($accountFile, $account, $pdo)
  115. {
  116. $walletExec = "$this->walletExecutable $this->walletCommandHistory --config $this->walletConfiguration " .
  117. "--accounts-file $accountFile 2>/dev/null";
  118. exec($walletExec, $arrayOutput);
  119. $transaction = [];
  120. $history = [];
  121. // setPrice flag
  122. $setPriceFlag = $this->conf['utility_account'] != $account['name'];
  123. foreach ($arrayOutput as $line) {
  124. $parameter = explode(': ', $line);
  125. if (isset($parameter[1])) {
  126. $key = $parameter[0];
  127. $value = $parameter[1];
  128. switch ($key) {
  129. case 'Block Number':
  130. $transaction['Blocknumber'] = $value;
  131. break;
  132. case 'Memo':
  133. $readings = json_decode($value, $assoc = TRUE);
  134. if (is_array($readings)) {
  135. foreach ($readings as $readingKey => $readingValue) {
  136. if (is_array($readingValue)) {
  137. if (!empty($readingValue['value'])) {
  138. $transaction[$key][$readingKey] = $readingValue['value'];
  139. }
  140. } else {
  141. $transaction[$key][$readingKey] = $readingValue;
  142. }
  143. }
  144. } else if(strlen($value) == 32){
  145. $transaction['Memo'] = [
  146. 'txID' => $value
  147. ];
  148. } else {
  149. $transaction['Memo'] = [
  150. 'Memo' => $value
  151. ];
  152. }
  153. $transaction['MemoHash'] = md5($value);
  154. break;
  155. default:
  156. $transaction[$key] = $value;
  157. break;
  158. }
  159. } else {
  160. $key = preg_replace('/\s/', '', $parameter[0]);
  161. switch ($key) {
  162. case '--------HistoryEnd--------':
  163. case '--------------------':
  164. // Qui devi salvare i record
  165. array_push($history, $transaction);
  166. // check if there is a setPrice in history
  167. if($setPriceFlag && isset($transaction['Asset']) && $transaction['Asset'] == 'TFT' && isset($transaction['Memo']) && count($transaction['Memo']) == 2) {
  168. if(isset($transaction['Memo']['sellPrice']) && isset($transaction['Memo']['buyPrice']) && isset($transaction['To'])) {
  169. if($account['sell'] != $transaction['Memo']['sellPrice'] || $account['buy'] != $transaction['Memo']['buyPrice']) {
  170. $stmt2 = $pdo->prepare("UPDATE meter SET sell = ".$transaction['Memo']['sellPrice'].", buy = ".$transaction['Memo']['buyPrice']." WHERE name = '".$transaction['To']."'");
  171. $stmt2->execute();
  172. }
  173. }
  174. $setPriceFlag = false;
  175. }
  176. $transaction = [];
  177. break;
  178. }
  179. }
  180. }
  181. if($setPriceFlag) {
  182. $this->setPrice($account['name'], $account['buy'], $account['sell']);
  183. }
  184. return $history;
  185. }
  186. public function status()
  187. {
  188. }
  189. public function updateHistoryDB($pdo=null, $account=null)
  190. {
  191. // check if the file with key exists
  192. $accountFile = $this->walletAccountDirectory."/".$account['name'].".txt";
  193. if(!file_exists($accountFile)) {
  194. echo PHP_EOL."ERROR: File $accountFile is not readable.".PHP_EOL;
  195. return;
  196. }
  197. foreach($history=$this->history($accountFile, $account, $pdo) as $block) {
  198. if($block["Type"] != "OperationTypeTransfer" || (!in_array($block["Asset"], ["PRS", "TFT"])) ) {
  199. continue;
  200. }
  201. $stmt = $pdo->prepare('select id from blocks where timestamp=:Timestamp and blocknumber=:Blocknumber and wallet_from=:From and wallet_to=:To and asset=:Asset and memo_hash=:MemoHash');
  202. isset($block['Timestamp']) && $block['Timestamp'] = $this->convertUTCToLocalTime($block['Timestamp']);
  203. $values=[
  204. 'Blocknumber'=>$block['Blocknumber'],
  205. 'Timestamp'=>$block['Timestamp'],
  206. 'From'=>$block['From'],
  207. 'To'=>$block['To'],
  208. 'Asset'=>$block['Asset'],
  209. 'MemoHash'=>$block['MemoHash']
  210. ];
  211. $stmt->execute($values);
  212. $entry = $stmt->fetch();
  213. if(empty($entry)) {
  214. /* memo keys in block table */
  215. $block_tx_id = null;
  216. $block_type = 'update_price';
  217. $block_purchased_sold = null;
  218. $block_buy_price = null;
  219. $block_sell_price = null;
  220. if(!empty($block['Memo'])) {
  221. $block_tx_id = $block['Memo']['txID'] ?? null;
  222. $block_purchased_sold = $block['Memo']['energyDiff'] ?? null;
  223. $block_buy_price = $block['Memo']['buyPrice'] ?? null;
  224. $block_sell_price = $block['Memo']['sellPrice'] ?? null;
  225. if($block['Asset'] == 'PRS') {
  226. $block_type = isset($block['Memo']['txID']) ? 'payment' : 'transfer';
  227. } else if($block['Asset'] == 'TFT') {
  228. if(isset($block['Memo']['Memo'])) {
  229. $block_type = 'transfer';
  230. } else if(count($block['Memo']) > 2) {
  231. $block_type = 'measure';
  232. }
  233. }
  234. }
  235. $stmt = $pdo->prepare('insert into blocks (id, blocknumber,wallet_type,wallet_from,wallet_to,asset,amount,timestamp,memo_hash,tx_id,type,purchased_sold,buy_price,sell_price) ' .
  236. ' values (null, :Blocknumber,:Type,:From,:To,:Asset,:Amount,:Timestamp,:MemoHash,:TxID,:BlockType,:PurchasedSold,:BuyPrice,:SellPrice) ');
  237. $values = [
  238. 'Blocknumber' => '',
  239. 'From' => '',
  240. 'To' => '',
  241. 'Asset' => '',
  242. 'Amount' => '',
  243. 'Timestamp' => '',
  244. 'MemoHash' => null,
  245. 'TxID' => $block_tx_id,
  246. 'Type' => '',
  247. 'BlockType' => $block_type,
  248. 'PurchasedSold' => $block_purchased_sold,
  249. 'BuyPrice' => $block_buy_price,
  250. 'SellPrice' => $block_sell_price,
  251. ];
  252. foreach (array_keys($values) as $memo_key) {
  253. if (isset($block[$memo_key])) {
  254. $values[$memo_key] = $block[$memo_key];
  255. }
  256. }
  257. $stmt->execute($values);
  258. $block_id = $pdo->lastInsertId();
  259. // print_r($block);
  260. if (($block_id) && (!empty($block['Memo']))) {
  261. /* automatic payments */
  262. $autopay_operation = null;
  263. $autopay_txid = null;
  264. $autopay_energyDiff = 0;
  265. $autopay_sellPrice = 0;
  266. foreach ($block['Memo'] as $memo_key => $memo_value) {
  267. switch ($memo_key) {
  268. case 'timestamp_sospendi':
  269. break;
  270. default:
  271. $sql = 'insert into memo (block_id,memo_key,memo_value) values (:block_id,:memo_key,:memo_value)';
  272. $stmt = $pdo->prepare($sql);
  273. $stmt->execute(compact('block_id', 'memo_key', 'memo_value'));
  274. if($account['name'] == $this->conf['utility_account']) {
  275. if($memo_key == "txID" && $block['Asset'] == "PRS") { // payment
  276. $autopay_operation = 'delete';
  277. $autopay_txid = $memo_value;
  278. }
  279. }
  280. if($block['Asset'] == "TFT" && $memo_key == "energyDiff") { // pending payment
  281. if($memo_value < 0) {
  282. $autopay_operation = 'pay';
  283. $autopay_txid = $block['Memo']['txID'] ?? null;
  284. $autopay_energyDiff = $memo_value;
  285. $autopay_sellPrice = $block['Memo']['sellPrice'] ?? 0;
  286. }
  287. }
  288. }
  289. }
  290. /*automatic payments */
  291. if($autopay_operation && $autopay_txid) {
  292. switch($autopay_operation) {
  293. case 'delete':
  294. $sql = 'delete from pending_payments where tx_id = "'.$autopay_txid.'"';
  295. var_dump($sql);
  296. $stmt = $pdo->prepare($sql);
  297. $stmt->execute();
  298. break;
  299. case 'pay':
  300. $sql = 'select * from memo where memo_key = "txID" and memo_value = "'.$autopay_txid.'" and block_id != "'.$block_id.'"';
  301. var_dump($sql);
  302. $stmt = $pdo->prepare($sql);
  303. $stmt->execute();
  304. $memo_rows = $stmt->fetchAll();
  305. if(!$memo_rows || empty($memo_rows)) {
  306. $sql = 'select * from pending_payments where tx_id = "'.$autopay_txid.'"';
  307. var_dump($sql);
  308. $stmt = $pdo->prepare($sql);
  309. $stmt->execute();
  310. $pending_payments_rows = $stmt->fetchAll();
  311. if(!$pending_payments_rows || empty($pending_payments_rows)) {
  312. $from = $this->conf['utility_account'] ?? null;
  313. if(!$from) {
  314. break;
  315. }
  316. $to = $block['From'];
  317. $amount = number_format(abs($autopay_energyDiff)*($autopay_sellPrice), 8);
  318. $asset = 'PRS';
  319. $memo = $autopay_txid;
  320. $autopay = $this->transfer($from, $to, $amount, $asset, $memo);
  321. if($autopay) {
  322. $sql = 'insert into pending_payments(tx_id) values("'.$autopay_txid.'")';
  323. var_dump($sql);
  324. $stmt = $pdo->prepare($sql);
  325. $stmt->execute();
  326. }
  327. }
  328. }
  329. break;
  330. }
  331. }
  332. }
  333. }
  334. }
  335. }
  336. private function convertUTCToLocalTime($datestring) {
  337. return (new \DateTime($datestring, new \DateTimeZone("UTC")))->setTimeZone(new \DateTimeZone("Europe/Rome"))->format("Y-m-d H:i:s");
  338. }
  339. private function transfer($from, $to, $amount, $asset, $memo) {
  340. if(in_array(null, func_get_args(), true)) {
  341. return false;
  342. } else {
  343. $accountFile = $this->walletAccountDirectory."/".$from.".txt";
  344. if(!file_exists($accountFile)) {
  345. return false;
  346. }
  347. $asset = strtoupper($asset);
  348. $memo = str_replace("'", "\'", html_entity_decode($memo, ENT_QUOTES));
  349. $walletExec = "$this->walletExecutable $this->walletCommandTransfer --accounts-file $accountFile --to $to --asset $asset --amount $amount --memo '$memo' --config $this->walletConfiguration 2>&1";
  350. var_dump($walletExec);
  351. $output = system($walletExec);
  352. return strpos($output, 'Assets correctly transferred', 0) !== false;
  353. }
  354. }
  355. private function setPrice($user, $buyPrice, $sellPrice)
  356. {
  357. if(in_array(null, func_get_args(), true)) {
  358. return false;
  359. } else {
  360. $walletExec = "$this->walletExecutable $this->walletCommandUpdatePrices --accounts-file $this->walletAccount -b $buyPrice -s $sellPrice -u $user --config $this->walletConfiguration 2>&1";
  361. var_dump($walletExec);
  362. $output = system($walletExec);
  363. return strpos($output, 'Assets correctly transferred', 0) !== false;
  364. }
  365. }
  366. }