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

<?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";
}