Initial commit

This commit is contained in:
Arionum
2018-01-08 14:50:15 +02:00
parent 6c5fcd9c4d
commit 2d1f3e8e05
16 changed files with 2355 additions and 0 deletions

202
api.php Executable file
View File

@@ -0,0 +1,202 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
error_reporting(0);
$ip=$_SERVER['REMOTE_ADDR'];
if($_config['public_api']==false&&!in_array($ip,$_config['allowed_hosts'])){
api_err("private-api");
}
$acc = new Account;
$block = new Block;
$trx = new Transaction;
$q=$_GET['q'];
if(!empty($_POST['data'])){
$data=json_decode($_POST['data'],true);
} else {
$data=$_GET;
}
if($q=="getAddress"){
$public_key=$data['public_key'];
if(strlen($public_key)<32) api_err("Invalid public key");
api_echo($acc->get_address($public_key));
}
elseif($q=="base58"){
api_echo(base58_encode($data['data']));
}
elseif($q=="getBalance"){
$public_key=$data['public_key'];
$account=$data['account'];
if(!empty($public_key)&&strlen($public_key)<32) api_err("Invalid public key");
if(!empty($public_key)) $account=$acc->get_address($public_key);
if(empty($account)) api_err("Invalid account id");
$account=san($account);
api_echo($acc->balance($account));
}
elseif($q=="getPendingBalance"){
$account=$data['account'];
if(empty($account)) api_err("Invalid account id");
$account=san($account);
api_echo($acc->pending_balance($account));
}
elseif($q=="getTransactions"){
$account=san($data['account']);
$transactions=$acc->get_mempool_transactions($account);
$transactions=array_merge($transactions, $acc->get_transactions($account));
api_echo($transactions);
}
elseif($q=="getPublicKey"){
$account=san($data['account']);
if(empty($account)) api_err("Invalid account id");
$public_key=$acc->public_key($account);
if($public_key===false) api_err("No public key found for this account");
else api_echo($public_key);
} elseif($q=="getTransaction"){
$id=san($data['transaction']);
$res=$trx->get_transaction($id);
if($res===false) {
$res=$trx->get_mempool_transaction($id);
if($res===false) api_err("invalid transaction");
}
api_Echo($res);
}
elseif($q=="currentBlock"){
$current=$block->current();
api_echo($current);
} elseif($q=="version"){
api_echo(VERSION);
} elseif($q=="send"){
$acc = new Account;
$block = new Block;
$trx = new Transaction;
$dst=san($data['dst']);
if(!$acc->valid($dst)) api_err("Invalid destination address");
$public_key=san($data['public_key']);
if(!$acc->valid_key($public_key)) api_err("Invalid public key");
$private_key=san($data['private_key']);
if(!$acc->valid_key($private_key)) api_err("Invalid private key");
$signature=san($data['signature']);
if(!$acc->valid_key($signature)) api_err("Invalid signature");
$date=$data['date']+0;
if($date==0) $date=time();
if($date<time()-(3600*24*48)) api_err("The date is too old");
if($date>time()+86400) api_err("Invalid Date");
$version=intval($data['version']);
$message=$data['message'];
if(strlen($message)>128) api_err("The message must be less than 128 chars");
$val=$data['val']+0;
$fee=$val*0.0025;
if($fee<0.00000001) $fee=0.00000001;
if($val<0.00000001) api_err("Invalid value");
if($version<1) api_err("Invalid version");
$val=number_format($val,8,'.','');
$fee=number_format($fee,8,'.','');
if(empty($public_key)&&empty($private_key)) api_err("Either the private key or the public key must be sent");
if(empty($private_key)&&empty($signature)) api_err("Either the private_key or the signature must be sent");
if(empty($public_key))
{
$pk=coin2pem($private_key,true);
$pkey=openssl_pkey_get_private($pk);
$pub = openssl_pkey_get_details($pkey);
$public_key= pem2coin($pub['key']);
}
$transaction=array("val"=>$val, "fee"=>$fee, "dst"=>$dst, "public_key"=>$public_key,"date"=>$date, "version"=>$version,"message"=>$message, "signature"=>$signature);
if(!empty($private_key)){
$signature=$trx->sign($transaction, $private_key);
$transaction['signature']=$signature;
}
$hash=$trx->hash($transaction);
$transaction['id']=$hash;
if(!$trx->check($transaction)) api_err("Transaction signature failed");
$res=$db->single("SELECT COUNT(1) FROM mempool WHERE id=:id",array(":id"=>$hash));
if($res!=0) api_err("The transaction is already in mempool");
$res=$db->single("SELECT COUNT(1) FROM transactions WHERE id=:id",array(":id"=>$hash));
if($res!=0) api_err("The transaction is already in a block");
$src=$acc->get_address($public_key);
$transaction['src']=$src;
$balance=$db->single("SELECT balance FROM accounts WHERE id=:id",array(":id"=>$src));
if($balance<$val+$fee) api_err("Not enough funds");
$memspent=$db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:src",array(":src"=>$src));
if($balance-$memspent<$val+$fee) api_err("Not enough funds (mempool)");
$trx->add_mempool($transaction, "local");
system("php propagate.php transaction $hash &>/dev/null &");
api_echo($hash);
} elseif($q=="mempoolSize"){
$res=$db->single("SELECT COUNT(1) FROM mempool");
api_echo($res);
} else {
api_err("Invalid request");
}
?>

138
include/account.inc.php Executable file
View File

