You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
5.6 KiB

  1. <?php
  2. /**
  3. * generate.php
  4. *
  5. * generate bitcoin address list file for ss-server/shadowsocks-libev.
  6. *
  7. * @author Pan Zhibiao <panzhibiao@tangpool.com>
  8. * @since 2014-03
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. date_default_timezone_set("Etc/GMT+0");
  24. /*
  25. * config settings
  26. */
  27. $_CFG = array();
  28. // API Key for Chain.com
  29. $_CFG['API-KEY-ID'] = "DEMO-4a5e1e4";
  30. // server bitcoin address which receive btc transactions from customers
  31. $_CFG['server_baddress'] = "1LDjqcu3rSUkWjL219oZTsHg79S9K6vPwP";
  32. $_CFG['cache_file'] = "/var/ss/{$_CFG['server_baddress']}-cached_txs.txt";
  33. $_CFG['ss_server_file'] = "/var/ss/{$_CFG['server_baddress']}-list-ss-server.txt";
  34. // price for one day, unit: satoshi
  35. $_CFG['satoshi_per_day'] = 15000;
  36. // load exist parsed txs
  37. $exist_txs = get_exist_txs();
  38. // try load new txs
  39. $exist_txs = try_load_txs($exist_txs);
  40. // write cache
  41. put_exist_txs($exist_txs);
  42. // dump file for ss-server
  43. dump_for_ss_server($exist_txs);
  44. exit;
  45. function dump_for_ss_server($exist_txs) {
  46. global $_CFG;
  47. $address = array();
  48. $now = time();
  49. foreach ($exist_txs as $_tx) {
  50. // total days
  51. $days = bcdiv($_tx['value'], $_CFG['satoshi_per_day'], 2);
  52. // passed days
  53. assert($now > $_tx['timestamp']);
  54. $passed_days = bcdiv($now - $_tx['timestamp'], 86400, 2);
  55. if ($days <= $passed_days) {
  56. continue; // expired
  57. }
  58. $address[] = $_tx['address'];
  59. }
  60. $str = implode("\n", $address)."\n";
  61. LOGI("try dump ".count($address)." address to ss_server_file");
  62. $exist = file_exists($_CFG['ss_server_file']);
  63. $fp = fopen($_CFG['ss_server_file'], $exist ? "r+" : "w");
  64. if (!$fp) {
  65. LOGI("write file failure: {$_CFG['ss_server_file']}");
  66. return;
  67. }
  68. // use flock when write file
  69. for ($i = 0; $i < 3; $i++) { // try 3 times if need
  70. if (!flock($fp, LOCK_EX | LOCK_NB)) {
  71. sleep(3);continue;
  72. }
  73. ftruncate($fp, 0);
  74. fwrite($fp, $str);
  75. fflush($fp);
  76. flock($fp, LOCK_UN);
  77. LOGI("write file success: {$_CFG['ss_server_file']}");
  78. break;
  79. }
  80. fclose($fp);
  81. }
  82. function try_load_txs($exist_txs) {
  83. global $_CFG;
  84. $next_range = "";
  85. $run_flag = 1;
  86. while ($run_flag) {
  87. $res = get_address_list(100/*limit*/, $next_range);
  88. // var_dump($res);
  89. usleep(500000); // 500ms
  90. if (empty($res['body'])) {
  91. break; // no more records
  92. }
  93. if (!empty($res['next_range'])) {
  94. $next_range = $res['next_range'];
  95. } else {
  96. $run_flag = 0; // next page is empty
  97. }
  98. $new_cnt = 0;
  99. foreach ($res['body'] as $_tx) {
  100. if (!empty($exist_txs[$_tx['hash']])) {
  101. continue; // already exist
  102. }
  103. if (empty($_tx['inputs'][0]['addresses'][0])) {
  104. continue; // can't get first input address
  105. }
  106. // use first input address as payment address
  107. $input_addr = $_tx['inputs'][0]['addresses'][0];
  108. // check server address
  109. foreach ($_tx['outputs'] as $_output) {
  110. if (empty($_output['addresses'][0]) ||
  111. $_output['addresses'][0] != $_CFG['server_baddress']) {
  112. continue;
  113. }
  114. $exist_txs[$_tx['hash']] = array(
  115. 'address' => $input_addr,
  116. 'value' => $_output['value'],
  117. 'timestamp' => strtotime($_tx['chain_received_at']),
  118. );
  119. $new_cnt++;
  120. LOGI("find new tx: {$_tx['hash']}, value: {$_output['value']}, address: {$input_addr}, received: {$_tx['chain_received_at']}");
  121. break;
  122. }
  123. }
  124. if ($new_cnt == 0 && count($res['body']) > 0) {
  125. break; // stop, mean all records are exists
  126. }
  127. }
  128. return $exist_txs;
  129. }
  130. function get_exist_txs() {
  131. global $_CFG;
  132. if (is_readable($_CFG['cache_file'])) {
  133. return json_decode(file_get_contents($_CFG['cache_file']), 1);
  134. }
  135. return array();
  136. }
  137. function put_exist_txs($exist_txs) {
  138. global $_CFG;
  139. if (!file_put_contents($_CFG['cache_file'], json_encode($exist_txs))) {
  140. LOGI("file put contents fail, file: {$_CFG['cache_file']}");
  141. }
  142. }
  143. function get_address_list($limit, $range) {
  144. global $_CFG;
  145. $res = array();
  146. $custom_headers = array();
  147. $url = "https://api.chain.com/v2/bitcoin/addresses/";
  148. $url .= rawurlencode($_CFG['server_baddress'])."/transactions?api-key-id=";
  149. $url .= rawurlencode($_CFG['API-KEY-ID']);
  150. $url .= "&limit=".intval($limit);
  151. if ($range != "") {
  152. $custom_headers[] = "Range: ".$range;
  153. }
  154. $ch = curl_init();
  155. curl_setopt($ch, CURLOPT_AUTOREFERER, TRUE);
  156. curl_setopt($ch, CURLOPT_HEADER, 1);
  157. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  158. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
  159. curl_setopt($ch, CURLOPT_URL, $url);
  160. curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  161. if (!empty($custom_headers)) {
  162. curl_setopt($ch, CURLOPT_HTTPHEADER, $custom_headers);
  163. }
  164. $r = curl_exec($ch);
  165. $curl_errno = curl_errno($ch);
  166. $curl_error = curl_error($ch);
  167. curl_close($ch);
  168. if ($curl_errno > 0) {
  169. LOGI("curl request fail, errno: {$curl_errno}, error: {$curl_error}");
  170. return false;
  171. }
  172. list($header, $body) = explode("\r\n\r\n", $r, 2);
  173. // find next range
  174. preg_match("/Next-Range: ([^\r]*)/", $header, $matches);
  175. if (!empty($matches[1])) {
  176. $res['next_range'] = $matches[1];
  177. }
  178. $res['body'] = json_decode($body, 1);
  179. return $res;
  180. }
  181. function LOGI($msg) {
  182. echo date("Y-m-d H:i:s"), " $msg\n";
  183. }