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
213 lines
5.6 KiB
<?php
|
|
/**
|
|
* generate.php
|
|
*
|
|
* generate bitcoin address list file for ss-server/shadowsocks-libev.
|
|
*
|
|
* @author Pan Zhibiao <panzhibiao@tangpool.com>
|
|
* @since 2014-03
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
date_default_timezone_set("Etc/GMT+0");
|
|
|
|
/*
|
|
* config settings
|
|
*/
|
|
$_CFG = array();
|
|
// API Key for Chain.com
|
|
$_CFG['API-KEY-ID'] = "DEMO-4a5e1e4";
|
|
// server bitcoin address which receive btc transactions from customers
|
|
$_CFG['server_baddress'] = "1LDjqcu3rSUkWjL219oZTsHg79S9K6vPwP";
|
|
$_CFG['cache_file'] = "/var/ss/{$_CFG['server_baddress']}-cached_txs.txt";
|
|
$_CFG['ss_server_file'] = "/var/ss/{$_CFG['server_baddress']}-list-ss-server.txt";
|
|
// price for one day, unit: satoshi
|
|
$_CFG['satoshi_per_day'] = 15000;
|
|
|
|
// load exist parsed txs
|
|
$exist_txs = get_exist_txs();
|
|
// try load new txs
|
|
$exist_txs = try_load_txs($exist_txs);
|
|
// write cache
|
|
put_exist_txs($exist_txs);
|
|
// dump file for ss-server
|
|
dump_for_ss_server($exist_txs);
|
|
|
|
exit;
|
|
|
|
|
|
|
|
function dump_for_ss_server($exist_txs) {
|
|
global $_CFG;
|
|
|
|
$address = array();
|
|
$now = time();
|
|
foreach ($exist_txs as $_tx) {
|
|
// total days
|
|
$days = bcdiv($_tx['value'], $_CFG['satoshi_per_day'], 2);
|
|
|
|
// passed days
|
|
assert($now > $_tx['timestamp']);
|
|
$passed_days = bcdiv($now - $_tx['timestamp'], 86400, 2);
|
|
|
|
if ($days <= $passed_days) {
|
|
continue; // expired
|
|
}
|
|
$address[] = $_tx['address'];
|
|
}
|
|
$str = implode("\n", $address)."\n";
|
|
LOGI("try dump ".count($address)." address to ss_server_file");
|
|
|
|
$exist = file_exists($_CFG['ss_server_file']);
|
|
$fp = fopen($_CFG['ss_server_file'], $exist ? "r+" : "w");
|
|
if (!$fp) {
|
|
LOGI("write file failure: {$_CFG['ss_server_file']}");
|
|
return;
|
|
}
|
|
|
|
// use flock when write file
|
|
for ($i = 0; $i < 3; $i++) { // try 3 times if need
|
|
if (!flock($fp, LOCK_EX | LOCK_NB)) {
|
|
sleep(3);continue;
|
|
}
|
|
ftruncate($fp, 0);
|
|
fwrite($fp, $str);
|
|
fflush($fp);
|
|
flock($fp, LOCK_UN);
|
|
|
|
LOGI("write file success: {$_CFG['ss_server_file']}");
|
|
break;
|
|
}
|
|
fclose($fp);
|
|
}
|
|
|
|
|
|
function try_load_txs($exist_txs) {
|
|
global $_CFG;
|
|
|
|
$next_range = "";
|
|
$run_flag = 1;
|
|
while ($run_flag) {
|
|
$res = get_address_list(100/*limit*/, $next_range);
|
|
// var_dump($res);
|
|
usleep(500000); // 500ms
|
|
|
|
if (empty($res['body'])) {
|
|
break; // no more records
|
|
}
|
|
if (!empty($res['next_range'])) {
|
|
$next_range = $res['next_range'];
|
|
} else {
|
|
$run_flag = 0; // next page is empty
|
|
}
|
|
|
|
$new_cnt = 0;
|
|
foreach ($res['body'] as $_tx) {
|
|
if (!empty($exist_txs[$_tx['hash']])) {
|
|
continue; // already exist
|
|
}
|
|
if (empty($_tx['inputs'][0]['addresses'][0])) {
|
|
continue; // can't get first input address
|
|
}
|
|
// use first input address as payment address
|
|
$input_addr = $_tx['inputs'][0]['addresses'][0];
|
|
|
|
// check server address
|
|
foreach ($_tx['outputs'] as $_output) {
|
|
if (empty($_output['addresses'][0]) ||
|
|
$_output['addresses'][0] != $_CFG['server_baddress']) {
|
|
continue;
|
|
}
|
|
$exist_txs[$_tx['hash']] = array(
|
|
'address' => $input_addr,
|
|
'value' => $_output['value'],
|
|
'timestamp' => strtotime($_tx['chain_received_at']),
|
|
);
|
|
$new_cnt++;
|
|
LOGI("find new tx: {$_tx['hash']}, value: {$_output['value']}, address: {$input_addr}, received: {$_tx['chain_received_at']}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($new_cnt == 0 && count($res['body']) > 0) {
|
|
break; // stop, mean all records are exists
|
|
}
|
|
}
|
|
return $exist_txs;
|
|
}
|
|
|
|
function get_exist_txs() {
|
|
global $_CFG;
|
|
if (is_readable($_CFG['cache_file'])) {
|
|
return json_decode(file_get_contents($_CFG['cache_file']), 1);
|
|
}
|
|
return array();
|
|
}
|
|
|
|
function put_exist_txs($exist_txs) {
|
|
global $_CFG;
|
|
if (!file_put_contents($_CFG['cache_file'], json_encode($exist_txs))) {
|
|
LOGI("file put contents fail, file: {$_CFG['cache_file']}");
|
|
}
|
|
}
|
|
|
|
function get_address_list($limit, $range) {
|
|
global $_CFG;
|
|
|
|
$res = array();
|
|
$custom_headers = array();
|
|
|
|
$url = "https://api.chain.com/v2/bitcoin/addresses/";
|
|
$url .= rawurlencode($_CFG['server_baddress'])."/transactions?api-key-id=";
|
|
$url .= rawurlencode($_CFG['API-KEY-ID']);
|
|
$url .= "&limit=".intval($limit);
|
|
if ($range != "") {
|
|
$custom_headers[] = "Range: ".$range;
|
|
}
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_AUTOREFERER, TRUE);
|
|
curl_setopt($ch, CURLOPT_HEADER, 1);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
if (!empty($custom_headers)) {
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $custom_headers);
|
|
}
|
|
|
|
$r = curl_exec($ch);
|
|
$curl_errno = curl_errno($ch);
|
|
$curl_error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($curl_errno > 0) {
|
|
LOGI("curl request fail, errno: {$curl_errno}, error: {$curl_error}");
|
|
return false;
|
|
}
|
|
|
|
list($header, $body) = explode("\r\n\r\n", $r, 2);
|
|
// find next range
|
|
preg_match("/Next-Range: ([^\r]*)/", $header, $matches);
|
|
if (!empty($matches[1])) {
|
|
$res['next_range'] = $matches[1];
|
|
}
|
|
$res['body'] = json_decode($body, 1);
|
|
return $res;
|
|
}
|
|
|
|
|
|
function LOGI($msg) {
|
|
echo date("Y-m-d H:i:s"), " $msg\n";
|
|
}
|