@@ -0,0 +1,138 @@
<?php
class Account {
public function add($public_key, $block){
global $db;
$id=$this->get_address($public_key);
$bind=array(":id"=>$id, ":public_key"=>$public_key, ":block"=>$block,":public_key2"=>$public_key );
$db->run("INSERT INTO accounts SET id=:id, public_key=:public_key, block=:block, balance=0 ON DUPLICATE KEY UPDATE public_key=if(public_key='',:public_key2,public_key)",$bind);
}
public function add_id($id, $block){
global $db;
$bind=array(":id"=>$id, ":block"=>$block);
$db->run("INSERT ignore INTO accounts SET id=:id, public_key='', block=:block, balance=0",$bind);
}
public function get_address($hash){
for($i=0;$i<9;$i++) $hash=hash('sha512',$hash, true);
return base58_encode($hash);
}
public function check_signature($data, $signature, $public_key){
return ec_verify($data ,$signature, $public_key);
}
public function generate_account(){
$args = array(
"curve_name" => "secp256k1",
"private_key_type" => OPENSSL_KEYTYPE_EC,
);
$key1 = openssl_pkey_new($args);
openssl_pkey_export($key1, $pvkey);
$private_key= pem2coin($pvkey);
$pub = openssl_pkey_get_details($key1);
$public_key= pem2coin($pub['key']);
$address=$this->get_address($public_key);
return array("address"=>$address, "public_key"=>$public_key,"private_key"=>$private_key);
}
public function valid_key($id){
$chars = str_split("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
for($i=0;$i<strlen($id);$i++) if(!in_array($id[$i],$chars)) return false;
return true;
}
public function valid($id){
if(strlen($id)<70||strlen($id)>128) return false;
$chars = str_split("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
for($i=0;$i<strlen($id);$i++) if(!in_array($id[$i],$chars)) return false;
return true;
}
public function balance($id){
global $db;
$res=$db->single("SELECT balance FROM accounts WHERE id=:id",array(":id"=>$id));
if($res===false) $res="0.00000000";
return $res;
}
public function pending_balance($id){
global $db;
$res=$db->single("SELECT balance FROM accounts WHERE id=:id",array(":id"=>$id));
if($res===false) $res="0.00000000";
if($res=="0.00000000") return $res;
$mem=$db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:id",array(":id"=>$id));
$rez=$res-$mem;
return $rez;
}
public function get_transactions($id){
global $db;
$block=new Block;
$current=$block->current();
$public_key=$this->public_key($id);
$res=$db->run("SELECT * FROM transactions WHERE dst=:dst or public_key=:src ORDER by height DESC LIMIT 100",array(":src"=>$public_key, ":dst"=>$id));
$transactions=array();
foreach($res as $x){
$trans=array("block"=>$x['block'],"height"=>$x['height'], "id"=>$x['id'],"dst"=>$x['dst'],"val"=>$x['val'],"fee"=>$x['fee'],"signature"=>$x['signature'], "message"=>$x['message'],"version"=>$x['version'],"date"=>$x['date'], "public_key"=>$x['public_key']);
$trans['src']=$this->get_address($x['public_key']);
$trans['confirmations']=$current['height']-$x['height'];
if($x['version']==0) $trans['type']="mining";
elseif($x['version']==1){
if($x['dst']==$id) $trans['type']="credit";
else $trans['type']="debit";
} else {
$trans['type']="other";
}
ksort($trans);
$transactions[]=$trans;
}
return $transactions;
}
public function get_mempool_transactions($id){
global $db;
$transactions=array();
$res=$db->run("SELECT * FROM mempool WHERE src=:src ORDER by height DESC LIMIT 100",array(":src"=>$id, ":dst"=>$id));
foreach($res as $x){
$trans=array("block"=>$x['block'],"height"=>$x['height'], "id"=>$x['id'],"src"=>$x['src'],"dst"=>$x['dst'],"val"=>$x['val'],"fee"=>$x['fee'],"signature"=>$x['signature'], "message"=>$x['message'],"version"=>$x['version'],"date"=>$x['date'], "public_key"=>$x['public_key']);
$trans['type']="mempool";
$trans['confirmations']=-1;
ksort($trans);
$transactions[]=$trans;
}
return $transactions;
}
public function public_key($id){
global $db;
$res=$db->single("SELECT public_key FROM accounts WHERE id=:id",array(":id"=>$id));
return $res;
}
}
?>

405
include/block.inc.php Executable file
View File

@@ -0,0 +1,405 @@
<?php
class Block {
public function add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon){
global $db;
$acc=new Account;
$trx=new Transaction;
//try {
// } catch (Exception $e){
// }
$generator=$acc->get_address($public_key);
ksort($data);
$hash=$this->hash($generator, $height, $date, $nonce, $data, $signature, $difficulty, $argon);
$json=json_encode($data);
$info="{$generator}-{$height}-{$date}-{$nonce}-{$json}-{$difficulty}-{$argon}";
if(!$acc->check_signature($info,$signature,$public_key)) return false;
if(!$this->parse_block($hash,$height,$data, true)) return false;
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$reward=$this->reward($height,$data);
$msg='';
$transaction=array("src"=>$generator, "dst"=>$generator, "val"=>$reward, "version"=>0, "date"=>$date, "message"=>$msg, "fee"=>"0.00000000","public_key"=>$public_key);
$transaction['signature']=$reward_signature;
$transaction['id']=$trx->hash($transaction);
$info=$transaction['val']."-".$transaction['fee']."-".$transaction['dst']."-".$transaction['message']."-".$transaction['version']."-".$transaction['public_key']."-".$transaction['date'];
if(!$acc->check_signature($info,$reward_signature,$public_key)) return false;
$db->beginTransaction();
$total=count($data);
$bind=array(":id"=>$hash,":generator"=>$generator, ":signature"=>$signature, ":height"=>$height, ":date"=>$date, ":nonce"=>$nonce, ":difficulty"=>$difficulty,":argon"=>$argon, ":transactions"=>$total);
$res=$db->run("INSERT into blocks SET id=:id, generator=:generator, height=:height,`date`=:date,nonce=:nonce, signature=:signature, difficulty=:difficulty, argon=:argon, transactions=:transactions",$bind);
if($res!=1) {
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
$trx->add($hash, $height,$transaction);
$res=$this->parse_block($hash,$height,$data, false);
if($res==false) $db->rollback();
else $db->commit();
$db->exec("UNLOCK TABLES");
return true;
}
public function current(){
global $db;
$current=$db->row("SELECT * FROM blocks ORDER by height DESC LIMIT 1");
if(!$current){
$this->genesis();
return $this->current(true);
}
return $current;
}
public function prev(){
global $db;
$current=$db->row("SELECT * FROM blocks ORDER by height DESC LIMIT 1,1");
return $current;
}
public function difficulty($height=0){
global $db;
if($height==0){
$current=$this->current();
} else{
$current=$this->get($height);
}
$height=$current['height'];
$limit=20;
if($height<20)
$limit=$height-1;
if($height<10) return $current['difficulty'];
$first=$db->row("SELECT `date` FROM blocks ORDER by height DESC LIMIT $limit,1");
$time=$current['date']-$first['date'];
$result=ceil($time/$limit);
if($result>220){
$dif= bcmul($current['difficulty'], 1.05);
} elseif($result<260){
$dif= bcmul($current['difficulty'], 0.95);
} else {
$dif=$current['difficulty'];
}
if($dif<1000) $dif=1000;
if($dif>9223372036854775800) $dif=9223372036854775800;
return $dif;
}
public function max_transactions(){
global $db;
$current=$this->current();
$limit=$current['height']-100;
$avg=$db->single("SELECT AVG(transactions) FROM blocks WHERE height>:limit",array(":limit"=>$limit));
if($avg<100) return 100;
return ceil($avg*1.1);
}
public function reward($id,$data=array()){
$reward=1000;
$factor=floor($id/10800)/100;
$reward-=$reward*$factor;
if($reward<0) $reward=0;
$fees=0;
if(count($data)>0){
foreach($data as $x){
$fees+=$x['fee'];
}
}
return number_format($reward+$fees,8,'.','');
}
public function check($data){
if(strlen($data['argon'])<20) return false;
$acc=new Account;
if(!$acc->valid_key($data['public_key'])) return false;
if($data['difficulty']!=$this->difficulty()) return false;
if(!$this->mine($data['public_key'],$data['nonce'], $data['argon'])) return false;
return true;
}
public function forge($nonce, $argon, $public_key, $private_key){
if(!$this->mine($public_key,$nonce, $argon)) return false;
$current=$this->current();
$height=$current['height']+=1;
$date=time();
if($date<=$current['date']) return 0;
$txn=new Transaction;
$data=$txn->mempool($this->max_transactions());
$difficulty=$this->difficulty();
$acc=new Account;
$generator=$acc->get_address($public_key);
ksort($data);
$signature=$this->sign($generator, $height, $date, $nonce, $data, $private_key, $difficulty, $argon);
// reward signature
$reward=$this->reward($height,$data);
$msg='';
$transaction=array("src"=>$generator, "dst"=>$generator, "val"=>$reward, "version"=>0, "date"=>$date, "message"=>$msg, "fee"=>"0.00000000","public_key"=>$public_key);
ksort($transaction);
$reward_signature=$txn->sign($transaction, $private_key);
$res=$this->add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon);
if(!$res) return false;
return true;
}
public function mine($public_key, $nonce, $argon, $difficulty=0, $current_id=0){
if($current_id===0){
$current=$this->current();
$current_id=$current['id'];
}
if($difficulty===0) $difficulty=$this->difficulty();
$argon='$argon2i$v=19$m=16384,t=4,p=4'.$argon;
$base="$public_key-$nonce-".$current_id."-$difficulty";
if(!password_verify($base,$argon)) { return false; }
$hash=$base.$argon;
for($i=0;$i<5;$i++) $hash=hash("sha512",$hash,true);
$hash=hash("sha512",$hash);
$m=str_split($hash,2);
$duration=hexdec($m[10]).hexdec($m[15]).hexdec($m[20]).hexdec($m[23]).hexdec($m[31]).hexdec($m[40]).hexdec($m[45]).hexdec($m[55]);
$duration=ltrim($duration, '0');
$result=gmp_div($duration, $difficulty);
if($result>0&&$result<=240) return true;
return false;
}
public function parse_block($block, $height, $data, $test=true){
global $db;
if($data===false) return false;
$acc=new Account;
$trx=new Transaction;
if(count($data)==0) return true;
$max=$this->max_transactions();
if(count($data)>$max) return false;
$balance=array();
foreach($data as &$x){
if(empty($x['src'])) $x['src']=$acc->get_address($x['public_key']);
if(!$trx->check($x)) return false;
$balance[$x['src']]+=$x['val']+$x['fee'];
if($db->single("SELECT COUNT(1) FROM transactions WHERE id=:id",array(":id"=>$x['id']))>0) return false; //duplicate transaction
}
foreach($balance as $id=>$bal){
$res=$db->single("SELECT COUNT(1) FROM accounts WHERE id=:id AND balance>=:balance",array(":id"=>$id, ":balance"=>$bal));
if($res==0) return false; // not enough balance for the transactions
}
if($test==false){
foreach($data as $d){
$res=$trx->add($block, $height, $d);
if($res==false) return false;
}
}
return true;
}
private function genesis(){
global $db;
$signature='AN1rKvtLTWvZorbiiNk5TBYXLgxiLakra2byFef9qoz1bmRzhQheRtiWivfGSwP6r8qHJGrf8uBeKjNZP1GZvsdKUVVN2XQoL';
$generator='2P67zUANj7NRKTruQ8nJRHNdKMroY6gLw4NjptTVmYk6Hh1QPYzzfEa9z4gv8qJhuhCNM8p9GDAEDqGUU1awaLW6';
$public_key='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCyjGMdVDanywM3CbqvswVqysqU8XS87FcjpqNijtpRSSQ36WexRDv3rJL5X8qpGvzvznuErSRMfb2G6aNoiaT3aEJ';
$reward_signature='381yXZ3yq2AXHHdXfEm8TDHS4xJ6nkV4suXtUUvLjtvuyi17jCujtwcwXuYALM1F3Wiae2A4yJ6pXL1kTHJxZbrJNgtsKEsb';
$argon='$M1ZpVzYzSUxYVFp6cXEwWA$CA6p39MVX7bvdXdIIRMnJuelqequanFfvcxzQjlmiik';
$difficulty="5555555555";
$height=1;
$data=array();
$date='1515324995';
$nonce='4QRKTSJ+i9Gf9ubPo487eSi+eWOnIBt9w4Y+5J+qbh8=';
$res=$this->add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature,$argon);
if(!$res) api_err("Could not add the genesis block.");
}
// delete last X blocks
public function pop($no=1){
$current=$this->current();
$this->delete($current['height']-$no+1);
}
public function delete($height){
if($height<2) $height=2;
global $db;
$trx=new Transaction;
$r=$db->run("SELECT * FROM blocks WHERE height>=:height ORDER by height DESC",array(":height"=>$height));
if(count($r)==0) return;
$db->beginTransaction();
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
foreach($r as $x){
$res=$trx->reverse($x['id']);
if($res===false) {
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
$res=$db->run("DELETE FROM blocks WHERE id=:id",array(":id"=>$x['id']));
if($res!=1){
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
}
$db->commit();
$db->exec("UNLOCK TABLES");
return true;
}
public function delete_id($id){
global $db;
$trx=new Transaction;
$x=$db->row("SELECT * FROM blocks WHERE id=:id",array(":id"=>$id));
if($x===false) return false;
$db->beginTransaction();
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$res=$trx->reverse($x['id']);
if($res===false) {
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
$res=$db->run("DELETE FROM blocks WHERE id=:id",array(":id"=>$x['id']));
if($res!=1){
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
$db->commit();
$db->exec("UNLOCK TABLES");
return true;
}
public function sign($generator, $height, $date, $nonce, $data, $key, $difficulty, $argon){
$json=json_encode($data);
$info="{$generator}-{$height}-{$date}-{$nonce}-{$json}-{$difficulty}-{$argon}";
$signature=ec_sign($info,$key);
return $signature;
}
public function hash($public_key, $height, $date, $nonce, $data, $signature, $difficulty, $argon){
$json=json_encode($data);
$hash= hash("sha512", "{$public_key}-{$height}-{$date}-{$nonce}-{$json}-{$signature}-{$difficulty}-{$argon}");
return hex2coin($hash);
}
public function export($id="",$height=""){
if(empty($id)&&empty($height)) return false;
global $db;
$trx=new Transaction;
if(!empty($height)) $block=$db->row("SELECT * FROM blocks WHERE height=:height",array(":height"=>$height));
else $block=$db->row("SELECT * FROM blocks WHERE id=:id",array(":id"=>$id));
if(!$block) return false;
$r=$db->run("SELECT * FROM transactions WHERE version>0 AND block=:block",array(":block"=>$block['id']));
$transactions=array();
foreach($r as $x){
$trans=array("id"=>$x['id'],"dst"=>$x['dst'],"val"=>$x['val'],"fee"=>$x['fee'],"signature"=>$x['signature'], "message"=>$x['message'],"version"=>$x['version'],"date"=>$x['date'], "public_key"=>$x['public_key']);
ksort($trans);
$transactions[$x['id']]=$trans;
}
ksort($transactions);
$block['data']=$transactions;
$gen=$db->row("SELECT public_key, signature FROM transactions WHERE version=0 AND block=:block",array(":block"=>$block['id']));
$block['public_key']=$gen['public_key'];
$block['reward_signature']=$gen['signature'];
return $block;
}
public function get($height){
global $db;
if(empty($height)) return false;
$block=$db->row("SELECT * FROM blocks WHERE height=:height",array(":height"=>$height));
return $block;
}
}
?>

17
include/config.inc.php Executable file
View File

@@ -0,0 +1,17 @@
<?php
$_config['db_connect']="mysql:host=localhost;dbname=ENTER-DB-NAME";
$_config['db_user']="ENTER-DB-USER";
$_config['db_pass']="ENTER-DB-PASS";
$_config['max_peers']=30;
$_config['testnet']=false;
$_config['coin']="arionum";
$_config['peer_max_mempool']=100;
$_config['max_mempool_rebroadcast']=5000;
$_config['sanity_rebroadcast_height']=30;
$_config['transaction_propagation_peers']=5;
$_config['max_test_peers']=5;
$_config['sanity_recheck_blocks']=10;
$_config['public_api']=true;
$_config['allowed_hosts']=array("127.0.0.1");
$_config['sanity_interval']=900;
?>

121
include/db.inc.php Executable file
View File

@@ -0,0 +1,121 @@
<?php
class db extends PDO {
private $error;
private $sql;
private $bind;
private $debugger=0;
public $working="yes";
public function __construct($dsn, $user="", $passwd="",$debug_level=0) {
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$this->debugger=$debug_level;
try {
parent::__construct($dsn, $user, $passwd, $options);
} catch (PDOException $e) {
$this->error = $e->getMessage();
die("Could not connect to the DB");
}
}
private function debug() {
if(!$this->debugger) return;
$error = array("Error" => $this->error);
if(!empty($this->sql))
$error["SQL Statement"] = $this->sql;
if(!empty($this->bind))
$error["Bind Parameters"] = trim(print_r($this->bind, true));
$backtrace = debug_backtrace();
if(!empty($backtrace)) {
foreach($backtrace as $info) {
if($info["file"] != __FILE__)
$error["Backtrace"] = $info["file"] . " at line " . $info["line"];
}
}
$msg = "";
$msg .= "SQL Error\n" . str_repeat("-", 50);
foreach($error as $key => $val)
$msg .= "\n\n$key:\n$val";
if($this->debugger){
echo nl2br($msg);
}
}
private function cleanup($bind,$sql="") {
if(!is_array($bind)) {
if(!empty($bind))
$bind = array($bind);
else
$bind = array();
}
foreach($bind as $key=>$val){
if(str_replace($key,"",$sql)==$sql) unset($bind[$key]);
}
return $bind;
}
public function single($sql,$bind="") {
$this->sql = trim($sql);
$this->bind = $this->cleanup($bind,$sql);
$this->error = "";
try {
$pdostmt = $this->prepare($this->sql);
if($pdostmt->execute($this->bind) !== false) {
return $pdostmt->fetchColumn();
}
} catch (PDOException $e) {
$this->error = $e->getMessage();
$this->debug();
return false;
}
}
public function run($sql, $bind="") {
$this->sql = trim($sql);
$this->bind = $this->cleanup($bind,$sql);
$this->error = "";
try {
$pdostmt = $this->prepare($this->sql);
if($pdostmt->execute($this->bind) !== false) {
if(preg_match("/^(" . implode("|", array("select", "describe", "pragma")) . ") /i", $this->sql))
return $pdostmt->fetchAll(PDO::FETCH_ASSOC);
elseif(preg_match("/^(" . implode("|", array("delete", "insert", "update")) . ") /i", $this->sql))
return $pdostmt->rowCount();
}
} catch (PDOException $e) {
$this->error = $e->getMessage();
$this->debug();
return false;
}
}
public function row($sql,$bind=""){
$query=$this->run($sql,$bind);
if(count($query)==0) return false;
if(count($query)>1) return $query;
if(count($query)==1){
foreach($query as $row) $result=$row;
return $result;
}
}
}
?>

212
include/functions.inc.php Executable file
View File

@@ -0,0 +1,212 @@
<?php
function san($a,$b=""){
$a = preg_replace("/[^a-zA-Z0-9".$b."]/", "", $a);
return $a;
}
function api_err($data){
global $_config;
echo json_encode(array("status"=>"error","data"=>$data, "coin"=>$_config['coin']));
exit;
}
function api_echo($data){
global $_config;
echo json_encode(array("status"=>"ok","data"=>$data, "coin"=>$_config['coin']));
exit;
}
function _log($data){
$date=date("[Y-m-d H:s:]");
$trace=debug_backtrace();
$location=$trace[1]['class'].'->'.$trace[1]['function'].'()';
//echo "$date [$location] $data\n";
}
function pem2hex ($data) {
$data=str_replace("-----BEGIN PUBLIC KEY-----","",$data);
$data=str_replace("-----END PUBLIC KEY-----","",$data);
$data=str_replace("-----BEGIN EC PRIVATE KEY-----","",$data);
$data=str_replace("-----END EC PRIVATE KEY-----","",$data);
$data=str_replace("\n","",$data);
$data=base64_decode($data);
$data=bin2hex($data);
return $data;
}
function hex2pem ($data, $is_private_key=false) {
$data=hex2bin($data);
$data=base64_encode($data);
if($is_private_key) return "-----BEGIN EC PRIVATE KEY-----\n".$data."\n-----END EC PRIVATE KEY-----";
return "-----BEGIN PUBLIC KEY-----\n".$data."\n-----END PUBLIC KEY-----";
}
//all credits for this base58 functions should go to tuupola / https://github.com/tuupola/base58/
function baseConvert(array $source, $source_base, $target_base)
{
$result = [];
while ($count = count($source)) {
$quotient = [];
$remainder = 0;
for ($i = 0; $i !== $count; $i++) {
$accumulator = $source[$i] + $remainder * $source_base;
$digit = (integer) ($accumulator / $target_base);
$remainder = $accumulator % $target_base;
if (count($quotient) || $digit) {
array_push($quotient, $digit);
};
}
array_unshift($result, $remainder);
$source = $quotient;
}
return $result;
}
function base58_encode($data)
{
if (is_integer($data)) {
$data = [$data];
} else {
$data = str_split($data);
$data = array_map(function ($character) {
return ord($character);
}, $data);
}
$converted = baseConvert($data, 256, 58);
return implode("", array_map(function ($index) {
$chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
return $chars[$index];
}, $converted));
}
function base58_decode($data, $integer = false)
{
$data = str_split($data);
$data = array_map(function ($character) {
$chars="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
return strpos($chars, $character);
}, $data);
/* Return as integer when requested. */
if ($integer) {
$converted = baseConvert($data, 58, 10);
return (integer) implode("", $converted);
}
$converted = baseConvert($data, 58, 256);
return implode("", array_map(function ($ascii) {
return chr($ascii);
}, $converted));
}
function pem2coin ($data) {
$data=str_replace("-----BEGIN PUBLIC KEY-----","",$data);
$data=str_replace("-----END PUBLIC KEY-----","",$data);
$data=str_replace("-----BEGIN EC PRIVATE KEY-----","",$data);
$data=str_replace("-----END EC PRIVATE KEY-----","",$data);
$data=str_replace("\n","",$data);
$data=base64_decode($data);
return base58_encode($data);
}
function coin2pem ($data, $is_private_key=false) {
$data=base58_decode($data);
$data=base64_encode($data);
$dat=str_split($data,64);
$data=implode("\n",$dat);
if($is_private_key) return "-----BEGIN EC PRIVATE KEY-----\n".$data."\n-----END EC PRIVATE KEY-----\n";
return "-----BEGIN PUBLIC KEY-----\n".$data."\n-----END PUBLIC KEY-----\n";
}
function ec_sign($data, $key){
$private_key=coin2pem($key,true);
$pkey=openssl_pkey_get_private($private_key);
$k=openssl_pkey_get_details($pkey);
openssl_sign($data,$signature,$pkey,OPENSSL_ALGO_SHA256);
return base58_encode($signature);
}
function ec_verify($data, $signature, $key){
$public_key=coin2pem($key);
$signature=base58_decode($signature);
$pkey=openssl_pkey_get_public($public_key);
$res=openssl_verify($data,$signature,$pkey,OPENSSL_ALGO_SHA256);
if($res===1) return true;
return false;
}
function peer_post($url, $data=array()){
global $_config;
$postdata = http_build_query(
array(
'data' => json_encode($data),
"coin"=>$_config['coin']
)
);
$opts = array('http' =>
array(
'timeout' => "60",
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
)
);
$context = stream_context_create($opts);
$result = file_get_contents($url, false, $context);
$res=json_decode($result,true);
if($res['status']!="ok"||$res['coin']!=$_config['coin']) return false;
return $res['data'];
}
function hex2coin($hex){
$data=hex2bin($hex);
return base58_encode($data);
}
function coin2hex($data){
$bin= base58_decode($data);
return bin2hex($bin);
}
?>

71
include/init.inc.php Executable file
View File

@@ -0,0 +1,71 @@
<?php
define("VERSION", "0.1a");
date_default_timezone_set("Europe/Amsterdam");
//error_reporting(E_ALL & ~E_NOTICE);
error_reporting(0);
ini_set('display_errors',"off");
require_once("include/config.inc.php");
require_once("include/db.inc.php");
require_once("include/functions.inc.php");
require_once("include/block.inc.php");
require_once("include/account.inc.php");
require_once("include/transaction.inc.php");
if($_config['db_pass']=="ENTER-DB-PASS") die("Please update your config file and set your db password");
// initial DB connection
$db=new DB($_config['db_connect'],$_config['db_user'],$_config['db_pass'],0);
if(!$db) die("Could not connect to the DB backend.");
if (!extension_loaded("openssl") && !defined("OPENSSL_KEYTYPE_EC")) api_err("Openssl php extension missing");
if (!extension_loaded("gmp")) api_err("gmp php extension missing");
if(floatval(phpversion())<7.1) api_err("The minimum php version required is 7.1");
// Getting extra configs from the database
$query=$db->run("SELECT cfg, val FROM config");
foreach($query as $res){
$_config[$res['cfg']]=trim($res['val']);
}
if($_config['maintenance']==1) api_err("under-maintenance");
if(file_exists("tmp/db-update")){
$res=unlink("tmp/db-update");
if($res){
require_once("include/schema.inc.php");
exit;
}
}
if($_config['dbversion']<2) exit;
if($_config['testnet']==true) $_config['coin'].="-testnet";
$hostname=(!empty($_SERVER['HTTPS'])?'https':'http')."://".$_SERVER['HTTP_HOST'];
if($_SERVER['SERVER_PORT']!=80&&$_SERVER['SERVER_PORT']!=443) $hostname.=":".$_SERVER['SERVER_PORT'];
if($hostname!=$_config['hostname']&&$_SERVER['HTTP_HOST']!="localhost"&&$_SERVER['HTTP_HOST']!="127.0.0.1"&&$_SERVER['hostname']!='::1'&&php_sapi_name() !== 'cli'){
$db->run("UPDATE config SET val=:hostname WHERE cfg='hostname' LIMIT 1",array(":hostname"=>$hostname));
$_config['hostname']=$hostname;
}
if(empty($_config['hostname'])||$_config['hostname']=="http://"||$_config['hostname']=="https://") api_err("Invalid hostname");
$t=time();
if($t-$_config['sanity_last']>$_config['sanity_interval']&& php_sapi_name() !== 'cli') system("php sanity.php &>>/dev/null &");
?>

142
include/schema.inc.php Executable file
View File

@@ -0,0 +1,142 @@
<?php
$dbversion=intval($_config['dbversion']);
$db->beginTransaction();
if($dbversion==0){
$db->run("
CREATE TABLE `accounts` (
`id` varbinary(128) NOT NULL,
`public_key` varbinary(1024) NOT NULL,
`block` varbinary(128) NOT NULL,
`balance` decimal(20,8) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;");
$db->run("CREATE TABLE `blocks` (
`id` varbinary(128) NOT NULL,
`generator` varbinary(128) NOT NULL,
`height` int(11) NOT NULL,
`date` int(11) NOT NULL,
`nonce` varbinary(128) NOT NULL,
`signature` varbinary(256) NOT NULL,
`difficulty` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`argon` varbinary(128) NOT NULL,
`transactions` INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
$db->run("CREATE TABLE `config` (
`cfg` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`val` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
$db->run("INSERT INTO `config` (`cfg`, `val`) VALUES
('hostname', '');");
$db->run("INSERT INTO `config` (`cfg`, `val`) VALUES
('dbversion', '1');");
$db->run("CREATE TABLE `mempool` (
`id` varbinary(128) NOT NULL,
`height` int(11) NOT NULL,
`src` varbinary(128) NOT NULL,
`dst` varbinary(128) NOT NULL,
`val` decimal(20,8) NOT NULL,
`fee` decimal(20,8) NOT NULL,
`signature` varbinary(256) NOT NULL,
`version` tinyint(4) NOT NULL,
`message` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '',
`public_key` varbinary(1024) NOT NULL,
`date` bigint(20) NOT NULL,
`peer` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
$db->run("CREATE TABLE `peers` (
`id` int(11) NOT NULL,
`hostname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`blacklisted` int(11) NOT NULL DEFAULT 0,
`ping` int(11) NOT NULL,
`reserve` tinyint(4) NOT NULL DEFAULT 1,
`ip` varchar(45) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
$db->run("CREATE TABLE `transactions` (
`id` varbinary(128) NOT NULL,
`block` varbinary(128) NOT NULL,
`height` int(11) NOT NULL,
`dst` varbinary(128) NOT NULL,
`val` decimal(20,8) NOT NULL,
`fee` decimal(20,8) NOT NULL,
`signature` varbinary(256) NOT NULL,
`version` tinyint(4) NOT NULL,
`message` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '',
`date` int(11) NOT NULL,
`public_key` varbinary(1024) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
$db->run("ALTER TABLE `peers`
ADD PRIMARY KEY (`id`);");
$db->run("ALTER TABLE `peers`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;");
$db->run("ALTER TABLE `accounts`
ADD PRIMARY KEY (`id`),
ADD KEY `accounts` (`block`);");
$db->run("ALTER TABLE `blocks`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `height` (`height`);");
$db->run("ALTER TABLE `config` ADD PRIMARY KEY (`cfg`);");
$db->run("ALTER TABLE `mempool`
ADD PRIMARY KEY (`id`),
ADD KEY `height` (`height`);");
$db->run("ALTER TABLE `peers`
ADD UNIQUE KEY `hostname` (`hostname`),
ADD UNIQUE KEY `ip` (`ip`),
ADD KEY `blacklisted` (`blacklisted`),
ADD KEY `ping` (`ping`),
ADD KEY `reserve` (`reserve`);");
$db->run("ALTER TABLE `transactions`
ADD PRIMARY KEY (`id`),
ADD KEY `block_id` (`block`);");
$db->run("ALTER TABLE `accounts`
ADD CONSTRAINT `accounts` FOREIGN KEY (`block`) REFERENCES `blocks` (`id`) ON DELETE CASCADE;");
$db->run("ALTER TABLE `transactions`
ADD CONSTRAINT `block_id` FOREIGN KEY (`block`) REFERENCES `blocks` (`id`) ON DELETE CASCADE;");
$dbversion++;
}
if($dbversion==1){
$db->run("INSERT INTO `config` (`cfg`, `val`) VALUES ('sanity_last', '0');");
$dbversion++;
}
if($dbversion==2){
$db->run("INSERT INTO `config` (`cfg`, `val`) VALUES ('sanity_sync', '0');");
$dbversion++;
}
if($dbversion==3){
$dbversion++;
}
if($dbversion==4){
$db->run("ALTER TABLE `mempool` ADD INDEX(`src`);");
$db->run("ALTER TABLE `mempool` ADD INDEX(`peer`); ");
$db->run("ALTER TABLE `mempool` ADD INDEX(`val`); ");
$dbversion++;
}
if($dbversion==5){
$db->run("ALTER TABLE `peers` ADD `fails` TINYINT NOT NULL DEFAULT '0' AFTER `ip`; ");
$dbversion++;
}
if($dbversion!=$_config['dbversion']) $db->run("UPDATE config SET val=:val WHERE cfg='dbversion'",array(":val"=>$dbversion));
$db->commit();
?>

205
include/transaction.inc.php Executable file
View File

@@ -0,0 +1,205 @@
<?php
class Transaction {
public function reverse($block){
global $db;
$acc=new Account;
$r=$db->run("SELECT * FROM transactions WHERE block=:block",array(":block"=>$block));
foreach($r as $x){
if(empty($x['src'])) $x['src']=$acc->get_address($x['public_key']);
$db->run("UPDATE accounts SET balance=balance-:val WHERE id=:id",array(":id"=>$x['dst'], ":val"=>$x['val']));
if($x['version']>0) $db->run("UPDATE accounts SET balance=balance+:val WHERE id=:id",array(":id"=>$x['src'], ":val"=>$x['val']+$x['fee']));
if($x['version']>0) $this->add_mempool($x);
$res= $db->run("DELETE FROM transactions WHERE id=:id",array(":id"=>$x['id']));
if($res!=1) return false;
}
}
public function clean_mempool(){
global $db;
$block= new Block;
$current=$block->current();
$height=$current['height'];
$limit=$height-1000;
$db->run("DELETE FROM mempool WHERE height<:limit",array(":limit"=>$limit));
}
public function mempool($max){
global $db;
$block=new Block;
$current=$block->current();
$height=$current['height']+1;
$r=$db->run("SELECT * FROM mempool WHERE height<=:height ORDER by val/fee DESC LIMIT :max",array(":height"=>$height, ":max"=>$max+50));
$transactions=array();
if(count($r)>0){
$i=0;
$balance=array();
foreach($r as $x){
$trans=array("id"=>$x['id'],"dst"=>$x['dst'],"val"=>$x['val'],"fee"=>$x['fee'],"signature"=>$x['signature'], "message"=>$x['message'],"version"=>$x['version'],"date"=>$x['date'], "public_key"=>$x['public_key']);
if($i>=$max) break;
if(empty($x['public_key'])){
_log("$x[id] - Transaction has empty public_key");
continue;
}
if(empty($x['src'])){
_log("$x[id] - Transaction has empty src");
continue;
}
if(!$this->check($trans)){
var_dump($trans);
_log("$x[id] - Transaction Check Failed");
continue;
}
$balance[$x['src']]+=$x['val']+$x['fee'];
if($db->single("SELECT COUNT(1) FROM transactions WHERE id=:id",array(":id"=>$x['id']))>0) {
_log("$x[id] - Duplicate transaction");
continue; //duplicate transaction
}
$res=$db->single("SELECT COUNT(1) FROM accounts WHERE id=:id AND balance>=:balance",array(":id"=>$x['src'], ":balance"=>$balance[$x['src']]));
if($res==0) {
_log("$x[id] - Not enough funds in balance");
continue; // not enough balance for the transactions
}
$i++;
ksort($trans);
$transactions[$x['id']]=$trans;
}
}
ksort($transactions);
return $transactions;
}
public function add_mempool($x, $peer=""){
global $db;
$block= new Block;
$current=$block->current();
$height=$current['height'];
$x['id']=san($x['id']);
$bind=array(":peer"=>$peer, ":id"=>$x['id'],"public_key"=>$x['public_key'], ":height"=>$height, ":src"=>$x['src'],":dst"=>$x['dst'],":val"=>$x['val'], ":fee"=>$x['fee'],":signature"=>$x['signature'], ":version"=>$x['version'],":date"=>$x['date'], ":message"=>$x['message']);
$db->run("INSERT into mempool SET peer=:peer, id=:id, public_key=:public_key, height=:height, src=:src, dst=:dst, val=:val, fee=:fee, signature=:signature, version=:version, message=:message, `date`=:date",$bind);
return true;
}
public function add($block,$height, $x){
global $db;
$acc= new Account;
$acc->add($x['public_key'], $block);
$acc->add_id($x['dst'],$block);
$x['id']=san($x['id']);
$bind=array(":id"=>$x['id'], ":public_key"=>$x['public_key'],":height"=>$height, ":block"=>$block, ":dst"=>$x['dst'],":val"=>$x['val'], ":fee"=>$x['fee'],":signature"=>$x['signature'], ":version"=>$x['version'],":date"=>$x['date'], ":message"=>$x['message']);
$res=$db->run("INSERT into transactions SET id=:id, public_key=:public_key, block=:block, height=:height, dst=:dst, val=:val, fee=:fee, signature=:signature, version=:version, message=:message, `date`=:date",$bind);
if($res!=1) return false;
$db->run("UPDATE accounts SET balance=balance+:val WHERE id=:id",array(":id"=>$x['dst'], ":val"=>$x['val']));
if($x['version']>0) $db->run("UPDATE accounts SET balance=balance-:val WHERE id=:id",array(":id"=>$x['src'], ":val"=>$x['val']+$x['fee']));
$db->run("DELETE FROM mempool WHERE id=:id",array(":id"=>$x['id']));
return true;
}
public function hash($x){
$info=$x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date']."-".$x['signature'];
$hash= hash("sha512",$info);
return hex2coin($hash);
}
public function check($x){
$acc= new Account;
$info=$x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date'];
if($x['val']<0){ _log("$x[id] - Value below 0"); return false; }
if($x['fee']<0) { _log("$x[id] - Fee below 0"); return false; }
$fee=$x['val']*0.0025;
if($fee<0.00000001) $fee=0.00000001;
if($fee!=$x['fee']) { _log("$x[id] - Fee not 0.25%"); return false; }
if(!$acc->valid($x['dst'])) { _log("$x[id] - Invalid destination address"); return false; }
if($x['version']<1) { _log("$x[id] - Invalid version <1"); return false; }
//if($x['version']>1) { _log("$x[id] - Invalid version >1"); return false; }
if(strlen($x['public_key'])<15) { _log("$x[id] - Invalid public key size"); return false; }
if($x['date']<1511725068) { _log("$x[id] - Date before genesis"); return false; }
if($x['date']>time()+86400) { _log("$x[id] - Date in the future"); return false; }
$id=$this->hash($x);
if($x['id']!=$id) { _log("$x[id] - Invalid hash"); return false; }
if(!$acc->check_signature($info, $x['signature'], $x['public_key'])) { _log("$x[id] - Invalid signature"); return false; }
return true;
}
public function sign($x, $private_key){
$info=$x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date'];
$signature=ec_sign($info,$private_key);
return $signature;
}
public function export($id){
global $db;
$r=$db->row("SELECT * FROM mempool WHERE id=:id",array(":id"=>$id));
//unset($r['peer']);
return $r;
}
public function get_transaction($id){
global $db;
$block=new Block;
$current=$block->current();
$acc=new Account;
$x=$db->row("SELECT * FROM transactions WHERE id=:id",array(":id"=>$id));
if(!$x) return false;
$trans=array("block"=>$x['block'],"height"=>$x['height'], "id"=>$x['id'],"dst"=>$x['dst'],"val"=>$x['val'],"fee"=>$x['fee'],"signature"=>$x['signature'], "message"=>$x['message'],"version"=>$x['version'],"date"=>$x['date'], "public_key"=>$x['public_key']);
$trans['src']=$acc->get_address($x['public_key']);
$trans['confirmations']=$current['height']-$x['height'];
if($x['version']==0) $trans['type']="mining";
elseif($x['version']==1){
if($x['dst']==$id) $trans['type']="credit";
else $trans['type']="debit";
} else {
$trans['type']="other";
}
ksort($trans);
return $trans;
}
public function get_mempool_transaction($id){
global $db;
$x=$db->row("SELECT * FROM mempool WHERE id=:id",array(":id"=>$id));
if(!$x) return false;
$trans=array("block"=>$x['block'],"height"=>$x['height'], "id"=>$x['id'],"dst"=>$x['dst'],"val"=>$x['val'],"fee"=>$x['fee'],"signature"=>$x['signature'], "message"=>$x['message'],"version"=>$x['version'],"date"=>$x['date'], "public_key"=>$x['public_key']);
$trans['src']=$x['src'];
$trans['type']="mempool";
$trans['confirmations']=-1;
ksort($trans);
return $trans;
}
}
?>

37
index.php Executable file
View File

@@ -0,0 +1,37 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
$block=new Block;
$current=$block->current();
echo "<h3>Arionum Node</h3>";
echo "System check complete.<br><br> Current block: $current[height]";
?>

68
mine.php Executable file
View File

@@ -0,0 +1,68 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
$block=new Block();
$acc=new Account();
set_time_limit(360);
$q=$_GET['q'];
$ip=$_SERVER['REMOTE_ADDR'];
if(!in_array($ip,$_config['allowed_hosts'])) api_err("unauthorized");
if($q=="info"){
$diff=$block->difficulty();
$current=$block->current();
api_echo(array("difficulty"=>$diff, "block"=>$current['id']));
exit;
} elseif($q=="submitNonce"){
if($_config['sanity_sync']==1) api_err("sanity-sync");
$nonce = san($_POST['nonce']);
$argon=$_POST['argon'];
$public_key=san($_POST['public_key']);
$private_key=san($_POST['private_key']);
$result=$block->mine($public_key, $nonce, $argon);
if($result) {
$res=$block->forge($nonce,$argon, $public_key, $private_key);
if($res){
$current=$block->current();
system("php propagate.php block $current[id] &>/dev/null &");
api_echo("accepted");
}
}
api_err("rejected");
} else {
api_err("invalid command");
}
?>

164
peer.php Executable file
View File

@@ -0,0 +1,164 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
$trx = new Transaction;
$block=new Block;
$q=$_GET['q'];
if(!empty($_POST['data'])){
$data=json_decode(trim($_POST['data']),true);
}
if($_POST['coin']!=$_config['coin']) api_err("Invalid coin");
$ip=$_SERVER['REMOTE_ADDR'];
if($q=="peer"){
$hostname = filter_var($data['hostname'], FILTER_SANITIZE_URL);
if (!filter_var($hostname, FILTER_VALIDATE_URL)) api_err("invalid-hostname");
$res=$db->single("SELECT COUNT(1) FROM peers WHERE hostname=:hostname AND ip=:ip",array(":hostname"=>$hostname,":ip"=>$ip));
if($res==1) api_echo("peer-ok-already");
$res=$db->single("SELECT COUNT(1) FROM peers WHERE blacklisted<UNIX_TIMESTAMP() AND ping >UNIX_TIMESTAMP()-86400 AND reserve=0");
$reserve=1;
if($res<$_config['max_peers']) $reserve=0;
$db->run("INSERT ignore INTO peers SET hostname=:hostname, reserve=:reserve, ping=UNIX_TIMESTAMP(), ip=:ip ON DUPLICATE KEY UPDATE hostname=:hostname2",array(":ip"=>$ip, ":hostname2"=>$hostname,":hostname"=>$hostname, ":reserve"=>$reserve));
$res=peer_post($hostname."/peer.php?q=peer",array("hostname"=>$_config['hostname']));
if($res!==false) api_echo("re-peer-ok");
else api_err("re-peer failed - $result");
}
elseif($q=="ping"){
api_echo("pong");
}
elseif($q=="submitTransaction"){
if($_config['sanity_sync']==1) api_err("sanity-sync");
$data['id']=san($data['id']);
if(!$trx->check($data)) api_err("Invalid transaction");
$hash=$data['id'];
$res=$db->single("SELECT COUNT(1) FROM mempool WHERE id=:id",array(":id"=>$hash));
if($res!=0) api_err("The transaction is already in mempool");
$res=$db->single("SELECT COUNT(1) FROM mempool WHERE src=:src",array(":src"=>$data['src']));
if($res>25) api_err("Too many transactions from this address in mempool. Please rebroadcast later.");
$res=$db->single("SELECT COUNT(1) FROM mempool WHERE peer=:peer",array(":peer"=>$_SERVER['REMOTE_ADDR']));
if($res>$_config['peer_max_mempool']) api_error("Too many transactions broadcasted from this peer");
$res=$db->single("SELECT COUNT(1) FROM transactions WHERE id=:id",array(":id"=>$hash));
if($res!=0) api_err("The transaction is already in a block");
$acc=new Account;
$src=$acc->get_address($data['public_key']);
$balance=$db->single("SELECT balance FROM accounts WHERE id=:id",array(":id"=>$src));
if($balance<$val+$fee) api_err("Not enough funds");
$memspent=$db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:src",array(":src"=>$src));
if($balance-$memspent<$val+$fee) api_err("Not enough funds (mempool)");
$trx->add_mempool($data, $_SERVER['REMOTE_ADDR']);
$res=$db->row("SELECT COUNT(1) as c, sum(val) as v FROM mempool ",array(":src"=>$data['src']));
if($res['c']<$_config['max_mempool_rebroadcast']&&$res['v']/$res['c']<$data['val']) system("php propagate.php transaction $data[id] &>/dev/null &");
api_echo("transaction-ok");
}
elseif($q=="submitBlock"){
if($_config['sanity_sync']==1) api_err("sanity-sync");
$data['id']=san($data['id']);
$current=$block->current();
if($current['id']==$data['id']) api_echo("block-ok");
if($current['height']==$data['height']&&$current['id']!=$data['id']){
$accept_new=false;
if($current['transactions']<$data['transactions']){
$accept_new=true;
} elseif($current['transactions']==$data['transactions']) {
$no1=hexdec(substr(coin2hex($current['id']),0,12));
$no2=hexdec(substr(coin2hex($data['id']),0,12));
if(gmp_cmp($no1,$no2)==1){
$accept_new=true;
}
}
if($accept_new){
system("php sanity.php microsanity $ip &>/dev/null &");
api_echo("microsanity");
}
}
if($current['height']!=$data['height']-1) {
if($data['height']<$current['height']) api_err("block-too-old");
if($data['height']-$current['height']>30) api_err("block-out-of-sync");
api_echo(array("request"=>"microsync","height"=>$current['height'], "block"=>$current['id']));
}
if(!$block->check($data)) api_err("invalid-block");
$b=$data;
$res=$block->add($b['height'], $b['public_key'], $b['nonce'], $b['data'], $b['date'], $b['signature'], $b['difficulty'], $b['reward_signature'], $b['argon']);
if(!$res) api_err("invalid-block-data");
api_echo("block-ok");
system("php propagate.php block $data[id] &>/dev/null &");
}
elseif($q=="currentBlock"){
$current=$block->current();
api_echo($current);
}
elseif($q=="getBlock"){
$height=intval($data['height']);
$export=$block->export("",$height);
if(!$export) api_err("invalid-block");
api_echo($export);
}
elseif($q=="getBlocks"){
$height=intval($data['height']);
$r=$db->run("SELECT id,height FROM blocks WHERE height>=:height ORDER by height ASC LIMIT 100",array(":height"=>$height));
foreach($r as $x){
$blocks[$x['height']]=$block->export($x['id']);
}
api_echo($blocks);
}
elseif($q=="getPeers"){
$peers=$db->run("SELECT ip,hostname FROM peers ORDER by RAND()");
api_echo($peers);
} else {
api_err("Invalid request");
}
?>

106
propagate.php Executable file
View File

@@ -0,0 +1,106 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
set_time_limit(360);
require_once("include/init.inc.php");
$block= new Block();
$type=san($argv[1]);
$id=san($argv[2]);
$peer=san(trim($argv[3]));
if(empty($peer)&&$type=="block"){
$whr="";
$data=$block->export($id);
if($data===false||empty($data)) die("Could not export block");
$data=json_encode($data);
// cache it to reduce the load
$res=file_put_contents("tmp/$id",$data);
if($res===false) die("Could not write the cachce file");
$r=$db->run("SELECT * FROM peers WHERE blacklisted < UNIX_TIMESTAMP() AND reserve=0");
foreach($r as $x) {
$host=base58_encode($x['hostname']);
system("php propagate.php $type $id $host &>/dev/null &");
}
exit;
}
if($type=="block"){
$data=file_get_contents("tmp/$id");
if(empty($data)) { echo "Invalid Block data"; exit; }
$data=json_decode($data,true);
$hostname=base58_decode($peer);
echo "Peer response - $hostname:\n";
$response= peer_post($hostname."/peer.php?q=submitBlock",$data);
if($response=="block-ok") { echo "Block $i accepted. Exiting.\n"; exit;}
elseif($response['request']=="microsync"){
echo "Microsync request\n";
$height=intval($response['height']);
$bl=san($response['block']);
$current=$block->current();
if($current['height']-$height>10) { echo "Height Differece too high\n"; exit; }
$last_block=$block->get($height);
if ($last_block['id'] != $bl ) { echo "Last block does not match\n"; exit; }
echo "Sending the requested blocks\n";
for($i=$height+1;$i<=$current['height'];$i++){
$data=$block->export("",$i);
$response = peer_post($hostname."/peer.php?q=submitBlock",$data);
if($response!="block-ok") { echo "Block $i not accepted. Exiting.\n"; exit;}
echo "Block\t$i\t accepted\n";
}
}
else echo "Block not accepted!\n";
}
if($type=="transaction"){
$trx=new Transaction;
$data=$trx->export($id);
if(!$data){ echo "Invalid transaction id\n"; exit; }
if($data['peer']=="local") $r=$db->run("SELECT hostname FROM peers WHERE blacklisted < UNIX_TIMESTAMP()");
else $r=$db->run("SELECT hostname FROM peers WHERE blacklisted < UNIX_TIMESTAMP() AND reserve=0 ORDER by RAND() LIMIT ".$_config['transaction_propagation_peers']);
foreach($r as $x){
$res= peer_post($x['hostname']."/peer.php?q=submitTransaction",$data);
if(!$res) echo "Transaction not accepted\n";
else echo "Transaction accepted\n";
}
}
?>

354
sanity.php Executable file
View File

@@ -0,0 +1,354 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
set_time_limit(0);
if(php_sapi_name() !== 'cli') die("This should only be run as cli");
if(file_exists("tmp/sanity-lock")){
$pid_time=filemtime("tmp/sanity-lock");
if(time()-$pid_time>3600){
@unlink("tmp/sanity-lock");
}
die("Sanity lock in place");
}
$lock = fopen("tmp/sanity-lock", "w");
fclose($lock);
$arg=trim($argv[1]);
$arg2=trim($argv[2]);
if($arg!="microsanity") sleep(10);
require_once("include/init.inc.php");
if($_config['dbversion']<2){
die("DB schema not created");
@unlink("tmp/sanity-lock");
exit;
}
$block=new Block();
$acc=new Account();
$current=$block->current();
$microsanity=false;
if($arg=="microsanity"&&!empty($arg2)){
do {
$x=$db->row("SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted<UNIX_TIMESTAMP() AND ip=:ip",array(":ip"=>$arg2));
if(!$x){ echo "Invalid node - $arg2\n"; break; }
$url=$x['hostname']."/peer.php?q=";
$data=peer_post($url."getBlock",array("height"=>$current['height']));
if(!$data) {echo "Invalid getBlock result\n"; break; }
if($data['id']==$current['id']) {echo "Same block\n"; break;}
if($current['transactions']>$data['transactions']){
echo "Block has less transactions\n";
break;
} elseif($current['transactions']==$data['transactions']) {
$no1=hexdec(substr(coin2hex($current['id']),0,12));
$no2=hexdec(substr(coin2hex($data['id']),0,12));
if(gmp_cmp($no1,$no2)!=-1){
echo "Block hex larger than current\n";
break;
}
}
$prev = $block->get($current['height']-1);
$public=$acc->public_key($data['generator']);
if(!$block->mine($public, $data['nonce'],$data['argon'],$block->difficulty($current['height']-1),$prev['id'])) { echo "Invalid prev-block\n"; break;}
$block->pop(1);
if(!$block->check($data)) break;
echo "Starting to sync last block from $x[hostname]\n";
$b=$data;
$res=$block->add($b['height'], $b['public_key'], $b['nonce'], $b['data'], $b['date'], $b['signature'], $b['difficulty'], $b['reward_signature'], $b['argon']);
if(!$res) {
_log("Block add: could not add block - $b[id] - $b[height]");
break;
}
_log("Synced block from $host - $b[height] $b[difficulty]");
} while(0);
@unlink("tmp/sanity-lock");
exit;
}
$t=time();
//if($t-$_config['sanity_last']<300) {@unlink("tmp/sanity-lock"); die("The sanity cron was already run recently"); }
$db->run("UPDATE config SET val=:time WHERE cfg='sanity_last'",array(":time"=>$t));
$block_peers=array();
$longest_size=0;
$longest=0;
$blocks=array();
$blocks_count=array();
$most_common="";
$most_common_size=0;
// checking peers
$db->run("DELETE from peers WHERE fails>50");
$r=$db->run("SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted<UNIX_TIMESTAMP()");
$total_peers=count($r);
if($total_peers==0){
$i=0;
echo "No peers found. Attempting to get peers from arionum.com\n";
$f=file("https://www.arionum.com/peers.txt");
shuffle($f);
if(count($f)<2) die("Could nto connect to arionum.com! Will try later!\n");
foreach($f as $peer){
$peer=trim($peer);
$res=peer_post($peer."/peer.php?q=peer",array("hostname"=>$_config['hostname']));
if($res!==false) {$i++; echo "Peering OK - $peer\n"; }
else echo "Peering FAIL - $peer\n";
if($i>$_config['max_peers']) break;
}
$r=$db->run("SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted<UNIX_TIMESTAMP()");
$total_peers=count($r);
if($total_peers==0) die("Could not peer to any peers! Please check internet connectivity!\n");
}
foreach($r as $x){
$url=$x['hostname']."/peer.php?q=";
$data=peer_post($url."getPeers");
if($data===false) {
$db->run("UPDATE peers SET fails=fails+1, blacklisted=UNIX_TIMESTAMP()+((fails+1)*3600) WHERE id=:id",array(":id"=>$x['id']));
continue;
}
$i=0;
foreach($data as $peer){
if($peer['hostname']==$_config['hostname']) continue;
if (!filter_var($peer['hostname'], FILTER_VALIDATE_URL)) continue;
if(!$db->single("SELECT COUNT(1) FROM peers WHERE ip=:ip or hostname=:hostname",array(":ip"=>$peer['ip'],":hostname"=>$peer['hostname']))){
$i++;
if($i>$_config['max_test_peers']) break;
$test=peer_post($peer['hostname']."/peer.php?q=peer",array("hostname"=>$_config['hostname']));
if($test!==false){
$total_peers++;
echo "Peered with: $peer[hostname]\n";
}
}
}
$data=peer_post($url."currentBlock");
if($data===false) continue;
$db->run("UPDATE peers SET fails=0 WHERE id=:id",array(":id"=>$x['id']));
$block_peers[$data['id']][]=$x['hostname'];
$blocks_count[$data['id']]++;
$blocks[$data['id']]=$data;
if($blocks_count[$data['id']]>$most_common_size){
$most_common=$data['id'];
$most_common_size=$blocks_count[$data['id']];
}
if($data['height']>$largest_height){
$largest_height=$data['height'];
$largest_height_block=$data['id'];
} elseif($data['height']==$largestblock&&$data['id']!=$largest_height_block){
if($data['difficulty']==$blocks[$largest_height_block]['difficulty']){
if($most_common==$data['id']){
$largest_height=$data['height'];
$largest_height_block=$data['id'];
} else {
if($blocks[$largest_height_block]['transactions']<$data['transactions']){
$largest_height=$data['height'];
$largest_height_block=$data['id'];
} elseif($blocks[$largest_height_block]['transactions']==$data['transactions']) {
$no1=hexdec(substr(coin2hex($largest_height_block),0,12));
$no2=hexdec(substr(coin2hex($data['id']),0,12));
if(gmp_cmp($no1,$no2)==1){
$largest_height=$data['height'];
$largest_height_block=$data['id'];
}
}
}
} elseif($data['difficulty']<$blocks[$largest_height_block]['difficulty']){
$largest_height=$data['height'];
$largest_height_block=$data['id'];
}
}
}
echo "Most common: $most_common\n";
echo "Most common block: $most_common_size\n";
echo "Max height: $largest_height\n";
echo "Current block: $current[height]\n";
if($current['height']<$largest_height&&$largest_height>1){
$db->run("UPDATE config SET val=1 WHERE cfg='sanity_sync'");
sleep(10);
_log("Longest chain rule triggered - $largest_height - $largest_height_block");
$peers=$block_peers[$largest_height_block];
shuffle($peers);
foreach($peers as $host){
_log("Starting to sync from $host");
$url=$host."/peer.php?q=";
$data=peer_post($url."getBlock",array("height"=>$current['height']));
if($data===false){ _log("Could not get block from $host - $current[height]"); continue; }
while($data['id']!=$current['id']){
$block->delete($current['height']-10);
$current=$block->current();
$data=peer_post($url."getBlock",array("height"=>$current['height']));
if($data===false){_log("Could not get block from $host - $current[height]"); break; }
}
if($data['id']!=$current['id']) continue;
while($current['height']<$largest_height){
$data=peer_post($url."getBlocks",array("height"=>$current['height']+1));
if($data===false){_log("Could not get blocks from $host - height: $current[height]"); break; }
$good_peer=true;
foreach($data as $b){
if(!$block->check($b)){
_log("Block check: could not add block - $b[id] - $b[height]");
$good_peer=false;
break;
}
$res=$block->add($b['height'], $b['public_key'], $b['nonce'], $b['data'], $b['date'], $b['signature'], $b['difficulty'], $b['reward_signature'], $b['argon']);
if(!$res) {
_log("Block add: could not add block - $b[id] - $b[height]");
$good_peer=false;
break;
}
_log("Synced block from $host - $b[height] $b[difficulty]");
}
if(!$good_peer) break;
$current=$block->current();
}
if($good_peer) break;
}
$db->run("UPDATE config SET val=0 WHERE cfg='sanity_sync'",array(":time"=>$t));
}
//rebroadcasting transactions
$forgotten=$current['height']-$_config['sanity_rebroadcast_height'];
$r=$db->run("SELECT id FROM mempool WHERE height<:forgotten ORDER by val DESC LIMIT 10",array(":forgotten"=>$forgotten));
foreach($r as $x){
system("php propagate.php transaction $x[id] &>/dev/null &");
$db->run("UPDATE mempool SET height=:current WHERE id=:id",array(":id"=>$x['id'], ":current"=>$current['height']));
}
//add new peers if there aren't enough active
if($total_peers<$_config['max_peers']*0.7){
$res=$_config['max_peers']-$total_peers;
$db->run("UPDATE peers SET reserve=0 WHERE reserve=1 AND blacklisted<UNIX_TIMESTAMP() LIMIT $res");
}
//random peer check
$r=$db->run("SELECT * FROM peers WHERE blacklisted<UNIX_TIMESTAMP() and reserve=1 LIMIT ".$_config['max_test_peers']);
foreach($r as $x){
$url=$x['hostname']."/peer.php?q=";
$data=peer_post($url."ping");
if($data===false) $db->run("UPDATE peers SET fails=fails+1, blacklisted=UNIX_TIMESTAMP()+((fails+1)*3600) WHERE id=:id",array(":id"=>$x['id']));
else $db->run("UPDATE peers SET fails=0 WHERE id=:id",array(":id"=>$x['id']));
}
//clean tmp files
$f=scandir("tmp/");
$time=time();
foreach($f as $x){
if(strlen($x)<5&&substr($x,0,1)==".") continue;
$pid_time=filemtime("tmp/$x");
if($time-$pid_time>7200) @unlink("tmp/$x");
}
//recheck blocks
if($_config['sanity_recheck_blocks']>0){
$blocks=array();
$all_blocks_ok=true;
$start=$current['height']-$_config['sanity_recheck_blocks'];
if($start<2) $start=2;
$r=$db->run("SELECT * FROM blocks WHERE height>=:height ORDER by height ASC",array(":height"=>$start));
foreach($r as $x){
$blocks[$x['height']]=$x;
$max_height=$x['height'];
}
for($i=$start+1;$i<=$max_height;$i++){
$data=$blocks[$i];
$key=$db->single("SELECT public_key FROM accounts WHERE id=:id",array(":id"=>$data['generator']));
if(!$block->mine($key,$data['nonce'], $data['argon'], $data['difficulty'], $blocks[$i-1]['id'])) {
$db->run("UPDATE config SET val=1 WHERE cfg='sanity_sync'");
_log("Invalid block detected. Deleting everything after $data[height] - $data[id]");
sleep(10);
$all_blocks_ok=false;
$block->delete($i);
$db->run("UPDATE config SET val=0 WHERE cfg='sanity_sync'");
break;
}
}
if($all_blocks_ok) echo "All checked blocks are ok\n";
}
@unlink("tmp/sanity-lock");
?>

0
tmp/db-update Normal file
View File

113
util.php Executable file
View File

@@ -0,0 +1,113 @@
<?php
/*
The MIT License (MIT)
Copyright (c) 2018 AroDev
www.arionum.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
$cmd=trim($argv[1]);
if($cmd=='clean'){
$tables=array("blocks","accounts","transactions","mempool");
foreach($tables as $table) $db->run("DELETE FROM {$table}");
echo "\n The database has been cleared\n";
}
elseif($cmd=='pop'){
$no=intval($argv[2]);
$block=new Block;
$block->pop($no);
}
elseif($cmd=='block-time'){
$t=time();
$r=$db->run("SELECT * FROM blocks ORDER by height DESC LIMIT 100");
$start=0;
foreach($r as $x){
if($start==0) $start=$x['date'];
$time=$t-$x['date'];
$t=$x['date'];
echo "$x[height] -> $time\n";
$end=$x['date'];
}
echo "Average block time: ".ceil(($start-$end)/100)." seconds\n";
}
elseif($cmd=="peer"){
$res=peer_post($argv[2]."/peer.php?q=peer",array("hostname"=>$_config['hostname']));
if($res!==false) echo "Peering OK\n";
else echo "Peering FAIL\n";
}
elseif ($cmd=="current") {
$block=new Block;
var_dump($block->current());
} elseif($cmd=="blocks"){
$height=intval($argv[2]);
$limit=intval($argv[3]);
if($limit<1) $limit=100;
$r=$db->run("SELECT * FROM blocks WHERE height>:height ORDER by height ASC LIMIT $limit",array(":height"=>$height));
foreach($r as $x){
echo "$x[height]\t$x[id]\n";
}
}
elseif($cmd=="recheck-blocks"){
$blocks=array();
$block=new Block();
$r=$db->run("SELECT * FROM blocks ORDER by height ASC");
foreach($r as $x){
$blocks[$x['height']]=$x;
$max_height=$x['height'];
}
for($i=2;$i<=$max_height;$i++){
$data=$blocks[$i];
$key=$db->single("SELECT public_key FROM accounts WHERE id=:id",array(":id"=>$data['generator']));
if(!$block->mine($key,$data['nonce'], $data['argon'], $data['difficulty'], $blocks[$i-1]['id'])) {
_log("Invalid block detected. We should delete everything after $data[height] - $data[id]");
break;
}
}
} elseif($cmd=="peers"){
$r=$db->run("SELECT * FROM peers ORDER by reserve ASC LIMIT 100");
foreach($r as $x){
echo "$x[hostname]\t$x[reserve]\n";
}
} elseif($cmd=="mempool"){
$res=$db->single("SELECT COUNT(1) from mempool");
echo "Mempool size: $res\n";
} else {
echo "Invalid command\n";
}
?>